corrigindo play/stop do editor de patterns

This commit is contained in:
JotaChina committed 2025-12-27 10:12:23 -03:00
1 parent ba3f1befa1
commit 45ad819197
2 files changed
+189 -134

No files matched your search

+81 -57
View File
@@ -65,14 +65,17 @@ export function playMetronomeSound(isDownbeat) {
// Dispara o sample de uma track, garantindo que o player esteja roteado corretamente
export function playSample(filePath, trackId) {
initializeAudioContext();
const track = trackId
? appState.pattern.tracks.find((t) => t.id == trackId)
: null;
// Se a faixa existe e tem um player pré-carregado
if (track && track.player) {
if (track.player.loaded) {
// Ajusta volume/pan sempre que tocar (robustez a alterações em tempo real)
// Se a track existe e tem player/preload
if (track && (track.previewPlayer || track.player)) {
const playerToUse = track.previewPlayer || track.player;
if (playerToUse.loaded) {
// Atualiza volume/pan ao tocar
if (track.volumeNode) {
track.volumeNode.volume.value =
track.volume === 0 ? -Infinity : Tone.gainToDb(track.volume);
@@ -81,29 +84,30 @@ export function playSample(filePath, trackId) {
track.pannerNode.pan.value = track.pan ?? 0;
}
// Garante conexão: player -> volumeNode
try {
track.player.disconnect();
} catch {}
if (track.volumeNode) {
track.player.connect(track.volumeNode);
}
// roteia playerToUse -> volumeNode
try { playerToUse.disconnect(); } catch {}
if (track.volumeNode) playerToUse.connect(track.volumeNode);
// Dispara imediatamente
track.player.start(Tone.now());
// Dispara (preview não interfere no player da playlist)
try {
playerToUse.start(Tone.now());
} catch (e) {
console.warn("Falha ao tocar preview/sample:", track.name, e);
}
} else {
console.warn(
`Player da trilha "${track.name}" ainda não carregado — pulando este tick.`
);
console.warn(`Player da trilha "${track.name}" ainda não carregado — pulando.`);
}
return;
}
// Fallback para preview de sample sem trackId
else if (!trackId && filePath) {
// Fallback: preview sem trackId
if (!trackId && filePath) {
const previewPlayer = new Tone.Player(filePath).toDestination();
previewPlayer.autostart = true;
}
}
function playSamplerNoteAtTime(track, midi, time, durationSec) {
if (!track?.buffer || !track.volumeNode) return;
@@ -179,12 +183,18 @@ export function stopPlayback(rewind = true) {
Tone.Transport.cancel();
stopScheduledPianoRoll();
// ✅ Pattern Editor: para apenas o preview (não mexe no track.player da playlist)
appState.pattern.tracks.forEach((track) => {
try { track.previewPlayer?.stop(); } catch {}
});
if (rewind) {
currentStep = 0;
updateStepHighlight(currentStep);
}
}
export function rewindPlayback() {
const lastStep =
appState.global.currentStep > 0
@@ -539,20 +549,17 @@ export function startSongPatternPlaybackOnTransport() {
if (songPatternScheduleId !== null) return;
songPatternScheduleId = Tone.Transport.scheduleRepeat((time) => {
// bpm atual
const bpm = parseInt(document.getElementById("bpm-input")?.value, 10) || 120;
const stepIntervalSec = 60 / (bpm * 4);
// step absoluto do song (considera seek do Transport)
const transportSec = Tone.Transport.getSecondsAtTime
? Tone.Transport.getSecondsAtTime(time)
: Tone.Transport.seconds;
const songStep = Math.floor(transportSec / stepIntervalSec + 1e-6);
const songTick = songStep * LMMS_TICKS_PER_STEP;
// quais patterns (colunas) estão ativas neste tick?
// Patterns ativas neste tick (pelas basslines/playlist clips)
const basslineTracks = appState.pattern.tracks.filter(
(t) => t.type === "bassline" && !t.isMuted
);
@@ -569,7 +576,7 @@ export function startSongPatternPlaybackOnTransport() {
if (activePatternHits.length === 0) return;
// dispara instrumentos reais (samplers/plugins)
// Dispara tracks reais (samplers/plugins)
for (const track of appState.pattern.tracks) {
if (track.type === "bassline") continue;
if (track.muted) continue;
@@ -584,24 +591,29 @@ export function startSongPatternPlaybackOnTransport() {
for (const n of patt.notes) {
const pos = Number(n.pos) || 0;
const rawLen = Number(n.len) || 0;
const len = Math.max(rawLen, LMMS_TICKS_PER_STEP); // mínimo 1 step
const len = rawLen < 0 ? LMMS_TICKS_PER_STEP : Math.max(rawLen, LMMS_TICKS_PER_STEP);
pattLenTicksByNotes = Math.max(pattLenTicksByNotes, pos + len);
}
}
const pattLenTicksBySteps = (patt.steps?.length || 0) * LMMS_TICKS_PER_STEP;
const pattLenTicksBySteps =
(patt.steps?.length || 0) * LMMS_TICKS_PER_STEP;
// garante pelo menos 1 step
const pattLenTicks = Math.max(pattLenTicksByNotes, pattLenTicksBySteps, LMMS_TICKS_PER_STEP);
const pattLenTicks = Math.max(
pattLenTicksByNotes,
pattLenTicksBySteps,
LMMS_TICKS_PER_STEP
);
// tick atual dentro do pattern (loopando)
const tickInPattern = (hit.localStep * LMMS_TICKS_PER_STEP) % pattLenTicks;
// tick atual dentro do pattern (loop interno ao esticar clip)
const tickInPattern =
(hit.localStep * LMMS_TICKS_PER_STEP) % pattLenTicks;
// step index (só pra lógica de steps)
// step index (para patterns de steps)
const pattLenSteps = patt.steps?.length || 0;
const stepInPattern = pattLenSteps > 0
? (Math.floor(tickInPattern / LMMS_TICKS_PER_STEP) % pattLenSteps)
: hit.localStep;
const stepInPattern =
pattLenSteps > 0
? (Math.floor(tickInPattern / LMMS_TICKS_PER_STEP) % pattLenSteps)
: hit.localStep;
// ✅ 1) PLUGIN com piano roll (notes)
if (
@@ -623,39 +635,44 @@ export function startSongPatternPlaybackOnTransport() {
if (!inWindow) continue;
const offsetTicks = wraps && nPos < stepStartTick
? (pattLenTicks - stepStartTick) + nPos
: nPos - stepStartTick;
const offsetTicks =
wraps && nPos < stepStartTick
? (pattLenTicks - stepStartTick) + nPos
: nPos - stepStartTick;
const t2 = time + ticksToSec(offsetTicks, stepIntervalSec);
const rawLen = Number(n.len) || 0;
const lenTicks = rawLen < 0 ? LMMS_TICKS_PER_STEP : Math.max(rawLen, LMMS_TICKS_PER_STEP);
const durSec = Math.max(0.01, ticksToSec(lenTicks, stepIntervalSec));
const vel = (Number(n.vol) || 100) / 100;
const midi = Number(n.key) || 0;
const freq = Tone.Frequency(midi, "midi").toFrequency();
try {
track.instrument.triggerAttackRelease(freq, durSec, t2, vel);
} catch {
try {
track.instrument.triggerAttackRelease(noteName, durSec, t2);
} catch {}
} catch (e) {
console.warn("[Playlist] Falha ao tocar plugin note:", track.name, e);
}
}
continue; // 👈 importante: não cair na lógica abaixo
continue; // não cai na lógica de steps
}
// ✅ 1b) SAMPLER com piano roll (notes) — loop interno ao esticar o clip
// ✅ 1b) SAMPLER com piano roll (notes)
if (
track.type === "sampler" &&
track.buffer &&
Array.isArray(patt.notes) &&
patt.notes.length > 0
) {
// 👇 chave do loop interno: usa tickInPattern (já calculado acima)
const stepStartTick = tickInPattern;
const stepEndTick = stepStartTick + LMMS_TICKS_PER_STEP;
for (const n of patt.notes) {
const nPos = Number(n.pos) || 0;
// janela do step, tratando “wrap” no fim do pattern
const wraps = stepEndTick > pattLenTicks;
const inWindow = wraps
? (nPos >= stepStartTick || nPos < (stepEndTick - pattLenTicks))
@@ -663,38 +680,45 @@ export function startSongPatternPlaybackOnTransport() {
if (!inWindow) continue;
const offsetTicks = wraps && nPos < stepStartTick
? (pattLenTicks - stepStartTick) + nPos
: nPos - stepStartTick;
const offsetTicks =
wraps && nPos < stepStartTick
? (pattLenTicks - stepStartTick) + nPos
: nPos - stepStartTick;
const t2 = time + ticksToSec(offsetTicks, stepIntervalSec);
const lenTicks = Math.max(1, Number(n.len) || LMMS_TICKS_PER_STEP);
const rawLen = Number(n.len) || 0;
const lenTicks = rawLen < 0 ? LMMS_TICKS_PER_STEP : Math.max(rawLen, LMMS_TICKS_PER_STEP);
const durSec = Math.max(0.01, ticksToSec(lenTicks, stepIntervalSec));
playSamplerNoteAtTime(track, Number(n.key) || 0, t2, durSec);
}
continue; // mantém: não cair na lógica de steps
continue; // não cai na lógica de steps
}
// ✅ 2) Lógica de STEP (sampler / plugin sem notes)
// ✅ 2) STEP (sampler/plugin sem notes)
if (!patt.steps) continue;
if (patt.steps[stepInPattern]) {
if (track.type === "sampler" && track.player) {
if (track.type === "sampler" && track.player) {
try {
try {
// ✅ retrigger LMMS-like: não “some” quando sample é longo
if (typeof track.player.restart === "function") {
track.player.restart(time);
} else {
if (track.player.state === "started") track.player.stop(time);
track.player.start(time);
} catch {}
}
} catch (e) {
console.warn("[Playlist] Falha ao retrigger sample:", track.name, e);
}
} else if (track.type === "plugin" && track.instrument) {
// plugin sem piano roll
try { track.instrument.triggerAttackRelease("C5", "16n", time); } catch {}
try {
track.instrument.triggerAttackRelease("C5", "16n", time);
} catch {}
}
}
}
}
}, "16n");