refactor some of the larger modules

This commit is contained in:
🪞👃🪞 2024-12-18 15:50:27 +01:00
parent 417b097c6f
commit 1261b07aa2
24 changed files with 1070 additions and 1050 deletions

View file

@ -1,8 +1,5 @@
mod jack; pub(crate) use self::jack::*;
mod phrase; pub(crate) use phrase::*;
mod clock; pub(crate) use clock::*;
mod note; pub(crate) use note::*;
mod player; pub(crate) use player::*;
mod scene; pub(crate) use scene::*;
mod track; pub(crate) use track::*;
mod sampler; pub(crate) use sampler::*;

View file

@ -1,110 +0,0 @@
use crate::*;
use Ordering::Relaxed;
pub trait MidiViewport<E: Engine>: MidiRange + MidiPoint + HasSize<E> {
/// Make sure cursor is within range
fn autoscroll (&self) {
let note_lo = self.note_lo();
let note_axis = self.note_axis();
let note_hi = self.note_hi();
let note_point = self.note_point().min(127);
if note_point < note_lo {
self.set_note_lo(note_point);
} else if note_point > note_hi {
self.set_note_lo((note_lo + note_point).saturating_sub(note_hi));
}
}
/// Make sure best usage of screen space is achieved by default
fn autozoom (&self) {
}
}
#[derive(Debug, Clone)]
pub struct MidiRangeModel {
/// Length of visible time axis
pub time_axis: Arc<AtomicUsize>,
/// Earliest time displayed
pub time_start: Arc<AtomicUsize>,
/// Time step
pub time_zoom: Arc<AtomicUsize>,
/// Auto rezoom to fit in time axis
pub time_lock: Arc<AtomicBool>,
/// Length of visible note axis
pub note_axis: Arc<AtomicUsize>,
// Lowest note displayed
pub note_lo: Arc<AtomicUsize>,
}
impl From<(usize, bool)> for MidiRangeModel {
fn from ((time_zoom, time_lock): (usize, bool)) -> Self {
Self {
note_axis: Arc::new(0.into()),
note_lo: Arc::new(0.into()),
time_axis: Arc::new(0.into()),
time_start: Arc::new(0.into()),
time_zoom: Arc::new(time_zoom.into()),
time_lock: Arc::new(time_lock.into()),
}
}
}
pub trait MidiRange {
fn time_zoom (&self) -> usize;
fn set_time_zoom (&mut self, x: usize);
fn time_lock (&self) -> bool;
fn set_time_lock (&self, x: bool);
fn time_start (&self) -> usize;
fn set_time_start (&self, x: usize);
fn note_lo (&self) -> usize;
fn set_note_lo (&self, x: usize);
fn note_axis (&self) -> usize;
fn time_axis (&self) -> usize;
fn note_hi (&self) -> usize { (self.note_lo() + self.note_axis().saturating_sub(1)).min(127) }
fn time_end (&self) -> usize { self.time_start() + self.time_axis() * self.time_zoom() }
}
impl MidiRange for MidiRangeModel {
fn time_zoom (&self) -> usize { self.time_zoom.load(Relaxed) }
fn set_time_zoom (&mut self, x: usize) { self.time_zoom.store(x, Relaxed); }
fn time_lock (&self) -> bool { self.time_lock.load(Relaxed) }
fn set_time_lock (&self, x: bool) { self.time_lock.store(x, Relaxed); }
fn time_start (&self) -> usize { self.time_start.load(Relaxed) }
fn set_time_start (&self, x: usize) { self.time_start.store(x, Relaxed); }
fn note_lo (&self) -> usize { self.note_lo.load(Relaxed).min(127) }
fn set_note_lo (&self, x: usize) { self.note_lo.store(x.min(127), Relaxed); }
fn note_axis (&self) -> usize { self.note_axis.load(Relaxed) }
fn time_axis (&self) -> usize { self.time_axis.load(Relaxed) }
}
#[derive(Debug, Clone)]
pub struct MidiPointModel {
/// Time coordinate of cursor
pub time_point: Arc<AtomicUsize>,
/// Note coordinate of cursor
pub note_point: Arc<AtomicUsize>,
/// Length of note that will be inserted, in pulses
pub note_len: Arc<AtomicUsize>,
}
impl Default for MidiPointModel {
fn default () -> Self {
Self {
time_point: Arc::new(0.into()),
note_point: Arc::new(36.into()),
note_len: Arc::new(24.into()),
}
}
}
pub trait MidiPoint {
fn note_len (&self) -> usize;
fn set_note_len (&self, x: usize);
fn note_point (&self) -> usize;
fn set_note_point (&self, x: usize);
fn time_point (&self) -> usize;
fn set_time_point (&self, x: usize);
fn note_end (&self) -> usize { self.note_point() + self.note_len() }
}
impl MidiPoint for MidiPointModel {
fn note_len (&self) -> usize { self.note_len.load(Relaxed)}
fn set_note_len (&self, x: usize) { self.note_len.store(x, Relaxed) }
fn note_point (&self) -> usize { self.note_point.load(Relaxed).min(127) }
fn set_note_point (&self, x: usize) { self.note_point.store(x.min(127), Relaxed) }
fn time_point (&self) -> usize { self.time_point.load(Relaxed) }
fn set_time_point (&self, x: usize) { self.time_point.store(x, Relaxed) }
}

View file

