fix some lints, add FromEdn trait

This commit is contained in:
🪞👃🪞 2024-12-27 13:18:00 +01:00
parent 7962bdf86b
commit e96faeb6d3
14 changed files with 126 additions and 124 deletions

View file

@ -50,11 +50,11 @@ impl Sampler {
pub fn process_midi_in (&mut self, scope: &ProcessScope) { pub fn process_midi_in (&mut self, scope: &ProcessScope) {
let Sampler { midi_in, mapped, voices, .. } = self; let Sampler { midi_in, mapped, voices, .. } = self;
for RawMidi { time, bytes } in midi_in.iter(scope) { for RawMidi { time, bytes } in midi_in.iter(scope) {
if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { if let LiveEvent::Midi {
if let MidiMessage::NoteOn { ref key, ref vel } = message { message: MidiMessage::NoteOn { ref key, ref vel }, ..
if let Some(sample) = mapped.get(key) { } = LiveEvent::parse(bytes).unwrap() {
voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); if let Some(sample) = mapped.get(key) {
} voices.write().unwrap().push(Sample::play(sample, time as usize, vel));
} }
} }
} }
@ -85,7 +85,7 @@ impl Sampler {
return false return false
} }
} }
return true true
}); });
} }
@ -135,13 +135,13 @@ impl Iterator for Voice {
type Item = [f32;2]; type Item = [f32;2];
fn next (&mut self) -> Option<Self::Item> { fn next (&mut self) -> Option<Self::Item> {
if self.after > 0 { if self.after > 0 {
self.after = self.after - 1; self.after -= 1;
return Some([0.0, 0.0]) return Some([0.0, 0.0])
} }
let sample = self.sample.read().unwrap(); let sample = self.sample.read().unwrap();
if self.position < sample.end { if self.position < sample.end {
let position = self.position; let position = self.position;
self.position = self.position + 1; self.position += 1;
return sample.channels[0].get(position).map(|_amplitude|[ return sample.channels[0].get(position).map(|_amplitude|[
sample.channels[0][position] * self.velocity, sample.channels[0][position] * self.velocity,
sample.channels[0][position] * self.velocity, sample.channels[0][position] * self.velocity,

View file

@ -46,7 +46,7 @@ impl ArrangerCli {
let mut app = ArrangerTui::try_from(jack)?; let mut app = ArrangerTui::try_from(jack)?;
let jack = jack.read().unwrap(); let jack = jack.read().unwrap();
app.color = ItemPalette::random(); app.color = ItemPalette::random();
add_tracks(&jack, &mut app, &self)?; add_tracks(&jack, &mut app, self)?;
add_scenes(&mut app, self.scenes)?; add_scenes(&mut app, self.scenes)?;
Ok(app) Ok(app)
})?)?; })?)?;
@ -82,9 +82,8 @@ fn add_tracks (jack: &JackClient, app: &mut ArrangerTui, cli: &ArrangerCli) -> U
panic!("Tried to connect track {track} or {n}. Pass -t {track} to increase number of tracks.") panic!("Tried to connect track {track} or {n}. Pass -t {track} to increase number of tracks.")
} }
if let Some(port) = split.next() { if let Some(port) = split.next() {
if let Some(port) = jack.port_by_name(&port) { if let Some(port) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(&port, &app.tracks[track-1].player.midi_ins[0])?; jack.client().connect_ports(port, &app.tracks[track-1].player.midi_ins[0])?;
//jack.client().connect_ports(&port, &app.tracks[track].player.midi_ins[0])?;
} else { } else {
panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names.");
} }
@ -106,8 +105,8 @@ fn add_tracks (jack: &JackClient, app: &mut ArrangerTui, cli: &ArrangerCli) -> U
panic!("Tried to connect track {track} or {n}. Pass -t {track} to increase number of tracks.") panic!("Tried to connect track {track} or {n}. Pass -t {track} to increase number of tracks.")
} }
if let Some(port) = split.next() { if let Some(port) = split.next() {
if let Some(port) = jack.port_by_name(&port) { if let Some(port) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(&app.tracks[track-1].player.midi_outs[0], &port)?; jack.client().connect_ports(&app.tracks[track-1].player.midi_outs[0], port)?;
} else { } else {
panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names.");
} }

View file

@ -24,15 +24,11 @@ pub struct SequencerCli {
/// MIDI ins to connect to (multiple instances accepted) /// MIDI ins to connect to (multiple instances accepted)
#[arg(short='o', long)] #[arg(short='o', long)]
midi_to: Vec<String>, midi_to: Vec<String>,
/// Default phrase duration (in pulses; default: 4 * PPQ = 1 bar)
#[arg(short, long)]
length: Option<usize>,
} }
impl SequencerCli { impl SequencerCli {
fn run (&self) -> Usually<()> { fn run (&self) -> Usually<()> {
let name = self.name.as_ref().map(|n|n.as_str()).unwrap_or("tek_sequencer"); let name = self.name.as_deref().unwrap_or("tek_sequencer");
Tui::run(JackClient::new(name)?.activate_with(|jack|{ Tui::run(JackClient::new(name)?.activate_with(|jack|{
let mut app = SequencerTui::try_from(jack)?; let mut app = SequencerTui::try_from(jack)?;
let jack = jack.read().unwrap(); let jack = jack.read().unwrap();
@ -42,11 +38,6 @@ impl SequencerCli {
connect_to(&jack, &midi_out, &self.midi_to)?; connect_to(&jack, &midi_out, &self.midi_to)?;
app.player.midi_ins.push(midi_in); app.player.midi_ins.push(midi_in);
app.player.midi_outs.push(midi_out); app.player.midi_outs.push(midi_out);
if let Some(_) = self.length {
// TODO: if let Some(phrase) = sequencer.phrase.as_mut() {
//phrase.write().unwrap().length = length;
//}
}
Ok(app) Ok(app)
})?)?; })?)?;
Ok(()) Ok(())
@ -55,8 +46,8 @@ impl SequencerCli {
fn connect_from (jack: &JackClient, input: &Port<MidiIn>, ports: &[String]) -> Usually<()> { fn connect_from (jack: &JackClient, input: &Port<MidiIn>, ports: &[String]) -> Usually<()> {
for port in ports.iter() { for port in ports.iter() {
if let Some(port) = jack.port_by_name(&port) { if let Some(port) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(&port, &input)?; jack.client().connect_ports(port, input)?;
} else { } else {
panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names.");
} }
@ -66,8 +57,8 @@ fn connect_from (jack: &JackClient, input: &Port<MidiIn>, ports: &[String]) -> U
fn connect_to (jack: &JackClient, output: &Port<MidiOut>, ports: &[String]) -> Usually<()> { fn connect_to (jack: &JackClient, output: &Port<MidiOut>, ports: &[String]) -> Usually<()> {
for port in ports.iter() { for port in ports.iter() {
if let Some(port) = jack.port_by_name(&port) { if let Some(port) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(&output, &port)?; jack.client().connect_ports(output, port)?;
} else { } else {
panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names.");
} }

View file

@ -4,6 +4,23 @@ use crate::*;
pub use clojure_reader::edn::Edn; pub use clojure_reader::edn::Edn;
//pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError}; //pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError};
pub trait FromEdn<C>: Sized {
const ID: &'static str;
fn from_edn <'e> (context: C, expr: &[Edn<'e>]) -> Usually<Self>;
}
/// Implements the [FromEdn] trait.
#[macro_export] macro_rules! from_edn {
(|$context:pat = $Context:ty, $id:expr, $args:ident| -> $T:ty $body:block) => {
impl FromEdn<$Context> for $T {
const ID: &'static str = $id;
fn from_edn <'e> ($context: $Context, $args: &[Edn<'e>]) -> Usually<Self> {
$body
}
}
}
}
/// EDN parsing helper. /// EDN parsing helper.
#[macro_export] macro_rules! edn { #[macro_export] macro_rules! edn {
($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => { ($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
@ -16,82 +33,79 @@ pub use clojure_reader::edn::Edn;
}; };
} }
impl crate::Sampler { from_edn!(|jack = &Arc<RwLock<JackClient>>, "sampler", args| -> crate::Sampler {
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<Self> { let mut name = String::new();
let mut name = String::new(); let mut dir = String::new();
let mut dir = String::new(); let mut samples = BTreeMap::new();
let mut samples = BTreeMap::new(); edn!(edn in args {
edn!(edn in args { Edn::Map(map) => {
Edn::Map(map) => { if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) { name = String::from(*n);
name = String::from(*n); }
} if let Some(Edn::Str(n)) = map.get(&Edn::Key(":dir")) {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":dir")) { dir = String::from(*n);
dir = String::from(*n); }
},
Edn::List(args) => match args.get(0) {
Some(Edn::Symbol("sample")) => {
let (midi, sample) = MidiSample::from_edn((jack, &dir), &args[1..])?;
if let Some(midi) = midi {
samples.insert(midi, sample);
} else {
panic!("sample without midi binding: {}", sample.read().unwrap().name);
} }
}, },
Edn::List(args) => match args.get(0) { _ => panic!("unexpected in sampler {name}: {args:?}")
Some(Edn::Symbol("sample")) => { },
let (midi, sample) = Sample::from_edn(jack, &dir, &args[1..])?; _ => panic!("unexpected in sampler {name}: {edn:?}")
if let Some(midi) = midi { });
samples.insert(midi, sample); let midi_in = jack.read().unwrap().client().register_port("in", MidiIn::default())?;
} else { Ok(Self {
panic!("sample without midi binding: {}", sample.read().unwrap().name); jack: jack.clone(),
} name: name.into(),
}, mapped: samples,
_ => panic!("unexpected in sampler {name}: {args:?}") unmapped: Default::default(),
}, voices: Default::default(),
_ => panic!("unexpected in sampler {name}: {edn:?}") buffer: Default::default(),
}); audio_outs: vec![],
let midi_in = jack.read().unwrap().client().register_port("in", MidiIn::default())?; output_gain: 0.,
Ok(Sampler { midi_in,
jack: jack.clone(), })
name: name.into(), });
mapped: samples,
unmapped: Default::default(),
voices: Default::default(),
buffer: Default::default(),
audio_outs: vec![],
output_gain: 0.,
midi_in,
})
}
}
impl crate::Sample { type MidiSample = (Option<u7>, Arc<RwLock<crate::Sample>>);
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, dir: &str, args: &[Edn<'e>]) -> Usually<(Option<u7>, Arc<RwLock<Self>>)> {
let mut name = String::new();
let mut file = String::new();
let mut midi = None;
let mut start = 0usize;
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Str(f)) = map.get(&Edn::Key(":file")) {
file = String::from(*f);
}
if let Some(Edn::Int(i)) = map.get(&Edn::Key(":start")) {
start = *i as usize;
}
if let Some(Edn::Int(m)) = map.get(&Edn::Key(":midi")) {
midi = Some(u7::from(*m as u8));
}
},
_ => panic!("unexpected in sample {name}"),
});
let (end, data) = Sample::read_data(&format!("{dir}/{file}"))?;
Ok((midi, Arc::new(RwLock::new(Self {
name: name.into(),
start,
end,
channels: data,
rate: None
}))))
}
}
from_edn!(|(jack, dir) = (&Arc<RwLock<JackClient>>, &str), "sample", args| -> MidiSample {
let mut name = String::new();
let mut file = String::new();
let mut midi = None;
let mut start = 0usize;
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Str(f)) = map.get(&Edn::Key(":file")) {
file = String::from(*f);
}
if let Some(Edn::Int(i)) = map.get(&Edn::Key(":start")) {
start = *i as usize;
}
if let Some(Edn::Int(m)) = map.get(&Edn::Key(":midi")) {
midi = Some(u7::from(*m as u8));
}
},
_ => panic!("unexpected in sample {name}"),
});
let (end, data) = Sample::read_data(&format!("{dir}/{file}"))?;
Ok((midi, Arc::new(RwLock::new(crate::Sample {
name,
start,
end,
channels: data,
rate: None
}))))
});
//impl ArrangerScene { //impl ArrangerScene {

