MidiPlayer -> Sequencer; connect sequencer to sampler in groovebox mode

This commit is contained in:
🪞👃🪞 2025-05-10 19:08:22 +03:00
parent c5586c3a35
commit 5fab1af138
10 changed files with 120 additions and 81 deletions

View file

@ -469,7 +469,7 @@ impl<'state> Context<'state, SamplerCommand> for App {
Ok(None) Ok(None)
} }
fn stop (app: &mut App, index: usize) -> Perhaps<Self> { fn stop (app: &mut App, index: usize) -> Perhaps<Self> {
app.tracks[index].player.enqueue_next(None); app.tracks[index].sequencer.enqueue_next(None);
Ok(None) Ok(None)
} }
fn add (app: &mut App) -> Perhaps<Self> { fn add (app: &mut App) -> Perhaps<Self> {
@ -535,7 +535,7 @@ impl<'state> Context<'state, SamplerCommand> for App {
} }
fn enqueue (app: &mut App, a: usize, b: usize) -> Perhaps<Self> { fn enqueue (app: &mut App, a: usize, b: usize) -> Perhaps<Self> {
//(Enqueue [t: usize, s: usize] //(Enqueue [t: usize, s: usize]
//cmd!(app.tracks[t].player.enqueue_next(app.scenes[s].clips[t].as_ref()))) //cmd!(app.tracks[t].sequencer.enqueue_next(app.scenes[s].clips[t].as_ref())))
//("enqueue" [a: usize, b: usize] Some(Self::Enqueue(a.unwrap(), b.unwrap()))) //("enqueue" [a: usize, b: usize] Some(Self::Enqueue(a.unwrap(), b.unwrap())))
todo!() todo!()
} }

View file

@ -57,7 +57,7 @@ audio!(
// Update track sequencers and devices // Update track sequencers and devices
for track in self.tracks.iter_mut() { for track in self.tracks.iter_mut() {
if Control::Quit == PlayerAudio( if Control::Quit == PlayerAudio(
track.player_mut(), &mut self.note_buf, &mut self.midi_buf track.sequencer_mut(), &mut self.note_buf, &mut self.midi_buf
).process(client, scope) { ).process(client, scope) {
return Control::Quit return Control::Quit
} }

View file

@ -102,7 +102,7 @@ impl App {
let mut track = Track { let mut track = Track {
width: (name.len() + 2).max(12), width: (name.len() + 2).max(12),
color: color.unwrap_or_else(ItemTheme::random), color: color.unwrap_or_else(ItemTheme::random),
player: MidiPlayer::new( sequencer: Sequencer::new(
&format!("{name}"), &format!("{name}"),
self.jack(), self.jack(),
Some(self.clock()), Some(self.clock()),
@ -141,7 +141,7 @@ impl App {
let exists = self.tracks().get(index).is_some(); let exists = self.tracks().get(index).is_some();
if exists { if exists {
let track = self.tracks_mut().remove(index); let track = self.tracks_mut().remove(index);
let Track { player: MidiPlayer { midi_ins, midi_outs, .. }, .. } = track; let Track { sequencer: Sequencer { midi_ins, midi_outs, .. }, .. } = track;
for port in midi_ins.into_iter() { for port in midi_ins.into_iter() {
port.close()?; port.close()?;
} }
@ -196,7 +196,7 @@ impl App {
/// Enqueue clips from a scene across all tracks /// Enqueue clips from a scene across all tracks
pub fn scene_enqueue (&mut self, scene: usize) { pub fn scene_enqueue (&mut self, scene: usize) {
for track in 0..self.tracks.len() { for track in 0..self.tracks.len() {
self.tracks[track].player.enqueue_next(self.scenes[scene].clips[track].as_ref()); self.tracks[track].sequencer.enqueue_next(self.scenes[scene].clips[track].as_ref());
} }
} }
@ -315,7 +315,7 @@ impl App {
/// Stop all playing clips /// Stop all playing clips
pub(crate) fn stop_all (&mut self) { pub(crate) fn stop_all (&mut self) {
for track in 0..self.tracks.len() { for track in 0..self.tracks.len() {
self.tracks[track].player.enqueue_next(None); self.tracks[track].sequencer.enqueue_next(None);
} }
} }
@ -324,14 +324,14 @@ impl App {
use Selection::*; use Selection::*;
match self.selected { match self.selected {
Track(t) => { Track(t) => {
self.tracks[t].player.enqueue_next(None) self.tracks[t].sequencer.enqueue_next(None)
}, },
TrackClip { track, scene } => { TrackClip { track, scene } => {
self.tracks[track].player.enqueue_next(self.scenes[scene].clips[track].as_ref()) self.tracks[track].sequencer.enqueue_next(self.scenes[scene].clips[track].as_ref())
}, },
Scene(s) => { Scene(s) => {
for t in 0..self.tracks.len() { for t in 0..self.tracks.len() {
self.tracks[t].player.enqueue_next(self.scenes[s].clips[t].as_ref()) self.tracks[t].sequencer.enqueue_next(self.scenes[s].clips[t].as_ref())
} }
}, },
_ => {} _ => {}
@ -417,7 +417,7 @@ impl App {
fn device_add_sampler (&mut self) -> Usually<()> { fn device_add_sampler (&mut self) -> Usually<()> {
let name = self.jack.with_client(|c|c.name().to_string()); let name = self.jack.with_client(|c|c.name().to_string());
let midi = self.track().expect("no active track").player.midi_outs[0].name(); let midi = self.track().expect("no active track").sequencer.midi_outs[0].name();
let sampler = if let Ok(sampler) = Sampler::new( let sampler = if let Ok(sampler) = Sampler::new(
&self.jack, &self.jack,
&format!("{}/Sampler", &self.track().expect("no active track").name), &format!("{}/Sampler", &self.track().expect("no active track").name),

View file

@ -24,7 +24,7 @@ impl Scene {
Some(c) => tracks Some(c) => tracks
.get(track_index) .get(track_index)
.map(|track|{ .map(|track|{
if let Some((_, Some(clip))) = track.player().play_clip() { if let Some((_, Some(clip))) = track.sequencer().play_clip() {
*clip.read().unwrap() == *c.read().unwrap() *clip.read().unwrap() == *c.read().unwrap()
} else { } else {
false false

View file

@ -7,8 +7,8 @@ use crate::*;
pub width: usize, pub width: usize,
/// Identifying color of track /// Identifying color of track
pub color: ItemTheme, pub color: ItemTheme,
/// MIDI player state /// MIDI sequencer state
pub player: MidiPlayer, pub sequencer: Sequencer,
/// Device chain /// Device chain
pub devices: Vec<Device>, pub devices: Vec<Device>,
/// Inputs of 1st device /// Inputs of 1st device
@ -17,52 +17,60 @@ use crate::*;
pub audio_outs: Vec<JackAudioOut>, pub audio_outs: Vec<JackAudioOut>,
} }
has_clock!(|self: Track|self.player.clock); has_clock!(|self: Track|self.sequencer.clock);
has_player!(|self: Track|self.player); has_sequencer!(|self: Track|self.sequencer);
impl Track { impl Track {
pub const MIN_WIDTH: usize = 9; /// Create a new track with only the default [Sequencer].
/// Create a new track with only the default [MidiPlayer]. pub fn new (
pub fn new () -> Self { name: &impl AsRef<str>,
Self::default() color: Option<ItemTheme>,
jack: &Jack,
clock: Option<&Clock>,
midi_from: &[PortConnect],
midi_to: &[PortConnect],
) -> Usually<Self> {
Ok(Self {
name: name.as_ref().into(),
color: color.unwrap_or_default(),
sequencer: Sequencer::new(
format!("{}/sequencer", name.as_ref()),
jack,
clock,
None,
midi_from,
midi_to
)?,
..Default::default()
})
} }
/// Create a new track connecting the [MidiPlayer] to a [Sampler]. /// Create a new track connecting the [Sequencer] to a [Sampler].
pub fn new_with_sampler ( pub fn new_with_sampler (
name: &impl AsRef<str>,
color: Option<ItemTheme>,
jack: &Jack, jack: &Jack,
clock: Option<&Clock>,
midi_from: &[PortConnect], midi_from: &[PortConnect],
midi_to: &[PortConnect],
audio_from: &[&[PortConnect];2], audio_from: &[&[PortConnect];2],
audio_to: &[&[PortConnect];2], audio_to: &[&[PortConnect];2],
) -> Usually<Self> { ) -> Usually<Self> {
let mut track = Self::new_sequencer(); let mut track = Self::new(
let name = jack.with_client(|c|c.name().to_string()); name, color, jack, clock, midi_from, midi_to
let midi = track.player.midi_outs[0].name(); )?;
let port = PortConnect::exact(format!("{name}:{midi}")); track.devices.push(Device::Sampler(Sampler::new(
let sampler = Sampler::new(jack, &"sampler", &[port], audio_from, audio_to)?; jack,
track.devices.push(Device::Sampler(sampler)); &"sampler",
&[PortConnect::exact(format!("{}:{}",
jack.with_client(|c|c.name().to_string()),
track.sequencer.midi_outs[0].name()
))],
audio_from,
audio_to
)?));
Ok(track) Ok(track)
} }
pub fn width_inc (&mut self) {
self.width += 1;
}
pub fn width_dec (&mut self) {
if self.width > Track::MIN_WIDTH {
self.width -= 1;
}
}
pub fn sequencer (&self, mut nth: usize) -> Option<&MidiPlayer> {
for device in self.devices.iter() {
match device {
Device::Sequencer(s) => if nth == 0 {
return Some(s);
} else {
nth -= 1;
},
_ => {}
}
}
None
}
pub fn sampler (&self, mut nth: usize) -> Option<&Sampler> { pub fn sampler (&self, mut nth: usize) -> Option<&Sampler> {
for device in self.devices.iter() { for device in self.devices.iter() {
match device { match device {
@ -91,6 +99,26 @@ impl Track {
} }
} }
pub trait HasWidth {
const MIN_WIDTH: usize;
/// Increment track width.
fn width_inc (&mut self);
/// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH].
fn width_dec (&mut self);
}
impl HasWidth for Track {
const MIN_WIDTH: usize = 9;
fn width_inc (&mut self) {
self.width += 1;
}
fn width_dec (&mut self) {
if self.width > Track::MIN_WIDTH {
self.width -= 1;
}
}
}
pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync { pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
fn midi_ins (&self) -> &Vec<JackMidiIn>; fn midi_ins (&self) -> &Vec<JackMidiIn>;
fn midi_outs (&self) -> &Vec<JackMidiOut>; fn midi_outs (&self) -> &Vec<JackMidiOut>;
@ -117,14 +145,14 @@ pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync
fn track_toggle_record (&mut self) { fn track_toggle_record (&mut self) {
if let Some(t) = self.selected().track() { if let Some(t) = self.selected().track() {
let tracks = self.tracks_mut(); let tracks = self.tracks_mut();
tracks[t-1].player.recording = !tracks[t-1].player.recording; tracks[t-1].sequencer.recording = !tracks[t-1].sequencer.recording;
} }
} }
/// Toggle track monitoring /// Toggle track monitoring
fn track_toggle_monitor (&mut self) { fn track_toggle_monitor (&mut self) {
if let Some(t) = self.selected().track() { if let Some(t) = self.selected().track() {
let tracks = self.tracks_mut(); let tracks = self.tracks_mut();
tracks[t-1].player.monitoring = !tracks[t-1].player.monitoring; tracks[t-1].sequencer.monitoring = !tracks[t-1].sequencer.monitoring;
} }
} }
} }

View file

@ -336,8 +336,8 @@ impl<'a> ArrangerView<'a> {
self.width_mid, self.width_mid,
||self.tracks_with_sizes_scrolled(), ||self.tracks_with_sizes_scrolled(),
move|t, track|{ move|t, track|{
let rec = track.player.recording; let rec = track.sequencer.recording;
let mon = track.player.monitoring; let mon = track.sequencer.monitoring;
let rec = if rec { White } else { track.color.darkest.rgb }; let rec = if rec { White } else { track.color.darkest.rgb };
let mon = if mon { White } else { track.color.darkest.rgb }; let mon = if mon { White } else { track.color.darkest.rgb };
let bg = if self.track_selected == Some(t) { let bg = if self.track_selected == Some(t) {
@ -377,10 +377,10 @@ impl<'a> ArrangerView<'a> {
let label = Align::ne("Next clip:"); let label = Align::ne("Next clip:");
Tryptich::top(2).left(self.width_side, label).middle(self.width_mid, per_track_top( Tryptich::top(2).left(self.width_side, label).middle(self.width_mid, per_track_top(
self.width_mid, ||self.tracks_with_sizes_scrolled(), |t, track|{ self.width_mid, ||self.tracks_with_sizes_scrolled(), |t, track|{
let queued = track.player.next_clip.is_some(); let queued = track.sequencer.next_clip.is_some();
let queued_blank = Thunk::new(||Tui::bg(Reset, " ------ ")); let queued_blank = Thunk::new(||Tui::bg(Reset, " ------ "));
let queued_clip = Thunk::new(||{ let queued_clip = Thunk::new(||{
Tui::bg(Reset, if let Some((_, clip)) = track.player.next_clip.as_ref() { Tui::bg(Reset, if let Some((_, clip)) = track.sequencer.next_clip.as_ref() {
if let Some(clip) = clip { if let Some(clip) = clip {
clip.read().unwrap().name.clone() clip.read().unwrap().name.clone()
} else { } else {
@ -1224,7 +1224,7 @@ impl std::fmt::Debug for PianoHorizontal {
} }
// Update sequencer playhead indicator // Update sequencer playhead indicator
//self.now().set(0.); //self.now().set(0.);
//if let Some((ref started_at, Some(ref playing))) = self.player.play_clip { //if let Some((ref started_at, Some(ref playing))) = self.sequencer.play_clip {
//let clip = clip.read().unwrap(); //let clip = clip.read().unwrap();
//if *playing.read().unwrap() == *clip { //if *playing.read().unwrap() == *clip {
//let pulse = self.current().pulse.get(); //let pulse = self.current().pulse.get();

View file

@ -136,11 +136,22 @@ impl Cli {
}, },
tracks: match mode { tracks: match mode {
LaunchMode::Sequencer => vec![ LaunchMode::Sequencer => vec![
Track::new() Track::new(
&name,
None,
jack,
None,
midi_froms.as_slice(),
midi_tos.as_slice()
)?
], ],
LaunchMode::Groovebox | LaunchMode::Sampler => vec![ LaunchMode::Groovebox | LaunchMode::Sampler => vec![
Track::new_with_sampler( Track::new_with_sampler(
&name,
None,
jack, jack,
None,
midi_froms.as_slice(),
midi_froms.as_slice(), midi_froms.as_slice(),
audio_froms, audio_froms,
audio_tos, audio_tos,

View file

@ -2,7 +2,7 @@ use crate::*;
#[derive(Debug)] #[derive(Debug)]
pub enum Device { pub enum Device {
#[cfg(feature = "sequencer")] Sequencer(MidiPlayer), #[cfg(feature = "sequencer")] Sequencer(Sequencer),
#[cfg(feature = "sampler")] Sampler(Sampler), #[cfg(feature = "sampler")] Sampler(Sampler),
#[cfg(feature = "lv2")] Lv2(Lv2), // TODO #[cfg(feature = "lv2")] Lv2(Lv2), // TODO
#[cfg(feature = "vst2")] Vst2, // TODO #[cfg(feature = "vst2")] Vst2, // TODO

View file

@ -22,6 +22,6 @@ mod seq_view; pub use self::seq_view::*;
} }
#[cfg(test)] #[test] fn test_midi_play () { #[cfg(test)] #[test] fn test_midi_play () {
let player = MidiPlayer::default(); let sequencer = Sequencer::default();
println!("{player:?}"); println!("{sequencer:?}");
} }

View file

@ -1,27 +1,27 @@
//! MIDI player //! MIDI sequencer
use crate::*; use crate::*;
use tek_engine::jack::*; use tek_engine::jack::*;
pub trait HasPlayer { pub trait HasSequencer {
fn player (&self) -> &impl MidiPlayerApi; fn sequencer (&self) -> &impl MidiPlayerApi;
fn player_mut (&mut self) -> &mut impl MidiPlayerApi; fn sequencer_mut (&mut self) -> &mut impl MidiPlayerApi;
} }
#[macro_export] macro_rules! has_player { #[macro_export] macro_rules! has_sequencer {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasPlayer for $Struct $(<$($L),*$($T),*>)? { impl $(<$($L),*$($T $(: $U)?),*>)? HasSequencer for $Struct $(<$($L),*$($T),*>)? {
fn player (&$self) -> &impl MidiPlayerApi { &$cb } fn sequencer (&$self) -> &impl MidiPlayerApi { &$cb }
fn player_mut (&mut $self) -> &mut impl MidiPlayerApi { &mut$cb } fn sequencer_mut (&mut $self) -> &mut impl MidiPlayerApi { &mut$cb }
} }
} }
} }
pub trait MidiPlayerApi: MidiRecordApi + MidiPlaybackApi + Send + Sync {} pub trait MidiPlayerApi: MidiRecordApi + MidiPlaybackApi + Send + Sync {}
impl MidiPlayerApi for MidiPlayer {} impl MidiPlayerApi for Sequencer {}
/// Contains state for playing a clip /// Contains state for playing a clip
pub struct MidiPlayer { pub struct Sequencer {
/// State of clock and playhead /// State of clock and playhead
pub clock: Clock, pub clock: Clock,
/// Start time and clip being played /// Start time and clip being played
@ -48,7 +48,7 @@ pub struct MidiPlayer {
pub note_buf: Vec<u8>, pub note_buf: Vec<u8>,
} }
impl Default for MidiPlayer { impl Default for Sequencer {
fn default () -> Self { fn default () -> Self {
Self { Self {
play_clip: None, play_clip: None,
@ -69,7 +69,7 @@ impl Default for MidiPlayer {
} }
} }
impl MidiPlayer { impl Sequencer {
pub fn new ( pub fn new (
name: impl AsRef<str>, name: impl AsRef<str>,
jack: &Jack, jack: &Jack,
@ -97,9 +97,9 @@ impl MidiPlayer {
} }
} }
impl std::fmt::Debug for MidiPlayer { impl std::fmt::Debug for Sequencer {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("MidiPlayer") f.debug_struct("Sequencer")
.field("clock", &self.clock) .field("clock", &self.clock)
.field("play_clip", &self.play_clip) .field("play_clip", &self.play_clip)
.field("next_clip", &self.next_clip) .field("next_clip", &self.next_clip)
@ -107,20 +107,20 @@ impl std::fmt::Debug for MidiPlayer {
} }
} }
has_clock!(|self: MidiPlayer|self.clock); has_clock!(|self: Sequencer|self.clock);
impl HasMidiIns for MidiPlayer { impl HasMidiIns for Sequencer {
fn midi_ins (&self) -> &Vec<JackMidiIn> { &self.midi_ins } fn midi_ins (&self) -> &Vec<JackMidiIn> { &self.midi_ins }
fn midi_ins_mut (&mut self) -> &mut Vec<JackMidiIn> { &mut self.midi_ins } fn midi_ins_mut (&mut self) -> &mut Vec<JackMidiIn> { &mut self.midi_ins }
} }
impl HasMidiOuts for MidiPlayer { impl HasMidiOuts for Sequencer {
fn midi_outs (&self) -> &Vec<JackMidiOut> { &self.midi_outs } fn midi_outs (&self) -> &Vec<JackMidiOut> { &self.midi_outs }
fn midi_outs_mut (&mut self) -> &mut Vec<JackMidiOut> { &mut self.midi_outs } fn midi_outs_mut (&mut self) -> &mut Vec<JackMidiOut> { &mut self.midi_outs }
fn midi_note (&mut self) -> &mut Vec<u8> { &mut self.note_buf } fn midi_note (&mut self) -> &mut Vec<u8> { &mut self.note_buf }
} }
/// Hosts the JACK callback for a single MIDI player /// Hosts the JACK callback for a single MIDI sequencer
pub struct PlayerAudio<'a, T: MidiPlayerApi>( pub struct PlayerAudio<'a, T: MidiPlayerApi>(
/// Player /// Player
pub &'a mut T, pub &'a mut T,
@ -130,7 +130,7 @@ pub struct PlayerAudio<'a, T: MidiPlayerApi>(
pub &'a mut Vec<Vec<Vec<u8>>>, pub &'a mut Vec<Vec<Vec<u8>>>,
); );
/// JACK process callback for a sequencer's clip player/recorder. /// JACK process callback for a sequencer's clip sequencer/recorder.
impl<T: MidiPlayerApi> Audio for PlayerAudio<'_, T> { impl<T: MidiPlayerApi> Audio for PlayerAudio<'_, T> {
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
let model = &mut self.0; let model = &mut self.0;
@ -157,7 +157,7 @@ impl<T: MidiPlayerApi> Audio for PlayerAudio<'_, T> {
} }
} }
impl MidiRecordApi for MidiPlayer { impl MidiRecordApi for Sequencer {
fn recording (&self) -> bool { fn recording (&self) -> bool {
self.recording self.recording
} }
@ -181,13 +181,13 @@ impl MidiRecordApi for MidiPlayer {
} }
} }
impl MidiPlaybackApi for MidiPlayer { impl MidiPlaybackApi for Sequencer {
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> { fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
&self.notes_out &self.notes_out
} }
} }
impl HasPlayClip for MidiPlayer { impl HasPlayClip for Sequencer {
fn reset (&self) -> bool { fn reset (&self) -> bool {
self.reset self.reset
} }