Files
virtual-controller/plugin-reaper-relearn/main/src/application/target_model.rs
T
Paul Lipscomb 7ecc718f5d Reorganize top-level directories with clearer naming convention
app -> app-desktop-macos, presets -> app-presets, server -> remote-server,
daw-config-reaper -> osc-config-daw, plugin-reaper-realearn -> plugin-reaper-relearn.
Updated run.py and presets.py path references accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 17:51:05 -04:00

4885 lines
167 KiB
Rust

use base::default_util::is_default;
use derive_more::Display;
use helgoboss_learn::{
AbsoluteValue, ControlType, Interval, OscArgDescriptor, OscTypeTag, Target,
DEFAULT_OSC_ARG_VALUE_RANGE,
};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use reaper_high::{
Action, BookmarkType, Fx, FxChain, FxParameter, Guid, Project, Track, TrackRoute,
TrackRoutePartner,
};
use serde::{Deserialize, Serialize};
use crate::application::{
build_action_from_smart_command_name, build_smart_command_name_from_action, Affected, Change,
GetProcessingRelevance, ProcessingRelevance, UnitModel,
};
use crate::domain::{
find_bookmark, get_fx_name, get_fx_params, get_non_present_virtual_route_label,
get_non_present_virtual_track_label, get_track_routes, ActionInvocationType, AnyOnParameter,
CompartmentKind, CompartmentParamIndex, CompoundMappingTarget, Exclusivity,
ExpressionEvaluator, ExtendedProcessorContext, FeedbackResolution, FxDescriptor, FxDisplayType,
FxParameterDescriptor, GroupId, MappingId, MappingKey, MappingRef, MappingSnapshotId,
MouseActionType, OscDeviceId, PotFilterItemsTargetSettings, ProcessorContext,
QualifiedMappingId, RealearnTarget, ReaperTarget, ReaperTargetType, SeekOptions,
SendMidiDestinationType, SoloBehavior, Tag, TagScope, TouchedRouteParameterType,
TouchedTrackParameterType, TrackDescriptor, TrackExclusivity, TrackGangBehavior,
TrackRouteDescriptor, TrackRouteSelector, TrackRouteType, TransportAction,
UnresolvedActionTarget, UnresolvedAllTrackFxEnableTarget, UnresolvedAnyOnTarget,
UnresolvedAutomationModeOverrideTarget, UnresolvedBrowseFxsTarget, UnresolvedBrowseGroupTarget,
UnresolvedBrowsePotFilterItemsTarget, UnresolvedBrowsePotPresetsTarget,
UnresolvedBrowseTracksTarget, UnresolvedCompartmentParameterValueTarget,
UnresolvedCompoundMappingTarget, UnresolvedDummyTarget, UnresolvedEnableInstancesTarget,
UnresolvedEnableMappingsTarget, UnresolvedEnableUnitsTarget, UnresolvedFxEnableTarget,
UnresolvedFxOnlineTarget, UnresolvedFxOpenTarget, UnresolvedFxParameterTarget,
UnresolvedFxParameterTouchStateTarget, UnresolvedFxPresetTarget, UnresolvedFxToolTarget,
UnresolvedGoToBookmarkTarget, UnresolvedLastTouchedTarget, UnresolvedLoadFxSnapshotTarget,
UnresolvedLoadMappingSnapshotTarget, UnresolvedLoadPotPresetTarget, UnresolvedMidiSendTarget,
UnresolvedModifyMappingTarget, UnresolvedMouseTarget, UnresolvedOscSendTarget,
UnresolvedPlayrateTarget, UnresolvedPreviewPotPresetTarget, UnresolvedReaperTarget,
UnresolvedRouteAutomationModeTarget, UnresolvedRouteMonoTarget, UnresolvedRouteMuteTarget,
UnresolvedRoutePanTarget, UnresolvedRoutePhaseTarget, UnresolvedRouteTouchStateTarget,
UnresolvedRouteVolumeTarget, UnresolvedSeekTarget, UnresolvedStreamDeckBrightnessTarget,
UnresolvedTakeMappingSnapshotTarget, UnresolvedTempoTarget, UnresolvedTrackArmTarget,
UnresolvedTrackAutomationModeTarget, UnresolvedTrackMonitoringModeTarget,
UnresolvedTrackMuteTarget, UnresolvedTrackPanTarget, UnresolvedTrackParentSendTarget,
UnresolvedTrackPeakTarget, UnresolvedTrackPhaseTarget, UnresolvedTrackSelectionTarget,
UnresolvedTrackShowTarget, UnresolvedTrackSoloTarget, UnresolvedTrackToolTarget,
UnresolvedTrackTouchStateTarget, UnresolvedTrackVolumeTarget, UnresolvedTrackWidthTarget,
UnresolvedTransportTarget, VirtualChainFx, VirtualControlElement, VirtualControlElementId,
VirtualFx, VirtualFxParameter, VirtualMappingSnapshotIdForLoad,
VirtualMappingSnapshotIdForTake, VirtualTarget, VirtualTrack, VirtualTrackRoute,
};
use crate::domain::{VirtualPlaytimeColumn, VirtualPlaytimeRow, VirtualPlaytimeSlot};
use serde_repr::*;
use std::borrow::Cow;
use std::error::Error;
use crate::domain::ui_util::format_tags_as_csv;
use base::hash_util::NonCryptoHashSet;
use helgobox_api::persistence::{
ActionScope, Axis, BrowseTracksMode, ClipColumnTrackContext, FxChainDescriptor,
FxDescriptorCommons, FxToolAction, InputDeviceMidiDestination, InstanceTagKind,
LearnTargetMappingModification, LearnableTargetKind, MappingModification,
MappingSnapshotDescForLoad, MappingSnapshotDescForTake, MonitoringMode, MouseAction,
MouseButton, PlaytimeColumnAction, PlaytimeColumnDescriptor, PlaytimeMatrixAction,
PlaytimeRowAction, PlaytimeRowDescriptor, PlaytimeSlotDescriptor, PlaytimeSlotManagementAction,
PlaytimeSlotTransportAction, PotFilterKind, SeekBehavior, SendMidiDestination,
SetTargetToLastTouchedMappingModification, TargetTouchCause, TrackDescriptorCommons,
TrackFxChain, TrackScope, TrackToolAction, VirtualControlElementCharacter,
};
use playtime_api::persistence::ColumnAddress;
use reaper_medium::{
AutomationMode, BookmarkId, GlobalAutomationModeOverride, InputMonitoringMode,
MidiInputDeviceId, SectionId, TrackArea, TrackLocation, TrackSendDirection,
};
use std::fmt;
use std::fmt::{Display, Formatter};
use std::rc::Rc;
use strum::{EnumIter, IntoEnumIterator};
use wildmatch::WildMatch;
#[allow(clippy::enum_variant_names)]
pub enum TargetCommand {
SetCategory(TargetCategory),
SetUnit(TargetUnit),
SetControlElementCharacter(VirtualControlElementCharacter),
SetControlElementId(VirtualControlElementId),
SetLearnable(bool),
SetTargetType(ReaperTargetType),
SetActionScope(ActionScope),
SetSmartCommandName(Option<String>),
SetActionInvocationType(ActionInvocationType),
SetWithTrack(bool),
SetTrackName(String),
SetTrackIndex(u32),
SetTrackExpression(String),
SetEnableOnlyIfTrackSelected(bool),
SetFxIsInputFx(bool),
SetFxName(String),
SetFxIndex(u32),
SetFxExpression(String),
SetEnableOnlyIfFxHasFocus(bool),
SetParamType(VirtualFxParameterType),
SetParamIndex(u32),
SetParamName(String),
SetParamExpression(String),
SetRetrigger(bool),
SetRealTime(bool),
SetRouteSelectorType(TrackRouteSelectorType),
SetRouteType(TrackRouteType),
SetRouteId(Option<Guid>),
SetRouteIndex(u32),
SetRouteName(String),
SetRouteExpression(String),
SetSeekBehavior(SeekBehavior),
SetSoloBehavior(SoloBehavior),
SetTrackExclusivity(TrackExclusivity),
SetTrackToolAction(TrackToolAction),
SetGangBehavior(TrackGangBehavior),
SetBrowseTracksMode(BrowseTracksMode),
SetFxToolAction(FxToolAction),
SetTransportAction(TransportAction),
SetAnyOnParameter(AnyOnParameter),
SetFxSnapshot(Option<FxSnapshot>),
SetTouchedTrackParameterType(TouchedTrackParameterType),
SetTouchedRouteParameterType(TouchedRouteParameterType),
SetBookmarkRef(u32),
SetBookmarkType(BookmarkType),
SetBookmarkAnchorType(BookmarkAnchorType),
SetUseTimeSelection(bool),
SetUseLoopPoints(bool),
SetUseRegions(bool),
SetUseProject(bool),
SetMoveView(bool),
SetSeekPlay(bool),
SetFeedbackResolution(FeedbackResolution),
SetTrackArea(RealearnTrackArea),
SetAutomationMode(RealearnAutomationMode),
SetMonitoringMode(MonitoringMode),
SetAutomationModeOverrideType(AutomationModeOverrideType),
SetFxDisplayType(FxDisplayType),
SetScrollArrangeView(bool),
SetScrollMixer(bool),
SetRawMidiPattern(String),
SetSendMidiDestinationType(SendMidiDestinationType),
SetMidiInputDevice(Option<MidiInputDeviceId>),
SetOscAddressPattern(String),
SetOscArgIndex(Option<u32>),
SetOscArgTypeTag(OscTypeTag),
SetOscArgValueRange(Interval<f64>),
SetOscDevId(Option<OscDeviceId>),
SetMouseActionType(MouseActionType),
SetAxis(Axis),
SetMouseButton(MouseButton),
SetPlaytimeSlot(PlaytimeSlotDescriptor),
SetPlaytimeColumn(PlaytimeColumnDescriptor),
SetPlaytimeRow(PlaytimeRowDescriptor),
SetPlaytimeSlotManagementAction(PlaytimeSlotManagementAction),
SetPlaytimeSlotTransportAction(PlaytimeSlotTransportAction),
SetPlaytimeMatrixAction(PlaytimeMatrixAction),
SetPlaytimeColumnAction(PlaytimeColumnAction),
SetPlaytimeRowAction(PlaytimeRowAction),
SetStopColumnIfSlotEmpty(bool),
SetPollForFeedback(bool),
SetInstanceTagKind(InstanceTagKind),
SetTags(Vec<Tag>),
SetExclusivity(Exclusivity),
SetGroupId(GroupId),
SetActiveMappingsOnly(bool),
SetMappingSnapshotTypeForLoad(MappingSnapshotTypeForLoad),
SetMappingSnapshotTypeForTake(MappingSnapshotTypeForTake),
SetMappingSnapshotId(Option<MappingSnapshotId>),
SetMappingSnapshotDefaultValue(Option<AbsoluteValue>),
SetPotFilterItemKind(PotFilterKind),
SetMappingModificationKind(MappingModificationKind),
SetMappingRef(MappingRefModel),
SetLearnableTargetKinds(NonCryptoHashSet<LearnableTargetKind>),
SetTouchCause(TargetTouchCause),
}
#[derive(Eq, PartialEq)]
pub enum TargetProp {
Category,
Unit,
ControlElementType,
ControlElementId,
Learnable,
TargetType,
ActionScope,
SmartCommandName,
ActionInvocationType,
WithTrack,
TrackType,
TrackId,
TrackName,
TrackIndex,
TrackExpression,
EnableOnlyIfTrackSelected,
FxType,
FxIsInputFx,
FxId,
FxName,
FxIndex,
FxExpression,
EnableOnlyIfFxHasFocus,
ParamType,
ParamIndex,
ParamName,
ParamExpression,
Retrigger,
RealTime,
RouteSelectorType,
RouteType,
RouteId,
RouteIndex,
RouteName,
RouteExpression,
SoloBehavior,
SeekBehavior,
TrackExclusivity,
TrackToolAction,
GangBehavior,
BrowseTracksMode,
FxToolAction,
TransportAction,
AnyOnParameter,
FxSnapshot,
TouchedTrackParameterType,
TouchedRouteParameterType,
BookmarkRef,
BookmarkType,
BookmarkAnchorType,
UseTimeSelection,
UseLoopPoints,
UseRegions,
UseProject,
MoveView,
SeekPlay,
FeedbackResolution,
TrackArea,
AutomationMode,
MonitoringMode,
AutomationModeOverrideType,
FxDisplayType,
ScrollArrangeView,
ScrollMixer,
RawMidiPattern,
SendMidiDestination,
MidiInputDevice,
OscAddressPattern,
OscArgIndex,
OscArgTypeTag,
OscArgValueRange,
OscDevId,
MouseActionType,
Axis,
MouseButton,
PlaytimeSlot,
PlaytimeColumn,
PlaytimeRow,
PlaytimeSlotManagementAction,
PlaytimeSlotTransportAction,
PlaytimeMatrixAction,
PlaytimeColumnAction,
PlaytimeRowAction,
StopColumnIfSlotEmpty,
PollForFeedback,
InstanceTagKind,
Tags,
Exclusivity,
GroupId,
ActiveMappingsOnly,
MappingSnapshotTypeForLoad,
MappingSnapshotTypeForTake,
MappingSnapshotId,
MappingSnapshotDefaultValue,
PotFilterItemKind,
MappingModificationKind,
MappingRef,
IncludedTargets,
TouchCause,
}
impl GetProcessingRelevance for TargetProp {
fn processing_relevance(&self) -> Option<ProcessingRelevance> {
// At the moment, all target aspects are relevant for processing.
Some(ProcessingRelevance::ProcessingRelevant)
}
}
impl Change<'_> for TargetModel {
type Command = TargetCommand;
type Prop = TargetProp;
fn change(&mut self, cmd: Self::Command) -> Option<Affected<TargetProp>> {
use Affected::*;
use TargetCommand as C;
use TargetProp as P;
let affected = match cmd {
C::SetCategory(v) => {
self.category = v;
One(P::Category)
}
C::SetUnit(v) => {
self.unit = v;
One(P::Unit)
}
C::SetControlElementCharacter(v) => {
self.control_element_character = v;
One(P::ControlElementType)
}
C::SetControlElementId(v) => {
self.control_element_id = v;
One(P::ControlElementId)
}
C::SetLearnable(v) => {
self.learnable = v;
One(P::Learnable)
}
C::SetTargetType(v) => {
self.r#type = v;
One(P::TargetType)
}
C::SetActionScope(v) => {
self.action_scope = v;
One(P::ActionScope)
}
C::SetSmartCommandName(v) => {
self.smart_command_name = v;
One(P::SmartCommandName)
}
C::SetActionInvocationType(v) => {
self.action_invocation_type = v;
One(P::ActionInvocationType)
}
C::SetWithTrack(v) => {
self.with_track = v;
One(P::WithTrack)
}
C::SetTrackName(v) => {
self.track_name = v;
One(P::TrackName)
}
C::SetTrackIndex(v) => {
self.track_index = v;
One(P::TrackIndex)
}
C::SetTrackExpression(v) => {
self.track_expression = v;
One(P::TrackExpression)
}
C::SetEnableOnlyIfTrackSelected(v) => {
self.enable_only_if_track_selected = v;
One(P::EnableOnlyIfTrackSelected)
}
C::SetFxIsInputFx(v) => {
self.fx_is_input_fx = v;
One(P::FxIsInputFx)
}
C::SetFxName(v) => {
self.fx_name = v;
One(P::FxName)
}
C::SetFxIndex(v) => {
self.fx_index = v;
One(P::FxIndex)
}
C::SetFxExpression(v) => {
self.fx_expression = v;
One(P::FxExpression)
}
C::SetEnableOnlyIfFxHasFocus(v) => {
self.enable_only_if_fx_has_focus = v;
One(P::EnableOnlyIfFxHasFocus)
}
C::SetParamType(v) => {
self.param_type = v;
One(P::ParamType)
}
C::SetParamIndex(v) => {
self.param_index = v;
One(P::ParamIndex)
}
C::SetParamName(v) => {
self.param_name = v;
One(P::ParamName)
}
C::SetParamExpression(v) => {
self.param_expression = v;
One(P::ParamExpression)
}
C::SetRetrigger(v) => {
self.retrigger = v;
One(P::Retrigger)
}
C::SetRealTime(v) => {
self.real_time = v;
One(P::RealTime)
}
C::SetRouteSelectorType(v) => {
self.route_selector_type = v;
One(P::RouteSelectorType)
}
C::SetRouteType(v) => {
self.route_type = v;
One(P::RouteType)
}
C::SetRouteId(v) => {
self.route_id = v;
One(P::RouteId)
}
C::SetRouteIndex(v) => {
self.route_index = v;
One(P::RouteIndex)
}
C::SetRouteName(v) => {
self.route_name = v;
One(P::RouteName)
}
C::SetRouteExpression(v) => {
self.route_expression = v;
One(P::RouteExpression)
}
C::SetSoloBehavior(v) => {
self.solo_behavior = v;
One(P::SoloBehavior)
}
C::SetSeekBehavior(v) => {
self.seek_behavior = v;
One(P::SeekBehavior)
}
C::SetTrackExclusivity(v) => {
self.track_exclusivity = v;
One(P::TrackExclusivity)
}
C::SetTrackToolAction(v) => {
self.track_tool_action = v;
One(P::TrackToolAction)
}
C::SetGangBehavior(v) => {
self.gang_behavior = v;
One(P::GangBehavior)
}
C::SetBrowseTracksMode(v) => {
self.browse_tracks_mode = v;
One(P::BrowseTracksMode)
}
C::SetFxToolAction(v) => {
self.fx_tool_action = v;
One(P::FxToolAction)
}
C::SetTransportAction(v) => {
self.transport_action = v;
One(P::TransportAction)
}
C::SetAnyOnParameter(v) => {
self.any_on_parameter = v;
One(P::AnyOnParameter)
}
C::SetFxSnapshot(v) => {
self.fx_snapshot = v;
One(P::FxSnapshot)
}
C::SetTouchedTrackParameterType(v) => {
self.touched_track_parameter_type = v;
One(P::TouchedTrackParameterType)
}
C::SetTouchedRouteParameterType(v) => {
self.touched_route_parameter_type = v;
One(P::TouchedRouteParameterType)
}
C::SetBookmarkRef(v) => {
self.bookmark_ref = v;
One(P::BookmarkRef)
}
C::SetBookmarkType(v) => {
self.bookmark_type = v;
One(P::BookmarkType)
}
C::SetBookmarkAnchorType(v) => {
self.bookmark_anchor_type = v;
One(P::BookmarkAnchorType)
}
C::SetUseTimeSelection(v) => {
self.use_time_selection = v;
One(P::UseTimeSelection)
}
C::SetUseLoopPoints(v) => {
self.use_loop_points = v;
One(P::UseLoopPoints)
}
C::SetUseRegions(v) => {
self.use_regions = v;
One(P::UseRegions)
}
C::SetUseProject(v) => {
self.use_project = v;
One(P::UseProject)
}
C::SetMoveView(v) => {
self.move_view = v;
One(P::MoveView)
}
C::SetSeekPlay(v) => {
self.seek_play = v;
One(P::SeekPlay)
}
C::SetFeedbackResolution(v) => {
self.feedback_resolution = v;
One(P::FeedbackResolution)
}
C::SetTrackArea(v) => {
self.track_area = v;
One(P::TrackArea)
}
C::SetAutomationMode(v) => {
self.automation_mode = v;
One(P::AutomationMode)
}
C::SetMonitoringMode(v) => {
self.monitoring_mode = v;
One(P::MonitoringMode)
}
C::SetAutomationModeOverrideType(v) => {
self.automation_mode_override_type = v;
One(P::AutomationModeOverrideType)
}
C::SetFxDisplayType(v) => {
self.fx_display_type = v;
One(P::FxDisplayType)
}
C::SetScrollArrangeView(v) => {
self.scroll_arrange_view = v;
One(P::ScrollArrangeView)
}
C::SetScrollMixer(v) => {
self.scroll_mixer = v;
One(P::ScrollMixer)
}
C::SetRawMidiPattern(v) => {
self.raw_midi_pattern = v;
One(P::RawMidiPattern)
}
C::SetSendMidiDestinationType(v) => {
self.send_midi_destination_type = v;
One(P::SendMidiDestination)
}
C::SetMidiInputDevice(v) => {
self.midi_input_device = v;
One(P::MidiInputDevice)
}
C::SetOscAddressPattern(v) => {
self.osc_address_pattern = v;
One(P::OscAddressPattern)
}
C::SetOscArgIndex(v) => {
self.osc_arg_index = v;
One(P::OscArgIndex)
}
C::SetOscArgTypeTag(v) => {
self.osc_arg_type_tag = v;
One(P::OscArgTypeTag)
}
C::SetOscArgValueRange(v) => {
self.osc_arg_value_range = v;
One(P::OscArgValueRange)
}
C::SetOscDevId(v) => {
self.osc_dev_id = v;
One(P::OscDevId)
}
C::SetMouseActionType(v) => {
self.mouse_action_type = v;
One(P::MouseActionType)
}
C::SetAxis(v) => {
self.axis = v;
One(P::Axis)
}
C::SetMouseButton(v) => {
self.mouse_button = v;
One(P::MouseButton)
}
C::SetPollForFeedback(v) => {
self.poll_for_feedback = v;
One(P::PollForFeedback)
}
C::SetInstanceTagKind(v) => {
self.instance_tag_kind = v;
One(P::InstanceTagKind)
}
C::SetTags(v) => {
self.tags = v;
One(P::Tags)
}
C::SetExclusivity(v) => {
self.exclusivity = v;
One(P::Exclusivity)
}
C::SetGroupId(v) => {
self.group_id = v;
One(P::GroupId)
}
C::SetActiveMappingsOnly(v) => {
self.active_mappings_only = v;
One(P::ActiveMappingsOnly)
}
C::SetMappingSnapshotTypeForLoad(v) => {
self.mapping_snapshot_type_for_load = v;
One(P::MappingSnapshotTypeForLoad)
}
C::SetMappingSnapshotTypeForTake(v) => {
self.mapping_snapshot_type_for_take = v;
One(P::MappingSnapshotTypeForTake)
}
C::SetMappingSnapshotId(v) => {
self.mapping_snapshot_id = v;
One(P::MappingSnapshotId)
}
C::SetMappingSnapshotDefaultValue(v) => {
self.mapping_snapshot_default_value = v;
One(P::MappingSnapshotDefaultValue)
}
C::SetPlaytimeSlot(s) => {
self.playtime_slot = s;
One(P::PlaytimeSlot)
}
C::SetPlaytimeColumn(c) => {
self.playtime_column = c;
One(P::PlaytimeColumn)
}
C::SetPlaytimeRow(r) => {
self.playtime_row = r;
One(P::PlaytimeRow)
}
C::SetPlaytimeSlotManagementAction(v) => {
self.playtime_slot_management_action = v;
One(P::PlaytimeSlotManagementAction)
}
C::SetPlaytimeSlotTransportAction(v) => {
self.playtime_slot_transport_action = v;
One(P::PlaytimeSlotTransportAction)
}
C::SetPlaytimeMatrixAction(v) => {
self.playtime_matrix_action = v;
One(P::PlaytimeMatrixAction)
}
C::SetPlaytimeColumnAction(v) => {
self.playtime_column_action = v;
One(P::PlaytimeColumnAction)
}
C::SetPlaytimeRowAction(v) => {
self.playtime_row_action = v;
One(P::PlaytimeRowAction)
}
C::SetStopColumnIfSlotEmpty(v) => {
self.stop_column_if_slot_empty = v;
One(P::StopColumnIfSlotEmpty)
}
C::SetPotFilterItemKind(v) => {
self.pot_filter_item_kind = v;
One(P::PotFilterItemKind)
}
C::SetMappingModificationKind(k) => {
self.mapping_modification_kind = k;
One(P::MappingModificationKind)
}
C::SetMappingRef(mapping_ref) => {
self.mapping_ref = mapping_ref;
One(P::MappingRef)
}
C::SetLearnableTargetKinds(kinds) => {
self.included_targets = kinds;
One(P::IncludedTargets)
}
C::SetTouchCause(touch_cause) => {
self.touch_cause = touch_cause;
One(P::TouchCause)
}
};
Some(affected)
}
}
/// A model for creating targets
#[derive(Clone, Debug)]
pub struct TargetModel {
// # For all targets
category: TargetCategory,
unit: TargetUnit,
// # For virtual targets
control_element_character: VirtualControlElementCharacter,
control_element_id: VirtualControlElementId,
learnable: bool,
// # For REAPER targets
// TODO-low Rename this to reaper_target_type
r#type: ReaperTargetType,
// # For action targets only
action_scope: ActionScope,
smart_command_name: Option<String>,
action_invocation_type: ActionInvocationType,
with_track: bool,
// # For track targets
track_type: VirtualTrackType,
track_id: Option<Guid>,
track_name: String,
track_index: u32,
track_expression: String,
enable_only_if_track_selected: bool,
clip_column_track_context: ClipColumnTrackContext,
track_tool_action: TrackToolAction,
gang_behavior: TrackGangBehavior,
browse_tracks_mode: BrowseTracksMode,
// # For track FX targets
fx_type: VirtualFxType,
fx_is_input_fx: bool,
fx_id: Option<Guid>,
fx_name: String,
fx_index: u32,
fx_expression: String,
enable_only_if_fx_has_focus: bool,
fx_tool_action: FxToolAction,
// # For track FX or compartment parameter targets
param_index: u32,
// # For track FX parameter targets
param_type: VirtualFxParameterType,
param_name: String,
param_expression: String,
retrigger: bool,
real_time: bool,
// # For track route targets
route_selector_type: TrackRouteSelectorType,
route_type: TrackRouteType,
route_id: Option<Guid>,
route_index: u32,
route_name: String,
route_expression: String,
touched_route_parameter_type: TouchedRouteParameterType,
// # For track solo targets
solo_behavior: SoloBehavior,
// # For seek and goto bookmark targets
seek_behavior: SeekBehavior,
// # For toggleable track targets
track_exclusivity: TrackExclusivity,
// # For transport target
transport_action: TransportAction,
// # For any-on target
any_on_parameter: AnyOnParameter,
// # For "Load FX snapshot" target
fx_snapshot: Option<FxSnapshot>,
// # For "Automation touch state" target
touched_track_parameter_type: TouchedTrackParameterType,
// # For "Go to marker/region" target
bookmark_ref: u32,
bookmark_type: BookmarkType,
bookmark_anchor_type: BookmarkAnchorType,
// # For "Go to marker/region" target and "Seek" target
use_time_selection: bool,
use_loop_points: bool,
// # For "Seek" target
use_regions: bool,
use_project: bool,
move_view: bool,
seek_play: bool,
feedback_resolution: FeedbackResolution,
// # For track show target
track_area: RealearnTrackArea,
// # For track and route automation mode target
automation_mode: RealearnAutomationMode,
// # For track monitoring mode target
monitoring_mode: MonitoringMode,
// # For automation mode override target
automation_mode_override_type: AutomationModeOverrideType,
// # For FX Open and Browse FXs target
fx_display_type: FxDisplayType,
// # For track selection related targets
scroll_arrange_view: bool,
scroll_mixer: bool,
// # For Send MIDI target
raw_midi_pattern: String,
send_midi_destination_type: SendMidiDestinationType,
midi_input_device: Option<MidiInputDeviceId>,
// # For Send OSC target
osc_address_pattern: String,
osc_arg_index: Option<u32>,
osc_arg_type_tag: OscTypeTag,
osc_arg_value_range: Interval<f64>,
osc_dev_id: Option<OscDeviceId>,
// # For mouse target
mouse_action_type: MouseActionType,
axis: Axis,
mouse_button: MouseButton,
// # For clip targets
playtime_slot: PlaytimeSlotDescriptor,
playtime_column: PlaytimeColumnDescriptor,
playtime_row: PlaytimeRowDescriptor,
playtime_slot_management_action: PlaytimeSlotManagementAction,
playtime_slot_transport_action: PlaytimeSlotTransportAction,
playtime_matrix_action: PlaytimeMatrixAction,
playtime_column_action: PlaytimeColumnAction,
playtime_row_action: PlaytimeRowAction,
stop_column_if_slot_empty: bool,
// # For targets that might have to be polled to get automatic feedback in all cases.
poll_for_feedback: bool,
instance_tag_kind: InstanceTagKind,
tags: Vec<Tag>,
mapping_snapshot_type_for_load: MappingSnapshotTypeForLoad,
mapping_snapshot_type_for_take: MappingSnapshotTypeForTake,
mapping_snapshot_id: Option<MappingSnapshotId>,
mapping_snapshot_default_value: Option<AbsoluteValue>,
exclusivity: Exclusivity,
group_id: GroupId,
active_mappings_only: bool,
mapping_modification_kind: MappingModificationKind,
mapping_ref: MappingRefModel,
// # For Pot targets
pot_filter_item_kind: PotFilterKind,
// # For targets that deal with target learning/touching
included_targets: NonCryptoHashSet<LearnableTargetKind>,
touch_cause: TargetTouchCause,
}
#[derive(Clone, Debug)]
pub enum MappingRefModel {
OwnMapping {
mapping_id: Option<MappingId>,
},
ForeignMapping {
session_id: String,
mapping_key: Option<MappingKey>,
},
}
impl MappingRefModel {
pub fn session_id(&self) -> Option<&str> {
if let Self::ForeignMapping { session_id, .. } = self {
Some(session_id)
} else {
None
}
}
}
impl Default for MappingRefModel {
fn default() -> Self {
Self::OwnMapping { mapping_id: None }
}
}
impl MappingRefModel {
fn create_mapping_ref(&self) -> Result<MappingRef, &'static str> {
let mapping_ref = match self {
MappingRefModel::OwnMapping { mapping_id } => MappingRef::OwnMapping {
mapping_id: (*mapping_id).ok_or("mapping not specified")?,
},
MappingRefModel::ForeignMapping {
session_id,
mapping_key,
} => MappingRef::ForeignMapping {
session_id: session_id.clone(),
mapping_key: mapping_key.clone().ok_or("mapping_not_specified")?,
},
};
Ok(mapping_ref)
}
}
impl Default for TargetModel {
fn default() -> Self {
Self {
category: TargetCategory::default(),
unit: Default::default(),
control_element_character: VirtualControlElementCharacter::default(),
control_element_id: Default::default(),
learnable: true,
r#type: ReaperTargetType::Dummy,
action_scope: Default::default(),
smart_command_name: None,
action_invocation_type: ActionInvocationType::default(),
track_type: Default::default(),
track_id: None,
track_name: "".to_owned(),
track_index: 0,
track_expression: "".to_owned(),
enable_only_if_track_selected: false,
with_track: false,
fx_type: Default::default(),
fx_is_input_fx: false,
fx_id: None,
fx_name: "".to_owned(),
fx_index: 0,
fx_expression: "".to_owned(),
enable_only_if_fx_has_focus: false,
param_type: Default::default(),
param_index: 0,
param_name: "".to_owned(),
param_expression: "".to_owned(),
retrigger: false,
real_time: false,
route_selector_type: Default::default(),
route_type: Default::default(),
route_id: None,
route_index: 0,
route_name: Default::default(),
route_expression: Default::default(),
touched_route_parameter_type: Default::default(),
solo_behavior: Default::default(),
seek_behavior: Default::default(),
track_exclusivity: Default::default(),
transport_action: TransportAction::default(),
any_on_parameter: AnyOnParameter::default(),
fx_snapshot: None,
touched_track_parameter_type: Default::default(),
bookmark_ref: 0,
bookmark_type: BookmarkType::Marker,
bookmark_anchor_type: Default::default(),
use_time_selection: false,
use_loop_points: false,
use_regions: false,
use_project: true,
move_view: true,
seek_play: true,
feedback_resolution: Default::default(),
track_area: Default::default(),
automation_mode: Default::default(),
monitoring_mode: Default::default(),
automation_mode_override_type: Default::default(),
fx_display_type: Default::default(),
scroll_arrange_view: false,
scroll_mixer: false,
raw_midi_pattern: Default::default(),
send_midi_destination_type: Default::default(),
midi_input_device: None,
osc_address_pattern: "".to_owned(),
osc_arg_index: Some(0),
osc_arg_type_tag: Default::default(),
osc_arg_value_range: DEFAULT_OSC_ARG_VALUE_RANGE,
osc_dev_id: None,
mouse_action_type: Default::default(),
axis: Default::default(),
mouse_button: Default::default(),
poll_for_feedback: true,
instance_tag_kind: Default::default(),
tags: Default::default(),
mapping_snapshot_type_for_load: MappingSnapshotTypeForLoad::Initial,
mapping_snapshot_type_for_take: MappingSnapshotTypeForTake::LastLoaded,
mapping_snapshot_id: None,
mapping_snapshot_default_value: None,
exclusivity: Default::default(),
group_id: Default::default(),
active_mappings_only: false,
playtime_slot: Default::default(),
playtime_column: Default::default(),
playtime_row: Default::default(),
playtime_slot_management_action: Default::default(),
playtime_slot_transport_action: Default::default(),
playtime_column_action: Default::default(),
playtime_matrix_action: Default::default(),
stop_column_if_slot_empty: false,
clip_column_track_context: Default::default(),
playtime_row_action: Default::default(),
track_tool_action: Default::default(),
fx_tool_action: Default::default(),
gang_behavior: Default::default(),
browse_tracks_mode: Default::default(),
pot_filter_item_kind: Default::default(),
mapping_modification_kind: Default::default(),
mapping_ref: Default::default(),
included_targets: LearnableTargetKind::iter().collect(),
touch_cause: Default::default(),
}
}
}
impl TargetModel {
pub fn category(&self) -> TargetCategory {
self.category
}
pub fn unit(&self) -> TargetUnit {
self.unit
}
pub fn control_element_character(&self) -> VirtualControlElementCharacter {
self.control_element_character
}
pub fn control_element_id(&self) -> VirtualControlElementId {
self.control_element_id
}
pub fn learnable(&self) -> bool {
self.learnable
}
pub fn target_type(&self) -> ReaperTargetType {
self.r#type
}
pub fn action_scope(&self) -> ActionScope {
self.action_scope
}
pub fn smart_command_name(&self) -> Option<&str> {
self.smart_command_name.as_deref()
}
pub fn action_invocation_type(&self) -> ActionInvocationType {
self.action_invocation_type
}
pub fn with_track(&self) -> bool {
self.with_track
}
pub fn track_type(&self) -> VirtualTrackType {
self.track_type
}
pub fn track_name(&self) -> &str {
&self.track_name
}
pub fn track_index(&self) -> u32 {
self.track_index
}
pub fn track_expression(&self) -> &str {
&self.track_expression
}
pub fn enable_only_if_track_selected(&self) -> bool {
self.enable_only_if_track_selected
}
pub fn fx_type(&self) -> VirtualFxType {
self.fx_type
}
pub fn fx_is_input_fx(&self) -> bool {
self.fx_is_input_fx
}
pub fn fx_name(&self) -> &str {
&self.fx_name
}
pub fn fx_index(&self) -> u32 {
self.fx_index
}
pub fn fx_expression(&self) -> &str {
&self.fx_expression
}
pub fn enable_only_if_fx_has_focus(&self) -> bool {
self.enable_only_if_fx_has_focus
}
pub fn fixed_gang_behavior(&self) -> TrackGangBehavior {
self.gang_behavior.fixed(self.r#type.definition())
}
pub fn browse_tracks_mode(&self) -> BrowseTracksMode {
self.browse_tracks_mode
}
pub fn param_type(&self) -> VirtualFxParameterType {
self.param_type
}
pub fn param_index(&self) -> u32 {
self.param_index
}
pub fn compartment_param_index(&self) -> CompartmentParamIndex {
CompartmentParamIndex::try_from(self.param_index).unwrap_or_default()
}
pub fn param_name(&self) -> &str {
&self.param_name
}
pub fn param_expression(&self) -> &str {
&self.param_expression
}
pub fn route_selector_type(&self) -> TrackRouteSelectorType {
self.route_selector_type
}
pub fn route_type(&self) -> TrackRouteType {
self.route_type
}
pub fn route_index(&self) -> u32 {
self.route_index
}
pub fn route_name(&self) -> &str {
&self.route_name
}
pub fn route_expression(&self) -> &str {
&self.route_expression
}
pub fn solo_behavior(&self) -> SoloBehavior {
self.solo_behavior
}
pub fn seek_behavior(&self) -> SeekBehavior {
self.seek_behavior
}
pub fn track_exclusivity(&self) -> TrackExclusivity {
self.track_exclusivity
}
pub fn track_tool_action(&self) -> TrackToolAction {
self.track_tool_action
}
pub fn fx_tool_action(&self) -> FxToolAction {
self.fx_tool_action
}
pub fn transport_action(&self) -> TransportAction {
self.transport_action
}
pub fn mouse_action_type(&self) -> MouseActionType {
self.mouse_action_type
}
pub fn axis(&self) -> Axis {
self.axis
}
pub fn mouse_button(&self) -> MouseButton {
self.mouse_button
}
pub fn any_on_parameter(&self) -> AnyOnParameter {
self.any_on_parameter
}
pub fn fx_snapshot(&self) -> Option<&FxSnapshot> {
self.fx_snapshot.as_ref()
}
pub fn mapping_snapshot_type_for_load(&self) -> MappingSnapshotTypeForLoad {
self.mapping_snapshot_type_for_load
}
pub fn mapping_snapshot_type_for_take(&self) -> MappingSnapshotTypeForTake {
self.mapping_snapshot_type_for_take
}
pub fn mapping_snapshot_id(&self) -> Option<&MappingSnapshotId> {
self.mapping_snapshot_id.as_ref()
}
pub fn touched_track_parameter_type(&self) -> TouchedTrackParameterType {
self.touched_track_parameter_type
}
pub fn touched_route_parameter_type(&self) -> TouchedRouteParameterType {
self.touched_route_parameter_type
}
pub fn bookmark_ref(&self) -> u32 {
self.bookmark_ref
}
pub fn bookmark_type(&self) -> BookmarkType {
self.bookmark_type
}
pub fn bookmark_anchor_type(&self) -> BookmarkAnchorType {
self.bookmark_anchor_type
}
pub fn use_time_selection(&self) -> bool {
self.use_time_selection
}
pub fn use_loop_points(&self) -> bool {
self.use_loop_points
}
pub fn use_regions(&self) -> bool {
self.use_regions
}
pub fn use_project(&self) -> bool {
self.use_project
}
pub fn move_view(&self) -> bool {
self.move_view
}
pub fn seek_play(&self) -> bool {
self.seek_play
}
pub fn feedback_resolution(&self) -> FeedbackResolution {
self.feedback_resolution
}
pub fn track_area(&self) -> RealearnTrackArea {
self.track_area
}
pub fn automation_mode(&self) -> RealearnAutomationMode {
self.automation_mode
}
pub fn monitoring_mode(&self) -> MonitoringMode {
self.monitoring_mode
}
pub fn automation_mode_override_type(&self) -> AutomationModeOverrideType {
self.automation_mode_override_type
}
pub fn fx_display_type(&self) -> FxDisplayType {
self.fx_display_type
}
pub fn scroll_arrange_view(&self) -> bool {
self.scroll_arrange_view
}
pub fn scroll_mixer(&self) -> bool {
self.scroll_mixer
}
pub fn raw_midi_pattern(&self) -> &str {
&self.raw_midi_pattern
}
pub fn send_midi_destination_type(&self) -> SendMidiDestinationType {
self.send_midi_destination_type
}
pub fn midi_input_device(&self) -> Option<MidiInputDeviceId> {
self.midi_input_device
}
pub fn osc_address_pattern(&self) -> &str {
&self.osc_address_pattern
}
pub fn mapping_snapshot_default_value(&self) -> Option<AbsoluteValue> {
self.mapping_snapshot_default_value
}
pub fn osc_arg_index(&self) -> Option<u32> {
self.osc_arg_index
}
pub fn osc_arg_type_tag(&self) -> OscTypeTag {
self.osc_arg_type_tag
}
pub fn osc_arg_value_range(&self) -> Interval<f64> {
self.osc_arg_value_range
}
pub fn osc_dev_id(&self) -> Option<OscDeviceId> {
self.osc_dev_id
}
pub fn playtime_slot_management_action(&self) -> PlaytimeSlotManagementAction {
self.playtime_slot_management_action
}
pub fn poll_for_feedback(&self) -> bool {
self.poll_for_feedback
}
pub fn retrigger(&self) -> bool {
self.retrigger
}
pub fn real_time(&self) -> bool {
self.real_time
}
pub fn instance_tag_kind(&self) -> InstanceTagKind {
self.instance_tag_kind
}
pub fn tags(&self) -> &[Tag] {
&self.tags
}
pub fn exclusivity(&self) -> Exclusivity {
self.exclusivity
}
pub fn group_id(&self) -> GroupId {
self.group_id
}
pub fn active_mappings_only(&self) -> bool {
self.active_mappings_only
}
pub fn supports_control(&self) -> bool {
use TargetCategory::*;
match self.category {
Reaper => self.r#type.supports_control(),
Virtual => true,
}
}
pub fn supports_feedback(&self) -> bool {
use TargetCategory::*;
match self.category {
Reaper => self.r#type.supports_feedback(),
Virtual => true,
}
}
pub fn make_track_sticky(
&mut self,
compartment: CompartmentKind,
context: ExtendedProcessorContext,
) -> Result<Option<Affected<TargetProp>>, Box<dyn Error>> {
if self.track_type.is_sticky() {
return Ok(None);
};
let track = self
.with_context(context, compartment)
.first_effective_track()?;
let virtual_track = virtualize_track(&track, context.context(), false);
let _ = self.set_virtual_track(virtual_track, Some(context.context()));
Ok(Some(Affected::Multiple))
}
pub fn make_fx_sticky(
&mut self,
compartment: CompartmentKind,
context: ExtendedProcessorContext,
) -> Result<Option<Affected<TargetProp>>, Box<dyn Error>> {
if self.fx_type.is_sticky() {
return Ok(None);
};
let fx = self.with_context(context, compartment).first_fx()?;
let virtual_fx = virtualize_fx(&fx, context.context(), false);
Ok(self.set_virtual_fx(virtual_fx, context, compartment))
}
pub fn make_route_sticky(
&mut self,
compartment: CompartmentKind,
context: ExtendedProcessorContext,
) -> Result<Option<Affected<TargetProp>>, Box<dyn Error>> {
if self.route_selector_type.is_sticky() {
return Ok(None);
};
let desc = self.route_descriptor()?;
let route = desc.resolve_first(context, compartment)?;
let virtual_route = virtualize_route(&route, context.context(), false);
Ok(self.set_virtual_route(virtual_route))
}
pub fn take_fx_snapshot(
&self,
context: ExtendedProcessorContext,
compartment: CompartmentKind,
) -> anyhow::Result<FxSnapshot> {
let fx = self
.with_context(context, compartment)
.first_fx()
.map_err(anyhow::Error::msg)?;
let fx_info = fx.info()?;
let fx_snapshot = FxSnapshot {
fx_type: if fx_info.sub_type_expression.is_empty() {
fx_info.type_expression
} else {
fx_info.sub_type_expression
},
fx_name: fx_info.effect_name,
preset_name: fx.preset_name().map(|n| n.into_string()),
chunk: Rc::new(fx.tag_chunk()?.content().to_owned()),
// chunk: Rc::new(fx.vst_chunk_encoded()?.into_string()),
};
Ok(fx_snapshot)
}
#[must_use]
pub fn invalidate_fx_index(
&mut self,
context: ExtendedProcessorContext,
compartment: CompartmentKind,
) -> Option<Affected<TargetProp>> {
if !self.supports_fx() {
return None;
}
if let Ok(actual_fx) = self.with_context(context, compartment).first_fx() {
let new_virtual_fx = match self.virtual_fx() {
Some(virtual_fx) => {
match virtual_fx {
VirtualFx::ChainFx {
is_input_fx,
chain_fx: anchor,
} => match anchor {
VirtualChainFx::ByIdOrIndex(guid, _) => Some(VirtualFx::ChainFx {
is_input_fx,
chain_fx: VirtualChainFx::ByIdOrIndex(guid, actual_fx.index()),
}),
_ => None,
},
// No update necessary
VirtualFx::Unit | VirtualFx::LastFocused | VirtualFx::This => None,
}
}
// Shouldn't happen
None => None,
};
if let Some(virtual_fx) = new_virtual_fx {
self.set_virtual_fx(virtual_fx, context, compartment)
} else {
None
}
} else {
None
}
}
#[must_use]
pub fn set_virtual_track(
&mut self,
track: VirtualTrack,
context: Option<&ProcessorContext>,
) -> Option<Affected<TargetProp>> {
self.set_track_from_prop_values(TrackPropValues::from_virtual_track(track), true, context)
}
/// Sets the track type and in certain cases also updates a few other target properties.
#[must_use]
pub fn set_track_type_from_ui(
&mut self,
track_type: VirtualTrackType,
context: &ProcessorContext,
) -> Option<Affected<TargetProp>> {
use VirtualTrackType::*;
match track_type {
This => {
self.set_concrete_track(ConcreteTrackInstruction::This(Some(context)), true, false)
}
ById => self.set_concrete_track(
ConcreteTrackInstruction::ById {
id: None,
context: Some(context),
},
true,
false,
),
_ => {
self.track_type = track_type;
Some(Affected::One(TargetProp::TrackType))
}
}
}
#[must_use]
pub fn set_fx_type_from_ui(
&mut self,
fx_type: VirtualFxType,
context: ExtendedProcessorContext,
compartment: CompartmentKind,
) -> Option<Affected<TargetProp>> {
use VirtualFxType::*;
match fx_type {
This => self.set_concrete_fx(
ConcreteFxInstruction::This(Some(context.context())),
true,
false,
),
ById => self.set_concrete_fx(
ConcreteFxInstruction::ById {
is_input_fx: None,
id: None,
track: self
.with_context(context, compartment)
.first_effective_track()
.ok(),
},
true,
false,
),
_ => {
self.fx_type = fx_type;
Some(Affected::One(TargetProp::FxType))
}
}
}
#[must_use]
pub fn set_track_from_prop_values(
&mut self,
track: TrackPropValues,
with_notification: bool,
context: Option<&ProcessorContext>,
) -> Option<Affected<TargetProp>> {
self.track_type = track.r#type;
self.track_expression = track.expression;
use VirtualTrackType::*;
match track.r#type {
This => {
let _ = self.set_concrete_track(
ConcreteTrackInstruction::This(context),
// Already notified above
false,
with_notification,
);
}
ById => {
let _ = self.set_concrete_track(
ConcreteTrackInstruction::ById {
id: track.id,
context,
},
// Already notified above
false,
with_notification,
);
}
ByName | AllByName => {
self.track_name = track.name;
}
ByIndex | ByIndexTcp | ByIndexMcp => {
self.track_index = track.index;
}
ByIdOrName => {
self.track_id = track.id;
self.track_name = track.name;
}
FromClipColumn => {
self.playtime_column = track.clip_column;
self.clip_column_track_context = track.clip_column_track_context;
}
Unit | Selected | AllSelected | Master | Dynamic | DynamicTcp | DynamicMcp => {}
}
Some(Affected::Multiple)
}
#[must_use]
pub fn set_virtual_route(&mut self, route: VirtualTrackRoute) -> Option<Affected<TargetProp>> {
self.set_route(TrackRoutePropValues::from_virtual_route(route))
}
#[must_use]
pub fn set_route(&mut self, route: TrackRoutePropValues) -> Option<Affected<TargetProp>> {
self.route_selector_type = route.selector_type;
self.route_type = route.r#type;
self.route_id = route.id;
self.route_name = route.name;
self.route_index = route.index;
self.route_expression = route.expression;
Some(Affected::Multiple)
}
#[must_use]
pub fn set_virtual_fx(
&mut self,
fx: VirtualFx,
context: ExtendedProcessorContext,
compartment: CompartmentKind,
) -> Option<Affected<TargetProp>> {
self.set_fx_from_prop_values(
FxPropValues::from_virtual_fx(fx),
true,
Some(context),
compartment,
)
}
#[must_use]
pub fn set_fx_from_prop_values(
&mut self,
fx: FxPropValues,
with_notification: bool,
context: Option<ExtendedProcessorContext>,
compartment: CompartmentKind,
) -> Option<Affected<TargetProp>> {
self.fx_type = fx.r#type;
self.fx_expression = fx.expression;
self.fx_is_input_fx = fx.is_input_fx;
use VirtualFxType::*;
match fx.r#type {
This => {
let _ = self.set_concrete_fx(
ConcreteFxInstruction::This(context.map(|c| c.context())),
// Already notified above
false,
with_notification,
);
}
ById => {
let _ = self.set_concrete_fx(
ConcreteFxInstruction::ById {
is_input_fx: Some(fx.is_input_fx),
id: fx.id,
track: context.and_then(|c| {
self.with_context(c, compartment)
.first_effective_track()
.ok()
}),
},
// Already notified above
false,
with_notification,
);
}
ByName | AllByName => {
self.fx_name = fx.name;
}
ByIndex => {
self.fx_index = fx.index;
}
ByIdOrIndex => {
self.fx_id = fx.id;
self.fx_index = fx.index;
}
Dynamic | Focused | Unit => {}
};
Some(Affected::Multiple)
}
#[must_use]
pub fn set_fx_parameter(
&mut self,
param: FxParameterPropValues,
) -> Option<Affected<TargetProp>> {
self.param_type = param.r#type;
self.param_name = param.name;
self.param_index = param.index;
self.param_expression = param.expression;
Some(Affected::Multiple)
}
#[must_use]
pub fn set_seek_options(&mut self, options: SeekOptions) -> Option<Affected<TargetProp>> {
self.use_time_selection = options.use_time_selection;
self.use_loop_points = options.use_loop_points;
self.use_regions = options.use_regions;
self.use_project = options.use_project;
self.move_view = options.move_view;
self.seek_play = options.seek_play;
self.feedback_resolution = options.feedback_resolution;
Some(Affected::Multiple)
}
/// Sets the track to one of the concrete types ById or This, also setting other important
/// properties for UI convenience.
#[must_use]
pub fn set_concrete_track(
&mut self,
instruction: ConcreteTrackInstruction,
notify_about_type_change: bool,
notify_about_id_change: bool,
) -> Option<Affected<TargetProp>> {
let resolved = instruction.resolve();
self.track_type = resolved.virtual_track_type();
if let Some(id) = resolved.id() {
self.track_id = Some(id);
}
// We also set index and name so that we can easily switch between types.
if let Some(i) = resolved.index() {
self.track_index = i;
}
if let Some(name) = resolved.name() {
self.track_name = name;
}
if !notify_about_type_change && !notify_about_id_change {
None
} else if !notify_about_type_change && notify_about_id_change {
Some(Affected::One(TargetProp::TrackId))
} else if notify_about_type_change && !notify_about_id_change {
Some(Affected::One(TargetProp::TrackType))
} else {
Some(Affected::Multiple)
}
}
/// Sets the FX to one of the concrete types (ById only for now), also setting other important
/// properties for UI convenience.
pub fn set_concrete_fx(
&mut self,
instruction: ConcreteFxInstruction,
notify_about_type_and_input_fx_change: bool,
notify_about_id_change: bool,
) -> Option<Affected<TargetProp>> {
let resolved = instruction.resolve();
self.fx_type = resolved.virtual_fx_type();
if let Some(is_input_fx) = resolved.is_input_fx() {
self.fx_is_input_fx = is_input_fx;
}
if let Some(id) = resolved.id() {
self.fx_id = Some(id);
}
// We also set index and name so that we can easily switch between types.
if let Some(i) = resolved.index() {
self.fx_index = i;
}
if let Some(name) = resolved.name() {
self.fx_name = name;
}
if notify_about_type_and_input_fx_change {
Some(Affected::Multiple)
} else if notify_about_id_change {
Some(Affected::One(TargetProp::FxId))
} else {
None
}
}
pub fn seek_options(&self) -> SeekOptions {
SeekOptions {
use_time_selection: self.use_time_selection,
use_loop_points: self.use_loop_points,
use_regions: self.use_regions,
use_project: self.use_project,
move_view: self.move_view,
seek_play: self.seek_play,
feedback_resolution: self.feedback_resolution,
}
}
#[must_use]
pub fn apply_from_target(
&mut self,
target: &ReaperTarget,
extended_context: ExtendedProcessorContext,
compartment: CompartmentKind,
) -> Option<Affected<TargetProp>> {
let context = extended_context.context();
use ReaperTarget::*;
self.category = TargetCategory::Reaper;
self.r#type = ReaperTargetType::from_target(target);
if let Some(actual_fx) = target.fx() {
let virtual_fx = virtualize_fx(actual_fx, context, true);
let _ = self.set_virtual_fx(virtual_fx, extended_context, compartment);
let track = if let Some(track) = actual_fx.track() {
track.clone()
} else {
// Must be monitoring FX. In this case we want the master track (it's REAPER's
// convention and ours).
context
.project_or_current_project()
.master_track()
.expect("no way")
};
let _ = self.set_virtual_track(virtualize_track(&track, context, true), Some(context));
} else if let Some(track) = target.track() {
let _ = self.set_virtual_track(virtualize_track(track, context, true), Some(context));
}
if let Some(send) = target.route() {
let virtual_route = virtualize_route(send, context, true);
let _ = self.set_virtual_route(virtual_route);
}
if let Some(track_exclusivity) = target.track_exclusivity() {
self.track_exclusivity = track_exclusivity;
}
if let Some(slot_address) = target.clip_slot_address() {
self.playtime_slot = PlaytimeSlotDescriptor::ByIndex(slot_address);
}
if let Some(column_address) = target.clip_column_address() {
self.playtime_column = PlaytimeColumnDescriptor::ByIndex(column_address);
}
if let Some(row_address) = target.clip_row_address() {
self.playtime_row = PlaytimeRowDescriptor::ByIndex(row_address);
}
match target {
Action(t) => {
let section_id = t.action.section().map(|s| s.id()).unwrap_or_default();
self.action_scope = ActionScope::guess_from_section_id(section_id.get());
self.smart_command_name = build_smart_command_name_from_action(&t.action);
self.action_invocation_type = t.invocation_type;
}
FxParameter(t) => {
self.param_type = VirtualFxParameterType::ById;
self.param_index = t.param.index();
}
Transport(t) => {
self.transport_action = t.action;
}
TrackSolo(t) => {
self.solo_behavior = t.behavior;
}
GoToBookmark(t) => {
self.bookmark_ref = t.index;
self.bookmark_type = t.bookmark_type;
}
TrackAutomationMode(t) => {
self.automation_mode = RealearnAutomationMode::from_reaper(t.mode);
}
TrackMonitoringMode(t) => {
self.monitoring_mode = convert_monitoring_mode_to_realearn(t.mode);
}
RouteAutomationMode(t) => {
self.automation_mode = RealearnAutomationMode::from_reaper(t.mode);
}
AutomationModeOverride(t) => match t.mode_override {
None => {
self.automation_mode_override_type = AutomationModeOverrideType::None;
}
Some(GlobalAutomationModeOverride::Bypass) => {
self.automation_mode_override_type = AutomationModeOverrideType::Bypass;
}
Some(GlobalAutomationModeOverride::Mode(am)) => {
self.automation_mode_override_type = AutomationModeOverrideType::Override;
self.automation_mode = RealearnAutomationMode::from_reaper(am);
}
},
PlaytimeMatrixAction(t) => {
self.playtime_matrix_action = t.action;
}
PlaytimeColumnAction(t) => {
self.playtime_column_action = t.action;
}
PlaytimeRowAction(t) => {
self.playtime_row_action = t.basics.action;
}
PlaytimeSlotTransportAction(t) => {
self.playtime_slot_transport_action = t.basics.action;
}
_ => {}
};
Some(Affected::Multiple)
}
pub fn virtual_default(
control_element_character: VirtualControlElementCharacter,
next_index: u32,
) -> Self {
TargetModel {
category: TargetCategory::Virtual,
control_element_character,
control_element_id: VirtualControlElementId::Indexed(next_index),
..Default::default()
}
}
pub fn default_for_compartment(compartment: CompartmentKind) -> Self {
use CompartmentKind::*;
TargetModel {
category: match compartment {
Controller => TargetCategory::Virtual,
Main => TargetCategory::Reaper,
},
..Default::default()
}
}
pub fn virtual_track(&self) -> Option<VirtualTrack> {
use VirtualTrackType::*;
let track = match self.track_type {
This => VirtualTrack::This,
Selected => VirtualTrack::Selected {
allow_multiple: false,
},
AllSelected => VirtualTrack::Selected {
allow_multiple: true,
},
Master => VirtualTrack::Master,
Unit => VirtualTrack::Unit,
ById => VirtualTrack::ById(self.track_id?),
ByName => VirtualTrack::ByName {
wild_match: WildMatch::new(&self.track_name),
allow_multiple: false,
},
AllByName => VirtualTrack::ByName {
wild_match: WildMatch::new(&self.track_name),
allow_multiple: true,
},
ByIndex | ByIndexTcp | ByIndexMcp => VirtualTrack::ByIndex {
index: self.track_index,
scope: self.track_type.virtual_track_scope().unwrap_or_default(),
},
ByIdOrName => {
VirtualTrack::ByIdOrName(self.track_id?, WildMatch::new(&self.track_name))
}
Dynamic | DynamicTcp | DynamicMcp => {
let evaluator = ExpressionEvaluator::compile(&self.track_expression).ok()?;
VirtualTrack::Dynamic {
evaluator: Box::new(evaluator),
scope: self.track_type.virtual_track_scope().unwrap_or_default(),
}
}
FromClipColumn => VirtualTrack::FromClipColumn {
column: self.virtual_clip_column().ok()?,
context: self.clip_column_track_context,
},
};
Some(track)
}
pub fn track(&self) -> TrackPropValues {
TrackPropValues {
r#type: self.track_type,
id: self.track_id,
name: self.track_name.clone(),
expression: self.track_expression.clone(),
index: self.track_index,
clip_column: self.playtime_column.clone(),
clip_column_track_context: self.clip_column_track_context,
}
}
pub fn virtual_fx(&self) -> Option<VirtualFx> {
use VirtualFxType::*;
let fx = match self.fx_type {
Focused => VirtualFx::LastFocused,
This => VirtualFx::This,
Unit => VirtualFx::Unit,
_ => VirtualFx::ChainFx {
is_input_fx: self.fx_is_input_fx,
chain_fx: self.virtual_chain_fx()?,
},
};
Some(fx)
}
pub fn virtual_mapping_snapshot_id_for_load(
&self,
) -> Result<VirtualMappingSnapshotIdForLoad, &'static str> {
match self.mapping_snapshot_type_for_load {
MappingSnapshotTypeForLoad::Initial => Ok(VirtualMappingSnapshotIdForLoad::Initial),
MappingSnapshotTypeForLoad::ById => {
let id = self
.mapping_snapshot_id
.as_ref()
.ok_or("no mapping snapshot ID")?
.clone();
Ok(VirtualMappingSnapshotIdForLoad::ById(id))
}
}
}
pub fn virtual_mapping_snapshot_id_for_take(
&self,
) -> Result<VirtualMappingSnapshotIdForTake, &'static str> {
match self.mapping_snapshot_type_for_take {
MappingSnapshotTypeForTake::LastLoaded => {
Ok(VirtualMappingSnapshotIdForTake::LastLoaded)
}
MappingSnapshotTypeForTake::ById => {
let id = self
.mapping_snapshot_id
.as_ref()
.ok_or("no mapping snapshot ID")?
.clone();
Ok(VirtualMappingSnapshotIdForTake::ById(id))
}
}
}
pub fn mapping_snapshot_desc_for_load(&self) -> MappingSnapshotDescForLoad {
if self.target_type() == ReaperTargetType::TakeMappingSnapshot {
Default::default()
} else {
match self.mapping_snapshot_type_for_load {
MappingSnapshotTypeForLoad::Initial => MappingSnapshotDescForLoad::Initial,
MappingSnapshotTypeForLoad::ById => MappingSnapshotDescForLoad::ById {
id: self
.mapping_snapshot_id
.as_ref()
.map(|id| id.to_string())
.unwrap_or_default(),
},
}
}
}
pub fn mapping_snapshot_desc_for_take(&self) -> MappingSnapshotDescForTake {
if self.target_type() == ReaperTargetType::LoadMappingSnapshot {
Default::default()
} else {
match self.mapping_snapshot_type_for_take {
MappingSnapshotTypeForTake::LastLoaded => MappingSnapshotDescForTake::LastLoaded,
MappingSnapshotTypeForTake::ById => MappingSnapshotDescForTake::ById {
id: self
.mapping_snapshot_id
.as_ref()
.map(|id| id.to_string())
.unwrap_or_default(),
},
}
}
}
pub fn track_route_selector(&self) -> Option<TrackRouteSelector> {
use TrackRouteSelectorType::*;
let selector = match self.route_selector_type {
Dynamic => {
let evaluator = ExpressionEvaluator::compile(&self.route_expression).ok()?;
TrackRouteSelector::Dynamic(Box::new(evaluator))
}
ById => {
if self.route_type == TrackRouteType::HardwareOutput {
// Hardware outputs don't offer stable IDs.
TrackRouteSelector::ByIndex(self.route_index)
} else {
TrackRouteSelector::ById(self.route_id?)
}
}
ByName => TrackRouteSelector::ByName(WildMatch::new(&self.route_name)),
ByIndex => TrackRouteSelector::ByIndex(self.route_index),
};
Some(selector)
}
pub fn virtual_chain_fx(&self) -> Option<VirtualChainFx> {
use VirtualFxType::*;
let fx = match self.fx_type {
Focused | This | Unit => return None,
ById => VirtualChainFx::ById(self.fx_id?, Some(self.fx_index)),
ByName => VirtualChainFx::ByName {
wild_match: WildMatch::new(&self.fx_name),
allow_multiple: false,
},
AllByName => VirtualChainFx::ByName {
wild_match: WildMatch::new(&self.fx_name),
allow_multiple: true,
},
ByIndex => VirtualChainFx::ByIndex(self.fx_index),
ByIdOrIndex => VirtualChainFx::ByIdOrIndex(self.fx_id, self.fx_index),
Dynamic => {
let evaluator = ExpressionEvaluator::compile(&self.fx_expression).ok()?;
VirtualChainFx::Dynamic(Box::new(evaluator))
}
};
Some(fx)
}
pub fn fx(&self) -> FxPropValues {
FxPropValues {
r#type: self.fx_type,
is_input_fx: self.fx_is_input_fx,
id: self.fx_id,
name: self.fx_name.clone(),
expression: self.fx_expression.clone(),
index: self.fx_index,
}
}
pub fn track_route(&self) -> TrackRoutePropValues {
TrackRoutePropValues {
selector_type: self.route_selector_type,
r#type: self.route_type,
id: self.route_id,
name: self.route_name.clone(),
expression: self.route_expression.clone(),
index: self.route_index,
}
}
pub fn fx_parameter(&self) -> FxParameterPropValues {
FxParameterPropValues {
r#type: self.param_type,
name: self.param_name.clone(),
expression: self.param_expression.clone(),
index: self.param_index,
}
}
pub fn track_descriptor(&self) -> Result<TrackDescriptor, &'static str> {
let desc = TrackDescriptor {
track: self.virtual_track().ok_or("virtual track not complete")?,
enable_only_if_track_selected: self.enable_only_if_track_selected,
};
Ok(desc)
}
pub fn api_track_descriptor(&self) -> helgobox_api::persistence::TrackDescriptor {
use helgobox_api::persistence::TrackDescriptor;
use VirtualTrackType::*;
let commons = TrackDescriptorCommons {
track_must_be_selected: Some(self.enable_only_if_track_selected),
};
match self.track_type {
This => TrackDescriptor::This { commons },
Selected => TrackDescriptor::Selected {
allow_multiple: Some(false),
},
AllSelected => TrackDescriptor::Selected {
allow_multiple: Some(true),
},
Master => TrackDescriptor::Master { commons },
Unit => TrackDescriptor::Instance { commons },
ById | ByIdOrName => TrackDescriptor::ById {
commons,
id: self
.track_id
.as_ref()
.map(|id| id.to_string_without_braces()),
},
ByName => TrackDescriptor::ByName {
commons,
name: self.track_name.clone(),
allow_multiple: Some(false),
},
AllByName => TrackDescriptor::ByName {
commons,
name: self.track_name.clone(),
allow_multiple: Some(true),
},
ByIndex | ByIndexTcp | ByIndexMcp => TrackDescriptor::ByIndex {
commons,
index: self.track_index,
scope: self.track_type.virtual_track_scope(),
},
Dynamic | DynamicTcp | DynamicMcp => TrackDescriptor::Dynamic {
commons,
expression: self.track_expression.clone(),
scope: self.track_type.virtual_track_scope(),
},
FromClipColumn => TrackDescriptor::FromClipColumn {
commons,
column: self.playtime_column.clone(),
context: self.clip_column_track_context,
},
}
}
pub fn api_fx_descriptor(&self) -> helgobox_api::persistence::FxDescriptor {
use helgobox_api::persistence::FxDescriptor;
use VirtualFxType::*;
let commons = FxDescriptorCommons {
fx_must_have_focus: Some(self.enable_only_if_fx_has_focus),
};
let chain = FxChainDescriptor::Track {
track: Some(self.api_track_descriptor()),
chain: Some(if self.fx_is_input_fx {
TrackFxChain::Input
} else {
TrackFxChain::Normal
}),
};
match self.fx_type {
This => FxDescriptor::This { commons },
Focused => FxDescriptor::Focused,
Dynamic => FxDescriptor::Dynamic {
commons,
chain,
expression: self.track_expression.clone(),
},
Unit => FxDescriptor::Instance { commons },
ById => FxDescriptor::ById {
commons,
chain,
id: self.fx_id.as_ref().map(|id| id.to_string_without_braces()),
},
ByName => FxDescriptor::ByName {
commons,
chain,
name: self.fx_name.clone(),
allow_multiple: Some(false),
},
AllByName => FxDescriptor::ByName {
commons,
chain,
name: self.fx_name.clone(),
allow_multiple: Some(true),
},
ByIndex | ByIdOrIndex => FxDescriptor::ByIndex {
commons,
chain,
index: self.fx_index,
},
}
}
fn virtual_clip_slot(&self) -> Result<VirtualPlaytimeSlot, &'static str> {
use PlaytimeSlotDescriptor::*;
let slot = match &self.playtime_slot {
Active => VirtualPlaytimeSlot::Active,
ByIndex(address) => VirtualPlaytimeSlot::ByIndex(*address),
Dynamic {
column_expression,
row_expression,
} => {
let column_evaluator = ExpressionEvaluator::compile(column_expression)
.map_err(|_| "couldn't evaluate row")?;
let row_evaluator = ExpressionEvaluator::compile(row_expression)
.map_err(|_| "couldn't evaluate row")?;
VirtualPlaytimeSlot::Dynamic {
column_evaluator: Box::new(column_evaluator),
row_evaluator: Box::new(row_evaluator),
}
}
};
Ok(slot)
}
fn virtual_clip_column(&self) -> Result<VirtualPlaytimeColumn, &'static str> {
VirtualPlaytimeColumn::from_descriptor(&self.playtime_column)
}
fn virtual_clip_row(&self) -> Result<VirtualPlaytimeRow, &'static str> {
use PlaytimeRowDescriptor::*;
let row = match &self.playtime_row {
Active => VirtualPlaytimeRow::Active,
ByIndex(address) => VirtualPlaytimeRow::ByIndex(address.index),
Dynamic {
expression: index_expression,
} => {
let index_evaluator = ExpressionEvaluator::compile(index_expression)
.map_err(|_| "couldn't evaluate row index")?;
VirtualPlaytimeRow::Dynamic(Box::new(index_evaluator))
}
};
Ok(row)
}
pub fn fx_descriptor(&self) -> Result<FxDescriptor, &'static str> {
let desc = FxDescriptor {
track_descriptor: if let Ok(desc) = self.track_descriptor() {
desc
} else if self.fx_type.requires_fx_chain() {
return Err("couldn't resolve track but track required");
} else {
TrackDescriptor::default()
},
enable_only_if_fx_has_focus: self.enable_only_if_fx_has_focus,
fx: self.virtual_fx().ok_or("FX not set")?,
};
Ok(desc)
}
pub fn route_descriptor(&self) -> Result<TrackRouteDescriptor, &'static str> {
let desc = TrackRouteDescriptor {
track_descriptor: self.track_descriptor()?,
route: self.virtual_track_route()?,
};
Ok(desc)
}
pub fn virtual_track_route(&self) -> Result<VirtualTrackRoute, &'static str> {
let route = VirtualTrackRoute {
r#type: self.route_type,
selector: self.track_route_selector().ok_or("track route not set")?,
};
Ok(route)
}
pub fn virtual_fx_parameter(&self) -> Option<VirtualFxParameter> {
use VirtualFxParameterType::*;
let param = match self.param_type {
ByName => VirtualFxParameter::ByName(WildMatch::new(&self.param_name)),
ById => VirtualFxParameter::ById(self.param_index),
ByIndex => VirtualFxParameter::ByIndex(self.param_index),
Dynamic => {
let evaluator = ExpressionEvaluator::compile(&self.param_expression).ok()?;
VirtualFxParameter::Dynamic(Box::new(evaluator))
}
};
Some(param)
}
fn fx_parameter_descriptor(&self) -> Result<FxParameterDescriptor, &'static str> {
let desc = FxParameterDescriptor {
fx_descriptor: self.fx_descriptor()?,
fx_parameter: self.virtual_fx_parameter().ok_or("FX parameter not set")?,
};
Ok(desc)
}
pub fn create_target(
&self,
compartment: CompartmentKind,
) -> Result<UnresolvedCompoundMappingTarget, &'static str> {
use TargetCategory::*;
match self.category {
Reaper => {
use ReaperTargetType::*;
let target = match self.r#type {
Mouse => UnresolvedReaperTarget::Mouse(UnresolvedMouseTarget {
action_type: self.mouse_action_type,
axis: self.axis,
button: self.mouse_button,
}),
CompartmentParameterValue => UnresolvedReaperTarget::CompartmentParameterValue(
UnresolvedCompartmentParameterValueTarget {
compartment,
index: self.compartment_param_index(),
},
),
Action => UnresolvedReaperTarget::Action(UnresolvedActionTarget {
action: self.resolved_available_action()?,
scope: self.action_scope,
invocation_type: self.action_invocation_type,
track_descriptor: if self.with_track {
Some(self.track_descriptor()?)
} else {
None
},
}),
FxParameterValue => {
UnresolvedReaperTarget::FxParameter(UnresolvedFxParameterTarget {
fx_parameter_descriptor: self.fx_parameter_descriptor()?,
poll_for_feedback: self.poll_for_feedback,
retrigger: self.retrigger,
real_time_even_if_not_rendering: self.real_time,
})
}
FxParameterTouchState => UnresolvedReaperTarget::FxParameterTouchState(
UnresolvedFxParameterTouchStateTarget {
fx_parameter_descriptor: self.fx_parameter_descriptor()?,
},
),
TrackVolume => {
UnresolvedReaperTarget::TrackVolume(UnresolvedTrackVolumeTarget {
track_descriptor: self.track_descriptor()?,
gang_behavior: self.fixed_gang_behavior(),
})
}
TrackTool => UnresolvedReaperTarget::TrackTool(UnresolvedTrackToolTarget {
track_descriptor: self.track_descriptor()?,
action: self.track_tool_action,
scope: self.tag_scope(),
}),
TrackPeak => UnresolvedReaperTarget::TrackPeak(UnresolvedTrackPeakTarget {
track_descriptor: self.track_descriptor()?,
}),
RouteVolume => {
UnresolvedReaperTarget::TrackSendVolume(UnresolvedRouteVolumeTarget {
descriptor: self.route_descriptor()?,
})
}
TrackPan => UnresolvedReaperTarget::TrackPan(UnresolvedTrackPanTarget {
track_descriptor: self.track_descriptor()?,
gang_behavior: self.fixed_gang_behavior(),
}),
TrackWidth => UnresolvedReaperTarget::TrackWidth(UnresolvedTrackWidthTarget {
track_descriptor: self.track_descriptor()?,
gang_behavior: self.fixed_gang_behavior(),
}),
TrackArm => UnresolvedReaperTarget::TrackArm(UnresolvedTrackArmTarget {
track_descriptor: self.track_descriptor()?,
exclusivity: self.track_exclusivity,
gang_behavior: self.fixed_gang_behavior(),
}),
TrackParentSend => {
UnresolvedReaperTarget::TrackParentSend(UnresolvedTrackParentSendTarget {
track_descriptor: self.track_descriptor()?,
exclusivity: self.track_exclusivity,
})
}
TrackSelection => {
UnresolvedReaperTarget::TrackSelection(UnresolvedTrackSelectionTarget {
track_descriptor: self.track_descriptor()?,
exclusivity: self.track_exclusivity,
scroll_arrange_view: self.scroll_arrange_view,
scroll_mixer: self.scroll_mixer,
})
}
TrackMute => UnresolvedReaperTarget::TrackMute(UnresolvedTrackMuteTarget {
track_descriptor: self.track_descriptor()?,
exclusivity: self.track_exclusivity,
gang_behavior: self.fixed_gang_behavior(),
}),
TrackPhase => UnresolvedReaperTarget::TrackPhase(UnresolvedTrackPhaseTarget {
track_descriptor: self.track_descriptor()?,
exclusivity: self.track_exclusivity,
gang_behavior: self.gang_behavior,
poll_for_feedback: self.poll_for_feedback,
}),
TrackShow => UnresolvedReaperTarget::TrackShow(UnresolvedTrackShowTarget {
track_descriptor: self.track_descriptor()?,
exclusivity: self.track_exclusivity,
area: match self.track_area {
RealearnTrackArea::Tcp => TrackArea::Tcp,
RealearnTrackArea::Mcp => TrackArea::Mcp,
},
}),
TrackAutomationMode => UnresolvedReaperTarget::TrackAutomationMode(
UnresolvedTrackAutomationModeTarget {
track_descriptor: self.track_descriptor()?,
exclusivity: self.track_exclusivity,
mode: self.automation_mode.to_reaper(),
},
),
TrackMonitoringMode => UnresolvedReaperTarget::TrackMonitoringMode(
UnresolvedTrackMonitoringModeTarget {
track_descriptor: self.track_descriptor()?,
exclusivity: self.track_exclusivity,
mode: convert_monitoring_mode_to_reaper(self.monitoring_mode),
gang_behavior: self.fixed_gang_behavior(),
},
),
TrackSolo => UnresolvedReaperTarget::TrackSolo(UnresolvedTrackSoloTarget {
track_descriptor: self.track_descriptor()?,
behavior: self.solo_behavior,
exclusivity: self.track_exclusivity,
gang_behavior: self.fixed_gang_behavior(),
}),
RoutePan => UnresolvedReaperTarget::RoutePan(UnresolvedRoutePanTarget {
descriptor: self.route_descriptor()?,
}),
RouteMute => UnresolvedReaperTarget::RouteMute(UnresolvedRouteMuteTarget {
descriptor: self.route_descriptor()?,
poll_for_feedback: self.poll_for_feedback,
}),
RoutePhase => UnresolvedReaperTarget::RoutePhase(UnresolvedRoutePhaseTarget {
descriptor: self.route_descriptor()?,
poll_for_feedback: self.poll_for_feedback,
}),
RouteMono => UnresolvedReaperTarget::RouteMono(UnresolvedRouteMonoTarget {
descriptor: self.route_descriptor()?,
poll_for_feedback: self.poll_for_feedback,
}),
RouteAutomationMode => UnresolvedReaperTarget::RouteAutomationMode(
UnresolvedRouteAutomationModeTarget {
descriptor: self.route_descriptor()?,
mode: self.automation_mode.to_reaper(),
poll_for_feedback: self.poll_for_feedback,
},
),
RouteTouchState => {
UnresolvedReaperTarget::RouteTouchState(UnresolvedRouteTouchStateTarget {
descriptor: self.route_descriptor()?,
parameter_type: self.touched_route_parameter_type,
})
}
Tempo => UnresolvedReaperTarget::Tempo(UnresolvedTempoTarget),
PlayRate => UnresolvedReaperTarget::Playrate(UnresolvedPlayrateTarget),
AutomationModeOverride => UnresolvedReaperTarget::AutomationModeOverride(
UnresolvedAutomationModeOverrideTarget {
mode_override: match self.automation_mode_override_type {
AutomationModeOverrideType::Bypass => {
Some(GlobalAutomationModeOverride::Bypass)
}
AutomationModeOverrideType::Override => {
Some(GlobalAutomationModeOverride::Mode(
self.automation_mode.to_reaper(),
))
}
AutomationModeOverrideType::None => None,
},
},
),
FxTool => UnresolvedReaperTarget::FxTool(UnresolvedFxToolTarget {
fx_descriptor: self.fx_descriptor()?,
action: self.fx_tool_action,
scope: self.tag_scope(),
}),
FxEnable => UnresolvedReaperTarget::FxEnable(UnresolvedFxEnableTarget {
fx_descriptor: self.fx_descriptor()?,
}),
FxOnline => UnresolvedReaperTarget::FxOnline(UnresolvedFxOnlineTarget {
fx_descriptor: self.fx_descriptor()?,
}),
FxOpen => UnresolvedReaperTarget::FxOpen(UnresolvedFxOpenTarget {
fx_descriptor: self.fx_descriptor()?,
display_type: self.fx_display_type,
}),
FxPreset => UnresolvedReaperTarget::FxPreset(UnresolvedFxPresetTarget {
fx_descriptor: self.fx_descriptor()?,
}),
BrowseTracks => {
UnresolvedReaperTarget::SelectedTrack(UnresolvedBrowseTracksTarget {
scroll_arrange_view: self.scroll_arrange_view,
scroll_mixer: self.scroll_mixer,
mode: self.browse_tracks_mode,
})
}
BrowseFxs => UnresolvedReaperTarget::BrowseFxs(UnresolvedBrowseFxsTarget {
track_descriptor: self.track_descriptor()?,
is_input_fx: self.fx_is_input_fx,
display_type: self.fx_display_type,
}),
AllTrackFxEnable => {
UnresolvedReaperTarget::AllTrackFxEnable(UnresolvedAllTrackFxEnableTarget {
track_descriptor: self.track_descriptor()?,
exclusivity: self.track_exclusivity,
poll_for_feedback: self.poll_for_feedback,
})
}
Transport => UnresolvedReaperTarget::Transport(UnresolvedTransportTarget {
action: self.transport_action,
}),
LoadFxSnapshot => {
UnresolvedReaperTarget::LoadFxPreset(UnresolvedLoadFxSnapshotTarget {
fx_descriptor: self.fx_descriptor()?,
chunk: self
.fx_snapshot
.as_ref()
.ok_or("FX chunk not set")?
.chunk
.clone(),
})
}
LastTouched => {
UnresolvedReaperTarget::LastTouched(UnresolvedLastTouchedTarget {
included_targets: self
.included_targets
.iter()
.copied()
.map(ReaperTargetType::from_learnable_target_kind)
.collect(),
touch_cause: self.touch_cause,
})
}
TrackTouchState => {
UnresolvedReaperTarget::TrackTouchState(UnresolvedTrackTouchStateTarget {
track_descriptor: self.track_descriptor()?,
parameter_type: self.touched_track_parameter_type,
exclusivity: self.track_exclusivity,
gang_behavior: self.fixed_gang_behavior(),
})
}
GoToBookmark => {
UnresolvedReaperTarget::GoToBookmark(UnresolvedGoToBookmarkTarget {
bookmark_type: self.bookmark_type,
bookmark_anchor_type: self.bookmark_anchor_type,
bookmark_ref: self.bookmark_ref,
set_time_selection: self.use_time_selection,
set_loop_points: self.use_loop_points,
seek_behavior: self.seek_behavior,
})
}
Seek => UnresolvedReaperTarget::Seek(UnresolvedSeekTarget {
options: self.seek_options(),
behavior: self.seek_behavior,
}),
SendMidi => UnresolvedReaperTarget::SendMidi(UnresolvedMidiSendTarget {
pattern: self.raw_midi_pattern.parse().unwrap_or_default(),
destination: match self.send_midi_destination_type {
SendMidiDestinationType::FxOutput => SendMidiDestination::FxOutput,
SendMidiDestinationType::FeedbackOutput => {
SendMidiDestination::FeedbackOutput
}
SendMidiDestinationType::InputDevice => {
SendMidiDestination::InputDevice(InputDeviceMidiDestination {
device_id: self.midi_input_device.map(|d| d.get()),
})
}
},
}),
SendOsc => UnresolvedReaperTarget::SendOsc(UnresolvedOscSendTarget {
address_pattern: self.osc_address_pattern.clone(),
arg_descriptor: self.osc_arg_descriptor(),
device_id: self.osc_dev_id,
}),
PlaytimeSlotTransportAction => {
UnresolvedReaperTarget::PlaytimeSlotTransportAction(
crate::domain::UnresolvedPlaytimeSlotTransportTarget {
slot: self.virtual_clip_slot()?,
action: self.playtime_slot_transport_action,
options: self.clip_transport_options(),
},
)
}
PlaytimeColumnAction => UnresolvedReaperTarget::PlaytimeColumnAction(
crate::domain::UnresolvedPlaytimeColumnActionTarget {
column: self.virtual_clip_column()?,
action: self.playtime_column_action,
},
),
PlaytimeRowAction => UnresolvedReaperTarget::PlaytimeRowAction(
crate::domain::UnresolvedPlaytimeRowActionTarget {
row: self.virtual_clip_row()?,
action: self.playtime_row_action,
},
),
PlaytimeSlotSeek => UnresolvedReaperTarget::PlaytimeSlotSeek(
crate::domain::UnresolvedPlaytimeSlotSeekTarget {
slot: self.virtual_clip_slot()?,
feedback_resolution: self.feedback_resolution,
},
),
PlaytimeSlotVolume => UnresolvedReaperTarget::PlaytimeSlotVolume(
crate::domain::UnresolvedPlaytimeSlotVolumeTarget {
slot: self.virtual_clip_slot()?,
},
),
PlaytimeSlotManagementAction => {
UnresolvedReaperTarget::PlaytimeSlotManagementAction(
crate::domain::UnresolvedPlaytimeSlotManagementActionTarget {
slot: self.virtual_clip_slot()?,
action: self.playtime_slot_management_action,
},
)
}
PlaytimeMatrixAction => UnresolvedReaperTarget::PlaytimeMatrixAction(
crate::domain::UnresolvedPlaytimeMatrixActionTarget {
action: self.playtime_matrix_action,
},
),
PlaytimeControlUnitScroll => UnresolvedReaperTarget::PlaytimeControlUnitScroll(
crate::domain::UnresolvedPlaytimeControlUnitScrollTarget {
axis: self.axis,
},
),
PlaytimeBrowseCells => UnresolvedReaperTarget::PlaytimeBrowseCells(
crate::domain::UnresolvedPlaytimeBrowseCellsTarget { axis: self.axis },
),
LoadMappingSnapshot => UnresolvedReaperTarget::LoadMappingSnapshot(
UnresolvedLoadMappingSnapshotTarget {
compartment,
scope: self.tag_scope(),
active_mappings_only: self.active_mappings_only,
snapshot_id: self.virtual_mapping_snapshot_id_for_load()?,
default_value: self.mapping_snapshot_default_value,
},
),
TakeMappingSnapshot => UnresolvedReaperTarget::TakeMappingSnapshot(
UnresolvedTakeMappingSnapshotTarget {
compartment,
scope: self.tag_scope(),
active_mappings_only: self.active_mappings_only,
snapshot_id: self.virtual_mapping_snapshot_id_for_take()?,
},
),
EnableMappings => {
UnresolvedReaperTarget::EnableMappings(UnresolvedEnableMappingsTarget {
compartment,
scope: TagScope {
tags: self.tags.iter().cloned().collect(),
},
exclusivity: self.exclusivity,
})
}
ModifyMapping => {
UnresolvedReaperTarget::ModifyMapping(UnresolvedModifyMappingTarget {
compartment,
modification: match self.mapping_modification_kind {
MappingModificationKind::LearnTarget => {
MappingModification::LearnTarget(
LearnTargetMappingModification {
included_targets: Some(
self.included_targets.iter().cloned().collect(),
),
touch_cause: Some(self.touch_cause),
},
)
}
MappingModificationKind::SetTargetToLastTouched => {
MappingModification::SetTargetToLastTouched(
SetTargetToLastTouchedMappingModification {
included_targets: Some(
self.included_targets.iter().cloned().collect(),
),
touch_cause: Some(self.touch_cause),
},
)
}
},
mapping_ref: self.mapping_ref.create_mapping_ref()?,
})
}
EnableInstances => {
UnresolvedReaperTarget::EnableInstances(UnresolvedEnableInstancesTarget {
tag_kind: self.instance_tag_kind,
scope: TagScope {
tags: self.tags.iter().cloned().collect(),
},
exclusivity: self.exclusivity,
})
}
EnableUnits => {
UnresolvedReaperTarget::EnableUnits(UnresolvedEnableUnitsTarget {
scope: TagScope {
tags: self.tags.iter().cloned().collect(),
},
exclusivity: self.exclusivity,
})
}
BrowseGroup => {
UnresolvedReaperTarget::BrowseGroup(UnresolvedBrowseGroupTarget {
compartment,
group_id: self.group_id,
exclusivity: self.exclusivity.into(),
})
}
AnyOn => UnresolvedReaperTarget::AnyOn(UnresolvedAnyOnTarget {
parameter: self.any_on_parameter,
}),
Dummy => UnresolvedReaperTarget::Dummy(UnresolvedDummyTarget),
BrowsePotFilterItems => UnresolvedReaperTarget::BrowsePotFilterItems(
UnresolvedBrowsePotFilterItemsTarget {
settings: PotFilterItemsTargetSettings {
kind: self.pot_filter_item_kind,
},
},
),
BrowsePotPresets => UnresolvedReaperTarget::BrowsePotPresets(
UnresolvedBrowsePotPresetsTarget {},
),
PreviewPotPreset => UnresolvedReaperTarget::PreviewPotPreset(
UnresolvedPreviewPotPresetTarget {},
),
LoadPotPreset => {
UnresolvedReaperTarget::LoadPotPreset(UnresolvedLoadPotPresetTarget {
fx_descriptor: self.fx_descriptor()?,
})
}
StreamDeckBrightness => UnresolvedReaperTarget::StreamDeckBrightness(
UnresolvedStreamDeckBrightnessTarget {},
),
};
Ok(UnresolvedCompoundMappingTarget::Reaper(Box::new(target)))
}
Virtual => {
let virtual_target = VirtualTarget {
control_element: self.create_control_element(),
learnable: self.learnable,
};
Ok(UnresolvedCompoundMappingTarget::Virtual(virtual_target))
}
}
}
pub fn playtime_slot(&self) -> &PlaytimeSlotDescriptor {
&self.playtime_slot
}
pub fn playtime_column(&self) -> &PlaytimeColumnDescriptor {
&self.playtime_column
}
pub fn playtime_row(&self) -> &PlaytimeRowDescriptor {
&self.playtime_row
}
pub fn playtime_slot_transport_action(&self) -> PlaytimeSlotTransportAction {
self.playtime_slot_transport_action
}
pub fn playtime_matrix_action(&self) -> PlaytimeMatrixAction {
self.playtime_matrix_action
}
pub fn playtime_column_action(&self) -> PlaytimeColumnAction {
self.playtime_column_action
}
pub fn simple_target(&self) -> Option<playtime_api::runtime::SimpleMappingTarget> {
use helgobox_api::persistence;
use playtime_api::runtime::SimpleMappingTarget;
use ReaperTargetType as T;
if self.category != TargetCategory::Reaper {
return None;
}
let t = match self.r#type {
T::PlaytimeSlotTransportAction
if self.playtime_slot_transport_action()
== persistence::PlaytimeSlotTransportAction::Trigger =>
{
SimpleMappingTarget::TriggerSlot(self.playtime_slot.fixed_address()?)
}
T::PlaytimeColumnAction
if self.playtime_column_action() == persistence::PlaytimeColumnAction::Stop =>
{
SimpleMappingTarget::TriggerColumn(self.playtime_column.fixed_address()?)
}
T::PlaytimeRowAction
if self.playtime_row_action() == persistence::PlaytimeRowAction::PlayScene =>
{
SimpleMappingTarget::TriggerRow(self.playtime_row.fixed_address()?)
}
T::PlaytimeMatrixAction => match self.playtime_matrix_action {
PlaytimeMatrixAction::Stop => SimpleMappingTarget::TriggerMatrix,
PlaytimeMatrixAction::SmartRecord => SimpleMappingTarget::SmartRecord,
PlaytimeMatrixAction::StartOrStopPlayback => {
SimpleMappingTarget::EnterSilenceModeOrPlayIgnited
}
PlaytimeMatrixAction::SequencerRecordOnOffState => {
SimpleMappingTarget::SequencerRecordOnOffState
}
PlaytimeMatrixAction::SequencerPlayOnOffState => {
SimpleMappingTarget::SequencerPlayOnOffState
}
PlaytimeMatrixAction::TapTempo => SimpleMappingTarget::TapTempo,
PlaytimeMatrixAction::Undo
| PlaytimeMatrixAction::Redo
| PlaytimeMatrixAction::BuildScene
| PlaytimeMatrixAction::SetRecordLengthMode
| PlaytimeMatrixAction::SetCustomRecordLengthInBars
| PlaytimeMatrixAction::ClickOnOffState
| PlaytimeMatrixAction::MidiAutoQuantizationOnOffState
| PlaytimeMatrixAction::SilenceModeOnOffState
| PlaytimeMatrixAction::Panic => return None,
},
_ => return None,
};
Some(t)
}
pub fn tag_scope(&self) -> TagScope {
TagScope {
tags: self.tags.iter().cloned().collect(),
}
}
pub fn playtime_row_action(&self) -> PlaytimeRowAction {
self.playtime_row_action
}
pub fn stop_column_if_slot_empty(&self) -> bool {
self.stop_column_if_slot_empty
}
pub fn clip_transport_options(&self) -> crate::domain::ClipTransportOptions {
crate::domain::ClipTransportOptions {
stop_column_if_slot_empty: self.stop_column_if_slot_empty,
}
}
fn osc_arg_descriptor(&self) -> Option<OscArgDescriptor> {
let arg_index = self.osc_arg_index?;
Some(OscArgDescriptor::new(
arg_index,
self.osc_arg_type_tag,
// Doesn't matter for sending
false,
self.osc_arg_value_range,
))
}
pub fn mouse_action(&self) -> MouseAction {
match self.mouse_action_type {
MouseActionType::MoveTo => MouseAction::MoveTo {
axis: Some(self.axis),
},
MouseActionType::MoveBy => MouseAction::MoveBy {
axis: Some(self.axis),
},
MouseActionType::PressOrRelease => MouseAction::PressOrRelease {
button: Some(self.mouse_button),
},
MouseActionType::Scroll => MouseAction::Scroll {
axis: Some(self.axis),
},
}
}
pub fn pot_filter_item_kind(&self) -> PotFilterKind {
self.pot_filter_item_kind
}
pub fn included_targets(&self) -> &NonCryptoHashSet<LearnableTargetKind> {
&self.included_targets
}
pub fn touch_cause(&self) -> TargetTouchCause {
self.touch_cause
}
pub fn mapping_modification_kind(&self) -> MappingModificationKind {
self.mapping_modification_kind
}
pub fn mapping_ref(&self) -> &MappingRefModel {
&self.mapping_ref
}
pub fn set_mouse_action_without_notification(&mut self, mouse_action: MouseAction) {
match mouse_action {
MouseAction::MoveTo { axis } => {
self.mouse_action_type = MouseActionType::MoveTo;
self.axis = axis.unwrap_or_default();
}
MouseAction::MoveBy { axis } => {
self.mouse_action_type = MouseActionType::MoveBy;
self.axis = axis.unwrap_or_default();
}
MouseAction::PressOrRelease { button } => {
self.mouse_action_type = MouseActionType::PressOrRelease;
self.mouse_button = button.unwrap_or_default();
}
MouseAction::Scroll { axis } => {
self.mouse_action_type = MouseActionType::Scroll;
self.axis = axis.unwrap_or_default();
}
}
}
pub fn with_context<'a>(
&'a self,
context: ExtendedProcessorContext<'a>,
compartment: CompartmentKind,
) -> TargetModelWithContext<'a> {
TargetModelWithContext {
target: self,
context,
compartment,
}
}
pub fn supports_axis(&self) -> bool {
if !self.r#type.definition().supports_axis() {
return false;
}
if self.r#type == ReaperTargetType::Mouse {
matches!(
self.mouse_action_type,
MouseActionType::MoveTo | MouseActionType::MoveBy | MouseActionType::Scroll
)
} else {
true
}
}
pub fn supports_mouse_button(&self) -> bool {
if !self.r#type.definition().supports_mouse_button() {
return false;
}
matches!(self.mouse_action_type, MouseActionType::PressOrRelease)
}
pub fn supports_gang_selected(&self) -> bool {
self.r#type.definition().supports_gang_selected()
}
pub fn supports_gang_grouping(&self) -> bool {
self.r#type.definition().supports_gang_grouping()
}
pub fn supports_track(&self) -> bool {
if !self.r#type.supports_track() {
return false;
}
self.requires_track_apart_from_type()
}
pub fn supports_fx_chain(&self) -> bool {
let target_type = self.r#type;
if !target_type.supports_fx_chain() {
return false;
}
match self.r#type {
t if t.supports_fx() => self.fx_type.requires_fx_chain(),
_ => true,
}
}
pub fn supports_track_must_be_selected(&self) -> bool {
if !self.r#type.supports_track_must_be_selected() {
return false;
}
self.uses_track_apart_from_type()
}
pub fn supports_osc_arg_value_range(&self) -> bool {
self.category == TargetCategory::Reaper
&& self.osc_arg_index.is_some()
&& self.osc_arg_type_tag.supports_value_range()
}
/// "Requires" means that it requires the user to provide a track in the GUI.
fn requires_track_apart_from_type(&self) -> bool {
if !self.uses_track_apart_from_type() {
return false;
}
if self.r#type.supports_fx() && !self.fx_type.requires_fx_chain() {
return false;
}
true
}
/// "Uses" means that it works on a track (even if the user doesn't need to provide it).
///
/// It makes sense then to present the "Track must be selected" checkbox then.
fn uses_track_apart_from_type(&self) -> bool {
match self.r#type {
ReaperTargetType::PlaytimeSlotTransportAction => {
use TransportAction::*;
matches!(self.transport_action, PlayStop | PlayPause)
}
ReaperTargetType::Action => self.with_track,
_ => true,
}
}
pub fn supports_fx(&self) -> bool {
if !self.is_reaper() {
return false;
}
self.r#type.supports_fx()
}
pub fn supports_route(&self) -> bool {
if !self.is_reaper() {
return false;
}
self.r#type.supports_send()
}
pub fn supports_automation_mode(&self) -> bool {
if !self.is_reaper() {
return false;
}
use ReaperTargetType::*;
match self.r#type {
TrackAutomationMode | RouteAutomationMode => true,
AutomationModeOverride => {
self.automation_mode_override_type == AutomationModeOverrideType::Override
}
_ => false,
}
}
pub fn supports_mapping_snapshot_id(&self) -> bool {
if !self.is_reaper() {
return false;
}
use ReaperTargetType::*;
match self.r#type {
LoadMappingSnapshot => {
self.mapping_snapshot_type_for_load == MappingSnapshotTypeForLoad::ById
}
TakeMappingSnapshot => {
self.mapping_snapshot_type_for_take == MappingSnapshotTypeForTake::ById
}
_ => false,
}
}
pub fn create_control_element(&self) -> VirtualControlElement {
VirtualControlElement::new(self.control_element_id, self.control_element_character)
}
fn is_reaper(&self) -> bool {
self.category == TargetCategory::Reaper
}
pub fn is_virtual(&self) -> bool {
self.category == TargetCategory::Virtual
}
fn command_id_label(&self) -> Cow<str> {
match self.resolve_action() {
None => "-".into(),
Some(action) => {
if action.is_available() {
action
.command_id()
.expect("should be available")
.to_string()
.into()
} else if let Some(command_name) = action.command_name() {
format!("<Not present> ({})", command_name.to_str()).into()
} else {
"<Not present>".into()
}
}
}
}
pub fn resolve_action(&self) -> Option<Action> {
let command_name = self.smart_command_name.as_deref()?;
build_action_from_smart_command_name(
SectionId::new(self.action_scope.section_id()),
command_name,
)
}
pub fn resolved_available_action(&self) -> Result<Action, &'static str> {
let action = self.resolve_action().ok_or("action not set")?;
if !action.is_available() {
return Err("action not available");
}
Ok(action.clone())
}
pub fn action_name_label(&self) -> Cow<str> {
match self.resolved_available_action().ok() {
None => "-".into(),
Some(a) => a
.name()
.map(|n| n.to_string().into())
.unwrap_or_else(|_| "<Failed to get action name>".into()),
}
}
}
pub struct TargetModelFormatVeryShort<'a>(pub &'a TargetModel);
impl Display for TargetModelFormatVeryShort<'_> {
/// Produces a short single-line name which is for example used to derive the automatic name.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0.category {
TargetCategory::Reaper => {
use ReaperTargetType::*;
let tt = self.0.r#type;
match tt {
Action => match self.0.resolved_available_action().ok() {
None => write!(f, "Action {}", self.0.command_id_label()),
Some(a) => f.write_str(a.name().expect("should be available").to_str()),
},
AutomationModeOverride => {
write!(f, "{}: ", tt.short_name())?;
use AutomationModeOverrideType::*;
let ovr_type = self.0.automation_mode_override_type;
match ovr_type {
None | Bypass => write!(f, "{ovr_type}"),
Override => write!(f, "{}", self.0.automation_mode),
}
}
Transport => {
write!(f, "{}", self.0.transport_action)
}
AnyOn => {
write!(f, "{}", self.0.any_on_parameter)
}
GoToBookmark => {
let type_label = match self.0.bookmark_type {
BookmarkType::Marker => "Marker",
BookmarkType::Region => "Region",
};
let bm_prefix = match self.0.bookmark_anchor_type {
BookmarkAnchorType::Id => "",
BookmarkAnchorType::Index => "#",
};
write!(
f,
"Go to {} {}{}",
type_label, bm_prefix, self.0.bookmark_ref
)
}
TrackAutomationMode => {
write!(f, "{}: {}", tt.short_name(), self.0.automation_mode)
}
TrackTouchState => {
write!(
f,
"{}: {}",
tt.short_name(),
self.0.touched_track_parameter_type
)
}
_ => f.write_str(tt.short_name()),
}
}
TargetCategory::Virtual => match self.0.control_element_id {
VirtualControlElementId::Indexed(i) => {
write!(f, "{} {}", self.0.control_element_character, i + 1)
}
VirtualControlElementId::Named(n) => {
write!(f, "{} ({})", n, self.0.control_element_character)
}
},
}
}
}
pub struct TargetModelFormatMultiLine<'a> {
target: &'a TargetModel,
context: ExtendedProcessorContext<'a>,
session: &'a UnitModel,
compartment: CompartmentKind,
}
impl<'a> TargetModelFormatMultiLine<'a> {
pub fn new(
target: &'a TargetModel,
session: &'a UnitModel,
compartment: CompartmentKind,
) -> Self {
TargetModelFormatMultiLine {
target,
context: session.extended_context(),
session,
compartment,
}
}
fn track_label(&self) -> String {
let virtual_track = self.target.virtual_track();
let virtual_track = match virtual_track.as_ref() {
None => return TARGET_UNDEFINED_LABEL.into(),
Some(t) => t,
};
if self.target.supports_track() {
get_virtual_track_label(virtual_track, self.compartment, self.context)
} else {
TARGET_OBJECT_IRRELEVANT_LABEL.to_string()
}
}
fn mapping_snapshot_for_load_label(&self) -> String {
match self.target.mapping_snapshot_type_for_load {
MappingSnapshotTypeForLoad::Initial => MappingSnapshotTypeForLoad::Initial.to_string(),
MappingSnapshotTypeForLoad::ById => self.mapping_snapshot_id_label(),
}
}
fn mapping_snapshot_for_take_label(&self) -> String {
match self.target.mapping_snapshot_type_for_take {
MappingSnapshotTypeForTake::LastLoaded => {
MappingSnapshotTypeForTake::LastLoaded.to_string()
}
MappingSnapshotTypeForTake::ById => self.mapping_snapshot_id_label(),
}
}
fn mapping_snapshot_id_label(&self) -> String {
match &self.target.mapping_snapshot_id {
None => "-".into(),
Some(id) => id.to_string(),
}
}
fn route_label(&self) -> Cow<str> {
let virtual_route = self.target.virtual_track_route().ok();
let virtual_route = match virtual_route.as_ref() {
None => return TARGET_UNDEFINED_LABEL.into(),
Some(r) => r,
};
use TrackRouteSelector::*;
match &virtual_route.selector {
ById(_) => {
if let Ok(r) = self.resolve_first_track_route() {
get_route_label(&r).into()
} else {
get_non_present_virtual_route_label(virtual_route).into()
}
}
_ => virtual_route.to_string().into(),
}
}
fn fx_label(&self) -> Cow<str> {
let fx_descriptor = match self.target.fx_descriptor() {
Ok(d) => d,
Err(_) => return TARGET_UNDEFINED_LABEL.into(),
};
get_virtual_fx_label(&fx_descriptor, self.compartment, self.context).into()
}
fn fx_param_label(&self) -> Cow<str> {
let virtual_param = self.target.virtual_fx_parameter();
let virtual_param = match virtual_param.as_ref() {
None => return TARGET_UNDEFINED_LABEL.into(),
Some(p) => p,
};
use VirtualFxParameter::*;
match virtual_param {
ById(_) => {
if let Ok(p) = self.resolve_first_fx_param() {
get_fx_param_label(Some(&p), p.index())
} else {
format!("<Not present> ({virtual_param})").into()
}
}
_ => virtual_param.to_string().into(),
}
}
fn bookmark_label(&self) -> String {
// TODO-medium We should do this similar to the other target objects and introduce a
// virtual struct.
let bookmark_type = self.target.bookmark_type;
let anchor_type = self.target.bookmark_anchor_type;
let bookmark_ref = self.target.bookmark_ref;
match anchor_type {
BookmarkAnchorType::Id => {
let res = find_bookmark(
self.context.context().project_or_current_project(),
bookmark_type,
anchor_type,
bookmark_ref,
);
if let Ok(res) = res {
get_bookmark_label_by_id(bookmark_type, res.basic_info.id, &res.bookmark.name())
} else {
get_non_present_bookmark_label(anchor_type, bookmark_ref)
}
}
BookmarkAnchorType::Index => {
get_bookmark_label_by_position(bookmark_type, bookmark_ref)
}
}
}
// Returns an error if that send (or track) doesn't exist.
pub fn resolve_first_track_route(&self) -> Result<TrackRoute, &'static str> {
let routes = get_track_routes(
self.context,
&self.target.route_descriptor()?,
self.compartment,
)?;
routes.into_iter().next().ok_or("empty list of routes")
}
// Returns an error if that param (or FX) doesn't exist.
fn resolve_first_fx_param(&self) -> Result<FxParameter, &'static str> {
let params = get_fx_params(
self.context,
&self.target.fx_parameter_descriptor()?,
self.compartment,
)?;
params.into_iter().next().ok_or("empty FX param list")
}
}
const UNIT_NOT_FOUND_LABEL: &str = "<Unit not found>";
const MAPPING_NOT_FOUND_LABEL: &str = "<Mapping not found>";
const NONE_LABEL: &str = "<None>";
const MAPPING_LABEL: &str = "Mapping: ";
impl Display for TargetModelFormatMultiLine<'_> {
/// Produces a multi-line description of the target.
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
use TargetCategory::*;
match self.target.category {
Reaper => {
use ReaperTargetType::*;
let tt = self.target.r#type;
match tt {
PlaytimeSlotTransportAction => {
let slot = &self.target.playtime_slot;
let action = &self.target.playtime_slot_transport_action;
write!(f, "{tt}\n{slot}\n{action}")
}
PlaytimeSlotSeek => {
let slot = &self.target.playtime_slot;
write!(f, "{tt}\n{slot}")
}
PlaytimeSlotVolume => {
let slot = &self.target.playtime_slot;
write!(f, "{tt}\n{slot}")
}
PlaytimeColumnAction => {
let column = &self.target.playtime_column;
let action = &self.target.playtime_column_action;
write!(f, "{tt}\n{column}\n{action}")
}
PlaytimeRowAction => {
let row = &self.target.playtime_row;
let action = &self.target.playtime_row_action;
write!(f, "{tt}\n{row}\n{action}")
}
PlaytimeMatrixAction => {
let action = &self.target.playtime_matrix_action;
write!(f, "{tt}\n{action}")
}
PlaytimeControlUnitScroll | PlaytimeBrowseCells => {
let axis = &self.target.axis;
write!(f, "{tt}\n{axis}")
}
PlaytimeSlotManagementAction => {
let slot = &self.target.playtime_slot;
let action = &self.target.playtime_slot_management_action;
write!(f, "{tt}\n{slot}\n{action}")
}
Action => write!(
f,
"{}\n{}\n{}",
tt,
self.target.command_id_label(),
self.target.action_name_label()
),
FxParameterValue => write!(
f,
"{}\nTrack {}\nFX {}\nParam {}",
tt,
self.track_label(),
self.fx_label(),
self.fx_param_label()
),
TrackTool | TrackVolume | TrackPeak | TrackPan | TrackWidth | TrackArm
| TrackSelection | TrackMute | TrackPhase | TrackSolo | TrackShow
| BrowseFxs | AllTrackFxEnable | TrackParentSend => {
write!(f, "{}\nTrack {}", tt, self.track_label())
}
EnableInstances => {
write!(
f,
"{tt}\n{}\n{}",
self.target.instance_tag_kind(),
format_tags_as_csv(&self.target.tags),
)
}
EnableUnits => {
write!(f, "{tt}\n{}", format_tags_as_csv(&self.target.tags),)
}
TrackAutomationMode => {
write!(
f,
"{}\nTrack {}\n{}",
tt,
self.track_label(),
self.target.automation_mode
)
}
RouteVolume | RoutePan | RouteMute | RoutePhase | RouteMono
| RouteAutomationMode => write!(
f,
"{}\nTrack {}\n{} {}",
tt,
self.track_label(),
self.target.route_type,
self.route_label()
),
FxOpen | FxEnable | FxPreset | FxTool => write!(
f,
"{}\nTrack {}\nFX {}",
tt,
self.track_label(),
self.fx_label(),
),
Transport => write!(f, "{}\n{}", tt, self.target.transport_action),
AnyOn => write!(f, "{}\n{}", tt, self.target.any_on_parameter),
AutomationModeOverride => {
write!(f, "{}\n{}", tt, self.target.automation_mode_override_type)
}
LoadFxSnapshot => write!(
f,
"{}\n{}",
tt,
self.target
.fx_snapshot
.as_ref()
.map(|s| s.to_string())
.unwrap_or_else(|| "-".to_owned())
),
LoadMappingSnapshot => {
write!(
f,
"{}\n\
Snapshot: {}\n\
Tags: {}",
tt,
self.mapping_snapshot_for_load_label(),
format_tags_as_csv(self.target.tags())
)
}
TakeMappingSnapshot => {
write!(
f,
"{}\n\
Snapshot: {}\n\
Tags: {}",
tt,
self.mapping_snapshot_for_take_label(),
format_tags_as_csv(self.target.tags())
)
}
TrackTouchState => write!(
f,
"{}\nTrack {}\n{}",
tt,
self.track_label(),
self.target.touched_track_parameter_type
),
GoToBookmark => {
write!(f, "{}\n{}", tt, self.bookmark_label())
}
Mouse => {
write!(f, "{}\n{}", tt, self.target.mouse_action_type)?;
if self.target.supports_axis() {
write!(f, "\n{}", self.target.axis)?;
}
if self.target.supports_mouse_button() {
write!(f, "\n{}", self.target.mouse_button)?;
}
Ok(())
}
ModifyMapping => {
write!(f, "{}\n{}\n", tt, self.target.mapping_modification_kind)?;
match &self.target.mapping_ref {
MappingRefModel::OwnMapping { mapping_id } => {
MAPPING_LABEL.fmt(f)?;
if let Some(id) = mapping_id {
let qualified_id =
QualifiedMappingId::new(self.compartment, *id);
if let Some(m) =
self.session.find_mapping_by_qualified_id(qualified_id)
{
m.borrow().effective_name().fmt(f)?;
} else {
MAPPING_NOT_FOUND_LABEL.fmt(f)?;
}
} else {
NONE_LABEL.fmt(f)?;
}
}
MappingRefModel::ForeignMapping {
session_id,
mapping_key,
} => {
write!(f, "Unit: {session_id}\n{MAPPING_LABEL}")?;
if let Some(mapping_key) = mapping_key {
let session = self
.context
.control_context()
.unit_container
.find_session_by_id(session_id);
if let Some(session) = session {
let session = session.borrow();
if let Some(m) = session
.find_mapping_by_key(self.compartment, mapping_key)
{
m.borrow().effective_name().fmt(f)?;
} else {
MAPPING_NOT_FOUND_LABEL.fmt(f)?;
}
} else {
UNIT_NOT_FOUND_LABEL.fmt(f)?;
}
} else {
NONE_LABEL.fmt(f)?;
}
}
}
Ok(())
}
_ => write!(f, "{tt}"),
}
}
Virtual => write!(f, "Virtual\n{}", self.target.create_control_element()),
}
}
}
pub fn get_fx_param_label(fx_param: Option<&FxParameter>, index: u32) -> Cow<'static, str> {
let position = index + 1;
match fx_param.and_then(|p| p.name().ok()) {
None => format!("{position}. <Not present>").into(),
Some(name) => {
let name = name.into_inner();
// Parameter names are not reliably UTF-8-encoded (e.g. "JS: Stereo Width")
let name = name.to_string_lossy();
if name.is_empty() {
position.to_string().into()
} else {
format!("{position}. {name}").into()
}
}
}
}
pub fn get_route_label(route: &TrackRoute) -> String {
format!("{}. {}", route.index() + 1, route.name().to_str())
}
pub fn get_optional_fx_label(virtual_chain_fx: &VirtualChainFx, fx: Option<&Fx>) -> String {
match virtual_chain_fx {
VirtualChainFx::Dynamic(_) => virtual_chain_fx.to_string(),
_ => match fx {
None => format!("<Not present> ({virtual_chain_fx})"),
Some(fx) => get_fx_label(fx.index(), fx),
},
}
}
pub fn get_fx_label(index: u32, fx: &Fx) -> String {
format!(
"{}. {}",
index + 1,
// When closing project, this is sometimes not available anymore although the FX is still
// picked up when querying the list of FXs! Prevent a panic.
if fx.is_available() {
get_fx_name(fx)
} else {
"".to_owned()
}
)
}
pub struct TargetModelWithContext<'a> {
target: &'a TargetModel,
context: ExtendedProcessorContext<'a>,
compartment: CompartmentKind,
}
impl TargetModelWithContext<'_> {
/// Creates a target based on this model's properties and the current REAPER state.
///
/// This returns a target regardless of the activation conditions of the target. Example:
/// If `enable_only_if_track_selected` is `true` and the track is _not_ selected when calling
/// this function, the target will still be created!
///
/// # Errors
///
/// Returns an error if not enough information is provided by the model or if something (e.g.
/// track/FX/parameter) is not available.
pub fn resolve(&self) -> Result<Vec<CompoundMappingTarget>, &'static str> {
let unresolved = self.target.create_target(self.compartment)?;
unresolved.resolve(self.context, self.compartment)
}
pub fn resolve_first(&self) -> Result<CompoundMappingTarget, &'static str> {
let targets = self.resolve()?;
targets.into_iter().next().ok_or("resolved to empty list")
}
pub fn is_known_to_be_roundable(&self) -> bool {
// TODO-low use cached
self.resolve_first()
.map(|t| {
matches!(
t.control_type(self.context.control_context()),
ControlType::AbsoluteContinuousRoundable { .. }
)
})
.unwrap_or(false)
}
// Returns an error if the FX doesn't exist.
pub fn first_fx(&self) -> Result<Fx, &'static str> {
first_effective_fx(
&self.target.fx_descriptor()?,
self.compartment,
self.context,
)
}
pub fn project(&self) -> Project {
self.context.context().project_or_current_project()
}
pub fn first_fx_chain(&self) -> Result<FxChain, &'static str> {
let track = self.first_effective_track()?;
let chain = if self.target.fx_is_input_fx {
track.input_fx_chain()
} else {
track.normal_fx_chain()
};
Ok(chain)
}
pub fn first_effective_track(&self) -> Result<Track, &'static str> {
let virtual_track = self
.target
.virtual_track()
.ok_or("virtual track not complete")?;
first_effective_track(&virtual_track, self.compartment, self.context)
}
}
pub fn first_effective_track(
virtual_track: &VirtualTrack,
compartment: CompartmentKind,
context: ExtendedProcessorContext,
) -> Result<Track, &'static str> {
virtual_track
.resolve(context, compartment)
.map_err(|_| "particular track couldn't be resolved")?
.into_iter()
.next()
.ok_or("resolved to empty track list")
}
pub fn first_effective_fx(
fx_descriptor: &FxDescriptor,
compartment: CompartmentKind,
context: ExtendedProcessorContext,
) -> Result<Fx, &'static str> {
fx_descriptor
.resolve(context, compartment)?
.into_iter()
.next()
.ok_or("resolves to empty FX list")
}
pub fn get_bookmark_label_by_id(bookmark_type: BookmarkType, id: BookmarkId, name: &str) -> String {
format!(
"{} {}. {}",
FormattableBookmarkType(bookmark_type),
id,
name
)
}
pub fn get_bookmark_label_by_position(
bookmark_type: BookmarkType,
index_within_type: u32,
) -> String {
format!(
"{} #{}",
FormattableBookmarkType(bookmark_type),
index_within_type + 1
)
}
struct FormattableBookmarkType(BookmarkType);
impl Display for FormattableBookmarkType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self.0 {
BookmarkType::Marker => f.write_str("Marker"),
BookmarkType::Region => f.write_str("Region"),
}
}
}
pub fn get_non_present_bookmark_label(
anchor_type: BookmarkAnchorType,
bookmark_ref: u32,
) -> String {
match anchor_type {
BookmarkAnchorType::Id => format!("<Not present> (ID {bookmark_ref})"),
BookmarkAnchorType::Index => format!("{bookmark_ref}. <Not present>"),
}
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Default,
Serialize,
Deserialize,
EnumIter,
TryFromPrimitive,
IntoPrimitive,
Display,
)]
#[repr(usize)]
pub enum TargetCategory {
#[default]
#[serde(rename = "reaper")]
#[display(fmt = "Real")]
Reaper,
#[serde(rename = "virtual")]
#[display(fmt = "Virtual")]
Virtual,
}
impl TargetCategory {
pub fn default_for(compartment: CompartmentKind) -> Self {
use TargetCategory::*;
match compartment {
CompartmentKind::Controller => Virtual,
CompartmentKind::Main => Reaper,
}
}
pub fn is_allowed_in(self, compartment: CompartmentKind) -> bool {
use TargetCategory::*;
match compartment {
CompartmentKind::Controller => true,
CompartmentKind::Main => match self {
Reaper => true,
Virtual => false,
},
}
}
}
fn virtualize_track(
track: &Track,
context: &ProcessorContext,
special_monitoring_fx_handling: bool,
) -> VirtualTrack {
if !track.is_available() {
// Fixes https://github.com/helgoboss/helgobox/issues/1126
return VirtualTrack::ById(*track.guid());
}
let own_track = context.track().cloned().unwrap_or_else(|| {
context
.project_or_current_project()
.master_track()
.expect("no way")
});
if own_track == *track {
VirtualTrack::This
} else if track.is_master_track() {
VirtualTrack::Master
} else if special_monitoring_fx_handling && context.is_on_monitoring_fx_chain() {
// It doesn't make sense to refer to tracks via ID if we are on the monitoring FX chain.
VirtualTrack::ByIndex {
index: track.index().expect("impossible"),
scope: TrackScope::AllTracks,
}
} else {
VirtualTrack::ById(*track.guid())
}
}
fn virtualize_fx(
fx: &Fx,
context: &ProcessorContext,
special_monitoring_fx_handling: bool,
) -> VirtualFx {
if context.containing_fx() == fx {
VirtualFx::This
} else {
VirtualFx::ChainFx {
is_input_fx: fx.is_input_fx(),
chain_fx: if special_monitoring_fx_handling && context.is_on_monitoring_fx_chain() {
// Doesn't make sense to refer to FX via UUID if we are on monitoring FX chain.
VirtualChainFx::ByIndex(fx.index())
} else if let Ok(guid) = fx.get_or_query_guid() {
VirtualChainFx::ById(guid, Some(fx.index()))
} else {
VirtualChainFx::ByIdOrIndex(None, fx.index())
},
}
}
}
fn virtualize_route(
route: &TrackRoute,
context: &ProcessorContext,
special_monitoring_fx_handling: bool,
) -> VirtualTrackRoute {
let partner = route.partner();
VirtualTrackRoute {
r#type: match route.direction() {
TrackSendDirection::Receive => TrackRouteType::Receive,
TrackSendDirection::Send => {
if matches!(partner, Some(TrackRoutePartner::HardwareOutput(_))) {
TrackRouteType::HardwareOutput
} else {
TrackRouteType::Send
}
}
},
selector: if special_monitoring_fx_handling && context.is_on_monitoring_fx_chain() {
// Doesn't make sense to refer to route via related-track UUID if we are on monitoring
// FX chain.
TrackRouteSelector::ByIndex(route.index())
} else {
match partner {
None | Some(TrackRoutePartner::HardwareOutput(_)) => {
TrackRouteSelector::ByIndex(route.index())
}
Some(TrackRoutePartner::Track(t)) => TrackRouteSelector::ById(*t.guid()),
}
},
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, TryFromPrimitive, IntoPrimitive, Display)]
#[repr(usize)]
pub enum VirtualTrackType {
#[display(fmt = "<This>")]
This,
#[display(fmt = "<Selected>")]
Selected,
#[display(fmt = "<All selected>")]
AllSelected,
#[display(fmt = "<Dynamic>")]
Dynamic,
#[display(fmt = "<Dynamic (TCP)>")]
DynamicTcp,
#[display(fmt = "<Dynamic (MCP)>")]
DynamicMcp,
#[display(fmt = "<Master>")]
Master,
#[display(fmt = "<Unit>")]
Unit,
#[display(fmt = "Particular")]
ById,
#[display(fmt = "Named")]
ByName,
#[display(fmt = "All named")]
AllByName,
#[display(fmt = "At position")]
ByIndex,
#[display(fmt = "At TCP position")]
ByIndexTcp,
#[display(fmt = "At MCP position")]
ByIndexMcp,
#[display(fmt = "By ID or name (legacy)")]
ByIdOrName,
#[display(fmt = "From Playtime column")]
FromClipColumn,
}
impl Default for VirtualTrackType {
fn default() -> Self {
Self::This
}
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
EnumIter,
TryFromPrimitive,
IntoPrimitive,
Serialize,
Deserialize,
Display,
)]
#[repr(usize)]
pub enum MappingSnapshotTypeForLoad {
#[display(fmt = "<Initial>")]
#[serde(rename = "initial")]
Initial,
#[display(fmt = "By ID")]
ById,
}
impl Default for MappingSnapshotTypeForLoad {
fn default() -> Self {
Self::Initial
}
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
EnumIter,
TryFromPrimitive,
IntoPrimitive,
Serialize,
Deserialize,
Display,
)]
#[repr(usize)]
pub enum MappingSnapshotTypeForTake {
#[display(fmt = "<Last loaded>")]
#[serde(rename = "last-loaded")]
LastLoaded,
#[display(fmt = "By ID")]
ById,
}
impl Default for MappingSnapshotTypeForTake {
fn default() -> Self {
Self::LastLoaded
}
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
EnumIter,
TryFromPrimitive,
IntoPrimitive,
Display,
Serialize,
Deserialize,
)]
#[repr(usize)]
pub enum BookmarkAnchorType {
#[display(fmt = "By ID")]
Id,
#[display(fmt = "At position")]
Index,
}
impl Default for BookmarkAnchorType {
fn default() -> Self {
Self::Id
}
}
impl VirtualTrackType {
pub fn from_virtual_track(virtual_track: &VirtualTrack) -> Self {
use VirtualTrack::*;
match virtual_track {
This => Self::This,
Selected { allow_multiple } => {
if *allow_multiple {
Self::AllSelected
} else {
Self::Selected
}
}
Dynamic { scope, .. } => match scope {
TrackScope::AllTracks => Self::Dynamic,
TrackScope::TracksVisibleInTcp => Self::DynamicTcp,
TrackScope::TracksVisibleInMcp => Self::DynamicMcp,
},
Master => Self::Master,
Unit => Self::Unit,
ByIdOrName(_, _) => Self::ByIdOrName,
ById(_) => Self::ById,
ByName { allow_multiple, .. } => {
if *allow_multiple {
Self::AllByName
} else {
Self::ByName
}
}
ByIndex { scope, .. } => match scope {
TrackScope::AllTracks => Self::ByIndex,
TrackScope::TracksVisibleInTcp => Self::ByIndexTcp,
TrackScope::TracksVisibleInMcp => Self::ByIndexMcp,
},
FromClipColumn { .. } => Self::FromClipColumn,
}
}
pub fn is_dynamic(&self) -> bool {
matches!(self, Self::Dynamic | Self::DynamicTcp | Self::DynamicMcp)
}
pub fn is_by_index(&self) -> bool {
matches!(self, Self::ByIndex | Self::ByIndexTcp | Self::ByIndexMcp)
}
pub fn virtual_track_scope(&self) -> Option<TrackScope> {
use VirtualTrackType::*;
match self {
ByIndex | Dynamic => Some(TrackScope::AllTracks),
ByIndexTcp | DynamicTcp => Some(TrackScope::TracksVisibleInTcp),
ByIndexMcp | DynamicMcp => Some(TrackScope::TracksVisibleInMcp),
_ => None,
}
}
pub fn refers_to_project(&self) -> bool {
use VirtualTrackType::*;
matches!(self, ByIdOrName | ById)
}
pub fn is_sticky(&self) -> bool {
use VirtualTrackType::*;
matches!(self, ByIdOrName | ById | This | Master)
}
pub fn track_selected_condition_makes_sense(&self) -> bool {
use VirtualTrackType::*;
!matches!(self, Selected | AllSelected)
}
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
EnumIter,
TryFromPrimitive,
IntoPrimitive,
Display,
Serialize,
Deserialize,
)]
#[repr(usize)]
pub enum VirtualFxType {
#[display(fmt = "<This>")]
#[serde(rename = "this")]
This,
#[display(fmt = "<Focused>")]
#[serde(rename = "focused")]
Focused,
#[display(fmt = "<Unit>")]
#[serde(rename = "instance")]
Unit,
#[display(fmt = "<Dynamic>")]
#[serde(rename = "dynamic")]
Dynamic,
#[display(fmt = "Particular")]
#[serde(rename = "id")]
ById,
#[display(fmt = "Named")]
#[serde(rename = "name")]
ByName,
#[display(fmt = "All named")]
AllByName,
#[display(fmt = "At position")]
#[serde(rename = "index")]
ByIndex,
#[display(fmt = "By ID or pos (legacy)")]
#[serde(rename = "id-or-index")]
ByIdOrIndex,
}
impl Default for VirtualFxType {
fn default() -> Self {
Self::ById
}
}
impl VirtualFxType {
pub fn from_virtual_fx(virtual_fx: &VirtualFx) -> Self {
use VirtualFx::*;
match virtual_fx {
This => VirtualFxType::This,
LastFocused => VirtualFxType::Focused,
Unit => VirtualFxType::Unit,
ChainFx { chain_fx, .. } => {
use VirtualChainFx::*;
match chain_fx {
Dynamic(_) => Self::Dynamic,
ById(_, _) => Self::ById,
ByName { allow_multiple, .. } => {
if *allow_multiple {
Self::AllByName
} else {
Self::ByName
}
}
ByIndex(_) => Self::ByIndex,
ByIdOrIndex(_, _) => Self::ByIdOrIndex,
}
}
}
}
pub fn requires_fx_chain(&self) -> bool {
use VirtualFxType::*;
match self {
This => false,
Focused => false,
Dynamic => true,
Unit => false,
ById => true,
ByName => true,
AllByName => true,
ByIndex => true,
ByIdOrIndex => true,
}
}
pub fn refers_to_project(&self) -> bool {
use VirtualFxType::*;
matches!(self, ById | ByIdOrIndex)
}
pub fn is_sticky(&self) -> bool {
use VirtualFxType::*;
matches!(self, ById | ByIdOrIndex | This)
}
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
EnumIter,
TryFromPrimitive,
IntoPrimitive,
Display,
Serialize,
Deserialize,
)]
#[repr(usize)]
pub enum VirtualFxParameterType {
#[display(fmt = "<Dynamic>")]
#[serde(rename = "dynamic")]
Dynamic,
#[display(fmt = "Named")]
#[serde(rename = "name")]
ByName,
#[display(fmt = "Particular")]
#[serde(rename = "index")]
ById,
#[display(fmt = "At position")]
#[serde(rename = "index-manual")]
ByIndex,
}
impl Default for VirtualFxParameterType {
fn default() -> Self {
Self::ById
}
}
impl VirtualFxParameterType {
pub fn from_virtual_fx_parameter(param: &VirtualFxParameter) -> Self {
use VirtualFxParameter::*;
match param {
Dynamic(_) => Self::Dynamic,
ByName(_) => Self::ByName,
ByIndex(_) => Self::ByIndex,
ById(_) => Self::ById,
}
}
pub fn is_sticky(&self) -> bool {
use VirtualFxParameterType::*;
matches!(self, ById)
}
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
EnumIter,
TryFromPrimitive,
IntoPrimitive,
Display,
Serialize,
Deserialize,
)]
#[repr(usize)]
pub enum TrackRouteSelectorType {
#[display(fmt = "<Dynamic>")]
#[serde(rename = "dynamic")]
Dynamic,
#[display(fmt = "Particular")]
#[serde(rename = "id")]
ById,
#[display(fmt = "Named")]
#[serde(rename = "name")]
ByName,
#[display(fmt = "At position")]
#[serde(rename = "index")]
ByIndex,
}
impl Default for TrackRouteSelectorType {
fn default() -> Self {
Self::ByIndex
}
}
impl TrackRouteSelectorType {
pub fn from_route_selector(selector: &TrackRouteSelector) -> Self {
use TrackRouteSelector::*;
match selector {
Dynamic(_) => Self::Dynamic,
ById(_) => Self::ById,
ByName(_) => Self::ByName,
ByIndex(_) => Self::ByIndex,
}
}
pub fn refers_to_project(&self) -> bool {
use TrackRouteSelectorType::*;
matches!(self, ById)
}
pub fn is_sticky(&self) -> bool {
use TrackRouteSelectorType::*;
matches!(self, ById)
}
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FxSnapshot {
#[serde(default, skip_serializing_if = "is_default")]
pub fx_type: String,
#[serde(default, skip_serializing_if = "is_default")]
pub fx_name: String,
#[serde(default, skip_serializing_if = "is_default")]
pub preset_name: Option<String>,
#[serde(default, skip_serializing_if = "is_default")]
pub chunk: Rc<String>,
}
impl Clone for FxSnapshot {
fn clone(&self) -> Self {
Self {
fx_type: self.fx_type.clone(),
fx_name: self.fx_name.clone(),
preset_name: self.preset_name.clone(),
// We want a totally detached duplicate.
chunk: Rc::new((*self.chunk).clone()),
}
}
}
impl Display for FxSnapshot {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let fmt_size = bytesize::ByteSize(self.chunk.len() as _);
write!(
f,
"{} | {} | {}",
self.preset_name.as_deref().unwrap_or("-"),
fmt_size,
self.fx_name,
)
}
}
#[derive(Default)]
pub struct TrackPropValues {
pub r#type: VirtualTrackType,
pub id: Option<Guid>,
pub name: String,
pub expression: String,
pub index: u32,
pub clip_column: PlaytimeColumnDescriptor,
pub clip_column_track_context: ClipColumnTrackContext,
}
impl TrackPropValues {
pub fn from_virtual_track(track: VirtualTrack) -> Self {
Self {
r#type: VirtualTrackType::from_virtual_track(&track),
id: track.id(),
name: track.name().unwrap_or_default(),
index: track.index().unwrap_or_default(),
clip_column: {
match track.clip_column().unwrap_or(&Default::default()) {
VirtualPlaytimeColumn::Active => PlaytimeColumnDescriptor::Active,
VirtualPlaytimeColumn::ByIndex(i) => {
PlaytimeColumnDescriptor::ByIndex(ColumnAddress::new(*i))
}
VirtualPlaytimeColumn::Dynamic(_) => PlaytimeColumnDescriptor::Dynamic {
expression: Default::default(),
},
}
},
expression: Default::default(),
clip_column_track_context: track.clip_column_track_context().unwrap_or_default(),
}
}
}
#[derive(Default)]
pub struct TrackRoutePropValues {
pub selector_type: TrackRouteSelectorType,
pub r#type: TrackRouteType,
pub id: Option<Guid>,
pub name: String,
pub expression: String,
pub index: u32,
}
impl TrackRoutePropValues {
pub fn from_virtual_route(route: VirtualTrackRoute) -> Self {
Self {
selector_type: TrackRouteSelectorType::from_route_selector(&route.selector),
r#type: route.r#type,
id: route.id(),
name: route.name().unwrap_or_default(),
index: route.index().unwrap_or_default(),
expression: Default::default(),
}
}
}
#[derive(Default)]
pub struct FxPropValues {
pub r#type: VirtualFxType,
pub is_input_fx: bool,
pub id: Option<Guid>,
pub name: String,
pub expression: String,
pub index: u32,
}
impl FxPropValues {
pub fn from_virtual_fx(fx: VirtualFx) -> Self {
Self {
r#type: VirtualFxType::from_virtual_fx(&fx),
is_input_fx: fx.is_input_fx(),
id: fx.id(),
name: fx.name().unwrap_or_default(),
index: fx.index().unwrap_or_default(),
expression: Default::default(),
}
}
}
#[derive(Default)]
pub struct FxParameterPropValues {
pub r#type: VirtualFxParameterType,
pub name: String,
pub expression: String,
pub index: u32,
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Serialize,
Deserialize,
EnumIter,
TryFromPrimitive,
IntoPrimitive,
Display,
)]
#[repr(usize)]
pub enum RealearnTrackArea {
#[serde(rename = "tcp")]
#[display(fmt = "Track control panel")]
Tcp,
#[serde(rename = "mcp")]
#[display(fmt = "Mixer control panel")]
Mcp,
}
impl Default for RealearnTrackArea {
fn default() -> Self {
Self::Tcp
}
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Serialize_repr,
Deserialize_repr,
EnumIter,
TryFromPrimitive,
IntoPrimitive,
Display,
)]
#[repr(usize)]
pub enum RealearnAutomationMode {
#[display(fmt = "Trim/Read")]
TrimRead = 0,
#[display(fmt = "Read")]
Read = 1,
#[display(fmt = "Touch")]
Touch = 2,
#[display(fmt = "Write")]
Write = 3,
#[display(fmt = "Latch")]
Latch = 4,
#[display(fmt = "Latch Preview")]
LatchPreview = 5,
}
impl Default for RealearnAutomationMode {
fn default() -> Self {
Self::TrimRead
}
}
impl RealearnAutomationMode {
fn to_reaper(self) -> AutomationMode {
use RealearnAutomationMode::*;
match self {
TrimRead => AutomationMode::TrimRead,
Read => AutomationMode::Read,
Touch => AutomationMode::Touch,
Write => AutomationMode::Write,
Latch => AutomationMode::Latch,
LatchPreview => AutomationMode::LatchPreview,
}
}
fn from_reaper(value: AutomationMode) -> Self {
use AutomationMode::*;
match value {
TrimRead => Self::TrimRead,
Read => Self::Read,
Touch => Self::Touch,
Write => Self::Write,
Latch => Self::Latch,
LatchPreview => Self::LatchPreview,
Unknown(_) => Self::TrimRead,
}
}
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
EnumIter,
Serialize,
Deserialize,
TryFromPrimitive,
IntoPrimitive,
Display,
)]
#[repr(usize)]
pub enum AutomationModeOverrideType {
#[serde(rename = "none")]
#[display(fmt = "None")]
None,
#[serde(rename = "bypass")]
#[display(fmt = "Bypass all envelopes")]
Bypass,
#[serde(rename = "override")]
#[display(fmt = "Override")]
Override,
}
impl Default for AutomationModeOverrideType {
fn default() -> Self {
Self::Bypass
}
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
EnumIter,
Serialize,
Deserialize,
TryFromPrimitive,
IntoPrimitive,
Display,
)]
#[repr(usize)]
pub enum TargetUnit {
#[serde(rename = "native")]
Native,
#[serde(rename = "percent")]
Percent,
}
impl Default for TargetUnit {
fn default() -> Self {
Self::Native
}
}
#[derive(Debug)]
pub enum ConcreteTrackInstruction<'a> {
/// If the context is not available, other track properties won't get set.
This(Option<&'a ProcessorContext>),
/// If the context is not available, other track properties won't get set.
ById {
id: Option<Guid>,
context: Option<&'a ProcessorContext>,
},
ByIdWithTrack(Track),
}
impl<'a> ConcreteTrackInstruction<'a> {
pub fn resolve(self) -> ResolvedConcreteTrackInstruction<'a> {
use ConcreteTrackInstruction::*;
ResolvedConcreteTrackInstruction {
track: match &self {
This(context) => context.and_then(|c| c.track().cloned()),
ById {
id: Some(id),
context: Some(c),
} => c
.project_or_current_project()
.track_by_guid(id)
.ok()
.and_then(|t| if t.is_available() { Some(t) } else { None }),
ByIdWithTrack(t) => Some(t.clone()),
_ => None,
},
instruction: self,
}
}
}
pub struct ResolvedConcreteTrackInstruction<'a> {
instruction: ConcreteTrackInstruction<'a>,
track: Option<Track>,
}
impl ResolvedConcreteTrackInstruction<'_> {
pub fn virtual_track_type(&self) -> VirtualTrackType {
use ConcreteTrackInstruction::*;
match &self.instruction {
This(_) => VirtualTrackType::This,
ById { .. } | ByIdWithTrack(_) => VirtualTrackType::ById,
}
}
pub fn id(&self) -> Option<Guid> {
use ConcreteTrackInstruction::*;
match &self.instruction {
ById { id, .. } => *id,
_ => Some(*self.track.as_ref()?.guid()),
}
}
pub fn name(&self) -> Option<String> {
Some(self.track.as_ref()?.name()?.into_string())
}
pub fn index(&self) -> Option<u32> {
self.track.as_ref()?.index()
}
}
#[derive(Debug)]
pub enum ConcreteFxInstruction<'a> {
/// If the context is not available, other FX properties won't get set.
This(Option<&'a ProcessorContext>),
/// If the context is not available, other FX properties won't get set.
ById {
is_input_fx: Option<bool>,
id: Option<Guid>,
track: Option<Track>,
},
ByIdWithFx(Fx),
}
impl<'a> ConcreteFxInstruction<'a> {
pub fn resolve(self) -> ResolvedConcreteFxInstruction<'a> {
use ConcreteFxInstruction::*;
ResolvedConcreteFxInstruction {
fx: match &self {
This(context) => context.map(|c| c.containing_fx().clone()),
ById {
is_input_fx: Some(is_input_fx),
id: Some(id),
track: Some(t),
} => {
let chain = if *is_input_fx {
t.input_fx_chain()
} else {
t.normal_fx_chain()
};
let fx = chain.fx_by_guid(id);
if fx.is_available() {
Some(fx)
} else {
None
}
}
ByIdWithFx(fx) => Some(fx.clone()),
_ => None,
},
instruction: self,
}
}
}
pub struct ResolvedConcreteFxInstruction<'a> {
instruction: ConcreteFxInstruction<'a>,
fx: Option<Fx>,
}
impl ResolvedConcreteFxInstruction<'_> {
pub fn virtual_fx_type(&self) -> VirtualFxType {
use ConcreteFxInstruction::*;
match self.instruction {
This(_) => VirtualFxType::This,
ById { .. } | ByIdWithFx(_) => VirtualFxType::ById,
}
}
pub fn is_input_fx(&self) -> Option<bool> {
use ConcreteFxInstruction::*;
match &self.instruction {
ById { is_input_fx, .. } => *is_input_fx,
_ => Some(self.fx.as_ref()?.is_input_fx()),
}
}
pub fn id(&self) -> Option<Guid> {
use ConcreteFxInstruction::*;
match &self.instruction {
ById { id, .. } => *id,
_ => self.fx.as_ref()?.get_or_query_guid().ok(),
}
}
pub fn name(&self) -> Option<String> {
Some(get_fx_name(self.fx.as_ref()?))
}
pub fn index(&self) -> Option<u32> {
Some(self.fx.as_ref()?.index())
}
}
const TARGET_UNDEFINED_LABEL: &str = "<Undefined>";
const TARGET_OBJECT_IRRELEVANT_LABEL: &str = "<Irrelevant>";
pub fn get_virtual_track_label(
virtual_track: &VirtualTrack,
compartment: CompartmentKind,
context: ExtendedProcessorContext,
) -> String {
use VirtualTrack::*;
match virtual_track {
ById(_) | ByIdOrName(_, _) => {
if let Ok(t) = first_effective_track(virtual_track, compartment, context) {
get_track_label(&t)
} else {
get_non_present_virtual_track_label(virtual_track)
}
}
_ => virtual_track.to_string(),
}
}
pub fn get_virtual_fx_label(
fx_descriptor: &FxDescriptor,
compartment: CompartmentKind,
context: ExtendedProcessorContext,
) -> String {
match &fx_descriptor.fx {
VirtualFx::ChainFx { chain_fx, .. } => {
use VirtualChainFx::*;
match chain_fx {
ById(_, _) | ByIdOrIndex(_, _) => {
let optional_fx = first_effective_fx(fx_descriptor, compartment, context);
get_optional_fx_label(chain_fx, optional_fx.ok().as_ref())
}
_ => fx_descriptor.fx.to_string(),
}
}
_ => fx_descriptor.fx.to_string(),
}
}
pub fn get_track_label(track: &Track) -> String {
match track.location() {
TrackLocation::MasterTrack => MASTER_TRACK_LABEL.into(),
TrackLocation::NormalTrack(i) => {
let position = i + 1;
let name = track.name().expect("non-master track must have name");
let name = name.to_str();
if name.is_empty() {
format!("{position}. <no name>")
} else {
format!("{position}. {name}")
}
}
}
}
pub const MASTER_TRACK_LABEL: &str = "<Master track>";
fn convert_monitoring_mode_to_reaper(monitoring_mode: MonitoringMode) -> InputMonitoringMode {
match monitoring_mode {
MonitoringMode::Off => InputMonitoringMode::Off,
MonitoringMode::Normal => InputMonitoringMode::Normal,
MonitoringMode::TapeStyle => InputMonitoringMode::NotWhenPlaying,
}
}
fn convert_monitoring_mode_to_realearn(monitoring_mode: InputMonitoringMode) -> MonitoringMode {
match monitoring_mode {
InputMonitoringMode::Off => MonitoringMode::Off,
InputMonitoringMode::Normal => MonitoringMode::Normal,
InputMonitoringMode::NotWhenPlaying => MonitoringMode::TapeStyle,
InputMonitoringMode::Unknown(_) => MonitoringMode::Off,
}
}
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Debug,
Default,
Serialize,
Deserialize,
derive_more::Display,
EnumIter,
num_enum::TryFromPrimitive,
num_enum::IntoPrimitive,
)]
#[repr(usize)]
pub enum MappingModificationKind {
#[display(fmt = "Learn target")]
#[default]
LearnTarget,
#[display(fmt = "Set target to last touched")]
SetTargetToLastTouched,
}
impl MappingModificationKind {
pub fn from_modification(modification: &MappingModification) -> Self {
match modification {
MappingModification::LearnTarget(_) => Self::LearnTarget,
MappingModification::SetTargetToLastTouched(_) => Self::SetTargetToLastTouched,
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, derive_more::Display, EnumIter)]
pub enum MakeFxNonStickyMode {
#[display(fmt = "<Focused>")]
Focused,
#[display(fmt = "<Unit>")]
Unit,
#[display(fmt = "Named")]
Named,
#[display(fmt = "All named")]
AllNamed,
#[display(fmt = "At position")]
AtPosition,
}
#[derive(Copy, Clone, Eq, PartialEq, derive_more::Display, EnumIter)]
pub enum MakeTrackNonStickyMode {
#[display(fmt = "<Selected>")]
Selected,
#[display(fmt = "<All selected>")]
AllSelected,
#[display(fmt = "<Unit>")]
Unit,
#[display(fmt = "Named")]
Named,
#[display(fmt = "All named")]
AllNamed,
#[display(fmt = "At position")]
AtPosition,
#[display(fmt = "At TCP position")]
AtTcpPosition,
#[display(fmt = "At MCP position")]
AtMcpPosition,
}
impl MakeFxNonStickyMode {
pub fn build_virtual_fx(&self, fx: Option<&Fx>) -> Option<VirtualFx> {
let virtual_fx = match self {
MakeFxNonStickyMode::Focused => VirtualFx::LastFocused,
MakeFxNonStickyMode::Unit => VirtualFx::Unit,
MakeFxNonStickyMode::Named | MakeFxNonStickyMode::AllNamed => {
let fx = fx?;
VirtualFx::ChainFx {
is_input_fx: fx.is_input_fx(),
chain_fx: VirtualChainFx::ByName {
wild_match: WildMatch::new(fx.name().to_str()),
allow_multiple: *self == MakeFxNonStickyMode::AllNamed,
},
}
}
MakeFxNonStickyMode::AtPosition => {
let fx = fx?;
VirtualFx::ChainFx {
is_input_fx: fx.is_input_fx(),
chain_fx: VirtualChainFx::ByIndex(fx.index()),
}
}
};
Some(virtual_fx)
}
}
impl MakeTrackNonStickyMode {
pub fn build_virtual_track(&self, track: Option<&Track>) -> Option<VirtualTrack> {
let virtual_track = match self {
MakeTrackNonStickyMode::Selected => VirtualTrack::Selected {
allow_multiple: false,
},
MakeTrackNonStickyMode::AllSelected => VirtualTrack::Selected {
allow_multiple: true,
},
MakeTrackNonStickyMode::Unit => VirtualTrack::Unit,
MakeTrackNonStickyMode::Named | MakeTrackNonStickyMode::AllNamed => {
if let Some(name) = track?.name() {
VirtualTrack::ByName {
wild_match: WildMatch::new(name.to_str()),
allow_multiple: *self == MakeTrackNonStickyMode::AllNamed,
}
} else {
VirtualTrack::Master
}
}
MakeTrackNonStickyMode::AtPosition
| MakeTrackNonStickyMode::AtTcpPosition
| MakeTrackNonStickyMode::AtMcpPosition => {
if let Some(index) = track?.index() {
VirtualTrack::ByIndex {
index,
scope: match *self {
MakeTrackNonStickyMode::AtPosition => TrackScope::AllTracks,
MakeTrackNonStickyMode::AtTcpPosition => TrackScope::TracksVisibleInTcp,
MakeTrackNonStickyMode::AtMcpPosition => TrackScope::TracksVisibleInMcp,
_ => unreachable!(),
},
}
} else {
VirtualTrack::Master
}
}
};
Some(virtual_track)
}
}