From 5cf10b0154826b622aad3ddf75e63c7e46d385ce Mon Sep 17 00:00:00 2001 From: Robin Davies Date: Fri, 11 Feb 2022 11:22:20 -0500 Subject: [PATCH] NeuralPi bug fixes. --- .gitignore | 1 - .vscode/c_cpp_properties.json | 18 + .vscode/launch.json | 77 ++++ .vscode/settings.json | 97 ++++ react/package.json | 6 + react/src/LoadPluginDialog.tsx | 384 ++++++++------- react/src/Lv2Plugin.tsx | 22 +- react/src/PluginControl.tsx | 2 +- react/src/SearchFilter.tsx | 1 + src/BeastServer.cpp | 3 + src/CMakeLists.txt | 1 + src/dbus/bluez.h | 4 +- src/dbus/bluez_test.cpp | 72 ++- src/gsl/README.txt | 13 + src/gsl/algorithm | 63 +++ src/gsl/assert | 136 ++++++ src/gsl/byte | 213 +++++++++ src/gsl/gsl | 32 ++ src/gsl/gsl_algorithm | 4 + src/gsl/gsl_assert | 3 + src/gsl/gsl_byte | 3 + src/gsl/gsl_narrow | 3 + src/gsl/gsl_util | 3 + src/gsl/narrow | 71 +++ src/gsl/pointers | 323 +++++++++++++ src/gsl/span | 821 +++++++++++++++++++++++++++++++++ src/gsl/span_ext | 212 +++++++++ src/gsl/string_span | 759 ++++++++++++++++++++++++++++++ src/gsl/util | 160 +++++++ 29 files changed, 3314 insertions(+), 193 deletions(-) create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 src/gsl/README.txt create mode 100644 src/gsl/algorithm create mode 100644 src/gsl/assert create mode 100644 src/gsl/byte create mode 100644 src/gsl/gsl create mode 100644 src/gsl/gsl_algorithm create mode 100644 src/gsl/gsl_assert create mode 100644 src/gsl/gsl_byte create mode 100644 src/gsl/gsl_narrow create mode 100644 src/gsl/gsl_util create mode 100644 src/gsl/narrow create mode 100644 src/gsl/pointers create mode 100644 src/gsl/span create mode 100644 src/gsl/span_ext create mode 100644 src/gsl/string_span create mode 100644 src/gsl/util diff --git a/.gitignore b/.gitignore index 11dcf1f..a5709ff 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ build/ #settings -.vscode Testing/ diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..2111419 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${default}", + "/usr/include/lilv-0" + ], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "linux-gcc-arm64", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "configurationProvider": "ms-vscode.cmake-tools" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..9ee6ffd --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,77 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "(gdb) pipedaltest Launch", + "type": "cppdbg", + "request": "launch", + // Resolved by CMake Tools: + "program": "${command:cmake.launchTargetPath}", + "args": [ "[bluetooth_service]" ], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [ + { + // add the directory where our target was built to the PATHs + // it gets resolved by CMake Tools: + "name": "PATH", + "value": "$PATH:${command:cmake.launchTargetDirectory}" + }, + { + "name": "OTHER_VALUE", + "value": "Something something" + } + ], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + }, + { + "name": "(gdb) pipedal Launch", + "type": "cppdbg", + "request": "launch", + // Resolved by CMake Tools: + "program": "${command:cmake.launchTargetPath}", + ///"args": [ "[lv2host_leak]" ], + + "args": [ + "/etc/pipedal/config", + "/etc/pipedal/react", + "-port", + "0.0.0.0:8080" + ], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [ + { + // add the directory where our target was built to the PATHs + // it gets resolved by CMake Tools: + "name": "PATH", + "value": "$PATH:${command:cmake.launchTargetDirectory}" + }, + { + "name": "OTHER_VALUE", + "value": "Something something" + } + ], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..fcea0b8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,97 @@ +{ + "files.associations": { + "functional": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "any": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "bitset": "cpp", + "chrono": "cpp", + "codecvt": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "coroutine": "cpp", + "cstdint": "cpp", + "deque": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "regex": "cpp", + "source_location": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "ostream": "cpp", + "ranges": "cpp", + "shared_mutex": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cfenv": "cpp", + "cinttypes": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "variant": "cpp", + "*.ipp": "cpp", + "numbers": "cpp", + "semaphore": "cpp", + "gsl": "cpp", + "byte": "cpp", + "assert": "cpp", + "gsl_algorithm": "cpp", + "gsl_byte": "cpp", + "gsl_narrow": "cpp", + "gsl_assert": "cpp", + "narrow": "cpp", + "pointers": "cpp", + "span": "cpp", + "span_ext": "cpp", + "string_span": "cpp", + "util": "cpp", + + + + } +} \ No newline at end of file diff --git a/react/package.json b/react/package.json index f41ec12..04e7f3a 100644 --- a/react/package.json +++ b/react/package.json @@ -15,6 +15,8 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "4.0.3", + "react-virtualized-auto-sizer": "^1.0.6", + "react-window": "^1.8.6", "typescript": "^4.3.4", "web-vitals": "^1.1.2" }, @@ -41,5 +43,9 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@types/react-virtualized-auto-sizer": "^1.0.1", + "@types/react-window": "^1.8.5" } } diff --git a/react/src/LoadPluginDialog.tsx b/react/src/LoadPluginDialog.tsx index 9b0737f..3655b86 100644 --- a/react/src/LoadPluginDialog.tsx +++ b/react/src/LoadPluginDialog.tsx @@ -17,7 +17,7 @@ // 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. -import React, { ReactNode, SyntheticEvent } from 'react'; +import React, { ReactNode, SyntheticEvent, CSSProperties } from 'react'; import { createStyles, withStyles, WithStyles, Theme } from '@material-ui/core/styles'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; import { UiPlugin, PluginType } from './Lv2Plugin'; @@ -42,6 +42,9 @@ import { TransitionProps } from '@material-ui/core/transitions/transition'; import Slide from '@material-ui/core/Slide'; import SearchControl from './SearchControl'; import SearchFilter from './SearchFilter'; +import { FixedSizeGrid } from 'react-window'; +import AutoSizer from 'react-virtualized-auto-sizer'; + export type CloseEventHandler = () => void; @@ -154,13 +157,13 @@ interface PluginGridProps extends WithStyles { type PluginGridState = { selected_uri?: string, - hover_uri?: string, search_string: string; search_collapsed: boolean; filterType: PluginType, client_width: number, client_height: number, grid_cell_width: number, + grid_cell_columns: number, minimumItemWidth: number, } @@ -192,13 +195,13 @@ export const LoadPluginDialog = this.state = { selected_uri: this.props.uri, - hover_uri: "", search_string: "", search_collapsed: true, filterType: filterType_, client_width: window.innerWidth, client_height: window.innerHeight, grid_cell_width: this.getCellWidth(window.innerWidth), + grid_cell_columns: this.getCellColumns(window.innerWidth), minimumItemWidth: props.minimumItemWidth ? props.minimumItemWidth : 220 }; @@ -212,24 +215,25 @@ export const LoadPluginDialog = nominal_column_width: number = 250; margin_reserve: number = 30; - getCellWidth(width: number) - { - let gridWidth = width-this.margin_reserve; - let columns = Math.floor((gridWidth)/this.nominal_column_width); + getCellColumns(width: number) { + let gridWidth = width - this.margin_reserve; + let columns = Math.floor((gridWidth) / this.nominal_column_width); if (columns < 1) columns = 1; - - return Math.floor(gridWidth/columns); + return columns; } - handleKeyPress(e: React.KeyboardEvent) - { + + getCellWidth(width: number) { + return Math.floor(width / this.getCellColumns(width)); + } + + handleKeyPress(e: React.KeyboardEvent) { let searchInput = this.searchInputRef.current; if (searchInput && e.target !== searchInput) // if the search input doesn't have focus. { if (this.searchInputRef.current) // we do have one, right? { - if (e.key.length === 1) - { + if (e.key.length === 1) { if (/[a-zA-Z0-9]/.exec(e.key)) // if it's alpha-numeric. { if (this.state.search_collapsed) // and search is collapsed. @@ -241,9 +245,10 @@ export const LoadPluginDialog = searchInput.focus(); this.setState({ search_string: newValue, - search_collapsed: false}) + search_collapsed: false + }) - } + } } } } @@ -254,7 +259,8 @@ export const LoadPluginDialog = this.setState({ client_width: window.innerWidth, client_height: window.innerHeight, - grid_cell_width: this.getCellWidth(window.innerWidth) + grid_cell_width: this.getCellWidth(window.innerWidth), + grid_cell_columns: this.getCellColumns(window.innerWidth) }); } componentDidMount() { @@ -268,14 +274,12 @@ export const LoadPluginDialog = } componentDidUpdate(oldProps: PluginGridProps) { - if (oldProps.open !== this.props.open) - { - if (this.props.open) - { - this.setState({ search_string: "", search_collapsed: true}); + if (oldProps.open !== this.props.open) { + if (this.props.open) { + this.setState({ search_string: "", search_collapsed: true }); } } - } + } onWindowSizeChanged(width: number, height: number): void { super.onWindowSizeChanged(width, height); @@ -333,19 +337,16 @@ export const LoadPluginDialog = this.setState({ selected_uri: item.uri }); } } - setHoverUri(uri: string) { - this.setState({ hover_uri: uri }); - } onClick(e: SyntheticEvent, uri: string): void { this.setState({ selected_uri: uri }); } handleMouseEnter(e: SyntheticEvent, uri: string): void { - this.setHoverUri(uri); + // this.setHoverUri(uri); } handleMouseLeave(e: SyntheticEvent, uri: string): void { - this.setHoverUri(""); + // this.setHoverUri(""); } onInfoClicked(): void { @@ -387,92 +388,51 @@ export const LoadPluginDialog = return result; } - applySearchFilter(plugins: UiPlugin[],searchString: string): UiPlugin[] - { - let results: { score: number; plugin: UiPlugin}[] = []; + getFilteredPlugins(): UiPlugin[] { + let plugins = this.model.ui_plugins.get(); + let searchString = this.state.search_string; + + let results: { score: number; plugin: UiPlugin }[] = []; let searchFilter = new SearchFilter(searchString); - - for (let i = 0; i < plugins.length; ++i) - { + let filterType = this.state.filterType; + let rootClass = this.model.plugin_classes.get(); + + + for (let i = 0; i < plugins.length; ++i) { let plugin = plugins[i]; - let score = searchFilter.score(plugin.name,plugin.plugin_display_type,plugin.author_name); - if (score !== 0) - { - results.push({score:score, plugin:plugin}); + 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); + + if (score !== 0) { + results.push({ score: score, plugin: plugin }); + } } } - results.sort((left: {score:number; plugin: UiPlugin},right: {score:number; plugin: UiPlugin}) => { + results.sort((left: { score: number; plugin: UiPlugin }, right: { score: number; plugin: UiPlugin }) => { if (right.score < left.score) return -1; if (right.score > left.score) return 1; return left.plugin.name.localeCompare(right.plugin.name); }); let t: UiPlugin[] = []; - for (let i = 0; i < results.length; ++i) - { + for (let i = 0; i < results.length; ++i) { t.push(results[i].plugin); } return t; } - filterPlugins(plugins: UiPlugin[]): ReactNode[] { - let result: ReactNode[] = []; - let filterType = this.state.filterType; - let classes = this.props.classes; - let rootClass = this.model.plugin_classes.get(); - if (this.state.search_string.length !== 0) - { - plugins = this.applySearchFilter(plugins,this.state.search_string); - } - for (let i = 0; i < plugins.length; ++i) { - let value = plugins[i]; - if (filterType === PluginType.Plugin || rootClass.is_type_of(filterType, value.plugin_type)) { - result.push( - ( -
{ this.onDoubleClick(e, value.uri) }} - onClick={(e) => { this.onClick(e, value.uri) }} - onMouseEnter={(e) => { this.handleMouseEnter(e, value.uri) }} - onMouseLeave={(e) => { this.handleMouseLeave(e, value.uri) }} - > - - -
-
- -
-
- - {value.name} - - - {value.plugin_display_type} {this.stereo_indicator(value)} - - -
-
-
-
- ) - ); - - } - } - return result; - } - changedSearchString? : string = undefined; + changedSearchString?: string = undefined; hSearchTimeout?: NodeJS.Timeout; handleSearchStringReady() { - if (this.changedSearchString !== undefined) - { - this.setState({search_string: this.changedSearchString}); + if (this.changedSearchString !== undefined) { + this.setState({ search_string: this.changedSearchString }); this.changedSearchString = undefined; } this.hSearchTimeout = undefined; } - handleSearchStringChanged(text: string) : void { + handleSearchStringChanged(text: string): void { if (this.hSearchTimeout) { clearTimeout(this.hSearchTimeout); this.hSearchTimeout = undefined; @@ -483,15 +443,49 @@ export const LoadPluginDialog = 2000); } + renderItem(row: number, column: number): React.ReactNode { + let item: number = (row) * this.gridColumnCount + (column); + if (item >= this.gridItems.length) { + return (
); + } + let value = this.gridItems[item]; + + let classes = this.props.classes; + return ( +
{ this.onDoubleClick(e, value.uri) }} + onClick={(e) => { this.onClick(e, value.uri) }} + onMouseEnter={(e) => { this.handleMouseEnter(e, value.uri) }} + onMouseLeave={(e) => { this.handleMouseLeave(e, value.uri) }} + > + + +
+
+ +
+
+ + {value.name} + + + {value.plugin_display_type} {this.stereo_indicator(value)} + + +
+
+
+
+ ); + + } + gridItems: UiPlugin[] = []; + gridColumnCount: number = 1; render() { const { classes } = this.props; - - let model = this.model; - let plugins = model.ui_plugins.get(); - let selectedPlugin: UiPlugin | undefined = undefined; if (this.state.selected_uri) { let t = this.model.getUiPlugin(this.state.selected_uri); @@ -499,16 +493,17 @@ export const LoadPluginDialog = } let showSearchIcon = true; - if (this.state.client_width < 500) - { + if (this.state.client_width < 500) { showSearchIcon = this.state.search_collapsed } - + this.gridItems = this.getFilteredPlugins(); + let gridColumnCount = this.state.grid_cell_columns; + this.gridColumnCount = gridColumnCount; return ( { this.handleKeyPress(e); }} + onKeyPress={(e) => { this.handleKeyPress(e); }} fullScreen={true} TransitionComponent={Transition} maxWidth={false} @@ -517,96 +512,127 @@ export const LoadPluginDialog = onClose={this.handleCancel} style={{ overflowX: "hidden", overflowY: "hidden", display: "flex", flexDirection: "column", flexWrap: "nowrap" }} aria-labelledby="select-plugin-dialog-title"> - -
- { this.cancel(); }} style={{ flex: "0 0 auto" }} > - - - - - {this.state.client_width > 520 ? "Select Plugin" : ""} - -
- { - this.handleSearchStringChanged(text); - }} - onClearFilterClick={()=> { - this.setState({search_collapsed: true,search_string:""}); - }} - onClick={()=> { - if (this.state.search_collapsed) { - this.setState({search_collapsed: false}); - } else { - this.setState({ search_collapsed: true, search_string: ""}); - } - - }} - /> -
- -
- { this.onClearFilter(); }}> - +
+ +
+ { this.cancel(); }} style={{ flex: "0 0 auto" }} > + + + + {this.state.client_width > 520 ? "Select Plugin" : ""} + +
+ { + this.handleSearchStringChanged(text); + }} + onClearFilterClick={() => { + this.setState({ search_collapsed: true, search_string: "" }); + }} + onClick={() => { + if (this.state.search_collapsed) { + this.setState({ search_collapsed: false }); + } else { + this.setState({ search_collapsed: true, search_string: "" }); + } + + }} + /> +
+ +
+ { this.onClearFilter(); }}> + + +
-
- - -
- { - this.filterPlugins(plugins) - } -
-
- {(this.state.client_width >= NARROW_DISPLAY_THRESHOLD) ? ( - -
-
+ + + + {(arg: { height: number, width: number }) => { + return ( + { + let index = args.columnIndex + this.gridColumnCount * args.rowIndex; + if (index >= this.gridItems.length) { + return "blank-" + args.columnIndex + "-" + args.rowIndex; + } + let plugin = this.gridItems[index]; + return plugin.uri + "-" + args.rowIndex + "-" + args.columnIndex; + + }} + + > + {(arg: { columnIndex: number, rowIndex: number, style: CSSProperties }) => ( +
+ { + this.renderItem(arg.rowIndex, arg.columnIndex) + } +
+ )} +
+ ); + } + } +
+
+ {(this.state.client_width >= NARROW_DISPLAY_THRESHOLD) ? ( + +
+
+ + + {this.info_string(selectedPlugin)} + +
+
+ + +
+
+
+ ) : ( + +
+
{this.info_string(selectedPlugin)}
-
- - +
+
+
+ + +
-
- - ) : ( - -
- -
- - {this.info_string(selectedPlugin)} - -
-
-
-
- - -
-
- + - )} + )} +
); diff --git a/react/src/Lv2Plugin.tsx b/react/src/Lv2Plugin.tsx index 17d1e4b..69b636c 100644 --- a/react/src/Lv2Plugin.tsx +++ b/react/src/Lv2Plugin.tsx @@ -224,7 +224,7 @@ export class UiControl implements Deserializable { } else { if (this.min_value === 0 && this.max_value === 1) { - if (this.toggled_property || this.enumeration_property || this.integer_property) + if (this.toggled_property || this.integer_property) { this.controlType = ControlType.OnOffSwitch; } @@ -267,6 +267,26 @@ export class UiControl implements Deserializable { port_group: string = ""; units: Units = Units.none; + // Return the value of the closest scale_point. + clampSelectValue(value: number): number{ + if (this.scale_points.length !== 0) + { + let minError = 1.0E100; + let bestValue = value; + for (let i = 0; i < this.scale_points.length; ++i) + { + let error = Math.abs(this.scale_points[i].value-value); + if (error < minError) + { + minError = error; + bestValue = this.scale_points[i].value; + } + } + return bestValue; + } else { + return value; + } + } isOnOffSwitch() : boolean { return this.controlType === ControlType.OnOffSwitch; } diff --git a/react/src/PluginControl.tsx b/react/src/PluginControl.tsx index 740d704..e5d9c5b 100644 --- a/react/src/PluginControl.tsx +++ b/react/src/PluginControl.tsx @@ -546,7 +546,7 @@ const PluginControl = return (