playlist funcional. inserindo loops de uma mesma faixa de pattern
Deploy / Deploy (push) Successful in 2m6s Details

This commit is contained in:
JotaChina 2025-12-25 17:10:46 -03:00
parent f9ae0d92a8
commit 37c7fa006f
2 changed files with 72 additions and 2 deletions

View File

@ -277,6 +277,47 @@ export function renderAudioEditor() {
});
}
function getLoopStepsForBasslineLane(basslineTrack) {
const patternIndex = basslineTrack.patternIndex ?? 0;
// pega os instrumentos que pertencem a esse rack (mesma lógica do pattern_ui) :contentReference[oaicite:1]{index=1}
const srcId = basslineTrack.instrumentSourceId || basslineTrack.id;
const children = (appState.pattern.tracks || []).filter(
(t) => t.type !== "bassline" && t.parentBasslineId === srcId && !t.muted
);
let maxSteps = 0;
for (const t of children) {
const p = t.patterns?.[patternIndex];
if (!p) continue;
if (Array.isArray(p.steps) && p.steps.length > 0) {
maxSteps = Math.max(maxSteps, p.steps.length);
continue;
}
// fallback pra patterns que só têm notes
if (Array.isArray(p.notes) && p.notes.length > 0) {
let endTick = 0;
for (const n of p.notes) {
const pos = Number(n.pos) || 0;
const len = Math.max(0, Number(n.len) || 0);
endTick = Math.max(endTick, pos + len);
}
const steps = Math.ceil(endTick / TICKS_PER_STEP);
maxSteps = Math.max(maxSteps, steps);
}
}
if (maxSteps <= 0) maxSteps = 16; // default
// arredonda pra múltiplo de 16 (bem “LMMS feel”)
maxSteps = Math.ceil(maxSteps / 16) * 16;
return maxSteps;
}
tracksToRender.forEach((trackData) => {
const audioTrackLane = document.createElement("div");
audioTrackLane.className = "audio-track-lane";
@ -369,6 +410,29 @@ export function renderAudioEditor() {
clipDiv.style.left = `${leftPos}px`;
clipDiv.style.width = `${widthDim}px`;
clipDiv.style.height = "100%";
// ✅ overlay de “marquinhas pretas” (loop do pattern)
const loopSteps = getLoopStepsForBasslineLane(trackData);
const loopPx = loopSteps * stepWidthPx;
if (loopPx > 0) {
clipDiv.style.position = "absolute"; // garante
const markers = document.createElement("div");
markers.style.position = "absolute";
markers.style.inset = "0";
markers.style.pointerEvents = "none";
markers.style.opacity = "0.9";
markers.style.backgroundImage = `repeating-linear-gradient(
to right,
rgba(0,0,0,0.75) 0px,
rgba(0,0,0,0.75) 2px,
transparent 2px,
transparent ${loopPx}px
)`;
// deixa o texto por cima
markers.style.zIndex = "6";
clipDiv.appendChild(markers);
}
const gridStyle = getComputedStyle(grid);
clipDiv.style.backgroundImage = gridStyle.backgroundImage;
clipDiv.style.backgroundSize = gridStyle.backgroundSize;
@ -391,6 +455,8 @@ export function renderAudioEditor() {
label.style.pointerEvents = "none";
label.style.whiteSpace = "nowrap";
label.style.overflow = "hidden";
label.style.position = "relative";
label.style.zIndex = "7";
clipDiv.appendChild(label);
clipDiv.addEventListener("dblclick", (e) => {

View File

@ -586,7 +586,11 @@ export function startSongPatternPlaybackOnTransport() {
for (const hit of activePatternHits) {
const patt = track.patterns?.[hit.patternIndex];
if (!patt) continue;
if (!patt?.steps) continue;
// 👇 ADD
const pattLen = patt.steps.length;
const stepInPattern = pattLen > 0 ? (hit.localStep % pattLen) : hit.localStep;
// ✅ 1) PLUGIN com piano roll (notes)
if (
@ -626,7 +630,7 @@ export function startSongPatternPlaybackOnTransport() {
// ✅ 2) Lógica antiga de STEP (sampler / plugin sem notes)
if (!patt.steps) continue;
if (patt.steps[hit.localStep]) {
if (patt.steps[stepInPattern]) {
if (track.type === "sampler" && track.player) {
track.player.restart = true;
try { track.player.start(time); } catch {}