Precached images. ToobTuner. key additions.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"socket_server_port": 8080,
|
||||
"socket_server_port": 80,
|
||||
"socket_server_address": "*",
|
||||
|
||||
"max_upload_size": 1048576,
|
||||
|
||||
@@ -31,6 +31,8 @@ import ToobToneStackViewFactory from './ToobToneStackView';
|
||||
import ToobCabSimViewFactory from './ToobCabSimView';
|
||||
import ToobPowerStage2Factory from './ToobPowerStage2View';
|
||||
import ToobSpectrumAnalyzerViewFactory from './ToobSpectrumAnalyzerView';
|
||||
import ToobMLViewFactory from './ToobMLView';
|
||||
import {ToobTunerViewFactory,GxTunerViewFactory> from './GxTunerView';
|
||||
|
||||
|
||||
let pluginFactories: IControlViewFactory[] = [
|
||||
@@ -38,7 +40,10 @@ let pluginFactories: IControlViewFactory[] = [
|
||||
new ToobToneStackViewFactory(),
|
||||
new ToobCabSimViewFactory(),
|
||||
new ToobPowerStage2Factory(),
|
||||
new ToobSpectrumAnalyzerViewFactory()
|
||||
new ToobSpectrumAnalyzerViewFactory(),
|
||||
new ToobMLViewFactory(),
|
||||
new GxTunerViewFactory(),
|
||||
new ToobTunerViewFactory()
|
||||
];
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
// 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 { Theme } from '@mui/material/styles';
|
||||
|
||||
import { WithStyles } from '@mui/styles';
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel";
|
||||
import { PedalBoardItem } from './PedalBoard';
|
||||
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
|
||||
import GxTunerControl from './GxTunerControl';
|
||||
|
||||
const GXTUNER_URI = "http://guitarix.sourceforge.net/plugins/gxtuner#tuner";
|
||||
const TOOBTUNER_URI = "http://two-play.com/plugins/toob-tuner";
|
||||
|
||||
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
});
|
||||
|
||||
interface GxTunerProps extends WithStyles<typeof styles> {
|
||||
instanceId: number;
|
||||
item: PedalBoardItem;
|
||||
|
||||
}
|
||||
interface GxTunerState {
|
||||
|
||||
}
|
||||
|
||||
const GxTunerView =
|
||||
withStyles(styles, { withTheme: true })(
|
||||
class extends React.Component<GxTunerProps, GxTunerState>
|
||||
implements ControlViewCustomization
|
||||
{
|
||||
model: PiPedalModel;
|
||||
|
||||
customizationId: number = 1;
|
||||
|
||||
constructor(props: GxTunerProps) {
|
||||
super(props);
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
this.state = {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getControlIndex(key: string): number {
|
||||
let item = this.props.item;
|
||||
for (let i = 0; i < item.controlValues.length; ++i)
|
||||
{
|
||||
if (item.controlValues[i].key === key)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new Error("GxTuner: Control '" + key + "' not found.");
|
||||
}
|
||||
ModifyControls(controls: (React.ReactNode| ControlGroup)[]): (React.ReactNode| ControlGroup)[]
|
||||
{
|
||||
let refFreqIndex = this.getControlIndex("REFFREQ");
|
||||
let thresholdIndex = this.getControlIndex("THRESHOLD");
|
||||
|
||||
let result: (React.ReactNode | ControlGroup)[] = [];
|
||||
|
||||
let tunerControl = (<GxTunerControl instanceId={this.props.instanceId} />);
|
||||
result.push(tunerControl);
|
||||
|
||||
result.push(controls[refFreqIndex]);
|
||||
result.push(controls[thresholdIndex]);
|
||||
return result;
|
||||
}
|
||||
render() {
|
||||
return (<PluginControlView
|
||||
instanceId={this.props.instanceId}
|
||||
item={this.props.item}
|
||||
customization={this}
|
||||
customizationId={this.customizationId}
|
||||
/>);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
class GxTunerViewFactory implements IControlViewFactory {
|
||||
uri: string = GXTUNER_URI;
|
||||
|
||||
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode {
|
||||
return (<GxTunerView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// ToobTuner uses the same controls and control semantics.
|
||||
class ToobTunerViewFactory implements IControlViewFactory {
|
||||
uri: string = TOOBTUNER_URI;
|
||||
|
||||
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode {
|
||||
return (<GxTunerView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export ToobTunerViewFactory;
|
||||
export GxTunerViewFactory;
|
||||
@@ -332,7 +332,7 @@ const JackServerSettingsDialog = withStyles(styles)(
|
||||
>
|
||||
{bufferSizes.map((buffSize) =>
|
||||
(
|
||||
<MenuItem value={buffSize}>{buffSize}</MenuItem>
|
||||
<MenuItem key={"b"+buffSize} value={buffSize}>{buffSize}</MenuItem>
|
||||
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -55,6 +55,9 @@ const NARROW_DISPLAY_THRESHOLD = 600;
|
||||
const FILTER_STORAGE_KEY = "com.twoplay.pipedal.load_dlg.filter";
|
||||
|
||||
|
||||
let lastClickTime: number = 0;
|
||||
|
||||
|
||||
const pluginGridStyles = (theme: Theme) => createStyles({
|
||||
frame: {
|
||||
|
||||
@@ -338,8 +341,26 @@ export const LoadPluginDialog =
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onClick(e: SyntheticEvent, uri: string): void {
|
||||
e.preventDefault();
|
||||
|
||||
let currentTime = Date.now();
|
||||
let dt = currentTime-lastClickTime;
|
||||
let DOUBLE_CLICK_TIME = 500;
|
||||
let isDoubleClick = dt < DOUBLE_CLICK_TIME;
|
||||
lastClickTime = currentTime;
|
||||
|
||||
|
||||
this.setState({ selected_uri: uri });
|
||||
|
||||
// we have to synthesize double clicks because
|
||||
// DOM rewrites interfere with natural double click.
|
||||
if (isDoubleClick)
|
||||
{
|
||||
this.props.onOk(uri);
|
||||
}
|
||||
|
||||
}
|
||||
handleMouseEnter(e: SyntheticEvent, uri: string): void {
|
||||
// this.setHoverUri(uri);
|
||||
@@ -373,7 +394,7 @@ export const LoadPluginDialog =
|
||||
for (let i = 0; i < classNode.children.length; ++i) {
|
||||
let child = classNode.children[i];
|
||||
let name = "\u00A0".repeat(level * 3 + 1) + child.display_name;
|
||||
result.push((<MenuItem value={child.plugin_type}>{name}</MenuItem>));
|
||||
result.push((<MenuItem key={child.plugin_type} value={child.plugin_type}>{name}</MenuItem>));
|
||||
if (child.children.length !== 0) {
|
||||
this.createFilterChildren(result, child, level + 1);
|
||||
}
|
||||
@@ -383,7 +404,7 @@ export const LoadPluginDialog =
|
||||
let classes = this.model.plugin_classes.get();
|
||||
let result: ReactNode[] = [];
|
||||
|
||||
result.push((<MenuItem value={PluginType.Plugin}> All</MenuItem>));
|
||||
result.push((<MenuItem key={PluginType.Plugin} value={PluginType.Plugin}> All</MenuItem>));
|
||||
this.createFilterChildren(result, classes, 1);
|
||||
return result;
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ export class Port implements Deserializable<Port> {
|
||||
this.is_atom_port = input.is_atom_port;
|
||||
this.is_valid = input.is_valid;
|
||||
this.supports_midi = input.supports_midi;
|
||||
this.supports_time_position = input.supports_time_position;
|
||||
this.port_group = input.port_group;
|
||||
return this;
|
||||
}
|
||||
@@ -70,6 +71,7 @@ export class Port implements Deserializable<Port> {
|
||||
is_atom_port: boolean = false;
|
||||
is_valid: boolean = false;
|
||||
supports_midi: boolean = false;
|
||||
supports_time_position: boolean = false;
|
||||
port_group: string = "";
|
||||
}
|
||||
|
||||
|
||||
+48
-50
@@ -311,7 +311,7 @@ export interface PiPedalModel {
|
||||
presets: ObservableProperty<PresetIndex>;
|
||||
|
||||
|
||||
zoomedUiControl: ObservableProperty< ZoomedControlInfo| undefined >;
|
||||
zoomedUiControl: ObservableProperty<ZoomedControlInfo | undefined>;
|
||||
|
||||
setJackServerSettings(jackServerSettings: JackServerSettings): void;
|
||||
setMidiBinding(instanceId: number, midiBinding: MidiBinding): void;
|
||||
@@ -382,7 +382,7 @@ export interface PiPedalModel {
|
||||
cancelListenForMidiEvent(listenHandle: ListenHandle): void;
|
||||
|
||||
addControlValueChangeListener(instanceId: number, onValueChanged: ControlValueChangedHandler): ControlValueChangedHandle
|
||||
removeControlValueChangeListener(handle: ControlValueChangedHandle): void;
|
||||
removeControlValueChangeListener(handle: ControlValueChangedHandle): void;
|
||||
|
||||
listenForAtomOutput(instanceId: number, onComplete: (instanceId: number, atomOutput: any) => void): ListenHandle;
|
||||
cancelListenForAtomOutput(listenHandle: ListenHandle): void;
|
||||
@@ -398,9 +398,9 @@ export interface PiPedalModel {
|
||||
|
||||
getWifiChannels(countryIso3661: string): Promise<WifiChannel[]>;
|
||||
|
||||
getAlsaDevices() : Promise<AlsaDeviceInfo[]>;
|
||||
getAlsaDevices(): Promise<AlsaDeviceInfo[]>;
|
||||
|
||||
zoomUiControl(sourceElement: HTMLElement,instanceId: number,uiControl: UiControl): void;
|
||||
zoomUiControl(sourceElement: HTMLElement, instanceId: number, uiControl: UiControl): void;
|
||||
clearZoomedControl(): void;
|
||||
};
|
||||
|
||||
@@ -438,7 +438,7 @@ class PiPedalModelImpl implements PiPedalModel {
|
||||
new PresetIndex()
|
||||
);
|
||||
|
||||
zoomedUiControl: ObservableProperty<ZoomedControlInfo | undefined> = new ObservableProperty<ZoomedControlInfo | undefined>(undefined);
|
||||
zoomedUiControl: ObservableProperty<ZoomedControlInfo | undefined> = new ObservableProperty<ZoomedControlInfo | undefined>(undefined);
|
||||
|
||||
svgImgUrl(svgImage: string): string {
|
||||
//return this.varServerUrl + "img/" + svgImage;
|
||||
@@ -466,7 +466,7 @@ class PiPedalModelImpl implements PiPedalModel {
|
||||
onSocketReconnecting(retry: number, maxRetries: number): void {
|
||||
if (this.visibilityState.get() === VisibilityState.Hidden) return;
|
||||
//if (retry !== 0) {
|
||||
this.setState(State.Reconnecting);
|
||||
this.setState(State.Reconnecting);
|
||||
//}
|
||||
}
|
||||
|
||||
@@ -536,11 +536,11 @@ class PiPedalModelImpl implements PiPedalModel {
|
||||
this.handleNotifyMidiListener(clientHandle, isNote, noteOrControl);
|
||||
} else if (message === "onNotifyAtomOut") {
|
||||
let clientHandle = body.clientHandle as number;
|
||||
let instanceId = body.instanceId as number;
|
||||
let instanceId = body.instanceId as number;
|
||||
let atomJson = body.atomJson as string;
|
||||
this.handleNotifyAtomOutput(clientHandle, instanceId,atomJson);
|
||||
this.handleNotifyAtomOutput(clientHandle, instanceId, atomJson);
|
||||
if (header.replyTo) {
|
||||
this.webSocket?.reply(header.replyTo,"onNotifyAtomOut",true);
|
||||
this.webSocket?.reply(header.replyTo, "onNotifyAtomOut", true);
|
||||
}
|
||||
} else if (message === "onControlChanged") {
|
||||
let controlChangedBody = body as ControlChangedBody;
|
||||
@@ -733,6 +733,10 @@ class PiPedalModelImpl implements PiPedalModel {
|
||||
})
|
||||
.then((clientId) => {
|
||||
this.clientId = clientId;
|
||||
return this.getWebSocket().request<string>("imageList");
|
||||
})
|
||||
.then((data) => {
|
||||
this.preloadImages(data);
|
||||
return true;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -842,20 +846,16 @@ class PiPedalModelImpl implements PiPedalModel {
|
||||
|
||||
}
|
||||
|
||||
exitBackgroundState()
|
||||
{
|
||||
if (this.state.get() === State.Background)
|
||||
{
|
||||
exitBackgroundState() {
|
||||
if (this.state.get() === State.Background) {
|
||||
console.log("Exiting background state.");
|
||||
this.visibilityState.set(VisibilityState.Visible);
|
||||
this.webSocket?.exitBackgroundState();
|
||||
}
|
||||
|
||||
}
|
||||
enterBackgroundState()
|
||||
{
|
||||
if (this.state.get() !== State.Background)
|
||||
{
|
||||
enterBackgroundState() {
|
||||
if (this.state.get() !== State.Background) {
|
||||
console.log("Entering background state.");
|
||||
this.visibilityState.set(VisibilityState.Hidden);
|
||||
this.setState(State.Background);
|
||||
@@ -864,12 +864,10 @@ class PiPedalModelImpl implements PiPedalModel {
|
||||
}
|
||||
|
||||
|
||||
onVisibilityChanged(doc: Document, event: Event) : any
|
||||
{
|
||||
onVisibilityChanged(doc: Document, event: Event): any {
|
||||
if (document.visibilityState) {
|
||||
|
||||
switch (document.visibilityState)
|
||||
{
|
||||
|
||||
switch (document.visibilityState) {
|
||||
case "visible":
|
||||
this.visibilityState.set(VisibilityState.Visible);
|
||||
this.exitBackgroundState();
|
||||
@@ -898,8 +896,8 @@ class PiPedalModelImpl implements PiPedalModel {
|
||||
});
|
||||
let t = this.onVisibilityChanged;
|
||||
|
||||
(document as any).addEventListener("visibilitychange",(doc: Document,event: Event) => {
|
||||
return t(doc,event);
|
||||
(document as any).addEventListener("visibilitychange", (doc: Document, event: Event) => {
|
||||
return t(doc, event);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -958,22 +956,18 @@ class PiPedalModelImpl implements PiPedalModel {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
_controlValueChangeItems: ControlValueChangeItem[] = [];
|
||||
|
||||
addControlValueChangeListener(instanceId: number, onValueChanged: ControlValueChangedHandler): ControlValueChangedHandle
|
||||
{
|
||||
addControlValueChangeListener(instanceId: number, onValueChanged: ControlValueChangedHandler): ControlValueChangedHandle {
|
||||
let handle = ++this.nextListenHandle;
|
||||
this._controlValueChangeItems.push({ handle: handle, instanceId: instanceId,onValueChanged: onValueChanged});
|
||||
return { _ControlValueChangedHandle: handle};
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -992,12 +986,10 @@ class PiPedalModelImpl implements PiPedalModel {
|
||||
if (notifyServer) {
|
||||
this._setServerControl("setControl", instanceId, key, value);
|
||||
}
|
||||
for (let i = 0; i < this._controlValueChangeItems.length; ++i)
|
||||
{
|
||||
for (let i = 0; i < this._controlValueChangeItems.length; ++i) {
|
||||
let item = this._controlValueChangeItems[i];
|
||||
if (instanceId === item.instanceId)
|
||||
{
|
||||
item.onValueChanged(key,value);
|
||||
if (instanceId === item.instanceId) {
|
||||
item.onValueChanged(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1557,7 +1549,7 @@ 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.atomOutputListeners.push(new AtomOutputListener(handle, instanceId, onComplete));
|
||||
|
||||
this.webSocket?.send("listenForAtomOutput", { instanceId: instanceId, handle: handle });
|
||||
return {
|
||||
@@ -1570,7 +1562,7 @@ class PiPedalModelImpl implements PiPedalModel {
|
||||
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);
|
||||
listener.callback(instanceId, jsonObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1760,8 +1752,7 @@ class PiPedalModelImpl implements PiPedalModel {
|
||||
return result;
|
||||
}
|
||||
|
||||
getAlsaDevices() : Promise<AlsaDeviceInfo[]>
|
||||
{
|
||||
getAlsaDevices(): Promise<AlsaDeviceInfo[]> {
|
||||
let result = new Promise<AlsaDeviceInfo[]>((resolve, reject) => {
|
||||
if (!this.webSocket) {
|
||||
reject("Connection closed.");
|
||||
@@ -1776,15 +1767,22 @@ class PiPedalModelImpl implements PiPedalModel {
|
||||
return result;
|
||||
|
||||
}
|
||||
zoomUiControl(sourceElement: HTMLElement,instanceId: number,uiControl: UiControl): void
|
||||
{
|
||||
this.zoomedUiControl.set({source: sourceElement,instanceId: instanceId,uiControl: uiControl});
|
||||
zoomUiControl(sourceElement: HTMLElement, instanceId: number, uiControl: UiControl): void {
|
||||
this.zoomedUiControl.set({ source: sourceElement, instanceId: instanceId, uiControl: uiControl });
|
||||
}
|
||||
clearZoomedControl(): void
|
||||
{
|
||||
clearZoomedControl(): void {
|
||||
this.zoomedUiControl.set(undefined);
|
||||
}
|
||||
|
||||
preloadImages(imageList: string): void {
|
||||
let imageNames = imageList.split(';');
|
||||
for (let i = 0; i < imageNames.length; ++i)
|
||||
{
|
||||
let imageName = imageNames[i];
|
||||
let img = new Image();
|
||||
img.src = "/img/" + imageName;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ import PluginControl from './PluginControl';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import VuMeter from './VuMeter';
|
||||
import { nullCast } from './Utility'
|
||||
import GxTunerControl from './GxTunerControl';
|
||||
import { PiPedalStateError } from './PiPedalError';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import FullScreenIME from './FullScreenIME';
|
||||
@@ -40,7 +39,6 @@ import FullScreenIME from './FullScreenIME';
|
||||
export const StandardItemSize = { width: 80, height: 110 };
|
||||
|
||||
|
||||
const GXTUNER_URI = "http://guitarix.sourceforge.net/plugins/gxtuner#tuner";
|
||||
|
||||
const LANDSCAPE_HEIGHT_BREAK = 500;
|
||||
|
||||
@@ -127,7 +125,7 @@ const styles = (theme: Theme) => createStyles({
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
border: "2pt #AAA solid",
|
||||
borderRadius: 4,
|
||||
borderRadius: 8,
|
||||
elevation: 12,
|
||||
display: "flex",
|
||||
flexDirection: "row", flexWrap: "wrap",
|
||||
@@ -144,7 +142,7 @@ const styles = (theme: Theme) => createStyles({
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
border: "2pt #AAA solid",
|
||||
borderRadius: 10,
|
||||
borderRadius: 8,
|
||||
elevation: 12,
|
||||
display: "flex",
|
||||
flexDirection: "row", flexWrap: "nowrap",
|
||||
@@ -152,7 +150,7 @@ const styles = (theme: Theme) => createStyles({
|
||||
},
|
||||
portGroupTitle: {
|
||||
position: "absolute",
|
||||
top: -10,
|
||||
top: -15,
|
||||
background: "white",
|
||||
marginLeft: 20,
|
||||
paddingLeft: 8,
|
||||
@@ -344,35 +342,6 @@ const PluginControlView =
|
||||
throw new Error("Not found.");
|
||||
}
|
||||
|
||||
getTunerControls(plugin: UiPlugin, controlValues: ControlValue[]): ControlNodes {
|
||||
// display standard version of just a few controls.
|
||||
let shortList: ControlValue[] = [];
|
||||
// for (let i = 0; i < controlValues.length; ++i)
|
||||
// {
|
||||
// shortList.push(controlValues[i]);
|
||||
// }
|
||||
shortList.push(this.getControl(controlValues, "REFFREQ"));
|
||||
shortList.push(this.getControl(controlValues, "THRESHOLD"));
|
||||
|
||||
|
||||
let controls = shortList.map((controlValue) => (
|
||||
<PluginControl instanceId={this.props.instanceId} uiControl={plugin.getControl(controlValue.key)} value={controlValue.value}
|
||||
onChange={(value: number) => { this.onValueChanged(controlValue.key, value) }}
|
||||
onPreviewChange={(value: number) => { this.onPreviewChange(controlValue.key, value) }}
|
||||
requestIMEEdit={(uiControl, value) => this.requestImeEdit(uiControl, value)}
|
||||
/>
|
||||
));
|
||||
let tunerControl = (<GxTunerControl instanceId={this.props.instanceId} />);
|
||||
let result: ControlNodes = [];
|
||||
result.push(tunerControl);
|
||||
|
||||
for (let i = 0; i < controls.length; ++i)
|
||||
{
|
||||
result.push(controls[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
onImeValueChange(key: string, value: number) {
|
||||
this.model.setPedalBoardControlValue(this.props.instanceId, key, value);
|
||||
this.onImeClose();
|
||||
@@ -464,11 +433,8 @@ const PluginControlView =
|
||||
let vuMeterRClass = this.state.landscapeGrid ? classes.vuMeterRLandscape : classes.vuMeterR;
|
||||
let controlNodes: ControlNodes;
|
||||
|
||||
if (plugin.uri === GXTUNER_URI) {
|
||||
controlNodes = this.getTunerControls(plugin, controlValues);
|
||||
} else {
|
||||
controlNodes = this.getStandardControlNodes(plugin, controlValues);
|
||||
}
|
||||
controlNodes = this.getStandardControlNodes(plugin, controlValues);
|
||||
|
||||
if (this.props.customization)
|
||||
{
|
||||
// allow wrapper class to insert/remove/rebuild controls.
|
||||
|
||||
@@ -138,7 +138,7 @@ function makeControls(controls: UiControl[]) {
|
||||
<Grid container direction="row" justifyContent="flex-start" alignItems="flex-start" spacing={1} style={{ paddingLeft: "24px" }}>
|
||||
{
|
||||
controls.map((control) => (
|
||||
<Grid item spacing={2} xs={6} sm={4} key={control.symbol} >
|
||||
<Grid xs={6} sm={4} key={control.symbol} >
|
||||
<Typography variant="body2">
|
||||
{control.name}
|
||||
</Typography>
|
||||
|
||||
@@ -279,7 +279,8 @@ const PluginPresetSelector =
|
||||
<Typography variant="caption" color="textSecondary" style={{marginLeft: 16,marginTop:4,marginBottom: 4}}>Plugin presets</Typography>
|
||||
{
|
||||
this.state.presets.map((preset) => {
|
||||
return (<MenuItem onClick={(e) => this.handleLoadPluginPreset(preset.presetUri)}>{preset.name}</MenuItem>);
|
||||
return (<MenuItem key={preset.presetUri}
|
||||
onClick={(e) => this.handleLoadPluginPreset(preset.presetUri)}>{preset.name}</MenuItem>);
|
||||
})
|
||||
}
|
||||
{/*
|
||||
|
||||
@@ -46,6 +46,10 @@ const styles = (theme: Theme) => createStyles({
|
||||
|
||||
interface ToobFrequencyResponseProps extends WithStyles<typeof styles> {
|
||||
instanceId: number;
|
||||
maxDb?: number;
|
||||
minDb?: number;
|
||||
minFrequency?: number;
|
||||
maxFrequency?: number;
|
||||
|
||||
}
|
||||
interface ToobFrequencyResponseState {
|
||||
@@ -104,8 +108,8 @@ const ToobFrequencyResponseView =
|
||||
requestOutstanding: boolean = false;
|
||||
requestDeferred: boolean = false;
|
||||
|
||||
dbMinOpt: number = -35;
|
||||
dbMaxOpt: number = 5;
|
||||
dbMinDefault: number = -35;
|
||||
dbMaxDefault: number = 5;
|
||||
dbTickSpacingOpt: number = 10;
|
||||
|
||||
|
||||
@@ -120,8 +124,6 @@ const ToobFrequencyResponseView =
|
||||
xMax: number = PLOT_WIDTH+4;
|
||||
yMin: number = 0;
|
||||
yMax: number = PLOT_HEIGHT;
|
||||
dbMin: number = this.dbMaxOpt; // deliberately reversed to flip up and down.
|
||||
dbMax: number = this.dbMinOpt;
|
||||
dbTickSpacing: number = this.dbTickSpacingOpt;
|
||||
|
||||
toX(frequency: number): number
|
||||
@@ -189,6 +191,10 @@ const ToobFrequencyResponseView =
|
||||
|
||||
currentPath: string = "";
|
||||
|
||||
majorGridLine(x0: number, y0: number, x1: number, y1: number): React.ReactNode {
|
||||
return (<line x1={x0} y1={y0} x2={x1} y2={y1} fill='none' stroke="#FFF" strokeWidth="0.75" opacity="1" />);
|
||||
}
|
||||
|
||||
gridLine(x0: number, y0: number, x1: number, y1: number): React.ReactNode {
|
||||
return (<line x1={x0} y1={y0} x2={x1} y2={y1} fill='none' stroke="#FFF" strokeWidth="0.25" opacity="1" />);
|
||||
}
|
||||
@@ -199,27 +205,54 @@ const ToobFrequencyResponseView =
|
||||
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);
|
||||
result.push(
|
||||
this.gridLine(this.xMin,y,this.xMax,y)
|
||||
);
|
||||
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 = 0; i < 10; ++i)
|
||||
for (var i = 1; i <= 10; ++i)
|
||||
{
|
||||
var f = decade*i;
|
||||
if (f > this.fMin && f < this.fMax)
|
||||
{
|
||||
var x = this.toX(f);
|
||||
result.push(this.gridLine(x,this.yMin,x,this.yMax));
|
||||
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 (
|
||||
<div className={classes.frame} >
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
// 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 { Theme } from '@mui/material/styles';
|
||||
|
||||
import { WithStyles } from '@mui/styles';
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel";
|
||||
import { PedalBoardItem } from './PedalBoard';
|
||||
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
|
||||
import ToobFrequencyResponseView from './ToobFrequencyResponseView';
|
||||
|
||||
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
});
|
||||
|
||||
interface ToobMLProps extends WithStyles<typeof styles> {
|
||||
instanceId: number;
|
||||
item: PedalBoardItem;
|
||||
|
||||
}
|
||||
interface ToobMLState {
|
||||
|
||||
}
|
||||
|
||||
const ToobMLView =
|
||||
withStyles(styles, { withTheme: true })(
|
||||
class extends React.Component<ToobMLProps, ToobMLState>
|
||||
implements ControlViewCustomization
|
||||
{
|
||||
model: PiPedalModel;
|
||||
|
||||
customizationId: number = 1;
|
||||
|
||||
constructor(props: ToobMLProps) {
|
||||
super(props);
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
this.state = {
|
||||
}
|
||||
}
|
||||
|
||||
ModifyControls(controls: (React.ReactNode| ControlGroup)[]): (React.ReactNode| ControlGroup)[]
|
||||
{
|
||||
let group = controls[1] as ControlGroup;
|
||||
group.controls.splice(0,0,
|
||||
( <ToobFrequencyResponseView instanceId={this.props.instanceId} minDb={-20} maxDb={20} />)
|
||||
);
|
||||
return controls;
|
||||
}
|
||||
render() {
|
||||
return (<PluginControlView
|
||||
instanceId={this.props.instanceId}
|
||||
item={this.props.item}
|
||||
customization={this}
|
||||
customizationId={this.customizationId}
|
||||
/>);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
class ToobMLViewFactory implements IControlViewFactory {
|
||||
uri: string = "http://two-play.com/plugins/toob-ml";
|
||||
|
||||
Create(model: PiPedalModel, pedalBoardItem: PedalBoardItem): React.ReactNode {
|
||||
return (<ToobMLView instanceId={pedalBoardItem.instanceId} item={pedalBoardItem} />);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
export default ToobMLViewFactory;
|
||||
@@ -25,7 +25,7 @@ import createStyles from '@mui/styles/createStyles';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
|
||||
import IControlViewFactory from './IControlViewFactory';
|
||||
import { PiPedalModelFactory, PiPedalModel } from "./PiPedalModel";
|
||||
import { PiPedalModelFactory, PiPedalModel,ControlValueChangedHandle } from "./PiPedalModel";
|
||||
import { PedalBoardItem } from './PedalBoard';
|
||||
import PluginControlView, { ControlGroup,ControlViewCustomization } from './PluginControlView';
|
||||
import ToobFrequencyResponseView from './ToobFrequencyResponseView';
|
||||
@@ -41,7 +41,7 @@ interface ToobToneStackProps extends WithStyles<typeof styles> {
|
||||
|
||||
}
|
||||
interface ToobToneStackState {
|
||||
|
||||
isBaxandall: boolean;
|
||||
}
|
||||
|
||||
const ToobToneStackView =
|
||||
@@ -57,14 +57,48 @@ const ToobToneStackView =
|
||||
super(props);
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
this.state = {
|
||||
isBaxandall: this.IsBaxandall()
|
||||
}
|
||||
}
|
||||
IsBaxandall() : boolean {
|
||||
return this.props.item.getControl("ampmodel").value === 2.0;
|
||||
}
|
||||
|
||||
|
||||
controlValueChangedHandle?: ControlValueChangedHandle;
|
||||
componentDidMount()
|
||||
{
|
||||
this.controlValueChangedHandle = this.model.addControlValueChangeListener(
|
||||
this.props.instanceId,
|
||||
(key,value) => {
|
||||
if (key === "ampmodel")
|
||||
{
|
||||
this.setState({isBaxandall: value === 2.0});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
if (this.controlValueChangedHandle)
|
||||
{
|
||||
this.model.removeControlValueChangeListener(this.controlValueChangedHandle);
|
||||
this.controlValueChangedHandle = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
ModifyControls(controls: (React.ReactNode| ControlGroup)[]): (React.ReactNode| ControlGroup)[]
|
||||
{
|
||||
controls.splice(0,0,
|
||||
( <ToobFrequencyResponseView instanceId={this.props.instanceId} />)
|
||||
);
|
||||
if (this.state.isBaxandall)
|
||||
{
|
||||
controls.splice(0,0,
|
||||
( <ToobFrequencyResponseView instanceId={this.props.instanceId}
|
||||
minDb={-20} maxDb={20} />)
|
||||
);
|
||||
} else {
|
||||
controls.splice(0,0,
|
||||
( <ToobFrequencyResponseView instanceId={this.props.instanceId} />)
|
||||
);
|
||||
}
|
||||
return controls;
|
||||
}
|
||||
render() {
|
||||
|
||||
+7
-3
@@ -754,7 +754,7 @@ public:
|
||||
#else
|
||||
xxx; // TODO!
|
||||
#endif
|
||||
|
||||
int underrunMessagesGiven = 0;
|
||||
try
|
||||
{
|
||||
|
||||
@@ -787,8 +787,12 @@ public:
|
||||
uint64_t underruns = this->underruns;
|
||||
if (underruns != lastUnderrunCount)
|
||||
{
|
||||
Lv2Log::info("Jack - Underrun count: %lu", (unsigned long)underruns);
|
||||
lastUnderrunCount = underruns;
|
||||
if (underrunMessagesGiven < 60) // limit how much log file clutter we generate.
|
||||
{
|
||||
Lv2Log::info("Jack - Underrun count: %lu", (unsigned long)underruns);
|
||||
lastUnderrunCount = underruns;
|
||||
++underrunMessagesGiven;
|
||||
}
|
||||
}
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_sec += pollRateS;
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "lv2/urid.lv2/urid.h"
|
||||
#include "lv2.h"
|
||||
#include "lv2/atom.lv2/atom.h"
|
||||
#include "lv2/time/time.h"
|
||||
#include "lv2/lv2plug.in/ns/ext/buf-size/buf-size.h"
|
||||
#include "lv2/lv2plug.in/ns/ext/presets/presets.h"
|
||||
#include "lv2/lv2plug.in/ns/ext/port-props/port-props.h"
|
||||
@@ -40,8 +41,10 @@
|
||||
#include "lv2/lv2plug.in/ns/ext/port-groups/port-groups.h"
|
||||
#include <fstream>
|
||||
#include "PiPedalException.hpp"
|
||||
|
||||
#include "Locale.hpp"
|
||||
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
#define MAP_CHECK() \
|
||||
@@ -115,6 +118,12 @@ void Lv2Host::LilvUris::Initialize(LilvWorld*pWorld)
|
||||
symbolUri = lilv_new_uri(pWorld, LV2_CORE__symbol);
|
||||
nameUri = lilv_new_uri(pWorld, LV2_CORE__name);
|
||||
|
||||
time_Position = lilv_new_uri(pWorld, LV2_TIME__Position);
|
||||
time_barBeat = lilv_new_uri(pWorld, LV2_TIME__barBeat);
|
||||
time_beatsPerMinute = lilv_new_uri(pWorld, LV2_TIME__beatsPerMinute);
|
||||
time_speed = lilv_new_uri(pWorld, LV2_TIME__speed);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void Lv2Host::LilvUris::Free()
|
||||
@@ -137,6 +146,11 @@ void Lv2Host::LilvUris::Free()
|
||||
symbolUri.Free();
|
||||
nameUri.Free();
|
||||
|
||||
time_Position.Free();
|
||||
time_barBeat.Free();
|
||||
time_beatsPerMinute.Free();
|
||||
time_speed.Free();
|
||||
|
||||
}
|
||||
|
||||
static std::string nodeAsString(const LilvNode *node)
|
||||
@@ -398,10 +412,12 @@ void Lv2Host::Load(const char *lv2Path)
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. (Has CV ports).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
|
||||
}
|
||||
#if !SUPPORT_MIDI
|
||||
else if (pluginInfo->plugin_class() == LV2_MIDI_PLUGIN)
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. (MIDI Plugin).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
|
||||
}
|
||||
#endif
|
||||
else if (!pluginInfo->is_valid())
|
||||
{
|
||||
auto &ports = pluginInfo->ports();
|
||||
@@ -439,6 +455,16 @@ void Lv2Host::Load(const char *lv2Path)
|
||||
Lv2PluginUiInfo info(this, plugin.get());
|
||||
if (plugin->is_valid())
|
||||
{
|
||||
#if SUPPORT_MIDI
|
||||
if (info.audio_inputs() == 0 && !info.has_midi_input())
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. No inputs.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
}
|
||||
else if (info.audio_outputs() == 0 && !info.has_midi_output())
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
}
|
||||
#else
|
||||
if (info.audio_inputs() == 0)
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. No audio inputs.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
@@ -447,6 +473,7 @@ void Lv2Host::Load(const char *lv2Path)
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. No audio outputs.", plugin->name().c_str(), plugin->uri().c_str());
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
ui_plugins_.push_back(std::move(info));
|
||||
@@ -718,6 +745,7 @@ Lv2PortInfo::Lv2PortInfo(Lv2Host *host, const LilvPlugin *plugin, const LilvPort
|
||||
is_cv_port_ = is_a(host, LV2_CORE__CVPort);
|
||||
|
||||
supports_midi_ = lilv_port_supports_event(plugin, pPort, host->lilvUris.midiEventNode);
|
||||
supports_time_position_ = lilv_port_supports_event(plugin, pPort, host->lilvUris.time_Position);
|
||||
|
||||
NodeAutoFree designationValue = lilv_port_get(plugin, pPort, host->lilvUris.designationNode);
|
||||
designation_ = nodeAsString(designationValue);
|
||||
@@ -1096,6 +1124,7 @@ json_map::storage_type<Lv2PortInfo> Lv2PortInfo::jmap{{
|
||||
json_map::reference("is_valid", &Lv2PortInfo::is_valid_),
|
||||
|
||||
json_map::reference("supports_midi", &Lv2PortInfo::supports_midi_),
|
||||
json_map::reference("supports_time_position", &Lv2PortInfo::supports_time_position_),
|
||||
json_map::reference("port_group", &Lv2PortInfo::port_group_),
|
||||
json_map::reference("is_logarithmic", &Lv2PortInfo::is_logarithmic_),
|
||||
|
||||
|
||||
@@ -208,6 +208,7 @@ private:
|
||||
|
||||
bool is_valid_ = false;
|
||||
bool supports_midi_ = false;
|
||||
bool supports_time_position_ = false;
|
||||
bool is_logarithmic_ = false;
|
||||
int display_priority_ = -1;
|
||||
int range_steps_ = 0;
|
||||
@@ -267,6 +268,7 @@ public:
|
||||
LV2_PROPERTY_GETSET_SCALAR(is_cv_port);
|
||||
LV2_PROPERTY_GETSET_SCALAR(is_valid);
|
||||
LV2_PROPERTY_GETSET_SCALAR(supports_midi);
|
||||
LV2_PROPERTY_GETSET_SCALAR(supports_time_position);
|
||||
LV2_PROPERTY_GETSET_SCALAR(is_logarithmic);
|
||||
LV2_PROPERTY_GETSET_SCALAR(display_priority);
|
||||
LV2_PROPERTY_GETSET_SCALAR(range_steps);
|
||||
@@ -381,6 +383,24 @@ public:
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool hasMidiInput() const {
|
||||
for (size_t i = 0; i < ports_.size(); ++i)
|
||||
{
|
||||
if (ports_[i]->is_atom_port() && ports_[i]->supports_midi() && ports_[i]->is_input())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool hasMidiOutput() const {
|
||||
for (size_t i = 0; i < ports_.size(); ++i)
|
||||
{
|
||||
if (ports_[i]->is_atom_port() && ports_[i]->supports_midi() && ports_[i]->is_output())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
@@ -598,6 +618,12 @@ private:
|
||||
LilvNodePtr symbolUri;
|
||||
LilvNodePtr nameUri;
|
||||
|
||||
LilvNodePtr time_Position;
|
||||
LilvNodePtr time_barBeat;
|
||||
LilvNodePtr time_beatsPerMinute;
|
||||
LilvNodePtr time_speed;
|
||||
|
||||
|
||||
};
|
||||
LilvUris lilvUris;
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ class PiPedalConfiguration
|
||||
private:
|
||||
std::string lv2_path_ = "/usr/lib/lv2:/usr/local/lib/lv2";
|
||||
std::filesystem::path docRoot_;
|
||||
std::filesystem::path webRoot_;
|
||||
std::string local_storage_path_;
|
||||
bool mlock_ = true;
|
||||
std::vector<std::string> reactServerAddresses_ = {"*:5000"};
|
||||
@@ -52,7 +53,10 @@ public:
|
||||
std::filesystem::path GetConfigFilePath() const {
|
||||
return docRoot_ / "config.jason";
|
||||
}
|
||||
void Load(std::filesystem::path path) {
|
||||
const std::filesystem::path& GetWebRoot() const {
|
||||
return webRoot_;
|
||||
}
|
||||
void Load(const std::filesystem::path& path, const std::filesystem::path&webRoot) {
|
||||
std::filesystem::path configPath = path / "config.json";
|
||||
if (!std::filesystem::exists(configPath))
|
||||
{
|
||||
@@ -68,6 +72,8 @@ public:
|
||||
json_reader reader(f);
|
||||
reader.read(this);
|
||||
docRoot_ = path;
|
||||
|
||||
webRoot_ = webRoot;
|
||||
|
||||
}
|
||||
const std::filesystem::path &GetDocRoot() const { return docRoot_; }
|
||||
|
||||
@@ -113,7 +113,7 @@ void PiPedalModel::LoadLv2PluginInfo(const PiPedalConfiguration&configuration)
|
||||
|
||||
void PiPedalModel::Load(const PiPedalConfiguration &configuration)
|
||||
{
|
||||
|
||||
this->webRoot = configuration.GetWebRoot();
|
||||
this->jackServerSettings.ReadJackConfiguration();
|
||||
|
||||
|
||||
@@ -1164,4 +1164,9 @@ void PiPedalModel::cancelListenForAtomOutputs(int64_t clientId, int64_t clientHa
|
||||
std::vector<AlsaDeviceInfo> PiPedalModel::GetAlsaDevices()
|
||||
{
|
||||
return this->alsaDevices.GetAlsaDevices();
|
||||
}
|
||||
}
|
||||
|
||||
const std::filesystem::path& PiPedalModel::GetWebRoot() const
|
||||
{
|
||||
return webRoot;
|
||||
}
|
||||
|
||||
@@ -92,6 +92,7 @@ private:
|
||||
std::unique_ptr<JackHost> jackHost;
|
||||
JackConfiguration jackConfiguration;
|
||||
std::shared_ptr<Lv2PedalBoard> lv2PedalBoard;
|
||||
std::filesystem::path webRoot;
|
||||
|
||||
|
||||
std::vector<IPiPedalModelSubscriber*> subscribers;
|
||||
@@ -219,7 +220,7 @@ public:
|
||||
void cancelListenForAtomOutputs(int64_t clientId, int64_t clientHandle);
|
||||
|
||||
std::vector<AlsaDeviceInfo> GetAlsaDevices();
|
||||
|
||||
const std::filesystem::path& GetWebRoot() const;
|
||||
};
|
||||
|
||||
} // namespace pipedal.
|
||||
+21
-1
@@ -34,6 +34,7 @@
|
||||
#include "WifiChannels.hpp"
|
||||
#include "SysExec.hpp"
|
||||
#include "PiPedalAlsa.hpp"
|
||||
#include <filesystem>
|
||||
|
||||
using namespace std;
|
||||
using namespace pipedal;
|
||||
@@ -344,6 +345,7 @@ private:
|
||||
std::recursive_mutex writeMutex;
|
||||
PiPedalModel &model;
|
||||
static std::atomic<uint64_t> nextClientId;
|
||||
std::string imageList;
|
||||
|
||||
uint64_t clientId;
|
||||
// pedalboard is mutable and not thread-safe.
|
||||
@@ -390,6 +392,19 @@ public:
|
||||
PiPedalSocketHandler(PiPedalModel &model)
|
||||
: model(model), clientId(++nextClientId)
|
||||
{
|
||||
std::stringstream imageList;
|
||||
const std::filesystem::path& webRoot = model.GetWebRoot() / "img";
|
||||
bool firstTime = true;
|
||||
for (const auto&entry: std::filesystem::directory_iterator(webRoot))
|
||||
{
|
||||
if (!firstTime)
|
||||
{
|
||||
imageList << ";";
|
||||
}
|
||||
firstTime = false;
|
||||
imageList << entry.path().filename().string();
|
||||
}
|
||||
this->imageList = imageList.str();
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -1067,7 +1082,7 @@ public:
|
||||
std::lock_guard<std::recursive_mutex> guard(subscriptionMutex);
|
||||
activeVuSubscriptions.push_back(VuSubscription{subscriptionHandle, instanceId});
|
||||
}
|
||||
this->Reply(replyTo, "addVuSubscriptin", subscriptionHandle);
|
||||
this->Reply(replyTo, "addVuSubscription", subscriptionHandle);
|
||||
}
|
||||
else if (message == "removeVuSubscription")
|
||||
{
|
||||
@@ -1087,6 +1102,11 @@ public:
|
||||
}
|
||||
model.removeVuSubscription(subscriptionHandle);
|
||||
}
|
||||
else if (message == "imageList")
|
||||
{
|
||||
|
||||
this->Reply(replyTo, "imageList", imageList);
|
||||
}
|
||||
else
|
||||
{
|
||||
Lv2Log::error("Unknown message received: %s", message.c_str());
|
||||
|
||||
+47
-13
@@ -416,6 +416,9 @@ void json_reader::skip_property()
|
||||
int c = is_.peek();
|
||||
switch (c)
|
||||
{
|
||||
case -1:
|
||||
throw_format_error("Premature end of file.");
|
||||
break;
|
||||
case '[':
|
||||
skip_array();
|
||||
break;
|
||||
@@ -442,8 +445,10 @@ void json_reader::skip_string()
|
||||
consume('"');
|
||||
while (true)
|
||||
{
|
||||
char c;
|
||||
int c;
|
||||
c = get();
|
||||
if (c == -1) throw_format_error("Premature end of file.");
|
||||
|
||||
if (c == '\"')
|
||||
{
|
||||
if (peek() == '\"')
|
||||
@@ -463,27 +468,54 @@ void json_reader::skip_string()
|
||||
void json_reader::skip_number()
|
||||
{
|
||||
skip_whitespace();
|
||||
char c;
|
||||
while (true)
|
||||
int c;
|
||||
if (is_.peek() == '-')
|
||||
{
|
||||
int ic = is_.peek();
|
||||
if (ic == -1)
|
||||
break;
|
||||
if (is_whitespace((char)ic))
|
||||
get();
|
||||
}
|
||||
if (!std::isdigit(is_.peek()))
|
||||
{
|
||||
throw_format_error("Expecting a number.");
|
||||
}
|
||||
while (std::isdigit(is_.peek()))
|
||||
{
|
||||
get();
|
||||
}
|
||||
if (is_.peek() == '.')
|
||||
{
|
||||
get();
|
||||
}
|
||||
while (std::isdigit(is_.peek()))
|
||||
{
|
||||
get();
|
||||
}
|
||||
c = is_.peek();
|
||||
if (c == 'e' || c == 'E')
|
||||
{
|
||||
get();
|
||||
c = is_.peek();
|
||||
if (c == '+' || c == 'i')
|
||||
{
|
||||
break;
|
||||
get();
|
||||
}
|
||||
c = get();
|
||||
while (std::isdigit(is_.peek()))
|
||||
{
|
||||
get();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
void json_reader::skip_array()
|
||||
{
|
||||
char c;
|
||||
int c;
|
||||
consume('[');
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (peek() == ']')
|
||||
c = peek();
|
||||
if (c == -1) throw_format_error("Premature end of file.");
|
||||
|
||||
if (c == ']')
|
||||
{
|
||||
c = get();
|
||||
break;
|
||||
@@ -499,11 +531,13 @@ void json_reader::skip_array()
|
||||
|
||||
void json_reader::skip_object()
|
||||
{
|
||||
char c;
|
||||
int c;
|
||||
consume('{');
|
||||
while (true)
|
||||
{
|
||||
if (peek() == '}')
|
||||
c = peek();
|
||||
if (c == -1) throw_format_error("Premature end of file.");
|
||||
if (c == '}')
|
||||
{
|
||||
c = get();
|
||||
break;
|
||||
|
||||
+48
-5
@@ -27,6 +27,7 @@
|
||||
#include <sstream>
|
||||
#include "HtmlHelper.hpp"
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include "PiPedalException.hpp"
|
||||
|
||||
|
||||
@@ -243,6 +244,7 @@ class json_writer {
|
||||
public:
|
||||
const char*CRLF;
|
||||
private:
|
||||
bool allowNaN_ = true;
|
||||
std::ostream &os;
|
||||
int indent_level;
|
||||
bool compressed;
|
||||
@@ -276,13 +278,17 @@ public:
|
||||
os << text;
|
||||
}
|
||||
using string_view = boost::string_view;
|
||||
json_writer(std::ostream &os, bool compressed = false)
|
||||
json_writer(std::ostream &os, bool compressed = false, bool allowNaN = true)
|
||||
: os(os)
|
||||
, compressed(compressed)
|
||||
, allowNaN_(allowNaN)
|
||||
, indent_level(0)
|
||||
{
|
||||
this->CRLF = compressed? "" : "\r\n";
|
||||
}
|
||||
bool allowNaN() const { return allowNaN_; }
|
||||
void allowNaN(bool allow) { allowNaN_ = allow; }
|
||||
|
||||
void write(long long value)
|
||||
{
|
||||
os << value;
|
||||
@@ -342,11 +348,21 @@ public:
|
||||
}
|
||||
void write(float f)
|
||||
{
|
||||
os << std::setprecision(std::numeric_limits<float>::max_digits10) << f; // round-trip format
|
||||
if (allowNaN_ && (std::isnan(f) || std::isinf(f)))
|
||||
{
|
||||
os << "NaN";
|
||||
} else {
|
||||
os << std::setprecision(std::numeric_limits<float>::max_digits10) << f; // round-trip format
|
||||
}
|
||||
}
|
||||
void write(double f)
|
||||
{
|
||||
os << std::setprecision(std::numeric_limits<double>::max_digits10) << f; // round-trip format
|
||||
if (allowNaN_ && (std::isnan(f) || std::isinf(f)))
|
||||
{
|
||||
os << "NaN";
|
||||
} else {
|
||||
os << std::setprecision(std::numeric_limits<double>::max_digits10) << f; // round-trip format
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void write(const std::vector<T> &value)
|
||||
@@ -526,20 +542,26 @@ public:
|
||||
|
||||
|
||||
class json_reader {
|
||||
private:
|
||||
std::istream &is_;
|
||||
|
||||
const uint16_t UTF16_SURROGATE_1_BASE = 0xD800U;
|
||||
const uint16_t UTF16_SURROGATE_2_BASE = 0xDC00U;
|
||||
const uint16_t UTF16_SURROGATE_MASK = 0x3FFU;
|
||||
bool allowNaN_ = true;
|
||||
|
||||
|
||||
|
||||
public:
|
||||
json_reader(std::istream &input)
|
||||
json_reader(std::istream &input, bool allowNaN = true)
|
||||
: is_(input)
|
||||
{
|
||||
|
||||
this->allowNaN_ = allowNaN;
|
||||
}
|
||||
|
||||
bool allowNaN() const { return allowNaN_;}
|
||||
void allowNaN(bool allow) { allowNaN_ = allow; }
|
||||
|
||||
private:
|
||||
void throw_format_error(const char*error);
|
||||
|
||||
@@ -719,11 +741,32 @@ public:
|
||||
|
||||
void read(float*value) {
|
||||
skip_whitespace();
|
||||
if (allowNaN_)
|
||||
{
|
||||
if (peek() == 'N')
|
||||
{
|
||||
consumeToken("NaN","Expecting a number.");
|
||||
*value = std::nanf("");
|
||||
return;
|
||||
}
|
||||
}
|
||||
is_ >> *value;
|
||||
if (is_.fail()) throw PiPedalException("Invalid format.");
|
||||
}
|
||||
void read(double*value) {
|
||||
skip_whitespace();
|
||||
if (allowNaN_)
|
||||
{
|
||||
if (peek() == 'N')
|
||||
{
|
||||
consumeToken("NaN","Expecting a number.");
|
||||
|
||||
*value = std::nan("");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
is_ >> *value;
|
||||
if (is_.fail()) throw PiPedalException("Invalid format.");
|
||||
}
|
||||
|
||||
+1
-1
@@ -480,7 +480,7 @@ int main(int argc, char *argv[])
|
||||
PiPedalConfiguration configuration;
|
||||
try
|
||||
{
|
||||
configuration.Load(doc_root);
|
||||
configuration.Load(doc_root,web_root);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
// Diagnostic type used to report calculated type T in an error message.
|
||||
template <typename T> class TypeDisplay;
|
||||
|
||||
#ifndef SUPPORT_MIDI // currently, only whether midi plugins can be loaded. No routing or handling implemented (yet).
|
||||
#define SUPPORT_MIDI 0
|
||||
#endif
|
||||
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
Reference in New Issue
Block a user