tentando obter playlist real do projeto mmp

This commit is contained in:
JotaChina committed 2025-12-20 14:58:18 -03:00
1 parent 3380b642b3
commit 7b569c1bde
4 files changed
+203 -223

No files matched your search

+43 -75
View File
@@ -18,7 +18,7 @@ import * as Tone from "https://esm.sh/tone";
import { sendAction } from "./socket.js";
//--------------------------------------------------------------
// MANIPULAÇÃO DE ARQUIVOS E PARSING
// MANIPULAÇÃO DE ARQUIVOS
//--------------------------------------------------------------
export function handleLocalProjectReset() {
@@ -90,7 +90,7 @@ export async function loadProjectFromServer(fileName) {
// =================================================================
// FUNÇÃO AUXILIAR: PARSE DE INSTRUMENTO ÚNICO
// =================================================================
function parseInstrumentNode(trackNode, sortedBBTrackNameNodes, pathMap) {
function parseInstrumentNode(trackNode, sortedBBTrackNameNodes, pathMap, parentBasslineId = null) {
const instrumentNode = trackNode.querySelector("instrument");
const instrumentTrackNode = trackNode.querySelector("instrumenttrack");
@@ -99,7 +99,7 @@ function parseInstrumentNode(trackNode, sortedBBTrackNameNodes, pathMap) {
const trackName = trackNode.getAttribute("name");
const instrumentName = instrumentNode.getAttribute("name");
// Identifica e ordena os patterns
// Lógica de Patterns
const allPatternsNodeList = trackNode.querySelectorAll("pattern");
const allPatternsArray = Array.from(allPatternsNodeList).sort((a, b) => {
return (parseInt(a.getAttribute("pos"), 10) || 0) - (parseInt(b.getAttribute("pos"), 10) || 0);
@@ -120,9 +120,12 @@ function parseInstrumentNode(trackNode, sortedBBTrackNameNodes, pathMap) {
const patternSteps = parseInt(patternNode.getAttribute("steps"), 10) || 16;
const steps = new Array(patternSteps).fill(false);
const notes = [];
const ticksPerStep = 12;
// === CORREÇÃO MATEMÁTICA ===
// No LMMS, 1 semínima (beat) = 192 ticks.
// 1 semicolcheia (1/16 step) = 192 / 4 = 48 ticks.
const ticksPerStep = 48;
// Extrai as notas e popula os steps
patternNode.querySelectorAll("note").forEach((noteNode) => {
const pos = parseInt(noteNode.getAttribute("pos"), 10);
notes.push({
@@ -133,7 +136,7 @@ function parseInstrumentNode(trackNode, sortedBBTrackNameNodes, pathMap) {
pan: parseInt(noteNode.getAttribute("pan"), 10),
});
// Converte posição em tick para índice do step (grid)
// Calcula qual quadradinho acender
const stepIndex = Math.round(pos / ticksPerStep);
if (stepIndex < patternSteps) steps[stepIndex] = true;
});
@@ -146,6 +149,7 @@ function parseInstrumentNode(trackNode, sortedBBTrackNameNodes, pathMap) {
};
});
// Lógica de Sample vs Plugin
let finalSamplePath = null;
let trackType = "plugin";
@@ -178,11 +182,12 @@ function parseInstrumentNode(trackNode, sortedBBTrackNameNodes, pathMap) {
pan: !isNaN(panFromFile) ? panFromFile / 100 : DEFAULT_PAN,
instrumentName: instrumentName,
instrumentXml: instrumentNode.innerHTML,
parentBasslineId: parentBasslineId // Guarda o ID do pai para filtragem na UI
};
}
// =================================================================
// 🔥 FUNÇÃO DE PARSING PRINCIPAL (CORRIGIDA)
// 🔥 FUNÇÃO DE PARSING PRINCIPAL
// =================================================================
export async function parseMmpContent(xmlString) {
resetProjectState();
@@ -224,27 +229,26 @@ export async function parseMmpContent(xmlString) {
}
// -------------------------------------------------------------
// 2. EXTRAÇÃO DE TODOS OS INSTRUMENTOS (RECURSIVO)
// 2. EXTRAÇÃO DE INSTRUMENTOS DA RAIZ (SONG EDITOR)
// -------------------------------------------------------------
// Aqui está a correção: Vamos buscar TODOS os instrumentos (type="0"),
// não importa se estão na raiz ou dentro de uma bassline.
// Isso garante que Kicker, Snare, etc., apareçam no Pattern Editor.
// Pega apenas os instrumentos que estão soltos no Song Editor (não dentro de BBTracks)
const songInstrumentNodes = Array.from(xmlDoc.querySelectorAll('song > trackcontainer > track[type="0"]'));
const allInstrumentNodes = Array.from(xmlDoc.querySelectorAll('track[type="0"]'));
const allInstruments = allInstrumentNodes
.map(node => parseInstrumentNode(node, sortedBBTrackNameNodes, pathMap))
const songTracks = songInstrumentNodes
.map(node => parseInstrumentNode(node, sortedBBTrackNameNodes, pathMap, null)) // null = Sem Pai
.filter(t => t !== null);
// -------------------------------------------------------------
// 3. EXTRAÇÃO DAS TRILHAS DE BASSLINE (CONTAINERS)
// 3. EXTRAÇÃO DAS TRILHAS DE BASSLINE E SEUS FILHOS
// -------------------------------------------------------------
// Isso garante que os blocos azuis apareçam na Playlist.
let allBasslineInstruments = [];
const basslineContainers = bbTrackNodes.map(trackNode => {
const trackName = trackNode.getAttribute("name") || "Beat/Bassline";
const containerId = `bassline_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// Extrai os clipes da timeline (blocos azuis)
// A. Extrai os clipes da timeline (blocos azuis)
const playlistClips = Array.from(trackNode.querySelectorAll(":scope > bbtco")).map(bbtco => {
return {
pos: parseInt(bbtco.getAttribute("pos"), 10) || 0,
@@ -253,22 +257,25 @@ export async function parseMmpContent(xmlString) {
};
});
// Se não tiver clipes, não cria a trilha visual inútil na playlist
// Se não tiver clipes, geralmente é container vazio, mas vamos criar mesmo assim
if (playlistClips.length === 0) return null;
// Extrai também os instrumentos internos apenas para referência (opcional)
// mas não os usamos para renderizar no main list para evitar duplicidade de lógica
// B. Extrai os instrumentos INTERNOS desta Bassline
const internalInstrumentNodes = Array.from(trackNode.querySelectorAll('bbtrack > trackcontainer > track[type="0"]'));
const internalInstruments = internalInstrumentNodes
.map(node => parseInstrumentNode(node, sortedBBTrackNameNodes, pathMap))
.map(node => parseInstrumentNode(node, sortedBBTrackNameNodes, pathMap, containerId)) // Passa ID do Pai
.filter(t => t !== null);
// Acumula na lista geral de instrumentos
allBasslineInstruments.push(...internalInstruments);
return {
id: `bassline_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
id: containerId,
name: trackName,
type: "bassline", // Tipo especial para o audio_ui.js
playlist_clips: playlistClips,
instruments: internalInstruments, // Mantém para o recurso de Double-Click
instruments: internalInstruments, // Mantém referência
volume: 1,
pan: 0,
patterns: [],
@@ -280,13 +287,11 @@ export async function parseMmpContent(xmlString) {
// 4. COMBINAÇÃO E FINALIZAÇÃO
// -------------------------------------------------------------
// A lista final contém:
// 1. Os Instrumentos (para que os steps apareçam no Pattern Editor)
// 2. As Basslines (para que os blocos apareçam na Playlist)
// Colocamos as Basslines no final ou no início, conforme preferência.
// Geralmente, instrumentos primeiro é melhor para o Pattern Editor.
const newTracks = [...allInstruments, ...basslineContainers];
// A lista final plana contém TODOS:
// 1. Instrumentos da Raiz
// 2. Instrumentos dentro de Basslines
// 3. As próprias Basslines (Containers)
const newTracks = [...songTracks, ...allBasslineInstruments, ...basslineContainers];
// Inicializa áudio apenas para instrumentos reais
newTracks.forEach((track) => {
@@ -298,7 +303,7 @@ export async function parseMmpContent(xmlString) {
}
});
// Configura tamanho da timeline baseado nas notas dos instrumentos
// Configura tamanho da timeline
let isFirstTrackWithNotes = true;
newTracks.forEach(track => {
if (track.type !== 'bassline' && isFirstTrackWithNotes) {
@@ -324,8 +329,8 @@ export async function parseMmpContent(xmlString) {
// Atualiza estado global
appState.pattern.tracks = newTracks;
appState.pattern.focusedBasslineId = null; // Reseta o foco
// Seleciona o primeiro instrumento real como ativo
const firstInst = newTracks.find(t => t.type !== 'bassline');
appState.pattern.activeTrackId = firstInst ? firstInst.id : null;
appState.pattern.activePatternIndex = 0;
@@ -367,16 +372,13 @@ function generateXmlFromState() {
head.setAttribute("timesig_denominator", document.getElementById("compasso-b-input").value || 4);
}
// Lógica de exportação simplificada:
// Remove todos os tracks do container BB e recria com base no estado atual.
// Nota: Isso coloca TODOS os instrumentos dentro da Bassline 0 na exportação,
// que é o comportamento padrão simplificado para garantir que tudo seja salvo.
// Exportação Simplificada: Coloca todos os instrumentos reais no primeiro container
const bbTrackContainer = xmlDoc.querySelector('track[type="1"] > bbtrack > trackcontainer');
if (bbTrackContainer) {
bbTrackContainer.querySelectorAll('track[type="0"]').forEach((node) => node.remove());
const tracksXml = appState.pattern.tracks
.filter(t => t.type !== 'bassline') // Ignora container visual
.filter(t => t.type !== 'bassline')
.map((track) => createTrackXml(track))
.join("");
@@ -400,7 +402,7 @@ export function syncPatternStateToServer() {
function createTrackXml(track) {
if (!track.patterns || track.patterns.length === 0) return "";
const ticksPerStep = 12;
const ticksPerStep = 48; // Sincronizado com o parsing
const lmmsVolume = Math.round(track.volume * 100);
const lmmsPan = Math.round(track.pan * 100);
@@ -451,42 +453,8 @@ function modifyAndSaveExistingMmp() {
}
function generateNewMmp() {
const bpm = document.getElementById("bpm-input").value;
const sig_num = document.getElementById("compasso-a-input").value;
const sig_den = document.getElementById("compasso-b-input").value;
const num_bars = document.getElementById("bars-input").value;
const tracksXml = appState.pattern.tracks
.filter(t => t.type !== 'bassline')
.map((track) => createTrackXml(track))
.join("");
const mmpContent = `<?xml version="1.0"?>
<!DOCTYPE lmms-project>
<lmms-project version="1.0" type="song" creator="MMPCreator" creatorversion="1.0">
<head mastervol="100" timesig_denominator="${sig_den}" bpm="${bpm}" timesig_numerator="${sig_num}" masterpitch="0" num_bars="${num_bars}"/>
<song>
<trackcontainer type="song">
<track type="1" solo="0" muted="0" name="Beat/Bassline 0">
<bbtrack>
<trackcontainer type="bbtrackcontainer">
${tracksXml}
</trackcontainer>
</bbtrack>
<bbtco color="4286611584" len="192" usestyle="1" pos="0" muted="0" name="Pattern 1"/>
</track>
</trackcontainer>
<timeline lp1pos="192" lp0pos="0" lpstate="0"/>
<controllers/>
<projectnotes width="686" y="10" minimized="0" visible="0" x="700" maximized="0" height="400"><![CDATA[<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
p, li { white-space: pre-wrap; }
</style></head><body>
<p>Feito com MMPCreator</p>
</body></html>]]></projectnotes>
</song>
</lmms-project>`;
downloadFile(mmpContent, "novo_projeto.mmp");
const content = generateXmlFromState();
downloadFile(content, "novo_projeto.mmp");
}
function downloadFile(content, fileName) {