@ -1,523 +0,0 @@
use crate::*;
pub trait HasPlayer {
fn player (&self) -> &impl MidiPlayerApi;
fn player_mut (&mut self) -> &mut impl MidiPlayerApi;
}
#[macro_export] macro_rules! has_player {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasPlayer for $Struct $(<$($L),*$($T),*>)? {
fn player (&$self) -> &impl MidiPlayerApi { &$cb }
fn player_mut (&mut $self) -> &mut impl MidiPlayerApi { &mut$cb }
}
}
}
/// Contains state for playing a phrase
pub struct PhrasePlayerModel {
/// State of clock and playhead
pub(crate) clock: ClockModel,
/// Start time and phrase being played
pub(crate) play_phrase: Option<(Moment, Option<Arc<RwLock<Phrase>>>)>,
/// Start time and next phrase
pub(crate) next_phrase: Option<(Moment, Option<Arc<RwLock<Phrase>>>)>,
/// Play input through output.
pub(crate) monitoring: bool,
/// Write input to sequence.
pub(crate) recording: bool,
/// Overdub input to sequence.
pub(crate) overdub: bool,
/// Send all notes off
pub(crate) reset: bool, // TODO?: after Some(nframes)
/// Record from MIDI ports to current sequence.
pub midi_ins: Vec<Port<MidiIn>>,
/// Play from current sequence to MIDI ports
pub midi_outs: Vec<Port<MidiOut>>,
/// Notes currently held at input
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
/// Notes currently held at output
pub(crate) notes_out: Arc<RwLock<[bool; 128]>>,
/// MIDI output buffer
pub note_buf: Vec<u8>,
}
impl std::fmt::Debug for PhrasePlayerModel {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("PhrasePlayerModel")
.field("clock", &self.clock)
.field("play_phrase", &self.play_phrase)
.field("next_phrase", &self.next_phrase)
.finish()
}
}
impl From<&ClockModel> for PhrasePlayerModel {
fn from (clock: &ClockModel) -> Self {
Self {
clock: clock.clone(),
midi_ins: vec![],
midi_outs: vec![],
note_buf: vec![0;8],
reset: true,
recording: false,
monitoring: false,
overdub: false,
play_phrase: None,
next_phrase: None,
notes_in: RwLock::new([false;128]).into(),
notes_out: RwLock::new([false;128]).into(),
}
}
}
impl From<(&ClockModel, &Arc<RwLock<Phrase>>)> for PhrasePlayerModel {
fn from ((clock, phrase): (&ClockModel, &Arc<RwLock<Phrase>>)) -> Self {
let mut model = Self::from(clock);
model.play_phrase = Some((Moment::zero(&clock.timebase), Some(phrase.clone())));
model
}
}
has_clock!(|self:PhrasePlayerModel|&self.clock);
impl HasMidiIns for PhrasePlayerModel {
fn midi_ins (&self) -> &Vec<Port<jack::MidiIn>> {
&self.midi_ins
}
fn midi_ins_mut (&mut self) -> &mut Vec<Port<jack::MidiIn>> {
&mut self.midi_ins
}
}
impl HasMidiOuts for PhrasePlayerModel {
fn midi_outs (&self) -> &Vec<Port<jack::MidiOut>> {
&self.midi_outs
}
fn midi_outs_mut (&mut self) -> &mut Vec<Port<jack::MidiOut>> {
&mut self.midi_outs
}
fn midi_note (&mut self) -> &mut Vec<u8> {
&mut self.note_buf
}
}
pub trait MidiPlayerApi: MidiRecordApi + MidiPlaybackApi + Send + Sync {}
impl MidiPlayerApi for PhrasePlayerModel {}
pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns {
fn notes_in (&self) -> &Arc<RwLock<[bool;128]>>;
fn recording (&self) -> bool;
fn recording_mut (&mut self) -> &mut bool;
fn toggle_record (&mut self) {
*self.recording_mut() = !self.recording();
}
fn record (&mut self, scope: &ProcessScope, midi_buf: &mut Vec<Vec<Vec<u8>>>) {
let sample0 = scope.last_frame_time() as usize;
// For highlighting keys and note repeat
let notes_in = self.notes_in().clone();
if self.clock().is_rolling() {
if let Some((started, ref phrase)) = self.play_phrase().clone() {
let start = started.sample.get() as usize;
let quant = self.clock().quant.get();
let timebase = self.clock().timebase().clone();
let monitoring = self.monitoring();
let recording = self.recording();
for input in self.midi_ins_mut().iter() {
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
if let LiveEvent::Midi { message, .. } = event {
if monitoring {
midi_buf[sample].push(bytes.to_vec())
}
if recording {
if let Some(phrase) = phrase {
let mut phrase = phrase.write().unwrap();
let length = phrase.length;
phrase.record_event({
let sample = (sample0 + sample - start) as f64;
let pulse = timebase.samples_to_pulse(sample);
let quantized = (pulse / quant).round() * quant;
let looped = quantized as usize % length;
looped
}, message);
}
}
update_keys(&mut*notes_in.write().unwrap(), &message);
}
}
}
}
if let Some((start_at, phrase)) = &self.next_phrase() {
// TODO switch to next phrase and record into it
}
}
}
fn monitoring (&self) -> bool;
fn monitoring_mut (&mut self) -> &mut bool;
fn toggle_monitor (&mut self) {
*self.monitoring_mut() = !self.monitoring();
}
fn monitor (&mut self, scope: &ProcessScope, midi_buf: &mut Vec<Vec<Vec<u8>>>) {
// For highlighting keys and note repeat
let notes_in = self.notes_in().clone();
for input in self.midi_ins_mut().iter() {
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
if let LiveEvent::Midi { message, .. } = event {
midi_buf[sample].push(bytes.to_vec());
update_keys(&mut*notes_in.write().unwrap(), &message);
}
}
}
}
fn overdub (&self) -> bool;
fn overdub_mut (&mut self) -> &mut bool;
fn toggle_overdub (&mut self) {
*self.overdub_mut() = !self.overdub();
}
}
impl MidiRecordApi for PhrasePlayerModel {
fn recording (&self) -> bool {
self.recording
}
fn recording_mut (&mut self) -> &mut bool {
&mut self.recording
}
fn monitoring (&self) -> bool {
self.monitoring
}
fn monitoring_mut (&mut self) -> &mut bool {
&mut self.monitoring
}
fn overdub (&self) -> bool {
self.overdub
}
fn overdub_mut (&mut self) -> &mut bool {
&mut self.overdub
}
fn notes_in (&self) -> &Arc<RwLock<[bool; 128]>> {
&self.notes_in
}
}
pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts {
fn notes_out (&self) -> &Arc<RwLock<[bool;128]>>;
/// Clear the section of the output buffer that we will be using,
/// emitting "all notes off" at start of buffer if requested.
fn clear (
&mut self, scope: &ProcessScope, out_buf: &mut Vec<Vec<Vec<u8>>>, reset: bool
) {
for frame in &mut out_buf[0..scope.n_frames() as usize] {
frame.clear();
}
if reset {
all_notes_off(out_buf);
}
}
/// Output notes from phrase to MIDI output ports.
fn play (
&mut self, scope: &ProcessScope, note_buf: &mut Vec<u8>, out_buf: &mut Vec<Vec<Vec<u8>>>
) -> bool {
let mut next = false;
// Write MIDI events from currently playing phrase (if any) to MIDI output buffer
if self.clock().is_rolling() {
let sample0 = scope.last_frame_time() as usize;
let samples = scope.n_frames() as usize;
// If no phrase is playing, prepare for switchover immediately
next = self.play_phrase().is_none();
let phrase = self.play_phrase();
let started0 = &self.clock().started;
let timebase = self.clock().timebase();
let notes_out = self.notes_out();
let next_phrase = self.next_phrase();
if let Some((started, phrase)) = phrase {
// First sample to populate. Greater than 0 means that the first
// pulse of the phrase falls somewhere in the middle of the chunk.
let sample = started.sample.get() as usize;
let sample = sample + started0.read().unwrap().as_ref().unwrap().sample.get() as usize;
let sample = sample0.saturating_sub(sample);
// Iterator that emits sample (index into output buffer at which to write MIDI event)
// paired with pulse (index into phrase from which to take the MIDI event) for each
// sample of the output buffer that corresponds to a MIDI pulse.
let pulses = timebase.pulses_between_samples(sample, sample + samples);
// Notes active during current chunk.
let notes = &mut notes_out.write().unwrap();
for (sample, pulse) in pulses {
// If a next phrase is enqueued, and we're past the end of the current one,
// break the loop here (FIXME count pulse correctly)
next = next_phrase.is_some() && if let Some(ref phrase) = phrase {
pulse >= phrase.read().unwrap().length
} else {
true
};
if next {
break
}
// If there's a currently playing phrase, output notes from it to buffer:
if let Some(ref phrase) = phrase {
// Source phrase from which the MIDI events will be taken.
let phrase = phrase.read().unwrap();
// Phrase with zero length is not processed
if phrase.length > 0 {
// Current pulse index in source phrase
let pulse = pulse % phrase.length;
// Output each MIDI event from phrase at appropriate frames of output buffer:
for message in phrase.notes[pulse].iter() {
// Clear output buffer for this MIDI event.
note_buf.clear();
// TODO: support MIDI channels other than CH1.
let channel = 0.into();
// Serialize MIDI event into message buffer.
LiveEvent::Midi { channel, message: *message }
.write(note_buf)
.unwrap();
// Append serialized message to output buffer.
out_buf[sample].push(note_buf.clone());
// Update the list of currently held notes.
update_keys(&mut*notes, &message);
}
}
}
}
}
}
next
}
/// Handle switchover from current to next playing phrase.
fn switchover (
&mut self, scope: &ProcessScope, note_buf: &mut Vec<u8>, out_buf: &mut Vec<Vec<Vec<u8>>>
) {
if self.clock().is_rolling() {
let sample0 = scope.last_frame_time() as usize;
//let samples = scope.n_frames() as usize;
if let Some((start_at, phrase)) = &self.next_phrase() {
let start = start_at.sample.get() as usize;
let sample = self.clock().started.read().unwrap().as_ref().unwrap().sample.get() as usize;
// If it's time to switch to the next phrase:
if start <= sample0.saturating_sub(sample) {
// Samples elapsed since phrase was supposed to start
let skipped = sample0 - start;
// Switch over to enqueued phrase
let started = Moment::from_sample(&self.clock().timebase(), start as f64);
*self.play_phrase_mut() = Some((started, phrase.clone()));
// Unset enqueuement (TODO: where to implement looping?)
*self.next_phrase_mut() = None
}
// TODO fill in remaining ticks of chunk from next phrase.
// ?? just call self.play(scope) again, since enqueuement is off ???
self.play(scope, note_buf, out_buf);
// ?? or must it be with modified scope ??
// likely not because start time etc
}
}
}
/// Write a chunk of MIDI notes to the output buffer.
fn write (
&mut self, scope: &ProcessScope, out_buf: &Vec<Vec<Vec<u8>>>
) {
let samples = scope.n_frames() as usize;
for port in self.midi_outs_mut().iter_mut() {
let writer = &mut port.writer(scope);
for time in 0..samples {
for event in out_buf[time].iter() {
writer.write(&RawMidi { time: time as u32, bytes: &event })
.expect(&format!("{event:?}"));
}
}
}
}
}
impl MidiPlaybackApi for PhrasePlayerModel {
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
&self.notes_in
}
}
pub trait HasPlayPhrase: HasClock {
fn reset (&self) -> bool;
fn reset_mut (&mut self) -> &mut bool;
fn play_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<Phrase>>>)>;
fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<Phrase>>>)>;
fn next_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<Phrase>>>)>;
fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<Phrase>>>)>;
fn pulses_since_start (&self) -> Option<f64> {
if let Some((started, Some(_))) = self.play_phrase().as_ref() {
let elapsed = self.clock().playhead.pulse.get() - started.pulse.get();
Some(elapsed)
} else {
None
}
}
fn pulses_since_start_looped (&self) -> Option<f64> {
if let Some((started, Some(phrase))) = self.play_phrase().as_ref() {
let elapsed = self.clock().playhead.pulse.get() - started.pulse.get();
let length = phrase.read().unwrap().length.max(1); // prevent div0 on empty phrase
let elapsed = (elapsed as usize % length) as f64;
Some(elapsed)
} else {
None
}
}
fn enqueue_next (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
let start = self.clock().next_launch_pulse() as f64;
let instant = Moment::from_pulse(&self.clock().timebase(), start);
let phrase = phrase.map(|p|p.clone());
*self.next_phrase_mut() = Some((instant, phrase));
*self.reset_mut() = true;
}
}
impl HasPlayPhrase for PhrasePlayerModel {
fn reset (&self) -> bool {
self.reset
}
fn reset_mut (&mut self) -> &mut bool {
&mut self.reset
}
fn play_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<Phrase>>>)> {
&self.play_phrase
}
fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<Phrase>>>)> {
&mut self.play_phrase
}
fn next_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<Phrase>>>)> {
&self.next_phrase
}
fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<Phrase>>>)> {
&mut self.next_phrase
}
}
/// Add "all notes off" to the start of a buffer.
pub fn all_notes_off (output: &mut [Vec<Vec<u8>>]) {
let mut buf = vec![];
let msg = MidiMessage::Controller { controller: 123.into(), value: 0.into() };
let evt = LiveEvent::Midi { channel: 0.into(), message: msg };
evt.write(&mut buf).unwrap();
output[0].push(buf);
}
/// Return boxed iterator of MIDI events
pub fn parse_midi_input (input: MidiIter) -> Box<dyn Iterator<Item=(usize, LiveEvent, &[u8])> + '_> {
Box::new(input.map(|RawMidi { time, bytes }|(
time as usize,
LiveEvent::parse(bytes).unwrap(),
bytes
)))
}
/// Update notes_in array
pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) {
match message {
MidiMessage::NoteOn { key, .. } => { keys[key.as_int() as usize] = true; }
MidiMessage::NoteOff { key, .. } => { keys[key.as_int() as usize] = false; },
_ => {}
}
}
/// Hosts the JACK callback for a single MIDI player
pub struct PlayerAudio<'a, T: MidiPlayerApi>(
/// Player
pub &'a mut T,
/// Note buffer
pub &'a mut Vec<u8>,
/// Note chunk buffer
pub &'a mut Vec<Vec<Vec<u8>>>,
);
/// JACK process callback for a sequencer's phrase player/recorder.
impl<'a, T: MidiPlayerApi> Audio for PlayerAudio<'a, T> {
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
let model = &mut self.0;
let note_buf = &mut self.1;
let midi_buf = &mut self.2;
// Clear output buffer(s)
model.clear(scope, midi_buf, false);
// Write chunk of phrase to output, handle switchover
if model.play(scope, note_buf, midi_buf) {
model.switchover(scope, note_buf, midi_buf);
}
if model.has_midi_ins() {
if model.recording() || model.monitoring() {
// Record and/or monitor input
model.record(scope, midi_buf)
} else if model.has_midi_outs() && model.monitoring() {
// Monitor input to output
model.monitor(scope, midi_buf)
}
}
// Write to output port(s)
model.write(scope, midi_buf);
Control::Continue
}
}
//#[derive(Debug)]
//pub struct MIDIPlayer {
///// Global timebase
//pub clock: Arc<Clock>,
///// Start time and phrase being played
//pub play_phrase: Option<(Moment, Option<Arc<RwLock<Phrase>>>)>,
///// Start time and next phrase
//pub next_phrase: Option<(Moment, Option<Arc<RwLock<Phrase>>>)>,
///// Play input through output.
//pub monitoring: bool,
///// Write input to sequence.
//pub recording: bool,
///// Overdub input to sequence.
//pub overdub: bool,
///// Send all notes off
//pub reset: bool, // TODO?: after Some(nframes)
///// Record from MIDI ports to current sequence.
//pub midi_inputs: Vec<Port<MidiIn>>,
///// Play from current sequence to MIDI ports
//pub midi_outputs: Vec<Port<MidiOut>>,
///// MIDI output buffer
//pub midi_note: Vec<u8>,
///// MIDI output buffer
//pub midi_chunk: Vec<Vec<Vec<u8>>>,
///// Notes currently held at input
//pub notes_in: Arc<RwLock<[bool; 128]>>,
///// Notes currently held at output
//pub notes_out: Arc<RwLock<[bool; 128]>>,
//}
///// Methods used primarily by the process callback
//impl MIDIPlayer {
//pub fn new (
//jack: &Arc<RwLock<JackClient>>,
//clock: &Arc<Clock>,
//name: &str
//) -> Usually<Self> {
//let jack = jack.read().unwrap();
//Ok(Self {
//clock: clock.clone(),
//phrase: None,
//next_phrase: None,
//notes_in: Arc::new(RwLock::new([false;128])),
//notes_out: Arc::new(RwLock::new([false;128])),
//monitoring: false,
//recording: false,
//overdub: true,
//reset: true,
//midi_note: Vec::with_capacity(8),
//midi_chunk: vec![Vec::with_capacity(16);16384],
//midi_outputs: vec![
//jack.client().register_port(format!("{name}_out0").as_str(), MidiOut::default())?
//],
//midi_inputs: vec![
//jack.client().register_port(format!("{name}_in0").as_str(), MidiIn::default())?
//],
//})
//}
//}