View file

@ -165,7 +165,7 @@ pub struct PlayerAudio<'a, T: MidiPlayerApi>(
); );
/// JACK process callback for a sequencer's phrase player/recorder. /// JACK process callback for a sequencer's phrase player/recorder.
impl<'a, T: MidiPlayerApi> Audio for PlayerAudio<'a, 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;
let note_buf = &mut self.1; let note_buf = &mut self.1;
@ -217,7 +217,7 @@ impl MidiRecordApi for MidiPlayer {
impl MidiPlaybackApi for MidiPlayer { impl MidiPlaybackApi for MidiPlayer {
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> { fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
&self.notes_in &self.notes_out
} }
} }

View file

@ -76,13 +76,12 @@ impl MidiClip {
} }
/// Check if a range `start..end` contains MIDI Note On `k` /// Check if a range `start..end` contains MIDI Note On `k`
pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool { pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool {
//panic!("{:?} {start} {end}", &self);
for events in self.notes[start.max(0)..end.min(self.notes.len())].iter() { for events in self.notes[start.max(0)..end.min(self.notes.len())].iter() {
for event in events.iter() { for event in events.iter() {
if let MidiMessage::NoteOn {key,..} = event { if *key == k { return true } } if let MidiMessage::NoteOn {key,..} = event { if *key == k { return true } }
} }
} }
return false false
} }
} }

