Vendor helgoboss/helgobox (ReaLearn) as basis for custom UI fork
Stripped upstream git history; starting point for replacing the native SWELL/Win32 mapping UI with something more suited to bulk editing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
[package]
|
||||
name = "helgobox-api"
|
||||
version = "0.1.0"
|
||||
authors = ["Benjamin Klum <benjamin.klum@helgoboss.org>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
# For being able to use the API macro
|
||||
helgobox-macros.workspace = true
|
||||
reaper-low.workspace = true
|
||||
serde.workspace = true
|
||||
semver.workspace = true
|
||||
serde_json.workspace = true
|
||||
playtime-api.workspace = true
|
||||
derive_more.workspace = true
|
||||
strum.workspace = true
|
||||
num_enum.workspace = true
|
||||
enum-map.workspace = true
|
||||
enumset = { workspace = true, features = ["serde", "alloc"] }
|
||||
helgoboss-license-api.workspace = true
|
||||
serde_with.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
# For testing Lua compatibility
|
||||
mlua.workspace = true
|
||||
# For generating Luau type definitions from our Rust APIs
|
||||
syn = { workspace = true, features = ["full", "extra-traits"] }
|
||||
# For generating Luau type definitions from our Rust APIs
|
||||
darling.workspace = true
|
||||
# For generating Luau type definitions from our Rust APIs
|
||||
heck.workspace = true
|
||||
# For formatting generated Luau Type definitions
|
||||
stylua = { workspace = true, features = ["luau"] }
|
||||
anyhow.workspace = true
|
||||
|
||||
[lints.clippy]
|
||||
enum_glob_use = "deny"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,109 @@
|
||||
use crate::bindings::luau::luau_converter::Hook;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use stylua_lib::OutputVerification;
|
||||
|
||||
mod luau_converter;
|
||||
|
||||
/// The final code formatting causes error `has overflowed its stack` by default. You need to set
|
||||
/// `RUST_MIN_STACK` environment variable (e.g. `RUST_MIN_STACK=104857600`) or execute the test in
|
||||
/// release mode for this to work.
|
||||
#[test]
|
||||
pub fn export_luau() {
|
||||
struct RealearnApiExportHook;
|
||||
impl Hook for RealearnApiExportHook {
|
||||
fn translate_crate_name(&self, rust_crate_ident: &str) -> Option<&'static str> {
|
||||
match rust_crate_ident {
|
||||
"playtime_api" => Some("playtime"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
export_luau_internal(
|
||||
"realearn",
|
||||
"Contains types and helper functions for building ReaLearn presets",
|
||||
[
|
||||
"src/persistence/compartment.rs",
|
||||
"src/persistence/glue.rs",
|
||||
"src/persistence/group.rs",
|
||||
"src/persistence/mapping.rs",
|
||||
"src/persistence/parameter.rs",
|
||||
"src/persistence/source.rs",
|
||||
"src/persistence/target.rs",
|
||||
],
|
||||
&RealearnApiExportHook,
|
||||
["playtime"],
|
||||
["../playtime-api/src/persistence/mod.rs"],
|
||||
);
|
||||
struct PlaytimeApiExportHook;
|
||||
impl Hook for PlaytimeApiExportHook {
|
||||
fn include_type(&self, simple_ident: &str) -> bool {
|
||||
!matches!(
|
||||
simple_ident,
|
||||
"FlexibleMatrix"
|
||||
| "PlaytimeApiError"
|
||||
| "PlaytimePersistenceRoot"
|
||||
| "RawEvenQuantization"
|
||||
)
|
||||
}
|
||||
}
|
||||
export_luau_internal(
|
||||
"playtime",
|
||||
"Contains types and helper functions for building Playtime presets",
|
||||
["../playtime-api/src/persistence/mod.rs"],
|
||||
&PlaytimeApiExportHook,
|
||||
[],
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
fn export_luau_internal<'a>(
|
||||
name: &str,
|
||||
description: &str,
|
||||
src_files: impl IntoIterator<Item = &'a str>,
|
||||
hook: &impl Hook,
|
||||
requires: impl AsRef<[&'a str]>,
|
||||
foreign_files: impl IntoIterator<Item = &'a str>,
|
||||
) {
|
||||
let rust_codes: Vec<_> = src_files
|
||||
.into_iter()
|
||||
.map(|src_file| {
|
||||
let code = fs::read_to_string(src_file).unwrap();
|
||||
let filtered_code: Vec<_> = code
|
||||
.lines()
|
||||
.filter(|line| !line.starts_with("//!"))
|
||||
.collect();
|
||||
filtered_code.join("\n")
|
||||
})
|
||||
.collect();
|
||||
let merged_rust_code = rust_codes.join("\n\n");
|
||||
let rust_file = parse_rust_code(&merged_rust_code);
|
||||
let foreign_rust_files: Vec<_> = foreign_files
|
||||
.into_iter()
|
||||
.map(|path| {
|
||||
let code = fs::read_to_string(path).unwrap();
|
||||
parse_rust_code(&code)
|
||||
})
|
||||
.collect();
|
||||
let luau_file = luau_converter::LuauFile::new(&rust_file, hook, &foreign_rust_files);
|
||||
use std::fmt::Write;
|
||||
let mut luau_code = "--!strict\n\n--- Attention: This file is generated from Rust code! Don't modify it directly!\n\n".to_string();
|
||||
for req in requires.as_ref() {
|
||||
writeln!(&mut luau_code, "local {req} = require(\"{req}\")").unwrap();
|
||||
}
|
||||
writeln!(&mut luau_code, "\n--- {description}").unwrap();
|
||||
write!(&mut luau_code, "{luau_file}").unwrap();
|
||||
let luau_code = stylua_lib::format_code(
|
||||
&luau_code,
|
||||
Default::default(),
|
||||
None,
|
||||
OutputVerification::Full,
|
||||
)
|
||||
.unwrap();
|
||||
let dest_file = PathBuf::from(format!("../resources/api/luau/{name}.luau"));
|
||||
fs::write(&dest_file, luau_code).unwrap();
|
||||
}
|
||||
|
||||
fn parse_rust_code(code: &str) -> syn::File {
|
||||
syn::parse_file(code).expect("unable to parse Rust file")
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
mod luau;
|
||||
@@ -0,0 +1,9 @@
|
||||
pub mod persistence;
|
||||
|
||||
pub mod runtime;
|
||||
|
||||
/// Bindings are generated as result of unit tests.
|
||||
#[cfg(test)]
|
||||
mod bindings;
|
||||
|
||||
mod util;
|
||||
@@ -0,0 +1,41 @@
|
||||
use crate::persistence::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
/// Complete content of a ReaLearn compartment, including mappings, groups, parameters etc.
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
pub struct Compartment {
|
||||
/// Settings of the default group in this compartment.
|
||||
///
|
||||
/// Group fields `id` and `name` will be ignored for the default group.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub default_group: Option<Group>,
|
||||
/// All parameters in this compartment
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub parameters: Option<Vec<Parameter>>,
|
||||
/// All mapping groups in this compartment.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub groups: Option<Vec<Group>>,
|
||||
/// All mappings in this compartment.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mappings: Option<Vec<Mapping>>,
|
||||
/// Lua code that will be compiled only once and can then be reused in various Lua scripts within mappings.
|
||||
///
|
||||
/// This code should return a value. This value will then be made available to the scripts. How exactly, depends
|
||||
/// on the particular kind of script. In most cases, you want to return a table that contains functions, variables
|
||||
/// and other stuff that you want to make available in your scripts.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub common_lua: Option<String>,
|
||||
/// Arbitrarily formed data in this compartment.
|
||||
///
|
||||
/// The first level is a key-value map where a key represents a sort of namespace. E.g. data that's relevant
|
||||
/// for the application ReaLearn Companion has key "companion" and data relevant for the application Playtime has
|
||||
/// the key "playtime". Everything nested below is application-specific.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub custom_data: Option<HashMap<String, serde_json::Value>>,
|
||||
/// Can contain text notes, e.g. a helpful description of this compartment, instructions etc.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub notes: Option<String>,
|
||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
||||
pub unknown_props: Option<BTreeMap<String, serde_json::Value>>,
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct ControllerConfig {
|
||||
/// All configured controllers.
|
||||
#[serde(default)]
|
||||
pub controllers: Vec<Controller>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct Controller {
|
||||
/// ID of the controller.
|
||||
///
|
||||
/// Should be unique on a particular machine and ideally globally unique (good for potential
|
||||
/// merging scenarios).
|
||||
pub id: String,
|
||||
/// Descriptive name of the controller.
|
||||
///
|
||||
/// If one uses multiple controllers of the same kind, this should make clear which
|
||||
/// particular controller instance we are talking about.
|
||||
pub name: String,
|
||||
/// If not enabled, no auto units will be created for that controller.
|
||||
#[serde(default)]
|
||||
pub enabled: bool,
|
||||
/// Controller color.
|
||||
///
|
||||
/// Used e.g. for the control unit rectangle.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub palette_color: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub connection: Option<ControllerConnection>,
|
||||
/// Default controller preset to load whenever an auto unit with this controller is created.
|
||||
///
|
||||
/// ReaLearn has mechanisms to automatically identify and load a suitable controller preset
|
||||
/// depending on which main preset is loaded. If it has to choose between multiple
|
||||
/// candidates and no default controller preset is set, it will prefer a factory controller
|
||||
/// preset. If a default controller preset is set and it satisfies the needs of the main preset,
|
||||
/// it will use this one instead. It will also use the default controller preset if it can't
|
||||
/// automatically identify the correct one.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub default_controller_preset: Option<CompartmentPresetId>,
|
||||
/// Default main preset to load whenever an auto unit with this controller is created.
|
||||
// TODO-high-playtime-after-release The plan is to introduce an advanced mode where you don't just set a main preset but can define
|
||||
// a decision table per controller. It's a list of rules. A rule is made from conditions
|
||||
// (fixed number, typed, every condition optional, AND) and effects (fixed number, typed, optional).
|
||||
// The default_main_preset would act as fallback, as last line in the list of rules, which doesn't
|
||||
// define any additional condition.
|
||||
// Possible conditions:
|
||||
// - Playtime matrix (if at least one instance is active that has a Playtime matrix)
|
||||
// - Active pot unit?
|
||||
// Possible effects:
|
||||
// - Main preset (optional)
|
||||
// - Use auto-load in unit
|
||||
// "Use auto-load in unit" uses the already existing global FX-to-preset links to do auto-load within
|
||||
// the unit. FX-to-preset links already is very similar to a decision table. It's going to be a
|
||||
// second global decision table, but a subordinate one, which acts on a single unit (not by adding/removing
|
||||
// units). The main preset defined in the controller rule should act as fallback if none of the
|
||||
// FX-to-preset links was effective. This should be implemented by the existing auto-load. Maybe by
|
||||
// memorizing the preset that was active when "Auto-load depending on instance FX" was active.
|
||||
// FX-to-preset links are not 100% a decision table already. Because their order doesn't matter.
|
||||
// But we can turn it into one by sorting the list according to our current automatic ranking
|
||||
// (a migration step). Making it a decision table would have the nice effect that the user has
|
||||
// much influence and it's immediately clear why something happens, no implicit ranking. The list
|
||||
// of conditions can be easily extended. E.g. we could not just react on what unit FX is
|
||||
// active but also unit track and so on. In any case, we should add a "Controller" condition, so
|
||||
// that one e.g. can load a different main preset depending on which FX is focused AND which controller
|
||||
// is connected.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub default_main_preset: Option<CompartmentPresetId>,
|
||||
}
|
||||
|
||||
/// The way a controller is connected to ReaLearn.
|
||||
///
|
||||
/// Protocol-specific.
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum ControllerConnection {
|
||||
Midi(MidiControllerConnection),
|
||||
Osc(OscControllerConnection),
|
||||
}
|
||||
|
||||
/// A connection via MIDI.
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct MidiControllerConnection {
|
||||
/// The expected response to a MIDI device inquiry.
|
||||
///
|
||||
/// Example: "F0 7E 00 06 02 00 20 6B 02 00 04 02 0E 02 01 01 F7"
|
||||
///
|
||||
/// Can be used by ReaLearn to verify whether the device connected to a port is the correct one.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub identity_response: Option<String>,
|
||||
/// The MIDI input port to which this controller is usually connected on this machine.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub input_port: Option<MidiInputPort>,
|
||||
/// The MIDI output port to which this controller is usually connected on this machine.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub output_port: Option<MidiOutputPort>,
|
||||
}
|
||||
|
||||
/// A connection via OSC.
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct OscControllerConnection {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub osc_device_id: Option<OscDeviceId>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct MidiInputPort(u32);
|
||||
|
||||
impl MidiInputPort {
|
||||
pub fn new(raw: u32) -> Self {
|
||||
Self(raw)
|
||||
}
|
||||
|
||||
pub fn get(&self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct MidiOutputPort(u32);
|
||||
|
||||
impl MidiOutputPort {
|
||||
pub fn new(raw: u32) -> Self {
|
||||
Self(raw)
|
||||
}
|
||||
|
||||
pub fn get(&self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct OscDeviceId(String);
|
||||
|
||||
impl OscDeviceId {
|
||||
pub fn get(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// ID of a controller or main preset (which one depends on the context).
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct CompartmentPresetId(String);
|
||||
|
||||
impl CompartmentPresetId {
|
||||
pub fn new(raw: String) -> Self {
|
||||
Self(raw)
|
||||
}
|
||||
|
||||
pub fn get(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct Glue {
|
||||
//region Relevant for control and feedback
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub absolute_mode: Option<AbsoluteMode>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub source_interval: Option<Interval<f64>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub target_interval: Option<Interval<f64>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub reverse: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub out_of_range_behavior: Option<OutOfRangeBehavior>,
|
||||
//endregion
|
||||
|
||||
//region Relevant for control only (might change in future)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub target_value_sequence: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub round_target_value: Option<bool>,
|
||||
//endregion
|
||||
|
||||
//region Relevant for control only (guaranteed)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub wrap: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub jump_interval: Option<Interval<f64>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub takeover_mode: Option<TakeoverMode>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub control_transformation: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub step_size_interval: Option<Interval<f64>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub step_factor_interval: Option<Interval<i32>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub button_filter: Option<ButtonFilter>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub encoder_filter: Option<EncoderFilter>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub relative_mode: Option<RelativeMode>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub interaction: Option<Interaction>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub fire_mode: Option<FireMode>,
|
||||
//endregion
|
||||
|
||||
//region Relevant for feedback only (guaranteed)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feedback: Option<Feedback>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feedback_value_table: Option<FeedbackValueTable>,
|
||||
//endregion
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum FeedbackValueTable {
|
||||
FromTextToDiscrete(DiscreteFeedbackValueTableContent),
|
||||
FromTextToContinuous(ContinuousFeedbackValueTableContent),
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct DiscreteFeedbackValueTableContent {
|
||||
pub value: HashMap<String, u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct ContinuousFeedbackValueTableContent {
|
||||
pub value: HashMap<String, f64>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub enum AbsoluteMode {
|
||||
#[default]
|
||||
Normal,
|
||||
IncrementalButton,
|
||||
ToggleButton,
|
||||
MakeRelative,
|
||||
PerformanceControl,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum RelativeMode {
|
||||
Normal,
|
||||
MakeAbsolute,
|
||||
}
|
||||
|
||||
impl Default for RelativeMode {
|
||||
fn default() -> Self {
|
||||
Self::Normal
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum FireMode {
|
||||
Normal(NormalFireMode),
|
||||
AfterTimeout(AfterTimeoutFireMode),
|
||||
AfterTimeoutKeepFiring(AfterTimeoutKeepFiringFireMode),
|
||||
OnSinglePress(OnSinglePressFireMode),
|
||||
OnDoublePress,
|
||||
}
|
||||
|
||||
impl Default for FireMode {
|
||||
fn default() -> Self {
|
||||
Self::Normal(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct NormalFireMode {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub press_duration_interval: Option<Interval<u32>>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct AfterTimeoutFireMode {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub timeout: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct AfterTimeoutKeepFiringFireMode {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub timeout: Option<u32>,
|
||||
pub rate: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct OnSinglePressFireMode {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_duration: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum VirtualColor {
|
||||
Rgb(RgbColor),
|
||||
Prop(PropColor),
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RgbColor(pub u8, pub u8, pub u8);
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PropColor {
|
||||
pub prop: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum OutOfRangeBehavior {
|
||||
MinOrMax,
|
||||
Min,
|
||||
Ignore,
|
||||
}
|
||||
|
||||
impl Default for OutOfRangeBehavior {
|
||||
fn default() -> Self {
|
||||
Self::MinOrMax
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum TakeoverMode {
|
||||
Off,
|
||||
PickUpTolerant,
|
||||
PickUp,
|
||||
LongTimeNoSee,
|
||||
Parallel,
|
||||
CatchUp,
|
||||
}
|
||||
|
||||
impl Default for TakeoverMode {
|
||||
fn default() -> Self {
|
||||
Self::Off
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ButtonFilter {
|
||||
PressOnly,
|
||||
ReleaseOnly,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum EncoderFilter {
|
||||
IncrementOnly,
|
||||
DecrementOnly,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Interaction {
|
||||
SameControl,
|
||||
SameTargetValue,
|
||||
InverseControl,
|
||||
InverseTargetValue,
|
||||
InverseTargetValueOnOnly,
|
||||
InverseTargetValueOffOnly,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct FeedbackCommons {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub color: Option<VirtualColor>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub background_color: Option<VirtualColor>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum Feedback {
|
||||
Numeric(NumericFeedback),
|
||||
Text(TextFeedback),
|
||||
Dynamic(DynamicFeedback),
|
||||
}
|
||||
|
||||
impl Default for Feedback {
|
||||
fn default() -> Self {
|
||||
Self::Numeric(NumericFeedback::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct NumericFeedback {
|
||||
#[serde(flatten)]
|
||||
pub commons: FeedbackCommons,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub transformation: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct TextFeedback {
|
||||
#[serde(flatten)]
|
||||
pub commons: FeedbackCommons,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub text_expression: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct DynamicFeedback {
|
||||
#[serde(flatten)]
|
||||
pub commons: FeedbackCommons,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub script: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct Interval<T>(pub T, pub T);
|
||||
@@ -0,0 +1,22 @@
|
||||
use crate::persistence::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Group {
|
||||
/// An optional ID that you can assign to this group in order to refer
|
||||
/// to it from somewhere else.
|
||||
///
|
||||
/// This ID should be unique within all groups in the same compartment.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tags: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub control_enabled: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feedback_enabled: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub activation_condition: Option<ActivationCondition>,
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct InstanceSettings {
|
||||
pub control: InstanceControlSettings,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct InstanceControlSettings {
|
||||
/// Whether auto units will be created for all controllers that have a main preset set.
|
||||
pub global_control_enabled: bool,
|
||||
// Local overrides of controller settings.
|
||||
//
|
||||
// If global control is enabled, each override will alter the behavior of the corresponding
|
||||
// controller.
|
||||
//
|
||||
// If global control is disabled, each override which has a main preset set will enable
|
||||
// that specific controller. This way you can selectively enable controllers, either with
|
||||
// the global default preset or with your own one.
|
||||
//
|
||||
// TODO-high-playtime-after-release Controller overrides are not yet implemented because about doubts.
|
||||
// What if the user deletes the controller? Then all project/instance that have
|
||||
// an override of that controller will reference a now gone controller. Consequently, the
|
||||
// overrides will not work anymore. Ideas:
|
||||
// 1. Memorize the original controller data as part of the override and update it whenever
|
||||
// the global controller changes. Then we can use that data if the controller is gone.
|
||||
// => the project will still work but it will be disconnected.
|
||||
// 2. Don't actually use the global controller anymore once there's an override ... that's like
|
||||
// disconnecting immediately.
|
||||
// 3. GOOD SOLUTION FOR NOW Don't provide the possibility for overrides. Force user to create
|
||||
// a ReaLearn setup that is completely self-containing (the other extreme instead of something
|
||||
// in-between), including the preset content.
|
||||
// 4. Is the controller role idea better after all? I don't think so. Yes, it allows a bit
|
||||
// more instance-specific tuning of global control without depending on particular
|
||||
// controllers. However, the kind of tuning that it allows is far from exhaustive. Also,
|
||||
// it's opinionated (clip/daw roles) and has other issues (being harder to grasp and
|
||||
// awkward when it comes to all-in-one controllers that do both clip/DAW control).
|
||||
// 5. INTERESTING If all we need is the possibility to disable e.g. global DAW control for a
|
||||
// specific instance, we could simply let the main preset declare which usage role
|
||||
// it implements (e.g. the "DAW control" role) and allow the instance to switch on/off
|
||||
// roles - which will cause the main preset to be loaded or not.
|
||||
// pub controller_overrides: Vec<ControllerOverride>,
|
||||
}
|
||||
|
||||
// #[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
|
||||
// pub struct ControllerOverride {
|
||||
// /// ID of the controller which should be overridden.
|
||||
// pub controller_id: String,
|
||||
// /// If this is `None`, the controller default main preset will be used.
|
||||
// pub main_preset: Option<CompartmentPresetId>,
|
||||
// }
|
||||
@@ -0,0 +1,190 @@
|
||||
use super::*;
|
||||
use derive_more::Display;
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct Mapping {
|
||||
/// An optional ID that you can assign to this mapping in order to refer
|
||||
/// to it from somewhere else.
|
||||
///
|
||||
/// This ID should be unique within all mappings in the compartment.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tags: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub group: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub visible_in_projection: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub enabled: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub control_enabled: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feedback_enabled: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub activation_condition: Option<ActivationCondition>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub on_activate: Option<LifecycleHook>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub on_deactivate: Option<LifecycleHook>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub source: Option<Source>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub glue: Option<Glue>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub target: Option<Target>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub success_audio_feedback: Option<SuccessAudioFeedback>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub unprocessed: Option<serde_json::Map<String, serde_json::Value>>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct LifecycleHook {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub send_midi_feedback: Option<Vec<SendMidiFeedbackAction>>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum SendMidiFeedbackAction {
|
||||
Raw { message: RawMidiMessage },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum RawMidiMessage {
|
||||
HexString(String),
|
||||
ByteArray(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum SuccessAudioFeedback {
|
||||
Simple,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum ActivationCondition {
|
||||
Modifier(ModifierActivationCondition),
|
||||
Bank(BankActivationCondition),
|
||||
Eel(EelActivationCondition),
|
||||
Expression(ExpressionActivationCondition),
|
||||
TargetValue(TargetValueActivationCondition),
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct ModifierActivationCondition {
|
||||
pub modifiers: Option<Vec<ModifierState>>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ModifierState {
|
||||
pub parameter: ParamRef,
|
||||
pub on: bool,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct BankActivationCondition {
|
||||
pub parameter: ParamRef,
|
||||
pub bank_index: u32,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct EelActivationCondition {
|
||||
pub condition: String,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ExpressionActivationCondition {
|
||||
pub condition: String,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TargetValueActivationCondition {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mapping: Option<String>,
|
||||
pub condition: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ParamRef {
|
||||
Index(u32),
|
||||
Key(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum VirtualControlElementId {
|
||||
Indexed(u32),
|
||||
Named(String),
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Hash,
|
||||
Debug,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Display,
|
||||
strum::EnumIter,
|
||||
TryFromPrimitive,
|
||||
IntoPrimitive,
|
||||
)]
|
||||
#[repr(usize)]
|
||||
pub enum VirtualControlElementCharacter {
|
||||
/// A control element that can represent more than 2 states.
|
||||
#[default]
|
||||
#[serde(alias = "multi")]
|
||||
Multi,
|
||||
/// A control element that can represent at a maximum 2 states.
|
||||
#[serde(alias = "button")]
|
||||
Button,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct OscArgument {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub index: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(alias = "kind")]
|
||||
pub arg_kind: Option<OscArgKind>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub value_range: Option<Interval<f64>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum OscArgKind {
|
||||
Float,
|
||||
Double,
|
||||
Bool,
|
||||
Nil,
|
||||
Inf,
|
||||
Int,
|
||||
String,
|
||||
Blob,
|
||||
Time,
|
||||
Long,
|
||||
Char,
|
||||
Color,
|
||||
Midi,
|
||||
Array,
|
||||
}
|
||||
|
||||
impl Default for OscArgKind {
|
||||
fn default() -> Self {
|
||||
Self::Float
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
mod compartment;
|
||||
mod controller;
|
||||
mod glue;
|
||||
mod group;
|
||||
mod instance;
|
||||
mod mapping;
|
||||
mod parameter;
|
||||
mod preset;
|
||||
mod root;
|
||||
mod session;
|
||||
mod source;
|
||||
mod target;
|
||||
|
||||
pub use compartment::*;
|
||||
pub use controller::*;
|
||||
pub use glue::*;
|
||||
pub use group::*;
|
||||
pub use instance::*;
|
||||
pub use mapping::*;
|
||||
pub use parameter::*;
|
||||
pub use preset::*;
|
||||
pub use root::*;
|
||||
pub use session::*;
|
||||
pub use source::*;
|
||||
pub use target::*;
|
||||
|
||||
use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Envelope<T> {
|
||||
#[serde(default)]
|
||||
pub version: Option<Version>,
|
||||
pub value: T,
|
||||
}
|
||||
|
||||
impl<T> Envelope<T> {
|
||||
pub fn new(version: Option<Version>, value: T) -> Self {
|
||||
Self { version, value }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum ApiObject {
|
||||
/// A Playtime matrix.
|
||||
ClipMatrix(Envelope<Box<Option<playtime_api::persistence::FlexibleMatrix>>>),
|
||||
/// Main compartment.
|
||||
MainCompartment(Envelope<Box<Compartment>>),
|
||||
/// Controller compartment.
|
||||
ControllerCompartment(Envelope<Box<Compartment>>),
|
||||
/// A flat list of mappings.
|
||||
Mappings(Envelope<Vec<Mapping>>),
|
||||
/// A single mapping.
|
||||
Mapping(Envelope<Box<Mapping>>),
|
||||
}
|
||||
|
||||
impl ApiObject {
|
||||
pub fn into_mappings(self) -> Option<Envelope<Vec<Mapping>>> {
|
||||
match self {
|
||||
ApiObject::Mappings(Envelope {
|
||||
value: mappings,
|
||||
version,
|
||||
}) => Some(Envelope::new(version, mappings)),
|
||||
ApiObject::Mapping(Envelope { value: m, version }) => {
|
||||
Some(Envelope::new(version, vec![*m]))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn example_to_json() {
|
||||
let mapping = Mapping {
|
||||
id: Some("volume".to_string()),
|
||||
name: Some("Volume".to_string()),
|
||||
tags: Some(vec!["mix".to_string(), "master".to_string()]),
|
||||
group: Some("faders".to_string()),
|
||||
visible_in_projection: Some(true),
|
||||
enabled: Some(true),
|
||||
control_enabled: Some(true),
|
||||
feedback_enabled: Some(true),
|
||||
activation_condition: None,
|
||||
source: Some(Source::MidiControlChangeValue(
|
||||
MidiControlChangeValueSource {
|
||||
feedback_behavior: Some(FeedbackBehavior::Normal),
|
||||
channel: Some(0),
|
||||
controller_number: Some(64),
|
||||
character: Some(SourceCharacter::Button),
|
||||
fourteen_bit: Some(false),
|
||||
},
|
||||
)),
|
||||
glue: Some(Glue {
|
||||
source_interval: Some(Interval(0.3, 0.7)),
|
||||
..Default::default()
|
||||
}),
|
||||
target: None,
|
||||
..Default::default()
|
||||
};
|
||||
serde_json::to_string_pretty(&mapping).unwrap();
|
||||
// std::fs::write("src/schema/test/example.json", json).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_from_lua() {
|
||||
use mlua::{Lua, LuaSerdeExt};
|
||||
let lua = Lua::new();
|
||||
let value = lua.load(include_str!("test/example.lua")).eval().unwrap();
|
||||
let mapping: Mapping = lua.from_value(value).unwrap();
|
||||
serde_json::to_string_pretty(&mapping).unwrap();
|
||||
// std::fs::write("src/schema/test/example_from_lua.json", json).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
#[derive(Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct Parameter {
|
||||
pub index: u32,
|
||||
/// An optional ID that you can assign to this parameter in order to refer
|
||||
/// to it from somewhere else.
|
||||
///
|
||||
/// This ID should be unique within all parameters in the same compartment.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub value_count: Option<NonZeroU32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub value_labels: Option<Vec<String>>,
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
use crate::util::deserialize_null_default;
|
||||
use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
/// Meta data that is common to both main and controller presets.
|
||||
///
|
||||
/// Preset meta data is everything that is loaded right at startup in order to be able to
|
||||
/// display a list of preset, do certain validations etc. It doesn't include the preset
|
||||
/// content which is necessary to actually use the preset (e.g. it doesn't include the mappings).
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct CommonPresetMetaData {
|
||||
/// Display name of the preset.
|
||||
pub name: String,
|
||||
/// The ReaLearn version for which this preset was built.
|
||||
///
|
||||
/// This can effect the way the preset is loaded, e.g. it can lead to different interpretation
|
||||
/// or migration of properties. So care should be taken to set this correctly!
|
||||
///
|
||||
/// If `None`, it's assumed that it was built for a very old version (< 1.12.0-pre18) that
|
||||
/// didn't have the versioning concept yet.
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_null_default",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
#[serde(alias = "version")]
|
||||
pub realearn_version: Option<Version>,
|
||||
/// Author of the preset.
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_null_default",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub author: Option<String>,
|
||||
/// Preset description (prose).
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_null_default",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub description: Option<String>,
|
||||
/// Preset setup instructions (prose).
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_null_default",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub setup_instructions: Option<String>,
|
||||
/// Device manufacturer.
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_null_default",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub device_manufacturer: Option<String>,
|
||||
/// Original name of the device.
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_null_default",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub device_name: Option<String>,
|
||||
}
|
||||
|
||||
/// Metadata that is specific to controller presets.
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct ControllerPresetMetaData {
|
||||
/// MIDI identity compatibility pattern.
|
||||
///
|
||||
/// Will be used for auto-adding controllers and for finding the correct controller preset when calculating auto
|
||||
/// units.
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_null_default",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub midi_identity_pattern: Option<String>,
|
||||
/// Possible MIDI identity compatibility patterns.
|
||||
///
|
||||
/// Will be used for auto-adding controllers and for finding the correct controller preset when calculating auto
|
||||
/// units.
|
||||
///
|
||||
/// It should only be provided if the device in question doesn't reply to device queries or if it exposes
|
||||
/// multiple ports which all respond with the same device identity and only one of the ports is the correct one.
|
||||
/// Example: APC Key 25 mk2, which exposes a "Control" and a "Keys" port.
|
||||
///
|
||||
/// It's a list because names often differ between operating systems. ReaLearn will match any in the list.
|
||||
#[serde(default)]
|
||||
pub midi_output_port_patterns: Vec<MidiPortPattern>,
|
||||
/// Provided virtual control schemes.
|
||||
///
|
||||
/// Will be used for finding the correct controller preset when calculating auto units.
|
||||
///
|
||||
/// The order matters! It directly influences the choice of the best-suited main presets. In particular,
|
||||
/// schemes that are more specific to this particular controller (e.g. "novation/launchpad-mk3") should come first.
|
||||
/// Generic schemes (e.g. "grid") should come last. When auto-picking a main preset, matches of more specific
|
||||
/// schemes will be favored over less specific ones.
|
||||
#[serde(default)]
|
||||
pub provided_schemes: Vec<VirtualControlSchemeId>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, SerializeDisplay, DeserializeFromStr)]
|
||||
pub struct MidiPortPattern {
|
||||
pub scope: Option<MidiPortPatternScope>,
|
||||
pub name_pattern: String,
|
||||
}
|
||||
|
||||
impl MidiPortPattern {
|
||||
pub fn scope_matches(&self) -> bool {
|
||||
let Some(scope) = self.scope else {
|
||||
return true;
|
||||
};
|
||||
match scope {
|
||||
MidiPortPatternScope::Windows => cfg!(windows),
|
||||
MidiPortPatternScope::MacOs => cfg!(target_os = "macos"),
|
||||
MidiPortPatternScope::Linux => cfg!(target_os = "linux"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for MidiPortPattern {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Some((scope_string, name_pattern)) = s.split_once(':') {
|
||||
if let Ok(scope) = MidiPortPatternScope::from_str(scope_string) {
|
||||
// MIDI port pattern with scope restriction
|
||||
let pattern = Self {
|
||||
scope: Some(scope),
|
||||
name_pattern: name_pattern.to_string(),
|
||||
};
|
||||
return Ok(pattern);
|
||||
}
|
||||
}
|
||||
// MIDI port pattern without scope restriction
|
||||
let pattern = Self {
|
||||
scope: None,
|
||||
name_pattern: s.to_string(),
|
||||
};
|
||||
Ok(pattern)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MidiPortPattern {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(s) = self.scope {
|
||||
write!(f, "{s}:")?;
|
||||
}
|
||||
self.name_pattern.fmt(f)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, EnumString)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum MidiPortPatternScope {
|
||||
Windows,
|
||||
MacOs,
|
||||
Linux,
|
||||
}
|
||||
|
||||
/// Metadata that is specific to main presets.
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct MainPresetMetaData {
|
||||
/// Used virtual control schemes.
|
||||
///
|
||||
/// Will be used for finding the correct controller preset when calculating auto units.
|
||||
#[serde(default)]
|
||||
pub used_schemes: HashSet<VirtualControlSchemeId>,
|
||||
/// A set of features that a Helgobox instance needs to provide for the preset to make sense.
|
||||
///
|
||||
/// See [instance_features].
|
||||
///
|
||||
/// Will be used for determining whether an auto unit should be created for a specific instance
|
||||
/// or not. Example: If the required feature is "playtime" and a controller is configured with
|
||||
/// this main preset but the instance doesn't contain a Playtime matrix, this instance will
|
||||
/// not load the main preset.
|
||||
#[serde(default)]
|
||||
pub required_features: HashSet<String>,
|
||||
}
|
||||
|
||||
impl MainPresetMetaData {
|
||||
pub fn requires_playtime(&self) -> bool {
|
||||
self.required_features.contains(instance_features::PLAYTIME)
|
||||
}
|
||||
|
||||
/// Higher specificity means that the main preset uses a scheme provided by the controller that's more specific to
|
||||
/// that particular controller (and therefore better suited).
|
||||
///
|
||||
/// When picking the "best" main preset for a given controller preset, this is the first criteria taken into
|
||||
/// account, if there are two competing main preset candidates.
|
||||
///
|
||||
/// Given a controller preset that provides the schemes [bla, foo].
|
||||
/// If main preset A uses schemes [bla] and main preset B [foo], we want main preset A to win because
|
||||
/// "bla" comes first in the controller preset's list of provided schemes, meaning that "bla" is the
|
||||
/// more specific scheme.
|
||||
///
|
||||
/// An example where this matters in practice:
|
||||
/// - Controller preset "Launchpad Pro mk3 - Live mode" provides schemes [novation/launchpad-pro-mk3/live, grid]
|
||||
/// - Main preset "Generic grid controller - Playtime" uses schemes [grid]
|
||||
/// - Main preset "Launchpad Pro mk3 - Playtime" uses schemes [novation/launchpad-pro-mk3/live]
|
||||
///
|
||||
/// Without that rule, it could easily happen that "Generic grid controller - Playtime" will be picked. Bad!
|
||||
///
|
||||
/// Returns `None` if no scheme matches.
|
||||
pub fn calc_scheme_specificity(
|
||||
&self,
|
||||
provided_schemes: &[VirtualControlSchemeId],
|
||||
) -> Option<u8> {
|
||||
let lowest_matching_index = self
|
||||
.used_schemes
|
||||
.iter()
|
||||
.filter_map(|used_scheme| provided_schemes.iter().position(|s| s == used_scheme))
|
||||
.min()?;
|
||||
Some((provided_schemes.len() - lowest_matching_index) as u8)
|
||||
}
|
||||
|
||||
/// Higher coverage means that the main preset uses more schemes provided by the controller.
|
||||
///
|
||||
/// When picking the "best" main preset for a given controller preset, this is the second criteria taken into
|
||||
/// account, if the specificity of two main preset candidates is the same.
|
||||
pub fn calc_scheme_coverage(&self, provided_schemes: &[VirtualControlSchemeId]) -> u8 {
|
||||
self.used_schemes
|
||||
.iter()
|
||||
.filter(|used_scheme| provided_schemes.contains(used_scheme))
|
||||
.count() as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
|
||||
pub struct VirtualControlSchemeId(String);
|
||||
|
||||
impl VirtualControlSchemeId {
|
||||
pub fn get(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Known instance features.
|
||||
pub mod instance_features {
|
||||
/// Instance owns a Playtime matrix.
|
||||
pub const PLAYTIME: &str = "playtime";
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
use crate::persistence::session::Session;
|
||||
|
||||
/// Only used for JSON schema generation.
|
||||
pub struct RealearnPersistenceRoot {
|
||||
_session: Session,
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
use crate::persistence::*;
|
||||
|
||||
/// Only used for JSON schema generation at the moment.
|
||||
pub struct Session {
|
||||
_main_compartment: Option<Compartment>,
|
||||
_clip_matrix: Option<playtime_api::persistence::Matrix>,
|
||||
_mapping_snapshots: Vec<MappingSnapshot>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct MappingSnapshot {
|
||||
pub id: String,
|
||||
pub mappings: Vec<MappingInSnapshot>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct MappingInSnapshot {
|
||||
pub id: String,
|
||||
pub target_value: TargetValue,
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
use crate::persistence::{OscArgument, VirtualControlElementCharacter, VirtualControlElementId};
|
||||
use derive_more::Display;
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::EnumIter;
|
||||
|
||||
#[derive(PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum Source {
|
||||
// None
|
||||
#[default]
|
||||
None,
|
||||
// REAPER
|
||||
MidiDeviceChanges,
|
||||
RealearnInstanceStart,
|
||||
RealearnCompartmentLoaded,
|
||||
Timer(TimerSource),
|
||||
RealearnParameter(RealearnParameterSource),
|
||||
Speech,
|
||||
// MIDI
|
||||
MidiNoteVelocity(MidiNoteVelocitySource),
|
||||
MidiNoteKeyNumber(MidiNoteKeyNumberSource),
|
||||
MidiPolyphonicKeyPressureAmount(MidiPolyphonicKeyPressureAmountSource),
|
||||
MidiControlChangeValue(MidiControlChangeValueSource),
|
||||
MidiProgramChangeNumber(MidiProgramChangeNumberSource),
|
||||
MidiSpecificProgramChange(MidiSpecificProgramChangeSource),
|
||||
MidiChannelPressureAmount(MidiChannelPressureAmountSource),
|
||||
MidiPitchBendChangeValue(MidiPitchBendChangeValueSource),
|
||||
MidiParameterNumberValue(MidiParameterNumberValueSource),
|
||||
MidiClockTempo,
|
||||
MidiClockTransport(MidiClockTransportSource),
|
||||
MidiRaw(MidiRawSource),
|
||||
MidiScript(MidiScriptSource),
|
||||
MackieLcd(MackieLcdSource),
|
||||
XTouchMackieLcd(XTouchMackieLcdSource),
|
||||
MackieSevenSegmentDisplay(MackieSevenSegmentDisplaySource),
|
||||
SlKeyboardDisplay(SlKeyboardDisplaySource),
|
||||
SiniConE24Display(SiniConE24DisplaySource),
|
||||
LaunchpadProScrollingTextDisplay,
|
||||
// OSC
|
||||
Osc(OscSource),
|
||||
// Keyboard
|
||||
Key(KeySource),
|
||||
// StreamDeck
|
||||
StreamDeck(StreamDeckSource),
|
||||
// Virtual
|
||||
Virtual(VirtualSource),
|
||||
}
|
||||
|
||||
// Only makes sense for sources that support both control *and* feedback.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum FeedbackBehavior {
|
||||
Normal,
|
||||
SendFeedbackAfterControl,
|
||||
PreventEchoFeedback,
|
||||
}
|
||||
|
||||
impl Default for FeedbackBehavior {
|
||||
fn default() -> Self {
|
||||
Self::Normal
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MidiNoteVelocitySource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feedback_behavior: Option<FeedbackBehavior>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub channel: Option<u8>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub key_number: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MidiNoteKeyNumberSource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feedback_behavior: Option<FeedbackBehavior>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub channel: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MidiPolyphonicKeyPressureAmountSource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feedback_behavior: Option<FeedbackBehavior>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub channel: Option<u8>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub key_number: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MidiControlChangeValueSource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feedback_behavior: Option<FeedbackBehavior>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub channel: Option<u8>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub controller_number: Option<u8>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub character: Option<SourceCharacter>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub fourteen_bit: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MidiProgramChangeNumberSource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feedback_behavior: Option<FeedbackBehavior>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub channel: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MidiSpecificProgramChangeSource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feedback_behavior: Option<FeedbackBehavior>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub channel: Option<u8>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub program_number: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MidiChannelPressureAmountSource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feedback_behavior: Option<FeedbackBehavior>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub channel: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MidiPitchBendChangeValueSource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feedback_behavior: Option<FeedbackBehavior>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub channel: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MidiParameterNumberValueSource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feedback_behavior: Option<FeedbackBehavior>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub channel: Option<u8>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub number: Option<u16>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub fourteen_bit: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub registered: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub character: Option<SourceCharacter>,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MidiClockTransportSource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub message: Option<MidiClockTransportMessage>,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MidiRawSource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feedback_behavior: Option<FeedbackBehavior>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub pattern: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub character: Option<SourceCharacter>,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MidiScriptSource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(alias = "kind")]
|
||||
pub script_kind: Option<MidiScriptKind>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub script: Option<String>,
|
||||
}
|
||||
|
||||
/// Kind of a MIDI script
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
EnumIter,
|
||||
TryFromPrimitive,
|
||||
IntoPrimitive,
|
||||
Display,
|
||||
)]
|
||||
#[repr(usize)]
|
||||
pub enum MidiScriptKind {
|
||||
#[default]
|
||||
#[serde(alias = "eel")]
|
||||
#[display(fmt = "EEL")]
|
||||
Eel,
|
||||
#[serde(alias = "lua")]
|
||||
#[display(fmt = "Lua")]
|
||||
Lua,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
|
||||
pub enum SourceCharacter {
|
||||
#[default]
|
||||
Range,
|
||||
Button,
|
||||
// 127 = decrement; 0 = none; 1 = increment
|
||||
Relative1,
|
||||
// 63 = decrement; 64 = none; 65 = increment
|
||||
Relative2,
|
||||
// 65 = decrement; 0 = none; 1 = increment
|
||||
Relative3,
|
||||
StatefulButton,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub enum MidiClockTransportMessage {
|
||||
#[default]
|
||||
Start,
|
||||
Continue,
|
||||
Stop,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MackieLcdSource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extender_index: Option<u8>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub channel: Option<u8>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub line: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct XTouchMackieLcdSource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extender_index: Option<u8>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub channel: Option<u8>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub line: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SlKeyboardDisplaySource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub section: Option<u8>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub line: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct MackieSevenSegmentDisplaySource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub scope: Option<MackieSevenSegmentDisplayScope>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub enum MackieSevenSegmentDisplayScope {
|
||||
All,
|
||||
#[default]
|
||||
Assignment,
|
||||
Tc,
|
||||
TcHoursBars,
|
||||
TcMinutesBeats,
|
||||
TcSecondsSub,
|
||||
TcFramesTicks,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SiniConE24DisplaySource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cell_index: Option<u8>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub item_index: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct OscSource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feedback_behavior: Option<FeedbackBehavior>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub address: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub argument: Option<OscArgument>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub relative: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feedback_arguments: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RealearnParameterSource {
|
||||
pub parameter_index: u32,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TimerSource {
|
||||
pub duration: u64,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct KeySource {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub keystroke: Option<Keystroke>,
|
||||
}
|
||||
|
||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct StreamDeckSource {
|
||||
pub button_index: u32,
|
||||
#[serde(default)]
|
||||
pub button_design: StreamDeckButtonDesign,
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct StreamDeckButtonDesign {
|
||||
#[serde(default)]
|
||||
pub background: StreamDeckButtonBackground,
|
||||
#[serde(default)]
|
||||
pub foreground: StreamDeckButtonForeground,
|
||||
#[serde(default)]
|
||||
pub static_text: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum StreamDeckButtonForeground {
|
||||
#[default]
|
||||
None,
|
||||
FadingColor(StreamDeckButtonFadingColorForeground),
|
||||
FadingImage(StreamDeckButtonFadingImageForeground),
|
||||
SlidingImage(StreamDeckButtonSlidingImageForeground),
|
||||
FullBar(StreamDeckButtonFullBarForeground),
|
||||
Knob(StreamDeckButtonKnobForeground),
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum StreamDeckButtonBackground {
|
||||
Color(StreamDeckButtonColorBackground),
|
||||
Image(StreamDeckButtonImageBackground),
|
||||
}
|
||||
|
||||
impl Default for StreamDeckButtonBackground {
|
||||
fn default() -> Self {
|
||||
Self::Color(StreamDeckButtonColorBackground::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize)]
|
||||
pub struct StreamDeckButtonFadingImageForeground {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize)]
|
||||
pub struct StreamDeckButtonSlidingImageForeground {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize)]
|
||||
pub struct StreamDeckButtonFadingColorForeground {}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize)]
|
||||
pub struct StreamDeckButtonFullBarForeground {}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize)]
|
||||
pub struct StreamDeckButtonKnobForeground {}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize)]
|
||||
pub struct StreamDeckButtonImageBackground {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize)]
|
||||
pub struct StreamDeckButtonColorBackground {}
|
||||
|
||||
#[derive(Copy, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Keystroke {
|
||||
pub modifiers: u8,
|
||||
pub key: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct VirtualSource {
|
||||
pub id: VirtualControlElementId,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub character: Option<VirtualControlElementCharacter>,
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
return {
|
||||
name = "Pedal to Delay",
|
||||
source = {
|
||||
kind = "MidiControlChangeValue",
|
||||
channel = 0,
|
||||
controller_number = 64,
|
||||
character = "Button",
|
||||
fourteen_bit = false,
|
||||
},
|
||||
glue = {
|
||||
target_interval = {0, 0.53},
|
||||
jump_interval = {0, 0.53},
|
||||
step_size_interval = {0.01, 0.01},
|
||||
step_factor_interval = {1, 1},
|
||||
},
|
||||
target = {
|
||||
kind = "FxParameterValue",
|
||||
parameter = {
|
||||
address = "ById",
|
||||
fx = {
|
||||
address = "ById",
|
||||
chain = {
|
||||
address = "Track",
|
||||
},
|
||||
id = "22FD4FC0-A4DD-4E6F-BCB3-38F242B557B2",
|
||||
},
|
||||
index = 23,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum GlobalInfoEvent {
|
||||
Generic(GenericGlobalInfoEvent),
|
||||
AutoAddedController(AutoAddedControllerEvent),
|
||||
PlaytimeActivationSucceeded,
|
||||
PlaytimeActivationFailed,
|
||||
}
|
||||
|
||||
impl GlobalInfoEvent {
|
||||
pub fn generic(message: impl Into<String>) -> Self {
|
||||
Self::Generic(GenericGlobalInfoEvent {
|
||||
message: message.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct GenericGlobalInfoEvent {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct AutoAddedControllerEvent {
|
||||
pub controller_id: String,
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum InstanceInfoEvent {
|
||||
Generic(GenericInstanceInfoEvent),
|
||||
/// If attempting to MIDI-learn but the track is either not armed or the input monitoring mode
|
||||
/// is not suitable.
|
||||
MidiLearnFromFxInputButTrackNotArmed,
|
||||
MidiLearnFromFxInputButTrackHasAudioInput,
|
||||
}
|
||||
|
||||
impl InstanceInfoEvent {
|
||||
pub fn generic(message: impl Into<String>) -> Self {
|
||||
Self::Generic(GenericInstanceInfoEvent {
|
||||
message: message.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct GenericInstanceInfoEvent {
|
||||
pub message: String,
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
use helgoboss_license_api::persistence::LicenseData;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Serialize)]
|
||||
pub struct LicenseInfo {
|
||||
pub licenses: Vec<ValidatedLicense>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Serialize)]
|
||||
pub struct ValidatedLicense {
|
||||
pub license: LicenseData,
|
||||
pub valid: bool,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
mod preset;
|
||||
pub use preset::*;
|
||||
|
||||
mod global_info_event;
|
||||
pub use global_info_event::*;
|
||||
|
||||
mod instance_info_event;
|
||||
pub use instance_info_event::*;
|
||||
|
||||
mod reaper;
|
||||
pub use reaper::*;
|
||||
|
||||
mod licensing;
|
||||
pub use licensing::*;
|
||||
@@ -0,0 +1,16 @@
|
||||
use crate::persistence::{CommonPresetMetaData, ControllerPresetMetaData, MainPresetMetaData};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize)]
|
||||
pub struct MainPreset {
|
||||
pub id: String,
|
||||
pub common: CommonPresetMetaData,
|
||||
pub specific: MainPresetMetaData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize)]
|
||||
pub struct ControllerPreset {
|
||||
pub id: String,
|
||||
pub common: CommonPresetMetaData,
|
||||
pub specific: ControllerPresetMetaData,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#![allow(non_snake_case)]
|
||||
use helgobox_macros::reaper_api;
|
||||
|
||||
reaper_api![
|
||||
HelgoboxApi, HelgoboxApiPointers, HelgoboxApiSession, register_helgobox_api
|
||||
{
|
||||
/// Finds the first Helgobox instance in the given project.
|
||||
///
|
||||
/// If the given project is `null`, it will look in the current project.
|
||||
///
|
||||
/// Returns the instance ID or -1 if none exists.
|
||||
HB_FindFirstHelgoboxInstanceInProject(project: *mut reaper_low::raw::ReaProject) -> std::ffi::c_int;
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,15 @@
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
/// Makes sure that JSON `null` is treated the same as omitting a property.
|
||||
///
|
||||
/// Use as `#[serde(deserialize_with = "deserialize_null_default")]`.
|
||||
///
|
||||
/// See https://github.com/serde-rs/serde/issues/1098#issuecomment-760711617.
|
||||
pub fn deserialize_null_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
|
||||
where
|
||||
T: Default + Deserialize<'de>,
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let opt = Option::deserialize(deserializer)?;
|
||||
Ok(opt.unwrap_or_default())
|
||||
}
|
||||
Reference in New Issue
Block a user