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 { DEFAULT_PAN, DEFAULT_VOLUME, NOTE_LENGTH } from "./config.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 { 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
|
||||
//--------------------------------------------------------------
|
||||
|
|
@ -48,6 +180,13 @@ export function handleLocalProjectReset() {
|
|||
export async function handleFileLoad(file) {
|
||||
let xmlContent = "";
|
||||
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")) {
|
||||
// eslint-disable-next-line no-undef
|
||||
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) {
|
||||
try {
|
||||
const response = await fetch(`src_mmpSearch/mmp/${fileName}`);
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import {
|
|||
|
||||
import {
|
||||
parseMmpContent,
|
||||
parseBeatIndexJson,
|
||||
handleLocalProjectReset,
|
||||
syncPatternStateToServer,
|
||||
generateXmlFromStateExported,
|
||||
|
|
@ -1086,6 +1087,28 @@ async function handleActionBroadcast(action) {
|
|||
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": {
|
||||
try {
|
||||
if (action.props?.__operation === "slice") {
|
||||
|
|
|
|||
Loading…
Reference in New Issue