View file

@ -5,6 +5,6 @@ pub trait HasMidiIns {
fn midi_ins (&self) -> &Vec<Port<MidiIn>>; fn midi_ins (&self) -> &Vec<Port<MidiIn>>;
fn midi_ins_mut (&mut self) -> &mut Vec<Port<MidiIn>>; fn midi_ins_mut (&mut self) -> &mut Vec<Port<MidiIn>>;
fn has_midi_ins (&self) -> bool { fn has_midi_ins (&self) -> bool {
self.midi_ins().len() > 0 !self.midi_ins().is_empty()
} }
} }

View file

@ -27,9 +27,8 @@ pub trait HasPlayPhrase: HasClock {
} }
fn enqueue_next (&mut self, phrase: Option<&Arc<RwLock<MidiClip>>>) { fn enqueue_next (&mut self, phrase: Option<&Arc<RwLock<MidiClip>>>) {
let start = self.clock().next_launch_pulse() as f64; let start = self.clock().next_launch_pulse() as f64;
let instant = Moment::from_pulse(&self.clock().timebase(), start); let instant = Moment::from_pulse(self.clock().timebase(), start);
let phrase = phrase.map(|p|p.clone()); *self.next_phrase_mut() = Some((instant, phrase.cloned()));
*self.next_phrase_mut() = Some((instant, phrase));
*self.reset_mut() = true; *self.reset_mut() = true;
} }
} }

