corrigindo play/stop do editor de patterns
This commit is contained in:
1 parent
ba3f1befa1
commit
45ad819197
2 files changed
+189
-134
No files matched your search
@@ -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");
|
||||
|
||||
Reference in new issue
Block a user