correção de estado circular na sessão
Deploy / Deploy (push) Successful in 1m40s Details

This commit is contained in:
JotaChina 2025-11-24 16:10:21 -03:00
parent 0947156d7e
commit 5e331243b7
5 changed files with 133 additions and 87 deletions

View File

@ -1,5 +1,5 @@
// js/file.js // js/file.js
import { appState, saveStateToSession, resetProjectState } from "./state.js"; import { appState, saveStateToSession, resetProjectState, loadStateFromSession } from "./state.js";
import { loadAudioForTrack } from "./pattern/pattern_state.js"; import { loadAudioForTrack } from "./pattern/pattern_state.js";
import { renderAll, getSamplePathMap } from "./ui.js"; import { renderAll, getSamplePathMap } from "./ui.js";
import { DEFAULT_PAN, DEFAULT_VOLUME, NOTE_LENGTH } from "./config.js"; import { DEFAULT_PAN, DEFAULT_VOLUME, NOTE_LENGTH } from "./config.js";

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3,7 +3,7 @@
// -------------------relómm------------------------------------------------------ // -------------------relómm------------------------------------------------------
// IMPORTS & STATE // IMPORTS & STATE
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
import { appState, saveStateToSession } from "./state.js"; import { appState, saveStateToSession, loadStateFromSession } from "./state.js";
import { import {
addTrackToState, addTrackToState,
removeLastTrackFromState, removeLastTrackFromState,

View File

@ -1,10 +1,16 @@
// js/state.js // js/state.js
import { initializePatternState } from "./pattern/pattern_state.js";
import { audioState, initializeAudioState } from "./audio/audio_state.js"; import { audioState, initializeAudioState } from "./audio/audio_state.js";
import { DEFAULT_VOLUME, DEFAULT_PAN } from "./config.js"; import { DEFAULT_VOLUME, DEFAULT_PAN } from "./config.js";
import * as Tone from "https://esm.sh/tone"; // Adicione esta importação import * as Tone from "https://esm.sh/tone";
// --- DEFINIÇÃO DOS ESTADOS INICIAIS ---
const patternState = {
tracks: [],
activeTrackId: null,
activePatternIndex: 0,
};
// Estado global da aplicação
const globalState = { const globalState = {
sliceToolActive: false, sliceToolActive: false,
isPlaying: false, isPlaying: false,
@ -26,16 +32,9 @@ const globalState = {
clipboard: null, clipboard: null,
lastRulerClickTime: 0, lastRulerClickTime: 0,
justReset: false, justReset: false,
syncMode: "global",
}; };
// Define o ESTADO INICIAL para o pattern module
const patternState = {
tracks: [],
activeTrackId: null,
activePatternIndex: 0,
};
// Combina todos os estados em um único objeto namespaced
export let appState = { export let appState = {
global: globalState, global: globalState,
pattern: patternState, pattern: patternState,
@ -44,9 +43,9 @@ export let appState = {
window.appState = appState; window.appState = appState;
export function resetProjectState() { export function resetProjectState() {
console.log("Executando resetProjectState completo..."); console.log("Executando resetProjectState (Limpeza Profunda)...");
// 1. Reseta o estado global // 1. Reseta Global
Object.assign(appState.global, { Object.assign(appState.global, {
sliceToolActive: false, sliceToolActive: false,
isPlaying: false, isPlaying: false,
@ -68,52 +67,68 @@ export function resetProjectState() {
clipboard: null, clipboard: null,
lastRulerClickTime: 0, lastRulerClickTime: 0,
justReset: false, justReset: false,
// syncMode mantemos o que estava
}); });
// 2. Reseta o estado do pattern // 2. Reseta Pattern
Object.assign(appState.pattern, { Object.assign(appState.pattern, {
tracks: [], tracks: [],
activeTrackId: null, activeTrackId: null,
activePatternIndex: 0, activePatternIndex: 0,
}); });
// 3. Reseta o estado de áudio (Força bruta para garantir limpeza na memória) // 3. Reseta Áudio (Remove tudo da memória)
if (appState.audio) { if (appState.audio) {
appState.audio.tracks = []; appState.audio.tracks = [];
appState.audio.clips = []; appState.audio.clips = [];
appState.audio.audioEditorSeekTime = 0; appState.audio.audioEditorSeekTime = 0;
} }
initializeAudioState(); initializeAudioState();
} }
/**
* SALVAR (Serialization):
* Transforma o estado complexo em um JSON leve (apenas strings e números).
* Remove objetos cíclicos como AudioBuffers e Nodes do Tone.js.
*/
export function saveStateToSession() { export function saveStateToSession() {
if (!window.ROOM_NAME) return; if (!window.ROOM_NAME) return;
// 1. Crie uma versão "limpa" dos tracks // 1. Sanitiza Tracks do Pattern (Instrumentos)
const cleanTracks = appState.pattern.tracks.map((track) => { const cleanPatternTracks = appState.pattern.tracks.map((track) => ({
return { id: track.id,
id: track.id, name: track.name,
name: track.name, samplePath: track.samplePath, // Caminho do arquivo (String)
samplePath: track.samplePath, patterns: track.patterns,
patterns: track.patterns, activePatternIndex: track.activePatternIndex,
activePatternIndex: track.activePatternIndex, volume: track.volume,
volume: track.volume, pan: track.pan,
pan: track.pan, instrumentName: track.instrumentName,
instrumentName: track.instrumentName, instrumentXml: track.instrumentXml,
instrumentXml: track.instrumentXml, // Note: NÃO salvamos volumeNode, pannerNode, etc.
}; }));
});
// 2. Sanitiza Clipes de Áudio (Timeline)
// AQUI ESTÁ O SEGREDO: Salvamos apenas o 'filePath', não o buffer de áudio.
const cleanAudioClips = (appState.audio.clips || []).map((clip) => ({
id: clip.id,
trackId: clip.trackId,
name: clip.name,
filePath: clip.filePath, // O endereço do áudio
startTimeInSeconds: clip.startTimeInSeconds,
durationInSeconds: clip.durationInSeconds,
offset: clip.offset,
pitch: clip.pitch,
originalDuration: clip.originalDuration,
patternData: clip.patternData, // Visualização dos steps (leve)
// Note: NÃO salvamos clip.buffer (que é o áudio pesado)
}));
// 2. Construa o objeto de estado final para salvar
const stateToSave = { const stateToSave = {
pattern: { pattern: { ...appState.pattern, tracks: cleanPatternTracks },
...appState.pattern,
tracks: cleanTracks,
},
audio: { audio: {
tracks: appState.audio.tracks || [], tracks: appState.audio.tracks || [], // Tracks são apenas containers leves
clips: appState.audio.clips || [], clips: cleanAudioClips
}, },
global: { global: {
bpm: document.getElementById("bpm-input")?.value || 140, bpm: document.getElementById("bpm-input")?.value || 140,
@ -126,78 +141,98 @@ export function saveStateToSession() {
try { try {
const roomName = window.ROOM_NAME || "default_room"; const roomName = window.ROOM_NAME || "default_room";
sessionStorage.setItem( // Agora o JSON.stringify funciona porque só tem dados simples
`temp_state_${roomName}`, sessionStorage.setItem(`temp_state_${roomName}`, JSON.stringify(stateToSave));
JSON.stringify(stateToSave)
);
} catch (e) { } catch (e) {
console.error("Falha ao salvar estado na sessão:", e); console.error("Erro ao salvar sessão:", e);
} }
} }
// --- NOVA FUNÇÃO PARA CORRIGIR O ERRO --- /**
export function loadStateFromSession() { * CARREGAR (Hydration):
* o JSON leve e RECONSTRÓI os objetos pesados (carrega os arquivos via HTTP).
*/
export async function loadStateFromSession() {
const roomName = window.ROOM_NAME || "default_room"; const roomName = window.ROOM_NAME || "default_room";
const tempStateJSON = sessionStorage.getItem(`temp_state_${roomName}`); const tempStateJSON = sessionStorage.getItem(`temp_state_${roomName}`);
if (!tempStateJSON) return false; if (!tempStateJSON) return false;
console.log("Restaurando estado da sessão..."); console.log("Hidratando estado da sessão...");
try { try {
const tempState = JSON.parse(tempStateJSON); const tempState = JSON.parse(tempStateJSON);
// 1. Restaura Pattern Tracks // 1. Restaura Pattern Tracks
// Precisamos recriar os Nodes do Tone.js que não foram salvos
appState.pattern.tracks.forEach((liveTrack) => { appState.pattern.tracks.forEach((liveTrack) => {
const savedTrack = tempState.pattern.tracks.find( const savedTrack = tempState.pattern.tracks.find(t => t.id === liveTrack.id);
(t) => t.id === liveTrack.id
);
if (savedTrack) { if (savedTrack) {
liveTrack.name = savedTrack.name; // Copia dados simples
liveTrack.patterns = savedTrack.patterns; Object.assign(liveTrack, {
liveTrack.activePatternIndex = savedTrack.activePatternIndex; name: savedTrack.name,
liveTrack.volume = savedTrack.volume; patterns: savedTrack.patterns,
liveTrack.pan = savedTrack.pan; activePatternIndex: savedTrack.activePatternIndex,
volume: savedTrack.volume,
pan: savedTrack.pan
});
if (liveTrack.volumeNode) { // Reconecta Nodes de Áudio (Hidratação)
liveTrack.volumeNode.volume.value = Tone.gainToDb(savedTrack.volume); if (liveTrack.volumeNode) liveTrack.volumeNode.volume.value = Tone.gainToDb(savedTrack.volume);
} if (liveTrack.pannerNode) liveTrack.pannerNode.pan.value = savedTrack.pan;
if (liveTrack.pannerNode) {
liveTrack.pannerNode.pan.value = savedTrack.pan;
}
} }
}); });
// Filtra tracks que não existem mais no salvo // Sincroniza lista de tracks (remove deletadas)
appState.pattern.tracks = appState.pattern.tracks.filter((liveTrack) => appState.pattern.tracks = appState.pattern.tracks.filter(liveTrack =>
tempState.pattern.tracks.some((t) => t.id === liveTrack.id) tempState.pattern.tracks.some(t => t.id === liveTrack.id)
); );
// 2. Restaura Áudio // 2. Restaura Áudio Timeline (A parte mais importante)
if (tempState.audio) { if (tempState.audio) {
appState.audio.tracks = tempState.audio.tracks || []; appState.audio.tracks = tempState.audio.tracks || [];
appState.audio.clips = tempState.audio.clips || [];
const clipsMetadata = tempState.audio.clips || [];
const loadedClips = [];
console.log(`Recarregando ${clipsMetadata.length} clips de áudio...`);
// Para cada clipe salvo, baixamos o áudio novamente usando o filePath
for (const metaClip of clipsMetadata) {
let buffer = null;
if (metaClip.filePath) {
try {
// Tone.Buffer carrega o arquivo .wav/.mp3 da URL
buffer = await new Tone.Buffer(metaClip.filePath).loaded;
} catch (err) {
console.warn(`Arquivo não encontrado: ${metaClip.filePath}`, err);
}
}
// Recria o objeto completo na memória
loadedClips.push({
...metaClip, // Pega id, start, duration, pitch...
buffer: buffer // Anexa o áudio pesado recém-carregado
});
}
appState.audio.clips = loadedClips;
} }
// 3. Restaura Global // 3. Restaura Global UI
if (tempState.global) { if (tempState.global) {
const bpmInput = document.getElementById("bpm-input"); const g = tempState.global;
if (bpmInput) bpmInput.value = tempState.global.bpm; const setVal = (id, val) => { const el = document.getElementById(id); if(el) el.value = val; };
const compassoA = document.getElementById("compasso-a-input"); setVal("bpm-input", g.bpm);
if (compassoA) compassoA.value = tempState.global.compassoA; setVal("compasso-a-input", g.compassoA);
setVal("compasso-b-input", g.compassoB);
setVal("bars-input", g.bars);
const compassoB = document.getElementById("compasso-b-input"); if (g.syncMode) {
if (compassoB) compassoB.value = tempState.global.compassoB; appState.global.syncMode = g.syncMode;
const btn = document.getElementById("sync-mode-btn");
const barsInput = document.getElementById("bars-input"); if (btn) {
if (barsInput) barsInput.value = tempState.global.bars; btn.classList.toggle("active", g.syncMode === "global");
btn.textContent = g.syncMode === "global" ? "Global" : "Local";
if (tempState.global.syncMode) {
appState.global.syncMode = tempState.global.syncMode;
const syncBtn = document.getElementById("sync-mode-btn");
if (syncBtn) {
syncBtn.classList.toggle("active", tempState.global.syncMode === "global");
syncBtn.textContent = tempState.global.syncMode === "global" ? "Global" : "Local";
} }
} }
} }
@ -205,7 +240,7 @@ export function loadStateFromSession() {
appState.pattern.activeTrackId = tempState.pattern.activeTrackId; appState.pattern.activeTrackId = tempState.pattern.activeTrackId;
return true; return true;
} catch (e) { } catch (e) {
console.error("Erro ao carregar estado da sessão:", e); console.error("Erro crítico ao carregar sessão:", e);
return false; return false;
} }
} }