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 94785956..fd8f910b 100644
Binary files a/_data/users.db and b/_data/users.db differ
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 @@
+
+
@@ -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 @@
});
-
-
-