View file

@ -1,4 +1,3 @@
pub(crate) mod audio; pub(crate) use audio::*;
pub(crate) mod color; pub(crate) use color::*;
pub(crate) mod command; pub(crate) use command::*;
pub(crate) mod engine; pub(crate) use engine::*;

View file

@ -1,181 +0,0 @@
use crate::*;
use jack::*;
#[derive(Debug, Clone)]
/// Event enum for JACK events.
pub enum JackEvent {
ThreadInit,
Shutdown(ClientStatus, String),
Freewheel(bool),
SampleRate(Frames),
ClientRegistration(String, bool),
PortRegistration(PortId, bool),
PortRename(PortId, String, String),
PortsConnected(PortId, PortId, bool),
GraphReorder,
XRun,
}
/// Wraps [Client] or [DynamicAsyncClient] in place.
#[derive(Debug)]
pub enum JackClient {
/// Before activation.
Inactive(Client),
/// During activation.
Activating,
/// After activation. Must not be dropped for JACK thread to persist.
Active(DynamicAsyncClient),
}
/// Trait for things that wrap a JACK client.
pub trait AudioEngine {
fn transport (&self) -> Transport {
self.client().transport()
}
fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
self.client().port_by_name(name)
}
fn register_port <PS: PortSpec> (&self, name: &str, spec: PS) -> Usually<Port<PS>> {
Ok(self.client().register_port(name, spec)?)
}
fn client (&self) -> &Client;
fn activate (
self,
process: impl FnMut(&Arc<RwLock<Self>>, &Client, &ProcessScope) -> Control + Send + 'static
) -> Usually<Arc<RwLock<Self>>> where Self: Send + Sync + 'static;
fn thread_init (&self, _: &Client) {}
unsafe fn shutdown (&mut self, _status: ClientStatus, _reason: &str) {}
fn freewheel (&mut self, _: &Client, _enabled: bool) {}
fn client_registration (&mut self, _: &Client, _name: &str, _reg: bool) {}
fn port_registration (&mut self, _: &Client, _id: PortId, _reg: bool) {}
fn ports_connected (&mut self, _: &Client, _a: PortId, _b: PortId, _are: bool) {}
fn sample_rate (&mut self, _: &Client, _frames: Frames) -> Control {
Control::Continue
}
fn port_rename (&mut self, _: &Client, _id: PortId, _old: &str, _new: &str) -> Control {
Control::Continue
}
fn graph_reorder (&mut self, _: &Client) -> Control {
Control::Continue
}
fn xrun (&mut self, _: &Client) -> Control {
Control::Continue
}
}
impl AudioEngine for JackClient {
fn client(&self) -> &Client {
match self {
Self::Inactive(ref client) => client,
Self::Activating => panic!("jack client has not finished activation"),
Self::Active(ref client) => client.as_client(),
}
}
fn activate(
self,
mut cb: impl FnMut(&Arc<RwLock<Self>>, &Client, &ProcessScope) -> Control + Send + 'static,
) -> Usually<Arc<RwLock<Self>>>
where
Self: Send + Sync + 'static
{
let client = Client::from(self);
let state = Arc::new(RwLock::new(Self::Activating));
let event = Box::new(move|_|{/*TODO*/}) as Box<dyn Fn(JackEvent) + Send + Sync>;
let events = Notifications(event);
let frame = Box::new({let state = state.clone(); move|c: &_, s: &_|cb(&state, c, s)});
let frames = contrib::ClosureProcessHandler::new(frame as BoxedAudioHandler);
*state.write().unwrap() = Self::Active(client.activate_async(events, frames)?);
Ok(state)
}
}
pub type DynamicAsyncClient = AsyncClient<DynamicNotifications, DynamicAudioHandler>;
pub type DynamicAudioHandler = contrib::ClosureProcessHandler<(), BoxedAudioHandler>;
pub type BoxedAudioHandler = Box<dyn FnMut(&Client, &ProcessScope) -> Control + Send>;
impl JackClient {
pub fn new (name: &str) -> Usually<Self> {
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
Ok(Self::Inactive(client))
}
}
impl From<JackClient> for Client {
fn from (jack: JackClient) -> Client {
match jack {
JackClient::Inactive(client) => client,
JackClient::Activating => panic!("jack client still activating"),
JackClient::Active(_) => panic!("jack client already activated"),
}
}
}
/// Notification handler used by the [Jack] factory
/// when constructing [JackDevice]s.
pub type DynamicNotifications = Notifications<Box<dyn Fn(JackEvent) + Send + Sync>>;
/// Generic notification handler that emits [JackEvent]
pub struct Notifications<T: Fn(JackEvent) + Send>(pub T);
impl<T: Fn(JackEvent) + Send> NotificationHandler for Notifications<T> {
fn thread_init(&self, _: &Client) {
self.0(JackEvent::ThreadInit);
}
unsafe fn shutdown(&mut self, status: ClientStatus, reason: &str) {
self.0(JackEvent::Shutdown(status, reason.into()));
}
fn freewheel(&mut self, _: &Client, enabled: bool) {
self.0(JackEvent::Freewheel(enabled));
}
fn sample_rate(&mut self, _: &Client, frames: Frames) -> Control {
self.0(JackEvent::SampleRate(frames));
Control::Quit
}
fn client_registration(&mut self, _: &Client, name: &str, reg: bool) {
self.0(JackEvent::ClientRegistration(name.into(), reg));
}
fn port_registration(&mut self, _: &Client, id: PortId, reg: bool) {
self.0(JackEvent::PortRegistration(id, reg));
}
fn port_rename(&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
self.0(JackEvent::PortRename(id, old.into(), new.into()));
Control::Continue
}
fn ports_connected(&mut self, _: &Client, a: PortId, b: PortId, are: bool) {
self.0(JackEvent::PortsConnected(a, b, are));
}
fn graph_reorder(&mut self, _: &Client) -> Control {
self.0(JackEvent::GraphReorder);
Control::Continue
}
fn xrun(&mut self, _: &Client) -> Control {
self.0(JackEvent::XRun);
Control::Continue
}
}

