+
+ The GuitarML Tone Library Project provides
+ a substantial collection of high-quality neural amp models for use in plugins that use the ML
+ model file format.
+
+
+ To use GuitarML models with TooB ML, you must first download the collection of models from the Guitar ML website
+ by clicking on the link. This should download a file named "Proteus_Tone_Packs.zip" into your browser's Downloads directory.
+ You must then upload the downloaded zip file, found in your browser's Download directory to the
+ Pipedal server using the Upload button in PiPedal's file browser dialog.
+ PiPedal automatically extracts bundles of model files from zip archives, so manual extraction is unnecessary.
+
+
+ Please consider sponsoring the GuitarML project in order to support the ongoing development of the Tone Library.
+
+
+
+
+
+
+
+
+ );
+}
+export default GuitarMlHelpDialog;
\ No newline at end of file
diff --git a/vite/src/pipedal/JackStatusView.tsx b/vite/src/pipedal/JackStatusView.tsx
index b47d96a..e8530a9 100644
--- a/vite/src/pipedal/JackStatusView.tsx
+++ b/vite/src/pipedal/JackStatusView.tsx
@@ -70,9 +70,9 @@ export default class JackStatusView extends React.Component
{JackHostStatus.getDisplayView("",this.state.jackStatus) }
diff --git a/vite/src/pipedal/LinkEx.tsx b/vite/src/pipedal/LinkEx.tsx
new file mode 100644
index 0000000..6b82c4d
--- /dev/null
+++ b/vite/src/pipedal/LinkEx.tsx
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2025 Robin E. R. Davies
+ * All rights reserved.
+
+ * 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 Link, {LinkProps} from '@mui/material/Link';
+import {PiPedalModel,PiPedalModelFactory} from './PiPedalModel';
+
+export interface LinkExProps extends LinkProps {
+
+}
+function LinkEx(props: LinkExProps) {
+ const { href, ...rest } = props;
+
+ function handleClick(event: React.MouseEvent) {
+ const model:PiPedalModel = PiPedalModelFactory.getInstance();
+
+ // Prevent default behavior if needed
+ if (model.isAndroidHosted()) {
+ model.androidHost?.launchExternalUrl(href as string);
+ } else {
+ window.open(href as string, '_blank', 'noopener,noreferrer');
+ }
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ return (
+ )=>{ handleClick(e); }} component="span"
+ {...rest} />
+
+ )
+}
+
+export default LinkEx;
\ No newline at end of file
diff --git a/vite/src/pipedal/PerformanceView.tsx b/vite/src/pipedal/PerformanceView.tsx
index db346c1..c05142b 100644
--- a/vite/src/pipedal/PerformanceView.tsx
+++ b/vite/src/pipedal/PerformanceView.tsx
@@ -79,6 +79,7 @@ interface PerformanceViewState {
presets: PresetIndex;
banks: BankIndex;
showSnapshotEditor: boolean;
+ showStatusMonitor: boolean;
snapshotEditorIndex: number;
presetModified: boolean;
}
@@ -100,15 +101,24 @@ export const PerformanceView =
banks: this.model.banks.get(),
wrapSelects: false,
showSnapshotEditor: false,
+ showStatusMonitor: this.model.showStatusMonitor.get(),
snapshotEditorIndex: 0,
presetModified: this.model.presetChanged.get()
};
this.onPresetsChanged = this.onPresetsChanged.bind(this);
this.onBanksChanged = this.onBanksChanged.bind(this);
this.onPresetChangedChanged = this.onPresetChangedChanged.bind(this);
+ this.showStatusMonitorHandler = this.showStatusMonitorHandler.bind(this);
}
+
+ showStatusMonitorHandler() {
+ this.setState({
+ showStatusMonitor: this.model.showStatusMonitor.get()
+ });
+ }
+
getTag() { return "performView"}
isOpen() { return this.props.open }
onDialogStackClose() {
@@ -156,6 +166,8 @@ export const PerformanceView =
this.model.presets.addOnChangedHandler(this.onPresetsChanged);
this.model.banks.addOnChangedHandler(this.onBanksChanged);
this.model.presetChanged.addOnChangedHandler(this.onPresetChangedChanged)
+ this.model.showStatusMonitor.addOnChangedHandler(this.showStatusMonitorHandler);
+
this.setState({
presets: this.model.presets.get(),
banks: this.model.banks.get(),
@@ -173,6 +185,8 @@ export const PerformanceView =
this.model.presetChanged.removeOnChangedHandler(this.onPresetChangedChanged)
this.model.presets.removeOnChangedHandler(this.onPresetsChanged);
this.model.banks.removeOnChangedHandler(this.onBanksChanged);
+ this.model.banks.removeOnChangedHandler(this.showStatusMonitorHandler);
+
this.mounted = false;
this.updateHooks();
@@ -393,10 +407,10 @@ export const PerformanceView =
{ return this.handleOnEdit(index); }} />
-
- {!this.state.showSnapshotEditor && (
- )
- }
+
+ {!this.state.showSnapshotEditor && this.state.showStatusMonitor && (
+
+ )}
{this.state.showSnapshotEditor && (
= 0) {
+ this.hostName = hostVersion.substring(0, pos).trim();
+ remainder = hostVersion.substring(pos + 1).trim();
+ } else {
+ this.hostName = "Unknown Host";
+ remainder = "v0.0.0";
+ }
+
+ const args = remainder.split(","); // make make provisions for more.
+ if (args.length >= 1)
+ {
+ let versionString = args[0].trim();
+ if (versionString.startsWith('v')) {
+ versionString = versionString.substring(1).trim();
+ }
+ let releaseTypePos = versionString.indexOf("-");
+ if (releaseTypePos >= 0) {
+ this.releaseType = versionString.substring(releaseTypePos + 1).trim();
+ versionString = versionString.substring(0, releaseTypePos).trim();
+ }
+ else {
+ this.releaseType = "Release";
+ }
+
+ const parts = args[0].split(".");
+ if (parts.length >= 3) {
+ this.majorVersion = parseInt(parts[0]);
+ this.minorVersion = parseInt(parts[1]);
+ this.buildNumber = parseInt(parts[2]);
+ }
+ }
+ }
+ lessThan(major: number, minor: number, build: number): boolean {
+ if (this.majorVersion < major) return true;
+ if (this.majorVersion > major) return false;
+ if (this.minorVersion < minor) return true;
+ if (this.minorVersion > minor) return false;
+ return this.buildNumber < build;
+ }
+ versionString: string = "";
+ hostName: string = "";
+ releaseType: string = "";
+ majorVersion: number = 0;
+ minorVersion: number = 0;
+ buildNumber: number = 0;
+}
export type PedalboardItemEnabledChangeCallback = (instanceId: number, isEnabled: boolean) => void;
interface PedalboardItemEnabledChangeItem {
handle: number;
@@ -440,6 +494,12 @@ export class PiPedalModel //implements PiPedalModel
hasTone3000Auth: ObservableProperty = new ObservableProperty(false);
+ canKeepScreenOn: boolean = false;
+ keepScreenOn: ObservableProperty = new ObservableProperty(false);
+
+ canSetScreenOrientation: boolean = false;
+ screenOrientation: ObservableProperty = new ObservableProperty(ScreenOrientation.SystemDefault);
+
hasWifiDevice: ObservableProperty = new ObservableProperty(false);
onSnapshotModified: ObservableEvent = new ObservableEvent();
@@ -504,6 +564,7 @@ export class PiPedalModel //implements PiPedalModel
}
androidHost?: AndroidHostInterface;
+ hostVersion?: HostVersion;
constructor() {
this.androidHost = (window as any).AndroidHost as AndroidHostInterface;
@@ -1295,6 +1356,17 @@ export class PiPedalModel //implements PiPedalModel
.then((succeeded) => {
if (succeeded) {
this.state.set(State.Ready);
+ if (this.androidHost) {
+ this.hostVersion = new HostVersion(this.androidHost.getHostVersion());
+ if (!this.hostVersion.lessThan(1,1,16))
+ {
+ this.androidHost.setServerVersion(this.serverVersion?.serverVersion??"");
+ this.canKeepScreenOn = true;
+ this.keepScreenOn.set(this.androidHost.getKeepScreenOn())
+ this.canSetScreenOrientation = true;
+ this.screenOrientation.set(this.androidHost.getScreenOrientation())
+ }
+ }
}
})
.catch((error) => {
@@ -3412,6 +3484,27 @@ export class PiPedalModel //implements PiPedalModel
return false;
}
+ setKeepScreenOn(keepScreenOn: boolean): void {
+ if (this.canKeepScreenOn) {
+ this.keepScreenOn.set(keepScreenOn);
+ if (this.androidHost) {
+ this.androidHost.setKeepScreenOn(keepScreenOn);
+ }
+ }
+ }
+ getKeepScreenOn(): boolean {
+ return this.keepScreenOn.get();
+ }
+
+ getScreenOrientation(): ScreenOrientation {
+ return this.screenOrientation.get();
+ }
+ setScreenOrientation(value: ScreenOrientation) {
+ if (this.canSetScreenOrientation) {
+ this.screenOrientation.set(value);
+ this.androidHost?.setScreenOrientation(value as number);
+ }
+ }
};
let instance: PiPedalModel | undefined = undefined;
diff --git a/vite/src/pipedal/ScreenOrientation.tsx b/vite/src/pipedal/ScreenOrientation.tsx
new file mode 100644
index 0000000..05f3465
--- /dev/null
+++ b/vite/src/pipedal/ScreenOrientation.tsx
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2025 Robin E. R. Davies
+ * All rights reserved.
+
+ * 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.
+ */
+
+enum ScreenOrientation {
+ // values must match Pipedal Remote ScreenOrientation.java
+ SystemDefault = 0,
+ Landscape = 1,
+ Portrait = 2
+}
+
+export default ScreenOrientation;
\ No newline at end of file
diff --git a/vite/src/pipedal/SelectScreenOrientationDialog.tsx b/vite/src/pipedal/SelectScreenOrientationDialog.tsx
new file mode 100644
index 0000000..6a002bf
--- /dev/null
+++ b/vite/src/pipedal/SelectScreenOrientationDialog.tsx
@@ -0,0 +1,74 @@
+// 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.
+
+import List from '@mui/material/List';
+import ListItemText from '@mui/material/ListItemText';
+import DialogEx from './DialogEx';
+
+import { ListItemButton } from '@mui/material';
+import ScreenOrientation from './ScreenOrientation';
+
+
+
+export interface SelectScreenOrientationDialogProps {
+ open: boolean;
+ screenOrientation: ScreenOrientation;
+ onClose: (screenOrientation: ScreenOrientation | null) => void;
+}
+
+
+function SelectScreenOrientationDialog(props: SelectScreenOrientationDialogProps) {
+ //const classes = useStyles();
+ const { onClose, screenOrientation, open } = props;
+
+ const handleCancel = () => {
+ onClose(null);
+ }
+ const handleOk = (value: ScreenOrientation) => {
+ onClose(value);
+ };
+
+ const handleListItemClick = (value: ScreenOrientation) => {
+ handleOk(value);
+ };
+
+ return (
+ { }}
+ >
+
+
+ handleListItemClick(ScreenOrientation.SystemDefault)} key={"0"}
+ selected={screenOrientation === ScreenOrientation.SystemDefault} >
+
+
+ handleListItemClick(ScreenOrientation.Portrait)} key={"1"}
+ selected={screenOrientation === ScreenOrientation.Portrait} >
+
+
+ handleListItemClick(ScreenOrientation.Landscape)} key={"2"}
+ selected={screenOrientation === ScreenOrientation.Landscape} >
+
+
+
+
+ );
+}
+
+export default SelectScreenOrientationDialog;
diff --git a/vite/src/pipedal/SettingsDialog.tsx b/vite/src/pipedal/SettingsDialog.tsx
index aa51806..cef8440 100644
--- a/vite/src/pipedal/SettingsDialog.tsx
+++ b/vite/src/pipedal/SettingsDialog.tsx
@@ -46,10 +46,10 @@ import DialogEx from './DialogEx'
import GovernorSettings from './GovernorSettings';
import SystemMidiBindingsDialog from './SystemMidiBindingsDialog';
import SelectThemeDialog from './SelectThemeDialog';
-import {AlsaSequencerConfiguration} from './AlsaSequencer';
+import { AlsaSequencerConfiguration } from './AlsaSequencer';
import Slide, { SlideProps } from '@mui/material/Slide';
-import {createStyles} from './WithStyles';
+import { createStyles } from './WithStyles';
import { Theme } from '@mui/material/styles';
import { withStyles } from "tss-react/mui";
@@ -57,6 +57,8 @@ import WithStyles from './WithStyles';
import { canScaleWindow, getWindowScaleOptions, getWindowScaleText, setWindowScale, getWindowScale } from './WindowScale';
import OptionsDialog from './OptionsDialog';
import { css } from '@emotion/react';
+import ScreenOrientation from './ScreenOrientation';
+import SelectScreenOrientationDialog from './SelectScreenOrientationDialog';
interface SettingsDialogProps extends WithStyles {
@@ -74,6 +76,8 @@ interface SettingsDialogState {
jackSettings: JackChannelSelection;
jackServerSettings: JackServerSettings;
alsaSequencerConfiguration: AlsaSequencerConfiguration;
+ keepScreenOn: boolean;
+ screenOrientation: ScreenOrientation;
jackStatus?: JackHostStatus;
governorSettings: GovernorSettings;
@@ -88,6 +92,7 @@ interface SettingsDialogState {
showGovernorSettingsDialog: boolean;
showInputSelectDialog: boolean;
showOutputSelectDialog: boolean;
+ showScreenOrientationDialog: boolean;
showMidiSelectDialog: boolean;
showThemeSelectDialog: boolean;
showJackServerSettingsDialog: boolean;
@@ -191,13 +196,15 @@ const SettingsDialog = withStyles(
wifiDirectConfigSettings: this.model.wifiDirectConfigSettings.get(),
governorSettings: this.model.governorSettings.get(),
continueDisabled: true,
-
+ keepScreenOn: this.model.keepScreenOn.get(),
+ screenOrientation: this.model.getScreenOrientation(),
showWindowScaleDialog: false,
showWifiConfigDialog: false,
showWifiDirectConfigDialog: false,
showGovernorSettingsDialog: false,
showInputSelectDialog: false,
showOutputSelectDialog: false,
+ showScreenOrientationDialog: false,
showMidiSelectDialog: false,
showThemeSelectDialog: false,
showJackServerSettingsDialog: false,
@@ -219,11 +226,21 @@ const SettingsDialog = withStyles(
this.handleConnectionStateChanged = this.handleConnectionStateChanged.bind(this);
this.handleShowStatusMonitorChanged = this.handleShowStatusMonitorChanged.bind(this);
this.handleHasWifiChanged = this.handleHasWifiChanged.bind(this);
+ this.handleKeepScreenOnChanged = this.handleKeepScreenOnChanged.bind(this);
+ this.handleScreenOrientationChanged = this.handleScreenOrientationChanged.bind(this);
}
+ handleScreenOrientationChanged(newValue: ScreenOrientation) {
+ this.setState({
+ screenOrientation: newValue
+ });
+ }
+ handleKeepScreenOnChanged(newValue: boolean) {
+ this.setState({ keepScreenOn: newValue });
+ }
handleHasWifiChanged(newValue: boolean) {
- this.setState({hasWifiDevice: newValue});
+ this.setState({ hasWifiDevice: newValue });
}
handleShowStatusMonitorChanged(): void {
@@ -337,6 +354,8 @@ const SettingsDialog = withStyles(
if (active !== this.active) {
this.active = active;
if (active) {
+ this.model.keepScreenOn.addOnChangedHandler(this.handleKeepScreenOnChanged);
+ this.model.screenOrientation.addOnChangedHandler(this.handleScreenOrientationChanged);
this.model.hasWifiDevice.addOnChangedHandler(this.handleHasWifiChanged);
this.model.state.addOnChangedHandler(this.handleConnectionStateChanged);
this.model.showStatusMonitor.addOnChangedHandler(this.handleShowStatusMonitorChanged);
@@ -368,7 +387,7 @@ const SettingsDialog = withStyles(
this.handleJackServerSettingsChanged();
this.handleWifiConfigSettingsChanged();
this.handleWifiDirectConfigSettingsChanged();
- this.setState({hasWifiDevice: this.model.hasWifiDevice.get()});
+ this.setState({ hasWifiDevice: this.model.hasWifiDevice.get() });
this.timerHandle = setInterval(() => this.tick(), 1000);
} else {
@@ -377,6 +396,8 @@ const SettingsDialog = withStyles(
}
this.model.state.removeOnChangedHandler(this.handleConnectionStateChanged);
this.model.showStatusMonitor.removeOnChangedHandler(this.handleShowStatusMonitorChanged);
+ this.model.keepScreenOn.removeOnChangedHandler(this.handleKeepScreenOnChanged);
+ this.model.screenOrientation.removeOnChangedHandler(this.handleScreenOrientationChanged);
this.model.jackConfiguration.removeOnChangedHandler(this.handleJackConfigurationChanged);
this.model.alsaSequencerConfiguration.removeOnChangedHandler(this.handleAlsaSequencerConfigurationChanged);
@@ -441,6 +462,28 @@ const SettingsDialog = withStyles(
showOutputSelectDialog: false
});
}
+ handleScreenOrientation() {
+ this.setState({
+ showScreenOrientationDialog: true,
+ });
+ }
+ getOrientationText() {
+ switch (this.state.screenOrientation) {
+ case ScreenOrientation.Portrait:
+ return "Portrait";
+ case ScreenOrientation.Landscape:
+ return "Landscape";
+ default:
+ return "System default";
+ }
+ }
+ handleScreenOrientationDialogResult(orientation: ScreenOrientation | null): void {
+ if (orientation !== null) {
+ this.setState({ screenOrientation: orientation });
+ this.model.setScreenOrientation(orientation);
+ }
+ this.setState({ showScreenOrientationDialog: false });
+ }
handleOutputSelection() {
this.setState({
@@ -548,11 +591,13 @@ const SettingsDialog = withStyles(
}
render() {
const classes = withStyles.getClasses(this.props);
-
+
let isConfigValid = this.state.jackConfiguration.isValid;
let selectedChannels: string[] = this.state.showInputSelectDialog ? this.state.jackSettings.inputAudioPorts : this.state.jackSettings.outputAudioPorts;
let disableShutdown = this.state.shuttingDown || this.state.restarting;
+ let canKeepScreenOn = this.model.canKeepScreenOn;
+
return (
{ this.props.onClose() }} TransitionComponent={Transition}
@@ -730,6 +775,51 @@ const SettingsDialog = withStyles(
+ {canKeepScreenOn &&
+ (
+ {
+ this.model.setKeepScreenOn(!this.state.keepScreenOn)
+ }} >
+
+