TooB Spectum Analyzer

This commit is contained in:
Robin Davies
2022-02-04 07:06:09 -05:00
parent caa2aa1312
commit 5c5bf29d48
14 changed files with 584 additions and 86 deletions
+3 -1
View File
@@ -30,13 +30,15 @@ import ToobInputStageViewFactory from './ToobInputStageView';
import ToobToneStackViewFactory from './ToobToneStackView';
import ToobCabSimViewFactory from './ToobCabSimView';
import ToobPowerStage2Factory from './ToobPowerStage2View';
import ToobSpectrumAnalyzerViewFactory from './ToobSpectrumAnalyzerView';
let pluginFactories: IControlViewFactory[] = [
new ToobInputStageViewFactory(),
new ToobToneStackViewFactory(),
new ToobCabSimViewFactory(),
new ToobPowerStage2Factory()
new ToobPowerStage2Factory(),
new ToobSpectrumAnalyzerViewFactory()
];
+1 -1
View File
@@ -288,7 +288,7 @@ const JackServerSettingsDialog = withStyles(styles)(
{((!noDevices) && !waitingForDevices) && (
this.state.alsaDevices!.map((device) =>
(
<MenuItem value={device.id}>{device.name}</MenuItem>
<MenuItem key={device.id} value={device.id}>{device.name}</MenuItem>
)
)
+1 -1
View File
@@ -127,7 +127,7 @@ const MidiBindingView =
generateControlSelects(): React.ReactNode[] {
return Utility.validMidiControllers.map((control) => (
<MenuItem value={control.value}>{control.displayName}</MenuItem>
<MenuItem key={control.value} value={control.value}>{control.displayName}</MenuItem>
)
);
}
+1 -1
View File
@@ -556,7 +556,7 @@ const PluginControl =
style={{ marginLeft: 4, marginRight: 4, width: 140 }}
>
{control.scale_points.map((scale_point: ScalePoint) => (
<MenuItem value={scale_point.value}>{scale_point.label}</MenuItem>
<MenuItem key={scale_point.value} value={scale_point.value}>{scale_point.label}</MenuItem>
))}
</Select>
+1
View File
@@ -365,6 +365,7 @@ const PluginControlView =
for (let i = 0; i < controls.length; ++i)
{
controls[i].key = 'k'+i;
result.push(controls[i]);
}
+1 -1
View File
@@ -298,7 +298,7 @@ const PresetSelector =
{
presets.presets.map((preset) => {
return (
<MenuItem value={preset.instanceId} >
<MenuItem key={preset.instanceId} value={preset.instanceId} >
{(presets.presetChanged && preset.instanceId === presets.selectedInstanceId)
? (preset.name + "*")
: (preset.name)
+89
View File
@@ -0,0 +1,89 @@
// 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 } from "./PiPedalModel";
import { PedalBoardItem } from './PedalBoard';
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
import ToobSpectrumResponseView from './ToobSpectrumResponseView';
const styles = (theme: Theme) => createStyles({
});
interface ToobSpectrumAnalyzerProps extends WithStyles<typeof styles> {
instanceId: number;
item: PedalBoardItem;
};
interface ToobSpectrumAnalyzerState {
};
const ToobSpectrumAnalyzerView =
withStyles(styles, { withTheme: true })(
class extends React.Component<ToobSpectrumAnalyzerProps, ToobSpectrumAnalyzerState>
implements ControlViewCustomization
{
model: PiPedalModel;
customizationId: number = 1;
constructor(props: ToobSpectrumAnalyzerProps) {
super(props);
this.model = PiPedalModelFactory.getInstance();
this.state = {
}
}
ModifyControls(controls: (React.ReactNode| ControlGroup)[]): (React.ReactNode| ControlGroup)[]
{
controls.splice(0,0,
( <ToobSpectrumResponseView instanceId={this.props.instanceId} />)
);
return controls;
}
render() {
return (<PluginControlView
instanceId={this.props.instanceId}
item={this.props.item}
customization={this}
customizationId={this.customizationId}
/>);
}
}
);
class ToobSpectrumAnalyzerViewFactory implements IControlViewFactory {
uri: string = "http://two-play.com/plugins/toob-spectrum";
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode {
return (<ToobSpectrumAnalyzerView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
}
};
export default ToobSpectrumAnalyzerViewFactory;
+307
View File
@@ -0,0 +1,307 @@
// 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,ListenHandle } from "./PiPedalModel";
import { StandardItemSize } from './PluginControlView';
import Utility from './Utility';
import { setInterval,clearInterval } from 'timers';
const SPECTRUM_RESPONSE_VECTOR_URI = "http://two-play.com/plugins/toob#spectrumResponse";
const PLOT_WIDTH = StandardItemSize.width*3-16;
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 ToobSpectrumResponseProps extends WithStyles<typeof styles> {
instanceId: number;
};
interface ToobSpectrumResponseState {
path: string;
minF: number;
maxF: number;
};
const ToobSpectrumResponseView =
withStyles(styles, { withTheme: true })(
class extends React.Component<ToobSpectrumResponseProps, ToobSpectrumResponseState>
{
model: PiPedalModel;
customizationId: number = 1;
constructor(props: ToobSpectrumResponseProps) {
super(props);
this.model = PiPedalModelFactory.getInstance();
let minF = 60;
let maxF = 18000;
let plugin = this.model.pedalBoard.get()?.maybeGetItem(this.props.instanceId);
if (plugin)
{
minF = plugin.getControlValue("minF");
maxF = plugin.getControlValue("maxF");
}
this.state = {
path: "",
minF: minF,
maxF: maxF
};
this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this);
this.onStateChanged = this.onStateChanged.bind(this);
}
onStateChanged()
{
if (this.model.state.get() === State.Ready)
{
this.requestDeferred = false;
this.requestOutstanding = false;
let plugin = this.model.pedalBoard.get()?.maybeGetItem(this.props.instanceId);
if (plugin)
{
let minF = plugin.getControlValue("minF");
let maxF = plugin.getControlValue("maxF");
this.setState({minF: minF, maxF: maxF});
}
}
this.isReady = this.model.state.get() === State.Ready;
this.maybeStartClock(); // after a reconnect.
}
onPedalBoardChanged()
{
this.maybeStartClock();
let plugin = this.model.pedalBoard.get()?.maybeGetItem(this.props.instanceId);
if (plugin)
{
let minF = plugin.getControlValue("minF");
let maxF = plugin.getControlValue("maxF");
this.setState({minF: minF, maxF: maxF});
}
}
mounted: boolean = false;
isReady: boolean = false;
componentDidMount()
{
this.model.state.addOnChangedHandler(this.onStateChanged);
this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged);
this.mounted = true;
this.isReady = this.model.state.get() === State.Ready;
this.maybeStartClock();
}
componentWillUnmount()
{
this.mounted = false;
this.maybeStartClock();
this.model.state.removeOnChangedHandler(this.onStateChanged);
this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged);
}
componentDidUpdate() {
}
requestOutstanding: boolean = false;
requestDeferred: boolean = false;
// Size of the SVG element.
xMin: number = 0;
xMax: number = PLOT_WIDTH+4;
yMin: number = 0;
yMax: number = PLOT_HEIGHT;
onSpectrumResponseUpdated(svgPath: string) {
this.setState({path: svgPath});
}
isTicking : boolean = false;
hClock?: NodeJS.Timeout;
hAtomOutput?: ListenHandle;
maybeStartClock() {
let shouldTick = this.isReady && this.mounted;
if (shouldTick !== this.isTicking)
{
this.isTicking = shouldTick;
if (shouldTick)
{
this.hAtomOutput = this.model.listenForAtomOutput(this.props.instanceId,
(instanceId,atomOutput)=> {
this.onSpectrumResponseUpdated(atomOutput.value as string);
if (this.requestDeferred) {
Utility.delay(30) // take breath
.then(
() => {
this.requestOutstanding = false;
this.requestDeferred = false;
this.requestSpectrum();
}
);
} else {
this.requestOutstanding = false;
}
});
this.hClock = setInterval(()=> {
this.requestSpectrum();
},
1000/15);
} else {
if (this.hAtomOutput)
{
this.model.cancelListenForAtomOutput(this.hAtomOutput);
this.hAtomOutput = undefined;
}
if (this.hClock)
{
clearInterval(this.hClock);
this.hClock = undefined;
}
}
}
}
requestSpectrum() {
if (this.requestOutstanding) { // throttling.
this.requestDeferred = true;
return;
}
this.requestOutstanding = true;
this.model.getLv2Parameter<string>(this.props.instanceId, SPECTRUM_RESPONSE_VECTOR_URI)
.then((data) => {
}).catch(error => {
// assume the connection was lost. We'll get saved by a reconnect.
});
}
numPoints = 200;
minF = 60;
maxF = 18000;
logMinF = Math.log(this.minF);
logMaxF = Math.log(this.maxF);
toX(f: number): number {
let width = PLOT_WIDTH-8;
return (Math.log(f)-this.logMinF)*width/(this.logMaxF-this.logMinF);
}
grid(): React.ReactNode[] {
let result: React.ReactNode[] = [];
let width = PLOT_WIDTH-8;
let height = PLOT_HEIGHT-8;
for (let db = -20; db >= -100; db -= 20)
{
let y = height*db/-100;
result.push(
(
<line x1={0} y1={y} x2={width} y2={y} key={"db" + db} />
)
);
}
let minF = this.state.minF;
let maxF = this.state.maxF;
this.minF = minF;
this.maxF = maxF;
this.logMinF = Math.log(this.minF);
this.logMaxF = Math.log(this.maxF);
for (let decade = 10; decade < 100000; decade *= 10)
{
for (let minorTick = 1; minorTick <= 5; ++ minorTick)
{
let f = decade*minorTick;
if (f > minF && f < maxF)
{
let x = this.toX(f);
if (minorTick === 1)
{
result.push(
(
<line x1={x} y1={0} x2={x} y2={height} key={"hgrid"+f} strokeWidth={1}/>
)
);
} else {
result.push(
(
<line x1={x} y1={0} x2={x} y2={height} key={"hgrid"+f} />
)
);
}
}
}
}
return result;
}
render() {
let classes = this.props.classes;
return (
<div className={classes.frame} style={{position: "relative"}} >
<div style={{position: "absolute", left: 4, top: 4}}>
<svg width={PLOT_WIDTH-8} height={PLOT_HEIGHT-8} stroke="#888" strokeWidth="0.5" opacity="1"
>
{this.grid()}
</svg>
</div>
<div style={{position: "absolute", left: 4, top: 4}}>
<svg width={PLOT_WIDTH-8} height={PLOT_HEIGHT-8} preserveAspectRatio='none' viewBox={"0 0 " + this.numPoints + " " + 1000} stroke="none" fill="#0FC" strokeWidth="2.5" opacity="0.6"
>
<path d={this.state.path} />
</svg>
</div>
</div>);
}
}
);
export default ToobSpectrumResponseView;
-10
View File
@@ -40,16 +40,6 @@ const styles = (theme: Theme) => createStyles({
},
});
interface SvgRect
{
x: number;
y: number;
width: number;
height: number;
color: string;
}
interface ToobWaveShapeProps extends WithStyles<typeof styles> {
instanceId: number;
controlNumber: number;
+10 -4
View File
@@ -345,10 +345,14 @@ export const VuMeter =
addVuSubscription() {
this.removeVuSubscription();
if (this.props.instanceId !== -1) {
this.subscribedInstanceId = this.props.instanceId;
this.subscriptionHandle = this.model.addVuSubscription(this.props.instanceId, this.onVuChanged);
this.resetTelltales();
if (this.model.state.get() === State.Ready)
{
if (this.props.instanceId !== -1) {
this.subscribedInstanceId = this.props.instanceId;
this.subscriptionHandle = this.model.addVuSubscription(this.props.instanceId, this.onVuChanged);
this.resetTelltales();
}
}
}
removeVuSubscription() {
@@ -407,6 +411,8 @@ export const VuMeter =
if (state === State.Ready)
{
this.addVuSubscription(); // re-subscribe.
} else {
this.removeVuSubscription();
}
}