Padronização da interface e atualização do sync entre clientes
This commit is contained in:
1 parent
3f714c88de
commit
7d9d2b0f63
10 files changed
+796
-377
No files matched your search
@@ -9,7 +9,87 @@ const { Server } = require("socket.io");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const pino = require("pino");
|
||||
//const { PORT_SOCK } = require("../config.js");
|
||||
|
||||
const DEFAULT_PROJECT_XML = `<?xml version="1.0"?>
|
||||
<!DOCTYPE lmms-project>
|
||||
<lmms-project version="1.0" type="song" creatorversion="1.2.2" creator="LMMS">
|
||||
<head mastervol="100" masterpitch="0" timesig_denominator="4" timesig_numerator="4" bpm="140"/>
|
||||
<song>
|
||||
<trackcontainer visible="1" y="5" minimized="0" height="300" type="song" x="5" maximized="0" width="600">
|
||||
<track muted="0" name="TripleOscillator" type="0" solo="0">
|
||||
<instrumenttrack pitchrange="1" fxch="0" usemasterpitch="1" vol="100" pitch="0" pan="0" basenote="57">
|
||||
<instrument name="tripleoscillator">
|
||||
<tripleoscillator wavetype1="0" pan2="0" finer1="0" finel1="0" userwavefile0="" wavetype0="0" userwavefile1="" finer0="0" stphdetun1="0" vol0="33" wavetype2="0" coarse0="0" finel2="0" finer2="0" modalgo2="2" finel0="0" coarse1="-12" phoffset1="0" pan0="0" modalgo3="2" coarse2="-24" phoffset2="0" pan1="0" vol1="33" phoffset0="0" stphdetun0="0" modalgo1="2" vol2="33" stphdetun2="0" userwavefile2=""/>
|
||||
</instrument>
|
||||
<eldata fres="0.5" fwet="0" ftype="0" fcut="14000">
|
||||
<elvol ctlenvamt="0" lspd_syncmode="0" x100="0" lpdel="0" latt="0" pdel="0" lspd="0.1" lshp="0" hold="0.5" lspd_numerator="4" userwavefile="" att="0" lamt="0" dec="0.5" lspd_denominator="4" sustain="0.5" rel="0.1" amt="0"/>
|
||||
<elcut ctlenvamt="0" lspd_syncmode="0" x100="0" lpdel="0" latt="0" pdel="0" lspd="0.1" lshp="0" hold="0.5" lspd_numerator="4" userwavefile="" att="0" lamt="0" dec="0.5" lspd_denominator="4" sustain="0.5" rel="0.1" amt="0"/>
|
||||
<elres ctlenvamt="0" lspd_syncmode="0" x100="0" lpdel="0" latt="0" pdel="0" lspd="0.1" lshp="0" hold="0.5" lspd_numerator="4" userwavefile="" att="0" lamt="0" dec="0.5" lspd_denominator="4" sustain="0.5" rel="0.1" amt="0"/>
|
||||
</eldata>
|
||||
<chordcreator chordrange="1" chord-enabled="0" chord="0"/>
|
||||
<arpeggiator arpcycle="0" arptime_denominator="4" arpgate="100" arpdir="0" arpmode="0" arptime_syncmode="0" arp="0" arpmiss="0" arp-enabled="0" arptime="100" arprange="1" arpskip="0" arptime_numerator="4"/>
|
||||
<midiport inputchannel="0" outputcontroller="0" fixedoutputvelocity="-1" outputchannel="1" fixedinputvelocity="-1" outputprogram="1" inputcontroller="0" readable="0" fixedoutputnote="-1" basevelocity="63" writable="0"/>
|
||||
<fxchain enabled="0" numofeffects="0"/>
|
||||
</instrumenttrack>
|
||||
</track>
|
||||
<track muted="0" name="Sample track" type="2" solo="0">
|
||||
<sampletrack vol="100" pan="0">
|
||||
<fxchain enabled="0" numofeffects="0"/>
|
||||
</sampletrack>
|
||||
</track>
|
||||
<track muted="0" name="Beat/Bassline 0" type="1" solo="0">
|
||||
<bbtrack>
|
||||
<trackcontainer visible="0" y="5" minimized="0" height="400" type="bbtrackcontainer" x="610" maximized="0" width="700">
|
||||
<track muted="0" name="Kicker" type="0" solo="0">
|
||||
<instrumenttrack pitchrange="1" fxch="0" usemasterpitch="1" vol="100" pitch="0" pan="0" basenote="57">
|
||||
<instrument name="kicker">
|
||||
<kicker decay="440" version="1" noise="0" endfreq="40" decay_syncmode="0" decay_denominator="4" startfreq="150" env="0.163" startnote="1" dist="0.8" slope="0.06" decay_numerator="4" click="0.4" distend="0.8" gain="1" endnote="0"/>
|
||||
</instrument>
|
||||
<eldata fres="0.5" fwet="0" ftype="0" fcut="14000">
|
||||
<elvol ctlenvamt="0" lspd_syncmode="0" x100="0" lpdel="0" latt="0" pdel="0" lspd="0.1" lshp="0" hold="0.5" lspd_numerator="4" userwavefile="" att="0" lamt="0" dec="0.5" lspd_denominator="4" sustain="0.5" rel="0.1" amt="0"/>
|
||||
<elcut ctlenvamt="0" lspd_syncmode="0" x100="0" lpdel="0" latt="0" pdel="0" lspd="0.1" lshp="0" hold="0.5" lspd_numerator="4" userwavefile="" att="0" lamt="0" dec="0.5" lspd_denominator="4" sustain="0.5" rel="0.1" amt="0"/>
|
||||
<elres ctlenvamt="0" lspd_syncmode="0" x100="0" lpdel="0" latt="0" pdel="0" lspd="0.1" lshp="0" hold="0.5" lspd_numerator="4" userwavefile="" att="0" lamt="0" dec="0.5" lspd_denominator="4" sustain="0.5" rel="0.1" amt="0"/>
|
||||
</eldata>
|
||||
<chordcreator chordrange="1" chord-enabled="0" chord="0"/>
|
||||
<arpeggiator arpcycle="0" arptime_denominator="4" arpgate="100" arpdir="0" arpmode="0" arptime_syncmode="0" arp="0" arpmiss="0" arp-enabled="0" arptime="100" arprange="1" arpskip="0" arptime_numerator="4"/>
|
||||
<midiport inputchannel="0" outputcontroller="0" fixedoutputvelocity="-1" outputchannel="1" fixedinputvelocity="-1" outputprogram="1" inputcontroller="0" readable="0" fixedoutputnote="-1" basevelocity="63" writable="0"/>
|
||||
<fxchain enabled="0" numofeffects="0"/>
|
||||
</instrumenttrack>
|
||||
<pattern pos="0" muted="0" name="Kicker" steps="16" type="0"/>
|
||||
</track>
|
||||
</trackcontainer>
|
||||
</bbtrack>
|
||||
</track>
|
||||
<track muted="0" name="Automation track" type="5" solo="0">
|
||||
<automationtrack/>
|
||||
</track>
|
||||
</trackcontainer>
|
||||
<track muted="0" name="Automation track" type="6" solo="0">
|
||||
<automationtrack/>
|
||||
<automationpattern pos="0" len="192" name="Numerator" prog="0" tens="1" mute="0"/>
|
||||
<automationpattern pos="0" len="192" name="Denominator" prog="0" tens="1" mute="0"/>
|
||||
<automationpattern pos="0" len="192" name="Tempo" prog="0" tens="1" mute="0"/>
|
||||
<automationpattern pos="0" len="192" name="Master volume" prog="0" tens="1" mute="0"/>
|
||||
<automationpattern pos="0" len="192" name="Master pitch" prog="0" tens="1" mute="0"/>
|
||||
</track>
|
||||
<fxmixer visible="1" y="310" minimized="0" height="333" x="5" maximized="0" width="543">
|
||||
<fxchannel volume="1" muted="0" num="0" name="Master" soloed="0">
|
||||
<fxchain enabled="0" numofeffects="0"/>
|
||||
</fxchannel>
|
||||
</fxmixer>
|
||||
<ControllerRackView visible="1" y="310" minimized="0" height="200" x="680" maximized="0" width="350"/>
|
||||
<pianoroll visible="0" y="5" minimized="0" height="480" x="5" maximized="0" width="860"/>
|
||||
<automationeditor visible="0" y="1" minimized="0" height="400" x="1" maximized="0" width="860"/>
|
||||
<projectnotes visible="0" y="10" minimized="0" height="400" x="700" maximized="0" width="679"><![CDATA[<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.5pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html>]]></projectnotes>
|
||||
<timeline lp1pos="192" lpstate="0" lp0pos="0"/>
|
||||
<controllers/>
|
||||
</song>
|
||||
</lmms-project>`;
|
||||
|
||||
|
||||
// -------------------------
|
||||
// LOGGER DINÂMICO (GERENCIADOR)
|
||||
@@ -111,7 +191,8 @@ function ensureRoom(roomName) {
|
||||
if (fs.existsSync(p)) {
|
||||
const j = JSON.parse(fs.readFileSync(p, "utf8"));
|
||||
roomStates[roomName] = {
|
||||
projectXml: j.projectXml || null,
|
||||
// 🔥 CORREÇÃO: Fallback para DEFAULT_PROJECT_XML se o JSON salvo estiver sem XML
|
||||
projectXml: j.projectXml || DEFAULT_PROJECT_XML,
|
||||
audio: j.audio || { tracks: [], clips: [] },
|
||||
seq: j.seq || 0,
|
||||
tokensSeen: new Set(),
|
||||
@@ -122,8 +203,10 @@ function ensureRoom(roomName) {
|
||||
console.warn(`[persist] falha ao carregar estado da sala ${roomName}:`, e);
|
||||
}
|
||||
|
||||
// Cria sala NOVA na memória
|
||||
roomStates[roomName] = {
|
||||
projectXml: null,
|
||||
// 🔥 CORREÇÃO: Inicia com o XML do LMMS completo, não mais null
|
||||
projectXml: DEFAULT_PROJECT_XML,
|
||||
audio: { tracks: [], clips: [] },
|
||||
seq: 0,
|
||||
tokensSeen: new Set(),
|
||||
@@ -245,9 +328,6 @@ function applyAuthoritativeAction(roomName, action) {
|
||||
}
|
||||
|
||||
case "UPDATE_AUDIO_CLIP": {
|
||||
// =================================================================
|
||||
// 👇 INÍCIO DA CORREÇÃO (Blindagem do Servidor para Bug 4)
|
||||
// =================================================================
|
||||
if (!action.clipId || !action.props) {
|
||||
console.warn(
|
||||
`[Server] Ação UPDATE_AUDIO_CLIP rejeitada (dados base inválidos):`,
|
||||
@@ -277,9 +357,6 @@ function applyAuthoritativeAction(roomName, action) {
|
||||
);
|
||||
return null;
|
||||
}
|
||||
// =================================================================
|
||||
// 👆 FIM DA CORREÇÃO
|
||||
// =================================================================
|
||||
|
||||
const c = state.clips.find((x) => String(x.id) === String(action.clipId));
|
||||
if (c && action.props && typeof action.props === "object") {
|
||||
@@ -300,6 +377,29 @@ function applyAuthoritativeAction(roomName, action) {
|
||||
break;
|
||||
}
|
||||
|
||||
case "REMOVE_AUDIO_LANE_BY_ID": {
|
||||
// 1. Acha e remove a Track
|
||||
const idx = state.tracks.findIndex((t) => t.id === action.trackId);
|
||||
if (idx !== -1) {
|
||||
state.tracks.splice(idx, 1);
|
||||
mutated = true;
|
||||
}
|
||||
|
||||
// 2. LIMPEZA PROFUNDA: Remove clips dessa track E clips órfãos (trackId null)
|
||||
// Se removemos a track, os clips dela têm que sumir.
|
||||
const prevCount = state.clips.length;
|
||||
state.clips = state.clips.filter((c) => {
|
||||
// Mantém apenas se tiver trackId E se esse trackId ainda existir na lista de tracks
|
||||
const parentTrackExists = state.tracks.some(t => t.id === c.trackId);
|
||||
return c.trackId && parentTrackExists;
|
||||
});
|
||||
|
||||
if (state.clips.length !== prevCount) {
|
||||
mutated = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// outras ações não são da persistência do editor de áudio
|
||||
mutated = false;
|
||||
@@ -414,13 +514,19 @@ io.on("connection", (socket) => {
|
||||
// Carrega estado da sala
|
||||
const room = ensureRoom(roomName);
|
||||
|
||||
// Persiste estado se for LOAD_PROJECT (compat)
|
||||
if (action.type === "LOAD_PROJECT" && action.xml) {
|
||||
room.projectXml = action.xml;
|
||||
saveRoom(roomName);
|
||||
console.log(
|
||||
`[broadcast_action] Estado da sala ${roomName} atualizado (LOAD_PROJECT).`
|
||||
);
|
||||
// Persiste estado do Pattern (Notas/Sequenciador)
|
||||
// Aceita tanto carregamento total quanto atualização de notas
|
||||
if ((action.type === "LOAD_PROJECT" || action.type === "SYNC_PATTERN_STATE") && action.xml) {
|
||||
// Proteção: Não salva se o XML for vazio (evita corromper a sala com o bug antigo)
|
||||
if (action.xml.trim().length > 0) {
|
||||
room.projectXml = action.xml;
|
||||
saveRoom(roomName);
|
||||
console.log(
|
||||
`[broadcast_action] XML da sala ${roomName} atualizado via ${action.type}.`
|
||||
);
|
||||
} else {
|
||||
console.warn(`[Server] Ignorando XML vazio vindo de ${action.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Aplica ações autoritativas do editor de áudio
|
||||
|
||||
Reference in new issue
Block a user