// js/audio_state.js import { DEFAULT_VOLUME, DEFAULT_PAN } from "../config.js"; import { renderAudioEditor } from "./audio_ui.js"; import { getMainGainNode } from "../audio.js"; import { getAudioContext } from "../audio.js"; export let audioState = { tracks: [], clips: [], audioEditorStartTime: 0, audioEditorAnimationId: null, audioEditorPlaybackTime: 0, isAudioEditorLoopEnabled: false, }; export function initializeAudioState() { audioState.clips.forEach(clip => { if (clip.pannerNode) clip.pannerNode.dispose(); if (clip.gainNode) clip.gainNode.dispose(); }); Object.assign(audioState, { tracks: [], clips: [], audioEditorStartTime: 0, audioEditorAnimationId: null, audioEditorPlaybackTime: 0, isAudioEditorLoopEnabled: false, }); } export async function loadAudioForClip(clip) { if (!clip.sourcePath) return clip; const audioCtx = getAudioContext(); if (!audioCtx) { console.error("AudioContext não disponível para carregar áudio."); return clip; } try { const response = await fetch(clip.sourcePath); if (!response.ok) throw new Error(`Falha ao buscar áudio: ${clip.sourcePath}`); const arrayBuffer = await response.arrayBuffer(); const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer); clip.buffer = audioBuffer; // --- CORREÇÃO: Salva a duração original --- if (clip.durationInSeconds === 0) { clip.durationInSeconds = audioBuffer.duration; } // Salva a duração real do buffer para cálculos de stretch clip.originalDuration = audioBuffer.duration; } catch (error) { console.error(`Falha ao carregar áudio para o clipe ${clip.name}:`, error); } return clip; } export function addAudioClipToTimeline(samplePath, trackId = 1, startTime = 0, clipName = null) { const newClip = { id: Date.now() + Math.random(), trackId: trackId, sourcePath: samplePath, // --- MODIFICAÇÃO AQUI --- // Usa o nome fornecido, ou extrai do caminho se não for fornecido name: clipName || samplePath.split('/').pop(), startTimeInSeconds: startTime, offset: 0, durationInSeconds: 0, originalDuration: 0, // Será preenchido pelo loadAudioForClip pitch: 0, volume: DEFAULT_VOLUME, pan: DEFAULT_PAN, gainNode: new Tone.Gain(Tone.gainToDb(DEFAULT_VOLUME)), pannerNode: new Tone.Panner(DEFAULT_PAN), buffer: null, player: null, }; newClip.gainNode.connect(newClip.pannerNode); newClip.pannerNode.connect(getMainGainNode()); audioState.clips.push(newClip); loadAudioForClip(newClip).then(() => { renderAudioEditor(); }); } export function updateAudioClipProperties(clipId, properties) { const clip = audioState.clips.find(c => c.id == clipId); if (clip) { Object.assign(clip, properties); } } export function sliceAudioClip(clipId, sliceTimeInTimeline) { const originalClip = audioState.clips.find(c => c.id == clipId); if (!originalClip || sliceTimeInTimeline <= originalClip.startTimeInSeconds || sliceTimeInTimeline >= (originalClip.startTimeInSeconds + originalClip.durationInSeconds)) { console.warn("Corte inválido: fora dos limites do clipe."); return; } const originalOffset = originalClip.offset || 0; const cutPointInClip = sliceTimeInTimeline - originalClip.startTimeInSeconds; const newClip = { id: Date.now() + Math.random(), trackId: originalClip.trackId, sourcePath: originalClip.sourcePath, name: originalClip.name, buffer: originalClip.buffer, startTimeInSeconds: sliceTimeInTimeline, offset: originalOffset + cutPointInClip, durationInSeconds: originalClip.durationInSeconds - cutPointInClip, // --- CORREÇÃO: Propaga a duração original --- originalDuration: originalClip.originalDuration, pitch: originalClip.pitch, volume: originalClip.volume, pan: originalClip.pan, gainNode: new Tone.Gain(Tone.gainToDb(originalClip.volume)), pannerNode: new Tone.Panner(originalClip.pan), player: null }; newClip.gainNode.connect(newClip.pannerNode); newClip.pannerNode.connect(getMainGainNode()); originalClip.durationInSeconds = cutPointInClip; audioState.clips.push(newClip); console.log("Clipe dividido. Original:", originalClip, "Novo:", newClip); } // ... (resto do arquivo 'audio_state.js' sem alterações) ... export function updateClipVolume(clipId, volume) { const clip = audioState.clips.find((c) => c.id == clipId); if (clip) { const clampedVolume = Math.max(0, Math.min(1.5, volume)); clip.volume = clampedVolume; if (clip.gainNode) { clip.gainNode.gain.value = Tone.gainToDb(clampedVolume); } } } export function updateClipPan(clipId, pan) { const clip = audioState.clips.find((c) => c.id == clipId); if (clip) { const clampedPan = Math.max(-1, Math.min(1, pan)); clip.pan = clampedPan; if (clip.pannerNode) { clip.pannerNode.pan.value = clampedPan; } } } export function addAudioTrackLane() { const newTrackName = `Pista de Áudio ${audioState.tracks.length + 1}`; audioState.tracks.push({ id: Date.now(), name: newTrackName }); } export function removeAudioClip(clipId) { const clipIndex = audioState.clips.findIndex(c => c.id == clipId); if (clipIndex === -1) return false; // Retorna false se não encontrou const clip = audioState.clips[clipIndex]; // 1. Limpa os nós de áudio do Tone.js if (clip.gainNode) { clip.gainNode.disconnect(); clip.gainNode.dispose(); } if (clip.pannerNode) { clip.pannerNode.disconnect(); clip.pannerNode.dispose(); } // 2. Remove o clipe do array de estado audioState.clips.splice(clipIndex, 1); // 3. Retorna true para o chamador (Controller) return true; }