View file

@ -29,7 +29,7 @@ impl PerfModel {
None
}
}
pub fn update (&self, t0: Option<u64>, scope: &jack::ProcessScope) {
pub fn update (&self, t0: Option<u64>, scope: &ProcessScope) {
if let Some(t0) = t0 {
let t1 = self.clock.raw();
self.used.store(

View file

@ -1,151 +1,14 @@
use crate::*;
/// Implement [TryFrom<&Arc<RwLock<JackClient>>>]: create app state from wrapped JACK handle.
#[macro_export] macro_rules! from_jack {
(|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc<RwLock<JackClient>>> for $Struct $(<$($L),*$($T),*>)? {
type Error = Box<dyn std::error::Error>;
fn try_from ($jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
Ok($cb)
}
}
};
}
/// Trait for thing that has a JACK process callback.
pub trait Audio: Send + Sync {
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
fn callback (
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
) -> Control where Self: Sized {
if let Ok(mut state) = state.write() {
state.process(client, scope)
} else {
Control::Quit
}
}
}
/// Implement [Audio]: provide JACK callbacks.
#[macro_export] macro_rules! audio {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
#[inline] fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control { $cb }
}
}
}
/// Trait for thing that may receive MIDI.
pub trait HasMidiIns {
fn midi_ins (&self) -> &Vec<Port<MidiIn>>;
fn midi_ins_mut (&mut self) -> &mut Vec<Port<MidiIn>>;
fn has_midi_ins (&self) -> bool {
self.midi_ins().len() > 0
}
}
/// Trait for thing that may output MIDI.
pub trait HasMidiOuts {
fn midi_outs (&self) -> &Vec<Port<MidiOut>>;
fn midi_outs_mut (&mut self) -> &mut Vec<Port<MidiOut>>;
fn has_midi_outs (&self) -> bool {
self.midi_outs().len() > 0
}
/// Buffer for serializing a MIDI event. FIXME rename
fn midi_note (&mut self) -> &mut Vec<u8>;
}
pub(crate) mod activate; pub(crate) use activate::*;
pub(crate) mod audio; pub(crate) use audio::*;
pub(crate) mod client; pub(crate) use client::*;
pub(crate) mod from_jack; pub(crate) use from_jack::*;
pub(crate) mod jack_event; pub(crate) use jack_event::*;
pub(crate) mod ports; pub(crate) use ports::*;
////////////////////////////////////////////////////////////////////////////////////
pub trait JackActivate: Sized {
fn activate_with <T: Audio + 'static> (
self,
init: impl FnOnce(&Arc<RwLock<JackClient>>)->Usually<T>
)
-> Usually<Arc<RwLock<T>>>;
}
impl JackActivate for JackClient {
fn activate_with <T: Audio + 'static> (
self,
init: impl FnOnce(&Arc<RwLock<JackClient>>)->Usually<T>
)
-> Usually<Arc<RwLock<T>>>
{
let client = Arc::new(RwLock::new(self));
let target = Arc::new(RwLock::new(init(&client)?));
let event = Box::new(move|_|{/*TODO*/}) as Box<dyn Fn(JackEvent) + Send + Sync>;
let events = Notifications(event);
let frame = Box::new({
let target = target.clone();
move|c: &_, s: &_|if let Ok(mut target) = target.write() {
target.process(c, s)
} else {
Control::Quit
}
});
let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler);
let mut buffer = Self::Activating;
std::mem::swap(&mut*client.write().unwrap(), &mut buffer);
*client.write().unwrap() = Self::Active(Client::from(buffer).activate_async(events, frames)?);
Ok(target)
}
}
/// A UI component that may be associated with a JACK client by the `Jack` factory.
pub trait AudioComponent<E: Engine>: Component<E> + Audio {
/// Perform type erasure for collecting heterogeneous devices.
fn boxed(self) -> Box<dyn AudioComponent<E>>
where
Self: Sized + 'static,
{
Box::new(self)
}
}
/// All things that implement the required traits can be treated as `AudioComponent`.
impl<E: Engine, W: Component<E> + Audio> AudioComponent<E> for W {}
/// Trait for things that may expose JACK ports.
pub trait Ports {
fn audio_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
fn audio_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
fn midi_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
fn midi_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
}
fn register_ports<T: PortSpec + Copy>(
client: &Client,
names: Vec<String>,
spec: T,
) -> Usually<BTreeMap<String, Port<T>>> {
names
.into_iter()
.try_fold(BTreeMap::new(), |mut ports, name| {
let port = client.register_port(&name, spec)?;
ports.insert(name, port);
Ok(ports)
})
}
fn query_ports(client: &Client, names: Vec<String>) -> BTreeMap<String, Port<Unowned>> {
names.into_iter().fold(BTreeMap::new(), |mut ports, name| {
let port = client.port_by_name(&name).unwrap();
ports.insert(name, port);
ports
})
}
///// A [AudioComponent] bound to a JACK client and a set of ports.
//pub struct JackDevice<E: Engine> {
///// The active JACK client of this device.
@ -373,7 +236,7 @@ fn query_ports(client: &Client, names: Vec<String>) -> BTreeMap<String, Port<Uno
////state.lock().unwrap().handle(&event).unwrap();
//}
//}) as Box<dyn Fn(JackEvent) + Send + Sync>),
//contrib::ClosureProcessHandler::new(Box::new({
//ClosureProcessHandler::new(Box::new({
//let state = state.clone();
//move |c: &Client, s: &ProcessScope| state.write().unwrap().process(c, s)
//}) as BoxedAudioHandler),
@ -406,78 +269,3 @@ fn query_ports(client: &Client, names: Vec<String>) -> BTreeMap<String, Port<Uno
//self
//}
//}
//impl Command<ArrangerModel> for ArrangerSceneCommand {
//}
//Edit(phrase) => { state.state.phrase = phrase.clone() },
//ToggleViewMode => { state.state.mode.to_next(); },
//Delete => { state.state.delete(); },
//Activate => { state.state.activate(); },
//ZoomIn => { state.state.zoom_in(); },
//ZoomOut => { state.state.zoom_out(); },
//MoveBack => { state.state.move_back(); },
//MoveForward => { state.state.move_forward(); },
//RandomColor => { state.state.randomize_color(); },
//Put => { state.state.phrase_put(); },
//Get => { state.state.phrase_get(); },
//AddScene => { state.state.scene_add(None, None)?; },
//AddTrack => { state.state.track_add(None, None)?; },
//ToggleLoop => { state.state.toggle_loop() },
//pub fn zoom_in (&mut self) {
//if let ArrangerEditorMode::Vertical(factor) = self.mode {
//self.mode = ArrangerEditorMode::Vertical(factor + 1)
//}
//}
//pub fn zoom_out (&mut self) {
//if let ArrangerEditorMode::Vertical(factor) = self.mode {
//self.mode = ArrangerEditorMode::Vertical(factor.saturating_sub(1))
//}
//}
//pub fn move_back (&mut self) {
//match self.selected {
//ArrangerEditorFocus::Scene(s) => {
//if s > 0 {
//self.scenes.swap(s, s - 1);
//self.selected = ArrangerEditorFocus::Scene(s - 1);
//}
//},
//ArrangerEditorFocus::Track(t) => {
//if t > 0 {
//self.tracks.swap(t, t - 1);
//self.selected = ArrangerEditorFocus::Track(t - 1);
//// FIXME: also swap clip order in scenes
//}
//},
//_ => todo!("arrangement: move forward")
//}
//}
//pub fn move_forward (&mut self) {
//match self.selected {
//ArrangerEditorFocus::Scene(s) => {
//if s < self.scenes.len().saturating_sub(1) {
//self.scenes.swap(s, s + 1);
//self.selected = ArrangerEditorFocus::Scene(s + 1);
//}
//},
//ArrangerEditorFocus::Track(t) => {
//if t < self.tracks.len().saturating_sub(1) {
//self.tracks.swap(t, t + 1);
//self.selected = ArrangerEditorFocus::Track(t + 1);
//// FIXME: also swap clip order in scenes
//}
//},
//_ => todo!("arrangement: move forward")
//}
//}
//impl From<Moment> for Clock {
//fn from (current: Moment) -> Self {
//Self {
//playing: Some(TransportState::Stopped).into(),
//started: None.into(),
//quant: 24.into(),
//sync: (current.timebase.ppq.get() * 4.).into(),
//current,
//}
//}
//}

View file

