diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b349d8..a4fbce2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,13 +1,13 @@ cmake_minimum_required(VERSION 3.16.0) project(pipedal - VERSION 1.5.94 + VERSION 1.5.95 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.94-Experimental") +set (DISPLAY_VERSION "PiPedal v1.5.95-Experimental") set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE}) set (CMAKE_INSTALL_PREFIX "/usr/") diff --git a/PiPedalCommon/src/AlsaSequencer.cpp b/PiPedalCommon/src/AlsaSequencer.cpp index 4e21ee4..24d3735 100644 --- a/PiPedalCommon/src/AlsaSequencer.cpp +++ b/PiPedalCommon/src/AlsaSequencer.cpp @@ -704,6 +704,9 @@ namespace pipedal queueId = -1; throw std::runtime_error(SS("Failed to set port timestamping: " << snd_strerror(rc))); } + + // Flush the output buffer to start the queue and apply port settings + snd_seq_drain_output(seqHandle); } return queueId; diff --git a/README.md b/README.md index e9be1df..156e2a7 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,11 @@ -Download: v1.5.94 +Download: v1.5.95 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.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! +#### NEW version 1.5.95 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/build-prod.sh b/build-prod.sh index 0cf9ae4..e6352b9 100755 --- a/build-prod.sh +++ b/build-prod.sh @@ -4,7 +4,11 @@ set -e # clean build -rm -rf build + +if [ "$1" != "--continue" ]; then + rm -rf build +fi + # configure for release mkdir -p build diff --git a/docs/Installing.md b/docs/Installing.md index ce9e32b..4f53ef2 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.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) +- [Raspberry Pi OS bookworm (aarch64) v1.5.95](https://github.com/rerdavies/pipedal/releases/download/v1.5.95/pipedal_1.5.95_arm64.deb) +- [Ubuntu 24.04 through 25.04 (aarch64) v1.5.95](https://github.com/rerdavies/pipedal/releases/download/v1.5.95/pipedal_1.5.95_arm64.deb) +- [Ubuntu 24.04 through 25.04 (amd64) v1.5.95](https://github.com/rerdavies/pipedal/releases/download/v1.5.95/pipedal_1.5.95_amd64.deb) -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: +Version 1.5.95 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.94_arm64.deb + sudo apt-get install ./pipedal_1.5.95_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 389e425..5a0115c 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -1,5 +1,21 @@ # Release Notes +## PiPedal 1.5.95 Beta + +Usability tweaks that have been blocked by a pending major feature, that needed to be released to satisfy commitments made to users. Now pushed out for general use. + +### Major features +- **Tap Tempo MIDI Bindings.** Use MIDI events to set time-based controls for any plugin using tap tempo. The Tap Tempo MIDI binding is only visible for controls that have been declared as having units of seconds, milliseconds, Hz, or BPM by their owning plugin, and also have value ranges that are suitable for tap tempo controls. + To use Tap Tempo, open the _Midi Bindings_ dialog, and scroll to find a suitable control. If the control supports Tap Tempo, you will find a _Tap Tempo_ entry in the dropdown that controls the event type. + Consecutive MIDI Note ON events are measured, and the target control will be set to match the measured time between events, as long as the time fits within valid value ranges for the control (again, as declared by the owning plugin). + Tap Tempo is only implemented for MIDI bindings, because the HTML event system used in the PiPedal UI is not responsive enough to implement usable tap-tempo control in the UI. + +### Minor Features +- **Mix Plugin Phase Controls**. The TooB Mix plugin has new controls that allow independent phase inversion of left and right input channels. + +- The maximum delay for the TooB Delay plugin has been increased from 1.2 seconds to 3 seconds, in order to make it suitable for use with the new Tap Tempo feature. + + ## PiPedal 1.5.94 Beta A minor release that allows use of plugins with audio sidechains. @@ -8,7 +24,7 @@ A minor release that allows use of plugins with audio sidechains. - **Sidechains**. You can now load plugins that have sidechain inputs. This allows you to use compressor and gate plugins, particularly, that often have sidechains, but also enables a broad class of dynamics processing plugins that would not be loadable in previous versions of PiPedal. 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 for the sidechain must appear earlier in the signal chain. - Although sidechain processing is occasionally useful in guitar effect pedalboards, the more important part of this feature is that it allows you to load plugins that were rejected by previous versions of PiPedal because they had optional sidechain inputs. +Although sidechain processing is occasionally useful in guitar effect pedalboards, the more important part of this feature is that it allows you to load third-part plugins (compressors, particularly) that were ignored by previous versions of PiPedal because they had optional sidechain inputs. ## PiPedal 1.5.93 Release diff --git a/docs/Snapshots.md b/docs/Snapshots.md index 1a7e99e..1450b84 100644 --- a/docs/Snapshots.md +++ b/docs/Snapshots.md @@ -16,7 +16,7 @@ You can create up to six snapshots for any given preset. A snapshot differs from - Loading a snapshot is much faster than loading a preset, because new plugins don't have to be created. - You can configure PiPedal to switch between snapshots in response to MIDI messages from a MIDI foot controller or other device.` -Snapshots are useful if you have a USB or MIDI foot controller. You can configure PiPedal to switch between snapshots when you step on buttons on your foot controller. To configure MIDI bindings for snapshots, select Settings from the main menu, and tap on System MIDI bindings. +Snapshots are particularly useful if you have a USB or MIDI foot controller. You can configure PiPedal to switch between snapshots when you step on buttons on your foot controller. To configure MIDI bindings for snapshots, select Settings from the main menu, and tap on System MIDI bindings. But snapshots are equally useful when you are using PiPedal's Performance View. To access the Performance View click on the Performance View menu item in the main PiPedal menu. diff --git a/docs/download.md b/docs/download.md index e3fdef9..edf7ba8 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.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) +- [Raspberry Pi OS bookworm (aarch64) v1.5.95 Beta](https://github.com/rerdavies/pipedal/releases/download/v1.5.95/pipedal_1.5.95_arm64.deb) +- [Ubuntu 24.x, 25.04 (aarch64) v1.5.95 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.95/pipedal_1.5.95_arm64.deb) +- [Ubuntu 24.x, 25.04 (amd64) v1.5.95 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.95/pipedal_1.5.95_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.94_arm64.deb + sudo apt-get install ./pipedal_1.5.95_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/lv2/aarch64/ToobAmp.lv2/CabIR.ttl b/lv2/aarch64/ToobAmp.lv2/CabIR.ttl index 4e5ea66..b7c3ad0 100644 --- a/lv2/aarch64/ToobAmp.lv2/CabIR.ttl +++ b/lv2/aarch64/ToobAmp.lv2/CabIR.ttl @@ -93,7 +93,7 @@ cabir:impulseFile3 doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ TooB Cab IR is a convolution-based guitar cabinet impulse response simulator. diff --git a/lv2/aarch64/ToobAmp.lv2/CabSim.ttl b/lv2/aarch64/ToobAmp.lv2/CabSim.ttl index 69a7c06..47ba153 100644 --- a/lv2/aarch64/ToobAmp.lv2/CabSim.ttl +++ b/lv2/aarch64/ToobAmp.lv2/CabSim.ttl @@ -50,7 +50,7 @@ toob:frequencyResponseVector doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; mod:brand "TooB"; mod:label "TooB CabSim"; diff --git a/lv2/aarch64/ToobAmp.lv2/ConvolutionReverb.ttl b/lv2/aarch64/ToobAmp.lv2/ConvolutionReverb.ttl index 6c10ed4..b8d77f9 100644 --- a/lv2/aarch64/ToobAmp.lv2/ConvolutionReverb.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ConvolutionReverb.ttl @@ -53,7 +53,7 @@ toobimpulse:impulseFile doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ Toob Convolution Reverb Stereo uses convolution reverb impulse/response files in order to produce highly diff --git a/lv2/aarch64/ToobAmp.lv2/ConvolutionReverbStereo.ttl b/lv2/aarch64/ToobAmp.lv2/ConvolutionReverbStereo.ttl index e70825c..89b2bff 100644 --- a/lv2/aarch64/ToobAmp.lv2/ConvolutionReverbStereo.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ConvolutionReverbStereo.ttl @@ -51,7 +51,7 @@ toobimpulse:impulseFile doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ diff --git a/lv2/aarch64/ToobAmp.lv2/InputStage.ttl b/lv2/aarch64/ToobAmp.lv2/InputStage.ttl index 233e430..a93c1d0 100644 --- a/lv2/aarch64/ToobAmp.lv2/InputStage.ttl +++ b/lv2/aarch64/ToobAmp.lv2/InputStage.ttl @@ -66,7 +66,7 @@ inputStage:filterGroup doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; mod:brand "TooB"; mod:label "TooB Input"; diff --git a/lv2/aarch64/ToobAmp.lv2/PowerStage2.ttl b/lv2/aarch64/ToobAmp.lv2/PowerStage2.ttl index b3cfdae..dd51cd1 100644 --- a/lv2/aarch64/ToobAmp.lv2/PowerStage2.ttl +++ b/lv2/aarch64/ToobAmp.lv2/PowerStage2.ttl @@ -68,7 +68,7 @@ pstage:stage3 doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; mod:brand "TooB"; mod:label "Power Stage"; diff --git a/lv2/aarch64/ToobAmp.lv2/SpectrumAnalyzer.ttl b/lv2/aarch64/ToobAmp.lv2/SpectrumAnalyzer.ttl index e5b91cd..a3cf56e 100644 --- a/lv2/aarch64/ToobAmp.lv2/SpectrumAnalyzer.ttl +++ b/lv2/aarch64/ToobAmp.lv2/SpectrumAnalyzer.ttl @@ -57,7 +57,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment "TooB spectrum analyzer" ; mod:brand "TooB"; diff --git a/lv2/aarch64/ToobAmp.lv2/ToneStack.ttl b/lv2/aarch64/ToobAmp.lv2/ToneStack.ttl index 9d869ea..43c2b55 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToneStack.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToneStack.ttl @@ -57,7 +57,7 @@ tonestack:eqGroup doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; uiext:ui ; @@ -121,7 +121,7 @@ tone stack used in Polytone and HiWatt amps. lv2:maximum 2.0; lv2:portProperty lv2:enumeration ; - lv2:scalePoint [ + lv2:scalePoint [ rdfs:label "Bassman" ; rdf:value 0.0 ] , [ diff --git a/lv2/aarch64/ToobAmp.lv2/Toob3BandEq.ttl b/lv2/aarch64/ToobAmp.lv2/Toob3BandEq.ttl index aa2e407..3641a19 100644 --- a/lv2/aarch64/ToobAmp.lv2/Toob3BandEq.ttl +++ b/lv2/aarch64/ToobAmp.lv2/Toob3BandEq.ttl @@ -52,7 +52,7 @@ toob:frequencyResponseVector doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; uiext:ui ; diff --git a/lv2/aarch64/ToobAmp.lv2/ToobAmpUI.so b/lv2/aarch64/ToobAmp.lv2/ToobAmpUI.so index 32a1c16..14d589b 100755 Binary files a/lv2/aarch64/ToobAmp.lv2/ToobAmpUI.so and b/lv2/aarch64/ToobAmp.lv2/ToobAmpUI.so differ diff --git a/lv2/aarch64/ToobAmp.lv2/ToobChorus.ttl b/lv2/aarch64/ToobAmp.lv2/ToobChorus.ttl index 67a19a7..7799141 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobChorus.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobChorus.ttl @@ -40,7 +40,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ Emulation of a Boss CE-2 Chorus. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl b/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl index 61082ef..62f3606 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl @@ -41,7 +41,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ A straightforward no-frills digital delay. @@ -61,7 +61,7 @@ A straightforward no-frills digital delay. lv2:name "Delay"; lv2:default 340 ; lv2:minimum 5; - lv2:maximum 1200; + lv2:maximum 3000; units:unit units:ms; lv2:portProperty ; rdfs:comment "Delay in ms"; diff --git a/lv2/aarch64/ToobAmp.lv2/ToobFlanger.ttl b/lv2/aarch64/ToobAmp.lv2/ToobFlanger.ttl index 2bb53b5..a35e540 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobFlanger.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobFlanger.ttl @@ -41,7 +41,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ Emulation of a Boss BF-2 Flanger, based on circuit analysis and simulation of the original. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobFlangerStereo.ttl b/lv2/aarch64/ToobAmp.lv2/ToobFlangerStereo.ttl index e3fda58..b4551be 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobFlangerStereo.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobFlangerStereo.ttl @@ -41,7 +41,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ Digital emulation of a Boss BF-2 Flanger. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobFreeverb.ttl b/lv2/aarch64/ToobAmp.lv2/ToobFreeverb.ttl index 05c18ed..3fa9b01 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobFreeverb.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobFreeverb.ttl @@ -41,7 +41,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ Toob Freeverb is an Lv2 implementation of the famous Freeverb reverb effect. FreeVerb delivers a well-balanced reverb with very little tonal coloration. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobGraphicEq.ttl b/lv2/aarch64/ToobAmp.lv2/ToobGraphicEq.ttl index 70d41fd..d469e2b 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobGraphicEq.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobGraphicEq.ttl @@ -41,7 +41,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ A 7 octave graphic equalizer, with frequencies chosen to be useful for EQ-ing guitar signals. """ ; diff --git a/lv2/aarch64/ToobAmp.lv2/ToobLooperFour.ttl b/lv2/aarch64/ToobAmp.lv2/ToobLooperFour.ttl index 940876b..cde2777 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobLooperFour.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobLooperFour.ttl @@ -92,7 +92,7 @@ myprefix:output_group doap:license ; doap:maintainer ; lv2:minorVersion 2 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; ui:ui ; diff --git a/lv2/aarch64/ToobAmp.lv2/ToobLooperOne.ttl b/lv2/aarch64/ToobAmp.lv2/ToobLooperOne.ttl index af0e8e3..1df02e0 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobLooperOne.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobLooperOne.ttl @@ -78,7 +78,7 @@ myprefix:output_group doap:license ; doap:maintainer ; lv2:minorVersion 2 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; ui:ui ; diff --git a/lv2/aarch64/ToobAmp.lv2/ToobML.ttl b/lv2/aarch64/ToobAmp.lv2/ToobML.ttl index c48acc7..b8d6545 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobML.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobML.ttl @@ -67,7 +67,7 @@ toobml:sagGroup doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ The TooB ML Amplifier plugin provides emulation of a variety of amplifiers and overdrive pedals that are implemented using neural-network-based machine learning models of real amplifiers. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobMix.ttl b/lv2/aarch64/ToobAmp.lv2/ToobMix.ttl index 8109f12..61125e4 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobMix.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobMix.ttl @@ -40,7 +40,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ Remix a stereo input signal. @@ -115,31 +115,75 @@ Remix a stereo input signal. lv2:maximum 1.0; rdfs:comment "Right Pan"; ], + [ + a lv2:InputPort , + lv2:ControlPort ; + + lv2:index 4; + lv2:symbol "phaseL" ; + lv2:name "Phase L"; + lv2:default 1.0 ; + lv2:minimum 0.0; + lv2:maximum 1.0; + lv2:portProperty lv2:enumeration ; + + lv2:scalePoint [ + rdfs:label "Inverted" ; + rdf:value 0.0 + ] , [ + rdfs:label "Normal" ; + rdf:value 1.0 + ]; + + rdfs:comment "Left phase (pre-mix)"; + ], + [ + a lv2:InputPort , + lv2:ControlPort ; + + lv2:index 5; + lv2:symbol "phaseR" ; + lv2:name "Phase R"; + lv2:default 1.0 ; + lv2:minimum 0.0; + lv2:maximum 1.0; + + lv2:portProperty lv2:enumeration ; + lv2:scalePoint [ + rdfs:label "Inverted" ; + rdf:value 0.0 + ] , [ + rdfs:label "Normal" ; + rdf:value 1.0 + ]; + + rdfs:comment "Right phase (pre-mix)"; + ], [ a lv2:AudioPort , lv2:InputPort ; - lv2:index 4 ; + lv2:index 6 ; lv2:symbol "inl" ; lv2:name "In L" ], [ a lv2:AudioPort , lv2:InputPort ; - lv2:index 5 ; + lv2:index 7 ; lv2:symbol "inr" ; lv2:name "In R" ], [ a lv2:AudioPort , lv2:OutputPort ; - lv2:index 6 ; + lv2:index 8 ; lv2:symbol "outl" ; lv2:name "Out L" ], [ a lv2:AudioPort , lv2:OutputPort ; - lv2:index 7 ; + lv2:index 9 ; lv2:symbol "outr" ; lv2:name "OutR" ] diff --git a/lv2/aarch64/ToobAmp.lv2/ToobNeuralAmpModeler.ttl b/lv2/aarch64/ToobAmp.lv2/ToobNeuralAmpModeler.ttl index e048033..2f83e35 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobNeuralAmpModeler.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobNeuralAmpModeler.ttl @@ -72,7 +72,7 @@ toobNam:calibrationGroup doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ TooB Neural Amp Modeler is a neural network based Amp Simulator. It uses .nam model files that are generated by training nueral networks on audio recordings of actual guitar amps, effects pedals and other equipment. .nam files contain data from the trained models, which can be loaded into diff --git a/lv2/aarch64/ToobAmp.lv2/ToobNoiseGate.ttl b/lv2/aarch64/ToobAmp.lv2/ToobNoiseGate.ttl index 329ad48..d498abb 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobNoiseGate.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobNoiseGate.ttl @@ -54,7 +54,7 @@ noisegate:envelope_group doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ A noise gate is an audio processing tool that controls the volume of an audio signal by allowing it to pass through only when it exceeds a set threshold. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobParametricEq.ttl b/lv2/aarch64/ToobAmp.lv2/ToobParametricEq.ttl index 9bcff31..c2f1769 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobParametricEq.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobParametricEq.ttl @@ -87,7 +87,7 @@ parametric_eq:hfGroup doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; uiext:ui ; diff --git a/lv2/aarch64/ToobAmp.lv2/ToobPhaser.ttl b/lv2/aarch64/ToobAmp.lv2/ToobPhaser.ttl index dabee14..306eff3 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobPhaser.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobPhaser.ttl @@ -40,7 +40,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ A loose emulation of an MXR® Phase 90 Phaser. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobPlayer.ttl b/lv2/aarch64/ToobAmp.lv2/ToobPlayer.ttl index ed8802c..a4dc596 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobPlayer.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobPlayer.ttl @@ -60,7 +60,7 @@ toobPlayer:seek doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ Play audio files. Audio files can optionally be loooped by pressing the "Set loop" button. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobRecordMono.ttl b/lv2/aarch64/ToobAmp.lv2/ToobRecordMono.ttl index 669b5e5..9ed03b3 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobRecordMono.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobRecordMono.ttl @@ -51,7 +51,7 @@ recordPrefix:audioFile doap:license ; doap:maintainer ; lv2:minorVersion 2 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; ui:ui ; diff --git a/lv2/aarch64/ToobAmp.lv2/ToobRecordStereo.ttl b/lv2/aarch64/ToobAmp.lv2/ToobRecordStereo.ttl index 5b8fcea..dc98228 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobRecordStereo.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobRecordStereo.ttl @@ -88,7 +88,7 @@ myprefix:loop3_group doap:license ; doap:maintainer ; lv2:minorVersion 2 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; ui:ui ; diff --git a/lv2/aarch64/ToobAmp.lv2/ToobTremolo.ttl b/lv2/aarch64/ToobAmp.lv2/ToobTremolo.ttl index 6bc4522..e4969f8 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobTremolo.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobTremolo.ttl @@ -41,7 +41,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ A tremolo effect, equivalent to "vibrato" on vintage Fender tube amps. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobTremoloMono.ttl b/lv2/aarch64/ToobAmp.lv2/ToobTremoloMono.ttl index 400eda8..153e1fa 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobTremoloMono.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobTremoloMono.ttl @@ -41,7 +41,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ A tremolo effect, equivalent to "vibrato" on vintage Fender tube amps. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobTuner.ttl b/lv2/aarch64/ToobAmp.lv2/ToobTuner.ttl index 9b095b0..0ccbecc 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobTuner.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobTuner.ttl @@ -40,7 +40,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ TooB Tuner is a chromatic guitar tuner. """ ; diff --git a/lv2/aarch64/ToobAmp.lv2/ToobVolume.ttl b/lv2/aarch64/ToobAmp.lv2/ToobVolume.ttl index ba43de3..12fb519 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobVolume.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobVolume.ttl @@ -39,7 +39,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 71 ; + lv2:microVersion 72 ; rdfs:comment """ Volume control. diff --git a/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a72.so b/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a72.so index 2f516c9..333c265 100755 Binary files a/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a72.so and b/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a72.so differ diff --git a/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a76.so b/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a76.so index 01d4764..cd0862b 100755 Binary files a/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a76.so and b/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a76.so differ diff --git a/lv2/x86_64/toobamp_1.2.71_amd64.deb b/lv2/x86_64/toobamp_1.2.72_amd64.deb similarity index 59% rename from lv2/x86_64/toobamp_1.2.71_amd64.deb rename to lv2/x86_64/toobamp_1.2.72_amd64.deb index 2c424c0..a236d7a 100644 Binary files a/lv2/x86_64/toobamp_1.2.71_amd64.deb and b/lv2/x86_64/toobamp_1.2.72_amd64.deb differ diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp index 319d494..c86c554 100644 --- a/src/AlsaDriver.cpp +++ b/src/AlsaDriver.cpp @@ -1686,7 +1686,8 @@ namespace pipedal continue; } MidiEvent *pEvent = midiEvents.data() + midiEventCount++; - pEvent->time = audioFrame; + pEvent->timeStamp = MidiTimestamp(message.realtime_sec, message.realtime_nsec); + pEvent->frame = audioFrame; pEvent->size = messageSize; pEvent->buffer = midiEventMemory.data() + midiEventMemoryIndex; diff --git a/src/AudioDriver.hpp b/src/AudioDriver.hpp index 245aeb3..3899b05 100644 --- a/src/AudioDriver.hpp +++ b/src/AudioDriver.hpp @@ -26,6 +26,7 @@ #include "JackConfiguration.hpp" #include #include "AlsaSequencer.hpp" +#include "MidiEvent.hpp" @@ -35,15 +36,6 @@ namespace pipedal { using ProcessCallback = std::function; - - struct MidiEvent - { - uint32_t time; /**< Sample frame at which event is valid */ - uint32_t size; /**< Number of bytes of data in \a buffer */ - uint8_t *buffer; /**< Raw MIDI data */ - }; - - class AudioDriverHost { public: virtual void OnProcess(size_t nFrames) = 0; diff --git a/src/AudioHost.cpp b/src/AudioHost.cpp index 2b39bdf..a744f84 100644 --- a/src/AudioHost.cpp +++ b/src/AudioHost.cpp @@ -365,6 +365,7 @@ bool SystemMidiBinding::IsMatch(const MidiEvent &event) switch (currentBinding.bindingType()) { case BINDING_TYPE_NOTE: + case BINDING_TYPE_TAP_TEMPO: { if (event.size != 3) return false; @@ -926,7 +927,8 @@ private: { // eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer); - this->realtimeActivePedalboard->OnMidiMessage(event.size, event.buffer, this, fnMidiValueChanged); + this->realtimeActivePedalboard->OnMidiMessage( + event, this, fnMidiValueChanged); if (listenForMidiEvent) { if (event.size >= 3) @@ -1077,7 +1079,7 @@ private: int8_t messageCount = deferredMidiMessages[i++]; event.size = messageCount; event.buffer = deferredMidiMessages + i; - event.time = 0; + event.frame = 0; ProcessMidiEvent(eventBufferWriter, iterator, event); diff --git a/src/DummyAudioDriver.cpp b/src/DummyAudioDriver.cpp index 185e99e..dd2301b 100644 --- a/src/DummyAudioDriver.cpp +++ b/src/DummyAudioDriver.cpp @@ -264,7 +264,7 @@ namespace pipedal continue; } MidiEvent *pEvent = midiEvents.data() + midiEventCount++; - pEvent->time = audioFrame; + pEvent->frame = audioFrame; pEvent->size = messageSize; pEvent->buffer = midiEventMemory.data() + midiEventMemoryIndex; diff --git a/src/Lv2Pedalboard.cpp b/src/Lv2Pedalboard.cpp index ede9b78..4c750c8 100644 --- a/src/Lv2Pedalboard.cpp +++ b/src/Lv2Pedalboard.cpp @@ -188,13 +188,15 @@ std::vector Lv2Pedalboard::PrepareItems( { pLv2Effect->SetAudioSidechainBuffer(i, this->pedalboardInputBuffers[i]); } - else + else { // just use the first output buffer for all sidechain inputs. pLv2Effect->SetAudioSidechainBuffer(i, this->pedalboardInputBuffers[0]); } } - } else if (item.sideChainInputId() != -1) { + } + else if (item.sideChainInputId() != -1) + { IEffect *pSideChainInput = GetEffect(item.sideChainInputId()); if (pSideChainInput) @@ -205,13 +207,15 @@ std::vector Lv2Pedalboard::PrepareItems( { pLv2Effect->SetAudioSidechainBuffer(i, pSideChainInput->GetAudioOutputBuffer(i)); } - else + else { // just use the first output buffer for all sidechain inputs. pLv2Effect->SetAudioSidechainBuffer(i, pSideChainInput->GetAudioOutputBuffer(0)); } } - } else { + } + else + { throw std::runtime_error("Internal error: Sidechain input IEffect not found."); } } @@ -418,11 +422,14 @@ void Lv2Pedalboard::PrepareMidiMap(const PedalboardItem &pedalboardItem) { mapping.mappingType = MidiControlType::Select; } + else if (binding.bindingType() == BINDING_TYPE_TAP_TEMPO) { + mapping.mappingType = MidiControlType::TapTempo; + } else { mapping.mappingType = MidiControlType::Dial; } - if (binding.bindingType() == BINDING_TYPE_NOTE) + if (binding.bindingType() == BINDING_TYPE_NOTE || binding.bindingType() == BINDING_TYPE_TAP_TEMPO) { mapping.key = 0x9000 | binding.note(); // i.e. midi note on. } @@ -539,6 +546,8 @@ bool Lv2Pedalboard::Run(float **inputBuffers, float **outputBuffers, uint32_t sa outputBuffers[c][i] = this->pedalboardOutputBuffers[c][i] * volume; } } + this->currentFrameOffset += samples; + return true; } @@ -715,14 +724,17 @@ void Lv2Pedalboard::GatherPatchProperties(RealtimePatchPropertyRequest *pParamet } } -void Lv2Pedalboard::OnMidiMessage(size_t size, uint8_t *message, - void *callbackHandle, - MidiCallbackFn *pfnCallback) +void Lv2Pedalboard::OnMidiMessage( + const MidiEvent&event, + void *callbackHandle, + MidiCallbackFn *pfnCallback) { if (midiMappings.size() == 0) return; + size_t size = event.size; + const uint8_t *message = event.buffer; if (size < 2) return; uint8_t cmd = message[0]; @@ -791,7 +803,7 @@ void Lv2Pedalboard::OnMidiMessage(size_t size, uint8_t *message, for (int i = min; i < midiMappings.size(); ++i) { - auto &mapping = midiMappings[i]; + MidiMapping &mapping = midiMappings[i]; if (mapping.key != searchKey) break; @@ -914,6 +926,11 @@ void Lv2Pedalboard::OnMidiMessage(size_t size, uint8_t *message, } break; } + case MidiControlType::TapTempo: + { + handleTapTempo(value, event.timeStamp, mapping, callbackHandle, pfnCallback); + break; + } case MidiControlType::None: default: break; @@ -921,4 +938,81 @@ void Lv2Pedalboard::OnMidiMessage(size_t size, uint8_t *message, } } } -} \ No newline at end of file +} + +void Lv2Pedalboard::handleTapTempo(uint8_t value, const MidiTimestamp& timestamp, MidiMapping &mapping, void *callbackHandle, MidiCallbackFn *pfnSetControlCallback) +{ + if (value != 0) // only on note on + { + if (!mapping.lastTapTimestamp.isEmpty()) + { + double seconds = timestamp.timeDiff(mapping.lastTapTimestamp); + if (seconds > 60.0/450.0) // debounce check. (~= 450bpm) + { + auto units = mapping.pPortInfo->units(); + float controlValue = -1; + switch (units) + { + case Units::bpm: + { + controlValue = 60.0f / (float)seconds; + break; + } + case Units::hz: + { + controlValue = 1.0f / (float)seconds; + break; + } + case Units::s: + { + controlValue = (float)(seconds); + break; + } + case Units::ms: + { + controlValue = (float)(seconds * 1000.0); + break; + } + default: + { + controlValue = -1; + } + } + if (mapping.pPortInfo->min_value() < mapping.pPortInfo->max_value()) + { + if (controlValue < mapping.pPortInfo->min_value()) + { + controlValue = -1; + } + else if (controlValue > mapping.pPortInfo->max_value()) + { + controlValue = -1; + } + } + else + { + if (controlValue > mapping.pPortInfo->min_value()) + { + controlValue = -1; + } + else if (controlValue < mapping.pPortInfo->max_value()) + { + controlValue = -1; + } + } + if (controlValue != -1) + { + + IEffect *pEffect = this->realtimeEffects[mapping.effectIndex]; + if (pEffect->IsLv2Effect()) + { + Lv2Effect *pLv2Effect = dynamic_cast(pEffect); + pEffect->SetControl(mapping.controlIndex, controlValue); + pfnSetControlCallback(callbackHandle, mapping.instanceId, mapping.pPortInfo->index(), controlValue); + } + } + } + } + mapping.lastTapTimestamp = timestamp; + } +} diff --git a/src/Lv2Pedalboard.hpp b/src/Lv2Pedalboard.hpp index 961b3e4..c893824 100644 --- a/src/Lv2Pedalboard.hpp +++ b/src/Lv2Pedalboard.hpp @@ -19,6 +19,7 @@ #pragma once #include "Pedalboard.hpp" +#include "MidiEvent.hpp" #include "PluginHost.hpp" #include "Lv2Effect.hpp" #include "BufferPool.hpp" @@ -49,8 +50,9 @@ namespace pipedal class Lv2Pedalboard { + private: IHost *pHost = nullptr; - + size_t currentFrameOffset = 0; DbDezipper inputVolume; DbDezipper outputVolume; @@ -82,7 +84,8 @@ namespace pipedal Dial, Toggle, Trigger, - MomentarySwitch + MomentarySwitch, + TapTempo }; class MidiMapping { @@ -93,6 +96,7 @@ namespace pipedal int effectIndex = -1; int controlIndex = -1; int key; // key to the note or control. internal use only. + MidiTimestamp lastTapTimestamp; bool hasLastValue = false; bool lastValueIncreasing = false; float lastValue = 0; @@ -119,13 +123,11 @@ namespace pipedal Lv2Pedalboard() {} ~Lv2Pedalboard() {} - void Prepare(IHost *pHost, Pedalboard &pedalboard, Lv2PedalboardErrorList &errorList, ExistingEffectMap *existingEffects = nullptr); std::vector &GetEffects() { return realtimeEffects; } std::vector> &GetSharedEffectList() { return effects; } - int GetIndexOfInstanceId(uint64_t instanceId) { for (int i = 0; i < this->realtimeEffects.size(); ++i) @@ -149,7 +151,7 @@ namespace pipedal void Activate(); void Deactivate(); void UpdateAudioPorts(); - + bool Run(float **inputBuffers, float **outputBuffers, uint32_t samples, RealtimeRingBufferWriter *realtimeWriter); void ResetAtomBuffers(); @@ -172,9 +174,19 @@ namespace pipedal float GetControlOutputValue(int effectIndex, int portIndex); typedef void(MidiCallbackFn)(void *data, uint64_t intanceId, int controlIndex, float value); - void OnMidiMessage(size_t size, uint8_t *data, - void *callbackHandle, - MidiCallbackFn *pfnCallback); + void OnMidiMessage( + const MidiEvent&message, + void *callbackHandle, + MidiCallbackFn *pfnCallback); +private: + void handleTapTempo( + uint8_t value, + const MidiTimestamp& timestamp, + MidiMapping &mapping, + void *callbackHandle, + MidiCallbackFn *pfnCallback); + + }; } // namespace \ No newline at end of file diff --git a/src/MidiBinding.hpp b/src/MidiBinding.hpp index 295da45..12885d7 100644 --- a/src/MidiBinding.hpp +++ b/src/MidiBinding.hpp @@ -53,6 +53,7 @@ class MapFeature; const int BINDING_TYPE_NONE = 0; const int BINDING_TYPE_NOTE = 1; const int BINDING_TYPE_CONTROL = 2; + const int BINDING_TYPE_TAP_TEMPO = 3; const int LINEAR_CONTROL_TYPE = 0; const int CIRCULAR_CONTROL_TYPE = 1; diff --git a/src/MidiEvent.hpp b/src/MidiEvent.hpp new file mode 100644 index 0000000..483d36b --- /dev/null +++ b/src/MidiEvent.hpp @@ -0,0 +1,68 @@ +/* + * MIT License + * +* Copyright (c) 2026 Robin E. R. Davies + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#pragma once +#ifndef PIPEDAL_MidiEvent_HPP +#define PIPEDAL_MidiEvent_HPP + +#include + +namespace pipedal { + struct MidiTimestamp + { + uint64_t seconds; + uint32_t nanoseconds; + + MidiTimestamp() + : seconds(0), nanoseconds(0) + { + } + MidiTimestamp(uint64_t sec, uint32_t nsec) + : seconds(sec), nanoseconds(nsec) + { + } + + bool isEmpty() const { + return seconds == 0 && nanoseconds == 0; + } + double timeDiff(const MidiTimestamp &other) const + { + double secDiff = (double)(int64_t)(this->seconds - other.seconds); + double nsecDiff = (double)((int64_t)this->nanoseconds - (int64_t)other.nanoseconds) * 1e-9; + return secDiff + nsecDiff; + } + }; + + struct MidiEvent + { + MidiTimestamp timeStamp; /**< Real-time timestamp of the event */ + uint32_t frame; /**< Sample frame at which event is valid */ + uint32_t size; /**< Number of bytes of data in \a buffer */ + uint8_t *buffer; /**< Raw MIDI data */ + }; + + + +}; + +#endif diff --git a/src/ModFileTypes.cpp b/src/ModFileTypes.cpp index f230f09..500a9e9 100644 --- a/src/ModFileTypes.cpp +++ b/src/ModFileTypes.cpp @@ -50,7 +50,7 @@ static std::vector *CreateModDirectories() // extensions observed in the field. {"audio", "shared/audio", "Audio", {"audio/*"}}, // all audio files (Ratatouille) {"nammodel", "NeuralAmpModels", "Neural Amp Models", {".nam" ,".aidax"}}, // Ratatouille, Mike's NAM. - {"aidadspmodel", "shared/aidaaix", "AIDA IAX Models", {".json", ".aidaiax"}}, // Ratatouille + {"aidadspmodel", "shared/aidaaix", "AIDA IAX Models", {".json", ".aidax",".aidaiax"}}, // Ratatouille {"mlmodel", "ToobMlModels", "ML Models", {".json"}}, // diff --git a/vite/src/pipedal/Lv2Plugin.tsx b/vite/src/pipedal/Lv2Plugin.tsx index cdaca30..341425f 100644 --- a/vite/src/pipedal/Lv2Plugin.tsx +++ b/vite/src/pipedal/Lv2Plugin.tsx @@ -35,6 +35,11 @@ function semitone12TETValue(value: number): string { } +export const MIN_TAP_TEMPO_SECONDS = 0.2; +export const MAX_TAP_TEMPO_SECONDS = 1.0; +export const MIN_TAP_TEMPO_HZ = 0.1; +export const MAX_TAP_TEMPO_HZ = 2.0; + export class Port implements Deserializable { deserialize(input: any): Port { @@ -117,7 +122,7 @@ export class PortGroup { symbol: string = ""; name: string = ""; sideChainof: string = ""; - + parent_group: string = ""; program_list_id: number = -1; }; @@ -654,6 +659,10 @@ export class UiControl implements Deserializable { this.controlType = ControlType.Trigger; } } + if (this.controlType === ControlType.Dial) { + if (this.canDoTapTempo()) { + } + } return this; } @@ -714,6 +723,7 @@ export class UiControl implements Deserializable { scale_points: ScalePoint[] = []; port_group: string = ""; units: Units = Units.none; + can_tap_tempo: boolean = false; comment: string = ""; is_bypass: boolean = false; is_program_controller: boolean = true; @@ -779,6 +789,34 @@ export class UiControl implements Deserializable { return this.controlType === ControlType.Dial; } + + canDoTapTempo(): boolean { + if (this.controlType === ControlType.Dial) { + let min_value = this.min_value; + let max_value = this.max_value; + if (min_value > max_value) { + let t = min_value; + min_value = max_value; + max_value = t; + } + switch (this.units) { + case Units.s: + return min_value <= MIN_TAP_TEMPO_SECONDS && max_value >= MAX_TAP_TEMPO_SECONDS; + case Units.ms: + return min_value <= MIN_TAP_TEMPO_SECONDS*1000 && max_value >= MAX_TAP_TEMPO_SECONDS*1000; + + case Units.bpm: + return true; + case Units.hz: + return min_value <= MIN_TAP_TEMPO_HZ && max_value >= MAX_TAP_TEMPO_HZ; + default: + return false; + + } + } + return false; + } + isTuner(): boolean { return this.controlType === ControlType.Tuner; } @@ -894,8 +932,8 @@ export class UiControl implements Deserializable { break; case Units.unknown: if (this.custom_units !== "") { - text = this.custom_units.replace("%f",text); - } + text = this.custom_units.replace("%f", text); + } break; default: break; @@ -950,7 +988,7 @@ export class Lv2PatchPropertyInfo { return result; } uri: string = ""; - writable : boolean = false; + writable: boolean = false; readable: boolean = false; label: string = ""; index: number = -1; @@ -1045,8 +1083,7 @@ export class UiPlugin implements Deserializable { } getFilePropertyByUri(patchPropertyUri: string): UiFileProperty | null { - for (let i = 0; i < this.fileProperties.length; ++i) - { + for (let i = 0; i < this.fileProperties.length; ++i) { let fileProperty = this.fileProperties[i]; if (fileProperty.patchProperty === patchPropertyUri) { return fileProperty; @@ -1078,7 +1115,7 @@ export class UiPlugin implements Deserializable { frequencyPlots: UiFrequencyPlot[] = []; is_vst3: boolean = false; modGui: ModGui | null = null; // null if no mod gui. - patchProperties: Lv2PatchPropertyInfo[] = []; + patchProperties: Lv2PatchPropertyInfo[] = []; } diff --git a/vite/src/pipedal/MidiBinding.tsx b/vite/src/pipedal/MidiBinding.tsx index 3a2add0..27f197d 100644 --- a/vite/src/pipedal/MidiBinding.tsx +++ b/vite/src/pipedal/MidiBinding.tsx @@ -67,6 +67,7 @@ export default class MidiBinding { static BINDING_TYPE_NONE: number = 0; static BINDING_TYPE_NOTE: number = 1; static BINDING_TYPE_CONTROL: number = 2; + static BINDING_TYPE_TAP_TEMPO: number = 3; setBindingType(bindingType:number) { diff --git a/vite/src/pipedal/MidiBindingView.tsx b/vite/src/pipedal/MidiBindingView.tsx index 4c657c6..cda8cda 100644 --- a/vite/src/pipedal/MidiBindingView.tsx +++ b/vite/src/pipedal/MidiBindingView.tsx @@ -57,6 +57,7 @@ export enum MidiControlType { MomentarySwitch } + export function getMidiControlType(uiPlugin: UiPlugin | undefined, symbol: string): MidiControlType { if (symbol === "__bypass") { return MidiControlType.Toggle; @@ -87,16 +88,14 @@ export function getMidiControlType(uiPlugin: UiPlugin | undefined, symbol: strin return MidiControlType.Dial; } -export function canBindToNote(controlType: MidiControlType): boolean { - return controlType === MidiControlType.Toggle - || controlType === MidiControlType.Trigger - || controlType === MidiControlType.MomentarySwitch -} + + interface MidiBindingViewProps extends WithStyles { instanceId: number; midiBinding: MidiBinding; midiControlType: MidiControlType; + canDoTapTempo: boolean; onChange: (instanceId: number, newBinding: MidiBinding) => void; } @@ -131,6 +130,21 @@ const MidiBindingView = }; } + canDoTapTempo(): boolean { + let controlType = this.props.midiControlType; + if (controlType == MidiControlType.Dial && this.props.canDoTapTempo) { + return true; + } + return false; + } + canBindToNote(): boolean { + let controlType = this.props.midiControlType; + return controlType === MidiControlType.Toggle + || controlType === MidiControlType.Trigger + || controlType === MidiControlType.MomentarySwitch + } + + handleBindingTypeChange(e: any, extra: any) { let newValue = parseInt(e.target.value); let newBinding = this.props.midiBinding.clone(); @@ -203,7 +217,7 @@ const MidiBindingView = generateMidiNoteSelects(): React.ReactNode[] { let result: React.ReactNode[] = []; - for (let i = 0; i < 127; ++i) { + for (let i = 0; i <= 127; ++i) { result.push( {i} {Utility.midiNoteName(i)} ) @@ -314,10 +328,13 @@ const MidiBindingView = let binding = this.props.midiBinding; let newBinding = binding.clone(); if (midiMessage.isNote()) { - if (!canBindToNote(this.props.midiControlType)) { + if (!this.canBindToNote() && !this.props.canDoTapTempo) { return; } - newBinding.bindingType = MidiBinding.BINDING_TYPE_NOTE + newBinding.bindingType = + this.props.canDoTapTempo ? + MidiBinding.BINDING_TYPE_TAP_TEMPO + : MidiBinding.BINDING_TYPE_NOTE newBinding.note = midiMessage.cc1; } else if (midiMessage.isControl()) { newBinding.bindingType = MidiBinding.BINDING_TYPE_CONTROL @@ -413,23 +430,28 @@ const MidiBindingView = this.handleBindingTypeChange(e, extra)} value={midiBinding.bindingType} > None - {(canBindToNote(this.props.midiControlType)) && ( - Note + {(this.canBindToNote()) && ( + Note + )} + Control + {(this.canDoTapTempo()) && ( + Tap Tempo )} - Control { - (midiBinding.bindingType === MidiBinding.BINDING_TYPE_NOTE) && + (midiBinding.bindingType === MidiBinding.BINDING_TYPE_NOTE + || midiBinding.bindingType === MidiBinding.BINDING_TYPE_TAP_TEMPO + ) && ( this.handleNoteChange(e, extra)} value={midiBinding.note} @@ -569,7 +591,7 @@ const MidiBindingView = ( this.handleLinearControlTypeChange(e, extra)} value={midiBinding.linearControlType} > diff --git a/vite/src/pipedal/MidiBindingsDialog.tsx b/vite/src/pipedal/MidiBindingsDialog.tsx index d5b9fa6..9492e50 100644 --- a/vite/src/pipedal/MidiBindingsDialog.tsx +++ b/vite/src/pipedal/MidiBindingsDialog.tsx @@ -69,6 +69,17 @@ const styles = (theme: Theme) => createStyles({ }), }); +function canDoTapTempo(uiPlugin: UiPlugin | undefined, symbol: string): boolean { + if (!uiPlugin) return false; + let port = uiPlugin.getControl(symbol); + if (!port) return false; + + if (port.canDoTapTempo()) { + return true; + } + return false; +} + function not_null(value: T | null) { if (!value) throw Error("Unexpected null value"); return value; @@ -200,7 +211,9 @@ export const MidiBindingDialog = this.handleItemChanged(instanceId, newItem)} /> @@ -244,6 +257,7 @@ export const MidiBindingDialog = this.handleItemChanged(instanceId, newItem)} /> diff --git a/vite/src/pipedal/NumericInput.tsx b/vite/src/pipedal/NumericInput.tsx index 1321b90..82a0f66 100644 --- a/vite/src/pipedal/NumericInput.tsx +++ b/vite/src/pipedal/NumericInput.tsx @@ -37,6 +37,15 @@ function isMobileDevice() { return (navigator.maxTouchPoints && navigator.maxTouchPoints > 1); } +function defaultStep(min: number, max: number): number { + let range = Math.abs(max - min); + if (range <= 1) return 0.01; + if (range <= 10) return 0.1; + if (range <= 100) return 1; + if (range <= 1000) return 10; + return 100; +} + interface NumericInputProps extends WithStyles { ariaLabel: string; value: number; @@ -155,7 +164,7 @@ export const NumericInput = render() { //const classes = withStyles.getClasses(this.props); - return ( {this.handleFocus(e);}} onBlur= {(e) => { this.handleBlur(e); }} diff --git a/vite/src/pipedal/PluginControl.tsx b/vite/src/pipedal/PluginControl.tsx index 80065cb..5d9cb52 100644 --- a/vite/src/pipedal/PluginControl.tsx +++ b/vite/src/pipedal/PluginControl.tsx @@ -499,6 +499,8 @@ const PluginControl = } private tapStartMs: number = 0; + + onPointerUp(e: PointerEvent) { if (this.isCapturedPointer(e)) { @@ -519,7 +521,7 @@ const PluginControl = let ms = e.timeStamp - this.tapStartMs; if (ms < 200) { this.onPointerTap(e); - } + } } // prevent click from firing on other elements // when the pointer goes up0. diff --git a/vite/src/pipedal/SystemMidiBindingView.tsx b/vite/src/pipedal/SystemMidiBindingView.tsx index 8b87e46..1299580 100644 --- a/vite/src/pipedal/SystemMidiBindingView.tsx +++ b/vite/src/pipedal/SystemMidiBindingView.tsx @@ -34,6 +34,7 @@ function SystemMidiBindingView(props: SystemMidiBindingViewProps) { return (