From 697b3a1956d36f0fc2740264d79d8e1888204653 Mon Sep 17 00:00:00 2001 From: stz09 Date: Thu, 2 Jul 2026 06:27:17 -0500 Subject: [PATCH] feat(control): add configurable CC send/default values for button CC toggle/momentary modes --- configurator/src/components/ManualTab.tsx | 2 +- configurator/src/demo/catalog.ts | 2 + faderpunk/src/apps/control.rs | 47 ++++++++++++++++++++--- 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/configurator/src/components/ManualTab.tsx b/configurator/src/components/ManualTab.tsx index 1a437b2fb..d2d47ffa5 100644 --- a/configurator/src/components/ManualTab.tsx +++ b/configurator/src/components/ManualTab.tsx @@ -36,7 +36,7 @@ const apps: ManualAppData[] = [ "Muted (if 'Store state' enabled)", "Attenuation", ], - text: "This app is designed to provide a simple way to manually control any parameters using either CV or MIDI CC. The MIDI channel and CC numbers can be adjusted in the app's settings, and both MIDI and CV outputs are always active simultaneously. The range can be adjusted using Shift + Fader, which affects both CC and CV ranges. The fader controls the level of the CV or CC. The button behavior is set by the Button mode parameter: Mute toggles the output on and off; CC toggle sends a CC value that alternates between 0 and 127; CC momentary sends 127 while held and 0 on release; Program Change sends a MIDI Program Change message on press using the Button CC / PC number as the program number—useful for switching presets on external devices. The fader level and the mute state can be saved in scenes if the 'Store state' parameter is enabled (active by default). You can then use this app as a way to save and recall CV voltage allowing for preset in a modular system for example. The curve can be adjusted in the settings; however, this only affects the CV output. Two voltage ranges are available in the settings: 0V to 10V or -5V to 5V. Note that this range also affects the level at which CV and CC are set when muting. In the 0V to 10V range, mute is at 0V and CC 0, making it ideal for controlling volume, send levels, or similar parameters. In the -5V to 5V range, mute is at 0V and CC 64, making it suitable for controlling panning, crossfading, or similar functions. The mute behavior can be set to trigger on press or on release, depending on your preference. Due to popular demand, the app's action can also be inverted—this means that when the fader is at the top, the output will be set to the minimum value, and when at the bottom, it will send the maximum CC and CV value. As with all apps where the LED color does not serve any specific function, you are free to configure it in the settings.", + text: "This app is designed to provide a simple way to manually control any parameters using either CV or MIDI CC. The MIDI channel and CC numbers can be adjusted in the app's settings, and both MIDI and CV outputs are always active simultaneously. The range can be adjusted using Shift + Fader, which affects both CC and CV ranges. The fader controls the level of the CV or CC. The button behavior is set by the Button mode parameter: Mute toggles the output on and off; CC toggle sends CC Send Value when active and CC Default Value when inactive (defaults 127 and −1, where −1 sends no CC); CC momentary sends CC Send Value while held and CC Default Value on release; Program Change sends a MIDI Program Change message on press using the Button CC / PC number as the program number—useful for switching presets on external devices. The fader level and the mute state can be saved in scenes if the 'Store state' parameter is enabled (active by default). You can then use this app as a way to save and recall CV voltage allowing for preset in a modular system for example. The curve can be adjusted in the settings; however, this only affects the CV output. Two voltage ranges are available in the settings: 0V to 10V or -5V to 5V. Note that this range also affects the level at which CV and CC are set when muting. In the 0V to 10V range, mute is at 0V and CC 0, making it ideal for controlling volume, send levels, or similar parameters. In the -5V to 5V range, mute is at 0V and CC 64, making it suitable for controlling panning, crossfading, or similar functions. The mute behavior can be set to trigger on press or on release, depending on your preference. Due to popular demand, the app's action can also be inverted—this means that when the fader is at the top, the output will be set to the minimum value, and when at the bottom, it will send the maximum CC and CV value. As with all apps where the LED color does not serve any specific function, you are free to configure it in the settings.", channels: [ { jackTitle: "Output", diff --git a/configurator/src/demo/catalog.ts b/configurator/src/demo/catalog.ts index f70072de9..3b228a215 100644 --- a/configurator/src/demo/catalog.ts +++ b/configurator/src/demo/catalog.ts @@ -115,6 +115,8 @@ const APPS: App[] = [ midiCc("Button CC"), MIDI_NRPN, MIDI_OUT, + i32("CC Send Value", 0, 127), + i32("CC Default Value", -1, 127), ]), makeApp(2, 1, "Yellow", "Sine", "LFO", "Multi shape LFO", [ enumParam("Speed", ["Normal", "Slow", "Slowest"]), diff --git a/faderpunk/src/apps/control.rs b/faderpunk/src/apps/control.rs index 83ac97e55..9cc24268f 100644 --- a/faderpunk/src/apps/control.rs +++ b/faderpunk/src/apps/control.rs @@ -16,8 +16,13 @@ use libfp::{Config, Curve, Param, Range, Value}; use crate::app::{App, AppParams, AppStorage, Led, ManagedStorage, ParamStore, SceneEvent}; +/// Values below 0 mean no CC is sent (used by CC Default Value). +fn button_cc_value_12(value: i32) -> Option { + (value >= 0).then(|| ((value.clamp(0, 127) as u32 * 4095) / 127) as u16) +} + pub const CHANNELS: usize = 1; -pub const PARAMS: usize = 13; +pub const PARAMS: usize = 15; pub static CONFIG: Config = Config::new( "Control", @@ -66,7 +71,17 @@ pub static CONFIG: Config = Config::new( }) .add_param(Param::MidiCc { name: "Button CC / PC" }) .add_param(Param::MidiNrpn) -.add_param(Param::MidiOut); +.add_param(Param::MidiOut) +.add_param(Param::i32 { + name: "CC Send Value", + min: 0, + max: 127, +}) +.add_param(Param::i32 { + name: "CC Default Value", + min: -1, + max: 127, +}); pub struct Params { curve: Curve, @@ -82,6 +97,8 @@ pub struct Params { button_ch: MidiChannel, button_cc: MidiCc, nrpn: bool, + cc_send_value: i32, + cc_default_value: i32, } impl AppParams for Params { @@ -103,6 +120,8 @@ impl AppParams for Params { button_cc: MidiCc::from_value(values[10]), nrpn: bool::from_value(values[11]), midi_out: MidiOut::from_value(values[12]), + cc_send_value: i32::from_value(values[13]), + cc_default_value: i32::from_value(values[14]), }) } @@ -121,6 +140,8 @@ impl AppParams for Params { vec.push(self.button_cc.into()).unwrap(); vec.push(Value::MidiNrpn(self.nrpn)).unwrap(); vec.push(self.midi_out.into()).unwrap(); + vec.push(self.cc_send_value.into()).unwrap(); + vec.push(self.cc_default_value.into()).unwrap(); vec } } @@ -164,6 +185,8 @@ pub async fn wrapper(app: App, exit_signal: &'static Signal::new(app.app_id, app.layout_id); @@ -204,6 +227,8 @@ pub async fn run( button_ch, button_cc, nrpn, + cc_send_value, + cc_default_value, ) = params.query(|p| { ( p.curve, @@ -219,6 +244,8 @@ pub async fn run( p.button_ch, p.button_cc, p.nrpn, + p.cc_send_value, + p.cc_default_value, ) }); @@ -411,11 +438,15 @@ pub async fn run( // Momentary mode: handle both press and release buttons.wait_for_down(0).await; leds.set(0, Led::Button, led_color, Brightness::Mid); - midi_button.send_cc(button_cc, 4095).await; + if let Some(v) = button_cc_value_12(cc_send_value) { + midi_button.send_cc(button_cc, v).await; + } buttons.wait_for_up(0).await; leds.unset(0, Led::Button); - midi_button.send_cc(button_cc, 0).await; + if let Some(v) = button_cc_value_12(cc_default_value) { + midi_button.send_cc(button_cc, v).await; + } } 5 => { // Invert momentary: default = CC 127 + LED Mid; press = CC 0 + LED off @@ -464,12 +495,16 @@ pub async fn run( if muted { leds.unset(0, Led::Button); if button_mode == 1 { - midi_button.send_cc(button_cc, 0).await; + if let Some(v) = button_cc_value_12(cc_default_value) { + midi_button.send_cc(button_cc, v).await; + } } } else { leds.set(0, Led::Button, led_color, Brightness::Mid); if button_mode == 1 { - midi_button.send_cc(button_cc, 4095).await; + if let Some(v) = button_cc_value_12(cc_send_value) { + midi_button.send_cc(button_cc, v).await; + } } } }