Skip to content

Commit

Permalink
Multiple additions
Browse files Browse the repository at this point in the history
#278 Add button filter dropdown
#129 Add encoder filter dropdown
  • Loading branch information
helgoboss committed Apr 1, 2021
1 parent 69afe8e commit 003201a
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 22 deletions.
25 changes: 19 additions & 6 deletions doc/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1977,6 +1977,12 @@ can be converted to relative values - rotary encoders and buttons. They don't af
- You can set the "Speed" slider to a negative value, e.g. -2. This is the opposite. It means you need to make your
encoder send 2 increments in order to move to the next preset. Or -5: You need to make your encoder send 5
increments to move to the next preset. This is like slowing down the encoder movement.
- **Encoder filter (dropdown)**: Allows you to react to clockwise or counter-clockwise encoder movements only, e.g. if
you want to invoke one action on clockwise movement and another one on counter-clockwise movement. Or if you want
to use different step sizes for different movements.
- **Increment & decrement:** ReaLearn will process both increments and decrements.
- **Increment only:** ReaLearn will ignore decrements.
- **Decrement only:** ReaLearn will ignore increments.
- **Rotate:** If unchecked, the target value will not change anymore if there's an incoming
decrement but the target already reached its minimum value. If checked, the target value will jump
to its maximum value instead. It works analogously if there's an incoming increment and the target
Expand All @@ -1998,10 +2004,10 @@ The following UI elements make sense for button-like control elements only (keys
...), not for knobs or faders. Also, they only affect *control* direction. However of course, in an indirect way they
affect *feedback* as well (because they might change some target values).

