tentando obter playlist real do projeto mmp
Deploy / Deploy (push) Successful in 2m1s Details

This commit is contained in:
JotaChina 2025-12-20 14:17:39 -03:00
parent 57ec600307
commit 73721f7b9b
1 changed files with 187 additions and 124 deletions

View File

@ -1,3 +1,5 @@
// js/creations/file.js
//-------------------------------------------------------------- //--------------------------------------------------------------
// IMPORTS NECESSÁRIOS // IMPORTS NECESSÁRIOS
//-------------------------------------------------------------- //--------------------------------------------------------------
@ -35,7 +37,6 @@ export function handleLocalProjectReset() {
const bpmInput = document.getElementById("bpm-input"); const bpmInput = document.getElementById("bpm-input");
if(bpmInput) bpmInput.value = 140; if(bpmInput) bpmInput.value = 140;
// Reseta inputs visuais se existirem
["bars-input", "compasso-a-input", "compasso-b-input"].forEach(id => { ["bars-input", "compasso-a-input", "compasso-b-input"].forEach(id => {
const el = document.getElementById(id); const el = document.getElementById(id);
if(el) el.value = (id === "bars-input") ? 1 : 4; if(el) el.value = (id === "bars-input") ? 1 : 4;
@ -87,7 +88,99 @@ export async function loadProjectFromServer(fileName) {
} }
// ================================================================= // =================================================================
// 🔥 FUNÇÃO DE PARSING PRINCIPAL (CORRIGIDA) // FUNÇÃO AUXILIAR: PARSE DE INSTRUMENTO ÚNICO
// =================================================================
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
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);
});
const patternsToCreate = sortedBBTrackNameNodes.length > 0
? sortedBBTrackNameNodes
: [{ getAttribute: () => "Pattern 1" }];
const patterns = patternsToCreate.map((bbTrack, index) => {
const patternNode = allPatternsArray[index];
const bbTrackName = bbTrack.getAttribute("name") || `Pattern ${index + 1}`;
if (!patternNode) {
return { name: bbTrackName, steps: new Array(16).fill(false), notes: [], pos: 0 };
}
const patternSteps = parseInt(patternNode.getAttribute("steps"), 10) || 16;
const steps = new Array(patternSteps).fill(false);
const notes = [];
const ticksPerStep = 12;
patternNode.querySelectorAll("note").forEach((noteNode) => {
notes.push({
pos: parseInt(noteNode.getAttribute("pos"), 10),
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);
if (stepIndex < patternSteps) steps[stepIndex] = true;
});
return {
name: bbTrackName,
steps: steps,
notes: notes,
pos: parseInt(patternNode.getAttribute("pos"), 10) || 0,
};
});
// Lógica de Sample vs Plugin
let finalSamplePath = null;
let trackType = "plugin";
if (instrumentName === "audiofileprocessor") {
trackType = "sampler";
const afpNode = instrumentNode.querySelector("audiofileprocessor");
const sampleSrc = afpNode ? afpNode.getAttribute("src") : null;
if (sampleSrc) {
const filename = sampleSrc.split("/").pop();
if (pathMap[filename]) {
finalSamplePath = pathMap[filename];
} else {
let cleanSrc = sampleSrc.startsWith("samples/") ? sampleSrc.substring("samples/".length) : sampleSrc;
finalSamplePath = `src/samples/${cleanSrc}`;
}
}
}
const volFromFile = parseFloat(instrumentTrackNode.getAttribute("vol"));
const panFromFile = parseFloat(instrumentTrackNode.getAttribute("pan"));
return {
id: Date.now() + Math.random(),
name: trackName,
type: trackType,
samplePath: finalSamplePath,
patterns: patterns,
activePatternIndex: 0,
volume: !isNaN(volFromFile) ? volFromFile / 100 : DEFAULT_VOLUME,
pan: !isNaN(panFromFile) ? panFromFile / 100 : DEFAULT_PAN,
instrumentName: instrumentName,
instrumentXml: instrumentNode.innerHTML,
};
}
// =================================================================
// 🔥 FUNÇÃO DE PARSING PRINCIPAL
// ================================================================= // =================================================================
export async function parseMmpContent(xmlString) { export async function parseMmpContent(xmlString) {
resetProjectState(); resetProjectState();
@ -105,7 +198,6 @@ export async function parseMmpContent(xmlString) {
appState.global.originalXmlDoc = xmlDoc; appState.global.originalXmlDoc = xmlDoc;
let newTracks = []; let newTracks = [];
// Configuração Global (BPM, Compasso)
const head = xmlDoc.querySelector("head"); const head = xmlDoc.querySelector("head");
if (head) { if (head) {
const setVal = (id, attr, def) => { const setVal = (id, attr, def) => {
@ -120,167 +212,97 @@ export async function parseMmpContent(xmlString) {
const pathMap = getSamplePathMap(); const pathMap = getSamplePathMap();
// ------------------------------------------------------------- // -------------------------------------------------------------
// 1. EXTRAÇÃO DE INSTRUMENTOS (Normal) // 1. PREPARAÇÃO
// ------------------------------------------------------------- // -------------------------------------------------------------
// Seleciona todos os instrumentos (Song Editor + Beat/Bassline internos)
// Nota: Isso pega os instrumentos DENTRO das basslines, o que é correto para o Pattern Editor.
const allInstrumentTrackNodes = Array.from(xmlDoc.querySelectorAll('track[type="0"]'));
// Identifica colunas de beat (bbtco) para dar nome aos patterns // Identifica colunas de beat (bbtco) para dar nome aos patterns
const bbTrackNodes = Array.from(xmlDoc.querySelectorAll('track[type="1"]')); const bbTrackNodes = Array.from(xmlDoc.querySelectorAll('track[type="1"]'));
let sortedBBTrackNameNodes = []; let sortedBBTrackNameNodes = [];
if (bbTrackNodes.length > 0) { if (bbTrackNodes.length > 0) {
// Pega do primeiro container para usar como referência de nomes de coluna
sortedBBTrackNameNodes = Array.from(bbTrackNodes[0].querySelectorAll("bbtco")).sort((a, b) => { sortedBBTrackNameNodes = Array.from(bbTrackNodes[0].querySelectorAll("bbtco")).sort((a, b) => {
return (parseInt(a.getAttribute("pos"), 10) || 0) - (parseInt(b.getAttribute("pos"), 10) || 0); return (parseInt(a.getAttribute("pos"), 10) || 0) - (parseInt(b.getAttribute("pos"), 10) || 0);
}); });
} }
// --- Processamento dos Instrumentos --- // -------------------------------------------------------------
const instrumentTracks = allInstrumentTrackNodes.map((trackNode) => { // 2. EXTRAÇÃO DE INSTRUMENTOS (Song Editor)
const instrumentNode = trackNode.querySelector("instrument"); // -------------------------------------------------------------
const instrumentTrackNode = trackNode.querySelector("instrumenttrack"); // Pega apenas os instrumentos que estão soltos no Song Editor (não dentro de BBTracks)
if (!instrumentNode || !instrumentTrackNode) return null; const songInstrumentNodes = Array.from(xmlDoc.querySelectorAll('song > trackcontainer > track[type="0"]'));
const trackName = trackNode.getAttribute("name"); const songTracks = songInstrumentNodes
const instrumentName = instrumentNode.getAttribute("name"); .map(node => parseInstrumentNode(node, sortedBBTrackNameNodes, pathMap))
.filter(t => t !== null);
// ... (Lógica de Patterns Mantida) ...
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);
});
const patternsToCreate = sortedBBTrackNameNodes.length > 0
? sortedBBTrackNameNodes
: [{ getAttribute: () => "Pattern 1" }];
const patterns = patternsToCreate.map((bbTrack, index) => {
const patternNode = allPatternsArray[index];
const bbTrackName = bbTrack.getAttribute("name") || `Pattern ${index + 1}`;
if (!patternNode) {
return { name: bbTrackName, steps: new Array(16).fill(false), notes: [], pos: 0 };
}
const patternSteps = parseInt(patternNode.getAttribute("steps"), 10) || 16;
const steps = new Array(patternSteps).fill(false);
const notes = [];
const ticksPerStep = 12;
patternNode.querySelectorAll("note").forEach((noteNode) => {
notes.push({
pos: parseInt(noteNode.getAttribute("pos"), 10),
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);
if (stepIndex < patternSteps) steps[stepIndex] = true;
});
return {
name: bbTrackName,
steps: steps,
notes: notes,
pos: parseInt(patternNode.getAttribute("pos"), 10) || 0,
};
});
// Lógica de Sample vs Plugin
let finalSamplePath = null;
let trackType = "plugin";
if (instrumentName === "audiofileprocessor") {
trackType = "sampler";
const afpNode = instrumentNode.querySelector("audiofileprocessor");
const sampleSrc = afpNode ? afpNode.getAttribute("src") : null;
if (sampleSrc) {
const filename = sampleSrc.split("/").pop();
if (pathMap[filename]) {
finalSamplePath = pathMap[filename];
} else {
let cleanSrc = sampleSrc.startsWith("samples/") ? sampleSrc.substring("samples/".length) : sampleSrc;
finalSamplePath = `src/samples/${cleanSrc}`;
}
}
}
const volFromFile = parseFloat(instrumentTrackNode.getAttribute("vol"));
const panFromFile = parseFloat(instrumentTrackNode.getAttribute("pan"));
return {
id: Date.now() + Math.random(),
name: trackName,
type: trackType,
samplePath: finalSamplePath,
patterns: patterns,
activePatternIndex: 0,
volume: !isNaN(volFromFile) ? volFromFile / 100 : DEFAULT_VOLUME,
pan: !isNaN(panFromFile) ? panFromFile / 100 : DEFAULT_PAN,
instrumentName: instrumentName,
instrumentXml: instrumentNode.innerHTML,
};
}).filter(t => t !== null);
// ------------------------------------------------------------- // -------------------------------------------------------------
// 2. EXTRAÇÃO DAS TRILHAS DE BASSLINE (Playlist Container) // 3. EXTRAÇÃO DAS TRILHAS DE BASSLINE (E SEUS INSTRUMENTOS)
// ------------------------------------------------------------- // -------------------------------------------------------------
// 👇 AQUI ESTAVA FALTANDO! 👇
const basslineTracks = bbTrackNodes.map(trackNode => { const basslineTracks = bbTrackNodes.map(trackNode => {
const trackName = trackNode.getAttribute("name") || "Beat/Bassline"; const trackName = trackNode.getAttribute("name") || "Beat/Bassline";
// Extrai os clipes da timeline (tags <bbtco>) // A. Extrai os clipes da timeline (blocos azuis)
// Eles são filhos diretos de <track type="1">
const playlistClips = Array.from(trackNode.querySelectorAll(":scope > bbtco")).map(bbtco => { const playlistClips = Array.from(trackNode.querySelectorAll(":scope > bbtco")).map(bbtco => {
return { return {
pos: parseInt(bbtco.getAttribute("pos"), 10) || 0, pos: parseInt(bbtco.getAttribute("pos"), 10) || 0,
len: parseInt(bbtco.getAttribute("len"), 10) || 192, len: parseInt(bbtco.getAttribute("len"), 10) || 192,
name: trackName // O clipe leva o nome da trilha (ex: "Caixa") name: trackName
}; };
}); });
// Se não tiver clipes, não precisa criar a trilha visual na playlist, // B. Extrai os instrumentos INTERNOS desta Bassline
// mas mantemos para consistência se desejar. // Eles estão dentro de <bbtrack><trackcontainer><track type="0">
if (playlistClips.length === 0) return null; const internalInstrumentNodes = Array.from(trackNode.querySelectorAll('bbtrack > trackcontainer > track[type="0"]'));
const internalInstruments = internalInstrumentNodes
.map(node => parseInstrumentNode(node, sortedBBTrackNameNodes, pathMap))
.filter(t => t !== null);
return { return {
id: `bassline_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, id: `bassline_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
name: trackName, name: trackName,
type: "bassline", // Tipo especial que o audio_ui.js reconhece type: "bassline",
playlist_clips: playlistClips, playlist_clips: playlistClips,
// Propriedades dummy para não quebrar o pattern_state/ui instruments: internalInstruments, // <--- AQUI ESTÁ A CORREÇÃO CRUCIAL!
// Fallbacks para evitar crashes
volume: 1, volume: 1,
pan: 0, pan: 0,
patterns: [], patterns: [],
isMuted: trackNode.getAttribute("muted") === "1" isMuted: trackNode.getAttribute("muted") === "1"
}; };
}).filter(t => t !== null); }).filter(t => t !== null && (t.playlist_clips.length > 0 || t.instruments.length > 0));
// Une tudo: Instrumentos + Basslines // Une tudo: Instrumentos Soltos + Basslines
// Adicionamos as basslines ao final para organização newTracks = [...songTracks, ...basslineTracks];
newTracks = [...instrumentTracks, ...basslineTracks];
// ------------------------------------------------------------- // -------------------------------------------------------------
// 3. INICIALIZAÇÃO DE ÁUDIO E ESTADO // 4. INICIALIZAÇÃO DE ÁUDIO E ESTADO
// ------------------------------------------------------------- // -------------------------------------------------------------
// Inicializa nós de áudio para os instrumentos (ignora basslines pois não tocam áudio direto) // Inicializa nós de áudio
newTracks.forEach((track) => { newTracks.forEach((track) => {
// Se for instrumento normal
if (track.type !== 'bassline') { if (track.type !== 'bassline') {
track.volumeNode = new Tone.Volume(Tone.gainToDb(track.volume)); track.volumeNode = new Tone.Volume(Tone.gainToDb(track.volume));
track.pannerNode = new Tone.Panner(track.pan); track.pannerNode = new Tone.Panner(track.pan);
track.volumeNode.connect(track.pannerNode); track.volumeNode.connect(track.pannerNode);
track.pannerNode.connect(getMainGainNode()); 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 baseado no conteúdo // Configura tamanho da timeline
let isFirstTrackWithNotes = true; let isFirstTrackWithNotes = true;
newTracks.forEach(track => { newTracks.forEach(track => {
// Verifica track normal
if (track.type !== 'bassline' && isFirstTrackWithNotes) { if (track.type !== 'bassline' && isFirstTrackWithNotes) {
const activePattern = track.patterns[track.activePatternIndex || 0]; const activePattern = track.patterns[track.activePatternIndex || 0];
if (activePattern && activePattern.steps && activePattern.steps.length > 0) { if (activePattern && activePattern.steps && activePattern.steps.length > 0) {
@ -290,23 +312,55 @@ export async function parseMmpContent(xmlString) {
isFirstTrackWithNotes = false; 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 // Carrega samples/plugins (Async)
try { try {
const trackLoadPromises = newTracks const promises = [];
.filter(t => t.type !== 'bassline') // Apenas instrumentos reais
.map(track => loadAudioForTrack(track)); newTracks.forEach(track => {
await Promise.all(trackLoadPromises); if (track.type !== 'bassline') {
promises.push(loadAudioForTrack(track));
} else {
// Carrega áudio dos instrumentos internos da bassline
track.instruments.forEach(inst => {
promises.push(loadAudioForTrack(inst));
});
}
});
await Promise.all(promises);
} catch (error) { } catch (error) {
console.error("Ocorreu um erro ao carregar os áudios do projeto:", error); console.error("Ocorreu um erro ao carregar os áudios do projeto:", error);
} }
// Atualiza estado global // Atualiza estado global
appState.pattern.tracks = newTracks; appState.pattern.tracks = newTracks;
// Define o track ativo como o primeiro instrumento real encontrado
const firstInstrument = newTracks.find(t => t.type !== 'bassline'); // Define faixa ativa (tenta pegar a primeira normal ou a primeira de dentro da bassline)
appState.pattern.activeTrackId = firstInstrument ? firstInstrument.id : null; 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;
appState.pattern.activePatternIndex = 0; appState.pattern.activePatternIndex = 0;
loadStateFromSession(); loadStateFromSession();
@ -351,9 +405,14 @@ function generateXmlFromState() {
if (bbTrackContainer) { if (bbTrackContainer) {
bbTrackContainer.querySelectorAll('track[type="0"]').forEach((node) => node.remove()); bbTrackContainer.querySelectorAll('track[type="0"]').forEach((node) => node.remove());
// Apenas instrumentos reais vão para dentro do bbtrack // 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 const tracksXml = appState.pattern.tracks
.filter(t => t.type !== 'bassline') .flatMap(track => {
if (track.type === 'bassline') return track.instruments;
return [track];
})
.filter(t => t && t.type !== 'bassline')
.map((track) => createTrackXml(track)) .map((track) => createTrackXml(track))
.join(""); .join("");
@ -434,7 +493,11 @@ function generateNewMmp() {
const num_bars = document.getElementById("bars-input").value; const num_bars = document.getElementById("bars-input").value;
const tracksXml = appState.pattern.tracks const tracksXml = appState.pattern.tracks
.filter(t => t.type !== 'bassline') .flatMap(track => {
if (track.type === 'bassline') return track.instruments;
return [track];
})
.filter(t => t && t.type !== 'bassline')
.map((track) => createTrackXml(track)) .map((track) => createTrackXml(track))
.join(""); .join("");