more sensible port creation

This commit is contained in:
🪞👃🪞 2025-01-22 00:10:31 +01:00
parent 4028b3bb29
commit bbe49ad463
7 changed files with 133 additions and 125 deletions

View file

@ -62,20 +62,28 @@ impl Default for MidiPlayer {
} }
impl MidiPlayer { impl MidiPlayer {
pub fn new ( pub fn new (
jack: &Jack, name: impl AsRef<str>,
name: impl AsRef<str>, jack: &Jack,
clip: Option<&Arc<RwLock<MidiClip>>>, clock: Option<&Clock>,
clip: Option<&Arc<RwLock<MidiClip>>>,
midi_from: &[PortConnect], midi_from: &[PortConnect],
midi_to: &[PortConnect], midi_to: &[PortConnect],
) -> Usually<Self> { ) -> Usually<Self> {
let name = name.as_ref(); let name = name.as_ref();
let clock = Clock::from(jack); let clock = clock.cloned().unwrap_or_default();
Ok(Self { Ok(Self {
midi_ins: vec![JackMidiIn::new(jack, format!("M/{name}"), midi_from)?,], midi_ins: vec![JackMidiIn::new(jack, format!("M/{name}"), midi_from)?,],
midi_outs: vec![JackMidiOut::new(jack, format!("{name}/M"), midi_to)?, ], midi_outs: vec![JackMidiOut::new(jack, format!("{name}/M"), midi_to)?, ],
play_clip: Some((Moment::zero(&clock.timebase), clip.cloned())), play_clip: clip.map(|clip|(Moment::zero(&clock.timebase), Some(clip.clone()))),
clock, clock,
..Default::default() note_buf: vec![0;8],
reset: true,
recording: false,
monitoring: false,
overdub: false,
next_clip: None,
notes_in: RwLock::new([false;128]).into(),
notes_out: RwLock::new([false;128]).into(),
}) })
} }
} }
@ -88,26 +96,6 @@ impl std::fmt::Debug for MidiPlayer {
.finish() .finish()
} }
} }
from!(|clock: &Clock| MidiPlayer = Self {
clock: clock.clone(),
midi_ins: vec![],
midi_outs: vec![],
note_buf: vec![0;8],
reset: true,
recording: false,
monitoring: false,
overdub: false,
play_clip: None,
next_clip: None,
notes_in: RwLock::new([false;128]).into(),
notes_out: RwLock::new([false;128]).into(),
});
from!(|state: (&Clock, &Arc<RwLock<MidiClip>>)|MidiPlayer = {
let (clock, clip) = state;
let mut model = Self::from(clock);
model.play_clip = Some((Moment::zero(&clock.timebase), Some(clip.clone())));
model
});
has_clock!(|self: MidiPlayer|self.clock); has_clock!(|self: MidiPlayer|self.clock);
impl HasMidiIns for MidiPlayer { impl HasMidiIns for MidiPlayer {
fn midi_ins (&self) -> &Vec<JackMidiIn> { &self.midi_ins } fn midi_ins (&self) -> &Vec<JackMidiIn> { &self.midi_ins }

View file

@ -1,7 +1,6 @@
use crate::*; use crate::*;
impl HasJack for Tek { fn jack (&self) -> &Jack { &self.jack } } impl HasJack for Tek { fn jack (&self) -> &Jack { &self.jack } }
audio!( audio!(
|self: Tek, client, scope|{ |self: Tek, client, scope|{
// Start profiling cycle // Start profiling cycle
let t0 = self.perf.get_t0(); let t0 = self.perf.get_t0();
@ -14,20 +13,20 @@ audio!(
.collect::<Vec<_>>()) .collect::<Vec<_>>())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// Update standalone MIDI sequencer // Update standalone MIDI sequencer
if let Some(player) = self.player.as_mut() { //if let Some(player) = self.player.as_mut() {
if Control::Quit == PlayerAudio( //if Control::Quit == PlayerAudio(
player, //player,
&mut self.note_buf, //&mut self.note_buf,
&mut self.midi_buf, //&mut self.midi_buf,
).process(client, scope) { //).process(client, scope) {
return Control::Quit //return Control::Quit
} //}
} //}
// Update standalone sampler // Update standalone sampler
if let Some(sampler) = self.sampler.as_mut() { //if let Some(sampler) = self.sampler.as_mut() {
if Control::Quit == SamplerAudio(sampler).process(client, scope) { //if Control::Quit == SamplerAudio(sampler).process(client, scope) {
return Control::Quit //return Control::Quit
} //}
//for port in midi_in.iter() { //for port in midi_in.iter() {
//for message in port.iter() { //for message in port.iter() {
//match message { //match message {
@ -35,30 +34,30 @@ audio!(
//} //}
//} //}
//} //}
} //}
// TODO move these to editor and sampler?: // TODO move these to editor and sampler?:
for port in midi_in.iter() { //for port in midi_in.iter() {
for event in port.iter() { //for event in port.iter() {
match event { //match event {
(time, Ok(LiveEvent::Midi {message, ..})) => match message { //(time, Ok(LiveEvent::Midi {message, ..})) => match message {
MidiMessage::NoteOn {ref key, ..} if let Some(editor) = self.editor.as_ref() => { //MidiMessage::NoteOn {ref key, ..} if let Some(editor) = self.editor.as_ref() => {
editor.set_note_pos(key.as_int() as usize); //editor.set_note_pos(key.as_int() as usize);
}, //},
MidiMessage::Controller {controller, value} if let (Some(editor), Some(sampler)) = ( //MidiMessage::Controller {controller, value} if let (Some(editor), Some(sampler)) = (
self.editor.as_ref(), //self.editor.as_ref(),
self.sampler.as_ref(), //self.sampler.as_ref(),
) => { //) => {
// TODO: give sampler its own cursor //// TODO: give sampler its own cursor
if let Some(sample) = &sampler.mapped[editor.note_pos()] { //if let Some(sample) = &sampler.mapped[editor.note_pos()] {
sample.write().unwrap().handle_cc(*controller, *value) //sample.write().unwrap().handle_cc(*controller, *value)
} //}
} //}
_ =>{} //_ =>{}
}, //},
_ =>{} //_ =>{}
} //}
} //}
} //}
// Update track sequencers // Update track sequencers
for track in self.tracks.iter_mut() { for track in self.tracks.iter_mut() {
if PlayerAudio( if PlayerAudio(
@ -71,20 +70,19 @@ audio!(
self.perf.update(t0, scope); self.perf.update(t0, scope);
Control::Continue Control::Continue
}; };
|self, event|{ |self, event|{
use JackEvent::*; use JackEvent::*;
match event { match event {
SampleRate(sr) => SampleRate(sr) =>
{ self.clock.timebase.sr.set(sr as f64); }, { self.clock.timebase.sr.set(sr as f64); },
PortRegistration(id, true) => PortRegistration(id, true) =>
{}, { println!("\rport add: {id}") },
PortRegistration(id, false) => PortRegistration(id, false) =>
{}, { println!("\rport del: {id}") },
PortsConnected(a, b, true) => PortsConnected(a, b, true) =>
{}, { println!("\rport conn: {a} {b}") },
PortsConnected(a, b, false) => PortsConnected(a, b, false) =>
{}, { println!("\rport disc: {a} {b}") },
ClientRegistration(id, true) => ClientRegistration(id, true) =>
{}, {},
ClientRegistration(id, false) => ClientRegistration(id, false) =>
@ -93,6 +91,8 @@ audio!(
{}, {},
XRun => XRun =>
{}, {},
GraphReorder =>
{},
_ => { panic!("{event:?}"); } _ => { panic!("{event:?}"); }
} }
} }

View file

@ -103,9 +103,9 @@ impl Tek {
view: SourceIter(include_str!("./view_transport.edn")), view: SourceIter(include_str!("./view_transport.edn")),
jack: jack.clone(), jack: jack.clone(),
color: ItemPalette::random(), color: ItemPalette::random(),
clock: Clock::new(jack, bpm), clock: Clock::new(jack, bpm)?,
midi_ins: vec![JackMidiIn::new(jack, "GlobalI", midi_froms)?], midi_ins: vec![],
midi_outs: vec![JackMidiOut::new(jack, "GlobalO", midi_tos)?], midi_outs: vec![],
keys: SourceIter(KEYS_APP), keys: SourceIter(KEYS_APP),
keys_clip: SourceIter(KEYS_CLIP), keys_clip: SourceIter(KEYS_CLIP),
keys_track: SourceIter(KEYS_TRACK), keys_track: SourceIter(KEYS_TRACK),
@ -129,14 +129,16 @@ impl Tek {
) -> Usually<Self> { ) -> Usually<Self> {
let clip = MidiClip::new("Clip", true, 384usize, None, Some(ItemColor::random().into())); let clip = MidiClip::new("Clip", true, 384usize, None, Some(ItemColor::random().into()));
let clip = Arc::new(RwLock::new(clip)); let clip = Arc::new(RwLock::new(clip));
let this = Self::new_clock(jack, bpm, sync_lead, sync_follow, midi_froms, midi_tos)?;
Ok(Self { Ok(Self {
view: SourceIter(include_str!("./view_sequencer.edn")), view: SourceIter(include_str!("./view_sequencer.edn")),
pool: Some((&clip).into()), pool: Some((&clip).into()),
editor: Some((&clip).into()), editor: Some((&clip).into()),
editing: false.into(), editing: false.into(),
midi_buf: vec![vec![];65536], midi_buf: vec![vec![];65536],
player: Some(MidiPlayer::new(&jack, "sequencer", Some(&clip), &midi_froms, &midi_tos)?), tracks: vec![Track::default()],
..Self::new_clock(jack, bpm, sync_lead, sync_follow, midi_froms, midi_tos)? //player: Some(MidiPlayer::new("sequencer", &jack, Some(&this.clock), Some(&clip), &midi_froms, &midi_tos)?),
..this
}) })
} }
pub fn new_groovebox ( pub fn new_groovebox (
@ -147,12 +149,15 @@ impl Tek {
) -> Usually<Self> { ) -> Usually<Self> {
let app = Self { let app = Self {
view: SourceIter(include_str!("./view_groovebox.edn")), view: SourceIter(include_str!("./view_groovebox.edn")),
sampler: Some(Sampler::new(jack, &"sampler", midi_froms, audio_froms, audio_tos)?), tracks: vec![Track {
devices: vec![Sampler::new(jack, &"sampler", midi_froms, audio_froms, audio_tos)?.boxed()],
..Track::default()
}],
..Self::new_sequencer(jack, bpm, sync_lead, sync_follow, midi_froms, midi_tos)? ..Self::new_sequencer(jack, bpm, sync_lead, sync_follow, midi_froms, midi_tos)?
}; };
if let Some(sampler) = app.sampler.as_ref().unwrap().midi_in.as_ref() { //if let Some(sampler) = app.sampler.as_ref().unwrap().midi_in.as_ref() {
app.player.as_ref().unwrap().midi_outs[0].connect_to(sampler.port())?; //app.player.as_ref().unwrap().midi_outs[0].connect_to(sampler.port())?;
} //}
Ok(app) Ok(app)
} }
pub fn new_arranger ( pub fn new_arranger (
@ -164,10 +169,11 @@ impl Tek {
) -> Usually<Self> { ) -> Usually<Self> {
let mut arranger = Self { let mut arranger = Self {
view: SourceIter(include_str!("./view_arranger.edn")), view: SourceIter(include_str!("./view_arranger.edn")),
..Self::new_groovebox( pool: Some(Default::default()),
jack, bpm, sync_lead, sync_follow, editor: Some(Default::default()),
midi_froms, midi_tos, audio_froms, audio_tos, editing: false.into(),
)? midi_buf: vec![vec![];65536],
..Self::new_clock(jack, bpm, sync_lead, sync_follow, midi_froms, midi_tos)?
}; };
arranger.scenes_add(scenes); arranger.scenes_add(scenes);
arranger.tracks_add(tracks, track_width, midi_froms, midi_tos); arranger.tracks_add(tracks, track_width, midi_froms, midi_tos);

View file

@ -69,8 +69,8 @@ atom_command!(TekCommand: |app: Tek| {
MidiEditCommand::try_from_expr(app.editor.as_ref().expect("no editor"), a).expect("invalid command"))) MidiEditCommand::try_from_expr(app.editor.as_ref().expect("no editor"), a).expect("invalid command")))
("pool" [,..a] Self::Pool( ("pool" [,..a] Self::Pool(
PoolCommand::try_from_expr(app.pool.as_ref().expect("no pool"), a).expect("invalid command"))) PoolCommand::try_from_expr(app.pool.as_ref().expect("no pool"), a).expect("invalid command")))
("sampler" [,..a] Self::Sampler( //("sampler" [,..a] Self::Sampler(
SamplerCommand::try_from_expr(app.sampler.as_ref().expect("no sampler"), a).expect("invalid command"))) //SamplerCommand::try_from_expr(app.sampler().as_ref().expect("no sampler"), a).expect("invalid command")))
("scene" [,..a] Self::Scene( ("scene" [,..a] Self::Scene(
SceneCommand::try_from_expr(app, a).expect("invalid command"))) SceneCommand::try_from_expr(app, a).expect("invalid command")))
("track" [,..a] Self::Track( ("track" [,..a] Self::Track(
@ -133,10 +133,10 @@ command!(|self: TekCommand, app: Tek|match self {
Self::Clip(cmd) => cmd.delegate(app, Self::Clip)?, Self::Clip(cmd) => cmd.delegate(app, Self::Clip)?,
Self::Editor(cmd) => app.editor.as_mut() Self::Editor(cmd) => app.editor.as_mut()
.map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(), .map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(),
Self::Sampler(cmd) => app.sampler.as_mut() //Self::Sampler(cmd) => app.sampler.as_mut()
.map(|sampler|cmd.delegate(sampler, Self::Sampler)).transpose()?.flatten(), //.map(|sampler|cmd.delegate(sampler, Self::Sampler)).transpose()?.flatten(),
Self::Enqueue(clip) => app.player.as_mut() //Self::Enqueue(clip) => app.player.as_mut()
.map(|player|{player.enqueue_next(clip.as_ref());None}).flatten(), //.map(|player|{player.enqueue_next(clip.as_ref());None}).flatten(),
Self::Color(palette) => { Self::Color(palette) => {
use Selection::*; use Selection::*;
Some(Self::Color(match app.selected { Some(Self::Color(match app.selected {
@ -190,6 +190,7 @@ command!(|self: TekCommand, app: Tek|match self {
} else { } else {
None None
}, },
_ => todo!()
}); });
#[derive(Clone, Debug)] pub enum TrackCommand { #[derive(Clone, Debug)] pub enum TrackCommand {
Add, Add,

View file

@ -8,8 +8,6 @@ use crate::*;
pub color: ItemPalette, pub color: ItemPalette,
pub pool: Option<MidiPool>, pub pool: Option<MidiPool>,
pub editor: Option<MidiEditor>, pub editor: Option<MidiEditor>,
pub player: Option<MidiPlayer>,
pub sampler: Option<Sampler>,
pub midi_buf: Vec<Vec<Vec<u8>>>, pub midi_buf: Vec<Vec<Vec<u8>>>,
pub midi_ins: Vec<JackMidiIn>, pub midi_ins: Vec<JackMidiIn>,
pub midi_outs: Vec<JackMidiOut>, pub midi_outs: Vec<JackMidiOut>,
@ -39,9 +37,9 @@ use crate::*;
has_size!(<TuiOut>|self: Tek|&self.size); has_size!(<TuiOut>|self: Tek|&self.size);
has_clock!(|self: Tek|self.clock); has_clock!(|self: Tek|self.clock);
has_clips!(|self: Tek|self.pool.as_ref().expect("no clip pool").clips); has_clips!(|self: Tek|self.pool.as_ref().expect("no clip pool").clips);
has_sampler!(|self: Tek|{ //has_sampler!(|self: Tek|{
sampler = self.sampler; //sampler = self.sampler;
index = self.editor.as_ref().map(|e|e.note_pos()).unwrap_or(0); }); //index = self.editor.as_ref().map(|e|e.note_pos()).unwrap_or(0); });
has_editor!(|self: Tek|{ has_editor!(|self: Tek|{
editor = self.editor; editor = self.editor;
editor_w = { editor_w = {
@ -108,24 +106,33 @@ impl Tek {
} }
pub fn track_add ( pub fn track_add (
&mut self, name: Option<&str>, color: Option<ItemPalette>, &mut self, name: Option<&str>, color: Option<ItemPalette>,
midi_from: &[PortConnect], midi_to: &[PortConnect], midi_froms: &[PortConnect],
midi_tos: &[PortConnect],
) -> Usually<(usize, &mut Track)> { ) -> Usually<(usize, &mut Track)> {
let name = name.map_or_else(||self.track_next_name(), |x|x.to_string().into()); let name = name.map_or_else(||self.track_next_name(), |x|x.to_string().into());
let mut track = Track { let mut track = Track {
width: (name.len() + 2).max(9), width: (name.len() + 2).max(9),
color: color.unwrap_or_else(ItemPalette::random), color: color.unwrap_or_else(ItemPalette::random),
player: MidiPlayer::from(self.clock()), player: MidiPlayer::new(
&format!("{name}"),
self.jack(),
Some(self.clock()),
None,
midi_froms,
midi_tos
)?,
name, name,
..Default::default() ..Default::default()
}; };
let midi_in = JackMidiIn::new(&self.jack, &format!("{}I", &track.name), midi_from)?; //let midi_in = JackMidiIn::new(&self.jack, &format!("{}I", &track.name), midi_from)?;
midi_in.connect_to_matching()?; //midi_in.connect_to_matching()?;
track.player.midi_ins.push(midi_in); //track.player.midi_ins.push(midi_in);
//let midi_out = JackMidiOut::new(&self.jack, &format!("{}O", &track.name), midi_to)?;
//midi_out.connect_to_matching()?;
//track.player.midi_outs.push(midi_out);
let midi_out = JackMidiOut::new(&self.jack, &format!("{}O", &track.name), midi_to)?;
midi_out.connect_to_matching()?;
track.player.midi_outs.push(midi_out);
self.tracks_mut().push(track); self.tracks_mut().push(track);
let len = self.tracks().len(); let len = self.tracks().len();
let index = len - 1; let index = len - 1;
@ -293,7 +300,9 @@ pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync
self.selected().track().and_then(|s|self.tracks_mut().get_mut(s)) self.selected().track().and_then(|s|self.tracks_mut().get_mut(s))
} }
} }
pub trait Device: Send + Sync + std::fmt::Debug {} pub trait Device: Send + Sync + std::fmt::Debug {
fn boxed <'a> (self) -> Box<dyn Device + 'a> where Self: Sized + 'a { Box::new(self) }
}
impl Device for Sampler {} impl Device for Sampler {}
impl Device for Plugin {} impl Device for Plugin {}
#[derive(Debug, Default)] pub struct Scene { #[derive(Debug, Default)] pub struct Scene {

View file

@ -53,8 +53,8 @@ impl Default for ViewCache {
view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); { view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); {
":editor" => (&self.editor).boxed(), ":editor" => (&self.editor).boxed(),
":pool" => self.view_pool().boxed(), ":pool" => self.view_pool().boxed(),
":sample" => self.view_sample(self.is_editing()).boxed(), //":sample" => self.view_sample(self.is_editing()).boxed(),
":sampler" => self.view_sampler(self.is_editing(), &self.editor).boxed(), //":sampler" => self.view_sampler(self.is_editing(), &self.editor).boxed(),
":status" => self.view_editor().boxed(), ":status" => self.view_editor().boxed(),
":toolbar" => self.view_clock().boxed(), ":toolbar" => self.view_clock().boxed(),
":tracks" => self.view_tracks().boxed(), ":tracks" => self.view_tracks().boxed(),

View file

@ -80,23 +80,12 @@ pub struct Clock {
pub sync: Arc<LaunchSync>, pub sync: Arc<LaunchSync>,
/// Size of buffer in samples /// Size of buffer in samples
pub chunk: Arc<AtomicUsize>, pub chunk: Arc<AtomicUsize>,
} /// For syncing the clock to an external source
impl From<&Jack> for Clock { pub midi_in: Arc<RwLock<Option<JackMidiIn>>>,
fn from (jack: &Jack) -> Self { /// For syncing other devices to this clock
let (chunk, transport) = jack.with_client(|c|(c.buffer_size(), c.transport())); pub midi_out: Arc<RwLock<Option<JackMidiOut>>>,
let timebase = Arc::new(Timebase::default()); /// For emitting a metronome
Self { pub click_out: Arc<RwLock<Option<JackAudioOut>>>,
quant: Arc::new(24.into()),
sync: Arc::new(384.into()),
transport: Arc::new(Some(transport)),
chunk: Arc::new((chunk as usize).into()),
global: Arc::new(Moment::zero(&timebase)),
playhead: Arc::new(Moment::zero(&timebase)),
offset: Arc::new(Moment::zero(&timebase)),
started: RwLock::new(None).into(),
timebase,
}
}
} }
impl std::fmt::Debug for Clock { impl std::fmt::Debug for Clock {
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> {
@ -112,12 +101,27 @@ impl std::fmt::Debug for Clock {
} }
} }
impl Clock { impl Clock {
pub fn new (jack: &Jack, bpm: Option<f64>) -> Self { pub fn new (jack: &Jack, bpm: Option<f64>) -> Usually<Self> {
let clock = Self::from(jack); let (chunk, transport) = jack.with_client(|c|(c.buffer_size(), c.transport()));
let timebase = Arc::new(Timebase::default());
let clock = Self {
quant: Arc::new(24.into()),
sync: Arc::new(384.into()),
transport: Arc::new(Some(transport)),
chunk: Arc::new((chunk as usize).into()),
global: Arc::new(Moment::zero(&timebase)),
playhead: Arc::new(Moment::zero(&timebase)),
offset: Arc::new(Moment::zero(&timebase)),
started: RwLock::new(None).into(),
timebase,
midi_in: Arc::new(RwLock::new(Some(JackMidiIn::new(jack, "M/clock", &[])?))),
midi_out: Arc::new(RwLock::new(Some(JackMidiOut::new(jack, "clock/M", &[])?))),
click_out: Arc::new(RwLock::new(Some(JackAudioOut::new(jack, "click", &[])?))),
};
if let Some(bpm) = bpm { if let Some(bpm) = bpm {
clock.timebase.bpm.set(bpm); clock.timebase.bpm.set(bpm);
} }
clock Ok(clock)
} }
pub fn timebase (&self) -> &Arc<Timebase> { pub fn timebase (&self) -> &Arc<Timebase> {
&self.timebase &self.timebase