Midi Bindings for split controls.

This commit is contained in:
Robin Davies
2024-08-10 10:24:43 -04:00
parent 2328467ee8
commit 31f1b9a84a
13 changed files with 256 additions and 67 deletions
+2 -2
View File
@@ -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/")
+2 -2
View File
@@ -1,12 +1,12 @@
<img src='docs/GithubBanner.png' width="100%" /><br/>
Download: <a href='https://rerdavies.github.io/pipedal/download.html'>v1.2.33 Beta1</a>
Download: <a href='https://rerdavies.github.io/pipedal/download.html'>v1.2.34 Beta1</a>
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.
+3 -3
View File
@@ -4,17 +4,17 @@
Download the most recent Debian (.deb) package for your platform:
- <a href="https://github.com/rerdavies/pipedal/releases/download/v1.2.33/pipedal_1.2.33_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.2.33 Beta1</a>
- <a href="https://github.com/rerdavies/pipedal/releases/download/v1.2.34/pipedal_1.2.34_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.2.34 Beta2</a>
- <a href="https://github.com/rerdavies/pipedal/releases/download/v1.1.31/pipedal_1.1.31_arm64.deb">Ubuntu 21.04 or Raspberry Pi OS Buster (64-bit) v1.1.31</a>
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.
+144 -2
View File
@@ -520,7 +520,10 @@ export class UiControl implements Deserializable<UiControl> {
}
}
return this;
}
applyProperties(properties: Partial<UiControl>): 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<UiControl> {
text += "s";
break;
// Midinote: not handled.
// semitone12TET not handled.
// semitone12TET not handled.
@@ -759,6 +762,7 @@ export class UiControl implements Deserializable<UiControl> {
}
export class UiPlugin implements Deserializable<UiPlugin> {
deserialize(input: any): UiPlugin
{
@@ -802,6 +806,11 @@ export class UiPlugin implements Deserializable<UiPlugin> {
}
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<UiPlugin> {
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,
}
);
}
+8 -3
View File
@@ -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<typeof styles> {
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 (<div />);
}
@@ -258,7 +259,11 @@ const MidiBindingView =
value={midiBinding.switchControlType}
>
<MenuItem value={MidiBinding.LATCH_CONTROL_TYPE}>Toggle</MenuItem>
<MenuItem value={MidiBinding.MOMENTARY_CONTROL_TYPE}>Momentary</MenuItem>
<MenuItem value={MidiBinding.MOMENTARY_CONTROL_TYPE}>{
(midiBinding.bindingType === MidiBinding.BINDING_TYPE_NOTE) ?
"Note on/off"
: "Control value"
}</MenuItem>
</Select>
</div>
))
+70 -24
View File
@@ -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<T>(value: T | null) {
if (!value) throw Error("Unexpected null value");
return value;
}
export interface MidiBindingDialogProps extends WithStyles<typeof styles> {
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(
<tr>
@@ -200,29 +241,33 @@ export const MidiBindingDialog =
</td>
</tr>
);
result.push(
<tr>
<td className={classes.nameTd}>
<Typography noWrap style={{ verticalAlign: "center", height: 48 }}>
Bypass
</Typography>
</td>
<td className={classes.bindingTd}>
<MidiBindingView instanceId={item.instanceId} midiBinding={item.getMidiBinding("__bypass")}
onListen={(instanceId: number, symbol: string, listenForControl: boolean) => {
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)}
/>
</td>
</tr>
);
if (!isSplit)
{
result.push(
<tr>
<td className={classes.nameTd}>
<Typography noWrap style={{ verticalAlign: "center", height: 48 }}>
Bypass
</Typography>
</td>
<td className={classes.bindingTd}>
<MidiBindingView instanceId={item.instanceId} midiBinding={item.getMidiBinding("__bypass")}
uiPlugin={plugin}
onListen={(instanceId: number, symbol: string, listenForControl: boolean) => {
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)}
/>
</td>
</tr>
);
}
for (let i = 0; i < plugin.controls.length; ++i) {
let control = plugin.controls[i];
@@ -244,6 +289,7 @@ export const MidiBindingDialog =
</td>
<td className={classes.bindingTd}>
<MidiBindingView instanceId={item.instanceId}
uiPlugin={plugin}
midiBinding={item.getMidiBinding(symbol)}
listen={item.instanceId === this.state.listenInstanceId && this.state.listenSymbol === symbol}
onListen={(instanceId: number, symbol: string, listenForControl: boolean) => {
+1
View File
@@ -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);
}
+9 -20
View File
@@ -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.
{
+6 -8
View File
@@ -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<Lv2PluginInfo> t = lv2Host.GetPluginInfo(pedalboardItem->uri());
const Lv2PluginInfo *pPlugin = t.get();
if (pPlugin == nullptr)
std::shared_ptr<Lv2PluginInfo> 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)
{
+4
View File
@@ -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<HostWorkerThread> PluginHost::GetHostWorkerThread()
{
return pHostWorkerThread;
+2
View File
@@ -337,8 +337,10 @@ namespace pipedal
friend class PluginHost;
public:
using ptr = std::shared_ptr<Lv2PluginInfo>;
Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *);
Lv2PluginInfo() {}
bool isSplit() const ;
private:
std::shared_ptr<PiPedalUI> FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin *pPlugin);
+4 -2
View File
@@ -33,6 +33,8 @@ static std::shared_ptr<Lv2PortInfo> 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<Lv2PluginInfo>(makeSplitterPluginInfo());
const Lv2PluginInfo *pipedal::GetSplitterPluginInfo() { return &g_splitterPluginInfo; }
Lv2PluginInfo::ptr pipedal::GetSplitterPluginInfo() { return g_splitterPluginInfo; }
int SplitEffect::GetControlIndex(const std::string &symbol) const
{
+1 -1
View File
@@ -36,7 +36,7 @@ namespace pipedal
class Lv2PluginInfo;
class Lv2PortInfo;
const Lv2PluginInfo *GetSplitterPluginInfo();
std::shared_ptr<Lv2PluginInfo> GetSplitterPluginInfo();
const Lv2PortInfo *GetBypassPortInfo();