From 799a6c4364794974474fdcba7bdaa5a1fb92e8a9 Mon Sep 17 00:00:00 2001 From: Rafael Sypriano Ribeiro Date: Sat, 4 Oct 2025 15:11:10 -0300 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20feat(yamaha-01v96):=20Add=20sup?= =?UTF-8?q?port=20for=20channel=20pairing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added support for channel pairing for CH, Aux, and Bus channels. - Added "Paired" property for channels. - Updated the user interface to display the pairing status of channels. - Updated fader colors when the channel is paired for better visualization. - Initial pair synchronization when connected to the mixer. --- .../src/devices/yamaha-01v96/device-config.ts | 6 +- backend/src/devices/yamaha-01v96/index.ts | 7 ++- backend/src/devices/yamaha-01v96/mapping.ts | 23 +++++++- .../src/devices/yamaha-01v96/pairs-groups.ts | 57 +++++++++++-------- config/remote-mixer-config.js | 1 + frontend/src/controls/entry-control.tsx | 2 + .../entry-dialog/entry-dialog-faders.tsx | 2 + frontend/src/ui/controls/fader/fader.tsx | 41 ++++++++++--- 8 files changed, 99 insertions(+), 40 deletions(-) diff --git a/backend/src/devices/yamaha-01v96/device-config.ts b/backend/src/devices/yamaha-01v96/device-config.ts index c0c5fea..5666eca 100644 --- a/backend/src/devices/yamaha-01v96/device-config.ts +++ b/backend/src/devices/yamaha-01v96/device-config.ts @@ -13,21 +13,21 @@ export const deviceConfig: DeviceConfiguration = { { key: 'value', label: 'CH' }, ...arrayRange(1, 8, it => ({ key: 'aux' + it, label: 'AUX' + it })), ], - additionalProperties: ['on'], + additionalProperties: ['on', 'paired'], }, { key: 'aux', label: 'AUX', count: 8, namePrefix: 'AUX', - additionalProperties: ['on'], + additionalProperties: ['on', 'paired'], }, { key: 'bus', label: 'BUS', count: 8, namePrefix: 'BUS', - additionalProperties: ['on'], + additionalProperties: ['on', 'paired'], }, { key: 'sum', diff --git a/backend/src/devices/yamaha-01v96/index.ts b/backend/src/devices/yamaha-01v96/index.ts index 1dc0a39..2c69a42 100644 --- a/backend/src/devices/yamaha-01v96/index.ts +++ b/backend/src/devices/yamaha-01v96/index.ts @@ -56,8 +56,11 @@ export default class Yamaha01v96DeviceController implements DeviceController { ) if (message) sendMessage(message) - if (category === 'ch' && (property === 'value' || property === 'on')) { - refreshDependentChannels(id, property) + if ( + (category === 'ch' || category === 'aux' || category === 'bus') && + (property === 'value' || property === 'on') + ) { + refreshDependentChannels(category, id, property) } } diff --git a/backend/src/devices/yamaha-01v96/mapping.ts b/backend/src/devices/yamaha-01v96/mapping.ts index 9c227ff..ec068f3 100644 --- a/backend/src/devices/yamaha-01v96/mapping.ts +++ b/backend/src/devices/yamaha-01v96/mapping.ts @@ -215,9 +215,26 @@ export const messageMapping: MessageMapping[] = [ // pairs { incoming: message => { - if (message.type !== 'kInputPair/kPair' || !message.data) return null - updateChannelPair(String(message.channel + 1), data2On(message.data)) - return true + if (!message.type?.endsWith('Pair/kPair') || !message.data) return null + + const category = message.type.startsWith('kInput') + ? 'ch' + : message.type.startsWith('kBus') + ? 'bus' + : 'aux' + + const paired = data2On(message.data) + const channelId = String(message.channel + 1) + + updateChannelPair(category, channelId, paired) + + return { + type: 'change', + category, + id: channelId, + property: 'paired', + value: paired, + } }, }, diff --git a/backend/src/devices/yamaha-01v96/pairs-groups.ts b/backend/src/devices/yamaha-01v96/pairs-groups.ts index 60ae90a..8d7ef27 100644 --- a/backend/src/devices/yamaha-01v96/pairs-groups.ts +++ b/backend/src/devices/yamaha-01v96/pairs-groups.ts @@ -4,7 +4,7 @@ import { sendMessage } from './connection' import { message } from './message' import { getRequestMessage } from './protocol' -const pairedChannels = new Set() +const pairedChannels = new Map>() const activeGroups = new Set() const channelsByGroup = new Map>() const groupsByChannel = new Map>() @@ -12,6 +12,7 @@ const groupsByChannel = new Map>() const groupTypes = ['Fader', 'Mute'] async function refreshChannels( + category: string, channelIds: Set, properties = ['value', 'on'] ) { @@ -19,7 +20,7 @@ async function refreshChannels( for (const channelId of channelIds) { for (const property of properties) { - const message = getRequestMessage('ch', channelId, property) + const message = getRequestMessage(category, channelId, property) if (message) { sendMessage(message) messageCount++ @@ -30,25 +31,21 @@ async function refreshChannels( } export async function syncPairsAndGroups(): Promise { - for (let channel = 1; channel <= 32; channel++) { - sendMessage( - message({ - type: 'kInputPair/kPair', - channel: channel - 1, - isRequest: true, - }) - ) + const categories = { + kInputPair: 32, + kBusPair: 8, + kAUXPair: 8, + } - for (let groupIndex = 1; groupIndex <= 8; groupIndex++) { - for (const groupType of groupTypes) { - sendMessage( - message({ - type: `kInputGroup/kInGroup${groupType}${groupIndex}`, - channel: channel - 1, - isRequest: true, - }) - ) - } + for (const [category, count] of Object.entries(categories)) { + for (let channel = 1; channel <= count; channel++) { + sendMessage( + message({ + type: `${category}/kPair`, + channel: channel - 1, + isRequest: true, + }) + ) } await delay(20) @@ -69,13 +66,14 @@ export async function syncPairsAndGroups(): Promise { } export async function refreshDependentChannels( + category: string, channelId: string, property?: string ): Promise { const channelsToRefresh = new Set() // pairs - if (pairedChannels.has(channelId)) { + if (pairedChannels.get(category)?.has(channelId)) { const channelIdNumber = Number(channelId) const dependentChannel = String( channelIdNumber + (channelIdNumber % 2 === 0 ? -1 : 1) @@ -102,12 +100,21 @@ export async function refreshDependentChannels( await delay(20) - await refreshChannels(channelsToRefresh, property ? [property] : undefined) + await refreshChannels( + category, + channelsToRefresh, + property ? [property] : undefined + ) } -export function updateChannelPair(channelId: string, paired: boolean): void { - if (paired) pairedChannels.add(channelId) - else pairedChannels.delete(channelId) +export function updateChannelPair( + category: string, + channelId: string, + paired: boolean +): void { + if (!pairedChannels.has(category)) pairedChannels.set(category, new Set()) + if (paired) pairedChannels.get(category)!.add(channelId) + else pairedChannels.get(category)!.delete(channelId) } export function updateGroupActive(group: string, active: boolean): void { diff --git a/config/remote-mixer-config.js b/config/remote-mixer-config.js index ecf2a1f..d9e84ef 100644 --- a/config/remote-mixer-config.js +++ b/config/remote-mixer-config.js @@ -7,6 +7,7 @@ const userConfig = { // httpPort: 8080, // logLevel: 'debug', // device: 'dummy' + device: 'yamaha-01v96' } module.exports = userConfig diff --git a/frontend/src/controls/entry-control.tsx b/frontend/src/controls/entry-control.tsx index 31e25bc..54ec0da 100644 --- a/frontend/src/controls/entry-control.tsx +++ b/frontend/src/controls/entry-control.tsx @@ -51,6 +51,8 @@ export function EntryControl({ subLabel={state.name} color={state.color ?? undefined} meterRef={meterRef} + category={category} + id={id} /> ))} diff --git a/frontend/src/ui/controls/fader/fader.tsx b/frontend/src/ui/controls/fader/fader.tsx index a04d2fb..d0ae976 100644 --- a/frontend/src/ui/controls/fader/fader.tsx +++ b/frontend/src/ui/controls/fader/fader.tsx @@ -8,6 +8,7 @@ import { useDelayedState } from '../../../hooks/delayed-state' import { FaderBase } from './fader-base' import { FaderButton } from './fader-button' +import { useEntryState } from '../../../api/state' export interface FaderProps { value: number @@ -19,9 +20,29 @@ export interface FaderProps { color?: string meterRef?: React.RefObject onChange: (value: number) => void - className?: string + category: string + id: string } +const pairColors = [ + '#e6194B', + '#3cb44b', + '#ffe119', + '#4363d8', + '#f58231', + '#911eb4', + '#42d4f4', + '#f032e6', + '#bfef45', + '#fabed4', + '#469990', + '#dcbeff', + '#9A6324', + '#fffac8', + '#800000', + '#aaffc3', +] + export const Fader = ({ value, min = 0, @@ -31,19 +52,25 @@ export const Fader = ({ subLabel, color, onChange, - ...passThrough + meterRef, + category, + id, }: FaderProps) => { const [localValue, setLocalValue] = useDelayedState(null) const valueToUse = localValue ?? value + const state = useEntryState(category, id) + + let pairColor = color + if (state?.paired) { + const pairIndex = Math.floor((parseInt(id) - 1) / 2) + pairColor = pairColors[pairIndex % pairColors.length] + } return ( { const newRawValue = fractionToValue(fraction, min, max) - if (newRawValue === valueToUse) { - return - } setLocalValue(newRawValue) const roundedValue = roundToStep(newRawValue, step) onChange(roundedValue) @@ -54,7 +81,7 @@ export const Fader = ({ fraction={valueToFraction(valueToUse, min, max)} label={label} subLabel={subLabel} - color={color} + color={pairColor} /> ) From 02ae529ae4a053b52de82f3355008cb98e87d51a Mon Sep 17 00:00:00 2001 From: Rafael Ribeiro Date: Sat, 4 Oct 2025 15:37:12 -0300 Subject: [PATCH 2/2] remove config device 'yamaha-01v96' --- config/remote-mixer-config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/config/remote-mixer-config.js b/config/remote-mixer-config.js index d9e84ef..ecf2a1f 100644 --- a/config/remote-mixer-config.js +++ b/config/remote-mixer-config.js @@ -7,7 +7,6 @@ const userConfig = { // httpPort: 8080, // logLevel: 'debug', // device: 'dummy' - device: 'yamaha-01v96' } module.exports = userConfig