sync mode on

This commit is contained in:
JotaChina committed 2025-10-26 16:43:01 -03:00
1 parent 1818932244
commit 1174b328e5
415 files changed
+44294 -1182

No files matched your search

+101 -50
View File
@@ -1,97 +1,148 @@
// js/pattern_state.js
import * as Tone from "https://esm.sh/tone";
import { appState } from "../state.js";
import { DEFAULT_VOLUME, DEFAULT_PAN } from "../config.js";
import { getAudioContext, getMainGainNode } from "../audio.js";
import { renderPatternEditor } from "./pattern_ui.js";
import { getMainGainNode } from "../audio.js";
import { getTotalSteps } from "../utils.js";
const initialState = {
tracks: [],
activeTrackId: null,
activePatternIndex: 0,
};
export let patternState = { ...initialState };
export function initializePatternState() {
Object.assign(patternState, initialState, { tracks: [] });
// Limpa players/buffers existentes
appState.pattern.tracks.forEach(track => {
try { track.player?.dispose(); } catch {}
try { track.buffer?.dispose?.(); } catch {}
});
// Reseta estado do editor de pattern
appState.pattern.tracks = [];
appState.pattern.activeTrackId = null;
appState.pattern.activePatternIndex = 0;
}
// --- FUNÇÃO CORRIGIDA ---
// Agora, esta função cria e pré-carrega um Tone.Player para a faixa.
export async function loadAudioForTrack(track) {
if (!track.samplePath) return track;
try {
// Se já existir um player antigo, o descartamos para liberar memória.
if (track.player) {
track.player.dispose();
}
// Cria um novo Tone.Player e o conecta à cadeia de áudio da faixa.
// O 'await' garante que o áudio seja totalmente carregado antes de prosseguirmos.
track.player = await new Tone.Player(track.samplePath).toDestination();
track.player.chain(track.gainNode, track.pannerNode, getMainGainNode());
} catch (error) {
console.error(`Falha ao carregar áudio para a trilha ${track.name}:`, error);
// Descartar player/buffer anteriores com segurança
try { track.player?.dispose(); } catch {}
track.player = null;
try { track.buffer?.dispose?.(); } catch {}
track.buffer = null;
// Garante nós de volume/pan (Opção B: Volume em dB)
if (!track.volumeNode) {
track.volumeNode = new Tone.Volume(
track.volume === 0 ? -Infinity : Tone.gainToDb(track.volume)
);
} else {
track.volumeNode.volume.value =
track.volume === 0 ? -Infinity : Tone.gainToDb(track.volume);
}
if (!track.pannerNode) {
track.pannerNode = new Tone.Panner(track.pan ?? 0);
} else {
track.pannerNode.pan.value = track.pan ?? 0;
}
// Encadeia: Volume(dB) -> Panner -> Master
try { track.volumeNode.disconnect(); } catch {}
try { track.pannerNode.disconnect(); } catch {}
track.volumeNode.connect(track.pannerNode);
track.pannerNode.connect(getMainGainNode());
// Cria e carrega o Player
const player = new Tone.Player({ url: track.samplePath, autostart: false });
await player.load(track.samplePath); // garante buffer carregado
// Conecta o player ao volumeNode
player.connect(track.volumeNode);
// Buffer separado (se você usar waveform em outro lugar)
const buffer = new Tone.Buffer();
await buffer.load(track.samplePath);
// Atribuições finais
track.player = player;
track.buffer = buffer;
} catch (error) {
console.error('Erro ao carregar sample:', track.samplePath);
console.error(`Falha ao carregar áudio para a trilha "${track.name}":`, error);
try { track.player?.dispose(); } catch {}
try { track.buffer?.dispose?.(); } catch {}
track.player = null;
track.buffer = null;
}
return track;
}
export function addTrackToState() {
const mainGainNode = getMainGainNode();
const totalSteps = getTotalSteps();
const referenceTrack = patternState.tracks[0];
const referenceTrack = appState.pattern.tracks[0];
const newTrack = {
id: Date.now(),
id: Date.now() + Math.random(),
name: "novo instrumento",
samplePath: null,
player: null, // <-- ADICIONADO: O player começará como nulo
patterns: referenceTrack
? referenceTrack.patterns.map(p => ({ name: p.name, steps: new Array(p.steps.length).fill(false), pos: p.pos }))
player: null,
buffer: null,
patterns: referenceTrack
? referenceTrack.patterns.map(p => ({
name: p.name,
steps: new Array(p.steps.length).fill(false),
pos: p.pos
}))
: [{ name: "Pattern 1", steps: new Array(totalSteps).fill(false), pos: 0 }],
activePatternIndex: 0,
volume: DEFAULT_VOLUME,
pan: DEFAULT_PAN,
gainNode: new Tone.Gain(Tone.gainToDb(DEFAULT_VOLUME)),
// Opção B: controlar volume em dB
volumeNode: new Tone.Volume(Tone.gainToDb(DEFAULT_VOLUME)),
pannerNode: new Tone.Panner(DEFAULT_PAN),
};
newTrack.gainNode.chain(newTrack.pannerNode, mainGainNode);
// Cadeia de áudio nova
newTrack.volumeNode.connect(newTrack.pannerNode);
newTrack.pannerNode.connect(getMainGainNode());
patternState.tracks.push(newTrack);
renderPatternEditor();
appState.pattern.tracks.push(newTrack);
appState.pattern.activeTrackId = newTrack.id;
}
export function removeLastTrackFromState() {
if (patternState.tracks.length > 0) {
const trackToRemove = patternState.tracks[patternState.tracks.length - 1];
if (trackToRemove.player) trackToRemove.player.dispose();
if (trackToRemove.pannerNode) trackToRemove.pannerNode.dispose();
if (trackToRemove.gainNode) trackToRemove.gainNode.dispose();
patternState.tracks.pop();
renderPatternEditor();
if (appState.pattern.tracks.length > 0) {
const trackToRemove = appState.pattern.tracks[appState.pattern.tracks.length - 1];
try { trackToRemove.player?.dispose(); } catch {}
try { trackToRemove.buffer?.dispose?.(); } catch {}
try { trackToRemove.pannerNode?.disconnect(); } catch {}
try { trackToRemove.volumeNode?.disconnect(); } catch {}
appState.pattern.tracks.pop();
if (appState.pattern.activeTrackId === trackToRemove.id) {
appState.pattern.activeTrackId = appState.pattern.tracks[0]?.id ?? null;
}
}
}
export async function updateTrackSample(trackId, samplePath) {
const track = patternState.tracks.find((t) => t.id == trackId);
export async function updateTrackSample(trackIndex, samplePath) {
const track = appState.pattern.tracks[trackIndex];
if (track) {
track.samplePath = samplePath;
track.name = samplePath.split("/").pop();
await loadAudioForTrack(track); // Carrega o novo player
renderPatternEditor();
// (re)carrega e reconecta corretamente o player nesta trilha
await loadAudioForTrack(track);
}
}
export function toggleStepState(trackId, stepIndex) {
const track = patternState.tracks.find((t) => t.id == trackId);
export function toggleStepState(trackIndex, stepIndex) {
const track = appState.pattern.tracks[trackIndex];
if (track && track.patterns && track.patterns.length > 0) {
const activePattern = track.patterns[track.activePatternIndex];
if (activePattern && activePattern.steps.length > stepIndex) {
activePattern.steps[stepIndex] = !activePattern.steps[stepIndex];
}
}
}
}