View file

@ -5,7 +5,7 @@ pub trait HasMidiOuts {
fn midi_outs (&self) -> &Vec<Port<MidiOut>>; fn midi_outs (&self) -> &Vec<Port<MidiOut>>;
fn midi_outs_mut (&mut self) -> &mut Vec<Port<MidiOut>>; fn midi_outs_mut (&mut self) -> &mut Vec<Port<MidiOut>>;
fn has_midi_outs (&self) -> bool { fn has_midi_outs (&self) -> bool {
self.midi_outs().len() > 0 !self.midi_outs().is_empty()
} }
/// Buffer for serializing a MIDI event. FIXME rename /// Buffer for serializing a MIDI event. FIXME rename
fn midi_note (&mut self) -> &mut Vec<u8>; fn midi_note (&mut self) -> &mut Vec<u8>;

View file

@ -49,7 +49,7 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts {
// Samples elapsed since phrase was supposed to start // Samples elapsed since phrase was supposed to start
let skipped = sample0 - start; let skipped = sample0 - start;
// Switch over to enqueued phrase // Switch over to enqueued phrase
let started = Moment::from_sample(&self.clock().timebase(), start as f64); let started = Moment::from_sample(self.clock().timebase(), start as f64);
// Launch enqueued phrase // Launch enqueued phrase
*self.play_phrase_mut() = Some((started, phrase.clone())); *self.play_phrase_mut() = Some((started, phrase.clone()));
// Unset enqueuement (TODO: where to implement looping?) // Unset enqueuement (TODO: where to implement looping?)
@ -123,7 +123,7 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts {
// Append serialized message to output buffer. // Append serialized message to output buffer.
out[sample].push(note_buf.clone()); out[sample].push(note_buf.clone());
// Update the list of currently held notes. // Update the list of currently held notes.
update_keys(&mut*notes, &message); update_keys(&mut*notes, message);
} }
} }
} }
@ -138,10 +138,11 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts {
/// Write a chunk of MIDI data from the output buffer to an output port. /// Write a chunk of MIDI data from the output buffer to an output port.
fn write_port (writer: &mut MidiWriter, samples: usize, out: &[Vec<Vec<u8>>]) { fn write_port (writer: &mut MidiWriter, samples: usize, out: &[Vec<Vec<u8>>]) {
for time in 0..samples { for (time, events) in out.iter().enumerate().take(samples) {
for event in out[time].iter() { for bytes in events.iter() {
writer.write(&RawMidi { time: time as u32, bytes: &event }) writer.write(&RawMidi { time: time as u32, bytes }).unwrap_or_else(|_|{
.expect(&format!("{event:?}")); panic!("Failed to write MIDI data: {bytes:?}");
});
} }
} }
} }

View file

@ -33,12 +33,11 @@ pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns {
let sample = (sample0 + sample - start) as f64; let sample = (sample0 + sample - start) as f64;
let pulse = timebase.samples_to_pulse(sample); let pulse = timebase.samples_to_pulse(sample);
let quantized = (pulse / quant).round() * quant; let quantized = (pulse / quant).round() * quant;
let looped = quantized as usize % length; quantized as usize % length
looped
}, message); }, message);
} }
} }
update_keys(&mut*notes_in.write().unwrap(), &message); update_keys(&mut notes_in.write().unwrap(), &message);
} }
} }
} }
@ -61,7 +60,7 @@ pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns {
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
if let LiveEvent::Midi { message, .. } = event { if let LiveEvent::Midi { message, .. } = event {
midi_buf[sample].push(bytes.to_vec()); midi_buf[sample].push(bytes.to_vec());
update_keys(&mut*notes_in.write().unwrap(), &message); update_keys(&mut notes_in.write().unwrap(), &message);
} }
} }
} }

