melhorando a leitura de projetos no mmpCreator
Deploy / Deploy (push) Successful in 2m8s
Details
Deploy / Deploy (push) Successful in 2m8s
Details
This commit is contained in:
parent
79f716e8a6
commit
c384a4a92f
|
|
@ -13,10 +13,142 @@ import { loadAudioForTrack } from "./pattern/pattern_state.js";
|
||||||
import { renderAll, getSamplePathMap } from "./ui.js";
|
import { renderAll, getSamplePathMap } from "./ui.js";
|
||||||
import { DEFAULT_PAN, DEFAULT_VOLUME, NOTE_LENGTH } from "./config.js";
|
import { DEFAULT_PAN, DEFAULT_VOLUME, NOTE_LENGTH } from "./config.js";
|
||||||
import { initializeAudioContext, getMainGainNode } from "./audio.js";
|
import { initializeAudioContext, getMainGainNode } from "./audio.js";
|
||||||
import { DEFAULT_PROJECT_XML } from "./utils.js";
|
import { DEFAULT_PROJECT_XML, getSecondsPerStep } from "./utils.js";
|
||||||
import * as Tone from "https://esm.sh/tone";
|
import * as Tone from "https://esm.sh/tone";
|
||||||
import { sendAction } from "./socket.js";
|
import { sendAction } from "./socket.js";
|
||||||
|
|
||||||
|
// ⚠️ vem do módulo de áudio (o mesmo que audio_ui usa)
|
||||||
|
import {
|
||||||
|
addAudioTrackLane,
|
||||||
|
addAudioClipToTimeline,
|
||||||
|
updateAudioClipProperties,
|
||||||
|
} from "./audio/audio_state.js";
|
||||||
|
|
||||||
|
const TICKS_PER_STEP = 12;
|
||||||
|
|
||||||
|
function safeId(prefix) {
|
||||||
|
return (crypto?.randomUUID?.() || `${prefix}_${Date.now()}_${Math.floor(Math.random() * 1e6)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function basename(path) {
|
||||||
|
return String(path || "").split(/[\\/]/).pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveSamplePath(sampleName, pathMap) {
|
||||||
|
// 1) tenta pelo manifest (melhor)
|
||||||
|
if (sampleName && pathMap[sampleName]) return pathMap[sampleName];
|
||||||
|
|
||||||
|
// 2) fallback simples (se você tiver essa convenção)
|
||||||
|
// ajuste se necessário
|
||||||
|
if (sampleName) return `src/samples/${sampleName}`;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function parseBeatIndexJson(data) {
|
||||||
|
resetProjectState();
|
||||||
|
initializeAudioContext();
|
||||||
|
|
||||||
|
// BPM
|
||||||
|
const bpm = Number(data?.bpm || 140);
|
||||||
|
const bpmInput = document.getElementById("bpm-input");
|
||||||
|
if (bpmInput) bpmInput.value = bpm;
|
||||||
|
|
||||||
|
// (opcional) nome do projeto
|
||||||
|
if (data?.original_title) {
|
||||||
|
appState.global.currentBeatBasslineName = data.original_title;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathMap = getSamplePathMap(); // vem do samples-manifest :contentReference[oaicite:6]{index=6}
|
||||||
|
const secondsPerStep = getSecondsPerStep();
|
||||||
|
|
||||||
|
const newPatternTracks = [];
|
||||||
|
|
||||||
|
// 1) monta pattern.tracks (plugin/bassline etc)
|
||||||
|
(data.tracks || []).forEach((t, idx) => {
|
||||||
|
if (t.type === "sample") return;
|
||||||
|
|
||||||
|
const id = t.id || safeId("ptrk");
|
||||||
|
|
||||||
|
// normaliza nome (teu JSON usa track_name / bassline_name)
|
||||||
|
const name =
|
||||||
|
t.track_name ||
|
||||||
|
t.bassline_name ||
|
||||||
|
t.instrument_name ||
|
||||||
|
t.instrumentName ||
|
||||||
|
`Track ${idx + 1}`;
|
||||||
|
|
||||||
|
newPatternTracks.push({
|
||||||
|
...t,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
appState.pattern.tracks = newPatternTracks;
|
||||||
|
|
||||||
|
// 2) cria lanes/clips de áudio a partir dos sample-tracks
|
||||||
|
const sampleTracks = (data.tracks || []).filter((t) => t.type === "sample");
|
||||||
|
|
||||||
|
for (const st of sampleTracks) {
|
||||||
|
const laneId = st.id || safeId("audioTrack");
|
||||||
|
const laneName = st.track_name || "Áudio";
|
||||||
|
|
||||||
|
// lane (pista)
|
||||||
|
addAudioTrackLane({
|
||||||
|
id: laneId,
|
||||||
|
name: laneName,
|
||||||
|
volume: Number(st.sample_info?.vol ?? 100) / 100,
|
||||||
|
pan: Number(st.sample_info?.pan ?? 0) / 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
// arquivo
|
||||||
|
const sampleName =
|
||||||
|
st.sample_name ||
|
||||||
|
basename(st.sample_info?.src) || // só pra extrair "#2.wav"
|
||||||
|
null;
|
||||||
|
|
||||||
|
const filePath = resolveSamplePath(sampleName, pathMap);
|
||||||
|
if (!filePath) continue;
|
||||||
|
|
||||||
|
// seus dados podem vir num único sample_info, OU você pode evoluir pra playlist_clips também
|
||||||
|
const clipDefs =
|
||||||
|
Array.isArray(st.playlist_clips) && st.playlist_clips.length
|
||||||
|
? st.playlist_clips
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
pos: Number(st.sample_info?.pos ?? 0),
|
||||||
|
len: Number(st.sample_info?.len ?? 0),
|
||||||
|
name: sampleName,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const c of clipDefs) {
|
||||||
|
const startTimeInSeconds = (Number(c.pos || 0) / TICKS_PER_STEP) * secondsPerStep;
|
||||||
|
const durationInSeconds = (Number(c.len || 0) / TICKS_PER_STEP) * secondsPerStep;
|
||||||
|
|
||||||
|
const clipId = safeId("clip");
|
||||||
|
|
||||||
|
// isso decodifica e já deixa pronto pra desenhar waveform (clip.buffer) :contentReference[oaicite:7]{index=7}
|
||||||
|
await addAudioClipToTimeline(filePath, laneId, startTimeInSeconds, clipId, c.name || sampleName);
|
||||||
|
|
||||||
|
// garante que o “tamanho na playlist” respeita seu len do beat-index
|
||||||
|
if (durationInSeconds > 0) {
|
||||||
|
updateAudioClipProperties(clipId, { durationInSeconds, offset: 0, pitch: 0 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// escolha de track ativa no pattern editor
|
||||||
|
const firstInst = newPatternTracks.find((t) => t.type !== "bassline");
|
||||||
|
appState.pattern.activeTrackId = firstInst ? firstInst.id : null;
|
||||||
|
appState.pattern.activePatternIndex = 0;
|
||||||
|
|
||||||
|
// mantém seu comportamento atual
|
||||||
|
await loadStateFromSession(); // se existir snapshot local, aplica
|
||||||
|
renderAll();
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
// MANIPULAÇÃO DE ARQUIVOS
|
// MANIPULAÇÃO DE ARQUIVOS
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
|
|
@ -48,6 +180,13 @@ export function handleLocalProjectReset() {
|
||||||
export async function handleFileLoad(file) {
|
export async function handleFileLoad(file) {
|
||||||
let xmlContent = "";
|
let xmlContent = "";
|
||||||
try {
|
try {
|
||||||
|
const lower = file.name.toLowerCase();
|
||||||
|
|
||||||
|
if (lower.endsWith(".json")) {
|
||||||
|
const json = JSON.parse(await file.text());
|
||||||
|
sendAction({ type: "LOAD_BEAT_INDEX", data: json });
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (file.name.toLowerCase().endsWith(".mmpz")) {
|
if (file.name.toLowerCase().endsWith(".mmpz")) {
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
const jszip = new JSZip();
|
const jszip = new JSZip();
|
||||||
|
|
@ -71,6 +210,13 @@ export async function handleFileLoad(file) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function loadBeatIndexFromServer(fileName) {
|
||||||
|
const response = await fetch(`src_mmpSearch/index/${fileName}.json`);
|
||||||
|
if (!response.ok) throw new Error("Não foi possível carregar beat index");
|
||||||
|
const data = await response.json();
|
||||||
|
sendAction({ type: "LOAD_BEAT_INDEX", data });
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadProjectFromServer(fileName) {
|
export async function loadProjectFromServer(fileName) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`src_mmpSearch/mmp/${fileName}`);
|
const response = await fetch(`src_mmpSearch/mmp/${fileName}`);
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import {
|
||||||
|
|
||||||
import {
|
import {
|
||||||
parseMmpContent,
|
parseMmpContent,
|
||||||
|
parseBeatIndexJson,
|
||||||
handleLocalProjectReset,
|
handleLocalProjectReset,
|
||||||
syncPatternStateToServer,
|
syncPatternStateToServer,
|
||||||
generateXmlFromStateExported,
|
generateXmlFromStateExported,
|
||||||
|
|
@ -1086,6 +1087,28 @@ async function handleActionBroadcast(action) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "LOAD_BEAT_INDEX": {
|
||||||
|
isLoadingProject = true;
|
||||||
|
showToast("📂 Carregando beat index...", "info");
|
||||||
|
|
||||||
|
if (window.ROOM_NAME) {
|
||||||
|
sessionStorage.removeItem(`temp_state_${window.ROOM_NAME}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await parseBeatIndexJson(action.data);
|
||||||
|
renderAll();
|
||||||
|
showToast("🎵 Beat index carregado", "success");
|
||||||
|
saveStateToSession();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Erro LOAD_BEAT_INDEX:", e);
|
||||||
|
showToast("❌ Erro ao carregar beat index", "error");
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoadingProject = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "UPDATE_AUDIO_CLIP": {
|
case "UPDATE_AUDIO_CLIP": {
|
||||||
try {
|
try {
|
||||||
if (action.props?.__operation === "slice") {
|
if (action.props?.__operation === "slice") {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue