From 6eb81089f7ee13c650892e6ab454efc2f955fe80 Mon Sep 17 00:00:00 2001 From: JotaChina Date: Mon, 24 Nov 2025 16:10:21 -0300 Subject: [PATCH] =?UTF-8?q?corre=C3=A7=C3=A3o=20de=20estado=20circular=20n?= =?UTF-8?q?a=20sess=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/js/creations/file.js | 2 +- assets/js/creations/socket.js | 2 +- assets/js/creations/state.js | 205 ++++++++++++++++++++-------------- 3 files changed, 122 insertions(+), 87 deletions(-) diff --git a/assets/js/creations/file.js b/assets/js/creations/file.js index 6282b8c..5161d3e 100644 --- a/assets/js/creations/file.js +++ b/assets/js/creations/file.js @@ -1,5 +1,5 @@ // 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 { renderAll, getSamplePathMap } from "./ui.js"; import { DEFAULT_PAN, DEFAULT_VOLUME, NOTE_LENGTH } from "./config.js"; diff --git a/assets/js/creations/socket.js b/assets/js/creations/socket.js index 2a79802..bb8d4ea 100644 --- a/assets/js/creations/socket.js +++ b/assets/js/creations/socket.js @@ -3,7 +3,7 @@ // -------------------relómm------------------------------------------------------ // IMPORTS & STATE // ----------------------------------------------------------------------------- -import { appState, saveStateToSession } from "./state.js"; +import { appState, saveStateToSession, loadStateFromSession } from "./state.js"; import { addTrackToState, removeLastTrackFromState, diff --git a/assets/js/creations/state.js b/assets/js/creations/state.js index 827f7ba..17b7e3a 100644 --- a/assets/js/creations/state.js +++ b/assets/js/creations/state.js @@ -1,10 +1,16 @@ // js/state.js -import { initializePatternState } from "./pattern/pattern_state.js"; import { audioState, initializeAudioState } from "./audio/audio_state.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 = { sliceToolActive: false, isPlaying: false, @@ -26,16 +32,9 @@ const globalState = { clipboard: null, lastRulerClickTime: 0, 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 = { global: globalState, pattern: patternState, @@ -44,9 +43,9 @@ export let appState = { window.appState = appState; 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, { sliceToolActive: false, isPlaying: false, @@ -68,52 +67,68 @@ export function resetProjectState() { clipboard: null, lastRulerClickTime: 0, justReset: false, + // syncMode mantemos o que estava }); - // 2. Reseta o estado do pattern + // 2. Reseta Pattern Object.assign(appState.pattern, { tracks: [], activeTrackId: null, 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) { appState.audio.tracks = []; appState.audio.clips = []; appState.audio.audioEditorSeekTime = 0; } - 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() { if (!window.ROOM_NAME) return; - // 1. Crie uma versão "limpa" dos tracks - const cleanTracks = appState.pattern.tracks.map((track) => { - return { - id: track.id, - name: track.name, - samplePath: track.samplePath, - patterns: track.patterns, - activePatternIndex: track.activePatternIndex, - volume: track.volume, - pan: track.pan, - instrumentName: track.instrumentName, - instrumentXml: track.instrumentXml, - }; - }); + // 1. Sanitiza Tracks do Pattern (Instrumentos) + const cleanPatternTracks = appState.pattern.tracks.map((track) => ({ + id: track.id, + name: track.name, + samplePath: track.samplePath, // Caminho do arquivo (String) + patterns: track.patterns, + activePatternIndex: track.activePatternIndex, + volume: track.volume, + pan: track.pan, + instrumentName: track.instrumentName, + 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 = { - pattern: { - ...appState.pattern, - tracks: cleanTracks, - }, - audio: { - tracks: appState.audio.tracks || [], - clips: appState.audio.clips || [], + pattern: { ...appState.pattern, tracks: cleanPatternTracks }, + audio: { + tracks: appState.audio.tracks || [], // Tracks são apenas containers leves + clips: cleanAudioClips }, global: { bpm: document.getElementById("bpm-input")?.value || 140, @@ -126,78 +141,98 @@ export function saveStateToSession() { try { const roomName = window.ROOM_NAME || "default_room"; - sessionStorage.setItem( - `temp_state_${roomName}`, - JSON.stringify(stateToSave) - ); + // Agora o JSON.stringify funciona porque só tem dados simples + sessionStorage.setItem(`temp_state_${roomName}`, JSON.stringify(stateToSave)); } 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): + * Lê 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 tempStateJSON = sessionStorage.getItem(`temp_state_${roomName}`); if (!tempStateJSON) return false; - console.log("Restaurando estado da sessão..."); + console.log("Hidratando estado da sessão..."); try { const tempState = JSON.parse(tempStateJSON); // 1. Restaura Pattern Tracks + // Precisamos recriar os Nodes do Tone.js que não foram salvos appState.pattern.tracks.forEach((liveTrack) => { - const savedTrack = tempState.pattern.tracks.find( - (t) => t.id === liveTrack.id - ); + const savedTrack = tempState.pattern.tracks.find(t => t.id === liveTrack.id); if (savedTrack) { - liveTrack.name = savedTrack.name; - liveTrack.patterns = savedTrack.patterns; - liveTrack.activePatternIndex = savedTrack.activePatternIndex; - liveTrack.volume = savedTrack.volume; - liveTrack.pan = savedTrack.pan; - - if (liveTrack.volumeNode) { - liveTrack.volumeNode.volume.value = Tone.gainToDb(savedTrack.volume); - } - if (liveTrack.pannerNode) { - liveTrack.pannerNode.pan.value = savedTrack.pan; - } + // Copia dados simples + Object.assign(liveTrack, { + name: savedTrack.name, + patterns: savedTrack.patterns, + activePatternIndex: savedTrack.activePatternIndex, + volume: savedTrack.volume, + pan: savedTrack.pan + }); + + // Reconecta Nodes de Áudio (Hidratação) + if (liveTrack.volumeNode) liveTrack.volumeNode.volume.value = Tone.gainToDb(savedTrack.volume); + if (liveTrack.pannerNode) liveTrack.pannerNode.pan.value = savedTrack.pan; } }); - - // Filtra tracks que não existem mais no salvo - appState.pattern.tracks = appState.pattern.tracks.filter((liveTrack) => - tempState.pattern.tracks.some((t) => t.id === liveTrack.id) + + // Sincroniza lista de tracks (remove deletadas) + appState.pattern.tracks = appState.pattern.tracks.filter(liveTrack => + tempState.pattern.tracks.some(t => t.id === liveTrack.id) ); - // 2. Restaura Áudio + // 2. Restaura Áudio Timeline (A parte mais importante) if (tempState.audio) { 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) { - const bpmInput = document.getElementById("bpm-input"); - if (bpmInput) bpmInput.value = tempState.global.bpm; + const g = tempState.global; + const setVal = (id, val) => { const el = document.getElementById(id); if(el) el.value = val; }; - const compassoA = document.getElementById("compasso-a-input"); - if (compassoA) compassoA.value = tempState.global.compassoA; + setVal("bpm-input", g.bpm); + 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 (compassoB) compassoB.value = tempState.global.compassoB; - - const barsInput = document.getElementById("bars-input"); - if (barsInput) barsInput.value = tempState.global.bars; - - 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"; + if (g.syncMode) { + appState.global.syncMode = g.syncMode; + const btn = document.getElementById("sync-mode-btn"); + if (btn) { + btn.classList.toggle("active", g.syncMode === "global"); + btn.textContent = g.syncMode === "global" ? "Global" : "Local"; } } } @@ -205,7 +240,7 @@ export function loadStateFromSession() { appState.pattern.activeTrackId = tempState.pattern.activeTrackId; return true; } catch (e) { - console.error("Erro ao carregar estado da sessão:", e); + console.error("Erro crítico ao carregar sessão:", e); return false; } } \ No newline at end of file