Merge pull request #211 from rerdavies/dev

Dev v1.3.54
This commit is contained in:
Robin Davies
2024-10-09 18:51:04 +01:00
committed by GitHub
64 changed files with 1170 additions and 294 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
#build files
build/
core
#VS Code config files.
# .vscode/ Sort this out later. .gitignore-ing an already tracked file doesn't actually work.
#settings
+2 -2
View File
@@ -1,10 +1,10 @@
cmake_minimum_required(VERSION 3.16.0)
project(pipedal
VERSION 1.3.53
VERSION 1.3.54
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
)
set (DISPLAY_VERSION "PiPedal v1.3.53-Release")
set (DISPLAY_VERSION "PiPedal v1.3.54-Release")
set (PACKAGE_ARCHITECTURE "arm64")
set (CMAKE_INSTALL_PREFIX "/usr/")
+13 -1
View File
@@ -168,7 +168,7 @@ void WifiConfigSettings::Save()
{
ofstream_synced f;
openWithPerms(f,CONFIG_PATH);
json_writer writer(f);
json_writer writer(f,false);
writer.write(&newSettings);
}
catch (const std::exception &e)
@@ -300,6 +300,18 @@ namespace pipedal::priv
connection["ipv4"]["method"] = sdbus::Variant(std::string("shared"));
connection["ipv6"]["method"] = sdbus::Variant(std::string("ignore"));
connection["ipv4"]["address-data"] = sdbus::Variant(std::vector<std::map<std::string, sdbus::Variant>>{{
{"address", sdbus::Variant("192.168.4.1")},
{"prefix", sdbus::Variant(uint32_t(24))}
}});
connection["ipv4"]["dhcp-send-hostname"] = sdbus::Variant(true);
connection["ipv4"]["dhcp-hostname"] = sdbus::Variant("raspberrypi");
connection["ipv4"]["dhcp-options"] = sdbus::Variant(std::vector<std::string>{
"option:classless-static-route,192.168.4.0/24,0.0.0.0,0.0.0.0/0,192.168.4.1"
//"option:classless-static-route,192.168.4.0/24,192.168.4.1"
});
auto connection_path = AddConnection(connection);
return connection_path;
}
+2 -2
View File
@@ -6,13 +6,13 @@
<a href="https://rerdavies.github.io/pipedal/LicensePiPedal.html"><img src="https://img.shields.io/badge/MIT-MIT?label=license&color=%23808080"/></a>
<a href="https://github.com/rerdavies/pipedal/actions"><img src="https://img.shields.io/github/actions/workflow/status/rerdavies/pipedal/cmake.yml?branch=main"/></a>
Download:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v1.3.53</a>
Download:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v1.3.54</a>
Website:&nbsp;[https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
Documentation:&nbsp;[https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html).
&nbsp;
#### NEW version 1.3.53 Release, providing [snapshots](https://rerdavies.github.io/pipedal/Snaphots.html), and a new Performance View. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
#### NEW version 1.3.54 Release, providing [snapshots](https://rerdavies.github.io/pipedal/Snaphots.html), and a new Performance View. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
&nbsp;
+3 -3
View File
@@ -13,17 +13,17 @@ page_icon: img/Install4.jpg
Download the most recent Debian (.deb) package for your platform:
- [Raspberry Pi OS bookworm (64-bit) v1.3.53](https://github.com/rerdavies/pipedal/releases/download/)
- [Raspberry Pi OS bookworm (64-bit) v1.3.54](https://github.com/rerdavies/pipedal/releases/download/)
- [Ubuntu/Raspberry Pi OS bullseyeye (64-bit) v1.2.31](https://github.com/rerdavies/pipedal/releases/download/v1.1.31/pipedal_1.1.31_arm64.deb)
Version 1.3.53 has not yet been tested on Ubuntu or Raspberry Pi OS bullseye. On these platforms, we recommend that you use version 1.1.31.
Version 1.3.54 has not yet been tested on Ubuntu or Raspberry Pi OS bullseye. On these platforms, we recommend that you use version 1.1.31.
Install the package by running
```
sudo apt update
cd ~/Downloads
sudo apt-get install pipedal_1.3.53_arm64.deb
sudo apt-get install pipedal_1.3.54_arm64.deb
```
Adjust accordingly if you have downloaded v1.1.31.
+34
View File
@@ -1,4 +1,38 @@
# Release Notes
## PiPedal 1.3.54 Release
Improvements to and fixes for hotspot functionality. Fixes for problems with snapshots.
Minor features;
- Enter key supported in most dialogs.
Bug fixes.
- Various crashes related to snapshots.
- Applying changes to wi-fi hostpot configuration would not change the configuration until PiPedal was rebooted (a regression).
The configuration of the Wi-Fi hotspot now changes as soon as you apply it.
- The state of bypass switches was not getting captured in snapshots.
- Crashes when scrolling through presets rapidly in the Snapshot view.
- Web server redirects link-local IPv6 connections to non-link local address. (Improves client connectivity).
- Force DHCP routing on hotspot connections. (Improves android client cnnectivity)
- Missing MIDI device causes audio to stop.
- The file model for snapshots was confusing and didn't entirely work as expected. Changes to snapshots now
only get saved when the preset to which they belong gets saved (explicit-save file model, instead of
Android file model).
- Layout improvement to a number of dialogs when displayed on wide screens.
Bug fixes:
## PiPedal 1.3.53 Release
Major features:
+2 -2
View File
@@ -4,7 +4,7 @@
Download the most recent Debian (.deb) package for your platform:
- <a href="https://github.com/rerdavies/pipedal/releases/download/v1.3.53/pipedal_1.3.53_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.3.53</a>
- <a href="https://github.com/rerdavies/pipedal/releases/download/v1.3.54/pipedal_1.3.54_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.3.54</a>
Install the package by running
@@ -12,7 +12,7 @@ Install the package by running
```
sudo apt update
cd ~/Downloads
sudo apt-get install ./pipedal_1.3.53_arm64.deb
sudo apt-get install ./pipedal_1.3.54_arm64.deb
```
Follow the instructions in [_Configuring PiPedal After Installation_](https://rerdavies.github.io/pipedal/Configuring.html) to complete the installation.
+2 -2
View File
@@ -1,7 +1,7 @@
<img src="GithubBanner.png" width="100%"/>
<a href="Installing.html"><i>v1.3.53</i></a>
<a href="Installing.html"><i>v1.3.54</i></a>
&nbsp;
@@ -9,7 +9,7 @@ To download PiPedal, click [here](download.md).
To view PiPedal documentation, click [here](Documentation.md).
#### NEW version 1.3.53 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
#### NEW version 1.3.54 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
&nbsp;
+9 -2
View File
@@ -33,6 +33,8 @@ add_custom_command(
src/AlsaMidiDeviceInfo.tsx
src/WifiDirectConfigSettings.tsx
src/WifiConfigSettings.tsx
src/SnapshotButton.tsx
src/WindowScale.tsx
src/JsonAtom.tsx
src/PiPedalSocket.tsx
src/TemporaryDrawer.tsx
@@ -43,6 +45,7 @@ add_custom_command(
src/SettingsDialog.tsx
src/PedalboardView.tsx
src/reportWebVitals.ts
src/OptionsDialog.tsx
src/SelectChannelsDialog.tsx
src/PluginPresetSelector.tsx
src/VuMeter.tsx
@@ -122,6 +125,7 @@ add_custom_command(
src/svg/fx_utility.svg
src/Updater.tsx
src/UploadPresetDialog.tsx
src/RadioSelectDialog.tsx
src/setupTests.ts
src/ToobCabSimView.tsx
src/RenameDialog.tsx
@@ -140,6 +144,7 @@ add_custom_command(
src/Draggable.tsx
src/PluginIcon.tsx
src/react-app-env.d.ts
src/TextFieldEx.tsx
src/FilePropertyDialog.tsx
src/FilePropertyDirectoryTree.tsx
src/StringBuilder.tsx
@@ -150,7 +155,6 @@ add_custom_command(
src/ToobSpectrumResponseView.tsx
src/PerformanceView.tsx
src/JackServerSettingsDialog.tsx
src/ListSelectDialog.tsx
src/Utility.tsx
src/JackStatusView.tsx
src/ToobSpectrumAnalyzerView.tsx
@@ -160,6 +164,7 @@ add_custom_command(
src/WifiChannel.tsx
src/AboutDialog.tsx
src/GxTunerControl.tsx
src/VirtualKeyboardHandler.tsx
src/PluginControl.tsx
src/LoadPluginDialog.tsx
src/ZoomedUiControl.tsx
@@ -175,8 +180,10 @@ add_custom_command(
src/SnapshotPanel.tsx
src/ToobMLView.tsx
src/WifiConfigDialog.tsx
src/Rectangle.tsx
src/SnapshotColors.tsx
src/PiPedalModel.tsx
src/ObservableEvent.tsx
src/JackHostStatus.tsx
src/index.tsx
src/FilePropertyControl.tsx
@@ -192,7 +199,7 @@ add_custom_command(
src/SnapshotPropertiesDialog.tsx
src/SearchControl.tsx
src/Units.tsx
)
add_custom_target (
+1
View File
@@ -199,6 +199,7 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
return (
<DialogEx tag="about" fullScreen open={this.props.open}
onClose={() => { this.props.onClose() }} TransitionComponent={Transition}
onEnterKey={() => { this.props.onClose() }}
style={{ userSelect: "none" }}
>
+1
View File
@@ -1020,6 +1020,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
<DialogEx tag="Alert"
open={this.state.alertDialogOpen}
onClose={this.handleCloseAlert}
onEnterKey={this.handleCloseAlert}
aria-describedby="alert-dialog-description"
>
<DialogContent>
+4 -1
View File
@@ -409,6 +409,7 @@ const BankDialog = withStyles(styles, { withTheme: true })(
return (
<DialogEx tag="bank" fullScreen open={this.props.show}
onClose={() => { this.handleDialogClose() }} TransitionComponent={Transition}
onEnterKey={()=>{}}
style={{ userSelect: "none" }}
>
<div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}>
@@ -544,7 +545,9 @@ const BankDialog = withStyles(styles, { withTheme: true })(
/>
<DialogEx tag="deletePrompt" open={this.state.showDeletePrompt} onClose={() => this.handleDeletePromptClose()}
style={{ userSelect: "none" }}>
style={{ userSelect: "none" }}
onEnterKey={()=>{ this.handleDeletePromptOk() }}
>
<DialogContent>
<Typography>Are you sure you want to delete bank '{this.getSelectedBankName()}'?</Typography>
</DialogContent>
+5 -1
View File
@@ -25,6 +25,7 @@ import Dialog, {DialogProps} from '@mui/material/Dialog';
interface DialogExProps extends DialogProps {
tag: string;
fullwidth?: boolean;
onEnterKey: () => void;
}
interface DialogExState {
@@ -186,7 +187,10 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> implements I
}
onEnterKey() {
if (this.props.onEnterKey)
{
this.props.onEnterKey();
}
}
onKeyDown(evt: React.KeyboardEvent<HTMLDivElement>)
{
+5 -2
View File
@@ -484,9 +484,12 @@ export default withStyles(styles, { withTheme: true })(
fullScreen={this.state.fullScreen}
onClose={() => {
this.props.onCancel();
}}
}}
onEnterKey={()=> {
this.openSelectedFile();
}}
open={this.props.open} tag="fileProperty"
fullWidth maxWidth="xl"
fullWidth maxWidth="md"
PaperProps={
this.state.fullScreen ?
{}
@@ -319,6 +319,7 @@ export default class FilePropertyDirectorySelectDialog extends ResizeResponsiveC
<DialogEx tag="fpDirectorySelect" open={this.props.open} fullWidth onClose={() => { this.onClose(); }} aria-labelledby="Rename-dialog-title"
fullScreen={this.state.fullScreen}
style={{ userSelect: "none" }}
onEnterKey={()=>{ this.onOK(); }}
PaperProps={{ style: { minHeight: "80%", maxHeight: "80%", minWidth: "75%", maxWidth: "75%", overflowY: "visible" } }}
>
<DialogTitle>
+14 -8
View File
@@ -166,15 +166,19 @@ const FullScreenIME =
}
}
onOk() {
if (this.inputChanged) {
this.inputChanged = false;
this.validateInput(true);
}
else {
this.props.onClose();
}
}
onInputKeyPress(e: any): void {
if (e.charCode === 13) {
if (this.inputChanged) {
this.inputChanged = false;
this.validateInput(true);
}
else {
this.props.onClose();
}
this.onOk();
}
}
@@ -196,7 +200,9 @@ const FullScreenIME =
let value = this.props.value;
return (
<DialogEx tag="ime" fullScreen open={!!(this.props.uiControl)} onClose={(e, r) => this.handleClose(e, r)} >
<DialogEx tag="ime" fullScreen open={!!(this.props.uiControl)} onClose={(e, r) => this.handleClose(e, r)}
onEnterKey={()=>{ /*nothing*/}}
>
<div style={{
width: "100%", height: "100%", position: "relative",
display: "flex", flexDirection: "column", flexWrap: "nowrap",
+10 -3
View File
@@ -393,8 +393,10 @@ const JackServerSettingsDialog = withStyles(styles)(
}
handleApply() {
this.props.onApply(this.state.jackServerSettings.clone());
if (this.state.okEnabled)
{
this.props.onApply(this.state.jackServerSettings.clone());
}
};
render() {
@@ -422,7 +424,12 @@ const JackServerSettingsDialog = withStyles(styles)(
let bufferCountDisabled = !selectedDevice;
return (
<DialogEx tag="jack" onClose={handleClose} aria-labelledby="select-channels-title" open={open}>
<DialogEx tag="jack" onClose={handleClose} aria-labelledby="select-channels-title" open={open}
onEnterKey={() => {
this.handleApply();
}}
>
<DialogContent>
<div>
<FormControl className={classes.formControl}>
+18 -2
View File
@@ -375,8 +375,22 @@ export const LoadPluginDialog =
e.preventDefault();
this.cancel();
}
handleOk(e: SyntheticEvent): void {
e.preventDefault();
handleOk(e?: SyntheticEvent): void {
if (e) {
e.preventDefault();
}
let selectedPlugin: UiPlugin | undefined = undefined;
if (this.state.selected_uri) {
let t = this.model.getUiPlugin(this.state.selected_uri);
if (t) selectedPlugin = t;
}
if (!selectedPlugin)
{
return;
}
if (this.state.selected_uri) {
this.props.onOk(this.state.selected_uri);
}
@@ -688,7 +702,9 @@ export const LoadPluginDialog =
return (
<React.Fragment>
<DialogEx tag="plugins"
onEnterKey={()=>{ this.handleOk(); }}
onKeyPress={(e) => { this.handleKeyPress(e); }}
fullScreen={true}
TransitionComponent={undefined}
maxWidth={false}
+2
View File
@@ -326,6 +326,8 @@ export const MidiBindingDialog =
<DialogEx tag="midiBindings" open={open} fullWidth onClose={this.handleClose} aria-labelledby="Rename-dialog-title"
fullScreen={true}
style={{userSelect: "none"}}
onEnterKey={()=>{}}
>
<div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}>
<div style={{ flex: "0 0 auto" }}>
+54
View File
@@ -0,0 +1,54 @@
// Copyright (c) 2022 Robin 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.
export type ObservableEventHandler<VALUE_TYPE> = (newValue: VALUE_TYPE) => void;
export default class ObservableEvent<VALUE_TYPE> {
private _on_event_handlers: ObservableEventHandler<VALUE_TYPE>[] = [];
addEventHandler(handler: ObservableEventHandler<VALUE_TYPE> ) : void
{
this._on_event_handlers.push(handler);
}
removeEventHandler(handler: ObservableEventHandler<VALUE_TYPE> ) : void
{
let newArray: ObservableEventHandler<VALUE_TYPE>[] = [];
for (let myHandler of this._on_event_handlers)
{
if (myHandler !== handler)
{
newArray.push(myHandler);
}
}
this._on_event_handlers = newArray;
}
fire(value: VALUE_TYPE)
{
for (let handler of this._on_event_handlers)
{
handler(value);
}
}
};
+1 -1
View File
@@ -12,7 +12,7 @@
//
// 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
// 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.
+1
View File
@@ -63,6 +63,7 @@ export default class OkCancelDialog extends React.Component<OkCancelDialogProps,
}
return (
<DialogEx tag="okCancel" open={open} onClose={handleClose}
onEnterKey={handleOk}
style={{userSelect: "none"}}
>
<DialogContent>
+1 -1
View File
@@ -70,7 +70,7 @@ function OptionsDialog(props: OptionsDialogProps) {
onClose();
};
return (
<DialogEx tag="options" onClose={handleCancel} aria-labelledby="select-option" open={open} >
<DialogEx tag="options" onClose={handleCancel} aria-labelledby="select-option" open={open} onEnterKey={()=>{}} >
{title && (
<DialogTitle style={{paddingLeft: 8}}>
<Typography variant="body2" color="textSecondary">
+4
View File
@@ -226,12 +226,14 @@ export class SnapshotValue {
{
let result = new SnapshotValue();
result.instanceId = item.instanceId;
result.isEnabled = item.isEnabled;
result.controlValues = ControlValue.deserializeArray(item.controlValues);
result.lv2State = item.lv2State; // we can do this, because lv2State is immutable.
result.pathProperties = {...item.pathProperties}; // clone the dictionary.
return result;
}
instanceId: number = -1;
isEnabled: boolean = true;
controlValues: ControlValue[] = ControlValue.EmptyArray;
lv2State: [boolean,any] = [false,{}];
pathProperties: {[Name: string]: string} = {};
@@ -239,6 +241,7 @@ export class SnapshotValue {
export class Snapshot {
deserialize(input: any): Snapshot {
this.values = SnapshotValue.deserializeArray(input.values);
this.isModified = input.isModified;
this.name = input.name;
this.color = input.color;
return this;
@@ -273,6 +276,7 @@ export class Snapshot {
return result;
}
name: string = "";
isModified: boolean = false;
color: string = "";
values: SnapshotValue[] = [];
};
+44 -5
View File
@@ -19,6 +19,7 @@
import { Theme } from '@mui/material/styles';
import SaveIconOutline from '@mui/icons-material/Save';
import { WithStyles } from '@mui/styles';
import createStyles from '@mui/styles/createStyles';
import withStyles from '@mui/styles/withStyles';
@@ -78,6 +79,7 @@ interface PerformanceViewState {
banks: BankIndex;
showSnapshotEditor: boolean;
snapshotEditorIndex: number;
presetModified: boolean;
}
@@ -97,11 +99,13 @@ export const PerformanceView =
banks: this.model.banks.get(),
wrapSelects: false,
showSnapshotEditor: false,
snapshotEditorIndex: 0
snapshotEditorIndex: 0,
presetModified: this.model.presetChanged.get()
};
this.onPresetsChanged = this.onPresetsChanged.bind(this);
this.onBanksChanged = this.onBanksChanged.bind(this);
this.handlePopState = this.handlePopState.bind(this);
this.onPresetChangedChanged = this.onPresetChangedChanged.bind(this);
}
@@ -181,6 +185,9 @@ export const PerformanceView =
onBanksChanged(newValue: BankIndex) {
this.setState({ banks: this.model.banks.get() })
}
onPresetChangedChanged(newValue: boolean) {
this.setState({ presetModified:newValue});
}
private mounted: boolean = false;
componentDidMount(): void {
@@ -188,9 +195,12 @@ export const PerformanceView =
this.mounted = true;
this.model.presets.addOnChangedHandler(this.onPresetsChanged);
this.model.banks.addOnChangedHandler(this.onBanksChanged);
this.model.presetChanged.addOnChangedHandler(this.onPresetChangedChanged)
this.setState({
presets: this.model.presets.get(),
banks: this.model.banks.get()
banks: this.model.banks.get(),
presetModified: this.model.presetChanged.get()
});
this.updateHooks();
@@ -200,7 +210,7 @@ export const PerformanceView =
componentWillUnmount(): void {
this.model.presetChanged.removeOnChangedHandler(this.onPresetChangedChanged)
this.model.presets.removeOnChangedHandler(this.onPresetsChanged);
this.model.banks.removeOnChangedHandler(this.onBanksChanged);
@@ -332,13 +342,27 @@ export const PerformanceView =
<IconButton
aria-label="next-bank"
onClick={() => { this.handleNextBank(); }}
size="large"
color="inherit"
style={{ borderTopLeftRadius: "3px", borderBottomLeftRadius: "3px", marginLeft: 4 }}
>
<ArrowForwardIosIcon style={{ opacity: 0.75 }} />
</IconButton>
{/** spacer */}
<IconButton
aria-label="next-bank"
onClick={() => { this.handleNextBank(); }}
size="medium"
color="inherit"
style={{visibility: "hidden"}}
>
<ArrowForwardIosIcon style={{ opacity: 0.75 }} />
</IconButton>
</div>
{/********* PRESETS *******************/}
<div style={{ flex: "1 1 1px", display: "flex", flexFlow: "row nowrap", maxWidth: 500 }}>
@@ -367,9 +391,14 @@ export const PerformanceView =
>
{
presets.presets.map((preset) => {
let name = preset.name;
if (this.state.presets.selectedInstanceId === preset.instanceId && this.state.presetModified)
{
name += "*";
}
return (
<MenuItem key={preset.instanceId} value={preset.instanceId} >
{preset.name}
{name}
</MenuItem>
);
})
@@ -385,6 +414,16 @@ export const PerformanceView =
<ArrowForwardIosIcon style={{ opacity: 0.75 }} />
</IconButton>
<IconButton
aria-label="save-preset"
onClick={() => { this.model.saveCurrentPreset(); }}
size="medium"
color="inherit"
style={{flexShrink: 0,visibility: this.state.presetModified? "visible": "hidden"}}
>
<SaveIconOutline style={{ opacity: 0.75 }} color="inherit" />
</IconButton>
</div>
</div>
</div>
+26 -16
View File
@@ -21,6 +21,7 @@ import { UiPlugin, UiControl, PluginType, UiFileProperty } from './Lv2Plugin';
import { PiPedalArgumentError, PiPedalStateError } from './PiPedalError';
import { UpdateStatus, UpdatePolicyT } from './Updater';
import ObservableEvent from './ObservableEvent';
import { ObservableProperty } from './ObservableProperty';
import { Pedalboard, PedalboardItem, ControlValue, Snapshot } from './Pedalboard'
import PluginClass from './PluginClass';
@@ -362,6 +363,11 @@ export interface FavoritesList {
[url: string]: boolean;
};
export interface SnapshotModifiedEvent {
snapshotIndex: number;
modified: boolean;
};
export class PiPedalModel //implements PiPedalModel
{
@@ -376,6 +382,9 @@ export class PiPedalModel //implements PiPedalModel
webSocket?: PiPedalSocket;
onSnapshotModified: ObservableEvent<SnapshotModifiedEvent> = new ObservableEvent<SnapshotModifiedEvent>();
ui_plugins: ObservableProperty<UiPlugin[]>
= new ObservableProperty<UiPlugin[]>([]);
state: ObservableProperty<State> = new ObservableProperty<State>(State.Loading);
@@ -503,6 +512,7 @@ export class PiPedalModel //implements PiPedalModel
private setModelPedalboard(pedalboard: Pedalboard) {
this.pedalboard.set(pedalboard);
this.selectedSnapshot.set(pedalboard.selectedSnapshot);
}
onSocketMessage(header: PiPedalMessageHeader, body?: any) {
if (this.visibilityState.get() === VisibilityState.Hidden) return;
@@ -564,6 +574,21 @@ export class PiPedalModel //implements PiPedalModel
let channelSelectionBody = body as ChannelSelectionChangedBody;
let channelSelection = new JackChannelSelection().deserialize(channelSelectionBody.jackChannelSelection);
this.jackSettings.set(channelSelection);
} else if (message === "onSnapshotModified") {
let {snapshotIndex,modified} = (body as {snapshotIndex: number, modified: boolean});
let snapshots = this.pedalboard.get().snapshots;
if (snapshotIndex >= 0 && snapshotIndex < snapshots.length)
{
let snapshot = snapshots[snapshotIndex]
if (snapshot)
{
if (snapshot.isModified !== modified)
{
snapshot.isModified = modified;
this.onSnapshotModified.fire({snapshotIndex: snapshotIndex,modified: modified});
}
}
}
} else if (message === "onSelectedSnapshotChanged") {
let selectedSnapshot = body as number;
this.pedalboard.get().selectedSnapshot = selectedSnapshot;
@@ -581,6 +606,7 @@ export class PiPedalModel //implements PiPedalModel
let presetsChangedBody = body as PresetsChangedBody;
let presets = new PresetIndex().deserialize(presetsChangedBody.presets);
this.presets.set(presets);
this.presetChanged.set(presets.presetChanged);
} else if (message === "onPluginPresetsChanged") {
let pluginUri = body as string;
this.handlePluginPresetsChanged(pluginUri);
@@ -1286,7 +1312,6 @@ export class PiPedalModel //implements PiPedalModel
changed = item.setControlValue(controlValue.key, controlValue.value) || changed;
}
if (changed) {
newPedalboard.selectedSnapshot = -1;
this.setModelPedalboard(newPedalboard);
}
}
@@ -1428,7 +1453,6 @@ export class PiPedalModel //implements PiPedalModel
if (pedalboard.input_volume_db !== volume_db) {
let newPedalboard = pedalboard.clone();
newPedalboard.input_volume_db = volume_db;
newPedalboard.selectedSnapshot = -1;
this.setModelPedalboard(newPedalboard);
changed = true;
}
@@ -1457,7 +1481,6 @@ export class PiPedalModel //implements PiPedalModel
if (pedalboard.output_volume_db !== volume_db) {
let newPedalboard = pedalboard.clone();
newPedalboard.output_volume_db = volume_db;
newPedalboard.selectedSnapshot = -1;
this.setModelPedalboard(newPedalboard);
changed = true;
}
@@ -1494,7 +1517,6 @@ export class PiPedalModel //implements PiPedalModel
if (notifyServer) {
this._setServerControl("setControl", instanceId, key, value);
}
newPedalboard.selectedSnapshot = -1;
this.setModelPedalboard(newPedalboard);
for (let i = 0; i < this._controlValueChangeItems.length; ++i) {
let item = this._controlValueChangeItems[i];
@@ -1547,7 +1569,6 @@ export class PiPedalModel //implements PiPedalModel
let changed = value !== item.isEnabled;
if (changed) {
item.isEnabled = value;
newPedalboard.selectedSnapshot = -1;
this.setModelPedalboard(newPedalboard);
if (notifyServer) {
let body: PedalboardItemEnableBody = {
@@ -1605,7 +1626,6 @@ export class PiPedalModel //implements PiPedalModel
// null -> we've never seen a value.
item.pathProperties[fileProperty.patchProperty] = "null";
}
newPedalboard.selectedSnapshot = -1;
this.setModelPedalboard(newPedalboard);
this.updateServerPedalboard()
return item.instanceId;
@@ -1646,7 +1666,6 @@ export class PiPedalModel //implements PiPedalModel
let result = newPedalboard.deleteItem(instanceId);
if (result !== null) {
newPedalboard.selectedSnapshot = -1;
this.setModelPedalboard(newPedalboard);
this.updateServerPedalboard();
}
@@ -1669,7 +1688,6 @@ export class PiPedalModel //implements PiPedalModel
newPedalboard.deleteItem(fromInstanceId);
newPedalboard.addBefore(fromItem, toInstanceId);
newPedalboard.selectedSnapshot = -1;
this.setModelPedalboard(newPedalboard);
this.updateServerPedalboard();
@@ -1690,7 +1708,6 @@ export class PiPedalModel //implements PiPedalModel
newPedalboard.addAfter(fromItem, toInstanceId);
newPedalboard.selectedSnapshot = -1;
this.setModelPedalboard(newPedalboard);
this.updateServerPedalboard();
}
@@ -1712,7 +1729,6 @@ export class PiPedalModel //implements PiPedalModel
newPedalboard.addToStart(fromItem);
newPedalboard.selectedSnapshot = -1;
this.setModelPedalboard(newPedalboard);
this.updateServerPedalboard();
}
@@ -1732,7 +1748,6 @@ export class PiPedalModel //implements PiPedalModel
newPedalboard.addToEnd(fromItem);
newPedalboard.selectedSnapshot = -1;
this.setModelPedalboard(newPedalboard);
this.updateServerPedalboard();
@@ -1761,7 +1776,6 @@ export class PiPedalModel //implements PiPedalModel
newPedalboard.replaceItem(fromInstanceId, emptyItem);
newPedalboard.replaceItem(toInstanceId, fromItem);
newPedalboard.selectedSnapshot = -1;
this.setModelPedalboard(newPedalboard);
this.updateServerPedalboard();
@@ -1785,7 +1799,6 @@ export class PiPedalModel //implements PiPedalModel
}
let newItem = newPedalboard.createEmptyItem();
newPedalboard.addItem(newItem, instanceId, append);
newPedalboard.selectedSnapshot = -1;
this.setModelPedalboard(newPedalboard);
this.updateServerPedalboard();
return newItem.instanceId;
@@ -1811,7 +1824,6 @@ export class PiPedalModel //implements PiPedalModel
}
let newItem = newPedalboard.createEmptySplit();
newPedalboard.addItem(newItem, instanceId, append);
newPedalboard.selectedSnapshot = -1;
this.setModelPedalboard(newPedalboard);
this.updateServerPedalboard();
return newItem.instanceId;
@@ -1829,7 +1841,6 @@ export class PiPedalModel //implements PiPedalModel
}
newPedalboard.setItemEmpty(item);
newPedalboard.selectedSnapshot = -1;
this.setModelPedalboard(newPedalboard);
this.updateServerPedalboard();
return item.instanceId;
@@ -2332,7 +2343,6 @@ export class PiPedalModel //implements PiPedalModel
let newPedalboard = pedalboard.clone();
this.updateVst3State(newPedalboard);
if (newPedalboard.setMidiBinding(instanceId, midiBinding)) {
newPedalboard.selectedSnapshot = -1;
this.setModelPedalboard(newPedalboard);
+3 -1
View File
@@ -235,7 +235,9 @@ const PluginInfoDialog = withStyles(styles)((props: PluginInfoProps) => {
<InfoOutlinedIcon className={classes.icon} color='inherit' />
</IconButton>
{open && (
<DialogEx tag="info" onClose={handleClose} open={open} fullWidth >
<DialogEx tag="info" onClose={handleClose} open={open} fullWidth maxWidth="md"
onEnterKey={handleClose}
>
<MuiDialogTitle >
<div style={{ display: "flex", flexDirection: "row", alignItems: "start", flexWrap: "nowrap" }}>
<div style={{ flex: "0 0 auto", marginRight: 16 }}>
+3 -1
View File
@@ -361,7 +361,9 @@ const PluginPresetsDialog = withStyles(styles, { withTheme: true })(
return (
<DialogEx tag="pluginPresets" fullScreen open={this.props.show}
onClose={() => { this.handleDialogClose() }} TransitionComponent={Transition}
style={{userSelect: "none"}}>
style={{userSelect: "none"}}
onEnterKey={()=>{}}
>
<div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}>
<div style={{ flex: "0 0 auto" }}>
<AppBar className={classes.dialogAppBar} style={{ display: this.isEditMode() ? "none" : "block" }} >
+3 -1
View File
@@ -340,7 +340,9 @@ const PresetDialog = withStyles(styles, { withTheme: true })(
return (
<DialogEx tag="preset" fullScreen open={this.props.show}
onClose={() => { this.handleDialogClose() }} TransitionComponent={Transition}
style={{userSelect: "none"}}>
style={{userSelect: "none"}}
onEnterKey={()=>{}}
>
<div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}>
<div style={{ flex: "0 0 auto" }}>
<AppBar className={classes.dialogAppBar} style={{ display: this.isEditMode() ? "none" : "block" }} >
@@ -18,7 +18,6 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import React from 'react';
import Button from '@mui/material/Button';
import Radio from '@mui/material/Radio';
@@ -29,11 +28,10 @@ import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import DialogEx from './DialogEx';
import DialogActions from '@mui/material/DialogActions';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
export interface ListSelectDialogProps {
export interface RadioSelectDialogProps {
open: boolean,
title: string,
width: number,
@@ -43,15 +41,15 @@ export interface ListSelectDialogProps {
onClose: () => void
};
export interface ListSelectDialogState {
export interface RadioSelectDialogState {
fullScreen: boolean;
};
export default class ListSelectDialog extends ResizeResponsiveComponent<ListSelectDialogProps, ListSelectDialogState> {
export default class RadioSelectDialog extends ResizeResponsiveComponent<RadioSelectDialogProps, RadioSelectDialogState> {
refText: React.RefObject<HTMLInputElement>;
constructor(props: ListSelectDialogProps) {
constructor(props: RadioSelectDialogProps) {
super(props);
this.state = {
fullScreen: false
@@ -82,7 +80,9 @@ export default class ListSelectDialog extends ResizeResponsiveComponent<ListSele
render() {
return (
<DialogEx tag="list" onClose={()=>this.props.onClose()} open={this.props.open}>
<DialogEx tag="list" onClose={()=>this.props.onClose()} open={this.props.open}
onEnterKey={()=>{ }}
>
<List sx={{pt: 0}}>
{
this.props.items.map(
@@ -105,11 +105,6 @@ export default class ListSelectDialog extends ResizeResponsiveComponent<ListSele
)
}
</List>
<DialogActions>
<Button variant="dialogSecondary" onClick={()=> this.props.onClose()} color="primary">
Cancel
</Button>
</DialogActions>
</DialogEx>
);
}
+1
View File
@@ -117,6 +117,7 @@ export default class RenameDialog extends ResizeResponsiveComponent<RenameDialog
<DialogEx tag="nameDialog" open={open} fullWidth maxWidth="sm" onClose={handleClose} aria-labelledby="Rename-dialog-title"
fullScreen={this.state.fullScreen}
style={{userSelect: "none"}}
onEnterKey={()=>{}}
>
<DialogContent style={{minHeight: 96}}>
<TextField
+6 -2
View File
@@ -90,7 +90,9 @@ function SelectChannelsDialog(props: SelectChannelsDialogProps) {
return (
<DialogEx tag="audioChannels" onClose={handleClose} aria-labelledby="select-channels-title" open={open}>
<DialogEx tag="audioChannels" onClose={handleClose} aria-labelledby="select-channels-title" open={open}
onEnterKey={handleOk}
>
<DialogTitle id="simple-dialog-title">Select Channels</DialogTitle>
<List>
{availableChannels.map((channel) => (
@@ -154,7 +156,9 @@ function SelectChannelsDialog(props: SelectChannelsDialogProps) {
}
return (
<DialogEx tag="channels" onClose={handleCancel} aria-labelledby="select-channels-title" open={open}>
<DialogEx tag="channels" onClose={handleCancel} aria-labelledby="select-channels-title" open={open}
onEnterKey={()=>{}}
>
<List style={{ marginLeft: 0, marginRight: 0}}>
<ListItem button onClick={() => handleListItemClick("Stereo")} key={"Stereo"} selected={selectionKey === "Stereo"} >
+3 -1
View File
@@ -93,7 +93,9 @@ function SelectMidiChannelsDialog(props: SelectMidiChannelsDialogProps) {
return (
<DialogEx tag="midiChannels" onClose={handleClose} aria-labelledby="select-channels-title" open={open}>
<DialogEx tag="midiChannels" onClose={handleClose} aria-labelledby="select-channels-title" open={open}
onEnterKey={handleOk}
>
<DialogTitle id="simple-dialog-title">Select MIDI Device</DialogTitle>
<List>
{availableChannels.map((channel) => (
+3 -1
View File
@@ -64,7 +64,9 @@ function SelectThemesDialog(props: SelectThemesDialogProps) {
return (
<DialogEx tag="theme" onClose={handleClose} open={open}>
<DialogEx tag="theme" onClose={handleClose} open={open}
onEnterKey={handleOk}
>
<DialogTitle id="simple-dialog-title">Theme</DialogTitle>
<DialogContent>
<FormControl>
+4 -3
View File
@@ -20,7 +20,7 @@
import React, { SyntheticEvent, Component } from 'react';
import Switch from "@mui/material/Switch";
import OkCancelDialog from './OkCancelDialog';
import ListSelectDialog from './ListSelectDialog';
import RadioSelectDialog from './RadioSelectDialog';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel';
@@ -542,6 +542,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
<DialogEx tag="settings" fullScreen open={this.props.open}
onClose={() => { this.props.onClose() }} TransitionComponent={Transition}
style={{ userSelect: "none" }}
onEnterKey={()=>{}}
>
<div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}>
@@ -882,7 +883,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
{
(this.state.showGovernorSettingsDialog) &&
(
<ListSelectDialog
<RadioSelectDialog
width={220}
open={this.state.showGovernorSettingsDialog}
items={this.state.governorSettings.governors}
@@ -901,7 +902,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
this.setState({ showGovernorSettingsDialog: false })
}}
>
</ListSelectDialog>
</RadioSelectDialog>
)
}
{
+212
View File
@@ -0,0 +1,212 @@
/*
* MIT License
*
* Copyright (c) 2024 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.
*/
import React from 'react';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import ButtonBase from '@mui/material/ButtonBase';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import SaveIconOutline from '@mui/icons-material/Save';
import Button from "@mui/material/Button";
import EditIconOutline from '@mui/icons-material/Edit';
import { Snapshot } from './Pedalboard';
import { colorKeys, getBackgroundColor, getBorderColor } from './MaterialColors';
import { PiPedalModel, PiPedalModelFactory, SnapshotModifiedEvent } from './PiPedalModel';
export interface SnapshotButtonProps {
snapshot: Snapshot | null;
snapshotIndex: number;
selected: boolean;
largeText: boolean;
collapseButtons: boolean;
onEditSnapshot: (index: number) => void;
onSaveSnapshot: (index: number) => void;
onSelectSnapshot: (index: number) => void;
};
export interface SnapshotButtonState {
modified: boolean;
};
export default class SnapshotButton extends ResizeResponsiveComponent<SnapshotButtonProps, SnapshotButtonState> {
private model: PiPedalModel;
constructor(props: SnapshotButtonProps) {
super(props);
this.state = {
modified: this.props.snapshot?.isModified ?? false,
};
this.model = PiPedalModelFactory.getInstance();
this.onSnapshotModified = this.onSnapshotModified.bind(this);
}
onWindowSizeChanged(width: number, height: number): void {
}
onSnapshotModified(event: SnapshotModifiedEvent) {
if (event.snapshotIndex === this.props.snapshotIndex) {
this.setState({ modified: event.modified });
}
}
componentDidMount(): void {
super.componentDidMount?.();
this.model.onSnapshotModified.addEventHandler(this.onSnapshotModified);
}
componentWillUnmount(): void {
this.model.onSnapshotModified.removeEventHandler(this.onSnapshotModified);
super.componentWillUnmount();
}
componentDidUpdate(
prevProps: Readonly<SnapshotButtonProps>,
prevState: Readonly<SnapshotButtonState>): void {
super.componentDidUpdate?.(prevProps, prevState);
if (this.props.snapshot !== prevProps.snapshot) {
this.setState({
modified: this.props.snapshot?.isModified ?? false
})
}
}
render() {
let { snapshot, snapshotIndex, selected } = this.props;
let { modified } = this.state;
// (snapshot: Snapshot | null, index: number) {
//let state = this.state;
let color: string;
let bordercolor: string;
if (snapshot) {
if (colorKeys.indexOf(snapshot.color) === -1) {
bordercolor = color = snapshot.color;
} else {
color = getBackgroundColor(snapshot.color);
bordercolor = getBorderColor(snapshot.color);
}
} else {
bordercolor = color = getBackgroundColor("grey");
}
let title = snapshot ? snapshot.name : "<unassigned>";
if (modified) {
title = title + "*";
}
let disabled = snapshot === null;
return (
<div style={{ display: "flex", position: "relative", flexGrow: 1, flexBasis: 1, flexFlow: "column nowrap", alignItems: "stretch", borderRadius: 16 }}>
<ButtonBase style={{
display: "flex", flexGrow: 1, flexBasis: 1, flexFlow: "column nowrap", alignItems: "stretch", borderRadius: 16,
background: color,
boxShadow: "1px 3px 6px #00000090"
}}
onMouseDown={(ev) => {
if (!selected) ev.stopPropagation();
}}
onClick={() => { if (snapshot) this.props.onSelectSnapshot(snapshotIndex); }}
>
<div
style={{ flexGrow: 1, flexShrink: 1, display: "flex", flexFlow: "column nowrap", alignItems: "stretch" }}
onMouseDown={(ev) => {
if (disabled) ev.stopPropagation();
}}
>
<div style={{
flexGrow: 1, flexShrink: 1, display: "flex", flexFlow: "column nowrap", alignItems: "stretch", justifyContent: "center", margin: 6,
borderColor: selected ? bordercolor : "transparent", borderStyle: "solid", borderWidth: 3, borderRadius: 10
}}
>
<div style={{ flexGrow: 1, display: "flex", alignItems: "center", justifyContent: "center" }}>
<Typography display="block" color="textPrimary" align="center" variant="body1"
style={{ fontSize: this.props.largeText ? "30px" : undefined }}
>{title}</Typography>
</div>
<Typography variant="h3"
style={{
position: "absolute",bottom: 0, left: 0, paddingBottom: 12, paddingLeft: 16, paddingRight: 12,
pointerEvents: "none", opacity: 0.15, fontSize: "44px", fontWeight: 900, fontFamily: "Arial Black", fontStyle: "italic"
}}
>
{(snapshotIndex + 1).toString()}
</Typography>
<div style={{ height: 54, flexGrow: 0, flexShrink: 0 }}>
{/* placeholder where we will float the buttons, which can't be witin another button (tons of DOM validation warning messages in chrome) */}
</div>
</div>
</div>
</ButtonBase >
<div style={{ flexGrow: 0, position: "absolute", bottom: 0, right: 0, paddingBottom: 12, marginRight: 12 }} >
{!this.props.collapseButtons ? (
<div style={{ marginRight: 8, display: "flex", flexFlow: "row nowrap" }}>
<Button variant="dialogSecondary" startIcon={<SaveIconOutline />} color="inherit" style={{ textTransform: "none" }}
onMouseDown={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
onMouseUp={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
disabled={false}
onClick={(ev) => { ev.stopPropagation(); this.props.onSaveSnapshot(snapshotIndex); }}
>
Save
</Button>
<Button variant="dialogSecondary" color="inherit" startIcon={<EditIconOutline />} style={{ textTransform: "none" }}
onMouseDown={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
onMouseUp={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
disabled={disabled}
onClick={(ev) => { ev.stopPropagation(); this.props.onEditSnapshot(snapshotIndex); }}
>
Edit
</Button>
</div>
) : (
<div style={{ marginLeft: "auto", marginRight: 8, display: "flex", flexFlow: "row nowrap" }}>
<div style={{ flexGrow: 1, flexBasis: 1 }} >&nbsp;</div>
<IconButton color="inherit" style={{ opacity: 0.66 }} disabled={false}
onMouseDown={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
onClick={() => { this.props.onSaveSnapshot(snapshotIndex); }}
>
<SaveIconOutline />
</IconButton>
<IconButton color="inherit" disabled={disabled} style={{ opacity: 0.66 }}
onMouseDown={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
onClick={() => { this.props.onEditSnapshot(snapshotIndex); }}
>
<EditIconOutline />
</IconButton>
</div>
)}
</div>
</div >
);
}
}
+1
View File
@@ -108,6 +108,7 @@ export default class SnapshotDialog extends ResizeResponsiveComponent<SnapshotDi
},
}}
onEnterKey={()=>{} }
>
<DialogTitle>
<div>
+16 -1
View File
@@ -216,8 +216,23 @@ const SnapshotEditor = withStyles(appStyles)(class extends ResizeResponsiveCompo
}
handleOk() {
let selectedSnapshot = this.props.snapshotIndex;
let currentPedalboard = this.model_.pedalboard.get();
let selectedSnapshot = this.props.snapshotIndex;
let currentSnapshot: Snapshot | null = currentPedalboard.snapshots[selectedSnapshot];
if (!currentSnapshot)
{
this.props.onClose();
return;
}
let changed = this.state.name !== currentSnapshot.name
|| this.state.color !== currentSnapshot.color
|| currentSnapshot.isModified;
if (!changed)
{
this.props.onClose();
return;
}
let newSnapshots = Snapshot.cloneSnapshots(currentPedalboard.snapshots);
let newSnapshot = this.model_.pedalboard.get().makeSnapshot();
newSnapshot.name = this.state.name;
+12 -111
View File
@@ -22,18 +22,12 @@
* SOFTWARE.
*/
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import ButtonBase from '@mui/material/ButtonBase';
import SnapshotButton from './SnapshotButton';
import { Pedalboard, Snapshot } from './Pedalboard';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import SaveIconOutline from '@mui/icons-material/Save';
import Button from "@mui/material/Button";
import EditIconOutline from '@mui/icons-material/Edit';
import SnapshotPropertiesDialog from './SnapshotPropertiesDialog';
import { colorKeys, getBackgroundColor, getBorderColor } from './MaterialColors';
@@ -100,7 +94,7 @@ export default class SnapshotPanel extends ResizeResponsiveComponent<SnapshotPan
}
onSelectedSnapshotChanged(value: number) {
this.setState({ selectedSnapshot: this.getSelectedSnapshot() })
this.setState({ selectedSnapshot: value })
}
onPedalboardChanged(value: Pedalboard) {
// boofs our current edit, oh well. we can't track property across a structural change.
@@ -193,110 +187,17 @@ export default class SnapshotPanel extends ResizeResponsiveComponent<SnapshotPan
this.model.selectSnapshot(index);
}
renderSnapshot(snapshot: Snapshot | null, index: number) {
//let state = this.state;
let color: string;
let bordercolor: string;
if (snapshot) {
if (colorKeys.indexOf(snapshot.color) === -1) {
bordercolor = color = snapshot.color;
} else {
color = getBackgroundColor(snapshot.color);
bordercolor = getBorderColor(snapshot.color);
}
} else {
bordercolor = color = getBackgroundColor("grey");
}
let title = snapshot ? snapshot.name : "<unassigned>";
let disabled = snapshot === null;
let selected = this.state.selectedSnapshot === index;
return (
<div style={{ display: "flex", position: "relative",flexGrow: 1, flexBasis: 1, flexFlow: "column nowrap", alignItems: "stretch", borderRadius: 16 }}>
<ButtonBase style={{
display: "flex", flexGrow: 1, flexBasis: 1, flexFlow: "column nowrap", alignItems: "stretch", borderRadius: 16,
background: color,
boxShadow: "1px 3px 6px #00000090"
}}
onMouseDown={(ev) => {
if (!selected) ev.stopPropagation();
}}
onClick={() => { if (snapshot) this.selectSnapshot(index); }}
>
<div
style={{ flexGrow: 1, flexShrink: 1, display: "flex", flexFlow: "column nowrap", alignItems: "stretch" }}
onMouseDown={(ev) => {
if (disabled) ev.stopPropagation();
}}
>
<div style={{
flexGrow: 1, flexShrink: 1, display: "flex", flexFlow: "column nowrap", alignItems: "stretch", justifyContent: "center", margin: 6,
borderColor: selected ? bordercolor : "transparent", borderStyle: "solid", borderWidth: 3, borderRadius: 10
}}
>
<div style={{ flexGrow: 1, display: "flex", alignItems: "center", justifyContent: "center" }}>
<Typography display="block" color="textPrimary" align="center" variant="body1"
style={{ fontSize: this.state.largeText ? "30px" : undefined }}
>{title}</Typography>
</div>
<div style={{ height: 54,flexGrow: 0,flexShrink: 0 }}>
{/* placeholder where we will float the buttons, which can't be witin another button (tons of DOM validation warning messages in chrome) */}
</div>
</div>
</div>
</ButtonBase >
<div style={{ flexGrow: 0, position: "absolute", bottom:0,left:0,right:0,paddingBottom:10,paddingLeft: 16, paddingRight:12 }} >
<Typography variant="h3" style={{opacity: 0.15,fontSize: "44px",fontWeight:900,fontFamily: "Arial Black",fontStyle: "italic"}} >{(index+1).toString()}</Typography>
</div>
<div style={{ flexGrow: 0, position: "absolute", bottom:0,left:0,right:0,paddingBottom:12,paddingLeft: 12, paddingRight:12 }} >
{!this.state.collapseButtons ? (
<div style={{ marginRight: 8, display: "flex", flexFlow: "row nowrap" }}>
<div style={{ flexGrow: 1, flexBasis: 1 }} >&nbsp;</div>
<Button variant="dialogSecondary" startIcon={<SaveIconOutline />} color="inherit" style={{ textTransform: "none" }}
onMouseDown={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
onMouseUp={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
disabled={false}
onClick={(ev) => { ev.stopPropagation(); this.onSaveSnapshot(index); }}
>
Save
</Button>
<Button variant="dialogSecondary" color="inherit" startIcon={<EditIconOutline />} style={{ textTransform: "none" }}
onMouseDown={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
onMouseUp={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
disabled={disabled}
onClick={(ev) => { ev.stopPropagation(); this.onEditSnapshot(index); }}
>
Edit
</Button>
</div>
) : (
<div style={{ marginLeft: "auto", marginRight: 8, display: "flex", flexFlow: "row nowrap" }}>
<div style={{ flexGrow: 1, flexBasis: 1 }} >&nbsp;</div>
<IconButton color="inherit" style={{ opacity: 0.66 }} disabled={false}
onMouseDown={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
onClick={() => { this.onSaveSnapshot(index); }}
>
<SaveIconOutline />
</IconButton>
<IconButton color="inherit" disabled={disabled} style={{ opacity: 0.66 }}
onMouseDown={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
onClick={() => { this.onEditSnapshot(index); }}
>
<EditIconOutline />
</IconButton>
</div>
)}
</div>
</div >
<SnapshotButton key={"s"+index}
snapshot={snapshot}
snapshotIndex={index}
collapseButtons={this.state.collapseButtons}
selected={this.state.selectedSnapshot === index}
largeText={this.state.largeText}
onEditSnapshot={(index)=>{this.onEditSnapshot(index)}}
onSaveSnapshot={(index)=>{ this.onSaveSnapshot(index); }}
onSelectSnapshot={(index)=>{this.model.selectSnapshot(index);}}
/>
);
}
+1
View File
@@ -113,6 +113,7 @@ export default class SnapshotPropertiesDialog extends ResizeResponsiveComponent<
return (
<DialogEx maxWidth="sm" fullWidth={true} tag="snapshotProps" open={this.props.open} onClose={handleClose}
style={{ userSelect: "none" }} fullScreen={this.state.compactVertical}
onEnterKey={()=> { this.handleOk(); }}
>
<DialogTitle>
<div>
+1
View File
@@ -328,6 +328,7 @@ export const SystemMidiBindingDialog =
<DialogEx tag="systemMidiBindings" open={open} fullWidth onClose={this.handleClose} aria-labelledby="Rename-dialog-title"
fullScreen={true}
style={{ userSelect: "none" }}
onEnterKey={()=>{}}
>
<div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}>
<div style={{ flex: "0 0 auto" }}>
+2
View File
@@ -166,6 +166,7 @@ export default class UpdateDialog extends ResizeResponsiveComponent<UpdateDialog
return (
<DialogEx tag="update" open={this.props.open} onClose={() => { this.handleClose(); }}
style={{ userSelect: "none" }}
onEnterKey={()=>{ this.handleOK();}}
>
<DialogTitle>
<div style={{ display: "flex", flexFlow: "row noWrap", alignItems: "center" }} >
@@ -288,6 +289,7 @@ export default class UpdateDialog extends ResizeResponsiveComponent<UpdateDialog
open={this.state.alertDialogOpen}
onClose={() => { this.handleCloseAlert(); }}
aria-describedby="Alert Dialog"
onEnterKey={()=>{ this.handleCloseAlert(); }}
>
<DialogContent>
<Typography variant="body2" color="secondaryText">
+8 -4
View File
@@ -311,9 +311,13 @@ export default class UploadFileDialog extends ResizeResponsiveComponent<UploadFi
let isAndroid = UploadFileDialog.IsAndroid();
return (
<DialogEx tag="uploadFile" open={this.props.open} fullWidth onClose={() => this.handleClose()}
fullScreen={this.state.fullScreen}
style={{ userSelect: "none" }}
<DialogEx tag="uploadFile" open={this.props.open} onClose={() => this.handleClose()}
fullScreen={false}
fullWidth={true}
maxWidth={"sm"}
style={{ userSelect: "none",}}
onEnterKey={()=>{ this.handleClose(); }}
>
<DialogTitle >
<div>
@@ -326,7 +330,7 @@ export default class UploadFileDialog extends ResizeResponsiveComponent<UploadFi
</DialogTitle>
<DialogContent style={{ paddingBottom: 0 }}>
<div style={{
width: "100%", height: 140, marginBottom: 0,
height: 140, marginBottom: 0,
border: (isAndroid? "2px solid #CCC" :"2px dashed #CCC"),
borderRadius: "10px",
fontFamily: "Roboto",
+2 -1
View File
@@ -157,9 +157,10 @@ export default class UploadPresetDialog extends ResizeResponsiveComponent<Upload
return (
<DialogEx tag="uploadPreset" open={this.props.open} fullWidth onClose={() => this.handleClose()}
<DialogEx tag="uploadPreset" open={this.props.open} fullWidth maxWidth="sm" onClose={() => this.handleClose()}
fullScreen={this.state.fullScreen}
style={{userSelect: "none"}}
onEnterKey={()=>{}}
>
<DialogTitle>{this.props.title}</DialogTitle>
<DialogContent>
+25 -7
View File
@@ -223,6 +223,10 @@ const WifiConfigDialog = withStyles(styles, { withTheme: true })(
this.state.newPassword;
}
onEnterKey() {
this.handleOk(false);
}
handleOk(wifiWarningGiven: boolean) {
let hasError = false;
if (this.state.autoStartMode !== 0) {
@@ -324,6 +328,13 @@ const WifiConfigDialog = withStyles(styles, { withTheme: true })(
}
return result;
}
handleWarningDialogOk()
{
this.setState({ showWifiWarningDialog: false });
this.handleOk(true);
}
render() {
let props = this.props;
let classes = this.props.classes;
@@ -334,8 +345,11 @@ const WifiConfigDialog = withStyles(styles, { withTheme: true })(
};
let enabled = this.state.autoStartMode !== 0;
return (
<DialogEx tag="wifiConfig" open={open} fullWidth onClose={handleClose} style={{ userSelect: "none" }}
fullScreen={this.state.fullScreen}
<DialogEx tag="wifiConfig" open={open} fullWidth maxWidth="sm" onClose={handleClose} style={{ userSelect: "none" }}
fullScreen={this.state.fullScreen
}
onEnterKey={()=>{ this.handleOk(false); }}
>
{(this.state.fullScreen || !this.state.compactHeight) && (
<DialogTitle>Wi-fi Auto-Hotspot</DialogTitle>
@@ -353,8 +367,8 @@ const WifiConfigDialog = withStyles(styles, { withTheme: true })(
}}
>
<FormControl variant="standard" style={{ flexGrow: 1, flexBasis: 1 }} >
<InputLabel htmlFor="behavior">Auto-start hotspot when</InputLabel>
<FormControl variant="standard" style={{ flexGrow: 1, flexBasis: 1, minWidth: 150 }} >
<InputLabel htmlFor="behavior">Auto-start hotspot when...</InputLabel>
<Select id="behavior" value={this.state.autoStartMode}
onChange={(el) => {
let value = el.target.value as number
@@ -562,7 +576,11 @@ const WifiConfigDialog = withStyles(styles, { withTheme: true })(
</DialogActions>
{this.state.showHelpDialog && (
<DialogEx open={this.state.showHelpDialog} tag="wifiHelp"
style={{ userSelect: "none" }}>
style={{ userSelect: "none" }}
onEnterKey={()=>{
this.setState({ showHelpDialog: false });
}}
>
<DialogContent>
<Typography className={classes.pgraph} variant="h6" color="textPrimary">
PiPedal Auto-Hotspot
@@ -626,6 +644,7 @@ const WifiConfigDialog = withStyles(styles, { withTheme: true })(
)}
{this.state.showWifiWarningDialog && (
<DialogEx open={this.state.showWifiWarningDialog} tag="wifiConfirm"
onEnterKey={()=>{ this.handleWarningDialogOk();}}
style={{ userSelect: "none" }}>
<DialogContent>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
@@ -647,8 +666,7 @@ const WifiConfigDialog = withStyles(styles, { withTheme: true })(
Cancel
</Button>
<Button onClick={() => {
this.setState({ showWifiWarningDialog: false });
this.handleOk(true);
this.handleWarningDialogOk();
}} variant="dialogPrimary" style={{ width: 120 }} >
PROCEED
</Button>
+1 -1
View File
@@ -307,7 +307,7 @@ const WifiDirectConfigDialog = withStyles(styles, { withTheme: true })(
return (
<DialogEx tag="p2pConfig" open={open} onClose={handleClose} style={{ userSelect: "none", }}
fullScreen={this.state.fullScreen} fullWidth={this.useLandscapeLayout()}
fullScreen={this.state.fullScreen} fullWidth={this.useLandscapeLayout()} onEnterKey={()=>{}}
>
{this.state.landscapeLayout && (
<DialogContent >
+1
View File
@@ -222,6 +222,7 @@ const ZoomedUiControl = withStyles(styles, { withTheme: true })(
<DialogEx tag="zoomedControl" open={this.props.dialogOpen}
onClose={() => { this.props.onDialogClose() }}
onAbort={() => { this.props.onDialogClose() }}
onEnterKey={()=>{/*nothing*/}}
>
<div style={{
width: 380, height: 300,
+8 -1
View File
@@ -10,6 +10,8 @@ src/SelectMidiChannelsDialog.tsx
src/AlsaMidiDeviceInfo.tsx
src/WifiDirectConfigSettings.tsx
src/WifiConfigSettings.tsx
src/SnapshotButton.tsx
src/WindowScale.tsx
src/JsonAtom.tsx
src/PiPedalSocket.tsx
src/TemporaryDrawer.tsx
@@ -20,6 +22,7 @@ src/SnapshotDialog.tsx
src/SettingsDialog.tsx
src/PedalboardView.tsx
src/reportWebVitals.ts
src/OptionsDialog.tsx
src/SelectChannelsDialog.tsx
src/PluginPresetSelector.tsx
src/VuMeter.tsx
@@ -99,6 +102,7 @@ src/svg/fx_mixer.svg
src/svg/fx_utility.svg
src/Updater.tsx
src/UploadPresetDialog.tsx
src/RadioSelectDialog.tsx
src/setupTests.ts
src/ToobCabSimView.tsx
src/RenameDialog.tsx
@@ -117,6 +121,7 @@ src/Pedal.tsx
src/Draggable.tsx
src/PluginIcon.tsx
src/react-app-env.d.ts
src/TextFieldEx.tsx
src/FilePropertyDialog.tsx
src/FilePropertyDirectoryTree.tsx
src/StringBuilder.tsx
@@ -127,7 +132,6 @@ src/Lv2Plugin.tsx
src/ToobSpectrumResponseView.tsx
src/PerformanceView.tsx
src/JackServerSettingsDialog.tsx
src/ListSelectDialog.tsx
src/Utility.tsx
src/JackStatusView.tsx
src/ToobSpectrumAnalyzerView.tsx
@@ -137,6 +141,7 @@ src/PresetDialog.tsx
src/WifiChannel.tsx
src/AboutDialog.tsx
src/GxTunerControl.tsx
src/VirtualKeyboardHandler.tsx
src/PluginControl.tsx
src/LoadPluginDialog.tsx
src/ZoomedUiControl.tsx
@@ -152,8 +157,10 @@ src/AndroidHost.tsx
src/SnapshotPanel.tsx
src/ToobMLView.tsx
src/WifiConfigDialog.tsx
src/Rectangle.tsx
src/SnapshotColors.tsx
src/PiPedalModel.tsx
src/ObservableEvent.tsx
src/JackHostStatus.tsx
src/index.tsx
src/FilePropertyControl.tsx
+19 -12
View File
@@ -191,10 +191,12 @@ namespace pipedal
void validate_capture_handle() { // leftover debugging for a buffer overrun :-/
// if (snd_pcm_type(captureHandle) != SND_PCM_TYPE_HW)
// {
// throw std::runtime_error("Capture handle has been overwritten");
// }
#ifdef DEBUG
if (snd_pcm_type(captureHandle) != SND_PCM_TYPE_HW)
{
throw std::runtime_error("Capture handle has been overwritten");
}
#endif
}
public:
AlsaDriverImpl(AudioDriverHost *driverHost)
@@ -1934,10 +1936,15 @@ namespace pipedal
for (size_t i = 0; i < devices.size(); ++i)
{
const auto &device = devices[i];
auto midiDevice = std::make_unique<AlsaMidiDeviceImpl>();
midiDevice->Open(device);
midiDevices.push_back(std::move(midiDevice));
try {
const auto &device = devices[i];
auto midiDevice = std::make_unique<AlsaMidiDeviceImpl>();
midiDevice->Open(device);
midiDevices.push_back(std::move(midiDevice));
} catch (const std::exception &e)
{
Lv2Log::error(e.what());
}
}
}
@@ -2016,11 +2023,11 @@ namespace pipedal
if (jackServerSettings.IsDummyAudioDevice())
{
inputAudioPorts.clear();
inputAudioPorts.push_back("system::capture_0");
inputAudioPorts.push_back("system::capture_1");
inputAudioPorts.push_back(std::string("system::capture_0"));
inputAudioPorts.push_back(std::string("system::capture_1"));
outputAudioPorts.clear();
outputAudioPorts.push_back("system::playback_0");
outputAudioPorts.push_back("system::playback_1");
outputAudioPorts.push_back(std::string("system::playback_0"));
outputAudioPorts.push_back(std::string("system::playback_1"));
return true;
}
+16 -8
View File
@@ -177,16 +177,24 @@ namespace pipedal
std::istringstream ss(pathProperty.second);
json_reader reader(ss);
reader.read(&vProperty);
vProperty = pluginHost.MapPath(vProperty);
if (!vProperty.is_null())
{
try {
vProperty = pluginHost.MapPath(vProperty);
// now to atom format (what we want on the rt thread0)
AtomConverter atomConverter(pluginHost.GetMapFeature());
LV2_Atom *atomValue = atomConverter.ToAtom(vProperty);
// now to atom format (what we want on the rt thread0)
AtomConverter atomConverter(pluginHost.GetMapFeature());
LV2_Atom *atomValue = atomConverter.ToAtom(vProperty);
size_t atomBufferSize = (atomValue->size + sizeof(LV2_Atom) + 3) / 4 * 4;
pathPatchProperty.atomBuffer.resize(atomValue->size + sizeof(LV2_Atom));
memcpy(pathPatchProperty.atomBuffer.data(), atomValue, pathPatchProperty.atomBuffer.size());
this->pathPatchProperties.push_back(std::move(pathPatchProperty));
size_t atomBufferSize = (atomValue->size + sizeof(LV2_Atom) + 3) / 4 * 4;
pathPatchProperty.atomBuffer.resize(atomValue->size + sizeof(LV2_Atom));
memcpy(pathPatchProperty.atomBuffer.data(), atomValue, pathPatchProperty.atomBuffer.size());
this->pathPatchProperties.push_back(std::move(pathPatchProperty));
} catch (const std::exception &e)
{
Lv2Log::info(SS("IndexedSnapshotValue: Failed to map path property " << pathProperty.first << ". " << e.what()));
}
}
}
}
}
+55 -14
View File
@@ -37,7 +37,7 @@ using namespace dbus::networkmanager;
namespace pipedal::impl
{
#define PIPEDAL_HOTSPOT_NAME "PiPedal Hotspot"
#define PIPEDAL_HOTSPOT_NAME "PiPedal Hotspot"
class HotspotManagerImpl : public HotspotManager
{
@@ -164,19 +164,20 @@ void HotspotManagerImpl::Open()
void HotspotManagerImpl::onClose()
{
this->closed = true; // avoids a memory barrier probelm.
this->closed = true; // avoids a memory barrier probelm.
CancelDeviceChangedTimer();
CancelWaitForNetworkManagerTimer();
CancelAccessPointsChangedTimer();
if (networkManager && activeConnection)
{
try {
try
{
networkManager->DeactivateConnection(activeConnection->getObjectPath());
activeConnection = nullptr;
} catch (const std::exception&e)
}
catch (const std::exception &e)
{
// nothrow.
}
@@ -443,16 +444,31 @@ void HotspotManagerImpl::onReload()
if (closed)
return;
WifiConfigSettings oldSettings = this->wifiConfigSettings;
;
wifiConfigSettings.Load();
this->wifiConfigSettings.Load();
if (wifiConfigSettings == oldSettings)
{
return;
}
switch (state)
{
case State::Initial:
case State::Error:
// ignore.
Lv2Log::error("NetworkManager is in an error state. Reboot and try again.");
return;
case State::Initial:
case State::WaitingForNetworkManager:
// ignore. new config will be picked up once initialization completes.
return;
case State::Closed:
return;
case State::HotspotConnecting:
case State::HotspotConnected:
default:
// force a reload.
StopHotspot();
SetState(State::Monitoring);
MaybeStartHotspot();
return;
}
@@ -536,7 +552,8 @@ void HotspotManagerImpl::UpdateKnownNetworks(
}
for (auto &accessPoint : allAccessPoints)
{
try {
try
{
uint8_t strength = accessPoint->Strength();
auto vSsid = accessPoint->Ssid();
std::string ssid = ssidToString(vSsid);
@@ -547,7 +564,8 @@ void HotspotManagerImpl::UpdateKnownNetworks(
record.visibleNetwork = true;
record.strength = strength;
}
} catch (const std::exception&ignored)
}
catch (const std::exception &ignored)
{
// race to get the info before it changes. np.
}
@@ -819,8 +837,6 @@ void HotspotManagerImpl::StartHotspot()
std::vector<uint8_t> vSsid{ssid.begin(), ssid.end()};
wireless["ssid"] = vSsid;
wireless["mode"] = "ap";
wireless["band"] = "a";
wireless["band"] = "bg";
uint32_t iChannel = 0;
auto channel = this->wifiConfigSettings.channel_;
@@ -829,9 +845,14 @@ void HotspotManagerImpl::StartHotspot()
std::stringstream ss{channel};
ss >> iChannel;
}
if (iChannel != 0)
if (iChannel <= 1)
{
wireless["channel"] = iChannel;
wireless["band"] = iChannel > 14 ? "a" : "bg";
}
else
{
wireless["band"] = iChannel == 0 ? "bg" : "a";
}
std::map<std::string, sdbus::Variant> &wirelessSecurity = settings["802-11-wireless-security"];
@@ -840,6 +861,26 @@ void HotspotManagerImpl::StartHotspot()
settings["ipv4"]["method"] = "shared";
settings["ipv6"]["method"] = "shared";
////////////////////////////////////////////////////////////////
// Create connection
settings["ipv4"]["method"] = sdbus::Variant(std::string("shared"));
//settings["ipv6"]["method"] = sdbus::Variant(std::string("ignore"));
settings["ipv4"]["address-data"] = sdbus::Variant(std::vector<std::map<std::string, sdbus::Variant>>{{{"address", sdbus::Variant("192.168.60.1")},
{"prefix", sdbus::Variant(uint32_t(24))}}});
settings["ipv4"]["dhcp-send-hostname"] = sdbus::Variant(true);
settings["ipv4"]["dhcp-hostname"] = sdbus::Variant("raspberrypi");
// settings["ipv4"]["dhcp-options"] = sdbus::Variant(std::vector<std::string>{
// "option:classless-static-route,192.168.60.0/24,0.0.0.0,0.0.0.0/0,192.168.60.1"});
settings["ipv4"]["route-data"] = sdbus::Variant(std::vector<std::map<std::string, sdbus::Variant>>{{
{"dest", sdbus::Variant("192.168.4.0")},
{"prefix", sdbus::Variant(uint32_t(24))},
{"metric", sdbus::Variant((uint32_t)99)}
}});
////////////////////////////////////////////////////////////
// settings["ipv6"]["addr-gen-mode"] = "stable-privacy";
std::map<std::string, sdbus::Variant> options;
+293 -4
View File
@@ -27,10 +27,12 @@
#include <memory.h>
#include <exception>
#include <netdb.h>
#include <regex>
#include "ss.hpp"
#include "Finally.hpp"
using namespace pipedal;
std::string pipedal::GetNonLinkLocalAddress(const std::string fromAddress);
static bool IsIpv4MappedAddress(const struct in6_addr &inetAddr6)
{
@@ -188,7 +190,6 @@ bool pipedal::IsOnLocalSubnet(const std::string &fromAddress)
// ipv4
struct in_addr inetAddr;
memset(&inetAddr, 0, sizeof(inetAddr));
if (inet_pton(AF_INET, address.c_str(), &inetAddr) == 1)
{
uint32_t remoteAddress = htonl(inetAddr.s_addr);
@@ -314,12 +315,89 @@ static std::string GetInterfaceForIp6Address(const in6_addr inetAddr6)
return result;
}
static std::string GetNonLinkLocalAddress(const boost::asio::ip::address_v6 &ipV6Address)
{
auto targetInterfaceId = ipV6Address.scope_id();
boost::asio::ip::address_v6::bytes_type bytes = ipV6Address.to_bytes();
auto scopeId = ipV6Address.scope_id();
struct ifaddrs *ifap = nullptr;
if (getifaddrs(&ifap) != 0)
throw std::runtime_error("No networks available.");
Finally f{[ifap]() {
freeifaddrs(ifap);
}};
std::string result;
const char *targetInterfaceName = nullptr;
const ifaddrs *targetInterface = nullptr;
// get the nameof the interface for this scopeId.
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
{
if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr)
{ // TODO: Add support for AF_INET6
struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr);
if (pAddr->sin6_scope_id == scopeId)
{
targetInterface = p;
targetInterfaceName = p->ifa_name;
break;
}
}
}
if (!targetInterface)
{
throw std::runtime_error("Unknown address.");
}
// an IPV4 address would be the ideal result.
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
{
if (p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
{
if (strcmp(p->ifa_name,targetInterfaceName) == 0)
{
constexpr int BUFSIZE = 128;
char host[BUFSIZE];
if (getnameinfo(p->ifa_addr, sizeof(struct sockaddr_in), host, BUFSIZE, NULL, 0, NI_NUMERICHOST) == 0)
{
host[BUFSIZE - 1] = '\0';
result = host;
}
return result;
}
}
}
// failing that, a global unique ip address.
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
{
if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
{ // TODO: Add support for AF_INET6
struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr);
if ((!IN6_IS_ADDR_LINKLOCAL(pAddr)) && (!IN6_IS_ADDR_SITELOCAL(pAddr)))
{
char buffer[128];
inet_ntop(AF_INET6, &(pAddr->sin6_addr),
buffer,sizeof(buffer));
return SS('[' << buffer << ']');
}
}
}
// probably network address settling after the hotspot comes up. :-/
throw std::runtime_error("Not ready. Try again later.");
}
static std::string GetNonLinkLocalAddressForInterface(const std::string &name)
{
struct ifaddrs *ifap = nullptr;
if (getifaddrs(&ifap) != 0)
return "";
std::string result = "notlinklocal.error";
std::string result;
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
{
if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
@@ -352,6 +430,10 @@ static std::string GetNonLinkLocalAddressForInterface(const std::string &name)
}
}
freeifaddrs(ifap);
if (result.empty())
{
throw std::logic_error("Address not supported.");
}
return result;
}
static std::string GetNonLinkLocalAddressForIp4Interface(const std::string &name)
@@ -385,7 +467,97 @@ static std::string GetNonLinkLocalAddressForIp4Interface(const std::string &name
return result;
}
std::string pipedal::GetNonLinkLocalAddress(const std::string fromAddress)
// bool pipedal::IsLinkLocalAddress(const std::string & fromAddress)
// {
// std::string address = fromAddress;
// std::string result;
// if (address[0] != '[')
// {
// return false;
// }
// else
// {
// // ipv6
// if (address[0] != '[' || address[address.length() - 1] != ']')
// throw std::invalid_argument("Bad address.");
// address = address.substr(1, address.length() - 2);
// // strip the interface suffice if present.
// auto pos = address.find_last_of('%'); //
// if (pos != std::string::npos)
// {
// address = address.substr(0,pos);
// }
// struct in6_addr inetAddr6;
// memset(&inetAddr6, 0, sizeof(inetAddr6));
// xx trashes memory. if (inet_pton(AF_INET6, address.c_str(), &address) == 1)
// {
// if (IN6_IS_ADDR_LINKLOCAL(&inetAddr6))
// {
// return true;
// }
// }
// }
// return false;
// }
// static std::string GetNonLinkLocalAddress(const std::string &address)
// {
// std::string address = fromAddress;
// std::string result;
// if (address[0] != '[')
// {
// return address;
// }
// else
// {
// // ipv6
// if (address[0] != '[' || address[address.length() - 1] != ']')
// throw std::invalid_argument("Bad address.");
// address = address.substr(1, address.length() - 2);
// auto nPos = address.find('%') ;
// if (nPos != std::string::npos)
// {
// std::string ifName = address.substr(nPos+1);
// return GetNonLinkLocalAddressForInterface(ifName);
// }
// struct sockaddr_in6 sa6;
// auto&inetAddr6 = sa6.sin6_addr;
// if (inet_pton(AF_INET6, address.c_str(), &inetAddr6) == 1)
// {
// int32_t remoteAddress = -1;
// // cases:
// // [::FFFF:ipv4 address]
// // [FE80:: ] link local.
// // [FEC0:: ] site local.
// // others?
// if (IN6_IS_ADDR_V4MAPPED(&inetAddr6))
// {
// int8_t *pAddr = (int8_t *)&inetAddr6;
// uint32_t remoteAddress = htonl(*(int32_t *)(pAddr + 12));
// std::string interfaceName = GetInterfaceForIp4Address(remoteAddress);
// result = GetNonLinkLocalAddressForIp4Interface(interfaceName);
// }
// else
// {
// std::string interfaceName = GetInterfaceForIp6Address(inetAddr6);
// result = GetNonLinkLocalAddressForInterface(interfaceName);
// }
// }
// }
// if (result == "")
// {
// result = GetNonLinkLocalAddressForInterface("");
// }
// return result;
// }
std::string pipedal::GetNonLinkLocalAddress(const std::string &fromAddress)
{
std::string address = fromAddress;
std::string result;
@@ -444,3 +616,120 @@ std::string pipedal::GetInterfaceIpv4Address(const std::string& interfaceName)
{
return GetNonLinkLocalAddressForIp4Interface(interfaceName);
}
// std::string getNonLinkLocalAddress(const std::string&address, const std::string&interface)
// {
// if (!interface.empty())
// {
// return GetNonLinkLocalAddressForInterface(interface);
// }
// return GetNonLinkLocalAddress(address);
// }
static bool parseForLinkLocalUrl(const std::string&url,std::string*prefix, std::string *suffix)
{
auto iter = url.begin();
auto end = url.end();
// proto:
while (true)
{
if (iter == end) return false;
if (*iter == ':')
{
++iter;
break;
}
++iter;
}
// //
if (iter == end || *iter != '/') return false;
++iter;
if (iter == end || *iter != '/') return false;
++iter;
// (user@)? of id: http://user@[::1]/
if (*iter != '[') {
while (true)
{
if (iter == end || *iter == '/') return false;
if (*iter == '@')
{
++iter;
break;
}
++iter;
}
}
// [fe80:*]
if (iter == end || *iter != '[')
{
return false;
}
auto endOfPrefix = iter;
++iter;
// fe80: ...
if (iter == end || !(*iter == 'f' || *iter == 'F')) return false;
++iter;
if (iter == end || !(*iter == 'e' || *iter == 'E')) return false;
++iter;
if (iter == end || *iter != '8' ) return false;
++iter;
if (iter == end || *iter != '0' ) return false;
++iter;
if (iter == end || *iter != ':' ) return false;
++iter;
while (true)
{
if (iter == end) break;
char c = *iter;
bool valid = (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || c >= '0' && c <= '9' || c == ':';
if (!valid) break;
++iter;
}
// potentially a scope ID
// [::1%wlan0]
auto endOfAddress = iter;
if (iter != end && *iter == '%')
{
while (true) {
if (iter == end) return false;
if (*iter == ']') break;
++iter;
}
}
// made it!
if (iter == end || *iter != ']') return false;
++iter;
auto startOfRest = iter;
*prefix = std::string(url.begin(),endOfPrefix);
*suffix = std::string(startOfRest,url.end());
return true;
}
bool pipedal::RemapLinkLocalUrl(
const boost::asio::ip::address &boostAddress,
const std::string&url,std::string*outputUrl)
{
std::string prefix, suffix;
if (!parseForLinkLocalUrl(url,&prefix,&suffix)) return false;
boost::asio::ip::address_v6 addressV6 = boostAddress.to_v6();
std::string directAddress;
directAddress = ::GetNonLinkLocalAddress(addressV6);
*outputUrl = SS(prefix << directAddress << suffix);
return true;
}
+10 -2
View File
@@ -20,17 +20,25 @@
#pragma once
#include <string>
#include <utility>
#include <boost/asio.hpp>
namespace pipedal {
std::string GetInterfaceIpv4Address(const std::string& interfaceName);
std::string GetNonLinkLocalAddress(const std::string fromAddress);
// bool IsLinkLocalAddress(const std::string &fromAddress);
std::string GetNonLinkLocalAddress(const std::string& fromAddress);
bool IsOnLocalSubnet(const std::string&fromAddress);
bool RemapLinkLocalUrl(
const boost::asio::ip::address &address,
const std::string&url,std::string*outputUrl);
bool ParseHttpAddress(const std::string address,
std::string *pUser,
std::string *pServer,
+51
View File
@@ -87,6 +87,18 @@ ControlValue* PedalboardItem::GetControlValue(const std::string&symbol)
return nullptr;
}
const ControlValue* PedalboardItem::GetControlValue(const std::string&symbol) const
{
for (size_t i = 0; i < this->controlValues().size(); ++i)
{
if (this->controlValues()[i].key() == symbol)
{
return &(this->controlValues()[i]);
}
}
return nullptr;
}
bool Pedalboard::SetItemEnabled(int64_t pedalItemId, bool enabled)
{
PedalboardItem*item = GetItem(pedalItemId);
@@ -228,6 +240,7 @@ void PedalboardItem::ApplySnapshotValue(SnapshotValue*snapshotValue)
this->lv2State(snapshotValue->lv2State_);
this->stateUpdateCount(this->stateUpdateCount()+1);
}
this->isEnabled(snapshotValue->isEnabled_);
}
@@ -265,6 +278,17 @@ bool PedalboardItem::IsStructurallyIdentical(const PedalboardItem&other) const
}
if (this->isSplit()) // so is the other by virtue of idential uris.
{
auto myValue = this->GetControlValue("splitType");
auto otherValue = other.GetControlValue("splitType");
if (myValue == nullptr || otherValue == nullptr) // actually an error.
{
return false;
}
// split type changes potentially trigger buffer allocation changes,
// so different split types are not structurally identical.
if (myValue->value() != otherValue->value()) {
return false;
}
if (topChain().size() != other.topChain().size())
{
return false;
@@ -294,6 +318,7 @@ void PedalboardItem::AddToSnapshotFromCurrentSettings(Snapshot&snapshot) const
{
SnapshotValue snapshotValue;
snapshotValue.instanceId_ = this->instanceId_;
snapshotValue.isEnabled_ = this->isEnabled_;
for (const ControlValue &value: this->controlValues_)
{
@@ -354,6 +379,30 @@ void PedalboardItem::AddResetsForMissingProperties(Snapshot&snapshot, size_t*ind
}
Pedalboard Pedalboard::DeepCopy()
{
Pedalboard result = *this;
for (size_t i= 0; i < snapshots_.size(); ++i)
{
if (snapshots_[i])
{
result.snapshots_[i] = std::make_shared<Snapshot>(*(snapshots_[i]));
}
}
return result;
}
void Pedalboard::SetCurrentSnapshotModified(bool modified)
{
if (selectedSnapshot() != -1)
{
auto& snapshot = snapshots_[selectedSnapshot_];
if (snapshot)
{
snapshot->isModified_ = modified;
}
}
}
Snapshot Pedalboard::MakeSnapshotFromCurrentSettings(const Pedalboard &previousPedalboard)
{
Snapshot snapshot;
@@ -410,6 +459,7 @@ JSON_MAP_END()
JSON_MAP_BEGIN(SnapshotValue)
JSON_MAP_REFERENCE(SnapshotValue,instanceId)
JSON_MAP_REFERENCE(SnapshotValue,isEnabled)
JSON_MAP_REFERENCE(SnapshotValue,controlValues)
JSON_MAP_REFERENCE(SnapshotValue,lv2State)
JSON_MAP_REFERENCE(SnapshotValue,pathProperties)
@@ -417,6 +467,7 @@ JSON_MAP_END()
JSON_MAP_BEGIN(Snapshot)
JSON_MAP_REFERENCE(Snapshot,name)
JSON_MAP_REFERENCE(Snapshot,isModified)
JSON_MAP_REFERENCE(Snapshot,color)
JSON_MAP_REFERENCE(Snapshot,values)
JSON_MAP_END()
+6
View File
@@ -101,6 +101,7 @@ public:
PropertyMap patchProperties;
public:
ControlValue*GetControlValue(const std::string&symbol);
const ControlValue*GetControlValue(const std::string&symbol) const;
bool IsStructurallyIdentical(const PedalboardItem&other) const;
@@ -154,6 +155,7 @@ public:
class SnapshotValue {
public:
uint64_t instanceId_;
bool isEnabled_ = true;
std::vector<ControlValue> controlValues_;
Lv2PluginState lv2State_;
std::map<std::string,std::string> pathProperties_;
@@ -165,6 +167,7 @@ class Snapshot {
public:
std::string name_;
std::string color_;
bool isModified_ = false;
std::vector<SnapshotValue> values_;
DECLARE_JSON_MAP(Snapshot);
@@ -184,10 +187,13 @@ class Pedalboard {
int64_t selectedSnapshot_ = -1;
public:
// deep copy, breaking shared pointers.
Pedalboard DeepCopy();
static constexpr int64_t INPUT_VOLUME_ID = -2; // synthetic PedalboardItem for input volume.
static constexpr int64_t OUTPUT_VOLUME_ID = -3; // synthetic PedalboardItem for output volume.
bool SetControlValue(int64_t pedalItemId, const std::string &symbol, float value);
bool SetItemEnabled(int64_t pedalItemId, bool enabled);
void SetCurrentSnapshotModified(bool modified);
bool IsStructureIdentical(const Pedalboard &other) const; // caan we just send a snapshot-style uddate instead of reloading plugins? All settings are ignored.
Snapshot MakeSnapshotFromCurrentSettings(const Pedalboard &previousPedalboard);
+66 -34
View File
@@ -101,6 +101,26 @@ PiPedalModel::PiPedalModel()
// don't actuall start the hotspotManager until after LV2 is initialized (in order to avoid logging oddities)
}
void PrepareSnapshostsForSave(Pedalboard&pedalboard)
{
if (pedalboard.selectedSnapshot() != -1) {
auto&currentSnapshot = pedalboard.snapshots()[pedalboard.selectedSnapshot()];
if (!currentSnapshot || currentSnapshot->isModified_)
{
pedalboard.selectedSnapshot(-1);
}
}
for (auto &snapshot: pedalboard.snapshots())
{
if (snapshot) {
snapshot->isModified_ = false;
}
}
}
void PiPedalModel::Close()
{
std::unique_ptr<AudioHost> oldAudioHost;
@@ -515,15 +535,19 @@ void PiPedalModel::SetPedalboard(int64_t clientId, Pedalboard &pedalboard)
void PiPedalModel::SetSnapshot(int64_t selectedSnapshot)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (this->pedalboard.selectedSnapshot() == selectedSnapshot)
{
return;
}
if (this->pedalboard.ApplySnapshot(selectedSnapshot))
{
this->pedalboard.SetCurrentSnapshotModified(false);
this->FireSnapshotModified(selectedSnapshot,false);
if (this->audioHost)
{
this->audioHost->LoadSnapshot(*(this->pedalboard.snapshots()[selectedSnapshot]), this->pluginHost); // no longer own it.
if (this->previousPedalboardLoaded && this->pedalboard.IsStructureIdentical(this->previousPedalboard))
{
this->audioHost->LoadSnapshot(*(this->pedalboard.snapshots()[selectedSnapshot]), this->pluginHost); // no longer own it.
} else {
LoadCurrentPedalboard();
}
}
SetPresetChanged(-1, true, false);
this->pedalboard.selectedSnapshot(selectedSnapshot);
@@ -535,43 +559,21 @@ void PiPedalModel::SetSnapshots(std::vector<std::shared_ptr<Snapshot>> &snapshot
{
std::lock_guard<std::recursive_mutex> lock(mutex);
// notionally, snapshots are saved whenever they are modified, but pedalboards are not.
// so we must add the current snapshots BOTH to the current pedalbaoard, AND to the
// current saved instance of the pedalboard to create the illusion that the are stored separately.
// In practice, snapshots have to track structure changes (delete pedalboard items that have been deleted,
// and set default settings for pedalboard items that have ben added since the snapshots was last saved.
//
// This is acheived by:
// 1. updating BOTH the current pedalboard, and its saved instance.
// 2. discarding references to dangling instances as plugins are loaded (see UpdateDefaults).
// 3. Setting missing snapshot controls to default values (NOT the current pedalboard values)
// 4. (We currently don't do the right thing with patch properties if the patch property has never been set).
UpdateVst3Settings(pedalboard);
{
// stealth update of the saved snapshots.
auto savedPedalboard = storage.GetCurrentPreset();
savedPedalboard.snapshots(snapshots); // makes a shallow copy
if (selectedSnapshot != -1)
{
// implies that this is a snapshot of the currently running pedalboard. so we can mark it as the selected pedalboard.
this->pedalboard.selectedSnapshot(selectedSnapshot);
}
// UpdateDefaults(&savedPedalboard); // The update is awfully fresh. wait until it gets loaded again before pruning.
storage.SaveCurrentPreset(savedPedalboard);
}
this->pedalboard.snapshots(std::move(snapshots));
if (selectedSnapshot != -1)
{
this->pedalboard.selectedSnapshot(selectedSnapshot);
}
this->FirePedalboardChanged(-1, false); // notify clients (but don't change the running pedalboard, because it's still the same)
// this means that all clients get an up-to-date copy of the snapshots AND the currently selected snapshot if that applies
// (and a fresh copy of the pedalboard settings as well, which is harmless, since they have not changed)
this->SetPresetChanged(-1, true,false);
}
void PiPedalModel::UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalboard)
@@ -634,8 +636,16 @@ void PiPedalModel::SetPresetChanged(int64_t clientId, bool value, bool changeSna
{
if (changeSnapshotSelect && value && this->pedalboard.selectedSnapshot() != -1)
{
this->pedalboard.selectedSnapshot(-1);
FireSelectedSnapshotChanged(-1);
auto & snapshot = this->pedalboard.snapshots()[pedalboard.selectedSnapshot()];
if (snapshot) {
if (!snapshot->isModified_)
{
snapshot->isModified_ = true;
FireSnapshotModified(pedalboard.selectedSnapshot(),true);
}
}
this->pedalboard.SetCurrentSnapshotModified(true);
}
if (value != this->hasPresetChanged)
{
@@ -644,6 +654,20 @@ void PiPedalModel::SetPresetChanged(int64_t clientId, bool value, bool changeSna
}
}
void PiPedalModel::FireSnapshotModified(int64_t snapshotIndex, bool modified)
{
std::lock_guard<std::recursive_mutex> guard{mutex};
{
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
for (auto &subscriber : t)
{
subscriber->OnSnapshotModified(snapshotIndex,modified);
}
}
}
void PiPedalModel::FireSelectedSnapshotChanged(int64_t selectedSnapshot)
{
std::lock_guard<std::recursive_mutex> guard{mutex};
@@ -762,6 +786,8 @@ void PiPedalModel::SaveCurrentPreset(int64_t clientId)
UpdateVst3Settings(this->pedalboard);
SyncLv2State();
PrepareSnapshostsForSave(pedalboard);
storage.SaveCurrentPreset(this->pedalboard);
this->SetPresetChanged(clientId, false);
@@ -791,12 +817,15 @@ int64_t PiPedalModel::SavePluginPresetAs(int64_t instanceId, const std::string &
return presetId;
}
int64_t PiPedalModel::SaveCurrentPresetAs(int64_t clientId, const std::string &name, int64_t saveAfterInstanceId)
{
std::lock_guard<std::recursive_mutex> guard{mutex};
SyncLv2State();
auto pedalboard = this->pedalboard;
auto pedalboard = this->pedalboard.DeepCopy();
PrepareSnapshostsForSave(pedalboard);
UpdateVst3Settings(pedalboard);
pedalboard.name(name);
int64_t result = storage.SaveCurrentPresetAs(pedalboard, name, saveAfterInstanceId);
@@ -1280,6 +1309,7 @@ void PiPedalModel::RestartAudio(bool useDummyAudioDriver)
this->audioHost->SetPedalboard(nullptr);
previousPedalboardLoaded = false;
auto jackServerSettings = this->jackServerSettings;
if (useDummyAudioDriver)
{
@@ -2359,6 +2389,8 @@ bool PiPedalModel::LoadCurrentPedalboard()
// return true if the error messages have changed
CheckForResourceInitialization(this->pedalboard);
audioHost->SetPedalboard(lv2Pedalboard);
previousPedalboard = this->pedalboard;
previousPedalboardLoaded = true;
return true;
}
+2
View File
@@ -68,6 +68,7 @@ namespace pipedal
virtual void OnPedalboardChanged(int64_t clientId, const Pedalboard &pedalboard) = 0;
virtual void OnPresetsChanged(int64_t clientId, const PresetIndex &presets) = 0;
virtual void OnPresetChanged(bool changed) = 0;
virtual void OnSnapshotModified(int64_t selectedSnapshot, bool modified) = 0;
virtual void OnSelectedSnapshotChanged(int64_t selectedSnapshot) = 0;
virtual void OnPluginPresetsChanged(const std::string &pluginUri) = 0;
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection &channelSelection) = 0;
@@ -180,6 +181,7 @@ namespace pipedal
std::vector<std::shared_ptr<IPiPedalModelSubscriber>> subscribers;
void SetPresetChanged(int64_t clientId, bool value, bool changeSnapshotSelect = true);
void FireSnapshotModified(int64_t snapshotIndex, bool modified);
void FireSelectedSnapshotChanged(int64_t selectedSnapshot);
void FirePresetsChanged(int64_t clientId);
void FirePresetChanged(bool changed);
+22
View File
@@ -396,6 +396,19 @@ JSON_MAP_REFERENCE(SetSnapshotsBody, snapshots)
JSON_MAP_REFERENCE(SetSnapshotsBody, selectedSnapshot)
JSON_MAP_END()
class SnapshotModifiedBody {
public:
int64_t snapshotIndex_;
bool modified_;
DECLARE_JSON_MAP(SnapshotModifiedBody);
};
JSON_MAP_BEGIN(SnapshotModifiedBody)
JSON_MAP_REFERENCE(SnapshotModifiedBody, snapshotIndex)
JSON_MAP_REFERENCE(SnapshotModifiedBody, modified)
JSON_MAP_END()
class ChannelSelectionChangedBody
{
public:
@@ -1762,6 +1775,15 @@ private:
body.jackChannelSelection_ = const_cast<JackChannelSelection *>(&channelSelection);
Send("onChannelSelectionChanged", body);
}
virtual void OnSnapshotModified(int64_t snapshotIndex, bool modified)
{
SnapshotModifiedBody body;
body.snapshotIndex_ = snapshotIndex;
body.modified_ = modified;
Send("onSnapshotModified", body);
}
virtual void OnSelectedSnapshotChanged(int64_t selectedSnapshot) override
{
Send("onSelectedSnapshotChanged", selectedSnapshot);
+3 -3
View File
@@ -722,7 +722,7 @@ void Storage::SaveChannelSelection()
try
{
pipedal::ofstream_synced s(fileName);
json_writer writer(s, true);
json_writer writer(s, false);
writer.write(this->jackChannelSelection);
}
catch (const std::exception &e)
@@ -933,7 +933,7 @@ void Storage::SaveUserSettings()
{
throw PiPedalException("Unable to write to " + ((std::string)path));
}
json_writer writer(f, true);
json_writer writer(f, false);
writer.write(userSettings);
}
}
@@ -1399,7 +1399,7 @@ void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfi
f.open(fileName);
if (f.is_open())
{
json_writer writer(f, true);
json_writer writer(f, false);
writer.write(jackConfiguration);
}
#if JACK_HOST
+37
View File
@@ -936,6 +936,20 @@ namespace pipedal
return result.str();
}
static void splitAddressAndPort(const std::string &address, std::string&addrOnly, std::string&port)
{
size_t portPos = address.length();
for (size_t i = address.length(); i > 0; --i)
{
if (address[i-1] == ':')
{
portPos = i-1;
break;
}
}
addrOnly = address.substr(0,portPos);
port = address.substr(portPos);
}
void on_http(connection_hdl hdl)
{
// Upgrade our connection handle to a full connection_ptr
@@ -974,6 +988,28 @@ namespace pipedal
std::string fromAddress = SS(con->get_remote_endpoint());
// redirect requests to IPV6 local connections to a better address.
std::string addrOnly,portStr;
splitAddressAndPort(fromAddress,addrOnly,portStr);
std::string nonLinkLocalUrl;
if (RemapLinkLocalUrl(con->get_socket().remote_endpoint().address(), con->get_uri()->str(),&nonLinkLocalUrl))
{
try {
Lv2Log::info(SS("Redirecting " << fromAddress << " to " << nonLinkLocalUrl));
res.keepAlive(false);
res.set(HttpField::location,nonLinkLocalUrl.c_str());
con->set_status(websocketpp::http::status_code::temporary_redirect);
res.setBody("");
return;
} catch (const std::exception&e)
{
ServerError(*con,"Invalid request on link-local address.");
return;
}
}
if (req.method() == HttpVerb::options)
{
res.set(HttpField::access_control_allow_origin, origin);
@@ -984,6 +1020,7 @@ namespace pipedal
return;
}
for (auto requestHandler : this->request_handlers)
{
+1
View File
@@ -70,6 +70,7 @@ public:
constexpr static const char* origin = "Origin";
constexpr static const char* date = "Date";
constexpr static const char* referer = "Referer";
constexpr static const char * location = "Location";
};
+2 -2
View File
@@ -74,12 +74,12 @@ std::vector<WifiChannelSelector> pipedal::getWifiChannelSelectors(const char*cou
if (forCommandline) {
WifiChannelSelector autoSelect;
autoSelect.channelId_ = "0";
autoSelect.channelName_ = "0 Select automatically";
autoSelect.channelName_ = "0 Automatic";
result.push_back(autoSelect);
} else {
WifiChannelSelector autoSelect;
autoSelect.channelId_ = "0";
autoSelect.channelName_ = "Select automatically";
autoSelect.channelName_ = "Automatic";
result.push_back(autoSelect);
}
WifiInfo wifiInfo = getWifiInfo(countryIso3661);
+1 -1
View File
@@ -72,7 +72,7 @@ void SignPackage()
cout << "--------------------------------------------------------------" << endl;
std::string signCmd =
SS("/usr/bin/gpg --yes --default-key " << UPDATE_GPG_FINGERPRINT2
SS("/usr/bin/gpg --pinentry-mode loopback --yes --default-key " << UPDATE_GPG_FINGERPRINT2
<< " --armor --output " << packagePath << ".asc"
<< " --detach-sign " << packagePath.c_str());
int result = system(signCmd.c_str());
+3 -11
View File
@@ -1,16 +1,8 @@
Main menu on Pixel 5 client.
x1 bank menu (landscape pixel 5) should show current bank.
Review current handleNotifyPatchProperty. I tink server-side support needs to be
removed, and rely on handleNotifyPathPatchPropertyChanged instead.
Restart discover on network change?
`private handleNotifyPatchProperty(clientHandle: number, instanceId: number, propertyUri: string, jsonObject: any) {
Midi bindings.
revamp shutdown procedure for the audio thread.
write a marker on exit, and don't release
audioHost UNTIL the service thread has read the close marker.
-verify clean removal of dhcpcd and nm_p2p2d