diff --git a/src/actions/graph.ts b/src/actions/graph.ts index adec1325..7a5a3939 100644 --- a/src/actions/graph.ts +++ b/src/actions/graph.ts @@ -7,13 +7,14 @@ import { ConnectionType, GraphConnectionRecord, GraphControlNodeRecord, GraphNod import { getConnectionsForSourceNodeAndPort, getNode, getPatcherNodeByIndex, getNodes, getSystemNodeByJackNameAndDirection, getConnections, getPatcherNodes, getSystemNodes, getControlNodes } from "../selectors/graph"; import { showNotification } from "./notifications"; import { NotificationLevel } from "../models/notification"; -import { InstanceStateRecord } from "../models/instance"; -import { deleteInstance, setInstance, setInstances } from "./instances"; -import { getInstance } from "../selectors/instances"; -import { PatcherRecord } from "../models/patcher"; -import { getPatchers } from "../selectors/patchers"; +import { PatcherInstanceRecord } from "../models/instance"; +import { deleteInstance, setInstance, setInstanceMessageInports, setInstanceMessageOutports, setInstanceParameters, setInstances } from "./patchers"; +import { getPatcherInstance, getPatcherExports } from "../selectors/patchers"; +import { PatcherExportRecord } from "../models/patcher"; import { defaultNodeGap, nodeDefaultWidth, nodeHeaderHeight } from "../lib/constants"; import { getGraphEditorInstance } from "../selectors/editor"; +import { ParameterRecord } from "../models/parameter"; +import { MessagePortRecord } from "../models/messageport"; const getPatcherOrControlNodeCoordinates = (node: GraphPatcherNodeRecord | GraphControlNodeRecord, nodes: GraphNodeRecord[]): { x: number, y: number } => { @@ -342,7 +343,10 @@ export const initNodes = (jackPortsInfo: OSCQueryRNBOJackPortInfo, instanceInfo: const state = getState(); const existingNodes = getNodes(state); - const instances: InstanceStateRecord[] = []; + const instances: PatcherInstanceRecord[] = []; + const instanceParameters: ParameterRecord[] = []; + const instanceMessageInports: MessagePortRecord[] = []; + const instanceMessageOutports: MessagePortRecord[] = []; const patcherAndControlNodes: Array = []; const meta: OSCQuerySetMeta = deserializeSetMeta(instanceInfo.CONTENTS.control.CONTENTS.sets.CONTENTS.meta.VALUE as string); @@ -356,7 +360,11 @@ export const initNodes = (jackPortsInfo: OSCQueryRNBOJackPortInfo, instanceInfo: node = node.updatePosition(x, y); patcherAndControlNodes.push(node); - instances.push(InstanceStateRecord.fromDescription(info)); + const instance = PatcherInstanceRecord.fromDescription(info); + instances.push(instance); + instanceParameters.push(...ParameterRecord.fromDescription(instance.index, info.CONTENTS.params)); + instanceMessageInports.push(...MessagePortRecord.fromDescription(instance.index, info.CONTENTS.messages?.CONTENTS?.in)); + instanceMessageOutports.push(...MessagePortRecord.fromDescription(instance.index, info.CONTENTS.messages?.CONTENTS?.out)); } // Build a list of all Jack generated names that have not been used for PatcherNodes above @@ -421,6 +429,9 @@ export const initNodes = (jackPortsInfo: OSCQueryRNBOJackPortInfo, instanceInfo: dispatch(deleteNodes(existingNodes.valueSeq().toArray())); dispatch(setNodes([...systemNodes, ...patcherAndControlNodes])); dispatch(setInstances(instances)); + dispatch(setInstanceParameters(instanceParameters)); + dispatch(setInstanceMessageInports(instanceMessageInports)); + dispatch(setInstanceMessageOutports(instanceMessageOutports)); dispatch(setPortsAliases(portAliases)); }; @@ -533,7 +544,7 @@ export const updateSystemOrControlPortInfo = (type: ConnectionType, direction: P let systemInputY = -defaultNodeGap; let systemOutputY = -defaultNodeGap; - const patchers = getPatchers(state).valueSeq(); + const patchers = getPatcherExports(state).valueSeq(); const missingSystemOrControlJackName = Array.from(systemOrControlJackNames.values()) .filter(name => !patchers.find(patcher => name.startsWith(`${patcher.name}-`))); @@ -617,7 +628,7 @@ export const unloadPatcherNodeByIndexOnRemote = (instanceIndex: number): AppThun } }; -export const loadPatcherNodeOnRemote = (patcher: PatcherRecord): AppThunk => +export const loadPatcherNodeOnRemote = (patcher: PatcherExportRecord): AppThunk => (dispatch) => { try { const message = { @@ -683,8 +694,15 @@ export const addPatcherNode = (desc: OSCQueryRNBOInstance, metaString: string): dispatch(setNode(node)); // Create Instance State - const instance = InstanceStateRecord.fromDescription(desc); + const instance = PatcherInstanceRecord.fromDescription(desc); + const parameters = ParameterRecord.fromDescription(instance.index, desc.CONTENTS.params); + const messageInports = MessagePortRecord.fromDescription(instance.index, desc.CONTENTS.messages?.CONTENTS?.in); + const messageOutports = MessagePortRecord.fromDescription(instance.index, desc.CONTENTS.messages?.CONTENTS?.out); + dispatch(setInstance(instance)); + dispatch(setInstanceParameters(parameters)); + dispatch(setInstanceMessageInports(messageInports)); + dispatch(setInstanceMessageOutports(messageOutports)); }; export const removePatcherNode = (index: number): AppThunk => @@ -696,8 +714,9 @@ export const removePatcherNode = (index: number): AppThunk => if (node?.type !== NodeType.Patcher) return; dispatch(deleteNode(node)); - const instance = getInstance(state, node.id); + const instance = getPatcherInstance(state, node.id); if (!instance) return; + dispatch(deleteInstance(instance)); } catch (e) { console.log(e); diff --git a/src/actions/instances.ts b/src/actions/instances.ts deleted file mode 100644 index 3241354a..00000000 --- a/src/actions/instances.ts +++ /dev/null @@ -1,601 +0,0 @@ -import Router from "next/router"; -import { ActionBase, AppThunk } from "../lib/store"; -import { OSCQueryRNBOInstance, OSCQueryRNBOInstancePresetEntries, OSCValue } from "../lib/types"; -import { InstanceStateRecord } from "../models/instance"; -import { getInstanceByIndex, getInstance } from "../selectors/instances"; -import { getAppSetting } from "../selectors/settings"; -import { ParameterRecord } from "../models/parameter"; -import { MessagePortRecord } from "../models/messageport"; -import { OSCArgument, writePacket } from "osc"; -import { showNotification } from "./notifications"; -import { NotificationLevel } from "../models/notification"; -import { oscQueryBridge } from "../controller/oscqueryBridgeController"; -import throttle from "lodash.throttle"; -import { PresetRecord } from "../models/preset"; -import { AppSetting } from "../models/settings"; -import { DataRefRecord } from "../models/dataref"; -import { DataFileRecord } from "../models/datafile"; - -export enum InstanceActionType { - SET_INSTANCE = "SET_INSTANCE", - SET_INSTANCES = "SET_INSTANCES", - DELETE_INSTANCE = "DELETE_INSTANCE", - DELETE_INSTANCES = "DELETE_INSTANCES" -} - -export interface ISetInstance extends ActionBase { - type: InstanceActionType.SET_INSTANCE; - payload: { - instance: InstanceStateRecord; - }; -} - -export interface ISetInstances extends ActionBase { - type: InstanceActionType.SET_INSTANCES; - payload: { - instances: InstanceStateRecord[]; - }; -} - -export interface IDeleteInstance extends ActionBase { - type: InstanceActionType.DELETE_INSTANCE; - payload: { - instance: InstanceStateRecord; - }; -} - -export interface IDeleteInstances extends ActionBase { - type: InstanceActionType.DELETE_INSTANCES; - payload: { - instances: InstanceStateRecord[]; - }; -} - -export type InstanceAction = ISetInstance | ISetInstances | IDeleteInstance | IDeleteInstances; - -export const setInstance = (instance: InstanceStateRecord): ISetInstance => ({ - type: InstanceActionType.SET_INSTANCE, - payload: { - instance - } -}); - -export const setInstances = (instances: InstanceStateRecord[]): ISetInstances => ({ - type: InstanceActionType.SET_INSTANCES, - payload: { - instances - } -}); - -export const deleteInstance = (instance: InstanceStateRecord): IDeleteInstance => ({ - type: InstanceActionType.DELETE_INSTANCE, - payload: { - instance - } -}); - -export const deleteInstances = (instances: InstanceStateRecord[]): IDeleteInstances => ({ - type: InstanceActionType.DELETE_INSTANCES, - payload: { - instances - } -}); - -// Trigger Events on Remote OSCQuery Runner -export const loadPresetOnRemoteInstance = (instance: InstanceStateRecord, preset: PresetRecord): AppThunk => - (dispatch) => { - try { - const message = { - address: `${instance.path}/presets/load`, - args: [ - { type: "s", value: preset.name } - ] - }; - oscQueryBridge.sendPacket(writePacket(message)); - } catch (err) { - dispatch(showNotification({ - level: NotificationLevel.error, - title: `Error while trying to load preset ${preset.name}`, - message: "Please check the console for further details." - })); - console.log(err); - } - }; - -export const savePresetToRemoteInstance = (instance: InstanceStateRecord, name: string): AppThunk => - (dispatch) => { - try { - const message = { - address: `${instance.path}/presets/save`, - args: [ - { type: "s", value: name } - ] - }; - oscQueryBridge.sendPacket(writePacket(message)); - } catch (err) { - dispatch(showNotification({ - level: NotificationLevel.error, - title: `Error while trying to save preset ${name}`, - message: "Please check the console for further details." - })); - console.log(err); - } - }; - -export const destroyPresetOnRemoteInstance = (instance: InstanceStateRecord, preset: PresetRecord): AppThunk => - (dispatch) => { - try { - const message = { - address: `${instance.path}/presets/delete`, - args: [ - { type: "s", value: preset.name } - ] - }; - oscQueryBridge.sendPacket(writePacket(message)); - } catch (err) { - dispatch(showNotification({ - level: NotificationLevel.error, - title: `Error while trying to delete preset ${preset.name}`, - message: "Please check the console for further details." - })); - console.log(err); - } - }; - -export const renamePresetOnRemoteInstance = (instance: InstanceStateRecord, preset: PresetRecord, name: string): AppThunk => - (dispatch) => { - try { - const message = { - address: `${instance.path}/presets/rename`, - args: [ - { type: "s", value: preset.name }, - { type: "s", value: name } - ] - }; - oscQueryBridge.sendPacket(writePacket(message)); - } catch (err) { - dispatch(showNotification({ - level: NotificationLevel.error, - title: `Error while trying to rename preset ${preset.name} to ${name}`, - message: "Please check the console for further details." - })); - console.log(err); - } - }; - -export const setInitialPresetOnRemoteInstance = (instance: InstanceStateRecord, preset: PresetRecord): AppThunk => - (dispatch) => { - try { - const message = { - address: `${instance.path}/presets/initial`, - args: [ - { type: "s", value: preset.name } - ] - }; - oscQueryBridge.sendPacket(writePacket(message)); - } catch (err) { - dispatch(showNotification({ - level: NotificationLevel.error, - title: `Error while trying to set initial preset to ${preset.name}`, - message: "Please check the console for further details." - })); - console.log(err); - } - }; - -export const sendInstanceMessageToRemote = (instance: InstanceStateRecord, inportId: string, value: string): AppThunk => - (dispatch) => { - const values = value.split(" ").reduce((values, v) => { - const fv = parseFloat(v.replaceAll(",", ".").trim()); - if (!isNaN(fv)) values.push({ type: "f", value: fv }); - return values; - }, [] as OSCArgument[]); - - if (!values.length) { - dispatch(showNotification({ - title: "Invalid Message Input", - level: NotificationLevel.warn, - message: `Could not send message input "${value}" as it appears to contain non-valid number input. Please provide a single or multiple numbers separated by a space.` - })); - return; - } - - - const message = { - address: `/rnbo/inst/${instance.index}/messages/in/${inportId}`, - args: values - }; - oscQueryBridge.sendPacket(writePacket(message)); - }; - -export const triggerInstanceMidiNoteOnEventOnRemote = (instance: InstanceStateRecord, note: number): AppThunk => - () => { - - const midiChannel = 0; - const routeByte = 144 + midiChannel; - const velocityByte = 100; - - const message = { - address: `${instance.path}/midi/in`, - args: [routeByte, note, velocityByte].map(byte => ({ type: "i", value: byte })) - }; - - oscQueryBridge.sendPacket(writePacket(message)); - }; - -export const triggerInstanceMidiNoteOffEventOnRemote = (instance: InstanceStateRecord, note: number): AppThunk => - () => { - - const midiChannel = 0; - const routeByte = 128 + midiChannel; - const velocityByte = 0; - - const message = { - address: `${instance.path}/midi/in`, - args: [routeByte, note, velocityByte].map(byte => ({ type: "i", value: byte })) - }; - - oscQueryBridge.sendPacket(writePacket(message)); - }; - -export const setInstanceParameterValueNormalizedOnRemote = throttle((instance: InstanceStateRecord, param: ParameterRecord, value: number): AppThunk => - (dispatch) => { - - const message = { - address: `${param.path}/normalized`, - args: [ - { type: "f", value } - ] - }; - - oscQueryBridge.sendPacket(writePacket(message)); - // optimistic local state update - dispatch(setInstance(instance.setParameterNormalizedValue(param.id, value))); - }, 25); - -export const setInstanceDataRefValueOnRemote = (instance: InstanceStateRecord, dataref: DataRefRecord, file?: DataFileRecord): AppThunk => - () => { - - const message = { - address: `${instance.path}/data_refs/${dataref.id}`, - args: [ - { type: "s", value: file?.fileName || "" } // no files unsets - ] - }; - - oscQueryBridge.sendPacket(writePacket(message)); - }; - -export const setInstanceParameterMetaOnRemote = (_instance: InstanceStateRecord, param: ParameterRecord, value: string): AppThunk => - () => { - const message = { - address: `${param.path}/meta`, - args: [ - { type: "s", value } - ] - }; - - oscQueryBridge.sendPacket(writePacket(message)); - }; - -export const restoreDefaultParameterMetaOnRemote = (_instance: InstanceStateRecord, param: ParameterRecord): AppThunk => - () => { - const message = { - address: `${param.path}/meta`, - args: [ - { type: "s", value: "" } - ] - }; - - oscQueryBridge.sendPacket(writePacket(message)); - }; - -export const activateParameterMIDIMappingFocus = (instance: InstanceStateRecord, param: ParameterRecord): AppThunk => - (dispatch) => { - dispatch(setInstance(instance.setParameterWaitingForMidiMapping(param.id))); - }; - -export const clearParameterMidiMappingOnRemote = (id: InstanceStateRecord["id"], paramId: ParameterRecord["id"]): AppThunk => - (_dispatch, getState) => { - const state = getState(); - const instance = getInstance(state, id); - if (!instance) return; - - const param = instance.parameters.get(paramId); - if (!param) return; - - const meta = param.getParsedMetaObject(); - delete meta.midi; - const message = { - address: `${param.path}/meta`, - args: [ - { type: "s", value: JSON.stringify(meta) } - ] - }; - - oscQueryBridge.sendPacket(writePacket(message)); - }; - -export const setInstanceMessagePortMetaOnRemote = (_instance: InstanceStateRecord, port: MessagePortRecord, value: string): AppThunk => - () => { - const message = { - address: `${port.path}/meta`, - args: [ - { type: "s", value } - ] - }; - - oscQueryBridge.sendPacket(writePacket(message)); - }; - -export const restoreDefaultMessagePortMetaOnRemote = (_instance: InstanceStateRecord, port: MessagePortRecord): AppThunk => - () => { - const message = { - address: `${port.path}/meta`, - args: [ - { type: "s", value: "" } - ] - }; - - oscQueryBridge.sendPacket(writePacket(message)); - }; - -// Updates in response to remote OSCQuery Updates -export const updateInstancePresetEntries = (index: number, entries: OSCQueryRNBOInstancePresetEntries): AppThunk => - (dispatch, getState) => { - try { - const state = getState(); - const instance = getInstanceByIndex(state, index); - if (!instance) return; - - dispatch(setInstance(instance.updatePresets(entries))); - } catch (e) { - console.log(e); - } - }; - -export const updateInstancePresetLatest = (index: number, name: string): AppThunk => - (dispatch, getState) => { - try { - const state = getState(); - const instance = getInstanceByIndex(state, index); - if (!instance) return; - - dispatch(setInstance(instance.setPresetLatest(name))); - } catch (e) { - console.log(e); - } - }; - -export const updateInstancePresetInitial = (index: number, name: string): AppThunk => - (dispatch, getState) => { - try { - const state = getState(); - const instance = getInstanceByIndex(state, index); - if (!instance) return; - - dispatch(setInstance(instance.setPresetInitial(name))); - } catch (e) { - console.log(e); - } - }; - -export const updateInstanceMessages = (index: number, desc: OSCQueryRNBOInstance["CONTENTS"]["messages"]): AppThunk => - (dispatch, getState) => { - try { - if (!desc) return; - - const state = getState(); - const instance = getInstanceByIndex(state, index); - if (!instance) return; - - dispatch(setInstance( - instance - .set("messageInports", InstanceStateRecord.messagesFromDescription(desc.CONTENTS?.in)) - .set("messageOutports", InstanceStateRecord.messagesFromDescription(desc.CONTENTS?.out)) - )); - } catch (e) { - console.log(e); - } - }; - -export const updateInstanceMessageOutportValue = (index: number, name: string, value: OSCValue | OSCValue[]): AppThunk => - (dispatch, getState) => { - try { - - const state = getState(); - - // Debug enabled?! - const enabled = getAppSetting(state, AppSetting.debugMessageOutput)?.value || false; - if (!enabled) return; - - // Active Instance view?! - if (Router.pathname !== "/instances/[index]" || Router.query.index !== `${index}`) return; - - const instance = getInstanceByIndex(state, index); - if (!instance) return; - - dispatch(setInstance( - instance.setMessageOutportValue(name, Array.isArray(value) ? value.join(", ") : `${value}`) - )); - } catch (e) { - console.log(e); - } - }; - -export const updateInstanceParameters = (index: number, desc: OSCQueryRNBOInstance["CONTENTS"]["params"]): AppThunk => - (dispatch, getState) => { - try { - if (!desc) return; - - const state = getState(); - const instance = getInstanceByIndex(state, index); - if (!instance) return; - - dispatch(setInstance( - instance.set("parameters", InstanceStateRecord.parametersFromDescription(desc)) - )); - } catch (e) { - console.log(e); - } - }; - -export const updateInstanceDataRefValue = (index: number, name: string, value: string): AppThunk => - (dispatch, getState) => { - try { - const state = getState(); - - const instance = getInstanceByIndex(state, index); - if (!instance) return; - - dispatch(setInstance( - instance.setDataRefValue(name, value) - )); - } catch (e) { - console.log(e); - } - }; - -export const updateInstanceParameterValue = (index: number, id: ParameterRecord["id"], value: number): AppThunk => - (dispatch, getState) => { - try { - const state = getState(); - const instance = getInstanceByIndex(state, index); - if (!instance) return; - - dispatch(setInstance(instance.setParameterValue(id, value))); - } catch (e) { - console.log(e); - } - }; - -export const updateInstanceParameterValueNormalized = (index: number, id: ParameterRecord["id"], value: number): AppThunk => - (dispatch, getState) => { - try { - const state = getState(); - const instance = getInstanceByIndex(state, index); - if (!instance) return; - - dispatch(setInstance(instance.setParameterNormalizedValue(id, value))); - } catch (e) { - console.log(e); - } - }; - -export const setInstanceWaitingForMidiMappingOnRemote = (id: InstanceStateRecord["id"], value: boolean): AppThunk => - (dispatch, getState) => { - try { - const state = getState(); - const instance = getInstance(state, id); - if (!instance) return; - - dispatch(setInstance(instance.setWaitingForMapping(value))); - - try { - const message = { - address: `${instance.path}/midi/last/report`, - args: [ - { type: value ? "T" : "F", value: value ? "true" : "false" } - ] - }; - oscQueryBridge.sendPacket(writePacket(message)); - } catch (err) { - dispatch(showNotification({ - level: NotificationLevel.error, - title: "Error while trying set midi mapping mode on remote", - message: "Please check the console for further details." - })); - console.log(err); - } - } catch (e) { - console.log(e); - } - }; - -export const updateInstanceMIDIReport = (index: number, value: boolean): AppThunk => - (dispatch, getState) => { - try { - const state = getState(); - const instance = getInstanceByIndex(state, index); - if (!instance) return; - dispatch(setInstance(instance.setWaitingForMapping(value))); - } catch (e) { - console.log(e); - } - }; - -export const updateInstanceMIDILastValue = (index: number, value: string): AppThunk => - (dispatch, getState) => { - try { - - const state = getState(); - - let instance = getInstanceByIndex(state, index); - if (!instance?.waitingForMidiMapping) return; - - const midiMeta = JSON.parse(value); - - // find waiting, update their meta, set them no longer waiting and update map - instance = instance.set("parameters", instance.parameters.map(param => { - if (param.waitingForMidiMapping) { - const meta = param.getParsedMetaObject(); - meta.midi = midiMeta; - - const message = { - address: `${param.path}/meta`, - args: [ - { type: "s", value: JSON.stringify(meta) } - ] - }; - - oscQueryBridge.sendPacket(writePacket(message)); - return param.setWaitingForMidiMapping(false); - } - return param; - })); - - dispatch(setInstance(instance)); - - } catch (e) { - console.log(e); - } - }; - -export const updateInstanceParameterMeta = (index: number, id: ParameterRecord["id"], value: string): AppThunk => - (dispatch, getState) => { - try { - const state = getState(); - const instance = getInstanceByIndex(state, index); - if (!instance) return; - - dispatch(setInstance(instance.setParameterMeta(id, value))); - } catch (e) { - console.log(e); - } - }; - -export const updateInstanceMessageOutportMeta = (index: number, id: MessagePortRecord["id"], value: string): AppThunk => - (dispatch, getState) => { - try { - const state = getState(); - const instance = getInstanceByIndex(state, index); - if (!instance) return; - - dispatch(setInstance(instance.setMessageOutportMeta(id, value))); - } catch (e) { - console.log(e); - } - }; - -export const updateInstanceMessageInportMeta = (index: number, id: MessagePortRecord["id"], value: string): AppThunk => - (dispatch, getState) => { - try { - const state = getState(); - const instance = getInstanceByIndex(state, index); - if (!instance) return; - - dispatch(setInstance(instance.setMessageInportMeta(id, value))); - } catch (e) { - console.log(e); - } - }; diff --git a/src/actions/patchers.ts b/src/actions/patchers.ts index 6990aa22..a0f7c54f 100644 --- a/src/actions/patchers.ts +++ b/src/actions/patchers.ts @@ -1,41 +1,186 @@ +import Router from "next/router"; import { ActionBase, AppThunk } from "../lib/store"; -import { OSCQueryRNBOPatchersState } from "../lib/types"; -import { PatcherRecord } from "../models/patcher"; -import { oscQueryBridge } from "../controller/oscqueryBridgeController"; -import { writePacket, OSCMessage } from "osc"; +import { OSCQueryRNBOInstance, OSCQueryRNBOInstancePresetEntries, OSCQueryRNBOPatchersState, OSCValue } from "../lib/types"; +import { PatcherInstanceRecord } from "../models/instance"; +import { getPatcherInstanceByIndex, getPatcherInstance, getPatcherInstanceParametersByInstanceIndex, getPatcherInstanceParameter, getPatcherInstanceMessageInportsByInstanceIndex, getPatcherInstanceMesssageOutportsByInstanceIndex, getPatcherInstanceMessageInportByPath, getPatcherInstanceMessageOutportByPath, getPatcherInstanceMesssageOutportsByInstanceIndexAndTag, getPatcherInstanceParameterByPath, getPatcherInstanceParametersByInstanceIndexAndName, getPatcherInstanceMessageInportsByInstanceIndexAndTag } from "../selectors/patchers"; +import { getAppSetting } from "../selectors/settings"; +import { ParameterRecord } from "../models/parameter"; +import { MessagePortRecord } from "../models/messageport"; +import { OSCArgument, OSCMessage, writePacket } from "osc"; import { showNotification } from "./notifications"; import { NotificationLevel } from "../models/notification"; +import { oscQueryBridge } from "../controller/oscqueryBridgeController"; +import throttle from "lodash.throttle"; +import { PresetRecord } from "../models/preset"; +import { AppSetting } from "../models/settings"; +import { DataRefRecord } from "../models/dataref"; +import { DataFileRecord } from "../models/datafile"; +import { PatcherExportRecord } from "../models/patcher"; export enum PatcherActionType { - INIT = "INIT_PATCHERS" + INIT_PATCHERS = "INIT_PATCHERS", + + SET_INSTANCE = "SET_INSTANCE", + SET_INSTANCES = "SET_INSTANCES", + DELETE_INSTANCE = "DELETE_INSTANCE", + DELETE_INSTANCES = "DELETE_INSTANCES", + + SET_PARAMETER = "SET_PARAMETER", + SET_PARAMETERS = "SET_PARAMETERS", + DELETE_PARAMETER = "DELETE_PARAMETER", + DELETE_PARAMETERS = "DELETE_PARAMETERS", + + SET_MESSAGE_INPORT = "SET_MESSAGE_INPORT", + SET_MESSAGE_INPORTS = "SET_MESSAGE_INPORTS", + DELETE_MESSAGE_INPORT = "DELETE_MESSAGE_INPORT", + DELETE_MESSAGE_INPORTS = "DELETE_MESSAGE_INPORTS", + + SET_MESSAGE_OUTPORT = "SET_MESSAGE_OUTPORT", + SET_MESSAGE_OUTPORTS = "SET_MESSAGE_OUTPORTS", + DELETE_MESSAGE_OUTPORT = "DELETE_MESSAGE_OUTPORT", + DELETE_MESSAGE_OUTPORTS = "DELETE_MESSAGE_OUTPORTS" } export interface IInitPatchers extends ActionBase { - type: PatcherActionType.INIT; + type: PatcherActionType.INIT_PATCHERS; payload: { - patchers: PatcherRecord[] - } + patchers: PatcherExportRecord[]; + }; } -export type PatcherAction = IInitPatchers; +export interface ISetInstance extends ActionBase { + type: PatcherActionType.SET_INSTANCE; + payload: { + instance: PatcherInstanceRecord; + }; +} +export interface ISetInstances extends ActionBase { + type: PatcherActionType.SET_INSTANCES; + payload: { + instances: PatcherInstanceRecord[]; + }; +} + +export interface IDeleteInstance extends ActionBase { + type: PatcherActionType.DELETE_INSTANCE; + payload: { + instance: PatcherInstanceRecord; + }; +} -export const initPatchers = (patchersInfo: OSCQueryRNBOPatchersState): PatcherAction => { +export interface IDeleteInstances extends ActionBase { + type: PatcherActionType.DELETE_INSTANCES; + payload: { + instances: PatcherInstanceRecord[]; + }; +} + +export interface ISetInstanceParameter extends ActionBase { + type: PatcherActionType.SET_PARAMETER; + payload: { + parameter: ParameterRecord; + }; +} + +export interface ISetInstanceParameters extends ActionBase { + type: PatcherActionType.SET_PARAMETERS; + payload: { + parameters: ParameterRecord[]; + }; +} + +export interface IDeleteInstanceParameter extends ActionBase { + type: PatcherActionType.DELETE_PARAMETER; + payload: { + parameter: ParameterRecord; + }; +} + +export interface IDeleteInstanceParameters extends ActionBase { + type: PatcherActionType.DELETE_PARAMETERS; + payload: { + parameters: ParameterRecord[]; + }; +} + +export interface ISetInstanceMessageInport extends ActionBase { + type: PatcherActionType.SET_MESSAGE_INPORT; + payload: { + port: MessagePortRecord; + }; +} + +export interface ISetInstanceMessageInports extends ActionBase { + type: PatcherActionType.SET_MESSAGE_INPORTS; + payload: { + ports: MessagePortRecord[]; + }; +} - const patchers: PatcherRecord[] = []; +export interface IDeleteInstanceMessageInport extends ActionBase { + type: PatcherActionType.DELETE_MESSAGE_INPORT; + payload: { + port: MessagePortRecord; + }; +} + +export interface IDeleteInstanceMessageInports extends ActionBase { + type: PatcherActionType.DELETE_MESSAGE_INPORTS; + payload: { + ports: MessagePortRecord[]; + }; +} + +export interface ISetInstanceMessageOutport extends ActionBase { + type: PatcherActionType.SET_MESSAGE_OUTPORT; + payload: { + port: MessagePortRecord; + }; +} + +export interface ISetInstanceMessageOutports extends ActionBase { + type: PatcherActionType.SET_MESSAGE_OUTPORTS; + payload: { + ports: MessagePortRecord[]; + }; +} + +export interface IDeleteInstanceMessageOutport extends ActionBase { + type: PatcherActionType.DELETE_MESSAGE_OUTPORT; + payload: { + port: MessagePortRecord; + }; +} + +export interface IDeleteInstanceMessageOutports extends ActionBase { + type: PatcherActionType.DELETE_MESSAGE_OUTPORTS; + payload: { + ports: MessagePortRecord[]; + }; +} + +export type InstanceAction = IInitPatchers | ISetInstance | ISetInstances | IDeleteInstance | IDeleteInstances | +ISetInstanceParameter | ISetInstanceParameters | IDeleteInstanceParameter | IDeleteInstanceParameters | +ISetInstanceMessageInport | ISetInstanceMessageInports | IDeleteInstanceMessageInport | IDeleteInstanceMessageInports | +ISetInstanceMessageOutport | ISetInstanceMessageOutports | IDeleteInstanceMessageOutport | IDeleteInstanceMessageOutports; + +export const initPatchers = (patchersInfo: OSCQueryRNBOPatchersState): IInitPatchers => { + + const patchers: PatcherExportRecord[] = []; for (const [name, desc] of Object.entries(patchersInfo.CONTENTS || {})) { - patchers.push(PatcherRecord.fromDescription(name, desc)); + patchers.push(PatcherExportRecord.fromDescription(name, desc)); } return { - type: PatcherActionType.INIT, + type: PatcherActionType.INIT_PATCHERS, payload: { patchers } }; }; -export const destroyPatcherOnRemote = (patcher: PatcherRecord): AppThunk => +export const destroyPatcherOnRemote = (patcher: PatcherExportRecord): AppThunk => (dispatch) => { try { const message: OSCMessage = { @@ -54,7 +199,7 @@ export const destroyPatcherOnRemote = (patcher: PatcherRecord): AppThunk => }; -export const renamePatcherOnRemote = (patcher: PatcherRecord, newName: string): AppThunk => +export const renamePatcherOnRemote = (patcher: PatcherExportRecord, newName: string): AppThunk => (dispatch) => { try { const message = { @@ -73,3 +218,702 @@ export const renamePatcherOnRemote = (patcher: PatcherRecord, newName: string): console.error(err); } }; + + +export const setInstance = (instance: PatcherInstanceRecord): ISetInstance => ({ + type: PatcherActionType.SET_INSTANCE, + payload: { + instance + } +}); + +export const setInstances = (instances: PatcherInstanceRecord[]): ISetInstances => ({ + type: PatcherActionType.SET_INSTANCES, + payload: { + instances + } +}); + +export const deleteInstance = (instance: PatcherInstanceRecord): IDeleteInstance => ({ + type: PatcherActionType.DELETE_INSTANCE, + payload: { + instance + } +}); + +export const deleteInstances = (instances: PatcherInstanceRecord[]): IDeleteInstances => ({ + type: PatcherActionType.DELETE_INSTANCES, + payload: { + instances + } +}); + +export const setInstanceParameter = (param: ParameterRecord): ISetInstanceParameter => ({ + type: PatcherActionType.SET_PARAMETER, + payload: { + parameter: param + } +}); + +export const setInstanceParameters = (params: ParameterRecord[]): ISetInstanceParameters => ({ + type: PatcherActionType.SET_PARAMETERS, + payload: { + parameters: params + } +}); + +export const deleteInstanceParameter = (param: ParameterRecord): IDeleteInstanceParameter => ({ + type: PatcherActionType.DELETE_PARAMETER, + payload: { + parameter: param + } +}); + +export const deleteInstanceParameters = (params: ParameterRecord[]): IDeleteInstanceParameters => ({ + type: PatcherActionType.DELETE_PARAMETERS, + payload: { + parameters: params + } +}); + +export const setInstanceMessageInport = (port: MessagePortRecord): ISetInstanceMessageInport => ({ + type: PatcherActionType.SET_MESSAGE_INPORT, + payload: { + port + } +}); + +export const setInstanceMessageInports = (ports: MessagePortRecord[]): ISetInstanceMessageInports => ({ + type: PatcherActionType.SET_MESSAGE_INPORTS, + payload: { + ports + } +}); + +export const deleteInstanceMessageInport = (port: MessagePortRecord): IDeleteInstanceMessageInport => ({ + type: PatcherActionType.DELETE_MESSAGE_INPORT, + payload: { + port + } +}); + +export const deleteInstanceMessageInports = (ports: MessagePortRecord[]): IDeleteInstanceMessageInports => ({ + type: PatcherActionType.DELETE_MESSAGE_INPORTS, + payload: { + ports + } +}); + +export const setInstanceMessageOutport = (port: MessagePortRecord): ISetInstanceMessageOutport => ({ + type: PatcherActionType.SET_MESSAGE_OUTPORT, + payload: { + port + } +}); + +export const setInstanceMessageOutports = (ports: MessagePortRecord[]): ISetInstanceMessageOutports => ({ + type: PatcherActionType.SET_MESSAGE_OUTPORTS, + payload: { + ports + } +}); + +export const deleteInstanceMessageOutport = (port: MessagePortRecord): IDeleteInstanceMessageOutport => ({ + type: PatcherActionType.DELETE_MESSAGE_OUTPORT, + payload: { + port + } +}); + +export const deleteInstanceMessageOutports = (ports: MessagePortRecord[]): IDeleteInstanceMessageOutports => ({ + type: PatcherActionType.DELETE_MESSAGE_OUTPORTS, + payload: { + ports + } +}); + +// Trigger Events on Remote OSCQuery Runner +export const loadPresetOnRemoteInstance = (instance: PatcherInstanceRecord, preset: PresetRecord): AppThunk => + (dispatch) => { + try { + const message = { + address: `${instance.path}/presets/load`, + args: [ + { type: "s", value: preset.name } + ] + }; + oscQueryBridge.sendPacket(writePacket(message)); + } catch (err) { + dispatch(showNotification({ + level: NotificationLevel.error, + title: `Error while trying to load preset ${preset.name}`, + message: "Please check the console for further details." + })); + console.log(err); + } + }; + +export const savePresetToRemoteInstance = (instance: PatcherInstanceRecord, name: string): AppThunk => + (dispatch) => { + try { + const message = { + address: `${instance.path}/presets/save`, + args: [ + { type: "s", value: name } + ] + }; + oscQueryBridge.sendPacket(writePacket(message)); + } catch (err) { + dispatch(showNotification({ + level: NotificationLevel.error, + title: `Error while trying to save preset ${name}`, + message: "Please check the console for further details." + })); + console.log(err); + } + }; + +export const destroyPresetOnRemoteInstance = (instance: PatcherInstanceRecord, preset: PresetRecord): AppThunk => + (dispatch) => { + try { + const message = { + address: `${instance.path}/presets/delete`, + args: [ + { type: "s", value: preset.name } + ] + }; + oscQueryBridge.sendPacket(writePacket(message)); + } catch (err) { + dispatch(showNotification({ + level: NotificationLevel.error, + title: `Error while trying to delete preset ${preset.name}`, + message: "Please check the console for further details." + })); + console.log(err); + } + }; + +export const renamePresetOnRemoteInstance = (instance: PatcherInstanceRecord, preset: PresetRecord, name: string): AppThunk => + (dispatch) => { + try { + const message = { + address: `${instance.path}/presets/rename`, + args: [ + { type: "s", value: preset.name }, + { type: "s", value: name } + ] + }; + oscQueryBridge.sendPacket(writePacket(message)); + } catch (err) { + dispatch(showNotification({ + level: NotificationLevel.error, + title: `Error while trying to rename preset ${preset.name} to ${name}`, + message: "Please check the console for further details." + })); + console.log(err); + } + }; + +export const setInitialPresetOnRemoteInstance = (instance: PatcherInstanceRecord, preset: PresetRecord): AppThunk => + (dispatch) => { + try { + const message = { + address: `${instance.path}/presets/initial`, + args: [ + { type: "s", value: preset.name } + ] + }; + oscQueryBridge.sendPacket(writePacket(message)); + } catch (err) { + dispatch(showNotification({ + level: NotificationLevel.error, + title: `Error while trying to set initial preset to ${preset.name}`, + message: "Please check the console for further details." + })); + console.log(err); + } + }; + +export const sendInstanceMessageToRemote = (instance: PatcherInstanceRecord, inportId: string, value: string): AppThunk => + (dispatch) => { + const values = value.split(" ").reduce((values, v) => { + const fv = parseFloat(v.replaceAll(",", ".").trim()); + if (!isNaN(fv)) values.push({ type: "f", value: fv }); + return values; + }, [] as OSCArgument[]); + + if (!values.length) { + dispatch(showNotification({ + title: "Invalid Message Input", + level: NotificationLevel.warn, + message: `Could not send message input "${value}" as it appears to contain non-valid number input. Please provide a single or multiple numbers separated by a space.` + })); + return; + } + + + const message = { + address: `/rnbo/inst/${instance.index}/messages/in/${inportId}`, + args: values + }; + oscQueryBridge.sendPacket(writePacket(message)); + }; + +export const triggerInstanceMidiNoteOnEventOnRemote = (instance: PatcherInstanceRecord, note: number): AppThunk => + () => { + + const midiChannel = 0; + const routeByte = 144 + midiChannel; + const velocityByte = 100; + + const message = { + address: `${instance.path}/midi/in`, + args: [routeByte, note, velocityByte].map(byte => ({ type: "i", value: byte })) + }; + + oscQueryBridge.sendPacket(writePacket(message)); + }; + +export const triggerInstanceMidiNoteOffEventOnRemote = (instance: PatcherInstanceRecord, note: number): AppThunk => + () => { + + const midiChannel = 0; + const routeByte = 128 + midiChannel; + const velocityByte = 0; + + const message = { + address: `${instance.path}/midi/in`, + args: [routeByte, note, velocityByte].map(byte => ({ type: "i", value: byte })) + }; + + oscQueryBridge.sendPacket(writePacket(message)); + }; + +export const setInstanceParameterValueNormalizedOnRemote = throttle((instance: PatcherInstanceRecord, param: ParameterRecord, value: number): AppThunk => + (dispatch) => { + + const message = { + address: `${param.path}/normalized`, + args: [ + { type: "f", value } + ] + }; + + oscQueryBridge.sendPacket(writePacket(message)); + // optimistic local state update + dispatch(setInstanceParameter(param.setNormalizedValue(value))); + }, 10); + +export const setInstanceDataRefValueOnRemote = (instance: PatcherInstanceRecord, dataref: DataRefRecord, file?: DataFileRecord): AppThunk => + () => { + + const message = { + address: `${instance.path}/data_refs/${dataref.id}`, + args: [ + { type: "s", value: file?.fileName || "" } // no files unsets + ] + }; + + oscQueryBridge.sendPacket(writePacket(message)); + }; + +export const setInstanceParameterMetaOnRemote = (_instance: PatcherInstanceRecord, param: ParameterRecord, value: string): AppThunk => + () => { + const message = { + address: `${param.path}/meta`, + args: [ + { type: "s", value } + ] + }; + + oscQueryBridge.sendPacket(writePacket(message)); + }; + +export const restoreDefaultParameterMetaOnRemote = (_instance: PatcherInstanceRecord, param: ParameterRecord): AppThunk => + () => { + const message = { + address: `${param.path}/meta`, + args: [ + { type: "s", value: "" } + ] + }; + + oscQueryBridge.sendPacket(writePacket(message)); + }; + +export const activateParameterMIDIMappingFocus = (instance: PatcherInstanceRecord, param: ParameterRecord): AppThunk => + (dispatch, getState) => { + + const state = getState(); + const params = getPatcherInstanceParametersByInstanceIndex(state, instance.index); + + dispatch(setInstanceParameters( + params.valueSeq().toArray().map(p => p.setWaitingForMidiMapping(p.id === param.id)) + )); + }; + +export const clearParameterMidiMappingOnRemote = (id: PatcherInstanceRecord["id"], paramId: ParameterRecord["id"]): AppThunk => + (_dispatch, getState) => { + const state = getState(); + const instance = getPatcherInstance(state, id); + if (!instance) return; + + const param = getPatcherInstanceParameter(state, paramId); + if (!param) return; + + const meta = param.getParsedMetaObject(); + delete meta.midi; + const message = { + address: `${param.path}/meta`, + args: [ + { type: "s", value: JSON.stringify(meta) } + ] + }; + + oscQueryBridge.sendPacket(writePacket(message)); + }; + +export const setInstanceMessagePortMetaOnRemote = (_instance: PatcherInstanceRecord, port: MessagePortRecord, value: string): AppThunk => + () => { + const message = { + address: `${port.path}/meta`, + args: [ + { type: "s", value } + ] + }; + + oscQueryBridge.sendPacket(writePacket(message)); + }; + +export const restoreDefaultMessagePortMetaOnRemote = (_instance: PatcherInstanceRecord, port: MessagePortRecord): AppThunk => + () => { + const message = { + address: `${port.path}/meta`, + args: [ + { type: "s", value: "" } + ] + }; + + oscQueryBridge.sendPacket(writePacket(message)); + }; + +// Updates in response to remote OSCQuery Updates +export const updateInstancePresetEntries = (index: number, entries: OSCQueryRNBOInstancePresetEntries): AppThunk => + (dispatch, getState) => { + try { + const state = getState(); + const instance = getPatcherInstanceByIndex(state, index); + if (!instance) return; + + dispatch(setInstance(instance.updatePresets(entries))); + } catch (e) { + console.log(e); + } + }; + +export const updateInstancePresetLatest = (index: number, name: string): AppThunk => + (dispatch, getState) => { + try { + const state = getState(); + const instance = getPatcherInstanceByIndex(state, index); + if (!instance) return; + + dispatch(setInstance(instance.setPresetLatest(name))); + } catch (e) { + console.log(e); + } + }; + +export const updateInstancePresetInitial = (index: number, name: string): AppThunk => + (dispatch, getState) => { + try { + const state = getState(); + const instance = getPatcherInstanceByIndex(state, index); + if (!instance) return; + + dispatch(setInstance(instance.setPresetInitial(name))); + } catch (e) { + console.log(e); + } + }; + +export const updateInstanceMessages = (index: number, desc: OSCQueryRNBOInstance["CONTENTS"]["messages"]): AppThunk => + (dispatch, getState) => { + try { + if (!desc) return; + + const state = getState(); + const instance = getPatcherInstanceByIndex(state, index); + if (!instance) return; + + const currentMessageInports = getPatcherInstanceMessageInportsByInstanceIndex(state, instance.index); + const currentMessageOutports = getPatcherInstanceMesssageOutportsByInstanceIndex(state, instance.index); + dispatch(deleteInstanceMessageInports(currentMessageInports.valueSeq().toArray())); + dispatch(deleteInstanceMessageOutports(currentMessageOutports.valueSeq().toArray())); + + const messageInports = MessagePortRecord.fromDescription(instance.index, desc.CONTENTS?.in); + const messageOutports = MessagePortRecord.fromDescription(instance.index, desc.CONTENTS?.out); + + dispatch(setInstanceMessageInports(messageInports)); + dispatch(setInstanceMessageOutports(messageOutports)); + + } catch (e) { + console.log(e); + } + }; + +export const removeInstanceMessageInportByPath = (path: string): AppThunk => + (dispatch, getState) => { + try { + const state = getState(); + const port = getPatcherInstanceMessageInportByPath(state, path); + if (!port) return; + + dispatch(deleteInstanceMessageInport(port)); + } catch (e) { + console.log(e); + } + }; + +export const removeInstanceMessageOutportByPath = (path: string): AppThunk => + (dispatch, getState) => { + try { + const state = getState(); + const port = getPatcherInstanceMessageOutportByPath(state, path); + if (!port) return; + + dispatch(deleteInstanceMessageOutport(port)); + } catch (e) { + console.log(e); + } + }; + +export const updateInstanceMessageOutportValue = (index: number, tag: MessagePortRecord["tag"], value: OSCValue | OSCValue[]): AppThunk => + (dispatch, getState) => { + try { + + const state = getState(); + + // Debug enabled?! + const enabled = getAppSetting(state, AppSetting.debugMessageOutput)?.value || false; + if (!enabled) return; + + // Active Instance view?! + if (Router.pathname !== "/instances/[index]" || Router.query.index !== `${index}`) return; + + const instance = getPatcherInstanceByIndex(state, index); + if (!instance) return; + + const port = getPatcherInstanceMesssageOutportsByInstanceIndexAndTag(state, instance.index, tag); + if (!port) return; + + dispatch(setInstanceMessageOutport(port.setValue(Array.isArray(value) ? value.join(", ") : `${value}`))); + } catch (e) { + console.log(e); + } + }; + +export const removeInstanceParameterByPath = (path: string): AppThunk => + (dispatch, getState) => { + try { + const state = getState(); + const param = getPatcherInstanceParameterByPath(state, path); + if (!param) return; + + dispatch(deleteInstanceParameter(param)); + } catch (e) { + console.log(e); + } + }; + +export const updateInstanceParameters = (index: number, desc: OSCQueryRNBOInstance["CONTENTS"]["params"]): AppThunk => + (dispatch, getState) => { + try { + if (!desc) return; + + const state = getState(); + const instance = getPatcherInstanceByIndex(state, index); + if (!instance) return; + + const currentParams = getPatcherInstanceParametersByInstanceIndex(state, instance.index); + dispatch(deleteInstanceParameters(currentParams.valueSeq().toArray())); + + const newParams = ParameterRecord.fromDescription(instance.index, desc); + dispatch(setInstanceParameters(newParams)); + } catch (e) { + console.log(e); + } + }; + +export const updateInstanceDataRefValue = (index: number, name: string, value: string): AppThunk => + (dispatch, getState) => { + try { + const state = getState(); + + const instance = getPatcherInstanceByIndex(state, index); + if (!instance) return; + + dispatch(setInstance( + instance.setDataRefValue(name, value) + )); + } catch (e) { + console.log(e); + } + }; + +export const updateInstanceParameterValue = (index: number, name: ParameterRecord["name"], value: number): AppThunk => + (dispatch, getState) => { + try { + const state = getState(); + const param = getPatcherInstanceParametersByInstanceIndexAndName(state, index, name); + if (!param) return; + + dispatch(setInstanceParameter(param.setValue(value))); + } catch (e) { + console.log(e); + } + }; + +export const updateInstanceParameterValueNormalized = (index: number, name: ParameterRecord["name"], value: number): AppThunk => + (dispatch, getState) => { + try { + const state = getState(); + const param = getPatcherInstanceParametersByInstanceIndexAndName(state, index, name); + if (!param) return; + + dispatch(setInstanceParameter(param.setNormalizedValue(value))); + } catch (e) { + console.log(e); + } + }; + +export const setInstanceWaitingForMidiMappingOnRemote = (id: PatcherInstanceRecord["id"], value: boolean): AppThunk => + (dispatch, getState) => { + try { + const state = getState(); + const instance = getPatcherInstance(state, id); + if (!instance) return; + + dispatch(setInstance(instance.setWaitingForMapping(value))); + const params = getPatcherInstanceParametersByInstanceIndex(state, instance.index).valueSeq().map(p => p.setWaitingForMidiMapping(false)); + dispatch(setInstanceParameters(params.toArray())); + + try { + const message = { + address: `${instance.path}/midi/last/report`, + args: [ + { type: value ? "T" : "F", value: value ? "true" : "false" } + ] + }; + oscQueryBridge.sendPacket(writePacket(message)); + } catch (err) { + dispatch(showNotification({ + level: NotificationLevel.error, + title: "Error while trying set midi mapping mode on remote", + message: "Please check the console for further details." + })); + console.log(err); + } + } catch (e) { + console.log(e); + } + }; + +export const updateInstanceMIDIReport = (index: number, value: boolean): AppThunk => + (dispatch, getState) => { + try { + const state = getState(); + const instance = getPatcherInstanceByIndex(state, index); + if (!instance) return; + dispatch(setInstance(instance.setWaitingForMapping(value))); + const params = getPatcherInstanceParametersByInstanceIndex(state, instance.index).valueSeq().map(p => p.setWaitingForMidiMapping(false)); + dispatch(setInstanceParameters(params.toArray())); + } catch (e) { + console.log(e); + } + }; + +export const updateInstanceMIDILastValue = (index: number, value: string): AppThunk => + (dispatch, getState) => { + try { + + const state = getState(); + + const instance = getPatcherInstanceByIndex(state, index); + if (!instance?.waitingForMidiMapping) return; + + const midiMeta = JSON.parse(value); + + // find waiting, update their meta, set them no longer waiting and update map + const parameters: ParameterRecord[] = []; + getPatcherInstanceParametersByInstanceIndex(state, instance.index).forEach(param => { + if (param.waitingForMidiMapping) { + const meta = param.getParsedMetaObject(); + meta.midi = midiMeta; + + const message = { + address: `${param.path}/meta`, + args: [ + { type: "s", value: JSON.stringify(meta) } + ] + }; + + oscQueryBridge.sendPacket(writePacket(message)); + parameters.push(param.setWaitingForMidiMapping(false)); + } + }); + + dispatch(setInstanceParameters(parameters)); + + } catch (e) { + console.log(e); + } + }; + +export const updateInstanceParameterMeta = (index: number, name: ParameterRecord["name"], value: string): AppThunk => + (dispatch, getState) => { + try { + const state = getState(); + const param = getPatcherInstanceParametersByInstanceIndexAndName(state, index, name); + if (!param) return; + + dispatch(setInstanceParameter(param.setMeta(value))); + } catch (e) { + console.log(e); + } + }; + +export const updateInstanceMessageOutportMeta = (index: number, tag: MessagePortRecord["tag"], value: string): AppThunk => + (dispatch, getState) => { + try { + const state = getState(); + const instance = getPatcherInstanceByIndex(state, index); + if (!instance) return; + + + const port = getPatcherInstanceMessageInportsByInstanceIndexAndTag(state, instance.index, tag); + if (!port) return; + + dispatch(setInstanceMessageOutport(port.setMeta(value))); + } catch (e) { + console.log(e); + } + }; + +export const updateInstanceMessageInportMeta = (index: number, tag: MessagePortRecord["tag"], value: string): AppThunk => + (dispatch, getState) => { + try { + const state = getState(); + const instance = getPatcherInstanceByIndex(state, index); + if (!instance) return; + + const port = getPatcherInstanceMessageInportsByInstanceIndexAndTag(state, instance.index, tag); + if (!port) return; + + dispatch(setInstanceMessageInport(port.setMeta(value))); + } catch (e) { + console.log(e); + } + }; + +// Events from remote diff --git a/src/components/dataref/list.tsx b/src/components/dataref/list.tsx index 5d77e440..a2f9a439 100644 --- a/src/components/dataref/list.tsx +++ b/src/components/dataref/list.tsx @@ -1,6 +1,6 @@ import { FunctionComponent, memo } from "react"; import DataRefEntry from "./item"; -import { InstanceStateRecord } from "../../models/instance"; +import { PatcherInstanceRecord } from "../../models/instance"; import { DataRefRecord } from "../../models/dataref"; import { Seq } from "immutable"; import { Table } from "@mantine/core"; @@ -10,7 +10,7 @@ import { DataFileRecord } from "../../models/datafile"; export type DataRefListProps = { onClearDataRef: (dataref: DataRefRecord) => any; onSetDataRef: (dataref: DataRefRecord, file: DataFileRecord) => any; - datarefs: InstanceStateRecord["datarefs"]; + datarefs: PatcherInstanceRecord["datarefs"]; options: Seq.Indexed; // soundfile list } diff --git a/src/components/instance/datarefTab.tsx b/src/components/instance/datarefTab.tsx index cde8fe64..26b5fbe0 100644 --- a/src/components/instance/datarefTab.tsx +++ b/src/components/instance/datarefTab.tsx @@ -3,15 +3,15 @@ import { FunctionComponent, memo, useCallback } from "react"; import { useAppDispatch } from "../../hooks/useAppDispatch"; import DataRefList from "../dataref/list"; import classes from "./instance.module.css"; -import { InstanceStateRecord } from "../../models/instance"; -import { setInstanceDataRefValueOnRemote } from "../../actions/instances"; +import { PatcherInstanceRecord } from "../../models/instance"; +import { setInstanceDataRefValueOnRemote } from "../../actions/patchers"; import { DataRefRecord } from "../../models/dataref"; import { modals } from "@mantine/modals"; import { DataFileRecord } from "../../models/datafile"; import { Seq } from "immutable"; export type InstanceDataRefTabProps = { - instance: InstanceStateRecord; + instance: PatcherInstanceRecord; datafiles: Seq.Indexed; } diff --git a/src/components/instance/index.tsx b/src/components/instance/index.tsx index 9fc1a8c3..720a8913 100644 --- a/src/components/instance/index.tsx +++ b/src/components/instance/index.tsx @@ -5,12 +5,14 @@ import { InstanceTab } from "../../lib/constants"; import InstanceParameterTab from "./paramTab"; import InstanceMessagesTab from "./messageTab"; import InstanceDataRefsTab from "./datarefTab"; -import { InstanceStateRecord } from "../../models/instance"; +import { PatcherInstanceRecord } from "../../models/instance"; import { AppSettingRecord } from "../../models/settings"; import { DataFileRecord } from "../../models/datafile"; -import { Seq } from "immutable"; +import { Map as ImmuMap, Seq } from "immutable"; import { IconElement } from "../elements/icon"; import { mdiFileMusic, mdiSwapHorizontal, mdiTune } from "@mdi/js"; +import { ParameterRecord } from "../../models/parameter"; +import { MessagePortRecord } from "../../models/messageport"; const tabs = [ { icon: mdiTune, value: InstanceTab.Parameters, label: "Parameters" }, @@ -19,9 +21,12 @@ const tabs = [ ]; export type InstanceProps = { - instance: InstanceStateRecord; + instance: PatcherInstanceRecord; datafiles: Seq.Indexed enabledMessageOuput: AppSettingRecord; + messageInports: ImmuMap; + messageOutports: ImmuMap; + parameters: ImmuMap; paramSortOrder: AppSettingRecord; paramSortAttr: AppSettingRecord; } @@ -30,6 +35,9 @@ const Instance: FunctionComponent = memo(function WrappedInstance instance, datafiles, enabledMessageOuput, + messageInports, + messageOutports, + parameters, paramSortOrder, paramSortAttr }) { @@ -60,10 +68,10 @@ const Instance: FunctionComponent = memo(function WrappedInstance
- + - + diff --git a/src/components/instance/messageTab.tsx b/src/components/instance/messageTab.tsx index 4a4f8270..bb4a7a9d 100644 --- a/src/components/instance/messageTab.tsx +++ b/src/components/instance/messageTab.tsx @@ -1,22 +1,27 @@ import { Stack } from "@mantine/core"; +import { Map as ImmuMap } from "immutable"; import { FunctionComponent, memo, useCallback } from "react"; import { useAppDispatch } from "../../hooks/useAppDispatch"; import MessageInportList from "../messages/inportList"; import { SectionTitle } from "../page/sectionTitle"; import MessageOutportList from "../messages/outportList"; import classes from "./instance.module.css"; -import { InstanceStateRecord } from "../../models/instance"; -import { sendInstanceMessageToRemote } from "../../actions/instances"; +import { PatcherInstanceRecord } from "../../models/instance"; +import { sendInstanceMessageToRemote } from "../../actions/patchers"; import { MessagePortRecord } from "../../models/messageport"; -import { restoreDefaultMessagePortMetaOnRemote, setInstanceMessagePortMetaOnRemote } from "../../actions/instances"; +import { restoreDefaultMessagePortMetaOnRemote, setInstanceMessagePortMetaOnRemote } from "../../actions/patchers"; export type InstanceMessageTabProps = { - instance: InstanceStateRecord; + instance: PatcherInstanceRecord; + messageInports: ImmuMap; + messageOutports: ImmuMap; outputEnabled: boolean; } const InstanceMessagesTab: FunctionComponent = memo(function WrappedInstanceMessagesTab({ instance, + messageInports, + messageOutports, outputEnabled }) { @@ -37,13 +42,13 @@ const InstanceMessagesTab: FunctionComponent = memo(fun Input Ports { - !instance.messageInports.size ? ( + !messageInports.size ? (
This patcher instance has no message input ports.
) : = memo(fun } Output Ports { - !instance.messageOutports.size ? ( + !messageOutports.size ? (
This patcher instance has no output ports.
) : => { - return ImmuOrderedSet(params.valueSeq().sort(parameterComparators[attr][order]).map(p => p.id)); +const getSortedParameterIds = (params: ImmuMap, attr: ParameterSortAttr, order: SortOrder): ImmuOrderedSet => { + return ImmuOrderedSet(params.valueSeq().sort(parameterComparators[attr][order]).map(p => p.id)); }; export type InstanceParameterTabProps = { - instance: InstanceStateRecord; + instance: PatcherInstanceRecord; + parameters: ImmuMap; sortAttr: AppSettingRecord; sortOrder: AppSettingRecord; } const InstanceParameterTab: FunctionComponent = memo(function WrappedInstanceParameterTab({ instance, + parameters, sortAttr, sortOrder }) { const [searchValue, setSearchValue] = useState(""); - const [sortedParamIds, setSortedParamIds] = useState>(getSortedParameterIds(instance.parameters, sortAttr.value as ParameterSortAttr, sortOrder.value as SortOrder)); + const [sortedParameterIds, setSortedParameterIds] = useState>(ImmuOrderedSet()); const dispatch = useAppDispatch(); @@ -165,8 +167,8 @@ const InstanceParameterTab: FunctionComponent = memo( }, 150); useEffect(() => { - setSortedParamIds(getSortedParameterIds(instance.parameters, sortAttr.value as ParameterSortAttr, sortOrder.value as SortOrder)); - }, [instance, sortAttr, sortOrder]); + setSortedParameterIds(getSortedParameterIds(parameters, sortAttr.value as ParameterSortAttr, sortOrder.value as SortOrder)); + }, [instance, parameters.size, sortAttr, sortOrder]); useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { @@ -187,8 +189,14 @@ const InstanceParameterTab: FunctionComponent = memo( }; }, [instance.id, dispatch]); - let parameters = sortedParamIds.map(id => instance.parameters.get(id)).filter(p => !!p); - if (searchValue?.length) parameters = parameters.filter(p => p.matchesQuery(searchValue)); + const displayParameters = ImmuOrderedSet().withMutations(set => { + sortedParameterIds.forEach(id => { + const p = parameters.get(id); + if (p && (!searchValue?.length || p.matchesQuery(searchValue))) { + set.add(p); + } + }); + }); return ( @@ -236,14 +244,14 @@ const InstanceParameterTab: FunctionComponent = memo( { - !instance.parameters.size ? ( + !parameters.size ? (
This patcher instance has no parameters
) : (
any; diff --git a/src/components/patchers/index.tsx b/src/components/patchers/index.tsx index f35dafe9..766ce702 100644 --- a/src/components/patchers/index.tsx +++ b/src/components/patchers/index.tsx @@ -1,7 +1,7 @@ import { Alert, Drawer, Group, Stack, Text } from "@mantine/core"; import { FunctionComponent, memo, useCallback } from "react"; import { Seq } from "immutable"; -import { PatcherRecord } from "../../models/patcher"; +import { PatcherExportRecord } from "../../models/patcher"; import { PatcherItem } from "./item"; import { modals } from "@mantine/modals"; import { IconElement } from "../elements/icon"; @@ -10,10 +10,10 @@ import { mdiFileExport } from "@mdi/js"; export type PatcherDrawerProps = { open: boolean; onClose: () => any; - onLoadPatcher: (patcher: PatcherRecord) => any; - onDeletePatcher: (patcher: PatcherRecord) => any; - onRenamePatcher: (patcher: PatcherRecord, newName: string) => any; - patchers: Seq.Indexed; + onLoadPatcher: (patcher: PatcherExportRecord) => any; + onDeletePatcher: (patcher: PatcherExportRecord) => any; + onRenamePatcher: (patcher: PatcherExportRecord, newName: string) => any; + patchers: Seq.Indexed; }; const PatcherDrawer: FunctionComponent = memo(function WrappedPatcherDrawer({ @@ -25,7 +25,7 @@ const PatcherDrawer: FunctionComponent = memo(function Wrapp patchers }: PatcherDrawerProps) { - const onTriggerDeletePatcher = useCallback((p: PatcherRecord) => { + const onTriggerDeletePatcher = useCallback((p: PatcherExportRecord) => { modals.openConfirmModal({ title: "Delete Patcher", centered: true, diff --git a/src/components/patchers/item.tsx b/src/components/patchers/item.tsx index cae1dbe1..5d2c7535 100644 --- a/src/components/patchers/item.tsx +++ b/src/components/patchers/item.tsx @@ -1,16 +1,16 @@ import { ChangeEvent, FormEvent, FunctionComponent, KeyboardEvent, MouseEvent, memo, useCallback, useEffect, useRef, useState } from "react"; import { ActionIcon, Button, Group, Menu, TextInput, Tooltip } from "@mantine/core"; -import { PatcherRecord } from "../../models/patcher"; +import { PatcherExportRecord } from "../../models/patcher"; import { keyEventIsValidForName, replaceInvalidNameChars } from "../../lib/util"; import classes from "./patchers.module.css"; import { IconElement } from "../elements/icon"; import { mdiCheck, mdiClose, mdiDotsVertical, mdiPencil, mdiPlus, mdiTrashCan } from "@mdi/js"; export type PatcherItemProps = { - patcher: PatcherRecord; - onLoad: (p: PatcherRecord) => any; - onDelete: (p: PatcherRecord) => any; - onRename: (p: PatcherRecord, name: string) => any; + patcher: PatcherExportRecord; + onLoad: (p: PatcherExportRecord) => any; + onDelete: (p: PatcherExportRecord) => any; + onRename: (p: PatcherExportRecord, name: string) => any; }; export const PatcherItem: FunctionComponent = memo(function WrappedPatcherItem({ diff --git a/src/controller/oscqueryBridgeController.ts b/src/controller/oscqueryBridgeController.ts index 7c62fbbf..1a392d19 100644 --- a/src/controller/oscqueryBridgeController.ts +++ b/src/controller/oscqueryBridgeController.ts @@ -17,8 +17,11 @@ import { updateInstanceMessageOutportValue, updateInstanceMessages, updateInstanceMessageOutportMeta, updateInstanceMessageInportMeta, updateInstanceParameterValue, updateInstanceParameterValueNormalized, updateInstanceParameters, updateInstanceParameterMeta, updateInstancePresetEntries, updateInstancePresetLatest, updateInstancePresetInitial, - updateInstanceMIDILastValue, updateInstanceMIDIReport -} from "../actions/instances"; + updateInstanceMIDILastValue, updateInstanceMIDIReport, + removeInstanceParameterByPath, + removeInstanceMessageInportByPath, + removeInstanceMessageOutportByPath +} from "../actions/patchers"; import { ConnectionType, PortDirection } from "../models/graph"; import { showNotification } from "../actions/notifications"; import { NotificationLevel } from "../models/notification"; @@ -417,18 +420,27 @@ export class OSCQueryBridgeControllerPrivate { // Removed Parameter if ( instInfoMatch.groups.content === "params" && + !instInfoMatch.groups.rest.endsWith("/index") && + !instInfoMatch.groups.rest.endsWith("/meta") && !instInfoMatch.groups.rest.endsWith("/normalized") ) { - const paramInfo = await this._requestState< OSCQueryRNBOInstance["CONTENTS"]["params"]>(`/rnbo/inst/${index}/params`); - return void dispatch(updateInstanceParameters(index, paramInfo)); + return void dispatch(removeInstanceParameterByPath(path)); } // Removed Message Inport if ( - instInfoMatch.groups.content === "messages/in" || instInfoMatch.groups.content === "messages/out" + instInfoMatch.groups.content === "messages/in" && + !instInfoMatch.groups.rest.endsWith("meta") ) { - const messagesInfo = await this._requestState(`/rnbo/inst/${index}/messages`); - return void dispatch(updateInstanceMessages(index, messagesInfo)); + return void dispatch(removeInstanceMessageInportByPath(path)); + } + + // Removed Message Outport + if ( + instInfoMatch.groups.content === "messages/out" && + !instInfoMatch.groups.rest.endsWith("meta") + ) { + return void dispatch(removeInstanceMessageOutportByPath(path)); } } @@ -608,7 +620,7 @@ export class OSCQueryBridgeControllerPrivate { return void dispatch(updateInstancePresetEntries(index, presetInfo.CONTENTS.entries)); } - // port meta + // Port meta if (packetMatch.groups.rest.endsWith("/meta")) { if (packetMatch.groups.content === "messages/out") { return void dispatch(updateInstanceMessageOutportMeta(index, packetMatch.groups.rest.replace(/\/meta$/, ""), packet.args[0] as unknown as string)); diff --git a/src/lib/types.ts b/src/lib/types.ts index c8510450..c35cda64 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -280,6 +280,7 @@ export type OSCQueryRNBOInstance = OSCQueryBaseNode & { name: OSCQueryStringValue & { VALUE: string; }; params: OSCQueryBaseNode & { CONTENTS: Record; + VALUE: undefined } data_refs: OSCQueryBaseNode & { CONTENTS: Record; diff --git a/src/models/instance.ts b/src/models/instance.ts index 84653d31..af5fe77f 100644 --- a/src/models/instance.ts +++ b/src/models/instance.ts @@ -1,11 +1,9 @@ import { Map as ImmuMap, Record as ImmuRecord, OrderedMap as ImmuOrderedMap } from "immutable"; -import { ParameterRecord } from "./parameter"; import { PresetRecord } from "./preset"; import { DataRefRecord } from "./dataref"; -import { MessagePortRecord } from "./messageport"; -import { OSCQueryRNBOInstance, OSCQueryRNBOInstanceMessages, OSCQueryRNBOInstanceMessageInfo, OSCQueryRNBOInstanceMessageValue, OSCQueryRNBOInstancePresetEntries } from "../lib/types"; +import { OSCQueryRNBOInstance, OSCQueryRNBOInstancePresetEntries } from "../lib/types"; -export type InstanceStateProps = { +export type PatcherInstanceProps = { index: number; patcher: string; path: string; @@ -14,9 +12,6 @@ export type InstanceStateProps = { presetInitial: string; presetLatest: string; - messageInports: ImmuMap; - messageOutports: ImmuMap; - parameters: ImmuMap; presets: ImmuOrderedMap; datarefs: ImmuOrderedMap; @@ -35,7 +30,7 @@ function sortPresets(left: PresetRecord, right: PresetRecord) : number { return collator.compare(left.name, right.name); } -export class InstanceStateRecord extends ImmuRecord({ +export class PatcherInstanceRecord extends ImmuRecord({ index: 0, name: "", @@ -43,10 +38,6 @@ export class InstanceStateRecord extends ImmuRecord({ path: "", presetInitial: "", presetLatest: "", - - messageInports: ImmuMap(), - messageOutports: ImmuMap(), - parameters: ImmuMap(), presets: ImmuMap(), datarefs: ImmuMap(), @@ -58,64 +49,17 @@ export class InstanceStateRecord extends ImmuRecord({ return this.name; } - public setMessageOutportValue(id: string, value: string): InstanceStateRecord { - const p = this.messageOutports.get(id); - if (!p) return this; - - return this.set("messageOutports", this.messageOutports.set(p.id, p.setValue(value))); - } - - public setMessageOutportMeta(id: MessagePortRecord["id"], value: string): InstanceStateRecord { - const p = this.messageOutports.get(id); - if (!p) return this; - return this.set("messageOutports", this.messageOutports.set(p.id, p.setMeta(value))); + public setWaitingForMapping(value: boolean): PatcherInstanceRecord { + return this.set("waitingForMidiMapping", value); } - public setMessageInportMeta(id: MessagePortRecord["id"], value: string): InstanceStateRecord { - const p = this.messageInports.get(id); - if (!p) return this; - return this.set("messageInports", this.messageInports.set(p.id, p.setMeta(value))); - } - - public setDataRefValue(id: string, fileId: string): InstanceStateRecord { + public setDataRefValue(id: string, fileId: string): PatcherInstanceRecord { const dataref = this.datarefs.get(id); if (!dataref) return this; return this.set("datarefs", this.datarefs.set(dataref.id, dataref.setFileId(fileId))); } - public setParameterValue(id: ParameterRecord["id"], value: number): InstanceStateRecord { - const param = this.parameters.get(id); - if (!param) return this; - - return this.set("parameters", this.parameters.set(param.id, param.setValue(value))); - } - - public setWaitingForMapping(value: boolean): InstanceStateRecord { - return this.set("waitingForMidiMapping", value).clearParametersWaitingForMidiMapping(); - } - - public clearParametersWaitingForMidiMapping(): InstanceStateRecord { - return this.set("parameters", this.parameters.map(p => p.setWaitingForMidiMapping(false))); - } - - public setParameterWaitingForMidiMapping(id: ParameterRecord["id"]): InstanceStateRecord { - return this.set("parameters", this.parameters.map(p => p.setWaitingForMidiMapping(p.id === id))); - } - - public setParameterNormalizedValue(id: ParameterRecord["id"], value: number): InstanceStateRecord { - const param = this.parameters.get(id); - if (!param) return this; - - return this.set("parameters", this.parameters.set(param.id, param.setNormalizedValue(value))); - } - - public setParameterMeta(id: ParameterRecord["id"], value: string): InstanceStateRecord { - const param = this.parameters.get(id); - if (!param) return this; - return this.set("parameters", this.parameters.set(param.id, param.setMeta(value))); - } - public static presetsFromDescription(entries: OSCQueryRNBOInstancePresetEntries, latest: string, initial: string): ImmuMap { return ImmuOrderedMap().withMutations((map) => { for (const name of entries.VALUE) { @@ -125,47 +69,16 @@ export class InstanceStateRecord extends ImmuRecord({ }).sort(sortPresets); } - public setPresetLatest(latest: string): InstanceStateRecord { + public setPresetLatest(latest: string): PatcherInstanceRecord { return this.set("presets", this.presets.map(preset => preset.setLatest(preset.name === latest))).set("presetLatest", latest); } - public setPresetInitial(initial: string): InstanceStateRecord { + public setPresetInitial(initial: string): PatcherInstanceRecord { return this.set("presetInitial", initial).set("presets", this.presets.map(preset => preset.setInitial(preset.name === initial)).sort(sortPresets)); } - public updatePresets(entries: OSCQueryRNBOInstancePresetEntries): InstanceStateRecord { - return this.set("presets", InstanceStateRecord.presetsFromDescription(entries, this.presetLatest, this.presetInitial)); - } - - private static messagesArrayFromDescription(desc: OSCQueryRNBOInstanceMessageInfo, name: string): MessagePortRecord[] { - if (typeof desc.VALUE !== "undefined") { - return [MessagePortRecord.fromDescription(name, desc as OSCQueryRNBOInstanceMessageValue)]; - } - - const result: MessagePortRecord[] = []; - for (const [subKey, subDesc] of Object.entries(desc.CONTENTS)) { - const subPrefix = name ? `${name}/${subKey}` : subKey; - result.push(...this.messagesArrayFromDescription(subDesc, subPrefix)); - } - return result; - } - - public static messagesFromDescription(messagesDesc?: OSCQueryRNBOInstanceMessages): ImmuMap { - return ImmuMap().withMutations((map) => { - for (const [name, desc] of Object.entries(messagesDesc?.CONTENTS || {})) { - const ports = this.messagesArrayFromDescription(desc, name); - ports.forEach(n => map.set(n.id, n)); - } - }); - } - - public static parametersFromDescription(paramsDesc: OSCQueryRNBOInstance["CONTENTS"]["params"]): ImmuMap { - return ImmuMap().withMutations((map) => { - for (const [name, desc] of Object.entries(paramsDesc.CONTENTS || {})) { - const params = ParameterRecord.arrayFromDescription(desc, name); - params.forEach(pr => map.set(pr.id, pr)); - } - }); + public updatePresets(entries: OSCQueryRNBOInstancePresetEntries): PatcherInstanceRecord { + return this.set("presets", PatcherInstanceRecord.presetsFromDescription(entries, this.presetLatest, this.presetInitial)); } public static datarefsFromDescription(datarefsDesc: OSCQueryRNBOInstance["CONTENTS"]["data_refs"]): ImmuMap { @@ -181,20 +94,16 @@ export class InstanceStateRecord extends ImmuRecord({ return desc.CONTENTS.name.VALUE as string; } - public static fromDescription(desc: OSCQueryRNBOInstance): InstanceStateRecord { + public static fromDescription(desc: OSCQueryRNBOInstance): PatcherInstanceRecord { const initialPreset: string = desc.CONTENTS.presets.CONTENTS?.initial?.VALUE || ""; const latestPreset: string = desc.CONTENTS.presets.CONTENTS?.loaded?.VALUE || ""; - - return new InstanceStateRecord({ + return new PatcherInstanceRecord({ index: parseInt(desc.FULL_PATH.split("/").pop(), 10), name: this.getJackName(desc.CONTENTS.jack), patcher: desc.CONTENTS.name.VALUE, path: desc.FULL_PATH, - messageInports: this.messagesFromDescription(desc.CONTENTS.messages?.CONTENTS?.in), - messageOutports: this.messagesFromDescription(desc.CONTENTS.messages?.CONTENTS?.out), - parameters: this.parametersFromDescription(desc.CONTENTS.params), presets: this.presetsFromDescription(desc.CONTENTS.presets.CONTENTS.entries, latestPreset, initialPreset), datarefs: this.datarefsFromDescription(desc.CONTENTS.data_refs) }); diff --git a/src/models/messageport.ts b/src/models/messageport.ts index 68a03862..62f0fa0c 100644 --- a/src/models/messageport.ts +++ b/src/models/messageport.ts @@ -1,7 +1,9 @@ import { Record as ImmuRecord } from "immutable"; -import { OSCQueryRNBOInstanceMessageValue } from "../lib/types"; +import { OSCQueryRNBOInstanceMessageInfo, OSCQueryRNBOInstanceMessages, OSCQueryRNBOInstanceMessageValue } from "../lib/types"; +import { PatcherInstanceRecord } from "./instance"; export type MessagePortRecordProps = { + instanceIndex: number; tag: string; meta: string; value: string; @@ -10,14 +12,43 @@ export type MessagePortRecordProps = { export class MessagePortRecord extends ImmuRecord({ + instanceIndex: 0, tag: "", meta: "", value: "", path: "" }) { + private static messagesArrayFromDescription(instanceIndex: PatcherInstanceRecord["index"], desc: OSCQueryRNBOInstanceMessageInfo, name: string): MessagePortRecord[] { + if (typeof desc.VALUE !== "undefined") { + return [ + new MessagePortRecord({ + instanceIndex, + tag: name, + path: (desc as OSCQueryRNBOInstanceMessageValue).FULL_PATH, + meta: (desc as OSCQueryRNBOInstanceMessageValue).CONTENTS?.meta?.VALUE || "" + }) + ]; + } + + const result: MessagePortRecord[] = []; + for (const [subKey, subDesc] of Object.entries(desc.CONTENTS)) { + const subPrefix = name ? `${name}/${subKey}` : subKey; + result.push(...this.messagesArrayFromDescription(instanceIndex, subDesc, subPrefix)); + } + return result; + } + + public static fromDescription(instanceIndex: PatcherInstanceRecord["index"], messagesDesc?: OSCQueryRNBOInstanceMessages): MessagePortRecord[] { + const ports: MessagePortRecord[] = []; + for (const [name, desc] of Object.entries(messagesDesc?.CONTENTS || {})) { + ports.push(...this.messagesArrayFromDescription(instanceIndex, desc, name)); + } + return ports; + } + public get id(): string { - return this.tag; + return this.path; } public get name(): string { @@ -32,12 +63,4 @@ export class MessagePortRecord extends ImmuRecord({ return this.set("value", value); } - public static fromDescription(tag: string, desc: OSCQueryRNBOInstanceMessageValue): MessagePortRecord { - return new MessagePortRecord({ - tag, - path: desc.FULL_PATH, - meta: desc.CONTENTS?.meta?.VALUE || "" - }); - } - } diff --git a/src/models/parameter.ts b/src/models/parameter.ts index a408e727..13be8795 100644 --- a/src/models/parameter.ts +++ b/src/models/parameter.ts @@ -1,10 +1,12 @@ import { Record as ImmuRecord } from "immutable"; -import { AnyJson, JsonMap, OSCQueryRNBOInstanceParameterInfo, OSCQueryRNBOInstanceParameterValue } from "../lib/types"; +import { AnyJson, JsonMap, OSCQueryRNBOInstance, OSCQueryRNBOInstanceParameterInfo, OSCQueryRNBOInstanceParameterValue } from "../lib/types"; import { parseParamMetaJSONString } from "../lib/util"; export type ParameterRecordProps = { + enumVals: Array; index: number; + instanceIndex: number; min: number; max: number; meta: string; @@ -20,6 +22,7 @@ export class ParameterRecord extends ImmuRecord({ enumVals: [], index: 0, + instanceIndex: 0, min: 0, max: 1, meta: "", @@ -32,7 +35,11 @@ export class ParameterRecord extends ImmuRecord({ isMidiMapped: false }) { - public static arrayFromDescription(desc: OSCQueryRNBOInstanceParameterInfo, name?: string): ParameterRecord[] { + private static arrayFromDescription( + instanceIndex: number, + desc: OSCQueryRNBOInstanceParameterInfo, + name?: string + ): ParameterRecord[] { const result: ParameterRecord[] = []; if (typeof desc.VALUE !== "undefined") { const paramInfo = desc as OSCQueryRNBOInstanceParameterValue; @@ -41,6 +48,7 @@ export class ParameterRecord extends ImmuRecord({ result.push((new ParameterRecord({ enumVals: paramInfo.RANGE?.[0]?.VALS || [], index: paramInfo.CONTENTS?.index?.VALUE || 0, + instanceIndex, min: paramInfo.RANGE?.[0]?.MIN, max: paramInfo.RANGE?.[0]?.MAX, name, @@ -53,14 +61,22 @@ export class ParameterRecord extends ImmuRecord({ // Polyphonic params for (const [subParamName, subDesc] of Object.entries(desc.CONTENTS) as Array<[string, OSCQueryRNBOInstanceParameterInfo]>) { const subPrefix = name ? `${name}/${subParamName}` : subParamName; - result.push(...this.arrayFromDescription(subDesc, subPrefix)); + result.push(...this.arrayFromDescription(instanceIndex, subDesc, subPrefix)); } } return result; } + public static fromDescription(instanceIndex: number, paramsDesc: OSCQueryRNBOInstance["CONTENTS"]["params"]): ParameterRecord[] { + const params: ParameterRecord[] = []; + for (const [name, desc] of Object.entries(paramsDesc.CONTENTS || {})) { + params.push(...ParameterRecord.arrayFromDescription(instanceIndex, desc, name)); + } + return params; + } + public get id(): string { - return this.name; + return this.path; } public get isEnum(): boolean { diff --git a/src/models/patcher.ts b/src/models/patcher.ts index 401487ac..85a954e9 100644 --- a/src/models/patcher.ts +++ b/src/models/patcher.ts @@ -3,20 +3,20 @@ import { OSCQueryRNBOPatcher } from "../lib/types"; export const UNLOAD_PATCHER_NAME = ""; -export type PatcherRecordProps = { +export type PatcherExportRecordProps = { name: string; io: [number, number, number, number]; } -export class PatcherRecord extends ImmuRecord({ +export class PatcherExportRecord extends ImmuRecord({ name: "", io: [0, 0, 0, 0] }) { - static fromDescription(name: string, desc: OSCQueryRNBOPatcher): PatcherRecord { - return new PatcherRecord({ + static fromDescription(name: string, desc: OSCQueryRNBOPatcher): PatcherExportRecord { + return new PatcherExportRecord({ name, io: desc.CONTENTS.io.VALUE }); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 81b5e1a2..5f3664e0 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -24,7 +24,7 @@ import { PresetRecord } from "../models/preset"; import { getGraphSetPresetsSortedByName, getGraphSetsSortedByName } from "../selectors/sets"; import { useDisclosure } from "@mantine/hooks"; import PatcherDrawer from "../components/patchers"; -import { PatcherRecord } from "../models/patcher"; +import { PatcherExportRecord } from "../models/patcher"; import { SortOrder } from "../lib/constants"; import { GraphSetRecord } from "../models/set"; import { modals } from "@mantine/modals"; @@ -58,7 +58,7 @@ const Index: FunctionComponent> = () => { const [presetDrawerIsOpen, { close: closePresetDrawer, toggle: togglePresetDrawer }] = useDisclosure(); // Instances - const onAddInstance = useCallback((patcher: PatcherRecord) => { + const onAddInstance = useCallback((patcher: PatcherExportRecord) => { dispatch(loadPatcherNodeOnRemote(patcher)); closePatcherDrawer(); }, [dispatch, closePatcherDrawer]); @@ -181,11 +181,11 @@ const Index: FunctionComponent> = () => { dispatch(renameSetPresetOnRemote(preset, name)); }, [dispatch]); - const onDeletePatcher = useCallback((p: PatcherRecord) => { + const onDeletePatcher = useCallback((p: PatcherExportRecord) => { dispatch(destroyPatcherOnRemote(p)); }, [dispatch]); - const onRenamePatcher = useCallback((p: PatcherRecord, name: string) => { + const onRenamePatcher = useCallback((p: PatcherExportRecord, name: string) => { dispatch(renamePatcherOnRemote(p, name)); }, [dispatch]); diff --git a/src/pages/instances/[index].tsx b/src/pages/instances/[index].tsx index 6e28e45b..c4096055 100644 --- a/src/pages/instances/[index].tsx +++ b/src/pages/instances/[index].tsx @@ -8,13 +8,13 @@ import classes from "../../components/instance/instance.module.css"; import { getAppStatus } from "../../selectors/appStatus"; import { AppStatus, SortOrder } from "../../lib/constants"; import Link from "next/link"; -import { getInstanceByIndex, getInstances } from "../../selectors/instances"; +import { getPatcherInstanceByIndex, getPatcherInstanceParametersByInstanceIndex, getPatcherInstances, getPatcherInstanceMessageInportsByInstanceIndex, getPatcherInstanceMesssageOutportsByInstanceIndex } from "../../selectors/patchers"; import { unloadPatcherNodeByIndexOnRemote } from "../../actions/graph"; import { getAppSetting } from "../../selectors/settings"; import { AppSetting } from "../../models/settings"; import PresetDrawer from "../../components/presets"; import { PresetRecord } from "../../models/preset"; -import { destroyPresetOnRemoteInstance, renamePresetOnRemoteInstance, setInitialPresetOnRemoteInstance, loadPresetOnRemoteInstance, savePresetToRemoteInstance } from "../../actions/instances"; +import { destroyPresetOnRemoteInstance, renamePresetOnRemoteInstance, setInitialPresetOnRemoteInstance, loadPresetOnRemoteInstance, savePresetToRemoteInstance } from "../../actions/patchers"; import { useDisclosure } from "@mantine/hooks"; import { getDataFilesSortedByName } from "../../selectors/datafiles"; import InstanceKeyboardModal from "../../components/keyroll/modal"; @@ -36,6 +36,9 @@ export default function Instance() { const [ currentInstance, + parameters, + messageInports, + messageOutports, appStatus, instances, datafiles, @@ -44,12 +47,15 @@ export default function Instance() { sortAttr, sortOrder ] = useAppSelector((state: RootStateType) => { - const currentInstance = getInstanceByIndex(state, instanceIndex); + const currentInstance = getPatcherInstanceByIndex(state, instanceIndex); return [ currentInstance, + currentInstance ? getPatcherInstanceParametersByInstanceIndex(state, currentInstance.index) : undefined, + currentInstance ? getPatcherInstanceMessageInportsByInstanceIndex(state, currentInstance.index) : undefined, + currentInstance ? getPatcherInstanceMesssageOutportsByInstanceIndex(state, currentInstance.index) : undefined, getAppStatus(state), - getInstances(state), + getPatcherInstances(state), getDataFilesSortedByName(state, SortOrder.Asc), getAppSetting(state, AppSetting.debugMessageOutput), getAppSetting(state, AppSetting.keyboardMIDIInput), @@ -102,7 +108,7 @@ export default function Instance() { if (!isReady || appStatus !== AppStatus.Ready) return null; - if (!currentInstance) { + if (!currentInstance || !parameters || !messageInports || !messageOutports) { // Instance not found / doesn't exist return (
@@ -157,6 +163,9 @@ export default function Instance() { ; -} - -export const instances = (state: InstanceInstancesState = { - - instances: ImmuMap() - -}, action: InstanceAction): InstanceInstancesState => { - - switch(action.type) { - - case InstanceActionType.SET_INSTANCE: { - const { instance } = action.payload; - - return { - ...state, - instances: state.instances.set(instance.id, instance) - }; - } - - case InstanceActionType.SET_INSTANCES: { - const { instances } = action.payload; - - return { - ...state, - instances: state.instances.withMutations(map => { - for (const instance of instances) { - map.set(instance.id, instance); - } - }) - }; - } - - case InstanceActionType.DELETE_INSTANCE: { - const { instance } = action.payload; - - return { - ...state, - instances: state.instances.delete(instance.id) - }; - } - - case InstanceActionType.DELETE_INSTANCES: { - const { instances } = action.payload; - - return { - ...state, - instances: state.instances.deleteAll(instances.map(d => d.id)) - }; - } - - default: - return state; - } -}; diff --git a/src/reducers/patchers.ts b/src/reducers/patchers.ts index d8bee179..d4843699 100644 --- a/src/reducers/patchers.ts +++ b/src/reducers/patchers.ts @@ -1,25 +1,203 @@ import { Map as ImmuMap } from "immutable"; -import { PatcherRecord } from "../models/patcher"; -import { PatcherAction, PatcherActionType } from "../actions/patchers"; +import { PatcherInstanceRecord } from "../models/instance"; +import { InstanceAction, PatcherActionType } from "../actions/patchers"; +import { ParameterRecord } from "../models/parameter"; +import { MessagePortRecord } from "../models/messageport"; +import { PatcherExportRecord } from "../models/patcher"; export interface PatcherState { - patchers: ImmuMap; + exports: ImmuMap; + instances: ImmuMap; + instanceMessageInports: ImmuMap; + instanceMessageOutports: ImmuMap; + instanceParameters: ImmuMap; } export const patchers = (state: PatcherState = { - patchers: ImmuMap() + exports: ImmuMap(), + instances: ImmuMap(), + instanceMessageInports: ImmuMap(), + instanceMessageOutports: ImmuMap(), + instanceParameters: ImmuMap() -}, action: PatcherAction): PatcherState => { +}, action: InstanceAction): PatcherState => { - switch (action.type) { + switch(action.type) { - case PatcherActionType.INIT: { + case PatcherActionType.INIT_PATCHERS: { const { patchers } = action.payload; return { ...state, - patchers: ImmuMap(patchers.map(p => [p.id, p])) + exports: ImmuMap(patchers.map(p => [p.id, p])) + }; + } + + case PatcherActionType.SET_INSTANCE: { + const { instance } = action.payload; + + return { + ...state, + instances: state.instances.set(instance.id, instance) + }; + } + + case PatcherActionType.SET_INSTANCES: { + const { instances } = action.payload; + + return { + ...state, + instances: state.instances.withMutations(map => { + for (const instance of instances) { + map.set(instance.id, instance); + } + }) + }; + } + + case PatcherActionType.DELETE_INSTANCE: { + const { instance } = action.payload; + + return { + ...state, + instances: state.instances.delete(instance.id), + instanceParameters: state.instanceParameters.filter(param => param.instanceIndex !== instance.index), + instanceMessageInports: state.instanceMessageInports.filter(port => port.instanceIndex !== instance.index), + instanceMessageOutports: state.instanceMessageOutports.filter(port => port.instanceIndex !== instance.index) + }; + } + + case PatcherActionType.DELETE_INSTANCES: { + const { instances } = action.payload; + const indexSet = new Set(instances.map(i => i.index)); + + return { + ...state, + instances: state.instances.deleteAll(instances.map(d => d.id)), + instanceParameters: state.instanceParameters.filter(param => !indexSet.has(param.instanceIndex)), + instanceMessageInports: state.instanceMessageInports.filter(port => !indexSet.has(port.instanceIndex)), + instanceMessageOutports: state.instanceMessageOutports.filter(port => !indexSet.has(port.instanceIndex)) + }; + } + + case PatcherActionType.SET_PARAMETER: { + const { parameter } = action.payload; + + return { + ...state, + instanceParameters: state.instanceParameters.set(parameter.id, parameter) + }; + } + + case PatcherActionType.SET_PARAMETERS: { + const { parameters } = action.payload; + + return { + ...state, + instanceParameters: state.instanceParameters.withMutations(map => { + for (const param of parameters) { + map.set(param.id, param); + } + }) + }; + } + + case PatcherActionType.DELETE_PARAMETER: { + const { parameter } = action.payload; + + return { + ...state, + instanceParameters: state.instanceParameters.delete(parameter.id) + }; + } + + case PatcherActionType.DELETE_PARAMETERS: { + const { parameters } = action.payload; + + return { + ...state, + instanceParameters: state.instanceParameters.deleteAll(parameters.map(d => d.id)) + }; + } + + case PatcherActionType.SET_MESSAGE_INPORT: { + const { port } = action.payload; + + return { + ...state, + instanceMessageInports: state.instanceMessageInports.set(port.id, port) + }; + } + + case PatcherActionType.SET_MESSAGE_INPORTS: { + const { ports } = action.payload; + + return { + ...state, + instanceMessageInports: state.instanceMessageInports.withMutations(map => { + for (const port of ports) { + map.set(port.id, port); + } + }) + }; + } + + case PatcherActionType.DELETE_MESSAGE_INPORT: { + const { port } = action.payload; + + return { + ...state, + instanceMessageInports: state.instanceMessageInports.delete(port.id) + }; + } + + case PatcherActionType.DELETE_MESSAGE_INPORTS: { + const { ports } = action.payload; + + return { + ...state, + instanceMessageInports: state.instanceMessageInports.deleteAll(ports.map(d => d.id)) + }; + } + + case PatcherActionType.SET_MESSAGE_OUTPORT: { + const { port } = action.payload; + + return { + ...state, + instanceMessageOutports: state.instanceMessageOutports.set(port.id, port) + }; + } + + case PatcherActionType.SET_MESSAGE_OUTPORTS: { + const { ports } = action.payload; + + return { + ...state, + instanceMessageOutports: state.instanceMessageOutports.withMutations(map => { + for (const port of ports) { + map.set(port.id, port); + } + }) + }; + } + + case PatcherActionType.DELETE_MESSAGE_OUTPORT: { + const { port } = action.payload; + + return { + ...state, + instanceMessageOutports: state.instanceMessageOutports.delete(port.id) + }; + } + + case PatcherActionType.DELETE_MESSAGE_OUTPORTS: { + const { ports } = action.payload; + + return { + ...state, + instanceMessageOutports: state.instanceMessageOutports.deleteAll(ports.map(d => d.id)) }; } diff --git a/src/selectors/instances.ts b/src/selectors/instances.ts deleted file mode 100644 index 64ec0bcb..00000000 --- a/src/selectors/instances.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Map as ImmuMap } from "immutable"; -import { RootStateType } from "../lib/store"; -import { InstanceStateRecord } from "../models/instance"; -import { createSelector } from "reselect"; -import { getPatcherIdsByIndex } from "./graph"; - -export const getInstances = (state: RootStateType): ImmuMap => state.instances.instances; - -export const getInstance = createSelector( - [ - getInstances, - (state: RootStateType, id: InstanceStateRecord["id"]): InstanceStateRecord["id"] => id - ], - (instances, id): InstanceStateRecord | undefined => instances.get(id) -); - -export const getInstanceByIndex = createSelector( - [ - getInstances, - (state: RootStateType, index: InstanceStateRecord["index"]): InstanceStateRecord["id"] | undefined => state.graph.patcherNodeIdByIndex.get(index) - ], - (instances, id): InstanceStateRecord | undefined => id ? instances.get(id) : undefined -); - -export const getInstancesByIndex = createSelector( - [ - getInstances, - getPatcherIdsByIndex - ], - (instances, idsByIndex): ImmuMap => { - return ImmuMap().withMutations(map => { - idsByIndex.forEach((id, index) => { - const node = instances.get(id); - if (node) map.set(index, node); - }); - }); - } -); diff --git a/src/selectors/patchers.ts b/src/selectors/patchers.ts index 9bd3d73f..a0b45778 100644 --- a/src/selectors/patchers.ts +++ b/src/selectors/patchers.ts @@ -1,19 +1,23 @@ import { Map as ImmuMap, Seq } from "immutable"; import { RootStateType } from "../lib/store"; -import { PatcherRecord } from "../models/patcher"; +import { PatcherInstanceRecord } from "../models/instance"; import { createSelector } from "reselect"; +import { getPatcherIdsByIndex } from "./graph"; +import { ParameterRecord } from "../models/parameter"; +import { MessagePortRecord } from "../models/messageport"; +import { PatcherExportRecord } from "../models/patcher"; import { SortOrder } from "../lib/constants"; -export const getPatchers = (state: RootStateType): ImmuMap => { - return state.patchers.patchers; +export const getPatcherExports = (state: RootStateType): ImmuMap => { + return state.patchers.exports; }; -export const getPatcher = createSelector( +export const getPatcherExport = createSelector( [ - getPatchers, + getPatcherExports, (state: RootStateType, name: string): string => name ], - (patchers, name): PatcherRecord | undefined => { + (patchers, name): PatcherExportRecord | undefined => { return patchers.get(name); } ); @@ -21,12 +25,183 @@ export const getPatcher = createSelector( const collator = new Intl.Collator("en-US"); export const getPatchersSortedByName = createSelector( [ - getPatchers, + getPatcherExports, (state: RootStateType, order: SortOrder): SortOrder => order ], - (patchers, order): Seq.Indexed => { + (patchers, order): Seq.Indexed => { return patchers.valueSeq().sort((pA, pB) => { return collator.compare(pA.name.toLowerCase(), pB.name.toLowerCase()) * (order === SortOrder.Asc ? 1 : -1); }); } ); + + +export const getPatcherInstances = (state: RootStateType): ImmuMap => state.patchers.instances; + +export const getPatcherInstance = createSelector( + [ + getPatcherInstances, + (state: RootStateType, id: PatcherInstanceRecord["id"]): PatcherInstanceRecord["id"] => id + ], + (instances, id): PatcherInstanceRecord | undefined => instances.get(id) +); + +export const getPatcherInstanceByIndex = createSelector( + [ + getPatcherInstances, + (state: RootStateType, index: PatcherInstanceRecord["index"]): PatcherInstanceRecord["id"] | undefined => state.graph.patcherNodeIdByIndex.get(index) + ], + (instances, id): PatcherInstanceRecord | undefined => id ? instances.get(id) : undefined +); + +export const getPatcherInstancesByIndex = createSelector( + [ + getPatcherInstances, + getPatcherIdsByIndex + ], + (instances, idsByIndex): ImmuMap => { + return ImmuMap().withMutations(map => { + idsByIndex.forEach((id, index) => { + const node = instances.get(id); + if (node) map.set(index, node); + }); + }); + } +); + +export const getPatcherInstanceParameters = (state: RootStateType): ImmuMap => state.patchers.instanceParameters; + +export const getPatcherInstanceParameter = createSelector( + [ + getPatcherInstanceParameters, + (state: RootStateType, id: ParameterRecord["id"]): ParameterRecord["id"] => id + ], + (parameters, id): ParameterRecord | undefined => { + return parameters.get(id); + } +); + +export const getPatcherInstanceParameterByPath = createSelector( + [ + getPatcherInstanceParameters, + (state: RootStateType, path: ParameterRecord["path"]): ParameterRecord["path"] => path + ], + (parameters, path): ParameterRecord | undefined => { + return parameters.find(p => p.path === path); + } +); + +export const getPatcherInstanceParametersByInstanceIndex = createSelector( + [ + getPatcherInstanceParameters, + (state: RootStateType, instanceIndex: PatcherInstanceRecord["index"]): PatcherInstanceRecord["index"] => instanceIndex + ], + (parameters, instanceIndex): ImmuMap => { + return parameters.filter(p => { + return p.instanceIndex === instanceIndex; + }); + } +); + + +export const getPatcherInstanceParametersByInstanceIndexAndName = createSelector( + [ + getPatcherInstanceParameters, + (state: RootStateType, instanceIndex: PatcherInstanceRecord["index"]): PatcherInstanceRecord["index"] => instanceIndex, + (state: RootStateType, instanceIndex: PatcherInstanceRecord["index"], name: ParameterRecord["name"]): ParameterRecord["name"] => name + ], + (parameters, instanceIndex, name): ParameterRecord | undefined => { + return parameters.find(p => p.instanceIndex === instanceIndex && p.name === name); + } +); + +export const getPatcherInstanceMessageInports = (state: RootStateType): ImmuMap => state.patchers.instanceMessageInports; + +export const getPatcherInstanceMessageInport = createSelector( + [ + getPatcherInstanceMessageInports, + (state: RootStateType, id: MessagePortRecord["id"]): MessagePortRecord["id"] => id + ], + (ports, id): MessagePortRecord | undefined => { + return ports.get(id); + } +); + +export const getPatcherInstanceMessageInportByPath = createSelector( + [ + getPatcherInstanceMessageInports, + (state: RootStateType, path: MessagePortRecord["path"]): MessagePortRecord["path"] => path + ], + (ports, path): MessagePortRecord | undefined => { + return ports.find(p => p.path === path); + } +); + +export const getPatcherInstanceMessageInportsByInstanceIndex = createSelector( + [ + getPatcherInstanceMessageInports, + (state: RootStateType, instanceIndex: PatcherInstanceRecord["index"]): PatcherInstanceRecord["index"] => instanceIndex + ], + (ports, instanceIndex): ImmuMap => { + console.log(ports.valueSeq().toArray().map(p => p.toJSON())); + return ports.filter(p => { + return p.instanceIndex === instanceIndex; + }); + } +); + +export const getPatcherInstanceMessageInportsByInstanceIndexAndTag = createSelector( + [ + getPatcherInstanceMessageInports, + (state: RootStateType, instanceIndex: PatcherInstanceRecord["index"]): PatcherInstanceRecord["index"] => instanceIndex, + (state: RootStateType, instanceIndex: PatcherInstanceRecord["index"], tag: MessagePortRecord["tag"]): MessagePortRecord["tag"] => tag + ], + (ports, instanceIndex, tag): MessagePortRecord | undefined => { + return ports.find(p => p.instanceIndex === instanceIndex && p.tag === tag); + } +); + +export const getPatcherInstanceMessageOutports = (state: RootStateType): ImmuMap => state.patchers.instanceMessageOutports; + +export const getPatcherInstanceMessageOutport = createSelector( + [ + getPatcherInstanceMessageOutports, + (state: RootStateType, id: MessagePortRecord["id"]): MessagePortRecord["id"] => id + ], + (ports, id): MessagePortRecord | undefined => { + return ports.get(id); + } +); + +export const getPatcherInstanceMessageOutportByPath = createSelector( + [ + getPatcherInstanceMessageOutports, + (state: RootStateType, path: MessagePortRecord["path"]): MessagePortRecord["path"] => path + ], + (ports, path): MessagePortRecord | undefined => { + return ports.find(p => p.path === path); + } +); + +export const getPatcherInstanceMesssageOutportsByInstanceIndex = createSelector( + [ + getPatcherInstanceMessageOutports, + (state: RootStateType, instanceIndex: PatcherInstanceRecord["index"]): PatcherInstanceRecord["index"] => instanceIndex + ], + (ports, instanceIndex): ImmuMap => { + return ports.filter(p => { + return p.instanceIndex === instanceIndex; + }); + } +); + +export const getPatcherInstanceMesssageOutportsByInstanceIndexAndTag = createSelector( + [ + getPatcherInstanceMessageOutports, + (state: RootStateType, instanceIndex: PatcherInstanceRecord["index"]): PatcherInstanceRecord["index"] => instanceIndex, + (state: RootStateType, instanceIndex: PatcherInstanceRecord["index"], tag: MessagePortRecord["tag"]): MessagePortRecord["tag"] => tag + ], + (ports, instanceIndex, tag): MessagePortRecord | undefined => { + return ports.find(p => p.instanceIndex === instanceIndex && p.tag === tag); + } +);