tentando melhorar a abertura dos arquivos no MMPCreator
Deploy / Deploy (push) Successful in 2m19s Details

This commit is contained in:
JotaChina 2025-12-20 11:15:44 -03:00
parent be3e1ff9e9
commit 44abcf855a
5 changed files with 165 additions and 63 deletions

View File

@ -3,7 +3,6 @@
- 210424.wav - 210424.wav
- 43yu.wav - 43yu.wav
- 4r3st.wav - 4r3st.wav
- 618.wav
- 7-is-the-answer-060224.wav - 7-is-the-answer-060224.wav
- advait.wav - advait.wav
- aelig.wav - aelig.wav

View File

@ -3147,6 +3147,9 @@
} }
}, },
"Teste_Testes": { "Teste_Testes": {
"gravacao_17-14-15.ogg": {
"_isFile": true
},
"gravacao_22-04-40.ogg": { "gravacao_22-04-40.ogg": {
"_isFile": true "_isFile": true
}, },

Binary file not shown.

View File

@ -12,13 +12,12 @@
<link rel="stylesheet" href="./assets/css/creator.css" /> <link rel="stylesheet" href="./assets/css/creator.css" />
<style> <style>
/* Estilo para clipes de pattern */ /* --- ESTILOS ORIGINAIS --- */
.timeline-clip.pattern-clip { .timeline-clip.pattern-clip {
background: linear-gradient(to bottom, #4a4f57, #3b3f45); background: linear-gradient(to bottom, #4a4f57, #3b3f45);
height: 70px; /* Mais alto para ver as notas */ height: 70px;
overflow: hidden; overflow: hidden;
} }
/* Container para as notas do pattern */
.pattern-clip-view { .pattern-clip-view {
position: absolute; position: absolute;
top: 0; top: 0;
@ -32,13 +31,11 @@
justify-content: space-around; justify-content: space-around;
gap: 1px; gap: 1px;
} }
/* Linha de trilha dentro do clipe */
.pattern-clip-track-row { .pattern-clip-track-row {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
/* Cada "nota" (bloco branco) */
.pattern-step-note { .pattern-step-note {
position: absolute; position: absolute;
background-color: rgba(255, 255, 255, 0.9); background-color: rgba(255, 255, 255, 0.9);
@ -47,7 +44,6 @@
border-radius: 1px; border-radius: 1px;
box-sizing: border-box; box-sizing: border-box;
} }
/* Menu de contexto */
#timeline-context-menu .menu-divider { #timeline-context-menu .menu-divider {
height: 1px; height: 1px;
background-color: var(--border-color); background-color: var(--border-color);
@ -67,6 +63,56 @@
color: var(--text-dark); color: var(--text-dark);
background-color: transparent; background-color: transparent;
} }
/* --- NOVOS ESTILOS PARA O BASSLINE EDITOR (Lógica 3) --- */
/* Linha do instrumento (Nome à esquerda) */
.instrument-row {
display: flex;
align-items: center;
height: 40px;
border-bottom: 1px solid #333;
padding-left: 10px;
background-color: #2b2b2b;
color: #ddd;
font-family: sans-serif;
font-size: 0.9rem;
box-sizing: border-box;
}
/* Linha dos steps (Botões à direita) */
.steps-row {
display: flex;
align-items: center;
height: 40px;
border-bottom: 1px solid #333;
padding-left: 5px;
background-color: #222; /* Fundo ligeiramente diferente para distinguir */
box-sizing: border-box;
}
/* Botão individual do Step */
.step-btn {
width: 20px;
height: 30px;
margin-right: 2px;
background-color: #444;
border: 1px solid #555;
border-radius: 2px;
cursor: pointer;
transition: background-color 0.1s;
}
.step-btn:hover {
border-color: #888;
}
/* Estado Ativo (Ligado) */
.step-btn.active {
background-color: #0f0; /* Verde LMMS */
box-shadow: 0 0 4px rgba(0, 255, 0, 0.4);
border-color: #0c0;
}
</style> </style>
</head> </head>
<body> <body>
@ -332,6 +378,7 @@
</div> </div>
<div id="sequencer-grid" class="sequencer-container"></div> <div id="sequencer-grid" class="sequencer-container"></div>
<div class="tool-icons"></div> <div class="tool-icons"></div>
<div id="timeline-context-menu"> <div id="timeline-context-menu">
<div id="copy-clip">Copiar</div> <div id="copy-clip">Copiar</div>
@ -348,6 +395,7 @@
<div id="ruler-set-loop-end">Definir Fim do Loop</div> <div id="ruler-set-loop-end">Definir Fim do Loop</div>
</div> </div>
</div> </div>
<div id="track-container"></div> <div id="track-container"></div>
</div> </div>
@ -565,7 +613,88 @@
const previewSynth = new Tone.PolySynth(Tone.Synth).toDestination(); const previewSynth = new Tone.PolySynth(Tone.Synth).toDestination();
previewSynth.volume.value = -10; 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 = "<div style='padding:10px; color:#ccc'>Nenhum instrumento.</div>";
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) { window.openPianoRoll = function (trackId) {
const track = appState.pattern.tracks.find((t) => t.id === trackId); const track = appState.pattern.tracks.find((t) => t.id === trackId);
if (!track) return; if (!track) return;
@ -585,12 +714,10 @@
gridContainer.scrollTop = middleY - 200; gridContainer.scrollTop = middleY - 200;
}; };
// --- DESENHO E LÓGICA --- // --- DESENHO E LÓGICA DO PIANO ROLL ---
function resizeCanvas() { function resizeCanvas() {
const totalHeight = CONSTANTS.TOTAL_KEYS * CONSTANTS.NOTE_HEIGHT; 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; const totalWidth = 3000;
keysCanvas.width = CONSTANTS.KEY_WIDTH; keysCanvas.width = CONSTANTS.KEY_WIDTH;
@ -598,10 +725,6 @@
gridCanvas.width = totalWidth; gridCanvas.width = totalWidth;
gridCanvas.height = totalHeight; 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; CONSTANTS.TICKS_PER_PIXEL = 48 / CONSTANTS.BEAT_WIDTH;
drawKeys(); drawKeys();
@ -674,13 +797,9 @@
gridCtx.strokeStyle = "#000"; gridCtx.strokeStyle = "#000";
notes.forEach((note) => { notes.forEach((note) => {
// Converter MIDI para Y (Invertido)
const keyIndex = const keyIndex =
CONSTANTS.TOTAL_KEYS - 1 - (note.key - CONSTANTS.START_NOTE); CONSTANTS.TOTAL_KEYS - 1 - (note.key - CONSTANTS.START_NOTE);
const y = keyIndex * CONSTANTS.NOTE_HEIGHT; 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 x = note.pos / CONSTANTS.TICKS_PER_PIXEL;
const width = note.len / CONSTANTS.TICKS_PER_PIXEL; const width = note.len / CONSTANTS.TICKS_PER_PIXEL;
@ -694,7 +813,6 @@
}); });
} }
// --- INTERAÇÃO: CRIAR NOTAS ---
gridCanvas.addEventListener("mousedown", (e) => { gridCanvas.addEventListener("mousedown", (e) => {
if (!currentTrackId) return; if (!currentTrackId) return;
@ -702,47 +820,34 @@
const x = e.clientX - rect.left; const x = e.clientX - rect.left;
const y = e.clientY - rect.top; const y = e.clientY - rect.top;
// 1. Calcular Nota (Y)
const keyIndex = Math.floor(y / CONSTANTS.NOTE_HEIGHT); const keyIndex = Math.floor(y / CONSTANTS.NOTE_HEIGHT);
const midiNote = const midiNote =
CONSTANTS.START_NOTE + (CONSTANTS.TOTAL_KEYS - 1 - keyIndex); 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 snapTicks = 12;
const rawTicks = x * CONSTANTS.TICKS_PER_PIXEL; const rawTicks = x * CONSTANTS.TICKS_PER_PIXEL;
const quantizedPos = Math.floor(rawTicks / snapTicks) * snapTicks; const quantizedPos = Math.floor(rawTicks / snapTicks) * snapTicks;
// 3. Criar Objeto Nota
const newNote = { const newNote = {
pos: quantizedPos, pos: quantizedPos,
len: 48, // Duração padrão (1 beat/seminima) - ajuste conforme desejar len: 48,
key: midiNote, key: midiNote,
vol: 100, vol: 100,
pan: 0, pan: 0,
}; };
// 4. Atualizar Estado e Redesenhar
const track = appState.pattern.tracks.find( const track = appState.pattern.tracks.find(
(t) => t.id === currentTrackId (t) => t.id === currentTrackId
); );
if (track) { if (track) {
const pattern = track.patterns[track.activePatternIndex]; const pattern = track.patterns[track.activePatternIndex];
// Se não existir array de notas, cria
if (!pattern.notes) pattern.notes = []; if (!pattern.notes) pattern.notes = [];
pattern.notes.push(newNote); pattern.notes.push(newNote);
// Toca som
const noteName = Tone.Frequency(midiNote, "midi").toNote(); const noteName = Tone.Frequency(midiNote, "midi").toNote();
previewSynth.triggerAttackRelease(noteName, "8n"); previewSynth.triggerAttackRelease(noteName, "8n");
// Redesenha Piano Roll
drawNotes(); drawNotes();
// IMPORTANTE: Atualizar a UI da lista de trilhas (para aparecer a miniatura)
// E enviar via Socket para colaboradores
renderAll(); renderAll();
sendAction({ sendAction({
type: "UPDATE_PATTERN_NOTES", type: "UPDATE_PATTERN_NOTES",
@ -753,12 +858,10 @@
} }
}); });
// Scroll Sync
gridContainer.addEventListener("scroll", () => { gridContainer.addEventListener("scroll", () => {
keysContainer.scrollTop = gridContainer.scrollTop; keysContainer.scrollTop = gridContainer.scrollTop;
}); });
// Fechar
document document
.getElementById("close-piano-roll-btn") .getElementById("close-piano-roll-btn")
.addEventListener("click", () => { .addEventListener("click", () => {
@ -778,27 +881,18 @@
if (downloadBtn) { if (downloadBtn) {
downloadBtn.addEventListener("click", () => { downloadBtn.addEventListener("click", () => {
// 1. Pega o parâmetro 'project' da URL (ex: creation.html?project=drake)
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
let projectName = params.get("project"); let projectName = params.get("project");
if (projectName) { if (projectName) {
// 2. Garante a extensão para a API (opcional, mas seguro)
if (!projectName.toLowerCase().endsWith(".mmp")) { if (!projectName.toLowerCase().endsWith(".mmp")) {
projectName += ".mmp"; projectName += ".mmp";
} }
// 3. Monta a URL da API Python e inicia o download
const apiUrl = `/api/download/${projectName}`; const apiUrl = `/api/download/${projectName}`;
// Feedback visual rápido
downloadBtn.style.opacity = "0.5"; downloadBtn.style.opacity = "0.5";
setTimeout(() => downloadBtn.style.opacity = "1", 500); setTimeout(() => downloadBtn.style.opacity = "1", 500);
// Dispara o download
window.location.href = apiUrl; window.location.href = apiUrl;
} else { } 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."); alert("Nenhum projeto selecionado na URL. Abra ou Salve um projeto primeiro.");
} }
}); });
@ -806,44 +900,35 @@
}); });
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
// --- LÓGICA DE UPLOAD DE SAMPLE AVULSO ---
const uploadSampleBtn = document.getElementById("upload-sample-btn"); const uploadSampleBtn = document.getElementById("upload-sample-btn");
const sampleInput = document.getElementById("sample-file-input"); const sampleInput = document.getElementById("sample-file-input");
if (uploadSampleBtn && sampleInput) { if (uploadSampleBtn && sampleInput) {
// 1. Botão clica no input invisível
uploadSampleBtn.addEventListener("click", () => { uploadSampleBtn.addEventListener("click", () => {
sampleInput.click(); sampleInput.click();
}); });
// 2. Quando o arquivo é selecionado
sampleInput.addEventListener("change", async () => { sampleInput.addEventListener("change", async () => {
if (sampleInput.files.length === 0) return; if (sampleInput.files.length === 0) return;
const file = sampleInput.files[0]; 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( const category = prompt(
"Em qual categoria este sample se encaixa? (Ex: drums, effects, vocals)", "Em qual categoria este sample se encaixa? (Ex: drums, effects, vocals)",
"imported" "imported"
); );
if (category === null) { if (category === null) {
// Usuário cancelou
sampleInput.value = ""; sampleInput.value = "";
return; return;
} }
// Prepara o formulário
const formData = new FormData(); const formData = new FormData();
formData.append("sample_file", file); // Deve bater com 'sample_file' no Python formData.append("sample_file", file);
formData.append("subfolder", category); // Deve bater com 'subfolder' no Python formData.append("subfolder", category);
// Feedback Visual (ícone girando ou ficando transparente)
const originalIcon = uploadSampleBtn.className; 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"; uploadSampleBtn.style.pointerEvents = "none";
try { try {
@ -856,8 +941,6 @@
if (response.ok) { if (response.ok) {
alert("Sucesso! " + result.message); alert("Sucesso! " + result.message);
// Opcional: Recarregar a lista de samples lateral se você tiver uma função para isso
// reloadBrowser();
} else { } else {
alert("Erro ao enviar: " + (result.error || "Desconhecido")); alert("Erro ao enviar: " + (result.error || "Desconhecido"));
} }
@ -866,7 +949,6 @@
console.error("Erro no upload:", error); console.error("Erro no upload:", error);
alert("Erro de conexão com o servidor."); alert("Erro de conexão com o servidor.");
} finally { } finally {
// Restaura o botão e limpa o input
uploadSampleBtn.className = originalIcon; uploadSampleBtn.className = originalIcon;
uploadSampleBtn.style.pointerEvents = "auto"; uploadSampleBtn.style.pointerEvents = "auto";
sampleInput.value = ""; sampleInput.value = "";
@ -876,8 +958,6 @@
}); });
</script> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.7.77/Tone.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.7.77/Tone.js"></script>
<script src="assets/js/creations/main.js" type="module"></script> <script src="assets/js/creations/main.js" type="module"></script>

View File

@ -64,6 +64,26 @@ def parse_mmp_file(file_path):
elif track_type == "1": # Bassline (aparentemente, correto) elif track_type == "1": # Bassline (aparentemente, correto)
bassline_info = parse_basslines(track) bassline_info = parse_basslines(track)
if bassline_info: 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) track_info.append(bassline_info)
if bassline_info["tags"] is not None: if bassline_info["tags"] is not None:
if "bassline" not in tags["TAG"]: # certo if "bassline" not in tags["TAG"]: # certo