criação de novas patterns de composição nos projetos
Deploy / Deploy (push) Successful in 2m5s Details

This commit is contained in:
JotaChina 2025-12-27 18:50:36 -03:00
parent 17d3419475
commit 43418094f8
3 changed files with 118 additions and 23 deletions

View File

@ -544,31 +544,31 @@ export async function parseMmpContent(xmlString) {
const basslineContainers = bbTrackNodes const basslineContainers = bbTrackNodes
.map((trackNode, idx) => { .map((trackNode, idx) => {
const trackName = trackNode.getAttribute("name") || "Beat/Bassline"; const trackName = trackNode.getAttribute("name") || "Beat/Bassline";
const playlistClips = Array.from(trackNode.querySelectorAll(":scope > bbtco")).map((bbtco, cidx) => {
const playlistClips = Array.from( const pos = parseInt(bbtco.getAttribute("pos"), 10) || 0;
trackNode.querySelectorAll(":scope > bbtco") const len = parseInt(bbtco.getAttribute("len"), 10) || 192;
).map((bbtco) => ({
pos: parseInt(bbtco.getAttribute("pos"), 10) || 0,
len: parseInt(bbtco.getAttribute("len"), 10) || 192,
name: trackName,
}));
// ❌ remove esta linha:
// if (playlistClips.length === 0) return null;
// ✅ mantém mesmo vazia
return { return {
id: `bassline_${Date.now()}_${Math.random().toString(36).slice(2)}`, id: `plc_${idx}_${pos}_${len}_${cidx}`, // determinístico
pos,
len,
name: trackName, name: trackName,
type: "bassline",
playlist_clips: playlistClips, // pode ser []
patternIndex: idx,
instrumentSourceId: rackId,
volume: 1,
pan: 0,
patterns: [],
isMuted: trackNode.getAttribute("muted") === "1",
}; };
});
// NÃO retornar null quando não tem clips:
return {
id: `bb_container_${idx}`,
name: trackName,
type: "bassline",
patternIndex: idx,
playlist_clips: playlistClips, // pode ser []
patterns: [],
isMuted: false,
instrumentSourceId: bbRackId, // ou algo equivalente no teu parse
volume: 1,
pan: 0,
};
}) })
.filter(Boolean); .filter(Boolean);

View File

@ -213,8 +213,27 @@ document.addEventListener("DOMContentLoaded", () => {
} }
}); });
//Seleção de pattern // --- Criar/Remover Pattern (toolbar) ---
const addPatternBtn = document.getElementById("add-pattern-btn");
const removePatternBtn = document.getElementById("remove-pattern-btn");
addPatternBtn?.addEventListener("click", () => {
const refTrack = (appState.pattern.tracks || []).find(t => t.type !== "bassline");
const nextIndex = refTrack?.patterns?.length ?? 0;
const defaultName = `Pattern ${nextIndex + 1}`;
const name = (prompt("Nome do novo pattern:", defaultName) || defaultName).trim();
// cria e já seleciona
sendAction({ type: "ADD_PATTERN", patternIndex: nextIndex, name, select: true });
});
// opcional (se quiser implementar remover depois)
removePatternBtn?.addEventListener("click", () => {
sendAction({ type: "REMOVE_LAST_PATTERN" });
});
//Seleção de pattern
const globalPatternSelector = document.getElementById( const globalPatternSelector = document.getElementById(
"global-pattern-selector" "global-pattern-selector"
); );

View File

@ -499,12 +499,16 @@ function _ensureBasslineForPatternIndex(patternIndex) {
instrumentSourceId: null, instrumentSourceId: null,
volume: 1, volume: 1,
pan: 0, pan: 0,
instrumentSourceId: _getDefaultRackId(),
}; };
appState.pattern.tracks.push(b); appState.pattern.tracks.push(b);
} }
if (!Array.isArray(b.playlist_clips)) b.playlist_clips = []; if (!Array.isArray(b.playlist_clips)) b.playlist_clips = [];
// Isso deixa o modo focado funcionando em patterns recém-criados.
if (!b.instrumentSourceId) b.instrumentSourceId = _getDefaultRackId();
// garante ids nos clips antigos // garante ids nos clips antigos
b.playlist_clips.forEach((c) => { b.playlist_clips.forEach((c) => {
if (!c.id) c.id = _genPlaylistClipId(); if (!c.id) c.id = _genPlaylistClipId();
@ -513,6 +517,13 @@ function _ensureBasslineForPatternIndex(patternIndex) {
return b; return b;
} }
function _getDefaultRackId() {
const inst = (appState.pattern.tracks || []).find(
(t) => t.type !== "bassline" && t.parentBasslineId
);
return inst?.parentBasslineId ?? null;
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// BROADCAST // BROADCAST
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -708,6 +719,71 @@ async function handleActionBroadcast(action) {
break; break;
} }
case "ADD_PATTERN": {
const who = actorOf(action);
const desiredIndex = Number.isFinite(Number(action.patternIndex))
? Number(action.patternIndex)
: null;
const nonBass = (appState.pattern.tracks || []).filter(t => t.type !== "bassline");
const currentCount = nonBass.reduce((m, t) => Math.max(m, t.patterns?.length ?? 0), 0);
const idx = desiredIndex != null ? desiredIndex : currentCount;
const finalName = String(action.name || `Pattern ${idx + 1}`).trim();
// 1) cria patterns vazios em TODAS as tracks (respeita bars-input)
_ensurePatternsUpTo(idx);
// 2) garante nome/pos estáveis no índice criado
for (const t of nonBass) {
t.patterns[idx] = t.patterns[idx] || _makeEmptyPattern(idx);
t.patterns[idx].name = finalName;
if (t.patterns[idx].pos == null) t.patterns[idx].pos = idx * LMMS_BAR_TICKS;
}
// 3) cria a lane "bassline" (a coluna do pattern)
const b = _ensureBasslineForPatternIndex(idx);
b.patternIndex = idx;
b.name = finalName;
if (!b.instrumentSourceId) b.instrumentSourceId = _getDefaultRackId();
// 4) opcional: já selecionar
if (action.select) {
appState.pattern.activePatternIndex = idx;
appState.pattern.tracks.forEach((track) => (track.activePatternIndex = idx));
}
renderAll();
showToast(` ${who} criou: ${finalName}`, "success");
saveStateToSession();
break;
}
case "REMOVE_LAST_PATTERN": {
const nonBass = (appState.pattern.tracks || []).filter(t => t.type !== "bassline");
const count = nonBass.reduce((m, t) => Math.max(m, t.patterns?.length ?? 0), 0);
const last = count - 1;
if (last <= 0) break;
// remove patterns nas tracks
for (const t of nonBass) t.patterns.pop();
// remove a lane bassline correspondente
appState.pattern.tracks = appState.pattern.tracks.filter(
t => !(t.type === "bassline" && Number(t.patternIndex) === last)
);
// ajusta seleção
const newIdx = Math.min(appState.pattern.activePatternIndex, last - 1);
appState.pattern.activePatternIndex = newIdx;
appState.pattern.tracks.forEach((t) => (t.activePatternIndex = newIdx));
renderAll();
saveStateToSession();
break;
}
case "ADD_PLAYLIST_PATTERN_CLIP": { case "ADD_PLAYLIST_PATTERN_CLIP": {
const { patternIndex, pos, len, clipId, name } = action; const { patternIndex, pos, len, clipId, name } = action;