diff --git a/assets/js/creations/pattern/pattern_audio.js b/assets/js/creations/pattern/pattern_audio.js index 792baec7..0f2a4f97 100755 --- a/assets/js/creations/pattern/pattern_audio.js +++ b/assets/js/creations/pattern/pattern_audio.js @@ -576,11 +576,32 @@ export function startSongPatternPlaybackOnTransport() { for (const hit of activePatternHits) { const patt = track.patterns?.[hit.patternIndex]; - if (!patt?.steps) continue; + if (!patt) continue; + + // comprimento do pattern em ticks (prioriza notes, depois steps) + let pattLenTicksByNotes = 0; + if (Array.isArray(patt.notes) && patt.notes.length > 0) { + 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 + pattLenTicksByNotes = Math.max(pattLenTicksByNotes, pos + len); + } + } + 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); + + // tick atual dentro do pattern (loopando) + const tickInPattern = (hit.localStep * LMMS_TICKS_PER_STEP) % pattLenTicks; + + // step index (só pra lógica de steps) + const pattLenSteps = patt.steps?.length || 0; + const stepInPattern = pattLenSteps > 0 + ? (Math.floor(tickInPattern / LMMS_TICKS_PER_STEP) % pattLenSteps) + : hit.localStep; - // 👇 ADD - const pattLen = patt.steps.length; - const stepInPattern = pattLen > 0 ? (hit.localStep % pattLen) : hit.localStep; // ✅ 1) PLUGIN com piano roll (notes) if ( @@ -589,22 +610,25 @@ export function startSongPatternPlaybackOnTransport() { Array.isArray(patt.notes) && patt.notes.length > 0 ) { - const stepStartTick = hit.localStep * LMMS_TICKS_PER_STEP; + const stepStartTick = tickInPattern; const stepEndTick = stepStartTick + LMMS_TICKS_PER_STEP; for (const n of patt.notes) { const nPos = Number(n.pos) || 0; - if (nPos < stepStartTick || nPos >= stepEndTick) continue; - const offsetTicks = nPos - stepStartTick; + const wraps = stepEndTick > pattLenTicks; + const inWindow = wraps + ? (nPos >= stepStartTick || nPos < (stepEndTick - pattLenTicks)) + : (nPos >= stepStartTick && nPos < stepEndTick); + + if (!inWindow) continue; + + 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 durSec = Math.max(0.01, ticksToSec(lenTicks, stepIntervalSec)); - - const vel = Math.max(0, Math.min(1, (Number(n.vol) || 100) / 100)); - const freq = Tone.Frequency(Number(n.key) || 0, "midi").toFrequency(); - try { track.instrument.triggerAttackRelease(freq, durSec, t2, vel); } catch { @@ -614,7 +638,7 @@ export function startSongPatternPlaybackOnTransport() { } } - continue; // 👈 importante: não cair na lógica de steps abaixo + continue; // 👈 importante: não cair na lógica abaixo } // ✅ 1b) SAMPLER com piano roll (notes) — respeita oitava/pitch via playbackRate