diff --git a/pNotifyCallbacks b/pNotifyCallbacks new file mode 100644 index 0000000..e69de29 diff --git a/react/CMakeLists.txt b/react/CMakeLists.txt index 614ce8b..70e3992 100644 --- a/react/CMakeLists.txt +++ b/react/CMakeLists.txt @@ -74,6 +74,7 @@ add_custom_command( src/TemporaryDrawer.tsx src/ToobCabSimView.tsx src/ToobFrequencyResponseView.tsx + src/ToobPowerStage2View.tsx src/ToobInputStageView.tsx src/ToobToneStackView.tsx src/Units.tsx diff --git a/react/src/ControlViewFactory.tsx b/react/src/ControlViewFactory.tsx index 5459025..9545070 100644 --- a/react/src/ControlViewFactory.tsx +++ b/react/src/ControlViewFactory.tsx @@ -29,12 +29,14 @@ import IControlViewFactory from './IControlViewFactory'; import ToobInputStageViewFactory from './ToobInputStageView'; import ToobToneStackViewFactory from './ToobToneStackView'; import ToobCabSimViewFactory from './ToobCabSimView'; +import ToobPowerStage2Factory from './ToobPowerStage2View'; let pluginFactories: IControlViewFactory[] = [ new ToobInputStageViewFactory(), new ToobToneStackViewFactory(), - new ToobCabSimViewFactory() + new ToobCabSimViewFactory(), + new ToobPowerStage2Factory() ]; diff --git a/react/src/PiPedalModel.tsx b/react/src/PiPedalModel.tsx index 3f527bc..276a370 100644 --- a/react/src/PiPedalModel.tsx +++ b/react/src/PiPedalModel.tsx @@ -45,6 +45,8 @@ export enum State { Reconnecting }; +export type ControlValueChangedHandler = (key: string, value: number) => void; + export interface ZoomedControlInfo { source: HTMLElement; instanceId: number; @@ -65,6 +67,17 @@ export interface VuUpdateInfo { export interface MonitorPortHandle { +}; +export interface ControlValueChangedHandle { + _ControlValueChangedHandle: number; + +}; + +interface ControlValueChangeItem { + handle: number; + instanceId: number; + onValueChanged: ControlValueChangedHandler; + }; class MidiEventListener { @@ -75,6 +88,18 @@ class MidiEventListener { handle: number; callback: (isNote: boolean, noteOrControl: number) => void; }; +class AtomOutputListener { + constructor(handle: number, instanceId: number, callback: (instanceId: number, atomObject: any) => void) { + this.handle = handle; + this.instanceId = instanceId; + this.callback = callback; + } + handle: number; + instanceId: number; + callback: (instanceId: number, atomObject: any) => void; +}; + + export interface ListenHandle { _handle: number; @@ -356,6 +381,14 @@ export interface PiPedalModel { listenForMidiEvent(listenForControlsOnly: boolean, onComplete: (isNote: boolean, noteOrControl: number) => void): ListenHandle; cancelListenForMidiEvent(listenHandle: ListenHandle): void; + addControlValueChangeListener(instanceId: number, onValueChanged: ControlValueChangedHandler): ControlValueChangedHandle + removeControlValueChangeListener(handle: ControlValueChangedHandle): void; + + listenForAtomOutput(instanceId: number, onComplete: (instanceId: number, atomOutput: any) => void): ListenHandle; + cancelListenForAtomOutput(listenHandle: ListenHandle): void; + + + download(targetType: string, isntanceId: number): void; uploadPreset(file: File, uploadAfter: number): Promise; @@ -501,6 +534,14 @@ class PiPedalModelImpl implements PiPedalModel { let isNote = body.isNote as boolean; let noteOrControl = body.noteOrControl as number; this.handleNotifyMidiListener(clientHandle, isNote, noteOrControl); + } else if (message === "onNotifyAtomOut") { + let clientHandle = body.clientHandle as number; + let instanceId = body.instanceId as number; + let atomJson = body.atomJson as string; + this.handleNotifyAtomOutput(clientHandle, instanceId,atomJson); + if (header.replyTo) { + this.webSocket?.reply(header.replyTo,"onNotifyAtomOut",true); + } } else if (message === "onControlChanged") { let controlChangedBody = body as ControlChangedBody; this._setPedalBoardControlValue( @@ -917,6 +958,26 @@ class PiPedalModelImpl implements PiPedalModel { } } + + _controlValueChangeItems: ControlValueChangeItem[] = []; + + addControlValueChangeListener(instanceId: number, onValueChanged: ControlValueChangedHandler): ControlValueChangedHandle + { + let handle = ++this.nextListenHandle; + this._controlValueChangeItems.push({ handle: handle, instanceId: instanceId,onValueChanged: onValueChanged}); + return { _ControlValueChangedHandle: handle}; + } + removeControlValueChangeListener(handle: ControlValueChangedHandle) + { + for (let i = 0; i < this._controlValueChangeItems.length; ++i) + { + if (this._controlValueChangeItems[i].handle === handle._ControlValueChangedHandle) + { + this._controlValueChangeItems.splice(i,1); + return; + } + } + } _setPedalBoardControlValue(instanceId: number, key: string, value: number, notifyServer: boolean): void { @@ -931,6 +992,14 @@ class PiPedalModelImpl implements PiPedalModel { if (notifyServer) { this._setServerControl("setControl", instanceId, key, value); } + for (let i = 0; i < this._controlValueChangeItems.length; ++i) + { + let item = this._controlValueChangeItems[i]; + if (instanceId === item.instanceId) + { + item.onValueChanged(key,value); + } + } } } @@ -1470,6 +1539,7 @@ class PiPedalModelImpl implements PiPedalModel { } } midiListeners: MidiEventListener[] = []; + atomOutputListeners: AtomOutputListener[] = []; nextListenHandle = 1; listenForMidiEvent(listenForControlsOnly: boolean, onComplete: (isNote: boolean, noteOrControl: number) => void): ListenHandle { @@ -1484,13 +1554,34 @@ class PiPedalModelImpl implements PiPedalModel { } + listenForAtomOutput(instanceId: number, onComplete: (instanceId: number, atomOutput: any) => void): ListenHandle { + let handle = this.nextListenHandle++; + + this.atomOutputListeners.push(new AtomOutputListener(handle,instanceId, onComplete)); + + this.webSocket?.send("listenForAtomOutput", { instanceId: instanceId, handle: handle }); + return { + _handle: handle + }; + } + + handleNotifyAtomOutput(clientHandle: number, instanceId: number, atomJson: string) { + let jsonObject: any = JSON.parse(atomJson); + for (let i = 0; i < this.atomOutputListeners.length; ++i) { + let listener = this.atomOutputListeners[i]; + if (listener.handle === clientHandle && listener.instanceId === instanceId) { + listener.callback(instanceId,jsonObject); + } + } + } + + handleNotifyMidiListener(clientHandle: number, isNote: boolean, noteOrControl: number) { for (let i = 0; i < this.midiListeners.length; ++i) { let listener = this.midiListeners[i]; if (listener.handle === clientHandle) { listener.callback(isNote, noteOrControl); - this.midiListeners.splice(i, 1); } } } @@ -1498,10 +1589,20 @@ class PiPedalModelImpl implements PiPedalModel { for (let i = 0; i < this.midiListeners.length; ++i) { if (this.midiListeners[i].handle === listenHandle._handle) { this.midiListeners.splice(i, 1); + break; } } this.webSocket?.send("cancelListenForMidiEvent", listenHandle._handle); } + cancelListenForAtomOutput(listenHandle: ListenHandle): void { + for (let i = 0; i < this.midiListeners.length; ++i) { + if (this.midiListeners[i].handle === listenHandle._handle) { + this.midiListeners.splice(i, 1); + break; + } + } + this.webSocket?.send("cancelListenForAtomOutput", listenHandle._handle); + } download(targetType: string, instanceId: number): void { if (instanceId === -1) return; diff --git a/react/src/PiPedalSocket.tsx b/react/src/PiPedalSocket.tsx index b5810bf..2cf1039 100644 --- a/react/src/PiPedalSocket.tsx +++ b/react/src/PiPedalSocket.tsx @@ -241,7 +241,7 @@ class PiPedalSocket { this.totalRetryDelay += this.retryDelay; Utility.delay(this.retryDelay).then(() => this.reconnect()); this.retryDelay *= 2; - if (this.retryDelay > 5000) this.retryDelay = 5000; + if (this.retryDelay > 3000) this.retryDelay = 3000; } }); } diff --git a/react/src/ToobFrequencyResponseView.tsx b/react/src/ToobFrequencyResponseView.tsx index f0fe844..8ee543d 100644 --- a/react/src/ToobFrequencyResponseView.tsx +++ b/react/src/ToobFrequencyResponseView.tsx @@ -75,18 +75,18 @@ const ToobFrequencyResponseView = { this.requestDeferred = false; this.requestOutstanding = false; - this.updateFrequencyResponse(); // after a reconnect. + this.updateAllWaveShapes(); // after a reconnect. } } onPedalBoardChanged() { - this.updateFrequencyResponse(); + this.updateAllWaveShapes(); } componentDidMount() { this.model.state.addOnChangedHandler(this.onStateChanged); this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged); - this.updateFrequencyResponse(); + this.updateAllWaveShapes(); } componentWillUnmount() { @@ -154,7 +154,7 @@ const ToobFrequencyResponseView = } - updateFrequencyResponse() { + updateAllWaveShapes() { if (this.requestOutstanding) { // throttling. this.requestDeferred = true; return; @@ -169,7 +169,7 @@ const ToobFrequencyResponseView = () => { this.requestOutstanding = false; this.requestDeferred = false; - this.updateFrequencyResponse(); + this.updateAllWaveShapes(); } ); diff --git a/react/src/ToobPowerStage2View.tsx b/react/src/ToobPowerStage2View.tsx new file mode 100644 index 0000000..d1394a1 --- /dev/null +++ b/react/src/ToobPowerStage2View.tsx @@ -0,0 +1,185 @@ +// Copyright (c) 2021 Robin Davies +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import React from 'react'; +import { createStyles, withStyles, WithStyles, Theme } from '@material-ui/core/styles'; + +import IControlViewFactory from './IControlViewFactory'; +import { PiPedalModelFactory, PiPedalModel, State,ListenHandle } from "./PiPedalModel"; +import { PedalBoardItem } from './PedalBoard'; +import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView'; +import ToobWaveShapeView, {BidirectionalVuPeak} from './ToobWaveShapeView'; + + + +const styles = (theme: Theme) => createStyles({ +}); + +interface ToobPowerstage2Props extends WithStyles { + instanceId: number; + item: PedalBoardItem; + +}; +interface ToobPowerstage2State { + uiState: number[]; +}; + +const ToobPowerstage2View = + withStyles(styles, { withTheme: true })( + class extends React.Component + implements ControlViewCustomization + { + model: PiPedalModel; + + customizationId: number = 5; + + constructor(props: ToobPowerstage2Props) { + super(props); + this.model = PiPedalModelFactory.getInstance(); + this.state = { + uiState: [0,0,0,0,0,0,0,0] + } + this.onStateChanged = this.onStateChanged.bind(this); + this.onAtomOutput = this.onAtomOutput.bind(this); + } + + listeningForOutput: boolean = false; + atomOutputHandle?: ListenHandle; + + onAtomOutput(instanceId: number, atomOutput: any): void { + if (atomOutput.lv2Type === "http://two-play.com/plugins/toob-power-stage-2#uiState" ) + { + this.setState({uiState: atomOutput.data as number[]}); + } + } + maybeListenForAtomOutput(): void { + let listenForOutput = this.isReady && this.isControlMounted; + + if (listenForOutput !== this.listeningForOutput) + { + this.listeningForOutput = listenForOutput; + if (listenForOutput) + { + this.atomOutputHandle = this.model.listenForAtomOutput(this.props.instanceId,this.onAtomOutput); + } else { + if (this.atomOutputHandle) + { + this.model.cancelListenForAtomOutput(this.atomOutputHandle); + this.atomOutputHandle = undefined; + } + + } + } + + } + isReady: boolean = false; + + gain1Peak: BidirectionalVuPeak = new BidirectionalVuPeak(); + gain2Peak: BidirectionalVuPeak = new BidirectionalVuPeak(); + gain3Peak: BidirectionalVuPeak = new BidirectionalVuPeak(); + gain1OutPeak: BidirectionalVuPeak = new BidirectionalVuPeak(); + gain2OutPeak: BidirectionalVuPeak = new BidirectionalVuPeak(); + gain3OutPeak: BidirectionalVuPeak = new BidirectionalVuPeak(); + + onStateChanged() { + this.isReady = this.model.state.get() === State.Ready; + this.maybeListenForAtomOutput(); + } + + isControlMounted: boolean = false; + + componentDidMount() { + this.model.state.addOnChangedHandler( + this.onStateChanged); + this.isControlMounted = true; + this.isReady = this.model.state.get() === State.Ready; + this.maybeListenForAtomOutput(); + } + componentWillUnmount() + { + this.isControlMounted = false; + this.maybeListenForAtomOutput(); + } + + ModifyControls(controls: (React.ReactNode| ControlGroup)[]): (React.ReactNode| ControlGroup)[] + { + let time = Date.now()*0.001; + + this.gain1Peak.update(time,this.state.uiState[0],this.state.uiState[1]); + this.gain1OutPeak.update(time,this.state.uiState[2],this.state.uiState[3]); + + this.gain2Peak.update(time,this.state.uiState[4],this.state.uiState[5]); + this.gain2OutPeak.update(time,this.state.uiState[6],this.state.uiState[7]); + + this.gain3Peak.update(time,this.state.uiState[8],this.state.uiState[9]); + this.gain3OutPeak.update(time,this.state.uiState[10],this.state.uiState[11]); + + var gain1 = ( + + ); + var gain2 = ( + + ); + var gain3 = ( + + ); + ((controls[0]) as ControlGroup).controls.splice(3,0,gain1); + ((controls[1]) as ControlGroup).controls.splice(3,0,gain2); + ((controls[2]) as ControlGroup).controls.splice(3,0,gain3); + + return controls; + } + render() { + return (); + } + } + ); + + + +class ToobPowerstage2ViewFactory implements IControlViewFactory { + uri: string = "http://two-play.com/plugins/toob-power-stage-2"; + + Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode { + return (); + } + + +}; +export default ToobPowerstage2ViewFactory; \ No newline at end of file diff --git a/react/src/ToobWaveShapeView.tsx b/react/src/ToobWaveShapeView.tsx new file mode 100644 index 0000000..aae497a --- /dev/null +++ b/react/src/ToobWaveShapeView.tsx @@ -0,0 +1,413 @@ +// Copyright (c) 2021 Robin Davies +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import React from 'react'; +import { createStyles, withStyles, WithStyles, Theme } from '@material-ui/core/styles'; + +import { PiPedalModelFactory, PiPedalModel, State, ControlValueChangedHandle } from "./PiPedalModel"; +import { StandardItemSize } from './PluginControlView'; +import Utility from './Utility'; +import SvgPathBuilder from './SvgPathBuilder'; + + +const WAVESHAPE_VECTOR_URI = "http://two-play.com/plugins/toob#waveShape"; + +const PLOT_WIDTH = StandardItemSize.height - 12; +const PLOT_HEIGHT = StandardItemSize.height - 12; + +const styles = (theme: Theme) => createStyles({ + frame: { + width: PLOT_WIDTH, height: PLOT_HEIGHT, background: "#444", + borderRadius: 6, + marginTop: 12, marginLeft: 8, marginRight: 8, + boxShadow: "1px 4px 8px #000 inset" + }, +}); + +interface SvgRect +{ + x: number; + y: number; + width: number; + height: number; + color: string; + +} + +interface ToobWaveShapeProps extends WithStyles { + instanceId: number; + controlNumber: number; + controlKeys: string[]; + + vuMin: number; + vuMax: number; + vuMinPeak: number; + vuMaxPeak: number; + + vuOutMin: number; + vuOutMax: number; + vuOutMinPeak: number; + vuOutMaxPeak: number; + +}; +interface ToobWaveShapeState { + data: number[]; +}; + +const HOLD_TIME: number = 1.6; // seconds. +const DECAY_RATE = 1.0; // full range/second. + +export class BidirectionalVuPeak { + minValue: number = 0; + maxValue: number = 0; + minPeak: number = 0; + maxPeak: number = 0; + holdTime: number = 1E120; // don't decay until we have an actual peak. + lastTime: number = 0; + + update(time: number, minValue: number, maxValue: number) + { + // decay peak. + if (time > this.holdTime) + { + let dt = time-this.lastTime; + let decay = dt*DECAY_RATE; + this.maxPeak -= decay; + if (this.maxPeak < 0) this.maxPeak = 0; + + this.minPeak += decay; + if (this.minPeak > 0) this.minPeak = 0; + this.lastTime = time; + + } + this.minValue = minValue; + this.maxValue = maxValue; + let resetPeakHold = false; + if (minValue < this.minPeak) + { + this.minPeak = minValue; + resetPeakHold = true; + } + if (maxValue > this.maxPeak) + { + this.maxPeak = maxValue; + resetPeakHold = true; + } + if (resetPeakHold) + { + this.holdTime = time+HOLD_TIME; + this.lastTime = this.holdTime; + } + } + +} + + +const ToobWaveShapeView = + withStyles(styles, { withTheme: true })( + class extends React.Component + { + model: PiPedalModel; + + customizationId: number = 1; + + constructor(props: ToobWaveShapeProps) { + super(props); + this.model = PiPedalModelFactory.getInstance(); + this.state = { + data: [] + }; + + this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this); + this.onStateChanged = this.onStateChanged.bind(this); + this.onControlValueChanged = this.onControlValueChanged.bind(this); + this.isReady = this.model.state.get() === State.Ready; + } + + isReady: boolean; + onStateChanged() { + let isReady = this.model.state.get() === State.Ready; + if (this.isReady !== isReady) { + this.isReady = isReady; + + if (isReady) { + this.requestDeferred = false; + this.requestOutstanding = false; + this.updateWaveShape(); // after a reconnect. + } + } + } + onPedalBoardChanged() { + this.updateWaveShape(); + } + _valueChangedHandle?: ControlValueChangedHandle; + + + mounted: boolean = false; + + componentDidMount() { + this.mounted = true; + this.model.state.addOnChangedHandler(this.onStateChanged); + this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged); + this._valueChangedHandle = this.model.addControlValueChangeListener( + this.props.instanceId, + this.onControlValueChanged); + + this.isReady = this.model.state.get() === State.Ready; + this.updateWaveShape(); + } + + lastVuMin: number = -1000; + lastVuMax: number = -1000; + + componentDidUpdate() { + } + + componentWillUnmount() { + this.mounted = false; + if (this._valueChangedHandle) { + this.model.removeControlValueChangeListener(this._valueChangedHandle); + this._valueChangedHandle = undefined; + } + this.model.state.removeOnChangedHandler(this.onStateChanged); + this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged); + } + + onControlValueChanged(key: string, value: number) { + for (let i = 0; i < this.props.controlKeys.length; ++i) { + if (this.props.controlKeys[i] === (key)) { + this.updateWaveShape(); + } + } + } + + requestOutstanding: boolean = false; + requestDeferred: boolean = false; + + // Size of the SVG element. + xMin: number = 4; + xMax: number = PLOT_WIDTH - 10; + yMin: number = 2; + yMax: number = PLOT_HEIGHT - 10; + vuHeight = 4; + nPoints: number = 0; + + indexToX(value: number): number { + return (this.xMax - this.xMin) * value / this.nPoints + this.xMin; + } + toY(value: number): number { + let yMid = (this.yMin + this.yMax) / 2; + return yMid - yMid * value; + } + toX(value: number): number { + let xMid = (this.xMin + this.xMax)/2; + return xMid+ (this.xMax-this.xMin)*0.5*value; + } + + + onWaveShapeUpdated(data: number[]) { + if (!this.mounted) { + return; + } + this.setState({data: data}); + } + + updateWaveShape() { + if (!this.isReady) + return; + if (this.requestOutstanding) { // throttling. + this.requestDeferred = true; + return; + } + this.requestOutstanding = true; + this.model.getLv2Parameter(this.props.instanceId, WAVESHAPE_VECTOR_URI + this.props.controlNumber) + .then((data) => { + this.onWaveShapeUpdated(data); + if (this.requestDeferred) { + Utility.delay(10) // take breath + .then( + () => { + this.requestOutstanding = false; + this.requestDeferred = false; + this.updateWaveShape(); + } + + ); + } else { + this.requestOutstanding = false; + } + }).catch(error => { + // assume the connection was lost. We'll get saved by a reconnect. + this.requestOutstanding = false; + this.requestDeferred = false; + }); + } + + currentPath: string = ""; + + redColor: string = "#F00"; + yellowColor: string = "#FF0"; + greenColor: string = "#0F0"; + + horizontalVuMeter(minValue: number, maxValue: number, minPeak: number, maxPeak: number): React.ReactNode[] + { + let result: React.ReactNode[] = []; + let greenXMin = this.toX(-0.5); + let greenXMax = this.toX(0.5); + if (minValue < -1.0) minValue = -1.0; + if (maxValue > 1.0) maxValue = 1.0; + if (minPeak < -1.0) minPeak = -1; + if (maxPeak > 1.0) maxPeak = 1.0; + let nKey = 0; + + let yTop = this.yMax+2; + + let minX = this.toX(minValue); + let maxX = this.toX(maxValue); + let minPeakX = this.toX(minPeak); + let maxPeakX = this.toX(maxPeak); + if (minX < greenXMin) + { + result.push( ( )); + minX = greenXMin; + } + if (maxX > greenXMax) + { + result.push( ( )) + maxX = greenXMax; + + } + if (maxX > minX) + { + result.push( ( )) + } + if (minPeakX+4 < maxPeakX) + { + let minColor = this.peakColor(minPeak); + let maxColor = this.peakColor(maxPeak); + result.push( ( )) + result.push( ( )) + } + + return result; + } + verticalVuMeter(minValue: number, maxValue: number, minPeak: number, maxPeak: number): React.ReactNode[] + { + let result: React.ReactNode[] = []; + let greenYMin = this.toY(-0.5); + let greenYMax = this.toY(0.5); + if (minValue < -1.0) minValue = -1.0; + if (maxValue > 1.0) maxValue = 1.0; + if (minPeak < -1.0) minPeak = -1; + if (maxPeak > 1.0) maxPeak = 1.0; + let nKey = 0; + + let xLeft = this.xMax+2; + + let minY = this.toY(minValue); + let maxY = this.toY(maxValue); + let minPeakY = this.toY(minPeak); + let maxPeakY = this.toY(maxPeak); + if (minY > greenYMin) + { + result.push( ( )); + minY = greenYMin; + } + if (maxY < greenYMax) + { + result.push( ( )) + maxY = greenYMax; + } + if (minY > maxY) + { + result.push( ( )) + } + if (minPeakY-4 > maxPeakY) + { + let minColor = this.peakColor(minPeak); + let maxColor = this.peakColor(maxPeak); + result.push( ( )) + result.push( ( )) + } + + return result; + } + peakColor(value: number): string { + if (value >= 1.0 || value <= -1.0) return this.redColor; + if (value >= 0.5 || value <= -0.5) return this.yellowColor; + return this.greenColor; + } + + render() { + let classes = this.props.classes; + + + let data = this.state.data; + let pathBuilder = new SvgPathBuilder(); + this.nPoints = data.length; + if (data.length > 2) { + pathBuilder.moveTo(this.indexToX(0), this.toY(data[0])); + for (let i = 1; i < data.length; ++i) { + pathBuilder.lineTo(this.indexToX(i), this.toY(data[i])); + } + } + let currentPath = pathBuilder.toString(); + + + + return ( +
+ + { this.horizontalVuMeter(this.props.vuMin,this.props.vuMax, + this.props.vuMinPeak,this.props.vuMaxPeak) + } + { this.verticalVuMeter(this.props.vuOutMin,this.props.vuOutMax, + this.props.vuOutMinPeak,this.props.vuOutMaxPeak) + } + + + +
); + } + } + ); + + + +export default ToobWaveShapeView; \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6d5c72e..c77bd7c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -212,6 +212,7 @@ set (REACT_BUILD_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../build/react/build/) set (REACT_NOTICES_FILE ${CMAKE_CURRENT_SOURCE_DIR}/../build/src/notices.txt) set (DEBIAN_COPYRIGHT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/../debian/copyright) + # generate Copyright section of settings page. # warning: there may be multiple versions. Pick the latest. if(EXISTS "/usr/share/doc/libboost1.74-dev") @@ -222,7 +223,7 @@ elseif(EXISTS "/usr/share/doc/libboost1.67-dev") set (BOOST_COPYRIGHT_DIR "libboost1.67-dev") else() message(ERROR "Boost libary version has changed. Please update me.") -endif() +endif() add_custom_command(OUTPUT ${REACT_NOTICES_FILE} diff --git a/src/IEffect.hpp b/src/IEffect.hpp index d31edec..c3a08cb 100644 --- a/src/IEffect.hpp +++ b/src/IEffect.hpp @@ -44,6 +44,7 @@ namespace pipedal { virtual void GatherParameter(RealtimeParameterRequest*pRequest) = 0; virtual std::string AtomToJson(uint8_t*pAtom) = 0; + virtual std::string GetAtomObjectType(uint8_t*pData) = 0; virtual void SetAudioOutputBuffer(int index, float*buffer) = 0; diff --git a/src/JackHost.cpp b/src/JackHost.cpp index d358fd6..80ddb09 100644 --- a/src/JackHost.cpp +++ b/src/JackHost.cpp @@ -130,10 +130,12 @@ private: RingBuffer inputRingBuffer; RingBuffer outputRingBuffer; - RingBufferReader realtimeReader; - RingBufferWriter realtimeWriter; - RingBufferReader hostReader; - RingBufferWriter hostWriter; + RingBufferWriter x; + + RealtimeRingBufferReader realtimeReader; + RealtimeRingBufferWriter realtimeWriter; + HostRingBufferReader hostReader; + HostRingBufferWriter hostWriter; JackChannelSelection channelSelection; bool active = false; @@ -577,7 +579,7 @@ private: pedalBoard->ResetAtomBuffers(); pedalBoard->ProcessParameterRequests(pParameterRequests); - processed = pedalBoard->Run(inputBuffers, outputBuffers, (uint32_t)nframes); + processed = pedalBoard->Run(inputBuffers, outputBuffers, (uint32_t)nframes,&realtimeWriter); if (processed) { if (this->realtimeVuBuffers != nullptr) @@ -723,6 +725,8 @@ public: Lv2Log::error("Audio processing terminated unexpectedly."); realtimeWriter.AudioStopped(); } + std::vector atomBuffer; + bool terminateThread; void ThreadProc() { @@ -871,6 +875,26 @@ public: this->pNotifyCallbacks->OnNotifyVusSubscription(*updates); } this->hostWriter.AckVuUpdate(); // please sir, can I have some more? + } else if (command == RingBufferCommand::AtomOutput) + { + uint64_t instanceId; + hostReader.read(&instanceId); + size_t extraBytes; + hostReader.read(&extraBytes); + if (atomBuffer.size() < extraBytes) + { + atomBuffer.resize(extraBytes); + } + hostReader.read(extraBytes,&(atomBuffer[0])); + + + IEffect *pEffect = currentPedalBoard->GetEffect(instanceId); + if (pEffect != nullptr &&this->pNotifyCallbacks && listenForAtomOutput) + { + std::string atomType = pEffect->GetAtomObjectType(&atomBuffer[0]); + auto json = pEffect->AtomToJson(&(atomBuffer[0])); + this->pNotifyCallbacks->OnNotifyAtomOutput(instanceId,atomType,json); + } } else if (command == RingBufferCommand::FreeVuSubscriptions) { @@ -1175,7 +1199,7 @@ public: } } - virtual void SetBypass(long instanceId, bool enabled) + virtual void SetBypass(uint64_t instanceId, bool enabled) { std::lock_guard guard(mutex); if (active && this->currentPedalBoard) @@ -1189,7 +1213,7 @@ public: } } - virtual void SetPluginPreset(long instanceId, const std::vector &values) + virtual void SetPluginPreset(uint64_t instanceId, const std::vector &values) { std::lock_guard guard(mutex); if (active && this->currentPedalBoard) @@ -1210,7 +1234,7 @@ public: } } - void SetControlValue(long instanceId, const std::string &symbol, float value) + void SetControlValue(uint64_t instanceId, const std::string &symbol, float value) { std::lock_guard guard(mutex); if (active && this->currentPedalBoard) @@ -1273,6 +1297,7 @@ public: result.subscriptionHandle = subscription.subscriptionHandle; result.instanceIndex = this->currentPedalBoard->GetIndexOfInstanceId(subscription.instanceid); IEffect *pEffect = this->currentPedalBoard->GetEffect(subscription.instanceid); + result.portIndex = pEffect->GetControlIndex(subscription.key); result.sampleRate = (int)(this->GetSampleRate() * subscription.updateInterval); result.samplesToNextCallback = result.sampleRate; @@ -1296,8 +1321,11 @@ public: for (size_t i = 0; i < subscriptions.size(); ++i) { - pSubscriptions->subscriptions.push_back( - MakeRealtimeSubscription(subscriptions[i])); + if (this->currentPedalBoard->GetEffect(subscriptions[i].instanceid) != nullptr) + { + pSubscriptions->subscriptions.push_back( + MakeRealtimeSubscription(subscriptions[i])); + } } this->hostWriter.SetMonitorPortSubscriptions(pSubscriptions); } @@ -1453,12 +1481,17 @@ public: return result; } - volatile bool listenForMidiEvent; + volatile bool listenForMidiEvent = false; + volatile bool listenForAtomOutput = false; virtual void SetListenForMidiEvent(bool listen) { this->listenForMidiEvent = listen; } + virtual void SetListenForAtomOutput(bool listen) + { + this->listenForAtomOutput = listen; + } }; int JackHostImpl::jackInstanceId = 0; diff --git a/src/JackHost.hpp b/src/JackHost.hpp index 2080b47..4968189 100644 --- a/src/JackHost.hpp +++ b/src/JackHost.hpp @@ -23,8 +23,8 @@ #include "Lv2PedalBoard.hpp" #include "VuUpdate.hpp" -#include "RingBuffer.hpp" #include "json.hpp" +#include "JackHost.hpp" #include "JackServerSettings.hpp" #include #include "PiPedalAlsa.hpp" @@ -97,6 +97,7 @@ public: virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0; virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) = 0; virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) = 0; + virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string&atomType,const std::string&atomJson) = 0; }; @@ -119,6 +120,7 @@ public: }; + class IHost; class JackHost { @@ -134,6 +136,7 @@ public: virtual void SetNotificationCallbacks(IJackHostCallbacks *pNotifyCallbacks) = 0; virtual void SetListenForMidiEvent(bool listen) = 0; + virtual void SetListenForAtomOutput(bool listen) = 0; virtual void Open(const JackChannelSelection & channelSelection) = 0; @@ -145,9 +148,9 @@ public: virtual void SetPedalBoard(const std::shared_ptr &pedalBoard) = 0; - virtual void SetControlValue(long instanceId,const std::string&symbol, float value) = 0; - virtual void SetPluginPreset(long isntanceId, const std::vector & values) = 0; - virtual void SetBypass(long instanceId, bool enabled) = 0; + virtual void SetControlValue(uint64_t instanceId,const std::string&symbol, float value) = 0; + virtual void SetPluginPreset(uint64_t instanceId, const std::vector & values) = 0; + virtual void SetBypass(uint64_t instanceId, bool enabled) = 0; virtual bool IsOpen() const = 0; diff --git a/src/Lv2Effect.cpp b/src/Lv2Effect.cpp index cb3ba4f..e8c90fd 100644 --- a/src/Lv2Effect.cpp +++ b/src/Lv2Effect.cpp @@ -38,6 +38,8 @@ #include "lv2/units.lv2/units.h" #include "lv2/atom.lv2/util.h" #include "JackHost.hpp" +#include +#include "RingBufferReader.hpp" using namespace pipedal; @@ -93,6 +95,10 @@ Lv2Effect::Lv2Effect( LilvInstance *pInstance = lilv_plugin_instantiate(pPlugin, pHost->GetSampleRate(), myFeatures); this->pInstance = pInstance; + if (this->pInstance == nullptr) + { + throw PiPedalException("Failed to create plugin."); + } if (info_->hasExtension(LV2_WORKER__interface)) { @@ -329,7 +335,7 @@ static inline void CopyBuffer(float *input, float *output, uint32_t frames) } } -void Lv2Effect::Run(uint32_t samples) +void Lv2Effect::Run(uint32_t samples,uint64_t instanceId, RealtimeRingBufferWriter *realtimeRingBufferWriter) { // close off the atom input frame. if (this->inputAtomBuffers.size() != 0) @@ -425,7 +431,9 @@ void Lv2Effect::Run(uint32_t samples) this->currentBypassDx = currentBypassDx; this->bypassSamplesRemaining = bypassSamplesRemaining; } + } + RelayOutputMessages(instanceId,realtimeRingBufferWriter); } LV2_Worker_Status Lv2Effect::worker_schedule_fn(LV2_Worker_Schedule_Handle handle, @@ -510,6 +518,30 @@ void Lv2Effect::RequestParameter(LV2_URID uridUri) lv2_atom_forge_pop(&inputForgeRt, &objectFrame); } +void Lv2Effect::RelayOutputMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter) +{ + LV2_Atom_Sequence*controlOutput = (LV2_Atom_Sequence*)GetAtomOutputBuffer(); + if (controlOutput == nullptr) + { + return; + } + + LV2_ATOM_SEQUENCE_FOREACH(controlOutput, ev) + { + + // frame_offset = ev->time.frames; // not really interested. + + if (lv2_atom_forge_is_object_type(&this->outputForgeRt, ev->body.type)) + { + const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body; + if (obj->body.otype != uris.patch_Set) // patch_Set is handled elsewhere. + { + realtimeRingBufferWriter->AtomOutput(instanceId,obj->atom.size +sizeof(obj->atom),(uint8_t*)obj); + } + } + } +} + void Lv2Effect::GatherParameter(RealtimeParameterRequest *pRequest) { LV2_Atom_Sequence*controlInput = (LV2_Atom_Sequence*)GetAtomOutputBuffer(); @@ -544,12 +576,13 @@ void Lv2Effect::GatherParameter(RealtimeParameterRequest *pRequest) LV2_URID key = ((const LV2_Atom_URID *)property)->body; if (key == pRequest->uridUri) { - if (value->size > sizeof(pRequest->response)) + int atom_size = value->size + sizeof(LV2_Atom); + if (atom_size > sizeof(pRequest->response)) { pRequest->errorMessage = "Response is too large."; } else { - pRequest->responseLength = value->size; - memcpy(pRequest->response,value,value->size); + pRequest->responseLength = atom_size; + memcpy(pRequest->response,value,atom_size); } break; } @@ -559,6 +592,16 @@ void Lv2Effect::GatherParameter(RealtimeParameterRequest *pRequest) } } +std::string Lv2Effect::GetAtomObjectType(uint8_t*pData) +{ + LV2_Atom_Object *pAtom = (LV2_Atom_Object*)pData; + if (pAtom->atom.type != uris.atom_Object) + { + throw std::invalid_argument("Not an Lv2 Object"); + } + return pHost->Lv2UriudToString(pAtom->body.otype); + +} std::string Lv2Effect::AtomToJson(uint8_t*pData) { std::stringstream s; @@ -569,6 +612,18 @@ std::string Lv2Effect::AtomToJson(uint8_t*pData) return s.str(); } +static std::string UriToFieldName(const std::string&uri){ + int pos; + for (pos = uri.length(); pos >= 0; --pos) + { + char c = uri[pos]; + if (c == '#' || c == '/' || c == ':') + { + break; + } + } + return uri.substr(pos+1); +} void Lv2Effect::WriteAtom(json_writer &writer, LV2_Atom*pAtom) { if (pAtom->type == uris.atom_Blank) @@ -612,7 +667,7 @@ void Lv2Effect::WriteAtom(json_writer &writer, LV2_Atom*pAtom) LV2_Atom_Vector *pVector = (LV2_Atom_Vector*)pAtom; writer.start_array(); { - size_t n = (pAtom->size-sizeof(LV2_Atom_Vector))/pVector->body.child_size; + size_t n = (pAtom->size-sizeof(pVector->body))/pVector->body.child_size; char *pItems = ((char*)pAtom) + sizeof(LV2_Atom_Vector); if (pVector->body.child_type == uris.atom_float) { @@ -662,22 +717,36 @@ void Lv2Effect::WriteAtom(json_writer &writer, LV2_Atom*pAtom) writer.start_object(); const LV2_Atom_Object *obj = (const LV2_Atom_Object *)pAtom; - std::string id = pHost->Lv2UriudToString(obj->body.id); - std::string type = pHost->Lv2UriudToString(obj->body.otype); - - writer.write_json_member("id",id.c_str()); - writer.write_json_member("lv2Type",type.c_str()); - bool firstMember = true; + if (obj->body.id != 0) + { + std::string id = pHost->Lv2UriudToString(obj->body.id); + writer.write_member("id",id.c_str()); + firstMember = false; + } + + if (obj->body.otype != 0) + { + std::string type = pHost->Lv2UriudToString(obj->body.otype); + writer.write_member("lv2Type",type.c_str()); + if (!firstMember) + { + writer.write_raw(","); + } + firstMember = false; + } + LV2_ATOM_OBJECT_FOREACH (obj, prop) { if (!firstMember) { writer.write_raw(","); } firstMember = false; std::string key = pHost->Lv2UriudToString(prop->key); + key = UriToFieldName(key); writer.write(key); writer.write_raw(": "); LV2_Atom *value = &(prop->value); + WriteAtom(writer,value); } writer.end_object(); diff --git a/src/Lv2Effect.hpp b/src/Lv2Effect.hpp index c03a10e..451018c 100644 --- a/src/Lv2Effect.hpp +++ b/src/Lv2Effect.hpp @@ -33,9 +33,12 @@ #include "lv2/lv2plug.in/ns/extensions/units/units.h" #include "lv2/atom.lv2/forge.h" + namespace pipedal { + class RealtimeRingBufferWriter; + class Lv2Effect : public IEffect { private: @@ -144,7 +147,7 @@ namespace pipedal Uris uris; - long instanceId; + uint64_t instanceId; BufferPool bufferPool; static LV2_Worker_Status worker_schedule_fn(LV2_Worker_Schedule_Handle handle, @@ -167,6 +170,8 @@ namespace pipedal virtual void RequestParameter(LV2_URID uridUri); virtual void GatherParameter(RealtimeParameterRequest*pRequest); + virtual void RelayOutputMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter); + virtual uint8_t*GetAtomInputBuffer() { if (this->inputAtomBuffers.size() == 0) return nullptr; return (uint8_t*)this->inputAtomBuffers[0]; @@ -178,6 +183,8 @@ namespace pipedal } virtual std::string AtomToJson(uint8_t*pAtom); + virtual std::string GetAtomObjectType(uint8_t*pData); + @@ -261,7 +268,7 @@ namespace pipedal } void Activate(); - void Run(uint32_t samples); + void Run(uint32_t samples, uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter); void Deactivate(); }; diff --git a/src/Lv2PedalBoard.cpp b/src/Lv2PedalBoard.cpp index 73508e1..91d4cb6 100644 --- a/src/Lv2PedalBoard.cpp +++ b/src/Lv2PedalBoard.cpp @@ -44,7 +44,7 @@ std::vector Lv2PedalBoard::AllocateAudioBuffers(int nChannels) return result; } -int Lv2PedalBoard::GetControlIndex(long instanceId, const std::string &symbol) +int Lv2PedalBoard::GetControlIndex(uint64_t instanceId, const std::string &symbol) { for (int i = 0; i < realtimeEffects.size(); ++i) { @@ -109,7 +109,7 @@ std::vector Lv2PedalBoard::PrepareItems( { pEffect = pLv2Effect; - long instanceId = pEffect->GetInstanceId(); + uint64_t instanceId = pEffect->GetInstanceId(); if (inputBuffers.size() == 1) { @@ -142,9 +142,9 @@ std::vector Lv2PedalBoard::PrepareItems( } this->processActions.push_back( - [pLv2Effect](uint32_t frames) + [pLv2Effect,this,instanceId](uint32_t frames) { - pLv2Effect->Run(frames); + pLv2Effect->Run(frames,instanceId,this->ringBufferWriter); }); } } @@ -356,9 +356,9 @@ static void Copy(float *input, float *output, uint32_t samples) output[i] = input[i]; } } -bool Lv2PedalBoard::Run(float **inputBuffers, float **outputBuffers, uint32_t samples) +bool Lv2PedalBoard::Run(float **inputBuffers, float **outputBuffers, uint32_t samples, RealtimeRingBufferWriter*ringBufferWriter) { - + this->ringBufferWriter = ringBufferWriter; for (int i = 0; i < this->pedalBoardInputBuffers.size(); ++i) { if (inputBuffers[i] == nullptr) diff --git a/src/Lv2PedalBoard.hpp b/src/Lv2PedalBoard.hpp index 734043d..293ba24 100644 --- a/src/Lv2PedalBoard.hpp +++ b/src/Lv2PedalBoard.hpp @@ -32,6 +32,7 @@ namespace pipedal { class RealtimeVuBuffers; class RealtimeParameterRequest; +class RealtimeRingBufferWriter; class Lv2PedalBoard { IHost *pHost = nullptr; @@ -54,6 +55,8 @@ class Lv2PedalBoard { float *CreateNewAudioBuffer(); + RealtimeRingBufferWriter *ringBufferWriter; + enum class MappingType { Linear, Circular, @@ -94,14 +97,14 @@ public: void Prepare(IHost *pHost,const PedalBoard&pedalBoard); - int GetIndexOfInstanceId(long instanceId) { + int GetIndexOfInstanceId(uint64_t instanceId) { for (int i = 0; i < this->realtimeEffects.size(); ++i) { if (this->realtimeEffects[i]->GetInstanceId() == instanceId) return i; } return -1; } - IEffect*GetEffect(long instanceId) { + IEffect*GetEffect(uint64_t instanceId) { for (int i = 0; i < realtimeEffects.size(); ++i) { if (realtimeEffects[i]->GetInstanceId() == instanceId) { @@ -112,7 +115,7 @@ public: } void Activate(); void Deactivate(); - bool Run(float**inputBuffers, float**outputBuffers,uint32_t samples); + bool Run(float**inputBuffers, float**outputBuffers,uint32_t samples, RealtimeRingBufferWriter*realtimeWriter); void ResetAtomBuffers(); @@ -126,7 +129,7 @@ public: - int GetControlIndex(long instanceId,const std::string&symbol); + int GetControlIndex(uint64_t instanceId,const std::string&symbol); void SetControlValue(int effectIndex, int portIndex, float value); void SetBypass(int effectIndex,bool enabled); diff --git a/src/PedalBoard.cpp b/src/PedalBoard.cpp index 92a2932..10631dc 100644 --- a/src/PedalBoard.cpp +++ b/src/PedalBoard.cpp @@ -96,7 +96,7 @@ bool PedalBoard::SetControlValue(long pedalItemId, const std::string &symbol, fl PedalBoardItem PedalBoard::MakeEmptyItem() { - long instanceId = NextInstanceId(); + uint64_t instanceId = NextInstanceId(); PedalBoardItem result; result.instanceId(instanceId); @@ -109,7 +109,7 @@ PedalBoardItem PedalBoard::MakeEmptyItem() PedalBoardItem PedalBoard::MakeSplit() { - long instanceId = NextInstanceId(); + uint64_t instanceId = NextInstanceId(); PedalBoardItem result; result.instanceId(instanceId); diff --git a/src/PedalBoard.hpp b/src/PedalBoard.hpp index ddfee3d..dd23295 100644 --- a/src/PedalBoard.hpp +++ b/src/PedalBoard.hpp @@ -122,8 +122,8 @@ public: class PedalBoard { std::string name_; std::vector items_; - long nextInstanceId_ = 0; - long NextInstanceId() { return ++nextInstanceId_; } + uint64_t nextInstanceId_ = 0; + uint64_t NextInstanceId() { return ++nextInstanceId_; } public: bool SetControlValue(long pedalItemId, const std::string &symbol, float value); diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index 0d14fd8..4a9ac60 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -53,7 +53,7 @@ PiPedalModel::PiPedalModel() void PiPedalModel::Close() { - std::lock_guard lock(this->mutex); + std::lock_guard guard(mutex); // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; @@ -186,13 +186,13 @@ IPiPedalModelSubscriber *PiPedalModel::GetNotificationSubscriber(int64_t clientI void PiPedalModel::AddNotificationSubscription(IPiPedalModelSubscriber *pSubscriber) { - std::lock_guard lock(this->mutex); + std::lock_guard guard(mutex); this->subscribers.push_back(pSubscriber); } void PiPedalModel::RemoveNotificationSubsription(IPiPedalModelSubscriber *pSubscriber) { { - std::lock_guard lock(this->mutex); + std::lock_guard guard(mutex); for (auto it = this->subscribers.begin(); it != this->subscribers.end(); ++it) { @@ -205,6 +205,7 @@ void PiPedalModel::RemoveNotificationSubsription(IPiPedalModelSubscriber *pSubsc int64_t clientId = pSubscriber->GetClientId(); this->deleteMidiListeners(clientId); + this->deleteAtomOutputListeners(clientId); for (int i = 0; i < this->outstandingParameterRequests.size(); ++i) { @@ -226,7 +227,7 @@ void PiPedalModel::previewControl(int64_t clientId, int64_t pedalItemId, const s void PiPedalModel::setControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) { { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); previewControl(clientId, pedalItemId, symbol, value); this->pedalBoard.SetControlValue(pedalItemId, symbol, value); { @@ -309,7 +310,7 @@ void PiPedalModel::firePedalBoardChanged(int64_t clientId) void PiPedalModel::setPedalBoard(int64_t clientId, PedalBoard &pedalBoard) { { - std::lock_guard guard(this->mutex); + std::lock_guard guard(mutex); this->pedalBoard = pedalBoard; updateDefaults(&this->pedalBoard); @@ -345,7 +346,7 @@ void PiPedalModel::setPedalBoardItemEnable(int64_t clientId, int64_t pedalItemId void PiPedalModel::getPresets(PresetIndex *pResult) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); this->storage.GetPresetIndex(pResult); pResult->presetChanged(this->hasPresetChanged); @@ -353,12 +354,12 @@ void PiPedalModel::getPresets(PresetIndex *pResult) PedalBoard PiPedalModel::getPreset(int64_t instanceId) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); return this->storage.GetPreset(instanceId); } void PiPedalModel::getBank(int64_t instanceId, BankFile *pResult) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); this->storage.GetBankFile(instanceId, pResult); } @@ -501,7 +502,7 @@ int64_t PiPedalModel::deletePreset(int64_t clientId, int64_t instanceId) } bool PiPedalModel::renamePreset(int64_t clientId, int64_t instanceId, const std::string &name) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); if (storage.RenamePreset(instanceId, name)) { this->firePresetsChanged(clientId); @@ -515,7 +516,7 @@ bool PiPedalModel::renamePreset(int64_t clientId, int64_t instanceId, const std: void PiPedalModel::setWifiConfigSettings(const WifiConfigSettings &wifiConfigSettings) { - std::lock_guard lock(this->mutex); + std::lock_guard guard(mutex); ShutdownClient::SetWifiConfig(wifiConfigSettings); @@ -540,19 +541,19 @@ void PiPedalModel::setWifiConfigSettings(const WifiConfigSettings &wifiConfigSet } WifiConfigSettings PiPedalModel::getWifiConfigSettings() { - std::lock_guard lock(this->mutex); + std::lock_guard guard(mutex); return this->storage.GetWifiConfigSettings(); } JackConfiguration PiPedalModel::getJackConfiguration() { - std::lock_guard lock(this->mutex); // copy atomically. + std::lock_guard guard(mutex); // copy atomically. return this->jackConfiguration; } void PiPedalModel::setJackChannelSelection(int64_t clientId, const JackChannelSelection &channelSelection) { - std::lock_guard lock(this->mutex); // copy atomically. + std::lock_guard guard(mutex); // copy atomically. this->storage.SetJackChannelSelection(channelSelection); this->fireChannelSelectionChanged(clientId); @@ -595,7 +596,7 @@ void PiPedalModel::fireChannelSelectionChanged(int64_t clientId) JackChannelSelection PiPedalModel::getJackChannelSelection() { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); JackChannelSelection t = this->storage.GetJackChannelSelection(this->jackConfiguration); if (this->jackConfiguration.isValid()) { @@ -606,7 +607,7 @@ JackChannelSelection PiPedalModel::getJackChannelSelection() int64_t PiPedalModel::addVuSubscription(int64_t instanceId) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); int64_t subscriptionId = ++nextSubscriptionId; activeVuSubscriptions.push_back(VuSubscription{subscriptionId, instanceId}); @@ -616,7 +617,7 @@ int64_t PiPedalModel::addVuSubscription(int64_t instanceId) } void PiPedalModel::removeVuSubscription(int64_t subscriptionHandle) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); for (auto i = activeVuSubscriptions.begin(); i != activeVuSubscriptions.end(); ++i) { if ((*i).subscriptionHandle == subscriptionHandle) @@ -630,7 +631,7 @@ void PiPedalModel::removeVuSubscription(int64_t subscriptionHandle) void PiPedalModel::OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); PedalBoardItem *item = this->pedalBoard.GetItem(instanceId); if (item) { @@ -702,7 +703,7 @@ void PiPedalModel::OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, f } void PiPedalModel::OnNotifyVusSubscription(const std::vector &updates) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); for (size_t i = 0; i < updates.size(); ++i) { // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) @@ -743,7 +744,7 @@ void PiPedalModel::updateRealtimeMonitorPortSubscriptions() int64_t PiPedalModel::monitorPort(int64_t instanceId, const std::string &key, float updateInterval, PortMonitorCallback onUpdate) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); int64_t subscriptionId = ++nextSubscriptionId; activeMonitorPortSubscriptions.push_back( MonitorPortSubscription{subscriptionId, instanceId, key, updateInterval, onUpdate}); @@ -754,7 +755,7 @@ int64_t PiPedalModel::monitorPort(int64_t instanceId, const std::string &key, fl } void PiPedalModel::unmonitorPort(int64_t subscriptionHandle) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); for (auto i = activeMonitorPortSubscriptions.begin(); i != activeMonitorPortSubscriptions.end(); ++i) { @@ -766,9 +767,11 @@ void PiPedalModel::unmonitorPort(int64_t subscriptionHandle) } } } + + void PiPedalModel::OnNotifyMonitorPort(const MonitorPortUpdate &update) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); for (auto i = activeMonitorPortSubscriptions.begin(); i != activeMonitorPortSubscriptions.end(); ++i) { @@ -793,7 +796,7 @@ void PiPedalModel::getLv2Parameter( [this](RealtimeParameterRequest *pParameter) { { - std::lock_guard guard(this->mutex); + std::lock_guard guard(mutex); bool cancelled = true; for (auto i = this->outstandingParameterRequests.begin(); i != this->outstandingParameterRequests.end(); ++i) @@ -829,27 +832,27 @@ void PiPedalModel::getLv2Parameter( onRequestComplete, clientId, instanceId, urid, onSuccess, onError); - std::lock_guard guard(constMutex(mutex)); + std::lock_guard guard(mutex); outstandingParameterRequests.push_back(request); this->jackHost->getRealtimeParameter(request); } BankIndex PiPedalModel::getBankIndex() const { - std::lock_guard guard(constMutex(mutex)); + std::lock_guard guard(const_cast(mutex)); return storage.GetBanks(); } void PiPedalModel::renameBank(int64_t clientId, int64_t bankId, const std::string &newName) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); storage.RenameBank(bankId, newName); fireBanksChanged(clientId); } int64_t PiPedalModel::saveBankAs(int64_t clientId, int64_t bankId, const std::string &newName) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); int64_t newId = storage.SaveBankAs(bankId, newName); fireBanksChanged(clientId); return newId; @@ -857,7 +860,7 @@ int64_t PiPedalModel::saveBankAs(int64_t clientId, int64_t bankId, const std::st void PiPedalModel::openBank(int64_t clientId, int64_t bankId) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); storage.LoadBank(bankId); fireBanksChanged(clientId); @@ -872,13 +875,13 @@ void PiPedalModel::openBank(int64_t clientId, int64_t bankId) JackServerSettings PiPedalModel::GetJackServerSettings() { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); return this->jackServerSettings; } void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSettings) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); if (!ShutdownClient::CanUseShutdownClient()) { @@ -916,7 +919,7 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet jackServerSettings, [this](bool success, const std::string &errorMessage) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); if (!success) { std::stringstream s; @@ -998,14 +1001,14 @@ void PiPedalModel::updateDefaults(PedalBoard *pedalBoard) std::vector PiPedalModel::GetPluginPresets(std::string pluginUri) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); return lv2Host.GetPluginPresets(pluginUri); } void PiPedalModel::LoadPluginPreset(int64_t instanceId, const std::string &presetUri) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); PedalBoardItem *pedalBoardItem = this->pedalBoard.GetItem(instanceId); if (pedalBoardItem != nullptr) @@ -1033,9 +1036,23 @@ void PiPedalModel::LoadPluginPreset(int64_t instanceId, const std::string &prese } } +void PiPedalModel::deleteAtomOutputListeners(int64_t clientId) +{ + std::lock_guard guard(mutex); + for (size_t i = 0; i < atomOutputListeners.size(); ++i) + { + if (atomOutputListeners[i].clientId == clientId) + { + atomOutputListeners.erase(atomOutputListeners.begin() + i); + --i; + } + } + jackHost->SetListenForAtomOutput(atomOutputListeners.size() != 0); +} + void PiPedalModel::deleteMidiListeners(int64_t clientId) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); for (size_t i = 0; i < midiEventListeners.size(); ++i) { if (midiEventListeners[i].clientId == clientId) @@ -1047,9 +1064,34 @@ void PiPedalModel::deleteMidiListeners(int64_t clientId) jackHost->SetListenForMidiEvent(midiEventListeners.size() != 0); } +void PiPedalModel::OnNotifyAtomOutput(uint64_t instanceId, const std::string&atomType,const std::string&atomJson) +{ + std::lock_guard guard(mutex); + + for (int i = 0; i < atomOutputListeners.size(); ++i) + { + auto &listener = atomOutputListeners[i]; + if (listener.instanceId == instanceId) + { + auto subscriber = this->GetNotificationSubscriber(listener.clientId); + if (subscriber) + { + subscriber->OnNotifyAtomOutput(listener.clientHandle,instanceId,atomType,atomJson); + } else { + atomOutputListeners.erase(atomOutputListeners.begin() + i); + --i; + } + } + } + jackHost->SetListenForAtomOutput(atomOutputListeners.size() != 0); + +} + + + void PiPedalModel::OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); for (int i = 0; i < midiEventListeners.size(); ++i) { @@ -1060,9 +1102,10 @@ void PiPedalModel::OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) if (subscriber) { subscriber->OnNotifyMidiListener(listener.clientHandle, isNote, noteOrControl); + } else { + midiEventListeners.erase(midiEventListeners.begin() + i); + --i; } - midiEventListeners.erase(midiEventListeners.begin() + i); - --i; } } jackHost->SetListenForMidiEvent(midiEventListeners.size() != 0); @@ -1070,17 +1113,48 @@ void PiPedalModel::OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) void PiPedalModel::listenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); MidiListener listener{clientId, clientHandle, listenForControlsOnly}; midiEventListeners.push_back(listener); jackHost->SetListenForMidiEvent(true); } -void PiPedalModel::cancelListenForMidiEvent(int64_t clientId, int64_t clientHandled) + +void PiPedalModel::listenForAtomOutputs(int64_t clientId, int64_t clientHandle, uint64_t instanceId) { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); + AtomOutputListener listener{clientId, clientHandle, instanceId}; + atomOutputListeners.push_back(listener); + jackHost->SetListenForAtomOutput(true); +} + +void PiPedalModel::cancelListenForMidiEvent(int64_t clientId, int64_t clientHandle) +{ + std::lock_guard guard(mutex); for (size_t i = 0; i < midiEventListeners.size(); ++i) { - midiEventListeners.erase(midiEventListeners.begin() + i); + const auto& listener = midiEventListeners[i]; + if (listener.clientId == clientId && listener.clientHandle == clientHandle) + { + midiEventListeners.erase(midiEventListeners.begin() + i); + break; + } + } + if (midiEventListeners.size() == 0) + { + jackHost->SetListenForMidiEvent(false); + } +} +void PiPedalModel::cancelListenForAtomOutputs(int64_t clientId, int64_t clientHandle) +{ + std::lock_guard guard(mutex); + for (size_t i = 0; i < atomOutputListeners.size(); ++i) + { + const auto&listener = atomOutputListeners[i]; + if (listener.clientId == clientId && listener.clientHandle == clientHandle) + { + atomOutputListeners.erase(atomOutputListeners.begin() + i); + break; + } } if (midiEventListeners.size() == 0) { diff --git a/src/PiPedalModel.hpp b/src/PiPedalModel.hpp index 6c705b2..48f71a5 100644 --- a/src/PiPedalModel.hpp +++ b/src/PiPedalModel.hpp @@ -54,6 +54,7 @@ public: virtual void OnLoadPluginPreset(int64_t instanceId,const std::vector&controlValues) = 0; virtual void OnMidiValueChanged(int64_t instanceId, const std::string&symbol, float value) = 0; virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl) = 0; + virtual void OnNotifyAtomOutput(int64_t clientModel,uint64_t instanceId,const std::string&atomType, const std::string&atomJson) = 0; virtual void OnWifiConfigSettingsChanged(const WifiConfigSettings&wifiConfigSettings) = 0; virtual void Close() = 0; }; @@ -61,6 +62,7 @@ public: class PiPedalModel: private IJackHostCallbacks { private: PiPedalAlsaDevices alsaDevices; + std::recursive_mutex mutex; class MidiListener { @@ -69,13 +71,20 @@ private: int64_t clientHandle; bool listenForControlsOnly; }; + class AtomOutputListener { + public: + int64_t clientId; + int64_t clientHandle; + uint64_t instanceId; + }; void deleteMidiListeners(int64_t clientId); + void deleteAtomOutputListeners(int64_t clientId); std::vector midiEventListeners; + std::vector atomOutputListeners; JackServerSettings jackServerSettings; Lv2Host lv2Host; - std::recursive_mutex mutex; PedalBoard pedalBoard; Storage storage; bool hasPresetChanged = false; @@ -120,7 +129,7 @@ private: // IJackHostCallbacks virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update); virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value); virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl); - + virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string&atomType,const std::string&atomJson); public: PiPedalModel(); @@ -132,7 +141,7 @@ public: const Lv2Host& getPlugins() const { return lv2Host; } PedalBoard getCurrentPedalBoardCopy() { - std::lock_guard guard(mutex); + std::lock_guard guard(mutex); return pedalBoard; } std::vector GetPluginPresets(std::string pluginUri); @@ -204,8 +213,13 @@ public: void SetJackServerSettings(const JackServerSettings& jackServerSettings); void listenForMidiEvent(int64_t clientId, int64_t clientHandle, bool listenForControlsOnly); - void cancelListenForMidiEvent(int64_t clientId, int64_t clientHandled); + void cancelListenForMidiEvent(int64_t clientId, int64_t clientHandle); + + void listenForAtomOutputs(int64_t clientId, int64_t clientHandle, uint64_t instanceId); + void cancelListenForAtomOutputs(int64_t clientId, int64_t clientHandle); + std::vector GetAlsaDevices(); }; + } // namespace pipedal. \ No newline at end of file diff --git a/src/PiPedalSocket.cpp b/src/PiPedalSocket.cpp index 4389101..d88b83d 100644 --- a/src/PiPedalSocket.cpp +++ b/src/PiPedalSocket.cpp @@ -53,6 +53,21 @@ JSON_MAP_REFERENCE(NotifyMidiListenerBody, isNote) JSON_MAP_REFERENCE(NotifyMidiListenerBody, noteOrControl) JSON_MAP_END() +class NotifyAtomOutputBody { +public: + int64_t clientHandle_; + uint64_t instanceId_; + + std::string atomJson_; + + DECLARE_JSON_MAP(NotifyAtomOutputBody); +}; +JSON_MAP_BEGIN(NotifyAtomOutputBody) +JSON_MAP_REFERENCE(NotifyAtomOutputBody, clientHandle) +JSON_MAP_REFERENCE(NotifyAtomOutputBody, instanceId) +JSON_MAP_REFERENCE(NotifyAtomOutputBody, atomJson) +JSON_MAP_END() + class ListenForMidiEventBody { public: bool listenForControlsOnly_; @@ -65,6 +80,18 @@ JSON_MAP_REFERENCE(ListenForMidiEventBody, listenForControlsOnly) JSON_MAP_REFERENCE(ListenForMidiEventBody, handle) JSON_MAP_END() +class ListenForAtomOutputBody { +public: + uint64_t instanceId_; + int64_t handle_; + DECLARE_JSON_MAP(ListenForAtomOutputBody); +}; + +JSON_MAP_BEGIN(ListenForAtomOutputBody) +JSON_MAP_REFERENCE(ListenForAtomOutputBody, instanceId) +JSON_MAP_REFERENCE(ListenForAtomOutputBody, handle) +JSON_MAP_END() + class OnLoadPluginPresetBody { public: @@ -390,7 +417,7 @@ private: writer.end_array(); { - std::lock_guard guard(this->writeMutex); + std::lock_guard guard(this->writeMutex); this->send(s.str()); } } @@ -421,7 +448,7 @@ private: } writer.end_array(); { - std::lock_guard guard(this->writeMutex); + std::lock_guard guard(this->writeMutex); this->send(s.str()); } } @@ -448,7 +475,7 @@ private: writer.end_array(); { - std::lock_guard guard(this->writeMutex); + std::lock_guard guard(this->writeMutex); this->send(s.str()); } } @@ -526,7 +553,7 @@ private: { } }; - std::mutex requestMutex; + std::recursive_mutex requestMutex; std::vector requestReservations; std::atomic nextRequestId{1}; @@ -543,7 +570,7 @@ public: onSuccess, onError); { - std::lock_guard lock(requestMutex); + std::lock_guard lock(requestMutex); requestReservations.push_back(reservation); } std::stringstream s(ios_base::out); @@ -563,7 +590,7 @@ public: } writer.end_array(); { - std::lock_guard guard(this->writeMutex); + std::lock_guard guard(this->writeMutex); this->send(s.str()); } } @@ -595,7 +622,7 @@ public: bool waitingForPortMonitorAck(uint64_t subscriptionId) { { - std::lock_guard guard(subscriptionMutex); + std::lock_guard guard(subscriptionMutex); for (int i = 0; i < this->activePortMonitors.size(); ++i) { auto &portMonitor = activePortMonitors[i]; @@ -613,7 +640,7 @@ public: void portMonitorAck(uint64_t subscriptionId) { { - std::lock_guard guard(subscriptionMutex); + std::lock_guard guard(subscriptionMutex); for (int i = 0; i < this->activePortMonitors.size(); ++i) { auto &portMonitor = activePortMonitors[i]; @@ -632,7 +659,7 @@ public: { IRequestReservation *reservation = nullptr; { - std::lock_guard guard(this->requestMutex); + std::lock_guard guard(this->requestMutex); for (auto i = this->requestReservations.begin(); i != this->requestReservations.end(); ++i) { if ((*i)->GetReservationid() == reply) @@ -686,6 +713,19 @@ public: pReader->read(&handle); this->model.cancelListenForMidiEvent(this->clientId,handle); } + else if (message == "listenForAtomOutput") + { + ListenForAtomOutputBody body; + pReader->read(&body); + this->model.listenForAtomOutputs(this->clientId,body.handle_, body.instanceId_); + } + else if (message == "cancelListenForAtomOutput") + { + int64_t handle; + pReader->read(&handle); + this->model.cancelListenForMidiEvent(this->clientId,handle); + } + else if (message == "getJackStatus") { JackHostStatus status = model.getJackStatus(); @@ -989,7 +1029,7 @@ public: } }); { - std::lock_guard guard(subscriptionMutex); + std::lock_guard guard(subscriptionMutex); activePortMonitors.push_back( PortMonitorSubscription{subscriptionHandle, body.instanceId_,false, body.key_}); } @@ -1002,7 +1042,7 @@ public: pReader->read(&subscriptionHandle); { { - std::lock_guard guard(subscriptionMutex); + std::lock_guard guard(subscriptionMutex); for (auto i = this->activePortMonitors.begin(); i != this->activePortMonitors.end(); ++i) { if ((*i).subscriptionHandle == subscriptionHandle) @@ -1024,7 +1064,7 @@ public: int64_t subscriptionHandle = model.addVuSubscription(instanceId); { - std::lock_guard guard(subscriptionMutex); + std::lock_guard guard(subscriptionMutex); activeVuSubscriptions.push_back(VuSubscription{subscriptionHandle, instanceId}); } this->Reply(replyTo, "addVuSubscriptin", subscriptionHandle); @@ -1034,7 +1074,7 @@ public: int64_t subscriptionHandle = -1; pReader->read(&subscriptionHandle); { - std::lock_guard guard(subscriptionMutex); + std::lock_guard guard(subscriptionMutex); for (auto i = activeVuSubscriptions.begin(); i != activeVuSubscriptions.end(); ++i) { @@ -1152,7 +1192,7 @@ public: virtual void OnVuMeterUpdate(const std::vector &updates) { - std::lock_guard guard(subscriptionMutex); + std::lock_guard guard(subscriptionMutex); if (updateRequestOutstanding < 5) // throttle to accomodate a web page that can't keep up. { vuUpdateDropped = false; @@ -1255,6 +1295,76 @@ public: } } + int outstandingNotifyAtomOutputs = 0; + + class PendingNotifyAtomOutput { + public: + int64_t clientHandle; + uint64_t instanceId; + std::string atomType; + std::string json; + }; + + std::vector pendingNotifyAtomOutputs; + + + void OnAckNotifyAtomOutput() { + std::lock_guard guard(subscriptionMutex); + if (--outstandingNotifyAtomOutputs <= 0) + { + outstandingNotifyAtomOutputs = 0; + if (pendingNotifyAtomOutputs.size() != 0) + { + PendingNotifyAtomOutput t = pendingNotifyAtomOutputs[0]; + pendingNotifyAtomOutputs.erase(pendingNotifyAtomOutputs.begin()); + OnNotifyAtomOutput( + t.clientHandle, + t.instanceId, + t.atomType, + t.json); + + } + } + } + + virtual void OnNotifyAtomOutput(int64_t clientHandle,uint64_t instanceId,const std::string&atomType, const std::string&atomJson) + { + NotifyAtomOutputBody body; + body.clientHandle_ = clientHandle; + body.instanceId_ = instanceId; + body.atomJson_ = atomJson; + + { + std::lock_guard guard(subscriptionMutex); + + if (outstandingNotifyAtomOutputs != 0) + { + for (size_t i = 0; i < pendingNotifyAtomOutputs.size(); ++i) + { + auto &output = pendingNotifyAtomOutputs[i]; + if (output.clientHandle == clientHandle && output.instanceId == instanceId && output.atomType == atomType) + { + output.json = atomJson; + return; + } + } + pendingNotifyAtomOutputs.push_back(PendingNotifyAtomOutput{ clientHandle, instanceId,atomType,atomJson }); + return; + } + ++outstandingNotifyAtomOutputs; + } + + Request("onNotifyAtomOut",body, + [this] (const bool& value) + { + this->OnAckNotifyAtomOutput(); + }, + [] (const std::exception &e) { + + } + ); + + } virtual void OnNotifyMidiListener(int64_t clientHandle, bool isNote, uint8_t noteOrControl) { NotifyMidiListenerBody body; diff --git a/src/Presets.hpp b/src/Presets.hpp index e2d96d9..5679e2b 100644 --- a/src/Presets.hpp +++ b/src/Presets.hpp @@ -33,7 +33,7 @@ public: }; class PedalBoardPreset { - long instanceId_; + uint64_t instanceId_; std::string displayName_; std::vector > values_; public: diff --git a/src/RingBuffer.hpp b/src/RingBuffer.hpp index e5c091e..4434522 100644 --- a/src/RingBuffer.hpp +++ b/src/RingBuffer.hpp @@ -163,6 +163,69 @@ namespace pipedal return true; } } + // Write two disjoint areas of memory atomically. + bool write(size_t bytes, uint8_t *data, size_t bytes2, uint8_t*data2) + { + if (MULTI_WRITER) + { + std::lock_guard guard(write_mutex); + if (writeSpace() <= bytes+sizeof(bytes2)+bytes2) { + return false; + } + size_t index = this->writePosition; + for (size_t i = 0; i < bytes; ++i) + { + buffer[(index+i) & ringBufferMask] = data[i]; + } + index = (index+bytes) & ringBufferMask; + + for (size_t i = 0; i < sizeof(bytes2); ++i) + { + buffer[(index+i) & ringBufferMask] = ((char*)&bytes2)[i]; + } + index = (index+sizeof(bytes2)) & ringBufferMask; + + for (size_t i = 0; i < bytes2; ++i) + { + buffer[(index+i) & ringBufferMask] = data2[i]; + } + this->writePosition = (index+bytes2) & ringBufferMask; + if (SEMAPHORE_READER) + { + sem_post(&readSemaphore); + } + return true; + } else { + if (writeSpace() <= bytes+sizeof(bytes2)+bytes2) { + return false; + } + size_t index = this->writePosition; + for (size_t i = 0; i < bytes; ++i) + { + buffer[(index+i) & ringBufferMask] = data[i]; + } + index = (index+bytes) & ringBufferMask; + + for (size_t i = 0; i < sizeof(bytes2); ++i) + { + buffer[(index+i) & ringBufferMask] = ((char*)&bytes2)[i]; + } + index = (index+sizeof(bytes2)) & ringBufferMask; + + for (size_t i = 0; i < bytes2; ++i) + { + buffer[(index+i) & ringBufferMask] = data2[i]; + } + this->writePosition = (index+bytes2) & ringBufferMask; + + if (SEMAPHORE_READER) + { + sem_post(&readSemaphore); + } + return true; + } + } + bool read(size_t bytes, uint8_t*data) { if (readSpace() < bytes) return false; diff --git a/src/RingBufferReader.hpp b/src/RingBufferReader.hpp index df4705c..658f2d4 100644 --- a/src/RingBufferReader.hpp +++ b/src/RingBufferReader.hpp @@ -23,7 +23,6 @@ #include "Lv2Log.hpp" #include "VuUpdate.hpp" #include "JackHost.hpp" - namespace pipedal { @@ -50,6 +49,8 @@ namespace pipedal OnMidiListen = 16, + AtomOutput = 17, + }; class RealtimeMonitorPortSubscription @@ -186,6 +187,16 @@ namespace pipedal } return true; } + + bool read(size_t size, uint8_t*data) + { + if (!ringBuffer->read(size,data)) + { + throw PiPedalStateException("Ringbuffer read failed. Did you forget to check for space?"); + } + return true; + + } template void readComplete(T *output) { @@ -251,6 +262,24 @@ namespace pipedal } } + template + void write(RingBufferCommand command, const T &value, size_t dataLength, uint8_t*variableData) + { + + // the goal: to atomically write the command and associated data. + CommandBuffer buffer(command, value); + + if (!ringBuffer->write(buffer.size(),(uint8_t *)&buffer,dataLength,variableData)) + { + Lv2Log::error("No space in audio service ringbuffer."); + return; + + } + } + void AtomOutput(uint64_t instanceId, size_t bytes, uint8_t*data) + { + write(RingBufferCommand::AtomOutput,instanceId,bytes,data); + } void ParameterRequest(RealtimeParameterRequest *pRequest) { write(RingBufferCommand::ParameterRequest,pRequest); @@ -354,4 +383,25 @@ namespace pipedal } }; + typedef RingBufferReader RealtimeRingBufferReader; + typedef RingBufferReader HostRingBufferReader; + typedef RingBufferWriter HostRingBufferWriter; + + // cures a forward-declaration problem. + class RealtimeRingBufferWriter: public RingBufferWriter + { + public: + RealtimeRingBufferWriter() + { + } + RealtimeRingBufferWriter(RingBuffer*ringBuffer) + : RingBufferWriter (ringBuffer) + { + } + + + + }; + + } //namespace \ No newline at end of file diff --git a/src/SplitEffect.hpp b/src/SplitEffect.hpp index 2c1d114..8448847 100644 --- a/src/SplitEffect.hpp +++ b/src/SplitEffect.hpp @@ -274,17 +274,18 @@ namespace pipedal } } } - long instanceId; + uint64_t instanceId; virtual uint8_t *GetAtomInputBuffer() { return nullptr; } virtual uint8_t *GetAtomOutputBuffer() { return nullptr; } virtual void RequestParameter(LV2_URID uridUri) {} virtual void GatherParameter(RealtimeParameterRequest *pRequest) {} virtual std::string AtomToJson(uint8_t *pAtom) { return ""; } + virtual std::string GetAtomObjectType(uint8_t*pData) { return "not implemented";} public: SplitEffect( - long instanceId, + uint64_t instanceId, double sampleRate, const std::vector &inputs) : instanceId(instanceId), inputs(inputs), sampleRate(sampleRate) diff --git a/src/VuUpdate.hpp b/src/VuUpdate.hpp index bb34c98..a274278 100644 --- a/src/VuUpdate.hpp +++ b/src/VuUpdate.hpp @@ -26,7 +26,7 @@ namespace pipedal class VuUpdate { public: - long instanceId_ = 0; + uint64_t instanceId_ = 0; long sampleTime_ = 0; bool isStereoInput_ = false; bool isStereoOutput_ = false;