// 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 { getMainGainNode } from "../audio.js"; import { getTotalSteps } from "../utils.js"; export function initializePatternState() { // 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; } export async function loadAudioForTrack(track) { if (!track.samplePath) return track; try { // 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 totalSteps = getTotalSteps(); const referenceTrack = appState.pattern.tracks[0]; const newTrack = { id: Date.now() + Math.random(), name: "novo instrumento", samplePath: null, 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, // Opção B: controlar volume em dB volumeNode: new Tone.Volume(Tone.gainToDb(DEFAULT_VOLUME)), pannerNode: new Tone.Panner(DEFAULT_PAN), }; // Cadeia de áudio nova newTrack.volumeNode.connect(newTrack.pannerNode); newTrack.pannerNode.connect(getMainGainNode()); appState.pattern.tracks.push(newTrack); appState.pattern.activeTrackId = newTrack.id; } export function removeLastTrackFromState() { 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(trackIndex, samplePath) { const track = appState.pattern.tracks[trackIndex]; if (track) { track.samplePath = samplePath; track.name = samplePath.split("/").pop(); // (re)carrega e reconecta corretamente o player nesta trilha await loadAudioForTrack(track); } } 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]; } } }