diff --git a/src/actions/graph.ts b/src/actions/graph.ts index 7fc10b3..914a411 100644 --- a/src/actions/graph.ts +++ b/src/actions/graph.ts @@ -364,8 +364,8 @@ export const initNodes = (jackPortsInfo: OSCQueryRNBOJackPortInfo, instanceInfo: const instance = PatcherInstanceRecord.fromDescription(info); instances.push(instance); instanceParameters.push(...ParameterRecord.fromDescription(instance.index, info.CONTENTS.params)); - instanceMessageInports.push(...MessagePortRecord.fromDescription(info.CONTENTS.messages?.CONTENTS?.in)); - instanceMessageOutports.push(...MessagePortRecord.fromDescription(info.CONTENTS.messages?.CONTENTS?.out)); + 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 @@ -697,8 +697,8 @@ export const addPatcherNode = (desc: OSCQueryRNBOInstance, metaString: string): // Create Instance State const instance = PatcherInstanceRecord.fromDescription(desc); const parameters = ParameterRecord.fromDescription(instance.index, desc.CONTENTS.params); - const messageInports = MessagePortRecord.fromDescription(desc.CONTENTS.messages?.CONTENTS?.in); - const messageOutports = MessagePortRecord.fromDescription(desc.CONTENTS.messages?.CONTENTS?.out); + 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)); diff --git a/src/actions/patchers.ts b/src/actions/patchers.ts new file mode 100644 index 0000000..a0f7c54 --- /dev/null +++ b/src/actions/patchers.ts @@ -0,0 +1,919 @@ +import Router from "next/router"; +import { ActionBase, AppThunk } from "../lib/store"; +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_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_PATCHERS; + payload: { + patchers: PatcherExportRecord[]; + }; +} + +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 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[]; + }; +} + +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(PatcherExportRecord.fromDescription(name, desc)); + } + + return { + type: PatcherActionType.INIT_PATCHERS, + payload: { + patchers + } + }; +}; + +export const destroyPatcherOnRemote = (patcher: PatcherExportRecord): AppThunk => + (dispatch) => { + try { + const message: OSCMessage = { + address: `/rnbo/patchers/${patcher.name}/destroy`, + args: [] + }; + oscQueryBridge.sendPacket(writePacket(message)); + } catch (err) { + dispatch(showNotification({ + level: NotificationLevel.error, + title: `Error while trying to delete patcher ${patcher.name}`, + message: "Please check the console for further details." + })); + console.error(err); + } + }; + + +export const renamePatcherOnRemote = (patcher: PatcherExportRecord, newName: string): AppThunk => + (dispatch) => { + try { + const message = { + address: `/rnbo/patchers/${patcher.name}/rename`, + args: [ + { type: "s", value: newName } + ] + }; + oscQueryBridge.sendPacket(writePacket(message)); + } catch (err) { + dispatch(showNotification({ + level: NotificationLevel.error, + title: `Error while trying to rename patcher ${patcher.name} -> ${newName}`, + message: "Please check the console for further details." + })); + 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/models/messageport.ts b/src/models/messageport.ts index 7ca76b7..62f0fa0 100644 --- a/src/models/messageport.ts +++ b/src/models/messageport.ts @@ -1,5 +1,6 @@ import { Record as ImmuRecord } from "immutable"; import { OSCQueryRNBOInstanceMessageInfo, OSCQueryRNBOInstanceMessages, OSCQueryRNBOInstanceMessageValue } from "../lib/types"; +import { PatcherInstanceRecord } from "./instance"; export type MessagePortRecordProps = { instanceIndex: number; @@ -18,11 +19,11 @@ export class MessagePortRecord extends ImmuRecord({ path: "" }) { - private static messagesArrayFromDescription(desc: OSCQueryRNBOInstanceMessageInfo, name: string): MessagePortRecord[] { + private static messagesArrayFromDescription(instanceIndex: PatcherInstanceRecord["index"], desc: OSCQueryRNBOInstanceMessageInfo, name: string): MessagePortRecord[] { if (typeof desc.VALUE !== "undefined") { return [ new MessagePortRecord({ - instanceIndex: 0, + instanceIndex, tag: name, path: (desc as OSCQueryRNBOInstanceMessageValue).FULL_PATH, meta: (desc as OSCQueryRNBOInstanceMessageValue).CONTENTS?.meta?.VALUE || "" @@ -33,15 +34,15 @@ export class MessagePortRecord extends ImmuRecord({ const result: MessagePortRecord[] = []; for (const [subKey, subDesc] of Object.entries(desc.CONTENTS)) { const subPrefix = name ? `${name}/${subKey}` : subKey; - result.push(...this.messagesArrayFromDescription(subDesc, subPrefix)); + result.push(...this.messagesArrayFromDescription(instanceIndex, subDesc, subPrefix)); } return result; } - public static fromDescription(messagesDesc?: OSCQueryRNBOInstanceMessages): MessagePortRecord[] { + 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(desc, name)); + ports.push(...this.messagesArrayFromDescription(instanceIndex, desc, name)); } return ports; } diff --git a/src/reducers/patchers.ts b/src/reducers/patchers.ts new file mode 100644 index 0000000..d484369 --- /dev/null +++ b/src/reducers/patchers.ts @@ -0,0 +1,207 @@ +import { Map as ImmuMap } from "immutable"; +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 { + exports: ImmuMap; + instances: ImmuMap; + instanceMessageInports: ImmuMap; + instanceMessageOutports: ImmuMap; + instanceParameters: ImmuMap; +} + +export const patchers = (state: PatcherState = { + + exports: ImmuMap(), + instances: ImmuMap(), + instanceMessageInports: ImmuMap(), + instanceMessageOutports: ImmuMap(), + instanceParameters: ImmuMap() + +}, action: InstanceAction): PatcherState => { + + switch(action.type) { + + case PatcherActionType.INIT_PATCHERS: { + const { patchers } = action.payload; + + return { + ...state, + 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)) + }; + } + + default: + return state; + } +};