tentando obter playlist real do projeto mmp
This commit is contained in:
1 parent
3380b642b3
commit
7b569c1bde
4 files changed
+203
-223
No files matched your search
+43
-75
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user