Skip to content

Commit

Permalink
#184 Make some domain logic support discrete values
Browse files Browse the repository at this point in the history
  • Loading branch information
helgoboss committed Jun 13, 2021
1 parent 8fee18c commit 6d2f69c
Show file tree
Hide file tree
Showing 42 changed files with 265 additions and 200 deletions.
10 changes: 7 additions & 3 deletions main/src/domain/eel_midi_source_script.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::base::eel;
use helgoboss_learn::{MidiSourceScript, RawMidiEvent, UnitValue};
use helgoboss_learn::{AbsoluteValue, MidiSourceScript, RawMidiEvent, UnitValue};

use std::sync::Arc;

Expand Down Expand Up @@ -40,9 +40,13 @@ impl EelMidiSourceScript {
}

impl MidiSourceScript for EelMidiSourceScript {
fn execute(&self, input_value: UnitValue) -> Result<Box<RawMidiEvent>, &'static str> {
fn execute(&self, input_value: AbsoluteValue) -> Result<Box<RawMidiEvent>, &'static str> {
let y_value = match input_value {
AbsoluteValue::Continuous(v) => v.get(),
AbsoluteValue::Discrete(f) => f.actual() as f64,
};
let slice = unsafe {
self.eel_unit.y.set(input_value.get());
self.eel_unit.y.set(y_value);
self.eel_unit.msg_size.set(0.0);
self.eel_unit.program.execute();
let msg_size = self.eel_unit.msg_size.get().round() as i32;
Expand Down
4 changes: 2 additions & 2 deletions main/src/domain/eventing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::domain::{
CompoundMappingSource, CompoundMappingTarget, MappingCompartment, MappingId, MidiSource,
ParameterArray, ProjectionFeedbackValue, SourceFeedbackValue,
};
use helgoboss_learn::{OscSource, UnitValue};
use helgoboss_learn::{AbsoluteValue, OscSource, UnitValue};
use std::collections::HashSet;
use std::fmt::Debug;

Expand All @@ -29,7 +29,7 @@ pub struct TargetValueChangedEvent<'a> {
pub compartment: MappingCompartment,
pub mapping_id: MappingId,
pub targets: &'a [CompoundMappingTarget],
pub new_value: UnitValue,
pub new_value: AbsoluteValue,
}

pub trait DomainEventHandler: Debug {
Expand Down
39 changes: 17 additions & 22 deletions main/src/domain/main_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ use crate::domain::{
use derive_more::Display;
use enum_map::EnumMap;
use helgoboss_learn::{
ControlValue, GroupInteraction, MidiSourceValue, MinIsMaxBehavior, ModeControlOptions,
OscSource, RawMidiEvent, Target, UnitValue, BASE_EPSILON, FEEDBACK_EPSILON,
AbsoluteValue, ControlValue, GroupInteraction, MidiSourceValue, MinIsMaxBehavior,
ModeControlOptions, OscSource, RawMidiEvent, Target, UnitValue, BASE_EPSILON, FEEDBACK_EPSILON,
};

use crate::domain::ui_util::{
Expand Down Expand Up @@ -90,7 +90,7 @@ struct Collections {
/// changing cursor position while stopped.
milli_dependent_feedback_mappings: EnumMap<MappingCompartment, HashSet<MappingId>>,
parameters: ParameterArray,
previous_target_values: EnumMap<MappingCompartment, HashMap<MappingId, UnitValue>>,
previous_target_values: EnumMap<MappingCompartment, HashMap<MappingId, AbsoluteValue>>,
}

#[derive(Debug)]
Expand Down Expand Up @@ -405,19 +405,18 @@ impl<EH: DomainEventHandler> MainProcessor<EH> {
&self.collections.mappings_with_virtual_targets,
&mut |t| {
if let Some(value) = t.current_value(control_context) {
let value = value.to_unit_value();
match previous_target_values[compartment].entry(*mapping_id) {
Entry::Occupied(mut e) => {
if (e.get().get() - value.get()).abs()
if (e.get().to_unit_value().get()
- value.to_unit_value().get())
.abs()
<= FEEDBACK_EPSILON
{
// Value hasn't changed significantly.
(false, None)
} else {
// Value has changed.
e.insert(value);
// TODO-high Here and in similar places we should
// return AbsoluteValue.
(true, Some(value))
}
}
Expand Down Expand Up @@ -1135,7 +1134,7 @@ impl<EH: DomainEventHandler> MainProcessor<EH> {
/// avoid a redundant query.
fn process_feedback_related_reaper_event(
&self,
mut f: impl Fn(&ReaperTarget) -> (bool, Option<UnitValue>),
mut f: impl Fn(&ReaperTarget) -> (bool, Option<AbsoluteValue>),
) {
for compartment in MappingCompartment::enum_iter() {
// Mappings with virtual targets don't need to be considered here because they don't
Expand All @@ -1150,7 +1149,7 @@ impl<EH: DomainEventHandler> MainProcessor<EH> {
&self,
compartment: MappingCompartment,
m: &MainMapping,
f: &mut impl FnMut(&ReaperTarget) -> (bool, Option<UnitValue>),
f: &mut impl FnMut(&ReaperTarget) -> (bool, Option<AbsoluteValue>),
) {
self.basics
.process_feedback_related_reaper_event_for_mapping(
Expand Down Expand Up @@ -1927,30 +1926,26 @@ impl<EH: DomainEventHandler> Basics<EH> {
}
let context = self.control_context();
if let Some(reference_value) = m.current_aggregated_target_value(context) {
let target_value = reference_value.normalize(
let normalized_target_value = reference_value.normalize(
&m.mode().target_value_interval,
&m.mode().discrete_target_value_interval,
MinIsMaxBehavior::PreferOne,
m.mode().use_discrete_processing,
BASE_EPSILON,
);

let interaction_value = match m.group_interaction() {
SameTargetValue => target_value,
InverseTargetValue => target_value.inverse(),
_ => unreachable!(),
};
let inverse = m.group_interaction() == InverseTargetValue;
self.process_other_mappings(
collections,
compartment,
mapping_id,
group_id,
|other_mapping, basics| {
let scaled_interaction_value = interaction_value
.denormalize(&other_mapping.mode().target_value_interval);
other_mapping.control_from_target(
ControlValue::AbsoluteContinuous(scaled_interaction_value),
normalized_target_value,
ControlOptions::default(),
basics.control_context(),
&basics.logger,
inverse,
)
},
);
Expand Down Expand Up @@ -1990,12 +1985,12 @@ impl<EH: DomainEventHandler> Basics<EH> {
compartment: MappingCompartment,
m: &MainMapping,
mappings_with_virtual_targets: &HashMap<MappingId, MainMapping>,
f: &mut impl FnMut(&ReaperTarget) -> (bool, Option<UnitValue>),
f: &mut impl FnMut(&ReaperTarget) -> (bool, Option<AbsoluteValue>),
) {
// It's enough if one of the resolved targets is affected. Then we are going to need the
// values of all of them!
let mut at_least_one_target_is_affected = false;
let new_values: Vec<(&ReaperTarget, Option<UnitValue>)> = m
let new_values: Vec<(&ReaperTarget, Option<AbsoluteValue>)> = m
.targets()
.iter()
.filter_map(|target| {
Expand Down Expand Up @@ -2114,7 +2109,7 @@ impl<EH: DomainEventHandler> Basics<EH> {
with_source_feedback,
value,
} => {
if let ControlValue::AbsoluteContinuous(v) = value.control_value() {
if let Ok(v) = value.control_value().to_absolute_value() {
for m in mappings_with_virtual_targets
.values()
.filter(|m| m.feedback_is_effectively_on())
Expand Down
85 changes: 48 additions & 37 deletions main/src/domain/mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,13 +501,29 @@ impl MainMapping {
/// functions. If `send_feedback_after_control` is on, this might return feedback.
pub fn control_from_target(
&mut self,
value: ControlValue,
value: AbsoluteValue,
options: ControlOptions,
context: ControlContext,
logger: &slog::Logger,
inverse: bool,
) -> Option<FeedbackValue> {
self.control_internal(options, context, logger, |_, _, _, _| {
Some(ModeControlResult::HitTarget(value))
self.control_internal(options, context, logger, |_, _, mode, target| {
let mut v = value;
let control_type = target.control_type();
// This is very similar to the mode logic, but just a small subset.
if inverse {
let normalized_max = control_type
.discrete_max()
.map(|m| mode.discrete_target_value_interval.normalize_to_min(m));
v = v.inverse(normalized_max);
}
v = v.denormalize(
&mode.target_value_interval,
&mode.discrete_target_value_interval,
mode.use_discrete_processing,
control_type.discrete_max(),
);
Some(ModeControlResult::HitTarget(ControlValue::from_absolute(v)))
})
.feedback_value
}
Expand Down Expand Up @@ -632,8 +648,7 @@ impl MainMapping {
})
.max()?;
self.feedback_given_target_value(
// TODO-high Discrete
combined_target_value.to_unit_value(),
combined_target_value,
with_projection_feedback,
!self.core.is_echo(),
)
Expand All @@ -645,20 +660,18 @@ impl MainMapping {

pub fn given_or_current_value(
&self,
target_value: Option<UnitValue>,
target_value: Option<AbsoluteValue>,
target: &ReaperTarget,
context: ControlContext,
) -> Option<UnitValue> {
// TODO-high discrete
target_value.or_else(|| target.current_value(context).map(|v| v.to_unit_value()))
) -> Option<AbsoluteValue> {
target_value.or_else(|| target.current_value(context))
}

pub fn current_aggregated_target_value(&self, context: ControlContext) -> Option<UnitValue> {
// TODO-high discrete
let values = self
.targets
.iter()
.map(|t| t.current_value(context).map(|v| v.to_unit_value()));
pub fn current_aggregated_target_value(
&self,
context: ControlContext,
) -> Option<AbsoluteValue> {
let values = self.targets.iter().map(|t| t.current_value(context));
aggregate_target_values(values)
}

Expand All @@ -672,30 +685,24 @@ impl MainMapping {

pub fn feedback_given_target_value(
&self,
target_value: UnitValue,
target_value: AbsoluteValue,
with_projection_feedback: bool,
with_source_feedback: bool,
) -> Option<FeedbackValue> {
let options = ModeFeedbackOptions {
source_is_virtual: self.core.source.is_virtual(),
max_discrete_source_value: self.core.source.max_discrete_value(),
};
// TODO-high discrete
let mode_value = self
.core
.mode
.feedback_with_options(AbsoluteValue::Continuous(target_value), options)?;
// TODO-high discrete
self.feedback_given_mode_value(
mode_value.to_unit_value(),
with_projection_feedback,
with_source_feedback,
)
.feedback_with_options(target_value, options)?;
self.feedback_given_mode_value(mode_value, with_projection_feedback, with_source_feedback)
}

pub fn feedback_given_mode_value(
&self,
mode_value: UnitValue,
mode_value: AbsoluteValue,
with_projection_feedback: bool,
with_source_feedback: bool,
) -> Option<FeedbackValue> {
Expand All @@ -712,7 +719,7 @@ impl MainMapping {
pub fn zero_feedback(&self) -> Option<FeedbackValue> {
// TODO-medium "Unused" and "zero" could be a difference for projection so we should
// have different values for that (at the moment it's not though).
self.feedback_given_mode_value(UnitValue::MIN, true, true)
self.feedback_given_mode_value(AbsoluteValue::Continuous(UnitValue::MIN), true, true)
}

fn feedback_after_control_if_enabled(
Expand Down Expand Up @@ -933,7 +940,7 @@ impl QualifiedSource {
self.compartment,
self.id,
&self.source,
UnitValue::MIN,
AbsoluteValue::Continuous(UnitValue::MIN),
true,
true,
)
Expand All @@ -947,7 +954,7 @@ impl CompoundMappingSource {
Midi(s) => s.format_control_value(value),
Virtual(s) => s.format_control_value(value),
Osc(s) => s.format_control_value(value),
Never => Ok(format_percentage_without_unit(value.as_unit_value()?.get())),
Never => Ok(format_percentage_without_unit(value.to_unit_value()?.get())),
}
}

Expand All @@ -971,11 +978,13 @@ impl CompoundMappingSource {
}
}

pub fn feedback(&self, feedback_value: UnitValue) -> Option<SourceFeedbackValue> {
pub fn feedback(&self, feedback_value: AbsoluteValue) -> Option<SourceFeedbackValue> {
use CompoundMappingSource::*;
match self {
Midi(s) => s.feedback(feedback_value).map(SourceFeedbackValue::Midi),
Osc(s) => s.feedback(feedback_value).map(SourceFeedbackValue::Osc),
Osc(s) => s
.feedback(feedback_value.to_unit_value())
.map(SourceFeedbackValue::Osc),
// This is handled in a special way by consumers.
Virtual(_) => None,
// No feedback for never source.
Expand All @@ -999,6 +1008,8 @@ impl CompoundMappingSource {
use CompoundMappingSource::*;
match self {
Midi(s) => s.max_discrete_value(),
// TODO-medium OSC will also support discrete values as soon as we allow integers and
// configuring max values
Virtual(_) | Osc(_) | Never => None,
}
}
Expand All @@ -1019,7 +1030,7 @@ impl FeedbackValue {
compartment: MappingCompartment,
id: MappingId,
source: &CompoundMappingSource,
mode_value: UnitValue,
mode_value: AbsoluteValue,
with_projection_feedback: bool,
with_source_feedback: bool,
) -> Option<FeedbackValue> {
Expand All @@ -1036,7 +1047,7 @@ impl FeedbackValue {
let projection = if with_projection_feedback
&& compartment == MappingCompartment::ControllerMappings
{
Some(ProjectionFeedbackValue::new(id, mode_value))
Some(ProjectionFeedbackValue::new(id, mode_value.to_unit_value()))
} else {
None
};
Expand Down Expand Up @@ -1345,7 +1356,7 @@ impl RealearnTarget for CompoundMappingTarget {
&self,
evt: &ChangeEvent,
control_context: ControlContext,
) -> (bool, Option<UnitValue>) {
) -> (bool, Option<AbsoluteValue>) {
use CompoundMappingTarget::*;
match self {
Reaper(t) => t.process_change_event(evt, control_context),
Expand All @@ -1356,7 +1367,7 @@ impl RealearnTarget for CompoundMappingTarget {
fn value_changed_from_additional_feedback_event(
&self,
evt: &AdditionalFeedbackEvent,
) -> (bool, Option<UnitValue>) {
) -> (bool, Option<AbsoluteValue>) {
use CompoundMappingTarget::*;
match self {
Reaper(t) => t.value_changed_from_additional_feedback_event(evt),
Expand All @@ -1367,7 +1378,7 @@ impl RealearnTarget for CompoundMappingTarget {
fn value_changed_from_instance_feedback_event(
&self,
evt: &InstanceFeedbackEvent,
) -> (bool, Option<UnitValue>) {
) -> (bool, Option<AbsoluteValue>) {
use CompoundMappingTarget::*;
match self {
Reaper(t) => t.value_changed_from_instance_feedback_event(evt),
Expand Down Expand Up @@ -1522,8 +1533,8 @@ pub(crate) enum ControlMode {
/// Supposed to be used to aggregate values of all resolved targets of one mapping into one single
/// value. At the moment we just take the maximum.
pub fn aggregate_target_values(
values: impl Iterator<Item = Option<UnitValue>>,
) -> Option<UnitValue> {
values: impl Iterator<Item = Option<AbsoluteValue>>,
) -> Option<AbsoluteValue> {
values.map(|v| v.unwrap_or_default()).max()
}

Expand Down
2 changes: 1 addition & 1 deletion main/src/domain/real_time_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1243,7 +1243,7 @@ fn process_real_mapping(
.into();
let v = control_value
.ok_or("target already has desired value")?
.as_unit_value()?;
.to_absolute_value()?;
match reaper_target {
RealTimeReaperTarget::SendMidi(t) => {
// This is a type of mapping that we should process right here because we want to
Expand Down
Loading

0 comments on commit 6d2f69c

Please sign in to comment.