wip: separate PhrasePlayer vs PhraseEditor

This commit is contained in:
🪞👃🪞 2024-10-08 12:05:47 +03:00
parent 7668a6f339
commit 25e54eba4e
8 changed files with 491 additions and 636 deletions

View file

@ -1,53 +1,33 @@
use crate::*;
/// A collection of MIDI messages.
pub type PhraseData = Vec<Vec<MidiMessage>>;
/// MIDI message serialized to bytes
pub type MIDIMessage = Vec<u8>;
/// MIDI message structural
pub type PhraseData = Vec<Vec<MidiMessage>>;
/// MIDI message serialized
pub type PhraseMessage = Vec<u8>;
/// Collection of serialized MIDI messages
pub type MIDIChunk = [Vec<MIDIMessage>];
/// Contains all phrases in the project
pub type PhraseChunk = [Vec<PhraseMessage>];
/// Root level object for standalone `tek_sequencer`
pub struct Sequencer<E: Engine> {
/// Controls the JACK transport.
pub transport: Option<Arc<RwLock<TransportToolbar<E>>>>,
/// Pool of all phrases available to the sequencer
pub phrases: Arc<RwLock<PhrasePool<E>>>,
/// Phrase editor view
pub editor: PhraseEditor<E>,
}
/// Contains all phrases in a project
pub struct PhrasePool<E: Engine> {
_engine: PhantomData<E>,
/// Phrases in the pool
pub phrases: Vec<Arc<RwLock<Option<Phrase>>>>,
}
impl<E: Engine> PhrasePool<E> {
pub fn new () -> Self {
Self {
_engine: Default::default(),
phrases: vec![Arc::new(RwLock::new(Some(Phrase::default())))]
}
}
}
/// Contains state for viewing and editing a phrase
pub struct PhraseEditor<E: Engine> {
_engine: PhantomData<E>,
pub phrase: Arc<RwLock<Option<Phrase>>>,
}
impl<E: Engine> PhraseEditor<E> {
pub fn new () -> Self {
Self {
_engine: Default::default(),
phrase: Arc::new(RwLock::new(None)),
}
}
pub fn show (&mut self, phrase: &Arc<RwLock<Option<Phrase>>>) {
self.phrase = phrase.clone();
}
}
/// A MIDI sequence.
#[derive(Debug)]
pub struct Phrase {
/// Name of phrase
pub name: Arc<RwLock<String>>,
/// Length of phrase
/// Temporal resolution in pulses per quarter note
pub ppq: usize,
/// Length of phrase in pulses
pub length: usize,
/// Notes in phrase
pub notes: PhraseData,
@ -60,15 +40,89 @@ pub struct Phrase {
/// All notes are displayed with minimum length
pub percussive: bool,
}
/// Contains state for viewing and editing a phrase
pub struct PhraseEditor<E: Engine> {
_engine: PhantomData<E>,
/// Phrase being played
pub phrase: Option<Arc<RwLock<Phrase>>>,
/// The full piano keys are rendered to this buffer
pub keys: Buffer,
/// The full piano roll is rendered to this buffer
pub buffer: BigBuffer,
/// Cursor/scroll/zoom in pitch axis
pub note_axis: FixedAxis<usize>,
/// Cursor/scroll/zoom in time axis
pub time_axis: ScaledAxis<usize>,
/// Whether this widget is focused
pub focused: bool,
/// Whether note enter mode is enabled
pub entered: bool,
/// Display mode
pub mode: bool,
/// Notes currently held at input
pub notes_in: Arc<RwLock<[bool; 128]>>,
/// Notes currently held at output
pub notes_out: Arc<RwLock<[bool; 128]>>,
}
/// Phrase player.
pub struct PhrasePlayer<E: Engine> {
_engine: PhantomData<E>,
/// Phrase being played
pub phrase: Option<Arc<RwLock<Phrase>>>,
/// Notes currently held at input
pub notes_in: Arc<RwLock<[bool; 128]>>,
/// Notes currently held at output
pub notes_out: Arc<RwLock<[bool; 128]>>,
/// Current point in playing phrase
pub now: usize,
/// Play input through output.
pub monitoring: bool,
/// Write input to sequence.
pub recording: bool,
/// Overdub input to sequence.
pub overdub: bool,
/// Output from current sequence.
pub midi_out: Option<Port<MidiOut>>,
/// MIDI output buffer
midi_out_buf: Vec<Vec<Vec<u8>>>,
/// Send all notes off
pub reset: bool, // TODO?: after Some(nframes)
}
impl<E: Engine> PhrasePool<E> {
pub fn new () -> Self {
Self {
_engine: Default::default(),
phrases: vec![Arc::new(RwLock::new(Some(Phrase::default())))]
}
}
}
impl<E: Engine> PhraseEditor<E> {
pub fn new () -> Self {
Self {
_engine: Default::default(),
phrase: None,
notes_in: Arc::new(RwLock::new([false;128])),
notes_out: Arc::new(RwLock::new([false;128])),
keys: keys_vert(),
buffer: Default::default(),
note_axis: FixedAxis { start: 12, point: Some(36) },
time_axis: ScaledAxis { start: 0, scale: 24, point: Some(0) },
focused: false,
entered: false,
mode: false,
}
}
}
impl Default for Phrase {
fn default () -> Self { Self::new("", false, 0, None) }
}
impl Phrase {
pub fn new (name: &str, loop_on: bool, length: usize, notes: Option<PhraseData>) -> Self {
pub fn new (
name: &str, loop_on: bool, length: usize, notes: Option<PhraseData>
) -> Self {
Self {
name: Arc::new(RwLock::new(name.into())),
name: Arc::new(RwLock::new(name.into())),
ppq: PPQ,
length,
notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]),
loop_on,
@ -106,7 +160,7 @@ impl Phrase {
/// Write a chunk of MIDI events to an output port.
pub fn process_out (
&self,
output: &mut MIDIChunk,
output: &mut PhraseChunk,
notes_on: &mut [bool;128],
timebase: &Arc<Timebase>,
(frame0, frames, _): (usize, usize, f64),
@ -130,136 +184,21 @@ impl Phrase {
}
}
}
pub fn from_edn <'e> (ppq: usize, args: &[Edn<'e>]) -> Usually<Self> {
let mut phrase = Self::default();
let mut name = String::new();
let mut beats = 0usize;
let mut steps = 0usize;
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Int(b)) = map.get(&Edn::Key(":beats")) {
beats = *b as usize;
phrase.length = ppq * beats;
for _ in phrase.notes.len()..phrase.length {
phrase.notes.push(Vec::with_capacity(16))
}
}
if let Some(Edn::Int(s)) = map.get(&Edn::Key(":steps")) {
steps = *s as usize;
}
},
Edn::List(args) => {
let time = (match args.get(0) {
Some(Edn::Key(text)) => text[1..].parse::<f64>()?,
Some(Edn::Int(i)) => *i as f64,
Some(Edn::Double(f)) => f64::from(*f),
_ => panic!("unexpected in phrase '{name}': {:?}", args.get(0)),
} * beats as f64 * ppq as f64 / steps as f64) as usize;
for edn in args[1..].iter() {
match edn {
Edn::List(args) => if let (
Some(Edn::Int(key)),
Some(Edn::Int(vel)),
) = (
args.get(0),
args.get(1),
) {
let (key, vel) = (
u7::from((*key as u8).min(127)),
u7::from((*vel as u8).min(127)),
);
phrase.notes[time].push(MidiMessage::NoteOn { key, vel })
} else {
panic!("unexpected list in phrase '{name}'")
},
_ => panic!("unexpected in phrase '{name}': {edn:?}")
}
}
},
_ => panic!("unexpected in phrase '{name}': {edn:?}"),
});
*phrase.name.write().unwrap() = name;
Ok(phrase)
}
}
/// Phrase player.
pub struct Sequencer<E: Engine> {
pub name: Arc<RwLock<String>>,
pub mode: bool,
pub focused: bool,
pub entered: bool,
pub transport: Option<Arc<RwLock<TransportToolbar<E>>>>,
/// The full piano roll is rendered to this buffer
pub buffer: BigBuffer,
/// The full piano keys is rendered to this buffer
pub keys: Buffer,
/// Highlight input keys
pub keys_in: [bool; 128],
/// Highlight output keys
pub keys_out: [bool; 128],
/// Current point in playing phrase
pub now: usize,
/// Temporal resolution (default 96)
pub ppq: usize,
/// Scroll/room in pitch axis
pub note_axis: FixedAxis<usize>,
/// Scroll/room in time axis
pub time_axis: ScaledAxis<usize>,
/// Play input through output.
pub monitoring: bool,
/// Write input to sequence.
pub recording: bool,
/// Overdub input to sequence.
pub overdub: bool,
/// Map: tick -> MIDI events at tick
pub phrases: Vec<Arc<RwLock<Phrase>>>,
/// Phrase currently being played
pub playing_phrase: Option<usize>,
/// Phrase currently being viewed
pub viewing_phrase: Option<usize>,
/// Output from current sequence.
pub midi_out: Option<Port<MidiOut>>,
/// MIDI output buffer
midi_out_buf: Vec<Vec<Vec<u8>>>,
/// Send all notes off
pub reset: bool, // TODO?: after Some(nframes)
/// Highlight keys on piano roll.
pub notes_in: [bool;128],
/// Highlight keys on piano roll.
pub notes_out: [bool;128],
}
impl<E: Engine> Sequencer<E> {
impl<E: Engine> PhrasePlayer<E> {
pub fn new (name: &str) -> Self {
Self {
name: Arc::new(RwLock::new(name.into())),
_engine: Default::default(),
phrase: None,
notes_in: Arc::new(RwLock::new([false;128])),
notes_out: Arc::new(RwLock::new([false;128])),
monitoring: false,
recording: false,
overdub: true,
phrases: vec![],
viewing_phrase: None,
playing_phrase: None,
midi_out: None,
midi_out_buf: vec![Vec::with_capacity(16);16384],
reset: true,
notes_in: [false;128],
notes_out: [false;128],
buffer: Default::default(),
keys: keys_vert(),
entered: false,
focused: false,
mode: false,
keys_in: [false;128],
keys_out: [false;128],
now: 0,
ppq: PPQ,
transport: None,
note_axis: FixedAxis { start: 12, point: Some(36) },
time_axis: ScaledAxis { start: 0, scale: 24, point: Some(0) },
}
}
pub fn toggle_monitor (&mut self) {
@ -281,7 +220,7 @@ impl<E: Engine> Sequencer<E> {
reset: bool,
scope: &ProcessScope,
(frame0, frames): (usize, usize),
(_usec0, _usecs): (usize, usize),
(_usec0, _usecs): (usize, usize),
period: f64,
) {
if self.midi_out.is_some() {
@ -298,15 +237,15 @@ impl<E: Engine> Sequencer<E> {
}
}
if let (
Some(TransportState::Rolling), Some((start_frame, _)), Some(phrase)
) = (
playing, started, self.playing_phrase.and_then(|id|self.phrases.get_mut(id))
) {
Some(TransportState::Rolling),
Some((start_frame, _)),
Some(ref phrase)
) = (playing, started, &self.phrase) {
phrase.read().map(|phrase|{
if self.midi_out.is_some() {
phrase.process_out(
&mut self.midi_out_buf,
&mut self.notes_out,
&mut self.notes_out.write().unwrap(),
timebase,
(frame0.saturating_sub(start_frame), frames, period)
);
@ -317,6 +256,7 @@ impl<E: Engine> Sequencer<E> {
// Monitor and record input
if input.is_some() && (self.recording || self.monitoring) {
// For highlighting keys and note repeat
let mut notes_in = self.notes_in.write().unwrap();
for (frame, event, bytes) in parse_midi_input(input.unwrap()) {
match event {
LiveEvent::Midi { message, .. } => {
@ -337,10 +277,10 @@ impl<E: Engine> Sequencer<E> {
}
match message {
MidiMessage::NoteOn { key, .. } => {
self.notes_in[key.as_int() as usize] = true;
notes_in[key.as_int() as usize] = true;
}
MidiMessage::NoteOff { key, .. } => {
self.notes_in[key.as_int() as usize] = false;
notes_in[key.as_int() as usize] = false;
},
_ => {}
}
@ -350,50 +290,45 @@ impl<E: Engine> Sequencer<E> {
}
}
} else if input.is_some() && self.midi_out.is_some() && self.monitoring {
let mut notes_in = self.notes_in.write().unwrap();
for (frame, event, bytes) in parse_midi_input(input.unwrap()) {
self.process_monitor_event(frame, &event, bytes)
match event {
LiveEvent::Midi { message, .. } => {
self.midi_out_buf[frame].push(bytes.to_vec());
match message {
MidiMessage::NoteOn { key, .. } => {
notes_in[key.as_int() as usize] = true;
}
MidiMessage::NoteOff { key, .. } => {
notes_in[key.as_int() as usize] = false;
},
_ => {}
}
},
_ => {}
}
}
}
if let Some(out) = &mut self.midi_out {
write_midi_output(&mut out.writer(scope), &self.midi_out_buf, frames);
}
}
#[inline]
fn process_monitor_event (&mut self, frame: usize, event: &LiveEvent, bytes: &[u8]) {
match event {
LiveEvent::Midi { message, .. } => {
self.write_to_output_buffer(frame, bytes);
self.process_monitor_message(&message);
},
_ => {}
}
}
#[inline] fn write_to_output_buffer (&mut self, frame: usize, bytes: &[u8]) {
self.midi_out_buf[frame].push(bytes.to_vec());
}
#[inline]
fn process_monitor_message (&mut self, message: &MidiMessage) {
match message {
MidiMessage::NoteOn { key, .. } => {
self.notes_in[key.as_int() as usize] = true;
let writer = &mut out.writer(scope);
let output = &self.midi_out_buf;
for time in 0..frames {
for event in output[time].iter() {
writer.write(&RawMidi { time: time as u32, bytes: &event })
.expect(&format!("{event:?}"));
}
}
MidiMessage::NoteOff { key, .. } => {
self.notes_in[key.as_int() as usize] = false;
},
_ => {}
}
}
}
/// Add "all notes off" to the start of a buffer.
pub fn all_notes_off (output: &mut MIDIChunk) {
pub fn all_notes_off (output: &mut PhraseChunk) {
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 }|(
@ -402,30 +337,3 @@ pub fn parse_midi_input (input: MidiIter) -> Box<dyn Iterator<Item=(usize, LiveE
bytes
)))
}
/// Write to JACK port from output buffer (containing notes from sequence and/or monitor)
pub fn write_midi_output (writer: &mut MidiWriter, output: &MIDIChunk, frames: usize) {
for time in 0..frames {
for event in output[time].iter() {
writer.write(&RawMidi { time: time as u32, bytes: &event })
.expect(&format!("{event:?}"));
}
}
}
#[derive(Default)]
pub struct SequencerProxy<E: Engine>(pub PhantomData<E>, pub bool);
impl Handle<Tui> for SequencerProxy<Tui> {
fn handle (&mut self, _: &TuiInput) -> Perhaps<bool> { unreachable!() }
}
impl Content for SequencerProxy<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> { "" }
}
impl Focusable<Tui> for SequencerProxy<Tui> {
fn is_focused (&self) -> bool { self.1 }
fn set_focused (&mut self, focus: bool) { self.1 = focus }
}