mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-02-21 16:29:04 +01:00
refactor: into one big pile it goes
This commit is contained in:
parent
604a42a4bc
commit
ec404b0305
24 changed files with 6559 additions and 6675 deletions
39
Cargo.lock
generated
39
Cargo.lock
generated
|
|
@ -2642,8 +2642,6 @@ dependencies = [
|
||||||
"proptest-derive",
|
"proptest-derive",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"symphonia",
|
"symphonia",
|
||||||
"tek_device",
|
|
||||||
"tek_engine",
|
|
||||||
"tengri",
|
"tengri",
|
||||||
"toml",
|
"toml",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
|
@ -2652,43 +2650,6 @@ dependencies = [
|
||||||
"xdg",
|
"xdg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tek_device"
|
|
||||||
version = "0.3.0"
|
|
||||||
dependencies = [
|
|
||||||
"atomic_float",
|
|
||||||
"backtrace",
|
|
||||||
"clap",
|
|
||||||
"jack",
|
|
||||||
"konst",
|
|
||||||
"livi",
|
|
||||||
"midly",
|
|
||||||
"palette",
|
|
||||||
"proptest",
|
|
||||||
"proptest-derive",
|
|
||||||
"rand 0.8.5",
|
|
||||||
"symphonia",
|
|
||||||
"tek_engine",
|
|
||||||
"tengri",
|
|
||||||
"toml",
|
|
||||||
"uuid",
|
|
||||||
"wavers",
|
|
||||||
"winit",
|
|
||||||
"xdg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tek_engine"
|
|
||||||
version = "0.3.0"
|
|
||||||
dependencies = [
|
|
||||||
"atomic_float",
|
|
||||||
"jack",
|
|
||||||
"midly",
|
|
||||||
"proptest",
|
|
||||||
"proptest-derive",
|
|
||||||
"tengri",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.25.0"
|
version = "3.25.0"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [ "./app", "./engine", "./device" ]
|
members = [ "./app" ]
|
||||||
exclude = [ "./tengri", "./dizzle" ]
|
exclude = [ "./tengri", "./dizzle" ]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
|
|
||||||
25
Justfile
25
Justfile
|
|
@ -119,7 +119,6 @@ plugin:
|
||||||
|
|
||||||
@stats:
|
@stats:
|
||||||
echo
|
echo
|
||||||
|
|
||||||
for file in $(ls app/*.rs); do echo $file; cloc $file | grep Rust; done
|
for file in $(ls app/*.rs); do echo $file; cloc $file | grep Rust; done
|
||||||
echo
|
echo
|
||||||
cloc app | grep Rust
|
cloc app | grep Rust
|
||||||
|
|
@ -131,27 +130,3 @@ plugin:
|
||||||
rg 'TODO' app/ | cat
|
rg 'TODO' app/ | cat
|
||||||
rg 'TODO' app/ | wc -l
|
rg 'TODO' app/ | wc -l
|
||||||
echo
|
echo
|
||||||
|
|
||||||
for file in $(ls engine/*.rs); do echo $file; cloc $file | grep Rust; done
|
|
||||||
echo
|
|
||||||
cloc engine | grep Rust
|
|
||||||
rg 'pub struct ' engine/ | cat
|
|
||||||
rg 'pub struct ' engine/ | wc -l
|
|
||||||
rg 'pub trait ' engine/ | cat
|
|
||||||
rg 'pub trait ' engine/ | wc -l
|
|
||||||
echo
|
|
||||||
rg 'TODO' engine/ | cat
|
|
||||||
rg 'TODO' engine/ | wc -l
|
|
||||||
echo
|
|
||||||
|
|
||||||
for file in $(ls device/*.rs); do echo $file; cloc $file | grep Rust; done
|
|
||||||
echo
|
|
||||||
cloc device | grep Rust
|
|
||||||
rg 'pub struct ' device/ | cat
|
|
||||||
rg 'pub struct ' device/ | wc -l
|
|
||||||
rg 'pub trait ' device/ | cat
|
|
||||||
rg 'pub trait ' device/ | wc -l
|
|
||||||
echo
|
|
||||||
rg 'TODO' device/ | cat
|
|
||||||
rg 'TODO' device/ | wc -l
|
|
||||||
echo
|
|
||||||
|
|
|
||||||
614
app/.scratch.rs
614
app/.scratch.rs
|
|
@ -377,3 +377,617 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//fn begin (browse: &mut Browse) => {
|
||||||
|
//unreachable!();
|
||||||
|
//}
|
||||||
|
//fn cancel (browse: &mut Browse) => {
|
||||||
|
//todo!()
|
||||||
|
////browse.mode = None;
|
||||||
|
////Ok(None)
|
||||||
|
//}
|
||||||
|
//fn confirm (browse: &mut Browse) => {
|
||||||
|
//todo!()
|
||||||
|
////Ok(match browse.mode {
|
||||||
|
////Some(PoolMode::Import(index, ref mut browse)) => {
|
||||||
|
////if browse.is_file() {
|
||||||
|
////let path = browse.path();
|
||||||
|
////browse.mode = None;
|
||||||
|
////let _undo = PoolClipCommand::import(browse, index, path)?;
|
||||||
|
////None
|
||||||
|
////} else if browse.is_dir() {
|
||||||
|
////browse.mode = Some(PoolMode::Import(index, browse.chdir()?));
|
||||||
|
////None
|
||||||
|
////} else {
|
||||||
|
////None
|
||||||
|
////}
|
||||||
|
////},
|
||||||
|
////Some(PoolMode::Export(index, ref mut browse)) => {
|
||||||
|
////todo!()
|
||||||
|
////},
|
||||||
|
////_ => unreachable!(),
|
||||||
|
////})
|
||||||
|
//}
|
||||||
|
//fn select (browse: &mut Browse, index: usize) => {
|
||||||
|
//todo!()
|
||||||
|
////Ok(match browse.mode {
|
||||||
|
////Some(PoolMode::Import(index, ref mut browse)) => {
|
||||||
|
////browse.index = index;
|
||||||
|
////None
|
||||||
|
////},
|
||||||
|
////Some(PoolMode::Export(index, ref mut browse)) => {
|
||||||
|
////browse.index = index;
|
||||||
|
////None
|
||||||
|
////},
|
||||||
|
////_ => unreachable!(),
|
||||||
|
////})
|
||||||
|
//}
|
||||||
|
//fn chdir (browse: &mut Browse, dir: PathBuf) => {
|
||||||
|
//todo!()
|
||||||
|
////Ok(match browse.mode {
|
||||||
|
////Some(PoolMode::Import(index, ref mut browse)) => {
|
||||||
|
////browse.mode = Some(PoolMode::Import(index, Browse::new(Some(dir))?));
|
||||||
|
////None
|
||||||
|
////},
|
||||||
|
////Some(PoolMode::Export(index, ref mut browse)) => {
|
||||||
|
////browse.mode = Some(PoolMode::Export(index, Browse::new(Some(dir))?));
|
||||||
|
////None
|
||||||
|
////},
|
||||||
|
////_ => unreachable!(),
|
||||||
|
////})
|
||||||
|
//}
|
||||||
|
//fn filter (browse: &mut Browse, filter: Arc<str>) => {
|
||||||
|
//todo!()
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
//def_command!(ArrangementCommand: |arranger: Arrangement| {
|
||||||
|
|
||||||
|
//Home => { arranger.editor = None; Ok(None) },
|
||||||
|
|
||||||
|
//Edit => {
|
||||||
|
//let selection = arranger.selection().clone();
|
||||||
|
//arranger.editor = if arranger.editor.is_some() {
|
||||||
|
//None
|
||||||
|
//} else {
|
||||||
|
//match selection {
|
||||||
|
//Selection::TrackClip { track, scene } => {
|
||||||
|
//let clip = &mut arranger.scenes_mut()[scene].clips[track];
|
||||||
|
//if clip.is_none() {
|
||||||
|
////app.clip_auto_create();
|
||||||
|
//*clip = Some(Arc::new(RwLock::new(MidiClip::new(
|
||||||
|
//&format!("t{track:02}s{scene:02}"),
|
||||||
|
//false, 384, None, Some(ItemTheme::random())
|
||||||
|
//))));
|
||||||
|
//}
|
||||||
|
//clip.as_ref().map(|c|c.into())
|
||||||
|
//}
|
||||||
|
//_ => {
|
||||||
|
//None
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//};
|
||||||
|
//if let Some(editor) = arranger.editor.as_mut() {
|
||||||
|
//if let Some(clip) = editor.clip() {
|
||||||
|
//let length = clip.read().unwrap().length.max(1);
|
||||||
|
//let width = arranger.size_inner.w().saturating_sub(20).max(1);
|
||||||
|
//editor.set_time_zoom(length / width);
|
||||||
|
//editor.redraw();
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//Ok(None)
|
||||||
|
//},
|
||||||
|
|
||||||
|
////// Set the selection
|
||||||
|
//Select { selection: Selection } => { *arranger.selection_mut() = *selection; Ok(None) },
|
||||||
|
|
||||||
|
////// Launch the selected clip or scene
|
||||||
|
//Launch => {
|
||||||
|
//match *arranger.selection() {
|
||||||
|
//Selection::Track(t) => {
|
||||||
|
//arranger.tracks[t].sequencer.enqueue_next(None)
|
||||||
|
//},
|
||||||
|
//Selection::TrackClip { track, scene } => {
|
||||||
|
//arranger.tracks[track].sequencer.enqueue_next(arranger.scenes[scene].clips[track].as_ref())
|
||||||
|
//},
|
||||||
|
//Selection::Scene(s) => {
|
||||||
|
//for t in 0..arranger.tracks.len() {
|
||||||
|
//arranger.tracks[t].sequencer.enqueue_next(arranger.scenes[s].clips[t].as_ref())
|
||||||
|
//}
|
||||||
|
//},
|
||||||
|
//_ => {}
|
||||||
|
//};
|
||||||
|
//Ok(None)
|
||||||
|
//},
|
||||||
|
|
||||||
|
////// Set the color of the selected entity
|
||||||
|
//SetColor { palette: Option<ItemTheme> } => {
|
||||||
|
//let mut palette = palette.unwrap_or_else(||ItemTheme::random());
|
||||||
|
//let selection = *arranger.selection();
|
||||||
|
//Ok(Some(Self::SetColor { palette: Some(match selection {
|
||||||
|
//Selection::Mix => {
|
||||||
|
//std::mem::swap(&mut palette, &mut arranger.color);
|
||||||
|
//palette
|
||||||
|
//},
|
||||||
|
//Selection::Scene(s) => {
|
||||||
|
//std::mem::swap(&mut palette, &mut arranger.scenes[s].color);
|
||||||
|
//palette
|
||||||
|
//}
|
||||||
|
//Selection::Track(t) => {
|
||||||
|
//std::mem::swap(&mut palette, &mut arranger.tracks[t].color);
|
||||||
|
//palette
|
||||||
|
//}
|
||||||
|
//Selection::TrackClip { track, scene } => {
|
||||||
|
//if let Some(ref clip) = arranger.scenes[scene].clips[track] {
|
||||||
|
//let mut clip = clip.write().unwrap();
|
||||||
|
//std::mem::swap(&mut palette, &mut clip.color);
|
||||||
|
//palette
|
||||||
|
//} else {
|
||||||
|
//return Ok(None)
|
||||||
|
//}
|
||||||
|
//},
|
||||||
|
//_ => todo!()
|
||||||
|
//}) }))
|
||||||
|
//},
|
||||||
|
|
||||||
|
//Track { track: TrackCommand } => { todo!("delegate") },
|
||||||
|
|
||||||
|
//TrackAdd => {
|
||||||
|
//let index = arranger.track_add(None, None, &[], &[])?.0;
|
||||||
|
//*arranger.selection_mut() = match arranger.selection() {
|
||||||
|
//Selection::Track(_) => Selection::Track(index),
|
||||||
|
//Selection::TrackClip { track: _, scene } => Selection::TrackClip {
|
||||||
|
//track: index, scene: *scene
|
||||||
|
//},
|
||||||
|
//_ => *arranger.selection()
|
||||||
|
//};
|
||||||
|
//Ok(Some(Self::TrackDelete { index }))
|
||||||
|
//},
|
||||||
|
|
||||||
|
//TrackSwap { index: usize, other: usize } => {
|
||||||
|
//let index = *index;
|
||||||
|
//let other = *other;
|
||||||
|
//Ok(Some(Self::TrackSwap { index, other }))
|
||||||
|
//},
|
||||||
|
|
||||||
|
//TrackDelete { index: usize } => {
|
||||||
|
//let index = *index;
|
||||||
|
//let exists = arranger.tracks().get(index).is_some();
|
||||||
|
//if exists {
|
||||||
|
//let track = arranger.tracks_mut().remove(index);
|
||||||
|
//let Track { sequencer: Sequencer { midi_ins, midi_outs, .. }, .. } = track;
|
||||||
|
//for port in midi_ins.into_iter() {
|
||||||
|
//port.close()?;
|
||||||
|
//}
|
||||||
|
//for port in midi_outs.into_iter() {
|
||||||
|
//port.close()?;
|
||||||
|
//}
|
||||||
|
//for scene in arranger.scenes_mut().iter_mut() {
|
||||||
|
//scene.clips.remove(index);
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//Ok(None)
|
||||||
|
////TODO:Ok(Some(Self::TrackAdd ( index, track: Some(deleted_track) })
|
||||||
|
//},
|
||||||
|
|
||||||
|
//MidiIn { input: MidiInputCommand } => {
|
||||||
|
//todo!("delegate"); Ok(None)
|
||||||
|
//},
|
||||||
|
|
||||||
|
//MidiInAdd => {
|
||||||
|
//arranger.midi_in_add()?;
|
||||||
|
//Ok(None)
|
||||||
|
//},
|
||||||
|
|
||||||
|
//MidiOut { output: MidiOutputCommand } => {
|
||||||
|
//todo!("delegate");
|
||||||
|
//Ok(None)
|
||||||
|
//},
|
||||||
|
|
||||||
|
//MidiOutAdd => {
|
||||||
|
//arranger.midi_out_add()?;
|
||||||
|
//Ok(None)
|
||||||
|
//},
|
||||||
|
|
||||||
|
//Device { command: DeviceCommand } => {
|
||||||
|
//todo!("delegate");
|
||||||
|
//Ok(None)
|
||||||
|
//},
|
||||||
|
|
||||||
|
//DeviceAdd { index: usize } => {
|
||||||
|
//todo!("delegate");
|
||||||
|
//Ok(None)
|
||||||
|
//},
|
||||||
|
|
||||||
|
//Scene { scene: SceneCommand } => {
|
||||||
|
//todo!("delegate");
|
||||||
|
//Ok(None)
|
||||||
|
//},
|
||||||
|
|
||||||
|
//OutputAdd => {
|
||||||
|
//arranger.midi_outs.push(MidiOutput::new(
|
||||||
|
//arranger.jack(),
|
||||||
|
//&format!("/M{}", arranger.midi_outs.len() + 1),
|
||||||
|
//&[]
|
||||||
|
//)?);
|
||||||
|
//Ok(None)
|
||||||
|
//},
|
||||||
|
|
||||||
|
//InputAdd => {
|
||||||
|
//arranger.midi_ins.push(MidiInput::new(
|
||||||
|
//arranger.jack(),
|
||||||
|
//&format!("M{}/", arranger.midi_ins.len() + 1),
|
||||||
|
//&[]
|
||||||
|
//)?);
|
||||||
|
//Ok(None)
|
||||||
|
//},
|
||||||
|
|
||||||
|
//SceneAdd => {
|
||||||
|
//let index = arranger.scene_add(None, None)?.0;
|
||||||
|
//*arranger.selection_mut() = match arranger.selection() {
|
||||||
|
//Selection::Scene(_) => Selection::Scene(index),
|
||||||
|
//Selection::TrackClip { track, scene } => Selection::TrackClip {
|
||||||
|
//track: *track,
|
||||||
|
//scene: index
|
||||||
|
//},
|
||||||
|
//_ => *arranger.selection()
|
||||||
|
//};
|
||||||
|
//Ok(None) // TODO
|
||||||
|
//},
|
||||||
|
|
||||||
|
//SceneSwap { index: usize, other: usize } => {
|
||||||
|
//let index = *index;
|
||||||
|
//let other = *other;
|
||||||
|
//Ok(Some(Self::SceneSwap { index, other }))
|
||||||
|
//},
|
||||||
|
|
||||||
|
//SceneDelete { index: usize } => {
|
||||||
|
//let index = *index;
|
||||||
|
//let scenes = arranger.scenes_mut();
|
||||||
|
//Ok(if scenes.get(index).is_some() {
|
||||||
|
//let _scene = scenes.remove(index);
|
||||||
|
//None
|
||||||
|
//} else {
|
||||||
|
//None
|
||||||
|
//})
|
||||||
|
//},
|
||||||
|
|
||||||
|
//SceneLaunch { index: usize } => {
|
||||||
|
//let index = *index;
|
||||||
|
//for track in 0..arranger.tracks.len() {
|
||||||
|
//let clip = arranger.scenes[index].clips[track].as_ref();
|
||||||
|
//arranger.tracks[track].sequencer.enqueue_next(clip);
|
||||||
|
//}
|
||||||
|
//Ok(None)
|
||||||
|
//},
|
||||||
|
|
||||||
|
//Clip { scene: ClipCommand } => {
|
||||||
|
//todo!("delegate")
|
||||||
|
//},
|
||||||
|
|
||||||
|
//ClipGet { a: usize, b: usize } => {
|
||||||
|
////(Get [a: usize, b: usize] cmd_todo!("\n\rtodo: clip: get: {a} {b}"))
|
||||||
|
////("get" [a: usize, b: usize] Some(Self::Get(a.unwrap(), b.unwrap())))
|
||||||
|
//todo!()
|
||||||
|
//},
|
||||||
|
|
||||||
|
//ClipPut { a: usize, b: usize } => {
|
||||||
|
////(Put [t: usize, s: usize, c: MaybeClip]
|
||||||
|
////Some(Self::Put(t, s, arranger.clip_put(t, s, c))))
|
||||||
|
////("put" [a: usize, b: usize, c: MaybeClip] Some(Self::Put(a.unwrap(), b.unwrap(), c.unwrap())))
|
||||||
|
//todo!()
|
||||||
|
//},
|
||||||
|
|
||||||
|
//ClipDel { a: usize, b: usize } => {
|
||||||
|
////("delete" [a: usize, b: usize] Some(Self::Put(a.unwrap(), b.unwrap(), None))))
|
||||||
|
//todo!()
|
||||||
|
//},
|
||||||
|
|
||||||
|
//ClipEnqueue { a: usize, b: usize } => {
|
||||||
|
////(Enqueue [t: usize, s: usize]
|
||||||
|
////cmd!(arranger.tracks[t].sequencer.enqueue_next(arranger.scenes[s].clips[t].as_ref())))
|
||||||
|
////("enqueue" [a: usize, b: usize] Some(Self::Enqueue(a.unwrap(), b.unwrap())))
|
||||||
|
//todo!()
|
||||||
|
//},
|
||||||
|
|
||||||
|
//ClipSwap { a: usize, b: usize }=> {
|
||||||
|
////(Edit [clip: MaybeClip] cmd_todo!("\n\rtodo: clip: edit: {clip:?}"))
|
||||||
|
////("edit" [a: MaybeClip] Some(Self::Edit(a.unwrap())))
|
||||||
|
//todo!()
|
||||||
|
//},
|
||||||
|
|
||||||
|
//});
|
||||||
|
// Update sequencer playhead indicator
|
||||||
|
//self.now().set(0.);
|
||||||
|
//if let Some((ref started_at, Some(ref playing))) = self.sequencer.play_clip {
|
||||||
|
//let clip = clip.read().unwrap();
|
||||||
|
//if *playing.read().unwrap() == *clip {
|
||||||
|
//let pulse = self.current().pulse.get();
|
||||||
|
//let start = started_at.pulse.get();
|
||||||
|
//let now = (pulse - start) % clip.length as f64;
|
||||||
|
//self.now().set(now);
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//fn jack_from_lv2 (name: &str, plugin: &::livi::Plugin) -> Usually<Jack> {
|
||||||
|
//let counts = plugin.port_counts();
|
||||||
|
//let mut jack = Jack::new(name)?;
|
||||||
|
//for i in 0..counts.atom_sequence_inputs {
|
||||||
|
//jack = jack.midi_in(&format!("midi-in-{i}"))
|
||||||
|
//}
|
||||||
|
//for i in 0..counts.atom_sequence_outputs {
|
||||||
|
//jack = jack.midi_out(&format!("midi-out-{i}"));
|
||||||
|
//}
|
||||||
|
//for i in 0..counts.audio_inputs {
|
||||||
|
//jack = jack.audio_in(&format!("audio-in-{i}"));
|
||||||
|
//}
|
||||||
|
//for i in 0..counts.audio_outputs {
|
||||||
|
//jack = jack.audio_out(&format!("audio-out-{i}"));
|
||||||
|
//}
|
||||||
|
//Ok(jack)
|
||||||
|
//}
|
||||||
|
//handle!(TuiIn: |self:Plugin, from|{
|
||||||
|
//match from.event() {
|
||||||
|
//kpat!(KeyCode::Up) => {
|
||||||
|
//self.selected = self.selected.saturating_sub(1);
|
||||||
|
//Ok(Some(true))
|
||||||
|
//},
|
||||||
|
//kpat!(KeyCode::Down) => {
|
||||||
|
//self.selected = (self.selected + 1).min(match &self.plugin {
|
||||||
|
//Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
||||||
|
//_ => unimplemented!()
|
||||||
|
//});
|
||||||
|
//Ok(Some(true))
|
||||||
|
//},
|
||||||
|
//kpat!(KeyCode::PageUp) => {
|
||||||
|
//self.selected = self.selected.saturating_sub(8);
|
||||||
|
//Ok(Some(true))
|
||||||
|
//},
|
||||||
|
//kpat!(KeyCode::PageDown) => {
|
||||||
|
//self.selected = (self.selected + 10).min(match &self.plugin {
|
||||||
|
//Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
||||||
|
//_ => unimplemented!()
|
||||||
|
//});
|
||||||
|
//Ok(Some(true))
|
||||||
|
//},
|
||||||
|
//kpat!(KeyCode::Char(',')) => {
|
||||||
|
//match self.plugin.as_mut() {
|
||||||
|
//Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
||||||
|
//let index = port_list[self.selected].index;
|
||||||
|
//if let Some(value) = instance.control_input(index) {
|
||||||
|
//instance.set_control_input(index, value - 0.01);
|
||||||
|
//}
|
||||||
|
//},
|
||||||
|
//_ => {}
|
||||||
|
//}
|
||||||
|
//Ok(Some(true))
|
||||||
|
//},
|
||||||
|
//kpat!(KeyCode::Char('.')) => {
|
||||||
|
//match self.plugin.as_mut() {
|
||||||
|
//Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
||||||
|
//let index = port_list[self.selected].index;
|
||||||
|
//if let Some(value) = instance.control_input(index) {
|
||||||
|
//instance.set_control_input(index, value + 0.01);
|
||||||
|
//}
|
||||||
|
//},
|
||||||
|
//_ => {}
|
||||||
|
//}
|
||||||
|
//Ok(Some(true))
|
||||||
|
//},
|
||||||
|
//kpat!(KeyCode::Char('g')) => {
|
||||||
|
//match self.plugin {
|
||||||
|
////Some(PluginKind::LV2(ref mut plugin)) => {
|
||||||
|
////plugin.ui_thread = Some(run_lv2_ui(LV2PluginUI::new()?)?);
|
||||||
|
////},
|
||||||
|
//Some(_) => unreachable!(),
|
||||||
|
//None => {}
|
||||||
|
//}
|
||||||
|
//Ok(Some(true))
|
||||||
|
//},
|
||||||
|
//_ => Ok(None)
|
||||||
|
//}
|
||||||
|
//});
|
||||||
|
|
||||||
|
//from_atom!("plugin/lv2" => |jack: &Jack, args| -> Plugin {
|
||||||
|
//let mut name = String::new();
|
||||||
|
//let mut path = String::new();
|
||||||
|
//atom!(atom in args {
|
||||||
|
//Atom::Map(map) => {
|
||||||
|
//if let Some(Atom::Str(n)) = map.get(&Atom::Key(":name")) {
|
||||||
|
//name = String::from(*n);
|
||||||
|
//}
|
||||||
|
//if let Some(Atom::Str(p)) = map.get(&Atom::Key(":path")) {
|
||||||
|
//path = String::from(*p);
|
||||||
|
//}
|
||||||
|
//},
|
||||||
|
//_ => panic!("unexpected in lv2 '{name}'"),
|
||||||
|
//});
|
||||||
|
//Plugin::new_lv2(jack, &name, &path)
|
||||||
|
//});
|
||||||
|
|
||||||
|
//pub struct LV2PluginUI {
|
||||||
|
//write: (),
|
||||||
|
//controller: (),
|
||||||
|
//widget: (),
|
||||||
|
//features: (),
|
||||||
|
//transfer: (),
|
||||||
|
//}
|
||||||
|
//take!(BrowseCommand |state: Pool, iter|Ok(state.browse.as_ref()
|
||||||
|
//.map(|p|Take::take(p, iter))
|
||||||
|
//.transpose()?
|
||||||
|
//.flatten()));
|
||||||
|
|
||||||
|
//fn file_browser_filter (&self) -> Arc<str> {
|
||||||
|
//todo!()
|
||||||
|
//}
|
||||||
|
//fn file_browser_path (&self) -> PathBuf {
|
||||||
|
//todo!();
|
||||||
|
//}
|
||||||
|
///// Immutable reference to sample at cursor.
|
||||||
|
//fn sample_selected (&self) -> Option<Arc<RwLock<Sample>>> {
|
||||||
|
//for (i, sample) in self.mapped.iter().enumerate() {
|
||||||
|
//if i == self.cursor().0 {
|
||||||
|
//return sample.as_ref()
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//for (i, sample) in self.unmapped.iter().enumerate() {
|
||||||
|
//if i + self.mapped.len() == self.cursor().0 {
|
||||||
|
//return Some(sample)
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//None
|
||||||
|
//}
|
||||||
|
//fn sample_gain (&self) -> f32 {
|
||||||
|
//todo!()
|
||||||
|
//}
|
||||||
|
//fn sample_above () -> usize {
|
||||||
|
//self.note_pos().min(119) + 8
|
||||||
|
//}
|
||||||
|
//fn sample_below () -> usize {
|
||||||
|
//self.note_pos().max(8) - 8
|
||||||
|
//}
|
||||||
|
//fn sample_to_left () -> usize {
|
||||||
|
//self.note_pos().min(126) + 1
|
||||||
|
//}
|
||||||
|
//fn sample_to_right () -> usize {
|
||||||
|
//self.note_pos().max(1) - 1
|
||||||
|
//}
|
||||||
|
//fn selected_pitch () -> u7 {
|
||||||
|
//(self.note_pos() as u8).into() // TODO
|
||||||
|
//}
|
||||||
|
|
||||||
|
//select (&self, state: &mut Sampler, i: usize) -> Option<Self> {
|
||||||
|
//Self::Select(state.set_note_pos(i))
|
||||||
|
//}
|
||||||
|
///// Assign sample to slot
|
||||||
|
//set (&self, slot: u7, sample: Option<Arc<RwLock<Sample>>>) -> Option<Self> {
|
||||||
|
//let i = slot.as_int() as usize;
|
||||||
|
//let old = self.mapped[i].clone();
|
||||||
|
//self.mapped[i] = sample;
|
||||||
|
//Some(Self::Set(old))
|
||||||
|
//}
|
||||||
|
//set_start (&self, state: &mut Sampler, slot: u7, frame: usize) -> Option<Self> {
|
||||||
|
//todo!()
|
||||||
|
//}
|
||||||
|
//set_gain (&self, state: &mut Sampler, slot: u7, g: f32) -> Option<Self> {
|
||||||
|
//todo!()
|
||||||
|
//}
|
||||||
|
//note_on (&self, state: &mut Sampler, slot: u7, v: u7) -> Option<Self> {
|
||||||
|
//todo!()
|
||||||
|
//}
|
||||||
|
//note_off (&self, state: &mut Sampler, slot: u7) -> Option<Self> {
|
||||||
|
//todo!()
|
||||||
|
//}
|
||||||
|
//set_sample (&self, state: &mut Sampler, slot: u7, s: Option<Arc<RwLock<Sample>>>) -> Option<Self> {
|
||||||
|
//Some(Self::SetSample(p, state.set_sample(p, s)))
|
||||||
|
//}
|
||||||
|
//import (&self, state: &mut Sampler, c: FileBrowserCommand) -> Option<Self> {
|
||||||
|
//match c {
|
||||||
|
//FileBrowserCommand::Begin => {
|
||||||
|
////let voices = &state.state.voices;
|
||||||
|
////let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![])));
|
||||||
|
//state.mode = Some(SamplerMode::Import(0, FileBrowser::new(None)?));
|
||||||
|
//None
|
||||||
|
//},
|
||||||
|
//_ => {
|
||||||
|
//println!("\n\rtodo: import: filebrowser: {c:?}");
|
||||||
|
//None
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
////(Select [i: usize] Some(Self::Select(state.set_note_pos(i))))
|
||||||
|
////(RecordBegin [p: u7] cmd!(state.begin_recording(p.as_int() as usize)))
|
||||||
|
////(RecordCancel [] cmd!(state.cancel_recording()))
|
||||||
|
////(RecordFinish [] cmd!(state.finish_recording()))
|
||||||
|
////(SetStart [p: u7, frame: usize] cmd_todo!("\n\rtodo: {self:?}"))
|
||||||
|
////(SetGain [p: u7, gain: f32] cmd_todo!("\n\rtodo: {self:?}"))
|
||||||
|
////(NoteOn [p: u7, velocity: u7] cmd_todo!("\n\rtodo: {self:?}"))
|
||||||
|
////(NoteOff [p: u7] cmd_todo!("\n\rtodo: {self:?}"))
|
||||||
|
////(SetSample [p: u7, s: Option<Arc<RwLock<Sample>>>] Some(Self::SetSample(p, state.set_sample(p, s))))
|
||||||
|
////(Import [c: FileBrowserCommand] match c {
|
||||||
|
////FileBrowserCommand::Begin => {
|
||||||
|
//////let voices = &state.state.voices;
|
||||||
|
//////let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![])));
|
||||||
|
////state.mode = Some(SamplerMode::Import(0, FileBrowser::new(None)?));
|
||||||
|
////None
|
||||||
|
////},
|
||||||
|
////_ => {
|
||||||
|
////println!("\n\rtodo: import: filebrowser: {c:?}");
|
||||||
|
////None
|
||||||
|
////}
|
||||||
|
////})));
|
||||||
|
////("import" [,..a]
|
||||||
|
////FileBrowserCommand::try_from_expr(state, a).map(Self::Import))
|
||||||
|
////("select" [i: usize]
|
||||||
|
////Some(Self::Select(i.expect("no index"))))
|
||||||
|
////("record/begin" [i: u7]
|
||||||
|
////Some(Self::RecordBegin(i.expect("no index"))))
|
||||||
|
////("record/cancel" []
|
||||||
|
////Some(Self::RecordCancel))
|
||||||
|
////("record/finish" []
|
||||||
|
////Some(Self::RecordFinish))
|
||||||
|
////("set/sample" [i: u7, s: Option<Arc<RwLock<Sample>>>]
|
||||||
|
////Some(Self::SetSample(i.expect("no index"), s.expect("no sampler"))))
|
||||||
|
////("set/start" [i: u7, s: usize]
|
||||||
|
////Some(Self::SetStart(i.expect("no index"), s.expect("no start"))))
|
||||||
|
////("set/gain" [i: u7, g: f32]
|
||||||
|
////Some(Self::SetGain(i.expect("no index"), g.expect("no gain"))))
|
||||||
|
////("note/on" [p: u7, v: u7]
|
||||||
|
////Some(Self::NoteOn(p.expect("no slot"), v.expect("no velocity"))))
|
||||||
|
////("note/off" [p: u7]
|
||||||
|
////Some(Self::NoteOff(p.expect("no slot"))))));
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
//for port in midi_in.iter() {
|
||||||
|
//for event in port.iter() {
|
||||||
|
//match event {
|
||||||
|
//(time, Ok(LiveEvent::Midi {message, ..})) => match message {
|
||||||
|
//MidiMessage::NoteOn {ref key, ..} if let Some(editor) = self.editor.as_ref() => {
|
||||||
|
//editor.set_note_pos(key.as_int() as usize);
|
||||||
|
//},
|
||||||
|
//MidiMessage::Controller {controller, value} if let (Some(editor), Some(sampler)) = (
|
||||||
|
//self.editor.as_ref(),
|
||||||
|
//self.sampler.as_ref(),
|
||||||
|
//) => {
|
||||||
|
//// TODO: give sampler its own cursor
|
||||||
|
//if let Some(sample) = &sampler.mapped[editor.note_pos()] {
|
||||||
|
//sample.write().unwrap().handle_cc(*controller, *value)
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//_ =>{}
|
||||||
|
//},
|
||||||
|
//_ =>{}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
//scene_scroll: Fill::Y(Fixed::X(1, ScrollbarV {
|
||||||
|
//offset: arrangement.scene_scroll,
|
||||||
|
//length: h_scenes_area as usize,
|
||||||
|
//total: h_scenes as usize,
|
||||||
|
//})),
|
||||||
|
//take!(SceneCommand |state: Arrangement, iter|state.selected_scene().as_ref()
|
||||||
|
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||||
|
|
||||||
|
//pub(crate) fn io_conns <'a, T: PortsSizes<'a>> (
|
||||||
|
//fg: Color, bg: Color, iter: &mut impl Iterator<Item = (usize, &'a Arc<str>, &'a [Connect], usize, usize)>
|
||||||
|
//) -> impl Content<TuiOut> + 'a {
|
||||||
|
//Fill::XY(Thunk::new(move|to: &mut TuiOut|for (_, _, connections, y, y2) in &mut *iter {
|
||||||
|
//to.place(&map_south(y as u16, (y2-y) as u16, Bsp::s(
|
||||||
|
//Fill::Y(Tui::bold(true, wrap(bg, fg, Fill::Y(Align::w(&"▞▞▞▞ ▞▞▞▞"))))),
|
||||||
|
//Thunk::new(|to: &mut TuiOut|for (index, _connection) in connections.iter().enumerate() {
|
||||||
|
//to.place(&map_south(index as u16, 1, Fill::Y(Align::w(Tui::bold(false,
|
||||||
|
//wrap(bg, fg, Fill::Y(&"")))))))
|
||||||
|
//})
|
||||||
|
//)))
|
||||||
|
//}))
|
||||||
|
//}
|
||||||
|
//track_scroll: Fill::Y(Fixed::Y(1, ScrollbarH {
|
||||||
|
//offset: arrangement.track_scroll,
|
||||||
|
//length: h_tracks_area as usize,
|
||||||
|
//total: h_scenes as usize,
|
||||||
|
//})),
|
||||||
|
//take!(TrackCommand |state: Arrangement, iter|state.selected_track().as_ref()
|
||||||
|
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,10 @@ path = "tek.rs"
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tek_device = { path = "../device" }
|
ansi_term = "0.12.1"
|
||||||
|
|
||||||
atomic_float = { workspace = true }
|
atomic_float = { workspace = true }
|
||||||
backtrace = { workspace = true }
|
backtrace = { workspace = true }
|
||||||
|
builder-pattern = "0.4.2"
|
||||||
clap = { workspace = true, optional = true }
|
clap = { workspace = true, optional = true }
|
||||||
jack = { workspace = true }
|
jack = { workspace = true }
|
||||||
konst = { workspace = true }
|
konst = { workspace = true }
|
||||||
|
|
@ -32,30 +32,32 @@ uuid = { workspace = true, optional = true }
|
||||||
wavers = { workspace = true, optional = true }
|
wavers = { workspace = true, optional = true }
|
||||||
winit = { workspace = true, optional = true }
|
winit = { workspace = true, optional = true }
|
||||||
xdg = { workspace = true }
|
xdg = { workspace = true }
|
||||||
ansi_term = "0.12.1"
|
|
||||||
builder-pattern = "0.4.2"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
proptest = { workspace = true }
|
proptest = { workspace = true }
|
||||||
proptest-derive = { workspace = true }
|
proptest-derive = { workspace = true }
|
||||||
tek_engine = { path = "../engine" }
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
arranger = ["port", "editor", "sequencer", "editor"]
|
arranger = ["port", "editor", "sequencer", "editor", "track", "scene", "clip", "select"]
|
||||||
browse = []
|
browse = []
|
||||||
clap = []
|
clap = []
|
||||||
cli = ["dep:clap"]
|
cli = ["dep:clap"]
|
||||||
|
clip = []
|
||||||
clock = []
|
clock = []
|
||||||
default = ["cli", "arranger", "sampler", "lv2"]
|
default = ["cli", "arranger", "sampler", "lv2"]
|
||||||
editor = []
|
editor = []
|
||||||
host = ["lv2"]
|
host = ["lv2"]
|
||||||
lv2 = ["port", "livi", "winit"]
|
lv2 = ["port", "livi"]
|
||||||
|
lv2_gui = ["lv2", "winit"]
|
||||||
meter = []
|
meter = []
|
||||||
mixer = []
|
mixer = []
|
||||||
pool = []
|
pool = []
|
||||||
port = []
|
port = []
|
||||||
sampler = ["port", "meter", "mixer", "browse", "symphonia", "wavers"]
|
sampler = ["port", "meter", "mixer", "browse", "symphonia", "wavers"]
|
||||||
|
scene = []
|
||||||
|
select = []
|
||||||
sequencer = ["port", "clock", "uuid", "pool"]
|
sequencer = ["port", "clock", "uuid", "pool"]
|
||||||
sf2 = []
|
sf2 = []
|
||||||
|
track = []
|
||||||
vst2 = []
|
vst2 = []
|
||||||
vst3 = []
|
vst3 = []
|
||||||
|
|
|
||||||
1079
app/tek.rs
1079
app/tek.rs
File diff suppressed because it is too large
Load diff
3741
app/tek_impls.rs
3741
app/tek_impls.rs
File diff suppressed because it is too large
Load diff
|
|
@ -2,12 +2,64 @@ use crate::*;
|
||||||
use clap::{self, Parser, Subcommand};
|
use clap::{self, Parser, Subcommand};
|
||||||
use builder_pattern::Builder;
|
use builder_pattern::Builder;
|
||||||
|
|
||||||
|
/// Wraps [JackState], and through it [jack::Client] when connected.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let jack = tek::Jack::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Debug, Default)] pub struct Jack<'j> (
|
||||||
|
pub(crate) Arc<RwLock<JackState<'j>>>
|
||||||
|
);
|
||||||
|
|
||||||
|
/// This is a connection which may be [Inactive], [Activating], or [Active].
|
||||||
|
/// In the [Active] and [Inactive] states, [JackState::client] returns a
|
||||||
|
/// [jack::Client], which you can use to talk to the JACK API.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let state = tek::JackState::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub enum JackState<'j> {
|
||||||
|
/// Unused
|
||||||
|
#[default] Inert,
|
||||||
|
/// Before activation.
|
||||||
|
Inactive(Client),
|
||||||
|
/// During activation.
|
||||||
|
Activating,
|
||||||
|
/// After activation. Must not be dropped for JACK thread to persist.
|
||||||
|
Active(DynamicAsyncClient<'j>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Event enum for JACK events.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let event = tek::JackEvent::XRun;
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, PartialEq)] pub enum JackEvent {
|
||||||
|
ThreadInit,
|
||||||
|
Shutdown(ClientStatus, Arc<str>),
|
||||||
|
Freewheel(bool),
|
||||||
|
SampleRate(Frames),
|
||||||
|
ClientRegistration(Arc<str>, bool),
|
||||||
|
PortRegistration(PortId, bool),
|
||||||
|
PortRename(PortId, Arc<str>, Arc<str>),
|
||||||
|
PortsConnected(PortId, PortId, bool),
|
||||||
|
GraphReorder,
|
||||||
|
XRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic notification handler that emits [JackEvent]
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let notify = tek::JackNotify(|_|{});
|
||||||
|
/// ```
|
||||||
|
pub struct JackNotify<T: Fn(JackEvent) + Send>(pub T);
|
||||||
|
|
||||||
/// Total state
|
/// Total state
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use tek::tek_device::TracksView;
|
/// use tek::{TracksView, ScenesView, AddScene};
|
||||||
/// let app: tek::App = Default::default();
|
/// let mut app = tek::App::default();
|
||||||
/// let _ = app.scene_add(None, None)?;
|
/// let _ = app.scene_add(None, None).unwrap();
|
||||||
/// let _ = app.update_clock();
|
/// let _ = app.update_clock();
|
||||||
/// app.project.editor = Some(Default::default());
|
/// app.project.editor = Some(Default::default());
|
||||||
/// //let _: Vec<_> = app.project.inputs_with_sizes().collect();
|
/// //let _: Vec<_> = app.project.inputs_with_sizes().collect();
|
||||||
|
|
@ -53,7 +105,7 @@ use builder_pattern::Builder;
|
||||||
/// Configuration: mode, view, and bind definitions.
|
/// Configuration: mode, view, and bind definitions.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// let conf: tek::Config = Default::default();
|
/// let conf = tek::Config::default();
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Default, Debug)] pub struct Config {
|
#[derive(Default, Debug)] pub struct Config {
|
||||||
/// XDG base directories of running user.
|
/// XDG base directories of running user.
|
||||||
|
|
@ -69,7 +121,7 @@ use builder_pattern::Builder;
|
||||||
/// Group of view and keys definitions.
|
/// Group of view and keys definitions.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// let mode: tek::Mode<std::sync::Arc<str>> = Default::default();
|
/// let mode = tek::Mode::<std::sync::Arc<str>>::default();
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Default, Debug)] pub struct Mode<D: Language + Ord> {
|
#[derive(Default, Debug)] pub struct Mode<D: Language + Ord> {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
|
@ -83,7 +135,7 @@ use builder_pattern::Builder;
|
||||||
/// An input binding.
|
/// An input binding.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// let bind: tek::Bind<(), ()> = Default::default();
|
/// let bind = tek::Bind::<(), ()>::default();
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug)] pub struct Bind<E, C>(
|
#[derive(Debug)] pub struct Bind<E, C>(
|
||||||
/// Map of each event (e.g. key combination) to
|
/// Map of each event (e.g. key combination) to
|
||||||
|
|
@ -142,9 +194,9 @@ use builder_pattern::Builder;
|
||||||
/// use clap::CommandFactory;
|
/// use clap::CommandFactory;
|
||||||
/// tek::Cli::command().debug_assert();
|
/// tek::Cli::command().debug_assert();
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Parser, Default)]
|
#[derive(Parser)]
|
||||||
#[command(name = "tek", version, about = Some(HEADER), long_about = Some(HEADER))]
|
#[command(name = "tek", version, about = Some(HEADER), long_about = Some(HEADER))]
|
||||||
pub struct Cli {
|
#[derive(Debug, Default)] pub struct Cli {
|
||||||
/// Pre-defined configuration modes.
|
/// Pre-defined configuration modes.
|
||||||
///
|
///
|
||||||
/// TODO: Replace these with scripted configurations.
|
/// TODO: Replace these with scripted configurations.
|
||||||
|
|
@ -213,8 +265,7 @@ pub struct Cli {
|
||||||
/// ```
|
/// ```
|
||||||
/// let axis = tek::ControlAxis::X;
|
/// let axis = tek::ControlAxis::X;
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)] pub enum ControlAxis {
|
||||||
pub enum ControlAxis {
|
|
||||||
X, Y, Z, I
|
X, Y, Z, I
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,3 +283,794 @@ pub enum ControlAxis {
|
||||||
Browse(BrowseTarget, Arc<Browse>),
|
Browse(BrowseTarget, Arc<Browse>),
|
||||||
Options,
|
Options,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat)
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone)] pub struct Timebase {
|
||||||
|
/// Audio samples per second
|
||||||
|
pub sr: SampleRate,
|
||||||
|
/// MIDI beats per minute
|
||||||
|
pub bpm: BeatsPerMinute,
|
||||||
|
/// MIDI ticks per beat
|
||||||
|
pub ppq: PulsesPerQuaver,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator that emits subsequent ticks within a range.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let iter = tek::TicksIterator::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct TicksIterator {
|
||||||
|
pub spp: f64,
|
||||||
|
pub sample: usize,
|
||||||
|
pub start: usize,
|
||||||
|
pub end: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tek::{TimeRange, NoteRange};
|
||||||
|
/// let model = tek::MidiSelection::from((1, false));
|
||||||
|
///
|
||||||
|
/// let _ = model.get_time_len();
|
||||||
|
/// let _ = model.get_time_zoom();
|
||||||
|
/// let _ = model.get_time_lock();
|
||||||
|
/// let _ = model.get_time_start();
|
||||||
|
/// let _ = model.get_time_axis();
|
||||||
|
/// let _ = model.get_time_end();
|
||||||
|
///
|
||||||
|
/// let _ = model.get_note_lo();
|
||||||
|
/// let _ = model.get_note_axis();
|
||||||
|
/// let _ = model.get_note_hi();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone)] pub struct MidiCursor {
|
||||||
|
/// Time coordinate of cursor
|
||||||
|
pub time_pos: Arc<AtomicUsize>,
|
||||||
|
/// Note coordinate of cursor
|
||||||
|
pub note_pos: Arc<AtomicUsize>,
|
||||||
|
/// Length of note that will be inserted, in pulses
|
||||||
|
pub note_len: Arc<AtomicUsize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Default)] pub struct MidiSelection {
|
||||||
|
pub time_len: Arc<AtomicUsize>,
|
||||||
|
/// Length of visible time axis
|
||||||
|
pub time_axis: Arc<AtomicUsize>,
|
||||||
|
/// Earliest time displayed
|
||||||
|
pub time_start: Arc<AtomicUsize>,
|
||||||
|
/// Time step
|
||||||
|
pub time_zoom: Arc<AtomicUsize>,
|
||||||
|
/// Auto rezoom to fit in time axis
|
||||||
|
pub time_lock: Arc<AtomicBool>,
|
||||||
|
/// Length of visible note axis
|
||||||
|
pub note_axis: Arc<AtomicUsize>,
|
||||||
|
// Lowest note displayed
|
||||||
|
pub note_lo: Arc<AtomicUsize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A point in time in all time scales (microsecond, sample, MIDI pulse)
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default, Clone)] pub struct Moment {
|
||||||
|
pub timebase: Arc<Timebase>,
|
||||||
|
/// Current time in microseconds
|
||||||
|
pub usec: Microsecond,
|
||||||
|
/// Current time in audio samples
|
||||||
|
pub sample: SampleCount,
|
||||||
|
/// Current time in MIDI pulses
|
||||||
|
pub pulse: Pulse,
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Default)] pub enum Moment2 {
|
||||||
|
#[default] None,
|
||||||
|
Zero,
|
||||||
|
Usec(Microsecond),
|
||||||
|
Sample(SampleCount),
|
||||||
|
Pulse(Pulse),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MIDI resolution in PPQ (pulses per quarter note)
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct PulsesPerQuaver (pub(crate) AtomicF64);
|
||||||
|
|
||||||
|
/// Timestamp in MIDI pulses
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct Pulse (pub(crate) AtomicF64);
|
||||||
|
|
||||||
|
/// Tempo in beats per minute
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct BeatsPerMinute (pub(crate) AtomicF64);
|
||||||
|
|
||||||
|
/// Quantization setting for launching clips
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct LaunchSync (pub(crate) AtomicF64);
|
||||||
|
|
||||||
|
/// Quantization setting for notes
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct Quantize (pub(crate) AtomicF64);
|
||||||
|
|
||||||
|
/// Timestamp in audio samples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct SampleCount (pub(crate) AtomicF64);
|
||||||
|
|
||||||
|
/// Audio sample rate in Hz (samples per second)
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct SampleRate (pub(crate) AtomicF64);
|
||||||
|
|
||||||
|
/// Timestamp in microseconds
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct Microsecond (pub(crate) AtomicF64);
|
||||||
|
|
||||||
|
/// The source of time.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let clock = tek::Clock::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Default)] pub struct Clock {
|
||||||
|
/// JACK transport handle.
|
||||||
|
pub transport: Arc<Option<Transport>>,
|
||||||
|
/// Global temporal resolution (shared by [Moment] fields)
|
||||||
|
pub timebase: Arc<Timebase>,
|
||||||
|
/// Current global sample and usec (monotonic from JACK clock)
|
||||||
|
pub global: Arc<Moment>,
|
||||||
|
/// Global sample and usec at which playback started
|
||||||
|
pub started: Arc<RwLock<Option<Moment>>>,
|
||||||
|
/// Playback offset (when playing not from start)
|
||||||
|
pub offset: Arc<Moment>,
|
||||||
|
/// Current playhead position
|
||||||
|
pub playhead: Arc<Moment>,
|
||||||
|
/// Note quantization factor
|
||||||
|
pub quant: Arc<Quantize>,
|
||||||
|
/// Launch quantization factor
|
||||||
|
pub sync: Arc<LaunchSync>,
|
||||||
|
/// Size of buffer in samples
|
||||||
|
pub chunk: Arc<AtomicUsize>,
|
||||||
|
// Cache of formatted strings
|
||||||
|
pub view_cache: Arc<RwLock<ClockView>>,
|
||||||
|
/// For syncing the clock to an external source
|
||||||
|
#[cfg(feature = "port")] pub midi_in: Arc<RwLock<Option<MidiInput>>>,
|
||||||
|
/// For syncing other devices to this clock
|
||||||
|
#[cfg(feature = "port")] pub midi_out: Arc<RwLock<Option<MidiOutput>>>,
|
||||||
|
/// For emitting a metronome
|
||||||
|
#[cfg(feature = "port")] pub click_out: Arc<RwLock<Option<AudioOutput>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contains memoized renders of clock values.
|
||||||
|
///
|
||||||
|
/// Performance optimization.
|
||||||
|
#[derive(Debug)] pub struct ClockView {
|
||||||
|
pub sr: Memo<Option<(bool, f64)>, String>,
|
||||||
|
pub buf: Memo<Option<f64>, String>,
|
||||||
|
pub lat: Memo<Option<f64>, String>,
|
||||||
|
pub bpm: Memo<Option<f64>, String>,
|
||||||
|
pub beat: Memo<Option<f64>, String>,
|
||||||
|
pub time: Memo<Option<f64>, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Arranger.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let arranger = tek::Arrangement::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Default, Debug)] pub struct Arrangement {
|
||||||
|
/// Project name.
|
||||||
|
pub name: Arc<str>,
|
||||||
|
/// Base color.
|
||||||
|
pub color: ItemTheme,
|
||||||
|
/// JACK client handle.
|
||||||
|
pub jack: Jack<'static>,
|
||||||
|
/// FIXME a render of the project arrangement, redrawn on update.
|
||||||
|
/// TODO rename to "render_cache" or smth
|
||||||
|
pub arranger: Arc<RwLock<Buffer>>,
|
||||||
|
/// Display size
|
||||||
|
pub size: Measure<TuiOut>,
|
||||||
|
/// Display size of clips area
|
||||||
|
pub size_inner: Measure<TuiOut>,
|
||||||
|
/// Source of time
|
||||||
|
#[cfg(feature = "clock")] pub clock: Clock,
|
||||||
|
/// Allows one MIDI clip to be edited
|
||||||
|
#[cfg(feature = "editor")] pub editor: Option<MidiEditor>,
|
||||||
|
/// List of global midi inputs
|
||||||
|
#[cfg(feature = "port")] pub midi_ins: Vec<MidiInput>,
|
||||||
|
/// List of global midi outputs
|
||||||
|
#[cfg(feature = "port")] pub midi_outs: Vec<MidiOutput>,
|
||||||
|
/// List of global audio inputs
|
||||||
|
#[cfg(feature = "port")] pub audio_ins: Vec<AudioInput>,
|
||||||
|
/// List of global audio outputs
|
||||||
|
#[cfg(feature = "port")] pub audio_outs: Vec<AudioOutput>,
|
||||||
|
/// Selected UI element
|
||||||
|
#[cfg(feature = "select")] pub selection: Selection,
|
||||||
|
/// Last track number (to avoid duplicate port names)
|
||||||
|
#[cfg(feature = "track")] pub track_last: usize,
|
||||||
|
/// List of tracks
|
||||||
|
#[cfg(feature = "track")] pub tracks: Vec<Track>,
|
||||||
|
/// Scroll offset of tracks
|
||||||
|
#[cfg(feature = "track")] pub track_scroll: usize,
|
||||||
|
/// List of scenes
|
||||||
|
#[cfg(feature = "scene")] pub scenes: Vec<Scene>,
|
||||||
|
/// Scroll offset of scenes
|
||||||
|
#[cfg(feature = "scene")] pub scene_scroll: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Browses for files to load/save.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let browse = tek::Browse::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq)] pub struct Browse {
|
||||||
|
pub cwd: PathBuf,
|
||||||
|
pub dirs: Vec<(OsString, String)>,
|
||||||
|
pub files: Vec<(OsString, String)>,
|
||||||
|
pub filter: String,
|
||||||
|
pub index: usize,
|
||||||
|
pub scroll: usize,
|
||||||
|
pub size: Measure<TuiOut>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct EntriesIterator<'a> {
|
||||||
|
pub browser: &'a Browse,
|
||||||
|
pub offset: usize,
|
||||||
|
pub length: usize,
|
||||||
|
pub index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)] pub enum BrowseTarget {
|
||||||
|
SaveProject,
|
||||||
|
LoadProject,
|
||||||
|
ImportSample(Arc<RwLock<Option<Sample>>>),
|
||||||
|
ExportSample(Arc<RwLock<Option<Sample>>>),
|
||||||
|
ImportClip(Arc<RwLock<Option<MidiClip>>>),
|
||||||
|
ExportClip(Arc<RwLock<Option<MidiClip>>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A MIDI sequence.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let clip = tek::MidiClip::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Default)] pub struct MidiClip {
|
||||||
|
pub uuid: uuid::Uuid,
|
||||||
|
/// Name of clip
|
||||||
|
pub name: Arc<str>,
|
||||||
|
/// Temporal resolution in pulses per quarter note
|
||||||
|
pub ppq: usize,
|
||||||
|
/// Length of clip in pulses
|
||||||
|
pub length: usize,
|
||||||
|
/// Notes in clip
|
||||||
|
pub notes: MidiData,
|
||||||
|
/// Whether to loop the clip or play it once
|
||||||
|
pub looped: bool,
|
||||||
|
/// Start of loop
|
||||||
|
pub loop_start: usize,
|
||||||
|
/// Length of loop
|
||||||
|
pub loop_length: usize,
|
||||||
|
/// All notes are displayed with minimum length
|
||||||
|
pub percussive: bool,
|
||||||
|
/// Identifying color of clip
|
||||||
|
pub color: ItemTheme,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A device that can be plugged into the chain.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let device = tek::Device::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub enum Device {
|
||||||
|
#[default]
|
||||||
|
Bypass,
|
||||||
|
Mute,
|
||||||
|
#[cfg(feature = "sampler")]
|
||||||
|
Sampler(Sampler),
|
||||||
|
#[cfg(feature = "lv2")] // TODO
|
||||||
|
Lv2(Lv2),
|
||||||
|
#[cfg(feature = "vst2")] // TODO
|
||||||
|
Vst2,
|
||||||
|
#[cfg(feature = "vst3")] // TODO
|
||||||
|
Vst3,
|
||||||
|
#[cfg(feature = "clap")] // TODO
|
||||||
|
Clap,
|
||||||
|
#[cfg(feature = "sf2")] // TODO
|
||||||
|
Sf2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Some sort of wrapper?
|
||||||
|
pub struct DeviceAudio<'a>(pub &'a mut Device);
|
||||||
|
|
||||||
|
/// Contains state for viewing and editing a clip.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::sync::{Arc, RwLock};
|
||||||
|
/// let clip = tek::MidiClip::stop_all();
|
||||||
|
/// let mut editor = tek::MidiEditor {
|
||||||
|
/// mode: tek::PianoHorizontal::new(Some(&Arc::new(RwLock::new(clip)))),
|
||||||
|
/// size: Default::default(),
|
||||||
|
/// //keys: Default::default(),
|
||||||
|
/// };
|
||||||
|
/// let _ = editor.put_note(true);
|
||||||
|
/// let _ = editor.put_note(false);
|
||||||
|
/// let _ = editor.clip_status();
|
||||||
|
/// let _ = editor.edit_status();
|
||||||
|
/// ```
|
||||||
|
pub struct MidiEditor {
|
||||||
|
/// Size of editor on screen
|
||||||
|
pub size: Measure<TuiOut>,
|
||||||
|
/// View mode and state of editor
|
||||||
|
pub mode: PianoHorizontal,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A clip, rendered as a horizontal piano roll.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let piano = tek::PianoHorizontal::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Default)] pub struct PianoHorizontal {
|
||||||
|
pub clip: Option<Arc<RwLock<MidiClip>>>,
|
||||||
|
/// Buffer where the whole clip is rerendered on change
|
||||||
|
pub buffer: Arc<RwLock<BigBuffer>>,
|
||||||
|
/// Size of actual notes area
|
||||||
|
pub size: Measure<TuiOut>,
|
||||||
|
/// The display window
|
||||||
|
pub range: MidiSelection,
|
||||||
|
/// The note cursor
|
||||||
|
pub point: MidiCursor,
|
||||||
|
/// The highlight color palette
|
||||||
|
pub color: ItemTheme,
|
||||||
|
/// Width of the keyboard
|
||||||
|
pub keys_width: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 12 piano keys, some highlighted.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let keys = tek::OctaveVertical::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Copy, Clone)] pub struct OctaveVertical {
|
||||||
|
pub on: [bool; 12],
|
||||||
|
pub colors: [Color; 3]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A LV2 plugin.
|
||||||
|
#[derive(Debug)] #[cfg(feature = "lv2")] pub struct Lv2 {
|
||||||
|
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
||||||
|
pub jack: Jack<'static>,
|
||||||
|
pub name: Arc<str>,
|
||||||
|
pub path: Option<Arc<str>>,
|
||||||
|
pub selected: usize,
|
||||||
|
pub mapping: bool,
|
||||||
|
pub midi_ins: Vec<Port<MidiIn>>,
|
||||||
|
pub midi_outs: Vec<Port<MidiOut>>,
|
||||||
|
pub audio_ins: Vec<Port<AudioIn>>,
|
||||||
|
pub audio_outs: Vec<Port<AudioOut>>,
|
||||||
|
|
||||||
|
pub lv2_world: livi::World,
|
||||||
|
pub lv2_instance: livi::Instance,
|
||||||
|
pub lv2_plugin: livi::Plugin,
|
||||||
|
pub lv2_features: Arc<livi::Features>,
|
||||||
|
pub lv2_port_list: Vec<livi::Port>,
|
||||||
|
pub lv2_input_buffer: Vec<livi::event::LV2AtomSequence>,
|
||||||
|
pub lv2_ui_thread: Option<JoinHandle<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A LV2 plugin's X11 UI.
|
||||||
|
#[cfg(feature = "lv2_gui")] pub struct LV2PluginUI {
|
||||||
|
pub window: Option<Window>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)] pub enum MeteringMode {
|
||||||
|
#[default] Rms,
|
||||||
|
Log10,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)] pub struct Log10Meter(pub f32);
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)] pub struct RmsMeter(pub f32);
|
||||||
|
|
||||||
|
#[derive(Debug, Default)] pub enum MixingMode {
|
||||||
|
#[default] Summing,
|
||||||
|
Average,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A clip pool.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let pool = tek::Pool::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug)] pub struct Pool {
|
||||||
|
pub visible: bool,
|
||||||
|
/// Selected clip
|
||||||
|
pub clip: AtomicUsize,
|
||||||
|
/// Mode switch
|
||||||
|
pub mode: Option<PoolMode>,
|
||||||
|
/// Embedded file browse
|
||||||
|
#[cfg(feature = "browse")] pub browse: Option<Browse>,
|
||||||
|
/// Collection of MIDI clips.
|
||||||
|
#[cfg(feature = "clip")] pub clips: Arc<RwLock<Vec<Arc<RwLock<MidiClip>>>>>,
|
||||||
|
/// Collection of sound samples.
|
||||||
|
#[cfg(feature = "sampler")] pub samples: Arc<RwLock<Vec<Arc<RwLock<Sample>>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays and edits clip length.
|
||||||
|
#[derive(Clone, Debug, Default)] pub struct ClipLength {
|
||||||
|
/// Pulses per beat (quaver)
|
||||||
|
pub ppq: usize,
|
||||||
|
/// Beats per bar
|
||||||
|
pub bpb: usize,
|
||||||
|
/// Length of clip in pulses
|
||||||
|
pub pulses: usize,
|
||||||
|
/// Selected subdivision
|
||||||
|
pub focus: Option<ClipLengthFocus>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Some sort of wrapper again?
|
||||||
|
pub struct PoolView<'a>(pub &'a Pool);
|
||||||
|
|
||||||
|
/// Audio input port.
|
||||||
|
#[derive(Debug)] pub struct AudioInput {
|
||||||
|
/// Handle to JACK client, for receiving reconnect events.
|
||||||
|
pub jack: Jack<'static>,
|
||||||
|
/// Port name
|
||||||
|
pub name: Arc<str>,
|
||||||
|
/// Port handle.
|
||||||
|
pub port: Port<AudioIn>,
|
||||||
|
/// List of ports to connect to.
|
||||||
|
pub connections: Vec<Connect>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Audio output port.
|
||||||
|
#[derive(Debug)] pub struct AudioOutput {
|
||||||
|
/// Handle to JACK client, for receiving reconnect events.
|
||||||
|
pub jack: Jack<'static>,
|
||||||
|
/// Port name
|
||||||
|
pub name: Arc<str>,
|
||||||
|
/// Port handle.
|
||||||
|
pub port: Port<AudioOut>,
|
||||||
|
/// List of ports to connect to.
|
||||||
|
pub connections: Vec<Connect>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MIDI input port.
|
||||||
|
#[derive(Debug)] pub struct MidiInput {
|
||||||
|
/// Handle to JACK client, for receiving reconnect events.
|
||||||
|
pub jack: Jack<'static>,
|
||||||
|
/// Port name
|
||||||
|
pub name: Arc<str>,
|
||||||
|
/// Port handle.
|
||||||
|
pub port: Port<MidiIn>,
|
||||||
|
/// List of currently held notes.
|
||||||
|
pub held: Arc<RwLock<[bool;128]>>,
|
||||||
|
/// List of ports to connect to.
|
||||||
|
pub connections: Vec<Connect>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MIDI output port.
|
||||||
|
#[derive(Debug)] pub struct MidiOutput {
|
||||||
|
/// Handle to JACK client, for receiving reconnect events.
|
||||||
|
pub jack: Jack<'static>,
|
||||||
|
/// Port name
|
||||||
|
pub name: Arc<str>,
|
||||||
|
/// Port handle.
|
||||||
|
pub port: Port<MidiOut>,
|
||||||
|
/// List of ports to connect to.
|
||||||
|
pub connections: Vec<Connect>,
|
||||||
|
/// List of currently held notes.
|
||||||
|
pub held: Arc<RwLock<[bool;128]>>,
|
||||||
|
/// Buffer
|
||||||
|
pub note_buffer: Vec<u8>,
|
||||||
|
/// Buffer
|
||||||
|
pub output_buffer: Vec<Vec<Vec<u8>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Port connection manager.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let connect = tek::Connect::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Debug, Default)] pub struct Connect {
|
||||||
|
pub name: Option<ConnectName>,
|
||||||
|
pub scope: Option<ConnectScope>,
|
||||||
|
pub status: Arc<RwLock<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>>,
|
||||||
|
pub info: Arc<str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Plays [Voice]s from [Sample]s.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let sampler = tek::Sampler::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct Sampler {
|
||||||
|
/// Name of sampler.
|
||||||
|
pub name: Arc<str>,
|
||||||
|
/// Device color.
|
||||||
|
pub color: ItemTheme,
|
||||||
|
/// Audio input ports. Samples get recorded here.
|
||||||
|
#[cfg(feature = "port")] pub audio_ins: Vec<AudioInput>,
|
||||||
|
/// Audio input meters.
|
||||||
|
#[cfg(feature = "meter")] pub input_meters: Vec<f32>,
|
||||||
|
/// Sample currently being recorded.
|
||||||
|
pub recording: Option<(usize, Option<Arc<RwLock<Sample>>>)>,
|
||||||
|
/// Recording buffer.
|
||||||
|
pub buffer: Vec<Vec<f32>>,
|
||||||
|
/// Samples mapped to MIDI notes.
|
||||||
|
pub samples: Samples<128>,
|
||||||
|
/// Samples that are not mapped to MIDI notes.
|
||||||
|
pub unmapped: Vec<Arc<RwLock<Sample>>>,
|
||||||
|
/// Sample currently being edited.
|
||||||
|
pub editing: Option<Arc<RwLock<Sample>>>,
|
||||||
|
/// MIDI input port. Triggers sample playback.
|
||||||
|
#[cfg(feature = "port")] pub midi_in: Option<MidiInput>,
|
||||||
|
/// Collection of currently playing instances of samples.
|
||||||
|
pub voices: Arc<RwLock<Vec<Voice>>>,
|
||||||
|
/// Audio output ports. Voices get played here.
|
||||||
|
#[cfg(feature = "port")] pub audio_outs: Vec<AudioOutput>,
|
||||||
|
/// Audio output meters.
|
||||||
|
#[cfg(feature = "meter")] pub output_meters: Vec<f32>,
|
||||||
|
/// How to mix the voices.
|
||||||
|
pub mixing_mode: MixingMode,
|
||||||
|
/// How to meter the inputs and outputs.
|
||||||
|
pub metering_mode: MeteringMode,
|
||||||
|
/// Fixed gain applied to all output.
|
||||||
|
pub output_gain: f32,
|
||||||
|
/// Currently active modal, if any.
|
||||||
|
pub mode: Option<SamplerMode>,
|
||||||
|
/// Size of rendered sampler.
|
||||||
|
pub size: Measure<TuiOut>,
|
||||||
|
/// Lowest note displayed.
|
||||||
|
pub note_lo: AtomicUsize,
|
||||||
|
/// Currently selected note.
|
||||||
|
pub note_pt: AtomicUsize,
|
||||||
|
/// Selected note as row/col.
|
||||||
|
pub cursor: (AtomicUsize, AtomicUsize),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collection of samples, one per slot, fixed number of slots.
|
||||||
|
///
|
||||||
|
/// TODO: Map more than one sample per slot.
|
||||||
|
///
|
||||||
|
/// History: Separated to cleanly implement [Default].
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let samples = tek::Samples([None, None, None, None]);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug)] pub struct Samples<const N: usize>(
|
||||||
|
pub [Option<Arc<RwLock<Sample>>>;N]
|
||||||
|
);
|
||||||
|
|
||||||
|
/// A sound cut.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let sample = tek::Sample::default();
|
||||||
|
/// let sample = tek::Sample::new("test", 0, 0, vec![]);
|
||||||
|
/// ```
|
||||||
|
#[derive(Default, Debug)] pub struct Sample {
|
||||||
|
pub name: Arc<str>,
|
||||||
|
pub start: usize,
|
||||||
|
pub end: usize,
|
||||||
|
pub channels: Vec<Vec<f32>>,
|
||||||
|
pub rate: Option<usize>,
|
||||||
|
pub gain: f32,
|
||||||
|
pub color: ItemTheme,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A currently playing instance of a sample.
|
||||||
|
#[derive(Default, Debug, Clone)] pub struct Voice {
|
||||||
|
pub sample: Arc<RwLock<Sample>>,
|
||||||
|
pub after: usize,
|
||||||
|
pub position: usize,
|
||||||
|
pub velocity: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AddSampleModal {
|
||||||
|
pub exited: bool,
|
||||||
|
pub dir: PathBuf,
|
||||||
|
pub subdirs: Vec<OsString>,
|
||||||
|
pub files: Vec<OsString>,
|
||||||
|
pub cursor: usize,
|
||||||
|
pub offset: usize,
|
||||||
|
pub sample: Arc<RwLock<Sample>>,
|
||||||
|
pub voices: Arc<RwLock<Vec<Voice>>>,
|
||||||
|
pub _search: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A scene consists of a set of clips to play together.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let scene: tek::Scene = Default::default();
|
||||||
|
/// let _ = scene.pulses();
|
||||||
|
/// let _ = scene.is_playing(&[]);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct Scene {
|
||||||
|
/// Name of scene
|
||||||
|
pub name: Arc<str>,
|
||||||
|
/// Identifying color of scene
|
||||||
|
pub color: ItemTheme,
|
||||||
|
/// Clips in scene, one per track
|
||||||
|
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the current user selection in the arranger
|
||||||
|
#[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection {
|
||||||
|
#[default]
|
||||||
|
/// Nothing is selected
|
||||||
|
Nothing,
|
||||||
|
/// The whole mix is selected
|
||||||
|
Mix,
|
||||||
|
/// A MIDI input is selected.
|
||||||
|
Input(usize),
|
||||||
|
/// A MIDI output is selected.
|
||||||
|
Output(usize),
|
||||||
|
/// A scene is selected.
|
||||||
|
#[cfg(feature = "scene")] Scene(usize),
|
||||||
|
/// A track is selected.
|
||||||
|
#[cfg(feature = "track")] Track(usize),
|
||||||
|
/// A clip (track × scene) is selected.
|
||||||
|
#[cfg(feature = "track")] TrackClip { track: usize, scene: usize },
|
||||||
|
/// A track's MIDI input connection is selected.
|
||||||
|
#[cfg(feature = "track")] TrackInput { track: usize, port: usize },
|
||||||
|
/// A track's MIDI output connection is selected.
|
||||||
|
#[cfg(feature = "track")] TrackOutput { track: usize, port: usize },
|
||||||
|
/// A track device slot is selected.
|
||||||
|
#[cfg(feature = "track")] TrackDevice { track: usize, device: usize },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contains state for playing a clip
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let clip = tek::MidiClip::default();
|
||||||
|
/// println!("Empty clip: {clip:?}");
|
||||||
|
///
|
||||||
|
/// let clip = tek::MidiClip::stop_all();
|
||||||
|
/// println!("Panic clip: {clip:?}");
|
||||||
|
///
|
||||||
|
/// let mut clip = tek::MidiClip::new("clip", true, 1, None, None);
|
||||||
|
/// clip.set_length(96);
|
||||||
|
/// clip.toggle_loop();
|
||||||
|
/// clip.record_event(12, midly::MidiMessage::NoteOn { key: 36.into(), vel: 100.into() });
|
||||||
|
/// assert!(clip.contains_note_on(36.into(), 6, 18));
|
||||||
|
/// assert_eq!(&clip.notes, &clip.duplicate().notes);
|
||||||
|
///
|
||||||
|
/// let clip = std::sync::Arc::new(clip);
|
||||||
|
/// assert_eq!(clip.clone(), clip);
|
||||||
|
///
|
||||||
|
/// let sequencer = tek::Sequencer::default();
|
||||||
|
/// println!("{sequencer:?}");
|
||||||
|
/// ```
|
||||||
|
pub struct Sequencer {
|
||||||
|
/// State of clock and playhead
|
||||||
|
#[cfg(feature = "clock")] pub clock: Clock,
|
||||||
|
/// Start time and clip being played
|
||||||
|
#[cfg(feature = "clip")] pub play_clip: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
||||||
|
/// Start time and next clip
|
||||||
|
#[cfg(feature = "clip")] pub next_clip: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
||||||
|
/// Record from MIDI ports to current sequence.
|
||||||
|
#[cfg(feature = "port")] pub midi_ins: Vec<MidiInput>,
|
||||||
|
/// Play from current sequence to MIDI ports
|
||||||
|
#[cfg(feature = "port")] pub midi_outs: Vec<MidiOutput>,
|
||||||
|
/// 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)
|
||||||
|
/// Notes currently held at input
|
||||||
|
pub notes_in: Arc<RwLock<[bool; 128]>>,
|
||||||
|
/// Notes currently held at output
|
||||||
|
pub notes_out: Arc<RwLock<[bool; 128]>>,
|
||||||
|
/// MIDI output buffer
|
||||||
|
pub note_buf: Vec<u8>,
|
||||||
|
/// MIDI output buffer
|
||||||
|
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A track consists of a sequencer and zero or more devices chained after it.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let track: tek::Track = Default::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default)] pub struct Track {
|
||||||
|
/// Name of track
|
||||||
|
pub name: Arc<str>,
|
||||||
|
/// Identifying color of track
|
||||||
|
pub color: ItemTheme,
|
||||||
|
/// Preferred width of track column
|
||||||
|
pub width: usize,
|
||||||
|
/// MIDI sequencer state
|
||||||
|
pub sequencer: Sequencer,
|
||||||
|
/// Device chain
|
||||||
|
pub devices: Vec<Device>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commands supported by [Browse]
|
||||||
|
//#[derive(Debug, Clone, PartialEq)]
|
||||||
|
//pub enum BrowseCommand {
|
||||||
|
//Begin,
|
||||||
|
//Cancel,
|
||||||
|
//Confirm,
|
||||||
|
//Select(usize),
|
||||||
|
//Chdir(PathBuf),
|
||||||
|
//Filter(Arc<str>),
|
||||||
|
//}
|
||||||
|
|
||||||
|
/// Modes for clip pool
|
||||||
|
#[derive(Debug, Clone)] pub enum PoolMode {
|
||||||
|
/// Renaming a pattern
|
||||||
|
Rename(usize, Arc<str>),
|
||||||
|
/// Editing the length of a pattern
|
||||||
|
Length(usize, usize, ClipLengthFocus),
|
||||||
|
/// Load clip from disk
|
||||||
|
Import(usize, Browse),
|
||||||
|
/// Save clip to disk
|
||||||
|
Export(usize, Browse),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Focused field of `ClipLength`
|
||||||
|
#[derive(Copy, Clone, Debug)] pub enum ClipLengthFocus {
|
||||||
|
/// Editing the number of bars
|
||||||
|
Bar,
|
||||||
|
/// Editing the number of beats
|
||||||
|
Beat,
|
||||||
|
/// Editing the number of ticks
|
||||||
|
Tick,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)] pub enum ConnectName {
|
||||||
|
/** Exact match */
|
||||||
|
Exact(Arc<str>),
|
||||||
|
/** Match regular expression */
|
||||||
|
RegExp(Arc<str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectScope {
|
||||||
|
One,
|
||||||
|
All
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectStatus {
|
||||||
|
Missing,
|
||||||
|
Disconnected,
|
||||||
|
Connected,
|
||||||
|
Mismatch,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)] pub enum SamplerMode {
|
||||||
|
// Load sample from path
|
||||||
|
Import(usize, Browse),
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,246 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
|
/// Things that can provide a [jack::Client] reference.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tek::{Jack, HasJack};
|
||||||
|
///
|
||||||
|
/// let jack: &Jack = Jacked::default().jack();
|
||||||
|
///
|
||||||
|
/// #[derive(Default)] struct Jacked<'j>(Jack<'j>);
|
||||||
|
///
|
||||||
|
/// impl<'j> tek::HasJack<'j> for Jacked<'j> {
|
||||||
|
/// fn jack (&self) -> &Jack<'j> { &self.0 }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub trait HasJack<'j>: Send + Sync {
|
||||||
|
|
||||||
|
/// Return the internal [jack::Client] handle
|
||||||
|
/// that lets you call the JACK API.
|
||||||
|
fn jack (&self) -> &Jack<'j>;
|
||||||
|
|
||||||
|
fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
|
||||||
|
self.jack().with_client(op)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
|
||||||
|
self.with_client(|client|client.port_by_name(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn port_by_id (&self, id: u32) -> Option<Port<Unowned>> {
|
||||||
|
self.with_client(|c|c.port_by_id(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_port <PS: PortSpec + Default> (&self, name: impl AsRef<str>) -> Usually<Port<PS>> {
|
||||||
|
self.with_client(|client|Ok(client.register_port(name.as_ref(), PS::default())?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_lead (&self, enable: bool, callback: impl Fn(TimebaseInfo)->Position) -> Usually<()> {
|
||||||
|
if enable {
|
||||||
|
self.with_client(|client|match client.register_timebase_callback(false, callback) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(e)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_follow (&self, _enable: bool) -> Usually<()> {
|
||||||
|
// TODO: sync follow
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for thing that has a JACK process callback.
|
||||||
|
pub trait Audio {
|
||||||
|
|
||||||
|
/// Handle a JACK event.
|
||||||
|
fn handle (&mut self, _event: JackEvent) {}
|
||||||
|
|
||||||
|
/// Projecss a JACK chunk.
|
||||||
|
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The JACK process callback function passed to the server.
|
||||||
|
fn callback (
|
||||||
|
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
|
||||||
|
) -> Control where Self: Sized {
|
||||||
|
if let Ok(mut state) = state.write() {
|
||||||
|
state.process(client, scope)
|
||||||
|
} else {
|
||||||
|
Control::Quit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement [Audio]: provide JACK callbacks.
|
||||||
|
#[macro_export] macro_rules! audio {
|
||||||
|
|
||||||
|
(|
|
||||||
|
$self1:ident:
|
||||||
|
$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident
|
||||||
|
|$cb:expr$(;|$self2:ident,$e:ident|$cb2:expr)?) => {
|
||||||
|
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
|
||||||
|
#[inline] fn process (&mut $self1, $c: &Client, $s: &ProcessScope) -> Control { $cb }
|
||||||
|
$(#[inline] fn handle (&mut $self2, $e: JackEvent) { $cb2 })?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
($Struct:ident: $process:ident, $handle:ident) => {
|
||||||
|
impl Audio for $Struct {
|
||||||
|
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
|
||||||
|
$process(self, c, s)
|
||||||
|
}
|
||||||
|
#[inline] fn handle (&mut self, e: JackEvent) {
|
||||||
|
$handle(self, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
($Struct:ident: $process:ident) => {
|
||||||
|
impl Audio for $Struct {
|
||||||
|
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
|
||||||
|
$process(self, c, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait JackPerfModel {
|
||||||
|
fn update_from_jack_scope (&self, t0: Option<u64>, scope: &ProcessScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//pub trait MaybeHas<T>: Send + Sync {
|
||||||
|
//fn get (&self) -> Option<&T>;
|
||||||
|
//}
|
||||||
|
|
||||||
|
pub trait HasN<T>: Send + Sync {
|
||||||
|
fn get_nth (&self, key: usize) -> &T;
|
||||||
|
fn get_nth_mut (&mut self, key: usize) -> &mut T;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Gettable<T> {
|
||||||
|
/// Returns current value
|
||||||
|
fn get (&self) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Mutable<T>: Gettable<T> {
|
||||||
|
/// Sets new value, returns old
|
||||||
|
fn set (&mut self, value: T) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait InteriorMutable<T>: Gettable<T> {
|
||||||
|
/// Sets new value, returns old
|
||||||
|
fn set (&self, value: T) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait NotePoint {
|
||||||
|
fn note_len (&self) -> &AtomicUsize;
|
||||||
|
/// Get the current length of the note cursor.
|
||||||
|
fn get_note_len (&self) -> usize {
|
||||||
|
self.note_len().load(Relaxed)
|
||||||
|
}
|
||||||
|
/// Set the length of the note cursor, returning the previous value.
|
||||||
|
fn set_note_len (&self, x: usize) -> usize {
|
||||||
|
self.note_len().swap(x, Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn note_pos (&self) -> &AtomicUsize;
|
||||||
|
/// Get the current pitch of the note cursor.
|
||||||
|
fn get_note_pos (&self) -> usize {
|
||||||
|
self.note_pos().load(Relaxed).min(127)
|
||||||
|
}
|
||||||
|
/// Set the current pitch fo the note cursor, returning the previous value.
|
||||||
|
fn set_note_pos (&self, x: usize) -> usize {
|
||||||
|
self.note_pos().swap(x.min(127), Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TimePoint {
|
||||||
|
fn time_pos (&self) -> &AtomicUsize;
|
||||||
|
/// Get the current time position of the note cursor.
|
||||||
|
fn get_time_pos (&self) -> usize {
|
||||||
|
self.time_pos().load(Relaxed)
|
||||||
|
}
|
||||||
|
/// Set the current time position of the note cursor, returning the previous value.
|
||||||
|
fn set_time_pos (&self, x: usize) -> usize {
|
||||||
|
self.time_pos().swap(x, Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MidiPoint: NotePoint + TimePoint {
|
||||||
|
/// Get the current end of the note cursor.
|
||||||
|
fn get_note_end (&self) -> usize {
|
||||||
|
self.get_time_pos() + self.get_note_len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TimeRange {
|
||||||
|
fn time_len (&self) -> &AtomicUsize;
|
||||||
|
fn get_time_len (&self) -> usize {
|
||||||
|
self.time_len().load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
fn time_zoom (&self) -> &AtomicUsize;
|
||||||
|
fn get_time_zoom (&self) -> usize {
|
||||||
|
self.time_zoom().load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
fn set_time_zoom (&self, value: usize) -> usize {
|
||||||
|
self.time_zoom().swap(value, Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
fn time_lock (&self) -> &AtomicBool;
|
||||||
|
fn get_time_lock (&self) -> bool {
|
||||||
|
self.time_lock().load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
fn set_time_lock (&self, value: bool) -> bool {
|
||||||
|
self.time_lock().swap(value, Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
fn time_start (&self) -> &AtomicUsize;
|
||||||
|
fn get_time_start (&self) -> usize {
|
||||||
|
self.time_start().load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
fn set_time_start (&self, value: usize) -> usize {
|
||||||
|
self.time_start().swap(value, Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
fn time_axis (&self) -> &AtomicUsize;
|
||||||
|
fn get_time_axis (&self) -> usize {
|
||||||
|
self.time_axis().load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
fn get_time_end (&self) -> usize {
|
||||||
|
self.time_start().get() + self.time_axis().get() * self.time_zoom().get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait NoteRange {
|
||||||
|
fn note_lo (&self) -> &AtomicUsize;
|
||||||
|
fn get_note_lo (&self) -> usize {
|
||||||
|
self.note_lo().load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
fn set_note_lo (&self, x: usize) -> usize {
|
||||||
|
self.note_lo().swap(x, Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
fn note_axis (&self) -> &AtomicUsize;
|
||||||
|
fn get_note_axis (&self) -> usize {
|
||||||
|
self.note_axis().load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
fn get_note_hi (&self) -> usize {
|
||||||
|
(self.note_lo().get() + self.note_axis().get().saturating_sub(1)).min(127)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MidiRange: TimeRange + NoteRange {}
|
||||||
|
|
||||||
|
/// A unit of time, represented as an atomic 64-bit float.
|
||||||
|
///
|
||||||
|
/// According to https://stackoverflow.com/a/873367, as per IEEE754,
|
||||||
|
/// every integer between 1 and 2^53 can be represented exactly.
|
||||||
|
/// This should mean that, even at 192kHz sampling rate, over 1 year of audio
|
||||||
|
/// can be clocked in microseconds with f64 without losing precision.
|
||||||
|
pub trait TimeUnit: InteriorMutable<f64> {}
|
||||||
|
|
||||||
pub trait HasSceneScroll: HasScenes {
|
pub trait HasSceneScroll: HasScenes {
|
||||||
fn scene_scroll (&self) -> usize;
|
fn scene_scroll (&self) -> usize;
|
||||||
|
|
@ -75,8 +317,9 @@ pub trait HasScenes: Has<Vec<Scene>> + Send + Sync {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ```
|
/// ```
|
||||||
|
/// use tek::{MidiEditor, HasEditor, tengri::Has};
|
||||||
/// struct TestEditorHost(Option<MidiEditor>);
|
/// struct TestEditorHost(Option<MidiEditor>);
|
||||||
/// has!(Option<MidiEditor>: |self: TestEditorHost|self.0);
|
/// tek::tengri::has!(Option<MidiEditor>: |self: TestEditorHost|self.0);
|
||||||
/// let mut host = TestEditorHost(Some(MidiEditor::default()));
|
/// let mut host = TestEditorHost(Some(MidiEditor::default()));
|
||||||
/// let _ = host.editor();
|
/// let _ = host.editor();
|
||||||
/// let _ = host.editor_mut();
|
/// let _ = host.editor_mut();
|
||||||
|
|
@ -715,12 +958,15 @@ pub trait JackPort: HasJack<'static> {
|
||||||
}
|
}
|
||||||
fn connect_to_matching <'k> (&'k self) -> Usually<()> {
|
fn connect_to_matching <'k> (&'k self) -> Usually<()> {
|
||||||
for connect in self.connections().iter() {
|
for connect in self.connections().iter() {
|
||||||
//panic!("{connect:?}");
|
match &connect.name {
|
||||||
let status = match &connect.name {
|
Some(Exact(name)) => {
|
||||||
Exact(name) => self.connect_exact(name),
|
*connect.status.write().unwrap() = self.connect_exact(name)?;
|
||||||
RegExp(re) => self.connect_regexp(re, connect.scope),
|
},
|
||||||
}?;
|
Some(RegExp(re)) => {
|
||||||
*connect.status.write().unwrap() = status;
|
*connect.status.write().unwrap() = self.connect_regexp(re, connect.scope)?;
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -745,7 +991,7 @@ pub trait JackPort: HasJack<'static> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn connect_regexp <'k> (
|
fn connect_regexp <'k> (
|
||||||
&'k self, re: &str, scope: ConnectScope
|
&'k self, re: &str, scope: Option<ConnectScope>
|
||||||
) -> Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>> {
|
) -> Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>> {
|
||||||
self.with_client(move|c|{
|
self.with_client(move|c|{
|
||||||
let mut status = vec![];
|
let mut status = vec![];
|
||||||
|
|
@ -755,7 +1001,7 @@ pub trait JackPort: HasJack<'static> {
|
||||||
let port_status = self.connect_to_unowned(&port)?;
|
let port_status = self.connect_to_unowned(&port)?;
|
||||||
let name = port.name()?.into();
|
let name = port.name()?.into();
|
||||||
status.push((port, name, port_status));
|
status.push((port, name, port_status));
|
||||||
if port_status == Connected && scope == One {
|
if port_status == Connected && scope == Some(One) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -25,3 +25,27 @@ pub type DynamicNotifications<'j> =
|
||||||
/// Boxed [JackEvent] callback.
|
/// Boxed [JackEvent] callback.
|
||||||
pub type BoxedJackEventHandler<'j> =
|
pub type BoxedJackEventHandler<'j> =
|
||||||
Box<dyn Fn(JackEvent) + Send + Sync + 'j>;
|
Box<dyn Fn(JackEvent) + Send + Sync + 'j>;
|
||||||
|
|
||||||
|
pub type MidiData =
|
||||||
|
Vec<Vec<MidiMessage>>;
|
||||||
|
|
||||||
|
pub type ClipPool =
|
||||||
|
Vec<Arc<RwLock<MidiClip>>>;
|
||||||
|
|
||||||
|
pub type CollectedMidiInput<'a> =
|
||||||
|
Vec<Vec<(u32, Result<LiveEvent<'a>, MidiError>)>>;
|
||||||
|
|
||||||
|
pub type SceneWith<'a, T> =
|
||||||
|
(usize, &'a Scene, usize, usize, T);
|
||||||
|
|
||||||
|
pub type MidiSample =
|
||||||
|
(Option<u7>, Arc<RwLock<crate::Sample>>);
|
||||||
|
|
||||||
|
/// Collection of interaction modes.
|
||||||
|
pub type Modes = Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode<Arc<str>>>>>>;
|
||||||
|
|
||||||
|
/// Collection of input bindings.
|
||||||
|
pub type Binds = Arc<RwLock<BTreeMap<Arc<str>, Bind<TuiEvent, Arc<str>>>>>;
|
||||||
|
|
||||||
|
/// Collection of view definitions.
|
||||||
|
pub type Views = Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>;
|
||||||
|
|
@ -1,614 +0,0 @@
|
||||||
//fn begin (browse: &mut Browse) => {
|
|
||||||
//unreachable!();
|
|
||||||
//}
|
|
||||||
//fn cancel (browse: &mut Browse) => {
|
|
||||||
//todo!()
|
|
||||||
////browse.mode = None;
|
|
||||||
////Ok(None)
|
|
||||||
//}
|
|
||||||
//fn confirm (browse: &mut Browse) => {
|
|
||||||
//todo!()
|
|
||||||
////Ok(match browse.mode {
|
|
||||||
////Some(PoolMode::Import(index, ref mut browse)) => {
|
|
||||||
////if browse.is_file() {
|
|
||||||
////let path = browse.path();
|
|
||||||
////browse.mode = None;
|
|
||||||
////let _undo = PoolClipCommand::import(browse, index, path)?;
|
|
||||||
////None
|
|
||||||
////} else if browse.is_dir() {
|
|
||||||
////browse.mode = Some(PoolMode::Import(index, browse.chdir()?));
|
|
||||||
////None
|
|
||||||
////} else {
|
|
||||||
////None
|
|
||||||
////}
|
|
||||||
////},
|
|
||||||
////Some(PoolMode::Export(index, ref mut browse)) => {
|
|
||||||
////todo!()
|
|
||||||
////},
|
|
||||||
////_ => unreachable!(),
|
|
||||||
////})
|
|
||||||
//}
|
|
||||||
//fn select (browse: &mut Browse, index: usize) => {
|
|
||||||
//todo!()
|
|
||||||
////Ok(match browse.mode {
|
|
||||||
////Some(PoolMode::Import(index, ref mut browse)) => {
|
|
||||||
////browse.index = index;
|
|
||||||
////None
|
|
||||||
////},
|
|
||||||
////Some(PoolMode::Export(index, ref mut browse)) => {
|
|
||||||
////browse.index = index;
|
|
||||||
////None
|
|
||||||
////},
|
|
||||||
////_ => unreachable!(),
|
|
||||||
////})
|
|
||||||
//}
|
|
||||||
//fn chdir (browse: &mut Browse, dir: PathBuf) => {
|
|
||||||
//todo!()
|
|
||||||
////Ok(match browse.mode {
|
|
||||||
////Some(PoolMode::Import(index, ref mut browse)) => {
|
|
||||||
////browse.mode = Some(PoolMode::Import(index, Browse::new(Some(dir))?));
|
|
||||||
////None
|
|
||||||
////},
|
|
||||||
////Some(PoolMode::Export(index, ref mut browse)) => {
|
|
||||||
////browse.mode = Some(PoolMode::Export(index, Browse::new(Some(dir))?));
|
|
||||||
////None
|
|
||||||
////},
|
|
||||||
////_ => unreachable!(),
|
|
||||||
////})
|
|
||||||
//}
|
|
||||||
//fn filter (browse: &mut Browse, filter: Arc<str>) => {
|
|
||||||
//todo!()
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
//def_command!(ArrangementCommand: |arranger: Arrangement| {
|
|
||||||
|
|
||||||
//Home => { arranger.editor = None; Ok(None) },
|
|
||||||
|
|
||||||
//Edit => {
|
|
||||||
//let selection = arranger.selection().clone();
|
|
||||||
//arranger.editor = if arranger.editor.is_some() {
|
|
||||||
//None
|
|
||||||
//} else {
|
|
||||||
//match selection {
|
|
||||||
//Selection::TrackClip { track, scene } => {
|
|
||||||
//let clip = &mut arranger.scenes_mut()[scene].clips[track];
|
|
||||||
//if clip.is_none() {
|
|
||||||
////app.clip_auto_create();
|
|
||||||
//*clip = Some(Arc::new(RwLock::new(MidiClip::new(
|
|
||||||
//&format!("t{track:02}s{scene:02}"),
|
|
||||||
//false, 384, None, Some(ItemTheme::random())
|
|
||||||
//))));
|
|
||||||
//}
|
|
||||||
//clip.as_ref().map(|c|c.into())
|
|
||||||
//}
|
|
||||||
//_ => {
|
|
||||||
//None
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//};
|
|
||||||
//if let Some(editor) = arranger.editor.as_mut() {
|
|
||||||
//if let Some(clip) = editor.clip() {
|
|
||||||
//let length = clip.read().unwrap().length.max(1);
|
|
||||||
//let width = arranger.size_inner.w().saturating_sub(20).max(1);
|
|
||||||
//editor.set_time_zoom(length / width);
|
|
||||||
//editor.redraw();
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//Ok(None)
|
|
||||||
//},
|
|
||||||
|
|
||||||
////// Set the selection
|
|
||||||
//Select { selection: Selection } => { *arranger.selection_mut() = *selection; Ok(None) },
|
|
||||||
|
|
||||||
////// Launch the selected clip or scene
|
|
||||||
//Launch => {
|
|
||||||
//match *arranger.selection() {
|
|
||||||
//Selection::Track(t) => {
|
|
||||||
//arranger.tracks[t].sequencer.enqueue_next(None)
|
|
||||||
//},
|
|
||||||
//Selection::TrackClip { track, scene } => {
|
|
||||||
//arranger.tracks[track].sequencer.enqueue_next(arranger.scenes[scene].clips[track].as_ref())
|
|
||||||
//},
|
|
||||||
//Selection::Scene(s) => {
|
|
||||||
//for t in 0..arranger.tracks.len() {
|
|
||||||
//arranger.tracks[t].sequencer.enqueue_next(arranger.scenes[s].clips[t].as_ref())
|
|
||||||
//}
|
|
||||||
//},
|
|
||||||
//_ => {}
|
|
||||||
//};
|
|
||||||
//Ok(None)
|
|
||||||
//},
|
|
||||||
|
|
||||||
////// Set the color of the selected entity
|
|
||||||
//SetColor { palette: Option<ItemTheme> } => {
|
|
||||||
//let mut palette = palette.unwrap_or_else(||ItemTheme::random());
|
|
||||||
//let selection = *arranger.selection();
|
|
||||||
//Ok(Some(Self::SetColor { palette: Some(match selection {
|
|
||||||
//Selection::Mix => {
|
|
||||||
//std::mem::swap(&mut palette, &mut arranger.color);
|
|
||||||
//palette
|
|
||||||
//},
|
|
||||||
//Selection::Scene(s) => {
|
|
||||||
//std::mem::swap(&mut palette, &mut arranger.scenes[s].color);
|
|
||||||
//palette
|
|
||||||
//}
|
|
||||||
//Selection::Track(t) => {
|
|
||||||
//std::mem::swap(&mut palette, &mut arranger.tracks[t].color);
|
|
||||||
//palette
|
|
||||||
//}
|
|
||||||
//Selection::TrackClip { track, scene } => {
|
|
||||||
//if let Some(ref clip) = arranger.scenes[scene].clips[track] {
|
|
||||||
//let mut clip = clip.write().unwrap();
|
|
||||||
//std::mem::swap(&mut palette, &mut clip.color);
|
|
||||||
//palette
|
|
||||||
//} else {
|
|
||||||
//return Ok(None)
|
|
||||||
//}
|
|
||||||
//},
|
|
||||||
//_ => todo!()
|
|
||||||
//}) }))
|
|
||||||
//},
|
|
||||||
|
|
||||||
//Track { track: TrackCommand } => { todo!("delegate") },
|
|
||||||
|
|
||||||
//TrackAdd => {
|
|
||||||
//let index = arranger.track_add(None, None, &[], &[])?.0;
|
|
||||||
//*arranger.selection_mut() = match arranger.selection() {
|
|
||||||
//Selection::Track(_) => Selection::Track(index),
|
|
||||||
//Selection::TrackClip { track: _, scene } => Selection::TrackClip {
|
|
||||||
//track: index, scene: *scene
|
|
||||||
//},
|
|
||||||
//_ => *arranger.selection()
|
|
||||||
//};
|
|
||||||
//Ok(Some(Self::TrackDelete { index }))
|
|
||||||
//},
|
|
||||||
|
|
||||||
//TrackSwap { index: usize, other: usize } => {
|
|
||||||
//let index = *index;
|
|
||||||
//let other = *other;
|
|
||||||
//Ok(Some(Self::TrackSwap { index, other }))
|
|
||||||
//},
|
|
||||||
|
|
||||||
//TrackDelete { index: usize } => {
|
|
||||||
//let index = *index;
|
|
||||||
//let exists = arranger.tracks().get(index).is_some();
|
|
||||||
//if exists {
|
|
||||||
//let track = arranger.tracks_mut().remove(index);
|
|
||||||
//let Track { sequencer: Sequencer { midi_ins, midi_outs, .. }, .. } = track;
|
|
||||||
//for port in midi_ins.into_iter() {
|
|
||||||
//port.close()?;
|
|
||||||
//}
|
|
||||||
//for port in midi_outs.into_iter() {
|
|
||||||
//port.close()?;
|
|
||||||
//}
|
|
||||||
//for scene in arranger.scenes_mut().iter_mut() {
|
|
||||||
//scene.clips.remove(index);
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//Ok(None)
|
|
||||||
////TODO:Ok(Some(Self::TrackAdd ( index, track: Some(deleted_track) })
|
|
||||||
//},
|
|
||||||
|
|
||||||
//MidiIn { input: MidiInputCommand } => {
|
|
||||||
//todo!("delegate"); Ok(None)
|
|
||||||
//},
|
|
||||||
|
|
||||||
//MidiInAdd => {
|
|
||||||
//arranger.midi_in_add()?;
|
|
||||||
//Ok(None)
|
|
||||||
//},
|
|
||||||
|
|
||||||
//MidiOut { output: MidiOutputCommand } => {
|
|
||||||
//todo!("delegate");
|
|
||||||
//Ok(None)
|
|
||||||
//},
|
|
||||||
|
|
||||||
//MidiOutAdd => {
|
|
||||||
//arranger.midi_out_add()?;
|
|
||||||
//Ok(None)
|
|
||||||
//},
|
|
||||||
|
|
||||||
//Device { command: DeviceCommand } => {
|
|
||||||
//todo!("delegate");
|
|
||||||
//Ok(None)
|
|
||||||
//},
|
|
||||||
|
|
||||||
//DeviceAdd { index: usize } => {
|
|
||||||
//todo!("delegate");
|
|
||||||
//Ok(None)
|
|
||||||
//},
|
|
||||||
|
|
||||||
//Scene { scene: SceneCommand } => {
|
|
||||||
//todo!("delegate");
|
|
||||||
//Ok(None)
|
|
||||||
//},
|
|
||||||
|
|
||||||
//OutputAdd => {
|
|
||||||
//arranger.midi_outs.push(MidiOutput::new(
|
|
||||||
//arranger.jack(),
|
|
||||||
//&format!("/M{}", arranger.midi_outs.len() + 1),
|
|
||||||
//&[]
|
|
||||||
//)?);
|
|
||||||
//Ok(None)
|
|
||||||
//},
|
|
||||||
|
|
||||||
//InputAdd => {
|
|
||||||
//arranger.midi_ins.push(MidiInput::new(
|
|
||||||
//arranger.jack(),
|
|
||||||
//&format!("M{}/", arranger.midi_ins.len() + 1),
|
|
||||||
//&[]
|
|
||||||
//)?);
|
|
||||||
//Ok(None)
|
|
||||||
//},
|
|
||||||
|
|
||||||
//SceneAdd => {
|
|
||||||
//let index = arranger.scene_add(None, None)?.0;
|
|
||||||
//*arranger.selection_mut() = match arranger.selection() {
|
|
||||||
//Selection::Scene(_) => Selection::Scene(index),
|
|
||||||
//Selection::TrackClip { track, scene } => Selection::TrackClip {
|
|
||||||
//track: *track,
|
|
||||||
//scene: index
|
|
||||||
//},
|
|
||||||
//_ => *arranger.selection()
|
|
||||||
//};
|
|
||||||
//Ok(None) // TODO
|
|
||||||
//},
|
|
||||||
|
|
||||||
//SceneSwap { index: usize, other: usize } => {
|
|
||||||
//let index = *index;
|
|
||||||
//let other = *other;
|
|
||||||
//Ok(Some(Self::SceneSwap { index, other }))
|
|
||||||
//},
|
|
||||||
|
|
||||||
//SceneDelete { index: usize } => {
|
|
||||||
//let index = *index;
|
|
||||||
//let scenes = arranger.scenes_mut();
|
|
||||||
//Ok(if scenes.get(index).is_some() {
|
|
||||||
//let _scene = scenes.remove(index);
|
|
||||||
//None
|
|
||||||
//} else {
|
|
||||||
//None
|
|
||||||
//})
|
|
||||||
//},
|
|
||||||
|
|
||||||
//SceneLaunch { index: usize } => {
|
|
||||||
//let index = *index;
|
|
||||||
//for track in 0..arranger.tracks.len() {
|
|
||||||
//let clip = arranger.scenes[index].clips[track].as_ref();
|
|
||||||
//arranger.tracks[track].sequencer.enqueue_next(clip);
|
|
||||||
//}
|
|
||||||
//Ok(None)
|
|
||||||
//},
|
|
||||||
|
|
||||||
//Clip { scene: ClipCommand } => {
|
|
||||||
//todo!("delegate")
|
|
||||||
//},
|
|
||||||
|
|
||||||
//ClipGet { a: usize, b: usize } => {
|
|
||||||
////(Get [a: usize, b: usize] cmd_todo!("\n\rtodo: clip: get: {a} {b}"))
|
|
||||||
////("get" [a: usize, b: usize] Some(Self::Get(a.unwrap(), b.unwrap())))
|
|
||||||
//todo!()
|
|
||||||
//},
|
|
||||||
|
|
||||||
//ClipPut { a: usize, b: usize } => {
|
|
||||||
////(Put [t: usize, s: usize, c: MaybeClip]
|
|
||||||
////Some(Self::Put(t, s, arranger.clip_put(t, s, c))))
|
|
||||||
////("put" [a: usize, b: usize, c: MaybeClip] Some(Self::Put(a.unwrap(), b.unwrap(), c.unwrap())))
|
|
||||||
//todo!()
|
|
||||||
//},
|
|
||||||
|
|
||||||
//ClipDel { a: usize, b: usize } => {
|
|
||||||
////("delete" [a: usize, b: usize] Some(Self::Put(a.unwrap(), b.unwrap(), None))))
|
|
||||||
//todo!()
|
|
||||||
//},
|
|
||||||
|
|
||||||
//ClipEnqueue { a: usize, b: usize } => {
|
|
||||||
////(Enqueue [t: usize, s: usize]
|
|
||||||
////cmd!(arranger.tracks[t].sequencer.enqueue_next(arranger.scenes[s].clips[t].as_ref())))
|
|
||||||
////("enqueue" [a: usize, b: usize] Some(Self::Enqueue(a.unwrap(), b.unwrap())))
|
|
||||||
//todo!()
|
|
||||||
//},
|
|
||||||
|
|
||||||
//ClipSwap { a: usize, b: usize }=> {
|
|
||||||
////(Edit [clip: MaybeClip] cmd_todo!("\n\rtodo: clip: edit: {clip:?}"))
|
|
||||||
////("edit" [a: MaybeClip] Some(Self::Edit(a.unwrap())))
|
|
||||||
//todo!()
|
|
||||||
//},
|
|
||||||
|
|
||||||
//});
|
|
||||||
// Update sequencer playhead indicator
|
|
||||||
//self.now().set(0.);
|
|
||||||
//if let Some((ref started_at, Some(ref playing))) = self.sequencer.play_clip {
|
|
||||||
//let clip = clip.read().unwrap();
|
|
||||||
//if *playing.read().unwrap() == *clip {
|
|
||||||
//let pulse = self.current().pulse.get();
|
|
||||||
//let start = started_at.pulse.get();
|
|
||||||
//let now = (pulse - start) % clip.length as f64;
|
|
||||||
//self.now().set(now);
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//fn jack_from_lv2 (name: &str, plugin: &::livi::Plugin) -> Usually<Jack> {
|
|
||||||
//let counts = plugin.port_counts();
|
|
||||||
//let mut jack = Jack::new(name)?;
|
|
||||||
//for i in 0..counts.atom_sequence_inputs {
|
|
||||||
//jack = jack.midi_in(&format!("midi-in-{i}"))
|
|
||||||
//}
|
|
||||||
//for i in 0..counts.atom_sequence_outputs {
|
|
||||||
//jack = jack.midi_out(&format!("midi-out-{i}"));
|
|
||||||
//}
|
|
||||||
//for i in 0..counts.audio_inputs {
|
|
||||||
//jack = jack.audio_in(&format!("audio-in-{i}"));
|
|
||||||
//}
|
|
||||||
//for i in 0..counts.audio_outputs {
|
|
||||||
//jack = jack.audio_out(&format!("audio-out-{i}"));
|
|
||||||
//}
|
|
||||||
//Ok(jack)
|
|
||||||
//}
|
|
||||||
//handle!(TuiIn: |self:Plugin, from|{
|
|
||||||
//match from.event() {
|
|
||||||
//kpat!(KeyCode::Up) => {
|
|
||||||
//self.selected = self.selected.saturating_sub(1);
|
|
||||||
//Ok(Some(true))
|
|
||||||
//},
|
|
||||||
//kpat!(KeyCode::Down) => {
|
|
||||||
//self.selected = (self.selected + 1).min(match &self.plugin {
|
|
||||||
//Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
|
||||||
//_ => unimplemented!()
|
|
||||||
//});
|
|
||||||
//Ok(Some(true))
|
|
||||||
//},
|
|
||||||
//kpat!(KeyCode::PageUp) => {
|
|
||||||
//self.selected = self.selected.saturating_sub(8);
|
|
||||||
//Ok(Some(true))
|
|
||||||
//},
|
|
||||||
//kpat!(KeyCode::PageDown) => {
|
|
||||||
//self.selected = (self.selected + 10).min(match &self.plugin {
|
|
||||||
//Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
|
||||||
//_ => unimplemented!()
|
|
||||||
//});
|
|
||||||
//Ok(Some(true))
|
|
||||||
//},
|
|
||||||
//kpat!(KeyCode::Char(',')) => {
|
|
||||||
//match self.plugin.as_mut() {
|
|
||||||
//Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
|
||||||
//let index = port_list[self.selected].index;
|
|
||||||
//if let Some(value) = instance.control_input(index) {
|
|
||||||
//instance.set_control_input(index, value - 0.01);
|
|
||||||
//}
|
|
||||||
//},
|
|
||||||
//_ => {}
|
|
||||||
//}
|
|
||||||
//Ok(Some(true))
|
|
||||||
//},
|
|
||||||
//kpat!(KeyCode::Char('.')) => {
|
|
||||||
//match self.plugin.as_mut() {
|
|
||||||
//Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
|
||||||
//let index = port_list[self.selected].index;
|
|
||||||
//if let Some(value) = instance.control_input(index) {
|
|
||||||
//instance.set_control_input(index, value + 0.01);
|
|
||||||
//}
|
|
||||||
//},
|
|
||||||
//_ => {}
|
|
||||||
//}
|
|
||||||
//Ok(Some(true))
|
|
||||||
//},
|
|
||||||
//kpat!(KeyCode::Char('g')) => {
|
|
||||||
//match self.plugin {
|
|
||||||
////Some(PluginKind::LV2(ref mut plugin)) => {
|
|
||||||
////plugin.ui_thread = Some(run_lv2_ui(LV2PluginUI::new()?)?);
|
|
||||||
////},
|
|
||||||
//Some(_) => unreachable!(),
|
|
||||||
//None => {}
|
|
||||||
//}
|
|
||||||
//Ok(Some(true))
|
|
||||||
//},
|
|
||||||
//_ => Ok(None)
|
|
||||||
//}
|
|
||||||
//});
|
|
||||||
|
|
||||||
//from_atom!("plugin/lv2" => |jack: &Jack, args| -> Plugin {
|
|
||||||
//let mut name = String::new();
|
|
||||||
//let mut path = String::new();
|
|
||||||
//atom!(atom in args {
|
|
||||||
//Atom::Map(map) => {
|
|
||||||
//if let Some(Atom::Str(n)) = map.get(&Atom::Key(":name")) {
|
|
||||||
//name = String::from(*n);
|
|
||||||
//}
|
|
||||||
//if let Some(Atom::Str(p)) = map.get(&Atom::Key(":path")) {
|
|
||||||
//path = String::from(*p);
|
|
||||||
//}
|
|
||||||
//},
|
|
||||||
//_ => panic!("unexpected in lv2 '{name}'"),
|
|
||||||
//});
|
|
||||||
//Plugin::new_lv2(jack, &name, &path)
|
|
||||||
//});
|
|
||||||
|
|
||||||
//pub struct LV2PluginUI {
|
|
||||||
//write: (),
|
|
||||||
//controller: (),
|
|
||||||
//widget: (),
|
|
||||||
//features: (),
|
|
||||||
//transfer: (),
|
|
||||||
//}
|
|
||||||
//take!(BrowseCommand |state: Pool, iter|Ok(state.browse.as_ref()
|
|
||||||
//.map(|p|Take::take(p, iter))
|
|
||||||
//.transpose()?
|
|
||||||
//.flatten()));
|
|
||||||
|
|
||||||
//fn file_browser_filter (&self) -> Arc<str> {
|
|
||||||
//todo!()
|
|
||||||
//}
|
|
||||||
//fn file_browser_path (&self) -> PathBuf {
|
|
||||||
//todo!();
|
|
||||||
//}
|
|
||||||
///// Immutable reference to sample at cursor.
|
|
||||||
//fn sample_selected (&self) -> Option<Arc<RwLock<Sample>>> {
|
|
||||||
//for (i, sample) in self.mapped.iter().enumerate() {
|
|
||||||
//if i == self.cursor().0 {
|
|
||||||
//return sample.as_ref()
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//for (i, sample) in self.unmapped.iter().enumerate() {
|
|
||||||
//if i + self.mapped.len() == self.cursor().0 {
|
|
||||||
//return Some(sample)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//None
|
|
||||||
//}
|
|
||||||
//fn sample_gain (&self) -> f32 {
|
|
||||||
//todo!()
|
|
||||||
//}
|
|
||||||
//fn sample_above () -> usize {
|
|
||||||
//self.note_pos().min(119) + 8
|
|
||||||
//}
|
|
||||||
//fn sample_below () -> usize {
|
|
||||||
//self.note_pos().max(8) - 8
|
|
||||||
//}
|
|
||||||
//fn sample_to_left () -> usize {
|
|
||||||
//self.note_pos().min(126) + 1
|
|
||||||
//}
|
|
||||||
//fn sample_to_right () -> usize {
|
|
||||||
//self.note_pos().max(1) - 1
|
|
||||||
//}
|
|
||||||
//fn selected_pitch () -> u7 {
|
|
||||||
//(self.note_pos() as u8).into() // TODO
|
|
||||||
//}
|
|
||||||
|
|
||||||
//select (&self, state: &mut Sampler, i: usize) -> Option<Self> {
|
|
||||||
//Self::Select(state.set_note_pos(i))
|
|
||||||
//}
|
|
||||||
///// Assign sample to slot
|
|
||||||
//set (&self, slot: u7, sample: Option<Arc<RwLock<Sample>>>) -> Option<Self> {
|
|
||||||
//let i = slot.as_int() as usize;
|
|
||||||
//let old = self.mapped[i].clone();
|
|
||||||
//self.mapped[i] = sample;
|
|
||||||
//Some(Self::Set(old))
|
|
||||||
//}
|
|
||||||
//set_start (&self, state: &mut Sampler, slot: u7, frame: usize) -> Option<Self> {
|
|
||||||
//todo!()
|
|
||||||
//}
|
|
||||||
//set_gain (&self, state: &mut Sampler, slot: u7, g: f32) -> Option<Self> {
|
|
||||||
//todo!()
|
|
||||||
//}
|
|
||||||
//note_on (&self, state: &mut Sampler, slot: u7, v: u7) -> Option<Self> {
|
|
||||||
//todo!()
|
|
||||||
//}
|
|
||||||
//note_off (&self, state: &mut Sampler, slot: u7) -> Option<Self> {
|
|
||||||
//todo!()
|
|
||||||
//}
|
|
||||||
//set_sample (&self, state: &mut Sampler, slot: u7, s: Option<Arc<RwLock<Sample>>>) -> Option<Self> {
|
|
||||||
//Some(Self::SetSample(p, state.set_sample(p, s)))
|
|
||||||
//}
|
|
||||||
//import (&self, state: &mut Sampler, c: FileBrowserCommand) -> Option<Self> {
|
|
||||||
//match c {
|
|
||||||
//FileBrowserCommand::Begin => {
|
|
||||||
////let voices = &state.state.voices;
|
|
||||||
////let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![])));
|
|
||||||
//state.mode = Some(SamplerMode::Import(0, FileBrowser::new(None)?));
|
|
||||||
//None
|
|
||||||
//},
|
|
||||||
//_ => {
|
|
||||||
//println!("\n\rtodo: import: filebrowser: {c:?}");
|
|
||||||
//None
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
////(Select [i: usize] Some(Self::Select(state.set_note_pos(i))))
|
|
||||||
////(RecordBegin [p: u7] cmd!(state.begin_recording(p.as_int() as usize)))
|
|
||||||
////(RecordCancel [] cmd!(state.cancel_recording()))
|
|
||||||
////(RecordFinish [] cmd!(state.finish_recording()))
|
|
||||||
////(SetStart [p: u7, frame: usize] cmd_todo!("\n\rtodo: {self:?}"))
|
|
||||||
////(SetGain [p: u7, gain: f32] cmd_todo!("\n\rtodo: {self:?}"))
|
|
||||||
////(NoteOn [p: u7, velocity: u7] cmd_todo!("\n\rtodo: {self:?}"))
|
|
||||||
////(NoteOff [p: u7] cmd_todo!("\n\rtodo: {self:?}"))
|
|
||||||
////(SetSample [p: u7, s: Option<Arc<RwLock<Sample>>>] Some(Self::SetSample(p, state.set_sample(p, s))))
|
|
||||||
////(Import [c: FileBrowserCommand] match c {
|
|
||||||
////FileBrowserCommand::Begin => {
|
|
||||||
//////let voices = &state.state.voices;
|
|
||||||
//////let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![])));
|
|
||||||
////state.mode = Some(SamplerMode::Import(0, FileBrowser::new(None)?));
|
|
||||||
////None
|
|
||||||
////},
|
|
||||||
////_ => {
|
|
||||||
////println!("\n\rtodo: import: filebrowser: {c:?}");
|
|
||||||
////None
|
|
||||||
////}
|
|
||||||
////})));
|
|
||||||
////("import" [,..a]
|
|
||||||
////FileBrowserCommand::try_from_expr(state, a).map(Self::Import))
|
|
||||||
////("select" [i: usize]
|
|
||||||
////Some(Self::Select(i.expect("no index"))))
|
|
||||||
////("record/begin" [i: u7]
|
|
||||||
////Some(Self::RecordBegin(i.expect("no index"))))
|
|
||||||
////("record/cancel" []
|
|
||||||
////Some(Self::RecordCancel))
|
|
||||||
////("record/finish" []
|
|
||||||
////Some(Self::RecordFinish))
|
|
||||||
////("set/sample" [i: u7, s: Option<Arc<RwLock<Sample>>>]
|
|
||||||
////Some(Self::SetSample(i.expect("no index"), s.expect("no sampler"))))
|
|
||||||
////("set/start" [i: u7, s: usize]
|
|
||||||
////Some(Self::SetStart(i.expect("no index"), s.expect("no start"))))
|
|
||||||
////("set/gain" [i: u7, g: f32]
|
|
||||||
////Some(Self::SetGain(i.expect("no index"), g.expect("no gain"))))
|
|
||||||
////("note/on" [p: u7, v: u7]
|
|
||||||
////Some(Self::NoteOn(p.expect("no slot"), v.expect("no velocity"))))
|
|
||||||
////("note/off" [p: u7]
|
|
||||||
////Some(Self::NoteOff(p.expect("no slot"))))));
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
//for port in midi_in.iter() {
|
|
||||||
//for event in port.iter() {
|
|
||||||
//match event {
|
|
||||||
//(time, Ok(LiveEvent::Midi {message, ..})) => match message {
|
|
||||||
//MidiMessage::NoteOn {ref key, ..} if let Some(editor) = self.editor.as_ref() => {
|
|
||||||
//editor.set_note_pos(key.as_int() as usize);
|
|
||||||
//},
|
|
||||||
//MidiMessage::Controller {controller, value} if let (Some(editor), Some(sampler)) = (
|
|
||||||
//self.editor.as_ref(),
|
|
||||||
//self.sampler.as_ref(),
|
|
||||||
//) => {
|
|
||||||
//// TODO: give sampler its own cursor
|
|
||||||
//if let Some(sample) = &sampler.mapped[editor.note_pos()] {
|
|
||||||
//sample.write().unwrap().handle_cc(*controller, *value)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//_ =>{}
|
|
||||||
//},
|
|
||||||
//_ =>{}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
//scene_scroll: Fill::Y(Fixed::X(1, ScrollbarV {
|
|
||||||
//offset: arrangement.scene_scroll,
|
|
||||||
//length: h_scenes_area as usize,
|
|
||||||
//total: h_scenes as usize,
|
|
||||||
//})),
|
|
||||||
//take!(SceneCommand |state: Arrangement, iter|state.selected_scene().as_ref()
|
|
||||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
|
||||||
|
|
||||||
//pub(crate) fn io_conns <'a, T: PortsSizes<'a>> (
|
|
||||||
//fg: Color, bg: Color, iter: &mut impl Iterator<Item = (usize, &'a Arc<str>, &'a [Connect], usize, usize)>
|
|
||||||
//) -> impl Content<TuiOut> + 'a {
|
|
||||||
//Fill::XY(Thunk::new(move|to: &mut TuiOut|for (_, _, connections, y, y2) in &mut *iter {
|
|
||||||
//to.place(&map_south(y as u16, (y2-y) as u16, Bsp::s(
|
|
||||||
//Fill::Y(Tui::bold(true, wrap(bg, fg, Fill::Y(Align::w(&"▞▞▞▞ ▞▞▞▞"))))),
|
|
||||||
//Thunk::new(|to: &mut TuiOut|for (index, _connection) in connections.iter().enumerate() {
|
|
||||||
//to.place(&map_south(index as u16, 1, Fill::Y(Align::w(Tui::bold(false,
|
|
||||||
//wrap(bg, fg, Fill::Y(&"")))))))
|
|
||||||
//})
|
|
||||||
//)))
|
|
||||||
//}))
|
|
||||||
//}
|
|
||||||
//track_scroll: Fill::Y(Fixed::Y(1, ScrollbarH {
|
|
||||||
//offset: arrangement.track_scroll,
|
|
||||||
//length: h_tracks_area as usize,
|
|
||||||
//total: h_scenes as usize,
|
|
||||||
//})),
|
|
||||||
//take!(TrackCommand |state: Arrangement, iter|state.selected_track().as_ref()
|
|
||||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "tek_device"
|
|
||||||
edition = { workspace = true }
|
|
||||||
version = { workspace = true }
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
path = "device.rs"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")']
|
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
tek_engine = { path = "../engine" }
|
|
||||||
|
|
||||||
atomic_float = { workspace = true }
|
|
||||||
backtrace = { workspace = true }
|
|
||||||
clap = { workspace = true, optional = true }
|
|
||||||
jack = { workspace = true }
|
|
||||||
konst = { workspace = true }
|
|
||||||
livi = { workspace = true, optional = true }
|
|
||||||
midly = { workspace = true }
|
|
||||||
palette = { workspace = true }
|
|
||||||
rand = { workspace = true }
|
|
||||||
symphonia = { workspace = true, optional = true }
|
|
||||||
tengri = { workspace = true }
|
|
||||||
toml = { workspace = true }
|
|
||||||
uuid = { workspace = true, optional = true }
|
|
||||||
wavers = { workspace = true, optional = true }
|
|
||||||
winit = { workspace = true, optional = true }
|
|
||||||
xdg = { workspace = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
proptest = { workspace = true }
|
|
||||||
proptest-derive = { workspace = true }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
arranger = ["port", "editor", "sequencer", "editor", "track", "scene", "clip", "select"]
|
|
||||||
browse = []
|
|
||||||
clap = []
|
|
||||||
cli = ["dep:clap"]
|
|
||||||
clip = []
|
|
||||||
clock = []
|
|
||||||
default = ["cli", "arranger", "sampler", "track", "lv2"]
|
|
||||||
editor = []
|
|
||||||
host = ["lv2"]
|
|
||||||
lv2 = ["port", "livi"]
|
|
||||||
lv2_gui = ["lv2", "winit"]
|
|
||||||
meter = []
|
|
||||||
mixer = []
|
|
||||||
pool = []
|
|
||||||
port = []
|
|
||||||
sampler = ["port", "meter", "mixer", "browse", "symphonia", "wavers"]
|
|
||||||
select = []
|
|
||||||
scene = []
|
|
||||||
sequencer = ["port", "clock", "uuid", "pool"]
|
|
||||||
sf2 = []
|
|
||||||
track = []
|
|
||||||
vst2 = []
|
|
||||||
vst3 = []
|
|
||||||
820
device/device.rs
820
device/device.rs
|
|
@ -1,820 +0,0 @@
|
||||||
#![feature(trait_alias)]
|
|
||||||
|
|
||||||
pub use tek_engine;
|
|
||||||
pub(crate) use ConnectName::*;
|
|
||||||
pub(crate) use ConnectScope::*;
|
|
||||||
pub(crate) use ConnectStatus::*;
|
|
||||||
|
|
||||||
mod device_struct; pub use self::device_struct::*;
|
|
||||||
mod device_trait; pub use self::device_trait::*;
|
|
||||||
mod device_type; pub use self::device_type::*;
|
|
||||||
mod device_impl;
|
|
||||||
|
|
||||||
pub(crate) use ::{
|
|
||||||
tek_engine::*,
|
|
||||||
tek_engine::tengri::{
|
|
||||||
Usually, Perhaps, Has, MaybeHas, has, maybe_has, from,
|
|
||||||
input::*,
|
|
||||||
output::*,
|
|
||||||
tui::*,
|
|
||||||
tui::ratatui,
|
|
||||||
tui::ratatui::widgets::{Widget, canvas::{Canvas, Line}},
|
|
||||||
tui::ratatui::prelude::{Rect, Style, Stylize, Buffer, Color::{self, *}},
|
|
||||||
},
|
|
||||||
std::{
|
|
||||||
cmp::Ord,
|
|
||||||
ffi::OsString,
|
|
||||||
fmt::{Debug, Formatter},
|
|
||||||
fs::File,
|
|
||||||
path::PathBuf,
|
|
||||||
sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed}},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "sampler")] pub(crate) use symphonia::{
|
|
||||||
core::{
|
|
||||||
formats::Packet,
|
|
||||||
codecs::{Decoder, CODEC_TYPE_NULL},
|
|
||||||
//errors::Error as SymphoniaError,
|
|
||||||
io::MediaSourceStream,
|
|
||||||
probe::Hint,
|
|
||||||
audio::SampleBuffer,
|
|
||||||
},
|
|
||||||
default::get_codecs,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "lv2")] use std::thread::{spawn, JoinHandle};
|
|
||||||
|
|
||||||
#[cfg(feature = "lv2_gui")] use ::winit::{
|
|
||||||
application::ApplicationHandler,
|
|
||||||
event::WindowEvent,
|
|
||||||
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
|
|
||||||
window::{Window, WindowId},
|
|
||||||
platform::x11::EventLoopBuilderExtX11
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Define a type alias for iterators of sized items (columns).
|
|
||||||
macro_rules! def_sizes_iter {
|
|
||||||
($Type:ident => $($Item:ty),+) => {
|
|
||||||
pub trait $Type<'a> =
|
|
||||||
Iterator<Item=(usize, $(&'a $Item,)+ usize, usize)> + Send + Sync + 'a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn view_transport (
|
|
||||||
play: bool,
|
|
||||||
bpm: Arc<RwLock<String>>,
|
|
||||||
beat: Arc<RwLock<String>>,
|
|
||||||
time: Arc<RwLock<String>>,
|
|
||||||
) -> impl Content<TuiOut> {
|
|
||||||
let theme = ItemTheme::G[96];
|
|
||||||
Tui::bg(Black, row!(Bsp::a(
|
|
||||||
Fill::XY(Align::w(button_play_pause(play))),
|
|
||||||
Fill::XY(Align::e(row!(
|
|
||||||
FieldH(theme, "BPM", bpm),
|
|
||||||
FieldH(theme, "Beat", beat),
|
|
||||||
FieldH(theme, "Time", time),
|
|
||||||
)))
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_status (
|
|
||||||
sel: Option<Arc<str>>,
|
|
||||||
sr: Arc<RwLock<String>>,
|
|
||||||
buf: Arc<RwLock<String>>,
|
|
||||||
lat: Arc<RwLock<String>>,
|
|
||||||
) -> impl Content<TuiOut> {
|
|
||||||
let theme = ItemTheme::G[96];
|
|
||||||
Tui::bg(Black, row!(Bsp::a(
|
|
||||||
Fill::XY(Align::w(sel.map(|sel|FieldH(theme, "Selected", sel)))),
|
|
||||||
Fill::XY(Align::e(row!(
|
|
||||||
FieldH(theme, "SR", sr),
|
|
||||||
FieldH(theme, "Buf", buf),
|
|
||||||
FieldH(theme, "Lat", lat),
|
|
||||||
)))
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn button_play_pause (playing: bool) -> impl Content<TuiOut> {
|
|
||||||
let compact = true;//self.is_editing();
|
|
||||||
Tui::bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) },
|
|
||||||
Either::new(compact,
|
|
||||||
Thunk::new(move|to: &mut TuiOut|to.place(&Fixed::X(9, Either::new(playing,
|
|
||||||
Tui::fg(Rgb(0, 255, 0), " PLAYING "),
|
|
||||||
Tui::fg(Rgb(255, 128, 0), " STOPPED ")))
|
|
||||||
)),
|
|
||||||
Thunk::new(move|to: &mut TuiOut|to.place(&Fixed::X(5, Either::new(playing,
|
|
||||||
Tui::fg(Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
|
|
||||||
Tui::fg(Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))
|
|
||||||
))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn swap_value <T: Clone + PartialEq, U> (
|
|
||||||
target: &mut T, value: &T, returned: impl Fn(T)->U
|
|
||||||
) -> Perhaps<U> {
|
|
||||||
if *target == *value {
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
let mut value = value.clone();
|
|
||||||
std::mem::swap(target, &mut value);
|
|
||||||
Ok(Some(returned(value)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toggle_bool <U> (
|
|
||||||
target: &mut bool, value: &Option<bool>, returned: impl Fn(Option<bool>)->U
|
|
||||||
) -> Perhaps<U> {
|
|
||||||
let mut value = value.unwrap_or(!*target);
|
|
||||||
if value == *target {
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
std::mem::swap(target, &mut value);
|
|
||||||
Ok(Some(returned(Some(value))))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn device_kinds () -> &'static [&'static str] {
|
|
||||||
&[
|
|
||||||
#[cfg(feature = "sampler")] "Sampler",
|
|
||||||
#[cfg(feature = "lv2")] "Plugin (LV2)",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Has<Vec<Device>>> HasDevices for T {
|
|
||||||
fn devices (&self) -> &Vec<Device> {
|
|
||||||
self.get()
|
|
||||||
}
|
|
||||||
fn devices_mut (&mut self) -> &mut Vec<Device> {
|
|
||||||
self.get_mut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Device {
|
|
||||||
pub fn name (&self) -> &str {
|
|
||||||
match self {
|
|
||||||
Self::Sampler(sampler) => sampler.name.as_ref(),
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn midi_ins (&self) -> &[MidiInput] {
|
|
||||||
match self {
|
|
||||||
//Self::Sampler(Sampler { midi_in, .. }) => &[midi_in],
|
|
||||||
_ => todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn midi_outs (&self) -> &[MidiOutput] {
|
|
||||||
match self {
|
|
||||||
Self::Sampler(_) => &[],
|
|
||||||
_ => todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn audio_ins (&self) -> &[AudioInput] {
|
|
||||||
match self {
|
|
||||||
Self::Sampler(Sampler { audio_ins, .. }) => audio_ins.as_slice(),
|
|
||||||
_ => todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn audio_outs (&self) -> &[AudioOutput] {
|
|
||||||
match self {
|
|
||||||
Self::Sampler(Sampler { audio_outs, .. }) => audio_outs.as_slice(),
|
|
||||||
_ => todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
audio!(|self: DeviceAudio<'a>, client, scope|{
|
|
||||||
use Device::*;
|
|
||||||
match self.0 {
|
|
||||||
Mute => { Control::Continue },
|
|
||||||
Bypass => { /*TODO*/ Control::Continue },
|
|
||||||
#[cfg(feature = "sampler")] Sampler(sampler) => sampler.process(client, scope),
|
|
||||||
#[cfg(feature = "lv2")] Lv2(lv2) => lv2.process(client, scope),
|
|
||||||
#[cfg(feature = "vst2")] Vst2 => { todo!() }, // TODO
|
|
||||||
#[cfg(feature = "vst3")] Vst3 => { todo!() }, // TODO
|
|
||||||
#[cfg(feature = "clap")] Clap => { todo!() }, // TODO
|
|
||||||
#[cfg(feature = "sf2")] Sf2 => { todo!() }, // TODO
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
def_command!(ClockCommand: |clock: Clock| {
|
|
||||||
SeekUsec { usec: f64 } => {
|
|
||||||
clock.playhead.update_from_usec(*usec); Ok(None) },
|
|
||||||
SeekSample { sample: f64 } => {
|
|
||||||
clock.playhead.update_from_sample(*sample); Ok(None) },
|
|
||||||
SeekPulse { pulse: f64 } => {
|
|
||||||
clock.playhead.update_from_pulse(*pulse); Ok(None) },
|
|
||||||
SetBpm { bpm: f64 } => Ok(Some(
|
|
||||||
Self::SetBpm { bpm: clock.timebase().bpm.set(*bpm) })),
|
|
||||||
SetQuant { quant: f64 } => Ok(Some(
|
|
||||||
Self::SetQuant { quant: clock.quant.set(*quant) })),
|
|
||||||
SetSync { sync: f64 } => Ok(Some(
|
|
||||||
Self::SetSync { sync: clock.sync.set(*sync) })),
|
|
||||||
|
|
||||||
Play { position: Option<u32> } => {
|
|
||||||
clock.play_from(*position)?; Ok(None) /* TODO Some(Pause(previousPosition)) */ },
|
|
||||||
Pause { position: Option<u32> } => {
|
|
||||||
clock.pause_at(*position)?; Ok(None) },
|
|
||||||
|
|
||||||
TogglePlayback { position: u32 } => Ok(if clock.is_rolling() {
|
|
||||||
clock.pause_at(Some(*position))?; None
|
|
||||||
} else {
|
|
||||||
clock.play_from(Some(*position))?; None
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
def_command!(DeviceCommand: |device: Device| {});
|
|
||||||
def_command!(ClipCommand: |clip: MidiClip| {
|
|
||||||
SetColor { color: Option<ItemTheme> } => {
|
|
||||||
//(SetColor [t: usize, s: usize, c: ItemTheme]
|
|
||||||
//clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o)))));
|
|
||||||
//("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random())))
|
|
||||||
todo!()
|
|
||||||
},
|
|
||||||
SetLoop { looping: Option<bool> } => {
|
|
||||||
//(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}"))
|
|
||||||
//("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap())))
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
def_command!(BrowseCommand: |browse: Browse| {
|
|
||||||
SetVisible => Ok(None),
|
|
||||||
SetPath { address: PathBuf } => Ok(None),
|
|
||||||
SetSearch { filter: Arc<str> } => Ok(None),
|
|
||||||
SetCursor { cursor: usize } => Ok(None),
|
|
||||||
});
|
|
||||||
//take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref()
|
|
||||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
|
||||||
|
|
||||||
#[cfg(feature = "track")]
|
|
||||||
pub fn view_track_row_section (
|
|
||||||
_theme: ItemTheme,
|
|
||||||
button: impl Content<TuiOut>,
|
|
||||||
button_add: impl Content<TuiOut>,
|
|
||||||
content: impl Content<TuiOut>,
|
|
||||||
) -> impl Content<TuiOut> {
|
|
||||||
Bsp::w(Fill::Y(Fixed::X(4, Align::nw(button_add))),
|
|
||||||
Bsp::e(Fixed::X(20, Fill::Y(Align::nw(button))), Fill::XY(Align::c(content))))
|
|
||||||
}
|
|
||||||
pub fn wrap (bg: Color, fg: Color, content: impl Content<TuiOut>) -> impl Content<TuiOut> {
|
|
||||||
let left = Tui::fg_bg(bg, Reset, Fixed::X(1, Repeat::Y("▐")));
|
|
||||||
let right = Tui::fg_bg(bg, Reset, Fixed::X(1, Repeat::Y("▌")));
|
|
||||||
Bsp::e(left, Bsp::w(right, Tui::fg_bg(fg, bg, content)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! has_clip {
|
|
||||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
|
||||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? {
|
|
||||||
fn clip (&$self) -> Option<Arc<RwLock<MidiClip>>> { $cb }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def_command!(MidiEditCommand: |editor: MidiEditor| {
|
|
||||||
Show { clip: Option<Arc<RwLock<MidiClip>>> } => {
|
|
||||||
editor.set_clip(clip.as_ref()); editor.redraw(); Ok(None) },
|
|
||||||
DeleteNote => {
|
|
||||||
editor.redraw(); todo!() },
|
|
||||||
AppendNote { advance: bool } => {
|
|
||||||
editor.put_note(*advance); editor.redraw(); Ok(None) },
|
|
||||||
SetNotePos { pos: usize } => {
|
|
||||||
editor.set_note_pos((*pos).min(127)); editor.redraw(); Ok(None) },
|
|
||||||
SetNoteLen { len: usize } => {
|
|
||||||
editor.set_note_len(*len); editor.redraw(); Ok(None) },
|
|
||||||
SetNoteScroll { scroll: usize } => {
|
|
||||||
editor.set_note_lo((*scroll).min(127)); editor.redraw(); Ok(None) },
|
|
||||||
SetTimePos { pos: usize } => {
|
|
||||||
editor.set_time_pos(*pos); editor.redraw(); Ok(None) },
|
|
||||||
SetTimeScroll { scroll: usize } => {
|
|
||||||
editor.set_time_start(*scroll); editor.redraw(); Ok(None) },
|
|
||||||
SetTimeZoom { zoom: usize } => {
|
|
||||||
editor.set_time_zoom(*zoom); editor.redraw(); Ok(None) },
|
|
||||||
SetTimeLock { lock: bool } => {
|
|
||||||
editor.set_time_lock(*lock); editor.redraw(); Ok(None) },
|
|
||||||
// TODO: 1-9 seek markers that by default start every 8th of the clip
|
|
||||||
});
|
|
||||||
|
|
||||||
fn to_key (note: usize) -> &'static str {
|
|
||||||
match note % 12 {
|
|
||||||
11 | 9 | 7 | 5 | 4 | 2 | 0 => "████▌",
|
|
||||||
10 | 8 | 6 | 3 | 1 => " ",
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16)
|
|
||||||
-> impl Iterator<Item=(usize, u16, usize)>
|
|
||||||
{
|
|
||||||
(note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_header (state: &Lv2, to: &mut TuiOut, x: u16, y: u16, w: u16) {
|
|
||||||
let style = Style::default().gray();
|
|
||||||
let label1 = format!(" {}", state.name);
|
|
||||||
to.blit(&label1, x + 1, y, Some(style.white().bold()));
|
|
||||||
if let Some(ref path) = state.path {
|
|
||||||
let label2 = format!("{}…", &path[..((w as usize - 10).min(path.len()))]);
|
|
||||||
to.blit(&label2, x + 2 + label1.len() as u16, y, Some(style.not_dim()));
|
|
||||||
}
|
|
||||||
//Ok(Rect { x, y, width: w, height: 1 })
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(feature = "lv2_gui")]
|
|
||||||
pub fn run_lv2_ui (mut ui: LV2PluginUI) -> Usually<JoinHandle<()>> {
|
|
||||||
Ok(spawn(move||{
|
|
||||||
let event_loop = EventLoop::builder().with_x11().with_any_thread(true).build().unwrap();
|
|
||||||
event_loop.set_control_flow(ControlFlow::Wait);
|
|
||||||
event_loop.run_app(&mut ui).unwrap()
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "lv2_gui")]
|
|
||||||
fn lv2_ui_instantiate (kind: &str) {
|
|
||||||
//let host = Suil
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mix_summing <const N: usize> (
|
|
||||||
buffer: &mut [Vec<f32>], gain: f32, frames: usize, mut next: impl FnMut()->Option<[f32;N]>,
|
|
||||||
) -> bool {
|
|
||||||
let channels = buffer.len();
|
|
||||||
for index in 0..frames {
|
|
||||||
if let Some(frame) = next() {
|
|
||||||
for (channel, sample) in frame.iter().enumerate() {
|
|
||||||
let channel = channel % channels;
|
|
||||||
buffer[channel][index] += sample * gain;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mix_average <const N: usize> (
|
|
||||||
buffer: &mut [Vec<f32>], gain: f32, frames: usize, mut next: impl FnMut()->Option<[f32;N]>,
|
|
||||||
) -> bool {
|
|
||||||
let channels = buffer.len();
|
|
||||||
for index in 0..frames {
|
|
||||||
if let Some(frame) = next() {
|
|
||||||
for (channel, sample) in frame.iter().enumerate() {
|
|
||||||
let channel = channel % channels;
|
|
||||||
let value = buffer[channel][index];
|
|
||||||
buffer[channel][index] = (value + sample * gain) / 2.0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_log10 (samples: &[f32]) -> f32 {
|
|
||||||
let total: f32 = samples.iter().map(|x|x.abs()).sum();
|
|
||||||
let count = samples.len() as f32;
|
|
||||||
10. * (total / count).log10()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn to_rms (samples: &[f32]) -> f32 {
|
|
||||||
let sum = samples.iter()
|
|
||||||
.map(|s|*s)
|
|
||||||
.reduce(|sum, sample|sum + sample.abs())
|
|
||||||
.unwrap_or(0.0);
|
|
||||||
(sum / samples.len() as f32).sqrt()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_meter <'a> (label: &'a str, value: f32) -> impl Content<TuiOut> + 'a {
|
|
||||||
col!(
|
|
||||||
FieldH(ItemTheme::G[128], label, format!("{:>+9.3}", value)),
|
|
||||||
Fixed::XY(if value >= 0.0 { 13 }
|
|
||||||
else if value >= -1.0 { 12 }
|
|
||||||
else if value >= -2.0 { 11 }
|
|
||||||
else if value >= -3.0 { 10 }
|
|
||||||
else if value >= -4.0 { 9 }
|
|
||||||
else if value >= -6.0 { 8 }
|
|
||||||
else if value >= -9.0 { 7 }
|
|
||||||
else if value >= -12.0 { 6 }
|
|
||||||
else if value >= -15.0 { 5 }
|
|
||||||
else if value >= -20.0 { 4 }
|
|
||||||
else if value >= -25.0 { 3 }
|
|
||||||
else if value >= -30.0 { 2 }
|
|
||||||
else if value >= -40.0 { 1 }
|
|
||||||
else { 0 }, 1, Tui::bg(if value >= 0.0 { Red }
|
|
||||||
else if value >= -3.0 { Yellow }
|
|
||||||
else { Green }, ())))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_meters (values: &[f32;2]) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
let left = format!("L/{:>+9.3}", values[0]);
|
|
||||||
let right = format!("R/{:>+9.3}", values[1]);
|
|
||||||
Bsp::s(left, right)
|
|
||||||
}
|
|
||||||
def_command!(PoolCommand: |pool: Pool| {
|
|
||||||
// Toggle visibility of pool
|
|
||||||
Show { visible: bool } => { pool.visible = *visible; Ok(Some(Self::Show { visible: !visible })) },
|
|
||||||
// Select a clip from the clip pool
|
|
||||||
Select { index: usize } => { pool.set_clip_index(*index); Ok(None) },
|
|
||||||
// Update the contents of the clip pool
|
|
||||||
Clip { command: PoolClipCommand } => Ok(command.execute(pool)?.map(|command|Self::Clip{command})),
|
|
||||||
// Rename a clip
|
|
||||||
Rename { command: RenameCommand } => Ok(command.delegate(pool, |command|Self::Rename{command})?),
|
|
||||||
// Change the length of a clip
|
|
||||||
Length { command: CropCommand } => Ok(command.delegate(pool, |command|Self::Length{command})?),
|
|
||||||
// Import from file
|
|
||||||
Import { command: BrowseCommand } => Ok(if let Some(browse) = pool.browse.as_mut() {
|
|
||||||
command.delegate(browse, |command|Self::Import{command})?
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
// Export to file
|
|
||||||
Export { command: BrowseCommand } => Ok(if let Some(browse) = pool.browse.as_mut() {
|
|
||||||
command.delegate(browse, |command|Self::Export{command})?
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
def_command!(PoolClipCommand: |pool: Pool| {
|
|
||||||
Delete { index: usize } => {
|
|
||||||
let index = *index;
|
|
||||||
let clip = pool.clips_mut().remove(index).read().unwrap().clone();
|
|
||||||
Ok(Some(Self::Add { index, clip }))
|
|
||||||
},
|
|
||||||
Swap { index: usize, other: usize } => {
|
|
||||||
let index = *index;
|
|
||||||
let other = *other;
|
|
||||||
pool.clips_mut().swap(index, other);
|
|
||||||
Ok(Some(Self::Swap { index, other }))
|
|
||||||
},
|
|
||||||
Export { index: usize, path: PathBuf } => {
|
|
||||||
todo!("export clip to midi file");
|
|
||||||
},
|
|
||||||
Add { index: usize, clip: MidiClip } => {
|
|
||||||
let index = *index;
|
|
||||||
let mut index = index;
|
|
||||||
let clip = Arc::new(RwLock::new(clip.clone()));
|
|
||||||
let mut clips = pool.clips_mut();
|
|
||||||
if index >= clips.len() {
|
|
||||||
index = clips.len();
|
|
||||||
clips.push(clip)
|
|
||||||
} else {
|
|
||||||
clips.insert(index, clip);
|
|
||||||
}
|
|
||||||
Ok(Some(Self::Delete { index }))
|
|
||||||
},
|
|
||||||
Import { index: usize, path: PathBuf } => {
|
|
||||||
let index = *index;
|
|
||||||
let bytes = std::fs::read(&path)?;
|
|
||||||
let smf = Smf::parse(bytes.as_slice())?;
|
|
||||||
let mut t = 0u32;
|
|
||||||
let mut events = vec![];
|
|
||||||
for track in smf.tracks.iter() {
|
|
||||||
for event in track.iter() {
|
|
||||||
t += event.delta.as_int();
|
|
||||||
if let TrackEventKind::Midi { channel, message } = event.kind {
|
|
||||||
events.push((t, channel.as_int(), message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut clip = MidiClip::new("imported", true, t as usize + 1, None, None);
|
|
||||||
for event in events.iter() {
|
|
||||||
clip.notes[event.0 as usize].push(event.2);
|
|
||||||
}
|
|
||||||
Ok(Self::Add { index, clip }.execute(pool)?)
|
|
||||||
},
|
|
||||||
SetName { index: usize, name: Arc<str> } => {
|
|
||||||
let index = *index;
|
|
||||||
let clip = &mut pool.clips_mut()[index];
|
|
||||||
let old_name = clip.read().unwrap().name.clone();
|
|
||||||
clip.write().unwrap().name = name.clone();
|
|
||||||
Ok(Some(Self::SetName { index, name: old_name }))
|
|
||||||
},
|
|
||||||
SetLength { index: usize, length: usize } => {
|
|
||||||
let index = *index;
|
|
||||||
let clip = &mut pool.clips_mut()[index];
|
|
||||||
let old_len = clip.read().unwrap().length;
|
|
||||||
clip.write().unwrap().length = *length;
|
|
||||||
Ok(Some(Self::SetLength { index, length: old_len }))
|
|
||||||
},
|
|
||||||
SetColor { index: usize, color: ItemColor } => {
|
|
||||||
let index = *index;
|
|
||||||
let mut color = ItemTheme::from(*color);
|
|
||||||
std::mem::swap(&mut color, &mut pool.clips()[index].write().unwrap().color);
|
|
||||||
Ok(Some(Self::SetColor { index, color: color.base }))
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
def_command!(RenameCommand: |pool: Pool| {
|
|
||||||
Begin => unreachable!(),
|
|
||||||
Cancel => {
|
|
||||||
if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() {
|
|
||||||
pool.clips()[clip].write().unwrap().name = old_name.clone().into();
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
Confirm => {
|
|
||||||
if let Some(PoolMode::Rename(_clip, ref mut old_name)) = pool.mode_mut().clone() {
|
|
||||||
let old_name = old_name.clone(); *pool.mode_mut() = None; return Ok(Some(Self::Set { value: old_name }))
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
Set { value: Arc<str> } => {
|
|
||||||
if let Some(PoolMode::Rename(clip, ref mut _old_name)) = pool.mode_mut().clone() {
|
|
||||||
pool.clips()[clip].write().unwrap().name = value.clone();
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
});
|
|
||||||
def_command!(CropCommand: |pool: Pool| {
|
|
||||||
Begin => unreachable!(),
|
|
||||||
Cancel => { if let Some(PoolMode::Length(..)) = pool.mode_mut().clone() { *pool.mode_mut() = None; } Ok(None) },
|
|
||||||
Set { length: usize } => {
|
|
||||||
if let Some(PoolMode::Length(clip, ref mut length, ref mut _focus))
|
|
||||||
= pool.mode_mut().clone()
|
|
||||||
{
|
|
||||||
let old_length;
|
|
||||||
{
|
|
||||||
let clip = pool.clips()[clip].clone();//.write().unwrap();
|
|
||||||
old_length = Some(clip.read().unwrap().length);
|
|
||||||
clip.write().unwrap().length = *length;
|
|
||||||
}
|
|
||||||
*pool.mode_mut() = None;
|
|
||||||
return Ok(old_length.map(|length|Self::Set { length }))
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
Next => {
|
|
||||||
if let Some(PoolMode::Length(_clip, ref mut _length, ref mut focus)) = pool.mode_mut().clone() { focus.next() }; Ok(None)
|
|
||||||
},
|
|
||||||
Prev => {
|
|
||||||
if let Some(PoolMode::Length(_clip, ref mut _length, ref mut focus)) = pool.mode_mut().clone() { focus.prev() }; Ok(None)
|
|
||||||
},
|
|
||||||
Inc => {
|
|
||||||
if let Some(PoolMode::Length(_clip, ref mut length, ref mut focus)) = pool.mode_mut().clone() {
|
|
||||||
match focus {
|
|
||||||
ClipLengthFocus::Bar => { *length += 4 * PPQ },
|
|
||||||
ClipLengthFocus::Beat => { *length += PPQ },
|
|
||||||
ClipLengthFocus::Tick => { *length += 1 },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
Dec => {
|
|
||||||
if let Some(PoolMode::Length(_clip, ref mut length, ref mut focus)) = pool.mode_mut().clone() {
|
|
||||||
match focus {
|
|
||||||
ClipLengthFocus::Bar => { *length = length.saturating_sub(4 * PPQ) },
|
|
||||||
ClipLengthFocus::Beat => { *length = length.saturating_sub(PPQ) },
|
|
||||||
ClipLengthFocus::Tick => { *length = length.saturating_sub(1) },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
def_command!(MidiInputCommand: |port: MidiInput| {
|
|
||||||
Close => todo!(),
|
|
||||||
Connect { midi_out: Arc<str> } => todo!(),
|
|
||||||
});
|
|
||||||
def_command!(MidiOutputCommand: |port: MidiOutput| {
|
|
||||||
Close => todo!(),
|
|
||||||
Connect { midi_in: Arc<str> } => todo!(),
|
|
||||||
});
|
|
||||||
def_command!(AudioInputCommand: |port: AudioInput| {
|
|
||||||
Close => todo!(),
|
|
||||||
Connect { audio_out: Arc<str> } => todo!(),
|
|
||||||
});
|
|
||||||
def_command!(AudioOutputCommand: |port: AudioOutput| {
|
|
||||||
Close => todo!(),
|
|
||||||
Connect { audio_in: Arc<str> } => todo!(),
|
|
||||||
});
|
|
||||||
def_sizes_iter!(InputsSizes => MidiInput);
|
|
||||||
def_sizes_iter!(OutputsSizes => MidiOutput);
|
|
||||||
def_sizes_iter!(PortsSizes => Arc<str>, [Connect]);
|
|
||||||
def_sizes_iter!(ScenesSizes => Scene);
|
|
||||||
|
|
||||||
fn draw_info (sample: Option<&Arc<RwLock<Sample>>>) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
When::new(sample.is_some(), Thunk::new(move|to: &mut TuiOut|{
|
|
||||||
let sample = sample.unwrap().read().unwrap();
|
|
||||||
let theme = sample.color;
|
|
||||||
to.place(&row!(
|
|
||||||
FieldH(theme, "Name", format!("{:<10}", sample.name.clone())),
|
|
||||||
FieldH(theme, "Length", format!("{:<8}", sample.channels[0].len())),
|
|
||||||
FieldH(theme, "Start", format!("{:<8}", sample.start)),
|
|
||||||
FieldH(theme, "End", format!("{:<8}", sample.end)),
|
|
||||||
FieldH(theme, "Trans", "0"),
|
|
||||||
FieldH(theme, "Gain", format!("{}", sample.gain)),
|
|
||||||
))
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_info_v (sample: Option<&Arc<RwLock<Sample>>>) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
Either::new(sample.is_some(), Thunk::new(move|to: &mut TuiOut|{
|
|
||||||
let sample = sample.unwrap().read().unwrap();
|
|
||||||
let theme = sample.color;
|
|
||||||
to.place(&Fixed::X(20, col!(
|
|
||||||
Fill::X(Align::w(FieldH(theme, "Name ", format!("{:<10}", sample.name.clone())))),
|
|
||||||
Fill::X(Align::w(FieldH(theme, "Length", format!("{:<8}", sample.channels[0].len())))),
|
|
||||||
Fill::X(Align::w(FieldH(theme, "Start ", format!("{:<8}", sample.start)))),
|
|
||||||
Fill::X(Align::w(FieldH(theme, "End ", format!("{:<8}", sample.end)))),
|
|
||||||
Fill::X(Align::w(FieldH(theme, "Trans ", "0"))),
|
|
||||||
Fill::X(Align::w(FieldH(theme, "Gain ", format!("{}", sample.gain)))),
|
|
||||||
)))
|
|
||||||
}), Thunk::new(|to: &mut TuiOut|to.place(&Tui::fg(Red, col!(
|
|
||||||
Tui::bold(true, "× No sample."),
|
|
||||||
"[r] record",
|
|
||||||
"[Shift-F9] import",
|
|
||||||
)))))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_status (sample: Option<&Arc<RwLock<Sample>>>) -> impl Content<TuiOut> {
|
|
||||||
Tui::bold(true, Tui::fg(Tui::g(224), sample
|
|
||||||
.map(|sample|{
|
|
||||||
let sample = sample.read().unwrap();
|
|
||||||
format!("Sample {}-{}", sample.start, sample.end)
|
|
||||||
})
|
|
||||||
.unwrap_or_else(||"No sample".to_string())))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_sample (
|
|
||||||
to: &mut TuiOut, x: u16, y: u16, note: Option<&u7>, sample: &Sample, focus: bool
|
|
||||||
) -> Usually<usize> {
|
|
||||||
let style = if focus { Style::default().green() } else { Style::default() };
|
|
||||||
if focus {
|
|
||||||
to.blit(&"🬴", x+1, y, Some(style.bold()));
|
|
||||||
}
|
|
||||||
let label1 = format!("{:3} {:12}",
|
|
||||||
note.map(|n|n.to_string()).unwrap_or(String::default()),
|
|
||||||
sample.name);
|
|
||||||
let label2 = format!("{:>6} {:>6} +0.0",
|
|
||||||
sample.start,
|
|
||||||
sample.end);
|
|
||||||
to.blit(&label1, x+2, y, Some(style.bold()));
|
|
||||||
to.blit(&label2, x+3+label1.len()as u16, y, Some(style));
|
|
||||||
Ok(label1.len() + label2.len() + 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_sample_data (_: &str) -> Usually<(usize, Vec<Vec<f32>>)> {
|
|
||||||
todo!();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scan (dir: &PathBuf) -> Usually<(Vec<OsString>, Vec<OsString>)> {
|
|
||||||
let (mut subdirs, mut files) = std::fs::read_dir(dir)?
|
|
||||||
.fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{
|
|
||||||
let entry = entry.expect("failed to read drectory entry");
|
|
||||||
let meta = entry.metadata().expect("failed to read entry metadata");
|
|
||||||
if meta.is_file() {
|
|
||||||
files.push(entry.file_name());
|
|
||||||
} else if meta.is_dir() {
|
|
||||||
subdirs.push(entry.file_name());
|
|
||||||
}
|
|
||||||
(subdirs, files)
|
|
||||||
});
|
|
||||||
subdirs.sort();
|
|
||||||
files.sort();
|
|
||||||
Ok((subdirs, files))
|
|
||||||
}
|
|
||||||
|
|
||||||
def_command!(SamplerCommand: |sampler: Sampler| {
|
|
||||||
RecordToggle { slot: usize } => {
|
|
||||||
let slot = *slot;
|
|
||||||
let recording = sampler.recording.as_ref().map(|x|x.0);
|
|
||||||
let _ = Self::RecordFinish.execute(sampler)?;
|
|
||||||
// autoslice: continue recording at next slot
|
|
||||||
if recording != Some(slot) {
|
|
||||||
Self::RecordBegin { slot }.execute(sampler)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RecordBegin { slot: usize } => {
|
|
||||||
let slot = *slot;
|
|
||||||
sampler.recording = Some((
|
|
||||||
slot,
|
|
||||||
Some(Arc::new(RwLock::new(Sample::new(
|
|
||||||
"Sample", 0, 0, vec![vec![];sampler.audio_ins.len()]
|
|
||||||
))))
|
|
||||||
));
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
RecordFinish => {
|
|
||||||
let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{
|
|
||||||
std::mem::swap(sample, &mut sampler.mapped[*index]);
|
|
||||||
sample
|
|
||||||
}); // TODO: undo
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
RecordCancel => {
|
|
||||||
sampler.recording = None;
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
PlaySample { slot: usize } => {
|
|
||||||
let slot = *slot;
|
|
||||||
if let Some(ref sample) = sampler.mapped[slot] {
|
|
||||||
sampler.voices.write().unwrap().push(Sample::play(sample, 0, &u7::from(128)));
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
StopSample { slot: usize } => {
|
|
||||||
let slot = *slot;
|
|
||||||
todo!();
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
def_command!(FileBrowserCommand: |sampler: Sampler|{
|
|
||||||
//("begin" [] Some(Self::Begin))
|
|
||||||
//("cancel" [] Some(Self::Cancel))
|
|
||||||
//("confirm" [] Some(Self::Confirm))
|
|
||||||
//("select" [i: usize] Some(Self::Select(i.expect("no index"))))
|
|
||||||
//("chdir" [p: PathBuf] Some(Self::Chdir(p.expect("no path"))))
|
|
||||||
//("filter" [f: Arc<str>] Some(Self::Filter(f.expect("no filter")))))
|
|
||||||
});
|
|
||||||
def_command!(SceneCommand: |scene: Scene| {
|
|
||||||
SetSize { size: usize } => { todo!() },
|
|
||||||
SetZoom { size: usize } => { todo!() },
|
|
||||||
SetName { name: Arc<str> } =>
|
|
||||||
swap_value(&mut scene.name, name, |name|Self::SetName{name}),
|
|
||||||
SetColor { color: ItemTheme } =>
|
|
||||||
swap_value(&mut scene.color, color, |color|Self::SetColor{color}),
|
|
||||||
});
|
|
||||||
def_sizes_iter!(TracksSizes => Track);
|
|
||||||
def_command!(TrackCommand: |track: Track| {
|
|
||||||
Stop => { track.sequencer.enqueue_next(None); Ok(None) },
|
|
||||||
SetMute { mute: Option<bool> } => todo!(),
|
|
||||||
SetSolo { solo: Option<bool> } => todo!(),
|
|
||||||
SetSize { size: usize } => todo!(),
|
|
||||||
SetZoom { zoom: usize } => todo!(),
|
|
||||||
SetName { name: Arc<str> } =>
|
|
||||||
swap_value(&mut track.name, name, |name|Self::SetName { name }),
|
|
||||||
SetColor { color: ItemTheme } =>
|
|
||||||
swap_value(&mut track.color, color, |color|Self::SetColor { color }),
|
|
||||||
SetRec { rec: Option<bool> } =>
|
|
||||||
toggle_bool(&mut track.sequencer.recording, rec, |rec|Self::SetRec { rec }),
|
|
||||||
SetMon { mon: Option<bool> } =>
|
|
||||||
toggle_bool(&mut track.sequencer.monitoring, mon, |mon|Self::SetMon { mon }),
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
pub(crate) fn track_width (_index: usize, track: &Track) -> u16 {
|
|
||||||
track.width as u16
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view_track_header (theme: ItemTheme, content: impl Content<TuiOut>) -> impl Content<TuiOut> {
|
|
||||||
Fixed::X(12, Tui::bg(theme.darker.rgb, Fill::X(Align::e(content))))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T])
|
|
||||||
-> impl Content<TuiOut> + use<'a, T>
|
|
||||||
{
|
|
||||||
let ins = ports.len() as u16;
|
|
||||||
let frame = Outer(true, Style::default().fg(Tui::g(96)));
|
|
||||||
let iter = move||ports.iter();
|
|
||||||
let names = Map::south(1, iter, move|port, index|Fill::Y(Align::w(format!(" {index} {}", port.port_name()))));
|
|
||||||
let field = FieldV(theme, title, names);
|
|
||||||
Fixed::XY(20, 1 + ins, frame.enclose(Fixed::XY(20, 1 + ins, field)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn per_track_top <'a, T: Content<TuiOut> + 'a, U: TracksSizes<'a>> (
|
|
||||||
tracks: impl Fn() -> U + Send + Sync + 'a,
|
|
||||||
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
|
||||||
) -> impl Content<TuiOut> + 'a {
|
|
||||||
Align::x(Tui::bg(Reset, Map::new(tracks,
|
|
||||||
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{
|
|
||||||
let width = (x2 - x1) as u16;
|
|
||||||
map_east(x1 as u16, width, Fixed::X(width, Tui::fg_bg(
|
|
||||||
track.color.lightest.rgb,
|
|
||||||
track.color.base.rgb,
|
|
||||||
callback(index, track))))})))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn per_track <'a, T: Content<TuiOut> + 'a, U: TracksSizes<'a>> (
|
|
||||||
tracks: impl Fn() -> U + Send + Sync + 'a,
|
|
||||||
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
|
||||||
) -> impl Content<TuiOut> + 'a {
|
|
||||||
per_track_top(tracks, move|index, track|Fill::Y(Align::y(callback(index, track))))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn io_ports <'a, T: PortsSizes<'a>> (
|
|
||||||
fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a
|
|
||||||
) -> impl Content<TuiOut> + 'a {
|
|
||||||
Map::new(iter, move|(
|
|
||||||
_index, name, connections, y, y2
|
|
||||||
): (usize, &'a Arc<str>, &'a [Connect], usize, usize), _|
|
|
||||||
map_south(y as u16, (y2-y) as u16, Bsp::s(
|
|
||||||
Fill::Y(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(&" ", name))))),
|
|
||||||
Map::new(||connections.iter(), move|connect: &'a Connect, index|map_south(index as u16, 1,
|
|
||||||
Fill::Y(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg,
|
|
||||||
&connect.info)))))))))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "vst2")]
|
|
||||||
fn set_vst_plugin <E: Engine> (
|
|
||||||
host: &Arc<Mutex<Plugin<E>>>, _path: &str
|
|
||||||
) -> Usually<PluginKind> {
|
|
||||||
let mut loader = ::vst::host::PluginLoader::load(
|
|
||||||
&std::path::Path::new("/nix/store/ij3sz7nqg5l7v2dygdvzy3w6cj62bd6r-helm-0.9.0/lib/lxvst/helm.so"),
|
|
||||||
host.clone()
|
|
||||||
)?;
|
|
||||||
Ok(PluginKind::VST2 {
|
|
||||||
instance: loader.instance()?
|
|
||||||
})
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,633 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// The source of time.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let clock = tek_device::Clock::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Clone, Default)] pub struct Clock {
|
|
||||||
/// JACK transport handle.
|
|
||||||
pub transport: Arc<Option<Transport>>,
|
|
||||||
/// Global temporal resolution (shared by [Moment] fields)
|
|
||||||
pub timebase: Arc<Timebase>,
|
|
||||||
/// Current global sample and usec (monotonic from JACK clock)
|
|
||||||
pub global: Arc<Moment>,
|
|
||||||
/// Global sample and usec at which playback started
|
|
||||||
pub started: Arc<RwLock<Option<Moment>>>,
|
|
||||||
/// Playback offset (when playing not from start)
|
|
||||||
pub offset: Arc<Moment>,
|
|
||||||
/// Current playhead position
|
|
||||||
pub playhead: Arc<Moment>,
|
|
||||||
/// Note quantization factor
|
|
||||||
pub quant: Arc<Quantize>,
|
|
||||||
/// Launch quantization factor
|
|
||||||
pub sync: Arc<LaunchSync>,
|
|
||||||
/// Size of buffer in samples
|
|
||||||
pub chunk: Arc<AtomicUsize>,
|
|
||||||
// Cache of formatted strings
|
|
||||||
pub view_cache: Arc<RwLock<ClockView>>,
|
|
||||||
/// For syncing the clock to an external source
|
|
||||||
#[cfg(feature = "port")] pub midi_in: Arc<RwLock<Option<MidiInput>>>,
|
|
||||||
/// For syncing other devices to this clock
|
|
||||||
#[cfg(feature = "port")] pub midi_out: Arc<RwLock<Option<MidiOutput>>>,
|
|
||||||
/// For emitting a metronome
|
|
||||||
#[cfg(feature = "port")] pub click_out: Arc<RwLock<Option<AudioOutput>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Contains memoized renders of clock values.
|
|
||||||
///
|
|
||||||
/// Performance optimization.
|
|
||||||
#[derive(Debug)] pub struct ClockView {
|
|
||||||
pub sr: Memo<Option<(bool, f64)>, String>,
|
|
||||||
pub buf: Memo<Option<f64>, String>,
|
|
||||||
pub lat: Memo<Option<f64>, String>,
|
|
||||||
pub bpm: Memo<Option<f64>, String>,
|
|
||||||
pub beat: Memo<Option<f64>, String>,
|
|
||||||
pub time: Memo<Option<f64>, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Arranger.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let arranger = tek_device::Arrangement::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Default, Debug)] pub struct Arrangement {
|
|
||||||
/// Project name.
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Base color.
|
|
||||||
pub color: ItemTheme,
|
|
||||||
/// JACK client handle.
|
|
||||||
pub jack: Jack<'static>,
|
|
||||||
/// FIXME a render of the project arrangement, redrawn on update.
|
|
||||||
/// TODO rename to "render_cache" or smth
|
|
||||||
pub arranger: Arc<RwLock<Buffer>>,
|
|
||||||
/// Display size
|
|
||||||
pub size: Measure<TuiOut>,
|
|
||||||
/// Display size of clips area
|
|
||||||
pub size_inner: Measure<TuiOut>,
|
|
||||||
/// Source of time
|
|
||||||
#[cfg(feature = "clock")] pub clock: Clock,
|
|
||||||
/// Allows one MIDI clip to be edited
|
|
||||||
#[cfg(feature = "editor")] pub editor: Option<MidiEditor>,
|
|
||||||
/// List of global midi inputs
|
|
||||||
#[cfg(feature = "port")] pub midi_ins: Vec<MidiInput>,
|
|
||||||
/// List of global midi outputs
|
|
||||||
#[cfg(feature = "port")] pub midi_outs: Vec<MidiOutput>,
|
|
||||||
/// List of global audio inputs
|
|
||||||
#[cfg(feature = "port")] pub audio_ins: Vec<AudioInput>,
|
|
||||||
/// List of global audio outputs
|
|
||||||
#[cfg(feature = "port")] pub audio_outs: Vec<AudioOutput>,
|
|
||||||
/// Selected UI element
|
|
||||||
#[cfg(feature = "select")] pub selection: Selection,
|
|
||||||
/// Last track number (to avoid duplicate port names)
|
|
||||||
#[cfg(feature = "track")] pub track_last: usize,
|
|
||||||
/// List of tracks
|
|
||||||
#[cfg(feature = "track")] pub tracks: Vec<Track>,
|
|
||||||
/// Scroll offset of tracks
|
|
||||||
#[cfg(feature = "track")] pub track_scroll: usize,
|
|
||||||
/// List of scenes
|
|
||||||
#[cfg(feature = "scene")] pub scenes: Vec<Scene>,
|
|
||||||
/// Scroll offset of scenes
|
|
||||||
#[cfg(feature = "scene")] pub scene_scroll: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Browses for phrase to import/export
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let browser = tek_device::Browser::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq)]
|
|
||||||
pub struct Browse {
|
|
||||||
pub cwd: PathBuf,
|
|
||||||
pub dirs: Vec<(OsString, String)>,
|
|
||||||
pub files: Vec<(OsString, String)>,
|
|
||||||
pub filter: String,
|
|
||||||
pub index: usize,
|
|
||||||
pub scroll: usize,
|
|
||||||
pub size: Measure<TuiOut>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)] pub(crate) struct EntriesIterator<'a> {
|
|
||||||
pub browser: &'a Browse,
|
|
||||||
pub offset: usize,
|
|
||||||
pub length: usize,
|
|
||||||
pub index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)] pub enum BrowseTarget {
|
|
||||||
SaveProject,
|
|
||||||
LoadProject,
|
|
||||||
ImportSample(Arc<RwLock<Option<Sample>>>),
|
|
||||||
ExportSample(Arc<RwLock<Option<Sample>>>),
|
|
||||||
ImportClip(Arc<RwLock<Option<MidiClip>>>),
|
|
||||||
ExportClip(Arc<RwLock<Option<MidiClip>>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A MIDI sequence.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let clip = tek_device::MidiClip::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, Default)] pub struct MidiClip {
|
|
||||||
pub uuid: uuid::Uuid,
|
|
||||||
/// Name of clip
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Temporal resolution in pulses per quarter note
|
|
||||||
pub ppq: usize,
|
|
||||||
/// Length of clip in pulses
|
|
||||||
pub length: usize,
|
|
||||||
/// Notes in clip
|
|
||||||
pub notes: MidiData,
|
|
||||||
/// Whether to loop the clip or play it once
|
|
||||||
pub looped: bool,
|
|
||||||
/// Start of loop
|
|
||||||
pub loop_start: usize,
|
|
||||||
/// Length of loop
|
|
||||||
pub loop_length: usize,
|
|
||||||
/// All notes are displayed with minimum length
|
|
||||||
pub percussive: bool,
|
|
||||||
/// Identifying color of clip
|
|
||||||
pub color: ItemTheme,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A device that can be plugged into the chain.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let device = tek_engie::Device::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub enum Device {
|
|
||||||
#[default]
|
|
||||||
Bypass,
|
|
||||||
Mute,
|
|
||||||
#[cfg(feature = "sampler")]
|
|
||||||
Sampler(Sampler),
|
|
||||||
#[cfg(feature = "lv2")] // TODO
|
|
||||||
Lv2(Lv2),
|
|
||||||
#[cfg(feature = "vst2")] // TODO
|
|
||||||
Vst2,
|
|
||||||
#[cfg(feature = "vst3")] // TODO
|
|
||||||
Vst3,
|
|
||||||
#[cfg(feature = "clap")] // TODO
|
|
||||||
Clap,
|
|
||||||
#[cfg(feature = "sf2")] // TODO
|
|
||||||
Sf2,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Some sort of wrapper?
|
|
||||||
pub struct DeviceAudio<'a>(pub &'a mut Device);
|
|
||||||
|
|
||||||
/// Contains state for viewing and editing a clip.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let mut editor = tek_device::MidiEditor {
|
|
||||||
/// mode: PianoHorizontal::new(Some(&Arc::new(RwLock::new(MidiClip::stop_all())))),
|
|
||||||
/// size: Default::default(),
|
|
||||||
/// //keys: Default::default(),
|
|
||||||
/// };
|
|
||||||
/// let _ = editor.put_note(true);
|
|
||||||
/// let _ = editor.put_note(false);
|
|
||||||
/// let _ = editor.clip_status();
|
|
||||||
/// let _ = editor.edit_status();
|
|
||||||
/// ```
|
|
||||||
pub struct MidiEditor {
|
|
||||||
/// Size of editor on screen
|
|
||||||
pub size: Measure<TuiOut>,
|
|
||||||
/// View mode and state of editor
|
|
||||||
pub mode: PianoHorizontal,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A clip, rendered as a horizontal piano roll.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let piano = tek_device::PianoHorizontal::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Clone, Default)] pub struct PianoHorizontal {
|
|
||||||
pub clip: Option<Arc<RwLock<MidiClip>>>,
|
|
||||||
/// Buffer where the whole clip is rerendered on change
|
|
||||||
pub buffer: Arc<RwLock<BigBuffer>>,
|
|
||||||
/// Size of actual notes area
|
|
||||||
pub size: Measure<TuiOut>,
|
|
||||||
/// The display window
|
|
||||||
pub range: MidiSelection,
|
|
||||||
/// The note cursor
|
|
||||||
pub point: MidiCursor,
|
|
||||||
/// The highlight color palette
|
|
||||||
pub color: ItemTheme,
|
|
||||||
/// Width of the keyboard
|
|
||||||
pub keys_width: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 12 piano keys, some highlighted.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let keys = tek_device::OctaveVertical::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Copy, Clone)] pub struct OctaveVertical {
|
|
||||||
pub on: [bool; 12],
|
|
||||||
pub colors: [Color; 3]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A LV2 plugin.
|
|
||||||
#[derive(Debug)] #[cfg(feature = "lv2")] pub struct Lv2 {
|
|
||||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
|
||||||
pub jack: Jack<'static>,
|
|
||||||
pub name: Arc<str>,
|
|
||||||
pub path: Option<Arc<str>>,
|
|
||||||
pub selected: usize,
|
|
||||||
pub mapping: bool,
|
|
||||||
pub midi_ins: Vec<Port<MidiIn>>,
|
|
||||||
pub midi_outs: Vec<Port<MidiOut>>,
|
|
||||||
pub audio_ins: Vec<Port<AudioIn>>,
|
|
||||||
pub audio_outs: Vec<Port<AudioOut>>,
|
|
||||||
|
|
||||||
pub lv2_world: livi::World,
|
|
||||||
pub lv2_instance: livi::Instance,
|
|
||||||
pub lv2_plugin: livi::Plugin,
|
|
||||||
pub lv2_features: Arc<livi::Features>,
|
|
||||||
pub lv2_port_list: Vec<livi::Port>,
|
|
||||||
pub lv2_input_buffer: Vec<livi::event::LV2AtomSequence>,
|
|
||||||
pub lv2_ui_thread: Option<JoinHandle<()>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A LV2 plugin's X11 UI.
|
|
||||||
#[cfg(feature = "lv2_gui")] pub struct LV2PluginUI {
|
|
||||||
pub window: Option<Window>
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)] pub enum MeteringMode {
|
|
||||||
#[default] Rms,
|
|
||||||
Log10,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
pub struct Log10Meter(pub f32);
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
pub struct RmsMeter(pub f32);
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub enum MixingMode {
|
|
||||||
#[default] Summing,
|
|
||||||
Average,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A clip pool.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let pool = tek_device::Pool::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug)] pub struct Pool {
|
|
||||||
pub visible: bool,
|
|
||||||
/// Selected clip
|
|
||||||
pub clip: AtomicUsize,
|
|
||||||
/// Mode switch
|
|
||||||
pub mode: Option<PoolMode>,
|
|
||||||
/// Embedded file browse
|
|
||||||
#[cfg(feature = "browse")] pub browse: Option<Browse>,
|
|
||||||
/// Collection of MIDI clips.
|
|
||||||
#[cfg(feature = "clip")] pub clips: Arc<RwLock<Vec<Arc<RwLock<MidiClip>>>>>,
|
|
||||||
/// Collection of sound samples.
|
|
||||||
#[cfg(feature = "sampler")] pub samples: Arc<RwLock<Vec<Arc<RwLock<Sample>>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Displays and edits clip length.
|
|
||||||
#[derive(Clone, Debug, Default)] pub struct ClipLength {
|
|
||||||
/// Pulses per beat (quaver)
|
|
||||||
pub ppq: usize,
|
|
||||||
/// Beats per bar
|
|
||||||
pub bpb: usize,
|
|
||||||
/// Length of clip in pulses
|
|
||||||
pub pulses: usize,
|
|
||||||
/// Selected subdivision
|
|
||||||
pub focus: Option<ClipLengthFocus>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Some sort of wrapper again?
|
|
||||||
pub struct PoolView<'a>(pub &'a Pool);
|
|
||||||
|
|
||||||
/// Audio input port.
|
|
||||||
#[derive(Debug)] pub struct AudioInput {
|
|
||||||
/// Handle to JACK client, for receiving reconnect events.
|
|
||||||
pub jack: Jack<'static>,
|
|
||||||
/// Port name
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Port handle.
|
|
||||||
pub port: Port<AudioIn>,
|
|
||||||
/// List of ports to connect to.
|
|
||||||
pub connections: Vec<Connect>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Audio output port.
|
|
||||||
#[derive(Debug)] pub struct AudioOutput {
|
|
||||||
/// Handle to JACK client, for receiving reconnect events.
|
|
||||||
pub jack: Jack<'static>,
|
|
||||||
/// Port name
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Port handle.
|
|
||||||
pub port: Port<AudioOut>,
|
|
||||||
/// List of ports to connect to.
|
|
||||||
pub connections: Vec<Connect>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// MIDI input port.
|
|
||||||
#[derive(Debug)] pub struct MidiInput {
|
|
||||||
/// Handle to JACK client, for receiving reconnect events.
|
|
||||||
pub jack: Jack<'static>,
|
|
||||||
/// Port name
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Port handle.
|
|
||||||
pub port: Port<MidiIn>,
|
|
||||||
/// List of currently held notes.
|
|
||||||
pub held: Arc<RwLock<[bool;128]>>,
|
|
||||||
/// List of ports to connect to.
|
|
||||||
pub connections: Vec<Connect>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// MIDI output port.
|
|
||||||
#[derive(Debug)] pub struct MidiOutput {
|
|
||||||
/// Handle to JACK client, for receiving reconnect events.
|
|
||||||
pub jack: Jack<'static>,
|
|
||||||
/// Port name
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Port handle.
|
|
||||||
pub port: Port<MidiOut>,
|
|
||||||
/// List of ports to connect to.
|
|
||||||
pub connections: Vec<Connect>,
|
|
||||||
/// List of currently held notes.
|
|
||||||
pub held: Arc<RwLock<[bool;128]>>,
|
|
||||||
/// Buffer
|
|
||||||
pub note_buffer: Vec<u8>,
|
|
||||||
/// Buffer
|
|
||||||
pub output_buffer: Vec<Vec<Vec<u8>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Port connection manager.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let connect = tek_engine::Connect::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Clone, Debug, Default)] pub struct Connect {
|
|
||||||
pub name: Option<ConnectName>,
|
|
||||||
pub scope: Option<ConnectScope>,
|
|
||||||
pub status: Arc<RwLock<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>>,
|
|
||||||
pub info: Arc<str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Plays [Voice]s from [Sample]s.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let sampler = tek_device::Sampler::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct Sampler {
|
|
||||||
/// Name of sampler.
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Device color.
|
|
||||||
pub color: ItemTheme,
|
|
||||||
/// Audio input ports. Samples get recorded here.
|
|
||||||
#[cfg(feature = "port")] pub audio_ins: Vec<AudioInput>,
|
|
||||||
/// Audio input meters.
|
|
||||||
#[cfg(feature = "meter")] pub input_meters: Vec<f32>,
|
|
||||||
/// Sample currently being recorded.
|
|
||||||
pub recording: Option<(usize, Option<Arc<RwLock<Sample>>>)>,
|
|
||||||
/// Recording buffer.
|
|
||||||
pub buffer: Vec<Vec<f32>>,
|
|
||||||
/// Samples mapped to MIDI notes.
|
|
||||||
pub mapped: [Option<Arc<RwLock<Sample>>>;128],
|
|
||||||
/// Samples that are not mapped to MIDI notes.
|
|
||||||
pub unmapped: Vec<Arc<RwLock<Sample>>>,
|
|
||||||
/// Sample currently being edited.
|
|
||||||
pub editing: Option<Arc<RwLock<Sample>>>,
|
|
||||||
/// MIDI input port. Triggers sample playback.
|
|
||||||
#[cfg(feature = "port")] pub midi_in: Option<MidiInput>,
|
|
||||||
/// Collection of currently playing instances of samples.
|
|
||||||
pub voices: Arc<RwLock<Vec<Voice>>>,
|
|
||||||
/// Audio output ports. Voices get played here.
|
|
||||||
#[cfg(feature = "port")] pub audio_outs: Vec<AudioOutput>,
|
|
||||||
/// Audio output meters.
|
|
||||||
#[cfg(feature = "meter")] pub output_meters: Vec<f32>,
|
|
||||||
/// How to mix the voices.
|
|
||||||
pub mixing_mode: MixingMode,
|
|
||||||
/// How to meter the inputs and outputs.
|
|
||||||
pub metering_mode: MeteringMode,
|
|
||||||
/// Fixed gain applied to all output.
|
|
||||||
pub output_gain: f32,
|
|
||||||
/// Currently active modal, if any.
|
|
||||||
pub mode: Option<SamplerMode>,
|
|
||||||
/// Size of rendered sampler.
|
|
||||||
pub size: Measure<TuiOut>,
|
|
||||||
/// Lowest note displayed.
|
|
||||||
pub note_lo: AtomicUsize,
|
|
||||||
/// Currently selected note.
|
|
||||||
pub note_pt: AtomicUsize,
|
|
||||||
/// Selected note as row/col.
|
|
||||||
pub cursor: (AtomicUsize, AtomicUsize),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A sound cut.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let sample = tek_device::Sample::default();
|
|
||||||
/// let sample = tek_device::Sample::new("test", 0, 0, vec![]);
|
|
||||||
/// ```
|
|
||||||
#[derive(Default, Debug)] pub struct Sample {
|
|
||||||
pub name: Arc<str>,
|
|
||||||
pub start: usize,
|
|
||||||
pub end: usize,
|
|
||||||
pub channels: Vec<Vec<f32>>,
|
|
||||||
pub rate: Option<usize>,
|
|
||||||
pub gain: f32,
|
|
||||||
pub color: ItemTheme,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A currently playing instance of a sample.
|
|
||||||
#[derive(Default, Debug, Clone)]
|
|
||||||
pub struct Voice {
|
|
||||||
pub sample: Arc<RwLock<Sample>>,
|
|
||||||
pub after: usize,
|
|
||||||
pub position: usize,
|
|
||||||
pub velocity: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AddSampleModal {
|
|
||||||
pub exited: bool,
|
|
||||||
pub dir: PathBuf,
|
|
||||||
pub subdirs: Vec<OsString>,
|
|
||||||
pub files: Vec<OsString>,
|
|
||||||
pub cursor: usize,
|
|
||||||
pub offset: usize,
|
|
||||||
pub sample: Arc<RwLock<Sample>>,
|
|
||||||
pub voices: Arc<RwLock<Vec<Voice>>>,
|
|
||||||
pub _search: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A scene consists of a set of clips to play together.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let scene: tek_device::Scene = Default::default();
|
|
||||||
/// let _ = scene.pulses();
|
|
||||||
/// let _ = scene.is_playing(&[]);
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct Scene {
|
|
||||||
/// Name of scene
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Identifying color of scene
|
|
||||||
pub color: ItemTheme,
|
|
||||||
/// Clips in scene, one per track
|
|
||||||
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the current user selection in the arranger
|
|
||||||
#[derive(PartialEq, Clone, Copy, Debug, Default)]
|
|
||||||
pub enum Selection {
|
|
||||||
#[default]
|
|
||||||
/// Nothing is selected
|
|
||||||
Nothing,
|
|
||||||
/// The whole mix is selected
|
|
||||||
Mix,
|
|
||||||
/// A MIDI input is selected.
|
|
||||||
Input(usize),
|
|
||||||
/// A MIDI output is selected.
|
|
||||||
Output(usize),
|
|
||||||
/// A scene is selected.
|
|
||||||
#[cfg(feature = "scene")] Scene(usize),
|
|
||||||
/// A track is selected.
|
|
||||||
#[cfg(feature = "track")] Track(usize),
|
|
||||||
/// A clip (track × scene) is selected.
|
|
||||||
#[cfg(feature = "track")] TrackClip { track: usize, scene: usize },
|
|
||||||
/// A track's MIDI input connection is selected.
|
|
||||||
#[cfg(feature = "track")] TrackInput { track: usize, port: usize },
|
|
||||||
/// A track's MIDI output connection is selected.
|
|
||||||
#[cfg(feature = "track")] TrackOutput { track: usize, port: usize },
|
|
||||||
/// A track device slot is selected.
|
|
||||||
#[cfg(feature = "track")] TrackDevice { track: usize, device: usize },
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Contains state for playing a clip
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let clip = tek_device::MidiClip::default();
|
|
||||||
/// println!("Empty clip: {clip:?}");
|
|
||||||
///
|
|
||||||
/// let clip = tek_device::MidiClip::stop_all();
|
|
||||||
/// println!("Panic clip: {clip:?}");
|
|
||||||
///
|
|
||||||
/// let mut clip = tek_device::MidiClip::new("clip", true, 1, None, None);
|
|
||||||
/// clip.set_length(96);
|
|
||||||
/// clip.toggle_loop();
|
|
||||||
/// clip.record_event(12, midly::MidiMessage::NoteOn { key: 36.into(), vel: 100.into() });
|
|
||||||
/// assert!(clip.contains_note_on(36.into(), 6, 18));
|
|
||||||
/// assert_eq!(&clip.notes, &clip.duplicate().notes);
|
|
||||||
///
|
|
||||||
/// let clip = std::sync::Arc::new(clip);
|
|
||||||
/// assert_eq!(clip.clone(), clip);
|
|
||||||
///
|
|
||||||
/// let sequencer = tek_device::Sequencer::default();
|
|
||||||
/// println!("{sequencer:?}");
|
|
||||||
/// ```
|
|
||||||
pub struct Sequencer {
|
|
||||||
/// State of clock and playhead
|
|
||||||
#[cfg(feature = "clock")] pub clock: Clock,
|
|
||||||
/// Start time and clip being played
|
|
||||||
#[cfg(feature = "clip")] pub play_clip: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
|
||||||
/// Start time and next clip
|
|
||||||
#[cfg(feature = "clip")] pub next_clip: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
|
||||||
/// Record from MIDI ports to current sequence.
|
|
||||||
#[cfg(feature = "port")] pub midi_ins: Vec<MidiInput>,
|
|
||||||
/// Play from current sequence to MIDI ports
|
|
||||||
#[cfg(feature = "port")] pub midi_outs: Vec<MidiOutput>,
|
|
||||||
/// 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)
|
|
||||||
/// Notes currently held at input
|
|
||||||
pub notes_in: Arc<RwLock<[bool; 128]>>,
|
|
||||||
/// Notes currently held at output
|
|
||||||
pub notes_out: Arc<RwLock<[bool; 128]>>,
|
|
||||||
/// MIDI output buffer
|
|
||||||
pub note_buf: Vec<u8>,
|
|
||||||
/// MIDI output buffer
|
|
||||||
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A track consists of a sequencer and zero or more devices chained after it.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let track: tek_device::Track = Default::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct Track {
|
|
||||||
/// Name of track
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Identifying color of track
|
|
||||||
pub color: ItemTheme,
|
|
||||||
/// Preferred width of track column
|
|
||||||
pub width: usize,
|
|
||||||
/// MIDI sequencer state
|
|
||||||
pub sequencer: Sequencer,
|
|
||||||
/// Device chain
|
|
||||||
pub devices: Vec<Device>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commands supported by [Browse]
|
|
||||||
//#[derive(Debug, Clone, PartialEq)]
|
|
||||||
//pub enum BrowseCommand {
|
|
||||||
//Begin,
|
|
||||||
//Cancel,
|
|
||||||
//Confirm,
|
|
||||||
//Select(usize),
|
|
||||||
//Chdir(PathBuf),
|
|
||||||
//Filter(Arc<str>),
|
|
||||||
//}
|
|
||||||
|
|
||||||
/// Modes for clip pool
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum PoolMode {
|
|
||||||
/// Renaming a pattern
|
|
||||||
Rename(usize, Arc<str>),
|
|
||||||
/// Editing the length of a pattern
|
|
||||||
Length(usize, usize, ClipLengthFocus),
|
|
||||||
/// Load clip from disk
|
|
||||||
Import(usize, Browse),
|
|
||||||
/// Save clip to disk
|
|
||||||
Export(usize, Browse),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Focused field of `ClipLength`
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub enum ClipLengthFocus {
|
|
||||||
/// Editing the number of bars
|
|
||||||
Bar,
|
|
||||||
/// Editing the number of beats
|
|
||||||
Beat,
|
|
||||||
/// Editing the number of ticks
|
|
||||||
Tick,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum ConnectName {
|
|
||||||
/** Exact match */
|
|
||||||
Exact(Arc<str>),
|
|
||||||
/** Match regular expression */
|
|
||||||
RegExp(Arc<str>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectScope {
|
|
||||||
One,
|
|
||||||
All
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectStatus {
|
|
||||||
Missing,
|
|
||||||
Disconnected,
|
|
||||||
Connected,
|
|
||||||
Mismatch,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum SamplerMode {
|
|
||||||
// Load sample from path
|
|
||||||
Import(usize, Browse),
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
pub type MidiData = Vec<Vec<MidiMessage>>;
|
|
||||||
|
|
||||||
pub type ClipPool = Vec<Arc<RwLock<MidiClip>>>;
|
|
||||||
|
|
||||||
pub type CollectedMidiInput<'a> = Vec<Vec<(u32, Result<LiveEvent<'a>, MidiError>)>>;
|
|
||||||
|
|
||||||
pub type SceneWith<'a, T> = (usize, &'a Scene, usize, usize, T);
|
|
||||||
|
|
||||||
pub type MidiSample = (Option<u7>, Arc<RwLock<crate::Sample>>);
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "tek_engine"
|
|
||||||
edition = { workspace = true }
|
|
||||||
version = { workspace = true }
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
path = "engine.rs"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")']
|
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
atomic_float = { workspace = true }
|
|
||||||
jack = { workspace = true }
|
|
||||||
midly = { workspace = true }
|
|
||||||
tengri = { workspace = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
proptest = { workspace = true }
|
|
||||||
proptest-derive = { workspace = true }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
196
engine/engine.rs
196
engine/engine.rs
|
|
@ -1,196 +0,0 @@
|
||||||
pub extern crate jack;
|
|
||||||
pub extern crate midly;
|
|
||||||
pub extern crate tengri;
|
|
||||||
mod engine_structs; pub use self::engine_structs::*;
|
|
||||||
mod engine_traits; pub use self::engine_traits::*;
|
|
||||||
mod engine_impls;
|
|
||||||
mod jack_structs; pub use self::jack_structs::*;
|
|
||||||
mod jack_traits; pub use self::jack_traits::*;
|
|
||||||
mod jack_types; pub use self::jack_types::*;
|
|
||||||
mod jack_impls;
|
|
||||||
pub(crate) use ::{
|
|
||||||
tengri::{Usually, from, output::PerfModel},
|
|
||||||
atomic_float::AtomicF64,
|
|
||||||
std::{
|
|
||||||
fmt::Debug, ops::{Add, Sub, Mul, Div, Rem},
|
|
||||||
sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed}},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
pub use ::{
|
|
||||||
jack::{*, contrib::{*, ClosureProcessHandler}},
|
|
||||||
midly::{Smf, TrackEventKind, MidiMessage, Error as MidiError, num::*, live::*,}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const DEFAULT_PPQ: f64 = 96.0;
|
|
||||||
|
|
||||||
/// FIXME: remove this and use PPQ from timebase everywhere:
|
|
||||||
pub const PPQ: usize = 96;
|
|
||||||
|
|
||||||
/// (pulses, name), assuming 96 PPQ
|
|
||||||
pub const NOTE_DURATIONS: [(usize, &str);26] = [
|
|
||||||
(1, "1/384"), (2, "1/192"),
|
|
||||||
(3, "1/128"), (4, "1/96"),
|
|
||||||
(6, "1/64"), (8, "1/48"),
|
|
||||||
(12, "1/32"), (16, "1/24"),
|
|
||||||
(24, "1/16"), (32, "1/12"),
|
|
||||||
(48, "1/8"), (64, "1/6"),
|
|
||||||
(96, "1/4"), (128, "1/3"),
|
|
||||||
(192, "1/2"), (256, "2/3"),
|
|
||||||
(384, "1/1"), (512, "4/3"),
|
|
||||||
(576, "3/2"), (768, "2/1"),
|
|
||||||
(1152, "3/1"), (1536, "4/1"),
|
|
||||||
(2304, "6/1"), (3072, "8/1"),
|
|
||||||
(3456, "9/1"), (6144, "16/1"),
|
|
||||||
];
|
|
||||||
|
|
||||||
pub const 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",
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Return boxed iterator of MIDI events
|
|
||||||
pub fn parse_midi_input <'a> (input: ::jack::MidiIter<'a>)
|
|
||||||
-> Box<dyn Iterator<Item=(usize, LiveEvent<'a>, &'a [u8])> + 'a>
|
|
||||||
{
|
|
||||||
Box::new(input.map(|::jack::RawMidi { time, bytes }|(
|
|
||||||
time as usize,
|
|
||||||
LiveEvent::parse(bytes).unwrap(),
|
|
||||||
bytes
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add "all notes off" to the start of a buffer.
|
|
||||||
pub fn all_notes_off (output: &mut [Vec<Vec<u8>>]) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update notes_in array
|
|
||||||
pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) {
|
|
||||||
match message {
|
|
||||||
MidiMessage::NoteOn { key, .. } => { keys[key.as_int() as usize] = true; }
|
|
||||||
MidiMessage::NoteOff { key, .. } => { keys[key.as_int() as usize] = false; },
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the next shorter length
|
|
||||||
pub fn note_duration_prev (pulses: usize) -> usize {
|
|
||||||
for (length, _) in NOTE_DURATIONS.iter().rev() { if *length < pulses { return *length } }
|
|
||||||
pulses
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the next longer length
|
|
||||||
pub fn note_duration_next (pulses: usize) -> usize {
|
|
||||||
for (length, _) in NOTE_DURATIONS.iter() { if *length > pulses { return *length } }
|
|
||||||
pulses
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn note_duration_to_name (pulses: usize) -> &'static str {
|
|
||||||
for (length, name) in NOTE_DURATIONS.iter() { if *length == pulses { return name } }
|
|
||||||
""
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn note_pitch_to_name (n: usize) -> &'static str {
|
|
||||||
if n > 127 {
|
|
||||||
panic!("to_note_name({n}): must be 0-127");
|
|
||||||
}
|
|
||||||
NOTE_NAMES[n]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//macro_rules! impl_port {
|
|
||||||
//($Name:ident : $Spec:ident -> $Pair:ident |$jack:ident, $name:ident|$port:expr) => {
|
|
||||||
//#[derive(Debug)] pub struct $Name {
|
|
||||||
///// Handle to JACK client, for receiving reconnect events.
|
|
||||||
//jack: Jack<'static>,
|
|
||||||
///// Port name
|
|
||||||
//name: Arc<str>,
|
|
||||||
///// Port handle.
|
|
||||||
//port: Port<$Spec>,
|
|
||||||
///// List of ports to connect to.
|
|
||||||
//conn: Vec<PortConnect>
|
|
||||||
//}
|
|
||||||
//impl AsRef<Port<$Spec>> for $Name {
|
|
||||||
//fn as_ref (&self) -> &Port<$Spec> { &self.port }
|
|
||||||
//}
|
|
||||||
//impl $Name {
|
|
||||||
//pub fn new ($jack: &Jack, name: impl AsRef<str>, connect: &[PortConnect])
|
|
||||||
//-> Usually<Self>
|
|
||||||
//{
|
|
||||||
//let $name = name.as_ref();
|
|
||||||
//let jack = $jack.clone();
|
|
||||||
//let port = $port?;
|
|
||||||
//let name = $name.into();
|
|
||||||
//let conn = connect.to_vec();
|
|
||||||
//let port = Self { jack, port, name, conn };
|
|
||||||
//port.connect_to_matching()?;
|
|
||||||
//Ok(port)
|
|
||||||
//}
|
|
||||||
//pub fn name (&self) -> &Arc<str> { &self.name }
|
|
||||||
//pub fn port (&self) -> &Port<$Spec> { &self.port }
|
|
||||||
//pub fn port_mut (&mut self) -> &mut Port<$Spec> { &mut self.port }
|
|
||||||
//pub fn into_port (self) -> Port<$Spec> { self.port }
|
|
||||||
//pub fn close (self) -> Usually<()> {
|
|
||||||
//let Self { jack, port, .. } = self;
|
|
||||||
//Ok(jack.with_client(|client|client.unregister_port(port))?)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//impl HasJack<'static> for $Name {
|
|
||||||
//fn jack (&self) -> &'static Jack<'static> { &self.jack }
|
|
||||||
//}
|
|
||||||
//impl JackPort<'static> for $Name {
|
|
||||||
//type Port = $Spec;
|
|
||||||
//type Pair = $Pair;
|
|
||||||
//fn port (&self) -> &Port<$Spec> { &self.port }
|
|
||||||
//}
|
|
||||||
//impl ConnectTo<'static, &str> for $Name {
|
|
||||||
//fn connect_to (&self, to: &str) -> Usually<PortConnectStatus> {
|
|
||||||
//self.with_client(|c|if let Some(ref port) = c.port_by_name(to.as_ref()) {
|
|
||||||
//self.connect_to(port)
|
|
||||||
//} else {
|
|
||||||
//Ok(Missing)
|
|
||||||
//})
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//impl ConnectTo<'static, &Port<Unowned>> for $Name {
|
|
||||||
//fn connect_to (&self, port: &Port<Unowned>) -> Usually<PortConnectStatus> {
|
|
||||||
//self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) {
|
|
||||||
//Connected
|
|
||||||
//} else if let Ok(_) = c.connect_ports(port, &self.port) {
|
|
||||||
//Connected
|
|
||||||
//} else {
|
|
||||||
//Mismatch
|
|
||||||
//}))
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//impl ConnectTo<'static, &Port<$Pair>> for $Name {
|
|
||||||
//fn connect_to (&self, port: &Port<$Pair>) -> Usually<PortConnectStatus> {
|
|
||||||
//self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) {
|
|
||||||
//Connected
|
|
||||||
//} else if let Ok(_) = c.connect_ports(port, &self.port) {
|
|
||||||
//Connected
|
|
||||||
//} else {
|
|
||||||
//Mismatch
|
|
||||||
//}))
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//impl ConnectAuto<'static> for $Name {
|
|
||||||
//fn connections (&self) -> &[PortConnect] {
|
|
||||||
//&self.conn
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//};
|
|
||||||
//}
|
|
||||||
|
|
@ -1,363 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl Gettable<bool> for AtomicBool {
|
|
||||||
fn get (&self) -> bool {
|
|
||||||
self.load(Relaxed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InteriorMutable<bool> for AtomicBool {
|
|
||||||
fn set (&self, value: bool) -> bool {
|
|
||||||
self.swap(value, Relaxed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Gettable<usize> for AtomicUsize {
|
|
||||||
fn get (&self) -> usize {
|
|
||||||
self.load(Relaxed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InteriorMutable<usize> for AtomicUsize {
|
|
||||||
fn set (&self, value: usize) -> usize {
|
|
||||||
self.swap(value, Relaxed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//impl<T, U: Has<Option<T>>> MaybeHas<T> for U {
|
|
||||||
//fn get (&self) -> Option<&T> {
|
|
||||||
//Has::<Option<T>>::get(self).as_ref()
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
impl Default for MidiCursor {
|
|
||||||
fn default () -> Self {
|
|
||||||
Self {
|
|
||||||
time_pos: Arc::new(0.into()),
|
|
||||||
note_pos: Arc::new(36.into()),
|
|
||||||
note_len: Arc::new(24.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NotePoint for MidiCursor {
|
|
||||||
fn note_len (&self) -> &AtomicUsize {
|
|
||||||
&self.note_len
|
|
||||||
}
|
|
||||||
fn note_pos (&self) -> &AtomicUsize {
|
|
||||||
&self.note_pos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimePoint for MidiCursor {
|
|
||||||
fn time_pos (&self) -> &AtomicUsize {
|
|
||||||
self.time_pos.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: NotePoint + TimePoint> MidiPoint for T {}
|
|
||||||
|
|
||||||
from!(MidiSelection: |data:(usize, bool)| Self {
|
|
||||||
time_len: Arc::new(0.into()),
|
|
||||||
note_axis: Arc::new(0.into()),
|
|
||||||
note_lo: Arc::new(0.into()),
|
|
||||||
time_axis: Arc::new(0.into()),
|
|
||||||
time_start: Arc::new(0.into()),
|
|
||||||
time_zoom: Arc::new(data.0.into()),
|
|
||||||
time_lock: Arc::new(data.1.into()),
|
|
||||||
});
|
|
||||||
|
|
||||||
impl<T: TimeRange + NoteRange> MidiRange for T {}
|
|
||||||
|
|
||||||
impl TimeRange for MidiSelection {
|
|
||||||
fn time_len (&self) -> &AtomicUsize { &self.time_len }
|
|
||||||
fn time_zoom (&self) -> &AtomicUsize { &self.time_zoom }
|
|
||||||
fn time_lock (&self) -> &AtomicBool { &self.time_lock }
|
|
||||||
fn time_start (&self) -> &AtomicUsize { &self.time_start }
|
|
||||||
fn time_axis (&self) -> &AtomicUsize { &self.time_axis }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NoteRange for MidiSelection {
|
|
||||||
fn note_lo (&self) -> &AtomicUsize { &self.note_lo }
|
|
||||||
fn note_axis (&self) -> &AtomicUsize { &self.note_axis }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Moment {
|
|
||||||
pub fn zero (timebase: &Arc<Timebase>) -> Self {
|
|
||||||
Self { usec: 0.into(), sample: 0.into(), pulse: 0.into(), timebase: timebase.clone() }
|
|
||||||
}
|
|
||||||
pub fn from_usec (timebase: &Arc<Timebase>, usec: f64) -> Self {
|
|
||||||
Self {
|
|
||||||
usec: usec.into(),
|
|
||||||
sample: timebase.sr.usecs_to_sample(usec).into(),
|
|
||||||
pulse: timebase.usecs_to_pulse(usec).into(),
|
|
||||||
timebase: timebase.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn from_sample (timebase: &Arc<Timebase>, sample: f64) -> Self {
|
|
||||||
Self {
|
|
||||||
sample: sample.into(),
|
|
||||||
usec: timebase.sr.samples_to_usec(sample).into(),
|
|
||||||
pulse: timebase.samples_to_pulse(sample).into(),
|
|
||||||
timebase: timebase.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn from_pulse (timebase: &Arc<Timebase>, pulse: f64) -> Self {
|
|
||||||
Self {
|
|
||||||
pulse: pulse.into(),
|
|
||||||
sample: timebase.pulses_to_sample(pulse).into(),
|
|
||||||
usec: timebase.pulses_to_usec(pulse).into(),
|
|
||||||
timebase: timebase.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[inline] pub fn update_from_usec (&self, usec: f64) {
|
|
||||||
self.usec.set(usec);
|
|
||||||
self.pulse.set(self.timebase.usecs_to_pulse(usec));
|
|
||||||
self.sample.set(self.timebase.sr.usecs_to_sample(usec));
|
|
||||||
}
|
|
||||||
#[inline] pub fn update_from_sample (&self, sample: f64) {
|
|
||||||
self.usec.set(self.timebase.sr.samples_to_usec(sample));
|
|
||||||
self.pulse.set(self.timebase.samples_to_pulse(sample));
|
|
||||||
self.sample.set(sample);
|
|
||||||
}
|
|
||||||
#[inline] pub fn update_from_pulse (&self, pulse: f64) {
|
|
||||||
self.usec.set(self.timebase.pulses_to_usec(pulse));
|
|
||||||
self.pulse.set(pulse);
|
|
||||||
self.sample.set(self.timebase.pulses_to_sample(pulse));
|
|
||||||
}
|
|
||||||
#[inline] pub fn format_beat (&self) -> Arc<str> {
|
|
||||||
self.timebase.format_beats_1(self.pulse.get()).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LaunchSync {
|
|
||||||
pub fn next (&self) -> f64 {
|
|
||||||
note_duration_next(self.get() as usize) as f64
|
|
||||||
}
|
|
||||||
pub fn prev (&self) -> f64 {
|
|
||||||
note_duration_prev(self.get() as usize) as f64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Quantize {
|
|
||||||
pub fn next (&self) -> f64 {
|
|
||||||
note_duration_next(self.get() as usize) as f64
|
|
||||||
}
|
|
||||||
pub fn prev (&self) -> f64 {
|
|
||||||
note_duration_prev(self.get() as usize) as f64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for TicksIterator {
|
|
||||||
type Item = (usize, usize);
|
|
||||||
fn next (&mut self) -> Option<Self::Item> {
|
|
||||||
loop {
|
|
||||||
if self.sample > self.end { return None }
|
|
||||||
let spp = self.spp;
|
|
||||||
let sample = self.sample as f64;
|
|
||||||
let start = self.start;
|
|
||||||
let end = self.end;
|
|
||||||
self.sample += 1;
|
|
||||||
//println!("{spp} {sample} {start} {end}");
|
|
||||||
let jitter = sample.rem_euclid(spp); // ramps
|
|
||||||
let next_jitter = (sample + 1.0).rem_euclid(spp);
|
|
||||||
if jitter > next_jitter { // at crossing:
|
|
||||||
let time = (sample as usize) % (end as usize-start as usize);
|
|
||||||
let tick = (sample / spp) as usize;
|
|
||||||
return Some((time, tick))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Timebase {
|
|
||||||
/// Specify sample rate, BPM and PPQ
|
|
||||||
pub fn new (
|
|
||||||
s: impl Into<SampleRate>,
|
|
||||||
b: impl Into<BeatsPerMinute>,
|
|
||||||
p: impl Into<PulsesPerQuaver>
|
|
||||||
) -> Self {
|
|
||||||
Self { sr: s.into(), bpm: b.into(), ppq: p.into() }
|
|
||||||
}
|
|
||||||
/// Iterate over ticks between start and end.
|
|
||||||
#[inline] pub fn pulses_between_samples (&self, start: usize, end: usize) -> TicksIterator {
|
|
||||||
TicksIterator { spp: self.samples_per_pulse(), sample: start, start, end }
|
|
||||||
}
|
|
||||||
/// Return the duration fo a beat in microseconds
|
|
||||||
#[inline] pub fn usec_per_beat (&self) -> f64 { 60_000_000f64 / self.bpm.get() }
|
|
||||||
/// Return the number of beats in a second
|
|
||||||
#[inline] pub fn beat_per_second (&self) -> f64 { self.bpm.get() / 60f64 }
|
|
||||||
/// Return the number of microseconds corresponding to a note of the given duration
|
|
||||||
#[inline] pub fn note_to_usec (&self, (num, den): (f64, f64)) -> f64 {
|
|
||||||
4.0 * self.usec_per_beat() * num / den
|
|
||||||
}
|
|
||||||
/// Return duration of a pulse in microseconds (BPM-dependent)
|
|
||||||
#[inline] pub fn pulse_per_usec (&self) -> f64 { self.ppq.get() / self.usec_per_beat() }
|
|
||||||
/// Return duration of a pulse in microseconds (BPM-dependent)
|
|
||||||
#[inline] pub fn usec_per_pulse (&self) -> f64 { self.usec_per_beat() / self.ppq.get() }
|
|
||||||
/// Return number of pulses to which a number of microseconds corresponds (BPM-dependent)
|
|
||||||
#[inline] pub fn usecs_to_pulse (&self, usec: f64) -> f64 { usec * self.pulse_per_usec() }
|
|
||||||
/// Convert a number of pulses to a sample number (SR- and BPM-dependent)
|
|
||||||
#[inline] pub fn pulses_to_usec (&self, pulse: f64) -> f64 { pulse / self.usec_per_pulse() }
|
|
||||||
/// Return number of pulses in a second (BPM-dependent)
|
|
||||||
#[inline] pub fn pulses_per_second (&self) -> f64 { self.beat_per_second() * self.ppq.get() }
|
|
||||||
/// Return fraction of a pulse to which a sample corresponds (SR- and BPM-dependent)
|
|
||||||
#[inline] pub fn pulses_per_sample (&self) -> f64 {
|
|
||||||
self.usec_per_pulse() / self.sr.usec_per_sample()
|
|
||||||
}
|
|
||||||
/// Return number of samples in a pulse (SR- and BPM-dependent)
|
|
||||||
#[inline] pub fn samples_per_pulse (&self) -> f64 {
|
|
||||||
self.sr.get() / self.pulses_per_second()
|
|
||||||
}
|
|
||||||
/// Convert a number of pulses to a sample number (SR- and BPM-dependent)
|
|
||||||
#[inline] pub fn pulses_to_sample (&self, p: f64) -> f64 {
|
|
||||||
self.pulses_per_sample() * p
|
|
||||||
}
|
|
||||||
/// Convert a number of samples to a pulse number (SR- and BPM-dependent)
|
|
||||||
#[inline] pub fn samples_to_pulse (&self, s: f64) -> f64 {
|
|
||||||
s / self.pulses_per_sample()
|
|
||||||
}
|
|
||||||
/// Return the number of samples corresponding to a note of the given duration
|
|
||||||
#[inline] pub fn note_to_samples (&self, note: (f64, f64)) -> f64 {
|
|
||||||
self.usec_to_sample(self.note_to_usec(note))
|
|
||||||
}
|
|
||||||
/// Return the number of samples corresponding to the given number of microseconds
|
|
||||||
#[inline] pub fn usec_to_sample (&self, usec: f64) -> f64 {
|
|
||||||
usec * self.sr.get() / 1000f64
|
|
||||||
}
|
|
||||||
/// Return the quantized position of a moment in time given a step
|
|
||||||
#[inline] pub fn quantize (&self, step: (f64, f64), time: f64) -> (f64, f64) {
|
|
||||||
let step = self.note_to_usec(step);
|
|
||||||
(time / step, time % step)
|
|
||||||
}
|
|
||||||
/// Quantize a collection of events
|
|
||||||
#[inline] pub fn quantize_into <E: Iterator<Item=(f64, f64)> + Sized, T> (
|
|
||||||
&self, step: (f64, f64), events: E
|
|
||||||
) -> Vec<(f64, f64)> {
|
|
||||||
events.map(|(time, event)|(self.quantize(step, time).0, event)).collect()
|
|
||||||
}
|
|
||||||
/// Format a number of pulses into Beat.Bar.Pulse starting from 0
|
|
||||||
#[inline] pub fn format_beats_0 (&self, pulse: f64) -> Arc<str> {
|
|
||||||
let pulse = pulse as usize;
|
|
||||||
let ppq = self.ppq.get() as usize;
|
|
||||||
let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
|
|
||||||
format!("{}.{}.{pulses:02}", beats / 4, beats % 4).into()
|
|
||||||
}
|
|
||||||
/// Format a number of pulses into Beat.Bar starting from 0
|
|
||||||
#[inline] pub fn format_beats_0_short (&self, pulse: f64) -> Arc<str> {
|
|
||||||
let pulse = pulse as usize;
|
|
||||||
let ppq = self.ppq.get() as usize;
|
|
||||||
let beats = if ppq > 0 { pulse / ppq } else { 0 };
|
|
||||||
format!("{}.{}", beats / 4, beats % 4).into()
|
|
||||||
}
|
|
||||||
/// Format a number of pulses into Beat.Bar.Pulse starting from 1
|
|
||||||
#[inline] pub fn format_beats_1 (&self, pulse: f64) -> Arc<str> {
|
|
||||||
let mut string = String::with_capacity(16);
|
|
||||||
self.format_beats_1_to(&mut string, pulse).expect("failed to format {pulse} into beat");
|
|
||||||
string.into()
|
|
||||||
}
|
|
||||||
/// Format a number of pulses into Beat.Bar.Pulse starting from 1
|
|
||||||
#[inline] pub fn format_beats_1_to (&self, w: &mut impl std::fmt::Write, pulse: f64) -> Result<(), std::fmt::Error> {
|
|
||||||
let pulse = pulse as usize;
|
|
||||||
let ppq = self.ppq.get() as usize;
|
|
||||||
let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
|
|
||||||
write!(w, "{}.{}.{pulses:02}", beats / 4 + 1, beats % 4 + 1)
|
|
||||||
}
|
|
||||||
/// Format a number of pulses into Beat.Bar.Pulse starting from 1
|
|
||||||
#[inline] pub fn format_beats_1_short (&self, pulse: f64) -> Arc<str> {
|
|
||||||
let pulse = pulse as usize;
|
|
||||||
let ppq = self.ppq.get() as usize;
|
|
||||||
let beats = if ppq > 0 { pulse / ppq } else { 0 };
|
|
||||||
format!("{}.{}", beats / 4 + 1, beats % 4 + 1).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Timebase {
|
|
||||||
fn default () -> Self { Self::new(48000f64, 150f64, DEFAULT_PPQ) }
|
|
||||||
}
|
|
||||||
impl SampleRate {
|
|
||||||
/// Return the duration of a sample in microseconds (floating)
|
|
||||||
#[inline] pub fn usec_per_sample (&self) -> f64 {
|
|
||||||
1_000_000f64 / self.get()
|
|
||||||
}
|
|
||||||
/// Return the duration of a sample in microseconds (floating)
|
|
||||||
#[inline] pub fn sample_per_usec (&self) -> f64 {
|
|
||||||
self.get() / 1_000_000f64
|
|
||||||
}
|
|
||||||
/// Convert a number of samples to microseconds (floating)
|
|
||||||
#[inline] pub fn samples_to_usec (&self, samples: f64) -> f64 {
|
|
||||||
self.usec_per_sample() * samples
|
|
||||||
}
|
|
||||||
/// Convert a number of microseconds to samples (floating)
|
|
||||||
#[inline] pub fn usecs_to_sample (&self, usecs: f64) -> f64 {
|
|
||||||
self.sample_per_usec() * usecs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Microsecond {
|
|
||||||
#[inline] pub fn format_msu (&self) -> Arc<str> {
|
|
||||||
let usecs = self.get() as usize;
|
|
||||||
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
|
|
||||||
let (minutes, seconds) = (seconds / 60, seconds % 60);
|
|
||||||
format!("{minutes}:{seconds:02}:{msecs:03}").into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implement an arithmetic operation for a unit of time
|
|
||||||
#[macro_export] macro_rules! impl_op {
|
|
||||||
($T:ident, $Op:ident, $method:ident, |$a:ident,$b:ident|{$impl:expr}) => {
|
|
||||||
impl $Op<Self> for $T {
|
|
||||||
type Output = Self; #[inline] fn $method (self, other: Self) -> Self::Output {
|
|
||||||
let $a = self.get(); let $b = other.get(); Self($impl.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl $Op<usize> for $T {
|
|
||||||
type Output = Self; #[inline] fn $method (self, other: usize) -> Self::Output {
|
|
||||||
let $a = self.get(); let $b = other as f64; Self($impl.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl $Op<f64> for $T {
|
|
||||||
type Output = Self; #[inline] fn $method (self, other: f64) -> Self::Output {
|
|
||||||
let $a = self.get(); let $b = other; Self($impl.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define and implement a unit of time
|
|
||||||
#[macro_export] macro_rules! impl_time_unit {
|
|
||||||
($T:ident) => {
|
|
||||||
impl Gettable<f64> for $T {
|
|
||||||
fn get (&self) -> f64 { self.0.load(Relaxed) }
|
|
||||||
}
|
|
||||||
impl InteriorMutable<f64> for $T {
|
|
||||||
fn set (&self, value: f64) -> f64 {
|
|
||||||
let old = self.get();
|
|
||||||
self.0.store(value, Relaxed);
|
|
||||||
old
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl TimeUnit for $T {}
|
|
||||||
impl_op!($T, Add, add, |a, b|{a + b});
|
|
||||||
impl_op!($T, Sub, sub, |a, b|{a - b});
|
|
||||||
impl_op!($T, Mul, mul, |a, b|{a * b});
|
|
||||||
impl_op!($T, Div, div, |a, b|{a / b});
|
|
||||||
impl_op!($T, Rem, rem, |a, b|{a % b});
|
|
||||||
impl From<f64> for $T { fn from (value: f64) -> Self { Self(value.into()) } }
|
|
||||||
impl From<usize> for $T { fn from (value: usize) -> Self { Self((value as f64).into()) } }
|
|
||||||
impl From<$T> for f64 { fn from (value: $T) -> Self { value.get() } }
|
|
||||||
impl From<$T> for usize { fn from (value: $T) -> Self { value.get() as usize } }
|
|
||||||
impl From<&$T> for f64 { fn from (value: &$T) -> Self { value.get() } }
|
|
||||||
impl From<&$T> for usize { fn from (value: &$T) -> Self { value.get() as usize } }
|
|
||||||
impl Clone for $T { fn clone (&self) -> Self { Self(self.get().into()) } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_time_unit!(SampleCount);
|
|
||||||
impl_time_unit!(SampleRate);
|
|
||||||
impl_time_unit!(Microsecond);
|
|
||||||
impl_time_unit!(Quantize);
|
|
||||||
impl_time_unit!(PulsesPerQuaver);
|
|
||||||
impl_time_unit!(Pulse);
|
|
||||||
impl_time_unit!(BeatsPerMinute);
|
|
||||||
impl_time_unit!(LaunchSync);
|
|
||||||
|
|
@ -1,156 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat)
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone)] pub struct Timebase {
|
|
||||||
/// Audio samples per second
|
|
||||||
pub sr: SampleRate,
|
|
||||||
/// MIDI beats per minute
|
|
||||||
pub bpm: BeatsPerMinute,
|
|
||||||
/// MIDI ticks per beat
|
|
||||||
pub ppq: PulsesPerQuaver,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterator that emits subsequent ticks within a range.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct TicksIterator {
|
|
||||||
pub spp: f64,
|
|
||||||
pub sample: usize,
|
|
||||||
pub start: usize,
|
|
||||||
pub end: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let model = MidiSelection::from((1, false));
|
|
||||||
///
|
|
||||||
/// let _ = model.get_time_len();
|
|
||||||
/// let _ = model.get_time_zoom();
|
|
||||||
/// let _ = model.get_time_lock();
|
|
||||||
/// let _ = model.get_time_start();
|
|
||||||
/// let _ = model.get_time_axis();
|
|
||||||
/// let _ = model.get_time_end();
|
|
||||||
///
|
|
||||||
/// let _ = model.get_note_lo();
|
|
||||||
/// let _ = model.get_note_axis();
|
|
||||||
/// let _ = model.get_note_hi();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone)] pub struct MidiCursor {
|
|
||||||
/// Time coordinate of cursor
|
|
||||||
pub time_pos: Arc<AtomicUsize>,
|
|
||||||
/// Note coordinate of cursor
|
|
||||||
pub note_pos: Arc<AtomicUsize>,
|
|
||||||
/// Length of note that will be inserted, in pulses
|
|
||||||
pub note_len: Arc<AtomicUsize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, Default)] pub struct MidiSelection {
|
|
||||||
pub time_len: Arc<AtomicUsize>,
|
|
||||||
/// Length of visible time axis
|
|
||||||
pub time_axis: Arc<AtomicUsize>,
|
|
||||||
/// Earliest time displayed
|
|
||||||
pub time_start: Arc<AtomicUsize>,
|
|
||||||
/// Time step
|
|
||||||
pub time_zoom: Arc<AtomicUsize>,
|
|
||||||
/// Auto rezoom to fit in time axis
|
|
||||||
pub time_lock: Arc<AtomicBool>,
|
|
||||||
/// Length of visible note axis
|
|
||||||
pub note_axis: Arc<AtomicUsize>,
|
|
||||||
// Lowest note displayed
|
|
||||||
pub note_lo: Arc<AtomicUsize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A point in time in all time scales (microsecond, sample, MIDI pulse)
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
pub struct Moment {
|
|
||||||
pub timebase: Arc<Timebase>,
|
|
||||||
/// Current time in microseconds
|
|
||||||
pub usec: Microsecond,
|
|
||||||
/// Current time in audio samples
|
|
||||||
pub sample: SampleCount,
|
|
||||||
/// Current time in MIDI pulses
|
|
||||||
pub pulse: Pulse,
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
pub enum Moment2 {
|
|
||||||
#[default] None,
|
|
||||||
Zero,
|
|
||||||
Usec(Microsecond),
|
|
||||||
Sample(SampleCount),
|
|
||||||
Pulse(Pulse),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// MIDI resolution in PPQ (pulses per quarter note)
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct PulsesPerQuaver (pub(crate) AtomicF64);
|
|
||||||
|
|
||||||
/// Timestamp in MIDI pulses
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct Pulse (pub(crate) AtomicF64);
|
|
||||||
|
|
||||||
/// Tempo in beats per minute
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct BeatsPerMinute (pub(crate) AtomicF64);
|
|
||||||
|
|
||||||
/// Quantization setting for launching clips
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct LaunchSync (pub(crate) AtomicF64);
|
|
||||||
|
|
||||||
/// Quantization setting for notes
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct Quantize (pub(crate) AtomicF64);
|
|
||||||
|
|
||||||
/// Timestamp in audio samples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct SampleCount (pub(crate) AtomicF64);
|
|
||||||
|
|
||||||
/// Audio sample rate in Hz (samples per second)
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct SampleRate (pub(crate) AtomicF64);
|
|
||||||
|
|
||||||
/// Timestamp in microseconds
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)] pub struct Microsecond (pub(crate) AtomicF64);
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
|
|
||||||
//pub trait MaybeHas<T>: Send + Sync {
|
|
||||||
//fn get (&self) -> Option<&T>;
|
|
||||||
//}
|
|
||||||
|
|
||||||
pub trait HasN<T>: Send + Sync {
|
|
||||||
fn get_nth (&self, key: usize) -> &T;
|
|
||||||
fn get_nth_mut (&mut self, key: usize) -> &mut T;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Gettable<T> {
|
|
||||||
/// Returns current value
|
|
||||||
fn get (&self) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Mutable<T>: Gettable<T> {
|
|
||||||
/// Sets new value, returns old
|
|
||||||
fn set (&mut self, value: T) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait InteriorMutable<T>: Gettable<T> {
|
|
||||||
/// Sets new value, returns old
|
|
||||||
fn set (&self, value: T) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait NotePoint {
|
|
||||||
fn note_len (&self) -> &AtomicUsize;
|
|
||||||
/// Get the current length of the note cursor.
|
|
||||||
fn get_note_len (&self) -> usize {
|
|
||||||
self.note_len().load(Relaxed)
|
|
||||||
}
|
|
||||||
/// Set the length of the note cursor, returning the previous value.
|
|
||||||
fn set_note_len (&self, x: usize) -> usize {
|
|
||||||
self.note_len().swap(x, Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn note_pos (&self) -> &AtomicUsize;
|
|
||||||
/// Get the current pitch of the note cursor.
|
|
||||||
fn get_note_pos (&self) -> usize {
|
|
||||||
self.note_pos().load(Relaxed).min(127)
|
|
||||||
}
|
|
||||||
/// Set the current pitch fo the note cursor, returning the previous value.
|
|
||||||
fn set_note_pos (&self, x: usize) -> usize {
|
|
||||||
self.note_pos().swap(x.min(127), Relaxed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait TimePoint {
|
|
||||||
fn time_pos (&self) -> &AtomicUsize;
|
|
||||||
/// Get the current time position of the note cursor.
|
|
||||||
fn get_time_pos (&self) -> usize {
|
|
||||||
self.time_pos().load(Relaxed)
|
|
||||||
}
|
|
||||||
/// Set the current time position of the note cursor, returning the previous value.
|
|
||||||
fn set_time_pos (&self, x: usize) -> usize {
|
|
||||||
self.time_pos().swap(x, Relaxed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait MidiPoint: NotePoint + TimePoint {
|
|
||||||
/// Get the current end of the note cursor.
|
|
||||||
fn get_note_end (&self) -> usize {
|
|
||||||
self.get_time_pos() + self.get_note_len()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait TimeRange {
|
|
||||||
fn time_len (&self) -> &AtomicUsize;
|
|
||||||
fn get_time_len (&self) -> usize {
|
|
||||||
self.time_len().load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
fn time_zoom (&self) -> &AtomicUsize;
|
|
||||||
fn get_time_zoom (&self) -> usize {
|
|
||||||
self.time_zoom().load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
fn set_time_zoom (&self, value: usize) -> usize {
|
|
||||||
self.time_zoom().swap(value, Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
fn time_lock (&self) -> &AtomicBool;
|
|
||||||
fn get_time_lock (&self) -> bool {
|
|
||||||
self.time_lock().load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
fn set_time_lock (&self, value: bool) -> bool {
|
|
||||||
self.time_lock().swap(value, Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
fn time_start (&self) -> &AtomicUsize;
|
|
||||||
fn get_time_start (&self) -> usize {
|
|
||||||
self.time_start().load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
fn set_time_start (&self, value: usize) -> usize {
|
|
||||||
self.time_start().swap(value, Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
fn time_axis (&self) -> &AtomicUsize;
|
|
||||||
fn get_time_axis (&self) -> usize {
|
|
||||||
self.time_axis().load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
fn get_time_end (&self) -> usize {
|
|
||||||
self.time_start().get() + self.time_axis().get() * self.time_zoom().get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait NoteRange {
|
|
||||||
fn note_lo (&self) -> &AtomicUsize;
|
|
||||||
fn get_note_lo (&self) -> usize {
|
|
||||||
self.note_lo().load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
fn set_note_lo (&self, x: usize) -> usize {
|
|
||||||
self.note_lo().swap(x, Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
fn note_axis (&self) -> &AtomicUsize;
|
|
||||||
fn get_note_axis (&self) -> usize {
|
|
||||||
self.note_axis().load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
fn get_note_hi (&self) -> usize {
|
|
||||||
(self.note_lo().get() + self.note_axis().get().saturating_sub(1)).min(127)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait MidiRange: TimeRange + NoteRange {}
|
|
||||||
|
|
||||||
/// A unit of time, represented as an atomic 64-bit float.
|
|
||||||
///
|
|
||||||
/// According to https://stackoverflow.com/a/873367, as per IEEE754,
|
|
||||||
/// every integer between 1 and 2^53 can be represented exactly.
|
|
||||||
/// This should mean that, even at 192kHz sampling rate, over 1 year of audio
|
|
||||||
/// can be clocked in microseconds with f64 without losing precision.
|
|
||||||
pub trait TimeUnit: InteriorMutable<f64> {}
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use JackState::*;
|
|
||||||
|
|
||||||
/// Implement [Jack] constructor and methods
|
|
||||||
impl<'j> Jack<'j> {
|
|
||||||
|
|
||||||
/// Register new [Client] and wrap it for shared use.
|
|
||||||
pub fn new_run <T: HasJack<'j> + Audio + Send + Sync + 'static> (
|
|
||||||
name: &impl AsRef<str>,
|
|
||||||
init: impl FnOnce(Jack<'j>)->Usually<T>
|
|
||||||
) -> Usually<Arc<RwLock<T>>> {
|
|
||||||
Jack::new(name)?.run(init)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new (name: &impl AsRef<str>) -> Usually<Self> {
|
|
||||||
let client = Client::new(name.as_ref(), ClientOptions::NO_START_SERVER)?.0;
|
|
||||||
Ok(Jack(Arc::new(RwLock::new(JackState::Inactive(client)))))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run <T: HasJack<'j> + Audio + Send + Sync + 'static>
|
|
||||||
(self, init: impl FnOnce(Self)->Usually<T>) -> Usually<Arc<RwLock<T>>>
|
|
||||||
{
|
|
||||||
let client_state = self.0.clone();
|
|
||||||
let app: Arc<RwLock<T>> = Arc::new(RwLock::new(init(self)?));
|
|
||||||
let mut state = Activating;
|
|
||||||
std::mem::swap(&mut*client_state.write().unwrap(), &mut state);
|
|
||||||
if let Inactive(client) = state {
|
|
||||||
// This is the misc notifications handler. It's a struct that wraps a [Box]
|
|
||||||
// which performs type erasure on a callback that takes [JackEvent], which is
|
|
||||||
// one of the available misc notifications.
|
|
||||||
let notify = JackNotify(Box::new({
|
|
||||||
let app = app.clone();
|
|
||||||
move|event|(&mut*app.write().unwrap()).handle(event)
|
|
||||||
}) as BoxedJackEventHandler);
|
|
||||||
// This is the main processing handler. It's a struct that wraps a [Box]
|
|
||||||
// which performs type erasure on a callback that takes [Client] and [ProcessScope]
|
|
||||||
// and passes them down to the `app`'s `process` callback, which in turn
|
|
||||||
// implements audio and MIDI input and output on a realtime basis.
|
|
||||||
let process = ClosureProcessHandler::new(Box::new({
|
|
||||||
let app = app.clone();
|
|
||||||
move|c: &_, s: &_|if let Ok(mut app) = app.write() {
|
|
||||||
app.process(c, s)
|
|
||||||
} else {
|
|
||||||
Control::Quit
|
|
||||||
}
|
|
||||||
}) as BoxedAudioHandler);
|
|
||||||
// Launch a client with the two handlers.
|
|
||||||
*client_state.write().unwrap() = Active(
|
|
||||||
client.activate_async(notify, process)?
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
Ok(app)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run something with the client.
|
|
||||||
pub fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
|
|
||||||
match &*self.0.read().unwrap() {
|
|
||||||
Inert => panic!("jack client not activated"),
|
|
||||||
Inactive(client) => op(client),
|
|
||||||
Activating => panic!("jack client has not finished activation"),
|
|
||||||
Active(client) => op(client.as_client()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'j> HasJack<'j> for Jack<'j> {
|
|
||||||
fn jack (&self) -> &Jack<'j> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'j> HasJack<'j> for &Jack<'j> {
|
|
||||||
fn jack (&self) -> &Jack<'j> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Fn(JackEvent) + Send> NotificationHandler for JackNotify<T> {
|
|
||||||
fn thread_init(&self, _: &Client) {
|
|
||||||
self.0(JackEvent::ThreadInit);
|
|
||||||
}
|
|
||||||
unsafe fn shutdown(&mut self, status: ClientStatus, reason: &str) {
|
|
||||||
self.0(JackEvent::Shutdown(status, reason.into()));
|
|
||||||
}
|
|
||||||
fn freewheel(&mut self, _: &Client, enabled: bool) {
|
|
||||||
self.0(JackEvent::Freewheel(enabled));
|
|
||||||
}
|
|
||||||
fn sample_rate(&mut self, _: &Client, frames: Frames) -> Control {
|
|
||||||
self.0(JackEvent::SampleRate(frames));
|
|
||||||
Control::Quit
|
|
||||||
}
|
|
||||||
fn client_registration(&mut self, _: &Client, name: &str, reg: bool) {
|
|
||||||
self.0(JackEvent::ClientRegistration(name.into(), reg));
|
|
||||||
}
|
|
||||||
fn port_registration(&mut self, _: &Client, id: PortId, reg: bool) {
|
|
||||||
self.0(JackEvent::PortRegistration(id, reg));
|
|
||||||
}
|
|
||||||
fn port_rename(&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
|
|
||||||
self.0(JackEvent::PortRename(id, old.into(), new.into()));
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
fn ports_connected(&mut self, _: &Client, a: PortId, b: PortId, are: bool) {
|
|
||||||
self.0(JackEvent::PortsConnected(a, b, are));
|
|
||||||
}
|
|
||||||
fn graph_reorder(&mut self, _: &Client) -> Control {
|
|
||||||
self.0(JackEvent::GraphReorder);
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
fn xrun(&mut self, _: &Client) -> Control {
|
|
||||||
self.0(JackEvent::XRun);
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JackPerfModel for PerfModel {
|
|
||||||
fn update_from_jack_scope (&self, t0: Option<u64>, scope: &ProcessScope) {
|
|
||||||
if let Some(t0) = t0 {
|
|
||||||
let t1 = self.clock.raw();
|
|
||||||
self.used.store(
|
|
||||||
self.clock.delta_as_nanos(t0, t1) as f64,
|
|
||||||
Relaxed,
|
|
||||||
);
|
|
||||||
self.window.store(
|
|
||||||
scope.cycle_times().unwrap().period_usecs as f64,
|
|
||||||
Relaxed,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Wraps [JackState], and through it [jack::Client] when connected.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let jack: tek_engine::Jack = Default::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct Jack<'j> (pub(crate) Arc<RwLock<JackState<'j>>>);
|
|
||||||
|
|
||||||
/// This is a connection which may be [Inactive], [Activating], or [Active].
|
|
||||||
/// In the [Active] and [Inactive] states, [JackState::client] returns a
|
|
||||||
/// [jack::Client], which you can use to talk to the JACK API.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let state: tek_engine::JackState = Default::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub enum JackState<'j> {
|
|
||||||
/// Unused
|
|
||||||
#[default] Inert,
|
|
||||||
/// Before activation.
|
|
||||||
Inactive(Client),
|
|
||||||
/// During activation.
|
|
||||||
Activating,
|
|
||||||
/// After activation. Must not be dropped for JACK thread to persist.
|
|
||||||
Active(DynamicAsyncClient<'j>),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Event enum for JACK events.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let event: tek_engine::JackState = JackEvent::XRun;
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, PartialEq)] pub enum JackEvent {
|
|
||||||
ThreadInit,
|
|
||||||
Shutdown(ClientStatus, Arc<str>),
|
|
||||||
Freewheel(bool),
|
|
||||||
SampleRate(Frames),
|
|
||||||
ClientRegistration(Arc<str>, bool),
|
|
||||||
PortRegistration(PortId, bool),
|
|
||||||
PortRename(PortId, Arc<str>, Arc<str>),
|
|
||||||
PortsConnected(PortId, PortId, bool),
|
|
||||||
GraphReorder,
|
|
||||||
XRun,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generic notification handler that emits [JackEvent]
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let notify = tek_engine::JackNotify(|_: JackEvent|{});
|
|
||||||
/// ```
|
|
||||||
pub struct JackNotify<T: Fn(JackEvent) + Send>(pub T);
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Things that can provide a [jack::Client] reference.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// #[derive(Default)] struct Jacked<'j>(tek_engine::Jack<'j>);
|
|
||||||
///
|
|
||||||
/// impl tek_engine::HasJack<'j> for Jacked<'j> {
|
|
||||||
/// fn jack (&self) -> &Jack<'j> { &self.0 }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// let jack: &Jack<'j> = Jacked::default().jack();
|
|
||||||
/// ```
|
|
||||||
pub trait HasJack<'j>: Send + Sync {
|
|
||||||
|
|
||||||
/// Return the internal [jack::Client] handle
|
|
||||||
/// that lets you call the JACK API.
|
|
||||||
fn jack (&self) -> &Jack<'j>;
|
|
||||||
|
|
||||||
fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
|
|
||||||
self.jack().with_client(op)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
|
|
||||||
self.with_client(|client|client.port_by_name(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn port_by_id (&self, id: u32) -> Option<Port<Unowned>> {
|
|
||||||
self.with_client(|c|c.port_by_id(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_port <PS: PortSpec + Default> (&self, name: impl AsRef<str>) -> Usually<Port<PS>> {
|
|
||||||
self.with_client(|client|Ok(client.register_port(name.as_ref(), PS::default())?))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sync_lead (&self, enable: bool, callback: impl Fn(TimebaseInfo)->Position) -> Usually<()> {
|
|
||||||
if enable {
|
|
||||||
self.with_client(|client|match client.register_timebase_callback(false, callback) {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(e) => Err(e)
|
|
||||||
})?
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sync_follow (&self, _enable: bool) -> Usually<()> {
|
|
||||||
// TODO: sync follow
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait for thing that has a JACK process callback.
|
|
||||||
pub trait Audio {
|
|
||||||
|
|
||||||
/// Handle a JACK event.
|
|
||||||
fn handle (&mut self, _event: JackEvent) {}
|
|
||||||
|
|
||||||
/// Projecss a JACK chunk.
|
|
||||||
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The JACK process callback function passed to the server.
|
|
||||||
fn callback (
|
|
||||||
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
|
|
||||||
) -> Control where Self: Sized {
|
|
||||||
if let Ok(mut state) = state.write() {
|
|
||||||
state.process(client, scope)
|
|
||||||
} else {
|
|
||||||
Control::Quit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implement [Audio]: provide JACK callbacks.
|
|
||||||
#[macro_export] macro_rules! audio {
|
|
||||||
|
|
||||||
(|
|
|
||||||
$self1:ident:
|
|
||||||
$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident
|
|
||||||
|$cb:expr$(;|$self2:ident,$e:ident|$cb2:expr)?) => {
|
|
||||||
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
|
|
||||||
#[inline] fn process (&mut $self1, $c: &Client, $s: &ProcessScope) -> Control { $cb }
|
|
||||||
$(#[inline] fn handle (&mut $self2, $e: JackEvent) { $cb2 })?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
($Struct:ident: $process:ident, $handle:ident) => {
|
|
||||||
impl Audio for $Struct {
|
|
||||||
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
|
|
||||||
$process(self, c, s)
|
|
||||||
}
|
|
||||||
#[inline] fn handle (&mut self, e: JackEvent) {
|
|
||||||
$handle(self, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait JackPerfModel {
|
|
||||||
fn update_from_jack_scope (&self, t0: Option<u64>, scope: &ProcessScope);
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue