inserindo server flask de upload e watchdog para verificar alterações em samples
This commit is contained in:
1 parent
c35f3233b7
commit
8c391412e1
6 files changed
+450
-177
No files matched your search
+56
-32
@@ -19,15 +19,16 @@ import {
|
||||
loadAndRenderSampleBrowser,
|
||||
showOpenProjectModal,
|
||||
closeOpenProjectModal,
|
||||
handleSampleUpload, // Importa handleSampleUpload, embora não seja mais usado diretamente
|
||||
} from "./ui.js";
|
||||
import { adjustValue, enforceNumericInput } from "./utils.js";
|
||||
import { DEFAULT_PAN, DEFAULT_VOLUME } from "./config.js";
|
||||
|
||||
// --- INICIALIZAÇÃO E EVENTOS FINAIS ---
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const newProjectBtn = document.getElementById("new-project-btn");
|
||||
const openMmpBtn = document.getElementById("open-mmp-btn");
|
||||
const saveMmpBtn = document.getElementById("save-mmp-btn");
|
||||
const uploadSampleBtn = document.getElementById("upload-sample-btn");
|
||||
const addInstrumentBtn = document.getElementById("add-instrument-btn");
|
||||
const removeInstrumentBtn = document.getElementById("remove-instrument-btn");
|
||||
const playBtn = document.getElementById("play-btn");
|
||||
@@ -35,12 +36,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const rewindBtn = document.getElementById("rewind-btn");
|
||||
const metronomeBtn = document.getElementById("metronome-btn");
|
||||
const mmpFileInput = document.getElementById("mmp-file-input");
|
||||
const sampleFileInput = document.getElementById("sample-file-input");
|
||||
const openProjectModal = document.getElementById("open-project-modal");
|
||||
const openModalCloseBtn = document.getElementById("open-modal-close-btn");
|
||||
const loadFromComputerBtn = document.getElementById("load-from-computer-btn");
|
||||
const sidebarToggle = document.getElementById("sidebar-toggle");
|
||||
const addBarBtn = document.getElementById("add-bar-btn");
|
||||
|
||||
const masterVolumeKnob = document.getElementById("master-volume-knob");
|
||||
const masterPanKnob = document.getElementById("master-pan-knob");
|
||||
|
||||
@@ -52,56 +53,53 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
return;
|
||||
Object.assign(appState, {
|
||||
tracks: [],
|
||||
activeTrackId: null,
|
||||
isPlaying: false,
|
||||
playbackIntervalId: null,
|
||||
currentStep: 0,
|
||||
metronomeEnabled: false,
|
||||
originalXmlDoc: null,
|
||||
currentBeatBasslineName: 'Novo Projeto',
|
||||
masterVolume: DEFAULT_VOLUME,
|
||||
masterPan: DEFAULT_PAN
|
||||
});
|
||||
// Reseta os inputs para o padrão
|
||||
document.getElementById('bpm-input').value = 140;
|
||||
document.getElementById('bars-input').value = 1;
|
||||
document.getElementById('compasso-a-input').value = 4;
|
||||
document.getElementById('compasso-b-input').value = 4;
|
||||
const titleElement = document.getElementById('beat-bassline-title');
|
||||
if(titleElement) titleElement.textContent = 'Novo Projeto';
|
||||
renderApp();
|
||||
setupMasterKnobs(); // Re-inicializa os knobs master
|
||||
setupMasterKnobs();
|
||||
});
|
||||
|
||||
addBarBtn.addEventListener("click", () => {
|
||||
const barsInput = document.getElementById("bars-input");
|
||||
if (barsInput) {
|
||||
adjustValue(barsInput, 1);
|
||||
}
|
||||
if (barsInput) adjustValue(barsInput, 1);
|
||||
});
|
||||
|
||||
function setupMasterKnobs() {
|
||||
function updateMasterKnobVisual(knobElement, controlType) {
|
||||
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 = appState.masterVolume;
|
||||
percentage = value / 1.5;
|
||||
title = `Volume Master: ${Math.round(value * 100)}%`;
|
||||
} else { // pan
|
||||
} else {
|
||||
const value = appState.masterPan;
|
||||
percentage = (value + 1) / 2;
|
||||
const panDisplay = Math.round(value * 100);
|
||||
title = `Pan Master: ${ 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;
|
||||
}
|
||||
|
||||
function addMasterKnobInteraction(knobElement, controlType) {
|
||||
knobElement.addEventListener("wheel", (e) => {
|
||||
e.preventDefault();
|
||||
@@ -109,56 +107,45 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const direction = e.deltaY < 0 ? 1 : -1;
|
||||
if (controlType === "volume") {
|
||||
const newValue = appState.masterVolume + direction * step;
|
||||
const clampedValue = Math.max(0, Math.min(1.5, newValue));
|
||||
appState.masterVolume = clampedValue;
|
||||
updateMasterVolume(clampedValue);
|
||||
appState.masterVolume = Math.max(0, Math.min(1.5, newValue));
|
||||
updateMasterVolume(appState.masterVolume);
|
||||
} else {
|
||||
const newValue = appState.masterPan + direction * step;
|
||||
const clampedValue = Math.max(-1, Math.min(1, newValue));
|
||||
appState.masterPan = clampedValue;
|
||||
updateMasterPan(clampedValue);
|
||||
appState.masterPan = Math.max(-1, Math.min(1, newValue));
|
||||
updateMasterPan(appState.masterPan);
|
||||
}
|
||||
updateMasterKnobVisual(knobElement, controlType);
|
||||
});
|
||||
|
||||
knobElement.addEventListener("mousedown", (e) => {
|
||||
if (e.button !== 0) return;
|
||||
e.preventDefault();
|
||||
|
||||
const startY = e.clientY;
|
||||
const startValue = controlType === "volume" ? appState.masterVolume : appState.masterPan;
|
||||
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") {
|
||||
const clampedValue = Math.max(0, Math.min(1.5, newValue));
|
||||
appState.masterVolume = clampedValue;
|
||||
updateMasterVolume(clampedValue);
|
||||
appState.masterVolume = Math.max(0, Math.min(1.5, newValue));
|
||||
updateMasterVolume(appState.masterVolume);
|
||||
} else {
|
||||
const clampedValue = Math.max(-1, Math.min(1, newValue));
|
||||
appState.masterPan = clampedValue;
|
||||
updateMasterPan(clampedValue);
|
||||
appState.masterPan = Math.max(-1, Math.min(1, newValue));
|
||||
updateMasterPan(appState.masterPan);
|
||||
}
|
||||
updateMasterKnobVisual(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);
|
||||
});
|
||||
}
|
||||
|
||||
addMasterKnobInteraction(masterVolumeKnob, "volume");
|
||||
updateMasterKnobVisual(masterVolumeKnob, "volume");
|
||||
|
||||
addMasterKnobInteraction(masterPanKnob, "pan");
|
||||
updateMasterKnobVisual(masterPanKnob, "pan");
|
||||
}
|
||||
@@ -172,6 +159,44 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
closeOpenProjectModal();
|
||||
}
|
||||
});
|
||||
|
||||
uploadSampleBtn.addEventListener("click", () => sampleFileInput.click());
|
||||
|
||||
// --- INÍCIO DA CORREÇÃO ---
|
||||
// Lógica de upload de sample para o servidor Flask
|
||||
sampleFileInput.addEventListener("change", async (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("sampleFile", file);
|
||||
|
||||
try {
|
||||
// ATENÇÃO: Verifique se a URL e a porta estão corretas para o seu servidor Flask
|
||||
const response = await fetch('http://localhost:5000/upload-sample', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
alert("Sample enviado com sucesso!");
|
||||
// Recarrega a lista de samples para exibir o novo arquivo
|
||||
await loadAndRenderSampleBrowser();
|
||||
} else {
|
||||
throw new Error(result.error || "Erro desconhecido no servidor.");
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Erro ao enviar o sample:", error);
|
||||
alert(`Falha no upload: ${error.message}`);
|
||||
}
|
||||
|
||||
event.target.value = null; // Limpa o input para permitir o mesmo arquivo de novo
|
||||
});
|
||||
// --- FIM DA CORREÇÃO ---
|
||||
|
||||
saveMmpBtn.addEventListener("click", generateMmpFile);
|
||||
addInstrumentBtn.addEventListener("click", addTrackToState);
|
||||
removeInstrumentBtn.addEventListener("click", removeLastTrackFromState);
|
||||
@@ -223,7 +248,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// Inicia a aplicação
|
||||
loadAndRenderSampleBrowser();
|
||||
renderApp();
|
||||
setupMasterKnobs();
|
||||
|
||||
+79
-31
@@ -6,33 +6,35 @@ import {
|
||||
updateTrackVolume,
|
||||
updateTrackPan,
|
||||
} from "./state.js";
|
||||
import { playSample } from "./audio.js";
|
||||
import { playSample, stopPlayback } from "./audio.js";
|
||||
import { getTotalSteps } from "./utils.js";
|
||||
import { loadProjectFromServer } from "./file.js";
|
||||
|
||||
let samplePathMap = {};
|
||||
const globalPatternSelector = document.getElementById('global-pattern-selector');
|
||||
|
||||
globalPatternSelector.addEventListener('change', () => {
|
||||
// Atualiza o índice GLOBAL
|
||||
appState.activePatternIndex = parseInt(globalPatternSelector.value, 10);
|
||||
|
||||
const firstTrack = appState.tracks[0];
|
||||
if (firstTrack) {
|
||||
const activePattern = firstTrack.patterns[appState.activePatternIndex];
|
||||
if (activePattern) {
|
||||
const stepsPerBar = 16;
|
||||
const requiredBars = Math.ceil(activePattern.steps.length / stepsPerBar);
|
||||
document.getElementById("bars-input").value = requiredBars > 0 ? requiredBars : 1;
|
||||
if (globalPatternSelector) {
|
||||
globalPatternSelector.addEventListener('change', () => {
|
||||
// A linha stopPlayback() foi REMOVIDA daqui, permitindo a troca em tempo real.
|
||||
appState.activePatternIndex = parseInt(globalPatternSelector.value, 10);
|
||||
|
||||
const firstTrack = appState.tracks[0];
|
||||
if (firstTrack) {
|
||||
const activePattern = firstTrack.patterns[appState.activePatternIndex];
|
||||
if (activePattern) {
|
||||
const stepsPerBar = 16;
|
||||
const requiredBars = Math.ceil(activePattern.steps.length / stepsPerBar);
|
||||
document.getElementById("bars-input").value = requiredBars > 0 ? requiredBars : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
redrawSequencer();
|
||||
});
|
||||
redrawSequencer();
|
||||
});
|
||||
}
|
||||
|
||||
export function updateGlobalPatternSelector() {
|
||||
if (!globalPatternSelector) return;
|
||||
const referenceTrack = appState.tracks[0];
|
||||
globalPatternSelector.innerHTML = '';
|
||||
|
||||
if (referenceTrack && referenceTrack.patterns.length > 0) {
|
||||
referenceTrack.patterns.forEach((pattern, index) => {
|
||||
const option = document.createElement('option');
|
||||
@@ -40,7 +42,6 @@ export function updateGlobalPatternSelector() {
|
||||
option.textContent = pattern.name;
|
||||
globalPatternSelector.appendChild(option);
|
||||
});
|
||||
// Usa o índice GLOBAL
|
||||
globalPatternSelector.selectedIndex = appState.activePatternIndex;
|
||||
globalPatternSelector.disabled = false;
|
||||
} else {
|
||||
@@ -51,6 +52,42 @@ export function updateGlobalPatternSelector() {
|
||||
}
|
||||
}
|
||||
|
||||
export function handleSampleUpload(file) {
|
||||
const validExtensions = ['.wav', '.flac', '.ogg', '.mp3'];
|
||||
const fileExtension = '.' + file.name.split('.').pop().toLowerCase();
|
||||
|
||||
if (!validExtensions.includes(fileExtension)) {
|
||||
alert("Formato de arquivo inválido. Por favor, envie .wav, .flac, .ogg, ou .mp3.");
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const dataURL = e.target.result;
|
||||
const browserContent = document.getElementById("browser-content");
|
||||
const list = browserContent.querySelector("ul");
|
||||
|
||||
if (list) {
|
||||
const li = document.createElement("li");
|
||||
li.innerHTML = `<i class="fa-solid fa-file-audio"></i> ${file.name}`;
|
||||
li.setAttribute("draggable", true);
|
||||
|
||||
li.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
playSample(dataURL, null);
|
||||
});
|
||||
|
||||
li.addEventListener("dragstart", (event) => {
|
||||
event.dataTransfer.setData("text/plain", dataURL);
|
||||
event.dataTransfer.effectAllowed = "copy";
|
||||
});
|
||||
|
||||
list.prepend(li);
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
export function getSamplePathMap() {
|
||||
return samplePathMap;
|
||||
}
|
||||
@@ -89,15 +126,11 @@ export function renderApp() {
|
||||
</div>
|
||||
<div class="track-controls">
|
||||
<div class="knob-container">
|
||||
<div class="knob" data-control="volume" data-track-id="${trackData.id}">
|
||||
<div class="knob-indicator"></div>
|
||||
</div>
|
||||
<div class="knob" data-control="volume" data-track-id="${trackData.id}"><div class="knob-indicator"></div></div>
|
||||
<span>VOL</span>
|
||||
</div>
|
||||
<div class="knob-container">
|
||||
<div class="knob" data-control="pan" data-track-id="${trackData.id}">
|
||||
<div class="knob-indicator"></div>
|
||||
</div>
|
||||
<div class="knob" data-control="pan" data-track-id="${trackData.id}"><div class="knob-indicator"></div></div>
|
||||
<span>PAN</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,11 +139,25 @@ export function renderApp() {
|
||||
|
||||
trackLane.addEventListener('click', () => {
|
||||
if (appState.activeTrackId === trackData.id) return;
|
||||
|
||||
// A linha stopPlayback() também foi REMOVIDA daqui
|
||||
|
||||
appState.activeTrackId = trackData.id;
|
||||
document.querySelectorAll('.track-lane').forEach(lane => lane.classList.remove('active-track'));
|
||||
trackLane.classList.add('active-track');
|
||||
updateGlobalPatternSelector();
|
||||
redrawSequencer();
|
||||
|
||||
// Apenas redesenha a UI, sem parar a música
|
||||
const activeTrack = appState.tracks.find(t => t.id === appState.activeTrackId);
|
||||
if (activeTrack) {
|
||||
const activePattern = activeTrack.patterns[activeTrack.activePatternIndex];
|
||||
if (activePattern) {
|
||||
const stepsPerBar = 16;
|
||||
const requiredBars = Math.ceil(activePattern.steps.length / stepsPerBar);
|
||||
document.getElementById("bars-input").value = requiredBars > 0 ? requiredBars : 1;
|
||||
redrawSequencer();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
trackLane.addEventListener("dragover", (e) => { e.preventDefault(); trackLane.classList.add("drag-over"); });
|
||||
@@ -152,15 +199,12 @@ export function redrawSequencer() {
|
||||
const trackData = appState.tracks.find((t) => t.id == trackId);
|
||||
|
||||
if (!trackData || !trackData.patterns || trackData.patterns.length === 0) {
|
||||
sequencerContainer.innerHTML = "";
|
||||
return;
|
||||
sequencerContainer.innerHTML = ""; return;
|
||||
}
|
||||
|
||||
// A CORREÇÃO FINAL: Usa o índice GLOBAL para desenhar CADA faixa
|
||||
const activePattern = trackData.patterns[appState.activePatternIndex];
|
||||
if (!activePattern) {
|
||||
sequencerContainer.innerHTML = "";
|
||||
return;
|
||||
sequencerContainer.innerHTML = ""; return;
|
||||
}
|
||||
const patternSteps = activePattern.steps;
|
||||
|
||||
@@ -315,7 +359,11 @@ export function highlightStep(stepIndex, isActive) {
|
||||
export async function loadAndRenderSampleBrowser() {
|
||||
const browserContent = document.getElementById("browser-content");
|
||||
try {
|
||||
const response = await fetch("metadata/samples-manifest.json");
|
||||
// --- CORREÇÃO AQUI ---
|
||||
// Adiciona um timestamp à URL para evitar que o navegador use uma versão antiga (em cache) do arquivo.
|
||||
const response = await fetch(`metadata/samples-manifest.json?v=${Date.now()}`);
|
||||
// --- FIM DA CORREÇÃO ---
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Arquivo samples-manifest.json não encontrado.");
|
||||
}
|
||||
@@ -323,7 +371,7 @@ export async function loadAndRenderSampleBrowser() {
|
||||
|
||||
samplePathMap = {};
|
||||
buildSamplePathMap(fileTree, "src/samples");
|
||||
|
||||
|
||||
renderFileTree(fileTree, browserContent, "src/samples");
|
||||
} catch (error) {
|
||||
console.error("Erro ao carregar samples:", error);
|
||||
|
||||
Reference in new issue
Block a user