diff --git a/CMakeLists.txt b/CMakeLists.txt index f4448b6..9b349d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,13 +1,13 @@ cmake_minimum_required(VERSION 3.16.0) project(pipedal - VERSION 1.5.93 + VERSION 1.5.94 DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi" HOMEPAGE_URL "https://rerdavies.github.io/pipedal" ) execute_process( COMMAND dpkg --print-architecture COMMAND tr -d '\n' OUTPUT_VARIABLE DEBIAN_ARCHITECTURE ) -set (DISPLAY_VERSION "PiPedal v1.5.93-Experimental") +set (DISPLAY_VERSION "PiPedal v1.5.94-Experimental") set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE}) set (CMAKE_INSTALL_PREFIX "/usr/") diff --git a/README.md b/README.md index b2416e1..e9be1df 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,11 @@ -Download: v1.5.93 +Download: v1.5.94 Website: [https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal). Documentation: [https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html). -#### NEW version 1.5.93 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. TooB NAM calibration, manipulation of i/r files in Toob Convolution Reverb, new Parametric EQ and 3 Band EQ plugins, and MORE! +#### NEW version 1.5.94 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. TooB NAM calibration, manipulation of i/r files in Toob Convolution Reverb, new Parametric EQ and 3 Band EQ plugins, and MORE!   diff --git a/docs/Installing.md b/docs/Installing.md index 25bd308..ce9e32b 100644 --- a/docs/Installing.md +++ b/docs/Installing.md @@ -13,18 +13,18 @@ page_icon: img/Install4.jpg Download the most recent Debian (.deb) package for your platform: -- [Raspberry Pi OS bookworm (aarch64) v1.5.93](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_arm64.deb) -- [Ubuntu 24.04 through 25.04 (aarch64) v1.5.93](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_arm64.deb) -- [Ubuntu 24.04 through 25.04 (amd64) v1.5.93](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_amd64.deb) +- [Raspberry Pi OS bookworm (aarch64) v1.5.94](https://github.com/rerdavies/pipedal/releases/download/v1.5.94/pipedal_1.5.94_arm64.deb) +- [Ubuntu 24.04 through 25.04 (aarch64) v1.5.94](https://github.com/rerdavies/pipedal/releases/download/v1.5.94/pipedal_1.5.94_arm64.deb) +- [Ubuntu 24.04 through 25.04 (amd64) v1.5.94](https://github.com/rerdavies/pipedal/releases/download/v1.5.94/pipedal_1.5.94_amd64.deb) -Version 1.5.93 has been tested on Raspberry Pi OS bookworm, Ubuntu 24.04 (amd64), Ubuntu 24.10 (aarch64), and Ubuntu 25.04 (aarch64). Download the appropriate package for your platform, and install using the following procedure: +Version 1.5.94 has been tested on Raspberry Pi OS bookworm, Ubuntu 24.04 (amd64), Ubuntu 24.10 (aarch64), and Ubuntu 25.04 (aarch64). Download the appropriate package for your platform, and install using the following procedure: ``` sudo apt update sudo apt upgrade cd ~/Downloads - sudo apt-get install ./pipedal_1.5.93_arm64.deb + sudo apt-get install ./pipedal_1.5.94_arm64.deb ``` You MUST use `apt-get`. `apt` will not install downloaded packages; and `dpkg -i` will not install dependencies. diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index cc7141c..5674902 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -1,5 +1,12 @@ # Release Notes +## PiPedal 1.5.94 Beta + +### Major Features in This Build + +- **Sidechains**. You can now load plugins that have sidechain inputs. This allows you to use compressor and gate plugins that often have +sidechains. Plugins that have sidechain inputs will show a dropdown control in their control view which allows you to select the source of the sidechain input. The source plugin must for the sidechain must appear earlier in the signal chain. + ## PiPedal 1.5.93 Release ### Major Features in This Build diff --git a/docs/download.md b/docs/download.md index c54df26..e3fdef9 100644 --- a/docs/download.md +++ b/docs/download.md @@ -4,9 +4,9 @@ Download the most recent Debian (.deb) package for your platform: -- [Raspberry Pi OS bookworm (aarch64) v1.5.93 Beta](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_arm64.deb) -- [Ubuntu 24.x, 25.04 (aarch64) v1.5.93 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_arm64.deb) -- [Ubuntu 24.x, 25.04 (amd64) v1.5.93 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_amd64.deb) +- [Raspberry Pi OS bookworm (aarch64) v1.5.94 Beta](https://github.com/rerdavies/pipedal/releases/download/v1.5.94/pipedal_1.5.94_arm64.deb) +- [Ubuntu 24.x, 25.04 (aarch64) v1.5.94 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.94/pipedal_1.5.94_arm64.deb) +- [Ubuntu 24.x, 25.04 (amd64) v1.5.94 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.94/pipedal_1.5.94_amd64.deb) Install the package by running @@ -14,7 +14,7 @@ Install the package by running ``` sudo apt update cd ~/Downloads - sudo apt-get install ./pipedal_1.5.93_arm64.deb + sudo apt-get install ./pipedal_1.5.94_arm64.deb ``` You MUST use `apt-get` to install the package. `apt install` will NOT install the package correctly. The message about missing permissions given by `apt-get` is expected, and can be safely ignored. diff --git a/vite/src/pipedal/AutoZoom.tsx b/vite/src/pipedal/AutoZoom.tsx index 2914403..8e50e90 100644 --- a/vite/src/pipedal/AutoZoom.tsx +++ b/vite/src/pipedal/AutoZoom.tsx @@ -24,17 +24,40 @@ import { withStyles } from "tss-react/mui"; import { createStyles } from './WithStyles'; import FullscreenIcon from '@mui/icons-material/Fullscreen'; import IconButtonEx from './IconButtonEx'; -import { isDarkMode } from './DarkMode'; import Backdrop from '@mui/material/Backdrop'; -import CloseIcon from '@mui/icons-material/Close'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; + import Rect from './Rect'; +import { css } from '@emotion/react'; + import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; const styles = (theme: Theme) => createStyles({ - pgraph: { + pgraph: css({ paddingBottom: 16 - } + }), + backdrop: css({ + zIndex: 1101, + overflow: "clip", + background: theme.palette.background.default, + + }), + noToolbar: css({ + display: "none" + }), + topToolBar: css({ + position: "absolute", + zIndex: 1101, + top: 0, + display: "flex", flexFlow: "row nowrap", justifyContent: "start", columnGap: 8, + }), + rightToolbar: css({ + position: "absolute", + zIndex: 1101, + top: 24, + display: "flex", flexFlow: "column nowrap", rowGap: 8 + }) }); @@ -42,6 +65,7 @@ export interface AutoZoomProps extends WithStyles { leftPad: number, rightPad: number, children?: React.ReactNode, + toolbarChildren?: React.ReactNode[], contentReady: boolean, showZoomed: boolean, setShowZoomed: (value: boolean) => void; @@ -53,6 +77,15 @@ export interface AutoZoomState { } +// non-react zoom state. +class UnzoomedLayout { + horizontalToolbar = false; + effectiveClientRect: Rect = new Rect(); + showToolbar = false; + zoom: number = 1; + showZoomButton = false; + contentRect: Rect = new Rect(); +} const AutoZoom = withStyles( class extends ResizeResponsiveComponent { @@ -97,38 +130,43 @@ const AutoZoom = withStyles( if (this.normalRef) { this.updateZoom(this.normalRef); } - if (this.maximizedRef) { - this.updateMaximizedZoom(); - } } } resizeObserver: ResizeObserver | null = null; - calculateZoomedClientRect( + calculateUnzoomedLayout( clientRect: Rect, contentWidth: number, contentHeight: number, buttonPadding: number - ): Rect { + ): UnzoomedLayout { + + let result = new UnzoomedLayout(); - console.log("Zoom: " + clientRect.toString() + " content: " + contentWidth + "x" + contentHeight + " buttonPadding: " + buttonPadding); let rightButtonZoom = Math.min((clientRect.width - buttonPadding) / contentWidth, clientRect.height / contentHeight); let topButtonZoom = Math.min((clientRect.width - buttonPadding) / contentWidth, (clientRect.height - buttonPadding) / contentHeight); let effectiveClientRect = clientRect.copy(); if (rightButtonZoom > topButtonZoom) { + result.horizontalToolbar = false; effectiveClientRect.width -= buttonPadding; } else { + result.horizontalToolbar = true; effectiveClientRect.y += buttonPadding; effectiveClientRect.height -= buttonPadding; } + result.effectiveClientRect = effectiveClientRect; let zoom = Math.min(effectiveClientRect.width / contentWidth, effectiveClientRect.height / contentHeight); if (zoom > 1) { zoom = 1; } + result.zoom = zoom; + result.showZoomButton = zoom !== 1; + result.showToolbar = result.showZoomButton || !!this.props.toolbarChildren; + let leftPad = (effectiveClientRect.width - contentWidth * zoom) / 2; if (leftPad < 0) { leftPad = 0; @@ -137,7 +175,8 @@ const AutoZoom = withStyles( if (topPad < 0) { topPad = 0; } - return new Rect(effectiveClientRect.x + leftPad, effectiveClientRect.y + topPad, contentWidth * zoom, contentHeight * zoom); + result.contentRect = new Rect(effectiveClientRect.x + leftPad, effectiveClientRect.y + topPad, contentWidth * zoom, contentHeight * zoom); + return result; } updateZoom(ref: HTMLDivElement) { @@ -145,59 +184,58 @@ const AutoZoom = withStyles( return; } let content = ref.firstElementChild as HTMLElement | null; + const classes = withStyles.getClasses(this.props); if (content) { const { leftPad, rightPad } = this.props; let bounds = new Rect(leftPad, 0, ref.clientWidth - leftPad - rightPad, ref.clientHeight - 8); - let zoomedClientRect = this.calculateZoomedClientRect( + let layout = this.calculateUnzoomedLayout( bounds, content.clientWidth, content.clientHeight, 48); + + let contentRect = layout.contentRect; + content.style.left = "0px"; content.style.top = "0px"; content.style.transformOrigin = "0 0"; // top-left corner content.style.transform = - `translate(${zoomedClientRect.x}px, ${zoomedClientRect.y}px) scale(${zoomedClientRect.width / content.clientWidth}, ${zoomedClientRect.height / content.clientHeight})`; + `translate(${contentRect.x}px, ${contentRect.y}px) scale(${layout.zoom}, ${layout.zoom})`; - if (this.zoomButton) { - this.zoomButton.style.display = zoomedClientRect.width < content.clientWidth ? "block" : "none"; - let rightSizeSpace = ref.clientWidth - zoomedClientRect.right; - if (rightSizeSpace - rightPad >= 48) { - this.zoomButton.style.top = `${zoomedClientRect.y + 24}px`; - this.zoomButton.style.left = `${zoomedClientRect.right + 4}px`; + if (this.zoomToolbar) { + if (!layout.showToolbar) { + + this.zoomToolbar.className = classes.noToolbar; + } else if (layout.horizontalToolbar) { + this.zoomToolbar.className = classes.topToolBar; + this.zoomToolbar.style.left = `${contentRect.x +16}px` + this.zoomToolbar.style.top = `${contentRect.y - 48}px`; + this.zoomToolbar.style.width = `${contentRect.width}px`; } else { - this.zoomButton.style.top = `${zoomedClientRect.y - 48}px`; - this.zoomButton.style.left = `${zoomedClientRect.right - 64 - 12}px`; + this.zoomToolbar.className = classes.rightToolbar; + this.zoomToolbar.style.left = ""; + this.zoomToolbar.style.top = `${contentRect.y}px`; + this.zoomToolbar.style.left = `${contentRect.right + 4}px`; + this.zoomToolbar.style.width = ""; } } + if (this.zoomButton) { + this.zoomButton.style.display = layout.showZoomButton ? "block" : "none"; + } // save the position of the Mod UI in page coordinates for later zooming. - this.zoomStartRect = zoomedClientRect.copy(); + this.zoomStartRect = layout.contentRect.copy(); let refBounds = ref.getBoundingClientRect(); this.zoomStartRect.x += refBounds.left; this.zoomStartRect.y += refBounds.top; + } else { + if (this.zoomToolbar) { + this.zoomToolbar.className = classes.noToolbar; + } } } updateMaximizedZoom() { - let ref = this.maximizedRef; - if (!this.mounted || !ref) { - return; - } - let content = ref.firstElementChild as HTMLElement | null; - - if (content) { - let leftPad = 16; let rightPad = 16; - let topPad = 16, bottomPad = 16; - let bounds: Rect; - bounds = new Rect(leftPad, topPad, ref.clientWidth - leftPad - rightPad, ref.clientHeight - topPad - bottomPad); - - let zoomedClientRect = this.calculateZoomedClientRect( - bounds, content.clientWidth, content.clientHeight, 48); - content.style.left = "0px"; - content.style.top = "0px"; - content.style.transformOrigin = "0 0"; // top-left corner - content.style.transform = `translate(${zoomedClientRect.x}px, ${zoomedClientRect.y}px) scale(${zoomedClientRect.width / content.clientWidth}, ${zoomedClientRect.height / content.clientHeight})`; - //console.log("Zoomed to: ", zoomedClientRect.toString()); - + if (this.normalRef) { + this.updateZoom(this.normalRef); } } @@ -234,20 +272,20 @@ const AutoZoom = withStyles( } this.maximizedAnimateFrameHandle = window.requestAnimationFrame(() => { this.maximizedAnimateFrameHandle = null; - if (this.maximizedRef) { - this.updateMaximizedZoom(); - } + this.updateMaximizedZoom(); }); } + private zoomToolbar: HTMLElement | null = null; private zoomButton: HTMLElement | null = null; private normalRef: HTMLDivElement | null = null; - setNormalRef(ref: HTMLDivElement | null) { + setContentRef(ref: HTMLDivElement | null) { this.normalRef = ref; if (ref) { - this.zoomButton = ref.querySelector("#maximize-button") as HTMLDivElement | null; + this.zoomToolbar = ref.querySelector("#zoom-toolbar") as HTMLDivElement | null; + this.zoomButton = ref.querySelector("#zoom-button") as HTMLDivElement | null; this.resizeObserver = new ResizeObserver(() => { this.requestZoomUpdate(); }); @@ -261,36 +299,15 @@ const AutoZoom = withStyles( if (this.resizeObserver) { this.resizeObserver.disconnect(); this.resizeObserver = null; - this.zoomButton = null; + this.zoomToolbar = null; } this.zoomStartRect = null; + this.zoomToolbar = null; + this.zoomButton = null; } } - private maximizedRef: HTMLDivElement | null = null; - private maximedResizeObserver: ResizeObserver | null = null; - - setMaximizedRef(ref: HTMLDivElement | null) { - this.maximizedRef = ref; - if (ref) { - this.maximedResizeObserver = new ResizeObserver(() => { - this.requestMaximizedZoomUpdate(); - }); - this.maximedResizeObserver.observe(ref); - let child = ref.firstElementChild as HTMLElement | null; - if (child) { - this.maximedResizeObserver.observe(child); - } - this.requestMaximizedZoomUpdate(); - } else { - if (this.maximedResizeObserver) { - this.maximedResizeObserver.disconnect(); - this.maximedResizeObserver = null; - } - } - } - private zoomStartRect: Rect | null = null; startZoom() { @@ -300,70 +317,77 @@ const AutoZoom = withStyles( this.props.setShowZoomed(true); } - render() { + content() { const classes = withStyles.getClasses(this.props); void classes; // suppress unused variable warning - const landscape = this.state.screenWidth > this.state.screenHeight; - return (
{ this.setNormalRef(ref); }}> -
- {!this.props.showZoomed && this.props.children} -
-
{ this.setContentRef(ref); }} + style={{ width: "100%", height: "100%" }} > - { - this.startZoom(); +
+ {this.props.children} +
+
- - -
- {this.props.showZoomed && ( - ({ - zIndex: this.props.showZoomed ? 1101 : -1, - overflow: "clip", - background: theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.9)' : 'rgba(255, 255, 255, 0.9)' - })} - open={this.props.showZoomed} - > -
{ this.setMaximizedRef(ref); }} - style={{ - position: "absolute", left: 0, top: 0, right: 0, bottom: 0, overflow: "hidden", - visibility: this.props.contentReady ? "visible" : "hidden" - }}> - -
- { - this.props.showZoomed && this.props.children - } -
- - { + this.startZoom(); }} + > + + + + ) : ( + { e.stopPropagation(); this.props.setShowZoomed(false); }} > - + -
+ )} + + + {this.props.toolbarChildren && this.props.toolbarChildren.map((child, index) => ( +
+ {child} +
+ ))} +
+ +
+ ); + + } + + render() { + const classes = withStyles.getClasses(this.props); + void classes; // suppress unused variable warning + + return (
+ {!this.props.showZoomed && this.content()} + + {this.props.showZoomed && ( + + {this.content()} + )}
); diff --git a/vite/src/pipedal/PluginControlView.tsx b/vite/src/pipedal/PluginControlView.tsx index 7dbd7ce..6c64e8a 100644 --- a/vite/src/pipedal/PluginControlView.tsx +++ b/vite/src/pipedal/PluginControlView.tsx @@ -22,6 +22,9 @@ import { Theme } from '@mui/material/styles'; import WithStyles, { withTheme } from './WithStyles'; import { createStyles } from './WithStyles'; import { css } from '@emotion/react'; +import IconButtonEx from './IconButtonEx'; + +import SidechainConnectIcon from './svg/ic_sidechain_open_48.svg?react'; import AutoZoom from './AutoZoom'; @@ -37,7 +40,7 @@ import { } from './Pedalboard'; import PluginControl from './PluginControl'; import ResizeResponsiveComponent from './ResizeResponsiveComponent'; -import SideChainSelectControl from './SideChainSelectControl'; +import SideChainSelectControl, {SideChainSelectDialog} from './SideChainSelectControl'; import VuMeter from './VuMeter'; import { nullCast } from './Utility' import { PiPedalStateError } from './PiPedalError'; @@ -373,11 +376,12 @@ type PluginControlViewState = { dialogFileValue: string, modGuiContentReady: boolean, showModGuiZoomed: boolean, + sidechainDialogOpen: boolean }; const PluginControlView = withTheme(withStyles( - class extends ResizeResponsiveComponent { + class extends ResizeResponsiveComponent { model: PiPedalModel; constructor(props: PluginControlViewProps) { @@ -393,7 +397,8 @@ const PluginControlView = dialogFileProperty: new UiFileProperty(), dialogFileValue: "", modGuiContentReady: false, - showModGuiZoomed: false + showModGuiZoomed: false, + sidechainDialogOpen: false } this.onPedalboardChanged = this.onPedalboardChanged.bind(this); @@ -608,15 +613,15 @@ const PluginControlView = let title = uiPlugin.audio_side_chain_title ? uiPlugin.audio_side_chain_title : "Side chain"; return ( - { this.model.setPedalboardSideChainInput(this.props.item.instanceId, instanceId); - + }} - /> + /> ); } @@ -876,6 +881,9 @@ const PluginControlView = return false; } + handleSelectSidechainInput() { + this.setState({sidechainDialogOpen: true}); + } handleModGuiFileProperty(instanceId: number, filePropertyUri: string, selectedFile: string) { let plugin = this.model.getUiPlugin(this.props.item.uri); @@ -900,10 +908,30 @@ const PluginControlView = if (!uiPlugin) { return (
); } + let toolBarChildren: ReactNode[] = []; + if (uiPlugin.audio_side_chain_inputs > 0) { + toolBarChildren.push( + { + this.handleSelectSidechainInput(); + }} + > + + + ); + } return (
{ this.setState({ showModGuiZoomed: value }); } } @@ -1085,6 +1113,16 @@ const PluginControlView = } /> )} + {this.state.sidechainDialogOpen && ( + { this.setState({sidechainDialogOpen: false});}} + selectItems={this.getSidechainSelectItems()} + selectedInstanceId={this.props.item.sideChainInputId} + onChanged={(instanceId: number) => { + this.model.setPedalboardSideChainInput(this.props.item.instanceId, instanceId); + }} + /> + )} {/* xxx: I don't think we need this anyore. */} void, + + selectItems: { instanceId: number, title: string }[]; + selectedInstanceId: number; + onChanged(instanceId: number): void; + +} +export function SideChainSelectDialog(props: SideChainSelectDialogProps) { + let { open, onClose, selectItems, selectedInstanceId, onChanged } = props; + return ( + { }} + > + + + { onClose(); }} aria-label="back" + > + + + + Sidechain Input + + + + + + + + ); +} \ No newline at end of file diff --git a/vite/src/pipedal/svg/ic_sidechain_open_48.svg b/vite/src/pipedal/svg/ic_sidechain_open_48.svg new file mode 100644 index 0000000..3a68ca5 --- /dev/null +++ b/vite/src/pipedal/svg/ic_sidechain_open_48.svg @@ -0,0 +1,17 @@ + + + + + + +