// js/pattern_audio.js import { appState } from "../state.js"; import { highlightStep } from "./pattern_ui.js"; import { getTotalSteps } from "../utils.js"; import { initializeAudioContext } from "../audio.js"; const timerDisplay = document.getElementById('timer-display'); function formatTime(milliseconds) { const totalSeconds = Math.floor(milliseconds / 1000); const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0'); const seconds = (totalSeconds % 60).toString().padStart(2, '0'); const centiseconds = Math.floor((milliseconds % 1000) / 10).toString().padStart(2, '0'); return `${minutes}:${seconds}:${centiseconds}`; } export function playMetronomeSound(isDownbeat) { initializeAudioContext(); const synth = new Tone.Synth().toDestination(); const freq = isDownbeat ? 1000 : 800; synth.triggerAttackRelease(freq, "8n", Tone.now()); } // --- FUNÇÃO CORRIGIDA E EFICIENTE --- export function playSample(filePath, trackId) { initializeAudioContext(); const track = trackId ? appState.pattern.tracks.find((t) => t.id == trackId) : null; // Se a faixa existe e tem um player pré-carregado, apenas o dispara. if (track && track.player) { // Atualiza o volume/pan caso tenham sido alterados track.gainNode.gain.value = Tone.gainToDb(track.volume); track.pannerNode.pan.value = track.pan; // Dispara o som imediatamente. Esta operação é instantânea. track.player.start(Tone.now()); } // Fallback para preview de samples no navegador (sem trackId) else if (!trackId && filePath) { const previewPlayer = new Tone.Player(filePath).toDestination(); previewPlayer.autostart = true; } } function tick() { if (!appState.global.isPlaying) { stopPlayback(); return; } const totalSteps = getTotalSteps(); const lastStepIndex = appState.global.currentStep === 0 ? totalSteps - 1 : appState.global.currentStep - 1; highlightStep(lastStepIndex, false); const bpm = parseInt(document.getElementById("bpm-input").value, 10) || 120; const stepInterval = (60 * 1000) / (bpm * 4); const currentTime = appState.global.currentStep * stepInterval; if (timerDisplay) { timerDisplay.textContent = formatTime(currentTime); } if (appState.global.metronomeEnabled) { const noteValue = parseInt(document.getElementById("compasso-b-input").value, 10) || 4; const stepsPerBeat = 16 / noteValue; if (appState.global.currentStep % stepsPerBeat === 0) { playMetronomeSound(appState.global.currentStep % (stepsPerBeat * 4) === 0); } } appState.pattern.tracks.forEach((track) => { if (!track.patterns || track.patterns.length === 0) return; const activePattern = track.patterns[appState.pattern.activePatternIndex]; if (activePattern && activePattern.steps[appState.global.currentStep] && track.samplePath) { playSample(track.samplePath, track.id); } }); highlightStep(appState.global.currentStep, true); appState.global.currentStep = (appState.global.currentStep + 1) % totalSteps; } export function startPlayback() { if (appState.global.isPlaying || appState.pattern.tracks.length === 0) return; initializeAudioContext(); if (appState.global.currentStep === 0) { rewindPlayback(); } const bpm = parseInt(document.getElementById("bpm-input").value, 10) || 120; Tone.Transport.bpm.value = bpm; const stepInterval = (60 * 1000) / (bpm * 4); if (appState.global.playbackIntervalId) clearInterval(appState.global.playbackIntervalId); appState.global.isPlaying = true; document.getElementById("play-btn").classList.remove("fa-play"); document.getElementById("play-btn").classList.add("fa-pause"); tick(); appState.global.playbackIntervalId = setInterval(tick, stepInterval); } export function stopPlayback() { if(appState.global.playbackIntervalId) { clearInterval(appState.global.playbackIntervalId); } appState.global.playbackIntervalId = null; appState.global.isPlaying = false; document.querySelectorAll('.step.playing').forEach(s => s.classList.remove('playing')); appState.global.currentStep = 0; if (timerDisplay) timerDisplay.textContent = '00:00:00'; const playBtn = document.getElementById("play-btn"); if (playBtn) { playBtn.classList.remove("fa-pause"); playBtn.classList.add("fa-play"); } } export function rewindPlayback() { const lastStep = appState.global.currentStep > 0 ? appState.global.currentStep - 1 : getTotalSteps() - 1; appState.global.currentStep = 0; if (!appState.global.isPlaying) { if (timerDisplay) timerDisplay.textContent = '00:00:00'; highlightStep(lastStep, false); } } export function togglePlayback() { initializeAudioContext(); if (appState.global.isPlaying) { stopPlayback(); } else { appState.global.currentStep = 0; startPlayback(); } }