From f7c81dd1dec81d06265270b3f3ae72f8b07084dd Mon Sep 17 00:00:00 2001 From: JotaChina Date: Sat, 27 Dec 2025 14:04:43 -0300 Subject: [PATCH] editando patterns na playlist --- assets/js/creations/audio/audio_ui.js | 413 ++++---------------------- 1 file changed, 61 insertions(+), 352 deletions(-) diff --git a/assets/js/creations/audio/audio_ui.js b/assets/js/creations/audio/audio_ui.js index 42967706..e2c8864e 100755 --- a/assets/js/creations/audio/audio_ui.js +++ b/assets/js/creations/audio/audio_ui.js @@ -188,10 +188,6 @@ export function renderAudioEditor() { if (!audioEditor || !existingTrackContainer) return; - // ✅ Salva o scroll atual (senão toda edição “pula” pro início) - const prevScrollLeft = existingTrackContainer.scrollLeft || 0; - const prevScrollTop = existingTrackContainer.scrollTop || 0; - _ensureGlobalPlaylistSelectionFields(); _installPlaylistKeybindOnce(); @@ -808,47 +804,22 @@ export function renderAudioEditor() { newTrackContainer.addEventListener("mousedown", (e) => { document.getElementById("timeline-context-menu").style.display = "none"; document.getElementById("ruler-context-menu").style.display = "none"; - const clipElement = e.target.closest(".timeline-clip"); - const isBasslineClip = - !!(clipElement && clipElement.classList.contains("bassline-clip")); - - // ✅ limpa seleções ao clicar no vazio (sem mexer no RMB) + if (!clipElement && e.button !== 2) { if (appState.global.selectedClipId) { appState.global.selectedClipId = null; - } - if (appState.global.selectedPlaylistClipId) { - appState.global.selectedPlaylistClipId = null; - appState.global.selectedPlaylistPatternIndex = null; - } - - newTrackContainer - .querySelectorAll(".timeline-clip.selected") - .forEach((c) => c.classList.remove("selected")); - } - - const currentPixelsPerSecond = getPixelsPerSecond(); - const handle = e.target.closest(".clip-resize-handle"); - const patternHandle = e.target.closest(".pattern-resize-handle"); - - // ✅ se clicou num clip de áudio, deseleciona a pattern da playlist - if (clipElement && !isBasslineClip && e.button === 0) { - if (appState.global.selectedPlaylistClipId) { - appState.global.selectedPlaylistClipId = null; - appState.global.selectedPlaylistPatternIndex = null; newTrackContainer - .querySelectorAll(".bassline-clip.selected") + .querySelectorAll(".timeline-clip.selected") .forEach((c) => c.classList.remove("selected")); } } - // Slice Tool (áudio apenas) - if ( - appState.global.sliceToolActive && - clipElement && - !clipElement.classList.contains("bassline-clip") - ) { + const currentPixelsPerSecond = getPixelsPerSecond(); + const handle = e.target.closest(".clip-resize-handle"); + + // Slice Tool + if (appState.global.sliceToolActive && clipElement && !clipElement.classList.contains("bassline-clip")) { e.preventDefault(); e.stopPropagation(); const clipId = clipElement.dataset.clipId; @@ -872,193 +843,14 @@ export function renderAudioEditor() { return; } - // ========================================================= - // ✅ BASSLINE / PATTERN CLIPS (drag horizontal + resize L/R) - // ========================================================= - if (isBasslineClip && e.button === 0) { - e.preventDefault(); - - _ensureGlobalPlaylistSelectionFields(); - - const patternIndex = Number(clipElement.dataset.patternIndex ?? 0); - const plClipId = String(clipElement.dataset.plClipId || ""); - if (!plClipId) return; - - // seleção visual/estado - appState.global.selectedPlaylistClipId = plClipId; - appState.global.selectedPlaylistPatternIndex = patternIndex; - - // desmarca seleção de áudio se tiver - if (appState.global.selectedClipId) { - appState.global.selectedClipId = null; - newTrackContainer - .querySelectorAll(".timeline-clip.selected") - .forEach((c) => c.classList.remove("selected")); - } - - newTrackContainer - .querySelectorAll(".bassline-clip.selected") - .forEach((c) => c.classList.remove("selected")); - clipElement.classList.add("selected"); - - // (opcional) sync de seleção - sendActionSafe({ - type: "SELECT_PLAYLIST_PATTERN_CLIP", - patternIndex, - clipId: plClipId, - }); - - const model = _getPlaylistClipModel(patternIndex, plClipId); - if (!model) return; - - const initialMouseX = e.clientX; - const initialScrollLeft = scrollEl.scrollLeft; - - const initialPosTicks = Number(model.pos || 0); - const initialLenTicks = Math.max( - PL_MIN_LEN_TICKS, - Number(model.len || PL_MIN_LEN_TICKS) - ); - const initialEndTicks = initialPosTicks + initialLenTicks; - - const previewUpdate = (posTicks, lenTicks) => { - const leftPx = _ticksToPx(posTicks, stepWidthPx); - const widthPx = Math.max(1, _ticksToPx(lenTicks, stepWidthPx)); - clipElement.style.left = `${leftPx}px`; - clipElement.style.width = `${widthPx}px`; - // mantém grid alinhado com a timeline - clipElement.style.backgroundPosition = `-${leftPx}px 0px`; - }; - - // ---------- RESIZE ---------- - if (patternHandle) { - const handleType = patternHandle.classList.contains("left") - ? "left" - : "right"; - - clipElement.classList.add("dragging"); - - const onMouseMove = (moveEvent) => { - const deltaPx = - (moveEvent.clientX - initialMouseX) + - (scrollEl.scrollLeft - initialScrollLeft); - - const deltaTicks = _pxToTicks(deltaPx, stepWidthPx); - - let newPos = initialPosTicks; - let newLen = initialLenTicks; - - if (handleType === "right") { - let newEnd = _snapTicks(initialEndTicks + deltaTicks, PL_SNAP_TICKS); - newEnd = Math.max(initialPosTicks + PL_MIN_LEN_TICKS, newEnd); - newLen = newEnd - initialPosTicks; - } else { - newPos = _snapTicks(initialPosTicks + deltaTicks, PL_SNAP_TICKS); - newPos = Math.max(0, newPos); - newPos = Math.min(newPos, initialEndTicks - PL_MIN_LEN_TICKS); - newLen = initialEndTicks - newPos; - } - - previewUpdate(newPos, newLen); - }; - - const onMouseUp = () => { - clipElement.classList.remove("dragging"); - document.removeEventListener("mousemove", onMouseMove); - document.removeEventListener("mouseup", onMouseUp); - - // converte estado final (px -> ticks) com snap - const finalLeftPx = clipElement.offsetLeft; - const finalWidthPx = clipElement.offsetWidth; - - let finalPos = _pxToTicks(finalLeftPx, stepWidthPx); - let finalLen = _pxToTicks(finalWidthPx, stepWidthPx); - - finalPos = _snapTicks(finalPos, PL_SNAP_TICKS); - finalLen = _snapTicks(finalLen, PL_SNAP_TICKS); - - finalPos = Math.max(0, finalPos); - finalLen = Math.max(PL_MIN_LEN_TICKS, finalLen); - - _updatePlaylistClipLocal(patternIndex, plClipId, { - pos: finalPos, - len: finalLen, - }); - - sendActionSafe({ - type: "UPDATE_PLAYLIST_PATTERN_CLIP", - patternIndex, - clipId: plClipId, - pos: finalPos, - len: finalLen, - }); - - renderAudioEditor(); - restartAudioEditorIfPlaying(); - }; - - document.addEventListener("mousemove", onMouseMove); - document.addEventListener("mouseup", onMouseUp); - return; - } - - // ---------- DRAG (horizontal apenas) ---------- - clipElement.classList.add("dragging"); - - const onMouseMove = (moveEvent) => { - const deltaPx = - (moveEvent.clientX - initialMouseX) + - (scrollEl.scrollLeft - initialScrollLeft); - - let newPos = initialPosTicks + _pxToTicks(deltaPx, stepWidthPx); - newPos = _snapTicks(newPos, PL_SNAP_TICKS); - newPos = Math.max(0, newPos); - - previewUpdate(newPos, initialLenTicks); - }; - - const onMouseUp = () => { - clipElement.classList.remove("dragging"); - document.removeEventListener("mousemove", onMouseMove); - document.removeEventListener("mouseup", onMouseUp); - - const finalLeftPx = clipElement.offsetLeft; - let finalPos = _pxToTicks(finalLeftPx, stepWidthPx); - finalPos = _snapTicks(finalPos, PL_SNAP_TICKS); - finalPos = Math.max(0, finalPos); - - _updatePlaylistClipLocal(patternIndex, plClipId, { - pos: finalPos, - len: initialLenTicks, - }); - - sendActionSafe({ - type: "UPDATE_PLAYLIST_PATTERN_CLIP", - patternIndex, - clipId: plClipId, - pos: finalPos, - len: initialLenTicks, - }); - - renderAudioEditor(); - restartAudioEditorIfPlaying(); - }; - - document.addEventListener("mousemove", onMouseMove); - document.addEventListener("mouseup", onMouseUp); - return; - } - - // ========================================================= - // Resize Handle (ÁUDIO) - // ========================================================= + // Resize Handle if (handle) { e.preventDefault(); e.stopPropagation(); const clipId = clipElement.dataset.clipId; const clip = appState.audio.clips.find((c) => c.id == clipId); if (!clip || !clip.buffer) return; - + const handleType = handle.classList.contains("left") ? "left" : "right"; const initialMouseX = e.clientX; const secondsPerStep = getSecondsPerStep(); @@ -1067,10 +859,9 @@ export function renderAudioEditor() { const initialStartTime = clip.startTimeInSeconds; const initialDuration = clip.durationInSeconds; const initialOffset = clip.offset || 0; - const initialOriginalDuration = - clip.originalDuration || clip.buffer.duration; + const initialOriginalDuration = clip.originalDuration || clip.buffer.duration; const bufferStartTime = initialStartTime - initialOffset; - + const onMouseMove = (moveEvent) => { const deltaX = moveEvent.clientX - initialMouseX; if (appState.global.resizeMode === "trim") { @@ -1081,125 +872,71 @@ export function renderAudioEditor() { newEndTime = Math.max(initialStartTime + secondsPerStep, newEndTime); const maxEndTime = bufferStartTime + initialOriginalDuration; newEndTime = Math.min(newEndTime, maxEndTime); - clipElement.style.width = `${ - (newEndTime - initialStartTime) * currentPixelsPerSecond - }px`; + clipElement.style.width = `${(newEndTime - initialStartTime) * currentPixelsPerSecond}px`; } else if (handleType === "left") { let newLeftPx = initialLeftPx + deltaX; let newStartTime = newLeftPx / currentPixelsPerSecond; newStartTime = quantizeTime(newStartTime); - const minStartTime = - initialStartTime + initialDuration - secondsPerStep; + const minStartTime = initialStartTime + initialDuration - secondsPerStep; newStartTime = Math.min(newStartTime, minStartTime); newStartTime = Math.max(bufferStartTime, newStartTime); const newLeftFinalPx = newStartTime * currentPixelsPerSecond; - const newWidthFinalPx = - (initialStartTime + initialDuration - newStartTime) * - currentPixelsPerSecond; + const newWidthFinalPx = (initialStartTime + initialDuration - newStartTime) * currentPixelsPerSecond; clipElement.style.left = `${newLeftFinalPx}px`; clipElement.style.width = `${newWidthFinalPx}px`; } } else if (appState.global.resizeMode === "stretch") { - if (handleType === "right") { - let newWidthPx = initialWidthPx + deltaX; - let newDuration = newWidthPx / currentPixelsPerSecond; - let newEndTime = quantizeTime(initialStartTime + newDuration); - newEndTime = Math.max(initialStartTime + secondsPerStep, newEndTime); - clipElement.style.width = `${ - (newEndTime - initialStartTime) * currentPixelsPerSecond - }px`; - } else if (handleType === "left") { - let newLeftPx = initialLeftPx + deltaX; - let newStartTime = newLeftPx / currentPixelsPerSecond; - newStartTime = quantizeTime(newStartTime); - const minStartTime = - initialStartTime + initialDuration - secondsPerStep; - newStartTime = Math.min(newStartTime, minStartTime); - const newLeftFinalPx = newStartTime * currentPixelsPerSecond; - const newWidthFinalPx = - (initialStartTime + initialDuration - newStartTime) * - currentPixelsPerSecond; - clipElement.style.left = `${newLeftFinalPx}px`; - clipElement.style.width = `${newWidthFinalPx}px`; - } + if (handleType === "right") { + let newWidthPx = initialWidthPx + deltaX; + let newDuration = newWidthPx / currentPixelsPerSecond; + let newEndTime = quantizeTime(initialStartTime + newDuration); + newEndTime = Math.max(initialStartTime + secondsPerStep, newEndTime); + clipElement.style.width = `${(newEndTime - initialStartTime) * currentPixelsPerSecond}px`; + } else if (handleType === "left") { + let newLeftPx = initialLeftPx + deltaX; + let newStartTime = newLeftPx / currentPixelsPerSecond; + newStartTime = quantizeTime(newStartTime); + const minStartTime = initialStartTime + initialDuration - secondsPerStep; + newStartTime = Math.min(newStartTime, minStartTime); + const newLeftFinalPx = newStartTime * currentPixelsPerSecond; + const newWidthFinalPx = (initialStartTime + initialDuration - newStartTime) * currentPixelsPerSecond; + clipElement.style.left = `${newLeftFinalPx}px`; + clipElement.style.width = `${newWidthFinalPx}px`; + } } }; - const onMouseUp = () => { + const onMouseUp = (upEvent) => { document.removeEventListener("mousemove", onMouseMove); document.removeEventListener("mouseup", onMouseUp); const finalLeftPx = clipElement.offsetLeft; const finalWidthPx = clipElement.offsetWidth; const newStartTime = finalLeftPx / currentPixelsPerSecond; const newDuration = finalWidthPx / currentPixelsPerSecond; - + if (appState.global.resizeMode === "trim") { - const newOffset = newStartTime - bufferStartTime; - if (handleType === "right") { - updateAudioClipProperties(clipId, { - durationInSeconds: newDuration, - pitch: 0, - }); - sendActionSafe({ - type: "UPDATE_AUDIO_CLIP", - clipId, - props: { durationInSeconds: newDuration, pitch: 0 }, - }); - } else { - updateAudioClipProperties(clipId, { - startTimeInSeconds: newStartTime, - durationInSeconds: newDuration, - offset: newOffset, - pitch: 0, - }); - sendActionSafe({ - type: "UPDATE_AUDIO_CLIP", - clipId, - props: { - startTimeInSeconds: newStartTime, - durationInSeconds: newDuration, - offset: newOffset, - pitch: 0, - }, - }); - } + const newOffset = newStartTime - bufferStartTime; + if(handleType === "right") { + updateAudioClipProperties(clipId, { durationInSeconds: newDuration, pitch: 0 }); + sendActionSafe({ type: "UPDATE_AUDIO_CLIP", clipId, props: { durationInSeconds: newDuration, pitch: 0 } }); + } else { + updateAudioClipProperties(clipId, { startTimeInSeconds: newStartTime, durationInSeconds: newDuration, offset: newOffset, pitch: 0 }); + sendActionSafe({ type: "UPDATE_AUDIO_CLIP", clipId, props: { startTimeInSeconds: newStartTime, durationInSeconds: newDuration, offset: newOffset, pitch: 0 } }); + } } else { - const newPlaybackRate = initialOriginalDuration / newDuration; - const newPitch = 12 * Math.log2(newPlaybackRate); - if (handleType === "right") { - updateAudioClipProperties(clipId, { - durationInSeconds: newDuration, - pitch: newPitch, - offset: 0, - }); - sendActionSafe({ - type: "UPDATE_AUDIO_CLIP", - clipId, - props: { durationInSeconds: newDuration, pitch: newPitch, offset: 0 }, - }); - } else { - updateAudioClipProperties(clipId, { - startTimeInSeconds: newStartTime, - durationInSeconds: newDuration, - pitch: newPitch, - offset: 0, - }); - sendActionSafe({ - type: "UPDATE_AUDIO_CLIP", - clipId, - props: { - startTimeInSeconds: newStartTime, - durationInSeconds: newDuration, - pitch: newPitch, - offset: 0, - }, - }); - } + const newPlaybackRate = initialOriginalDuration / newDuration; + const newPitch = 12 * Math.log2(newPlaybackRate); + if(handleType === "right") { + updateAudioClipProperties(clipId, { durationInSeconds: newDuration, pitch: newPitch, offset: 0 }); + sendActionSafe({ type: "UPDATE_AUDIO_CLIP", clipId, props: { durationInSeconds: newDuration, pitch: newPitch, offset: 0 } }); + } else { + updateAudioClipProperties(clipId, { startTimeInSeconds: newStartTime, durationInSeconds: newDuration, pitch: newPitch, offset: 0 }); + sendActionSafe({ type: "UPDATE_AUDIO_CLIP", clipId, props: { startTimeInSeconds: newStartTime, durationInSeconds: newDuration, pitch: newPitch, offset: 0 } }); + } } restartAudioEditorIfPlaying(); renderAudioEditor(); }; - document.addEventListener("mousemove", onMouseMove); document.addEventListener("mouseup", onMouseUp); return; @@ -1209,9 +946,8 @@ export function renderAudioEditor() { if (clipElement && !clipElement.classList.contains("bassline-clip")) { const clipId = clipElement.dataset.clipId; - const clipModel = appState.audio.clips.find( - (c) => String(c.id) === String(clipId) - ); + // 🔑 pega o clip no estado pra ter o tempo inicial real + const clipModel = appState.audio.clips.find(c => String(c.id) === String(clipId)); const initialStartTime = Number(clipModel?.startTimeInSeconds || 0); e.preventDefault(); @@ -1225,13 +961,8 @@ export function renderAudioEditor() { const deltaX = moveEvent.clientX - initialMouseX; clipElement.style.transform = `translateX(${deltaX}px)`; - const overElement = document.elementFromPoint( - moveEvent.clientX, - moveEvent.clientY - ); - const overLane = overElement - ? overElement.closest(".audio-track-lane") - : null; + const overElement = document.elementFromPoint(moveEvent.clientX, moveEvent.clientY); + const overLane = overElement ? overElement.closest(".audio-track-lane") : null; if (overLane && overLane !== lastOverLane) { if (lastOverLane) lastOverLane.classList.remove("drag-over"); overLane.classList.add("drag-over"); @@ -1251,24 +982,15 @@ export function renderAudioEditor() { const newTrackId = finalLane.dataset.trackId; - const deltaX = - (upEvent.clientX - initialMouseX) + - (scrollEl.scrollLeft - initialScrollLeft); + // ✅ delta do mouse + delta de scroll durante o drag (se houver) + const deltaX = (upEvent.clientX - initialMouseX) + (scrollEl.scrollLeft - initialScrollLeft); - let newStartTime = - initialStartTime + deltaX / currentPixelsPerSecond; + let newStartTime = initialStartTime + (deltaX / currentPixelsPerSecond); newStartTime = Math.max(0, newStartTime); newStartTime = quantizeTime(newStartTime); - updateAudioClipProperties(clipId, { - trackId: newTrackId, - startTimeInSeconds: newStartTime, - }); - sendActionSafe({ - type: "UPDATE_AUDIO_CLIP", - clipId, - props: { trackId: newTrackId, startTimeInSeconds: newStartTime }, - }); + updateAudioClipProperties(clipId, { trackId: newTrackId, startTimeInSeconds: newStartTime }); + sendActionSafe({ type: "UPDATE_AUDIO_CLIP", clipId, props: { trackId: newTrackId, startTimeInSeconds: newStartTime } }); renderAudioEditor(); }; @@ -1277,6 +999,7 @@ export function renderAudioEditor() { return; } + // Seek na Pista const timelineContainer = e.target.closest(".timeline-container"); if (timelineContainer) { @@ -1289,7 +1012,7 @@ export function renderAudioEditor() { const newTime = absoluteX / currentPixelsPerSecond; sendAction({ type: "SET_SEEK_TIME", seekTime: newTime }); }; - handleSeek(e); + handleSeek(e); const onMouseMoveSeek = (moveEvent) => handleSeek(moveEvent); const onMouseUpSeek = () => { document.removeEventListener("mousemove", onMouseMoveSeek); @@ -1374,22 +1097,8 @@ export function renderAudioEditor() { } } }); - - // ✅ Restaura o scroll anterior após reconstruir o container - // (evita “voltar pro início” depois de mover/redimensionar/deletar) - try { - newTrackContainer.scrollLeft = prevScrollLeft; - newTrackContainer.scrollTop = prevScrollTop; - - // mantém régua alinhada (caso ela suporte scrollLeft) - const mainRuler = tracksParent.querySelector(".timeline-ruler"); - if (mainRuler) mainRuler.scrollLeft = prevScrollLeft; - } catch (err) { - // silencioso: não pode quebrar a DAW - } } - export function updateAudioEditorUI() { const playBtn = document.getElementById("audio-editor-play-btn"); if (!playBtn) return;