- **Fire:** Normally, when a button gets pressed, it controls the target immediately. However, by using this dropdown
- **Fire mode (left dropdown):** Normally, when a button gets pressed, it controls the target immediately. However, by using this dropdown
and by changing the values below it, you can change this behavior. This dropdown provides different fire modes that
decide how exactly ReaLearn should cope with button presses.
- **Normal (fire on release if Min/Max > 0 ms):** This mode is essential in order to be able to distinguish between
- **Fire on press (or release if > 0 ms):** This mode is essential in order to be able to distinguish between
different press durations.
- **Min** and **Max** decide how long a button needs to be pressed to have an effect.
- By default, both min and max will be at 0 ms, which means that the duration doesn't matter and
Expand All @@ -2016,19 +2022,19 @@ affect *feedback* as well (because they might change some target values).
depending on how long it has been pressed. For this, use settings like the following:
- Short press: 0 ms - 250 ms
- Long press: 250 ms - 5000 ms
- **After timeout:** This mode is more "satisfying" because it will let ReaLearn "fire" immediately once a certain
- **Fire after timeout:** This mode is more "satisfying" because it will let ReaLearn "fire" immediately once a certain
time has passed since the press of the button. However, obviously it doesn't have the concept of a "Maximum"
press duration, so it can't be used to execute different things depending on different press durations (or only as
the last part in the press duration chain, so to say).
- **Timeout:** Sets the timeout in milliseconds. If this is zero, everything will behave as usual.
- **After timeout, keep firing (turbo):** Welcome to turbo mode. It will keep hitting your target (always with
- **Fire after timeout, keep firing (turbo):** Welcome to turbo mode. It will keep hitting your target (always with
with the initial button press velocity) at a specific rate. Optionally with an initial delay. Epic!
- **Timeout:** This is the initial delay before anything happens. Can be zero, then turbo stage is entered
instantly on press.
- **Rate:** This is how frequently the target will be hit once the timeout has passed. In practice it won't
happen more frequently than about 30 ms (REAPER's main thread loop frequency).
- **Double press:** This reacts to double presses of a button (analog to double clicks with the mouse).
- **Single press (if hold < Max ms):** If you want to do something in response to a double press, chances are that
- **Fire on double press:** This reacts to double presses of a button (analog to double clicks with the mouse).
- **Fire after single press (if hold < Max ms):** If you want to do something in response to a double press, chances are that
you want to do something *else* in response to just a single press. The *Normal* fire mode will fire no matter
what! That's why there's an additional *Single press* mode that will not respond to double clicks. Needless to
say, the response happens *slightly* delayed - because ReaLearn needs to wait a bit to see if it's going to be a
Expand All @@ -2039,6 +2045,13 @@ affect *feedback* as well (because they might change some target values).
- Mapping 1 "Single press" with Max = 499ms
- Mapping 2 "Double press"
- Mapping 3 "After timeout" with Timeout = 500ms
- **Button filter (right dropdown)**: This allows you to easily ignore button presses or releases.
- **Press & release:** ReaLearn will process both button presses (control value = 0%) and button releases (control
value > 0%). This is the default.
- **Press only:** Makes ReaLearn ignore the release of the button. The same thing can be achieved by setting
*Source Min* to 1. However, doing so would also affect the feedback direction, which is often undesirable
because it will mess with the button LED color or on/off state.
- **Release only:** Makes ReaLearn ignore the press of the button (just processing its release). Rare, but possible.


### REAPER actions
Expand Down
2 changes: 1 addition & 1 deletion main/lib/helgoboss-learn
15 changes: 13 additions & 2 deletions main/src/application/mode_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ use crate::core::{prop, Prop};
use crate::domain::{EelTransformation, Mode, OutputVariable};

use helgoboss_learn::{
full_unit_interval, AbsoluteMode, DiscreteIncrement, FireMode, Interval, OutOfRangeBehavior,
PressDurationProcessor, SoftSymmetricUnitValue, TakeoverMode, UnitValue,
full_unit_interval, AbsoluteMode, ButtonUsage, DiscreteIncrement, EncoderUsage, FireMode,
Interval, OutOfRangeBehavior, PressDurationProcessor, SoftSymmetricUnitValue, TakeoverMode,
UnitValue,
};

use rx_util::UnitEvent;
Expand All @@ -24,6 +25,8 @@ pub struct ModeModel {
pub fire_mode: Prop<FireMode>,
pub round_target_value: Prop<bool>,
pub takeover_mode: Prop<TakeoverMode>,
pub button_usage: Prop<ButtonUsage>,
pub encoder_usage: Prop<EncoderUsage>,
pub eel_control_transformation: Prop<String>,
pub eel_feedback_transformation: Prop<String>,
// For relative control values.
Expand Down Expand Up @@ -65,6 +68,8 @@ impl Default for ModeModel {
fire_mode: prop(Default::default()),
round_target_value: prop(false),
takeover_mode: prop(Default::default()),
button_usage: prop(Default::default()),
encoder_usage: prop(Default::default()),
eel_control_transformation: prop(String::new()),
eel_feedback_transformation: prop(String::new()),
step_interval: prop(Self::default_step_size_interval()),
Expand Down Expand Up @@ -99,6 +104,8 @@ impl ModeModel {
self.fire_mode.set(def.fire_mode.get());
self.round_target_value.set(def.round_target_value.get());
self.takeover_mode.set(def.takeover_mode.get());
self.button_usage.set(def.button_usage.get());
self.encoder_usage.set(def.encoder_usage.get());
self.rotate.set(def.rotate.get());
self.make_absolute.set(def.make_absolute.get());
self.reverse.set(def.reverse.get());
Expand All @@ -120,6 +127,8 @@ impl ModeModel {
.merge(self.fire_mode.changed())
.merge(self.round_target_value.changed())
.merge(self.takeover_mode.changed())
.merge(self.button_usage.changed())
.merge(self.encoder_usage.changed())
.merge(self.eel_control_transformation.changed())
.merge(self.eel_feedback_transformation.changed())
.merge(self.step_interval.changed())
Expand Down Expand Up @@ -147,6 +156,8 @@ impl ModeModel {
self.turbo_rate.get(),
),
takeover_mode: self.takeover_mode.get(),
encoder_usage: self.encoder_usage.get(),
button_usage: self.button_usage.get(),
reverse: self.reverse.get(),
rotate: self.rotate.get(),
increment_counter: 0,
Expand Down
16 changes: 14 additions & 2 deletions main/src/infrastructure/data/mode_model_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use crate::core::default_util::{is_default, is_unit_value_one, unit_value_one};
use crate::infrastructure::data::MigrationDescriptor;
use crate::infrastructure::plugin::App;
use helgoboss_learn::{
AbsoluteMode, FireMode, Interval, OutOfRangeBehavior, SoftSymmetricUnitValue, TakeoverMode,
UnitValue,
AbsoluteMode, ButtonUsage, EncoderUsage, FireMode, Interval, OutOfRangeBehavior,
SoftSymmetricUnitValue, TakeoverMode, UnitValue,
};
use serde::{Deserialize, Serialize};
use slog::debug;
Expand Down Expand Up @@ -66,6 +66,10 @@ pub struct ModeModelData {
#[serde(default, skip_serializing_if = "is_default")]
takeover_mode: TakeoverMode,
#[serde(default, skip_serializing_if = "is_default")]
button_usage: ButtonUsage,
#[serde(default, skip_serializing_if = "is_default")]
encoder_usage: EncoderUsage,
#[serde(default, skip_serializing_if = "is_default")]
rotate_is_enabled: bool,
#[serde(default, skip_serializing_if = "is_default")]
make_absolute_enabled: bool,
Expand Down Expand Up @@ -113,6 +117,8 @@ impl ModeModelData {
// Not used anymore since ReaLearn v2.8.0-pre3
scale_mode_enabled: false,
takeover_mode: model.takeover_mode.get(),
button_usage: model.button_usage.get(),
encoder_usage: model.encoder_usage.get(),
rotate_is_enabled: model.rotate.get(),
make_absolute_enabled: model.make_absolute.get(),
}
Expand Down Expand Up @@ -215,6 +221,12 @@ impl ModeModelData {
model
.takeover_mode
.set_with_optional_notification(takeover_mode, with_notification);
model
.button_usage
.set_with_optional_notification(self.button_usage, with_notification);
model
.encoder_usage
.set_with_optional_notification(self.encoder_usage, with_notification);
model
.rotate
.set_with_optional_notification(self.rotate_is_enabled, with_notification);
Expand Down
2 changes: 2 additions & 0 deletions main/src/infrastructure/ui/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,6 @@ pub mod root {
pub const ID_TARGET_CHECK_BOX_6: u32 = 40107;
pub const ID_MODE_TAKEOVER_MODE: u32 = 40108;
pub const ID_TARGET_HINT: u32 = 40109;
pub const ID_MODE_RELATIVE_FILTER_COMBO_BOX: u32 = 40110;
pub const ID_MODE_BUTTON_FILTER_COMBO_BOX: u32 = 40111;
}
67 changes: 65 additions & 2 deletions main/src/infrastructure/ui/mapping_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use crate::infrastructure::ui::{ItemProp, MainPanel, MappingHeaderPanel, YamlEdi

use enum_iterator::IntoEnumIterator;
use helgoboss_learn::{
AbsoluteMode, ControlValue, FireMode, MidiClockTransportMessage, OscTypeTag,
OutOfRangeBehavior, SoftSymmetricUnitValue, SourceCharacter, TakeoverMode, Target, UnitValue,
AbsoluteMode, ButtonUsage, ControlValue, EncoderUsage, FireMode, MidiClockTransportMessage,
OscTypeTag, OutOfRangeBehavior, SoftSymmetricUnitValue, SourceCharacter, TakeoverMode, Target,
UnitValue,
};
use helgoboss_midi::{Channel, U14, U7};
use reaper_high::{
Expand Down Expand Up @@ -781,6 +782,26 @@ impl<'a> MutableMappingPanel<'a> {
self.mapping.mode_model.takeover_mode.set(mode);
}

fn update_button_usage(&mut self) {
let mode = self
.view
.require_control(root::ID_MODE_BUTTON_FILTER_COMBO_BOX)
.selected_combo_box_item_index()
.try_into()
.expect("invalid button usage");
self.mapping.mode_model.button_usage.set(mode);
}

fn update_encoder_usage(&mut self) {
let mode = self
.view
.require_control(root::ID_MODE_RELATIVE_FILTER_COMBO_BOX)
.selected_combo_box_item_index()
.try_into()
.expect("invalid encoder usage");
self.mapping.mode_model.encoder_usage.set(mode);
}

fn update_mode_reverse(&mut self) {
self.mapping.mode_model.reverse.set(
self.view
Expand Down Expand Up @@ -1618,6 +1639,8 @@ impl<'a> ImmutableMappingPanel<'a> {
self.fill_mode_type_combo_box();
self.fill_mode_out_of_range_behavior_combo_box();
self.fill_mode_takeover_mode_combo_box();
self.fill_mode_button_usage_combo_box();
self.fill_mode_encoder_usage_combo_box();
self.fill_mode_fire_mode_combo_box();
self.fill_target_category_combo_box();
}
Expand Down Expand Up @@ -3103,6 +3126,8 @@ impl<'a> ImmutableMappingPanel<'a> {
self.invalidate_mode_out_of_range_behavior_combo_box();
self.invalidate_mode_round_target_value_check_box();
self.invalidate_mode_takeover_mode_combo_box();
self.invalidate_mode_button_usage_combo_box();
self.invalidate_mode_encoder_usage_combo_box();
self.invalidate_mode_reverse_check_box();
self.invalidate_mode_eel_control_transformation_edit_control();
self.invalidate_mode_eel_feedback_transformation_edit_control();
Expand Down Expand Up @@ -3544,6 +3569,22 @@ impl<'a> ImmutableMappingPanel<'a> {
.unwrap();
}

fn invalidate_mode_button_usage_combo_box(&self) {
let usage = self.mode.button_usage.get();
self.view
.require_control(root::ID_MODE_BUTTON_FILTER_COMBO_BOX)
.select_combo_box_item_by_index(usage.into())
.unwrap();
}

fn invalidate_mode_encoder_usage_combo_box(&self) {
let usage = self.mode.encoder_usage.get();
self.view
.require_control(root::ID_MODE_RELATIVE_FILTER_COMBO_BOX)
.select_combo_box_item_by_index(usage.into())
.unwrap();
}

fn invalidate_mode_reverse_check_box(&self) {
self.view
.require_control(root::ID_SETTINGS_REVERSE_CHECK_BOX)
Expand Down Expand Up @@ -3762,6 +3803,14 @@ impl<'a> ImmutableMappingPanel<'a> {
.when_do_sync(mode.takeover_mode.changed(), |view| {
view.invalidate_mode_takeover_mode_combo_box();
});
self.panel
.when_do_sync(mode.button_usage.changed(), |view| {
view.invalidate_mode_button_usage_combo_box();
});
self.panel
.when_do_sync(mode.encoder_usage.changed(), |view| {
view.invalidate_mode_encoder_usage_combo_box();
});
self.panel.when_do_sync(mode.rotate.changed(), |view| {
view.invalidate_mode_rotate_check_box();
});
Expand Down Expand Up @@ -3892,6 +3941,18 @@ impl<'a> ImmutableMappingPanel<'a> {
.fill_combo_box_indexed(TakeoverMode::into_enum_iter());
}

fn fill_mode_button_usage_combo_box(&self) {
self.view
.require_control(root::ID_MODE_BUTTON_FILTER_COMBO_BOX)
.fill_combo_box_indexed(ButtonUsage::into_enum_iter());
}

fn fill_mode_encoder_usage_combo_box(&self) {
self.view
.require_control(root::ID_MODE_RELATIVE_FILTER_COMBO_BOX)
.fill_combo_box_indexed(EncoderUsage::into_enum_iter());
}

fn fill_target_type_combo_box(&self) {
let b = self.view.require_control(root::ID_TARGET_TYPE_COMBO_BOX);
use TargetCategory::*;
Expand Down Expand Up @@ -4007,6 +4068,8 @@ impl View for MappingPanel {
self.write(|p| p.update_mode_out_of_range_behavior())
}
root::ID_MODE_TAKEOVER_MODE => self.write(|p| p.update_takeover_mode()),
root::ID_MODE_BUTTON_FILTER_COMBO_BOX => self.write(|p| p.update_button_usage()),
root::ID_MODE_RELATIVE_FILTER_COMBO_BOX => self.write(|p| p.update_encoder_usage()),
root::ID_MODE_FIRE_COMBO_BOX => self.write(|p| p.update_mode_fire_mode()),
// Target
root::ID_TARGET_CATEGORY_COMBO_BOX => self.write(|p| p.update_target_category()),
Expand Down
Loading

0 comments on commit 003201a

Please sign in to comment.