use iced::{
Point, Rectangle, Size, mouse,
widget::{Id, text_editor},
};
use maolan_engine::{kind::Kind, message::Action};
use maolan_widgets::curve_editor::{CurveEditorMessage, CurvePoint as CurveEditorPoint};
pub use maolan_widgets::midi::{PianoControllerLane, PianoNrpnKind, PianoRpnKind};
use std::path::PathBuf;
use crate::state::AudioBackendOption;
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))]
use crate::state::AudioDeviceOption;
use std::fmt;
#[derive(Debug, Clone)]
pub enum Show {
AddTrack,
AddFolder,
TrackPluginList,
GenerateAudio,
ExportSettings,
SessionMetadata,
Preferences,
AutosaveRecovery,
UnsavedChanges,
Save,
SaveAs,
SaveTemplateAs,
Open,
About,
TrackColor { track_name: String },
ApplyTemplate { track_name: String },
BranchManager,
BranchTrackList(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum BurnBackendOption {
Cpu,
Vulkan,
}
impl BurnBackendOption {
pub const ALL: [Self; 2] = [Self::Cpu, Self::Vulkan];
pub fn as_ipc_str(self) -> &'static str {
match self {
Self::Cpu => "cpu",
Self::Vulkan => "vulkan",
}
}
}
impl fmt::Display for BurnBackendOption {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Cpu => write!(f, "CPU"),
Self::Vulkan => write!(f, "Vulkan"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum GenerateAudioModelOption {
#[serde(rename = "happy-new-year")]
HappyNewYear,
#[serde(rename = "RL")]
Rl,
}
impl GenerateAudioModelOption {
pub const ALL: [Self; 2] = [Self::HappyNewYear, Self::Rl];
}
impl fmt::Display for GenerateAudioModelOption {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::HappyNewYear => write!(f, "happy-new-year"),
Self::Rl => write!(f, "RL"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub enum SnapMode {
#[default]
NoSnap,
Clips,
Bar,
Beat,
Eighth,
Sixteenth,
ThirtySecond,
SixtyFourth,
}
impl fmt::Display for SnapMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoSnap => write!(f, "No Snap"),
Self::Clips => write!(f, "Clips"),
Self::Bar => write!(f, "Bar"),
Self::Beat => write!(f, "Beat"),
Self::Eighth => write!(f, "1/8"),
Self::Sixteenth => write!(f, "1/16"),
Self::ThirtySecond => write!(f, "1/32"),
Self::SixtyFourth => write!(f, "1/64"),
}
}
}
impl SnapMode {
pub fn interval_samples(self, samples_per_beat: f64, samples_per_bar: f64) -> f64 {
match self {
SnapMode::NoSnap => 1.0,
SnapMode::Clips => 1.0,
SnapMode::Bar => samples_per_bar.max(1.0),
SnapMode::Beat => samples_per_beat.max(1.0),
SnapMode::Eighth => (samples_per_beat / 2.0).max(1.0),
SnapMode::Sixteenth => (samples_per_beat / 4.0).max(1.0),
SnapMode::ThirtySecond => (samples_per_beat / 8.0).max(1.0),
SnapMode::SixtyFourth => (samples_per_beat / 16.0).max(1.0),
}
}
pub fn snap_sample(self, sample: f64, samples_per_beat: f64, samples_per_bar: f64) -> f64 {
if matches!(self, SnapMode::NoSnap | SnapMode::Clips) {
return sample.max(0.0);
}
let interval = self.interval_samples(samples_per_beat, samples_per_bar);
((sample.max(0.0) / interval).round() * interval).max(0.0)
}
pub fn snap_sample_drag(
self,
sample: f64,
delta_samples: f64,
samples_per_beat: f64,
samples_per_bar: f64,
) -> f64 {
if matches!(self, SnapMode::NoSnap | SnapMode::Clips) {
return sample.max(0.0);
}
let interval = self.interval_samples(samples_per_beat, samples_per_bar);
if delta_samples >= 0.0 {
((sample.max(0.0) / interval).floor() * interval).max(0.0)
} else {
((sample.max(0.0) / interval).ceil() * interval).max(0.0)
}
}
pub fn launch_quantization(self) -> maolan_engine::message::LaunchQuantization {
use maolan_engine::message::LaunchQuantization;
match self {
SnapMode::NoSnap | SnapMode::Clips | SnapMode::Bar => LaunchQuantization::Bar,
SnapMode::Beat => LaunchQuantization::Beat,
SnapMode::Eighth => LaunchQuantization::Eighth,
SnapMode::Sixteenth => LaunchQuantization::Sixteenth,
SnapMode::ThirtySecond => LaunchQuantization::ThirtySecond,
SnapMode::SixtyFourth => LaunchQuantization::SixtyFourth,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PreferencesDeviceOption {
pub id: String,
pub label: String,
}
impl fmt::Display for PreferencesDeviceOption {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.label)
}
}
#[derive(Debug, Clone)]
pub enum ModulatorChange {
Name(String),
Shape(crate::state::ModulatorShape),
Rate(crate::state::ModulatorRate),
Phase(f32),
Enabled(bool),
Targets(Vec<crate::state::ModulatorTarget>),
}
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum TrackAutomationTarget {
Volume,
Balance,
MidiCc {
channel: u8,
cc: u8,
},
Lv2Parameter {
instance_id: usize,
index: u32,
min: f32,
max: f32,
},
Vst3Parameter {
instance_id: usize,
param_id: u32,
},
ClapParameter {
instance_id: usize,
param_id: u32,
min: f64,
max: f64,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum TrackAutomationMode {
Read,
Touch,
Latch,
Write,
}
impl fmt::Display for TrackAutomationMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Read => write!(f, "Read"),
Self::Touch => write!(f, "Touch"),
Self::Latch => write!(f, "Latch"),
Self::Write => write!(f, "Write"),
}
}
}
impl From<maolan_engine::message::TrackAutomationMode> for TrackAutomationMode {
fn from(mode: maolan_engine::message::TrackAutomationMode) -> Self {
match mode {
maolan_engine::message::TrackAutomationMode::Read => Self::Read,
maolan_engine::message::TrackAutomationMode::Touch => Self::Touch,
maolan_engine::message::TrackAutomationMode::Latch => Self::Latch,
maolan_engine::message::TrackAutomationMode::Write => Self::Write,
}
}
}
impl From<TrackAutomationMode> for maolan_engine::message::TrackAutomationMode {
fn from(mode: TrackAutomationMode) -> Self {
match mode {
TrackAutomationMode::Read => Self::Read,
TrackAutomationMode::Touch => Self::Touch,
TrackAutomationMode::Latch => Self::Latch,
TrackAutomationMode::Write => Self::Write,
}
}
}
impl fmt::Display for TrackAutomationTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Volume => write!(f, "Volume"),
Self::Balance => write!(f, "Balance"),
Self::MidiCc { channel, cc } => {
let name = crate::midi::standard_cc_name(*cc);
if name.is_empty() {
write!(f, "MIDI CC {} (ch {})", cc, channel + 1)
} else {
write!(f, "MIDI CC {} (ch {}) - {}", cc, channel + 1, name)
}
}
Self::Lv2Parameter {
instance_id, index, ..
} => write!(f, "LV2 {}:{}", instance_id, index),
Self::Vst3Parameter {
instance_id,
param_id,
} => write!(f, "VST3 {}:{}", instance_id, param_id),
Self::ClapParameter {
instance_id,
param_id,
..
} => write!(f, "CLAP {}:{}", instance_id, param_id),
}
}
}
impl TrackAutomationTarget {
pub fn is_modulatable(self) -> bool {
true
}
pub fn default_range(self) -> (f32, f32) {
match self {
Self::Volume => (-90.0, 20.0),
Self::Balance => (-1.0, 1.0),
Self::MidiCc { .. } => (0.0, 127.0),
Self::Vst3Parameter { .. } => (0.0, 1.0),
Self::ClapParameter { min, max, .. } => (min as f32, max as f32),
Self::Lv2Parameter { min, max, .. } => (min, max),
}
}
pub fn same_as(&self, other: &Self) -> bool {
match (self, other) {
(Self::Volume, Self::Volume) => true,
(Self::Balance, Self::Balance) => true,
(Self::MidiCc { channel: a, cc: b }, Self::MidiCc { channel: c, cc: d }) => {
a == c && b == d
}
(
Self::Vst3Parameter {
instance_id: a,
param_id: b,
},
Self::Vst3Parameter {
instance_id: c,
param_id: d,
},
) => a == c && b == d,
(
Self::ClapParameter {
instance_id: a,
param_id: b,
..
},
Self::ClapParameter {
instance_id: c,
param_id: d,
..
},
) => a == c && b == d,
(
Self::Lv2Parameter {
instance_id: a,
index: b,
..
},
Self::Lv2Parameter {
instance_id: c,
index: d,
..
},
) => a == c && b == d,
_ => false,
}
}
}
impl CurveEditorMessage for Message {
type Context = (String, TrackAutomationTarget);
fn draw_line(context: Self::Context, points: Vec<CurveEditorPoint>) -> Self {
let (track_name, target) = context;
Message::TrackAutomationLaneInsertPoints {
track_name,
target,
points: points
.into_iter()
.map(|point| crate::state::TrackAutomationPoint {
sample: point.sample,
value: point.value,
})
.collect(),
}
}
fn delete_point(context: Self::Context, sample: usize) -> Self {
let (track_name, target) = context;
Message::TrackAutomationLaneDeletePoint {
track_name,
target,
sample,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MidiLaneChannelSelection {
Omni,
Channel(u8),
}
impl MidiLaneChannelSelection {
pub const ALL: [Self; 17] = [
Self::Omni,
Self::Channel(0),
Self::Channel(1),
Self::Channel(2),
Self::Channel(3),
Self::Channel(4),
Self::Channel(5),
Self::Channel(6),
Self::Channel(7),
Self::Channel(8),
Self::Channel(9),
Self::Channel(10),
Self::Channel(11),
Self::Channel(12),
Self::Channel(13),
Self::Channel(14),
Self::Channel(15),
];
pub fn from_engine(channel: Option<u8>) -> Self {
match channel {
Some(channel) => Self::Channel(channel.min(15)),
None => Self::Omni,
}
}
pub fn to_engine(self) -> Option<u8> {
match self {
Self::Omni => None,
Self::Channel(channel) => Some(channel.min(15)),
}
}
}
impl fmt::Display for MidiLaneChannelSelection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Omni => write!(f, "Omni"),
Self::Channel(channel) => write!(f, "Ch {}", channel + 1),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PianoVelocityKind {
NoteVelocity,
ReleaseVelocity,
}
impl fmt::Display for PianoVelocityKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoteVelocity => write!(f, "Note Velocity"),
Self::ReleaseVelocity => write!(f, "Release Velocity"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PianoScaleRoot {
C,
CSharp,
D,
DSharp,
E,
F,
FSharp,
G,
GSharp,
A,
ASharp,
B,
}
impl PianoScaleRoot {
pub fn semitone(self) -> u8 {
match self {
PianoScaleRoot::C => 0,
PianoScaleRoot::CSharp => 1,
PianoScaleRoot::D => 2,
PianoScaleRoot::DSharp => 3,
PianoScaleRoot::E => 4,
PianoScaleRoot::F => 5,
PianoScaleRoot::FSharp => 6,
PianoScaleRoot::G => 7,
PianoScaleRoot::GSharp => 8,
PianoScaleRoot::A => 9,
PianoScaleRoot::ASharp => 10,
PianoScaleRoot::B => 11,
}
}
}
impl fmt::Display for PianoScaleRoot {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PianoScaleRoot::C => write!(f, "C"),
PianoScaleRoot::CSharp => write!(f, "C#"),
PianoScaleRoot::D => write!(f, "D"),
PianoScaleRoot::DSharp => write!(f, "D#"),
PianoScaleRoot::E => write!(f, "E"),
PianoScaleRoot::F => write!(f, "F"),
PianoScaleRoot::FSharp => write!(f, "F#"),
PianoScaleRoot::G => write!(f, "G"),
PianoScaleRoot::GSharp => write!(f, "G#"),
PianoScaleRoot::A => write!(f, "A"),
PianoScaleRoot::ASharp => write!(f, "A#"),
PianoScaleRoot::B => write!(f, "B"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PianoChordKind {
MajorTriad,
MinorTriad,
Dominant7,
Major7,
Minor7,
}
impl PianoChordKind {
pub fn intervals(self) -> &'static [u8] {
match self {
PianoChordKind::MajorTriad => &[4, 7],
PianoChordKind::MinorTriad => &[3, 7],
PianoChordKind::Dominant7 => &[4, 7, 10],
PianoChordKind::Major7 => &[4, 7, 11],
PianoChordKind::Minor7 => &[3, 7, 10],
}
}
}
impl fmt::Display for PianoChordKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PianoChordKind::MajorTriad => write!(f, "Maj"),
PianoChordKind::MinorTriad => write!(f, "Min"),
PianoChordKind::Dominant7 => write!(f, "7"),
PianoChordKind::Major7 => write!(f, "Maj7"),
PianoChordKind::Minor7 => write!(f, "Min7"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExportBitDepth {
Int16,
Int24,
Int32,
Float32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExportFormat {
Wav,
Mp3,
Ogg,
Flac,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExportMp3Mode {
Cbr,
Vbr,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExportNormalizeMode {
Peak,
Loudness,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExportRenderMode {
Mixdown,
StemsPostFader,
StemsPreFader,
}
impl fmt::Display for ExportNormalizeMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExportNormalizeMode::Peak => write!(f, "Peak"),
ExportNormalizeMode::Loudness => write!(f, "Loudness"),
}
}
}
impl fmt::Display for ExportFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExportFormat::Wav => write!(f, "WAV"),
ExportFormat::Mp3 => write!(f, "MP3"),
ExportFormat::Ogg => write!(f, "OGG"),
ExportFormat::Flac => write!(f, "FLAC"),
}
}
}
impl fmt::Display for ExportMp3Mode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExportMp3Mode::Cbr => write!(f, "CBR"),
ExportMp3Mode::Vbr => write!(f, "VBR"),
}
}
}
impl fmt::Display for ExportRenderMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExportRenderMode::Mixdown => write!(f, "Mixdown"),
ExportRenderMode::StemsPostFader => write!(f, "Stems (Post-Fader)"),
ExportRenderMode::StemsPreFader => write!(f, "Stems (Pre-Fader)"),
}
}
}
impl fmt::Display for ExportBitDepth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExportBitDepth::Int16 => write!(f, "16-bit PCM"),
ExportBitDepth::Int24 => write!(f, "24-bit PCM"),
ExportBitDepth::Int32 => write!(f, "32-bit PCM"),
ExportBitDepth::Float32 => write!(f, "32-bit float"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExportDither {
None,
Rectangular,
Triangular,
}
impl fmt::Display for ExportDither {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExportDither::None => write!(f, "None"),
ExportDither::Rectangular => write!(f, "Rectangular"),
ExportDither::Triangular => write!(f, "Triangular"),
}
}
}
#[derive(Debug, Clone)]
pub enum AddTrack {
Name(String),
Count(usize),
AudioIns(usize),
AudioOuts(usize),
MIDIIns(usize),
MIDIOuts(usize),
TemplateSelected(String),
IsFolder(bool),
Submit,
}
#[derive(Debug, Clone)]
pub enum ApplyTemplate {
TemplateSelected(String),
Submit,
}
#[derive(Debug, Clone)]
pub struct DraggedClip {
pub kind: Kind,
pub index: usize,
pub track_index: String,
pub start: Point,
pub end: Point,
pub copy: bool,
}
impl DraggedClip {
pub fn new(kind: Kind, index: usize, track_index: String) -> Self {
Self {
kind,
index,
track_index: track_index.clone(),
start: Point::new(0.0, 0.0),
end: Point::new(0.0, 0.0),
copy: false,
}
}
}
#[derive(Debug, Clone)]
pub struct ClipStretchRequest {
pub track_idx: String,
pub clip_idx: usize,
pub clip_name: String,
pub start: usize,
pub original_start: usize,
pub length: usize,
pub offset: usize,
pub input_channel: usize,
pub muted: bool,
pub fade_enabled: bool,
pub fade_in_samples: usize,
pub fade_out_samples: usize,
pub stretch_ratio: f32,
}
#[derive(Debug, Clone)]
pub struct ClipPitchCorrectionRequest {
pub track_idx: String,
pub clip_idx: usize,
pub clip_name: String,
pub start: usize,
pub source_name: String,
pub source_offset: usize,
pub source_length: usize,
pub frame_likeness: f32,
}
#[derive(Debug, Clone)]
pub struct PreparedFreezeClip {
pub clip_index: usize,
pub preview_name: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MidiEditorViewMode {
#[default]
PianoRoll,
DrumGrid,
}
impl fmt::Display for MidiEditorViewMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PianoRoll => write!(f, "Piano"),
Self::DrumGrid => write!(f, "Drum"),
}
}
}
#[derive(Debug, Clone)]
pub enum Message {
None,
Request(Action),
Response(Result<Action, String>),
Show(Show),
Cancel,
AddTrack(AddTrack),
ApplyTemplate(ApplyTemplate),
SelectTrack(String),
SelectTrackFromMixer(String),
TrackFreezeToggle {
track_name: String,
},
TrackFreezePrepared {
track_name: String,
prepared_clips: Vec<PreparedFreezeClip>,
result: Result<(), String>,
},
TrackFreezeFlatten {
track_name: String,
},
TrackToggleFolder {
track_name: String,
},
TrackSetFolder {
track_name: String,
is_folder: bool,
},
TrackSetParent {
track_name: String,
parent_name: Option<String>,
},
TrackAddReturn(String),
TrackAddSend(String),
TrackMidiLearnArm {
track_name: String,
target: maolan_engine::message::TrackMidiLearnTarget,
},
TrackMidiLearnClear {
track_name: String,
target: maolan_engine::message::TrackMidiLearnTarget,
},
GlobalMidiLearnArm {
target: maolan_engine::message::GlobalMidiLearnTarget,
},
GlobalMidiLearnClear {
target: maolan_engine::message::GlobalMidiLearnTarget,
},
SessionMidiLearnArm {
target: maolan_engine::message::SessionMidiLearnTarget,
},
SessionMidiLearnClear {
target: maolan_engine::message::SessionMidiLearnTarget,
},
TrackAutomationCycleMode {
track_name: String,
},
TrackAutomationToggleLane {
track_name: String,
target: TrackAutomationTarget,
},
TrackAutomationAddPluginLanes {
track_name: String,
plugin_id: String,
format: String,
},
TrackAutomationLaneInsertPoints {
track_name: String,
target: TrackAutomationTarget,
points: Vec<crate::state::TrackAutomationPoint>,
},
TrackAutomationLaneDeletePoint {
track_name: String,
target: TrackAutomationTarget,
sample: usize,
},
MarkerNameInput(String),
MarkerNameConfirm,
MarkerNameCancel,
MarkerLaneCreate {
sample: usize,
},
SelectClip {
track_idx: String,
clip_idx: usize,
kind: Kind,
},
RemoveSelectedTracks,
RemoveSelected,
Remove,
DeselectClips,
DeselectAll,
ConnectionViewSelectTrack(String),
ConnectionViewSelectConnection(usize),
ConnectionViewDeselectAll,
ConnectionPositionsChanged,
SaveFolderSelected(Option<PathBuf>),
OpenFolderSelected(Option<PathBuf>),
LoadSessionPath(PathBuf),
RecoverAutosaveSnapshot,
RecoverAutosaveIgnore,
OpenExporter,
GenerateAudioModelSelected(GenerateAudioModelOption),
GenerateAudioPromptAction(text_editor::Action),
GenerateAudioTagsInput(String),
GenerateAudioBackendSelected(BurnBackendOption),
GenerateAudioCfgScaleInput(String),
GenerateAudioStepsInput(usize),
GenerateAudioSecondsTotalInput(usize),
GenerateAudioSubmit,
GenerateAudioProgress {
progress: f32,
operation: Option<String>,
},
GenerateAudioCancel,
GenerateAudioFinished(Result<String, String>),
MidiLearnMappingsPanelToggle,
MidiLearnMappingsReportRequest,
MidiLearnMappingsExportRequest,
MidiLearnMappingsImportRequest,
MidiLearnMappingsClearAllRequest,
ExportSampleRateSelected(u32),
ExportFormatWavToggled(bool),
ExportFormatMp3Toggled(bool),
ExportFormatOggToggled(bool),
ExportFormatFlacToggled(bool),
ExportMp3ModeSelected(ExportMp3Mode),
ExportMp3BitrateSelected(u16),
ExportOggQualityInput(String),
ExportRenderModeSelected(ExportRenderMode),
ExportHwOutPortToggled(usize, bool),
ExportRealtimeFallbackToggled(bool),
ExportBitDepthSelected(ExportBitDepth),
ExportDitherSelected(ExportDither),
ExportNormalizeToggled(bool),
ExportNormalizeModeSelected(ExportNormalizeMode),
ExportNormalizeDbfsInput(String),
ExportNormalizeLufsInput(String),
ExportNormalizeDbtpInput(String),
ExportNormalizeLimiterToggled(bool),
ExportMasterLimiterToggled(bool),
ExportMasterLimiterCeilingInput(String),
ExportSettingsConfirm,
ExportFileSelected(Option<PathBuf>),
ExportProgress {
progress: f32,
operation: Option<String>,
},
PreferencesSampleRateSelected(u32),
PreferencesSnapModeSelected(SnapMode),
PreferencesMidiSnapModeSelected(SnapMode),
PreferencesBitDepthSelected(usize),
PreferencesOscEnabledToggled(bool),
PreferencesOutputDeviceSelected(PreferencesDeviceOption),
PreferencesInputDeviceSelected(PreferencesDeviceOption),
PreferencesSave,
SessionMetadataAuthorInput(String),
SessionMetadataAlbumInput(String),
SessionMetadataYearInput(String),
SessionMetadataTrackNumberInput(String),
SessionMetadataGenreInput(String),
SessionMetadataSave,
TrackResizeStart(String),
TrackResizeHover(String, bool),
TrackLaneResizeStart {
track_name: String,
divider: usize,
},
TrackLaneDividerReset {
track_name: String,
divider: usize,
},
ShortcutsHint(Option<String>),
ClipResizeHandleHover {
kind: Kind,
track_idx: String,
clip_idx: usize,
is_right_side: bool,
hovered: bool,
},
TracksResizeStart,
MixerResizeStart,
MixerLevelEditStart(String),
MixerLevelEditInput(String),
MixerLevelEditCommit,
ClipResizeStart(Kind, String, usize, bool),
FadeResizeStart {
kind: Kind,
track_idx: String,
clip_idx: usize,
is_fade_out: bool,
},
ClipDrag(DraggedClip),
HandleClipZones(Vec<(Id, Rectangle)>),
HandleClipPreviewZones(Vec<(Id, Rectangle)>),
TrackDrag {
index: usize,
position: Point,
},
TrackDropped(Point, Rectangle),
TrackContextMenuHover {
track_name: String,
position: Point,
},
TrackContextMenuSubmenuOpen(crate::state::TrackContextSubmenu),
TrackContextMenuSubmenuClose,
HandleTrackZones(Vec<(Id, Rectangle)>),
MouseMoved(mouse::Event),
EditorMouseMoved(Point),
EditorScrollXChanged(f32),
EditorScrollYChanged(f32),
MixerScrollXChanged(f32),
MousePressed(mouse::Button),
MouseReleased,
ShiftPressed,
CtrlPressed,
ShiftReleased,
CtrlReleased,
SelectAll,
WindowResized(Size),
WindowCloseRequested,
EscapePressed,
ConfirmCloseSave,
ConfirmCloseDiscard,
ConfirmCloseCancel,
PlaybackTick,
MeterPollTick,
AutosaveSnapshotTick,
RecordingPreviewTick,
RecordingPreviewPeaksTick,
ToggleTransport,
ZoomSliderChanged(f32),
PianoZoomXChanged(f32),
PianoZoomYChanged(f32),
PianoScrollChanged {
x: f32,
y: f32,
},
PianoScrollXChanged(f32),
PianoScrollYChanged(f32),
PianoSysExScrollYChanged(f32),
TracksResizeHover(bool),
MixerResizeHover(bool),
OpenFileImporter,
DeleteUnusedSessionMediaFiles,
CollectToSession,
ImportFilesSelected(Option<Vec<std::path::PathBuf>>),
ImportProgress {
file_index: usize,
total_files: usize,
file_progress: f32,
filename: String,
operation: Option<String>,
},
ImportFinished {
total_files: usize,
failed_files: Vec<String>,
},
ImportPreparedAudioPeaks {
track_name: String,
clip_name: String,
start: usize,
length: usize,
offset: usize,
peaks: crate::state::ClipPeaks,
},
ClipOpenPitchCorrectionProgress {
clip_name: String,
progress: f32,
operation: Option<String>,
},
TrackTemplatesLoaded(Vec<String>, Vec<String>),
#[cfg(any(
target_os = "linux",
target_os = "windows",
target_os = "freebsd",
target_os = "openbsd"
))]
PreferencesDevicesLoaded {
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))]
output_devices: Vec<AudioDeviceOption>,
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))]
input_devices: Vec<AudioDeviceOption>,
#[cfg(target_os = "windows")]
output_devices: Vec<String>,
#[cfg(target_os = "windows")]
input_devices: Vec<String>,
},
DrainAudioPeakUpdates,
TransportPlay,
TransportPause,
TransportStop,
TransportPanic,
JumpToStart,
JumpToEnd,
TransportRecordToggle,
ToggleLoop,
SetLoopRange(Option<(usize, usize)>),
TogglePunch,
SetPunchRange(Option<(usize, usize)>),
SetClipSnapTargets(Vec<crate::state::ClipId>),
ToggleMetronome,
TempoAdjust(f32),
TempoPointAdd(usize),
TempoPointSelect {
sample: usize,
additive: bool,
},
TempoPointsMove {
from_samples: Vec<usize>,
to_samples: Vec<usize>,
},
TempoSelectionDuplicate,
TempoSelectionResetToPrevious,
TempoSelectionDelete,
TimeSignaturePointAdd(usize),
TimeSignaturePointSelect {
sample: usize,
additive: bool,
},
TimeSignaturePointsMove {
from_samples: Vec<usize>,
to_samples: Vec<usize>,
},
TimeSignatureSelectionDuplicate,
TimeSignatureSelectionResetToPrevious,
TimeSignatureSelectionDelete,
ClearTimingPointSelection,
TimeSignatureNumeratorAdjust(i8),
TimeSignatureDenominatorAdjust(i8),
TempoInputChanged(String),
TempoInputCommit,
TapTempo,
TimeSignatureNumeratorInputChanged(String),
TimeSignatureDenominatorInputChanged(String),
TimeSignatureInputCommit,
SetSnapMode(SnapMode),
SetMidiSnapMode(SnapMode),
ToggleStepRecording,
StepRecordNote {
device: String,
channel: u8,
pitch: u8,
velocity: u8,
},
RecordFolderSelected(Option<PathBuf>),
SendMessageFinished(Result<(), String>),
Workspace,
Connections,
X32,
Session,
ToggleMixerVisibility,
ToggleTracksVisibility,
ToggleEditorVisibility,
ToggleToolbarVisibility,
ToggleLogVisibility,
ToggleShortcutsPane,
ToggleModulatorsPane,
ToggleClipsPane,
ToggleSelectedPluginBypass,
ModulatorAdd,
ModulatorRemove(usize),
ModulatorSelect(Option<usize>),
ModulatorToggleTarget {
id: usize,
target: crate::state::ModulatorTarget,
},
ModulatorToggleSelectedTarget {
target: crate::state::ModulatorTarget,
},
ModulatorUpdate {
id: usize,
change: ModulatorChange,
},
ModulatorTargetShow {
modulator_id: usize,
track_name: String,
target: TrackAutomationTarget,
},
ModulatorTargetMinInput(String),
ModulatorTargetMaxInput(String),
ModulatorTargetConfirm,
ModulatorTargetCancel,
ModulatorTargetRemove {
modulator_id: usize,
track_name: String,
target: TrackAutomationTarget,
},
ToggleCutIndicator,
HwMixer(mixosc::app::Message),
LogViewAction(text_editor::Action),
OpenTrackPlugins(String),
OpenFolderConnections(String),
SessionViewConnectionsOpen(String),
SessionViewConnectionsClose,
EditorConnectionsOpen(String),
EditorConnectionsClose,
OpenJackConnections,
CloseJackConnections,
JackPortClick {
port: String,
is_output: bool,
},
JackDisconnect {
source: String,
destination: String,
},
OpenHwPorts {
input: bool,
},
OpenClipPlugins {
track_idx: String,
clip_idx: usize,
},
ClipConnectPlugin {
from_node: maolan_engine::message::PluginGraphNode,
from_port: usize,
to_node: maolan_engine::message::PluginGraphNode,
to_port: usize,
kind: maolan_engine::kind::Kind,
},
ClipConnectPlugins(Vec<maolan_engine::message::PluginGraphConnection>),
OpenMidiPiano {
track_idx: String,
clip_idx: usize,
},
MidiClipPreviewLoaded {
track_idx: String,
clip_idx: usize,
clip_name: String,
notes: Vec<crate::state::PianoNote>,
},
PianoKeyPressed(u8),
PianoKeyReleased(u8),
PianoNoteClick {
note_index: usize,
position: Point,
},
PianoNotesDrag {
position: Point,
},
PianoNotesEndDrag,
PitchCorrectionPointClick {
point_index: usize,
position: Point,
},
PitchCorrectionSnapToNearest {
point_index: usize,
},
PitchCorrectionPointsDrag {
position: Point,
},
PitchCorrectionPointsEndDrag,
PitchCorrectionSelectRectStart {
position: Point,
},
PitchCorrectionSelectRectDrag {
position: Point,
},
PitchCorrectionSelectRectEnd,
PitchCorrectionClearSelection,
PitchCorrectionFrameLikenessChanged(f32),
PitchCorrectionInertiaChanged(u16),
PitchCorrectionFormantCompensationChanged(bool),
PianoNoteResizeStart {
note_index: usize,
position: Point,
resize_start: bool,
},
PianoNoteResizeDrag {
position: Point,
},
PianoNoteResizeEnd,
PianoSelectRectStart {
position: Point,
},
PianoSelectRectDrag {
position: Point,
},
PianoSelectRectEnd,
PianoCreateNoteStart {
position: Point,
},
PianoCreateNoteDrag {
position: Point,
},
PianoCreateNoteEnd,
PianoAdjustVelocity {
note_index: usize,
delta: i8,
},
PianoSetVelocity {
note_index: usize,
velocity: u8,
},
PianoAdjustController {
controller_index: usize,
delta: i8,
},
PianoSetControllerValue {
controller_index: usize,
value: u8,
},
PianoInsertControllers {
controllers: Vec<PianoControllerEditData>,
},
PianoSysExSelect(Option<usize>),
PianoSysExOpenEditor(Option<usize>),
PianoSysExCloseEditor,
PianoSysExHexInput(String),
PianoSysExAdd,
PianoSysExUpdate,
PianoSysExDelete,
PianoSysExMove {
index: usize,
sample: usize,
},
DrumNoteSelected(usize),
DrumNoteCreate {
start_sample: usize,
pitch: u8,
},
DrumNoteDelete(usize),
DrumNoteMove {
note_index: usize,
delta_samples: i64,
},
DrumSelectRectStart {
position: iced::Point,
},
DrumSelectRectDrag {
position: iced::Point,
},
DrumSelectRectEnd,
PianoControllerLaneSelected(PianoControllerLane),
MidiEditorViewModeSelected(MidiEditorViewMode),
PianoControllerKindSelected(u8),
TrackMidiLaneChannelSelected {
track_name: String,
lane: usize,
channel: MidiLaneChannelSelection,
},
TrackSetupToggle(String),
TrackMidiSetupChannelSelected {
track_name: String,
channel: MidiLaneChannelSelection,
},
PianoVelocityKindSelected(PianoVelocityKind),
PianoRpnKindSelected(PianoRpnKind),
PianoNrpnKindSelected(PianoNrpnKind),
PianoScaleSelectedNotes,
PianoScaleRootSelected(PianoScaleRoot),
PianoScaleMinorToggled(bool),
PianoChordSelectedNotes,
PianoChordKindSelected(PianoChordKind),
PianoLegatoSelectedNotes,
PianoVelocityShapeSelectedNotes,
PianoVelocityShapeAmountChanged(f32),
PianoQuantizeSelectedNotes,
PianoHumanizeSelectedNotes,
PianoGrooveSelectedNotes,
PianoHumanizeTimeAmountChanged(f32),
PianoHumanizeVelocityAmountChanged(f32),
PianoGrooveAmountChanged(f32),
PianoDeleteSelectedNotes,
PianoDeleteNotes {
note_indices: Vec<usize>,
},
PianoDeleteControllers {
controller_indices: Vec<usize>,
},
#[cfg(all(unix, not(target_os = "macos")))]
RefreshLv2Plugins,
#[cfg(all(unix, not(target_os = "macos")))]
SelectLv2Plugin(String),
#[cfg(all(unix, not(target_os = "macos")))]
OpenLv2PluginUi {
track_name: String,
clip_idx: Option<usize>,
instance_id: usize,
},
RefreshVst3Plugins,
SelectVst3Plugin(String),
RefreshClapPlugins,
SelectClapPlugin(String),
FilterPluginList(String),
LoadSelectedPlugins,
PluginGraphControllerMenuOpen {
track_name: String,
instance_id: usize,
position: iced::Point,
},
PluginGraphControllerMenuClose,
PluginGraphControllerMenuHover(Option<usize>),
PluginGraphShowController {
track_name: String,
instance_id: usize,
param_id: u32,
name: String,
value: f32,
min: f32,
max: f32,
},
PluginGraphHideController {
track_name: String,
instance_id: usize,
param_id: u32,
},
RequestBatch(Vec<maolan_engine::message::Action>),
ShowClapPluginUi {
track_name: String,
clip_idx: Option<usize>,
instance_id: usize,
plugin_id: String,
},
OpenVst3PluginUi {
track_name: String,
clip_idx: Option<usize>,
instance_id: usize,
plugin_id: String,
},
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))]
HWSelected(AudioDeviceOption),
#[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd")))]
HWSelected(String),
#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "openbsd"))]
HWInputSelected(AudioDeviceOption),
#[cfg(target_os = "windows")]
HWInputSelected(String),
HWBackendSelected(AudioBackendOption),
HWExclusiveToggled(bool),
#[cfg(any(unix, target_os = "windows"))]
HWBitsChanged(usize),
HWSampleRateChanged(i32),
HWPeriodFramesChanged(usize),
HWNPeriodsChanged(usize),
HWSyncModeToggled(bool),
ClipRenameShow {
track_idx: String,
clip_idx: usize,
kind: Kind,
},
ClipRenameInput(String),
ClipRenameConfirm,
ClipRenameCancel,
ClipToggleFade {
track_idx: String,
clip_idx: usize,
kind: Kind,
},
ClipSetMuted {
track_idx: String,
clip_idx: usize,
kind: Kind,
muted: bool,
},
ClipAssignToSessionSlot {
track_idx: String,
clip_idx: usize,
kind: Kind,
},
GroupSelectedClips,
UngroupClip {
track_idx: String,
clip_idx: usize,
kind: Kind,
},
ClipStretchFinished {
request: ClipStretchRequest,
result: Result<(String, usize), String>,
},
ClipOpenPitchCorrection {
track_idx: String,
clip_idx: usize,
},
ClipExportPitchCorrectionMidi {
track_idx: String,
clip_idx: usize,
},
ClipPitchCorrectionMidiFileSelected {
track_idx: String,
clip_idx: usize,
path: Option<PathBuf>,
},
ClipOpenPitchCorrectionFinished {
request: ClipPitchCorrectionRequest,
result: Result<crate::state::PitchCorrectionData, String>,
},
TrackRenameShow(String),
TrackRenameInput(String),
TrackRenameConfirm,
TrackRenameCancel,
TrackTemplateSaveShow(String),
TrackTemplateSaveInput(String),
TrackTemplateSaveConfirm,
TrackTemplateSaveCancel,
TrackContextMenuToggle(String),
TemplateSaveInput(String),
TemplateSaveConfirm,
TemplateSaveCancel,
NewSession,
NewFromTemplate(String),
BranchInput(String),
BranchCreate(String),
BranchSwitch(String),
BranchMerge(String),
BranchResetHard(String),
BranchCopyTrack {
branch: String,
track_name: String,
},
AddTrackFromTemplate {
name: String,
template: String,
audio_ins: usize,
midi_ins: usize,
audio_outs: usize,
midi_outs: usize,
},
AddFolderFromTemplate {
name: String,
template: String,
},
ApplyTrackTemplate {
track_name: String,
template: String,
},
ApplyFolderTemplate {
track_name: String,
template: String,
},
Undo,
Redo,
OpenUrl(String),
TrackColorChanged {
track_name: String,
color: Option<iced::Color>,
},
TrackColorClear(String),
SessionSlotPressed {
track_name: String,
scene_index: usize,
},
SessionSlotReleased {
track_name: String,
scene_index: usize,
},
SessionSlotSetPlayStopIcon {
track_name: String,
scene_index: usize,
icon: Option<bool>,
},
SessionSlotRightClick {
track_name: String,
scene_index: usize,
point: Point,
},
SessionSlotContextMenuHover {
track_name: String,
scene_index: usize,
position: Point,
},
SessionScenePressed(usize),
SessionSceneReleased(usize),
SessionSceneRightClick {
scene_index: usize,
point: Point,
},
SessionSceneContextMenuHover {
scene_index: usize,
position: Point,
},
SessionSceneContextMenuHide,
SessionSceneRenameShow(usize),
SessionSceneRenameInput(String),
SessionSceneRenameConfirm,
SessionSceneRenameCancel,
SessionSceneRemove(usize),
SessionSceneSetColor {
scene_index: usize,
color: iced::Color,
},
SessionSceneClearColor(usize),
SessionSceneSetTempo {
scene_index: usize,
bpm: Option<f32>,
},
SessionSceneSetLaunchQuantization {
scene_index: usize,
quantization: crate::state::LaunchQuantization,
},
SessionSceneAdd,
SessionStopTrackPressed(String),
SessionStopAllPressed,
SessionSlotDrag {
from: (String, usize),
to: (String, usize),
},
SessionSlotDragStart {
track_name: String,
scene_index: usize,
position: Point,
},
SessionClipDragStart {
source_track_name: Option<String>,
clip_id: String,
kind: maolan_engine::kind::Kind,
},
SessionClipDropped {
point: Point,
},
SessionClipHandleZones(Vec<(Id, Rectangle)>),
PaneClipDragStart {
source_track_name: Option<String>,
clip_id: String,
kind: maolan_engine::kind::Kind,
},
PaneClipDropped {
point: Point,
},
HandlePaneClipDropZones(Vec<(Id, Rectangle)>),
SessionSlotDropped {
track_name: String,
scene_index: usize,
point: Point,
rect: Rectangle,
},
SessionSlotHandleZones(Vec<(Id, Rectangle)>),
SessionSlotDoubleClick {
track_name: String,
scene_index: usize,
},
SessionSlotClearRef {
track_name: String,
scene_index: usize,
},
SessionSlotDuplicate {
track_name: String,
scene_index: usize,
},
SessionSlotCopyToArrangement {
track_name: String,
scene_index: usize,
},
SessionSlotRecord {
track_name: String,
scene_index: usize,
},
SessionViewScrollChanged {
x: f32,
y: f32,
},
SessionSlotSelect {
track_name: String,
scene_index: usize,
additive: bool,
},
SessionSceneSelect(usize),
SessionNavMove {
delta_x: i32,
delta_y: i32,
},
SessionNavLaunch,
SessionNavStopAll,
SessionImportArrangement,
SessionRecordToArrangement,
WorkspaceSessionSlotDropped,
}
#[derive(Debug, Clone)]
pub struct PianoControllerEditData {
pub sample: usize,
pub controller: u8,
pub value: u8,
pub channel: u8,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn snap_sample_rounds_to_nearest_interval_when_enabled() {
let snapped = SnapMode::Beat.snap_sample(740.0, 480.0, 1920.0);
assert_eq!(snapped, 960.0);
}
#[test]
fn snap_sample_leaves_position_when_snap_disabled() {
let snapped = SnapMode::NoSnap.snap_sample(740.0, 480.0, 1920.0);
assert_eq!(snapped, 740.0);
}
#[test]
fn snap_mode_interval_samples_bar() {
assert_eq!(SnapMode::Bar.interval_samples(480.0, 1920.0), 1920.0);
}
#[test]
fn snap_mode_interval_samples_beat() {
assert_eq!(SnapMode::Beat.interval_samples(480.0, 1920.0), 480.0);
}
#[test]
fn snap_mode_interval_samples_eighth() {
assert_eq!(SnapMode::Eighth.interval_samples(480.0, 1920.0), 240.0);
}
#[test]
fn snap_mode_interval_samples_sixteenth() {
assert_eq!(SnapMode::Sixteenth.interval_samples(480.0, 1920.0), 120.0);
}
#[test]
fn snap_mode_interval_samples_no_snap() {
assert_eq!(SnapMode::NoSnap.interval_samples(480.0, 1920.0), 1.0);
}
#[test]
fn snap_mode_interval_samples_clips() {
assert_eq!(SnapMode::Clips.interval_samples(480.0, 1920.0), 1.0);
}
#[test]
fn snap_sample_drag_positive_delta() {
let result = SnapMode::Beat.snap_sample_drag(740.0, 10.0, 480.0, 1920.0);
assert_eq!(result, 480.0);
}
#[test]
fn snap_sample_drag_negative_delta() {
let result = SnapMode::Beat.snap_sample_drag(740.0, -10.0, 480.0, 1920.0);
assert_eq!(result, 960.0);
}
#[test]
fn snap_sample_bar_vs_beat_produces_different_results() {
let beat_snapped = SnapMode::Beat.snap_sample(30_000.0, 24_000.0, 96_000.0);
let bar_snapped = SnapMode::Bar.snap_sample(30_000.0, 24_000.0, 96_000.0);
assert_eq!(beat_snapped, 24_000.0);
assert_eq!(bar_snapped, 0.0);
}
#[test]
fn snap_sample_sixteenth_snaps_to_grid() {
let snapped = SnapMode::Sixteenth.snap_sample(7_000.0, 24_000.0, 96_000.0);
assert_eq!(snapped, 6_000.0);
}
#[test]
fn midi_lane_channel_selection_from_engine_some() {
assert_eq!(
MidiLaneChannelSelection::from_engine(Some(5)),
MidiLaneChannelSelection::Channel(5)
);
}
#[test]
fn midi_lane_channel_selection_from_engine_none() {
assert_eq!(
MidiLaneChannelSelection::from_engine(None),
MidiLaneChannelSelection::Omni
);
}
#[test]
fn midi_lane_channel_selection_to_engine_omni() {
assert_eq!(MidiLaneChannelSelection::Omni.to_engine(), None);
}
#[test]
fn midi_lane_channel_selection_to_engine_channel() {
assert_eq!(MidiLaneChannelSelection::Channel(7).to_engine(), Some(7));
}
#[test]
fn midi_lane_channel_selection_to_engine_clamps() {
assert_eq!(MidiLaneChannelSelection::Channel(20).to_engine(), Some(15));
}
#[test]
fn burn_backend_option_as_ipc_str() {
assert_eq!(BurnBackendOption::Cpu.as_ipc_str(), "cpu");
assert_eq!(BurnBackendOption::Vulkan.as_ipc_str(), "vulkan");
}
#[test]
fn snap_mode_display() {
assert_eq!(format!("{}", SnapMode::NoSnap), "No Snap");
assert_eq!(format!("{}", SnapMode::Bar), "Bar");
assert_eq!(format!("{}", SnapMode::Beat), "Beat");
assert_eq!(format!("{}", SnapMode::Eighth), "1/8");
assert_eq!(format!("{}", SnapMode::Sixteenth), "1/16");
}
#[test]
fn track_automation_mode_display() {
assert_eq!(format!("{}", TrackAutomationMode::Read), "Read");
assert_eq!(format!("{}", TrackAutomationMode::Touch), "Touch");
assert_eq!(format!("{}", TrackAutomationMode::Latch), "Latch");
assert_eq!(format!("{}", TrackAutomationMode::Write), "Write");
}
#[test]
fn piano_scale_root_semitone() {
assert_eq!(PianoScaleRoot::C.semitone(), 0);
assert_eq!(PianoScaleRoot::CSharp.semitone(), 1);
assert_eq!(PianoScaleRoot::D.semitone(), 2);
assert_eq!(PianoScaleRoot::A.semitone(), 9);
assert_eq!(PianoScaleRoot::B.semitone(), 11);
}
#[test]
fn piano_scale_root_display() {
assert_eq!(format!("{}", PianoScaleRoot::C), "C");
assert_eq!(format!("{}", PianoScaleRoot::CSharp), "C#");
assert_eq!(format!("{}", PianoScaleRoot::FSharp), "F#");
}
#[test]
fn piano_chord_kind_intervals() {
assert_eq!(PianoChordKind::MajorTriad.intervals(), &[4, 7]);
assert_eq!(PianoChordKind::MinorTriad.intervals(), &[3, 7]);
assert_eq!(PianoChordKind::Dominant7.intervals(), &[4, 7, 10]);
assert_eq!(PianoChordKind::Major7.intervals(), &[4, 7, 11]);
assert_eq!(PianoChordKind::Minor7.intervals(), &[3, 7, 10]);
}
#[test]
fn piano_chord_kind_display() {
assert_eq!(format!("{}", PianoChordKind::MajorTriad), "Maj");
assert_eq!(format!("{}", PianoChordKind::MinorTriad), "Min");
assert_eq!(format!("{}", PianoChordKind::Dominant7), "7");
}
#[test]
fn piano_velocity_kind_display() {
assert_eq!(
format!("{}", PianoVelocityKind::NoteVelocity),
"Note Velocity"
);
assert_eq!(
format!("{}", PianoVelocityKind::ReleaseVelocity),
"Release Velocity"
);
}
#[test]
fn generate_audio_model_option_display() {
assert_eq!(
format!("{}", GenerateAudioModelOption::HappyNewYear),
"happy-new-year"
);
assert_eq!(format!("{}", GenerateAudioModelOption::Rl), "RL");
}
#[test]
fn curve_editor_draw_line_maps_to_automation_insert_points() {
let context = ("Track 1".to_string(), TrackAutomationTarget::Volume);
let points = vec![
CurveEditorPoint {
sample: 100,
value: 0.25,
},
CurveEditorPoint {
sample: 200,
value: 0.75,
},
];
let message = Message::draw_line(context, points);
match message {
Message::TrackAutomationLaneInsertPoints {
track_name,
target,
points,
} => {
assert_eq!(track_name, "Track 1");
assert_eq!(target, TrackAutomationTarget::Volume);
assert_eq!(points.len(), 2);
assert_eq!(points[0].sample, 100);
assert_eq!(points[0].value, 0.25);
assert_eq!(points[1].sample, 200);
assert_eq!(points[1].value, 0.75);
}
other => panic!("expected TrackAutomationLaneInsertPoints, got {:?}", other),
}
}
#[test]
fn curve_editor_delete_point_maps_to_automation_delete_point() {
let context = ("Track 2".to_string(), TrackAutomationTarget::Balance);
let message = Message::delete_point(context, 480);
match message {
Message::TrackAutomationLaneDeletePoint {
track_name,
target,
sample,
} => {
assert_eq!(track_name, "Track 2");
assert_eq!(target, TrackAutomationTarget::Balance);
assert_eq!(sample, 480);
}
other => panic!("expected TrackAutomationLaneDeletePoint, got {:?}", other),
}
}
}