diff --git a/config/config.json b/config/config.json index d87a2c3..53374cd 100644 --- a/config/config.json +++ b/config/config.json @@ -33,7 +33,9 @@ "logLevel": 3, /* Maximum filesize to allow when uploading */ - "maxUploadSize": 536870912 // 512MiB + "maxUploadSize": 536870912, // 512MiB + /* false-> Download A1 models; true -> Download A2 models */ + "tone3000A2Models": true } \ No newline at end of file diff --git a/src/PiPedalConfiguration.cpp b/src/PiPedalConfiguration.cpp index f333d95..6beaecf 100644 --- a/src/PiPedalConfiguration.cpp +++ b/src/PiPedalConfiguration.cpp @@ -101,6 +101,7 @@ JSON_MAP_REFERENCE(PiPedalConfiguration, threads) JSON_MAP_REFERENCE(PiPedalConfiguration, logLevel) JSON_MAP_REFERENCE(PiPedalConfiguration, logHttpRequests) JSON_MAP_REFERENCE(PiPedalConfiguration, maxUploadSize) +JSON_MAP_REFERENCE(PiPedalConfiguration, tone3000A2Models) JSON_MAP_REFERENCE(PiPedalConfiguration, accessPointGateway) JSON_MAP_REFERENCE(PiPedalConfiguration, accessPointServerAddress) JSON_MAP_REFERENCE(PiPedalConfiguration, isVst3Enabled) diff --git a/src/PiPedalConfiguration.hpp b/src/PiPedalConfiguration.hpp index 282c9f8..779f7e8 100644 --- a/src/PiPedalConfiguration.hpp +++ b/src/PiPedalConfiguration.hpp @@ -45,6 +45,7 @@ private: bool logHttpRequests_ = false; int logLevel_ = 0; uint64_t maxUploadSize_ = 1024*1024; + bool tone3000A2Models_ = true; std::string accessPointGateway_; std::string accessPointServerAddress_; bool isVst3Enabled_ = true; @@ -67,6 +68,7 @@ public: bool GetMLock() const { return mlock_; } uint64_t GetMaxUploadSize() const { return maxUploadSize_; } + bool GetTone3000A2Models() const { return tone3000A2Models_; } LogLevel GetLogLevel() const { return (LogLevel)this->logLevel_; } bool LogHttpRequests() const { return this->logHttpRequests_; } diff --git a/src/PiPedalModel.hpp b/src/PiPedalModel.hpp index b24cfd6..d62d84b 100644 --- a/src/PiPedalModel.hpp +++ b/src/PiPedalModel.hpp @@ -364,6 +364,8 @@ namespace pipedal void Init(const PiPedalConfiguration &configuration); + const PiPedalConfiguration &Configuration() const { return configuration; } + void LoadLv2PluginInfo(); void Load(); diff --git a/src/WebServerConfig.cpp b/src/WebServerConfig.cpp index e255b3f..b711322 100644 --- a/src/WebServerConfig.cpp +++ b/src/WebServerConfig.cpp @@ -1315,12 +1315,14 @@ static std::string StripPortNumber(const std::string &fromAddress) class InterceptConfig : public RequestHandler { private: + PiPedalModel&model; uint64_t maxUploadSize; int portNumber; public: - InterceptConfig(int portNumber, uint64_t maxUploadSize) + InterceptConfig(PiPedalModel&model,int portNumber, uint64_t maxUploadSize) : RequestHandler("/var/config.json"), + model(model), maxUploadSize(maxUploadSize), portNumber(portNumber) { @@ -1337,6 +1339,7 @@ public: << "\", \"ui_plugins\": [ ], \"max_upload_size\": " << maxUploadSize << ", \"enable_auto_update\": " << (ENABLE_AUTO_UPDATE ? " true" : "false") << ", \"has_wifi_device\": " << (HotspotManager::HasWifiDevice() ? " true" : "false") + << ", \"tone3000_A2_models\": " << (model.Configuration().GetTone3000A2Models() ? " true" : "false") << " }"; return s.str(); @@ -1396,7 +1399,7 @@ void pipedal::ConfigureWebServer( int port, size_t maxUploadSize) { - std::shared_ptr interceptConfig{new InterceptConfig(port, maxUploadSize)}; + std::shared_ptr interceptConfig{new InterceptConfig(model,port, maxUploadSize)}; server.AddRequestHandler(interceptConfig); std::shared_ptr downloadIntercept = std::make_shared(&model); diff --git a/todo.txt b/todo.txt index 8775d09..d6800b0 100644 --- a/todo.txt +++ b/todo.txt @@ -1,4 +1,17 @@ -Benchmark Results: +A2 Questions + +Does A2 support Callibration? +input_level_dbu, output_level_dbu accessors on NeuralAmpModelerCore DSP +slimmable_weights accessors on NeuralAmpModelerCore DSP +standard values for slimmable_weights? + +Implement NAM Model Weight selection UI + +NAM INput VU is f*ed up. + +File Player Underrun is NOT CPU use related. There's an underrun somewhere. + +Update Pi 4 Benchmark Results: The benchmark is to run TooB Neural Amp Modeler with an A1 model to generate 100s of audio. Lower times are better. diff --git a/vite/public/var/config.json b/vite/public/var/config.json index ff7662c..ed6f056 100644 --- a/vite/public/var/config.json +++ b/vite/public/var/config.json @@ -6,5 +6,7 @@ "fakeAndroid": false, "ui_plugins": [], "enable_auto_update": true, - "has_wifi_device": false + "has_wifi_device": false, + "tone3000_A2_models": true, + "end": true } \ No newline at end of file diff --git a/vite/src/pipedal/Lv2Plugin.tsx b/vite/src/pipedal/Lv2Plugin.tsx index be37817..c1c0bba 100644 --- a/vite/src/pipedal/Lv2Plugin.tsx +++ b/vite/src/pipedal/Lv2Plugin.tsx @@ -949,7 +949,7 @@ export class UiControl implements Deserializable { return scale_point.label; } } - return "#invalid"; + return value.toString(); } else if (this.integer_property) { return value.toFixed(0); } else { diff --git a/vite/src/pipedal/PiPedalModel.tsx b/vite/src/pipedal/PiPedalModel.tsx index 7e87e11..d7ac129 100644 --- a/vite/src/pipedal/PiPedalModel.tsx +++ b/vite/src/pipedal/PiPedalModel.tsx @@ -1316,6 +1316,7 @@ export class PiPedalModel //implements PiPedalModel maxFileUploadSize: number = 512 * 1024 * 1024; maxPresetUploadSize: number = 1024 * 1024; debug: boolean = false; + tone3000_A2_models: boolean = true; enableAutoUpdate: boolean = false; @@ -1326,6 +1327,7 @@ export class PiPedalModel //implements PiPedalModel let response: Response = await fetch(myRequest); let data = await response.json(); + this.tone3000_A2_models = data.tone3000_A2_models ?? true; this.enableAutoUpdate = !!data.enable_auto_update; this.hasWifiDevice.set(!!data.has_wifi_device); if (data.max_upload_size) { diff --git a/vite/src/pipedal/PluginControl.tsx b/vite/src/pipedal/PluginControl.tsx index bf3ba93..2689be4 100644 --- a/vite/src/pipedal/PluginControl.tsx +++ b/vite/src/pipedal/PluginControl.tsx @@ -154,6 +154,66 @@ export type PluginControlState = { previewValue?: string; }; +export interface CustomPluginControlProps extends WithStyles { + title: string; + isSelect?: boolean; + isButton?: boolean; + item_width?: number; + mainControl: React.ReactNode; + editControl?: React.ReactNode; +}; +export interface CustomPluginControlState { + +}; +const CustomPluginControl = + withStyles( + class extends Component { + constructor(props: CustomPluginControlProps) { + super(props); + } + render() { + let classes = withStyles.getClasses(this.props); + return ( +
+ {/* TITLE SECTION */} +
+ {this.props.isButton ? "\u00A0" : this.props.title} +
+ {/* CONTROL SECTION */} + +
+ {this.props.mainControl} +
+ + {/* LABEL/EDIT SECTION*/} +
+ {this.props.editControl} +
+ +
+ ); + return (
); + } + + }, + + pluginControlStyles + ); + +export { CustomPluginControl }; + + const PluginControl = withTheme(withStyles( class extends Component { @@ -490,7 +550,7 @@ const PluginControl = let tapTime = e.timeStamp; let dT = tapTime - this.lastTapMs; - + this.lastTapMs = tapTime; if (dT < 500) { @@ -521,7 +581,7 @@ const PluginControl = let ms = e.timeStamp - this.tapStartMs; if (ms < 200) { this.onPointerTap(e); - } + } } // prevent click from firing on other elements // when the pointer goes up0. @@ -619,7 +679,7 @@ const PluginControl = break; } } - this.setState({ previewValue: undefined}); + this.setState({ previewValue: undefined }); } previewInputValue(value: number, commitValue: boolean) { let range = this.valueToRange(value); @@ -688,7 +748,7 @@ const PluginControl = this.setState({ previewValue: undefined }); } else { this.setState({ previewValue: v }); - } + } } let selectElement = this.selectRef.current; if (selectElement) { @@ -890,7 +950,7 @@ const PluginControl = private defaultStep(min: number, max: number): number { let range = Math.abs(max - min); - if (range <= 1) return 0.01; + if (range <= 1) return 0.01; if (range <= 10) return 0.1; if (range <= 100) return 1; if (range <= 1000) return 10; @@ -1056,7 +1116,7 @@ const PluginControl = ) : (isGraphicEq) ? (
- + ) : (
- { @@ -495,6 +497,7 @@ const PluginControlView = } private ixKey: number = 1; + makeStandardControl(uiControl: UiControl, controlValues: ControlValue[]): ReactNode { let symbol = uiControl.symbol; if (!uiControl.is_input) { @@ -882,7 +885,7 @@ const PluginControlView = } handleSelectSidechainInput() { - this.setState({sidechainDialogOpen: true}); + this.setState({ sidechainDialogOpen: true }); } handleModGuiFileProperty(instanceId: number, filePropertyUri: string, selectedFile: string) { @@ -963,7 +966,9 @@ const PluginControlView = ) } - + makeCustomControl(title: string, mainControl: ReactNode, editControl?: ReactNode): ReactNode { + return (); + } renderPiPedalControl(pedalboardItem?: PedalboardItem): ReactNode { this.controlKeyIndex = 0; @@ -1115,12 +1120,12 @@ const PluginControlView = )} {this.state.sidechainDialogOpen && ( { this.setState({sidechainDialogOpen: false});}} - selectItems={this.getSidechainSelectItems()} - selectedInstanceId={this.props.item.sideChainInputId} - onChanged={(instanceId: number) => { - this.model.setPedalboardSideChainInput(this.props.item.instanceId, instanceId); - }} + onClose={() => { this.setState({ sidechainDialogOpen: false }); }} + selectItems={this.getSidechainSelectItems()} + selectedInstanceId={this.props.item.sideChainInputId} + onChanged={(instanceId: number) => { + this.model.setPedalboardSideChainInput(this.props.item.instanceId, instanceId); + }} /> )} {/* xxx: I don't think we need this anyore. */} @@ -1142,3 +1147,5 @@ const PluginControlView = export default PluginControlView; + + diff --git a/vite/src/pipedal/Tone3000Downloader.tsx b/vite/src/pipedal/Tone3000Downloader.tsx index f5e72bd..5e8d9a4 100644 --- a/vite/src/pipedal/Tone3000Downloader.tsx +++ b/vite/src/pipedal/Tone3000Downloader.tsx @@ -28,7 +28,6 @@ const THROTTLING_TRIGGGER_LEVEL = Math.floor(ALLOWED_DOWNLOADS_PER_MINUTE/2); class DownloadThrottler { - private throttleActive: boolean = false; private fifo: number[] = []; private downloadCount: number = 0; @@ -39,11 +38,7 @@ class DownloadThrottler { this.fifo.shift(); } - if (this.fifo.length > THROTTLING_TRIGGGER_LEVEL) { - this.throttleActive = true; - } else { - this.throttleActive = false; - } + this.downloadCount = this.fifo.length; } getThrottleDelay(now : number = Date.now()): number { while (this.fifo.length > 0 && this.fifo[0] + 60000 <= now) { @@ -264,7 +259,7 @@ export class Tone3000DownloadHandler { let models: Model[] = []; let architectureFilter: number | undefined = undefined; // Take A2 models only for NAM downloads, and only if the tone actually has A2 models. - if (downloadType === Tone3000DownloadType.Nam && !!tone.a2_models_count) { + if (downloadType === Tone3000DownloadType.Nam && !!tone.a2_models_count && this.model.tone3000_A2_models) { architectureFilter = 2; // NAM models only } diff --git a/vite/src/pipedal/ToobNamView.tsx b/vite/src/pipedal/ToobNamView.tsx index 31043e4..dfa2677 100644 --- a/vite/src/pipedal/ToobNamView.tsx +++ b/vite/src/pipedal/ToobNamView.tsx @@ -27,14 +27,20 @@ import { withStyles } from "tss-react/mui"; import IControlViewFactory from './IControlViewFactory'; import { PiPedalModelFactory, PiPedalModel, ListenHandle, State } from "./PiPedalModel"; -import { PedalboardItem,ControlValue } from './Pedalboard'; +import { PedalboardItem, ControlValue } from './Pedalboard'; import PluginControlView, { ICustomizationHost, ControlGroup, ControlViewCustomization } from './PluginControlView'; -import { UiPlugin,UiControl, ControlType } from './Lv2Plugin'; +import { UiPlugin, UiControl, ControlType } from './Lv2Plugin'; +import Select from '@mui/material/Select/Select'; +import MenuItem from '@mui/material/MenuItem/MenuItem'; +import { CustomPluginControl } from './PluginControl'; -const TOOB_NAM__MODEL_METADATA = "http://two-play.com/plugins/toob-nam#model_metadata"; +const TOOB_NAM__MODEL_METADATA_URI = "http://two-play.com/plugins/toob-nam#model_metadata"; +const TOOB_NAM__MODEL_WEIGHT_URI = "http://two-play.com/plugins/toob-nam#modelWeight"; // offset 0: an integer with the folloing bits set. +// Must match ToobAmp/src/NeuralAmpModeler_Lv2Extensions.hpp enum TOOB_NAM_METADATA_OFFSETS +const MAX_SLIMMABLE_WEIGHTS = 16; class TOOB_NAM_METADATA_OFFSETS { static readonly flags = 0; static readonly preset_version = 1; @@ -42,7 +48,11 @@ class TOOB_NAM_METADATA_OFFSETS { static readonly gain = 3; static readonly input_level_dbu = 4; static readonly output_level_dbu = 5; - static readonly max_medatadata = 6; + static readonly is_a2 = 6; + static readonly model_weight = 7; + static readonly slimmable_weights_size = 8; + static readonly slimmable_weights = 9; + static readonly max_medatadata = TOOB_NAM_METADATA_OFFSETS.slimmable_weights + MAX_SLIMMABLE_WEIGHTS; }; @@ -55,10 +65,9 @@ class TOOB_NAM_METADATA_FLAGS { }; class ModelMetadata { - constructor(metadataValues?: number[]) - { + constructor(metadataValues?: number[]) { + - if (metadataValues) { let flags = metadataValues[TOOB_NAM_METADATA_OFFSETS.flags]; this.hasModel = (flags & TOOB_NAM_METADATA_FLAGS.has_model) !== 0; @@ -72,6 +81,12 @@ class ModelMetadata { this.inputlevelDBU = metadataValues[TOOB_NAM_METADATA_OFFSETS.input_level_dbu]; this.outputlevelDBU = metadataValues[TOOB_NAM_METADATA_OFFSETS.output_level_dbu]; this.preset_version = metadataValues[TOOB_NAM_METADATA_OFFSETS.preset_version]; + this.isA2 = metadataValues[TOOB_NAM_METADATA_OFFSETS.is_a2] !== 0; + this.modelWeight = metadataValues[TOOB_NAM_METADATA_OFFSETS.model_weight]; + let slimmableWeightsSize = metadataValues[TOOB_NAM_METADATA_OFFSETS.slimmable_weights_size]; + for (let i = 0; i < slimmableWeightsSize && i < 16; ++i) { + this.slimmable_weights.push(metadataValues[TOOB_NAM_METADATA_OFFSETS.slimmable_weights + i]); + } } else { this.hasModel = false; this.hasLoudness = false; @@ -84,11 +99,16 @@ class ModelMetadata { this.inputlevelDBU = 0; this.outputlevelDBU = 0; this.preset_version = 1; + this.modelWeight = -1; + this.isA2 = false; + this.slimmable_weights = []; } } preset_version: number; - + isA2: boolean = false; + modelWeight: number = 1.0; + slimmable_weights: number[] = []; hasModel: boolean; hasLoudness: boolean; hasGain: boolean; @@ -100,6 +120,10 @@ class ModelMetadata { inputlevelDBU: number; outputlevelDBU: number; + hasSlimmableWeights(): boolean { + return this.slimmable_weights.length > 0; + } + } @@ -114,8 +138,10 @@ interface ToobNamViewProps extends WithStyles { interface ToobNamViewState { showEqSection: boolean; enableCalibration: boolean; + showCalibration: boolean; enableOutputNormalization: boolean; modelMetadata: ModelMetadata; + modelWeight: number, } const ToobNamView = @@ -133,18 +159,18 @@ const ToobNamView = this.model = PiPedalModelFactory.getInstance(); this.state = { showEqSection: false, + showCalibration: false, enableCalibration: false, enableOutputNormalization: false, - modelMetadata: new ModelMetadata() + modelMetadata: new ModelMetadata(), + modelWeight: -1 } let pluginInfo: UiPlugin | null = this.model.getUiPlugin(this.props.item.uri); - if (pluginInfo === null) - { + if (pluginInfo === null) { throw new Error("Plugin not fouund."); } let inputCalibrationControl = pluginInfo.getControl("inputCalibrationMode"); - if (!inputCalibrationControl) - { + if (!inputCalibrationControl) { throw new Error("Control not found."); } let patchedInputControl = new UiControl().deserialize(inputCalibrationControl); @@ -152,14 +178,13 @@ const ToobNamView = this.patchedInputControl = patchedInputControl let outputCalibrationControl = pluginInfo.getControl("outputCalibration"); - if (!outputCalibrationControl) - { + if (!outputCalibrationControl) { throw new Error("Control not found."); } let patchedOutputControl = new UiControl().deserialize(outputCalibrationControl); patchedOutputControl.controlType = ControlType.Select; this.noCalibrationOutputControl = patchedOutputControl - this.noCalibrationOutputControl.scale_points.splice(1,1); + this.noCalibrationOutputControl.scale_points.splice(1, 1); } private patchedInputControl: UiControl; @@ -171,7 +196,7 @@ const ToobNamView = disableControl(control: React.ReactNode, key: string) { return (
{ e.stopPropagation(); @@ -189,42 +214,94 @@ const ToobNamView = modifyControls(host: ICustomizationHost, controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[] { let EqPos = 7; - let CalibrationGroupPos = 8; + const CalibrationGroupPos = 8; + const ModelSizeControlPos = 9; + const ModelWeightControlPos = 10; + let calibrationGroup = controls[CalibrationGroupPos] as ControlGroup; - calibrationGroup.controls[0] = host.makeStandardControl(this.patchedInputControl,this.props.item.controlValues); - if (this.state.enableCalibration) - { - calibrationGroup.controls[0] = host.makeStandardControl(this.patchedInputControl,this.props.item.controlValues); + if (this.state.showCalibration) { + calibrationGroup.controls[0] = host.makeStandardControl(this.patchedInputControl, this.props.item.controlValues); - } else { - calibrationGroup.controls[0] = host.makeStandardControl(this.patchedInputControl, - [ new ControlValue("inputCalibrationMode",0.0)] - ); - calibrationGroup.controls[0] = this.disableControl(calibrationGroup.controls[0], "cg_01d"); - calibrationGroup.controls[1] = this.disableControl(calibrationGroup.controls[1], "cg_02d"); + if (this.state.enableCalibration) { + calibrationGroup.controls[0] = host.makeStandardControl(this.patchedInputControl, this.props.item.controlValues); - if (this.state.enableOutputNormalization) - { - calibrationGroup.controls[2] = host.makeStandardControl(this.noCalibrationOutputControl, this.props.item.controlValues); } else { - let tControl: UiControl = this.noCalibrationOutputControl.clone(); - tControl.symbol = "_disabled_output"; - calibrationGroup.controls[2] = host.makeStandardControl( - tControl, - [ new ControlValue("_disabled_output",2.0)]); - calibrationGroup.controls[2] = this.disableControl(calibrationGroup.controls[2], "cg_03d"); + calibrationGroup.controls[0] = host.makeStandardControl(this.patchedInputControl, + [new ControlValue("inputCalibrationMode", 0.0)] + ); + calibrationGroup.controls[0] = this.disableControl(calibrationGroup.controls[0], "cg_01d"); + calibrationGroup.controls[1] = this.disableControl(calibrationGroup.controls[1], "cg_02d"); + if (this.state.enableOutputNormalization) { + calibrationGroup.controls[2] = host.makeStandardControl(this.noCalibrationOutputControl, this.props.item.controlValues); + } else { + let tControl: UiControl = this.noCalibrationOutputControl.clone(); + tControl.symbol = "_disabled_output"; + calibrationGroup.controls[2] = host.makeStandardControl( + tControl, + [new ControlValue("_disabled_output", 2.0)]); + calibrationGroup.controls[2] = this.disableControl(calibrationGroup.controls[2], "cg_03d"); + + } } + } else { + controls[CalibrationGroupPos] = null; } + controls[ModelSizeControlPos] = null; + controls[ModelWeightControlPos] = null; if (!this.state.showEqSection) { controls.splice(EqPos, 1); } + if (this.state.modelMetadata.hasSlimmableWeights()) { + controls.splice(3, 0, this.MakeModelWeightSelector(host)); + } return controls; } + ModelWeightToString(weight: number): string { + switch (weight) { + case 1.0: return "Standard"; + case 0.5: return "Lite"; + case 0.25: return "Feather"; + default: return weight.toString(); + } + } + + SelectModelWeight(weight: number) { + this.setState({ modelWeight: weight }); + this.model.setPatchProperty(this.props.instanceId, TOOB_NAM__MODEL_WEIGHT_URI, weight); + + } + MakeModelWeightSelector(host: ICustomizationHost): React.ReactNode { + if ((this.state.modelMetadata.modelWeight === undefined || this.state.modelMetadata.modelWeight || this.state.modelMetadata.hasSlimmableWeights()) === false) { + return null; + } + let selectControl = ( + + ); + return ( +
+ +
); + } + private handleConnectionStateChanged(state: State) { if (state === State.Ready) { this.unsubscribeFromMetadata(); @@ -235,11 +312,16 @@ const ToobNamView = handleModelMetadata(atomData: any) { if (atomData && atomData.otype_ === "Vector" && atomData.value) { let metadata = new ModelMetadata(atomData.value as number[]); - this.setState({ + metadata.hasInputLevelDBU || metadata.hasOutputLevelDBU + this.setState({ modelMetadata: metadata, showEqSection: metadata.preset_version === 0, + showCalibration: ( + metadata.hasInputLevelDBU || metadata.hasOutputLevelDBU || + (metadata.hasLoudness && metadata.hasGain)) && metadata.hasModel, enableCalibration: metadata.hasInputLevelDBU && metadata.hasModel, - enableOutputNormalization: metadata.hasLoudness && metadata.hasModel + enableOutputNormalization: metadata.hasLoudness && metadata.hasModel, + modelWeight: metadata.modelWeight }); } } @@ -248,13 +330,13 @@ const ToobNamView = this.subscribedId = this.props.instanceId; this.listenHandle = this.model.monitorPatchProperty( this.props.instanceId, - TOOB_NAM__MODEL_METADATA, + TOOB_NAM__MODEL_METADATA_URI, (instanceId, propertyUri, atomData) => { this.handleModelMetadata(atomData); }); this.model.getPatchProperty( this.props.instanceId, - TOOB_NAM__MODEL_METADATA + TOOB_NAM__MODEL_METADATA_URI ).then((atomData) => { this.handleModelMetadata(atomData); }).catch((e) => {