From 31f1b9a84aaeea6c4033b8fcd791011ebea5e4a7 Mon Sep 17 00:00:00 2001 From: Robin Davies Date: Sat, 10 Aug 2024 10:24:43 -0400 Subject: [PATCH] Midi Bindings for split controls. --- CMakeLists.txt | 4 +- README.md | 4 +- docs/download.md | 6 +- react/src/Lv2Plugin.tsx | 146 ++++++++++++++++++++++++++++++- react/src/MidiBindingView.tsx | 11 ++- react/src/MidiBindingsDialog.tsx | 94 +++++++++++++++----- react/src/PiPedalModel.tsx | 1 + src/Lv2Pedalboard.cpp | 29 ++---- src/PiPedalModel.cpp | 14 ++- src/PluginHost.cpp | 4 + src/PluginHost.hpp | 2 + src/SplitEffect.cpp | 6 +- src/SplitEffect.hpp | 2 +- 13 files changed, 256 insertions(+), 67 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 851ef76..fca0ed8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,10 @@ cmake_minimum_required(VERSION 3.16.0) project(pipedal - VERSION 1.2.33 + VERSION 1.2.34 DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi" HOMEPAGE_URL "https://rerdavies.github.io/pipedal" ) -set (DISPLAY_VERSION "v1.2.31beta1") +set (DISPLAY_VERSION "v1.2.34beta2") set (CMAKE_INSTALL_PREFIX "/usr/") diff --git a/README.md b/README.md index 7c9f656..9a56385 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@
-Download: v1.2.33 Beta1 +Download: v1.2.34 Beta1 Website: [https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal). -> NEW version 1.2.33 beta1 release, providing support for Raspberry Pi OS Bookworm. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. +> NEW version 1.2.34 beta2 release, providing support for Raspberry Pi OS Bookworm. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. Use your Raspberry Pi as a guitar effects pedal. Configure and control PiPedal with your phone or tablet. PiPedal running on a Raspberry Pi 4 provides stable super-low-latency audio via external USB audio devices, or internal Raspberry Pi audio hats. diff --git a/docs/download.md b/docs/download.md index 167f4b7..453b855 100644 --- a/docs/download.md +++ b/docs/download.md @@ -4,17 +4,17 @@ Download the most recent Debian (.deb) package for your platform: -- Raspberry Pi OS Bookworm (64-bit) v1.2.33 Beta1 +- Raspberry Pi OS Bookworm (64-bit) v1.2.34 Beta2 - Ubuntu 21.04 or Raspberry Pi OS Buster (64-bit) v1.1.31 -v1.2.33 Beta1 is does not currently support Ubuntu 21.04, or older versions of Raspberry Pi OS. +v1.2.34 Beta1 is does not currently support Ubuntu 21.04, or older versions of Raspberry Pi OS. Install the package by running ``` sudo apt update cd ~/Downloads - sudo apt-get install ./pipedal_1.2.33_arm64.deb + sudo apt-get install ./pipedal_1.2.34_arm64.deb ``` Follow the instructions in [_Configuring PiPedal After Installation_](https://rerdavies.github.io/pipedal/Configuring.html) to complete the installation. diff --git a/react/src/Lv2Plugin.tsx b/react/src/Lv2Plugin.tsx index 379f219..4ec695f 100644 --- a/react/src/Lv2Plugin.tsx +++ b/react/src/Lv2Plugin.tsx @@ -520,7 +520,10 @@ export class UiControl implements Deserializable { } } return this; - + } + applyProperties(properties: Partial): UiControl + { + return {...this,...properties}; } private hasScalePoint(value: number): boolean { for (let scale_point of this.scale_points) @@ -726,7 +729,7 @@ export class UiControl implements Deserializable { text += "s"; break; // Midinote: not handled. - // semitone12TET not handled. + // semitone12TET not handled. @@ -759,6 +762,7 @@ export class UiControl implements Deserializable { } + export class UiPlugin implements Deserializable { deserialize(input: any): UiPlugin { @@ -802,6 +806,11 @@ export class UiPlugin implements Deserializable { } return result; } + + isSplit(): boolean { + return this.uri === "uri://two-play/pipedal/pedalboard#Split"; + + } getControl(key: string): UiControl | undefined { for (let i = 0; i < this.controls.length; ++i) { @@ -860,3 +869,136 @@ export class UiPlugin implements Deserializable { is_vst3 : boolean = false; } + + +export function makeSplitUiPlugin(): UiPlugin +{ + + return new UiPlugin().deserialize({ + uri: "uri://two-play/pipedal/pedalboard#Split", + name: "Split", + brand: "", + label: "", + plugin_type: PluginType.SplitA, + plugin_display_type: "Split", + author_name: "", + author_homepage: "", + audio_inputs: 1, + audio_outputs: 1, + has_midi_input: 0, + has_midi_output: 0, + description: "", + controls: [ + new UiControl().applyProperties({ + symbol: "splitType", + name: "Type", + index: 0, + is_input: true, + min_value: 0.0, + max_value: 2.0, + enumeration_property: true, + scale_points: [ + new ScalePoint().deserialize({value: 0, label: "A/B"}), + new ScalePoint().deserialize({value: 1, label: "mix"}), + new ScalePoint().deserialize({value: 1, label: "L/R"}), + ], + is_bypass: false, + is_program_controller: false, + custom_units: "", + connection_optional: false, + + }) , + new UiControl().applyProperties({ + symbol: "select", + name: "Select", + index: 1, + is_input: true, + min_value: 0.0, + max_value: 1.0, + enumeration_property: true, + scale_points: [ + new ScalePoint().deserialize({value: 0, label: "A"}), + new ScalePoint().deserialize({value: 1, label: "B"}), + ], + is_bypass: false, + is_program_controller: false, + custom_units: "", + connection_optional: false, + + }) , + + new UiControl().applyProperties({ + symbol: "mix", + name: "Mix", + index: 2, + is_input: true, + min_value: -1.0, + max_value: 1.0, + is_bypass: false, + is_program_controller: false, + custom_units: "", + connection_optional: false, + + }) , + new UiControl().applyProperties({ + symbol: "panL", + name: "Pan Top", + index: 3, + is_input: true, + min_value: -1.0, + max_value: 1.0, + is_bypass: false, + is_program_controller: false, + custom_units: "", + connection_optional: false, + + }) , + new UiControl().applyProperties({ + symbol: "volL", + name: "Vol Top", + index: 4, + is_input: true, + min_value: -60.0, + max_value: 12.0, + is_bypass: false, + is_program_controller: false, + custom_units: "", + connection_optional: false, + + }) , + new UiControl().applyProperties({ + symbol: "panR", + name: "Pan Bottom", + index: 5, + is_input: true, + min_value: -1.0, + max_value: 1.0, + is_bypass: false, + is_program_controller: false, + custom_units: "", + connection_optional: false, + + }) , + new UiControl().applyProperties({ + symbol: "volR", + name: "Vol Bottom", + index: 6, + is_input: true, + min_value: -60.0, + max_value: 12.0, + is_bypass: false, + is_program_controller: false, + custom_units: "", + connection_optional: false, + }) + ], + port_groups: [], + fileProperties: [], + frequencyPlots: [], + is_vst3 : false, + + } + ); +} + + diff --git a/react/src/MidiBindingView.tsx b/react/src/MidiBindingView.tsx index b9fc6e7..4df072f 100644 --- a/react/src/MidiBindingView.tsx +++ b/react/src/MidiBindingView.tsx @@ -32,6 +32,7 @@ import MicNoneOutlinedIcon from '@mui/icons-material/MicNoneOutlined'; import MicOutlinedIcon from '@mui/icons-material/MicOutlined'; import IconButton from '@mui/material/IconButton'; import NumericInput from './NumericInput'; +import { UiPlugin } from './Lv2Plugin'; @@ -45,6 +46,7 @@ interface MidiBindingViewProps extends WithStyles { instanceId: number; listen: boolean; midiBinding: MidiBinding; + uiPlugin: UiPlugin; onChange: (instanceId: number, newBinding: MidiBinding) => void; onListen: (instanceId: number, key: string, listenForControl: boolean) => void; } @@ -139,8 +141,7 @@ const MidiBindingView = render() { let classes = this.props.classes; let midiBinding = this.props.midiBinding; - let pedalboardItem = this.model.pedalboard.get().getItem(this.props.instanceId); - let uiPlugin = this.model.getUiPlugin(pedalboardItem.uri); + let uiPlugin = this.props.uiPlugin; if (!uiPlugin) { return (
); } @@ -258,7 +259,11 @@ const MidiBindingView = value={midiBinding.switchControlType} > Toggle - Momentary + { + (midiBinding.bindingType === MidiBinding.BINDING_TYPE_NOTE) ? + "Note on/off" + : "Control value" + }
)) diff --git a/react/src/MidiBindingsDialog.tsx b/react/src/MidiBindingsDialog.tsx index 5b9efb8..bd4c6f1 100644 --- a/react/src/MidiBindingsDialog.tsx +++ b/react/src/MidiBindingsDialog.tsx @@ -33,6 +33,7 @@ import IconButton from '@mui/material/IconButton'; import MidiBinding from './MidiBinding'; import MidiBindingView from './MidiBindingView'; import Snackbar from '@mui/material/Snackbar'; +import { UiPlugin,makeSplitUiPlugin } from './Lv2Plugin'; const styles = (theme: Theme) => createStyles({ dialogAppBar: { @@ -62,6 +63,10 @@ const styles = (theme: Theme) => createStyles({ }, }); +function not_null(value: T | null) { + if (!value) throw Error("Unexpected null value"); + return value; +} export interface MidiBindingDialogProps extends WithStyles { open: boolean, @@ -189,7 +194,43 @@ export const MidiBindingDialog = let item = v.value; - let plugin = this.model.getUiPlugin(item.uri); + let isSplit = item.uri === "uri://two-play/pipedal/pedalboard#Split"; + let plugin : UiPlugin | null = this.model.getUiPlugin(item.uri); + if (plugin === null && isSplit) + { + plugin = makeSplitUiPlugin(); + let splitType = item.getControlValue("splitType"); + not_null(plugin.getControl("splitType")).not_on_gui = true; + switch (splitType) + { + case 0: // A/B + not_null(plugin.getControl("select")).not_on_gui = false; + not_null(plugin.getControl("mix")).not_on_gui = true; + not_null(plugin.getControl("volL")).not_on_gui = true; + not_null(plugin.getControl("panL")).not_on_gui = true; + not_null(plugin.getControl("volR")).not_on_gui = true; + not_null(plugin.getControl("panR")).not_on_gui = true; + break; + case 1: //mixer + not_null(plugin.getControl("select")).not_on_gui = true; + not_null(plugin.getControl("mix")).not_on_gui = false; + not_null(plugin.getControl("volL")).not_on_gui = true; + not_null(plugin.getControl("panL")).not_on_gui = true; + not_null(plugin.getControl("volR")).not_on_gui = true; + not_null(plugin.getControl("panR")).not_on_gui = true; + break; + case 2: // L/R + not_null(plugin.getControl("select")).not_on_gui = true; + not_null(plugin.getControl("mix")).not_on_gui = true; + not_null(plugin.getControl("volL")).not_on_gui = false; + not_null(plugin.getControl("panL")).not_on_gui = false; + not_null(plugin.getControl("volR")).not_on_gui = false; + not_null(plugin.getControl("panR")).not_on_gui = false; + break; + } + + // xxx + } if (plugin) { result.push( @@ -200,29 +241,33 @@ export const MidiBindingDialog = ); - result.push( - - - - Bypass - - - - { - if (instanceId === -2) - { - this.cancelListenForControl(); - } else { - this.handleListenForControl(instanceId, symbol, listenForControl); - } - }} - listen={item.instanceId === this.state.listenInstanceId && this.state.listenSymbol === "__bypass"} - onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)} - /> - - - ); + if (!isSplit) + { + result.push( + + + + Bypass + + + + { + if (instanceId === -2) + { + this.cancelListenForControl(); + } else { + this.handleListenForControl(instanceId, symbol, listenForControl); + } + }} + listen={item.instanceId === this.state.listenInstanceId && this.state.listenSymbol === "__bypass"} + onChange={(instanceId: number, newItem: MidiBinding) => this.handleItemChanged(instanceId, newItem)} + /> + + + ); + } for (let i = 0; i < plugin.controls.length; ++i) { let control = plugin.controls[i]; @@ -244,6 +289,7 @@ export const MidiBindingDialog = { diff --git a/react/src/PiPedalModel.tsx b/react/src/PiPedalModel.tsx index fe277dd..3b98ccf 100644 --- a/react/src/PiPedalModel.tsx +++ b/react/src/PiPedalModel.tsx @@ -423,6 +423,7 @@ export class PiPedalModel //implements PiPedalModel //if (retry !== 0) { if (this.restartExpected) { this.setState(State.ApplyingChanges); + this.restartExpected = false; } else { this.setState(State.Reconnecting); } diff --git a/src/Lv2Pedalboard.cpp b/src/Lv2Pedalboard.cpp index 038b1d4..129e9b4 100644 --- a/src/Lv2Pedalboard.cpp +++ b/src/Lv2Pedalboard.cpp @@ -261,15 +261,15 @@ void Lv2Pedalboard::PrepareMidiMap(const PedalboardItem &pedalboardItem) { if (pedalboardItem.midiBindings().size() != 0) { - auto pluginInfo = pHost->GetPluginInfo(pedalboardItem.uri()); - const Lv2PluginInfo *pPluginInfo; - if (pluginInfo == nullptr && pedalboardItem.uri() == SPLIT_PEDALBOARD_ITEM_URI) + Lv2PluginInfo::ptr pluginInfo; + if (pedalboardItem.uri() == SPLIT_PEDALBOARD_ITEM_URI) { - pPluginInfo = GetSplitterPluginInfo(); + pluginInfo = GetSplitterPluginInfo(); } else { - pPluginInfo = pluginInfo.get(); + pluginInfo = pHost->GetPluginInfo(pedalboardItem.uri()); + } int effectIndex = this->GetIndexOfInstanceId(pedalboardItem.instanceId()); @@ -351,21 +351,10 @@ void Lv2Pedalboard::PrepareMidiMap(const Pedalboard &pedalboard) auto &item = pedalboard.items()[i]; PrepareMidiMap(item); - auto pluginInfo = pHost->GetPluginInfo(item.uri()); - - if (pluginInfo) - { - for (size_t bindingIndex = 0; bindingIndex < item.midiBindings().size(); ++bindingIndex) - { - auto &binding = item.midiBindings()[i]; - { - } - } - } - std::sort(this->midiMappings.begin(), this->midiMappings.end(), - [](const MidiMapping &left, const MidiMapping &right) - { return left.key < right.key; }); } + std::sort(this->midiMappings.begin(), this->midiMappings.end(), + [](const MidiMapping &left, const MidiMapping &right) + { return left.key < right.key; }); } void Lv2Pedalboard::Activate() { @@ -606,7 +595,7 @@ void Lv2Pedalboard::OnMidiMessage(size_t size, uint8_t *message, if (size < 3) return; index = message[1]; - value = 127; + value = message[2] == 0? 0: 127; // zero velocity = note off. } else if (cmd == 0xB0) // midi control. { diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index cf209d5..a001da1 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -1266,21 +1266,20 @@ void PiPedalModel::OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, f PedalboardItem *item = this->pedalboard.GetItem(instanceId); if (item) { - const Lv2PluginInfo *pPluginInfo; + Lv2PluginInfo::ptr pPluginInfo; if (item->uri() == SPLIT_PEDALBOARD_ITEM_URI) { pPluginInfo = GetSplitterPluginInfo(); } else { - auto pluginInfo = lv2Host.GetPluginInfo(item->uri()); - pPluginInfo = pluginInfo.get(); + pPluginInfo = lv2Host.GetPluginInfo(item->uri()); } if (pPluginInfo) { if (portIndex == -1) { - // bypass! + // bypass! yyy this->value!!! this->pedalboard.SetItemEnabled(instanceId, value != 0); // 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()]; @@ -1675,16 +1674,15 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem) { - std::shared_ptr t = lv2Host.GetPluginInfo(pedalboardItem->uri()); - const Lv2PluginInfo *pPlugin = t.get(); - if (pPlugin == nullptr) + std::shared_ptr pPlugin = lv2Host.GetPluginInfo(pedalboardItem->uri()); + if (!pPlugin) { if (pedalboardItem->uri() == SPLIT_PEDALBOARD_ITEM_URI) { pPlugin = GetSplitterPluginInfo(); } } - if (pPlugin != nullptr) + if (pPlugin) { for (size_t i = 0; i < pPlugin->ports().size(); ++i) { diff --git a/src/PluginHost.cpp b/src/PluginHost.cpp index ea74f97..ecdc638 100644 --- a/src/PluginHost.cpp +++ b/src/PluginHost.cpp @@ -1353,6 +1353,10 @@ Lv2PortGroup::Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri) name_ = nodeAsString(nameNode); } +bool Lv2PluginInfo::isSplit() const +{ + return uri_ == SPLIT_PEDALBOARD_ITEM_URI; +} std::shared_ptr PluginHost::GetHostWorkerThread() { return pHostWorkerThread; diff --git a/src/PluginHost.hpp b/src/PluginHost.hpp index 9423ff2..29b52e3 100644 --- a/src/PluginHost.hpp +++ b/src/PluginHost.hpp @@ -337,8 +337,10 @@ namespace pipedal friend class PluginHost; public: + using ptr = std::shared_ptr; Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *); Lv2PluginInfo() {} + bool isSplit() const ; private: std::shared_ptr FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin *pPlugin); diff --git a/src/SplitEffect.cpp b/src/SplitEffect.cpp index 13ba886..54acba2 100644 --- a/src/SplitEffect.cpp +++ b/src/SplitEffect.cpp @@ -33,6 +33,8 @@ static std::shared_ptr MakeBypassPortInfo() bypassPortInfo->is_input(true); bypassPortInfo->is_control_port(true); bypassPortInfo->index(-1); + + bypassPortInfo->min_value(0); bypassPortInfo->max_value(1); bypassPortInfo->default_value(1); @@ -159,9 +161,9 @@ static Lv2PluginInfo makeSplitterPluginInfo() return result; } -Lv2PluginInfo g_splitterPluginInfo = makeSplitterPluginInfo(); +Lv2PluginInfo::ptr g_splitterPluginInfo = std::make_shared(makeSplitterPluginInfo()); -const Lv2PluginInfo *pipedal::GetSplitterPluginInfo() { return &g_splitterPluginInfo; } +Lv2PluginInfo::ptr pipedal::GetSplitterPluginInfo() { return g_splitterPluginInfo; } int SplitEffect::GetControlIndex(const std::string &symbol) const { diff --git a/src/SplitEffect.hpp b/src/SplitEffect.hpp index 22384cb..5114213 100644 --- a/src/SplitEffect.hpp +++ b/src/SplitEffect.hpp @@ -36,7 +36,7 @@ namespace pipedal class Lv2PluginInfo; class Lv2PortInfo; - const Lv2PluginInfo *GetSplitterPluginInfo(); + std::shared_ptr GetSplitterPluginInfo(); const Lv2PortInfo *GetBypassPortInfo();