tentando obter playlist real do projeto mmp
Deploy / Deploy (push) Successful in 1m53s Details

This commit is contained in:
JotaChina 2025-12-20 14:37:12 -03:00
parent 73721f7b9b
commit 09c3ee742a
1 changed files with 56 additions and 96 deletions

View File

@ -94,13 +94,12 @@ function parseInstrumentNode(trackNode, sortedBBTrackNameNodes, pathMap) {
const instrumentNode = trackNode.querySelector("instrument");
const instrumentTrackNode = trackNode.querySelector("instrumenttrack");
// Se não tiver instrumento ou track válida, retorna null
if (!instrumentNode || !instrumentTrackNode) return null;
const trackName = trackNode.getAttribute("name");
const instrumentName = instrumentNode.getAttribute("name");
// Lógica de Patterns
// Identifica e ordena os 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);
@ -123,15 +122,19 @@ function parseInstrumentNode(trackNode, sortedBBTrackNameNodes, pathMap) {
const notes = [];
const ticksPerStep = 12;
// Extrai as notas e popula os steps
patternNode.querySelectorAll("note").forEach((noteNode) => {
const pos = parseInt(noteNode.getAttribute("pos"), 10);
notes.push({
pos: parseInt(noteNode.getAttribute("pos"), 10),
pos: pos,
len: parseInt(noteNode.getAttribute("len"), 10),
key: parseInt(noteNode.getAttribute("key"), 10),
vol: parseInt(noteNode.getAttribute("vol"), 10),
pan: parseInt(noteNode.getAttribute("pan"), 10),
});
const stepIndex = Math.round(parseInt(noteNode.getAttribute("pos"), 10) / ticksPerStep);
// Converte posição em tick para índice do step (grid)
const stepIndex = Math.round(pos / ticksPerStep);
if (stepIndex < patternSteps) steps[stepIndex] = true;
});
@ -143,7 +146,6 @@ function parseInstrumentNode(trackNode, sortedBBTrackNameNodes, pathMap) {
};
});
// Lógica de Sample vs Plugin
let finalSamplePath = null;
let trackType = "plugin";
@ -180,7 +182,7 @@ function parseInstrumentNode(trackNode, sortedBBTrackNameNodes, pathMap) {
}
// =================================================================
// 🔥 FUNÇÃO DE PARSING PRINCIPAL
// 🔥 FUNÇÃO DE PARSING PRINCIPAL (CORRIGIDA)
// =================================================================
export async function parseMmpContent(xmlString) {
resetProjectState();
@ -196,8 +198,8 @@ export async function parseMmpContent(xmlString) {
const xmlDoc = parser.parseFromString(xmlString, "application/xml");
appState.global.originalXmlDoc = xmlDoc;
let newTracks = [];
// Configuração Global (BPM, Compasso)
const head = xmlDoc.querySelector("head");
if (head) {
const setVal = (id, attr, def) => {
@ -211,11 +213,8 @@ export async function parseMmpContent(xmlString) {
const pathMap = getSamplePathMap();
// -------------------------------------------------------------
// 1. PREPARAÇÃO
// -------------------------------------------------------------
// Identifica colunas de beat (bbtco) para dar nome aos patterns
// 1. Identifica colunas de beat/patterns (usado para mapear steps)
// Normalmente ficam dentro do primeiro container de Bassline
const bbTrackNodes = Array.from(xmlDoc.querySelectorAll('track[type="1"]'));
let sortedBBTrackNameNodes = [];
if (bbTrackNodes.length > 0) {
@ -225,23 +224,27 @@ export async function parseMmpContent(xmlString) {
}
// -------------------------------------------------------------
// 2. EXTRAÇÃO DE INSTRUMENTOS (Song Editor)
// 2. EXTRAÇÃO DE TODOS OS INSTRUMENTOS (RECURSIVO)
// -------------------------------------------------------------
// 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"]'));
// 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.
const songTracks = songInstrumentNodes
const allInstrumentNodes = Array.from(xmlDoc.querySelectorAll('track[type="0"]'));
const allInstruments = allInstrumentNodes
.map(node => parseInstrumentNode(node, sortedBBTrackNameNodes, pathMap))
.filter(t => t !== null);
// -------------------------------------------------------------
// 3. EXTRAÇÃO DAS TRILHAS DE BASSLINE (E SEUS INSTRUMENTOS)
// 3. EXTRAÇÃO DAS TRILHAS DE BASSLINE (CONTAINERS)
// -------------------------------------------------------------
// Isso garante que os blocos azuis apareçam na Playlist.
const basslineTracks = bbTrackNodes.map(trackNode => {
const basslineContainers = bbTrackNodes.map(trackNode => {
const trackName = trackNode.getAttribute("name") || "Beat/Bassline";
// A. Extrai os clipes da timeline (blocos azuis)
// 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,
@ -250,10 +253,12 @@ export async function parseMmpContent(xmlString) {
};
});
// B. Extrai os instrumentos INTERNOS desta Bassline
// Eles estão dentro de <bbtrack><trackcontainer><track type="0">
const internalInstrumentNodes = Array.from(trackNode.querySelectorAll('bbtrack > trackcontainer > track[type="0"]'));
// Se não tiver clipes, não cria a trilha visual inútil na playlist
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
const internalInstrumentNodes = Array.from(trackNode.querySelectorAll('bbtrack > trackcontainer > track[type="0"]'));
const internalInstruments = internalInstrumentNodes
.map(node => parseInstrumentNode(node, sortedBBTrackNameNodes, pathMap))
.filter(t => t !== null);
@ -261,48 +266,41 @@ export async function parseMmpContent(xmlString) {
return {
id: `bassline_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
name: trackName,
type: "bassline",
type: "bassline", // Tipo especial para o audio_ui.js
playlist_clips: playlistClips,
instruments: internalInstruments, // <--- AQUI ESTÁ A CORREÇÃO CRUCIAL!
// Fallbacks para evitar crashes
instruments: internalInstruments, // Mantém para o recurso de Double-Click
volume: 1,
pan: 0,
patterns: [],
isMuted: trackNode.getAttribute("muted") === "1"
};
}).filter(t => t !== null && (t.playlist_clips.length > 0 || t.instruments.length > 0));
// Une tudo: Instrumentos Soltos + Basslines
newTracks = [...songTracks, ...basslineTracks];
}).filter(t => t !== null);
// -------------------------------------------------------------
// 4. INICIALIZAÇÃO DE ÁUDIO E ESTADO
// 4. COMBINAÇÃO E FINALIZAÇÃO
// -------------------------------------------------------------
// Inicializa nós de áudio
// 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];
// Inicializa áudio apenas para instrumentos reais
newTracks.forEach((track) => {
// Se for instrumento normal
if (track.type !== 'bassline') {
track.volumeNode = new Tone.Volume(Tone.gainToDb(track.volume));
track.pannerNode = new Tone.Panner(track.pan);
track.volumeNode.connect(track.pannerNode);
track.pannerNode.connect(getMainGainNode());
}
// Se for Bassline, inicializa os nós dos instrumentos INTERNOS
else if (track.instruments && track.instruments.length > 0) {
track.instruments.forEach(inst => {
inst.volumeNode = new Tone.Volume(Tone.gainToDb(inst.volume));
inst.pannerNode = new Tone.Panner(inst.pan);
inst.volumeNode.connect(inst.pannerNode);
inst.pannerNode.connect(getMainGainNode());
});
}
});
// Configura tamanho da timeline
// Configura tamanho da timeline baseado nas notas dos instrumentos
let isFirstTrackWithNotes = true;
newTracks.forEach(track => {
// Verifica track normal
if (track.type !== 'bassline' && isFirstTrackWithNotes) {
const activePattern = track.patterns[track.activePatternIndex || 0];
if (activePattern && activePattern.steps && activePattern.steps.length > 0) {
@ -312,55 +310,24 @@ export async function parseMmpContent(xmlString) {
isFirstTrackWithNotes = false;
}
}
// Verifica dentro da bassline
else if (track.type === 'bassline' && isFirstTrackWithNotes && track.instruments.length > 0) {
const firstInst = track.instruments[0];
const activePattern = firstInst.patterns[firstInst.activePatternIndex || 0];
if (activePattern && activePattern.steps && activePattern.steps.length > 0) {
const bars = Math.ceil(activePattern.steps.length / 16);
const barsInput = document.getElementById("bars-input");
if(barsInput) barsInput.value = bars > 0 ? bars : 1;
isFirstTrackWithNotes = false;
}
}
});
// Carrega samples/plugins (Async)
// Carrega samples/plugins
try {
const promises = [];
newTracks.forEach(track => {
if (track.type !== 'bassline') {
promises.push(loadAudioForTrack(track));
} else {
// Carrega áudio dos instrumentos internos da bassline
track.instruments.forEach(inst => {
promises.push(loadAudioForTrack(inst));
});
}
});
const promises = newTracks
.filter(t => t.type !== 'bassline')
.map(track => loadAudioForTrack(track));
await Promise.all(promises);
} catch (error) {
console.error("Ocorreu um erro ao carregar os áudios do projeto:", error);
console.error("Erro ao carregar áudios:", error);
}
// Atualiza estado global
appState.pattern.tracks = newTracks;
// Define faixa ativa (tenta pegar a primeira normal ou a primeira de dentro da bassline)
let firstInstrumentId = null;
const firstInstTrack = newTracks.find(t => t.type !== 'bassline');
if (firstInstTrack) {
firstInstrumentId = firstInstTrack.id;
} else {
const firstBassline = newTracks.find(t => t.type === 'bassline' && t.instruments.length > 0);
if (firstBassline) {
firstInstrumentId = firstBassline.instruments[0].id;
}
}
appState.pattern.activeTrackId = firstInstrumentId;
// 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;
loadStateFromSession();
@ -383,7 +350,6 @@ export function generateMmpFile() {
function generateXmlFromState() {
if (!appState.global.originalXmlDoc) {
console.log("Gerando XML a partir do template em branco...");
const parser = new DOMParser();
appState.global.originalXmlDoc = parser.parseFromString(
DEFAULT_PROJECT_XML,
@ -401,18 +367,16 @@ 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.
const bbTrackContainer = xmlDoc.querySelector('track[type="1"] > bbtrack > trackcontainer');
if (bbTrackContainer) {
bbTrackContainer.querySelectorAll('track[type="0"]').forEach((node) => node.remove());
// Procura por instrumentos que estão dentro de objetos bassline no state
// (Esta lógica de exportação é básica e pode precisar de ajustes se você editar muito os instrumentos internos)
const tracksXml = appState.pattern.tracks
.flatMap(track => {
if (track.type === 'bassline') return track.instruments;
return [track];
})
.filter(t => t && t.type !== 'bassline')
.filter(t => t.type !== 'bassline') // Ignora container visual
.map((track) => createTrackXml(track))
.join("");
@ -493,11 +457,7 @@ function generateNewMmp() {
const num_bars = document.getElementById("bars-input").value;
const tracksXml = appState.pattern.tracks
.flatMap(track => {
if (track.type === 'bassline') return track.instruments;
return [track];
})
.filter(t => t && t.type !== 'bassline')
.filter(t => t.type !== 'bassline')
.map((track) => createTrackXml(track))
.join("");