fix: WS protocol — send positional args from React, add OutputRoutingPanel, add deleteScene handler

This commit is contained in:
2026-06-20 20:47:23 -04:00
parent 7f24a0f807
commit 0d9f31f341
6 changed files with 3130 additions and 11 deletions
+10 -1
View File
@@ -1407,7 +1407,16 @@ public:
std::string result = this->mixerApi.listScenes();
this->JsonReply(replyTo, "mixerGetScenes", result.c_str());
}
REGISTER_MESSAGE_HANDLER(mixerGetScenes)
REGISTER_MESSAGE_HANDLER(mixerGetScenes)
void handle_mixerDeleteScene(int replyTo, json_reader *pReader)
{
int64_t sceneId;
pReader->read(&sceneId);
bool ok = this->mixerApi.deleteScene(std::to_string(sceneId));
this->Reply(replyTo, "mixerDeleteScene", ok);
}
REGISTER_MESSAGE_HANDLER(mixerDeleteScene)
/************************************************************************/
/* Mixer Output Routing Messages */
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,11 +1,11 @@
{
"name": "pipedal",
"name": "op-pedal",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pipedal",
"name": "op-pedal",
"version": "0.0.0",
"dependencies": {
"@emotion/react": "^11.14.0",
+7 -7
View File
@@ -113,7 +113,7 @@ const MixerPage: React.FC<MixerPageProps> = ({ onClose }) => {
const handleLearnTarget = useCallback(
(targetType: string, targetId: number) => {
if (learnModeActive) {
ws.send("mixerSetMidiLearnTarget", { targetType, targetId }).catch(() => {});
ws.send("mixerSetMidiLearnTarget", [targetType, targetId]).catch(() => {});
}
},
[ws, learnModeActive],
@@ -123,42 +123,42 @@ const MixerPage: React.FC<MixerPageProps> = ({ onClose }) => {
const handleChannelVolume = useCallback(
(channelIndex: number, volume: number) => {
ws.send("mixerSetChannelVolume", { channelIndex, volume }).catch(() => {});
ws.send("mixerSetChannelVolume", [channelIndex, volume]).catch(() => {});
},
[ws]
);
const handleChannelPan = useCallback(
(channelIndex: number, pan: number) => {
ws.send("mixerSetChannelPan", { channelIndex, pan }).catch(() => {});
ws.send("mixerSetChannelPan", [channelIndex, pan]).catch(() => {});
},
[ws]
);
const handleChannelMute = useCallback(
(channelIndex: number, mute: boolean) => {
ws.send("mixerSetChannelMute", { channelIndex, mute }).catch(() => {});
ws.send("mixerSetChannelMute", [channelIndex, mute]).catch(() => {});
},
[ws]
);
const handleChannelSolo = useCallback(
(channelIndex: number, solo: boolean) => {
ws.send("mixerSetChannelSolo", { channelIndex, solo }).catch(() => {});
ws.send("mixerSetChannelSolo", [channelIndex, solo]).catch(() => {});
},
[ws]
);
const handleBusVolume = useCallback(
(busId: number, volume: number) => {
ws.send("mixerSetBusVolume", { busId, volume }).catch(() => {});
ws.send("mixerSetBusVolume", [busId, volume]).catch(() => {});
},
[ws]
);
const handleBusMute = useCallback(
(busId: number, mute: boolean) => {
ws.send("mixerSetBusMute", { busId, mute }).catch(() => {});
ws.send("mixerSetBusMute", [busId, mute]).catch(() => {});
},
[ws]
);
@@ -0,0 +1,51 @@
// OutputRoutingPanel - configure which hardware output channels each bus routes to
import React from "react";
import type { MixerBusState, MixerOutputRouteState } from "./useMixerWS";
interface Props {
buses: MixerBusState[];
routes: MixerOutputRouteState[];
onClose: () => void;
}
export default function OutputRoutingPanel({ buses, routes, onClose }: Props) {
return (
<div style={{
position: "fixed", top: 0, left: 0, right: 0, bottom: 0,
background: "rgba(0,0,0,0.7)", display: "flex",
alignItems: "center", justifyContent: "center", zIndex: 1000
}}>
<div style={{
background: "#1a1a2e", borderRadius: 12, padding: 24,
minWidth: 480, maxHeight: "80vh", overflow: "auto",
color: "#e0e0e0", fontFamily: "system-ui, sans-serif"
}}>
<h2 style={{ margin: "0 0 16px", color: "#a78bfa" }}>Output Routing</h2>
{routes.length === 0 && (
<p style={{ color: "#888", fontStyle: "italic" }}>No output routes configured.</p>
)}
{routes.map((route, idx) => (
<div key={idx} style={{
display: "flex", gap: 12, alignItems: "center",
padding: "8px 0", borderBottom: "1px solid #333"
}}>
<span style={{ minWidth: 80, fontWeight: 600 }}>
{buses.find(b => b.id === route.sourceBusId)?.name ?? ("Bus " + String(route.sourceBusId))}
</span>
<span style={{ color: "#888" }}>CH {route.sourceStartChannel}</span>
<span style={{ color: "#555" }}>{"->"}</span>
<span style={{ color: "#888" }}>Out {route.targetStartChannel}</span>
<span style={{ color: "#555", marginLeft: 8 }}>({route.channels} ch)</span>
</div>
))}
<div style={{ marginTop: 20, display: "flex", gap: 8, justifyContent: "flex-end" }}>
<button onClick={onClose} style={{
padding: "8px 20px", border: "1px solid #555",
borderRadius: 6, background: "transparent", color: "#ccc",
cursor: "pointer"
}}>Close</button>
</div>
</div>
</div>
);
}
+9 -1
View File
@@ -39,7 +39,15 @@ export interface MixerRouteState {
sourceType: string; // "channel" | "bus"
}
export interface MixerOutputRouteState {
sourceBusId: number;
sourceStartChannel: number;
targetStartChannel: number;
channels: number;
}
export interface MixerState {
outputRoutes?: MixerOutputRouteState[];
channels: MixerChannelState[];
buses: MixerBusState[];
routes: MixerRouteState[];
@@ -237,7 +245,7 @@ export function useMixerWS(wsUrl: string = DEFAULT_WS_URL): MixerWsHandle {
// Build message in PiPedalSocket format: [header, body?]
const header: Record<string, unknown> = { message: command, replyTo };
const msg = params !== undefined ? [header, params] : [header];
const msg = params !== undefined ? [header, ...(Array.isArray(params) ? params : [params])] : [header];
// Register the reply handler before sending (race-safe).
replyMapRef.current.set(replyTo, (value: unknown) => {