mmpSearch/assets/js/creations/state.js

250 lines
6.2 KiB
JavaScript

// 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;
}
}