att upload
This commit is contained in:
1 parent
33a2a3acdf
commit
3a9e9547b9
63 files changed
+44080
-907767
No files matched your search
+116
-66
@@ -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 };
|
||||
Reference in new issue
Block a user