// Copyright (c) 2022 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 { Theme } from '@mui/material/styles'; import { WithStyles } from '@mui/styles'; import createStyles from '@mui/styles/createStyles'; import withStyles from '@mui/styles/withStyles'; import { PiPedalModelFactory, PiPedalModel, State } from "./PiPedalModel"; import { StandardItemSize } from './PluginControlView'; import Utility from './Utility'; import SvgPathBuilder from './SvgPathBuilder'; const FREQUENCY_RESPONSE_VECTOR_URI = "http://two-play.com/plugins/toob#frequencyResponseVector"; const PLOT_WIDTH = StandardItemSize.width*2-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 ToobFrequencyResponseProps extends WithStyles { instanceId: number; maxDb?: number; minDb?: number; minFrequency?: number; maxFrequency?: number; } interface ToobFrequencyResponseState { path: string; } const ToobFrequencyResponseView = withStyles(styles, { withTheme: true })( class extends React.Component { model: PiPedalModel; customizationId: number = 1; pathRef: React.RefObject; constructor(props: ToobFrequencyResponseProps) { super(props); this.model = PiPedalModelFactory.getInstance(); this.state = { path: "" }; this.pathRef = React.createRef(); 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; this.updateAllWaveShapes(); // after a reconnect. } } onPedalBoardChanged() { this.updateAllWaveShapes(); } componentDidMount() { this.model.state.addOnChangedHandler(this.onStateChanged); this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged); this.updateAllWaveShapes(); } componentWillUnmount() { this.model.state.removeOnChangedHandler(this.onStateChanged); this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged); } componentDidUpdate() { //this.updateFrequencyResponse(); } requestOutstanding: boolean = false; requestDeferred: boolean = false; dbMinDefault: number = -35; dbMaxDefault: number = 5; dbTickSpacingOpt: number = 10; MIN_DB_AF: number = Math.pow(10,-192/20); fMin: number = 30; fMax: number = 20000; logMin: number = Math.log(this.fMin); logMax: number = Math.log(this.fMax); // Size of the SVG element. xMin: number = 0; xMax: number = PLOT_WIDTH+4; yMin: number = 0; yMax: number = PLOT_HEIGHT; dbTickSpacing: number = this.dbTickSpacingOpt; toX(frequency: number): number { var logV = Math.log(frequency); return (this.xMax-this.xMin) * (logV-this.logMin)/(this.logMax-this.logMin) + this.xMin; } toY(value: number): number { value = Math.abs(value); var db; if (value < this.MIN_DB_AF) { db = -192.0; } else { db = 20*Math.log10(value); } var y = (db-this.dbMin)/(this.dbMax-this.dbMin)*(this.yMax-this.yMin) + this.yMin; return y; } onFrequencyResponseUpdated(data: number[]) { let pathBuilder = new SvgPathBuilder(); if (data.length > 2) { pathBuilder.moveTo(this.toX(data[0]),this.toY(data[1])) for (let i = 2; i < data.length; i += 2) { pathBuilder.lineTo(this.toX(data[i]),this.toY(data[i+1])); } } this.currentPath = pathBuilder.toString(); this.setState({path: this.currentPath}); } updateAllWaveShapes() { if (this.requestOutstanding) { // throttling. this.requestDeferred = true; return; } this.requestOutstanding = true; this.model.getLv2Parameter(this.props.instanceId, FREQUENCY_RESPONSE_VECTOR_URI) .then((data) => { this.onFrequencyResponseUpdated(data); if (this.requestDeferred) { Utility.delay(10) // take breath .then( () => { this.requestOutstanding = false; this.requestDeferred = false; this.updateAllWaveShapes(); } ); } 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 = ""; majorGridLine(x0: number, y0: number, x1: number, y1: number): React.ReactNode { return (); } gridLine(x0: number, y0: number, x1: number, y1: number): React.ReactNode { return (); } grid(): React.ReactNode[] { let result: React.ReactNode[] = []; for (var db = Math.ceil(this.dbMax/this.dbTickSpacing)*this.dbTickSpacing; db < this.dbMin; db += this.dbTickSpacing ) { var y = (db-this.dbMin)/(this.dbMax-this.dbMin)*(this.yMax-this.yMin); if (db === 0) { result.push( this.majorGridLine(this.xMin,y,this.xMax,y) ); } else { result.push( this.gridLine(this.xMin,y,this.xMax,y) ); } } let decade0 = Math.pow(10,Math.floor(Math.log10(this.fMin))); for (var decade = decade0; decade < this.fMax; decade *= 10) { for (var i = 1; i <= 10; ++i) { var f = decade*i; if (f > this.fMin && f < this.fMax) { var x = this.toX(f); if (i === 10) { result.push(this.majorGridLine(x,this.yMin,x,this.yMax)); } else { result.push(this.gridLine(x,this.yMin,x,this.yMax)); } } } } return result; } dbMin: number = 5; dbMax: number = -35; render() { // deliberately reversed to flip up and down. this.dbMax = this.props.minDb ?? this.dbMinDefault; this.dbMin = this.props.maxDb ?? this.dbMaxDefault; this.fMin = this.props.minFrequency ?? 30; this.fMax = this.props.maxFrequency ?? 20000; this.logMin = Math.log(this.fMin); this.logMax = Math.log(this.fMax); let classes = this.props.classes; return (
{ this.grid() }
); } } ); export default ToobFrequencyResponseView;