diff --git a/assets/js/creations/file.js b/assets/js/creations/file.js index 75409de1..e29627dc 100755 --- a/assets/js/creations/file.js +++ b/assets/js/creations/file.js @@ -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 + // 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 // ------------------------------------------------------------- + + // 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 nós de áudio + // 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) { @@ -311,56 +309,25 @@ export async function parseMmpContent(xmlString) { if(barsInput) barsInput.value = bars > 0 ? bars : 1; 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("");