diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json
index 28cd883..998791f 100644
--- a/.vscode/c_cpp_properties.json
+++ b/.vscode/c_cpp_properties.json
@@ -4,7 +4,10 @@
"name": "Linux",
"includePath": [
"${default}",
- "/usr/include/lilv-0"
+ "${workspaceFolder}",
+ "${workspaceFolder}/src",
+ "/usr/include/lilv-0",
+ "~/src/vst3sdk"
],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c17",
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 8f4a4b8..e151156 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -90,9 +90,8 @@
"span_ext": "cpp",
"string_span": "cpp",
"util": "cpp",
-
-
-
+ "charconv": "cpp",
+ "valarray": "cpp"
},
"cSpell.words": [
"Guitarix",
diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock
index 769b5c6..39f457c 100644
--- a/docs/Gemfile.lock
+++ b/docs/Gemfile.lock
@@ -265,7 +265,7 @@ GEM
thread_safe (0.3.6)
typhoeus (1.4.0)
ethon (>= 0.9.0)
- tzinfo (1.2.9)
+ tzinfo (1.2.10)
thread_safe (~> 0.1)
unf (0.1.4)
unf_ext
diff --git a/lv2/aarch64/ToobAmp.lv2/ToobAmp.so.0.1.7 b/lv2/aarch64/ToobAmp.lv2/ToobAmp.so.0.1.7
index 207df41..4a691c8 100644
Binary files a/lv2/aarch64/ToobAmp.lv2/ToobAmp.so.0.1.7 and b/lv2/aarch64/ToobAmp.lv2/ToobAmp.so.0.1.7 differ
diff --git a/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl b/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl
index b636728..d94421a 100644
--- a/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl
+++ b/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl
@@ -77,7 +77,7 @@ A straighforward no-frills digital delay.
lv2:minimum 0.0 ;
lv2:maximum 100.0 ;
units:unit units:pc;
- rdfs:comment "Attenuation of the first delay";
+ rdfs:comment "Attenuation of the first echo";
], [
a lv2:InputPort ,
@@ -92,7 +92,7 @@ A straighforward no-frills digital delay.
lv2:minimum 0.0 ;
lv2:maximum 100.0 ;
units:unit units:pc;
- rdfs:comment "Attenuation of subequent delays";
+ rdfs:comment "Attenuation of subequent echoes";
],
[
a lv2:AudioPort ,
diff --git a/react/public/img/VST_Logo_Steinberg.ico b/react/public/img/VST_Logo_Steinberg.ico
new file mode 100644
index 0000000..a31b629
Binary files /dev/null and b/react/public/img/VST_Logo_Steinberg.ico differ
diff --git a/react/public/img/VST_Logo_Steinberg.png b/react/public/img/VST_Logo_Steinberg.png
new file mode 100644
index 0000000..a4194d6
Binary files /dev/null and b/react/public/img/VST_Logo_Steinberg.png differ
diff --git a/react/public/img/vst.png b/react/public/img/vst.png
new file mode 100644
index 0000000..637cd4e
Binary files /dev/null and b/react/public/img/vst.png differ
diff --git a/react/public/img/vst.svg b/react/public/img/vst.svg
new file mode 100644
index 0000000..1a473e8
--- /dev/null
+++ b/react/public/img/vst.svg
@@ -0,0 +1,328 @@
+
+
diff --git a/react/public/var/config.json b/react/public/var/config.json
index 80d344a..bf8d4ce 100644
--- a/react/public/var/config.json
+++ b/react/public/var/config.json
@@ -1,5 +1,5 @@
{
- "socket_server_port": 80,
+ "socket_server_port": 8080,
"socket_server_address": "*",
"debug": true,
"max_upload_size": 1048576,
diff --git a/react/src/LoadPluginDialog.tsx b/react/src/LoadPluginDialog.tsx
index 7cd4291..e1ae1ed 100644
--- a/react/src/LoadPluginDialog.tsx
+++ b/react/src/LoadPluginDialog.tsx
@@ -228,6 +228,7 @@ export const LoadPluginDialog =
let gridWidth = width - this.margin_reserve;
let columns = Math.floor((gridWidth) / this.nominal_column_width);
if (columns < 1) columns = 1;
+ if (columns > 3) columns = 3;
return columns;
}
@@ -388,6 +389,14 @@ export const LoadPluginDialog =
// let selectedUri = this.state.selected_uri;
}
+ vst3_indicator(uiPlugin? : UiPlugin)
+ {
+ if (uiPlugin?.is_vst3)
+ {
+ return (
)
+ }
+ return null;
+ }
stereo_indicator(uiPlugin?: UiPlugin): string {
if (!uiPlugin) return "";
if (uiPlugin.audio_inputs === 2 || uiPlugin.audio_outputs === 2) {
@@ -436,7 +445,13 @@ export const LoadPluginDialog =
for (let i = 0; i < plugins.length; ++i) {
let plugin = plugins[i];
if (filterType === PluginType.Plugin || rootClass.is_type_of(filterType, plugin.plugin_type)) {
- let score = searchFilter.score(plugin.name, plugin.plugin_display_type, plugin.author_name);
+ let score:number = 0;
+ if (plugin.is_vst3)
+ {
+ score = searchFilter.score(plugin.name, plugin.plugin_display_type, plugin.author_name,"vst3");
+ } else {
+ score = searchFilter.score(plugin.name, plugin.plugin_display_type, plugin.author_name);
+ }
if (score !== 0) {
if (this.state.favoritesList[plugin.uri]) {
@@ -668,8 +683,9 @@ export const LoadPluginDialog =
- {this.info_string(selectedPlugin)}
+ {this.info_string(selectedPlugin)}
+ {this.vst3_indicator(selectedPlugin)}
{
this.supports_time_position = input.supports_time_position;
this.port_group = input.port_group;
this.comment = input.comment;
+ this.is_bypass = input.is_bypass;
+ this.is_program_controller = input.is_program_controller;
return this;
}
@@ -75,12 +77,17 @@ export class Port implements Deserializable
{
supports_time_position: boolean = false;
port_group: string = "";
comment: string = "";
+ is_bypass: boolean = false;
+ is_program_controller: boolean = false;
}
export class PortGroup {
deserialize(input: any): PortGroup {
this.symbol = input.symbol;
this.name = input.name;
+ this.parent_group = input.parent_group;
+ this.program_list_id = input.program_list_id ?? -1;
+
return this;
}
static deserialize_array(input: any) : PortGroup[] {
@@ -94,6 +101,8 @@ export class PortGroup {
symbol: string = "";
name: string = "";
+ parent_group: string = "";
+ program_list_id: number = -1;
};
export class Lv2Plugin implements Deserializable {
@@ -223,10 +232,19 @@ export class UiControl implements Deserializable {
this.scale_points = ScalePoint.deserialize_array(input.scale_points);
this.port_group = input.port_group;
this.units = input.units as Units;
- this.comment = input.comment;
+
+ this.comment = input.comment ?? "";
+ this.is_bypass = input.is_bypass ? true: false;
+ this.is_program_controller = input.is_program_controller? true: false;
+ this.custom_units = input.custom_units ?? "";
this.controlType = ControlType.Dial;
+ if (this.is_bypass)
+ {
+ this.not_on_gui = true;
+ }
+
if (this.enumeration_property && this.scale_points.length === 2)
{
@@ -234,7 +252,7 @@ export class UiControl implements Deserializable {
} else {
if (this.min_value === 0 && this.max_value === 1)
{
- if (this.toggled_property || this.integer_property)
+ if (this.toggled_property || this.integer_property || this.range_steps === 2)
{
this.controlType = ControlType.OnOffSwitch;
}
@@ -276,6 +294,10 @@ export class UiControl implements Deserializable {
port_group: string = "";
units: Units = Units.none;
comment: string = "";
+ is_bypass: boolean = true;
+ is_program_controller: boolean = true;
+ custom_units: string = "";
+
// Return the value of the closest scale_point.
clampSelectValue(value: number): number{
@@ -452,6 +474,7 @@ export class UiPlugin implements Deserializable {
this.description = input.description;
this.controls = UiControl.deserialize_array(input.controls);
this.port_groups = PortGroup.deserialize_array(input.port_groups);
+ this.is_vst3 = input.is_vst3;
return this;
}
@@ -501,5 +524,6 @@ export class UiPlugin implements Deserializable {
description: string = "";
controls: UiControl[] = [];
port_groups: PortGroup[] = [];
+ is_vst3 : boolean = false;
}
diff --git a/react/src/PedalBoard.tsx b/react/src/PedalBoard.tsx
index 44f7d8f..41deef0 100644
--- a/react/src/PedalBoard.tsx
+++ b/react/src/PedalBoard.tsx
@@ -67,6 +67,7 @@ export class PedalBoardItem implements Deserializable {
this.midiBindings = MidiBinding.deserialize_array(input.midiBindings);
this.controlValues = ControlValue.deserializeArray(input.controlValues);
+ this.vstState = input.vstState ?? "";
return this;
}
deserialize(input: any): PedalBoardItem {
@@ -183,6 +184,7 @@ export class PedalBoardItem implements Deserializable {
pluginName?: string;
controlValues: ControlValue[] = ControlValue.EmptyArray;
midiBindings: MidiBinding[] = [];
+ vstState: string = "";
};
diff --git a/react/src/PedalBoardView.tsx b/react/src/PedalBoardView.tsx
index ea43750..c62c708 100644
--- a/react/src/PedalBoardView.tsx
+++ b/react/src/PedalBoardView.tsx
@@ -62,6 +62,12 @@ const EMPTY_ICON_URL = "img/fx_empty.svg";
const ERROR_ICON_URL = "img/fx_error.svg";
const TERMINAL_ICON_URL = "img/fx_terminal.svg";
+function CalculateConnection(numberOfInputs: number, numberOfOutputs: number)
+{
+ let result = Math.min(numberOfInputs, numberOfOutputs);
+ if (result > 2) result = 2;
+ return result;
+}
const pedalBoardStyles = (theme: Theme) => createStyles({
scrollContainer: {
@@ -210,10 +216,9 @@ class PedalLayout {
bounds: Rect = new Rect();
- inputs: number = 0;
- outputs: number = 0;
- stereoInput: boolean = false;
- stereoOutput: boolean = false;
+ numberOfInputs: number = 2;
+ numberOfOutputs: number = 2;
+
pedalItem?: PedalBoardItem;
// Split Layout only.
@@ -227,8 +232,8 @@ class PedalLayout {
let t: PedalLayout = new PedalLayout();
t.uri = START_PEDALBOARD_ITEM_URI;
t.iconUrl = TERMINAL_ICON_URL;
- t.inputs = 0;
- t.outputs = 1;
+ t.numberOfInputs = 0;
+ t.numberOfOutputs = 2;
return t;
}
static End(): PedalLayout {
@@ -236,8 +241,8 @@ class PedalLayout {
t.pluginType = PluginType.UtilityPlugin;
t.uri = END_PEDALBOARD_ITEM_URI;
t.iconUrl = TERMINAL_ICON_URL;
- t.inputs = 2;
- t.outputs = 0;
+ t.numberOfInputs = 2;
+ t.numberOfOutputs = 0;
return t;
}
constructor(model?: PiPedalModel, pedalItem?: PedalBoardItem) {
@@ -256,27 +261,27 @@ class PedalLayout {
this.topChildren = makeChain(model, splitter.topChain);
this.bottomChildren = makeChain(model, splitter.bottomChain);
- this.inputs = 2;
- this.outputs = 2;
+ this.numberOfInputs = 2;
+ this.numberOfOutputs = 2;
} else if (pedalItem.isEmpty()) {
this.pluginType = PluginType.UtilityPlugin;
this.iconUrl = EMPTY_ICON_URL;
- this.inputs = 2;
- this.outputs = 2;
+ this.numberOfInputs = 2;
+ this.numberOfOutputs = 2;
} else if (pedalItem.uri === START_PEDALBOARD_ITEM_URI) {
this.pluginType = PluginType.UtilityPlugin;
this.iconUrl = TERMINAL_ICON_URL;
- this.inputs = 0;
- this.outputs = PiPedalModelFactory.getInstance().jackSettings.get().inputAudioPorts.length;
+ this.numberOfInputs = 0;
+ this.numberOfOutputs = PiPedalModelFactory.getInstance().jackSettings.get().inputAudioPorts.length;
} else if (pedalItem.uri === END_PEDALBOARD_ITEM_URI) {
this.pluginType = PluginType.UtilityPlugin;
this.iconUrl = TERMINAL_ICON_URL;
- this.inputs = PiPedalModelFactory.getInstance().jackSettings.get().outputAudioPorts.length;
- this.outputs = 0;
+ this.numberOfInputs = PiPedalModelFactory.getInstance().jackSettings.get().outputAudioPorts.length;
+ this.numberOfOutputs = 0;
}
else {
let uiPlugin = model.getUiPlugin(pedalItem.uri);
@@ -284,15 +289,15 @@ class PedalLayout {
this.pluginType = uiPlugin.plugin_type;
this.iconUrl = SelectIcon(uiPlugin.plugin_type,uiPlugin.uri);
this.name = uiPlugin.name;
- this.inputs = uiPlugin.audio_inputs;
- this.outputs = uiPlugin.audio_outputs;
+ this.numberOfInputs = Math.max(uiPlugin.audio_inputs,2);
+ this.numberOfOutputs = Math.max(uiPlugin.audio_outputs,2);
} else {
// default to empty plugin.
this.pluginType = PluginType.UtilityPlugin;
this.name = pedalItem.pluginName??"#error";
this.iconUrl = ERROR_ICON_URL;
- this.inputs = 2;
- this.outputs = 2;
+ this.numberOfInputs = 2;
+ this.numberOfOutputs = 2;
}
}
}
@@ -677,18 +682,18 @@ const PedalBoardView =
// let classes = this.props.classes;
let x_ = item.bounds.x + CELL_WIDTH / 2;
let y_ = item.bounds.y + CELL_HEIGHT / 2;
- let isStereo = item.stereoOutput;
+ let numberOfOutputs = item.numberOfOutputs;
let color = enabled ? ENABLED_CONNECTOR_COLOR : DISABLED_CONNECTOR_COLOR;
let svgPath = new SvgPathBuilder().moveTo(x_, y_).lineTo(x_ + CELL_WIDTH, y_).toString();
- if (isStereo) {
+ if (numberOfOutputs === 2) {
output.push((
));
output.push((
));
- } else {
+ } else if (numberOfOutputs === 1) {
output.push((
));
@@ -712,18 +717,18 @@ const PedalBoardView =
let topStartPath = new SvgPathBuilder().moveTo(x_, y_).lineTo(x_, yTop).lineTo(x_ + CELL_WIDTH, yTop).toString();
let bottomStartPath = new SvgPathBuilder().moveTo(x_, y_).lineTo(x_, yBottom).lineTo(x_ + CELL_WIDTH, yBottom).toString();
- if (item.stereoInput && item.topChildren[0].stereoInput) {
+ if (item.numberOfInputs === 2 && item.topChildren[0].numberOfInputs === 2) {
output.push(());
output.push(());
- } else {
+ } else if (item.numberOfInputs !== 0 && item.topChildren[0].numberOfInputs !== 0) {
output.push(());
}
- if (item.stereoInput && item.bottomChildren[0].stereoInput) {
+ if (item.numberOfInputs === 2 && item.bottomChildren[0].numberOfInputs === 2) {
output.push(());
output.push(());
- } else {
+ } else if (item.numberOfInputs !== 0 && item.bottomChildren[0].numberOfInputs !== 0) {
output.push(());
}
@@ -741,6 +746,8 @@ const PedalBoardView =
let firstPathStereo: boolean;
let secondPathStereo: boolean;
+ let firstPathAbsent: boolean;
+ let secondPathAbsent: boolean;
let firstPathEnabled: boolean;
let secondPathEnabled: boolean;
let xTee: number;
@@ -753,11 +760,13 @@ const PedalBoardView =
// Third case: L/R stereo output, when both outputs are mono, requires a third stroke.
let thirdPath: string | null = null; // for L/R stereo output (which can be stereo even if both outputs are mono)
- let hasThirdPath = item.stereoOutput && (!lastTop.stereoOutput) && (!lastBottom.stereoOutput);
+ let hasThirdPath = item.numberOfOutputs === 2 && (lastTop.numberOfOutputs !== 2) && (lastBottom.numberOfOutputs !== 2);
if (hasThirdPath) {
firstPathStereo = false;
secondPathStereo = false;
+ firstPathAbsent = lastTop.numberOfOutputs === 0 || item.numberOfOutputs === 0;
+ secondPathAbsent = lastBottom.numberOfOutputs === 0 || item.numberOfOutputs === 0;
xTee = xTee0 - monoAdjustment;
firstPath = new SvgPathBuilder().moveTo(xBottom, yBottom).lineTo(xTee, yBottom).lineTo(xTee, y_).toString();
@@ -767,10 +776,13 @@ const PedalBoardView =
thirdPath= new SvgPathBuilder().moveTo(xTee0,y_).lineTo(xEnd,y_).toString();
firstPathEnabled = bottomEnabled;
secondPathEnabled = topEnabled;
- } else if (bottomPathFirst || (topEnabled && lastTop.stereoOutput)) {
+ } else if (bottomPathFirst || (topEnabled && lastTop.numberOfOutputs === 2)) {
// draw the bottom path first.
- firstPathStereo = item.stereoOutput && lastBottom.stereoOutput;
- secondPathStereo = item.stereoOutput && lastTop.stereoOutput;
+ firstPathStereo = item.numberOfOutputs === 2 && lastBottom.numberOfOutputs === 2;
+ secondPathStereo = item.numberOfOutputs === 2 && lastTop.numberOfOutputs === 2;
+ firstPathAbsent = item.numberOfOutputs === 0 || lastBottom.numberOfOutputs === 0;
+ secondPathAbsent = item.numberOfOutputs === 0 || lastTop.numberOfOutputs === 0;
+
xTee = firstPathStereo ? xTee0 : xTee0 - monoAdjustment;
firstPath = new SvgPathBuilder().moveTo(xBottom, yBottom).lineTo(xTee, yBottom).lineTo(xTee, y_).toString();
xTee = secondPathStereo ? xTee0 : xTee0 - monoAdjustment;
@@ -780,8 +792,10 @@ const PedalBoardView =
secondPathEnabled = topEnabled;
} else {
// draw the top path first.
- firstPathStereo = item.stereoOutput && lastTop.stereoOutput;
- secondPathStereo = item.stereoOutput && lastBottom.stereoOutput;
+ firstPathStereo = item.numberOfOutputs === 2 && lastTop.numberOfOutputs === 2;
+ secondPathStereo = item.numberOfOutputs === 2 && lastBottom.numberOfOutputs === 2;
+ firstPathAbsent = item.numberOfOutputs === 0 || lastTop.numberOfOutputs === 0;
+ secondPathAbsent = item.numberOfOutputs === 0 || lastBottom.numberOfOutputs === 0;
xTee = firstPathStereo ? xTee0 : xTee0 - monoAdjustment;
firstPath = new SvgPathBuilder().moveTo(xTop, yTop).lineTo(xTee, yTop).lineTo(xTee, y_).toString();
@@ -804,7 +818,7 @@ const PedalBoardView =
output.push((
));
- } else {
+ } else if (!firstPathAbsent) {
output.push((
));
@@ -816,7 +830,7 @@ const PedalBoardView =
output.push((
));
- } else {
+ } else if (!secondPathAbsent) {
output.push((
));
@@ -828,7 +842,7 @@ const PedalBoardView =
output.push((
));
- } else {
+ } else if (!firstPathAbsent) {
output.push((
));
@@ -837,7 +851,7 @@ const PedalBoardView =
output.push((
));
- } else {
+ } else if (!secondPathAbsent) {
output.push((
));
@@ -1011,126 +1025,130 @@ const PedalBoardView =
}
return true;
}
- canOutputStereo(item: PedalLayout): boolean {
+ getNumberOfInputs(item: PedalLayout): number {
if (item.pedalItem) {
let plugin = this.model.getUiPlugin(item.pedalItem.uri);
if (plugin) {
- return plugin.audio_outputs === 2;
+ return plugin.audio_inputs;
}
}
- return true;
+ return 1;
+ }
+ getNumberOfOutputs(item: PedalLayout): number {
+ if (item.pedalItem) {
+ let plugin = this.model.getUiPlugin(item.pedalItem.uri);
+ if (plugin) {
+ return plugin.audio_outputs;
+ }
+ }
+ return 1;
}
- markStereoOutputs(layoutChain: PedalLayout[], stereoInput: boolean, stereoOutput: boolean) {
+ markStereoOutputs(layoutChain: PedalLayout[], numberOfInputs: number, numberOfOutputs: number) {
// analyze forward flow.
- this.markStereoForward(layoutChain, stereoInput);
+ this.markStereoForward(layoutChain, numberOfInputs);
// mark items that feed a mono effect as mono.
- this.markStereoBackward(layoutChain, stereoOutput);
+ this.markStereoBackward(layoutChain, numberOfOutputs);
}
- markStereoBackward(layoutChain: PedalLayout[], stereoOutput: boolean) : boolean
+ markStereoBackward(layoutChain: PedalLayout[], numberOfOutputs: number) : number
{
for (let i = layoutChain.length-1; i >= 0; --i)
{
let item = layoutChain[i];
if (item.isSplitter())
{
- if (!stereoOutput)
- {
- item.stereoOutput = false;
- }
- this.markStereoBackward(item.topChildren,stereoOutput);
- this.markStereoBackward(item.bottomChildren,stereoOutput);
- let topStereoInput = item.topChildren[0].stereoInput;
- let bottomStereoInput = item.bottomChildren[0].stereoInput;
+ item.numberOfOutputs = CalculateConnection(item.numberOfOutputs, numberOfOutputs)
- if (! (topStereoInput || bottomStereoInput))
+ this.markStereoBackward(item.topChildren,numberOfOutputs);
+ this.markStereoBackward(item.bottomChildren,numberOfOutputs);
+ let topInputs = item.topChildren[0].numberOfInputs;
+ let bottomInputs = item.bottomChildren[0].numberOfInputs;
+
+ let splitItem = item.pedalItem as PedalBoardSplitItem;
+ if (splitItem.getSplitType() !== SplitType.Lr)
{
- let splitItem = item.pedalItem as PedalBoardSplitItem;
- if (splitItem.getSplitType() !== SplitType.Lr)
- {
- item.stereoInput = false;
- }
+ item.numberOfInputs = CalculateConnection(item.numberOfInputs, Math.max(topInputs,bottomInputs));
}
} else if (item.isEnd())
{
} else if (item.isStart())
{
- if (!stereoOutput)
- {
- item.stereoOutput = false;
- }
- return item.stereoOutput;
+ item.numberOfOutputs = CalculateConnection(item.numberOfOutputs,numberOfOutputs);
+ return item.numberOfOutputs;
} else if (item.isEmpty()) {
- if (!stereoOutput)
+ if (numberOfOutputs === 0)
{
- item.stereoOutput = false;
- item.stereoInput = false;
+ item.numberOfOutputs = 0;
+ item.numberOfInputs = CalculateConnection(item.numberOfInputs,2);
+ } else {
+ item.numberOfOutputs = CalculateConnection(item.numberOfOutputs,numberOfOutputs);
+ item.numberOfInputs = CalculateConnection(item.numberOfInputs,numberOfOutputs);
}
} else {
- if (!stereoOutput)
- {
- item.stereoOutput = false;
- }
+ item.numberOfOutputs = CalculateConnection(item.numberOfOutputs,numberOfOutputs);
}
- stereoOutput = item.stereoInput;
+ numberOfOutputs = item.numberOfInputs;
}
- return stereoOutput;
+ return numberOfOutputs;
}
- markStereoForward(layoutChain: PedalLayout[],stereoInput: boolean): boolean
+ markStereoForward(layoutChain: PedalLayout[],numberOfInputs: number): number
{
if (layoutChain.length === 0) {
- return stereoInput;
+ return numberOfInputs;
}
for (let i = 0; i < layoutChain.length; ++i) {
let item = layoutChain[i];
if (item.isSplitter()) {
let splitter = item.pedalItem as PedalBoardSplitItem;
- item.stereoInput = stereoInput;
+ item.numberOfInputs = numberOfInputs;
- let isChainInputStereo = stereoInput;
+ let chainInputs = numberOfInputs;
if (splitter.getSplitType() === SplitType.Lr)
{
- isChainInputStereo = false;
+ chainInputs = CalculateConnection(numberOfInputs,1);
}
-
- let topStereo = this.markStereoForward(item.topChildren, isChainInputStereo);
- let bottomStereo = this.markStereoForward(item.bottomChildren, isChainInputStereo);
+ let topOutputs = this.markStereoForward(item.topChildren, chainInputs);
+ let bottomOutputs = this.markStereoForward(item.bottomChildren, chainInputs);
if (splitter.getSplitType() === SplitType.Ab) {
if (splitter.isASelected()) {
- item.stereoOutput = topStereo;
+ item.numberOfOutputs = topOutputs;
} else {
- item.stereoOutput = bottomStereo;
+ item.numberOfOutputs = bottomOutputs;
}
} else if (splitter.getSplitType() === SplitType.Lr) {
- item.stereoOutput = true;
+ item.numberOfOutputs = (topOutputs !== 0 && bottomOutputs !== 0) ? 2: 0;
} else {
- item.stereoOutput = topStereo || bottomStereo;
+ item.numberOfOutputs = CalculateConnection(topOutputs,bottomOutputs);
}
} else if (item.isStart())
{
- item.stereoOutput = (PiPedalModelFactory.getInstance().jackSettings.get().inputAudioPorts.length > 1);
+ item.numberOfOutputs = Math.min(PiPedalModelFactory.getInstance().jackSettings.get().inputAudioPorts.length,2);
} else if (item.isEnd()) {
- item.stereoInput = (PiPedalModelFactory.getInstance().jackSettings.get().outputAudioPorts.length > 1) && stereoInput;
- return item.stereoInput;
+ item.numberOfInputs =
+ CalculateConnection(
+ Math.min(PiPedalModelFactory.getInstance().jackSettings.get().outputAudioPorts.length,2),
+ numberOfInputs);
+ return item.numberOfInputs;
} else if (item.isEmpty()) {
- item.stereoInput = stereoInput;
- item.stereoOutput = stereoInput;
- } else {
- if (stereoInput && this.canOutputStereo(item)) {
- item.stereoInput = true;
+ item.numberOfInputs = numberOfInputs;
+ if (numberOfInputs === 0)
+ {
+ item.numberOfOutputs = 2;
} else {
- item.stereoInput = false;
+ item.numberOfOutputs = item.numberOfInputs;
}
- item.stereoOutput = this.canOutputStereo(item);
+ } else {
+ item.numberOfInputs = CalculateConnection(numberOfInputs,this.getNumberOfInputs(item));
+ item.numberOfOutputs = this.getNumberOfOutputs(item);
}
- stereoInput = item.stereoOutput;
+ numberOfInputs = item.numberOfOutputs;
}
- return stereoInput;
+ return numberOfInputs;
}
currentLayout?: PedalLayout[];
@@ -1145,7 +1163,7 @@ const PedalBoardView =
{
layoutChain.splice(0, 0, start);
layoutChain.splice(layoutChain.length, 0, end);
- this.markStereoOutputs(layoutChain, true, true);
+ this.markStereoOutputs(layoutChain, 2,2);
}
let layoutSize = this.doLayout(layoutChain);
diff --git a/react/src/PiPedalModel.tsx b/react/src/PiPedalModel.tsx
index ea30295..e7b9654 100644
--- a/react/src/PiPedalModel.tsx
+++ b/react/src/PiPedalModel.tsx
@@ -22,7 +22,7 @@ import { UiPlugin, UiControl, PluginType } from './Lv2Plugin';
import { PiPedalArgumentError, PiPedalStateError } from './PiPedalError';
import { ObservableProperty } from './ObservableProperty';
-import { PedalBoard, ControlValue } from './PedalBoard'
+import { PedalBoard, PedalBoardItem, ControlValue } from './PedalBoard'
import PluginClass from './PluginClass';
import PiPedalSocket, { PiPedalMessageHeader } from './PiPedalSocket';
import { nullCast } from './Utility'
@@ -303,6 +303,13 @@ interface ControlChangedBody {
symbol: string;
value: number;
};
+interface Vst3ControlChangedBody {
+ clientId: number;
+ instanceId: number;
+ symbol: string;
+ value: number;
+ state: string;
+};
export interface FavoritesList {
[url: string]: boolean;
@@ -634,6 +641,16 @@ class PiPedalModelImpl implements PiPedalModel {
controlChangedBody.value,
false // do NOT notify the server of the change.
);
+ } else if (message === "onVst3ControlChanged") {
+ let controlChangedBody = body as Vst3ControlChangedBody;
+ this._setVst3PedalBoardControlValue(
+ controlChangedBody.instanceId,
+ controlChangedBody.symbol,
+ controlChangedBody.value,
+ controlChangedBody.state,
+ false // do NOT notify the server of the change.
+ );
+
} else if (message === "onJackServerSettingsChanged") {
let jackServerSettings = new JackServerSettings().deserialize(body);
this.jackServerSettings.set(jackServerSettings);
@@ -1195,6 +1212,29 @@ class PiPedalModelImpl implements PiPedalModel {
}
}
+ }
+ _setVst3PedalBoardControlValue(instanceId: number, key: string, value: number, state: string, notifyServer: boolean ): void {
+ let pedalBoard = this.pedalBoard.get();
+ if (pedalBoard === undefined) throw new PiPedalStateError("Pedalboard not ready.");
+ let newPedalBoard = pedalBoard.clone();
+
+ let item: PedalBoardItem = newPedalBoard.getItem(instanceId);
+ let changed = item.setControlValue(key, value);
+ item.vstState = state;
+
+ if (changed) {
+ this.pedalBoard.set(newPedalBoard);
+ if (notifyServer) {
+ this._setServerControl("setControl", instanceId, key, value);
+ }
+ for (let i = 0; i < this._controlValueChangeItems.length; ++i) {
+ let item = this._controlValueChangeItems[i];
+ if (instanceId === item.instanceId) {
+ item.onValueChanged(key, value);
+ }
+ }
+ }
+
}
setPedalBoardControlValue(instanceId: number, key: string, value: number): void {
@@ -1232,7 +1272,7 @@ class PiPedalModelImpl implements PiPedalModel {
clientId: this.clientId,
pedalBoard: this.pedalBoard.get()
};
- this.webSocket?.send("setCurrentPedalBoard", body);
+ this.webSocket?.send("updateCurrentPedalBoard", body);
}
@@ -1288,6 +1328,8 @@ class PiPedalModelImpl implements PiPedalModel {
deletePedalBoardPedal(instanceId: number): number | null {
let pedalBoard = this.pedalBoard.get();
let newPedalBoard = pedalBoard.clone();
+ this.updateVst3State(newPedalBoard);
+
let result = newPedalBoard.deleteItem(instanceId);
if (result != null) {
this.pedalBoard.set(newPedalBoard);
@@ -1301,6 +1343,9 @@ class PiPedalModelImpl implements PiPedalModel {
if (fromInstanceId === toInstanceId) return;
let pedalBoard = this.pedalBoard.get();
let newPedalBoard = pedalBoard.clone();
+
+ this.updateVst3State(newPedalBoard);
+
let fromItem = newPedalBoard.getItem(fromInstanceId);
if (fromItem === null) {
throw new PiPedalArgumentError("fromInstanceId not found.");
@@ -1317,6 +1362,9 @@ class PiPedalModelImpl implements PiPedalModel {
if (fromInstanceId === toInstanceId) return;
let pedalBoard = this.pedalBoard.get();
let newPedalBoard = pedalBoard.clone();
+
+ this.updateVst3State(newPedalBoard);
+
let fromItem = newPedalBoard.getItem(fromInstanceId);
if (fromItem === null) {
throw new PiPedalArgumentError("fromInstanceId not found.");
@@ -1334,6 +1382,10 @@ class PiPedalModelImpl implements PiPedalModel {
let pedalBoard = this.pedalBoard.get();
let newPedalBoard = pedalBoard.clone();
+
+ this.updateVst3State(newPedalBoard);
+
+
let fromItem = newPedalBoard.getItem(instanceId);
if (fromItem === null) {
throw new PiPedalArgumentError("fromInstanceId not found.");
@@ -1350,6 +1402,9 @@ class PiPedalModelImpl implements PiPedalModel {
let pedalBoard = this.pedalBoard.get();
let newPedalBoard = pedalBoard.clone();
+ this.updateVst3State(newPedalBoard);
+
+
let fromItem = newPedalBoard.getItem(instanceId);
if (fromItem === null) {
throw new PiPedalArgumentError("fromInstanceId not found.");
@@ -1371,6 +1426,9 @@ class PiPedalModelImpl implements PiPedalModel {
let pedalBoard = this.pedalBoard.get();
let newPedalBoard = pedalBoard.clone();
+ this.updateVst3State(newPedalBoard);
+
+
let fromItem = newPedalBoard.getItem(fromInstanceId);
let toItem = newPedalBoard.getItem(toInstanceId);
if (fromItem === null) {
@@ -1389,6 +1447,9 @@ class PiPedalModelImpl implements PiPedalModel {
addPedalBoardItem(instanceId: number, append: boolean): number {
let pedalBoard = this.pedalBoard.get();
let newPedalBoard = pedalBoard.clone();
+ this.updateVst3State(newPedalBoard);
+
+
let item = newPedalBoard.getItem(instanceId);
if (item === null) {
throw new PiPedalArgumentError("instanceId not found.");
@@ -1402,6 +1463,8 @@ class PiPedalModelImpl implements PiPedalModel {
addPedalBoardSplitItem(instanceId: number, append: boolean): number {
let pedalBoard = this.pedalBoard.get();
let newPedalBoard = pedalBoard.clone();
+ this.updateVst3State(newPedalBoard);
+
let item = newPedalBoard.getItem(instanceId);
if (item === null) {
throw new PiPedalArgumentError("instanceId not found.");
@@ -1416,6 +1479,9 @@ class PiPedalModelImpl implements PiPedalModel {
setPedalBoardItemEmpty(instanceId: number): number {
let pedalBoard = this.pedalBoard.get();
let newPedalBoard = pedalBoard.clone();
+ this.updateVst3State(newPedalBoard);
+
+
let item = newPedalBoard.getItem(instanceId);
if (item === null) {
throw new PiPedalArgumentError("instanceId not found.");
@@ -1768,12 +1834,24 @@ class PiPedalModelImpl implements PiPedalModel {
this.showAlert(error);
});
}
+
+ updateVst3State(pedalBoard: PedalBoard)
+ {
+ // let it = pedalBoard.itemsGenerator();
+ // while (true) {
+ // let v = it.next();
+ // if (v.done) break;
+ // let item = v.value;
+ // }
+ }
+
setMidiBinding(instanceId: number, midiBinding: MidiBinding): void {
let pedalBoard = this.pedalBoard.get();
if (!pedalBoard) {
throw new PiPedalStateError("Pedalboard not loaded.");
}
let newPedalBoard = pedalBoard.clone();
+ this.updateVst3State(newPedalBoard);
if (newPedalBoard.setMidiBinding(instanceId, midiBinding)) {
this.pedalBoard.set(newPedalBoard);
// notify the server.
diff --git a/react/src/PluginControl.tsx b/react/src/PluginControl.tsx
index 5ebe0c3..b5c5d70 100644
--- a/react/src/PluginControl.tsx
+++ b/react/src/PluginControl.tsx
@@ -603,17 +603,23 @@ const PluginControl =
let uiControl = this.props.uiControl;
if (uiControl) {
if (uiControl.range_steps > 1) {
- range = Math.round(range * uiControl.range_steps-1) / (uiControl.range_steps-1);
+ range = Math.round(range * (uiControl.range_steps-1)) / (uiControl.range_steps-1);
}
let value: number;
- if (uiControl.is_logarithmic)
+ if (uiControl.min_value === uiControl.max_value)
{
- value = uiControl.min_value*Math.pow(uiControl.max_value/uiControl.min_value,range);
- } else {
- value = range * (uiControl.max_value - uiControl.min_value) + uiControl.min_value;
- }
- if (uiControl.integer_property) {
- value = Math.round(value);
+ value = uiControl.min_value;
+ } else
+ {
+ if (uiControl.is_logarithmic)
+ {
+ value = uiControl.min_value*Math.pow(uiControl.max_value/uiControl.min_value,range);
+ } else {
+ value = range * (uiControl.max_value - uiControl.min_value) + uiControl.min_value;
+ }
+ if (uiControl.integer_property) {
+ value = Math.round(value);
+ }
}
return value;
}
diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp
index 8ac7e9e..5cc9be8 100644
--- a/src/AlsaDriver.cpp
+++ b/src/AlsaDriver.cpp
@@ -29,6 +29,7 @@
#include "AlsaDriver.hpp"
#include "JackServerSettings.hpp"
#include
+#include "RtInversionGuard.hpp"
#include "CpuUse.hpp"
@@ -967,7 +968,7 @@ namespace pipedal
struct sched_param param;
memset(¶m, 0, sizeof(param));
- param.sched_priority = 79;
+ param.sched_priority = RT_THREAD_PRIORITY;
int result = sched_setscheduler(0, SCHED_RR, ¶m);
if (result == 0)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 75725f0..946cfc7 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -7,6 +7,37 @@ set (USE_PCH 1)
include(FindPkgConfig)
+#################################################################
+# ENABLE/DISABLE VST3 Support.
+# Disabled, pending Steinberg licensing agreement implementation
+# and approval..
+# Do not enable unless you have non-GPL3 access to the Steinberg
+# SDK.
+#################################################################
+
+set (ENABLE_VST3 0)
+
+
+if (ENABLE_VST3)
+ set (VST3PATH "../../vst3sdk")
+ message(STATUS " VST3 Support Enabled")
+ set (VST3_INCLUDES "${VST3PATH}")
+
+ if (CMAKE_BUILD_TYPE MATCHES Debug)
+ add_compile_definitions("DEVELOPMENT")
+ else()
+ add_compile_definitions("RELEASE")
+ endif()
+ add_compile_definitions("ENABLE_VST3" )
+ #add_compile_definitions("_GLIBCXX_USE_CXX11_ABI")
+
+else()
+ message(STATUS " VST3 Support Disabled")
+ set (VST3_INCLUDES "")
+endif()
+
+########### VST 3 SUPPORT #############################################3
+
# Can't get the pkg_check to work.
@@ -60,19 +91,27 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set (USE_SANITIZE False)
+
+if (!ENABLE_VST3)
+ set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wno-psabi")
+else()
+ set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-multichar -Wno-psabi")
+endif()
+
+
if (CMAKE_BUILD_TYPE MATCHES Debug)
message(STATUS "Debug build")
- set (CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-Wno-psabi -O0 -DDEBUG -Werror" )
+ set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -DDEBUG" )
if (USE_SANITIZE)
- set (CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-fsanitize=address -static-libasan " )
+ set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -static-libasan " )
endif()
elseif(CMAKE_BUILD_TYPE MATCHES RelWithDebInfo)
message(STATUS "RelWithgDebInfo build")
- set (CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-Wno-psabi -Werror" )
+ #set (CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "" )
else()
message(STATUS "Release build")
- set (CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-Wno-psabi -Werror" )
+ #set (CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "" )
endif()
@@ -84,8 +123,19 @@ message (STATUS "Cxx flags: ${CMAKE_CXX_FLAGS}" )
add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY -DBOOST_BIND_GLOBAL_PLACEHOLDERS -DONBOARDING)
+if (VST3PATH)
+set (VST3_SOURCES
+ vst3/Vst3Effect.hpp Vst3Effect.cpp
+ vst3/Vst3Host.hpp Vst3Host.cpp
+ Vst3MidiToEvent.hpp
+ vst3/Vst3RtStream.hpp Vst3RtStream.cpp
+ )
+else()
+ set (VST3_SOURCES)
+endif()
set (PIPEDAL_SOURCES
+ RtInversionGuard.hpp
CpuUse.hpp CpuUse.cpp
P2pConfigFiles.hpp
AvahiService.cpp AvahiService.hpp
@@ -104,7 +154,7 @@ set (PIPEDAL_SOURCES
WifiDirectConfigSettings.hpp WifiDirectConfigSettings.cpp
ConfigUtil.hpp ConfigUtil.cpp
- RequestHandler.hpp json.cpp json.hpp Scratch.cpp Lv2Host.hpp Lv2Host.cpp
+ RequestHandler.hpp json.cpp json.hpp Scratch.cpp PiPedalHost.hpp PiPedalHost.cpp
PluginType.hpp PluginType.cpp
Lv2Log.hpp Lv2Log.cpp
PiPedalSocket.hpp PiPedalSocket.cpp
@@ -152,6 +202,7 @@ set (PIPEDAL_SOURCES
AudioDriver.hpp
AudioConfig.hpp
+ ${VST3_SOURCES}
)
@@ -161,16 +212,25 @@ include_directories( ${pipedald_SOURCE_DIR}/. ../build/src)
set (PIPEDAL_INCLUDES
${LV2DEV_INCLUDE_DIRS}
${JACK_INCLUDE_DIRS} ${LILV_0_INCLUDE_DIRS} ${LIBNL3_INCLUDE_DIRS} ${LVDEV_INCLUDE_DIRS}
+ ${VST3_INCLUDES}
+ .
)
-set(PIPEDAL_LIBS libpipedald pthread atomic stdc++fs asound avahi-common avahi-client systemd
+set(PIPEDAL_LIBS libpipedald
+ pthread atomic stdc++fs asound avahi-common avahi-client systemd
${LILV_0_LIBRARIES}
# ${JACK_LIBRARIES}
- ${LIBNL3_LIBRARIES} )
+ ${LIBNL3_LIBRARIES}
+ )
+
+if (VST3_ENABLED)
+
+ set (PIPEDAL_LIBS vst3_lib ${PIPEDAL_LIBS})
+endif()
##########################
-
+
add_library(libpipedald STATIC ${PIPEDAL_SOURCES})
target_include_directories(libpipedald PRIVATE ${PIPEDAL_INCLUDES}
@@ -226,6 +286,217 @@ target_link_libraries(pipedaltest PRIVATE ${PIPEDAL_LIBS}
)
+#########################################################
+# VST3 Source.
+#
+# VST3 support is optional, because it poses unfortunate
+# licensing problems.
+
+# There's no easy way to get the VST SDK to build without
+# GCC c++11 linkage, which is not compatible with pipedal's
+# use of C++20 features (strictly a link time breakage).
+#
+# So pipedal rebuilds the VST3 SDK using pipedal-compatible
+# compile flags.
+#
+# The current build supports only Linux builds. Refer to the
+# original VST3 SDK build for details on how to build
+# for other platforms.
+#
+#########################################################
+if(ENABLE_VST3)
+
+ set (VST3_SRCPATH "${VST3PATH}/public.sdk/source")
+
+ set (VST3_BASEPATH "${VST3PATH}/base")
+ set (VST3_PLUGININTERFACES_BASE_PATH "${VST3PATH}/pluginterfaces/base")
+
+ set(vst3_sources
+
+
+ ${VST3_SRCPATH}/vst/vstpresetfile.cpp
+ ${VST3_SRCPATH}/vst/vstpresetfile.h
+
+ # base files.
+
+
+ ${VST3_BASEPATH}/source/baseiids.cpp
+ ${VST3_BASEPATH}/source/classfactoryhelpers.h
+ ${VST3_BASEPATH}/source/fbuffer.cpp
+ ${VST3_BASEPATH}/source/fbuffer.h
+ ${VST3_BASEPATH}/source/fcleanup.h
+ ${VST3_BASEPATH}/source/fcommandline.h
+ ${VST3_BASEPATH}/source/fdebug.cpp
+ ${VST3_BASEPATH}/source/fdebug.h
+ ${VST3_BASEPATH}/source/fdynlib.cpp
+ ${VST3_BASEPATH}/source/fdynlib.h
+ ${VST3_BASEPATH}/source/fobject.cpp
+ ${VST3_BASEPATH}/source/fobject.h
+ ${VST3_BASEPATH}/source/fstreamer.cpp
+ ${VST3_BASEPATH}/source/fstreamer.h
+ ${VST3_BASEPATH}/source/fstring.cpp
+ ${VST3_BASEPATH}/source/fstring.h
+ ${VST3_BASEPATH}/source/timer.cpp
+ ${VST3_BASEPATH}/source/timer.h
+ ${VST3_BASEPATH}/source/updatehandler.cpp
+ ${VST3_BASEPATH}/source/updatehandler.h
+
+ ${VST3_BASEPATH}/thread/include/fcondition.h
+ ${VST3_BASEPATH}/thread/include/flock.h
+ ${VST3_BASEPATH}/thread/source/fcondition.cpp
+ ${VST3_BASEPATH}/thread/source/flock.cpp
+
+
+
+ # sdk_common files.
+ ${VST3_SRCPATH}/common/commoniids.cpp
+ ${VST3_SRCPATH}/common/openurl.cpp
+ ${VST3_SRCPATH}/common/openurl.h
+ ${VST3_SRCPATH}/common/systemclipboard.h
+ ${VST3_SRCPATH}/common/systemclipboard_win32.cpp
+ ${VST3_SRCPATH}/common/threadchecker_linux.cpp
+ ${VST3_SRCPATH}/common/threadchecker_win32.cpp
+ ${VST3_SRCPATH}/common/threadchecker.h
+ ${VST3_SRCPATH}/common/memorystream.cpp
+ ${VST3_SRCPATH}/common/memorystream.h
+
+ # pluginterfaces
+ ${VST3_PLUGININTERFACES_BASE_PATH}/conststringtable.cpp
+ ${VST3_PLUGININTERFACES_BASE_PATH}/conststringtable.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/coreiids.cpp
+ ${VST3_PLUGININTERFACES_BASE_PATH}/falignpop.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/falignpush.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/fplatform.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/fstrdefs.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/ftypes.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/funknown.cpp
+ ${VST3_PLUGININTERFACES_BASE_PATH}/funknown.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/funknownimpl.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/futils.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/fvariant.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/geoconstants.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/ibstream.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/icloneable.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/ierrorcontext.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/ipersistent.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/ipluginbase.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/istringresult.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/iupdatehandler.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/keycodes.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/pluginbasefwd.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/smartpointer.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/typesizecheck.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/ucolorspec.h
+ ${VST3_PLUGININTERFACES_BASE_PATH}/ustring.cpp
+ ${VST3_PLUGININTERFACES_BASE_PATH}/ustring.h
+
+
+ # sdk files.
+ # ${VST3_SRCPATH}/main/moduleinit.cpp
+ # ${VST3_SRCPATH}/main/moduleinit.h
+ ${VST3_SRCPATH}/vst/utility/audiobuffers.h
+ ${VST3_SRCPATH}/vst/utility/processcontextrequirements.h
+ ${VST3_SRCPATH}/vst/utility/processdataslicer.h
+ ${VST3_SRCPATH}/vst/utility/ringbuffer.h
+ ${VST3_SRCPATH}/vst/utility/rttransfer.h
+ ${VST3_SRCPATH}/vst/utility/sampleaccurate.h
+ ${VST3_SRCPATH}/vst/utility/stringconvert.cpp
+ ${VST3_SRCPATH}/vst/utility/stringconvert.h
+ ${VST3_SRCPATH}/vst/utility/testing.cpp
+ ${VST3_SRCPATH}/vst/utility/testing.h
+ ${VST3_SRCPATH}/vst/utility/vst2persistence.h
+ ${VST3_SRCPATH}/vst/utility/vst2persistence.cpp
+ ${VST3_SRCPATH}/vst/vstaudioeffect.cpp
+ ${VST3_SRCPATH}/vst/vstaudioeffect.h
+ ${VST3_SRCPATH}/vst/vstbus.cpp
+ ${VST3_SRCPATH}/vst/vstbus.h
+ ${VST3_SRCPATH}/vst/vstbypassprocessor.h
+ ${VST3_SRCPATH}/vst/vstcomponent.cpp
+ ${VST3_SRCPATH}/vst/vstcomponent.h
+ ${VST3_SRCPATH}/vst/vstcomponentbase.cpp
+ ${VST3_SRCPATH}/vst/vstcomponentbase.h
+ ${VST3_SRCPATH}/vst/vsteditcontroller.cpp
+ ${VST3_SRCPATH}/vst/vsteditcontroller.h
+ ${VST3_SRCPATH}/vst/vsteventshelper.h
+ ${VST3_SRCPATH}/vst/vsthelpers.h
+ ${VST3_SRCPATH}/vst/vstinitiids.cpp
+ ${VST3_SRCPATH}/vst/vstnoteexpressiontypes.cpp
+ ${VST3_SRCPATH}/vst/vstnoteexpressiontypes.h
+ ${VST3_SRCPATH}/vst/vstparameters.cpp
+ ${VST3_SRCPATH}/vst/vstparameters.h
+ ${VST3_SRCPATH}/vst/vstrepresentation.cpp
+ ${VST3_SRCPATH}/vst/vstrepresentation.h
+
+ # sdk_host.a files.
+
+ ${VST3_SRCPATH}/vst/hosting/connectionproxy.cpp
+ ${VST3_SRCPATH}/vst/hosting/connectionproxy.h
+ ${VST3_SRCPATH}/vst/hosting/eventlist.cpp
+ ${VST3_SRCPATH}/vst/hosting/eventlist.h
+ ${VST3_SRCPATH}/vst/hosting/hostclasses.cpp
+ ${VST3_SRCPATH}/vst/hosting/hostclasses.h
+ ${VST3_SRCPATH}/vst/hosting/module.cpp
+ ${VST3_SRCPATH}/vst/hosting/module.h
+ ${VST3_SRCPATH}/vst/hosting/parameterchanges.cpp
+ ${VST3_SRCPATH}/vst/hosting/parameterchanges.h
+ ${VST3_SRCPATH}/vst/hosting/pluginterfacesupport.cpp
+ ${VST3_SRCPATH}/vst/hosting/pluginterfacesupport.h
+ ${VST3_SRCPATH}/vst/hosting/plugprovider.cpp
+ ${VST3_SRCPATH}/vst/hosting/plugprovider.h
+ ${VST3_SRCPATH}/vst/hosting/processdata.cpp
+ ${VST3_SRCPATH}/vst/hosting/processdata.h
+ ${VST3_SRCPATH}/vst/utility/optional.h
+ ${VST3_SRCPATH}/vst/utility/stringconvert.cpp
+ ${VST3_SRCPATH}/vst/utility/stringconvert.h
+ ${VST3_SRCPATH}/vst/utility/uid.h
+ ${VST3_SRCPATH}/vst/vstinitiids.cpp
+
+ ${VST3_SRCPATH}/vst/hosting/module_linux.cpp
+
+ # source/audiohost.cpp
+ # source/audiohost.h
+ # source/media/audioclient.cpp
+ # ource/media/audioclient.h
+ # source/media/imediaserver.h
+ # source/media/iparameterclient.h
+ # #source/media/jack/jackclient.cpp
+ # source/media/miditovst.h
+ # source/platform/appinit.h
+ # source/usediids.cpp
+ )
+
+ add_library(vst3_lib STATIC ${vst3_sources})
+
+ target_include_directories(vst3_lib PRIVATE ${VST3_INCLUDES}
+ )
+
+
+ add_executable(vst3test Vst3test.cpp
+
+ MemDebug.cpp
+ MemDebug.hpp
+
+ Vst3SdkRepro.cpp
+
+ ${VST3_FILES}
+
+ #vst3/Vst3PresetFile.hpp Vst3PresetFile.cpp
+ asan_options.cpp
+ )
+
+ target_include_directories(vst3test PRIVATE ${PIPEDAL_INCLUDES}
+ )
+
+
+ target_link_libraries(vst3test PRIVATE ${PIPEDAL_LIBS}
+ vst3_lib dl pthread
+ )
+
+endif(ENABLE_VST3)
+
+
+
+
# Build machine tests. Run tests that are not dependent on hardware.
add_test(NAME BuildTest COMMAND pipedaltest "[Build]")
diff --git a/src/IEffect.hpp b/src/IEffect.hpp
index fe85b66..9472b91 100644
--- a/src/IEffect.hpp
+++ b/src/IEffect.hpp
@@ -23,11 +23,12 @@
namespace pipedal {
class RealtimeParameterRequest;
+ class RealtimeRingBufferWriter;
class IEffect {
public:
virtual ~IEffect() {}
- virtual long GetInstanceId() const = 0;
+ virtual uint64_t GetInstanceId() const = 0;
virtual int GetControlIndex(const std::string&symbol) const = 0;
virtual void SetControl(int index, float value) = 0;
virtual float GetControlValue(int index) const = 0;
@@ -35,6 +36,7 @@ namespace pipedal {
virtual float GetOutputControlValue(int controlIndex) const = 0;
+
virtual int GetNumberOfInputAudioPorts() const = 0;
virtual int GetNumberOfOutputAudioPorts() const = 0;
virtual float *GetAudioInputBuffer(int index) const = 0;
@@ -43,13 +45,19 @@ namespace pipedal {
virtual void RequestParameter(LV2_URID uridUri) = 0;
virtual void GatherParameter(RealtimeParameterRequest*pRequest) = 0;
- virtual std::string AtomToJson(uint8_t*pAtom) = 0;
- virtual std::string GetAtomObjectType(uint8_t*pData) = 0;
+ //virtual std::string AtomToJson(uint8_t*pAtom) = 0;
+ //virtual std::string GetAtomObjectType(uint8_t*pData) = 0;
+ virtual void SetAudioInputBuffer(int index, float *buffer) = 0;
virtual void SetAudioOutputBuffer(int index, float*buffer) = 0;
virtual void Activate() = 0;
+
+ virtual void Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter) = 0;
+
virtual void Deactivate() = 0;
+
+ virtual bool IsVst3() const = 0;
};
} //namespace
\ No newline at end of file
diff --git a/src/JackHost.cpp b/src/JackHost.cpp
index 2e7254a..57186fc 100644
--- a/src/JackHost.cpp
+++ b/src/JackHost.cpp
@@ -106,6 +106,73 @@ static std::string GetGovernor()
class JackHostImpl : public JackHost, private AudioDriverHost
{
private:
+ IHost *pHost = nullptr;
+
+ class Uris
+ {
+ public:
+ Uris(IHost *pHost)
+ {
+ atom_Blank = pHost->GetLv2Urid(LV2_ATOM__Blank);
+ atom_Path = pHost->GetLv2Urid(LV2_ATOM__Path);
+ atom_float = pHost->GetLv2Urid(LV2_ATOM__Float);
+ atom_Double = pHost->GetLv2Urid(LV2_ATOM__Double);
+ atom_Int = pHost->GetLv2Urid(LV2_ATOM__Int);
+ atom_Long = pHost->GetLv2Urid(LV2_ATOM__Long);
+ atom_Bool = pHost->GetLv2Urid(LV2_ATOM__Bool);
+ atom_String = pHost->GetLv2Urid(LV2_ATOM__String);
+ atom_Vector = pHost->GetLv2Urid(LV2_ATOM__Vector);
+ atom_Object = pHost->GetLv2Urid(LV2_ATOM__Object);
+
+
+ atom_Sequence = pHost->GetLv2Urid(LV2_ATOM__Sequence);
+ atom_Chunk = pHost->GetLv2Urid(LV2_ATOM__Chunk);
+ atom_URID = pHost->GetLv2Urid(LV2_ATOM__URID);
+ atom_eventTransfer = pHost->GetLv2Urid(LV2_ATOM__eventTransfer);
+ patch_Get = pHost->GetLv2Urid(LV2_PATCH__Get);
+ patch_Set = pHost->GetLv2Urid(LV2_PATCH__Set);
+ patch_Put = pHost->GetLv2Urid(LV2_PATCH__Put);
+ patch_body = pHost->GetLv2Urid(LV2_PATCH__body);
+ patch_subject = pHost->GetLv2Urid(LV2_PATCH__subject);
+ patch_property = pHost->GetLv2Urid(LV2_PATCH__property);
+ //patch_accept = pHost->GetLv2Urid(LV2_PATCH__accept);
+ patch_value = pHost->GetLv2Urid(LV2_PATCH__value);
+ unitsFrame = pHost->GetLv2Urid(LV2_UNITS__frame);
+
+ }
+ //LV2_URID patch_accept;
+
+ LV2_URID unitsFrame;
+ LV2_URID pluginUri;
+ LV2_URID atom_Blank;
+ LV2_URID atom_Bool;
+ LV2_URID atom_float;
+ LV2_URID atom_Double;
+ LV2_URID atom_Int;
+ LV2_URID atom_Long;
+ LV2_URID atom_String;
+ LV2_URID atom_Object;
+ LV2_URID atom_Vector;
+ LV2_URID atom_Path;
+ LV2_URID atom_Sequence;
+ LV2_URID atom_Chunk;
+ LV2_URID atom_URID;
+ LV2_URID atom_eventTransfer;
+ LV2_URID midi_Event;
+ LV2_URID patch_Get;
+ LV2_URID patch_Set;
+ LV2_URID patch_Put;
+ LV2_URID patch_body;
+ LV2_URID patch_subject;
+ LV2_URID patch_property;
+ LV2_URID patch_value;
+ LV2_URID param_uiState;
+ };
+
+ Uris uris;
+
+
+
AudioDriver*audioDriver = nullptr;
inherit_priority_recursive_mutex mutex;
@@ -146,6 +213,30 @@ private:
std::atomic lastUnderrunTime =
std::chrono::system_clock::from_time_t(0);
+
+ std::string GetAtomObjectType(uint8_t*pData)
+ {
+ LV2_Atom_Object *pAtom = (LV2_Atom_Object*)pData;
+ if (pAtom->atom.type != uris.atom_Object)
+ {
+ throw std::invalid_argument("Not an Lv2 Object");
+ }
+ return pHost->Lv2UriudToString(pAtom->body.otype);
+
+ }
+
+ void WriteAtom(json_writer &writer, LV2_Atom*pAtom);
+
+ std::string AtomToJson(uint8_t*pData)
+ {
+ std::stringstream s;
+ json_writer writer(s);
+ LV2_Atom *pAtom = (LV2_Atom*)pData;
+
+ WriteAtom(writer,pAtom);
+ return s.str();
+ }
+
virtual void OnUnderrun()
{
++this->underruns;
@@ -582,7 +673,6 @@ private:
}
}
-
public:
JackHostImpl(IHost *pHost)
@@ -592,7 +682,9 @@ public:
realtimeWriter(&this->outputRingBuffer),
hostReader(&this->outputRingBuffer),
hostWriter(&this->inputRingBuffer),
- eventBufferUrids(pHost)
+ eventBufferUrids(pHost),
+ pHost(pHost),
+ uris(pHost)
{
#if JACK_HOST
@@ -758,7 +850,7 @@ public:
}
else
{
- pRequest->jsonResponse = pEffect->AtomToJson(pRequest->response);
+ pRequest->jsonResponse = AtomToJson(pRequest->response);
}
}
pRequest->onJackRequestComplete(pRequest);
@@ -802,8 +894,8 @@ public:
IEffect *pEffect = currentPedalBoard->GetEffect(instanceId);
if (pEffect != nullptr &&this->pNotifyCallbacks && listenForAtomOutput)
{
- std::string atomType = pEffect->GetAtomObjectType(&atomBuffer[0]);
- auto json = pEffect->AtomToJson(&(atomBuffer[0]));
+ std::string atomType = GetAtomObjectType(&atomBuffer[0]);
+ auto json = AtomToJson(&(atomBuffer[0]));
this->pNotifyCallbacks->OnNotifyAtomOutput(instanceId,atomType,json);
}
}
@@ -1275,6 +1367,148 @@ public:
}
};
+static std::string UriToFieldName(const std::string&uri){
+ int pos;
+ for (pos = uri.length(); pos >= 0; --pos)
+ {
+ char c = uri[pos];
+ if (c == '#' || c == '/' || c == ':')
+ {
+ break;
+ }
+ }
+ return uri.substr(pos+1);
+}
+
+void JackHostImpl::WriteAtom(json_writer &writer, LV2_Atom*pAtom)
+{
+ if (pAtom->type == uris.atom_Blank)
+ {
+ writer.write_raw("null");
+ }
+ else if (pAtom->type == uris.atom_float)
+ {
+ writer.write(
+ ((LV2_Atom_Float*)pAtom)->body
+ );
+
+ } else if (pAtom->type == uris.atom_Int)
+ {
+ writer.write(
+ ((LV2_Atom_Int*)pAtom)->body
+ );
+ } else if (pAtom->type == uris.atom_Long)
+ {
+ writer.write(
+ ((LV2_Atom_Long*)pAtom)->body
+ );
+ } else if (pAtom->type == uris.atom_Double)
+ {
+ writer.write(
+ ((LV2_Atom_Double*)pAtom)->body
+ );
+ } else if (pAtom->type == uris.atom_Bool)
+ {
+ writer.write(
+ ((LV2_Atom_Bool*)pAtom)->body
+ );
+ } else if (pAtom->type == uris.atom_String)
+ {
+ const char *p = (((const char*) pAtom) + sizeof(LV2_Atom_String));
+ writer.write(
+ p
+ );
+ } else if (pAtom->type == uris.atom_Vector)
+ {
+ LV2_Atom_Vector *pVector = (LV2_Atom_Vector*)pAtom;
+ writer.start_array();
+ {
+ size_t n = (pAtom->size-sizeof(pVector->body))/pVector->body.child_size;
+ char *pItems = ((char*)pAtom) + sizeof(LV2_Atom_Vector);
+ if (pVector->body.child_type == uris.atom_float)
+ {
+ float *p = (float*)pItems;
+ for (size_t i = 0; i < n; ++i)
+ {
+ if (i != 0) writer.write_raw(",");
+ writer.write(*p++);
+ }
+ } else if (pVector->body.child_type == uris.atom_Int)
+ {
+ int32_t *p = (int32_t*)pItems;
+ for (size_t i = 0; i < n; ++i)
+ {
+ if (i != 0) writer.write_raw(",");
+ writer.write(*p++);
+ }
+ } else if (pVector->body.child_type == uris.atom_Long)
+ {
+ int64_t *p = (int64_t*)pItems;
+ for (size_t i = 0; i < n; ++i)
+ {
+ if (i != 0) writer.write_raw(",");
+ writer.write(*p++);
+ }
+ } else if (pVector->body.child_type == uris.atom_Double)
+ {
+ double *p = (double*)pItems;
+ for (size_t i = 0; i < n; ++i)
+ {
+ if (i != 0) writer.write_raw(",");
+ writer.write(*p++);
+ }
+ } else if (pVector->body.child_type == uris.atom_Bool)
+ {
+ bool *p = (bool*)pItems;
+ for (size_t i = 0; i < n; ++i)
+ {
+ if (i != 0) writer.write_raw(",");
+ writer.write(*p++);
+ }
+ }
+ }
+ writer.end_array();
+ } else if (pAtom->type == uris.atom_Object)
+ {
+ writer.start_object();
+
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)pAtom;
+ bool firstMember = true;
+ if (obj->body.id != 0)
+ {
+ std::string id = pHost->Lv2UriudToString(obj->body.id);
+ writer.write_member("id",id.c_str());
+ firstMember = false;
+ }
+
+ if (obj->body.otype != 0)
+ {
+ std::string type = pHost->Lv2UriudToString(obj->body.otype);
+ writer.write_member("lv2Type",type.c_str());
+ if (!firstMember)
+ {
+ writer.write_raw(",");
+ }
+ firstMember = false;
+ }
+
+ LV2_ATOM_OBJECT_FOREACH (obj, prop) {
+ if (!firstMember) {
+ writer.write_raw(",");
+ }
+ firstMember = false;
+ std::string key = pHost->Lv2UriudToString(prop->key);
+ key = UriToFieldName(key);
+ writer.write(key);
+ writer.write_raw(": ");
+ LV2_Atom *value = &(prop->value);
+ WriteAtom(writer,value);
+ }
+ writer.end_object();
+
+ }
+
+}
JackHost *JackHost::CreateInstance(IHost *pHost)
{
diff --git a/src/LiteralVersion.cpp b/src/LiteralVersion.cpp
new file mode 100644
index 0000000..5d4ae77
--- /dev/null
+++ b/src/LiteralVersion.cpp
@@ -0,0 +1,74 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022 Robin E. R. 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.
+ */
+
+
+#include "LiteralVersion.hpp"
+#include
+#include
+
+using namespace pipedal;
+
+LiteralVersion::LiteralVersion()
+{
+}
+LiteralVersion::LiteralVersion(const std::string &version)
+{
+ Parse(version);
+}
+
+bool LiteralVersion::Parse(const std::string &version)
+{
+ int ix = 0;
+
+ while (ix < version.length())
+ {
+ char c = version[ix];
+ int n = 0;
+ if (std::isdigit(c))
+ {
+ while (std::isdigit(c))
+ {
+ c = version[ix++];
+ n = c -'0' + n;
+ }
+ this->versionNumbers.push_back(n);
+
+ if (version[ix] == '.')
+ {
+ ++ix;
+ }
+ } else {
+ std::stringstream t;
+
+ while (ix < version.length())
+ {
+ c = version[ix++];
+ t << c;
+ }
+ suffix = t.str();
+ }
+ }
+
+ return true;
+}
diff --git a/src/LiteralVersion.hpp b/src/LiteralVersion.hpp
new file mode 100644
index 0000000..dfe094b
--- /dev/null
+++ b/src/LiteralVersion.hpp
@@ -0,0 +1,45 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022 Robin E. R. 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.
+ */
+
+#pragma once
+
+#include
+#include
+
+namespace pipedal {
+ class LiteralVersion {
+ public:
+ LiteralVersion();
+ LiteralVersion(const std::string&version);
+
+ bool Parse(const std::string&version);
+
+ const std::vector &getVersionNumbers() const { return versionNumbers; }
+ const std::string&getSuffix() const { return suffix; }
+ int compare(const LiteralVersion&other);
+ private:
+ std::vector versionNumbers;
+ std::string suffix;
+ };
+};
\ No newline at end of file
diff --git a/src/Lv2Effect.cpp b/src/Lv2Effect.cpp
index a4ed1ad..eed9efb 100644
--- a/src/Lv2Effect.cpp
+++ b/src/Lv2Effect.cpp
@@ -216,7 +216,7 @@ void Lv2Effect::SetAudioInputBuffer(int index, float *buffer)
void Lv2Effect::SetAudioInputBuffer(float *left)
{
- if (GetNumberOfInputs() > 1)
+ if (GetNumberOfInputAudioPorts() > 1)
{
SetAudioInputBuffer(0, left);
SetAudioInputBuffer(1, left);
@@ -229,7 +229,7 @@ void Lv2Effect::SetAudioInputBuffer(float *left)
void Lv2Effect::SetAudioInputBuffers(float *left, float *right)
{
- if (GetNumberOfInputs() == 1)
+ if (GetNumberOfInputAudioPorts() == 1)
{
SetAudioInputBuffer(0, left);
}
@@ -343,7 +343,7 @@ static inline void CopyBuffer(float *input, float *output, uint32_t frames)
}
}
-void Lv2Effect::Run(uint32_t samples,uint64_t instanceId, RealtimeRingBufferWriter *realtimeRingBufferWriter)
+void Lv2Effect::Run(uint32_t samples,RealtimeRingBufferWriter *realtimeRingBufferWriter)
{
// close off the atom input frame.
if (this->inputAtomBuffers.size() != 0)
@@ -441,7 +441,7 @@ void Lv2Effect::Run(uint32_t samples,uint64_t instanceId, RealtimeRingBufferWrit
}
}
- RelayOutputMessages(instanceId,realtimeRingBufferWriter);
+ RelayOutputMessages(this->instanceId,realtimeRingBufferWriter);
}
LV2_Worker_Status Lv2Effect::worker_schedule_fn(LV2_Worker_Schedule_Handle handle,
@@ -600,164 +600,5 @@ void Lv2Effect::GatherParameter(RealtimeParameterRequest *pRequest)
}
}
-std::string Lv2Effect::GetAtomObjectType(uint8_t*pData)
-{
- LV2_Atom_Object *pAtom = (LV2_Atom_Object*)pData;
- if (pAtom->atom.type != uris.atom_Object)
- {
- throw std::invalid_argument("Not an Lv2 Object");
- }
- return pHost->Lv2UriudToString(pAtom->body.otype);
-}
-std::string Lv2Effect::AtomToJson(uint8_t*pData)
-{
- std::stringstream s;
- json_writer writer(s);
- LV2_Atom *pAtom = (LV2_Atom*)pData;
- WriteAtom(writer,pAtom);
- return s.str();
-}
-
-static std::string UriToFieldName(const std::string&uri){
- int pos;
- for (pos = uri.length(); pos >= 0; --pos)
- {
- char c = uri[pos];
- if (c == '#' || c == '/' || c == ':')
- {
- break;
- }
- }
- return uri.substr(pos+1);
-}
-void Lv2Effect::WriteAtom(json_writer &writer, LV2_Atom*pAtom)
-{
- if (pAtom->type == uris.atom_Blank)
- {
- writer.write_raw("null");
- }
- else if (pAtom->type == uris.atom_float)
- {
- writer.write(
- ((LV2_Atom_Float*)pAtom)->body
- );
-
- } else if (pAtom->type == uris.atom_Int)
- {
- writer.write(
- ((LV2_Atom_Int*)pAtom)->body
- );
- } else if (pAtom->type == uris.atom_Long)
- {
- writer.write(
- ((LV2_Atom_Long*)pAtom)->body
- );
- } else if (pAtom->type == uris.atom_Double)
- {
- writer.write(
- ((LV2_Atom_Double*)pAtom)->body
- );
- } else if (pAtom->type == uris.atom_Bool)
- {
- writer.write(
- ((LV2_Atom_Bool*)pAtom)->body
- );
- } else if (pAtom->type == uris.atom_String)
- {
- const char *p = (((const char*) pAtom) + sizeof(LV2_Atom_String));
- writer.write(
- p
- );
- } else if (pAtom->type == uris.atom_Vector)
- {
- LV2_Atom_Vector *pVector = (LV2_Atom_Vector*)pAtom;
- writer.start_array();
- {
- size_t n = (pAtom->size-sizeof(pVector->body))/pVector->body.child_size;
- char *pItems = ((char*)pAtom) + sizeof(LV2_Atom_Vector);
- if (pVector->body.child_type == uris.atom_float)
- {
- float *p = (float*)pItems;
- for (size_t i = 0; i < n; ++i)
- {
- if (i != 0) writer.write_raw(",");
- writer.write(*p++);
- }
- } else if (pVector->body.child_type == uris.atom_Int)
- {
- int32_t *p = (int32_t*)pItems;
- for (size_t i = 0; i < n; ++i)
- {
- if (i != 0) writer.write_raw(",");
- writer.write(*p++);
- }
- } else if (pVector->body.child_type == uris.atom_Long)
- {
- int64_t *p = (int64_t*)pItems;
- for (size_t i = 0; i < n; ++i)
- {
- if (i != 0) writer.write_raw(",");
- writer.write(*p++);
- }
- } else if (pVector->body.child_type == uris.atom_Double)
- {
- double *p = (double*)pItems;
- for (size_t i = 0; i < n; ++i)
- {
- if (i != 0) writer.write_raw(",");
- writer.write(*p++);
- }
- } else if (pVector->body.child_type == uris.atom_Bool)
- {
- bool *p = (bool*)pItems;
- for (size_t i = 0; i < n; ++i)
- {
- if (i != 0) writer.write_raw(",");
- writer.write(*p++);
- }
- }
- }
- writer.end_array();
- } else if (pAtom->type == uris.atom_Object)
- {
- writer.start_object();
-
- const LV2_Atom_Object *obj = (const LV2_Atom_Object *)pAtom;
- bool firstMember = true;
- if (obj->body.id != 0)
- {
- std::string id = pHost->Lv2UriudToString(obj->body.id);
- writer.write_member("id",id.c_str());
- firstMember = false;
- }
-
- if (obj->body.otype != 0)
- {
- std::string type = pHost->Lv2UriudToString(obj->body.otype);
- writer.write_member("lv2Type",type.c_str());
- if (!firstMember)
- {
- writer.write_raw(",");
- }
- firstMember = false;
- }
-
- LV2_ATOM_OBJECT_FOREACH (obj, prop) {
- if (!firstMember) {
- writer.write_raw(",");
- }
- firstMember = false;
- std::string key = pHost->Lv2UriudToString(prop->key);
- key = UriToFieldName(key);
- writer.write(key);
- writer.write_raw(": ");
- LV2_Atom *value = &(prop->value);
- WriteAtom(writer,value);
- }
- writer.end_object();
-
- }
-
-}
diff --git a/src/Lv2Effect.hpp b/src/Lv2Effect.hpp
index add4390..97e64cc 100644
--- a/src/Lv2Effect.hpp
+++ b/src/Lv2Effect.hpp
@@ -20,7 +20,7 @@
#pragma once
-#include "Lv2Host.hpp"
+#include "PiPedalHost.hpp"
#include "PedalBoard.hpp"
#include
#include "BufferPool.hpp"
@@ -82,7 +82,6 @@ namespace pipedal
LV2_Atom_Forge outputForgeRt;
- void WriteAtom(json_writer &writer, LV2_Atom*pAtom);
class Uris
{
@@ -167,9 +166,10 @@ namespace pipedal
void BypassTo(float value);
+
virtual void RequestParameter(LV2_URID uridUri);
virtual void GatherParameter(RealtimeParameterRequest*pRequest);
-
+ virtual bool IsVst3() const { return false; }
virtual void RelayOutputMessages(uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter);
virtual uint8_t*GetAtomInputBuffer() {
@@ -182,10 +182,6 @@ namespace pipedal
return (uint8_t*)this->outputAtomBuffers[0];
}
- virtual std::string AtomToJson(uint8_t*pAtom);
- virtual std::string GetAtomObjectType(uint8_t*pData);
-
-
public:
@@ -196,29 +192,30 @@ namespace pipedal
~Lv2Effect();
+
virtual void ResetAtomBuffers();
- virtual long GetInstanceId() const { return instanceId; }
+ virtual uint64_t GetInstanceId() const { return instanceId; }
virtual int GetNumberOfInputAudioPorts() const { return inputAudioPortIndices.size(); }
virtual int GetNumberOfOutputAudioPorts() const { return outputAudioPortIndices.size(); }
- int GetNumberOfInputAtomPorts() const { return inputAtomPortIndices.size(); }
- int GetNumberOfOutputAtomPorts() const { return outputAtomPortIndices.size(); }
- int GetNumberOfMidiInputPorts() const { return inputMidiPortIndices.size(); }
- int GetNumberOfMidiOutputPorts() const { return outputMidiPortIndices.size(); }
+ virtual int GetNumberOfInputAtomPorts() const { return inputAtomPortIndices.size(); }
+ virtual int GetNumberOfOutputAtomPorts() const { return outputAtomPortIndices.size(); }
+ virtual int GetNumberOfMidiInputPorts() const { return inputMidiPortIndices.size(); }
+ virtual int GetNumberOfMidiOutputPorts() const { return outputMidiPortIndices.size(); }
virtual void SetAudioInputBuffer(int index, float *buffer);
- float *GetAudioInputBuffer(int index) const { return this->inputAudioBuffers[index]; }
+ virtual float *GetAudioInputBuffer(int index) const { return this->inputAudioBuffers[index]; }
- void SetAudioInputBuffer(float *buffer);
- void SetAudioInputBuffers(float *left, float *right);
+ virtual void SetAudioInputBuffer(float *buffer);
+ virtual void SetAudioInputBuffers(float *left, float *right);
virtual void SetAudioOutputBuffer(int index, float *buffer);
- float *GetAudioOutputBuffer(int index) const { return this->outputAudioBuffers[index]; }
+ virtual float *GetAudioOutputBuffer(int index) const { return this->outputAudioBuffers[index]; }
- void SetAtomInputBuffer(int index, void *buffer);
- void *GetAtomInputBuffer(int index) const { return this->inputAtomBuffers[index]; }
+ virtual void SetAtomInputBuffer(int index, void *buffer) { this->inputAtomBuffers[index] = (char*)buffer;}
+ virtual void *GetAtomInputBuffer(int index) const { return this->inputAtomBuffers[index]; }
- void SetAtomOutputBuffer(int index, void *buffer);
- void *GetAtomOutputBuffer(int index) const { return this->outputAtomBuffers[index]; }
+ virtual void SetAtomOutputBuffer(int index, void *buffer) { this->outputAtomBuffers[index] = (char*)buffer; }
+ virtual void *GetAtomOutputBuffer(int index) const { return this->outputAtomBuffers[index]; }
virtual int GetControlIndex(const std::string &symbol) const;
@@ -254,22 +251,10 @@ namespace pipedal
}
}
- int GetNumberOfInputs() const
- {
- return this->inputAudioPortIndices.size();
- }
- int GetNumberOfOutputs() const
- {
- return this->outputAudioPortIndices.size();
- }
- int GetNumberOfMidiInputs() const
- {
- return this->inputMidiPortIndices.size();
- }
- void Activate();
- void Run(uint32_t samples, uint64_t instanceId,RealtimeRingBufferWriter *realtimeRingBufferWriter);
- void Deactivate();
+ virtual void Activate();
+ virtual void Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter);
+ virtual void Deactivate();
};
} // namespace
\ No newline at end of file
diff --git a/src/Lv2EventBufferWriter.cpp b/src/Lv2EventBufferWriter.cpp
index f5d905d..7027687 100644
--- a/src/Lv2EventBufferWriter.cpp
+++ b/src/Lv2EventBufferWriter.cpp
@@ -19,7 +19,7 @@
#include "pch.h"
#include "Lv2EventBufferWriter.hpp"
-#include "Lv2Host.hpp"
+#include "PiPedalHost.hpp"
using namespace pipedal;
diff --git a/src/Lv2HostLeakTest.cpp b/src/Lv2HostLeakTest.cpp
index 16bdc16..6f86217 100644
--- a/src/Lv2HostLeakTest.cpp
+++ b/src/Lv2HostLeakTest.cpp
@@ -24,17 +24,17 @@
#include
-#include "Lv2Host.hpp"
+#include "PiPedalHost.hpp"
#include "MemDebug.hpp"
using namespace pipedal;
-TEST_CASE( "Lv2Host memory leak", "[lv2host_leak][Build][Dev]" ) {
+TEST_CASE( "PiPedalHost memory leak", "[lv2host_leak][Build][Dev]" ) {
MemStats initialMemory = GetMemStats();
{
- Lv2Host host;
+ PiPedalHost host;
host.Load("/usr/lib/lv2:/usr/local/lib/lv2:/usr/modep/lv2");
}
diff --git a/src/Lv2PedalBoard.cpp b/src/Lv2PedalBoard.cpp
index d19cbf5..038ae00 100644
--- a/src/Lv2PedalBoard.cpp
+++ b/src/Lv2PedalBoard.cpp
@@ -21,6 +21,7 @@
#include "Lv2PedalBoard.hpp"
#include "Lv2Effect.hpp"
+
#include "SplitEffect.hpp"
#include "RingBufferReader.hpp"
#include "VuUpdate.hpp"
@@ -57,7 +58,7 @@ int Lv2PedalBoard::GetControlIndex(uint64_t instanceId, const std::string &symbo
return -1;
}
std::vector Lv2PedalBoard::PrepareItems(
- const std::vector &items,
+ std::vector &items,
std::vector inputBuffers)
{
for (int i = 0; i < items.size(); ++i)
@@ -142,9 +143,9 @@ std::vector Lv2PedalBoard::PrepareItems(
}
this->processActions.push_back(
- [pLv2Effect,this,instanceId](uint32_t frames)
+ [pLv2Effect,this](uint32_t frames)
{
- pLv2Effect->Run(frames,instanceId,this->ringBufferWriter);
+ pLv2Effect->Run(frames,this->ringBufferWriter);
});
}
}
@@ -197,7 +198,7 @@ std::vector Lv2PedalBoard::PrepareItems(
return inputBuffers;
}
-void Lv2PedalBoard::Prepare(IHost *pHost, const PedalBoard &pedalBoard)
+void Lv2PedalBoard::Prepare(IHost *pHost, PedalBoard &pedalBoard)
{
this->pHost = pHost;
diff --git a/src/Lv2PedalBoard.hpp b/src/Lv2PedalBoard.hpp
index 9ac0f38..d3d8a14 100644
--- a/src/Lv2PedalBoard.hpp
+++ b/src/Lv2PedalBoard.hpp
@@ -19,7 +19,7 @@
#pragma once
#include "PedalBoard.hpp"
-#include "Lv2Host.hpp"
+#include "PiPedalHost.hpp"
#include "Lv2Effect.hpp"
#include "BufferPool.hpp"
#include
@@ -81,7 +81,7 @@ class Lv2PedalBoard {
std::vector PrepareItems(
- const std::vector & items,
+ std::vector & items,
std::vector inputBuffers
);
@@ -95,7 +95,9 @@ public:
Lv2PedalBoard() { }
~Lv2PedalBoard() { }
- void Prepare(IHost *pHost,const PedalBoard&pedalBoard);
+ void Prepare(IHost *pHost,PedalBoard&pedalBoard);
+
+ std::vector GetEffects() { return realtimeEffects; }
int GetIndexOfInstanceId(uint64_t instanceId) {
for (int i = 0; i < this->realtimeEffects.size(); ++i)
diff --git a/src/PedalBoard.hpp b/src/PedalBoard.hpp
index 7dd8513..9075d16 100644
--- a/src/PedalBoard.hpp
+++ b/src/PedalBoard.hpp
@@ -82,10 +82,12 @@ class PedalBoardItem: public JsonWritable {
std::vector topChain_;
std::vector bottomChain_;
std::vector midiBindings_;
+ std::string vstState_;
public:
ControlValue*GetControlValue(const std::string&symbol);
GETTER_SETTER(instanceId)
GETTER_SETTER_REF(uri)
+ GETTER_SETTER_REF(vstState);
GETTER_SETTER_REF(pluginName)
GETTER_SETTER(isEnabled)
GETTER_SETTER_VEC(controlValues)
diff --git a/src/PiPedalConfiguration.cpp b/src/PiPedalConfiguration.cpp
index 604532a..5fec21b 100644
--- a/src/PiPedalConfiguration.cpp
+++ b/src/PiPedalConfiguration.cpp
@@ -36,4 +36,5 @@ JSON_MAP_REFERENCE(PiPedalConfiguration, logHttpRequests)
JSON_MAP_REFERENCE(PiPedalConfiguration, maxUploadSize)
JSON_MAP_REFERENCE(PiPedalConfiguration, accessPointGateway)
JSON_MAP_REFERENCE(PiPedalConfiguration, accessPointServerAddress)
+JSON_MAP_REFERENCE(PiPedalConfiguration, isVst3Enabled)
JSON_MAP_END()
diff --git a/src/PiPedalConfiguration.hpp b/src/PiPedalConfiguration.hpp
index 862a647..c2fe24e 100644
--- a/src/PiPedalConfiguration.hpp
+++ b/src/PiPedalConfiguration.hpp
@@ -47,9 +47,10 @@ private:
uint64_t maxUploadSize_ = 1024*1024;
std::string accessPointGateway_;
std::string accessPointServerAddress_;
+ bool isVst3Enabled_ = true;
public:
-
+ bool IsVst3Enabled() const { return isVst3Enabled_; }
std::filesystem::path GetConfigFilePath() const {
return docRoot_ / "config.jason";
}
diff --git a/src/Lv2Host.cpp b/src/PiPedalHost.cpp
similarity index 80%
rename from src/Lv2Host.cpp
rename to src/PiPedalHost.cpp
index 4ac7683..f7ab849 100644
--- a/src/Lv2Host.cpp
+++ b/src/PiPedalHost.cpp
@@ -18,7 +18,7 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "pch.h"
-#include "Lv2Host.hpp"
+#include "PiPedalHost.hpp"
#include
#include
#include
@@ -85,7 +85,7 @@ namespace pipedal
static const char *LV2_MIDI_EVENT = "http://lv2plug.in/ns/ext/midi#MidiEvent";
static const char *LV2_DESIGNATION = "http://lv2plug.in/ns/lv2core#Designation";
- class Lv2Host::Urids
+ class PiPedalHost::Urids
{
public:
Urids(MapFeature &mapFeature)
@@ -96,9 +96,17 @@ namespace pipedal
};
}
-void Lv2Host::LilvUris::Initialize(LilvWorld *pWorld)
+void PiPedalHost::SetConfiguration(const PiPedalConfiguration &configuration)
{
- rdfsComment = lilv_new_uri(pWorld, Lv2Host::RDFS_COMMENT_URI);
+ this->vst3CachePath =
+ std::filesystem::path(configuration.GetLocalStoragePath()) / "vst3cache.json";
+ this->vst3Enabled = configuration.IsVst3Enabled();
+}
+
+
+void PiPedalHost::LilvUris::Initialize(LilvWorld *pWorld)
+{
+ rdfsComment = lilv_new_uri(pWorld, PiPedalHost::RDFS_COMMENT_URI);
logarithic_uri = lilv_new_uri(pWorld, LV2_PORT_LOGARITHMIC);
display_priority_uri = lilv_new_uri(pWorld, LV2_PORT_DISPLAY_PRIORITY);
range_steps_uri = lilv_new_uri(pWorld, LV2_PORT_RANGE_STEPS);
@@ -124,7 +132,7 @@ void Lv2Host::LilvUris::Initialize(LilvWorld *pWorld)
appliesTo = lilv_new_uri(pWorld, LV2_CORE__appliesTo);
}
-void Lv2Host::LilvUris::Free()
+void PiPedalHost::LilvUris::Free()
{
rdfsComment.Free();
logarithic_uri.Free();
@@ -242,7 +250,7 @@ public:
}
};
-Lv2Host::Lv2Host()
+PiPedalHost::PiPedalHost()
{
pWorld = nullptr;
@@ -260,7 +268,7 @@ Lv2Host::Lv2Host()
this->urids = new Urids(mapFeature);
}
-void Lv2Host::OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings)
+void PiPedalHost::OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings)
{
this->sampleRate = configuration.GetSampleRate();
if (configuration.isValid())
@@ -272,7 +280,7 @@ void Lv2Host::OnConfigurationChanged(const JackConfiguration &configuration, con
}
}
-Lv2Host::~Lv2Host()
+PiPedalHost::~PiPedalHost()
{
delete[] lv2Features;
lilvUris.Free();
@@ -280,7 +288,7 @@ Lv2Host::~Lv2Host()
delete urids;
}
-void Lv2Host::free_world()
+void PiPedalHost::free_world()
{
if (pWorld)
{
@@ -288,7 +296,7 @@ void Lv2Host::free_world()
pWorld = nullptr;
}
}
-std::shared_ptr Lv2Host::GetPluginClass(const std::string &uri) const
+std::shared_ptr PiPedalHost::GetPluginClass(const std::string &uri) const
{
auto it = this->classesMap.find(uri);
if (it == this->classesMap.end())
@@ -296,7 +304,7 @@ std::shared_ptr Lv2Host::GetPluginClass(const std::string &uri)
return (*it).second;
}
-void Lv2Host::AddJsonClassesToMap(std::shared_ptr pluginClass)
+void PiPedalHost::AddJsonClassesToMap(std::shared_ptr pluginClass)
{
this->classesMap[pluginClass->uri()] = pluginClass;
for (int i = 0; i < pluginClass->children_.size(); ++i)
@@ -306,7 +314,7 @@ void Lv2Host::AddJsonClassesToMap(std::shared_ptr pluginClass)
child->parent_ = pluginClass.get();
}
}
-void Lv2Host::LoadPluginClassesFromJson(std::filesystem::path jsonFile)
+void PiPedalHost::LoadPluginClassesFromJson(std::filesystem::path jsonFile)
{
std::ifstream f(jsonFile);
json_reader reader(f);
@@ -320,7 +328,7 @@ void Lv2Host::LoadPluginClassesFromJson(std::filesystem::path jsonFile)
MAP_CHECK();
}
-std::shared_ptr Lv2Host::GetPluginClass(const LilvPluginClass *pClass)
+std::shared_ptr PiPedalHost::GetPluginClass(const LilvPluginClass *pClass)
{
MAP_CHECK();
@@ -328,7 +336,7 @@ std::shared_ptr Lv2Host::GetPluginClass(const LilvPluginClass *p
std::shared_ptr pResult = this->classesMap[uri];
return pResult;
}
-std::shared_ptr Lv2Host::MakePluginClass(const LilvPluginClass *pClass)
+std::shared_ptr PiPedalHost::MakePluginClass(const LilvPluginClass *pClass)
{
std::string uri = nodeAsString(lilv_plugin_class_get_uri(pClass));
auto t = this->classesMap.find(uri);
@@ -348,7 +356,7 @@ std::shared_ptr Lv2Host::MakePluginClass(const LilvPluginClass *
return result;
}
-void Lv2Host::LoadPluginClassesFromLilv()
+void PiPedalHost::LoadPluginClassesFromLilv()
{
const auto classes = lilv_world_get_plugin_classes(pWorld);
@@ -378,7 +386,7 @@ void Lv2Host::LoadPluginClassesFromLilv()
}
}
-void Lv2Host::Load(const char *lv2Path)
+void PiPedalHost::Load(const char *lv2Path)
{
this->plugins_.clear();
@@ -390,13 +398,14 @@ void Lv2Host::Load(const char *lv2Path)
free_world();
+ Lv2Log::info("Scanning for LV2 Plugins");
+
setenv("LV2_PATH", lv2Path, true);
- StdErrorCapture stdoutCapture; // captures lilv messages written to STDOUT.
+ StdErrorCapture stdoutCapture; // captures lilv messages written to STDOUT.
pWorld = lilv_world_new();
{
lilv_world_load_all(pWorld);
-
}
lilvUris.Initialize(pWorld);
@@ -443,7 +452,7 @@ void Lv2Host::Load(const char *lv2Path)
}
auto messages = stdoutCapture.GetOutputLines();
- for (const std::string & s: messages)
+ for (const std::string &s : messages)
{
Lv2Log::info("lilv: " + s);
}
@@ -491,8 +500,35 @@ void Lv2Host::Load(const char *lv2Path)
}
}
}
-}
-;
+
+#if ENABLE_VST3
+ if (vst3Enabled)
+ {
+ Lv2Log::info("Scanning for VST3 Plugins");
+ this->vst3Host = Vst3Host::CreateInstance(
+ this->vst3CachePath);
+ this->vst3Host->RefreshPlugins();
+
+ const auto &vst3PluginList = this->vst3Host->getPluginList();
+ for (const auto &vst3Plugin : vst3PluginList)
+ {
+ // copy not move!
+ ui_plugins_.push_back(vst3Plugin->pluginInfo_);
+ }
+ auto ui_compare = [&collation](
+ Lv2PluginUiInfo &left,
+ Lv2PluginUiInfo &right)
+ {
+ const char *pb1 = left.name().c_str();
+ const char *pb2 = right.name().c_str();
+ return collation.compare(
+ pb1, pb1 + left.name().size(),
+ pb2, pb2 + right.name().size()) < 0;
+ };
+ std::sort(this->ui_plugins_.begin(), this->ui_plugins_.end(), ui_compare);
+ }
+#endif
+};
class NodesAutoFree
{
@@ -532,10 +568,10 @@ static std::vector nodeAsStringArray(const LilvNodes *nodes)
return result;
}
-const char *Lv2Host::RDFS_COMMENT_URI = "http://www.w3.org/2000/01/rdf-schema#"
- "comment";
+const char *PiPedalHost::RDFS_COMMENT_URI = "http://www.w3.org/2000/01/rdf-schema#"
+ "comment";
-LilvNode *Lv2Host::get_comment(const std::string &uri)
+LilvNode *PiPedalHost::get_comment(const std::string &uri)
{
NodeAutoFree uriNode = lilv_new_uri(pWorld, uri.c_str());
LilvNode *result = lilv_world_get(pWorld, uriNode, lilvUris.rdfsComment, nullptr);
@@ -547,7 +583,7 @@ static bool ports_sort_compare(std::shared_ptr &p1, const std::shar
return p1->index() < p2->index();
}
-bool Lv2PluginInfo::HasFactoryPresets(Lv2Host *lv2Host, const LilvPlugin *plugin)
+bool Lv2PluginInfo::HasFactoryPresets(PiPedalHost *lv2Host, const LilvPlugin *plugin)
{
NodesAutoFree nodes = lilv_plugin_get_related(plugin, lv2Host->lilvUris.pset_Preset);
bool result = false;
@@ -559,7 +595,7 @@ bool Lv2PluginInfo::HasFactoryPresets(Lv2Host *lv2Host, const LilvPlugin *plugin
return result;
}
-Lv2PluginInfo::Lv2PluginInfo(Lv2Host *lv2Host, const LilvPlugin *pPlugin)
+Lv2PluginInfo::Lv2PluginInfo(PiPedalHost *lv2Host, const LilvPlugin *pPlugin)
{
const LilvNode *pluginUri = lilv_plugin_get_uri(pPlugin);
@@ -664,7 +700,7 @@ Lv2PluginInfo::~Lv2PluginInfo()
{
}
-bool Lv2PortInfo::is_a(Lv2Host *lv2Plugins, const char *classUri)
+bool Lv2PortInfo::is_a(PiPedalHost *lv2Plugins, const char *classUri)
{
return classes_.is_a(lv2Plugins, classUri);
}
@@ -686,7 +722,7 @@ static bool scale_points_sort_compare(const Lv2ScalePoint &v1, const Lv2ScalePoi
{
return v1.value() < v2.value();
}
-Lv2PortInfo::Lv2PortInfo(Lv2Host *host, const LilvPlugin *plugin, const LilvPort *pPort)
+Lv2PortInfo::Lv2PortInfo(PiPedalHost *host, const LilvPlugin *plugin, const LilvPort *pPort)
{
auto pWorld = host->pWorld;
index_ = lilv_port_get_index(plugin, pPort);
@@ -785,7 +821,7 @@ Lv2PortInfo::Lv2PortInfo(Lv2Host *host, const LilvPlugin *plugin, const LilvPort
NodeAutoFree unitsValueUri = lilv_port_get(plugin, pPort, host->lilvUris.unitsUri);
this->units_ = UriToUnits(nodeAsString(unitsValueUri));
- NodeAutoFree commentNode = lilv_port_get(plugin,pPort,host->lilvUris.rdfsComment);
+ NodeAutoFree commentNode = lilv_port_get(plugin, pPort, host->lilvUris.rdfsComment);
this->comment_ = nodeAsString(commentNode);
NodeAutoFree bufferType = lilv_port_get(plugin, pPort, host->lilvUris.bufferType_uri);
@@ -800,7 +836,7 @@ Lv2PortInfo::Lv2PortInfo(Lv2Host *host, const LilvPlugin *plugin, const LilvPort
((is_input_ || is_output_) && (is_audio_port_ || is_atom_port_ || is_cv_port_));
}
-Lv2PluginClasses Lv2Host::GetPluginPortClass(const LilvPlugin *lilvPlugin, const LilvPort *lilvPort)
+Lv2PluginClasses PiPedalHost::GetPluginPortClass(const LilvPlugin *lilvPlugin, const LilvPort *lilvPort)
{
std::vector result;
const LilvNodes *nodes = lilv_port_get_classes(lilvPlugin, lilvPort);
@@ -832,7 +868,7 @@ bool Lv2PluginClass::is_a(const std::string &classUri) const
}
return false;
}
-bool Lv2PluginClasses::is_a(Lv2Host *lv2Plugins, const char *classUri) const
+bool Lv2PluginClasses::is_a(PiPedalHost *lv2Plugins, const char *classUri) const
{
std::string classUri_(classUri);
for (auto i : classes_)
@@ -845,7 +881,7 @@ bool Lv2PluginClasses::is_a(Lv2Host *lv2Plugins, const char *classUri) const
return false;
}
-bool Lv2Host::is_a(const std::string &class_, const std::string &target_class)
+bool PiPedalHost::is_a(const std::string &class_, const std::string &target_class)
{
if (class_ == target_class)
return true;
@@ -857,7 +893,7 @@ bool Lv2Host::is_a(const std::string &class_, const std::string &target_class)
return false;
}
-Lv2PluginUiInfo::Lv2PluginUiInfo(Lv2Host *pHost, const Lv2PluginInfo *plugin)
+Lv2PluginUiInfo::Lv2PluginUiInfo(PiPedalHost *pHost, const Lv2PluginInfo *plugin)
: uri_(plugin->uri()),
name_(plugin->name()),
author_name_(plugin->author_name()),
@@ -919,12 +955,12 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(Lv2Host *pHost, const Lv2PluginInfo *plugin)
}
}
-std::shared_ptr Lv2Host::GetLv2PluginClass() const
+std::shared_ptr PiPedalHost::GetLv2PluginClass() const
{
return this->GetPluginClass(LV2_PLUGIN);
}
-std::shared_ptr Lv2Host::GetPluginInfo(const std::string &uri) const
+std::shared_ptr PiPedalHost::GetPluginInfo(const std::string &uri) const
{
for (auto i = this->plugins_.begin(); i != this->plugins_.end(); ++i)
{
@@ -936,7 +972,7 @@ std::shared_ptr Lv2Host::GetPluginInfo(const std::string &uri) co
return nullptr;
}
-Lv2PedalBoard *Lv2Host::CreateLv2PedalBoard(const PedalBoard &pedalBoard)
+Lv2PedalBoard *PiPedalHost::CreateLv2PedalBoard(PedalBoard &pedalBoard)
{
Lv2PedalBoard *pPedalBoard = new Lv2PedalBoard();
try
@@ -953,18 +989,18 @@ Lv2PedalBoard *Lv2Host::CreateLv2PedalBoard(const PedalBoard &pedalBoard)
struct StateCallbackData
{
- Lv2Host *pHost;
+ PiPedalHost *pHost;
std::vector *pResult;
};
-void Lv2Host::fn_LilvSetPortValueFunc(const char *port_symbol,
- void *user_data,
- const void *value,
- uint32_t size,
- uint32_t type)
+void PiPedalHost::fn_LilvSetPortValueFunc(const char *port_symbol,
+ void *user_data,
+ const void *value,
+ uint32_t size,
+ uint32_t type)
{
StateCallbackData *pData = (StateCallbackData *)user_data;
- Lv2Host *pHost = pData->pHost;
+ PiPedalHost *pHost = pData->pHost;
std::vector *pResult = pData->pResult;
LV2_URID valueType = (LV2_URID)type;
if (valueType != pHost->urids->atom_Float)
@@ -974,7 +1010,7 @@ void Lv2Host::fn_LilvSetPortValueFunc(const char *port_symbol,
pResult->push_back(ControlValue(port_symbol, *(float *)value));
}
-std::vector Lv2Host::LoadFactoryPluginPreset(
+std::vector PiPedalHost::LoadFactoryPluginPreset(
PedalBoardItem *pedalBoardItem, const std::string &presetUri)
{
@@ -1032,15 +1068,15 @@ std::vector Lv2Host::LoadFactoryPluginPreset(
struct PresetCallbackState
{
- Lv2Host *pHost;
+ PiPedalHost *pHost;
std::map *values;
bool failed = false;
};
-void Lv2Host::PortValueCallback(const char *symbol, void *user_data, const void *value, uint32_t size, uint32_t type)
+void PiPedalHost::PortValueCallback(const char *symbol, void *user_data, const void *value, uint32_t size, uint32_t type)
{
PresetCallbackState *pState = static_cast(user_data);
- Lv2Host *pHost = pState->pHost;
+ PiPedalHost *pHost = pState->pHost;
if (type == pHost->urids->atom_Float)
{
@@ -1051,7 +1087,7 @@ void Lv2Host::PortValueCallback(const char *symbol, void *user_data, const void
pState->failed = true;
}
}
-PluginPresets Lv2Host::GetFactoryPluginPresets(const std::string &pluginUri)
+PluginPresets PiPedalHost::GetFactoryPluginPresets(const std::string &pluginUri)
{
const LilvPlugins *plugins = lilv_world_get_all_plugins(this->pWorld);
@@ -1093,17 +1129,38 @@ PluginPresets Lv2Host::GetFactoryPluginPresets(const std::string &pluginUri)
return result;
}
-Lv2Effect *Lv2Host::CreateEffect(const PedalBoardItem &pedalBoardItem)
+IEffect *PiPedalHost::CreateEffect(PedalBoardItem &pedalBoardItem)
{
+ if (pedalBoardItem.uri().starts_with("vst3:"))
+ {
+ #if ENABLE_VST3
+ try
+ {
+ Vst3Effect::Ptr vst3Plugin = this->vst3Host->CreatePlugin(pedalBoardItem, this);
+ return vst3Plugin.release();
+ }
+ catch (const std::exception &e)
+ {
+ Lv2Log::error(std::string("Failed to create VST3 plugin: ") + e.what());
+ throw;
+ }
+ #else
+ Lv2Log::error(std::string("VST3 support not enabled at compile time."));
+ throw PiPedalException("VST3 support not enabled at compile time, SEE ENABLE_VST3 in CMakeList.txt.");
- auto info = this->GetPluginInfo(pedalBoardItem.uri());
- if (!info)
- return nullptr;
+ #endif
+ }
+ else
+ {
+ auto info = this->GetPluginInfo(pedalBoardItem.uri());
+ if (!info)
+ return nullptr;
- return new Lv2Effect(this, info, pedalBoardItem);
+ return new Lv2Effect(this, info, pedalBoardItem);
+ }
}
-Lv2PortGroup::Lv2PortGroup(Lv2Host *lv2Host, const std::string &groupUri)
+Lv2PortGroup::Lv2PortGroup(PiPedalHost *lv2Host, const std::string &groupUri)
{
LilvWorld *pWorld = lv2Host->pWorld;
@@ -1131,47 +1188,45 @@ json_map::storage_type Lv2ScalePoint::jmap{{
json_map::reference("label", &Lv2ScalePoint::label_),
}};
-json_map::storage_type Lv2PortInfo::jmap{{
- json_map::reference("index", &Lv2PortInfo::index_),
- json_map::reference("symbol", &Lv2PortInfo::symbol_),
- json_map::reference("index", &Lv2PortInfo::index_),
- json_map::reference("name", &Lv2PortInfo::name_),
+json_map::storage_type Lv2PortInfo::jmap{{json_map::reference("index", &Lv2PortInfo::index_),
+ json_map::reference("symbol", &Lv2PortInfo::symbol_),
+ json_map::reference("index", &Lv2PortInfo::index_),
+ json_map::reference("name", &Lv2PortInfo::name_),
- json_map::reference("min_value", &Lv2PortInfo::min_value_),
- json_map::reference("max_value", &Lv2PortInfo::max_value_),
- json_map::reference("default_value", &Lv2PortInfo::default_value_),
- json_map::reference("classes", &Lv2PortInfo::classes_),
+ json_map::reference("min_value", &Lv2PortInfo::min_value_),
+ json_map::reference("max_value", &Lv2PortInfo::max_value_),
+ json_map::reference("default_value", &Lv2PortInfo::default_value_),
+ json_map::reference("classes", &Lv2PortInfo::classes_),
- json_map::reference("scale_points", &Lv2PortInfo::scale_points_),
+ json_map::reference("scale_points", &Lv2PortInfo::scale_points_),
- json_map::reference("is_input", &Lv2PortInfo::is_input_),
- json_map::reference("is_output", &Lv2PortInfo::is_output_),
+ json_map::reference("is_input", &Lv2PortInfo::is_input_),
+ json_map::reference("is_output", &Lv2PortInfo::is_output_),
- json_map::reference("is_control_port", &Lv2PortInfo::is_control_port_),
- json_map::reference("is_audio_port", &Lv2PortInfo::is_audio_port_),
- json_map::reference("is_atom_port", &Lv2PortInfo::is_audio_port_),
- json_map::reference("is_cv_port", &Lv2PortInfo::is_cv_port_),
+ json_map::reference("is_control_port", &Lv2PortInfo::is_control_port_),
+ json_map::reference("is_audio_port", &Lv2PortInfo::is_audio_port_),
+ json_map::reference("is_atom_port", &Lv2PortInfo::is_audio_port_),
+ json_map::reference("is_cv_port", &Lv2PortInfo::is_cv_port_),
- json_map::reference("is_valid", &Lv2PortInfo::is_valid_),
+ 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_),
+ 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_),
- MAP_REF(Lv2PortInfo, is_logarithmic),
- MAP_REF(Lv2PortInfo, display_priority),
- MAP_REF(Lv2PortInfo, range_steps),
- MAP_REF(Lv2PortInfo, trigger),
- MAP_REF(Lv2PortInfo, integer_property),
- MAP_REF(Lv2PortInfo, enumeration_property),
- MAP_REF(Lv2PortInfo, toggled_property),
- MAP_REF(Lv2PortInfo, not_on_gui),
- MAP_REF(Lv2PortInfo, buffer_type),
- MAP_REF(Lv2PortInfo, port_group),
- json_map::enum_reference("units", &Lv2PortInfo::units_, get_units_enum_converter()),
- MAP_REF(Lv2PortInfo, comment)
-}};
+ MAP_REF(Lv2PortInfo, is_logarithmic),
+ MAP_REF(Lv2PortInfo, display_priority),
+ MAP_REF(Lv2PortInfo, range_steps),
+ MAP_REF(Lv2PortInfo, trigger),
+ MAP_REF(Lv2PortInfo, integer_property),
+ MAP_REF(Lv2PortInfo, enumeration_property),
+ MAP_REF(Lv2PortInfo, toggled_property),
+ MAP_REF(Lv2PortInfo, not_on_gui),
+ MAP_REF(Lv2PortInfo, buffer_type),
+ MAP_REF(Lv2PortInfo, port_group),
+ json_map::enum_reference("units", &Lv2PortInfo::units_, get_units_enum_converter()),
+ MAP_REF(Lv2PortInfo, comment)}};
json_map::storage_type Lv2PortGroup::jmap{{
MAP_REF(Lv2PortGroup, uri),
@@ -1197,21 +1252,19 @@ json_map::storage_type Lv2PluginInfo::jmap{{
json_map::reference("has_factory_presets", &Lv2PluginInfo::has_factory_presets_),
}};
-json_map::storage_type Lv2PluginUiInfo::jmap{{
- json_map::reference("uri", &Lv2PluginUiInfo::uri_),
- json_map::reference("name", &Lv2PluginUiInfo::name_),
- json_map::enum_reference("plugin_type", &Lv2PluginUiInfo::plugin_type_, get_plugin_type_enum_converter()),
- json_map::reference("plugin_display_type", &Lv2PluginUiInfo::plugin_display_type_),
- json_map::reference("author_name", &Lv2PluginUiInfo::author_name_),
- json_map::reference("author_homepage", &Lv2PluginUiInfo::author_homepage_),
- json_map::reference("audio_inputs", &Lv2PluginUiInfo::audio_inputs_),
- json_map::reference("audio_outputs", &Lv2PluginUiInfo::audio_outputs_),
- json_map::reference("description", &Lv2PluginUiInfo::description_),
- json_map::reference("has_midi_input", &Lv2PluginUiInfo::has_midi_input_),
- json_map::reference("has_midi_output", &Lv2PluginUiInfo::has_midi_output_),
- json_map::reference("controls", &Lv2PluginUiInfo::controls_),
- json_map::reference("port_groups", &Lv2PluginUiInfo::port_groups_),
+json_map::storage_type Lv2PluginClass::jmap{{
+ MAP_REF(Lv2PluginClass, uri),
+ MAP_REF(Lv2PluginClass, display_name),
+ MAP_REF(Lv2PluginClass, parent_uri),
+ json_map::enum_reference("plugin_type", &Lv2PluginClass::plugin_type_, get_plugin_type_enum_converter()),
+ MAP_REF(Lv2PluginClass, children),
+}};
+json_map::storage_type Lv2PluginUiPortGroup::jmap{{
+ MAP_REF(Lv2PluginUiPortGroup, symbol),
+ MAP_REF(Lv2PluginUiPortGroup, name),
+ MAP_REF(Lv2PluginUiPortGroup, parent_group),
+ MAP_REF(Lv2PluginUiPortGroup, program_list_id),
}};
json_map::storage_type Lv2PluginUiControlPort::jmap{{
@@ -1234,18 +1287,26 @@ json_map::storage_type Lv2PluginUiControlPort::jmap{{
json_map::enum_reference("units", &Lv2PluginUiControlPort::units_, get_units_enum_converter()),
MAP_REF(Lv2PluginUiControlPort, comment),
+ MAP_REF(Lv2PluginUiControlPort, is_bypass),
+ MAP_REF(Lv2PluginUiControlPort, is_program_controller),
+ MAP_REF(Lv2PluginUiControlPort, custom_units),
}};
-json_map::storage_type Lv2PluginClass::jmap{{
- MAP_REF(Lv2PluginClass, uri),
- MAP_REF(Lv2PluginClass, display_name),
- MAP_REF(Lv2PluginClass, parent_uri),
- json_map::enum_reference("plugin_type", &Lv2PluginClass::plugin_type_, get_plugin_type_enum_converter()),
- MAP_REF(Lv2PluginClass, children),
-}};
+json_map::storage_type Lv2PluginUiInfo::jmap{{
+ json_map::reference("uri", &Lv2PluginUiInfo::uri_),
+ json_map::reference("name", &Lv2PluginUiInfo::name_),
+ json_map::enum_reference("plugin_type", &Lv2PluginUiInfo::plugin_type_, get_plugin_type_enum_converter()),
+ json_map::reference("plugin_display_type", &Lv2PluginUiInfo::plugin_display_type_),
+ json_map::reference("author_name", &Lv2PluginUiInfo::author_name_),
+ json_map::reference("author_homepage", &Lv2PluginUiInfo::author_homepage_),
+ json_map::reference("audio_inputs", &Lv2PluginUiInfo::audio_inputs_),
+ json_map::reference("audio_outputs", &Lv2PluginUiInfo::audio_outputs_),
+ json_map::reference("description", &Lv2PluginUiInfo::description_),
+ json_map::reference("has_midi_input", &Lv2PluginUiInfo::has_midi_input_),
+ json_map::reference("has_midi_output", &Lv2PluginUiInfo::has_midi_output_),
+ json_map::reference("controls", &Lv2PluginUiInfo::controls_),
+ json_map::reference("port_groups", &Lv2PluginUiInfo::port_groups_),
+ json_map::reference("is_vst3", &Lv2PluginUiInfo::is_vst3_),
-json_map::storage_type Lv2PluginUiPortGroup::jmap{{
- MAP_REF(Lv2PluginUiPortGroup, symbol),
- MAP_REF(Lv2PluginUiPortGroup, name),
}};
diff --git a/src/Lv2Host.hpp b/src/PiPedalHost.hpp
similarity index 71%
rename from src/Lv2Host.hpp
rename to src/PiPedalHost.hpp
index 5d8197c..b29a0b0 100644
--- a/src/Lv2Host.hpp
+++ b/src/PiPedalHost.hpp
@@ -30,11 +30,15 @@
#include "OptionsFeature.hpp"
#include
#include
+#include
#include "lv2.h"
#include "Units.hpp"
#include "PluginPreset.hpp"
+#include "IEffect.hpp"
+#include "PiPedalConfiguration.hpp"
+
namespace pipedal {
@@ -42,19 +46,23 @@ namespace pipedal {
// forward declarations
class Lv2Effect;
class Lv2PedalBoard;
-class Lv2Host;
+class PiPedalHost;
class JackConfiguration;
class JackChannelSelection;
+#ifndef LV2_PROPERTY_GETSET
#define LV2_PROPERTY_GETSET(name) \
const decltype(name##_) & name() const { return name##_; }; \
decltype(name##_) & name() { return name##_; }; \
void name(const decltype(name##_) &value) { name##_ = value; };
+#endif
+#ifndef LV2_PROPERTY_GETSET_SCALAR
#define LV2_PROPERTY_GETSET_SCALAR(name) \
decltype(name##_) name() const { return name##_;}; \
void name(decltype(name##_) value) { name##_ = value; };
+#endif
@@ -62,7 +70,7 @@ class JackChannelSelection;
class Lv2PluginClass {
public:
- friend class Lv2Host;
+ friend class PiPedalHost;
private:
Lv2PluginClass* parent_ = nullptr; // NOT SERIALIZED!
std::string parent_uri_;
@@ -72,7 +80,7 @@ private:
std::vector> children_;
- friend class ::pipedal::Lv2Host;
+ friend class ::pipedal::PiPedalHost;
// hide copy constructor.
Lv2PluginClass(const Lv2PluginClass &other)
{
@@ -135,7 +143,7 @@ public:
{
return classes_;
}
- bool is_a(Lv2Host*lv2Plugins,const char*classUri) const;
+ bool is_a(PiPedalHost*lv2Plugins,const char*classUri) const;
static json_map::storage_type jmap;
@@ -171,7 +179,7 @@ enum class Lv2BufferType {
class Lv2PortInfo {
public:
- Lv2PortInfo(Lv2Host*lv2Host,const LilvPlugin*pPlugin,const LilvPort *pPort);
+ Lv2PortInfo(PiPedalHost*lv2Host,const LilvPlugin*pPlugin,const LilvPort *pPort);
private:
friend class Lv2PluginInfo;
@@ -276,7 +284,7 @@ public:
public:
Lv2PortInfo() { }
~Lv2PortInfo() = default;
- bool is_a(Lv2Host*lv2Plugins,const char*classUri);
+ bool is_a(PiPedalHost*lv2Plugins,const char*classUri);
static json_map::storage_type jmap;
@@ -294,19 +302,19 @@ public:
LV2_PROPERTY_GETSET(name);
Lv2PortGroup() { }
- Lv2PortGroup(Lv2Host*lv2Host,const std::string &groupUri);
+ Lv2PortGroup(PiPedalHost*lv2Host,const std::string &groupUri);
static json_map::storage_type jmap;
};
class Lv2PluginInfo {
private:
- friend class Lv2Host;
+ friend class PiPedalHost;
public:
- Lv2PluginInfo(Lv2Host*lv2Host,const LilvPlugin*);
+ Lv2PluginInfo(PiPedalHost*lv2Host,const LilvPlugin*);
Lv2PluginInfo() { }
private:
- bool HasFactoryPresets(Lv2Host*lv2Host, const LilvPlugin*plugin);
+ bool HasFactoryPresets(PiPedalHost*lv2Host, const LilvPlugin*plugin);
std::string uri_;
std::string name_;
std::string plugin_class_;
@@ -399,139 +407,151 @@ public:
};
-
-class Lv2PluginUiControlPort {
-public:
- Lv2PluginUiControlPort() {
- }
- Lv2PluginUiControlPort(const Lv2PluginInfo *pPlugin,const Lv2PortInfo*pPort)
- : symbol_(pPort->symbol())
- , index_(pPort->index())
- , name_(pPort->name())
- , min_value_(pPort->min_value())
- , max_value_(pPort->max_value())
- , default_value_(pPort->default_value())
- , range_steps_(pPort->range_steps())
- , display_priority_(pPort->display_priority())
- , is_logarithmic_(pPort->is_logarithmic())
- , integer_property_(pPort->integer_property())
- , enumeration_property_(pPort->enumeration_property())
- , toggled_property_(pPort->toggled_property())
- , not_on_gui_(pPort->not_on_gui())
- , scale_points_(pPort->scale_points())
- , comment_(pPort->comment())
- , units_(pPort->units())
+ class Lv2PluginUiPortGroup
{
- // Use symbols to index port groups, instead of uris.
- // symbols are guaranteed to be unique.
- auto &portGroup = pPort->port_group();
- for (int i = 0; i < pPlugin->port_groups().size(); ++i)
+ private:
+ std::string symbol_;
+ std::string name_;
+
+ std::string parent_group_;
+ int32_t program_list_id_ = -1; // used by VST3.
+ public:
+ LV2_PROPERTY_GETSET(symbol)
+ LV2_PROPERTY_GETSET(name)
+ LV2_PROPERTY_GETSET(parent_group)
+ LV2_PROPERTY_GETSET_SCALAR(program_list_id)
+
+ public:
+ Lv2PluginUiPortGroup() {}
+ Lv2PluginUiPortGroup(Lv2PortGroup *pPortGroup)
+ : symbol_(pPortGroup->symbol()), name_(pPortGroup->name())
+ {
+ }
+ Lv2PluginUiPortGroup(
+ const std::string &symbol, const std::string &name,
+ const std::string &parent_group, int32_t programListId)
+ : symbol_(symbol),name_(name),parent_group_(parent_group),program_list_id_(programListId)
{
- auto &p = pPlugin->port_groups()[i];
- if (p->uri() == portGroup)
- {
- this->port_group_ = p->symbol();
- break;
- }
-
+ }
- }
- }
-private:
- std::string symbol_;
- int index_;
- std::string name_;
- float min_value_ = 0, max_value_ = 1,default_value_ = 0;
- int range_steps_ = 0;
- int display_priority_ = -1;
- bool is_logarithmic_ = false;
- bool integer_property_ = false;
- bool enumeration_property_ = false;
- bool toggled_property_ = false;
- bool not_on_gui_ = false;
- std::vector scale_points_;
- std::string port_group_;
- Units units_ = Units::none;
- std::string comment_;
-public:
- LV2_PROPERTY_GETSET(symbol);
- LV2_PROPERTY_GETSET_SCALAR(index);
- LV2_PROPERTY_GETSET(name);
- LV2_PROPERTY_GETSET_SCALAR(min_value);
- LV2_PROPERTY_GETSET_SCALAR(max_value);
- LV2_PROPERTY_GETSET_SCALAR(default_value);
- LV2_PROPERTY_GETSET_SCALAR(range_steps);
- LV2_PROPERTY_GETSET_SCALAR(display_priority);
- LV2_PROPERTY_GETSET_SCALAR(is_logarithmic);
- LV2_PROPERTY_GETSET_SCALAR(integer_property);
- LV2_PROPERTY_GETSET_SCALAR(enumeration_property);
- LV2_PROPERTY_GETSET_SCALAR(toggled_property);
- LV2_PROPERTY_GETSET_SCALAR(not_on_gui);
- LV2_PROPERTY_GETSET(scale_points);
- LV2_PROPERTY_GETSET(units);
- LV2_PROPERTY_GETSET(comment);
+ public:
+ static json_map::storage_type jmap;
+ };
-public:
- static json_map::storage_type jmap;
-
-};
-
-class Lv2PluginUiPortGroup {
-private:
- std::string symbol_;
- std::string name_;
-public:
- Lv2PluginUiPortGroup() { }
- Lv2PluginUiPortGroup(Lv2PortGroup *pPortGroup)
- : symbol_(pPortGroup->symbol())
- , name_(pPortGroup->name())
+ class Lv2PluginUiControlPort
{
- }
-public:
- static json_map::storage_type jmap;
+ public:
+ Lv2PluginUiControlPort()
+ {
+ }
+ Lv2PluginUiControlPort(const Lv2PluginInfo *pPlugin, const Lv2PortInfo *pPort)
+ : symbol_(pPort->symbol()), index_(pPort->index()), name_(pPort->name()), min_value_(pPort->min_value()), max_value_(pPort->max_value()), default_value_(pPort->default_value()), range_steps_(pPort->range_steps()), display_priority_(pPort->display_priority()), is_logarithmic_(pPort->is_logarithmic()), integer_property_(pPort->integer_property()), enumeration_property_(pPort->enumeration_property()), toggled_property_(pPort->toggled_property()), not_on_gui_(pPort->not_on_gui()), scale_points_(pPort->scale_points()), comment_(pPort->comment()), units_(pPort->units())
+ {
+ // Use symbols to index port groups, instead of uris.
+ // symbols are guaranteed to be unique.
+ auto &portGroup = pPort->port_group();
+ for (int i = 0; i < pPlugin->port_groups().size(); ++i)
+ {
-};
+ auto &p = pPlugin->port_groups()[i];
+ if (p->uri() == portGroup)
+ {
+ this->port_group_ = p->symbol();
+ break;
+ }
+ }
+ }
-class Lv2PluginUiInfo {
-public:
- Lv2PluginUiInfo() { }
- Lv2PluginUiInfo(Lv2Host *pPlugins,const Lv2PluginInfo *plugin);
+ private:
+ std::string symbol_;
+ int index_;
+ std::string name_;
+ float min_value_ = 0, max_value_ = 1, default_value_ = 0;
+ int range_steps_ = 0;
+ int display_priority_ = -1;
+ bool is_logarithmic_ = false;
+ bool integer_property_ = false;
+ bool enumeration_property_ = false;
+ bool toggled_property_ = false;
+ bool not_on_gui_ = false;
+ std::vector scale_points_;
+ std::string port_group_;
+ Units units_ = Units::none;
+ std::string comment_;
+ bool is_bypass_ = false;
+ bool is_program_controller_ = false;
+ std::string custom_units_;
-private:
- std::string uri_;
- std::string name_;
- std::string author_name_;
- std::string author_homepage_;
- PluginType plugin_type_;
- std::string plugin_display_type_;
- int audio_inputs_ = 0;
- int audio_outputs_ = 0;
- int has_midi_input_ = false;
- int has_midi_output_ = false;
- std::string description_;
+ public:
+ LV2_PROPERTY_GETSET(symbol);
+ LV2_PROPERTY_GETSET_SCALAR(index);
+ LV2_PROPERTY_GETSET(name);
+ LV2_PROPERTY_GETSET(port_group);
+ LV2_PROPERTY_GETSET_SCALAR(min_value);
+ LV2_PROPERTY_GETSET_SCALAR(max_value);
+ LV2_PROPERTY_GETSET_SCALAR(default_value);
+ LV2_PROPERTY_GETSET_SCALAR(range_steps);
+ LV2_PROPERTY_GETSET_SCALAR(display_priority);
+ LV2_PROPERTY_GETSET_SCALAR(is_logarithmic);
+ LV2_PROPERTY_GETSET_SCALAR(integer_property);
+ LV2_PROPERTY_GETSET_SCALAR(enumeration_property);
+ LV2_PROPERTY_GETSET_SCALAR(toggled_property);
+ LV2_PROPERTY_GETSET_SCALAR(not_on_gui);
+ LV2_PROPERTY_GETSET(scale_points);
+ LV2_PROPERTY_GETSET(units);
+ LV2_PROPERTY_GETSET(comment);
+ LV2_PROPERTY_GETSET_SCALAR(is_bypass);
+ LV2_PROPERTY_GETSET_SCALAR(is_program_controller);
+ LV2_PROPERTY_GETSET(custom_units);
- std::vector controls_;
- std::vector port_groups_;
+ public:
+ static json_map::storage_type jmap;
+ };
-public:
- LV2_PROPERTY_GETSET(uri)
- LV2_PROPERTY_GETSET(name)
- LV2_PROPERTY_GETSET(author_name)
- LV2_PROPERTY_GETSET(author_homepage)
- LV2_PROPERTY_GETSET_SCALAR(plugin_type)
- LV2_PROPERTY_GETSET(plugin_display_type)
- LV2_PROPERTY_GETSET_SCALAR(audio_inputs)
- LV2_PROPERTY_GETSET_SCALAR(audio_outputs)
- LV2_PROPERTY_GETSET_SCALAR(has_midi_input)
- LV2_PROPERTY_GETSET_SCALAR(has_midi_output)
- LV2_PROPERTY_GETSET_SCALAR(description)
- LV2_PROPERTY_GETSET(port_groups)
+ class Lv2PluginUiInfo
+ {
+ public:
+ Lv2PluginUiInfo() {}
+ Lv2PluginUiInfo(PiPedalHost *pPlugins, const Lv2PluginInfo *plugin);
+
+ private:
+ std::string uri_;
+ std::string name_;
+ std::string author_name_;
+ std::string author_homepage_;
+ PluginType plugin_type_;
+ std::string plugin_display_type_;
+ int audio_inputs_ = 0;
+ int audio_outputs_ = 0;
+ int has_midi_input_ = false;
+ int has_midi_output_ = false;
+ std::string description_;
+ bool is_vst3_ = false;
+
+ std::vector controls_;
+ std::vector port_groups_;
+
+ public:
+ LV2_PROPERTY_GETSET(uri)
+ LV2_PROPERTY_GETSET(name)
+ LV2_PROPERTY_GETSET(author_name)
+ LV2_PROPERTY_GETSET(author_homepage)
+ LV2_PROPERTY_GETSET_SCALAR(plugin_type)
+ LV2_PROPERTY_GETSET(plugin_display_type)
+ LV2_PROPERTY_GETSET_SCALAR(audio_inputs)
+ LV2_PROPERTY_GETSET_SCALAR(audio_outputs)
+ LV2_PROPERTY_GETSET_SCALAR(has_midi_input)
+ LV2_PROPERTY_GETSET_SCALAR(has_midi_output)
+ LV2_PROPERTY_GETSET_SCALAR(description)
+ LV2_PROPERTY_GETSET(controls)
+ LV2_PROPERTY_GETSET(port_groups)
+ LV2_PROPERTY_GETSET_SCALAR(is_vst3)
+
+ static json_map::storage_type jmap;
+ };
- static json_map::storage_type jmap;
-
-};
class IHost {
public:
@@ -552,7 +572,7 @@ public:
virtual std::shared_ptr GetPluginInfo(const std::string&uri) const = 0;
- virtual Lv2Effect*CreateEffect(const PedalBoardItem &pedalBoard) = 0;
+ virtual IEffect*CreateEffect(PedalBoardItem &pedalBoard) = 0;
};
@@ -583,8 +603,24 @@ public:
LilvNodePtr&operator=(LilvNode*node) { Free(); this->node = node; return *this;}
};
-class Lv2Host: private IHost {
+}
+
+#if ENABLE_VST3
+#include "vst3/Vst3Host.hpp"
+#endif
+
+
+namespace pipedal{
+
+
+
+
+class PiPedalHost: private IHost {
private:
+#if ENABLE_VST3
+ Vst3Host::Ptr vst3Host;
+
+#endif
static const char *RDFS_COMMENT_URI;
class LilvUris {
public:
@@ -619,6 +655,8 @@ private:
};
+ bool vst3Enabled = true;
+
LilvUris lilvUris;
LilvNode *get_comment(const std::string &uri);
@@ -631,6 +669,8 @@ private:
int numberOfAudioOutputChannels = 2;
double sampleRate = 0;
+ std::string vst3CachePath;
+
LV2_Feature*const*lv2Features = nullptr;
MapFeature mapFeature;
LogFeature logFeature;
@@ -678,17 +718,20 @@ private:
}
static void PortValueCallback(const char*symbol,void*user_data,const void* value,uint32_t size, uint32_t type);
- virtual Lv2Effect*CreateEffect(const PedalBoardItem &pedalBoardItem);
+ virtual IEffect*CreateEffect(PedalBoardItem &pedalBoardItem);
void LoadPluginClassesFromLilv();
void AddJsonClassesToMap(std::shared_ptr pluginClass);
public:
- Lv2Host();
- virtual ~Lv2Host();
+ PiPedalHost();
+
+ void SetConfiguration(const PiPedalConfiguration&configuration);
+
+ virtual ~PiPedalHost();
IHost* asIHost() { return this;}
- virtual Lv2PedalBoard * CreateLv2PedalBoard(const PedalBoard& pedalBoard);
+ virtual Lv2PedalBoard * CreateLv2PedalBoard(PedalBoard& pedalBoard);
void setSampleRate(double sampleRate)
{
@@ -719,7 +762,7 @@ public:
void LoadPluginClassesFromJson(std::filesystem::path jsonFile);
- void Load(const char*lv2Path = Lv2Host::DEFAULT_LV2_PATH);
+ void Load(const char*lv2Path = PiPedalHost::DEFAULT_LV2_PATH);
virtual LV2_URID GetLv2Urid(const char*uri) {
return this->mapFeature.GetUrid(uri);
@@ -736,5 +779,5 @@ public:
};
#undef LV2_PROPERTY_GETSET
-
+#undef LV2_PROPERTY_GETSET_SCALAR
} // namespace pipedal.
\ No newline at end of file
diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp
index 60a14fc..c2abee5 100644
--- a/src/PiPedalModel.cpp
+++ b/src/PiPedalModel.cpp
@@ -43,9 +43,18 @@ T &constMutex(const T &mutex)
return const_cast(mutex);
}
-std::string AtomToJson(int length, uint8_t *pData)
+static const char *hexChars = "0123456789ABCDEF";
+
+static std::string BytesToHex(const std::vector &bytes)
{
- return "";
+ std::stringstream s;
+
+ for (size_t i = 0; i < bytes.size(); ++i)
+ {
+ uint8_t b = bytes[i];
+ s << hexChars[(b >> 4) & 0x0F] << hexChars[b & 0x0F];
+ }
+ return s.str();
}
PiPedalModel::PiPedalModel()
@@ -119,6 +128,7 @@ PiPedalModel::~PiPedalModel()
void PiPedalModel::Init(const PiPedalConfiguration &configuration)
{
this->configuration = configuration;
+ lv2Host.SetConfiguration(configuration);
storage.SetConfigRoot(configuration.GetDocRoot());
storage.SetDataRoot(configuration.GetLocalStoragePath());
storage.Initialize();
@@ -282,7 +292,17 @@ void PiPedalModel::RemoveNotificationSubsription(IPiPedalModelSubscriber *pSubsc
void PiPedalModel::PreviewControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value)
{
- jackHost->SetControlValue(pedalItemId, symbol, value);
+ IEffect* effect = lv2PedalBoard->GetEffect(pedalItemId);
+ if (effect->IsVst3())
+ {
+ int index = lv2PedalBoard->GetControlIndex(pedalItemId, symbol);
+ if (index != -1)
+ {
+ effect->SetControl(index,value);;
+ }
+ } else {
+ jackHost->SetControlValue(pedalItemId, symbol, value);
+ }
}
void PiPedalModel::SetControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value)
@@ -291,6 +311,8 @@ void PiPedalModel::SetControl(int64_t clientId, int64_t pedalItemId, const std::
std::lock_guard guard(mutex);
PreviewControl(clientId, pedalItemId, symbol, value);
this->pedalBoard.SetControlValue(pedalItemId, symbol, value);
+ IEffect *pEffect = this->lv2PedalBoard->GetEffect(pedalItemId);
+
{
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
@@ -300,9 +322,11 @@ void PiPedalModel::SetControl(int64_t clientId, int64_t pedalItemId, const std::
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
- for (size_t i = 0; i < n; ++i)
{
- t[i]->OnControlChanged(clientId, pedalItemId, symbol, value);
+ for (size_t i = 0; i < n; ++i)
+ {
+ t[i]->OnControlChanged(clientId, pedalItemId, symbol, value);
+ }
}
delete[] t;
@@ -379,6 +403,23 @@ void PiPedalModel::SetPedalBoard(int64_t clientId, PedalBoard &pedalBoard)
this->SetPresetChanged(clientId, true);
}
}
+void PiPedalModel::UpdateCurrentPedalBoard(int64_t clientId, PedalBoard &pedalBoard)
+{
+ {
+ std::lock_guard guard(mutex);
+
+ // update vst3 presets if neccessary.
+ // the pedalboard must be a manipualted instance of the current Lv2Pedalboard.
+
+ UpdateVst3Settings(pedalBoard);
+
+ this->pedalBoard = pedalBoard;
+ UpdateDefaults(&this->pedalBoard);
+
+ this->FirePedalBoardChanged(clientId);
+ this->SetPresetChanged(clientId, true);
+ }
+}
void PiPedalModel::SetPedalBoardItemEnable(int64_t clientId, int64_t pedalItemId, bool enabled)
{
@@ -475,10 +516,35 @@ void PiPedalModel::FirePluginPresetsChanged(const std::string &pluginUri)
}
}
+void PiPedalModel::UpdateVst3Settings(PedalBoard &pedalBoard)
+{
+ // get the vst3 state bundle from lv2Pedalboard for the current pedalBoard.
+#if ENABLE_VST3
+ PedalBoard pb;
+ for (IEffect *effect : lv2PedalBoard->GetEffects())
+ {
+ if (effect->IsVst3())
+ {
+ PedalBoardItem *item = pedalBoard.GetItem(effect->GetInstanceId());
+ if (item)
+ {
+ Vst3Effect *vst3Effect = (Vst3Effect *)effect;
+ std::vector state;
+ if (vst3Effect->GetState(&state))
+ {
+ item->vstState(BytesToHex(state));
+ }
+ }
+ }
+ }
+#endif
+}
+
void PiPedalModel::SaveCurrentPreset(int64_t clientId)
{
std::lock_guard guard{mutex};
+ UpdateVst3Settings(this->pedalBoard);
storage.SaveCurrentPreset(this->pedalBoard);
this->SetPresetChanged(clientId, false);
}
@@ -517,6 +583,7 @@ int64_t PiPedalModel::SaveCurrentPresetAs(int64_t clientId, const std::string &n
std::lock_guard guard{mutex};
auto pedalboard = this->pedalBoard;
+ UpdateVst3Settings(pedalBoard);
pedalboard.name(name);
int64_t result = storage.SaveCurrentPresetAs(pedalboard, name, saveAfterInstanceId);
FirePresetsChanged(clientId);
@@ -697,14 +764,13 @@ void PiPedalModel::SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSet
}
}
-
static std::string GetP2pdName()
{
std::string name = "/etc/pipedal/config/pipedal_p2pd.conf";
std::string result;
- if (ConfigUtil::GetConfigLine(name,"p2p_device_name",&result))
+ if (ConfigUtil::GetConfigLine(name, "p2p_device_name", &result))
{
return result;
}
@@ -718,17 +784,15 @@ void PiPedalModel::UpdateDnsSd()
ServiceConfiguration deviceIdFile;
deviceIdFile.Load();
-
std::string p2pdName = GetP2pdName();
- if (p2pdName != "") {
+ if (p2pdName != "")
+ {
deviceIdFile.deviceName = p2pdName;
}
if (deviceIdFile.deviceName != "" && deviceIdFile.uuid != "")
{
avahiService.Announce(webPort, deviceIdFile.deviceName, deviceIdFile.uuid, "pipedal");
-
-
}
else
{
@@ -820,16 +884,16 @@ void PiPedalModel::RestartAudio()
this->jackHost->Close();
}
- // restarting is a bit dodgy. It was impossible with Jack, but
- // now very plausible with the ALSA audio stack.
+ // restarting is a bit dodgy. It was impossible with Jack, but
+ // now very plausible with the ALSA audio stack.
- // Still bugs wrt/ restarting the circular buffers for the audio thread.
+ // Still bugs wrt/ restarting the circular buffers for the audio thread.
- // for the meantime, just rely on the fact that the admin service will restart
- // the process.
- //...
+ // for the meantime, just rely on the fact that the admin service will restart
+ // the process.
+ //...
- // do a complete reload.
+ // do a complete reload.
this->jackHost->SetPedalBoard(nullptr);
@@ -839,14 +903,14 @@ void PiPedalModel::RestartAudio()
{
JackChannelSelection selection = storage.GetJackChannelSelection(jackConfiguration);
selection = selection.RemoveInvalidChannels(jackConfiguration);
-
- } else {
+ }
+ else
+ {
jackConfiguration.SetErrorStatus("Error");
}
FireJackConfigurationChanged(jackConfiguration);
-
if (!jackServerSettings.IsValid() || !jackConfiguration.isValid())
{
Lv2Log::error("Audio configuration not valid.");
@@ -1223,12 +1287,11 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
}
delete[] t;
-
#if ALSA_HOST
storage.SetJackServerSettings(jackServerSettings);
FireJackConfigurationChanged(this->jackConfiguration);
-
+
guard.unlock();
RestartAudio();
@@ -1280,7 +1343,6 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
#endif
}
});
-
}
#endif
}
diff --git a/src/PiPedalModel.hpp b/src/PiPedalModel.hpp
index dd2a7cc..3e1b3ae 100644
--- a/src/PiPedalModel.hpp
+++ b/src/PiPedalModel.hpp
@@ -19,7 +19,7 @@
#pragma once
#include
-#include "Lv2Host.hpp"
+#include "PiPedalHost.hpp"
#include "GovernorSettings.hpp"
#include "PedalBoard.hpp"
#include "Storage.hpp"
@@ -52,6 +52,7 @@ public:
virtual int64_t GetClientId() = 0;
virtual void OnItemEnabledChanged(int64_t clientId, int64_t pedalItemId, bool enabled) = 0;
virtual void OnControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value) = 0;
+ virtual void OnVst3ControlChanged(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value, const std::string&state) = 0;
virtual void OnPedalBoardChanged(int64_t clientId, const PedalBoard &pedalBoard) = 0;
virtual void OnPresetsChanged(int64_t clientId, const PresetIndex&presets) = 0;
virtual void OnPluginPresetsChanged(const std::string&pluginUri) = 0;
@@ -104,7 +105,7 @@ private:
std::vector atomOutputListeners;
JackServerSettings jackServerSettings;
- Lv2Host lv2Host;
+ PiPedalHost lv2Host;
PedalBoard pedalBoard;
Storage storage;
bool hasPresetChanged = false;
@@ -155,6 +156,7 @@ private: // IJackHostCallbacks
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl);
virtual void OnNotifyAtomOutput(uint64_t instanceId, const std::string&atomType,const std::string&atomJson);
+ void UpdateVst3Settings(PedalBoard&pedalBoard);
PiPedalConfiguration configuration;
public:
@@ -173,7 +175,7 @@ public:
void LoadLv2PluginInfo();
void Load();
- const Lv2Host& GetLv2Host() const { return lv2Host; }
+ const PiPedalHost& GetLv2Host() const { return lv2Host; }
PedalBoard GetCurrentPedalBoardCopy()
{
std::lock_guard guard(mutex);
@@ -191,6 +193,8 @@ public:
void SetControl(int64_t clientId,int64_t pedalItemId, const std::string&symbol,float value);
void PreviewControl(int64_t clientId,int64_t pedalItemId, const std::string&symbol,float value);
void SetPedalBoard(int64_t clientId,PedalBoard &pedalBoard);
+ void UpdateCurrentPedalBoard(int64_t clientId, PedalBoard &pedalBoard);
+
void GetPresets(PresetIndex*pResult);
PedalBoard GetPreset(int64_t instanceId);
diff --git a/src/PiPedalSocket.cpp b/src/PiPedalSocket.cpp
index bc4bf46..d058eea 100644
--- a/src/PiPedalSocket.cpp
+++ b/src/PiPedalSocket.cpp
@@ -277,18 +277,18 @@ JSON_MAP_REFERENCE(PedalBoardItemEnabledBody, instanceId)
JSON_MAP_REFERENCE(PedalBoardItemEnabledBody, enabled)
JSON_MAP_END()
-class SetCurrentPedalBoardBody
+class UpdateCurrentPedalBoardBody
{
public:
int64_t clientId_ = -1;
PedalBoard pedalBoard_;
- DECLARE_JSON_MAP(SetCurrentPedalBoardBody);
+ DECLARE_JSON_MAP(UpdateCurrentPedalBoardBody);
};
-JSON_MAP_BEGIN(SetCurrentPedalBoardBody)
-JSON_MAP_REFERENCE(SetCurrentPedalBoardBody, clientId)
-JSON_MAP_REFERENCE(SetCurrentPedalBoardBody, pedalBoard)
+JSON_MAP_BEGIN(UpdateCurrentPedalBoardBody)
+JSON_MAP_REFERENCE(UpdateCurrentPedalBoardBody, clientId)
+JSON_MAP_REFERENCE(UpdateCurrentPedalBoardBody, pedalBoard)
JSON_MAP_END()
class ChannelSelectionChangedBody
@@ -335,6 +335,26 @@ JSON_MAP_REFERENCE(ControlChangedBody, symbol)
JSON_MAP_REFERENCE(ControlChangedBody, value)
JSON_MAP_END()
+class Vst3ControlChangedBody
+{
+public:
+ int64_t clientId_;
+ int64_t instanceId_;
+ std::string symbol_;
+ float value_;
+ std::string state_;
+
+ DECLARE_JSON_MAP(Vst3ControlChangedBody);
+};
+JSON_MAP_BEGIN(Vst3ControlChangedBody)
+JSON_MAP_REFERENCE(Vst3ControlChangedBody, clientId)
+JSON_MAP_REFERENCE(Vst3ControlChangedBody, instanceId)
+JSON_MAP_REFERENCE(Vst3ControlChangedBody, symbol)
+JSON_MAP_REFERENCE(Vst3ControlChangedBody, value)
+JSON_MAP_REFERENCE(Vst3ControlChangedBody, state)
+JSON_MAP_END()
+
+
class PiPedalSocketHandler : public SocketHandler, public IPiPedalModelSubscriber
{
private:
@@ -940,13 +960,13 @@ public:
pReader->read(&body);
model.SetPedalBoardItemEnable(body.clientId_, body.instanceId_, body.enabled_);
}
- else if (message == "setCurrentPedalBoard")
+ else if (message == "updateCurrentPedalBoard")
{
{
- SetCurrentPedalBoardBody body;
+ UpdateCurrentPedalBoardBody body;
pReader->read(&body);
- this->model.SetPedalBoard(body.clientId_, body.pedalBoard_);
+ this->model.UpdateCurrentPedalBoard(body.clientId_, body.pedalBoard_);
}
}
else if (message == "currentPedalBoard")
@@ -1377,6 +1397,17 @@ public:
}
}
+ virtual void OnVst3ControlChanged(int64_t clientId, int64_t instanceId, const std::string &key, float value, const std::string &state)
+ {
+ Vst3ControlChangedBody body;
+ body.clientId_ = clientId;
+ body.instanceId_ = instanceId;
+ body.symbol_ = key;
+ body.value_ = value;
+ body.state_ = state;
+ Send("onVst3ControlChanged", body);
+ }
+
virtual void OnControlChanged(int64_t clientId, int64_t instanceId, const std::string &key, float value)
{
ControlChangedBody body;
@@ -1544,7 +1575,7 @@ public:
virtual void OnPedalBoardChanged(int64_t clientId, const PedalBoard &pedalBoard)
{
- SetCurrentPedalBoardBody body;
+ UpdateCurrentPedalBoardBody body;
body.clientId_ = clientId;
body.pedalBoard_ = pedalBoard;
Send("onPedalBoardChanged", body);
diff --git a/src/RtInversionGuard.hpp b/src/RtInversionGuard.hpp
new file mode 100644
index 0000000..295b9eb
--- /dev/null
+++ b/src/RtInversionGuard.hpp
@@ -0,0 +1,69 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022 Robin E. R. 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.
+ */
+
+#pragma once
+
+#include "ss.hpp"
+#include "Lv2Log.hpp"
+
+namespace pipedal
+{
+
+ constexpr int RT_THREAD_PRIORITY = 80;
+ /**
+ * @brief RAII Priority boost to avoid Rt-thread priority inversion
+ * Boosts current thread to realtime priority for the duration of the current scope.
+ * Do this before acquiring a mutex that the RT thread might block on.
+ *
+ */
+
+ class RtInversionGuard
+ {
+ private:
+ int currentScheduler;
+ struct sched_param currentPriority;
+ ;
+
+ public:
+ RtInversionGuard()
+ {
+ currentScheduler = sched_getscheduler(0);
+ sched_getparam(0, ¤tPriority);
+
+ struct sched_param param;
+ memset(¶m, 0, sizeof(param));
+ param.sched_priority = RT_THREAD_PRIORITY;
+
+ int result = sched_setscheduler(0, SCHED_RR, ¶m);
+ if (result != 0)
+ {
+ Lv2Log::error(SS("Failed to set ALSA AudioThread priority. (" << strerror(result) << ")"));
+ }
+ }
+ ~RtInversionGuard()
+ {
+ sched_setscheduler(0, currentScheduler, ¤tPriority);
+ }
+ };
+}
\ No newline at end of file
diff --git a/src/SplitEffect.cpp b/src/SplitEffect.cpp
index d666cfd..4ad5524 100644
--- a/src/SplitEffect.cpp
+++ b/src/SplitEffect.cpp
@@ -20,7 +20,7 @@
#include "pch.h"
#include "SplitEffect.hpp"
-#include "Lv2Host.hpp"
+#include "PiPedalHost.hpp"
#include "PedalBoard.hpp"
using namespace pipedal;
diff --git a/src/SplitEffect.hpp b/src/SplitEffect.hpp
index 5adb299..cc51c5a 100644
--- a/src/SplitEffect.hpp
+++ b/src/SplitEffect.hpp
@@ -22,6 +22,7 @@
#include "IEffect.hpp"
#include "PiPedalException.hpp"
#include "PiPedalMath.hpp"
+#include
namespace pipedal
{
@@ -60,6 +61,18 @@ namespace pipedal
class SplitEffect : public IEffect
{
private:
+
+
+ virtual void SetAudioInputBuffer(int index, float *buffer)
+ {
+ assert(false); // not used.
+ }
+ virtual void Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter)
+ {
+ assert(false); // not used.
+ }
+
+
const double MIX_TRANSITION_TIME_S = 0.1;
double sampleRate;
std::vector inputs;
@@ -283,6 +296,8 @@ namespace pipedal
virtual std::string AtomToJson(uint8_t *pAtom) { return ""; }
virtual std::string GetAtomObjectType(uint8_t*pData) { return "not implemented";}
+ virtual bool IsVst3() const { return false; }
+
public:
SplitEffect(
uint64_t instanceId,
@@ -297,7 +312,7 @@ namespace pipedal
}
- virtual long GetInstanceId() const
+ virtual uint64_t GetInstanceId() const
{
return instanceId;
}
diff --git a/src/Units.hpp b/src/Units.hpp
index 01db7fb..2dfe5d0 100644
--- a/src/Units.hpp
+++ b/src/Units.hpp
@@ -44,6 +44,7 @@ enum class Units {
pc,
s,
semitone12TET,
+ custom
};
diff --git a/src/Vst3Effect.cpp b/src/Vst3Effect.cpp
new file mode 100644
index 0000000..e0a2ccd
--- /dev/null
+++ b/src/Vst3Effect.cpp
@@ -0,0 +1,885 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022 Robin E. R. 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.
+ */
+
+#include "ss.hpp"
+#include
+#include "PiPedalHost.hpp"
+
+#include "vst3/Vst3Host.hpp"
+#include "Lv2Log.hpp"
+#include
+#include
+#include
+#include
+
+#include "RingBufferReader.hpp"
+
+#include "public.sdk/source/vst/hosting/module.h"
+#include "public.sdk/source/vst/hosting/hostclasses.h"
+#include "public.sdk/source/vst/hosting/plugprovider.h"
+#include "public.sdk/source/common/memorystream.h"
+#include "pluginterfaces/vst/ivsteditcontroller.h"
+#include "pluginterfaces/vst/ivstaudioprocessor.h"
+#include "pluginterfaces/vst/ivstunits.h"
+#include "public.sdk/source/vst/utility/stringconvert.h"
+#include "public.sdk/source/vst/hosting/eventlist.h"
+#include "public.sdk/source/vst/hosting/parameterchanges.h"
+#include "public.sdk/source/vst/hosting/processdata.h"
+#include "pluginterfaces/vst/ivstaudioprocessor.h"
+#include "pluginterfaces/vst/ivstmidicontrollers.h"
+
+#include "public.sdk/samples/vst-hosting/audiohost/source/media/iparameterclient.h"
+#include "public.sdk/samples/vst-hosting/audiohost/source/media/imediaserver.h"
+
+#include
+
+#include
+#include "LiteralVersion.hpp"
+
+#include "Vst3MidiToEvent.hpp"
+
+#include "vst3/Vst3EffectImpl.hpp"
+#include "PiPedalHost.hpp"
+
+using namespace pipedal;
+
+std::unique_ptr Vst3Effect::CreateInstance(uint64_t instanceId, const Vst3PluginInfo &info, IHost *pHost)
+{
+ std::unique_ptr result = std::make_unique();
+
+ result->Load(instanceId, info, pHost);
+
+ return std::move(result);
+}
+
+static double NaNGuard(double value)
+{
+ if (std::isnan(value)) return 0;
+ if (std::isinf(value)) return 0;
+ return value;
+}
+
+static uint32_t GetRefCount(FUnknown *p)
+{
+ p->addRef();
+ return p->release();
+}
+
+//------------------------------------------------------------------------
+// From Vst2Wrapper
+static MidiCCMapping initMidiCtrlerAssignment(IComponent *component, IMidiMapping *midiMapping)
+{
+ MidiCCMapping midiCCMapping{};
+
+ if (!midiMapping || !component)
+ return midiCCMapping;
+
+ int32 busses = std::min(component->getBusCount(kEvent, kInput), kMaxMidiMappingBusses);
+
+ if (midiCCMapping[0][0].empty())
+ {
+ for (int32 b = 0; b < busses; b++)
+ for (int32 i = 0; i < kMaxMidiChannels; i++)
+ midiCCMapping[b][i].resize(Vst::kCountCtrlNumber);
+ }
+
+ ParamID paramID;
+ for (int32 b = 0; b < busses; b++)
+ {
+ for (int16 ch = 0; ch < kMaxMidiChannels; ch++)
+ {
+ for (int32 i = 0; i < Vst::kCountCtrlNumber; i++)
+ {
+ paramID = kNoParamId;
+ if (midiMapping->getMidiControllerAssignment(b, ch, (CtrlNumber)i, paramID) ==
+ kResultTrue)
+ {
+ // TODO check if tag is associated to a parameter
+ midiCCMapping[b][ch][i] = paramID;
+ }
+ else
+ midiCCMapping[b][ch][i] = kNoParamId;
+ }
+ }
+ }
+ return midiCCMapping;
+}
+
+//------------------------------------------------------------------------
+static void assignBusBuffers(const IAudioClient::Buffers &buffers, HostProcessData &processData,
+ bool unassign = false)
+{
+ // Set outputs
+ auto bufferIndex = 0;
+ for (auto busIndex = 0; busIndex < processData.numOutputs; busIndex++)
+ {
+ auto channelCount = processData.outputs[busIndex].numChannels;
+ for (auto chanIndex = 0; chanIndex < channelCount; chanIndex++)
+ {
+ if (bufferIndex < buffers.numOutputs)
+ {
+ processData.setChannelBuffer(BusDirections::kOutput, busIndex, chanIndex,
+ unassign ? nullptr : buffers.outputs[bufferIndex]);
+ bufferIndex++;
+ }
+ }
+ }
+
+ // Set inputs
+ bufferIndex = 0;
+ for (auto busIndex = 0; busIndex < processData.numInputs; busIndex++)
+ {
+ auto channelCount = processData.inputs[busIndex].numChannels;
+ for (auto chanIndex = 0; chanIndex < channelCount; chanIndex++)
+ {
+ if (bufferIndex < buffers.numInputs)
+ {
+ processData.setChannelBuffer(BusDirections::kInput, busIndex, chanIndex,
+ unassign ? nullptr : buffers.inputs[bufferIndex]);
+
+ bufferIndex++;
+ }
+ }
+ }
+}
+
+//------------------------------------------------------------------------
+static void unassignBusBuffers(const IAudioClient::Buffers &buffers, HostProcessData &processData)
+{
+ assignBusBuffers(buffers, processData, true);
+}
+
+//------------------------------------------------------------------------
+// Vst3Processor
+//------------------------------------------------------------------------
+Vst3EffectImpl::Vst3EffectImpl()
+{
+ buffers.inputs = nullptr;
+ buffers.outputs = nullptr;
+ componentHandler.setEffect(this);
+}
+
+//------------------------------------------------------------------------
+Vst3EffectImpl::~Vst3EffectImpl()
+{
+ delete[] buffers.inputs;
+ delete[] buffers.outputs;
+ terminate();
+}
+
+//------------------------------------------------------------------------
+
+void Vst3EffectImpl::SetControl(int index, float value)
+{
+ this->parameterValues[index] = value;
+ ParamID paramId = lv2ToVstParam[index];
+
+ double normalizedValue = controller->plainParamToNormalized(paramId, value);
+ this->controller->setParamNormalized(paramId, normalizedValue);
+
+ {
+ RtInversionGuard inversionGuard; // boost priority to prevent priority inversion.
+ std::lock_guard guard{parameterMutex};
+
+ paramTransferrer.addChange(paramId, normalizedValue, 0);
+ }
+ if (!isProcessing)
+ {
+ Run(0, nullptr); // pump parameter changes on UI thread.
+ }
+}
+
+void Vst3EffectImpl::Load(uint64_t instanceId, const Vst3PluginInfo &info, IHost *pHost)
+{
+ processContext = {};
+ processContext.tempo = 120;
+
+
+ this->pHost = pHost;
+ this->instanceId = instanceId;
+ this->info = info;
+
+ size_t nControls = info.pluginInfo_.controls().size();
+
+ inputParameterChanges.setMaxParameters(nControls);
+ outputParameterChanges.setMaxParameters(nControls);
+
+ lv2ToVstParam.resize(nControls);
+ parameterValues.resize(nControls);
+ for (size_t i = 0; i < nControls; ++i)
+ {
+ const auto &control = info.pluginInfo_.controls()[i];
+ ParamID paramId;
+ std::stringstream ss(control.symbol());
+ ss >> paramId;
+
+ assert(i == control.index());
+
+ lv2ToVstParam[control.index()] = paramId;
+
+ parameterValues[control.index()] = control.default_value();
+ if (control.is_bypass())
+ {
+ bypassControl = i;
+ }
+ }
+
+ std::string error;
+ this->module = Module::create(info.filePath_, error);
+
+ if (!module)
+ {
+ throw Vst3Exception(SS("Vst3 load failed: " << error << "(" << info.filePath_ << ")"));
+ }
+ const PluginFactory &factory = module->getFactory();
+
+ ClassInfo myClassInfo;
+ bool foundClassInfo = false;
+
+ VST3::Optional myUid = VST3::UID::fromString(info.uid_);
+
+ if (!myUid)
+ {
+ throw Vst3Exception(SS("Invalid UID"));
+ }
+ for (const ClassInfo &classInfo : factory.classInfos())
+ {
+ if (classInfo.category() == kVstAudioEffectClass &&
+ classInfo.ID() == *myUid)
+ {
+ myClassInfo = classInfo;
+ foundClassInfo = true;
+ }
+ }
+ if (!foundClassInfo)
+ {
+ throw Vst3Exception(SS("Vst3 load failed: effect class not found in " << info.filePath_));
+ }
+
+ plugProvider = owned(NEW PlugProvider(factory, myClassInfo, true));
+
+ this->component = plugProvider->getComponent();
+ this->processor = component;
+
+ this->controller = plugProvider->getController();
+ controller->queryInterface(IMidiMapping::iid, (void **)&midiMapping);
+
+ paramTransferrer.setMaxParameters(1000);
+ if (midiMapping)
+ midiCCMapping = initMidiCtrlerAssignment(component, midiMapping);
+
+ setBlockSize((Steinberg::int32)pHost->GetMaxAudioBufferSize());
+ setSamplerate((SampleRate)pHost->GetSampleRate());
+
+ ProcessSetup setup{kRealtime, kSample32, blockSize, sampleRate};
+
+ initProcessData();
+
+ if (processor->setupProcessing(setup) != kResultOk)
+ {
+ throw Vst3Exception(SS(info.pluginInfo_.name() << ": setupProcessing() failed."));
+ }
+
+ if (component->setActive(true) != kResultOk)
+ throw Vst3Exception(SS(info.pluginInfo_.name() << ": setActive() failed."));
+
+ OPtr state = new MemoryStream();
+
+ assert(GetRefCount(state.get()) == 1);
+ this->supportsState = false;
+ if (component->getState(state) == kResultOk)
+ {
+ this->supportsState = true;
+ state->seek(0, IBStream::kIBSeekSet);
+ controller->setComponentState(state);
+ refreshControlValues();
+ }
+ IComponentHandler *handler;
+
+ controller->setComponentHandler(&componentHandler);
+
+ paramTransferrer.setMaxParameters(1000);
+
+ if (midiMapping)
+ midiCCMapping = initMidiCtrlerAssignment(component, midiMapping);
+
+ component->setActive(true);
+
+ for (size_t i = 0; i < this->lv2ToVstParam.size(); ++i)
+ {
+ fireControlChanged(i, (float)(controller->getParamNormalized(lv2ToVstParam[i])));
+ }
+}
+//------------------------------------------------------------------------
+//------------------------------------------------------------------------
+// void Vst3PluginImpl::createLocalMediaServer (const Name& name)
+// {
+// mediaServer = createMediaServer (name);
+// mediaServer->registerAudioClient (this);
+// mediaServer->registerMidiClient (this);
+// }
+
+//------------------------------------------------------------------------
+
+//------------------------------------------------------------------------
+void Vst3EffectImpl::terminate()
+{
+ // mediaServer = nullptr;
+
+ if (!processor)
+ return;
+
+ processor->setProcessing(false);
+ component->setActive(false);
+}
+
+//------------------------------------------------------------------------
+void Vst3EffectImpl::initProcessData()
+{
+ // processData.prepare will be done in setBlockSize
+
+ buffers.numInputs = info.pluginInfo_.audio_inputs();
+ buffers.numOutputs = info.pluginInfo_.audio_outputs();
+ buffers.numSamples = 0;
+ buffers.inputs = new float *[buffers.numInputs + 1];
+ buffers.outputs = new float *[buffers.numOutputs + 1];
+
+ for (size_t i = 0; i < buffers.numInputs + 1; ++i)
+ {
+ buffers.inputs[i] = nullptr;
+ }
+ for (size_t i = 0; i < buffers.numOutputs + 1; ++i)
+ {
+ buffers.outputs[i] = nullptr;
+ }
+
+ processData.inputEvents = &eventList;
+ processData.inputParameterChanges = &inputParameterChanges;
+ processData.outputParameterChanges = &outputParameterChanges;
+ processData.processContext = &processContext;
+
+ setSamplerate(pHost->GetSampleRate());
+ setBlockSize((Steinberg::int32)pHost->GetMaxAudioBufferSize());
+}
+
+//------------------------------------------------------------------------
+IMidiClient::IOSetup Vst3EffectImpl::getMidiIOSetup() const
+{
+ IMidiClient::IOSetup iosetup;
+ auto count = component->getBusCount(MediaTypes::kEvent, BusDirections::kInput);
+ for (int32_t i = 0; i < count; i++)
+ {
+ BusInfo info;
+ if (component->getBusInfo(MediaTypes::kEvent, BusDirections::kInput, i, info) != kResultOk)
+ continue;
+
+ auto busName = VST3::StringConvert::convert(info.name, 128);
+ iosetup.inputs.push_back(busName);
+ }
+
+ count = component->getBusCount(MediaTypes::kEvent, BusDirections::kOutput);
+ for (int32_t i = 0; i < count; i++)
+ {
+ BusInfo info;
+ if (component->getBusInfo(MediaTypes::kEvent, BusDirections::kOutput, i, info) !=
+ kResultOk)
+ continue;
+
+ auto busName = VST3::StringConvert::convert(info.name, 128);
+ iosetup.outputs.push_back(busName);
+ }
+
+ return iosetup;
+}
+
+//------------------------------------------------------------------------
+IAudioClient::IOSetup Vst3EffectImpl::getIOSetup() const
+{
+ IAudioClient::IOSetup iosetup;
+ auto count = component->getBusCount(MediaTypes::kAudio, BusDirections::kOutput);
+ for (int32_t i = 0; i < count; i++)
+ {
+ BusInfo info;
+ if (component->getBusInfo(MediaTypes::kAudio, BusDirections::kOutput, i, info) !=
+ kResultOk)
+ continue;
+
+ for (int32_t j = 0; j < info.channelCount; j++)
+ {
+ auto channelName = VST3::StringConvert::convert(info.name, 128);
+ iosetup.outputs.push_back(channelName + " " + std::to_string(j));
+ }
+ }
+
+ count = component->getBusCount(MediaTypes::kAudio, BusDirections::kInput);
+ for (int32_t i = 0; i < count; i++)
+ {
+ BusInfo info;
+ if (component->getBusInfo(MediaTypes::kAudio, BusDirections::kInput, i, info) != kResultOk)
+ continue;
+
+ for (int32_t j = 0; j < info.channelCount; j++)
+ {
+ auto channelName = VST3::StringConvert::convert(info.name, 128);
+ iosetup.inputs.push_back(channelName + " " + std::to_string(j));
+ }
+ }
+
+ return iosetup;
+}
+
+//------------------------------------------------------------------------
+void Vst3EffectImpl::preprocess(Buffers &buffers, int64_t continousFrames)
+{
+ processData.numSamples = buffers.numSamples;
+ processContext.continousTimeSamples = continousFrames;
+ assignBusBuffers(buffers, processData);
+
+ {
+ std::lock_guard guard{parameterMutex};
+
+ paramTransferrer.transferChangesTo(inputParameterChanges);
+ }
+ outputParameterChanges.clearQueue();
+}
+
+//------------------------------------------------------------------------
+bool Vst3EffectImpl::process(Buffers &buffers, int64_t continousFrames)
+{
+ if (!processor || !isProcessing)
+ return false;
+ buffers.numSamples = continousFrames;
+ preprocess(buffers, continousFrames);
+
+ if (processor->process(processData) != kResultOk)
+ return false;
+
+ postprocess(buffers);
+
+ return true;
+}
+//------------------------------------------------------------------------
+void Vst3EffectImpl::postprocess(Buffers &buffers)
+{
+ eventList.clear();
+ inputParameterChanges.clearQueue();
+ unassignBusBuffers(buffers, processData);
+}
+
+void Vst3EffectImpl::SendControlChanges(RealtimeRingBufferWriter *realtimeRingBufferWriter)
+{
+ for (auto i = 0; i < outputParameterChanges.getParameterCount(); ++i)
+ {
+ IParamValueQueue *queue = outputParameterChanges.getParameterData(i);
+ auto points = queue->getPointCount();
+ if (points != 0)
+ {
+ Steinberg::Vst::ParamValue value;
+ Steinberg::int32 sampleOffset;
+ queue->getPoint(points - 1, sampleOffset, value);
+
+ auto paramId = queue->getParameterId();
+
+ int thisControlId = -1;
+ for (size_t lv2Id = 0; lv2Id < this->lv2ToVstParam.size(); ++lv2Id)
+ {
+ if (lv2ToVstParam[lv2Id] == paramId)
+ {
+ thisControlId = (int)lv2Id;
+ break;
+ }
+ }
+ if (thisControlId != -1)
+ {
+ // realtimeRingBufferWriter->NotifyVst3ControlValue(this->instanceId,thisControlId,(float)value);
+ }
+ }
+ }
+}
+
+//------------------------------------------------------------------------
+bool Vst3EffectImpl::setSamplerate(SampleRate value)
+{
+ if (sampleRate == value)
+ return true;
+
+ sampleRate = value;
+ processContext.sampleRate = sampleRate;
+ if (blockSize == 0)
+ return true;
+ return true;
+}
+
+//------------------------------------------------------------------------
+bool Vst3EffectImpl::setBlockSize(int32 value)
+{
+ blockSize = value;
+ if (sampleRate == 0)
+ return true;
+
+ processData.prepare(*component, blockSize, kSample32);
+ return true;
+}
+
+//------------------------------------------------------------------------
+
+//------------------------------------------------------------------------
+bool Vst3EffectImpl::isPortInRange(int32 port, int32 channel) const
+{
+ return port < kMaxMidiMappingBusses && !midiCCMapping[port][channel].empty();
+}
+
+//------------------------------------------------------------------------
+bool Vst3EffectImpl::processVstEvent(const IMidiClient::Event &event, int32 port)
+{
+ auto vstEvent = midiToEvent(event.type, event.channel, event.data0, event.data1);
+ if (vstEvent)
+ {
+ vstEvent->busIndex = port;
+ if (eventList.addEvent(*vstEvent) != kResultOk)
+ {
+ assert(false && "Event was not added to EventList!");
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+//------------------------------------------------------------------------
+bool Vst3EffectImpl::processParamChange(const IMidiClient::Event &event, int32 port)
+{
+ auto paramMapping = [port, this](int32 channel, MidiData data1) -> ParamID
+ {
+ if (!isPortInRange(port, channel))
+ return kNoParamId;
+
+ return midiCCMapping[port][channel][data1];
+ };
+
+ auto paramChange =
+ midiToParameter(event.type, event.channel, event.data0, event.data1, paramMapping);
+ if (paramChange)
+ {
+ int32 index = 0;
+ IParamValueQueue *queue =
+ inputParameterChanges.addParameterData((*paramChange).first, index);
+ if (queue)
+ {
+ if (queue->addPoint(event.timestamp, (*paramChange).second, index) != kResultOk)
+ {
+ assert(false && "Parameter point was not added to ParamValueQueue!");
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+//------------------------------------------------------------------------
+bool Vst3EffectImpl::onEvent(const IMidiClient::Event &event, int32_t port)
+{
+ // Try to create Event first.
+ if (processVstEvent(event, port))
+ return true;
+
+ // In case this is no event it must be a parameter.
+ if (processParamChange(event, port))
+ return true;
+
+ // TODO: Something else???
+
+ return true;
+}
+
+//------------------------------------------------------------------------
+
+void Vst3EffectImpl::Activate()
+{
+ processor->setProcessing(true); // != kResultOk
+ this->isProcessing = true;
+}
+
+void Vst3EffectImpl::Deactivate()
+{
+ if (isProcessing)
+ {
+ Run(0, nullptr); // make sure all pending events have been processed.
+ isProcessing = false;
+ }
+}
+
+void Vst3EffectImpl::Prepare(int32_t sampleRate, size_t maxBufferSize, int inputChannels, int outputChannels)
+{
+}
+void Vst3EffectImpl::Unprepare()
+{
+ if (isProcessing)
+ {
+ Deactivate();
+ }
+}
+
+ssize_t Vst3EffectImpl::ParamIdToLv2Id(ParamID id) const
+{
+ for (size_t i = 0; i < this->lv2ToVstParam.size(); ++i)
+ {
+ if (lv2ToVstParam[i] == id)
+ {
+ return (ssize_t)i;
+ }
+ }
+ return -1;
+}
+
+tresult Vst3EffectImpl::beginEdit(ParamID id)
+{
+ return kResultOk;
+}
+tresult Vst3EffectImpl::performEdit(ParamID id, ParamValue valueNormalized)
+{
+ int ix = ParamIdToLv2Id(id);
+ if (ix != -1)
+ {
+ fireControlChanged(ix, (float)valueNormalized);
+ }
+ return kResultOk;
+}
+
+tresult Vst3EffectImpl::endEdit(ParamID id)
+{
+ return kResultOk;
+}
+
+void Vst3EffectImpl::transferControllerStateToComponent()
+{
+ OPtr bStream = this->streamPool.AllocateBStream();
+ assert(GetRefCount(bStream.get()) == 1);
+
+ RtInversionGuard inversionGuard; // boost priority to prevent priority inversion.
+ std::lock_guard guard{parameterMutex};
+
+ paramTransferrer.removeChanges();
+
+ // assume that parameters are straightforward and uncomplicated if they
+ // didn't provide BStream-based state management
+ // Vst2 used to do this. It's not clear that this is still legal in Vst3.
+
+ for (int index = 0; index < this->lv2ToVstParam.size(); ++index)
+ {
+ ParamID paramId = lv2ToVstParam[index];
+ paramTransferrer.addChange(paramId,
+ controller->plainParamToNormalized(paramId, parameterValues[index]),
+ 0);
+ }
+ if (!this->isProcessing)
+ {
+ Run(0,nullptr);
+ }
+}
+
+
+tresult Vst3EffectImpl::restartComponent(int32 flags)
+{
+ if (flags & (RestartFlags::kParamValuesChanged))
+ {
+ refreshControlValues();
+ transferControllerStateToComponent();
+ }
+
+ return kResultOk;
+}
+
+static std::vector StreamToVec(IBStream *stream)
+{
+ Steinberg::int64 length;
+ stream->seek(0, IBStream::kIBSeekEnd, &length);
+ stream->seek(0, IBStream::kIBSeekSet);
+ std::vector result;
+ result.resize(length);
+ Steinberg::int32 numRead = 0;
+ stream->read(&result[0], length, &numRead);
+ if (numRead != length)
+ {
+ throw Vst3Exception("Failed to read.");
+ }
+ return result;
+}
+
+void Vst3EffectImpl::CheckSync()
+{
+ OPtr stream{new MemoryStream()};
+
+ if (this->controller->getState(stream) == kResultOk)
+ {
+ std::vector initialState = StreamToVec(stream);
+ OPtr stream2{new MemoryStream()};
+ this->component->getState(stream2);
+ stream2->seek(0, IBStream::kIBSeekSet);
+ this->controller->setState(stream2);
+
+ OPtr stream3{new MemoryStream()};
+ this->controller->getState(stream3);
+ std::vector newState = StreamToVec(stream3);
+
+ if (initialState.size() != newState.size())
+ {
+ throw Vst3Exception("State changed.");
+ }
+ for (size_t i = 0; i < newState.size(); ++i)
+ {
+ if (initialState[i] != newState[i])
+ {
+ throw Vst3Exception("State changed.");
+ }
+ }
+ }
+}
+
+bool Vst3EffectImpl::GetState(std::vector*state)
+{
+ OPtr stream{new MemoryStream()};
+
+ if (component->getState(stream) != kResultOk)
+ {
+ return false;
+ }
+ Steinberg::int64 length = 0;
+ if (stream->seek(0, IBStream::kIBSeekEnd, &length) != kResultOk)
+ {
+ return false;
+ }
+ stream->seek(0, IBStream::kIBSeekSet);
+ std::vector result;
+
+ result.resize(length);
+ int64 ix = 0;
+ while (length != 0)
+ {
+ int32 thisTime;
+ if (length > 0x10000000)
+ {
+ thisTime = 0x10000000;
+ }
+ else
+ {
+ thisTime = (int32)length;
+ }
+ if (stream->read((void *)(&result[ix]), thisTime, &thisTime) != kResultOk)
+ {
+ throw Vst3Exception(this->info.pluginInfo_.name() + ": Failed to read state.");
+ }
+ if (thisTime == 0)
+ {
+ throw Vst3Exception(this->info.pluginInfo_.name() + ": Unexpected end of file while reading state.");
+ }
+ ix += thisTime;
+ length -= thisTime;
+ }
+ *state = std::move(result);
+ return true;
+}
+void Vst3EffectImpl::SetState(const std::vector state)
+{
+ OPtr stream{new MemoryStream((void *)(&state[0]), state.size())};
+
+ if (controller->setComponentState(stream) != kResultOk)
+ {
+ throw Vst3Exception(this->info.pluginInfo_.name() + " Failed to restore state.");
+ }
+ refreshControlValues();
+ stream->seek(0,IBStream::kIBSeekSet);
+ if (component->setState(stream) != kResultOk)
+ {
+ transferControllerStateToComponent();
+ }
+}
+
+void Vst3EffectImpl::refreshControlValues()
+{
+ for (size_t i = 0; i < lv2ToVstParam.size(); ++i)
+ {
+ fireControlChanged(i, controller->getParamNormalized(lv2ToVstParam[i]));
+ }
+}
+std::vector Vst3EffectImpl::GetProgramList(int32_t programListId)
+{
+ std::vector result;
+
+ FUnknownPtr iUnitInfo(controller);
+ if (!iUnitInfo)
+ throw Vst3Exception("Invalid unit info");
+
+ int count = iUnitInfo->getProgramListCount();
+ for (int i = 0; i < count; ++i)
+ {
+ ProgramListInfo programListInfo;
+ if (iUnitInfo->getProgramListInfo(i, programListInfo) != kResultOk)
+ {
+ break;
+ }
+ if (programListInfo.id == programListId)
+ {
+ Vst3ProgramList list;
+
+ list.name = VST3::StringConvert::convert(programListInfo.name);
+
+ for (int prog = 0; prog < programListInfo.programCount; ++prog)
+ {
+ Vst::String128 progName;
+ if (iUnitInfo->getProgramName(programListInfo.id, prog, progName) == kResultOk)
+ {
+ Vst3ProgramListEntry entry;
+ entry.id = prog;
+ entry.name = VST3::StringConvert::convert(progName);
+ list.programs.push_back(std::move(entry));
+ }
+ }
+ result.push_back(std::move(list));
+ }
+ }
+ return result;
+}
+
+void Vst3EffectImpl::fireControlChanged(int control, float normalizedValue)
+{
+ normalizedValue = NaNGuard(normalizedValue);
+ float plainValue = NaNGuard(this->controller->normalizedParamToPlain(this->lv2ToVstParam[control], normalizedValue));
+ if (parameterValues[control] != plainValue)
+ {
+ parameterValues[control] = plainValue;
+ controlChangedHandler(control,plainValue);
+ }
+}
+
+JSON_MAP_BEGIN(Vst3PluginInfo)
+JSON_MAP_REFERENCE(Vst3PluginInfo, filePath)
+JSON_MAP_REFERENCE(Vst3PluginInfo, version)
+JSON_MAP_REFERENCE(Vst3PluginInfo, uid)
+JSON_MAP_REFERENCE(Vst3PluginInfo, pluginInfo)
+
+JSON_MAP_END()
diff --git a/src/Vst3Host.cpp b/src/Vst3Host.cpp
new file mode 100644
index 0000000..c10740b
--- /dev/null
+++ b/src/Vst3Host.cpp
@@ -0,0 +1,689 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022 Robin E. R. 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.
+ */
+
+
+#include "ss.hpp"
+#include "PiPedalHost.hpp"
+#include "vst3/Vst3Host.hpp"
+#include "Lv2Log.hpp"
+#include
+#include
+#include
+#include
+#include "public.sdk/source/vst/hosting/module.h"
+#include "public.sdk/source/vst/hosting/hostclasses.h"
+#include "public.sdk/source/vst/hosting/plugprovider.h"
+#include "pluginterfaces/vst/ivsteditcontroller.h"
+#include "pluginterfaces/vst/ivstaudioprocessor.h"
+#include "pluginterfaces/vst/ivstunits.h"
+#include "public.sdk/source/vst/utility/stringconvert.h"
+#include "public.sdk/source/vst/hosting/eventlist.h"
+#include "public.sdk/source/vst/hosting/parameterchanges.h"
+#include "public.sdk/source/vst/hosting/processdata.h"
+#include "pluginterfaces/vst/ivstaudioprocessor.h"
+#include "pluginterfaces/vst/ivstmidicontrollers.h"
+
+#include "public.sdk/samples/vst-hosting/audiohost/source/media/iparameterclient.h"
+#include "public.sdk/samples/vst-hosting/audiohost/source/media/imediaserver.h"
+
+#include
+
+#include
+#include "LiteralVersion.hpp"
+#include "json.hpp"
+
+using namespace std;
+using namespace pipedal;
+using namespace VST3::Hosting;
+using namespace Steinberg;
+using namespace Steinberg::Vst;
+
+namespace pipedal
+{
+
+ class Vst3HostApplication : public HostApplication
+ {
+ public:
+ Vst3HostApplication() {}
+ //--- IHostApplication ---------------
+ tresult PLUGIN_API getName(String128 name)
+ {
+ return VST3::StringConvert::convert("PiPedal", name) ? kResultTrue : kInternalError;
+ }
+ };
+
+ class Vst3HostImpl : public Vst3Host
+ {
+ private:
+ OPtr hostApplication;
+ std::string cacheFilePath;
+
+ public:
+ Vst3HostImpl(const std::string&cacheFilePath);
+
+ const Vst3Host::PluginList &RescanPlugins() override;
+ const Vst3Host::PluginList &RefreshPlugins() override;
+ const Vst3Host::PluginList &getPluginList() override { return pluginList; }
+
+ std::unique_ptr CreatePlugin(long instanceId, const std::string &url, IHost *pHost) override;
+ std::unique_ptr CreatePlugin(PedalBoardItem&pedalBoardItem, IHost*pHost) override;
+
+ private:
+ Lv2PluginUiInfo *GetPluginInfo(const std::string&uri);
+
+ void LoadPluginCache();
+ void SavePluginCache();
+
+ bool havePluginsChanged() const;
+ PluginList pluginList;
+
+ void EnsureContext();
+ std::unique_ptr MakeDetailedPluginInfo(const PluginFactory &factory, const ClassInfo &classInfo, const std::string &path);
+
+ void MakeVst3Info(const std::filesystem::path &path, Vst3Host::PluginList &list);
+ };
+
+ Vst3Host::Ptr Vst3Host::CreateInstance(const std::string&cacheFilePath)
+ {
+ return std::make_unique(cacheFilePath);
+ }
+
+ //--------------------------------------------------------------
+
+} // namespace pipedal
+
+Vst3HostImpl::Vst3HostImpl(const std::string&cacheFilePath)
+:cacheFilePath(cacheFilePath)
+{
+ this->hostApplication = new Vst3HostApplication();
+ EnsureContext();
+
+}
+
+
+static bool hasSubCategory(const ClassInfo &classInfo, const std::string &subcategory)
+{
+ for (auto &category : classInfo.subCategories())
+ {
+ if (category == subcategory)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+static PluginType getVst3PluginType(const ClassInfo &classInfo)
+{
+ if (hasSubCategory(classInfo, "Instrument"))
+ {
+ return PluginType::InstrumentPlugin;
+ }
+ if (!hasSubCategory(classInfo, "Fx"))
+ {
+ return PluginType::InvalidPlugin;
+ }
+
+ // kFxAnalyzer = "Fx|Analyzer"; ///< Scope, FFT-Display, Loudness Processing...
+ if (hasSubCategory(classInfo, "Analyzer"))
+ {
+ return PluginType::AnalyserPlugin;
+ }
+ // kFxDelay = "Fx|Delay"; ///< Delay, Multi-tap Delay, Ping-Pong Delay...
+ if (hasSubCategory(classInfo, "Delay"))
+ {
+ return PluginType::DelayPlugin;
+ }
+
+ // kFxDistortion = "Fx|Distortion"; ///< Amp Simulator, Sub-Harmonic, SoftClipper...
+ if (hasSubCategory(classInfo, "Distortion"))
+ {
+ return PluginType::DistortionPlugin;
+ }
+ // kFxDynamics = "Fx|Dynamics"; ///< Compressor, Expander, Gate, Limiter, Maximizer, Tape Simulator, EnvelopeShaper...
+ if (hasSubCategory(classInfo, "Dynamics"))
+ {
+ return PluginType::DynamicsPlugin;
+ }
+ // kFxEQ = "Fx|EQ"; ///< Equalization, Graphical EQ...
+ if (hasSubCategory(classInfo, "EQ"))
+ {
+ return PluginType::EQPlugin;
+ }
+
+ // kFxFilter = "Fx|Filter"; ///< WahWah, ToneBooster, Specific Filter,...
+ if (hasSubCategory(classInfo, "Filter"))
+ {
+ return PluginType::FilterPlugin;
+ }
+
+ // kFx = "Fx"; ///< others type (not categorized)
+ // kFxSpatial = "Fx|Spatial"; ///< MonoToStereo, StereoEnhancer,...
+ if (hasSubCategory(classInfo, "Spatial"))
+ {
+ return PluginType::SpatialPlugin;
+ }
+
+ // kFxGenerator = "Fx|Generator"; ///< Tone Generator, Noise Generator...
+ if (hasSubCategory(classInfo, "Generator"))
+ {
+ return PluginType::GeneratorPlugin;
+ }
+
+ // kFxReverb = "Fx|Reverb"; ///< Reverberation, Room Simulation, Convolution Reverb...
+ if (hasSubCategory(classInfo, "Reverb"))
+ {
+ return PluginType::ReverbPlugin;
+ }
+ // kFxPitchShift = "Fx|Pitch Shift"; ///< Pitch Processing, Pitch Correction, Vocal Tuning...
+ if (hasSubCategory(classInfo, "Pitch Shift"))
+ {
+ return PluginType::PitchPlugin;
+ }
+
+ // kFxModulation = "Fx|Modulation"; ///< Phaser, Flanger, Chorus, Tremolo, Vibrato, AutoPan, Rotary, Cloner...
+ if (hasSubCategory(classInfo, "Modulation"))
+ {
+ return PluginType::ModulatorPlugin;
+ }
+
+ // kFxRestoration = "Fx|Restoration"; ///< Denoiser, Declicker,...
+ if (hasSubCategory(classInfo, "Restoration"))
+ {
+ return PluginType::UtilityPlugin;
+ }
+ // kFxSurround = "Fx|Surround"; ///< dedicated to surround processing: LFE Splitter, Bass Manager...
+ if (hasSubCategory(classInfo, "Surround"))
+ {
+ return PluginType::UtilityPlugin;
+ }
+
+ // kFxTools = "Fx|Tools"; ///< Volume, Mixer, Tuner...
+ if (hasSubCategory(classInfo, "Tools"))
+ {
+ return PluginType::UtilityPlugin;
+ }
+ // kFxMastering = "Fx|Mastering"; ///< Dither, Noise Shaping,...
+ if (hasSubCategory(classInfo, "Mastering"))
+ {
+ return PluginType::UtilityPlugin;
+ }
+
+ // kFxNetwork = "Fx|Network"; ///< using Network
+ if (hasSubCategory(classInfo, "Network"))
+ {
+ return PluginType::UtilityPlugin;
+ }
+ // kFxInstrument = "Fx|Instrument"; ///< Fx which could be loaded as Instrument too
+ // kFxInstrumentExternal = "Fx|Instrument|External"; ///< Fx which could be loaded as Instrument too and is external (wrapped Hardware)
+ if (hasSubCategory(classInfo, "Instrument"))
+ {
+ return PluginType::InstrumentPlugin;
+ }
+ return PluginType::Plugin;
+}
+
+void Vst3HostImpl::EnsureContext()
+{
+ PluginContextFactory::instance().setPluginContext(this->hostApplication);
+}
+
+unique_ptr Vst3HostImpl::MakeDetailedPluginInfo(const PluginFactory &factory, const ClassInfo &classInfo, const std::string &path)
+{
+ IPtr plugProvider = owned(NEW PlugProvider(factory, classInfo, true));
+ {
+ OPtr component = plugProvider->getComponent();
+ OPtr controller = plugProvider->getController();
+
+ FUnknownPtr midiMapping(controller);
+
+ FUnknownPtr iUnitInfo(controller);
+
+ unique_ptr info = make_unique();
+
+ info->filePath_ = path;
+ info->version_ = classInfo.version();
+ info->uid_ = classInfo.ID().toString();
+
+ Lv2PluginUiInfo &pluginInfo = info->pluginInfo_;
+
+ pluginInfo.uri(SS("vst3:" << classInfo.ID().toString()));
+
+ pluginInfo.is_vst3(true);
+ pluginInfo.name(classInfo.name());
+ pluginInfo.author_name(classInfo.vendor());
+
+ pluginInfo.plugin_type(
+ getVst3PluginType(classInfo));
+ pluginInfo.plugin_display_type(""); // fill this in later. VSTHost has list of name translations; we don;'t.
+
+ if (iUnitInfo)
+ {
+ auto nUnits = iUnitInfo->getUnitCount();
+ for (int i = 0; i < nUnits; ++i)
+ {
+ Steinberg::Vst::UnitInfo unitInfo;
+ iUnitInfo->getUnitInfo(i, unitInfo);
+ Lv2PluginUiPortGroup portGroup(
+ SS(unitInfo.id), VST3::StringConvert::convert(unitInfo.name), SS(unitInfo.parentUnitId), unitInfo.programListId);
+
+ pluginInfo.port_groups().push_back(portGroup);
+ }
+ }
+
+ auto nParams = controller->getParameterCount();
+ for (int32 param = 0; param < nParams; ++param)
+ {
+ Steinberg::Vst::ParameterInfo info;
+ tresult tErr = controller->getParameterInfo(param, info);
+
+ Lv2PluginUiControlPort port;
+ port.index(param); // used for LV2 purposes.
+ port.symbol(SS(info.id)); // Used for VST3 purposes. convert to int to specifiy a vst control id.
+
+ std::string shortTitle = VST3::StringConvert::convert(info.shortTitle);
+ std::string title = VST3::StringConvert::convert(info.title);
+ if (shortTitle.length() == 0)
+ {
+ shortTitle = title;
+ }
+ port.name(shortTitle);
+ port.comment(title);
+
+
+ // guard against NaN value (mda Talkbox)
+ port.display_priority(param);
+
+ bool isList = (info.flags & ParameterInfo::kIsList) != ParameterInfo::kNoFlags;
+ bool canAutomate = (info.flags & ParameterInfo::kCanAutomate) != ParameterInfo::kNoFlags;
+ bool isProgramChange = (info.flags & ParameterInfo::kIsProgramChange) != ParameterInfo::kNoFlags;
+ bool isReadOnly = (info.flags & ParameterInfo::kIsReadOnly) != ParameterInfo::kNoFlags;
+ bool notOnGui = (info.flags & ParameterInfo::kIsHidden) != ParameterInfo::kNoFlags;
+ bool isBypass = (info.flags & ParameterInfo::kIsBypass) != ParameterInfo::kNoFlags;
+
+ port.is_bypass(isBypass);
+
+ port.not_on_gui(isReadOnly | notOnGui);
+
+ port.min_value(controller->normalizedParamToPlain(info.id,0));
+ port.max_value(controller->normalizedParamToPlain(info.id,1));
+
+ double t = controller->normalizedParamToPlain(info.id,info.defaultNormalizedValue);
+ if (std::isnan(t) || std::isinf(t))
+ {
+ t = 0;
+ }
+
+ port.default_value(t);
+
+ port.range_steps(info.stepCount == 0 ? 0 : info.stepCount + 1);
+ if (isList && !isProgramChange)
+ {
+ port.enumeration_property(true);
+ for (int i = 0; i <= info.stepCount; ++i)
+ {
+ double normalizedValue;
+ if (info.stepCount == 0)
+ {
+ normalizedValue = 0;
+ }
+ else
+ {
+ normalizedValue = i * 1.0 / (info.stepCount);
+ }
+
+ String128 strValue;
+ if (controller->getParamStringByValue(info.id, normalizedValue, strValue) == kResultOk)
+ {
+ Lv2ScalePoint scalePoint((float)controller->normalizedParamToPlain(info.id,normalizedValue), VST3::StringConvert::convert(strValue));
+ port.scale_points().push_back(scalePoint);
+ }
+ }
+ }
+ if (info.unitId != 0)
+ {
+ port.port_group(SS(info.unitId));
+ }
+ std::string units = VST3::StringConvert::convert(info.units);
+ if (units.size() != 0)
+ {
+ if (units == "dB")
+ {
+ port.units(Units::db);
+ } else if (units == "%")
+ {
+ port.units(Units::pc);
+ } else if (units == "Hz")
+ {
+ port.units(Units::hz);
+ } else if (units == "kHz")
+ {
+ port.units(Units::khz);
+ } else if (units == "sec" || units == "s")
+ {
+ port.units(Units::s);
+ } else if (units == "ms")
+ {
+ port.units(Units::ms);
+ } else if (units == "cent")
+ {
+ port.units(Units::cent);
+ } else {
+ port.units(Units::custom);
+ port.custom_units(units);
+ }
+ }
+
+ pluginInfo.controls().push_back(port);
+ }
+
+ auto count = component->getBusCount(MediaTypes::kAudio, BusDirections::kOutput);
+ for (int32_t i = 0; i < count; i++)
+ {
+ BusInfo busInfo;
+ if (component->getBusInfo(MediaTypes::kAudio, BusDirections::kOutput, i, busInfo) ==
+ kResultOk)
+ {
+ auto channelName = VST3::StringConvert::convert(busInfo.name, 128);
+
+ if (busInfo.busType == BusTypes::kMain) // aux channels not currently supported.
+ {
+ pluginInfo.audio_outputs(pluginInfo.audio_outputs() + busInfo.channelCount);
+ }
+ }
+ }
+ count = component->getBusCount(MediaTypes::kAudio, BusDirections::kInput);
+ for (int32_t i = 0; i < count; i++)
+ {
+ BusInfo busInfo;
+ if (component->getBusInfo(MediaTypes::kAudio, BusDirections::kInput, i, busInfo) ==
+ kResultOk)
+ {
+ auto channelName = VST3::StringConvert::convert(busInfo.name, 128);
+
+ if (busInfo.busType == BusTypes::kMain) // aux channels not currently supported.
+ {
+ pluginInfo.audio_inputs(pluginInfo.audio_inputs() + busInfo.channelCount);
+ }
+ }
+ }
+ count = component->getBusCount(MediaTypes::kEvent, BusDirections::kInput);
+ if (count >= 1)
+ {
+ pluginInfo.has_midi_input(1);
+ }
+ count = component->getBusCount(MediaTypes::kEvent, BusDirections::kOutput);
+ if (count >= 1)
+ {
+ pluginInfo.has_midi_output(1);
+ }
+ return info;
+ }
+}
+void Vst3HostImpl::MakeVst3Info(const std::filesystem::path &path, Vst3Host::PluginList &list)
+{
+
+ EnsureContext();
+ unique_ptr result = std::make_unique();
+ result->filePath_ = path;
+
+ string error;
+ Module::Ptr module = Module::create(path.string(), error);
+
+ if (!module)
+ {
+ Lv2Log::error(SS("Vst3 load failed: " << error << "(" << path.string() << ")"));
+ return;
+ }
+ const PluginFactory &factory = module->getFactory();
+
+ for (const ClassInfo &classInfo : factory.classInfos())
+ {
+ if (classInfo.category() == kVstAudioEffectClass)
+ {
+ bool found = false;
+ std::string newUid = classInfo.ID().toString();
+ for (const auto &existingPlugin : list)
+ {
+ if (existingPlugin->uid_ == newUid)
+ {
+ found = true;
+ break; // first instance takes precedence (e.g. ~/.vst3 takes precedence over /usr/lib/vst3 )
+ }
+ }
+ if (!found)
+ {
+ std::unique_ptr pluginInfo = MakeDetailedPluginInfo(factory, classInfo, path);
+
+ list.push_back(std::move(pluginInfo));
+ }
+ }
+ }
+}
+
+bool Vst3HostImpl::havePluginsChanged() const
+{
+
+ std::unordered_set modules;
+
+ for (const auto &plugin : pluginList)
+ {
+ if (!std::filesystem::exists(plugin->filePath_))
+ {
+ return true;
+ }
+ modules.insert(plugin->filePath_);
+ }
+ Module::PathList pathList = Module::getModulePaths();
+
+ for (const auto &path : pathList)
+ {
+ if (!modules.contains(path))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+const Vst3Host::PluginList &Vst3HostImpl::RefreshPlugins()
+{
+ if (cacheFilePath.length() != 0)
+ {
+ LoadPluginCache();
+ }
+ if (havePluginsChanged())
+ {
+ RescanPlugins();
+ }
+ return pluginList;
+}
+
+const Vst3Host::PluginList &Vst3HostImpl::RescanPlugins()
+{
+ Module::PathList pathList = Module::getModulePaths();
+
+ PluginList list;
+
+ //pathList.insert(pathList.begin(),"/home/pi/.vst3/mda-vst3.vst3");
+
+ for (auto &path : pathList)
+ {
+ if (path == std::string("/home/pi/.vst3/mda-vst3.vst3"))
+ {
+ Lv2Log::debug("Processing MDA plugins");
+ }
+ filesystem::path p{path};
+ MakeVst3Info(p, list);
+ }
+ this->pluginList = std::move(list);
+ SavePluginCache();
+ return pluginList;
+}
+
+static std::string ParseVst3Url(const std::string &url)
+{
+ if (!url.starts_with("vst3:"))
+ {
+ throw Vst3Exception("Not a valid vst3 url.: " + url);
+ }
+ std::string strUrl = url.substr(5);
+ return strUrl;
+}
+
+std::unique_ptr Vst3HostImpl::CreatePlugin(long instanceId, const std::string &url, IHost *pHost)
+{
+ EnsureContext();
+
+ auto uuid = ParseVst3Url(url);
+
+ std::unique_ptr result;
+
+ for (const auto &plugin : pluginList)
+ {
+ if (plugin->uid_ == uuid)
+ {
+ return Vst3Effect::CreateInstance(instanceId, *(plugin.get()), pHost);
+ }
+ }
+ throw Vst3Exception("Plugin not found.");
+}
+
+static inline uint8_t Hex(char c)
+{
+ if (c >= '0' && c <= '9') return c-'0';
+ if (c >= 'a' && c <= 'z') return c-'a'+10;
+ if (c >= 'A' && c <= 'Z') return c-'A'+10;
+
+ throw Vst3Exception("Invalid state bundle.");
+}
+
+static std::vector HexToByteArray(const std::string&hexState)
+{
+ std::vector result;
+ result.resize(hexState.length()/2);
+ for (int i = 0; i < result.size(); ++i)
+ {
+ result[i] = Hex(hexState[i*2])*16 + Hex(hexState[i*2+1]);
+ }
+ return result;
+}
+std::unique_ptr Vst3HostImpl::CreatePlugin(PedalBoardItem&pedalBoardItem, IHost*pHost)
+{
+ std::unique_ptr result = CreatePlugin(pedalBoardItem.instanceId(),pedalBoardItem.uri(),pHost);
+ auto pluginInfo = this->GetPluginInfo(pedalBoardItem.uri());
+ if (!pluginInfo)
+ {
+ throw Vst3Exception(SS("Plugin " << pedalBoardItem.pluginName() << " not found."));
+ }
+ if (pedalBoardItem.vstState().length() != 0)
+ {
+ std::vector state = HexToByteArray(pedalBoardItem.vstState());
+ result->SetState(state);
+
+ for (ControlValue &controlValue: pedalBoardItem.controlValues())
+ {
+ int32_t index = -1;
+ for (size_t i = 0; i < pluginInfo->controls().size(); ++i)
+ {
+ if (pluginInfo->controls()[i].symbol() == controlValue.key())
+ {
+ index = (int32_t)(pluginInfo->controls()[i].index());
+ break;
+ }
+ }
+ if (index != -1)
+ {
+ float value = result->GetControlValue(index);
+ controlValue.value(value);
+ }
+
+ }
+
+ } else {
+ for (const ControlValue &controlValue: pedalBoardItem.controlValues())
+ {
+ int32_t index = -1;
+ for (size_t i = 0; i < pluginInfo->controls().size(); ++i)
+ {
+ if (pluginInfo->controls()[i].symbol() == controlValue.key())
+ {
+ index = (int32_t)(pluginInfo->controls()[i].index());
+ break;
+ }
+ }
+ if (index != -1)
+ {
+ result->SetControl(index,controlValue.value());
+ }
+
+ }
+ }
+ return result;
+}
+
+
+#include
+
+void Vst3HostImpl::LoadPluginCache()
+{
+ if (cacheFilePath.length() != 0)
+ {
+ std::ifstream f(cacheFilePath);
+ if (f.is_open())
+ {
+ json_reader reader(f);
+
+ reader.read(&(this->pluginList));
+ }
+
+ }
+}
+void Vst3HostImpl::SavePluginCache()
+{
+ if (cacheFilePath.length() != 0)
+ {
+ std::ofstream f(cacheFilePath);
+ if (f.is_open())
+ {
+ json_writer writer(f);
+
+ writer.write(this->pluginList);
+ }
+
+ }
+
+}
+
+Lv2PluginUiInfo *Vst3HostImpl::GetPluginInfo(const std::string&uri)
+{
+ for (const auto &pluginInfo: pluginList)
+ {
+ if (pluginInfo->pluginInfo_.uri() == uri)
+ {
+ return &(pluginInfo->pluginInfo_);
+ }
+ }
+ return nullptr;
+}
+
\ No newline at end of file
diff --git a/src/Vst3MidiToEvent.hpp b/src/Vst3MidiToEvent.hpp
new file mode 100644
index 0000000..f6af30c
--- /dev/null
+++ b/src/Vst3MidiToEvent.hpp
@@ -0,0 +1,159 @@
+//-----------------------------------------------------------------------------
+// Flags : clang-format auto
+// Project : VST SDK
+//
+// Category : AudioHost
+// Filename : public.sdk/samples/vst-hosting/audiohost/source/media/miditovst.h
+// Created by : Steinberg 09.2016
+// Description : Audio Host Example for VST 3
+//
+//-----------------------------------------------------------------------------
+// LICENSE
+// (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved
+//-----------------------------------------------------------------------------
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of the Steinberg Media Technologies nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+// OF THE POSSIBILITY OF SUCH DAMAGE.
+//-----------------------------------------------------------------------------
+
+#pragma once
+
+#include "public.sdk/source/vst/utility/optional.h"
+#include "pluginterfaces/vst/ivstevents.h"
+#include "pluginterfaces/vst/ivstmidicontrollers.h"
+#include
+#include
+
+//------------------------------------------------------------------------
+namespace Steinberg {
+namespace Vst {
+
+const uint8_t kNoteOff = 0x80; ///< note, off velocity
+const uint8_t kNoteOn = 0x90; ///< note, on velocity
+const uint8_t kPolyPressure = 0xA0; ///< note, pressure
+const uint8_t kController = 0xB0; ///< controller, value
+const uint8_t kProgramChangeStatus = 0xC0; ///< program change
+const uint8_t kAfterTouchStatus = 0xD0; ///< channel pressure
+const uint8_t kPitchBendStatus = 0xE0; ///< lsb, msb
+static const uint32 kDataMask = 0x7F;
+
+const float kMidiScaler = 1.f / 127.f;
+
+using MidiData = uint8_t;
+
+float toNormalized (const MidiData& data)
+{
+ return (float)data * kMidiScaler;
+}
+
+using OptionalEvent = VST3::Optional;
+
+using ParameterChange = std::pair;
+using OptionParamChange = VST3::Optional;
+
+OptionalEvent midiToEvent (MidiData status, MidiData channel, MidiData midiData0,
+ MidiData midiData1)
+{
+ Event new_event = {};
+ if (status == kNoteOn || status == kNoteOff)
+ {
+ if (status == kNoteOff) // note off
+ {
+ new_event.noteOff.noteId = -1;
+ new_event.type = Event::kNoteOffEvent;
+ new_event.noteOff.channel = channel;
+ new_event.noteOff.pitch = midiData0;
+ new_event.noteOff.velocity = toNormalized (midiData1);
+ return std::move (new_event);
+ }
+ else if (status == kNoteOn) // note on
+ {
+ new_event.noteOn.noteId = -1;
+ new_event.type = Event::kNoteOnEvent;
+ new_event.noteOn.channel = channel;
+ new_event.noteOn.pitch = midiData0;
+ new_event.noteOn.velocity = toNormalized (midiData1);
+ return std::move (new_event);
+ }
+ }
+ //--- -----------------------------
+ else if (status == kPolyPressure)
+ {
+ new_event.type = Vst::Event::kPolyPressureEvent;
+ new_event.polyPressure.channel = channel;
+ new_event.polyPressure.pitch = midiData0;
+ new_event.polyPressure.pressure = toNormalized (midiData1);
+ return std::move (new_event);
+ }
+
+ return {};
+}
+
+//------------------------------------------------------------------------
+using ToParameterIdFunc = std::function;
+OptionParamChange midiToParameter (MidiData status, MidiData channel, MidiData midiData1,
+ MidiData midiData2, const ToParameterIdFunc& toParamID)
+{
+ if (!toParamID)
+ return {};
+
+ ParameterChange paramChange;
+ if (status == kController) // controller
+ {
+ paramChange.first = toParamID (channel, midiData1);
+ if (paramChange.first != kNoParamId)
+ {
+ paramChange.second = (double)midiData2 * kMidiScaler;
+ return std::move (paramChange);
+ }
+ }
+ else if (status == kPitchBendStatus)
+ {
+ paramChange.first = toParamID (channel, Vst::kPitchBend);
+ if (paramChange.first != kNoParamId)
+ {
+ const double kPitchWheelScaler = 1. / (double)0x3FFF;
+
+ const int32 ctrl = (midiData1 & kDataMask) | (midiData2 & kDataMask) << 7;
+ paramChange.second = kPitchWheelScaler * (double)ctrl;
+ return std::move (paramChange);
+ };
+ }
+ else if (status == kAfterTouchStatus)
+ {
+ paramChange.first = toParamID (channel, Vst::kAfterTouch);
+ if (paramChange.first != kNoParamId)
+ {
+ paramChange.second = (ParamValue) (midiData1 & kDataMask) * kMidiScaler;
+ return std::move (paramChange);
+ };
+ }
+ else if (status == kProgramChangeStatus)
+ {
+ // TODO
+ }
+
+ return {};
+}
+//------------------------------------------------------------------------
+} // Vst
+} // Steinberg
diff --git a/src/Vst3PresetFile.cpp b/src/Vst3PresetFile.cpp
new file mode 100644
index 0000000..fe79443
--- /dev/null
+++ b/src/Vst3PresetFile.cpp
@@ -0,0 +1,86 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022 Robin E. R. 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.
+ */
+
+#include "vst3/Vst3PresetFile.hpp"
+#include "public.sdk/source/vst/vstpresetfile.h"
+using namespace pipedal;
+using namespace Steinberg;
+using namespace Steinberg::Vst;
+
+
+namespace pipedal {
+
+class Vst3PresetFileImpl : public Vst3PresetFile {
+public:
+ virtual ~Vst3PresetFileImpl() { }
+
+
+ virtual void Load(const std::string&path);
+
+ virtual std::vector GetPresets();
+
+ virtual std::vector GetPresetChunk(uint32_t index);
+private:
+ std::unique_ptr presetFile;
+
+};
+
+
+void Vst3PresetFileImpl::Load(const std::string&path)
+{
+ OPtr stream = FileStream::open(path.c_str(),"r");
+ presetFile = std::make_unique(stream);
+
+}
+
+std::vector Vst3PresetFileImpl::GetPresets()
+{
+ std::vector result;
+ auto count = presetFile->getEntryCount();
+ result.reserve(count);
+
+ for (int i = 0; i < count; ++i)
+ {
+ const PresetFile::Entry& entry = presetFile->at(i);
+ }
+
+ return result;
+}
+
+std::vector Vst3PresetFileImpl::GetPresetChunk(uint32_t index)
+{
+ return std::vector(); // STUB
+}
+
+
+
+std::unique_ptr Vst3PresetFile::CreateInstance()
+{
+ return std::make_unique();
+}
+
+
+
+
+} // namespace pipedal
\ No newline at end of file
diff --git a/src/Vst3RtStream.cpp b/src/Vst3RtStream.cpp
new file mode 100644
index 0000000..6a0bdc5
--- /dev/null
+++ b/src/Vst3RtStream.cpp
@@ -0,0 +1,358 @@
+//-----------------------------------------------------------------------------
+// Project : SDK Core
+//
+// Category : Common Classes
+// Filename : public.sdk/source/common/memorystream.cpp
+// Created by : Steinberg, 03/2008
+// Description : IBStream Implementation for memory blocks
+//
+//-----------------------------------------------------------------------------
+// LICENSE
+// (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved
+//-----------------------------------------------------------------------------
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of the Steinberg Media Technologies nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+// OF THE POSSIBILITY OF SUCH DAMAGE.
+//-----------------------------------------------------------------------------
+
+#include "vst3/Vst3RtStream.hpp"
+#include "pluginterfaces/base/futils.h"
+#include
+
+namespace pipedal {
+
+//-----------------------------------------------------------------------------
+IMPLEMENT_FUNKNOWN_METHODS (RtStream, IBStream, IBStream::iid)
+static const TSize kMemGrowAmount = 4096;
+
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+RtStream::RtStream(RtStreamPool*pool)
+: pool(pool)
+, memory (nullptr)
+, memorySize (0)
+, size (0)
+, cursor (0)
+, ownMemory (true)
+, allocationError (false)
+{
+ FUNKNOWN_CTOR
+}
+
+//-----------------------------------------------------------------------------
+RtStream::~RtStream ()
+{
+ if (ownMemory && memory)
+ ::free (memory);
+
+ FUNKNOWN_DTOR
+}
+
+//-----------------------------------------------------------------------------
+tresult PLUGIN_API RtStream::read (void* data, int32 numBytes, int32* numBytesRead)
+{
+ if (memory == nullptr)
+ {
+ if (allocationError)
+ return kOutOfMemory;
+ numBytes = 0;
+ }
+ else
+ {
+ // Does read exceed size ?
+ if (cursor + numBytes > size)
+ {
+ int32 maxBytes = int32 (size - cursor);
+
+ // Has length become zero or negative ?
+ if (maxBytes <= 0)
+ {
+ cursor = size;
+ numBytes = 0;
+ }
+ else
+ numBytes = maxBytes;
+ }
+
+ if (numBytes)
+ {
+ memcpy (data, &memory[cursor], static_cast (numBytes));
+ cursor += numBytes;
+ }
+ }
+
+ if (numBytesRead)
+ *numBytesRead = numBytes;
+
+ return kResultTrue;
+}
+
+//-----------------------------------------------------------------------------
+tresult PLUGIN_API RtStream::write (void* buffer, int32 numBytes, int32* numBytesWritten)
+{
+ if (allocationError)
+ return kOutOfMemory;
+ if (buffer == nullptr)
+ return kInvalidArgument;
+
+ // Does write exceed size ?
+ TSize requiredSize = cursor + numBytes;
+ if (requiredSize > size)
+ {
+ if (requiredSize > memorySize)
+ setSize (requiredSize);
+ else
+ size = requiredSize;
+ }
+
+ // Copy data
+ if (memory && cursor >= 0 && numBytes > 0)
+ {
+ memcpy (&memory[cursor], buffer, static_cast (numBytes));
+ // Update cursor
+ cursor += numBytes;
+ }
+ else
+ numBytes = 0;
+
+ if (numBytesWritten)
+ *numBytesWritten = numBytes;
+
+ return kResultTrue;
+}
+
+//-----------------------------------------------------------------------------
+tresult PLUGIN_API RtStream::seek (int64 pos, int32 mode, int64* result)
+{
+ switch (mode)
+ {
+ case kIBSeekSet:
+ cursor = pos;
+ break;
+ case kIBSeekCur:
+ cursor = cursor + pos;
+ break;
+ case kIBSeekEnd:
+ cursor = size + pos;
+ break;
+ }
+
+ if (ownMemory == false)
+ if (cursor > memorySize)
+ cursor = memorySize;
+
+ if (result)
+ *result = cursor;
+
+ return kResultTrue;
+}
+
+//-----------------------------------------------------------------------------
+tresult PLUGIN_API RtStream::tell (int64* pos)
+{
+ if (!pos)
+ return kInvalidArgument;
+
+ *pos = cursor;
+ return kResultTrue;
+}
+
+//------------------------------------------------------------------------
+TSize RtStream::getSize () const
+{
+ return size;
+}
+
+//------------------------------------------------------------------------
+void RtStream::setSize (TSize s)
+{
+ if (s <= 0)
+ {
+ if (ownMemory && memory)
+ free (memory);
+
+ memory = nullptr;
+ memorySize = 0;
+ size = 0;
+ cursor = 0;
+ return;
+ }
+
+ TSize newMemorySize = (((Max (memorySize, s) - 1) / kMemGrowAmount) + 1) * kMemGrowAmount;
+ if (newMemorySize == memorySize)
+ {
+ size = s;
+ return;
+ }
+
+ if (memory && ownMemory == false)
+ {
+ allocationError = true;
+ return;
+ }
+
+ ownMemory = true;
+ char* newMemory = nullptr;
+
+ if (memory)
+ {
+ newMemory = (char*)realloc (memory, (size_t)newMemorySize);
+ if (newMemory == nullptr && newMemorySize > 0)
+ {
+ newMemory = (char*)malloc ((size_t)newMemorySize);
+ if (newMemory)
+ {
+ memcpy (newMemory, memory, (size_t)Min (newMemorySize, memorySize));
+ free (memory);
+ }
+ }
+ }
+ else
+ newMemory = (char*)malloc ((size_t)newMemorySize);
+
+ if (newMemory == nullptr)
+ {
+ if (newMemorySize > 0)
+ allocationError = true;
+
+ memory = nullptr;
+ memorySize = 0;
+ size = 0;
+ cursor = 0;
+ }
+ else
+ {
+ memory = newMemory;
+ memorySize = newMemorySize;
+ size = s;
+ }
+}
+
+//------------------------------------------------------------------------
+char* RtStream::getData () const
+{
+ return memory;
+}
+
+//------------------------------------------------------------------------
+char* RtStream::detachData ()
+{
+ if (ownMemory)
+ {
+ char* result = memory;
+ memory = nullptr;
+ memorySize = 0;
+ size = 0;
+ cursor = 0;
+ return result;
+ }
+ return nullptr;
+}
+
+//------------------------------------------------------------------------
+bool RtStream::truncate ()
+{
+ if (ownMemory == false)
+ return false;
+
+ if (memorySize == size)
+ return true;
+
+ memorySize = size;
+
+ if (memorySize == 0)
+ {
+ if (memory)
+ {
+ free (memory);
+ memory = nullptr;
+ }
+ }
+ else
+ {
+ if (memory)
+ {
+ char* newMemory = (char*)realloc (memory, (size_t)memorySize);
+ if (newMemory)
+ memory = newMemory;
+ }
+ }
+ return true;
+}
+
+//------------------------------------------------------------------------
+bool RtStream::truncateToCursor ()
+{
+ size = cursor;
+ return truncate ();
+}
+
+}
+
+// namespace
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022 Robin E. R. 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.
+ */
+
+#include "vst3/Vst3RtStream.hpp"
+
+using namespace Steinberg;
+using namespace pipedal;
+
+
+
+void RtStreamPool::ReleaseStreams()
+{
+ RtStream *freeStreamList;
+ {
+ freeStreamList = this->freeStreamList;
+ this->freeStreamList = nullptr;
+ }
+ while (freeStreamList)
+ {
+ RtStream *next = freeStreamList->next;
+
+ freeStreamList->release();
+ freeStreamList = next;
+ }
+}
\ No newline at end of file
diff --git a/src/Vst3SdkRepro.cpp b/src/Vst3SdkRepro.cpp
new file mode 100644
index 0000000..7efad66
--- /dev/null
+++ b/src/Vst3SdkRepro.cpp
@@ -0,0 +1,49 @@
+#include
+#include "public.sdk/source/vst/hosting/module.h"
+#include "public.sdk/source/vst/hosting/hostclasses.h"
+#include "public.sdk/source/vst/hosting/plugprovider.h"
+#include "pluginterfaces/vst/ivsteditcontroller.h"
+#include "pluginterfaces/vst/ivstaudioprocessor.h"
+#include "pluginterfaces/vst/ivstunits.h"
+#include "public.sdk/source/vst/utility/stringconvert.h"
+#include "public.sdk/source/vst/hosting/eventlist.h"
+#include "public.sdk/source/vst/hosting/parameterchanges.h"
+#include "public.sdk/source/vst/hosting/processdata.h"
+#include "pluginterfaces/vst/ivstaudioprocessor.h"
+#include "pluginterfaces/vst/ivstmidicontrollers.h"
+
+#include "public.sdk/samples/vst-hosting/audiohost/source/media/iparameterclient.h"
+#include "public.sdk/samples/vst-hosting/audiohost/source/media/imediaserver.h"
+
+using namespace std;
+using namespace VST3::Hosting;
+using namespace Steinberg;
+using namespace Steinberg::Vst;
+
+PluginFactory::ClassInfos GetVst3ClassInfos(const std::string& pluginPath)
+{
+ std::string error;
+ Module::Ptr module = Module::create(pluginPath.c_str(), error);
+
+ if (!module)
+ {
+ assert(false && "Vst3 load failed.");
+ }
+ const PluginFactory &factory = module->getFactory();
+
+ return factory.classInfos();
+}
+
+static std::string HomePath() { return getenv("HOME"); }
+
+void BugReportTest()
+{
+ auto _ = GetVst3ClassInfos(HomePath() + "/.vst3/adelay.vst3");
+ PluginFactory::ClassInfos classInfos = GetVst3ClassInfos(HomePath() + "/.vst3/mda-vst3.vst3");
+
+ // right size.
+ assert(classInfos.size() == 68); // succeeds
+
+ // wrong classInfo (from adelay.vst3)
+ assert(classInfos[0].name() == "mda Ambience"); // actual result "ADelay"
+}
diff --git a/src/Vst3test.cpp b/src/Vst3test.cpp
new file mode 100644
index 0000000..183490b
--- /dev/null
+++ b/src/Vst3test.cpp
@@ -0,0 +1,149 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022 Robin E. R. 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.
+ */
+
+#include "PiPedalHost.hpp"
+#include "vst3/Vst3Host.hpp"
+#include
+
+using namespace pipedal;
+using namespace std;
+
+void EnumerateVst3Plugins()
+{
+ cout << "--- Enumeration Test --------" << endl;
+ Vst3Host::Ptr vst3Host = Vst3Host::CreateInstance();
+ vst3Host->RescanPlugins();
+
+
+ vst3Host->RefreshPlugins();
+}
+
+
+void CheckSync(IEffect*effect)
+{
+ Vst3Effect *vst3Effect = (Vst3Effect*)effect;
+
+ vst3Effect->CheckSync();
+}
+
+void TestPrograms(const Vst3PluginInfo &info, Vst3Effect *effect)
+{
+ for (size_t i = 0; i < info.pluginInfo_.port_groups().size(); ++i)
+ {
+ const auto &port_group = info.pluginInfo_.port_groups()[i];
+ if (port_group.program_list_id() != -1)
+ {
+ auto programList = effect->GetProgramList(port_group.program_list_id());
+ for (const auto& programGroup: programList)
+ {
+ cout <<" " << port_group.program_list_id() << " " << programGroup.name << " (" << programGroup.programs.size() << " programs)" << std::endl;
+ // for (const auto&program: programGroup.programs)
+ // {
+ // cout << " " << program.id << " " << program.name << endl;
+ // }
+ }
+ }
+ }
+}
+
+void RunVsts()
+{
+ cout << "--- RunVsts ----------------" << endl;
+ Vst3Host::Ptr vst3Host = Vst3Host::CreateInstance();
+
+ cout << "Scanning" << endl;
+ const auto & plugins = vst3Host->RescanPlugins();
+
+ PiPedalHost host;
+
+ IHost *pHost = host.asIHost();
+ host.setSampleRate(44100);
+
+ for (const auto & info : plugins)
+ {
+
+ cout << " Running " << info->pluginInfo_.name() << endl;
+ auto plugin = vst3Host->CreatePlugin(0,info->pluginInfo_.uri(),pHost);
+
+ int nInputs = info->pluginInfo_.audio_inputs();
+ int nOutputs = info->pluginInfo_.audio_outputs();
+
+ plugin->Prepare(host.GetSampleRate(),pHost->GetMaxAudioBufferSize(),info->pluginInfo_.audio_inputs(), info->pluginInfo_.audio_outputs());
+
+ plugin->Activate();
+
+ std::vector inputs;
+ std::vector outputs;
+ inputs.resize(nInputs);
+ for (int i = 0; i < nInputs; ++i)
+ {
+ inputs[i] = new float[512];
+ plugin->SetAudioInputBuffer(i,inputs[i]);
+
+ for (int j = 0; j < 512; ++j)
+ {
+ inputs[i][j] = j / 512.0;
+ }
+ }
+ outputs.resize(nOutputs);
+ for (int i = 0; i < nOutputs; ++i)
+ {
+ outputs[i] = new float[512];
+ plugin->SetAudioOutputBuffer(i,outputs[i]);
+ }
+
+ plugin->Run(512,nullptr);
+
+ for (int i = 0; i < info->pluginInfo_.controls().size(); ++i)
+ {
+ float v = plugin->GetControlValue(i);
+ plugin->SetControl(i,v);
+
+ plugin->Run(0,nullptr);
+
+ plugin->CheckSync();
+ }
+
+ TestPrograms(*info,plugin.get());
+
+ plugin->Deactivate();
+ }
+
+
+
+}
+
+void BugReportTest();
+
+int main(int, char**)
+{
+ BugReportTest();
+
+ RunVsts();
+
+ EnumerateVst3Plugins();
+
+
+ return 0;
+}
\ No newline at end of file
diff --git a/src/asan_options.cpp b/src/asan_options.cpp
index a32a26b..58d3c67 100644
--- a/src/asan_options.cpp
+++ b/src/asan_options.cpp
@@ -25,6 +25,7 @@ extern "C" {
"detect_leaks=0" // undesirable behavior on abnormal termination.
":alloc_dealloc_mismatch=0" // Guitarix components trigger this. It's not actually a problem on GCC.
":new_delete_type_mismatch=0" //GxTuner
+ ":detect_odr_violation=0"
":print_stacktrace=1"
":halt_on_error=1"
;
diff --git a/src/main.cpp b/src/main.cpp
index 8805600..84b27f8 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -27,7 +27,7 @@
#include "AvahiService.hpp"
#include "PiPedalSocket.hpp"
-#include "Lv2Host.hpp"
+#include "PiPedalHost.hpp"
#include
#include
#include "PiPedalConfiguration.hpp"
diff --git a/src/vst3/Vst3Effect.hpp b/src/vst3/Vst3Effect.hpp
new file mode 100644
index 0000000..0b3271c
--- /dev/null
+++ b/src/vst3/Vst3Effect.hpp
@@ -0,0 +1,83 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022 Robin E. R. 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.
+ */
+
+#pragma once
+
+#include "PiPedalHost.hpp"
+#include
+#include "json.hpp"
+
+
+namespace pipedal {
+ struct Vst3PluginInfo {
+ public:
+
+ std::string filePath_;
+ std::string version_;
+ std::string uid_;
+
+ Lv2PluginUiInfo pluginInfo_;
+
+ DECLARE_JSON_MAP(Vst3PluginInfo);
+
+ };
+
+ struct Vst3ProgramListEntry {
+ int32_t id;
+ std::string name;
+ };
+
+ struct Vst3ProgramList {
+ std::string name;
+
+ std::vector programs;
+
+ };
+
+ class Vst3Effect: public IEffect {
+ public:
+ using Ptr = std::unique_ptr;
+
+ virtual void Prepare(int32_t sampleRate, size_t maxBufferSize,int inputChannels, int outputChannels) = 0;
+
+ virtual void Activate() = 0;
+ virtual void Deactivate() = 0;
+ virtual void Unprepare() = 0;
+ virtual void CheckSync() = 0; // throw if audioProcessor and controller are not sync (test only)
+
+ virtual bool SupportsState() const = 0;
+ virtual bool GetState(std::vector *state) = 0;
+
+ virtual void SetState(const std::vector state) = 0;
+
+ using ControlChangedHandler = std::function;
+ virtual void SetControlChangedHandler(const ControlChangedHandler& handler) = 0;
+ virtual void RemoveControlChangedHandler() = 0;
+
+ virtual std::vector GetProgramList(int32_t programListId) = 0;
+ public:
+ static std::unique_ptr CreateInstance(uint64_t instanceId,const Vst3PluginInfo& info, IHost *pHost);
+ };
+
+}
\ No newline at end of file
diff --git a/src/vst3/Vst3EffectImpl.hpp b/src/vst3/Vst3EffectImpl.hpp
new file mode 100644
index 0000000..a544cd6
--- /dev/null
+++ b/src/vst3/Vst3EffectImpl.hpp
@@ -0,0 +1,286 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022 Robin E. R. 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.
+ */
+
+#include "Vst3Effect.hpp"
+#include
+
+#include "RtInversionGuard.hpp"
+#include "Vst3RtStream.hpp"
+
+#pragma once
+
+namespace pipedal
+{
+ using namespace std;
+ using namespace pipedal;
+ using namespace VST3::Hosting;
+ using namespace Steinberg;
+ using namespace Steinberg::Vst;
+
+ enum
+ {
+ kMaxMidiMappingBusses = 4,
+ kMaxMidiChannels = 16
+ };
+ using Controllers = std::vector;
+ using Channels = std::array;
+ using Busses = std::array;
+ using MidiCCMapping = Busses;
+
+ class Vst3EffectImpl : public Vst3Effect,
+ public IAudioClient,
+ public IMidiClient
+ {
+ public:
+ //--------------------------------------------------------------------
+ using Name = std::string;
+
+ Vst3EffectImpl();
+ virtual ~Vst3EffectImpl();
+ void Load(uint64_t instanceId, const Vst3PluginInfo &info, IHost *pHost);
+
+ // IEffect
+ virtual bool IsVst3() const { return true; }
+ virtual uint64_t GetInstanceId() const { return instanceId; }
+ virtual int GetControlIndex(const std::string &symbol) const
+ {
+ for (size_t i = 0; i < info.pluginInfo_.controls().size(); ++i)
+ {
+ if (info.pluginInfo_.controls()[i].symbol() == symbol)
+ {
+ return (int)i;
+ }
+ }
+ return -1;
+ }
+
+ bool SupportsState() const { return supportsState; }
+
+ bool GetState(std::vector *state);
+
+ void SetState(const std::vector state);
+
+ virtual void CheckSync();
+
+ //- PiPedalHost Interfaces.
+ virtual void SetControl(int index, float value);
+ virtual float GetControlValue(int index) const
+ {
+ return this->parameterValues[index];
+ }
+
+ virtual std::vector GetProgramList(int32_t programListId);
+
+ int bypassControl = -1;
+ bool bypassEnable = false;
+
+ virtual void SetBypass(bool enable)
+ {
+ if (bypassControl != -1)
+ {
+ SetControl(bypassControl, enable ? 1 : 0);
+ }
+ else
+ {
+ bypassEnable = enable;
+ }
+ }
+
+ virtual void Activate();
+ virtual void Deactivate();
+
+ virtual float GetOutputControlValue(int controlIndex) const
+ {
+ return this->parameterValues[controlIndex];
+ }
+
+ virtual int GetNumberOfInputAudioPorts() const
+ {
+ return info.pluginInfo_.audio_inputs();
+ }
+ virtual int GetNumberOfOutputAudioPorts() const
+ {
+ return info.pluginInfo_.audio_outputs();
+ }
+ virtual float *GetAudioInputBuffer(int index) const { return buffers.inputs[index]; }
+ virtual float *GetAudioOutputBuffer(int index) const { return buffers.outputs[index]; }
+ virtual void ResetAtomBuffers() {}
+ virtual void RequestParameter(LV2_URID uridUri) {} // no vst equivalent.
+ virtual void GatherParameter(RealtimeParameterRequest *pRequest) {} // no vst equivalent.
+
+ virtual void SetAudioInputBuffer(int index, float *buffer)
+ {
+ buffers.inputs[index] = buffer;
+ }
+ virtual void SetAudioOutputBuffer(int index, float *buffer)
+ {
+ buffers.outputs[index] = buffer;
+ }
+
+
+ virtual void Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter)
+ {
+ process(buffers, samples);
+ SendControlChanges(realtimeRingBufferWriter);
+ }
+
+ // Vst3Effect interfaces.
+
+ virtual void Prepare(int32_t sampleRate, size_t maxBufferSize, int inputChannels, int outputChannels);
+ virtual void Unprepare();
+
+ // IAudioClient
+ bool process(Buffers &buffers, int64_t continousFrames) override;
+ bool setSamplerate(SampleRate value) override;
+ bool setBlockSize(int32 value) override;
+ IAudioClient::IOSetup getIOSetup() const override;
+
+ // IMidiClient
+ bool onEvent(const Event &event, int32_t port) override;
+ IMidiClient::IOSetup getMidiIOSetup() const override;
+
+ // IParameterClient
+
+
+ //--------------------------------------------------------------------
+ private:
+ bool supportsState = false;
+ IHost *pHost;
+ Module::Ptr module;
+ IPtr midiMapping;
+ IPtr plugProvider;
+
+ RtStreamPool streamPool;
+
+ void SendControlChanges(RealtimeRingBufferWriter *realtimeRingBufferWriter);
+ //--------------------------------------------------------------------
+ private:
+ std::mutex parameterMutex;
+ Buffers buffers;
+ std::vector lv2ToVstParam;
+ std::vector parameterValues;
+
+ // void createLocalMediaServer(const Name &name);
+ uint64_t instanceId;
+ Vst3PluginInfo info;
+ void terminate();
+ void initProcessData();
+ void updateBusBuffers(Buffers &buffers, HostProcessData &processData);
+ void preprocess(Buffers &buffers, int64_t continousFrames);
+ void postprocess(Buffers &buffers);
+ bool isPortInRange(int32 port, int32 channel) const;
+ bool processVstEvent(const IMidiClient::Event &event, int32 port);
+ bool processParamChange(const IMidiClient::Event &event, int32 port);
+
+ SampleRate sampleRate = 0;
+ int32 blockSize = 0;
+ HostProcessData processData;
+ ProcessContext processContext;
+ EventList eventList;
+ ParameterChanges inputParameterChanges;
+ ParameterChanges outputParameterChanges;
+ IComponent *component = nullptr;
+ IEditController *controller = nullptr;
+ FUnknownPtr processor;
+
+ ParameterChangeTransfer paramTransferrer;
+
+ MidiCCMapping midiCCMapping;
+ // IMediaServerPtr mediaServer;
+ bool isProcessing = false;
+
+ Name name;
+
+ private:
+ ssize_t ParamIdToLv2Id(ParamID id) const;
+
+ class ComponentHandler : public IComponentHandler
+ {
+ private:
+ Vst3EffectImpl *pEffect = nullptr;
+ public:
+ ComponentHandler() { }
+
+ void setEffect(Vst3EffectImpl*effect)
+ {
+ pEffect = effect;
+ }
+ tresult PLUGIN_API beginEdit(ParamID id) override
+ {
+ return pEffect->beginEdit(id);
+ }
+ tresult PLUGIN_API performEdit(ParamID id, ParamValue valueNormalized) override
+ {
+ return pEffect->performEdit(id, valueNormalized);
+ }
+ tresult PLUGIN_API endEdit(ParamID id) override
+ {
+ return pEffect->endEdit(id);
+ }
+ tresult PLUGIN_API restartComponent(int32 flags) override
+ {
+ return pEffect->restartComponent(flags);
+ }
+
+ private:
+ tresult PLUGIN_API queryInterface(const TUID _iid, void ** obj) override
+ {
+ if (_iid == IComponentHandler_iid)
+ {
+ addRef();
+ *((FUnknown**)obj) = this;
+ return kResultOk;
+ }
+
+ return kNoInterface;
+ }
+ uint32 PLUGIN_API addRef() override { return 1000; }
+ uint32 PLUGIN_API release() override { return 1000; }
+ };
+
+ ComponentHandler componentHandler;
+ void transferControllerStateToComponent();
+ void refreshControlValues();
+
+ virtual tresult beginEdit(ParamID id);
+ virtual tresult performEdit(ParamID id, ParamValue valueNormalized);
+ virtual tresult endEdit(ParamID id);
+ virtual tresult restartComponent(int32 flags);
+
+ private:
+ ControlChangedHandler controlChangedHandler = [] (int control,float value) {};
+ void fireControlChanged(int control, float normalizedValue);
+
+ public:
+ virtual void SetControlChangedHandler(const ControlChangedHandler& handler)
+ {
+ controlChangedHandler = handler;
+ }
+ virtual void RemoveControlChangedHandler() {
+ controlChangedHandler = [] (int,float){};
+ }
+
+ };
+
+}
\ No newline at end of file
diff --git a/src/vst3/Vst3Host.hpp b/src/vst3/Vst3Host.hpp
new file mode 100644
index 0000000..f4b1049
--- /dev/null
+++ b/src/vst3/Vst3Host.hpp
@@ -0,0 +1,70 @@
+/*
+ /* MIT License
+ *
+ * Copyright (c) 2022 Robin E. R. 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.
+ */
+
+#pragma once
+#include
+#include
+#include
+#include
+#include
+
+#include "public.sdk/source/vst/utility/uid.h"
+
+#include "vst3/Vst3Effect.hpp"
+#include "PedalBoard.hpp"
+
+
+ namespace pipedal {
+
+
+ class Vst3Exception: public std::logic_error {
+ public:
+ Vst3Exception(const std::string&error)
+ :logic_error(error)
+ {
+ }
+ };
+
+ class Vst3Host {
+
+ public:
+ using Ptr = std::unique_ptr;
+ using PluginList = std::vector>;
+
+ virtual ~Vst3Host() {}
+ virtual const PluginList &RescanPlugins() = 0;
+ virtual const PluginList &RefreshPlugins() = 0;
+ virtual const PluginList &getPluginList() = 0;
+
+
+ virtual std::unique_ptr CreatePlugin(long instanceId,const std::string&url, IHost*pHost) = 0;
+ virtual std::unique_ptr CreatePlugin(PedalBoardItem&pedalBoardItem, IHost*pHost) = 0;
+
+
+ static Ptr CreateInstance(const std::string &cacheFilePath = "");
+
+ };
+
+
+ };
\ No newline at end of file
diff --git a/src/vst3/Vst3PresetFile.hpp b/src/vst3/Vst3PresetFile.hpp
new file mode 100644
index 0000000..6b4be94
--- /dev/null
+++ b/src/vst3/Vst3PresetFile.hpp
@@ -0,0 +1,53 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022 Robin E. R. 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.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+namespace pipedal {
+ struct Vst3PresetInfo {
+ uint32_t index;
+ std::string name;
+ };
+ class Vst3PresetFile {
+ protected:
+ Vst3PresetFile() { }
+ public:
+ virtual ~Vst3PresetFile() { }
+
+
+ virtual void Load(const std::string&path) = 0;
+
+ virtual std::vector GetPresets() = 0;
+
+ virtual std::vector GetPresetChunk(uint32_t index) = 0;
+
+ std::unique_ptr CreateInstance();
+ };
+
+}
\ No newline at end of file
diff --git a/src/vst3/Vst3RtStream.hpp b/src/vst3/Vst3RtStream.hpp
new file mode 100644
index 0000000..c1ef79b
--- /dev/null
+++ b/src/vst3/Vst3RtStream.hpp
@@ -0,0 +1,109 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022 Robin E. R. 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.
+ */
+
+#pragma once
+
+#include "public.sdk/source/common/memorystream.h"
+#include
+#include "Vst3RtStream.hpp"
+
+namespace pipedal
+{
+ using namespace Steinberg;
+
+ class IRtStream : public IBStream
+ {
+ public:
+ virtual void QueueForRelease() = 0;
+ };
+
+ class RtStreamPool;
+
+ class RtStream : public IRtStream
+ {
+ friend class RtStreamPool;
+
+ virtual void PLUGIN_API QueueForRelease();
+ RtStream(RtStreamPool *pool);
+
+ public:
+ virtual ~RtStream();
+ //---IBStream---------------------------------------
+ tresult PLUGIN_API read(void *buffer, int32 numBytes, int32 *numBytesRead) SMTG_OVERRIDE;
+ tresult PLUGIN_API write(void *buffer, int32 numBytes, int32 *numBytesWritten) SMTG_OVERRIDE;
+ tresult PLUGIN_API seek(int64 pos, int32 mode, int64 *result) SMTG_OVERRIDE;
+ tresult PLUGIN_API tell(int64 *pos) SMTG_OVERRIDE;
+
+ TSize getSize() const; ///< returns the current memory size
+ void setSize(TSize size); ///< set the memory size, a realloc will occur if memory already used
+ char *getData() const; ///< returns the memory pointer
+ char *detachData(); ///< returns the memory pointer and give up ownership
+ bool truncate(); ///< realloc to the current use memory size if needed
+ bool truncateToCursor(); ///< truncate memory at current cursor position
+
+ //------------------------------------------------------------------------
+ DECLARE_FUNKNOWN_METHODS
+ protected:
+ RtStreamPool *pool = nullptr;
+ RtStream *next = nullptr;
+ char *memory; // memory block
+ TSize memorySize; // size of the memory block
+ TSize size; // size of the stream
+ int64 cursor; // stream pointer
+ bool ownMemory; // stream has allocated memory itself
+ bool allocationError; // stream invalid
+ };
+
+ class RtStreamPool
+ {
+ public:
+ ~RtStreamPool()
+ {
+ ReleaseStreams();
+ }
+ IRtStream *AllocateBStream()
+ {
+ ReleaseStreams();
+ return new RtStream(this);
+ }
+ void ReleaseStreams();
+
+ private:
+ friend class RtStream;
+
+ RtStream *freeStreamList = nullptr;
+
+ void QueueForRelease(RtStream *pStream)
+ {
+ pStream->next = freeStreamList;
+ freeStreamList = pStream;
+ }
+ };
+
+
+ inline void RtStream::QueueForRelease()
+ {
+ pool->QueueForRelease(this);
+ }
+};
\ No newline at end of file
diff --git a/submodules/pipedal_p2pd b/submodules/pipedal_p2pd
index 5b037c8..862a029 160000
--- a/submodules/pipedal_p2pd
+++ b/submodules/pipedal_p2pd
@@ -1 +1 @@
-Subproject commit 5b037c80b0eab465f5635cc45fc4eb1ccebfe433
+Subproject commit 862a02937db00fa0fb582dd362089404c46ecdf8
diff --git a/test/Lv2Test.cpp b/test/Lv2Test.cpp
index dcff663..e89d014 100644
--- a/test/Lv2Test.cpp
+++ b/test/Lv2Test.cpp
@@ -19,7 +19,7 @@
#include "pch.h"
#include "catch.hpp"
#include
-#include "Lv2Host.hpp"
+#include "PiPedalHost.hpp"
using namespace piddle;