diff --git a/assets/css/creator.css b/assets/css/creator.css index 18185720..abeaa6cf 100755 --- a/assets/css/creator.css +++ b/assets/css/creator.css @@ -318,11 +318,21 @@ body.sidebar-hidden .sample-browser { width: 0; min-width: 0; border-right: none } /* CORREÇÃO CSS: Adicionado para garantir layout correto */ .audio-tracks-wrapper { - flex: 1; - display: flex; - flex-direction: column; - position: relative; - overflow: hidden; + flex: 1; + display: flex; + flex-direction: column; + position: relative; + overflow: hidden; + position: relative; + overflow-x: auto; /* barra horizontal */ + overflow-y: auto; /* se quiser rolar vertical com várias tracks */ +} + +/* Importantíssimo: esses caras precisam poder ficar mais largos que a tela */ +#audio-timeline-ruler, +#audio-track-container{ + position: relative; + min-width: 100%; /* no mínimo ocupa a viewport */ } #audio-track-container { flex-grow: 1; overflow: auto; } diff --git a/assets/js/creations/audio/audio_ui.js b/assets/js/creations/audio/audio_ui.js index 3b50375b..e6d4808c 100755 --- a/assets/js/creations/audio/audio_ui.js +++ b/assets/js/creations/audio/audio_ui.js @@ -57,19 +57,47 @@ export function renderAudioEditor() { const pixelsPerSecond = getPixelsPerSecond(); - let maxTime = appState.global.loopEndTime; - appState.audio.clips.forEach((clip) => { + let maxTime = appState.global.loopEndTime || 0; + + // áudio (segundos) + (appState.audio.clips || []).forEach((clip) => { const endTime = (clip.startTimeInSeconds || 0) + (clip.durationInSeconds || 0); if (endTime > maxTime) maxTime = endTime; }); - const containerWidth = existingTrackContainer.offsetWidth; + // basslines (ticks -> steps -> segundos) + const TICKS_PER_STEP = 12; // você já usa 12 na renderização do bassline + const secondsPerStep = getSecondsPerStep(); + + if (appState.pattern?.tracks) { + appState.pattern.tracks.forEach((t) => { + if (t.type !== "bassline" || !Array.isArray(t.playlist_clips)) return; + + t.playlist_clips.forEach((c) => { + const endTicks = (c.pos || 0) + (c.len || 0); + const endSteps = endTicks / TICKS_PER_STEP; + const endTimeSec = endSteps * secondsPerStep; + if (endTimeSec > maxTime) maxTime = endTimeSec; + }); + }); + } + + const containerWidth = + tracksParent.clientWidth || existingTrackContainer.offsetWidth; + const contentWidth = maxTime * pixelsPerSecond; - const totalWidth = Math.max(contentWidth, containerWidth, 2000); + + // não encolhe se já tiver sido expandido antes + const previousWidth = appState.audio.timelineWidthPx || 0; + + const totalWidth = Math.max(contentWidth, containerWidth, previousWidth, 2000); + + appState.audio.timelineWidthPx = totalWidth; ruler.style.width = `${totalWidth}px`; + const zoomFactor = ZOOM_LEVELS[appState.global.zoomLevelIndex]; const beatsPerBar = getBeatsPerBar(); const stepWidthPx = PIXELS_PER_STEP * zoomFactor; @@ -461,14 +489,61 @@ export function renderAudioEditor() { }); // Sync Scroll + function appendRulerMarkers(rulerEl, oldWidth, newWidth, barWidthPx) { + if (!rulerEl || barWidthPx <= 0) return; + + const loopEl = rulerEl.querySelector("#loop-region"); + const startBar = Math.floor(oldWidth / barWidthPx) + 1; + const endBar = Math.ceil(newWidth / barWidthPx); + + for (let i = startBar; i <= endBar; i++) { + const marker = document.createElement("div"); + marker.className = "ruler-marker"; + marker.textContent = i; + marker.style.left = `${(i - 1) * barWidthPx}px`; + // mantém loop por cima/ordem estável + rulerEl.insertBefore(marker, loopEl || null); + } + } + + function applyTimelineWidth(widthPx) { + appState.audio.timelineWidthPx = widthPx; + + const r = tracksParent.querySelector(".timeline-ruler"); + if (r) r.style.width = `${widthPx}px`; + + tracksParent + .querySelectorAll(".spectrogram-view-grid") + .forEach((g) => (g.style.width = `${widthPx}px`)); + } + newTrackContainer.addEventListener("scroll", () => { const scrollPos = newTrackContainer.scrollLeft; - const mainRuler = document.querySelector(".timeline-ruler"); + + // sync ruler + const mainRuler = tracksParent.querySelector(".timeline-ruler"); if (mainRuler && mainRuler.scrollLeft !== scrollPos) { mainRuler.scrollLeft = scrollPos; } + + // expansão "infinita" + const threshold = 300; + const rightEdge = scrollPos + newTrackContainer.clientWidth; + const currentWidth = appState.audio.timelineWidthPx || totalWidth; + + if (rightEdge > currentWidth - threshold) { + const newWidth = Math.ceil(currentWidth * 1.5); + + // aplica widths + applyTimelineWidth(newWidth); + + // adiciona mais marcadores na régua (sem re-render geral) + const rulerEl = tracksParent.querySelector(".timeline-ruler"); + appendRulerMarkers(rulerEl, currentWidth, newWidth, barWidthPx); + } }); + // Event Listener Principal (mousedown) newTrackContainer.addEventListener("mousedown", (e) => { document.getElementById("timeline-context-menu").style.display = "none";