mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 04:06:45 +01:00
965 lines
34 KiB
Rust
965 lines
34 KiB
Rust
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,
|
||
}
|
||
}
|
||
}
|
||
|
||
///////////////////////////////////////////////////////////////////////////////////////////////////
|