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 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 (vst) + } + 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;