Fix: sync mode is not required
This commit is contained in:
@@ -14,4 +14,4 @@ pub const MAX_STEPS: usize = 1024;
|
|||||||
pub const DEFAULT_LENGTH: usize = 16;
|
pub const DEFAULT_LENGTH: usize = 16;
|
||||||
|
|
||||||
pub use file::{load, load_str, save, FileError};
|
pub use file::{load, load_str, save, FileError};
|
||||||
pub use project::{Bank, FollowUp, LaunchQuantization, Pattern, PatternSpeed, Project, Step, SyncMode};
|
pub use project::{Bank, FollowUp, LaunchQuantization, Pattern, PatternSpeed, Project, Step};
|
||||||
|
|||||||
@@ -206,39 +206,6 @@ impl LaunchQuantization {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How a pattern synchronizes when launched: restart or phase-lock.
|
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
|
|
||||||
pub enum SyncMode {
|
|
||||||
#[default]
|
|
||||||
Reset,
|
|
||||||
PhaseLock,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SyncMode {
|
|
||||||
/// Human-readable label for display.
|
|
||||||
pub fn label(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Self::Reset => "Reset",
|
|
||||||
Self::PhaseLock => "Phase-Lock",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn short_label(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Self::Reset => "Rst",
|
|
||||||
Self::PhaseLock => "Plk",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Toggle between Reset and PhaseLock.
|
|
||||||
pub fn toggle(&self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Reset => Self::PhaseLock,
|
|
||||||
Self::PhaseLock => Self::Reset,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// What happens when a pattern finishes: loop, stop, or chain to another.
|
/// What happens when a pattern finishes: loop, stop, or chain to another.
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
|
||||||
pub enum FollowUp {
|
pub enum FollowUp {
|
||||||
@@ -315,7 +282,7 @@ impl Default for Step {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sequence of steps with playback settings (speed, quantization, sync, follow-up).
|
/// Sequence of steps with playback settings (speed, quantization, follow-up).
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Pattern {
|
pub struct Pattern {
|
||||||
pub steps: Vec<Step>,
|
pub steps: Vec<Step>,
|
||||||
@@ -324,7 +291,6 @@ pub struct Pattern {
|
|||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub quantization: LaunchQuantization,
|
pub quantization: LaunchQuantization,
|
||||||
pub sync_mode: SyncMode,
|
|
||||||
pub follow_up: FollowUp,
|
pub follow_up: FollowUp,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,8 +327,6 @@ struct SparsePattern {
|
|||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "is_default_quantization")]
|
#[serde(default, skip_serializing_if = "is_default_quantization")]
|
||||||
quantization: LaunchQuantization,
|
quantization: LaunchQuantization,
|
||||||
#[serde(default, skip_serializing_if = "is_default_sync_mode")]
|
|
||||||
sync_mode: SyncMode,
|
|
||||||
#[serde(default, skip_serializing_if = "is_default_follow_up")]
|
#[serde(default, skip_serializing_if = "is_default_follow_up")]
|
||||||
follow_up: FollowUp,
|
follow_up: FollowUp,
|
||||||
}
|
}
|
||||||
@@ -371,10 +335,6 @@ fn is_default_quantization(q: &LaunchQuantization) -> bool {
|
|||||||
*q == LaunchQuantization::default()
|
*q == LaunchQuantization::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_default_sync_mode(s: &SyncMode) -> bool {
|
|
||||||
*s == SyncMode::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct LegacyPattern {
|
struct LegacyPattern {
|
||||||
steps: Vec<Step>,
|
steps: Vec<Step>,
|
||||||
@@ -388,8 +348,6 @@ struct LegacyPattern {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
quantization: LaunchQuantization,
|
quantization: LaunchQuantization,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
sync_mode: SyncMode,
|
|
||||||
#[serde(default)]
|
|
||||||
follow_up: FollowUp,
|
follow_up: FollowUp,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,7 +374,6 @@ impl Serialize for Pattern {
|
|||||||
name: self.name.clone(),
|
name: self.name.clone(),
|
||||||
description: self.description.clone(),
|
description: self.description.clone(),
|
||||||
quantization: self.quantization,
|
quantization: self.quantization,
|
||||||
sync_mode: self.sync_mode,
|
|
||||||
follow_up: self.follow_up,
|
follow_up: self.follow_up,
|
||||||
};
|
};
|
||||||
sparse.serialize(serializer)
|
sparse.serialize(serializer)
|
||||||
@@ -452,7 +409,6 @@ impl<'de> Deserialize<'de> for Pattern {
|
|||||||
name: sparse.name,
|
name: sparse.name,
|
||||||
description: sparse.description,
|
description: sparse.description,
|
||||||
quantization: sparse.quantization,
|
quantization: sparse.quantization,
|
||||||
sync_mode: sparse.sync_mode,
|
|
||||||
follow_up: sparse.follow_up,
|
follow_up: sparse.follow_up,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -463,7 +419,6 @@ impl<'de> Deserialize<'de> for Pattern {
|
|||||||
name: legacy.name,
|
name: legacy.name,
|
||||||
description: legacy.description,
|
description: legacy.description,
|
||||||
quantization: legacy.quantization,
|
quantization: legacy.quantization,
|
||||||
sync_mode: legacy.sync_mode,
|
|
||||||
follow_up: legacy.follow_up,
|
follow_up: legacy.follow_up,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@@ -479,7 +434,6 @@ impl Default for Pattern {
|
|||||||
name: None,
|
name: None,
|
||||||
description: None,
|
description: None,
|
||||||
quantization: LaunchQuantization::default(),
|
quantization: LaunchQuantization::default(),
|
||||||
sync_mode: SyncMode::default(),
|
|
||||||
follow_up: FollowUp::default(),
|
follow_up: FollowUp::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,7 +219,6 @@ impl Plugin for CagirePlugin {
|
|||||||
source: s.source,
|
source: s.source,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
sync_mode: pat.sync_mode,
|
|
||||||
follow_up: pat.follow_up,
|
follow_up: pat.follow_up,
|
||||||
};
|
};
|
||||||
let _ = self.bridge.cmd_tx.send(SeqCommand::PatternUpdate {
|
let _ = self.bridge.cmd_tx.send(SeqCommand::PatternUpdate {
|
||||||
|
|||||||
@@ -199,7 +199,6 @@ impl App {
|
|||||||
length,
|
length,
|
||||||
speed,
|
speed,
|
||||||
quantization,
|
quantization,
|
||||||
sync_mode,
|
|
||||||
follow_up,
|
follow_up,
|
||||||
} => {
|
} => {
|
||||||
self.playback.staged_prop_changes.insert(
|
self.playback.staged_prop_changes.insert(
|
||||||
@@ -210,7 +209,6 @@ impl App {
|
|||||||
length,
|
length,
|
||||||
speed,
|
speed,
|
||||||
quantization,
|
quantization,
|
||||||
sync_mode,
|
|
||||||
follow_up,
|
follow_up,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -203,7 +203,6 @@ impl App {
|
|||||||
length: pat.length.to_string(),
|
length: pat.length.to_string(),
|
||||||
speed: pat.speed,
|
speed: pat.speed,
|
||||||
quantization: pat.quantization,
|
quantization: pat.quantization,
|
||||||
sync_mode: pat.sync_mode,
|
|
||||||
follow_up: pat.follow_up,
|
follow_up: pat.follow_up,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,7 +138,6 @@ impl App {
|
|||||||
self.playback.queued_changes.push(StagedChange {
|
self.playback.queued_changes.push(StagedChange {
|
||||||
change: PatternChange::Start { bank, pattern },
|
change: PatternChange::Start { bank, pattern },
|
||||||
quantization: crate::model::LaunchQuantization::Immediate,
|
quantization: crate::model::LaunchQuantization::Immediate,
|
||||||
sync_mode: crate::model::SyncMode::PhaseLock,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ impl App {
|
|||||||
bank,
|
bank,
|
||||||
pattern,
|
pattern,
|
||||||
quantization: staged.quantization,
|
quantization: staged.quantization,
|
||||||
sync_mode: staged.sync_mode,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
PatternChange::Stop { bank, pattern } => {
|
PatternChange::Stop { bank, pattern } => {
|
||||||
@@ -68,7 +67,6 @@ impl App {
|
|||||||
source: s.source,
|
source: s.source,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
sync_mode: pat.sync_mode,
|
|
||||||
follow_up: pat.follow_up,
|
follow_up: pat.follow_up,
|
||||||
};
|
};
|
||||||
let _ = cmd_tx.send(SeqCommand::PatternUpdate {
|
let _ = cmd_tx.send(SeqCommand::PatternUpdate {
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ impl App {
|
|||||||
self.playback.staged_changes.push(StagedChange {
|
self.playback.staged_changes.push(StagedChange {
|
||||||
change: PatternChange::Stop { bank, pattern },
|
change: PatternChange::Stop { bank, pattern },
|
||||||
quantization: pattern_data.quantization,
|
quantization: pattern_data.quantization,
|
||||||
sync_mode: pattern_data.sync_mode,
|
|
||||||
});
|
});
|
||||||
self.ui
|
self.ui
|
||||||
.set_status(format!("{} armed to stop", bp_label(bank, pattern)));
|
.set_status(format!("{} armed to stop", bp_label(bank, pattern)));
|
||||||
@@ -37,7 +36,6 @@ impl App {
|
|||||||
self.playback.staged_changes.push(StagedChange {
|
self.playback.staged_changes.push(StagedChange {
|
||||||
change: PatternChange::Start { bank, pattern },
|
change: PatternChange::Start { bank, pattern },
|
||||||
quantization: pattern_data.quantization,
|
quantization: pattern_data.quantization,
|
||||||
sync_mode: pattern_data.sync_mode,
|
|
||||||
});
|
});
|
||||||
self.ui
|
self.ui
|
||||||
.set_status(format!("{} armed to play", bp_label(bank, pattern)));
|
.set_status(format!("{} armed to play", bp_label(bank, pattern)));
|
||||||
@@ -84,7 +82,6 @@ impl App {
|
|||||||
}
|
}
|
||||||
pat.speed = props.speed;
|
pat.speed = props.speed;
|
||||||
pat.quantization = props.quantization;
|
pat.quantization = props.quantization;
|
||||||
pat.sync_mode = props.sync_mode;
|
|
||||||
pat.follow_up = props.follow_up;
|
pat.follow_up = props.follow_up;
|
||||||
self.project_state.mark_dirty(bank, pattern);
|
self.project_state.mark_dirty(bank, pattern);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::model::{FollowUp, LaunchQuantization, PatternSpeed, SyncMode};
|
use crate::model::{FollowUp, LaunchQuantization, PatternSpeed};
|
||||||
use crate::page::Page;
|
use crate::page::Page;
|
||||||
use crate::state::{ColorScheme, DeviceKind, Modal, OptionsFocus, PatternField, ScriptField, SettingKind};
|
use crate::state::{ColorScheme, DeviceKind, Modal, OptionsFocus, PatternField, ScriptField, SettingKind};
|
||||||
|
|
||||||
@@ -169,7 +169,6 @@ pub enum AppCommand {
|
|||||||
length: Option<usize>,
|
length: Option<usize>,
|
||||||
speed: PatternSpeed,
|
speed: PatternSpeed,
|
||||||
quantization: LaunchQuantization,
|
quantization: LaunchQuantization,
|
||||||
sync_mode: SyncMode,
|
|
||||||
follow_up: FollowUp,
|
follow_up: FollowUp,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,20 @@ impl LinkState {
|
|||||||
self.link.commit_app_session_state(&state);
|
self.link.commit_app_session_state(&state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn start_playing(&self, beat: f64, time: i64, quantum: f64) {
|
||||||
|
let mut state = SessionState::new();
|
||||||
|
self.link.capture_app_session_state(&mut state);
|
||||||
|
state.set_is_playing_and_request_beat_at_time(true, time, beat, quantum);
|
||||||
|
self.link.commit_app_session_state(&state);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop_playing(&self, time: i64) {
|
||||||
|
let mut state = SessionState::new();
|
||||||
|
self.link.capture_app_session_state(&mut state);
|
||||||
|
state.set_is_playing(false, time);
|
||||||
|
self.link.commit_app_session_state(&state);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn capture_app_state(&self) -> SessionState {
|
pub fn capture_app_state(&self) -> SessionState {
|
||||||
let mut state = SessionState::new();
|
let mut state = SessionState::new();
|
||||||
self.link.capture_app_session_state(&mut state);
|
self.link.capture_app_session_state(&mut state);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ pub mod realtime;
|
|||||||
pub mod sequencer;
|
pub mod sequencer;
|
||||||
mod timing;
|
mod timing;
|
||||||
|
|
||||||
pub use timing::{substeps_in_window, StepTiming, SyncTime};
|
pub use timing::{next_boundary, substeps_in_window, SyncTime};
|
||||||
|
|
||||||
pub use audio::{preload_sample_heads, AnalysisHandle, ScopeBuffer, SpectrumBuffer};
|
pub use audio::{preload_sample_heads, AnalysisHandle, ScopeBuffer, SpectrumBuffer};
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,17 @@ use std::thread::{self, JoinHandle};
|
|||||||
|
|
||||||
use super::dispatcher::{dispatcher_loop, MidiDispatch, TimedMidiCommand};
|
use super::dispatcher::{dispatcher_loop, MidiDispatch, TimedMidiCommand};
|
||||||
use super::realtime::set_realtime_priority;
|
use super::realtime::set_realtime_priority;
|
||||||
use super::{substeps_in_window, LinkState, StepTiming, SyncTime};
|
use super::{next_boundary, substeps_in_window, LinkState, SyncTime};
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
CcAccess, Dictionary, ExecutionTrace, Rng, ScriptEngine, StepContext, Value, Variables,
|
CcAccess, Dictionary, ExecutionTrace, Rng, ScriptEngine, StepContext, Value, Variables,
|
||||||
};
|
};
|
||||||
use crate::model::{FollowUp, LaunchQuantization, SyncMode, MAX_BANKS, MAX_PATTERNS};
|
use crate::model::{FollowUp, LaunchQuantization, MAX_BANKS, MAX_PATTERNS};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub(crate) enum SyncMode {
|
||||||
|
Reset,
|
||||||
|
PhaseLock,
|
||||||
|
}
|
||||||
use crate::state::LiveKeyState;
|
use crate::state::LiveKeyState;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||||
@@ -114,7 +120,6 @@ pub enum SeqCommand {
|
|||||||
bank: usize,
|
bank: usize,
|
||||||
pattern: usize,
|
pattern: usize,
|
||||||
quantization: LaunchQuantization,
|
quantization: LaunchQuantization,
|
||||||
sync_mode: SyncMode,
|
|
||||||
},
|
},
|
||||||
PatternStop {
|
PatternStop {
|
||||||
bank: usize,
|
bank: usize,
|
||||||
@@ -141,7 +146,6 @@ pub struct PatternSnapshot {
|
|||||||
pub speed: crate::model::PatternSpeed,
|
pub speed: crate::model::PatternSpeed,
|
||||||
pub length: usize,
|
pub length: usize,
|
||||||
pub steps: Vec<StepSnapshot>,
|
pub steps: Vec<StepSnapshot>,
|
||||||
pub sync_mode: SyncMode,
|
|
||||||
pub follow_up: FollowUp,
|
pub follow_up: FollowUp,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,18 +303,24 @@ struct ActivePattern {
|
|||||||
step_index: usize,
|
step_index: usize,
|
||||||
iter: usize,
|
iter: usize,
|
||||||
last_step_beat: f64,
|
last_step_beat: f64,
|
||||||
|
origin_beat: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
struct PendingPattern {
|
struct PendingPattern {
|
||||||
id: PatternId,
|
id: PatternId,
|
||||||
quantization: LaunchQuantization,
|
target_beat: Option<f64>,
|
||||||
sync_mode: SyncMode,
|
sync_mode: SyncMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum PlayState {
|
||||||
|
Idle { pause_beat: Option<f64> },
|
||||||
|
Playing { frontier: f64 },
|
||||||
|
}
|
||||||
|
|
||||||
struct AudioState {
|
struct AudioState {
|
||||||
prev_beat: f64,
|
play_state: PlayState,
|
||||||
pause_beat: Option<f64>,
|
|
||||||
active_patterns: HashMap<PatternId, ActivePattern>,
|
active_patterns: HashMap<PatternId, ActivePattern>,
|
||||||
pending_starts: Vec<PendingPattern>,
|
pending_starts: Vec<PendingPattern>,
|
||||||
pending_stops: Vec<PendingPattern>,
|
pending_stops: Vec<PendingPattern>,
|
||||||
@@ -320,8 +330,7 @@ struct AudioState {
|
|||||||
impl AudioState {
|
impl AudioState {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
prev_beat: -1.0,
|
play_state: PlayState::Idle { pause_beat: None },
|
||||||
pause_beat: None,
|
|
||||||
active_patterns: HashMap::new(),
|
active_patterns: HashMap::new(),
|
||||||
pending_starts: Vec::new(),
|
pending_starts: Vec::new(),
|
||||||
pending_stops: Vec::new(),
|
pending_stops: Vec::new(),
|
||||||
@@ -474,22 +483,6 @@ impl PatternSnapshot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_quantization_boundary(
|
|
||||||
quantization: LaunchQuantization,
|
|
||||||
beat: f64,
|
|
||||||
prev_beat: f64,
|
|
||||||
quantum: f64,
|
|
||||||
) -> bool {
|
|
||||||
match quantization {
|
|
||||||
LaunchQuantization::Immediate => prev_beat >= 0.0,
|
|
||||||
LaunchQuantization::Beat => StepTiming::NextBeat.crossed(prev_beat, beat, quantum),
|
|
||||||
LaunchQuantization::Bar => StepTiming::NextBar.crossed(prev_beat, beat, quantum),
|
|
||||||
LaunchQuantization::Bars2 => StepTiming::NextBar.crossed(prev_beat, beat, quantum * 2.0),
|
|
||||||
LaunchQuantization::Bars4 => StepTiming::NextBar.crossed(prev_beat, beat, quantum * 4.0),
|
|
||||||
LaunchQuantization::Bars8 => StepTiming::NextBar.crossed(prev_beat, beat, quantum * 8.0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type StepKey = (usize, usize, usize);
|
type StepKey = (usize, usize, usize);
|
||||||
|
|
||||||
struct RunsCounter {
|
struct RunsCounter {
|
||||||
@@ -582,7 +575,7 @@ pub struct SequencerState {
|
|||||||
script_text: String,
|
script_text: String,
|
||||||
script_speed: crate::model::PatternSpeed,
|
script_speed: crate::model::PatternSpeed,
|
||||||
script_length: usize,
|
script_length: usize,
|
||||||
script_frontier: f64,
|
script_frontier: Option<f64>,
|
||||||
script_step: usize,
|
script_step: usize,
|
||||||
script_trace: Option<ExecutionTrace>,
|
script_trace: Option<ExecutionTrace>,
|
||||||
print_output: Option<String>,
|
print_output: Option<String>,
|
||||||
@@ -621,7 +614,7 @@ impl SequencerState {
|
|||||||
script_text: String::new(),
|
script_text: String::new(),
|
||||||
script_speed: crate::model::PatternSpeed::default(),
|
script_speed: crate::model::PatternSpeed::default(),
|
||||||
script_length: 16,
|
script_length: 16,
|
||||||
script_frontier: -1.0,
|
script_frontier: None,
|
||||||
script_step: 0,
|
script_step: 0,
|
||||||
script_trace: None,
|
script_trace: None,
|
||||||
print_output: None,
|
print_output: None,
|
||||||
@@ -639,7 +632,7 @@ impl SequencerState {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_commands(&mut self, commands: Vec<SeqCommand>) {
|
fn process_commands(&mut self, commands: Vec<SeqCommand>, quantum: f64) {
|
||||||
for cmd in commands {
|
for cmd in commands {
|
||||||
match cmd {
|
match cmd {
|
||||||
SeqCommand::PatternUpdate {
|
SeqCommand::PatternUpdate {
|
||||||
@@ -660,15 +653,15 @@ impl SequencerState {
|
|||||||
bank,
|
bank,
|
||||||
pattern,
|
pattern,
|
||||||
quantization,
|
quantization,
|
||||||
sync_mode,
|
|
||||||
} => {
|
} => {
|
||||||
let id = PatternId { bank, pattern };
|
let id = PatternId { bank, pattern };
|
||||||
self.audio_state.pending_stops.retain(|p| p.id != id);
|
self.audio_state.pending_stops.retain(|p| p.id != id);
|
||||||
if !self.audio_state.pending_starts.iter().any(|p| p.id == id) {
|
if !self.audio_state.pending_starts.iter().any(|p| p.id == id) {
|
||||||
|
let target_beat = next_boundary(self.last_beat, quantization, quantum);
|
||||||
self.audio_state.pending_starts.push(PendingPattern {
|
self.audio_state.pending_starts.push(PendingPattern {
|
||||||
id,
|
id,
|
||||||
quantization,
|
target_beat,
|
||||||
sync_mode,
|
sync_mode: SyncMode::PhaseLock,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -680,9 +673,10 @@ impl SequencerState {
|
|||||||
let id = PatternId { bank, pattern };
|
let id = PatternId { bank, pattern };
|
||||||
self.audio_state.pending_starts.retain(|p| p.id != id);
|
self.audio_state.pending_starts.retain(|p| p.id != id);
|
||||||
if !self.audio_state.pending_stops.iter().any(|p| p.id == id) {
|
if !self.audio_state.pending_stops.iter().any(|p| p.id == id) {
|
||||||
|
let target_beat = next_boundary(self.last_beat, quantization, quantum);
|
||||||
self.audio_state.pending_stops.push(PendingPattern {
|
self.audio_state.pending_stops.push(PendingPattern {
|
||||||
id,
|
id,
|
||||||
quantization,
|
target_beat,
|
||||||
sync_mode: SyncMode::Reset,
|
sync_mode: SyncMode::Reset,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -722,7 +716,8 @@ impl SequencerState {
|
|||||||
self.audio_state.active_patterns.clear();
|
self.audio_state.active_patterns.clear();
|
||||||
self.audio_state.pending_starts.clear();
|
self.audio_state.pending_starts.clear();
|
||||||
self.audio_state.pending_stops.clear();
|
self.audio_state.pending_stops.clear();
|
||||||
self.audio_state.pause_beat = None;
|
self.audio_state.play_state = PlayState::Idle { pause_beat: None };
|
||||||
|
self.script_frontier = None;
|
||||||
self.step_traces = Arc::new(HashMap::new());
|
self.step_traces = Arc::new(HashMap::new());
|
||||||
self.runs_counter.counts.clear();
|
self.runs_counter.counts.clear();
|
||||||
self.audio_state.flush_midi_notes = true;
|
self.audio_state.flush_midi_notes = true;
|
||||||
@@ -732,9 +727,8 @@ impl SequencerState {
|
|||||||
active.step_index = 0;
|
active.step_index = 0;
|
||||||
active.iter = 0;
|
active.iter = 0;
|
||||||
}
|
}
|
||||||
self.audio_state.prev_beat = -1.0;
|
self.audio_state.play_state = PlayState::Idle { pause_beat: None };
|
||||||
self.audio_state.pause_beat = None;
|
self.script_frontier = None;
|
||||||
self.script_frontier = -1.0;
|
|
||||||
self.script_step = 0;
|
self.script_step = 0;
|
||||||
self.script_trace = None;
|
self.script_trace = None;
|
||||||
self.variables.store(Arc::new(HashMap::new()));
|
self.variables.store(Arc::new(HashMap::new()));
|
||||||
@@ -758,30 +752,26 @@ impl SequencerState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(&mut self, input: TickInput) -> TickOutput {
|
pub fn tick(&mut self, input: TickInput) -> TickOutput {
|
||||||
self.process_commands(input.commands);
|
|
||||||
self.last_tempo = input.tempo;
|
self.last_tempo = input.tempo;
|
||||||
self.last_beat = input.beat;
|
self.last_beat = input.beat;
|
||||||
self.last_playing = input.playing;
|
self.last_playing = input.playing;
|
||||||
|
self.process_commands(input.commands, input.quantum);
|
||||||
|
|
||||||
if !input.playing {
|
if !input.playing {
|
||||||
return self.tick_paused();
|
return self.tick_paused();
|
||||||
}
|
}
|
||||||
|
|
||||||
let frontier = self.audio_state.prev_beat;
|
let (frontier, resuming) = match self.audio_state.play_state {
|
||||||
let lookahead_end = input.lookahead_end;
|
PlayState::Playing { frontier } => (frontier, false),
|
||||||
let resuming = frontier < 0.0;
|
PlayState::Idle { pause_beat } => (pause_beat.unwrap_or(input.beat), true),
|
||||||
|
|
||||||
let boundary_frontier = if resuming {
|
|
||||||
self.audio_state.pause_beat.take().unwrap_or(input.beat)
|
|
||||||
} else {
|
|
||||||
frontier
|
|
||||||
};
|
};
|
||||||
|
let lookahead_end = input.lookahead_end;
|
||||||
|
|
||||||
self.activate_pending(lookahead_end, boundary_frontier, input.quantum);
|
self.activate_pending(frontier, lookahead_end);
|
||||||
self.deactivate_pending(lookahead_end, boundary_frontier, input.quantum);
|
self.deactivate_pending(frontier, lookahead_end);
|
||||||
|
|
||||||
if resuming {
|
if resuming {
|
||||||
self.realign_phaselock_patterns(lookahead_end);
|
self.reset_origins_on_resume(lookahead_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
let steps = self.execute_steps(
|
let steps = self.execute_steps(
|
||||||
@@ -818,7 +808,7 @@ impl SequencerState {
|
|||||||
let new_tempo = self.read_tempo_variable(steps.any_step_fired);
|
let new_tempo = self.read_tempo_variable(steps.any_step_fired);
|
||||||
self.apply_follow_ups();
|
self.apply_follow_ups();
|
||||||
|
|
||||||
self.audio_state.prev_beat = lookahead_end;
|
self.audio_state.play_state = PlayState::Playing { frontier: lookahead_end };
|
||||||
|
|
||||||
let flush = std::mem::take(&mut self.audio_state.flush_midi_notes);
|
let flush = std::mem::take(&mut self.audio_state.flush_midi_notes);
|
||||||
TickOutput {
|
TickOutput {
|
||||||
@@ -840,11 +830,12 @@ impl SequencerState {
|
|||||||
self.pattern_cache.set(key.0, key.1, snapshot);
|
self.pattern_cache.set(key.0, key.1, snapshot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.audio_state.prev_beat >= 0.0 {
|
let pause_beat = match self.audio_state.play_state {
|
||||||
self.audio_state.pause_beat = Some(self.audio_state.prev_beat);
|
PlayState::Playing { frontier } => Some(frontier),
|
||||||
}
|
PlayState::Idle { pause_beat } => pause_beat,
|
||||||
self.audio_state.prev_beat = -1.0;
|
};
|
||||||
self.script_frontier = -1.0;
|
self.audio_state.play_state = PlayState::Idle { pause_beat };
|
||||||
|
self.script_frontier = None;
|
||||||
self.script_step = 0;
|
self.script_step = 0;
|
||||||
self.script_trace = None;
|
self.script_trace = None;
|
||||||
self.print_output = None;
|
self.print_output = None;
|
||||||
@@ -858,35 +849,46 @@ impl SequencerState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realign_phaselock_patterns(&mut self, beat: f64) {
|
fn pause_beat(&self) -> Option<f64> {
|
||||||
for (id, active) in &mut self.audio_state.active_patterns {
|
match self.audio_state.play_state {
|
||||||
let Some(pattern) = self.pattern_cache.get(id.bank, id.pattern) else {
|
PlayState::Idle { pause_beat } => pause_beat,
|
||||||
continue;
|
PlayState::Playing { .. } => None,
|
||||||
};
|
|
||||||
if pattern.sync_mode != SyncMode::PhaseLock {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let speed_mult = pattern.speed.multiplier();
|
|
||||||
let subs_per_beat = 4.0 * speed_mult;
|
|
||||||
let step = (beat * subs_per_beat).floor() as usize + 1;
|
|
||||||
active.step_index = step % pattern.length;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate_pending(&mut self, beat: f64, prev_beat: f64, quantum: f64) {
|
fn reset_origins_on_resume(&mut self, lookahead_end: f64) {
|
||||||
|
for (id, active) in &mut self.audio_state.active_patterns {
|
||||||
|
active.origin_beat = lookahead_end;
|
||||||
|
let Some(pattern) = self.pattern_cache.get(id.bank, id.pattern) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let subs_per_beat = 4.0 * pattern.speed.multiplier();
|
||||||
|
let step = (lookahead_end * subs_per_beat).floor() as usize + 1;
|
||||||
|
active.step_index = step % pattern.length;
|
||||||
|
}
|
||||||
|
self.script_frontier = Some(lookahead_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn activate_pending(&mut self, frontier: f64, lookahead_end: f64) {
|
||||||
self.buf_activated.clear();
|
self.buf_activated.clear();
|
||||||
for pending in &self.audio_state.pending_starts {
|
for pending in &self.audio_state.pending_starts {
|
||||||
if check_quantization_boundary(pending.quantization, beat, prev_beat, quantum) {
|
let should_activate = match pending.target_beat {
|
||||||
|
None => true,
|
||||||
|
Some(target) => target <= lookahead_end,
|
||||||
|
};
|
||||||
|
if should_activate {
|
||||||
|
let origin_beat = match pending.target_beat {
|
||||||
|
Some(t) if t > frontier => t - 1e-9,
|
||||||
|
_ => frontier,
|
||||||
|
};
|
||||||
let start_step = match pending.sync_mode {
|
let start_step = match pending.sync_mode {
|
||||||
SyncMode::Reset => 0,
|
SyncMode::Reset => 0,
|
||||||
SyncMode::PhaseLock => {
|
SyncMode::PhaseLock => {
|
||||||
if let Some(pat) =
|
if let Some(pat) =
|
||||||
self.pattern_cache.get(pending.id.bank, pending.id.pattern)
|
self.pattern_cache.get(pending.id.bank, pending.id.pattern)
|
||||||
{
|
{
|
||||||
let speed_mult = pat.speed.multiplier();
|
let subs_per_beat = 4.0 * pat.speed.multiplier();
|
||||||
let subs_per_beat = 4.0 * speed_mult;
|
(origin_beat * subs_per_beat).floor() as usize % pat.length
|
||||||
let first_sub = (prev_beat * subs_per_beat).floor() as usize + 1;
|
|
||||||
first_sub % pat.length
|
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
@@ -901,7 +903,8 @@ impl SequencerState {
|
|||||||
pattern: pending.id.pattern,
|
pattern: pending.id.pattern,
|
||||||
step_index: start_step,
|
step_index: start_step,
|
||||||
iter: 0,
|
iter: 0,
|
||||||
last_step_beat: beat,
|
last_step_beat: lookahead_end,
|
||||||
|
origin_beat,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
self.buf_activated.push(pending.id);
|
self.buf_activated.push(pending.id);
|
||||||
@@ -913,15 +916,18 @@ impl SequencerState {
|
|||||||
.retain(|p| !activated.contains(&p.id));
|
.retain(|p| !activated.contains(&p.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deactivate_pending(&mut self, beat: f64, prev_beat: f64, quantum: f64) {
|
fn deactivate_pending(&mut self, _frontier: f64, lookahead_end: f64) {
|
||||||
self.buf_stopped.clear();
|
self.buf_stopped.clear();
|
||||||
for pending in &self.audio_state.pending_stops {
|
for pending in &self.audio_state.pending_stops {
|
||||||
if check_quantization_boundary(pending.quantization, beat, prev_beat, quantum) {
|
let should_deactivate = match pending.target_beat {
|
||||||
|
None => true,
|
||||||
|
Some(target) => target <= lookahead_end,
|
||||||
|
};
|
||||||
|
if should_deactivate {
|
||||||
self.audio_state.active_patterns.remove(&pending.id);
|
self.audio_state.active_patterns.remove(&pending.id);
|
||||||
Arc::make_mut(&mut self.step_traces).retain(|&(bank, pattern, _), _| {
|
Arc::make_mut(&mut self.step_traces).retain(|&(bank, pattern, _), _| {
|
||||||
bank != pending.id.bank || pattern != pending.id.pattern
|
bank != pending.id.bank || pattern != pending.id.pattern
|
||||||
});
|
});
|
||||||
// Flush pending update so cache stays current for future launches
|
|
||||||
let key = (pending.id.bank, pending.id.pattern);
|
let key = (pending.id.bank, pending.id.pattern);
|
||||||
if let Some(snapshot) = self.pending_updates.remove(&key) {
|
if let Some(snapshot) = self.pending_updates.remove(&key) {
|
||||||
self.pattern_cache.set(key.0, key.1, snapshot);
|
self.pattern_cache.set(key.0, key.1, snapshot);
|
||||||
@@ -981,7 +987,8 @@ impl SequencerState {
|
|||||||
.copied()
|
.copied()
|
||||||
.unwrap_or_else(|| pattern.speed.multiplier());
|
.unwrap_or_else(|| pattern.speed.multiplier());
|
||||||
|
|
||||||
let step_beats = substeps_in_window(frontier, lookahead_end, speed_mult);
|
let pattern_frontier = frontier.max(active.origin_beat);
|
||||||
|
let step_beats = substeps_in_window(pattern_frontier, lookahead_end, speed_mult);
|
||||||
|
|
||||||
for step_beat in step_beats {
|
for step_beat in step_beats {
|
||||||
result.any_step_fired = true;
|
result.any_step_fired = true;
|
||||||
@@ -1116,11 +1123,7 @@ impl SequencerState {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let script_frontier = if self.script_frontier < 0.0 {
|
let script_frontier = self.script_frontier.unwrap_or(frontier);
|
||||||
frontier
|
|
||||||
} else {
|
|
||||||
self.script_frontier
|
|
||||||
};
|
|
||||||
|
|
||||||
let speed_mult = self.script_speed.multiplier();
|
let speed_mult = self.script_speed.multiplier();
|
||||||
let fire_beats = substeps_in_window(script_frontier, lookahead_end, speed_mult);
|
let fire_beats = substeps_in_window(script_frontier, lookahead_end, speed_mult);
|
||||||
@@ -1187,7 +1190,7 @@ impl SequencerState {
|
|||||||
self.script_step += 1;
|
self.script_step += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.script_frontier = lookahead_end;
|
self.script_frontier = Some(lookahead_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_tempo_variable(&self, any_step_fired: bool) -> Option<f64> {
|
fn read_tempo_variable(&self, any_step_fired: bool) -> Option<f64> {
|
||||||
@@ -1220,21 +1223,21 @@ impl SequencerState {
|
|||||||
FollowUp::Stop => {
|
FollowUp::Stop => {
|
||||||
self.audio_state.pending_stops.push(PendingPattern {
|
self.audio_state.pending_stops.push(PendingPattern {
|
||||||
id: *completed_id,
|
id: *completed_id,
|
||||||
quantization: LaunchQuantization::Immediate,
|
target_beat: None,
|
||||||
sync_mode: SyncMode::Reset,
|
sync_mode: SyncMode::Reset,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
FollowUp::Chain { bank, pattern } => {
|
FollowUp::Chain { bank, pattern } => {
|
||||||
self.audio_state.pending_stops.push(PendingPattern {
|
self.audio_state.pending_stops.push(PendingPattern {
|
||||||
id: *completed_id,
|
id: *completed_id,
|
||||||
quantization: LaunchQuantization::Immediate,
|
target_beat: None,
|
||||||
sync_mode: SyncMode::Reset,
|
sync_mode: SyncMode::Reset,
|
||||||
});
|
});
|
||||||
let target = PatternId { bank, pattern };
|
let target = PatternId { bank, pattern };
|
||||||
if !self.audio_state.pending_starts.iter().any(|p| p.id == target) {
|
if !self.audio_state.pending_starts.iter().any(|p| p.id == target) {
|
||||||
self.audio_state.pending_starts.push(PendingPattern {
|
self.audio_state.pending_starts.push(PendingPattern {
|
||||||
id: target,
|
id: target,
|
||||||
quantization: LaunchQuantization::Immediate,
|
target_beat: None,
|
||||||
sync_mode: SyncMode::Reset,
|
sync_mode: SyncMode::Reset,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1325,9 +1328,19 @@ fn sequencer_loop(
|
|||||||
|
|
||||||
let state = link.capture_app_state();
|
let state = link.capture_app_state();
|
||||||
let current_time_us = link.clock_micros() as SyncTime;
|
let current_time_us = link.clock_micros() as SyncTime;
|
||||||
let beat = state.beat_at_time(current_time_us as i64, quantum);
|
let mut beat = state.beat_at_time(current_time_us as i64, quantum);
|
||||||
let tempo = state.tempo();
|
let tempo = state.tempo();
|
||||||
|
|
||||||
|
let is_playing = playing.load(Ordering::Relaxed);
|
||||||
|
if is_playing && !seq_state.last_playing {
|
||||||
|
let anchor_beat = seq_state.pause_beat().unwrap_or(0.0);
|
||||||
|
link.start_playing(anchor_beat, current_time_us as i64, quantum);
|
||||||
|
let state = link.capture_app_state();
|
||||||
|
beat = state.beat_at_time(current_time_us as i64, quantum);
|
||||||
|
} else if !is_playing && seq_state.last_playing {
|
||||||
|
link.stop_playing(current_time_us as i64);
|
||||||
|
}
|
||||||
|
|
||||||
let lookahead_beats = if tempo > 0.0 {
|
let lookahead_beats = if tempo > 0.0 {
|
||||||
lookahead_secs * tempo / 60.0
|
lookahead_secs * tempo / 60.0
|
||||||
} else {
|
} else {
|
||||||
@@ -1339,7 +1352,7 @@ fn sequencer_loop(
|
|||||||
let audio_samples = audio_sample_pos.load(Ordering::Acquire);
|
let audio_samples = audio_sample_pos.load(Ordering::Acquire);
|
||||||
let input = TickInput {
|
let input = TickInput {
|
||||||
commands,
|
commands,
|
||||||
playing: playing.load(Ordering::Relaxed),
|
playing: is_playing,
|
||||||
beat,
|
beat,
|
||||||
lookahead_end,
|
lookahead_end,
|
||||||
tempo,
|
tempo,
|
||||||
@@ -1550,7 +1563,6 @@ mod tests {
|
|||||||
source: None,
|
source: None,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
follow_up: FollowUp::default(),
|
follow_up: FollowUp::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1621,7 +1633,6 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
1.0,
|
1.0,
|
||||||
@@ -1649,7 +1660,6 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
}],
|
}],
|
||||||
1.0,
|
1.0,
|
||||||
));
|
));
|
||||||
@@ -1697,7 +1707,6 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
}],
|
}],
|
||||||
0.5,
|
0.5,
|
||||||
));
|
));
|
||||||
@@ -1706,43 +1715,32 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_quantization_boundaries() {
|
fn test_next_boundary() {
|
||||||
assert!(check_quantization_boundary(
|
use super::super::next_boundary;
|
||||||
LaunchQuantization::Immediate,
|
|
||||||
1.5,
|
// Immediate returns None
|
||||||
1.0,
|
assert_eq!(next_boundary(1.5, LaunchQuantization::Immediate, 4.0), None);
|
||||||
4.0
|
|
||||||
));
|
// Beat: next integer beat
|
||||||
assert!(check_quantization_boundary(
|
assert_eq!(next_boundary(1.5, LaunchQuantization::Beat, 4.0), Some(2.0));
|
||||||
LaunchQuantization::Beat,
|
assert_eq!(next_boundary(1.9, LaunchQuantization::Beat, 4.0), Some(2.0));
|
||||||
2.0,
|
// On exact beat boundary, targets next beat
|
||||||
1.9,
|
assert_eq!(next_boundary(2.0, LaunchQuantization::Beat, 4.0), Some(3.0));
|
||||||
4.0
|
|
||||||
));
|
// Bar (quantum=4): next multiple of 4
|
||||||
assert!(!check_quantization_boundary(
|
assert_eq!(next_boundary(3.5, LaunchQuantization::Bar, 4.0), Some(4.0));
|
||||||
LaunchQuantization::Beat,
|
assert_eq!(next_boundary(3.9, LaunchQuantization::Bar, 4.0), Some(4.0));
|
||||||
1.5,
|
// On exact bar boundary, targets next bar
|
||||||
1.2,
|
assert_eq!(next_boundary(4.0, LaunchQuantization::Bar, 4.0), Some(8.0));
|
||||||
4.0
|
|
||||||
));
|
// Bars2 (quantum=4): next multiple of 8
|
||||||
assert!(check_quantization_boundary(
|
assert_eq!(next_boundary(3.5, LaunchQuantization::Bars2, 4.0), Some(8.0));
|
||||||
LaunchQuantization::Bar,
|
|
||||||
4.0,
|
// Bars4 (quantum=4): next multiple of 16
|
||||||
3.9,
|
assert_eq!(next_boundary(3.5, LaunchQuantization::Bars4, 4.0), Some(16.0));
|
||||||
4.0
|
|
||||||
));
|
// Bars8 (quantum=4): next multiple of 32
|
||||||
assert!(!check_quantization_boundary(
|
assert_eq!(next_boundary(3.5, LaunchQuantization::Bars8, 4.0), Some(32.0));
|
||||||
LaunchQuantization::Bar,
|
|
||||||
3.5,
|
|
||||||
3.2,
|
|
||||||
4.0
|
|
||||||
));
|
|
||||||
assert!(!check_quantization_boundary(
|
|
||||||
LaunchQuantization::Immediate,
|
|
||||||
1.0,
|
|
||||||
-1.0,
|
|
||||||
4.0
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1763,7 +1761,6 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
}],
|
}],
|
||||||
0.5,
|
0.5,
|
||||||
));
|
));
|
||||||
@@ -1808,7 +1805,6 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
}],
|
}],
|
||||||
0.5,
|
0.5,
|
||||||
));
|
));
|
||||||
@@ -1844,7 +1840,6 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
},
|
},
|
||||||
SeqCommand::PatternStop {
|
SeqCommand::PatternStop {
|
||||||
bank: 0,
|
bank: 0,
|
||||||
@@ -1879,13 +1874,11 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Bar,
|
quantization: LaunchQuantization::Bar,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
},
|
},
|
||||||
SeqCommand::PatternStart {
|
SeqCommand::PatternStart {
|
||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 1,
|
pattern: 1,
|
||||||
quantization: LaunchQuantization::Beat,
|
quantization: LaunchQuantization::Beat,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
0.0,
|
0.0,
|
||||||
@@ -1921,7 +1914,6 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
}],
|
}],
|
||||||
0.5,
|
0.5,
|
||||||
));
|
));
|
||||||
@@ -1990,7 +1982,6 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
}],
|
}],
|
||||||
..tick_at(1.0, false)
|
..tick_at(1.0, false)
|
||||||
});
|
});
|
||||||
@@ -2019,7 +2010,6 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
}],
|
}],
|
||||||
1.0,
|
1.0,
|
||||||
));
|
));
|
||||||
@@ -2049,13 +2039,11 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Bar,
|
quantization: LaunchQuantization::Bar,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
},
|
},
|
||||||
SeqCommand::PatternStart {
|
SeqCommand::PatternStart {
|
||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Bar,
|
quantization: LaunchQuantization::Bar,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
0.0,
|
0.0,
|
||||||
@@ -2088,7 +2076,6 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
}],
|
}],
|
||||||
0.5,
|
0.5,
|
||||||
));
|
));
|
||||||
@@ -2115,7 +2102,6 @@ mod tests {
|
|||||||
source: None,
|
source: None,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
follow_up: FollowUp::default(),
|
follow_up: FollowUp::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2138,7 +2124,6 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
}],
|
}],
|
||||||
0.5,
|
0.5,
|
||||||
));
|
));
|
||||||
@@ -2186,13 +2171,11 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
},
|
},
|
||||||
SeqCommand::PatternStart {
|
SeqCommand::PatternStart {
|
||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 1,
|
pattern: 1,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
0.5,
|
0.5,
|
||||||
@@ -2227,7 +2210,6 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Bar,
|
quantization: LaunchQuantization::Bar,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
}],
|
}],
|
||||||
3.5,
|
3.5,
|
||||||
));
|
));
|
||||||
@@ -2266,7 +2248,6 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Immediate,
|
quantization: LaunchQuantization::Immediate,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
}],
|
}],
|
||||||
..tick_at(2.0, false)
|
..tick_at(2.0, false)
|
||||||
});
|
});
|
||||||
@@ -2306,13 +2287,11 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Bar,
|
quantization: LaunchQuantization::Bar,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
},
|
},
|
||||||
SeqCommand::PatternStart {
|
SeqCommand::PatternStart {
|
||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 1,
|
pattern: 1,
|
||||||
quantization: LaunchQuantization::Bar,
|
quantization: LaunchQuantization::Bar,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
3.5,
|
3.5,
|
||||||
@@ -2344,7 +2323,6 @@ mod tests {
|
|||||||
source: None,
|
source: None,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
sync_mode: SyncMode::PhaseLock,
|
|
||||||
follow_up: FollowUp::default(),
|
follow_up: FollowUp::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2368,7 +2346,6 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Bar,
|
quantization: LaunchQuantization::Bar,
|
||||||
sync_mode: SyncMode::PhaseLock,
|
|
||||||
}],
|
}],
|
||||||
3.5,
|
3.5,
|
||||||
));
|
));
|
||||||
@@ -2410,7 +2387,6 @@ mod tests {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
quantization: LaunchQuantization::Bar,
|
quantization: LaunchQuantization::Bar,
|
||||||
sync_mode: SyncMode::Reset,
|
|
||||||
}],
|
}],
|
||||||
1.0,
|
1.0,
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -1,26 +1,28 @@
|
|||||||
|
use crate::model::LaunchQuantization;
|
||||||
|
|
||||||
/// Microsecond-precision timestamp for audio synchronization.
|
/// Microsecond-precision timestamp for audio synchronization.
|
||||||
pub type SyncTime = u64;
|
pub type SyncTime = u64;
|
||||||
|
|
||||||
/// Timing boundary types for step and pattern scheduling.
|
/// Compute the exact next quantization boundary beat after `current_beat`.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
/// Returns `None` for Immediate (activate now), `Some(beat)` for all others.
|
||||||
pub enum StepTiming {
|
pub fn next_boundary(current_beat: f64, quantization: LaunchQuantization, quantum: f64) -> Option<f64> {
|
||||||
/// Fire when a beat boundary is crossed.
|
match quantization {
|
||||||
NextBeat,
|
LaunchQuantization::Immediate => None,
|
||||||
/// Fire when a bar/quantum boundary is crossed.
|
LaunchQuantization::Beat => Some(current_beat.floor() + 1.0),
|
||||||
NextBar,
|
LaunchQuantization::Bar => {
|
||||||
}
|
Some((current_beat / quantum).floor() * quantum + quantum)
|
||||||
|
|
||||||
impl StepTiming {
|
|
||||||
/// Returns true if the boundary was crossed between prev_beat and curr_beat.
|
|
||||||
pub fn crossed(&self, prev_beat: f64, curr_beat: f64, quantum: f64) -> bool {
|
|
||||||
if prev_beat < 0.0 {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
match self {
|
LaunchQuantization::Bars2 => {
|
||||||
Self::NextBeat => prev_beat.floor() as i64 != curr_beat.floor() as i64,
|
let p = quantum * 2.0;
|
||||||
Self::NextBar => {
|
Some((current_beat / p).floor() * p + p)
|
||||||
(prev_beat / quantum).floor() as i64 != (curr_beat / quantum).floor() as i64
|
}
|
||||||
}
|
LaunchQuantization::Bars4 => {
|
||||||
|
let p = quantum * 4.0;
|
||||||
|
Some((current_beat / p).floor() * p + p)
|
||||||
|
}
|
||||||
|
LaunchQuantization::Bars8 => {
|
||||||
|
let p = quantum * 8.0;
|
||||||
|
Some((current_beat / p).floor() * p + p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,7 +31,7 @@ impl StepTiming {
|
|||||||
/// Each entry is the exact beat at which that substep fires.
|
/// Each entry is the exact beat at which that substep fires.
|
||||||
/// Clamped to 64 results max to prevent runaway.
|
/// Clamped to 64 results max to prevent runaway.
|
||||||
pub fn substeps_in_window(frontier: f64, end: f64, speed: f64) -> Vec<f64> {
|
pub fn substeps_in_window(frontier: f64, end: f64, speed: f64) -> Vec<f64> {
|
||||||
if frontier < 0.0 || end <= frontier || speed <= 0.0 {
|
if end <= frontier || speed <= 0.0 {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
let substeps_per_beat = 4.0 * speed;
|
let substeps_per_beat = 4.0 * speed;
|
||||||
@@ -55,9 +57,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn substeps_crossed(prev_beat: f64, curr_beat: f64, speed: f64) -> usize {
|
fn substeps_crossed(prev_beat: f64, curr_beat: f64, speed: f64) -> usize {
|
||||||
if prev_beat < 0.0 {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
let prev_substep = (prev_beat * 4.0 * speed).floor() as i64;
|
let prev_substep = (prev_beat * 4.0 * speed).floor() as i64;
|
||||||
let curr_substep = (curr_beat * 4.0 * speed).floor() as i64;
|
let curr_substep = (curr_beat * 4.0 * speed).floor() as i64;
|
||||||
(curr_substep - prev_substep).clamp(0, 16) as usize
|
(curr_substep - prev_substep).clamp(0, 16) as usize
|
||||||
@@ -88,23 +87,15 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_step_timing_beat_crossed() {
|
fn test_next_boundary() {
|
||||||
// Crossing from beat 0 to beat 1
|
assert_eq!(next_boundary(1.5, LaunchQuantization::Immediate, 4.0), None);
|
||||||
assert!(StepTiming::NextBeat.crossed(0.9, 1.1, 4.0));
|
assert_eq!(next_boundary(1.5, LaunchQuantization::Beat, 4.0), Some(2.0));
|
||||||
// Not crossing (both in same beat)
|
assert_eq!(next_boundary(2.0, LaunchQuantization::Beat, 4.0), Some(3.0));
|
||||||
assert!(!StepTiming::NextBeat.crossed(0.5, 0.9, 4.0));
|
assert_eq!(next_boundary(3.5, LaunchQuantization::Bar, 4.0), Some(4.0));
|
||||||
// Negative prev_beat returns false
|
assert_eq!(next_boundary(4.0, LaunchQuantization::Bar, 4.0), Some(8.0));
|
||||||
assert!(!StepTiming::NextBeat.crossed(-1.0, 1.0, 4.0));
|
assert_eq!(next_boundary(3.5, LaunchQuantization::Bars2, 4.0), Some(8.0));
|
||||||
}
|
assert_eq!(next_boundary(3.5, LaunchQuantization::Bars4, 4.0), Some(16.0));
|
||||||
|
assert_eq!(next_boundary(3.5, LaunchQuantization::Bars8, 4.0), Some(32.0));
|
||||||
#[test]
|
|
||||||
fn test_step_timing_bar_crossed() {
|
|
||||||
// Crossing from bar 0 to bar 1 (quantum=4)
|
|
||||||
assert!(StepTiming::NextBar.crossed(3.9, 4.1, 4.0));
|
|
||||||
// Not crossing (both in same bar)
|
|
||||||
assert!(!StepTiming::NextBar.crossed(2.0, 3.0, 4.0));
|
|
||||||
// Crossing with different quantum
|
|
||||||
assert!(StepTiming::NextBar.crossed(7.9, 8.1, 8.0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -126,9 +117,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_substeps_crossed_negative_prev() {
|
fn test_substeps_crossed_same_position() {
|
||||||
// Negative prev_beat returns 0
|
// Same position returns 0
|
||||||
assert_eq!(substeps_crossed(-1.0, 0.5, 1.0), 0);
|
assert_eq!(substeps_crossed(0.5, 0.5, 1.0), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -205,8 +196,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_substeps_in_window_negative_frontier() {
|
fn test_substeps_in_window_reversed() {
|
||||||
let result = substeps_in_window(-1.0, 0.5, 1.0);
|
// end <= frontier returns empty
|
||||||
|
let result = substeps_in_window(0.5, 0.3, 1.0);
|
||||||
assert!(result.is_empty());
|
assert!(result.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,8 +83,7 @@ pub fn init(args: InitArgs) -> Init {
|
|||||||
for (bank, pattern) in playing {
|
for (bank, pattern) in playing {
|
||||||
app.playback.queued_changes.push(StagedChange {
|
app.playback.queued_changes.push(StagedChange {
|
||||||
change: PatternChange::Start { bank, pattern },
|
change: PatternChange::Start { bank, pattern },
|
||||||
quantization: model::LaunchQuantization::Immediate,
|
quantization: model::LaunchQuantization::Bar,
|
||||||
sync_mode: model::SyncMode::PhaseLock,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
app.ui.set_status(format!("Demo: {}", demo.name));
|
app.ui.set_status(format!("Demo: {}", demo.name));
|
||||||
@@ -96,8 +95,7 @@ pub fn init(args: InitArgs) -> Init {
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
pattern: 0,
|
pattern: 0,
|
||||||
},
|
},
|
||||||
quantization: model::LaunchQuantization::Immediate,
|
quantization: model::LaunchQuantization::Bar,
|
||||||
sync_mode: model::SyncMode::PhaseLock,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -461,7 +461,6 @@ pub(super) fn handle_modal_input(ctx: &mut InputContext, key: KeyEvent) -> Input
|
|||||||
length,
|
length,
|
||||||
speed,
|
speed,
|
||||||
quantization,
|
quantization,
|
||||||
sync_mode,
|
|
||||||
follow_up,
|
follow_up,
|
||||||
} => {
|
} => {
|
||||||
let (bank, pattern) = (*bank, *pattern);
|
let (bank, pattern) = (*bank, *pattern);
|
||||||
@@ -472,7 +471,6 @@ pub(super) fn handle_modal_input(ctx: &mut InputContext, key: KeyEvent) -> Input
|
|||||||
KeyCode::Left => match field {
|
KeyCode::Left => match field {
|
||||||
PatternPropsField::Speed => *speed = speed.prev(),
|
PatternPropsField::Speed => *speed = speed.prev(),
|
||||||
PatternPropsField::Quantization => *quantization = quantization.prev(),
|
PatternPropsField::Quantization => *quantization = quantization.prev(),
|
||||||
PatternPropsField::SyncMode => *sync_mode = sync_mode.toggle(),
|
|
||||||
PatternPropsField::FollowUp => *follow_up = follow_up.prev_mode(),
|
PatternPropsField::FollowUp => *follow_up = follow_up.prev_mode(),
|
||||||
PatternPropsField::ChainBank => {
|
PatternPropsField::ChainBank => {
|
||||||
if let FollowUp::Chain { bank: b, .. } = follow_up {
|
if let FollowUp::Chain { bank: b, .. } = follow_up {
|
||||||
@@ -489,7 +487,6 @@ pub(super) fn handle_modal_input(ctx: &mut InputContext, key: KeyEvent) -> Input
|
|||||||
KeyCode::Right => match field {
|
KeyCode::Right => match field {
|
||||||
PatternPropsField::Speed => *speed = speed.next(),
|
PatternPropsField::Speed => *speed = speed.next(),
|
||||||
PatternPropsField::Quantization => *quantization = quantization.next(),
|
PatternPropsField::Quantization => *quantization = quantization.next(),
|
||||||
PatternPropsField::SyncMode => *sync_mode = sync_mode.toggle(),
|
|
||||||
PatternPropsField::FollowUp => *follow_up = follow_up.next_mode(),
|
PatternPropsField::FollowUp => *follow_up = follow_up.next_mode(),
|
||||||
PatternPropsField::ChainBank => {
|
PatternPropsField::ChainBank => {
|
||||||
if let FollowUp::Chain { bank: b, .. } = follow_up {
|
if let FollowUp::Chain { bank: b, .. } = follow_up {
|
||||||
@@ -535,7 +532,6 @@ pub(super) fn handle_modal_input(ctx: &mut InputContext, key: KeyEvent) -> Input
|
|||||||
let length_val = length.parse().ok();
|
let length_val = length.parse().ok();
|
||||||
let speed_val = *speed;
|
let speed_val = *speed;
|
||||||
let quant_val = *quantization;
|
let quant_val = *quantization;
|
||||||
let sync_val = *sync_mode;
|
|
||||||
let follow_up_val = *follow_up;
|
let follow_up_val = *follow_up;
|
||||||
ctx.dispatch(AppCommand::StagePatternProps {
|
ctx.dispatch(AppCommand::StagePatternProps {
|
||||||
bank,
|
bank,
|
||||||
@@ -545,7 +541,6 @@ pub(super) fn handle_modal_input(ctx: &mut InputContext, key: KeyEvent) -> Input
|
|||||||
length: length_val,
|
length: length_val,
|
||||||
speed: speed_val,
|
speed: speed_val,
|
||||||
quantization: quant_val,
|
quantization: quant_val,
|
||||||
sync_mode: sync_val,
|
|
||||||
follow_up: follow_up_val,
|
follow_up: follow_up_val,
|
||||||
});
|
});
|
||||||
ctx.dispatch(AppCommand::CloseModal);
|
ctx.dispatch(AppCommand::CloseModal);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ pub use cagire_forth::{
|
|||||||
};
|
};
|
||||||
pub use cagire_project::{
|
pub use cagire_project::{
|
||||||
load, load_str, save, share, Bank, FollowUp, LaunchQuantization, Pattern, PatternSpeed,
|
load, load_str, save, share, Bank, FollowUp, LaunchQuantization, Pattern, PatternSpeed,
|
||||||
Project, SyncMode, MAX_BANKS, MAX_PATTERNS,
|
Project, MAX_BANKS, MAX_PATTERNS,
|
||||||
};
|
};
|
||||||
pub use script::ScriptEngine;
|
pub use script::ScriptEngine;
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ pub enum PatternPropsField {
|
|||||||
Length,
|
Length,
|
||||||
Speed,
|
Speed,
|
||||||
Quantization,
|
Quantization,
|
||||||
SyncMode,
|
|
||||||
FollowUp,
|
FollowUp,
|
||||||
ChainBank,
|
ChainBank,
|
||||||
ChainPattern,
|
ChainPattern,
|
||||||
@@ -44,8 +43,7 @@ impl PatternPropsField {
|
|||||||
Self::Description => Self::Length,
|
Self::Description => Self::Length,
|
||||||
Self::Length => Self::Speed,
|
Self::Length => Self::Speed,
|
||||||
Self::Speed => Self::Quantization,
|
Self::Speed => Self::Quantization,
|
||||||
Self::Quantization => Self::SyncMode,
|
Self::Quantization => Self::FollowUp,
|
||||||
Self::SyncMode => Self::FollowUp,
|
|
||||||
Self::FollowUp if follow_up_is_chain => Self::ChainBank,
|
Self::FollowUp if follow_up_is_chain => Self::ChainBank,
|
||||||
Self::FollowUp => Self::FollowUp,
|
Self::FollowUp => Self::FollowUp,
|
||||||
Self::ChainBank => Self::ChainPattern,
|
Self::ChainBank => Self::ChainPattern,
|
||||||
@@ -60,8 +58,7 @@ impl PatternPropsField {
|
|||||||
Self::Length => Self::Description,
|
Self::Length => Self::Description,
|
||||||
Self::Speed => Self::Length,
|
Self::Speed => Self::Length,
|
||||||
Self::Quantization => Self::Speed,
|
Self::Quantization => Self::Speed,
|
||||||
Self::SyncMode => Self::Quantization,
|
Self::FollowUp => Self::Quantization,
|
||||||
Self::FollowUp => Self::SyncMode,
|
|
||||||
Self::ChainBank => Self::FollowUp,
|
Self::ChainBank => Self::FollowUp,
|
||||||
Self::ChainPattern if follow_up_is_chain => Self::ChainBank,
|
Self::ChainPattern if follow_up_is_chain => Self::ChainBank,
|
||||||
Self::ChainPattern => Self::FollowUp,
|
Self::ChainPattern => Self::FollowUp,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::model::{self, FollowUp, LaunchQuantization, PatternSpeed, SyncMode};
|
use crate::model::{self, FollowUp, LaunchQuantization, PatternSpeed};
|
||||||
use crate::state::editor::{EuclideanField, PatternField, PatternPropsField, ScriptField};
|
use crate::state::editor::{EuclideanField, PatternField, PatternPropsField, ScriptField};
|
||||||
use crate::state::file_browser::FileBrowserState;
|
use crate::state::file_browser::FileBrowserState;
|
||||||
|
|
||||||
@@ -85,7 +85,6 @@ pub enum Modal {
|
|||||||
length: String,
|
length: String,
|
||||||
speed: PatternSpeed,
|
speed: PatternSpeed,
|
||||||
quantization: LaunchQuantization,
|
quantization: LaunchQuantization,
|
||||||
sync_mode: SyncMode,
|
|
||||||
follow_up: FollowUp,
|
follow_up: FollowUp,
|
||||||
},
|
},
|
||||||
KeybindingsHelp {
|
KeybindingsHelp {
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
use crate::engine::PatternChange;
|
use crate::engine::PatternChange;
|
||||||
use crate::model::{FollowUp, LaunchQuantization, PatternSpeed, SyncMode};
|
use crate::model::{FollowUp, LaunchQuantization, PatternSpeed};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct StagedChange {
|
pub struct StagedChange {
|
||||||
pub change: PatternChange,
|
pub change: PatternChange,
|
||||||
pub quantization: LaunchQuantization,
|
pub quantization: LaunchQuantization,
|
||||||
pub sync_mode: SyncMode,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
@@ -21,7 +20,6 @@ pub struct StagedPropChange {
|
|||||||
pub length: Option<usize>,
|
pub length: Option<usize>,
|
||||||
pub speed: PatternSpeed,
|
pub speed: PatternSpeed,
|
||||||
pub quantization: LaunchQuantization,
|
pub quantization: LaunchQuantization,
|
||||||
pub sync_mode: SyncMode,
|
|
||||||
pub follow_up: FollowUp,
|
pub follow_up: FollowUp,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -507,11 +507,7 @@ fn render_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, a
|
|||||||
};
|
};
|
||||||
let props_indicator = if has_staged_props { "~" } else { "" };
|
let props_indicator = if has_staged_props { "~" } else { "" };
|
||||||
let quant_sync = if is_selected {
|
let quant_sync = if is_selected {
|
||||||
format!(
|
format!("{} ", pattern.quantization.short_label())
|
||||||
"{}:{} ",
|
|
||||||
pattern.quantization.short_label(),
|
|
||||||
pattern.sync_mode.short_label()
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
@@ -755,8 +751,6 @@ fn render_properties(
|
|||||||
let steps_label = format!("{}/{}", content_count, pattern.length);
|
let steps_label = format!("{}/{}", content_count, pattern.length);
|
||||||
let speed_label = pattern.speed.label();
|
let speed_label = pattern.speed.label();
|
||||||
let quant_label = pattern.quantization.label();
|
let quant_label = pattern.quantization.label();
|
||||||
let sync_label = pattern.sync_mode.label();
|
|
||||||
|
|
||||||
let label_style = Style::new().fg(theme.ui.text_muted);
|
let label_style = Style::new().fg(theme.ui.text_muted);
|
||||||
let value_style = Style::new().fg(theme.ui.text_primary);
|
let value_style = Style::new().fg(theme.ui.text_primary);
|
||||||
|
|
||||||
@@ -781,10 +775,6 @@ fn render_properties(
|
|||||||
Span::styled(" Quant ", label_style),
|
Span::styled(" Quant ", label_style),
|
||||||
Span::styled(quant_label, value_style),
|
Span::styled(quant_label, value_style),
|
||||||
]),
|
]),
|
||||||
Line::from(vec![
|
|
||||||
Span::styled(" Sync ", label_style),
|
|
||||||
Span::styled(sync_label, value_style),
|
|
||||||
]),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if pattern.follow_up != FollowUp::Loop {
|
if pattern.follow_up != FollowUp::Loop {
|
||||||
|
|||||||
@@ -737,7 +737,6 @@ fn render_modal(
|
|||||||
length,
|
length,
|
||||||
speed,
|
speed,
|
||||||
quantization,
|
quantization,
|
||||||
sync_mode,
|
|
||||||
follow_up,
|
follow_up,
|
||||||
} => {
|
} => {
|
||||||
use crate::model::FollowUp;
|
use crate::model::FollowUp;
|
||||||
@@ -766,7 +765,6 @@ fn render_modal(
|
|||||||
("Length", length.clone(), *field == PatternPropsField::Length),
|
("Length", length.clone(), *field == PatternPropsField::Length),
|
||||||
("Speed", speed_label, *field == PatternPropsField::Speed),
|
("Speed", speed_label, *field == PatternPropsField::Speed),
|
||||||
("Quantization", quantization.label().to_string(), *field == PatternPropsField::Quantization),
|
("Quantization", quantization.label().to_string(), *field == PatternPropsField::Quantization),
|
||||||
("Sync Mode", sync_mode.label().to_string(), *field == PatternPropsField::SyncMode),
|
|
||||||
("Follow Up", follow_up_label, *field == PatternPropsField::FollowUp),
|
("Follow Up", follow_up_label, *field == PatternPropsField::FollowUp),
|
||||||
];
|
];
|
||||||
if is_chain {
|
if is_chain {
|
||||||
|
|||||||
Reference in New Issue
Block a user