tek/crates/tek_sequencer/src/sequencer.rs

965 lines
34 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::*;
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Represents the tracks and scenes of the composition.
pub struct Arranger<E: Engine> {
/// Name of arranger
pub name: Arc<RwLock<String>>,
/// Collection of tracks.
pub tracks: Vec<Sequencer<E>>,
/// Collection of scenes.
pub scenes: Vec<Scene>,
/// Currently selected element.
pub selected: ArrangerFocus,
/// Display mode of arranger
pub mode: ArrangerViewMode,
/// Slot for modal dialog displayed on top of app.
pub modal: Option<Box<dyn ContentComponent<E>>>,
/// Whether the arranger is currently focused
pub focused: bool
}
impl<E: Engine> Arranger<E> {
pub fn new (name: &str) -> Self {
Self {
name: Arc::new(RwLock::new(name.into())),
mode: ArrangerViewMode::Vertical(2),
selected: ArrangerFocus::Clip(0, 0),
scenes: vec![],
tracks: vec![],
modal: None,
focused: false
}
}
pub fn activate (&mut self) {
match self.selected {
ArrangerFocus::Scene(s) => {
for (track_index, track) in self.tracks.iter_mut().enumerate() {
track.sequence = self.scenes[s].clips[track_index];
track.reset = true;
}
},
ArrangerFocus::Clip(t, s) => {
self.tracks[t].sequence = self.scenes[s].clips[t];
self.tracks[t].reset = true;
},
_ => {}
}
}
pub fn sequencer (&self) -> Option<&Sequencer<E>> {
self.selected.track()
.map(|track|self.tracks.get(track))
.flatten()
}
pub fn sequencer_mut (&mut self) -> Option<&mut Sequencer<E>> {
self.selected.track()
.map(|track|self.tracks.get_mut(track))
.flatten()
}
pub fn show_phrase (&mut self) -> Usually<()> {
//unimplemented!()
//let phrase = self.phrase();
//self.sequencer.show(phrase)
Ok(())
}
pub fn is_first_row (&self) -> bool {
let selected = self.selected;
selected.is_mix() || selected.is_track()
}
pub fn is_last_row (&self) -> bool {
let selected = self.selected;
(self.scenes.len() == 0 && (selected.is_mix() || selected.is_track())) || match selected {
ArrangerFocus::Scene(s) =>
s == self.scenes.len() - 1,
ArrangerFocus::Clip(_, s) =>
s == self.scenes.len() - 1,
_ => false
}
}
pub fn track (&self) -> Option<&Sequencer<E>> {
self.selected.track().map(|t|self.tracks.get(t)).flatten()
}
pub fn track_mut (&mut self) -> Option<&mut Sequencer<E>> {
self.selected.track().map(|t|self.tracks.get_mut(t)).flatten()
}
pub fn track_next (&mut self) {
self.selected.track_next(self.tracks.len() - 1)
}
pub fn track_prev (&mut self) {
self.selected.track_prev()
}
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Sequencer<E>> {
self.tracks.push(name.map_or_else(
|| Sequencer::new(&self.track_default_name()),
|name| Sequencer::new(name),
));
let index = self.tracks.len() - 1;
Ok(&mut self.tracks[index])
}
pub fn track_del (&mut self) {
unimplemented!("Arranger::track_del");
}
pub fn track_default_name (&self) -> String {
format!("Track {}", self.tracks.len() + 1)
}
pub fn scene (&self) -> Option<&Scene> {
self.selected.scene().map(|s|self.scenes.get(s)).flatten()
}
pub fn scene_mut (&mut self) -> Option<&mut Scene> {
self.selected.scene().map(|s|self.scenes.get_mut(s)).flatten()
}
pub fn scene_next (&mut self) {
self.selected.scene_next(self.scenes.len() - 1)
}
pub fn scene_prev (&mut self) {
self.selected.scene_prev()
}
pub fn scene_add (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
let clips = vec![None;self.tracks.len()];
self.scenes.push(match name {
Some(name) => Scene::new(name, clips),
None => Scene::new(&self.scene_default_name(), clips),
});
let index = self.scenes.len() - 1;
Ok(&mut self.scenes[index])
}
pub fn scene_del (&mut self) {
unimplemented!("Arranger::scene_del");
}
pub fn scene_default_name (&self) -> String {
format!("Scene {}", self.scenes.len() + 1)
}
pub fn phrase (&self) -> Option<&Arc<RwLock<Phrase>>> {
let track_id = self.selected.track()?;
self.tracks.get(track_id)?.phrases.get((*self.scene()?.clips.get(track_id)?)?)
}
pub fn phrase_next (&mut self) {
let track_index = self.selected.track();
let scene_index = self.selected.scene();
track_index
.and_then(|index|self.tracks.get_mut(index).map(|track|(index, track)))
.and_then(|(track_index, track)|{
let phrases = track.phrases.len();
scene_index
.and_then(|index|self.scenes.get_mut(index))
.and_then(|scene|{
if let Some(phrase_index) = scene.clips[track_index] {
if phrase_index >= phrases - 1 {
scene.clips[track_index] = None;
} else {
scene.clips[track_index] = Some(phrase_index + 1);
}
} else if phrases > 0 {
scene.clips[track_index] = Some(0);
}
Some(())
})
});
}
pub fn phrase_prev (&mut self) {
let track_index = self.selected.track();
let scene_index = self.selected.scene();
track_index
.and_then(|index|self.tracks.get_mut(index).map(|track|(index, track)))
.and_then(|(track_index, track)|{
let phrases = track.phrases.len();
scene_index
.and_then(|index|self.scenes.get_mut(index))
.and_then(|scene|{
if let Some(phrase_index) = scene.clips[track_index] {
scene.clips[track_index] = if phrase_index == 0 {
None
} else {
Some(phrase_index - 1)
};
} else if phrases > 0 {
scene.clips[track_index] = Some(phrases - 1);
}
Some(())
})
});
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(PartialEq, Clone, Copy)]
/// Represents the current user selection in the arranger
pub enum ArrangerFocus {
/** The whole mix is selected */
Mix,
/// A track is selected.
Track(usize),
/// A scene is selected.
Scene(usize),
/// A clip (track × scene) is selected.
Clip(usize, usize),
}
/// Focus identification methods
impl ArrangerFocus {
pub fn description <E: Engine> (
&self,
tracks: &Vec<Sequencer<E>>,
scenes: &Vec<Scene>,
) -> String {
format!("Selected: {}", match self {
Self::Mix => format!("Everything"),
Self::Track(t) => if let Some(track) = tracks.get(*t) {
format!("T{t}: {}", &track.name.read().unwrap())
} else {
format!("T??")
},
Self::Scene(s) => if let Some(scene) = scenes.get(*s) {
format!("S{s}: {}", &scene.name.read().unwrap())
} else {
format!("S??")
},
Self::Clip(t, s) => if let (Some(track), Some(scene)) = (
tracks.get(*t),
scenes.get(*s),
) {
if let Some(Some(slot)) = scene.clips.get(*t) {
if let Some(clip) = track.phrases.get(*slot) {
format!("T{t} S{s} C{slot} ({})", &clip.read().unwrap().name.read().unwrap())
} else {
format!("T{t} S{s}: Empty")
}
} else {
format!("T{t} S{s}: Empty")
}
} else {
format!("T{t} S{s}: Empty")
}
})
}
pub fn is_mix (&self) -> bool {
match self { Self::Mix => true, _ => false }
}
pub fn is_track (&self) -> bool {
match self { Self::Track(_) => true, _ => false }
}
pub fn is_scene (&self) -> bool {
match self { Self::Scene(_) => true, _ => false }
}
pub fn is_clip (&self) -> bool {
match self { Self::Clip(_, _) => true, _ => false }
}
pub fn track (&self) -> Option<usize> {
match self {
Self::Clip(t, _) => Some(*t),
Self::Track(t) => Some(*t),
_ => None
}
}
pub fn track_next (&mut self, last_track: usize) {
*self = match self {
Self::Mix => Self::Track(0),
Self::Track(t) => Self::Track(last_track.min(*t + 1)),
Self::Scene(s) => Self::Clip(0, *s),
Self::Clip(t, s) => Self::Clip(last_track.min(*t + 1), *s),
}
}
pub fn track_prev (&mut self) {
*self = match self {
Self::Mix => Self::Mix,
Self::Scene(s) => Self::Scene(*s),
Self::Track(t) => if *t == 0 {
Self::Mix
} else {
Self::Track(*t - 1)
},
Self::Clip(t, s) => if *t == 0 {
Self::Scene(*s)
} else {
Self::Clip(t.saturating_sub(1), *s)
}
}
}
pub fn scene (&self) -> Option<usize> {
match self {
Self::Clip(_, s) => Some(*s),
Self::Scene(s) => Some(*s),
_ => None
}
}
pub fn scene_next (&mut self, last_scene: usize) {
*self = match self {
Self::Mix => Self::Scene(0),
Self::Track(t) => Self::Clip(*t, 0),
Self::Scene(s) => Self::Scene(last_scene.min(*s + 1)),
Self::Clip(t, s) => Self::Clip(*t, last_scene.min(*s + 1)),
}
}
pub fn scene_prev (&mut self) {
*self = match self {
Self::Mix => Self::Mix,
Self::Track(t) => Self::Track(*t),
Self::Scene(s) => if *s == 0 {
Self::Mix
} else {
Self::Scene(*s - 1)
},
Self::Clip(t, s) => if *s == 0 {
Self::Track(*t)
} else {
Self::Clip(*t, s.saturating_sub(1))
}
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Display mode of arranger
#[derive(PartialEq)]
pub enum ArrangerViewMode { Horizontal, Vertical(usize) }
/// Arranger display mode can be cycled
impl ArrangerViewMode {
/// Cycle arranger display mode
pub fn to_next (&mut self) {
*self = match self {
Self::Horizontal => Self::Vertical(1),
Self::Vertical(1) => Self::Vertical(2),
Self::Vertical(2) => Self::Vertical(2),
Self::Vertical(0) => Self::Horizontal,
Self::Vertical(_) => Self::Vertical(0),
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
pub struct VerticalArranger<'a, E: Engine>(
pub &'a Arranger<E>, pub usize
);
pub struct VerticalArrangerGrid<'a>(
pub u16, pub &'a [(usize, usize)], pub &'a [(usize, usize)]
);
pub struct VerticalArrangerCursor<'a>(
pub bool, pub ArrangerFocus, pub u16, pub &'a [(usize, usize)], pub &'a [(usize, usize)],
);
///////////////////////////////////////////////////////////////////////////////////////////////////
pub struct HorizontalArranger<'a, E: Engine>(
pub &'a Arranger<E>
);
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Appears on first run (i.e. if state dir is missing).
pub struct ArrangerRenameModal<E: Engine> {
_engine: std::marker::PhantomData<E>,
pub done: bool,
pub target: ArrangerFocus,
pub value: String,
pub result: Arc<RwLock<String>>,
pub cursor: usize
}
impl<E: Engine> ArrangerRenameModal<E> {
pub fn new (target: ArrangerFocus, value: &Arc<RwLock<String>>) -> Self {
Self {
_engine: Default::default(),
done: false,
value: value.read().unwrap().clone(),
cursor: value.read().unwrap().len(),
result: value.clone(),
target,
}
}
}
impl<E: Engine + Send> Exit for ArrangerRenameModal<E> {
fn exited (&self) -> bool { self.done }
fn exit (&mut self) { self.done = true }
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/// A collection of phrases to play on each track.
#[derive(Default)]
pub struct Scene {
pub name: Arc<RwLock<String>>,
pub clips: Vec<Option<usize>>,
}
impl Scene {
pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually<Self> {
let mut name = None;
let mut clips = vec![];
edn!(edn in args {
Edn::Map(map) => {
let key = map.get(&Edn::Key(":name"));
if let Some(Edn::Str(n)) = key {
name = Some(*n);
} else {
panic!("unexpected key in scene '{name:?}': {key:?}")
}
},
Edn::Symbol("_") => {
clips.push(None);
},
Edn::Int(i) => {
clips.push(Some(*i as usize));
},
_ => panic!("unexpected in scene '{name:?}': {edn:?}")
});
let scene = Self::new(name.unwrap_or(""), clips);
Ok(scene)
}
pub fn new (name: impl AsRef<str>, clips: impl AsRef<[Option<usize>]>) -> Self {
let name = Arc::new(RwLock::new(name.as_ref().into()));
let clips = clips.as_ref().iter().map(|x|x.clone()).collect();
Self { name, clips, }
}
/// Returns the pulse length of the longest phrase in the scene
pub fn pulses <E: Engine> (&self, tracks: &[Sequencer<E>]) -> usize {
self.clips.iter().enumerate()
.filter_map(|(i, c)|c
.map(|c|tracks
.get(i)
.map(|track|track
.phrases
.get(c))))
.filter_map(|p|p)
.filter_map(|p|p)
.fold(0, |a, p|a.max(p.read().unwrap().length))
}
/// Returns true if all phrases in the scene are currently playing
pub fn is_playing <E: Engine> (&self, tracks: &[Sequencer<E>]) -> bool {
self.clips.iter().enumerate()
.all(|(track_index, phrase_index)|match phrase_index {
Some(i) => tracks
.get(track_index)
.map(|track|track.sequence == Some(*i))
.unwrap_or(false),
None => true
})
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Phrase editor.
pub struct Sequencer<E: Engine> {
pub name: Arc<RwLock<String>>,
pub mode: bool,
pub focused: bool,
pub entered: bool,
pub phrase: Option<Arc<RwLock<Phrase>>>,
pub transport: Option<Arc<RwLock<TransportToolbar<E>>>>,
pub buffer: BigBuffer,
pub keys: Buffer,
/// Highlight input keys
pub keys_in: [bool; 128],
/// Highlight output keys
pub keys_out: [bool; 128],
pub now: usize,
pub ppq: usize,
pub note_axis: FixedAxis<usize>,
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 selector
pub sequence: 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> {
pub fn new (name: &str) -> Self {
Self {
name: Arc::new(RwLock::new(name.into())),
monitoring: false,
recording: false,
overdub: true,
phrases: vec![],
sequence: 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],
phrase: None,
now: 0,
ppq: 96,
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) {
self.monitoring = !self.monitoring;
}
pub fn toggle_record (&mut self) {
self.recording = !self.recording;
}
pub fn toggle_overdub (&mut self) {
self.overdub = !self.overdub;
}
pub fn process (
&mut self,
input: Option<MidiIter>,
timebase: &Arc<Timebase>,
playing: Option<TransportState>,
started: Option<(usize, usize)>,
quant: usize,
reset: bool,
scope: &ProcessScope,
(frame0, frames): (usize, usize),
(_usec0, _usecs): (usize, usize),
period: f64,
) {
if self.midi_out.is_some() {
// Clear the section of the output buffer that we will be using
for frame in &mut self.midi_out_buf[0..frames] {
frame.clear();
}
// Emit "all notes off" at start of buffer if requested
if self.reset {
all_notes_off(&mut self.midi_out_buf);
self.reset = false;
} else if reset {
all_notes_off(&mut self.midi_out_buf);
}
}
if let (
Some(TransportState::Rolling), Some((start_frame, _)), Some(phrase)
) = (
playing, started, self.sequence.and_then(|id|self.phrases.get_mut(id))
) {
phrase.read().map(|phrase|{
if self.midi_out.is_some() {
phrase.process_out(
&mut self.midi_out_buf,
&mut self.notes_out,
timebase,
(frame0.saturating_sub(start_frame), frames, period)
);
}
}).unwrap();
let mut phrase = phrase.write().unwrap();
let length = phrase.length;
// Monitor and record input
if input.is_some() && (self.recording || self.monitoring) {
// For highlighting keys and note repeat
for (frame, event, bytes) in parse_midi_input(input.unwrap()) {
match event {
LiveEvent::Midi { message, .. } => {
if self.monitoring {
self.midi_out_buf[frame].push(bytes.to_vec())
}
if self.recording {
phrase.record_event({
let pulse = timebase.frame_to_pulse(
(frame0 + frame - start_frame) as f64
);
let quantized = (
pulse / quant as f64
).round() as usize * quant;
let looped = quantized % length;
looped
}, message);
}
match message {
MidiMessage::NoteOn { key, .. } => {
self.notes_in[key.as_int() as usize] = true;
}
MidiMessage::NoteOff { key, .. } => {
self.notes_in[key.as_int() as usize] = false;
},
_ => {}
}
},
_ => {}
}
}
}
} else if input.is_some() && self.midi_out.is_some() && self.monitoring {
for (frame, event, bytes) in parse_midi_input(input.unwrap()) {
self.process_monitor_event(frame, &event, bytes)
}
}
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;
}
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) {
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
)))
}
/// 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:?}"));
}
}
}
/// MIDI message serialized to bytes
pub type MIDIMessage = Vec<u8>;
/// Collection of serialized MIDI messages
pub type MIDIChunk = [Vec<MIDIMessage>];
///////////////////////////////////////////////////////////////////////////////////////////////////
/// A collection of MIDI messages.
pub type PhraseData = Vec<Vec<MidiMessage>>;
/// A MIDI sequence.
#[derive(Debug)]
pub struct Phrase {
pub name: Arc<RwLock<String>>,
pub length: usize,
pub notes: PhraseData,
pub looped: Option<(usize, usize)>,
/// All notes are displayed with minimum length
pub percussive: bool
}
impl Default for Phrase {
fn default () -> Self { Self::new("", 0, None) }
}
impl Phrase {
pub fn new (name: &str, length: usize, notes: Option<PhraseData>) -> Self {
Self {
name: Arc::new(RwLock::new(name.into())),
length,
notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]),
looped: Some((0, length)),
percussive: true,
}
}
pub fn record_event (&mut self, pulse: usize, message: MidiMessage) {
if pulse >= self.length {
panic!("extend phrase first")
}
self.notes[pulse].push(message);
}
/// Check if a range `start..end` contains MIDI Note On `k`
pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool {
//panic!("{:?} {start} {end}", &self);
for events in self.notes[start.max(0)..end.min(self.notes.len())].iter() {
for event in events.iter() {
match event {
MidiMessage::NoteOn {key,..} => {
if *key == k {
return true
}
}
_ => {}
}
}
}
return false
}
/// Write a chunk of MIDI events to an output port.
pub fn process_out (
&self,
output: &mut MIDIChunk,
notes_on: &mut [bool;128],
timebase: &Arc<Timebase>,
(frame0, frames, _): (usize, usize, f64),
) {
let mut buf = Vec::with_capacity(8);
for (time, tick) in Ticks(timebase.pulse_per_frame()).between_frames(
frame0, frame0 + frames
) {
let tick = tick % self.length;
for message in self.notes[tick].iter() {
buf.clear();
let channel = 0.into();
let message = *message;
LiveEvent::Midi { channel, message }.write(&mut buf).unwrap();
output[time as usize].push(buf.clone());
match message {
MidiMessage::NoteOn { key, .. } => notes_on[key.as_int() as usize] = true,
MidiMessage::NoteOff { key, .. } => notes_on[key.as_int() as usize] = false,
_ => {}
}
}
}
}
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)
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Stores and displays time-related state.
pub struct TransportToolbar<E: Engine> {
_engine: PhantomData<E>,
/// Enable metronome?
pub metronome: bool,
/// Current sample rate, tempo, and PPQ.
pub timebase: Arc<Timebase>,
/// JACK client handle (needs to not be dropped for standalone mode to work).
pub jack: Option<JackClient>,
/// JACK transport handle.
pub transport: Option<Transport>,
/// Quantization factor
/// Global frame and usec at which playback started
pub started: Option<(usize, usize)>,
pub focused: bool,
pub focus: TransportToolbarFocus,
pub playing: Option<TransportState>,
pub bpm: f64,
pub quant: usize,
pub sync: usize,
pub frame: usize,
pub pulse: usize,
pub ppq: usize,
pub usecs: usize,
}
impl<E: Engine> TransportToolbar<E> {
pub fn standalone () -> Usually<Arc<RwLock<Self>>> where Self: 'static {
let jack = JackClient::Inactive(
Client::new("tek_transport", ClientOptions::NO_START_SERVER)?.0
);
let mut transport = Self::new(Some(jack.transport()));
transport.focused = true;
let transport = Arc::new(RwLock::new(transport));
transport.write().unwrap().jack = Some(
jack.activate(
&transport.clone(),
|state: &Arc<RwLock<TransportToolbar<E>>>, client, scope| {
state.write().unwrap().process(client, scope)
}
)?
);
Ok(transport)
}
pub fn new (transport: Option<Transport>) -> Self {
let timebase = Arc::new(Timebase::default());
Self {
_engine: Default::default(),
focused: false,
focus: TransportToolbarFocus::PlayPause,
playing: Some(TransportState::Stopped),
bpm: timebase.bpm(),
quant: 24,
sync: timebase.ppq() as usize * 4,
frame: 0,
pulse: 0,
ppq: 0,
usecs: 0,
metronome: false,
started: None,
jack: None,
transport,
timebase,
}
}
pub fn toggle_play (&mut self) -> Usually<()> {
let transport = self.transport.as_ref().unwrap();
self.playing = match self.playing.expect("1st frame has not been processed yet") {
TransportState::Stopped => {
transport.start()?;
Some(TransportState::Starting)
},
_ => {
transport.stop()?;
transport.locate(0)?;
Some(TransportState::Stopped)
},
};
Ok(())
}
pub fn update (&mut self, scope: &ProcessScope) -> (bool, usize, usize, usize, usize, f64) {
let times = scope.cycle_times().unwrap();
let CycleTimes { current_frames, current_usecs, next_usecs, period_usecs } = times;
let chunk_size = scope.n_frames() as usize;
let transport = self.transport.as_ref().unwrap().query().unwrap();
self.frame = transport.pos.frame() as usize;
let mut reset = false;
if self.playing != Some(transport.state) {
match transport.state {
TransportState::Rolling => {
self.started = Some((current_frames as usize, current_usecs as usize));
},
TransportState::Stopped => {
self.started = None;
reset = true;
},
_ => {}
}
}
self.playing = Some(transport.state);
(
reset,
current_frames as usize,
chunk_size as usize,
current_usecs as usize,
next_usecs as usize,
period_usecs as f64
)
}
pub fn bpm (&self) -> usize {
self.timebase.bpm() as usize
}
pub fn ppq (&self) -> usize {
self.timebase.ppq() as usize
}
pub fn pulse (&self) -> usize {
self.timebase.frame_to_pulse(self.frame as f64) as usize
}
pub fn usecs (&self) -> usize {
self.timebase.frame_to_usec(self.frame as f64) as usize
}
pub fn quant (&self) -> usize {
self.quant
}
pub fn sync (&self) -> usize {
self.sync
}
}
impl<E: Engine> Audio for TransportToolbar<E> {
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
self.update(&scope);
Control::Continue
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Clone, Copy, PartialEq)]
pub enum TransportToolbarFocus { PlayPause, Bpm, Quant, Sync, Clock, }
impl TransportToolbarFocus {
pub fn next (&mut self) {
*self = match self {
Self::PlayPause => Self::Bpm,
Self::Bpm => Self::Quant,
Self::Quant => Self::Sync,
Self::Sync => Self::Clock,
Self::Clock => Self::PlayPause,
}
}
pub fn prev (&mut self) {
*self = match self {
Self::PlayPause => Self::Clock,
Self::Bpm => Self::PlayPause,
Self::Quant => Self::Bpm,
Self::Sync => Self::Quant,
Self::Clock => Self::Sync,
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////