@ -0,0 +1,50 @@
use crate::*;
pub trait JackActivate: Sized {
fn activate_with <T: Audio + 'static> (
self,
init: impl FnOnce(&Arc<RwLock<JackClient>>)->Usually<T>
)
-> Usually<Arc<RwLock<T>>>;
}
impl JackActivate for JackClient {
fn activate_with <T: Audio + 'static> (
self,
init: impl FnOnce(&Arc<RwLock<JackClient>>)->Usually<T>
)
-> Usually<Arc<RwLock<T>>>
{
let client = Arc::new(RwLock::new(self));
let target = Arc::new(RwLock::new(init(&client)?));
let event = Box::new(move|_|{/*TODO*/}) as Box<dyn Fn(JackEvent) + Send + Sync>;
let events = Notifications(event);
let frame = Box::new({
let target = target.clone();
move|c: &_, s: &_|if let Ok(mut target) = target.write() {
target.process(c, s)
} else {
Control::Quit
}
});
let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler);
let mut buffer = Self::Activating;
std::mem::swap(&mut*client.write().unwrap(), &mut buffer);
*client.write().unwrap() = Self::Active(Client::from(buffer).activate_async(events, frames)?);
Ok(target)
}
}
/// A UI component that may be associated with a JACK client by the `Jack` factory.
pub trait AudioComponent<E: Engine>: Component<E> + Audio {
/// Perform type erasure for collecting heterogeneous devices.
fn boxed(self) -> Box<dyn AudioComponent<E>>
where
Self: Sized + 'static,
{
Box::new(self)
}
}
/// All things that implement the required traits can be treated as `AudioComponent`.
impl<E: Engine, W: Component<E> + Audio> AudioComponent<E> for W {}

View file

@ -0,0 +1,78 @@
use crate::*;
/// Implement [Audio]: provide JACK callbacks.
#[macro_export] macro_rules! audio {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
#[inline] fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control { $cb }
}
}
}
/// Trait for thing that has a JACK process callback.
pub trait Audio: Send + Sync {
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
fn callback (
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
) -> Control where Self: Sized {
if let Ok(mut state) = state.write() {
state.process(client, scope)
} else {
Control::Quit
}
}
}
/// Trait for things that wrap a JACK client.
pub trait AudioEngine {
fn transport (&self) -> Transport {
self.client().transport()
}
fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
self.client().port_by_name(name)
}
fn register_port <PS: PortSpec> (&self, name: &str, spec: PS) -> Usually<Port<PS>> {
Ok(self.client().register_port(name, spec)?)
}
fn client (&self) -> &Client;
fn activate (
self,
process: impl FnMut(&Arc<RwLock<Self>>, &Client, &ProcessScope) -> Control + Send + 'static
) -> Usually<Arc<RwLock<Self>>> where Self: Send + Sync + 'static;
fn thread_init (&self, _: &Client) {}
unsafe fn shutdown (&mut self, _status: ClientStatus, _reason: &str) {}
fn freewheel (&mut self, _: &Client, _enabled: bool) {}
fn client_registration (&mut self, _: &Client, _name: &str, _reg: bool) {}
fn port_registration (&mut self, _: &Client, _id: PortId, _reg: bool) {}
fn ports_connected (&mut self, _: &Client, _a: PortId, _b: PortId, _are: bool) {}
fn sample_rate (&mut self, _: &Client, _frames: Frames) -> Control {
Control::Continue
}
fn port_rename (&mut self, _: &Client, _id: PortId, _old: &str, _new: &str) -> Control {
Control::Continue
}
fn graph_reorder (&mut self, _: &Client) -> Control {
Control::Continue
}
fn xrun (&mut self, _: &Client) -> Control {
Control::Continue
}
}

View file

@ -0,0 +1,61 @@
use crate::*;
/// Wraps [Client] or [DynamicAsyncClient] in place.
#[derive(Debug)]
pub enum JackClient {
/// Before activation.
Inactive(Client),
/// During activation.
Activating,
/// After activation. Must not be dropped for JACK thread to persist.
Active(DynamicAsyncClient),
}
impl AudioEngine for JackClient {
fn client(&self) -> &Client {
match self {
Self::Inactive(ref client) => client,
Self::Activating => panic!("jack client has not finished activation"),
Self::Active(ref client) => client.as_client(),
}
}
fn activate(
self,
mut cb: impl FnMut(&Arc<RwLock<Self>>, &Client, &ProcessScope) -> Control + Send + 'static,
) -> Usually<Arc<RwLock<Self>>>
where
Self: Send + Sync + 'static
{
let client = Client::from(self);
let state = Arc::new(RwLock::new(Self::Activating));
let event = Box::new(move|_|{/*TODO*/}) as Box<dyn Fn(JackEvent) + Send + Sync>;
let events = Notifications(event);
let frame = Box::new({let state = state.clone(); move|c: &_, s: &_|cb(&state, c, s)});
let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler);
*state.write().unwrap() = Self::Active(client.activate_async(events, frames)?);
Ok(state)
}
}
pub type DynamicAsyncClient = AsyncClient<DynamicNotifications, DynamicAudioHandler>;
pub type DynamicAudioHandler = ClosureProcessHandler<(), BoxedAudioHandler>;
pub type BoxedAudioHandler = Box<dyn FnMut(&Client, &ProcessScope) -> Control + Send>;
impl JackClient {
pub fn new (name: &str) -> Usually<Self> {
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
Ok(Self::Inactive(client))
}
}
impl From<JackClient> for Client {
fn from (jack: JackClient) -> Client {
match jack {
JackClient::Inactive(client) => client,
JackClient::Activating => panic!("jack client still activating"),
JackClient::Active(_) => panic!("jack client already activated"),
}
}
}

View file

@ -0,0 +1,13 @@
/// Implement [TryFrom<&Arc<RwLock<JackClient>>>]: create app state from wrapped JACK handle.
#[macro_export] macro_rules! from_jack {
(|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc<RwLock<JackClient>>> for $Struct $(<$($L),*$($T),*>)? {
type Error = Box<dyn std::error::Error>;
fn try_from ($jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
Ok($cb)
}
}
};
}

View file

@ -0,0 +1,69 @@
use crate::*;
#[derive(Debug, Clone)]
/// Event enum for JACK events.
pub enum JackEvent {
ThreadInit,
Shutdown(ClientStatus, String),
Freewheel(bool),
SampleRate(Frames),
ClientRegistration(String, bool),
PortRegistration(PortId, bool),
PortRename(PortId, String, String),
PortsConnected(PortId, PortId, bool),
GraphReorder,
XRun,
}
/// Notification handler used by the [Jack] factory
/// when constructing [JackDevice]s.
pub type DynamicNotifications = Notifications<Box<dyn Fn(JackEvent) + Send + Sync>>;
/// Generic notification handler that emits [JackEvent]
pub struct Notifications<T: Fn(JackEvent) + Send>(pub T);
impl<T: Fn(JackEvent) + Send> NotificationHandler for Notifications<T> {
fn thread_init(&self, _: &Client) {
self.0(JackEvent::ThreadInit);
}
unsafe fn shutdown(&mut self, status: ClientStatus, reason: &str) {
self.0(JackEvent::Shutdown(status, reason.into()));
}
fn freewheel(&mut self, _: &Client, enabled: bool) {
self.0(JackEvent::Freewheel(enabled));
}
fn sample_rate(&mut self, _: &Client, frames: Frames) -> Control {
self.0(JackEvent::SampleRate(frames));
Control::Quit
}
fn client_registration(&mut self, _: &Client, name: &str, reg: bool) {
self.0(JackEvent::ClientRegistration(name.into(), reg));
}
fn port_registration(&mut self, _: &Client, id: PortId, reg: bool) {
self.0(JackEvent::PortRegistration(id, reg));
}
fn port_rename(&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
self.0(JackEvent::PortRename(id, old.into(), new.into()));
Control::Continue
}
fn ports_connected(&mut self, _: &Client, a: PortId, b: PortId, are: bool) {
self.0(JackEvent::PortsConnected(a, b, are));
}
fn graph_reorder(&mut self, _: &Client) -> Control {
self.0(JackEvent::GraphReorder);
Control::Continue
}
fn xrun(&mut self, _: &Client) -> Control {
self.0(JackEvent::XRun);
Control::Continue
}
}

View file

@ -0,0 +1,39 @@
use crate::*;
/// Trait for things that may expose JACK ports.
pub trait Ports {
fn audio_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
fn audio_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
fn midi_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
fn midi_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
}
fn register_ports<T: PortSpec + Copy>(
client: &Client,
names: Vec<String>,
spec: T,
) -> Usually<BTreeMap<String, Port<T>>> {
names
.into_iter()
.try_fold(BTreeMap::new(), |mut ports, name| {
let port = client.register_port(&name, spec)?;
ports.insert(name, port);
Ok(ports)
})
}
fn query_ports(client: &Client, names: Vec<String>) -> BTreeMap<String, Port<Unowned>> {
names.into_iter().fold(BTreeMap::new(), |mut ports, name| {
let port = client.port_by_name(&name).unwrap();
ports.insert(name, port);
ports
})
}

View file

