// state.js (versão conceitual nova) import { audioState, initializeAudioState, getAudioSnapshot, applyAudioSnapshot, } from "./audio/audio_state.js"; import { DEFAULT_VOLUME, DEFAULT_PAN } from "./config.js"; import * as Tone from "https://esm.sh/tone"; // ---------------- ESTADOS INICIAIS ---------------- const patternState = { tracks: [], activeTrackId: null, activePatternIndex: 0, }; const globalState = { sliceToolActive: false, isPlaying: false, isAudioEditorPlaying: false, playbackIntervalId: null, currentStep: 0, metronomeEnabled: false, originalXmlDoc: null, currentBeatBasslineName: "Novo Projeto", masterVolume: DEFAULT_VOLUME, masterPan: DEFAULT_PAN, zoomLevelIndex: 2, isLoopActive: false, loopStartTime: 0, loopEndTime: 8, resizeMode: "trim", selectedClipId: null, isRecording: false, clipboard: null, lastRulerClickTime: 0, justReset: false, syncMode: "global", }; export let appState = { global: globalState, pattern: patternState, audio: audioState, // compartilhado com módulo de áudio }; window.appState = appState; // ---------------- HELPERS ---------------- function makePatternSnapshot() { return { tracks: appState.pattern.tracks.map((t) => ({ id: t.id, name: t.name, type: t.type, samplePath: t.samplePath, patterns: (t.patterns || []).map((p) => ({ name: p.name, steps: p.steps, notes: p.notes, pos: p.pos, })), activePatternIndex: t.activePatternIndex || 0, volume: t.volume, pan: t.pan, instrumentName: t.instrumentName, instrumentXml: t.instrumentXml, })), activeTrackId: appState.pattern.activeTrackId, activePatternIndex: appState.pattern.activePatternIndex, }; } // ---------------------- // Helper: existe snapshot local com áudio? // ---------------------- export function hasLocalAudioSnapshot() { if (!window.ROOM_NAME) return false; const raw = sessionStorage.getItem(`temp_state_${window.ROOM_NAME}`); if (!raw) return false; try { const data = JSON.parse(raw); // se tem tracks ou clips, consideramos snapshot válido const audio = data.audioSnapshot; if (!audio) return false; const hasTracks = audio.tracks && audio.tracks.length > 0; const hasClips = audio.clips && audio.clips.length > 0; return hasTracks || hasClips; } catch (e) { console.warn("hasLocalAudioSnapshot: erro ao analisar snapshot", e); return false; } } // ---------------------- // Helper: checa se um snapshot de áudio é "não vazio" // ---------------------- function hasAudioInSnapshot(audioSnapshot) { if (!audioSnapshot) return false; const hasTracks = Array.isArray(audioSnapshot.tracks) && audioSnapshot.tracks.length > 0; const hasClips = Array.isArray(audioSnapshot.clips) && audioSnapshot.clips.length > 0; return hasTracks || hasClips; } // ---------------- RESET GERAL ---------------- export function resetProjectState() { console.log("Executando resetProjectState (Limpeza Profunda)..."); Object.assign(appState.global, { sliceToolActive: false, isPlaying: false, isAudioEditorPlaying: false, playbackIntervalId: null, currentStep: 0, metronomeEnabled: false, originalXmlDoc: null, currentBeatBasslineName: "Novo Projeto", masterVolume: DEFAULT_VOLUME, masterPan: DEFAULT_PAN, zoomLevelIndex: 2, isLoopActive: false, loopStartTime: 0, loopEndTime: 8, resizeMode: "trim", selectedClipId: null, isRecording: false, clipboard: null, lastRulerClickTime: 0, justReset: false, syncMode: appState.global.syncMode ?? "global", }); Object.assign(appState.pattern, { tracks: [], activeTrackId: null, activePatternIndex: 0, }); initializeAudioState(); } // ---------------- SALVAR SESSÃO LOCAL ---------------- export function saveStateToSession() { if (!window.ROOM_NAME) return; try { // Pattern “puro” const patternSnapshot = makePatternSnapshot(); // XML original em string let originalXmlString = null; if (appState.global.originalXmlDoc) { const serializer = new XMLSerializer(); try { originalXmlString = serializer.serializeToString( appState.global.originalXmlDoc ); } catch (e) { console.warn("Não consegui serializar originalXmlDoc:", e); } } const audioSnapshot = getAudioSnapshot(); const snapshot = { version: 1, global: { ...appState.global, playbackIntervalId: null, originalXmlDoc: originalXmlString, }, pattern: patternSnapshot, audioSnapshot, }; sessionStorage.setItem( `temp_state_${window.ROOM_NAME}`, JSON.stringify(snapshot) ); } catch (e) { console.error("Erro salvando sessão:", e); } } // ---------------- CARREGAR SESSÃO LOCAL ---------------- // mode: // - "full" (global + pattern + audio) // - "audioOnly" (apenas áudio, usado como fallback // depois de carregar XML do servidor) export async function loadStateFromSession(mode = "full") { if (!window.ROOM_NAME) return false; const raw = sessionStorage.getItem(`temp_state_${window.ROOM_NAME}`); if (!raw) return false; try { const data = JSON.parse(raw); // GLOBAL & PATTERN só se for "full" if (mode === "full") { if (data.global) { const { originalXmlDoc: xmlString, ...rest } = data.global; Object.assign(appState.global, rest); if (xmlString) { try { const parser = new DOMParser(); appState.global.originalXmlDoc = parser.parseFromString( xmlString, "application/xml" ); } catch (e) { console.warn("Falha ao reconstituir originalXmlDoc:", e); } } } if (data.pattern) { Object.assign(appState.pattern, data.pattern); } } // ÁUDIO em qualquer modo, se existir if (hasAudioInSnapshot(data.audioSnapshot)) { initializeAudioState(); await applyAudioSnapshot(data.audioSnapshot); } return true; } catch (e) { console.error("Erro carregando sessão:", e); return false; } }