Amostras de áudio funcionais até o momento. Agulha de tempo, mute/solo e ajustado com a velocidade de reprodução, loop funcional.
This commit is contained in:
1 parent
602df1db6d
commit
d506332268
6 files changed
+163
-95
No files matched your search
@@ -488,4 +488,14 @@ body.sidebar-hidden .global-toolbar {
|
||||
z-index: 10;
|
||||
pointer-events: none; /* Impede que a agulha intercepte cliques do mouse */
|
||||
transition: background-color 0.3s; /* Efeito suave ao parar */
|
||||
}
|
||||
|
||||
/* Estilo para o botão de loop na barra de ferramentas do editor de áudio */
|
||||
#audio-editor-loop-btn {
|
||||
color: var(--text-dark); /* Cor padrão quando está desligado */
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
#audio-editor-loop-btn.active {
|
||||
color: var(--accent-green); /* Cor de destaque quando está ligado */
|
||||
}
|
||||
@@ -31,7 +31,6 @@ 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);
|
||||
@@ -195,23 +194,33 @@ export function togglePlayback() {
|
||||
|
||||
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;
|
||||
|
||||
let totalElapsedTime = (audioContext.currentTime - appState.audioEditorStartTime) + appState.audioEditorPlaybackTime;
|
||||
|
||||
const maxDuration = appState.audioTracks.reduce((max, track) =>
|
||||
(track.audioBuffer && track.audioBuffer.duration > max) ? track.audioBuffer.duration : max, 0
|
||||
);
|
||||
|
||||
if (appState.isAudioEditorLoopEnabled && maxDuration > 0) {
|
||||
totalElapsedTime = totalElapsedTime % maxDuration;
|
||||
} else {
|
||||
if (totalElapsedTime >= maxDuration && maxDuration > 0) {
|
||||
stopAudioEditorPlayback();
|
||||
appState.audioEditorPlaybackTime = 0;
|
||||
resetPlayheadVisual();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const newPositionPx = totalElapsedTime * pixelsPerSecond;
|
||||
updatePlayheadVisual(newPositionPx);
|
||||
appState.audioEditorAnimationId = requestAnimationFrame(animationLoop);
|
||||
}
|
||||
|
||||
// --- LÓGICA DE REPRODUÇÃO ATUALIZADA ---
|
||||
export function startAudioEditorPlayback() {
|
||||
if (appState.isAudioEditorPlaying || appState.audioTracks.length === 0) return;
|
||||
initializeAudioContext();
|
||||
@@ -223,25 +232,13 @@ export function startAudioEditorPlayback() {
|
||||
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 (track.audioBuffer && !track.isMuted && track.isSoloed) {
|
||||
if (appState.audioEditorPlaybackTime >= track.audioBuffer.duration) return;
|
||||
|
||||
const source = audioContext.createBufferSource();
|
||||
source.buffer = track.audioBuffer;
|
||||
source.loop = appState.isAudioEditorLoopEnabled;
|
||||
source.connect(track.gainNode);
|
||||
source.start(startTime, appState.audioEditorPlaybackTime);
|
||||
appState.activeAudioSources.push(source);
|
||||
@@ -261,17 +258,35 @@ export function startAudioEditorPlayback() {
|
||||
|
||||
export function stopAudioEditorPlayback() {
|
||||
if (!appState.isAudioEditorPlaying) return;
|
||||
const elapsedTime = (audioContext.currentTime - appState.audioEditorStartTime);
|
||||
appState.audioEditorPlaybackTime += elapsedTime;
|
||||
|
||||
let totalElapsedTime = (audioContext.currentTime - appState.audioEditorStartTime) + appState.audioEditorPlaybackTime;
|
||||
|
||||
const maxDuration = appState.audioTracks.reduce((max, track) =>
|
||||
(track.audioBuffer && track.audioBuffer.duration > max) ? track.audioBuffer.duration : max, 0
|
||||
);
|
||||
|
||||
// --- CORREÇÃO FINAL E ROBUSTA ---
|
||||
// Sempre aplica o módulo ao salvar o tempo.
|
||||
// Se não estava em loop, totalElapsedTime < maxDuration, e o módulo não faz nada.
|
||||
// Se estava em loop, ele corrige o valor para a posição visual correta.
|
||||
if (maxDuration > 0) {
|
||||
appState.audioEditorPlaybackTime = totalElapsedTime % maxDuration;
|
||||
} else {
|
||||
appState.audioEditorPlaybackTime = totalElapsedTime;
|
||||
}
|
||||
// --- FIM DA CORREÇÃO ---
|
||||
|
||||
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();
|
||||
@@ -291,4 +306,11 @@ export function seekAudioEditor(newTime) {
|
||||
if (wasPlaying) {
|
||||
startAudioEditorPlayback();
|
||||
}
|
||||
}
|
||||
|
||||
export function restartAudioEditorIfPlaying() {
|
||||
if (appState.isAudioEditorPlaying) {
|
||||
stopAudioEditorPlayback();
|
||||
startAudioEditorPlayback();
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
updateMasterPan,
|
||||
startAudioEditorPlayback,
|
||||
stopAudioEditorPlayback,
|
||||
restartAudioEditorIfPlaying,
|
||||
} from "./audio.js";
|
||||
import { handleFileLoad, generateMmpFile } from "./file.js";
|
||||
import {
|
||||
@@ -21,7 +22,7 @@ import {
|
||||
loadAndRenderSampleBrowser,
|
||||
showOpenProjectModal,
|
||||
closeOpenProjectModal,
|
||||
handleSampleUpload, // Importa handleSampleUpload, embora não seja mais usado diretamente
|
||||
handleSampleUpload,
|
||||
} from "./ui.js";
|
||||
import { adjustValue, enforceNumericInput } from "./utils.js";
|
||||
import { DEFAULT_PAN, DEFAULT_VOLUME } from "./config.js";
|
||||
@@ -37,6 +38,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const stopBtn = document.getElementById("stop-btn");
|
||||
const audioEditorPlayBtn = document.getElementById("audio-editor-play-btn");
|
||||
const audioEditorStopBtn = document.getElementById("audio-editor-stop-btn");
|
||||
const audioEditorLoopBtn = document.getElementById("audio-editor-loop-btn");
|
||||
const rewindBtn = document.getElementById("rewind-btn");
|
||||
const metronomeBtn = document.getElementById("metronome-btn");
|
||||
const mmpFileInput = document.getElementById("mmp-file-input");
|
||||
@@ -57,6 +59,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
return;
|
||||
Object.assign(appState, {
|
||||
tracks: [],
|
||||
audioTracks: [],
|
||||
activeTrackId: null,
|
||||
isPlaying: false,
|
||||
playbackIntervalId: null,
|
||||
@@ -166,8 +169,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
uploadSampleBtn.addEventListener("click", () => sampleFileInput.click());
|
||||
|
||||
// --- INÍCIO DA CORREÇÃO ---
|
||||
// Lógica de upload de sample para o servidor Flask
|
||||
sampleFileInput.addEventListener("change", async (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
@@ -176,7 +177,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
formData.append("sampleFile", file);
|
||||
|
||||
try {
|
||||
// ATENÇÃO: Verifique se a URL e a porta estão corretas para o seu servidor Flask
|
||||
const response = await fetch('http://localhost:5000/upload-sample', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
@@ -186,7 +186,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
if (response.ok) {
|
||||
alert("Sample enviado com sucesso!");
|
||||
// Recarrega a lista de samples para exibir o novo arquivo
|
||||
await loadAndRenderSampleBrowser();
|
||||
} else {
|
||||
throw new Error(result.error || "Erro desconhecido no servidor.");
|
||||
@@ -197,9 +196,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
alert(`Falha no upload: ${error.message}`);
|
||||
}
|
||||
|
||||
event.target.value = null; // Limpa o input para permitir o mesmo arquivo de novo
|
||||
event.target.value = null;
|
||||
});
|
||||
// --- FIM DA CORREÇÃO ---
|
||||
|
||||
saveMmpBtn.addEventListener("click", generateMmpFile);
|
||||
addInstrumentBtn.addEventListener("click", addTrackToState);
|
||||
@@ -252,7 +250,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// Listeners para os controles do editor de áudio
|
||||
audioEditorPlayBtn.addEventListener("click", () => {
|
||||
if (appState.isAudioEditorPlaying) {
|
||||
stopAudioEditorPlayback();
|
||||
@@ -263,6 +260,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
audioEditorStopBtn.addEventListener("click", stopAudioEditorPlayback);
|
||||
|
||||
audioEditorLoopBtn.addEventListener("click", () => {
|
||||
appState.isAudioEditorLoopEnabled = !appState.isAudioEditorLoopEnabled;
|
||||
audioEditorLoopBtn.classList.toggle("active", appState.isAudioEditorLoopEnabled);
|
||||
restartAudioEditorIfPlaying();
|
||||
});
|
||||
|
||||
loadAndRenderSampleBrowser();
|
||||
renderApp();
|
||||
setupMasterKnobs();
|
||||
|
||||
@@ -18,7 +18,8 @@ export let appState = {
|
||||
activeAudioSources: [],
|
||||
audioEditorStartTime: 0,
|
||||
audioEditorAnimationId: null,
|
||||
audioEditorPlaybackTime: 0,
|
||||
audioEditorPlaybackTime: 0,
|
||||
isAudioEditorLoopEnabled: false, // <-- ADICIONADO: Estado para controlar o loop
|
||||
playbackIntervalId: null,
|
||||
currentStep: 0,
|
||||
metronomeEnabled: false,
|
||||
|
||||
Reference in new issue
Block a user