View file

@ -19,7 +19,7 @@ pub trait Coordinate: Send + Sync + Copy
0.into() 0.into()
} }
} }
fn ZERO () -> Self { fn zero () -> Self {
0.into() 0.into()
} }
} }

View file

@ -80,7 +80,7 @@ where
let mut h: E::Unit = 0.into(); let mut h: E::Unit = 0.into();
(self.0)(&mut |component: &dyn Render<E>| { (self.0)(&mut |component: &dyn Render<E>| {
let max = to.h().minus(h); let max = to.h().minus(h);
if max > E::Unit::ZERO() { if max > E::Unit::zero() {
let item = E::max_y(max, E::push_y(h, component)); let item = E::max_y(max, E::push_y(h, component));
let size = item.min_size(to)?.map(|size|size.wh()); let size = item.min_size(to)?.map(|size|size.wh());
if let Some([width, height]) = size { if let Some([width, height]) = size {
@ -98,7 +98,7 @@ where
let mut h: E::Unit = 0.into(); let mut h: E::Unit = 0.into();
(self.0)(&mut |component: &dyn Render<E>| { (self.0)(&mut |component: &dyn Render<E>| {
let max = to.w().minus(w); let max = to.w().minus(w);
if max > E::Unit::ZERO() { if max > E::Unit::zero() {
let item = E::max_x(max, E::push_x(h, component)); let item = E::max_x(max, E::push_x(h, component));
let size = item.min_size(to)?.map(|size|size.wh()); let size = item.min_size(to)?.map(|size|size.wh());
if let Some([width, height]) = size { if let Some([width, height]) = size {
@ -116,7 +116,7 @@ where
let mut h: E::Unit = 0.into(); let mut h: E::Unit = 0.into();
(self.0)(&mut |component: &dyn Render<E>| { (self.0)(&mut |component: &dyn Render<E>| {
let max = to.h().minus(h); let max = to.h().minus(h);
if max > E::Unit::ZERO() { if max > E::Unit::zero() {
let item = E::max_y(to.h() - h, component); let item = E::max_y(to.h() - h, component);
let size = item.min_size(to)?.map(|size|size.wh()); let size = item.min_size(to)?.map(|size|size.wh());
if let Some([width, height]) = size { if let Some([width, height]) = size {

View file

@ -37,7 +37,7 @@ render!(<Tui>|self: ArrangerVHead<'a>|Tui::push_x(self.scenes_w, row!(
impl<'a> ArrangerVHead<'a> { impl<'a> ArrangerVHead<'a> {
/// name and width of track /// name and width of track
fn format_name (track: &ArrangerTrack, w: usize) -> impl Render<Tui> { fn format_name (track: &ArrangerTrack, _w: usize) -> impl Render<Tui> {
let name = track.name().read().unwrap().clone(); let name = track.name().read().unwrap().clone();
Tui::bold(true, Tui::fg(track.color.lightest.rgb, name)) Tui::bold(true, Tui::fg(track.color.lightest.rgb, name))
} }