@ -34,14 +34,20 @@ pub(crate) use tui::*;
pub mod edn;
pub(crate) use edn::*;
pub mod jack;
pub(crate) use jack::*;
pub mod midi;
pub(crate) use midi::*;
testmod! { test }
pub(crate) use clap::{self, Parser};
pub use better_panic;
pub use ::better_panic;
pub(crate) use better_panic::{Settings, Verbosity};
pub use atomic_float;
pub use ::atomic_float;
pub(crate) use atomic_float::*;
pub(crate) use std::sync::{Arc, Mutex, RwLock};
@ -58,28 +64,32 @@ pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem};
pub(crate) use std::cmp::{Ord, Eq, PartialEq};
pub(crate) use std::fmt::{Debug, Display};
pub use crossterm;
pub use ::crossterm;
pub(crate) use crossterm::{ExecutableCommand};
pub(crate) use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode};
pub(crate) use crossterm::event::{KeyCode, KeyModifiers, KeyEvent, KeyEventKind, KeyEventState};
pub use ratatui;
pub use ::ratatui;
pub(crate) use ratatui::{
prelude::{Style, Color, Buffer},
style::{Stylize, Modifier},
backend::{Backend, CrosstermBackend, ClearType}
};
pub use jack;
pub(crate) use jack::{
pub use ::jack as libjack;
pub(crate) use ::jack::{
contrib::ClosureProcessHandler,
Client, ProcessScope, Control, CycleTimes,
Port, PortSpec, MidiIn, MidiOut, AudioIn, AudioOut, Unowned,
Client, AsyncClient, ClientOptions, ClientStatus,
ProcessScope, Control, CycleTimes,
Port, PortId,
PortSpec, MidiIn, MidiOut, AudioIn, AudioOut, Unowned,
Transport, TransportState, MidiIter, RawMidi,
Frames,
NotificationHandler,
};
pub use midly;
pub(crate) use midly::{
pub use ::midly;
pub(crate) use ::midly::{
Smf,
MidiMessage,
TrackEventKind,
@ -87,8 +97,8 @@ pub(crate) use midly::{
num::u7
};
pub use palette;
pub(crate) use palette::{
pub use ::palette;
pub(crate) use ::palette::{
*,
convert::*,
okhsl::*

36
crates/tek/src/midi.rs Normal file
View file

@ -0,0 +1,36 @@
use crate::*;
pub(crate) mod midi_note; pub(crate) use midi_note::*;
pub(crate) mod midi_in; pub(crate) use midi_in::*;
pub(crate) mod midi_out; pub(crate) use midi_out::*;
pub(crate) mod midi_player; pub(crate) use midi_player::*;
pub(crate) mod midi_launch; pub(crate) use midi_launch::*;
pub(crate) mod midi_play; pub(crate) use midi_play::*;
pub(crate) mod midi_rec; pub(crate) use midi_rec::*;
/// Add "all notes off" to the start of a buffer.
pub fn all_notes_off (output: &mut [Vec<Vec<u8>>]) {
let mut buf = vec![];
let msg = MidiMessage::Controller { controller: 123.into(), value: 0.into() };
let evt = LiveEvent::Midi { channel: 0.into(), message: msg };
evt.write(&mut buf).unwrap();
output[0].push(buf);
}
/// Return boxed iterator of MIDI events
pub fn parse_midi_input (input: MidiIter) -> Box<dyn Iterator<Item=(usize, LiveEvent, &[u8])> + '_> {
Box::new(input.map(|RawMidi { time, bytes }|(
time as usize,
LiveEvent::parse(bytes).unwrap(),
bytes
)))
}
/// Update notes_in array
pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) {
match message {
MidiMessage::NoteOn { key, .. } => { keys[key.as_int() as usize] = true; }
MidiMessage::NoteOff { key, .. } => { keys[key.as_int() as usize] = false; },
_ => {}
}
}

View file

@ -0,0 +1,10 @@
use crate::*;
/// Trait for thing that may receive MIDI.
pub trait HasMidiIns {
fn midi_ins (&self) -> &Vec<Port<MidiIn>>;
fn midi_ins_mut (&mut self) -> &mut Vec<Port<MidiIn>>;
fn has_midi_ins (&self) -> bool {
self.midi_ins().len() > 0
}
}

View file

@ -0,0 +1,35 @@
use crate::*;
pub trait HasPlayPhrase: HasClock {
fn reset (&self) -> bool;
fn reset_mut (&mut self) -> &mut bool;
fn play_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<Phrase>>>)>;
fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<Phrase>>>)>;
fn next_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<Phrase>>>)>;
fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<Phrase>>>)>;
fn pulses_since_start (&self) -> Option<f64> {
if let Some((started, Some(_))) = self.play_phrase().as_ref() {
let elapsed = self.clock().playhead.pulse.get() - started.pulse.get();
Some(elapsed)
} else {
None
}
}
fn pulses_since_start_looped (&self) -> Option<f64> {
if let Some((started, Some(phrase))) = self.play_phrase().as_ref() {
let elapsed = self.clock().playhead.pulse.get() - started.pulse.get();
let length = phrase.read().unwrap().length.max(1); // prevent div0 on empty phrase
let elapsed = (elapsed as usize % length) as f64;
Some(elapsed)
} else {
None
}
}
fn enqueue_next (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
let start = self.clock().next_launch_pulse() as f64;
let instant = Moment::from_pulse(&self.clock().timebase(), start);
let phrase = phrase.map(|p|p.clone());
*self.next_phrase_mut() = Some((instant, phrase));
*self.reset_mut() = true;
}
}

View file

@ -0,0 +1,110 @@
use crate::*;
use Ordering::Relaxed;
pub trait MidiViewport<E: Engine>: MidiRange + MidiPoint + HasSize<E> {
/// Make sure cursor is within range
fn autoscroll (&self) {
let note_lo = self.note_lo();
let note_axis = self.note_axis();
let note_hi = self.note_hi();
let note_point = self.note_point().min(127);
if note_point < note_lo {
self.set_note_lo(note_point);
} else if note_point > note_hi {
self.set_note_lo((note_lo + note_point).saturating_sub(note_hi));
}
}
/// Make sure best usage of screen space is achieved by default
fn autozoom (&self) {
}
}
#[derive(Debug, Clone)]
pub struct MidiRangeModel {
/// Length of visible time axis
pub time_axis: Arc<AtomicUsize>,
/// Earliest time displayed
pub time_start: Arc<AtomicUsize>,
/// Time step
pub time_zoom: Arc<AtomicUsize>,
/// Auto rezoom to fit in time axis
pub time_lock: Arc<AtomicBool>,
/// Length of visible note axis
pub note_axis: Arc<AtomicUsize>,
// Lowest note displayed
pub note_lo: Arc<AtomicUsize>,
}
impl From<(usize, bool)> for MidiRangeModel {
fn from ((time_zoom, time_lock): (usize, bool)) -> Self {
Self {
note_axis: Arc::new(0.into()),
note_lo: Arc::new(0.into()),
time_axis: Arc::new(0.into()),
time_start: Arc::new(0.into()),
time_zoom: Arc::new(time_zoom.into()),
time_lock: Arc::new(time_lock.into()),
}
}
}
pub trait MidiRange {
fn time_zoom (&self) -> usize;
fn set_time_zoom (&mut self, x: usize);
fn time_lock (&self) -> bool;
fn set_time_lock (&self, x: bool);
fn time_start (&self) -> usize;
fn set_time_start (&self, x: usize);
fn note_lo (&self) -> usize;
fn set_note_lo (&self, x: usize);
fn note_axis (&self) -> usize;
fn time_axis (&self) -> usize;
fn note_hi (&self) -> usize { (self.note_lo() + self.note_axis().saturating_sub(1)).min(127) }
fn time_end (&self) -> usize { self.time_start() + self.time_axis() * self.time_zoom() }
}
impl MidiRange for MidiRangeModel {
fn time_zoom (&self) -> usize { self.time_zoom.load(Relaxed) }
fn set_time_zoom (&mut self, x: usize) { self.time_zoom.store(x, Relaxed); }
fn time_lock (&self) -> bool { self.time_lock.load(Relaxed) }
fn set_time_lock (&self, x: bool) { self.time_lock.store(x, Relaxed); }
fn time_start (&self) -> usize { self.time_start.load(Relaxed) }
fn set_time_start (&self, x: usize) { self.time_start.store(x, Relaxed); }
fn note_lo (&self) -> usize { self.note_lo.load(Relaxed).min(127) }
fn set_note_lo (&self, x: usize) { self.note_lo.store(x.min(127), Relaxed); }
fn note_axis (&self) -> usize { self.note_axis.load(Relaxed) }
fn time_axis (&self) -> usize { self.time_axis.load(Relaxed) }
}
#[derive(Debug, Clone)]
pub struct MidiPointModel {
/// Time coordinate of cursor
pub time_point: Arc<AtomicUsize>,
/// Note coordinate of cursor
pub note_point: Arc<AtomicUsize>,
/// Length of note that will be inserted, in pulses
pub note_len: Arc<AtomicUsize>,
}
impl Default for MidiPointModel {
fn default () -> Self {
Self {
time_point: Arc::new(0.into()),
note_point: Arc::new(36.into()),
note_len: Arc::new(24.into()),
}
}
}
pub trait MidiPoint {
fn note_len (&self) -> usize;
fn set_note_len (&self, x: usize);
fn note_point (&self) -> usize;
fn set_note_point (&self, x: usize);
fn time_point (&self) -> usize;
fn set_time_point (&self, x: usize);
fn note_end (&self) -> usize { self.note_point() + self.note_len() }
}
impl MidiPoint for MidiPointModel {
fn note_len (&self) -> usize { self.note_len.load(Relaxed)}
fn set_note_len (&self, x: usize) { self.note_len.store(x, Relaxed) }
fn note_point (&self) -> usize { self.note_point.load(Relaxed).min(127) }
fn set_note_point (&self, x: usize) { self.note_point.store(x.min(127), Relaxed) }
fn time_point (&self) -> usize { self.time_point.load(Relaxed) }
fn set_time_point (&self, x: usize) { self.time_point.store(x, Relaxed) }
}

