// js/ui.js import { appState, toggleStepState, updateTrackSample, updateTrackVolume, updateTrackPan, } from "./state.js"; import { playSample } from "./audio.js"; import { getTotalSteps } from "./utils.js"; import { loadProjectFromServer } from "./file.js"; // Variável para armazenar o mapa de samples (nome do arquivo -> caminho completo) let samplePathMap = {}; // Função para exportar o mapa de samples, que estava faltando export function getSamplePathMap() { return samplePathMap; } // Função recursiva para construir o mapa de samples a partir do manifest function buildSamplePathMap(tree, currentPath) { for (const key in tree) { if (key === "_isFile") continue; // Ignora a propriedade de metadados const node = tree[key]; const newPath = `${currentPath}/${key}`; if (node._isFile) { // Se for um arquivo, adiciona ao mapa samplePathMap[key] = newPath; } else { // Se for um diretório, continua a busca recursivamente buildSamplePathMap(node, newPath); } } } // RENDERIZAÇÃO PRINCIPAL export function renderApp() { const trackContainer = document.getElementById("track-container"); trackContainer.innerHTML = ""; appState.tracks.forEach((trackData) => { const trackLane = document.createElement("div"); trackLane.className = "track-lane"; trackLane.dataset.trackId = trackData.id; trackLane.innerHTML = `
${trackData.name}
VOL
PAN
`; trackLane.addEventListener("dragover", (e) => { e.preventDefault(); trackLane.classList.add("drag-over"); }); trackLane.addEventListener("dragleave", () => trackLane.classList.remove("drag-over") ); trackLane.addEventListener("drop", (e) => { e.preventDefault(); trackLane.classList.remove("drag-over"); const filePath = e.dataTransfer.getData("text/plain"); if (filePath) { updateTrackSample(trackData.id, filePath); } }); trackContainer.appendChild(trackLane); const volumeKnob = trackLane.querySelector(".knob[data-control='volume']"); addKnobInteraction(volumeKnob); updateKnobVisual(volumeKnob, "volume"); const panKnob = trackLane.querySelector(".knob[data-control='pan']"); addKnobInteraction(panKnob); updateKnobVisual(panKnob, "pan"); }); redrawSequencer(); } export function redrawSequencer() { const beatsPerBar = parseInt(document.getElementById("compasso-a-input").value, 10) || 4; const noteValue = parseInt(document.getElementById("compasso-b-input").value, 10) || 4; const subdivisions = Math.round(16 / noteValue); const stepsPerBar = beatsPerBar * subdivisions; const totalSteps = getTotalSteps(); document.querySelectorAll(".step-sequencer-wrapper").forEach((wrapper) => { let sequencerContainer = wrapper.querySelector(".step-sequencer"); if (!sequencerContainer) { sequencerContainer = document.createElement("div"); sequencerContainer.className = "step-sequencer"; wrapper.appendChild(sequencerContainer); } const parentTrackElement = wrapper.closest(".track-lane"); const trackId = parentTrackElement.dataset.trackId; const trackData = appState.tracks.find((t) => t.id == trackId); if (trackData && trackData.steps.length !== totalSteps) { const newStepsState = new Array(totalSteps).fill(false); for (let i = 0; i < Math.min(trackData.steps.length, totalSteps); i++) { newStepsState[i] = trackData.steps[i]; } trackData.steps = newStepsState; } sequencerContainer.innerHTML = ""; for (let i = 0; i < totalSteps; i++) { const stepWrapper = document.createElement("div"); stepWrapper.className = "step-wrapper"; const stepElement = document.createElement("div"); stepElement.className = "step"; if (trackData && trackData.steps[i] === true) { stepElement.classList.add("active"); } stepElement.addEventListener("click", () => { toggleStepState(trackData.id, i); stepElement.classList.toggle("active"); if (trackData && trackData.samplePath) { playSample(trackData.samplePath, trackData.id); } }); const groupIndex = Math.floor(i / beatsPerBar); if (groupIndex % 2 === 0) { stepElement.classList.add("step-dark"); } if (i > 0 && i % stepsPerBar === 0) { const marker = document.createElement("div"); marker.className = "step-marker"; marker.textContent = Math.floor(i / stepsPerBar) + 1; stepWrapper.appendChild(marker); } stepWrapper.appendChild(stepElement); sequencerContainer.appendChild(stepWrapper); } }); } function addKnobInteraction(knobElement) { const controlType = knobElement.dataset.control; knobElement.addEventListener("mousedown", (e) => { if (e.button === 1) { e.preventDefault(); const trackId = knobElement.dataset.trackId; const defaultValue = controlType === "volume" ? 0.8 : 0.0; if (controlType === "volume") { updateTrackVolume(trackId, defaultValue); } else { updateTrackPan(trackId, defaultValue); } updateKnobVisual(knobElement, controlType); } }); knobElement.addEventListener("mousedown", (e) => { if (e.button !== 0) return; e.preventDefault(); const trackId = knobElement.dataset.trackId; const track = appState.tracks.find((t) => t.id == trackId); if (!track) return; const startY = e.clientY; const startValue = controlType === "volume" ? track.volume : track.pan; document.body.classList.add("knob-dragging"); function onMouseMove(moveEvent) { const deltaY = startY - moveEvent.clientY; const sensitivity = controlType === "volume" ? 150 : 200; const newValue = startValue + deltaY / sensitivity; if (controlType === "volume") { updateTrackVolume(trackId, newValue); } else { updateTrackPan(trackId, newValue); } updateKnobVisual(knobElement, controlType); } function onMouseUp() { document.body.classList.remove("knob-dragging"); document.removeEventListener("mousemove", onMouseMove); document.removeEventListener("mouseup", onMouseUp); } document.addEventListener("mousemove", onMouseMove); document.addEventListener("mouseup", onMouseUp); }); knobElement.addEventListener("wheel", (e) => { e.preventDefault(); const trackId = knobElement.dataset.trackId; const track = appState.tracks.find((t) => t.id == trackId); if (!track) return; const step = 0.05; const direction = e.deltaY < 0 ? 1 : -1; if (controlType === "volume") { const newValue = track.volume + direction * step; updateTrackVolume(trackId, newValue); } else { const newValue = track.pan + direction * step; updateTrackPan(trackId, newValue); } updateKnobVisual(knobElement, controlType); }); } function updateKnobVisual(knobElement, controlType) { const trackId = knobElement.dataset.trackId; const track = appState.tracks.find((t) => t.id == trackId); if (!track) return; const indicator = knobElement.querySelector(".knob-indicator"); if (!indicator) return; const minAngle = -135; const maxAngle = 135; let percentage = 0.5; let title = ""; if (controlType === "volume") { const value = track.volume; const clampedValue = Math.max(0, Math.min(1.5, value)); percentage = clampedValue / 1.5; title = `Volume: ${Math.round(clampedValue * 100)}%`; } else { const value = track.pan; const clampedValue = Math.max(-1, Math.min(1, value)); percentage = (clampedValue + 1) / 2; const panDisplay = Math.round(clampedValue * 100); title = `Pan: ${ panDisplay === 0 ? "Centro" : panDisplay < 0 ? `${-panDisplay} L` : `${panDisplay} R` }`; } const angle = minAngle + percentage * (maxAngle - minAngle); indicator.style.transform = `translateX(-50%) rotate(${angle}deg)`; knobElement.title = title; } export function highlightStep(stepIndex, isActive) { if (stepIndex < 0) return; document.querySelectorAll(".track-lane").forEach((track) => { const stepWrapper = track.querySelector( `.step-sequencer .step-wrapper:nth-child(${stepIndex + 1})` ); if (stepWrapper) { const stepElement = stepWrapper.querySelector(".step"); if (stepElement) { stepElement.classList.toggle("playing", isActive); } } }); } export async function loadAndRenderSampleBrowser() { const browserContent = document.getElementById("browser-content"); try { const response = await fetch("metadata/samples-manifest.json"); if (!response.ok) { throw new Error("Arquivo samples-manifest.json não encontrado."); } const fileTree = await response.json(); samplePathMap = {}; buildSamplePathMap(fileTree, "src/samples"); console.log("Mapa de samples construído:", samplePathMap); renderFileTree(fileTree, browserContent, "src/samples"); } catch (error) { console.error("Erro ao carregar samples:", error); browserContent.innerHTML = `

