correção de estado circular na sessão
Deploy / Deploy (push) Successful in 1m40s
Details
Deploy / Deploy (push) Successful in 1m40s
Details
This commit is contained in:
parent
0947156d7e
commit
5e331243b7
|
|
@ -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
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
audio: {
|
||||||
tracks: cleanTracks,
|
tracks: appState.audio.tracks || [], // Tracks são apenas containers leves
|
||||||
},
|
clips: cleanAudioClips
|
||||||
audio: {
|
|
||||||
tracks: appState.audio.tracks || [],
|
|
||||||
clips: appState.audio.clips || [],
|
|
||||||
},
|
},
|
||||||
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):
|
||||||
|
* 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 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,
|
||||||
if (liveTrack.volumeNode) {
|
pan: savedTrack.pan
|
||||||
liveTrack.volumeNode.volume.value = Tone.gainToDb(savedTrack.volume);
|
});
|
||||||
}
|
|
||||||
if (liveTrack.pannerNode) {
|
// Reconecta Nodes de Áudio (Hidratação)
|
||||||
liveTrack.pannerNode.pan.value = savedTrack.pan;
|
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
|
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue