samples de áudio não reiniciavam ao fim do loop
Deploy / Deploy (push) Successful in 2m14s
Details
Deploy / Deploy (push) Successful in 2m14s
Details
This commit is contained in:
parent
7a3df9f15c
commit
d23c3aee4d
|
|
@ -164,46 +164,36 @@ function _schedulerTick() {
|
|||
const now = audioCtx.currentTime;
|
||||
const logicalTime =
|
||||
now - startTime + (appState.audio.audioEditorSeekTime || 0);
|
||||
|
||||
const scheduleWindowStartSec = logicalTime;
|
||||
const scheduleWindowEndSec = logicalTime + SCHEDULE_AHEAD_TIME_SEC;
|
||||
|
||||
for (const clip of appState.audio.clips) {
|
||||
const clipRuntime = runtimeClipState.get(clip.id) || { isScheduled: false };
|
||||
if (clipRuntime.isScheduled) continue;
|
||||
if (!clip.buffer) continue;
|
||||
if (!clip?.buffer) continue;
|
||||
|
||||
const clipStartTimeSec = clip.startTimeInSeconds;
|
||||
const clipDurationSec = clip.durationInSeconds;
|
||||
if (
|
||||
typeof clipStartTimeSec === "undefined" ||
|
||||
typeof clipDurationSec === "undefined"
|
||||
)
|
||||
continue;
|
||||
const clipDurationSec =
|
||||
clip.durationInSeconds ?? clip.buffer?.duration;
|
||||
|
||||
let occurrenceStartTimeSec = clipStartTimeSec;
|
||||
if (typeof clipStartTimeSec === "undefined") continue;
|
||||
if (typeof clipDurationSec === "undefined") continue;
|
||||
|
||||
// ✅ Em modo loop: NÃO “trazer” starts de antes do loopStart pra dentro do loop.
|
||||
// Esses casos são tratados por overlap (clip atravessando loopStart).
|
||||
if (isLoopActive) {
|
||||
const loopDuration = loopEndTimeSec - loopStartTimeSec;
|
||||
if (loopDuration <= 0) continue;
|
||||
if (
|
||||
occurrenceStartTimeSec < loopStartTimeSec &&
|
||||
logicalTime >= loopStartTimeSec
|
||||
) {
|
||||
const offsetFromLoopStart =
|
||||
(occurrenceStartTimeSec - loopStartTimeSec) % loopDuration;
|
||||
occurrenceStartTimeSec =
|
||||
loopStartTimeSec +
|
||||
(offsetFromLoopStart < 0
|
||||
? offsetFromLoopStart + loopDuration
|
||||
: offsetFromLoopStart);
|
||||
}
|
||||
if (occurrenceStartTimeSec < logicalTime) {
|
||||
const loopsMissed =
|
||||
Math.floor((logicalTime - occurrenceStartTimeSec) / loopDuration) + 1;
|
||||
occurrenceStartTimeSec += loopsMissed * loopDuration;
|
||||
|
||||
// start fora da janela do loop -> não agenda (senão toca errado e pode “matar” o resto)
|
||||
if (clipStartTimeSec < loopStartTimeSec || clipStartTimeSec >= loopEndTimeSec) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const occurrenceStartTimeSec = clipStartTimeSec;
|
||||
|
||||
if (
|
||||
occurrenceStartTimeSec >= scheduleWindowStartSec &&
|
||||
occurrenceStartTimeSec < scheduleWindowEndSec
|
||||
|
|
@ -211,13 +201,65 @@ function _schedulerTick() {
|
|||
const absolutePlayTime =
|
||||
startTime +
|
||||
(occurrenceStartTimeSec - (appState.audio.audioEditorSeekTime || 0));
|
||||
|
||||
_scheduleClip(clip, absolutePlayTime, clipDurationSec);
|
||||
|
||||
clipRuntime.isScheduled = true;
|
||||
runtimeClipState.set(clip.id, clipRuntime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ Se a agulha está no meio de um clip (clip começou antes e ainda não acabou),
|
||||
* precisamos iniciar o Player “agora”, com offset adequado.
|
||||
* Isso resolve: seek no meio + reinício do loop + play sem stop em certos casos.
|
||||
*/
|
||||
function _scheduleOverlappingClipsAtTime(playheadSec) {
|
||||
if (!audioCtx) return;
|
||||
|
||||
const t = Number(playheadSec);
|
||||
if (!isFinite(t) || t < 0) return;
|
||||
|
||||
for (const clip of appState.audio.clips) {
|
||||
if (!clip?.buffer) continue;
|
||||
|
||||
const s = Number(clip.startTimeInSeconds);
|
||||
if (!isFinite(s)) continue;
|
||||
|
||||
const d =
|
||||
Number(clip.durationInSeconds) ||
|
||||
clip.buffer?.duration ||
|
||||
0;
|
||||
|
||||
if (!(d > 0)) continue;
|
||||
|
||||
const e = s + d;
|
||||
|
||||
// clip já começou e ainda estaria tocando nesse playhead
|
||||
if (!(s < t && e > t)) continue;
|
||||
|
||||
// já foi agendado nessa “volta”?
|
||||
const clipRuntime = runtimeClipState.get(clip.id) || { isScheduled: false };
|
||||
if (clipRuntime.isScheduled) continue;
|
||||
|
||||
const baseOffset = clip.offsetInSeconds ?? clip.offset ?? 0;
|
||||
|
||||
// quanto “dentro” do clip estamos
|
||||
const delta = t - s;
|
||||
|
||||
const offset = Math.max(0, baseOffset + delta);
|
||||
const remaining = Math.max(0, e - t);
|
||||
|
||||
// agenda para tocar imediatamente (em termos de AudioContext),
|
||||
// mas sincronizado ao Transport via _scheduleClip()
|
||||
_scheduleClip(clip, audioCtx.currentTime, remaining, offset);
|
||||
|
||||
clipRuntime.isScheduled = true;
|
||||
runtimeClipState.set(clip.id, clipRuntime);
|
||||
}
|
||||
}
|
||||
|
||||
function _scheduleOverlappingClipsAtLoopStart(loopStartSec, loopEndSec) {
|
||||
const loopLen = loopEndSec - loopStartSec;
|
||||
if (loopLen <= 0) return;
|
||||
|
|
@ -270,17 +312,29 @@ function _animationLoop() {
|
|||
if (isLoopActive) {
|
||||
if (newLogicalTime >= loopEndTimeSec) {
|
||||
const loopDuration = loopEndTimeSec - loopStartTimeSec;
|
||||
if (loopDuration > 0) {
|
||||
newLogicalTime =
|
||||
loopStartTimeSec + ((newLogicalTime - loopStartTimeSec) % loopDuration);
|
||||
loopStartTimeSec +
|
||||
((newLogicalTime - loopStartTimeSec) % loopDuration);
|
||||
} else {
|
||||
newLogicalTime = loopStartTimeSec;
|
||||
}
|
||||
|
||||
// 1) reseta relógio lógico
|
||||
// realinha relógio interno
|
||||
startTime = now;
|
||||
appState.audio.audioEditorSeekTime = newLogicalTime;
|
||||
|
||||
// 2) mantém Transport alinhado
|
||||
try { Tone.Transport.seconds = newLogicalTime; } catch {}
|
||||
// ✅ força o Transport “pular” junto na virada do loop
|
||||
try {
|
||||
// (desativa loop do Transport aqui pra não brigar com a sua lógica de loop da playlist)
|
||||
Tone.Transport.loop = false;
|
||||
} catch {}
|
||||
|
||||
// 3) limpa runtime e mata players antigos
|
||||
try {
|
||||
Tone.Transport.seconds = newLogicalTime;
|
||||
} catch {}
|
||||
|
||||
// ✅ limpa players/estado pra permitir reagendamento limpo
|
||||
runtimeClipState.clear();
|
||||
scheduledNodes.forEach(({ player }) => {
|
||||
try { player.unsync(); } catch {}
|
||||
|
|
@ -289,22 +343,23 @@ function _animationLoop() {
|
|||
});
|
||||
scheduledNodes.clear();
|
||||
|
||||
// 4) reinicia patterns (seu comportamento atual)
|
||||
// ✅ reinicia patterns do song (seu scheduler da playlist)
|
||||
try {
|
||||
stopSongPatternPlaybackOnTransport();
|
||||
startSongPatternPlaybackOnTransport();
|
||||
} catch {}
|
||||
|
||||
// ✅ 5) reativa imediatamente os clips que atravessam o loopStart (DAW-like)
|
||||
_scheduleOverlappingClipsAtLoopStart(loopStartTimeSec, loopEndTimeSec);
|
||||
// ✅ IMPORTANTÍSSIMO: recomeça clips que atravessam o loopStart
|
||||
_scheduleOverlappingClipsAtTime(newLogicalTime);
|
||||
|
||||
// ✅ 6) agenda imediatamente o que estiver na janela (não espera 25ms)
|
||||
// e já agenda os próximos inícios sem esperar o próximo interval tick
|
||||
_schedulerTick();
|
||||
}
|
||||
}
|
||||
|
||||
appState.audio.audioEditorLogicalTime = newLogicalTime;
|
||||
|
||||
// fim do song sem loop
|
||||
if (!isLoopActive) {
|
||||
let maxTime = 0;
|
||||
appState.audio.clips.forEach((clip) => {
|
||||
|
|
@ -313,8 +368,9 @@ function _animationLoop() {
|
|||
const endTime = clipStartTime + clipDuration;
|
||||
if (endTime > maxTime) maxTime = endTime;
|
||||
});
|
||||
|
||||
if (maxTime > 0 && appState.audio.audioEditorLogicalTime >= maxTime) {
|
||||
stopAudioEditorPlayback(true); // Rebobina no fim
|
||||
stopAudioEditorPlayback(true);
|
||||
resetPlayheadVisual();
|
||||
return;
|
||||
}
|
||||
|
|
@ -323,10 +379,12 @@ function _animationLoop() {
|
|||
const pixelsPerSecond = getPixelsPerSecond();
|
||||
const newPositionPx = appState.audio.audioEditorLogicalTime * pixelsPerSecond;
|
||||
updatePlayheadVisual(newPositionPx);
|
||||
|
||||
animationFrameId = requestAnimationFrame(_animationLoop);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- API Pública ---
|
||||
|
||||
export function updateTransportLoop() {
|
||||
|
|
@ -396,6 +454,8 @@ export async function startAudioEditorPlayback(seekTime) {
|
|||
const bpm = parseFloat(document.getElementById("bpm-input")?.value) || 120;
|
||||
Tone.Transport.bpm.value = bpm;
|
||||
Tone.Transport.start();
|
||||
// ✅ se começou no meio de algum clip, inicia com offset correto
|
||||
_scheduleOverlappingClipsAtTime(timeToStart);
|
||||
} catch {}
|
||||
|
||||
// mantém seu scheduler/animador
|
||||
|
|
|
|||
Loading…
Reference in New Issue