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:
JotaChina committed 2025-10-12 09:23:27 -03:00
1 parent 8c391412e1
commit 602df1db6d
10 files changed
+942 -689

No files matched your search

+63 -18
View File
@@ -5,18 +5,25 @@ import {
getAudioContext,
getMainGainNode,
} from "./audio.js";
import { renderApp } from "./ui.js";
import { renderApp, renderAudioEditor } from "./ui.js";
import { getTotalSteps } from "./utils.js";
export let appState = {
tracks: [],
audioTracks: [],
activeTrackId: null,
activePatternIndex: 0, // <-- VOLTOU A SER GLOBAL
activePatternIndex: 0,
isPlaying: false,
isAudioEditorPlaying: false,
activeAudioSources: [],
audioEditorStartTime: 0,
audioEditorAnimationId: null,
audioEditorPlaybackTime: 0,
playbackIntervalId: null,
currentStep: 0,
metronomeEnabled: false,
originalXmlDoc: null,
currentBeatBasslineName: 'Novo Projeto',
masterVolume: DEFAULT_VOLUME,
masterPan: DEFAULT_PAN,
};
@@ -37,6 +44,54 @@ export async function loadAudioForTrack(track) {
return track;
}
export function addAudioTrack(samplePath) {
initializeAudioContext();
const audioContext = getAudioContext();
const mainGainNode = getMainGainNode();
const newAudioTrack = {
id: Date.now() + Math.random(),
name: samplePath.split('/').pop(),
samplePath: samplePath,
audioBuffer: null,
volume: DEFAULT_VOLUME,
pan: DEFAULT_PAN,
isMuted: false,
isSoloed: false, // <-- ADICIONADO: Começa como não-solada
gainNode: audioContext.createGain(),
pannerNode: audioContext.createStereoPanner(),
};
newAudioTrack.gainNode.connect(newAudioTrack.pannerNode);
newAudioTrack.pannerNode.connect(mainGainNode);
newAudioTrack.gainNode.gain.value = newAudioTrack.volume;
newAudioTrack.pannerNode.pan.value = newAudioTrack.pan;
appState.audioTracks.push(newAudioTrack);
loadAudioForTrack(newAudioTrack).then(() => {
renderAudioEditor();
});
}
// A função de mute agora será a de solo.
export function toggleAudioTrackSolo(trackId) {
const track = appState.audioTracks.find(t => t.id == trackId);
if (track) {
track.isSoloed = !track.isSoloed;
renderAudioEditor(); // Re-renderiza para mostrar a nova cor
}
}
// Mantemos a função de mute caso precise no futuro, mas ela não está conectada ao botão.
export function toggleAudioTrackMute(trackId) {
const track = appState.audioTracks.find(t => t.id == trackId);
if (track) {
track.isMuted = !track.isMuted;
renderAudioEditor();
}
}
export function addTrackToState() {
initializeAudioContext();
const audioContext = getAudioContext();
@@ -52,7 +107,7 @@ export function addTrackToState() {
patterns: referenceTrack
? referenceTrack.patterns.map(p => ({ name: p.name, steps: new Array(p.steps.length).fill(false), pos: p.pos }))
: [{ name: "Pattern 1", steps: new Array(totalSteps).fill(false), pos: 0 }],
// activePatternIndex foi removido daqui
activePatternIndex: 0,
volume: DEFAULT_VOLUME,
pan: DEFAULT_PAN,
gainNode: audioContext.createGain(),
@@ -64,18 +119,12 @@ export function addTrackToState() {
newTrack.pannerNode.pan.value = newTrack.pan;
appState.tracks.push(newTrack);
if (!appState.activeTrackId) {
appState.activeTrackId = newTrack.id;
}
renderApp();
}
export function removeLastTrackFromState() {
if (appState.tracks.length > 0) {
const removedTrack = appState.tracks.pop();
if (appState.activeTrackId === removedTrack.id) {
appState.activeTrackId = appState.tracks[0]?.id || null;
}
appState.tracks.pop();
renderApp();
}
}
@@ -87,18 +136,14 @@ export async function updateTrackSample(trackId, samplePath) {
track.name = samplePath.split("/").pop();
track.audioBuffer = null;
await loadAudioForTrack(track);
const trackLane = document.querySelector(`.track-lane[data-track-id="${trackId}"] .track-name`);
if (trackLane) {
trackLane.textContent = track.name;
}
renderApp();
}
}
export function toggleStepState(trackId, stepIndex) {
const track = appState.tracks.find((t) => t.id == trackId);
if (track && track.patterns && track.patterns.length > 0) {
// Usa o índice GLOBAL para saber qual pattern modificar
const activePattern = track.patterns[appState.activePatternIndex];
const activePattern = track.patterns[track.activePatternIndex];
if (activePattern && activePattern.steps.length > stepIndex) {
activePattern.steps[stepIndex] = !activePattern.steps[stepIndex];
}
@@ -106,7 +151,7 @@ export function toggleStepState(trackId, stepIndex) {
}
export function updateTrackVolume(trackId, volume) {
const track = appState.tracks.find((t) => t.id == trackId);
const track = appState.tracks.find((t) => t.id == trackId) || appState.audioTracks.find((t) => t.id == trackId);
if (track) {
const clampedVolume = Math.max(0, Math.min(1.5, volume));
track.volume = clampedVolume;
@@ -117,7 +162,7 @@ export function updateTrackVolume(trackId, volume) {
}
export function updateTrackPan(trackId, pan) {
const track = appState.tracks.find((t) => t.id == trackId);
const track = appState.tracks.find((t) => t.id == trackId) || appState.audioTracks.find((t) => t.id == trackId);
if (track) {
const clampedPan = Math.max(-1, Math.min(1, pan));
track.pan = clampedPan;