// js/audio/audio_ui.js
import { appState } from "../state.js";
import {
addAudioClipToTimeline,
updateAudioClipProperties,
sliceAudioClip,
removeAudioClip,
} from "./audio_state.js";
import {
seekAudioEditor,
restartAudioEditorIfPlaying,
updateTransportLoop,
} from "./audio_audio.js";
import { drawWaveform } from "../waveform.js";
import { PIXELS_PER_BAR, PIXELS_PER_STEP, ZOOM_LEVELS } from "../config.js";
import {
getPixelsPerSecond,
quantizeTime,
getBeatsPerBar,
getSecondsPerStep,
} from "../utils.js";
import { sendAction } from "../socket.js";
export function renderAudioEditor() {
const audioEditor = document.querySelector(".audio-editor");
const existingTrackContainer = document.getElementById(
"audio-track-container"
);
if (!audioEditor || !existingTrackContainer) return;
// --- CRIAÇÃO E RENDERIZAÇÃO DA RÉGUA ---
let rulerWrapper = audioEditor.querySelector(".ruler-wrapper");
if (!rulerWrapper) {
rulerWrapper = document.createElement("div");
rulerWrapper.className = "ruler-wrapper";
rulerWrapper.innerHTML = `
`;
audioEditor.insertBefore(rulerWrapper, existingTrackContainer);
}
const ruler = rulerWrapper.querySelector(".timeline-ruler");
ruler.innerHTML = "";
const pixelsPerSecond = getPixelsPerSecond();
let maxTime = appState.global.loopEndTime;
appState.audio.clips.forEach((clip) => {
const endTime =
(clip.startTimeInSeconds || 0) + (clip.durationInSeconds || 0);
if (endTime > maxTime) maxTime = endTime;
});
const containerWidth = existingTrackContainer.offsetWidth;
const contentWidth = maxTime * pixelsPerSecond;
const totalWidth = Math.max(contentWidth, containerWidth, 2000);
ruler.style.width = `${totalWidth}px`;
const zoomFactor = ZOOM_LEVELS[appState.global.zoomLevelIndex];
const beatsPerBar = getBeatsPerBar();
const stepWidthPx = PIXELS_PER_STEP * zoomFactor;
const beatWidthPx = stepWidthPx * 4;
const barWidthPx = beatWidthPx * beatsPerBar;
if (barWidthPx > 0) {
const numberOfBars = Math.ceil(totalWidth / barWidthPx);
for (let i = 1; i <= numberOfBars; i++) {
const marker = document.createElement("div");
marker.className = "ruler-marker";
marker.textContent = i;
marker.style.left = `${(i - 1) * barWidthPx}px`;
ruler.appendChild(marker);
}
}
const loopRegion = document.createElement("div");
loopRegion.id = "loop-region";
loopRegion.style.left = `${
appState.global.loopStartTime * pixelsPerSecond
}px`;
loopRegion.style.width = `${
(appState.global.loopEndTime - appState.global.loopStartTime) *
pixelsPerSecond
}px`;
loopRegion.innerHTML = ``;
loopRegion.classList.toggle("visible", appState.global.isLoopActive);
ruler.appendChild(loopRegion);
// --- LISTENER DA RÉGUA (MODIFICADO para enviar Ações de Loop/Seek) ---
const newRuler = ruler.cloneNode(true);
ruler.parentNode.replaceChild(newRuler, ruler);
newRuler.addEventListener("mousedown", (e) => {
// Esconde menus
document.getElementById("timeline-context-menu").style.display = "none";
document.getElementById("ruler-context-menu").style.display = "none";
const currentPixelsPerSecond = getPixelsPerSecond();
const loopHandle = e.target.closest(".loop-handle");
const loopRegionBody = e.target.closest("#loop-region:not(.loop-handle)");
// Drag Handle Loop
if (loopHandle) {
e.preventDefault();
e.stopPropagation();
const handleType = loopHandle.classList.contains("left")
? "left"
: "right";
const initialMouseX = e.clientX;
const initialStart = appState.global.loopStartTime;
const initialEnd = appState.global.loopEndTime;
const onMouseMove = (moveEvent) => {
const deltaX = moveEvent.clientX - initialMouseX;
const deltaTime = deltaX / currentPixelsPerSecond;
let newStart = appState.global.loopStartTime;
let newEnd = appState.global.loopEndTime;
if (handleType === "left") {
newStart = Math.max(0, initialStart + deltaTime);
newStart = Math.min(newStart, appState.global.loopEndTime - 0.1);
appState.global.loopStartTime = newStart;
} else {
newEnd = Math.max(
appState.global.loopStartTime + 0.1,
initialEnd + deltaTime
);
appState.global.loopEndTime = newEnd;
}
updateTransportLoop();
const loopRegionEl = newRuler.querySelector("#loop-region");
if (loopRegionEl) {
loopRegionEl.style.left = `${newStart * currentPixelsPerSecond}px`;
loopRegionEl.style.width = `${
(newEnd - newStart) * currentPixelsPerSecond
}px`;
}
};
const onMouseUp = () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
// =================================================================
// 👇 INÍCIO DA CORREÇÃO (Sincronia de Loop Drag Handle)
// =================================================================
sendAction({
type: "SET_LOOP_STATE",
isLoopActive: appState.global.isLoopActive,
loopStartTime: appState.global.loopStartTime,
loopEndTime: appState.global.loopEndTime,
});
// renderAudioEditor(); // Removido
// =================================================================
// 👆 FIM DA CORREÇÃO
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
return;
}
// Drag Body Loop
if (loopRegionBody) {
e.preventDefault();
e.stopPropagation();
const initialMouseX = e.clientX;
const initialStart = appState.global.loopStartTime;
const initialEnd = appState.global.loopEndTime;
const initialDuration = initialEnd - initialStart;
const onMouseMove = (moveEvent) => {
const deltaX = moveEvent.clientX - initialMouseX;
const deltaTime = deltaX / currentPixelsPerSecond;
let newStart = Math.max(0, initialStart + deltaTime);
let newEnd = newStart + initialDuration;
appState.global.loopStartTime = newStart;
appState.global.loopEndTime = newEnd;
updateTransportLoop();
const loopRegionEl = newRuler.querySelector("#loop-region");
if (loopRegionEl)
loopRegionEl.style.left = `${newStart * currentPixelsPerSecond}px`;
};
const onMouseUp = () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
// =================================================================
// 👇 INÍCIO DA CORREÇÃO (Sincronia de Loop Drag Body)
// =================================================================
sendAction({
type: "SET_LOOP_STATE",
isLoopActive: appState.global.isLoopActive,
loopStartTime: appState.global.loopStartTime,
loopEndTime: appState.global.loopEndTime,
});
// renderAudioEditor(); // Removido
// =================================================================
// 👆 FIM DA CORREÇÃO
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
return;
}
// Seek na Régua
e.preventDefault();
const handleSeek = (event) => {
const rect = newRuler.getBoundingClientRect();
const scrollLeft = newRuler.scrollLeft;
const clickX = event.clientX - rect.left;
const absoluteX = clickX + scrollLeft;
const newTime = absoluteX / currentPixelsPerSecond;
// =================================================================
// 👇 INÍCIO DA CORREÇÃO (Sincronia de Seek na Régua)
// =================================================================
sendAction({ type: "SET_SEEK_TIME", seekTime: newTime });
// seekAudioEditor(newTime); // 👈 Substituído
// =================================================================
// 👆 FIM DA CORREÇÃO
};
handleSeek(e); // Aplica no mousedown
const onMouseMoveSeek = (moveEvent) => handleSeek(moveEvent);
const onMouseUpSeek = () => {
document.removeEventListener("mousemove", onMouseMoveSeek);
document.removeEventListener("mouseup", onMouseUpSeek);
};
document.addEventListener("mousemove", onMouseMoveSeek);
document.addEventListener("mouseup", onMouseUpSeek);
});
// Menu Contexto Régua (sem alterações)
newRuler.addEventListener("contextmenu", (e) => {
e.preventDefault();
document.getElementById("timeline-context-menu").style.display = "none";
const menu = document.getElementById("ruler-context-menu");
const currentPixelsPerSecond = getPixelsPerSecond();
const rect = newRuler.getBoundingClientRect();
const scrollLeft = newRuler.scrollLeft;
const clickX = e.clientX - rect.left;
const absoluteX = clickX + scrollLeft;
const clickTime = absoluteX / currentPixelsPerSecond;
appState.global.lastRulerClickTime = clickTime;
menu.style.display = "block";
menu.style.left = `${e.clientX}px`;
menu.style.top = `${e.clientY}px`;
});
// Recriação Container Pistas (sem alterações)
const newTrackContainer = existingTrackContainer.cloneNode(false);
audioEditor.replaceChild(newTrackContainer, existingTrackContainer);
// Render Pistas (sem alterações)
appState.audio.tracks.forEach((trackData) => {
const audioTrackLane = document.createElement("div");
audioTrackLane.className = "audio-track-lane";
audioTrackLane.dataset.trackId = trackData.id;
audioTrackLane.innerHTML = `
`;
newTrackContainer.appendChild(audioTrackLane);
const timelineContainer = audioTrackLane.querySelector(
".timeline-container"
);
timelineContainer.addEventListener("dragover", (e) => {
e.preventDefault();
audioTrackLane.classList.add("drag-over");
});
timelineContainer.addEventListener("dragleave", () =>
audioTrackLane.classList.remove("drag-over")
);
timelineContainer.addEventListener("drop", (e) => {
e.preventDefault();
audioTrackLane.classList.remove("drag-over");
const filePath = e.dataTransfer.getData("text/plain");
if (!filePath) return;
const rect = timelineContainer.getBoundingClientRect();
const dropX = e.clientX - rect.left + timelineContainer.scrollLeft;
let startTimeInSeconds = dropX / pixelsPerSecond;
startTimeInSeconds = quantizeTime(startTimeInSeconds);
if (
!trackData.id ||
startTimeInSeconds == null ||
isNaN(startTimeInSeconds)
) {
console.error("Drop inválido. Ignorando.", {
id: trackData.id,
time: startTimeInSeconds,
});
return;
}
const clipId =
crypto?.randomUUID?.() ||
`clip_${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
addAudioClipToTimeline(
filePath,
trackData.id,
startTimeInSeconds,
clipId
);
try {
sendAction({
type: "ADD_AUDIO_CLIP",
filePath,
trackId: trackData.id,
startTimeInSeconds,
clipId,
name: String(filePath).split(/[\\/]/).pop(),
});
} catch (err) {
console.warn("[SYNC] Falha ao emitir ADD_AUDIO_CLIP", err);
}
});
const grid = timelineContainer.querySelector(".spectrogram-view-grid");
grid.style.setProperty("--step-width", `${stepWidthPx}px`);
grid.style.setProperty("--beat-width", `${beatWidthPx}px`);
grid.style.setProperty("--bar-width", `${barWidthPx}px`);
});
// Render Clips (sem alterações)
appState.audio.clips.forEach((clip) => {
const parentGrid = newTrackContainer.querySelector(
`.audio-track-lane[data-track-id="${clip.trackId}"] .spectrogram-view-grid`
);
if (!parentGrid) return;
const clipElement = document.createElement("div");
clipElement.className = "timeline-clip";
clipElement.dataset.clipId = clip.id;
if (clip.id === appState.global.selectedClipId)
clipElement.classList.add("selected");
if (appState.global.clipboard?.cutSourceId === clip.id)
clipElement.classList.add("cut");
clipElement.style.left = `${
(clip.startTimeInSeconds || 0) * pixelsPerSecond
}px`;
clipElement.style.width = `${
(clip.durationInSeconds || 0) * pixelsPerSecond
}px`;
let pitchStr =
clip.pitch > 0 ? `+${clip.pitch.toFixed(1)}` : `${clip.pitch.toFixed(1)}`;
if (clip.pitch === 0) pitchStr = "";
clipElement.innerHTML = ` ${clip.name} ${pitchStr} `;
parentGrid.appendChild(clipElement);
if (clip.buffer) {
const canvas = clipElement.querySelector(".waveform-canvas-clip");
const canvasWidth = (clip.durationInSeconds || 0) * pixelsPerSecond;
if (canvasWidth > 0) {
canvas.width = canvasWidth;
canvas.height = 40;
const audioBuffer = clip.buffer;
const isStretched = clip.pitch !== 0;
const sourceOffset = isStretched ? 0 : clip.offset || 0;
const sourceDuration = isStretched
? clip.originalDuration
: clip.durationInSeconds;
drawWaveform(
canvas,
audioBuffer,
"var(--accent-green)",
sourceOffset,
sourceDuration
);
}
}
clipElement.addEventListener("wheel", (e) => {
e.preventDefault();
const clipToUpdate = appState.audio.clips.find(
(c) => c.id == clipElement.dataset.clipId
);
if (!clipToUpdate) return;
const direction = e.deltaY < 0 ? 1 : -1;
let newPitch = clipToUpdate.pitch + direction;
newPitch = Math.max(-24, Math.min(24, newPitch));
updateAudioClipProperties(clipToUpdate.id, { pitch: newPitch });
try {
sendAction({
type: "UPDATE_AUDIO_CLIP",
clipId: clipToUpdate.id,
props: { pitch: newPitch },
});
} catch (err) {
console.warn("[SYNC] Falha ao emitir UPDATE_AUDIO_CLIP (wheel)", err);
}
renderAudioEditor();
restartAudioEditorIfPlaying();
});
});
// Sync Scroll (sem alterações)
newTrackContainer.addEventListener("scroll", () => {
const scrollPos = newTrackContainer.scrollLeft;
const mainRuler = document.querySelector(".timeline-ruler");
if (mainRuler && mainRuler.scrollLeft !== scrollPos) {
mainRuler.scrollLeft = scrollPos;
}
});
// Event Listener Principal (mousedown no container de pistas)
newTrackContainer.addEventListener("mousedown", (e) => {
// Esconde menus
document.getElementById("timeline-context-menu").style.display = "none";
document.getElementById("ruler-context-menu").style.display = "none";
const clipElement = e.target.closest(".timeline-clip");
// Desseleciona se clicar fora
if (!clipElement && e.button !== 2) {
if (appState.global.selectedClipId) {
appState.global.selectedClipId = null;
newTrackContainer
.querySelectorAll(".timeline-clip.selected")
.forEach((c) => c.classList.remove("selected"));
}
}
const currentPixelsPerSecond = getPixelsPerSecond();
const handle = e.target.closest(".clip-resize-handle");
// Slice Tool
if (appState.global.sliceToolActive && clipElement) {
e.preventDefault();
e.stopPropagation();
const clipId = clipElement.dataset.clipId;
const timelineContainer = clipElement.closest(".timeline-container");
const rect = timelineContainer.getBoundingClientRect();
const clickX = e.clientX - rect.left;
const absoluteX = clickX + timelineContainer.scrollLeft;
let sliceTimeInTimeline = absoluteX / currentPixelsPerSecond;
sliceTimeInTimeline = quantizeTime(sliceTimeInTimeline);
sliceAudioClip(clipId, sliceTimeInTimeline);
try {
sendAction({
type: "UPDATE_AUDIO_CLIP",
clipId,
props: { __operation: "slice", sliceTimeInTimeline },
});
} catch (err) {
console.warn("[SYNC] Falha ao emitir UPDATE_AUDIO_CLIP (slice)", err);
}
renderAudioEditor();
return;
}
// 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();
const initialLeftPx = clipElement.offsetLeft;
const initialWidthPx = clipElement.offsetWidth;
const initialStartTime = clip.startTimeInSeconds;
const initialDuration = clip.durationInSeconds;
const initialOffset = clip.offset || 0;
const initialOriginalDuration =
clip.originalDuration || clip.buffer.duration;
const bufferStartTime = initialStartTime - initialOffset;
const onMouseMove = (moveEvent) => {
const deltaX = moveEvent.clientX - initialMouseX;
// Trim Mode
if (appState.global.resizeMode === "trim") {
if (handleType === "right") {
let newWidthPx = initialWidthPx + deltaX;
let newDuration = newWidthPx / currentPixelsPerSecond;
let newEndTime = quantizeTime(initialStartTime + newDuration);
newEndTime = Math.max(
initialStartTime + secondsPerStep,
newEndTime
);
const maxEndTime = bufferStartTime + initialOriginalDuration;
newEndTime = Math.min(newEndTime, maxEndTime);
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);
newStartTime = Math.max(bufferStartTime, newStartTime);
const newLeftFinalPx = newStartTime * currentPixelsPerSecond;
const newWidthFinalPx =
(initialStartTime + initialDuration - newStartTime) *
currentPixelsPerSecond;
clipElement.style.left = `${newLeftFinalPx}px`;
clipElement.style.width = `${newWidthFinalPx}px`;
}
}
// Stretch Mode
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`;
}
}
};
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;
// Trim Mode
if (appState.global.resizeMode === "trim") {
const newOffset = newStartTime - bufferStartTime;
if (handleType === "right") {
updateAudioClipProperties(clipId, {
durationInSeconds: newDuration,
pitch: 0,
});
try {
sendAction({
type: "UPDATE_AUDIO_CLIP",
clipId,
props: { durationInSeconds: newDuration, pitch: 0 },
});
} catch (err) {
console.warn(
"[SYNC] Falha ao emitir UPDATE_AUDIO_CLIP (trim-right)",
err
);
}
} else if (handleType === "left") {
updateAudioClipProperties(clipId, {
startTimeInSeconds: newStartTime,
durationInSeconds: newDuration,
offset: newOffset,
pitch: 0,
});
try {
sendAction({
type: "UPDATE_AUDIO_CLIP",
clipId,
props: {
startTimeInSeconds: newStartTime,
durationInSeconds: newDuration,
offset: newOffset,
pitch: 0,
},
});
} catch (err) {
console.warn(
"[SYNC] Falha ao emitir UPDATE_AUDIO_CLIP (trim-left)",
err
);
}
}
}
// Stretch Mode
else if (appState.global.resizeMode === "stretch") {
const newPlaybackRate = initialOriginalDuration / newDuration;
const newPitch = 12 * Math.log2(newPlaybackRate);
if (handleType === "right") {
updateAudioClipProperties(clipId, {
durationInSeconds: newDuration,
pitch: newPitch,
offset: 0,
});
try {
sendAction({
type: "UPDATE_AUDIO_CLIP",
clipId,
props: {
durationInSeconds: newDuration,
pitch: newPitch,
offset: 0,
},
});
} catch (err) {
console.warn(
"[SYNC] Falha ao emitir UPDATE_AUDIO_CLIP (stretch-right)",
err
);
}
} else if (handleType === "left") {
updateAudioClipProperties(clipId, {
startTimeInSeconds: newStartTime,
durationInSeconds: newDuration,
pitch: newPitch,
offset: 0,
});
try {
sendAction({
type: "UPDATE_AUDIO_CLIP",
clipId,
props: {
startTimeInSeconds: newStartTime,
durationInSeconds: newDuration,
pitch: newPitch,
offset: 0,
},
});
} catch (err) {
console.warn(
"[SYNC] Falha ao emitir UPDATE_AUDIO_CLIP (stretch-left)",
err
);
}
}
}
restartAudioEditorIfPlaying();
renderAudioEditor();
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
return;
}
// Drag Clip
if (clipElement) {
const clipId = clipElement.dataset.clipId;
if (appState.global.selectedClipId !== clipId) {
appState.global.selectedClipId = clipId;
newTrackContainer
.querySelectorAll(".timeline-clip.selected")
.forEach((c) => c.classList.remove("selected"));
clipElement.classList.add("selected");
}
e.preventDefault();
const clickOffsetInClip =
e.clientX - clipElement.getBoundingClientRect().left;
clipElement.classList.add("dragging");
let lastOverLane = clipElement.closest(".audio-track-lane");
const onMouseMove = (moveEvent) => {
const deltaX = moveEvent.clientX - e.clientX;
clipElement.style.transform = `translateX(${deltaX}px)`;
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");
lastOverLane = overLane;
}
};
const onMouseUp = (upEvent) => {
clipElement.classList.remove("dragging");
if (lastOverLane) lastOverLane.classList.remove("drag-over");
clipElement.style.transform = "";
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
const finalLane = lastOverLane;
if (!finalLane) return;
const newTrackId = finalLane.dataset.trackId; // (é uma string)
const timelineContainer = finalLane.querySelector(
".timeline-container"
);
const wrapperRect = timelineContainer.getBoundingClientRect();
const newLeftPx =
upEvent.clientX -
wrapperRect.left -
clickOffsetInClip +
timelineContainer.scrollLeft;
const constrainedLeftPx = Math.max(0, newLeftPx);
let newStartTime = constrainedLeftPx / currentPixelsPerSecond;
newStartTime = quantizeTime(newStartTime);
// (Correção Bug 4 - remove Number())
updateAudioClipProperties(clipId, {
trackId: newTrackId,
startTimeInSeconds: newStartTime,
});
try {
sendAction({
type: "UPDATE_AUDIO_CLIP",
clipId,
props: { trackId: newTrackId, startTimeInSeconds: newStartTime },
});
} catch (err) {
console.warn("[SYNC] Falha ao emitir UPDATE_AUDIO_CLIP (move)", err);
}
renderAudioEditor();
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
return;
}
// Seek na Pista
const timelineContainer = e.target.closest(".timeline-container");
if (timelineContainer) {
e.preventDefault();
const handleSeek = (event) => {
const rect = timelineContainer.getBoundingClientRect();
const scrollLeft = timelineContainer.scrollLeft;
const clickX = event.clientX - rect.left;
const absoluteX = clickX + scrollLeft;
const newTime = absoluteX / currentPixelsPerSecond;
// =================================================================
// 👇 INÍCIO DA CORREÇÃO (Sincronia de Seek na Pista)
// =================================================================
sendAction({ type: "SET_SEEK_TIME", seekTime: newTime });
// seekAudioEditor(newTime); // 👈 Substituído
// =================================================================
// 👆 FIM DA CORREÇÃO
};
handleSeek(e); // Aplica no mousedown
const onMouseMoveSeek = (moveEvent) => handleSeek(moveEvent);
const onMouseUpSeek = () => {
document.removeEventListener("mousemove", onMouseMoveSeek);
document.removeEventListener("mouseup", onMouseUpSeek);
};
document.addEventListener("mousemove", onMouseMoveSeek);
document.addEventListener("mouseup", onMouseUpSeek);
}
});
// Menu Contexto Pista (sem alterações)
newTrackContainer.addEventListener("contextmenu", (e) => {
e.preventDefault();
document.getElementById("ruler-context-menu").style.display = "none";
const menu = document.getElementById("timeline-context-menu");
if (!menu) return;
const clipElement = e.target.closest(".timeline-clip");
const copyItem = document.getElementById("copy-clip");
const cutItem = document.getElementById("cut-clip");
const pasteItem = document.getElementById("paste-clip");
const deleteItem = document.getElementById("delete-clip");
const canPaste = appState.global.clipboard?.type === "audio";
pasteItem.style.display = canPaste ? "block" : "none";
if (clipElement) {
const clipId = clipElement.dataset.clipId;
if (appState.global.selectedClipId !== clipId) {
appState.global.selectedClipId = clipId;
newTrackContainer
.querySelectorAll(".timeline-clip.selected")
.forEach((c) => c.classList.remove("selected"));
clipElement.classList.add("selected");
}
copyItem.style.display = "block";
cutItem.style.display = "block";
deleteItem.style.display = "block";
menu.style.display = "block";
menu.style.left = `${e.clientX}px`;
menu.style.top = `${e.clientY}px`;
if (!deleteItem.__synced) {
deleteItem.__synced = true;
deleteItem.addEventListener("click", () => {
const id = appState.global.selectedClipId;
if (!id) return;
const ok = removeAudioClip(id);
try {
sendAction({ type: "REMOVE_AUDIO_CLIP", clipId: id });
} catch (err) {
console.warn("[SYNC] Falha ao emitir REMOVE_AUDIO_CLIP", err);
}
if (ok) renderAudioEditor();
menu.style.display = "none";
});
}
} else {
copyItem.style.display = "none";
cutItem.style.display = "none";
deleteItem.style.display = "none";
if (canPaste) {
menu.style.display = "block";
menu.style.left = `${e.clientX}px`;
menu.style.top = `${e.clientY}px`;
} else {
menu.style.display = "none";
}
}
});
}
// Funções de UI (sem alterações)
export function updateAudioEditorUI() {
const playBtn = document.getElementById("audio-editor-play-btn");
if (!playBtn) return;
if (appState.global.isAudioEditorPlaying) {
playBtn.classList.remove("fa-play");
playBtn.classList.add("fa-pause");
} else {
playBtn.classList.remove("fa-pause");
playBtn.classList.add("fa-play");
}
}
export function updatePlayheadVisual(pixels) {
document.querySelectorAll(".audio-track-lane .playhead").forEach((ph) => {
ph.style.left = `${pixels}px`;
});
}
export function resetPlayheadVisual() {
document.querySelectorAll(".audio-track-lane .playhead").forEach((ph) => {
ph.style.left = "0px";
});
}