Amostras de áudio funcionais até o momento. Agulha de tempo, mute/solo e ajustado com a velocidade de reprodução
This commit is contained in:
1 parent
8c391412e1
commit
602df1db6d
10 files changed
+942
-689
No files matched your search
@@ -1,7 +1,8 @@
|
||||
// js/audio.js
|
||||
import { appState } from "./state.js";
|
||||
import { highlightStep } from "./ui.js";
|
||||
import { highlightStep, updateAudioEditorUI, updatePlayheadVisual, resetPlayheadVisual } from "./ui.js";
|
||||
import { getTotalSteps } from "./utils.js";
|
||||
import { PIXELS_PER_STEP } from "./config.js";
|
||||
|
||||
let audioContext;
|
||||
let mainGainNode;
|
||||
@@ -30,6 +31,7 @@ export function initializeAudioContext() {
|
||||
}
|
||||
}
|
||||
|
||||
// ... (funções de master volume/pan, formatTime, metronome, sample player, tick, etc. permanecem iguais)...
|
||||
export function updateMasterVolume(volume) {
|
||||
if (mainGainNode) {
|
||||
mainGainNode.gain.setValueAtTime(volume, audioContext.currentTime);
|
||||
@@ -120,7 +122,6 @@ function tick() {
|
||||
appState.tracks.forEach((track) => {
|
||||
if (!track.patterns || track.patterns.length === 0) return;
|
||||
|
||||
// Usa o índice GLOBAL para saber qual pattern tocar, sincronizando com a UI.
|
||||
const activePattern = track.patterns[appState.activePatternIndex];
|
||||
|
||||
if (activePattern && activePattern.steps[appState.currentStep] && track.samplePath) {
|
||||
@@ -190,4 +191,104 @@ export function togglePlayback() {
|
||||
appState.currentStep = 0;
|
||||
startPlayback();
|
||||
}
|
||||
}
|
||||
|
||||
function animationLoop() {
|
||||
if (!appState.isAudioEditorPlaying || !audioContext) return;
|
||||
const bpm = parseInt(document.getElementById("bpm-input").value, 10) || 120;
|
||||
const stepsPerSecond = (bpm / 60) * 4;
|
||||
const pixelsPerSecond = stepsPerSecond * PIXELS_PER_STEP;
|
||||
const totalElapsedTime = (audioContext.currentTime - appState.audioEditorStartTime) + appState.audioEditorPlaybackTime;
|
||||
const newPositionPx = totalElapsedTime * pixelsPerSecond;
|
||||
const maxDuration = appState.audioTracks.reduce((max, track) => (track.audioBuffer && track.audioBuffer.duration > max) ? track.audioBuffer.duration : max, 0);
|
||||
if (totalElapsedTime >= maxDuration && maxDuration > 0) {
|
||||
stopAudioEditorPlayback();
|
||||
appState.audioEditorPlaybackTime = 0;
|
||||
resetPlayheadVisual();
|
||||
return;
|
||||
}
|
||||
updatePlayheadVisual(newPositionPx);
|
||||
appState.audioEditorAnimationId = requestAnimationFrame(animationLoop);
|
||||
}
|
||||
|
||||
// --- LÓGICA DE REPRODUÇÃO ATUALIZADA ---
|
||||
export function startAudioEditorPlayback() {
|
||||
if (appState.isAudioEditorPlaying || appState.audioTracks.length === 0) return;
|
||||
initializeAudioContext();
|
||||
|
||||
appState.isAudioEditorPlaying = true;
|
||||
appState.activeAudioSources = [];
|
||||
updateAudioEditorUI();
|
||||
|
||||
const startTime = audioContext.currentTime;
|
||||
appState.audioEditorStartTime = startTime;
|
||||
|
||||
// Verifica se existe alguma faixa no modo "solo"
|
||||
const isAnyTrackSoloed = appState.audioTracks.some(t => t.isSoloed);
|
||||
|
||||
appState.audioTracks.forEach(track => {
|
||||
// Condições para tocar:
|
||||
// 1. A faixa deve ter um buffer de áudio.
|
||||
// 2. A faixa não pode estar mutada.
|
||||
const canPlay = track.audioBuffer && !track.isMuted;
|
||||
|
||||
// 3. Lógica de solo:
|
||||
// - Se houver alguma faixa solada, esta faixa TAMBÉM deve estar solada.
|
||||
// - Se NENHUMA faixa estiver solada, todas podem tocar.
|
||||
const shouldPlay = isAnyTrackSoloed ? track.isSoloed : true;
|
||||
|
||||
if (canPlay && shouldPlay) {
|
||||
if (appState.audioEditorPlaybackTime >= track.audioBuffer.duration) return;
|
||||
|
||||
const source = audioContext.createBufferSource();
|
||||
source.buffer = track.audioBuffer;
|
||||
source.connect(track.gainNode);
|
||||
source.start(startTime, appState.audioEditorPlaybackTime);
|
||||
appState.activeAudioSources.push(source);
|
||||
}
|
||||
});
|
||||
|
||||
if (appState.activeAudioSources.length > 0) {
|
||||
if (appState.audioEditorAnimationId) {
|
||||
cancelAnimationFrame(appState.audioEditorAnimationId);
|
||||
}
|
||||
animationLoop();
|
||||
} else {
|
||||
appState.isAudioEditorPlaying = false;
|
||||
updateAudioEditorUI();
|
||||
}
|
||||
}
|
||||
|
||||
export function stopAudioEditorPlayback() {
|
||||
if (!appState.isAudioEditorPlaying) return;
|
||||
const elapsedTime = (audioContext.currentTime - appState.audioEditorStartTime);
|
||||
appState.audioEditorPlaybackTime += elapsedTime;
|
||||
if (appState.audioEditorAnimationId) {
|
||||
cancelAnimationFrame(appState.audioEditorAnimationId);
|
||||
appState.audioEditorAnimationId = null;
|
||||
}
|
||||
appState.activeAudioSources.forEach(source => {
|
||||
try {
|
||||
source.stop(0);
|
||||
} catch (e) { /* Ignora erros */ }
|
||||
});
|
||||
appState.activeAudioSources = [];
|
||||
appState.isAudioEditorPlaying = false;
|
||||
updateAudioEditorUI();
|
||||
}
|
||||
|
||||
export function seekAudioEditor(newTime) {
|
||||
const wasPlaying = appState.isAudioEditorPlaying;
|
||||
if (wasPlaying) {
|
||||
stopAudioEditorPlayback();
|
||||
}
|
||||
appState.audioEditorPlaybackTime = newTime;
|
||||
const bpm = parseInt(document.getElementById("bpm-input").value, 10) || 120;
|
||||
const stepsPerSecond = (bpm / 60) * 4;
|
||||
const pixelsPerSecond = stepsPerSecond * PIXELS_PER_STEP;
|
||||
const newPositionPx = newTime * pixelsPerSecond;
|
||||
updatePlayheadVisual(newPositionPx);
|
||||
if (wasPlaying) {
|
||||
startAudioEditorPlayback();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user