View file

@ -0,0 +1,12 @@
use crate::*;
/// Trait for thing that may output MIDI.
pub trait HasMidiOuts {
fn midi_outs (&self) -> &Vec<Port<MidiOut>>;
fn midi_outs_mut (&mut self) -> &mut Vec<Port<MidiOut>>;
fn has_midi_outs (&self) -> bool {
self.midi_outs().len() > 0
}
/// Buffer for serializing a MIDI event. FIXME rename
fn midi_note (&mut self) -> &mut Vec<u8>;
}

View file

@ -0,0 +1,134 @@
use crate::*;
pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts {
fn notes_out (&self) -> &Arc<RwLock<[bool;128]>>;
/// Clear the section of the output buffer that we will be using,
/// emitting "all notes off" at start of buffer if requested.
fn clear (
&mut self, scope: &ProcessScope, out_buf: &mut Vec<Vec<Vec<u8>>>, reset: bool
) {
for frame in &mut out_buf[0..scope.n_frames() as usize] {
frame.clear();
}
if reset {
all_notes_off(out_buf);
}
}
/// Output notes from phrase to MIDI output ports.
fn play (
&mut self, scope: &ProcessScope, note_buf: &mut Vec<u8>, out_buf: &mut Vec<Vec<Vec<u8>>>
) -> bool {
let mut next = false;
// Write MIDI events from currently playing phrase (if any) to MIDI output buffer
if self.clock().is_rolling() {
let sample0 = scope.last_frame_time() as usize;
let samples = scope.n_frames() as usize;
// If no phrase is playing, prepare for switchover immediately
next = self.play_phrase().is_none();
let phrase = self.play_phrase();
let started0 = &self.clock().started;
let timebase = self.clock().timebase();
let notes_out = self.notes_out();
let next_phrase = self.next_phrase();
if let Some((started, phrase)) = phrase {
// First sample to populate. Greater than 0 means that the first
// pulse of the phrase falls somewhere in the middle of the chunk.
let sample = started.sample.get() as usize;
let sample = sample + started0.read().unwrap().as_ref().unwrap().sample.get() as usize;
let sample = sample0.saturating_sub(sample);
// Iterator that emits sample (index into output buffer at which to write MIDI event)
// paired with pulse (index into phrase from which to take the MIDI event) for each
// sample of the output buffer that corresponds to a MIDI pulse.
let pulses = timebase.pulses_between_samples(sample, sample + samples);
// Notes active during current chunk.
let notes = &mut notes_out.write().unwrap();
for (sample, pulse) in pulses {
// If a next phrase is enqueued, and we're past the end of the current one,
// break the loop here (FIXME count pulse correctly)
next = next_phrase.is_some() && if let Some(ref phrase) = phrase {
pulse >= phrase.read().unwrap().length
} else {
true
};
if next {
break
}
// If there's a currently playing phrase, output notes from it to buffer:
if let Some(ref phrase) = phrase {
// Source phrase from which the MIDI events will be taken.
let phrase = phrase.read().unwrap();
// Phrase with zero length is not processed
if phrase.length > 0 {
// Current pulse index in source phrase
let pulse = pulse % phrase.length;
// Output each MIDI event from phrase at appropriate frames of output buffer:
for message in phrase.notes[pulse].iter() {
// Clear output buffer for this MIDI event.
note_buf.clear();
// TODO: support MIDI channels other than CH1.
let channel = 0.into();
// Serialize MIDI event into message buffer.
LiveEvent::Midi { channel, message: *message }
.write(note_buf)
.unwrap();
// Append serialized message to output buffer.
out_buf[sample].push(note_buf.clone());
// Update the list of currently held notes.
update_keys(&mut*notes, &message);
}
}
}
}
}
}
next
}
/// Handle switchover from current to next playing phrase.
fn switchover (
&mut self, scope: &ProcessScope, note_buf: &mut Vec<u8>, out_buf: &mut Vec<Vec<Vec<u8>>>
) {
if self.clock().is_rolling() {
let sample0 = scope.last_frame_time() as usize;
//let samples = scope.n_frames() as usize;
if let Some((start_at, phrase)) = &self.next_phrase() {
let start = start_at.sample.get() as usize;
let sample = self.clock().started.read().unwrap().as_ref().unwrap().sample.get() as usize;
// If it's time to switch to the next phrase:
if start <= sample0.saturating_sub(sample) {
// Samples elapsed since phrase was supposed to start
let skipped = sample0 - start;
// Switch over to enqueued phrase
let started = Moment::from_sample(&self.clock().timebase(), start as f64);
*self.play_phrase_mut() = Some((started, phrase.clone()));
// Unset enqueuement (TODO: where to implement looping?)
*self.next_phrase_mut() = None
}
// TODO fill in remaining ticks of chunk from next phrase.
// ?? just call self.play(scope) again, since enqueuement is off ???
self.play(scope, note_buf, out_buf);
// ?? or must it be with modified scope ??
// likely not because start time etc
}
}
}
/// Write a chunk of MIDI notes to the output buffer.
fn write (
&mut self, scope: &ProcessScope, out_buf: &Vec<Vec<Vec<u8>>>
) {
let samples = scope.n_frames() as usize;
for port in self.midi_outs_mut().iter_mut() {
let writer = &mut port.writer(scope);
for time in 0..samples {
for event in out_buf[time].iter() {
writer.write(&RawMidi { time: time as u32, bytes: &event })
.expect(&format!("{event:?}"));
}
}
}
}
}

View file