${error.message}

`; } } function renderFileTree(tree, parentElement, currentPath) { parentElement.innerHTML = ""; const ul = document.createElement("ul"); const sortedKeys = Object.keys(tree).sort((a, b) => { const aIsFile = tree[a]._isFile; const bIsFile = tree[b]._isFile; if (aIsFile === bIsFile) return a.localeCompare(b); return aIsFile ? 1 : -1; }); for (const key of sortedKeys) { const node = tree[key]; const li = document.createElement("li"); const newPath = `${currentPath}/${key}`; if (node._isFile) { li.innerHTML = ` ${key}`; li.setAttribute("draggable", true); li.addEventListener("click", (e) => { e.stopPropagation(); playSample(newPath, null); }); li.addEventListener("dragstart", (e) => { e.dataTransfer.setData("text/plain", newPath); e.dataTransfer.effectAllowed = "copy"; }); ul.appendChild(li); } else { li.className = "directory"; li.innerHTML = ` ${key}`; const nestedUl = document.createElement("ul"); renderFileTree(node, nestedUl, newPath); li.appendChild(nestedUl); li.addEventListener("click", (e) => { e.stopPropagation(); li.classList.toggle("open"); }); ul.appendChild(li); } } parentElement.appendChild(ul); } export async function showOpenProjectModal() { const openProjectModal = document.getElementById("open-project-modal"); const serverProjectsList = document.getElementById("server-projects-list"); serverProjectsList.innerHTML = "

Carregando...

"; openProjectModal.classList.add("visible"); try { const response = await fetch("metadata/mmp-manifest.json"); if (!response.ok) throw new Error("Arquivo mmp-manifest.json não encontrado."); const projects = await response.json(); serverProjectsList.innerHTML = ""; if (projects.length === 0) { serverProjectsList.innerHTML = '

Nenhum projeto encontrado no servidor.

'; } projects.forEach((projectName) => { const projectItem = document.createElement("div"); projectItem.className = "project-item"; projectItem.textContent = projectName; projectItem.addEventListener("click", async () => { const success = await loadProjectFromServer(projectName); if (success) { closeOpenProjectModal(); } }); serverProjectsList.appendChild(projectItem); }); } catch (error) { console.error("Erro ao carregar lista de projetos:", error); serverProjectsList.innerHTML = `

${error.message}

`; } } export function closeOpenProjectModal() { const openProjectModal = document.getElementById("open-project-modal"); openProjectModal.classList.remove("visible"); }