mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
wip: separate PhrasePlayer vs PhraseEditor
This commit is contained in:
parent
7668a6f339
commit
25e54eba4e
8 changed files with 491 additions and 636 deletions
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue