mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
refactor midi module
This commit is contained in:
parent
1723826cc2
commit
7f55c3bfc8
8 changed files with 333 additions and 330 deletions
|
|
@ -1,23 +1,25 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
use std::sync::RwLock;
|
|
||||||
|
|
||||||
impl<E: Engine> Layout<E> for E {}
|
impl<E: Engine> Layout<E> for E {}
|
||||||
|
|
||||||
pub trait Layout<E: Engine> {
|
pub trait Layout<E: Engine> {
|
||||||
/// Content `item` when `cond` is true.
|
/// Content `item` when `cond` is true.
|
||||||
fn when <A: Content<E>> (cond: bool, item: A) -> When<E, A> {
|
fn when <A> (cond: bool, item: A) -> When<E, A> where
|
||||||
|
A: Content<E>
|
||||||
|
{
|
||||||
When(cond, item, Default::default())
|
When(cond, item, Default::default())
|
||||||
}
|
}
|
||||||
/// Content `item` if `cond` is true, otherwise render `other`.
|
/// Content `item` if `cond` is true, otherwise render `other`.
|
||||||
fn either <A: Content<E>, B: Content<E>> (cond: bool, a: A, b: B)
|
fn either <A, B> (cond: bool, a: A, b: B) -> Either<E, A, B> where
|
||||||
-> Either<E, A, B>
|
A: Content<E>,
|
||||||
|
B: Content<E>,
|
||||||
{
|
{
|
||||||
Either(cond, a, b, Default::default())
|
Either(cond, a, b, Default::default())
|
||||||
}
|
}
|
||||||
/// Maps an [Option<T>] through a callback `F`
|
/// Maps an [Option<T>] through a callback `F`
|
||||||
fn opt <A, F: Fn(A)->R, R: Content<E>> (option: Option<A>, cb: F)
|
fn opt <A, F, R> (option: Option<A>, cb: F) -> Opt<E, A, F, R> where
|
||||||
-> Opt<E, A, F, R>
|
F: Fn(A) -> R,
|
||||||
|
R: Content<E>
|
||||||
{
|
{
|
||||||
Opt(option, cb, Default::default())
|
Opt(option, cb, Default::default())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
322
src/midi.rs
322
src/midi.rs
|
|
@ -3,8 +3,9 @@ use crate::*;
|
||||||
pub(crate) mod midi_pool; pub(crate) use midi_pool::*;
|
pub(crate) mod midi_pool; pub(crate) use midi_pool::*;
|
||||||
pub(crate) mod midi_clip; pub(crate) use midi_clip::*;
|
pub(crate) mod midi_clip; pub(crate) use midi_clip::*;
|
||||||
pub(crate) mod midi_launch; pub(crate) use midi_launch::*;
|
pub(crate) mod midi_launch; pub(crate) use midi_launch::*;
|
||||||
pub(crate) mod midi_play; pub(crate) use midi_play::*;
|
pub(crate) mod midi_player; pub(crate) use midi_player::*;
|
||||||
pub(crate) mod midi_rec; pub(crate) use midi_rec::*;
|
pub(crate) mod midi_in; pub(crate) use midi_in::*;
|
||||||
|
pub(crate) mod midi_out; pub(crate) use midi_out::*;
|
||||||
|
|
||||||
pub(crate) mod midi_note; pub(crate) use midi_note::*;
|
pub(crate) mod midi_note; pub(crate) use midi_note::*;
|
||||||
pub(crate) mod midi_range; pub(crate) use midi_range::*;
|
pub(crate) mod midi_range; pub(crate) use midi_range::*;
|
||||||
|
|
@ -13,26 +14,6 @@ pub(crate) mod midi_view; pub(crate) use midi_view::*;
|
||||||
|
|
||||||
pub(crate) mod midi_editor; pub(crate) use midi_editor::*;
|
pub(crate) mod midi_editor; pub(crate) use midi_editor::*;
|
||||||
|
|
||||||
/// 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().is_empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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().is_empty()
|
|
||||||
}
|
|
||||||
/// Buffer for serializing a MIDI event. FIXME rename
|
|
||||||
fn midi_note (&mut self) -> &mut Vec<u8>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add "all notes off" to the start of a buffer.
|
/// Add "all notes off" to the start of a buffer.
|
||||||
pub fn all_notes_off (output: &mut [Vec<Vec<u8>>]) {
|
pub fn all_notes_off (output: &mut [Vec<Vec<u8>>]) {
|
||||||
let mut buf = vec![];
|
let mut buf = vec![];
|
||||||
|
|
@ -59,300 +40,3 @@ pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_note_name (n: usize) -> &'static str {
|
|
||||||
if n > 127 {
|
|
||||||
panic!("to_note_name({n}): must be 0-127");
|
|
||||||
}
|
|
||||||
MIDI_NOTE_NAMES[n]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const MIDI_NOTE_NAMES: [&str; 128] = [
|
|
||||||
"C0", "C#0", "D0", "D#0", "E0", "F0", "F#0", "G0", "G#0", "A0", "A#0", "B0",
|
|
||||||
"C1", "C#1", "D1", "D#1", "E1", "F1", "F#1", "G1", "G#1", "A1", "A#1", "B1",
|
|
||||||
"C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2",
|
|
||||||
"C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3",
|
|
||||||
"C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A#4", "B4",
|
|
||||||
"C5", "C#5", "D5", "D#5", "E5", "F5", "F#5", "G5", "G#5", "A5", "A#5", "B5",
|
|
||||||
"C6", "C#6", "D6", "D#6", "E6", "F6", "F#6", "G6", "G#6", "A6", "A#6", "B6",
|
|
||||||
"C7", "C#7", "D7", "D#7", "E7", "F7", "F#7", "G7", "G#7", "A7", "A#7", "B7",
|
|
||||||
"C8", "C#8", "D8", "D#8", "E8", "F8", "F#8", "G8", "G#8", "A8", "A#8", "B8",
|
|
||||||
"C9", "C#9", "D9", "D#9", "E9", "F9", "F#9", "G9", "G#9", "A9", "A#9", "B9",
|
|
||||||
"C10", "C#10", "D10", "D#10", "E10", "F10", "F#10", "G10",
|
|
||||||
];
|
|
||||||
|
|
||||||
pub trait MidiPlayerApi: MidiRecordApi + MidiPlaybackApi + Send + Sync {}
|
|
||||||
|
|
||||||
impl MidiPlayerApi for MidiPlayer {}
|
|
||||||
|
|
||||||
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 MidiPlayer {
|
|
||||||
/// State of clock and playhead
|
|
||||||
pub(crate) clock: Clock,
|
|
||||||
/// Start time and phrase being played
|
|
||||||
pub(crate) play_phrase: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
|
||||||
/// Start time and next phrase
|
|
||||||
pub(crate) next_phrase: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
|
||||||
/// 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 MidiPlayer {
|
|
||||||
pub fn new (
|
|
||||||
jack: &Arc<RwLock<JackConnection>>,
|
|
||||||
name: impl AsRef<str>,
|
|
||||||
midi_from: &[impl AsRef<str>],
|
|
||||||
midi_to: &[impl AsRef<str>],
|
|
||||||
) -> Usually<Self> {
|
|
||||||
let name = name.as_ref();
|
|
||||||
Ok(Self {
|
|
||||||
clock: Clock::from(jack),
|
|
||||||
play_phrase: None,
|
|
||||||
next_phrase: None,
|
|
||||||
recording: false,
|
|
||||||
monitoring: false,
|
|
||||||
overdub: false,
|
|
||||||
|
|
||||||
notes_in: RwLock::new([false;128]).into(),
|
|
||||||
midi_ins: vec![
|
|
||||||
jack.midi_in(&format!("M/{name}"), midi_from)?,
|
|
||||||
],
|
|
||||||
|
|
||||||
midi_outs: vec![
|
|
||||||
jack.midi_out(&format!("{name}/M"), midi_to)?,
|
|
||||||
],
|
|
||||||
notes_out: RwLock::new([false;128]).into(),
|
|
||||||
reset: true,
|
|
||||||
|
|
||||||
note_buf: vec![0;8],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::fmt::Debug for MidiPlayer {
|
|
||||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
|
||||||
f.debug_struct("MidiPlayer")
|
|
||||||
.field("clock", &self.clock)
|
|
||||||
.field("play_phrase", &self.play_phrase)
|
|
||||||
.field("next_phrase", &self.next_phrase)
|
|
||||||
.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_phrase: None,
|
|
||||||
next_phrase: None,
|
|
||||||
notes_in: RwLock::new([false;128]).into(),
|
|
||||||
notes_out: RwLock::new([false;128]).into(),
|
|
||||||
});
|
|
||||||
from!(|state: (&Clock, &Arc<RwLock<MidiClip>>)|MidiPlayer = {
|
|
||||||
let (clock, phrase) = state;
|
|
||||||
let mut model = Self::from(clock);
|
|
||||||
model.play_phrase = Some((Moment::zero(&clock.timebase), Some(phrase.clone())));
|
|
||||||
model
|
|
||||||
});
|
|
||||||
has_clock!(|self: MidiPlayer|&self.clock);
|
|
||||||
|
|
||||||
impl HasMidiIns for MidiPlayer {
|
|
||||||
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 MidiPlayer {
|
|
||||||
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<T: MidiPlayerApi> Audio for PlayerAudio<'_, 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 MidiPlayer {
|
|
||||||
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 MidiPlayer {
|
|
||||||
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
|
|
||||||
&self.notes_out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasPlayPhrase for MidiPlayer {
|
|
||||||
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<MidiClip>>>)> {
|
|
||||||
&self.play_phrase
|
|
||||||
}
|
|
||||||
fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)> {
|
|
||||||
&mut self.play_phrase
|
|
||||||
}
|
|
||||||
fn next_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<MidiClip>>>)> {
|
|
||||||
&self.next_phrase
|
|
||||||
}
|
|
||||||
fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)> {
|
|
||||||
&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<JackConnection>>,
|
|
||||||
//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())?
|
|
||||||
//],
|
|
||||||
//})
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,14 @@
|
||||||
use crate::*;
|
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().is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns {
|
pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns {
|
||||||
fn notes_in (&self) -> &Arc<RwLock<[bool;128]>>;
|
fn notes_in (&self) -> &Arc<RwLock<[bool;128]>>;
|
||||||
|
|
||||||
|
|
@ -3,6 +3,26 @@ use crate::*;
|
||||||
pub struct Note;
|
pub struct Note;
|
||||||
|
|
||||||
impl Note {
|
impl Note {
|
||||||
|
pub const NAMES: [&str; 128] = [
|
||||||
|
"C0", "C#0", "D0", "D#0", "E0", "F0", "F#0", "G0", "G#0", "A0", "A#0", "B0",
|
||||||
|
"C1", "C#1", "D1", "D#1", "E1", "F1", "F#1", "G1", "G#1", "A1", "A#1", "B1",
|
||||||
|
"C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2",
|
||||||
|
"C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3",
|
||||||
|
"C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A#4", "B4",
|
||||||
|
"C5", "C#5", "D5", "D#5", "E5", "F5", "F#5", "G5", "G#5", "A5", "A#5", "B5",
|
||||||
|
"C6", "C#6", "D6", "D#6", "E6", "F6", "F#6", "G6", "G#6", "A6", "A#6", "B6",
|
||||||
|
"C7", "C#7", "D7", "D#7", "E7", "F7", "F#7", "G7", "G#7", "A7", "A#7", "B7",
|
||||||
|
"C8", "C#8", "D8", "D#8", "E8", "F8", "F#8", "G8", "G#8", "A8", "A#8", "B8",
|
||||||
|
"C9", "C#9", "D9", "D#9", "E9", "F9", "F#9", "G9", "G#9", "A9", "A#9", "B9",
|
||||||
|
"C10", "C#10", "D10", "D#10", "E10", "F10", "F#10", "G10",
|
||||||
|
];
|
||||||
|
pub fn pitch_to_name (n: usize) -> &'static str {
|
||||||
|
if n > 127 {
|
||||||
|
panic!("to_note_name({n}): must be 0-127");
|
||||||
|
}
|
||||||
|
Self::NAMES[n]
|
||||||
|
}
|
||||||
|
|
||||||
/// (pulses, name), assuming 96 PPQ
|
/// (pulses, name), assuming 96 PPQ
|
||||||
pub const DURATIONS: [(usize, &str);26] = [
|
pub const DURATIONS: [(usize, &str);26] = [
|
||||||
(1, "1/384"), (2, "1/192"),
|
(1, "1/384"), (2, "1/192"),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,16 @@
|
||||||
use crate::*;
|
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().is_empty()
|
||||||
|
}
|
||||||
|
/// Buffer for serializing a MIDI event. FIXME rename
|
||||||
|
fn midi_note (&mut self) -> &mut Vec<u8>;
|
||||||
|
}
|
||||||
|
|
||||||
pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts {
|
pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts {
|
||||||
|
|
||||||
fn notes_out (&self) -> &Arc<RwLock<[bool;128]>>;
|
fn notes_out (&self) -> &Arc<RwLock<[bool;128]>>;
|
||||||
277
src/midi/midi_player.rs
Normal file
277
src/midi/midi_player.rs
Normal file
|
|
@ -0,0 +1,277 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub trait MidiPlayerApi: MidiRecordApi + MidiPlaybackApi + Send + Sync {}
|
||||||
|
|
||||||
|
impl MidiPlayerApi for MidiPlayer {}
|
||||||
|
|
||||||
|
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 MidiPlayer {
|
||||||
|
/// State of clock and playhead
|
||||||
|
pub(crate) clock: Clock,
|
||||||
|
/// Start time and phrase being played
|
||||||
|
pub(crate) play_phrase: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
||||||
|
/// Start time and next phrase
|
||||||
|
pub(crate) next_phrase: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
||||||
|
/// 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 MidiPlayer {
|
||||||
|
pub fn new (
|
||||||
|
jack: &Arc<RwLock<JackConnection>>,
|
||||||
|
name: impl AsRef<str>,
|
||||||
|
midi_from: &[impl AsRef<str>],
|
||||||
|
midi_to: &[impl AsRef<str>],
|
||||||
|
) -> Usually<Self> {
|
||||||
|
let name = name.as_ref();
|
||||||
|
Ok(Self {
|
||||||
|
clock: Clock::from(jack),
|
||||||
|
play_phrase: None,
|
||||||
|
next_phrase: None,
|
||||||
|
recording: false,
|
||||||
|
monitoring: false,
|
||||||
|
overdub: false,
|
||||||
|
|
||||||
|
notes_in: RwLock::new([false;128]).into(),
|
||||||
|
midi_ins: vec![
|
||||||
|
jack.midi_in(&format!("M/{name}"), midi_from)?,
|
||||||
|
],
|
||||||
|
|
||||||
|
midi_outs: vec![
|
||||||
|
jack.midi_out(&format!("{name}/M"), midi_to)?,
|
||||||
|
],
|
||||||
|
notes_out: RwLock::new([false;128]).into(),
|
||||||
|
reset: true,
|
||||||
|
|
||||||
|
note_buf: vec![0;8],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Debug for MidiPlayer {
|
||||||
|
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
f.debug_struct("MidiPlayer")
|
||||||
|
.field("clock", &self.clock)
|
||||||
|
.field("play_phrase", &self.play_phrase)
|
||||||
|
.field("next_phrase", &self.next_phrase)
|
||||||
|
.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_phrase: None,
|
||||||
|
next_phrase: None,
|
||||||
|
notes_in: RwLock::new([false;128]).into(),
|
||||||
|
notes_out: RwLock::new([false;128]).into(),
|
||||||
|
});
|
||||||
|
from!(|state: (&Clock, &Arc<RwLock<MidiClip>>)|MidiPlayer = {
|
||||||
|
let (clock, phrase) = state;
|
||||||
|
let mut model = Self::from(clock);
|
||||||
|
model.play_phrase = Some((Moment::zero(&clock.timebase), Some(phrase.clone())));
|
||||||
|
model
|
||||||
|
});
|
||||||
|
has_clock!(|self: MidiPlayer|&self.clock);
|
||||||
|
|
||||||
|
impl HasMidiIns for MidiPlayer {
|
||||||
|
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 MidiPlayer {
|
||||||
|
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<T: MidiPlayerApi> Audio for PlayerAudio<'_, 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 MidiPlayer {
|
||||||
|
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 MidiPlayer {
|
||||||
|
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
|
||||||
|
&self.notes_out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasPlayPhrase for MidiPlayer {
|
||||||
|
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<MidiClip>>>)> {
|
||||||
|
&self.play_phrase
|
||||||
|
}
|
||||||
|
fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)> {
|
||||||
|
&mut self.play_phrase
|
||||||
|
}
|
||||||
|
fn next_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<MidiClip>>>)> {
|
||||||
|
&self.next_phrase
|
||||||
|
}
|
||||||
|
fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)> {
|
||||||
|
&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<JackConnection>>,
|
||||||
|
//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())?
|
||||||
|
//],
|
||||||
|
//})
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
@ -19,9 +19,9 @@ render!(Tui: |self: PianoHorizontalKeys<'a>, to|{
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if note == note_point {
|
if note == note_point {
|
||||||
to.blit(&format!("{:<5}", to_note_name(note)), x, screen_y, on_style)
|
to.blit(&format!("{:<5}", Note::pitch_to_name(note)), x, screen_y, on_style)
|
||||||
} else {
|
} else {
|
||||||
to.blit(&to_note_name(note), x, screen_y, off_style)
|
to.blit(&Note::pitch_to_name(note), x, screen_y, off_style)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ render!(Tui: (self:MidiEditStatus<'a>) => {
|
||||||
if self.0.time_lock().get() { "[lock]" } else { " " })),
|
if self.0.time_lock().get() { "[lock]" } else { " " })),
|
||||||
" ",
|
" ",
|
||||||
field(" Note", format!("{} ({}) {} | {}-{} ({})",
|
field(" Note", format!("{} ({}) {} | {}-{} ({})",
|
||||||
self.0.note_point(), to_note_name(self.0.note_point()), self.0.note_len(),
|
self.0.note_point(), Note::pitch_to_name(self.0.note_point()), self.0.note_len(),
|
||||||
to_note_name(self.0.note_lo().get()), to_note_name(self.0.note_hi()),
|
Note::pitch_to_name(self.0.note_lo().get()), Note::pitch_to_name(self.0.note_hi()),
|
||||||
self.0.note_axis().get()))
|
self.0.note_axis().get()))
|
||||||
))))
|
))))
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue