From 44abcf855a388187ea352281db9c7da255f0bf83 Mon Sep 17 00:00:00 2001 From: JotaChina Date: Sat, 20 Dec 2025 11:15:44 -0300 Subject: [PATCH] tentando melhorar a abertura dos arquivos no MMPCreator --- _data/beats.yml | 1 - _data/samples-manifest.json | 3 + _data/users.db | Bin 24576 -> 24576 bytes creation.html | 204 +++++++++++++++++++++++---------- scripts/handler/file_parser.py | 20 ++++ 5 files changed, 165 insertions(+), 63 deletions(-) diff --git a/_data/beats.yml b/_data/beats.yml index 86aee508..49da83c4 100644 --- a/_data/beats.yml +++ b/_data/beats.yml @@ -3,7 +3,6 @@ - 210424.wav - 43yu.wav - 4r3st.wav -- 618.wav - 7-is-the-answer-060224.wav - advait.wav - aelig.wav diff --git a/_data/samples-manifest.json b/_data/samples-manifest.json index f8fda781..ac3ae1a2 100644 --- a/_data/samples-manifest.json +++ b/_data/samples-manifest.json @@ -3147,6 +3147,9 @@ } }, "Teste_Testes": { + "gravacao_17-14-15.ogg": { + "_isFile": true + }, "gravacao_22-04-40.ogg": { "_isFile": true }, diff --git a/_data/users.db b/_data/users.db index 947859561a71f1474321aa059c89da42867e9180..fd8f910bd825be9ade9d0608b3766679ecfddd25 100644 GIT binary patch delta 199 zcmZoTz}Rqrae_1>&qNt#MxKocOY}LI_~$Y3pW~mmSx{jTzkwbT8-u*KBWFrcVs@%t zZf*gXVPs%rs%vPZYiO=uWME}vXk}`tXJ}$zY+*Y2oV^(j3*QO`{O z;EN1rVP`NmS9Rn}FG?&+Ois*?H#FBZG|@FQ)yq#$$0`_-T3nJE4Cc delta 56 zcmV-80LTA;zyW~30gxL33XvQ`0Sd8Tq%Q^m56%D&&a)99#1E6sKQ;;j4*>uVtq)WW O0kaVh^A58CPyZ0$-Vm7p diff --git a/creation.html b/creation.html index cbbf8844..7d61b125 100755 --- a/creation.html +++ b/creation.html @@ -12,13 +12,12 @@ @@ -332,6 +378,7 @@
+
Copiar
@@ -348,6 +395,7 @@
Definir Fim do Loop
+
@@ -565,7 +613,88 @@ const previewSynth = new Tone.PolySynth(Tone.Synth).toDestination(); previewSynth.volume.value = -10; - // --- FUNÇÃO GLOBAL PARA ABRIR O EDITOR --- + // ======================================================= + // LÓGICA 3: FUNÇÃO PARA ABRIR O EDITOR DE BASSLINE + // Esta função popula a área superior (Beat Editor) + // quando se clica duas vezes num clipe da playlist. + // ======================================================= + + window.openPatternEditor = function(basslineTrack) { + console.log("Abrindo editor para:", basslineTrack.track_name); + + // 1. Identificar containers + // No seu HTML, 'sequencer-grid' está em cima e 'track-container' embaixo (na toolbar e body do editor) + const nameContainer = document.getElementById("track-container"); + const stepsContainer = document.getElementById("sequencer-grid"); + + if(!nameContainer || !stepsContainer) { + console.error("Containers do editor não encontrados!"); + return; + } + + // 2. Limpar + nameContainer.innerHTML = ""; + stepsContainer.innerHTML = ""; + + // 3. Iterar sobre os instrumentos internos + const instruments = basslineTrack.instruments || []; + + if (instruments.length === 0) { + nameContainer.innerHTML = "
Nenhum instrumento.
"; + return; + } + + instruments.forEach(inst => { + // --- A. Nome do Instrumento (Coluna Esquerda) --- + const nameRow = document.createElement("div"); + nameRow.className = "instrument-row"; + + // Texto do nome + const label = document.createElement("span"); + label.innerText = inst.instrument_name || inst.plugin_name || "Sem Nome"; + nameRow.appendChild(label); + + nameContainer.appendChild(nameRow); + + // --- B. Steps (Coluna Direita - Grid) --- + // Recupera steps (True/False array) + let stepData = []; + if (inst.patterns && inst.patterns.length > 0) { + stepData = inst.patterns[0].steps; + } else { + stepData = Array(16).fill(false); // Default + } + + const stepsRow = document.createElement("div"); + stepsRow.className = "steps-row"; + + stepData.forEach((isActive, index) => { + const stepBtn = document.createElement("div"); + stepBtn.className = "step-btn"; + if (isActive) stepBtn.classList.add("active"); + + // Interação de Clique (Toggle) + stepBtn.addEventListener("click", () => { + // Inverte estado + const newState = !stepBtn.classList.contains("active"); + + if(newState) stepBtn.classList.add("active"); + else stepBtn.classList.remove("active"); + + // Atualiza dado em memória + if(!inst.patterns) inst.patterns = [{steps: []}]; + if(!inst.patterns[0]) inst.patterns[0] = {steps: []}; + inst.patterns[0].steps[index] = newState; + }); + + stepsRow.appendChild(stepBtn); + }); + + stepsContainer.appendChild(stepsRow); + }); + }; + + // --- FUNÇÃO GLOBAL PARA ABRIR O PIANO ROLL (JÁ EXISTENTE) --- window.openPianoRoll = function (trackId) { const track = appState.pattern.tracks.find((t) => t.id === trackId); if (!track) return; @@ -585,12 +714,10 @@ gridContainer.scrollTop = middleY - 200; }; - // --- DESENHO E LÓGICA --- + // --- DESENHO E LÓGICA DO PIANO ROLL --- function resizeCanvas() { const totalHeight = CONSTANTS.TOTAL_KEYS * CONSTANTS.NOTE_HEIGHT; - // 64 compassos * 192 ticks / (ticks por beat) * largura... simplificando: - // Vamos fixar uma largura grande por enquanto const totalWidth = 3000; keysCanvas.width = CONSTANTS.KEY_WIDTH; @@ -598,10 +725,6 @@ gridCanvas.width = totalWidth; gridCanvas.height = totalHeight; - // Importante: Sincronizar a conversão de Pixel <-> Tick - // 1 Beat = 48 ticks (em 16th) ou 192 ticks por bar? - // No seu file.js: ticksPerStep = 12 (1/16). - // Então BEAT_WIDTH (40px) = 4 steps = 48 ticks. CONSTANTS.TICKS_PER_PIXEL = 48 / CONSTANTS.BEAT_WIDTH; drawKeys(); @@ -674,13 +797,9 @@ gridCtx.strokeStyle = "#000"; notes.forEach((note) => { - // Converter MIDI para Y (Invertido) const keyIndex = CONSTANTS.TOTAL_KEYS - 1 - (note.key - CONSTANTS.START_NOTE); const y = keyIndex * CONSTANTS.NOTE_HEIGHT; - - // Converter Ticks (pos) para X - // Se 48 ticks = BEAT_WIDTH (40px) -> pos / 1.2 const x = note.pos / CONSTANTS.TICKS_PER_PIXEL; const width = note.len / CONSTANTS.TICKS_PER_PIXEL; @@ -694,7 +813,6 @@ }); } - // --- INTERAÇÃO: CRIAR NOTAS --- gridCanvas.addEventListener("mousedown", (e) => { if (!currentTrackId) return; @@ -702,47 +820,34 @@ const x = e.clientX - rect.left; const y = e.clientY - rect.top; - // 1. Calcular Nota (Y) const keyIndex = Math.floor(y / CONSTANTS.NOTE_HEIGHT); const midiNote = CONSTANTS.START_NOTE + (CONSTANTS.TOTAL_KEYS - 1 - keyIndex); - // 2. Calcular Posição (X) e Snap - // Snap padrão 1/16 = 12 ticks const snapTicks = 12; const rawTicks = x * CONSTANTS.TICKS_PER_PIXEL; const quantizedPos = Math.floor(rawTicks / snapTicks) * snapTicks; - // 3. Criar Objeto Nota const newNote = { pos: quantizedPos, - len: 48, // Duração padrão (1 beat/seminima) - ajuste conforme desejar + len: 48, key: midiNote, vol: 100, pan: 0, }; - // 4. Atualizar Estado e Redesenhar const track = appState.pattern.tracks.find( (t) => t.id === currentTrackId ); if (track) { const pattern = track.patterns[track.activePatternIndex]; - - // Se não existir array de notas, cria if (!pattern.notes) pattern.notes = []; - pattern.notes.push(newNote); - // Toca som const noteName = Tone.Frequency(midiNote, "midi").toNote(); previewSynth.triggerAttackRelease(noteName, "8n"); - // Redesenha Piano Roll drawNotes(); - - // IMPORTANTE: Atualizar a UI da lista de trilhas (para aparecer a miniatura) - // E enviar via Socket para colaboradores renderAll(); sendAction({ type: "UPDATE_PATTERN_NOTES", @@ -753,12 +858,10 @@ } }); - // Scroll Sync gridContainer.addEventListener("scroll", () => { keysContainer.scrollTop = gridContainer.scrollTop; }); - // Fechar document .getElementById("close-piano-roll-btn") .addEventListener("click", () => { @@ -778,27 +881,18 @@ if (downloadBtn) { downloadBtn.addEventListener("click", () => { - // 1. Pega o parâmetro 'project' da URL (ex: creation.html?project=drake) const params = new URLSearchParams(window.location.search); let projectName = params.get("project"); if (projectName) { - // 2. Garante a extensão para a API (opcional, mas seguro) if (!projectName.toLowerCase().endsWith(".mmp")) { projectName += ".mmp"; } - - // 3. Monta a URL da API Python e inicia o download const apiUrl = `/api/download/${projectName}`; - - // Feedback visual rápido downloadBtn.style.opacity = "0.5"; setTimeout(() => downloadBtn.style.opacity = "1", 500); - - // Dispara o download window.location.href = apiUrl; } else { - // Caso o usuário tenha entrado direto em creation.html sem parâmetro alert("Nenhum projeto selecionado na URL. Abra ou Salve um projeto primeiro."); } }); @@ -806,44 +900,35 @@ }); document.addEventListener("DOMContentLoaded", () => { - // --- LÓGICA DE UPLOAD DE SAMPLE AVULSO --- const uploadSampleBtn = document.getElementById("upload-sample-btn"); const sampleInput = document.getElementById("sample-file-input"); if (uploadSampleBtn && sampleInput) { - // 1. Botão clica no input invisível uploadSampleBtn.addEventListener("click", () => { sampleInput.click(); }); - // 2. Quando o arquivo é selecionado sampleInput.addEventListener("change", async () => { if (sampleInput.files.length === 0) return; const file = sampleInput.files[0]; - - // Pergunta a categoria para organizar no servidor (opcional) - // O backend usa isso para criar pastas: samples/drums, samples/vocals, etc. const category = prompt( "Em qual categoria este sample se encaixa? (Ex: drums, effects, vocals)", "imported" ); if (category === null) { - // Usuário cancelou sampleInput.value = ""; return; } - // Prepara o formulário const formData = new FormData(); - formData.append("sample_file", file); // Deve bater com 'sample_file' no Python - formData.append("subfolder", category); // Deve bater com 'subfolder' no Python + formData.append("sample_file", file); + formData.append("subfolder", category); - // Feedback Visual (ícone girando ou ficando transparente) const originalIcon = uploadSampleBtn.className; - uploadSampleBtn.className = "fa-solid fa-spinner fa-spin"; // Ícone de loading + uploadSampleBtn.className = "fa-solid fa-spinner fa-spin"; uploadSampleBtn.style.pointerEvents = "none"; try { @@ -856,8 +941,6 @@ if (response.ok) { alert("Sucesso! " + result.message); - // Opcional: Recarregar a lista de samples lateral se você tiver uma função para isso - // reloadBrowser(); } else { alert("Erro ao enviar: " + (result.error || "Desconhecido")); } @@ -866,7 +949,6 @@ console.error("Erro no upload:", error); alert("Erro de conexão com o servidor."); } finally { - // Restaura o botão e limpa o input uploadSampleBtn.className = originalIcon; uploadSampleBtn.style.pointerEvents = "auto"; sampleInput.value = ""; @@ -876,12 +958,10 @@ }); - - - + \ No newline at end of file diff --git a/scripts/handler/file_parser.py b/scripts/handler/file_parser.py index 1ac4090f..ce09d95c 100755 --- a/scripts/handler/file_parser.py +++ b/scripts/handler/file_parser.py @@ -64,6 +64,26 @@ def parse_mmp_file(file_path): elif track_type == "1": # Bassline (aparentemente, correto) bassline_info = parse_basslines(track) if bassline_info: + playlist_clips = [] + bbtrack = track.find("bbtrack") + if bbtrack is not None: + trackcontent = bbtrack.find("trackcontent") + if trackcontent is not None: + for pattern in trackcontent.findall("pattern"): + playlist_clips.append({ + "pos": int(pattern.attrib.get("pos", 0)), + "len": int(pattern.attrib.get("len", 192)), # Padrão 1 bar + "steps": int(pattern.attrib.get("steps", 16)), + "name": track_name # O clipe leva o nome da trilha + }) + # Adiciona essa lista de clipes ao objeto final + bassline_info["playlist_clips"] = playlist_clips + + # Garante que o tipo e nome estão corretos no nível superior + bassline_info["type"] = "bassline" + bassline_info["track_name"] = track_name + + track_info.append(bassline_info) track_info.append(bassline_info) if bassline_info["tags"] is not None: if "bassline" not in tags["TAG"]: # certo