@ -0,0 +1,255 @@
use crate::*;
pub trait MidiPlayerApi: MidiRecordApi + MidiPlaybackApi + Send + Sync {}
impl MidiPlayerApi for PhrasePlayerModel {}
pub trait HasPlayer {
fn player (&self) -> &impl MidiPlayerApi;
fn player_mut (&mut self) -> &mut impl MidiPlayerApi;
}
#[macro_export] macro_rules! has_player {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasPlayer for $Struct $(<$($L),*$($T),*>)? {
fn player (&$self) -> &impl MidiPlayerApi { &$cb }
fn player_mut (&mut $self) -> &mut impl MidiPlayerApi { &mut$cb }
}
}
}
/// Contains state for playing a phrase
pub struct PhrasePlayerModel {
/// State of clock and playhead
pub(crate) clock: ClockModel,
/// Start time and phrase being played
pub(crate) play_phrase: Option<(Moment, Option<Arc<RwLock<Phrase>>>)>,
/// Start time and next phrase
pub(crate) next_phrase: Option<(Moment, Option<Arc<RwLock<Phrase>>>)>,
/// Play input through output.
pub(crate) monitoring: bool,
/// Write input to sequence.
pub(crate) recording: bool,
/// Overdub input to sequence.
pub(crate) overdub: bool,
/// Send all notes off
pub(crate) reset: bool, // TODO?: after Some(nframes)
/// Record from MIDI ports to current sequence.
pub midi_ins: Vec<Port<MidiIn>>,
/// Play from current sequence to MIDI ports
pub midi_outs: Vec<Port<MidiOut>>,
/// Notes currently held at input
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
/// Notes currently held at output
pub(crate) notes_out: Arc<RwLock<[bool; 128]>>,
/// MIDI output buffer
pub note_buf: Vec<u8>,
}
impl std::fmt::Debug for PhrasePlayerModel {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("PhrasePlayerModel")
.field("clock", &self.clock)
.field("play_phrase", &self.play_phrase)
.field("next_phrase", &self.next_phrase)
.finish()
}
}
impl From<&ClockModel> for PhrasePlayerModel {
fn from (clock: &ClockModel) -> Self {
Self {
clock: clock.clone(),
midi_ins: vec![],
midi_outs: vec![],
note_buf: vec![0;8],
reset: true,
recording: false,
monitoring: false,
overdub: false,
play_phrase: None,
next_phrase: None,
notes_in: RwLock::new([false;128]).into(),
notes_out: RwLock::new([false;128]).into(),
}
}
}
impl From<(&ClockModel, &Arc<RwLock<Phrase>>)> for PhrasePlayerModel {
fn from ((clock, phrase): (&ClockModel, &Arc<RwLock<Phrase>>)) -> Self {
let mut model = Self::from(clock);
model.play_phrase = Some((Moment::zero(&clock.timebase), Some(phrase.clone())));
model
}
}
has_clock!(|self:PhrasePlayerModel|&self.clock);
impl HasMidiIns for PhrasePlayerModel {
fn midi_ins (&self) -> &Vec<Port<MidiIn>> {
&self.midi_ins
}
fn midi_ins_mut (&mut self) -> &mut Vec<Port<MidiIn>> {
&mut self.midi_ins
}
}
impl HasMidiOuts for PhrasePlayerModel {
fn midi_outs (&self) -> &Vec<Port<MidiOut>> {
&self.midi_outs
}
fn midi_outs_mut (&mut self) -> &mut Vec<Port<MidiOut>> {
&mut self.midi_outs
}
fn midi_note (&mut self) -> &mut Vec<u8> {
&mut self.note_buf
}
}
/// Hosts the JACK callback for a single MIDI player
pub struct PlayerAudio<'a, T: MidiPlayerApi>(
/// Player
pub &'a mut T,
/// Note buffer
pub &'a mut Vec<u8>,
/// Note chunk buffer
pub &'a mut Vec<Vec<Vec<u8>>>,
);
/// JACK process callback for a sequencer's phrase player/recorder.
impl<'a, T: MidiPlayerApi> Audio for PlayerAudio<'a, T> {
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
let model = &mut self.0;
let note_buf = &mut self.1;
let midi_buf = &mut self.2;
// Clear output buffer(s)
model.clear(scope, midi_buf, false);
// Write chunk of phrase to output, handle switchover
if model.play(scope, note_buf, midi_buf) {
model.switchover(scope, note_buf, midi_buf);
}
if model.has_midi_ins() {
if model.recording() || model.monitoring() {
// Record and/or monitor input
model.record(scope, midi_buf)
} else if model.has_midi_outs() && model.monitoring() {
// Monitor input to output
model.monitor(scope, midi_buf)
}
}
// Write to output port(s)
model.write(scope, midi_buf);
Control::Continue
}
}
impl MidiRecordApi for PhrasePlayerModel {
fn recording (&self) -> bool {
self.recording
}
fn recording_mut (&mut self) -> &mut bool {
&mut self.recording
}
fn monitoring (&self) -> bool {
self.monitoring
}
fn monitoring_mut (&mut self) -> &mut bool {
&mut self.monitoring
}
fn overdub (&self) -> bool {
self.overdub
}
fn overdub_mut (&mut self) -> &mut bool {
&mut self.overdub
}
fn notes_in (&self) -> &Arc<RwLock<[bool; 128]>> {
&self.notes_in
}
}
impl MidiPlaybackApi for PhrasePlayerModel {
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
&self.notes_in
}
}
impl HasPlayPhrase for PhrasePlayerModel {
fn reset (&self) -> bool {
self.reset
}
fn reset_mut (&mut self) -> &mut bool {
&mut self.reset
}
fn play_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<Phrase>>>)> {
&self.play_phrase
}
fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<Phrase>>>)> {
&mut self.play_phrase
}
fn next_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<Phrase>>>)> {
&self.next_phrase
}
fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<Phrase>>>)> {
&mut self.next_phrase
}
}
//#[derive(Debug)]
//pub struct MIDIPlayer {
///// Global timebase
//pub clock: Arc<Clock>,
///// Start time and phrase being played
//pub play_phrase: Option<(Moment, Option<Arc<RwLock<Phrase>>>)>,
///// Start time and next phrase
//pub next_phrase: Option<(Moment, Option<Arc<RwLock<Phrase>>>)>,
///// Play input through output.
//pub monitoring: bool,
///// Write input to sequence.
//pub recording: bool,
///// Overdub input to sequence.
//pub overdub: bool,
///// Send all notes off
//pub reset: bool, // TODO?: after Some(nframes)
///// Record from MIDI ports to current sequence.
//pub midi_inputs: Vec<Port<MidiIn>>,
///// Play from current sequence to MIDI ports
//pub midi_outputs: Vec<Port<MidiOut>>,
///// MIDI output buffer
//pub midi_note: Vec<u8>,
///// MIDI output buffer
//pub midi_chunk: Vec<Vec<Vec<u8>>>,
///// Notes currently held at input
//pub notes_in: Arc<RwLock<[bool; 128]>>,
///// Notes currently held at output
//pub notes_out: Arc<RwLock<[bool; 128]>>,
//}
///// Methods used primarily by the process callback
//impl MIDIPlayer {
//pub fn new (
//jack: &Arc<RwLock<JackClient>>,
//clock: &Arc<Clock>,
//name: &str
//) -> Usually<Self> {
//let jack = jack.read().unwrap();
//Ok(Self {
//clock: clock.clone(),
//phrase: None,
//next_phrase: None,
//notes_in: Arc::new(RwLock::new([false;128])),
//notes_out: Arc::new(RwLock::new([false;128])),
//monitoring: false,
//recording: false,
//overdub: true,
//reset: true,
//midi_note: Vec::with_capacity(8),
//midi_chunk: vec![Vec::with_capacity(16);16384],
//midi_outputs: vec![
//jack.client().register_port(format!("{name}_out0").as_str(), MidiOut::default())?
//],
//midi_inputs: vec![
//jack.client().register_port(format!("{name}_in0").as_str(), MidiIn::default())?
//],
//})
//}
//}

View file

@ -0,0 +1,75 @@
use crate::*;
pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns {
fn notes_in (&self) -> &Arc<RwLock<[bool;128]>>;
fn recording (&self) -> bool;
fn recording_mut (&mut self) -> &mut bool;
fn toggle_record (&mut self) {
*self.recording_mut() = !self.recording();
}
fn record (&mut self, scope: &ProcessScope, midi_buf: &mut Vec<Vec<Vec<u8>>>) {
let sample0 = scope.last_frame_time() as usize;
// For highlighting keys and note repeat
let notes_in = self.notes_in().clone();
if self.clock().is_rolling() {
if let Some((started, ref phrase)) = self.play_phrase().clone() {
let start = started.sample.get() as usize;
let quant = self.clock().quant.get();
let timebase = self.clock().timebase().clone();
let monitoring = self.monitoring();
let recording = self.recording();
for input in self.midi_ins_mut().iter() {
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
if let LiveEvent::Midi { message, .. } = event {
if monitoring {
midi_buf[sample].push(bytes.to_vec())
}
if recording {
if let Some(phrase) = phrase {
let mut phrase = phrase.write().unwrap();
let length = phrase.length;
phrase.record_event({
let sample = (sample0 + sample - start) as f64;
let pulse = timebase.samples_to_pulse(sample);
let quantized = (pulse / quant).round() * quant;
let looped = quantized as usize % length;
looped
}, message);
}
}
update_keys(&mut*notes_in.write().unwrap(), &message);
}
}
}
}
if let Some((start_at, phrase)) = &self.next_phrase() {
// TODO switch to next phrase and record into it
}
}
}
fn monitoring (&self) -> bool;
fn monitoring_mut (&mut self) -> &mut bool;
fn toggle_monitor (&mut self) {
*self.monitoring_mut() = !self.monitoring();
}
fn monitor (&mut self, scope: &ProcessScope, midi_buf: &mut Vec<Vec<Vec<u8>>>) {
// For highlighting keys and note repeat
let notes_in = self.notes_in().clone();
for input in self.midi_ins_mut().iter() {
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
if let LiveEvent::Midi { message, .. } = event {
midi_buf[sample].push(bytes.to_vec());
update_keys(&mut*notes_in.write().unwrap(), &message);
}
}
}
}
fn overdub (&self) -> bool;
fn overdub_mut (&mut self) -> &mut bool;
fn toggle_overdub (&mut self) {
*self.overdub_mut() = !self.overdub();
}
}

View file

@ -1121,3 +1121,66 @@ pub fn arranger_content_horizontal (
//)
//)
//}
//impl Command<ArrangerModel> for ArrangerSceneCommand {
//}
//Edit(phrase) => { state.state.phrase = phrase.clone() },
//ToggleViewMode => { state.state.mode.to_next(); },
//Delete => { state.state.delete(); },
//Activate => { state.state.activate(); },
//ZoomIn => { state.state.zoom_in(); },
//ZoomOut => { state.state.zoom_out(); },
//MoveBack => { state.state.move_back(); },
//MoveForward => { state.state.move_forward(); },
//RandomColor => { state.state.randomize_color(); },
//Put => { state.state.phrase_put(); },
//Get => { state.state.phrase_get(); },
//AddScene => { state.state.scene_add(None, None)?; },
//AddTrack => { state.state.track_add(None, None)?; },
//ToggleLoop => { state.state.toggle_loop() },
//pub fn zoom_in (&mut self) {
//if let ArrangerEditorMode::Vertical(factor) = self.mode {
//self.mode = ArrangerEditorMode::Vertical(factor + 1)
//}
//}
//pub fn zoom_out (&mut self) {
//if let ArrangerEditorMode::Vertical(factor) = self.mode {
//self.mode = ArrangerEditorMode::Vertical(factor.saturating_sub(1))
//}
//}
//pub fn move_back (&mut self) {
//match self.selected {
//ArrangerEditorFocus::Scene(s) => {
//if s > 0 {
//self.scenes.swap(s, s - 1);
//self.selected = ArrangerEditorFocus::Scene(s - 1);
//}
//},
//ArrangerEditorFocus::Track(t) => {
//if t > 0 {
//self.tracks.swap(t, t - 1);
//self.selected = ArrangerEditorFocus::Track(t - 1);
//// FIXME: also swap clip order in scenes
//}
//},
//_ => todo!("arrangement: move forward")
//}
//}
//pub fn move_forward (&mut self) {
//match self.selected {
//ArrangerEditorFocus::Scene(s) => {
//if s < self.scenes.len().saturating_sub(1) {
//self.scenes.swap(s, s + 1);
//self.selected = ArrangerEditorFocus::Scene(s + 1);
//}
//},
//ArrangerEditorFocus::Track(t) => {
//if t < self.tracks.len().saturating_sub(1) {
//self.tracks.swap(t, t + 1);
//self.selected = ArrangerEditorFocus::Track(t + 1);
//// FIXME: also swap clip order in scenes
//}
//},
//_ => todo!("arrangement: move forward")
//}
//}