att upload

This commit is contained in:
JotaChina committed 2025-12-04 20:48:05 -03:00
1 parent 33a2a3acdf
commit 3a9e9547b9
63 files changed
+44080 -907767

No files matched your search

+116 -66
View File
@@ -1,15 +1,17 @@
//--------------------------------------------------------------
// IMPORTS NECESSÁRIOS
//--------------------------------------------------------------
import { appState, saveStateToSession, resetProjectState, loadStateFromSession } from "./state.js";
import {
appState,
saveStateToSession,
resetProjectState,
loadStateFromSession,
} from "./state.js";
import { loadAudioForTrack } from "./pattern/pattern_state.js";
import { renderAll, getSamplePathMap } from "./ui.js";
import { DEFAULT_PAN, DEFAULT_VOLUME, NOTE_LENGTH } from "./config.js";
import {
initializeAudioContext,
getMainGainNode,
} from "./audio.js";
import { DEFAULT_PROJECT_XML } from "./utils.js"
import { initializeAudioContext, getMainGainNode } from "./audio.js";
import { DEFAULT_PROJECT_XML } from "./utils.js";
import * as Tone from "https://esm.sh/tone";
import { sendAction } from "./socket.js";
@@ -72,7 +74,7 @@ export async function loadProjectFromServer(fileName) {
const xmlContent = await response.text();
sendAction({ type: "LOAD_PROJECT", xml: xmlContent });
return true;
return true;
} catch (error) {
console.error("Erro ao carregar projeto do servidor:", error);
alert(`Erro ao carregar projeto: ${error.message}`);
@@ -84,7 +86,7 @@ export async function parseMmpContent(xmlString) {
resetProjectState();
initializeAudioContext();
appState.global.justReset = xmlString === DEFAULT_PROJECT_XML;
const audioContainer = document.getElementById("audio-track-container");
if (audioContainer) {
audioContainer.innerHTML = "";
@@ -110,7 +112,9 @@ export async function parseMmpContent(xmlString) {
// 1. Identifica as faixas containers de Beat/Bassline (Type 1)
const bbEditorTrackNodes = Array.from(
xmlDoc.querySelectorAll('song > trackcontainer[type="song"] > track[type="1"]')
xmlDoc.querySelectorAll(
'song > trackcontainer[type="song"] > track[type="1"]'
)
);
// 2. Identifica os nomes dos patterns (colunas do B/B Editor) - tag <bbtco>
@@ -118,33 +122,41 @@ export async function parseMmpContent(xmlString) {
let sortedBBTrackNameNodes = [];
if (bbEditorTrackNodes.length > 0) {
// Pega do primeiro editor encontrado
sortedBBTrackNameNodes = Array.from(bbEditorTrackNodes[0].querySelectorAll("bbtco"))
.sort((a, b) => {
const posA = parseInt(a.getAttribute("pos"), 10) || 0;
const posB = parseInt(b.getAttribute("pos"), 10) || 0;
return posA - posB;
});
sortedBBTrackNameNodes = Array.from(
bbEditorTrackNodes[0].querySelectorAll("bbtco")
).sort((a, b) => {
const posA = parseInt(a.getAttribute("pos"), 10) || 0;
const posB = parseInt(b.getAttribute("pos"), 10) || 0;
return posA - posB;
});
}
// 3. Identifica os instrumentos dentro do Beat/Bassline (Type 0 aninhado)
const bbInstrumentTracks = [];
bbEditorTrackNodes.forEach(container => {
const instruments = container.querySelectorAll('bbtrack > trackcontainer > track[type="0"]');
bbEditorTrackNodes.forEach((container) => {
const instruments = container.querySelectorAll(
'bbtrack > trackcontainer > track[type="0"]'
);
bbInstrumentTracks.push(...Array.from(instruments));
});
// 4. Identifica os instrumentos do Song Editor (Type 0 direto)
const songInstrumentTracks = Array.from(
xmlDoc.querySelectorAll('song > trackcontainer[type="song"] > track[type="0"]')
xmlDoc.querySelectorAll(
'song > trackcontainer[type="song"] > track[type="0"]'
)
);
// Junta tudo
const allInstrumentTrackNodes = [...bbInstrumentTracks, ...songInstrumentTracks];
const allInstrumentTrackNodes = [
...bbInstrumentTracks,
...songInstrumentTracks,
];
if (allInstrumentTrackNodes.length === 0) {
appState.pattern.tracks = [];
renderAll();
return;
appState.pattern.tracks = [];
renderAll();
return;
}
// Define um nome padrão para referência
@@ -170,11 +182,15 @@ export async function parseMmpContent(xmlString) {
// Mapeia os patterns baseados nas colunas do B/B editor (sortedBBTrackNameNodes)
// Se não houver colunas B/B (ex: projeto só Song Editor), cria 1 pattern padrão
const patternsToCreate = sortedBBTrackNameNodes.length > 0 ? sortedBBTrackNameNodes : [{ getAttribute: () => "Pattern 1" }];
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}`;
const bbTrackName =
bbTrack.getAttribute("name") || `Pattern ${index + 1}`;
if (!patternNode) {
return {
@@ -185,10 +201,11 @@ export async function parseMmpContent(xmlString) {
};
}
const patternSteps = parseInt(patternNode.getAttribute("steps"), 10) || 16;
const patternSteps =
parseInt(patternNode.getAttribute("steps"), 10) || 16;
const steps = new Array(patternSteps).fill(false);
const notes = [];
const notes = [];
const ticksPerStep = 12;
patternNode.querySelectorAll("note").forEach((noteNode) => {
@@ -203,7 +220,7 @@ export async function parseMmpContent(xmlString) {
len: len,
key: key,
vol: vol,
pan: pan
pan: pan,
});
const stepIndex = Math.round(pos / ticksPerStep);
@@ -215,25 +232,27 @@ export async function parseMmpContent(xmlString) {
return {
name: bbTrackName,
steps: steps,
notes: notes,
notes: notes,
pos: parseInt(patternNode.getAttribute("pos"), 10) || 0,
};
});
// Verifica se tem notas em algum pattern
const hasNotes = patterns.some((p) => p.notes.length > 0 || p.steps.includes(true));
const hasNotes = patterns.some(
(p) => p.notes.length > 0 || p.steps.includes(true)
);
// Opcional: Se quiser carregar tracks vazias, remova a linha abaixo
if (!hasNotes && patterns.length === 0) return null;
let finalSamplePath = null;
let trackType = "plugin";
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]) {
@@ -246,7 +265,7 @@ export async function parseMmpContent(xmlString) {
finalSamplePath = `src/samples/${cleanSrc}`;
}
}
}
}
const volFromFile = parseFloat(instrumentTrackNode.getAttribute("vol"));
const panFromFile = parseFloat(instrumentTrackNode.getAttribute("pan"));
@@ -280,7 +299,8 @@ export async function parseMmpContent(xmlString) {
if (activePattern && activePattern.steps) {
const stepsLength = activePattern.steps.length;
const requiredBars = Math.ceil(stepsLength / 16);
document.getElementById("bars-input").value = requiredBars > 0 ? requiredBars : 1;
document.getElementById("bars-input").value =
requiredBars > 0 ? requiredBars : 1;
isFirstTrackWithNotes = false;
}
}
@@ -299,7 +319,7 @@ export async function parseMmpContent(xmlString) {
appState.pattern.activeTrackId = appState.pattern.tracks[0]?.id || null;
appState.pattern.activePatternIndex = 0;
loadStateFromSession();
loadStateFromSession();
await Promise.resolve();
renderAll();
@@ -317,32 +337,51 @@ 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, "application/xml");
appState.global.originalXmlDoc = parser.parseFromString(
DEFAULT_PROJECT_XML,
"application/xml"
);
}
const xmlDoc = appState.global.originalXmlDoc.cloneNode(true);
const head = xmlDoc.querySelector("head");
if (head) {
head.setAttribute("bpm", document.getElementById("bpm-input").value || 140);
head.setAttribute("num_bars", document.getElementById("bars-input").value || 1);
head.setAttribute("timesig_numerator", document.getElementById("compasso-a-input").value || 4);
head.setAttribute("timesig_denominator", document.getElementById("compasso-b-input").value || 4);
head.setAttribute(
"num_bars",
document.getElementById("bars-input").value || 1
);
head.setAttribute(
"timesig_numerator",
document.getElementById("compasso-a-input").value || 4
);
head.setAttribute(
"timesig_denominator",
document.getElementById("compasso-b-input").value || 4
);
}
const bbTrackContainer = xmlDoc.querySelector('track[type="1"] > bbtrack > trackcontainer');
const bbTrackContainer = xmlDoc.querySelector(
'track[type="1"] > bbtrack > trackcontainer'
);
if (bbTrackContainer) {
bbTrackContainer.querySelectorAll('track[type="0"]').forEach((node) => node.remove());
bbTrackContainer
.querySelectorAll('track[type="0"]')
.forEach((node) => node.remove());
const tracksXml = appState.pattern.tracks
.map((track) => createTrackXml(track))
.join("");
const tempDoc = new DOMParser().parseFromString(`<root>${tracksXml}</root>`, "application/xml");
const tempDoc = new DOMParser().parseFromString(
`<root>${tracksXml}</root>`,
"application/xml"
);
Array.from(tempDoc.documentElement.children).forEach((newTrackNode) => {
bbTrackContainer.appendChild(newTrackNode);
});
}
const serializer = new XMLSerializer();
return serializer.serializeToString(xmlDoc);
}
@@ -356,37 +395,48 @@ export function syncPatternStateToServer() {
function createTrackXml(track) {
if (track.patterns.length === 0) return "";
const ticksPerStep = 12;
const lmmsVolume = Math.round(track.volume * 100);
const lmmsPan = Math.round(track.pan * 100);
// 🔥 PROTEÇÃO: Se não tiver instrumento definido, usa Kicker padrão
const instrName = track.instrumentName || "kicker";
const instrXml = track.instrumentXml || `<kicker><env amt="0" attack="0.01" hold="0.1" decay="0.1" release="0.1" sustain="0.5" sync_mode="0"/></kicker>`;
const instrXml =
track.instrumentXml ||
`<kicker><env amt="0" attack="0.01" hold="0.1" decay="0.1" release="0.1" sustain="0.5" sync_mode="0"/></kicker>`;
const patternsXml = track.patterns.map((pattern) => {
const patternsXml = track.patterns
.map((pattern) => {
let patternNotesXml = "";
if (track.type === "plugin" && pattern.notes && pattern.notes.length > 0) {
patternNotesXml = pattern.notes.map(note => {
return `<note vol="${note.vol}" len="${note.len}" pos="${note.pos}" pan="${note.pan}" key="${note.key}"/>`;
}).join("\n ");
}
else {
patternNotesXml = pattern.steps.map((isActive, index) => {
if (isActive) {
const notePos = Math.round(index * ticksPerStep);
return `<note vol="100" len="${NOTE_LENGTH}" pos="${notePos}" pan="0" key="57"/>`;
}
return "";
}).join("\n ");
if (
track.type === "plugin" &&
pattern.notes &&
pattern.notes.length > 0
) {
patternNotesXml = pattern.notes
.map((note) => {
return `<note vol="${note.vol}" len="${note.len}" pos="${note.pos}" pan="${note.pan}" key="${note.key}"/>`;
})
.join("\n ");
} else {
patternNotesXml = pattern.steps
.map((isActive, index) => {
if (isActive) {
const notePos = Math.round(index * ticksPerStep);
return `<note vol="100" len="${NOTE_LENGTH}" pos="${notePos}" pan="0" key="57"/>`;
}
return "";
})
.join("\n ");
}
return `<pattern type="0" pos="${pattern.pos}" muted="0" steps="${pattern.steps.length}" name="${pattern.name}">
${patternNotesXml}
</pattern>`;
}).join("\n ");
})
.join("\n ");
return `
<track type="0" solo="0" muted="0" name="${track.name}">
@@ -413,7 +463,7 @@ function generateNewMmp() {
const tracksXml = appState.pattern.tracks
.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">
@@ -454,4 +504,4 @@ function downloadFile(content, fileName) {
URL.revokeObjectURL(url);
}
export { generateXmlFromState as generateXmlFromStateExported };
export { generateXmlFromState as generateXmlFromStateExported };