mmpSearch/assets/js/creations/state.js

112 lines
3.4 KiB
JavaScript

// js/state.js
import { DEFAULT_VOLUME, DEFAULT_PAN } from "./config.js";
import {
initializeAudioContext,
getAudioContext,
getMainGainNode,
} from "./audio.js";
import { renderApp } from "./ui.js";
// O "cérebro" da aplicação
export let appState = {
tracks: [],
isPlaying: false,
playbackIntervalId: null,
currentStep: 0,
metronomeEnabled: false,
originalXmlDoc: null,
};
export function addTrackToState() {
initializeAudioContext();
const audioContext = getAudioContext();
const mainGainNode = getMainGainNode();
const newTrack = {
id: Date.now(),
name: "novo instrumento",
samplePath: null,
audioBuffer: null, // (NOVO) Adicionado para armazenar o áudio decodificado
steps: [],
volume: DEFAULT_VOLUME,
pan: DEFAULT_PAN,
gainNode: audioContext.createGain(),
pannerNode: audioContext.createStereoPanner(),
};
newTrack.gainNode.connect(newTrack.pannerNode);
newTrack.pannerNode.connect(mainGainNode);
newTrack.gainNode.gain.value = newTrack.volume;
newTrack.pannerNode.pan.value = newTrack.pan;
appState.tracks.push(newTrack);
renderApp();
}
export function removeLastTrackFromState() {
appState.tracks.pop();
renderApp();
}
// (ALTERADO) A função agora é 'async' para carregar e decodificar o áudio
export async function updateTrackSample(trackId, samplePath) {
const track = appState.tracks.find((t) => t.id == trackId);
if (track) {
track.samplePath = samplePath;
track.name = samplePath.split("/").pop();
track.audioBuffer = null; // Limpa o buffer antigo enquanto carrega o novo
renderApp(); // Renderiza imediatamente para mostrar o novo nome
// (NOVO) Lógica para carregar e decodificar o áudio em segundo plano
try {
const audioContext = getAudioContext();
if (!audioContext) initializeAudioContext(); // Garante que o contexto de áudio exista
const response = await fetch(samplePath);
const arrayBuffer = await response.arrayBuffer();
const decodedAudio = await audioContext.decodeAudioData(arrayBuffer);
track.audioBuffer = decodedAudio; // Armazena o buffer decodificado no estado da track
console.log(`Sample ${track.name} carregado e decodificado com sucesso.`);
} catch (error) {
console.error("Erro ao carregar ou decodificar o sample:", error);
track.samplePath = null;
track.name = "erro ao carregar";
renderApp(); // Re-renderiza para mostrar a mensagem de erro
}
}
}
export function toggleStepState(trackId, stepIndex) {
const track = appState.tracks.find((t) => t.id == trackId);
if (track) {
track.steps[stepIndex] = !track.steps[stepIndex];
}
}
export function updateTrackVolume(trackId, volume) {
const track = appState.tracks.find((t) => t.id == trackId);
const audioContext = getAudioContext();
if (track) {
const clampedVolume = Math.max(0, Math.min(1.5, volume));
track.volume = clampedVolume;
if (track.gainNode) {
track.gainNode.gain.setValueAtTime(
clampedVolume,
audioContext.currentTime
);
}
}
}
export function updateTrackPan(trackId, pan) {
const track = appState.tracks.find((t) => t.id == trackId);
const audioContext = getAudioContext();
if (track) {
const clampedPan = Math.max(-1, Math.min(1, pan));
track.pan = clampedPan;
if (track.pannerNode) {
track.pannerNode.pan.setValueAtTime(clampedPan, audioContext.currentTime);
}
}
}