mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
wip: down to 25 errors woo
This commit is contained in:
parent
89288f2920
commit
6ce83fb27a
25 changed files with 688 additions and 620 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2315,6 +2315,7 @@ dependencies = [
|
|||
"jack",
|
||||
"midly",
|
||||
"tengri",
|
||||
"tengri_proc",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -21,12 +21,12 @@ pub struct App {
|
|||
pub dialog: Option<Dialog>,
|
||||
/// Browses external resources, such as directories
|
||||
pub browser: Option<Browser>,
|
||||
/// Contains the currently edited musical arrangement
|
||||
pub arranger: Option<Arrangement>,
|
||||
/// Contains all clips in the project
|
||||
pub pool: Option<Pool>,
|
||||
/// Contains the currently edited MIDI clip
|
||||
pub editor: Option<MidiEditor>,
|
||||
/// Contains the currently edited musical arrangement
|
||||
pub arranger: Arrangement,
|
||||
|
||||
// Cache of formatted strings
|
||||
pub view_cache: Arc<RwLock<ViewCache>>,
|
||||
|
|
@ -34,6 +34,12 @@ pub struct App {
|
|||
pub color: ItemTheme,
|
||||
}
|
||||
|
||||
has!(Option<Selection>: |self: App|self.arrangement.selected);
|
||||
has!(Vec<JackMidiIn>: |self: App|self.arrangement.midi_ins);
|
||||
has!(Vec<JackMidiOut>: |self: App|self.arrangement.midi_outs);
|
||||
has!(Vec<Scene>: |self: App|self.arrangement.scenes);
|
||||
has!(Vec<Track>: |self: App|self.arrangement.tracks);
|
||||
|
||||
impl App {
|
||||
pub fn toggle_dialog (&mut self, dialog: Option<Dialog>) {
|
||||
self.dialog = if self.dialog == dialog {
|
||||
|
|
@ -86,16 +92,10 @@ pub enum Message {
|
|||
FailedToAddDevice,
|
||||
}
|
||||
|
||||
content!(TuiOut: |self: Message| match self {
|
||||
Self::FailedToAddDevice => "Failed to add device."
|
||||
});
|
||||
|
||||
content!(TuiOut: |self: Message| match self { Self::FailedToAddDevice => "Failed to add device." });
|
||||
has_size!(<TuiOut>|self: App|&self.size);
|
||||
|
||||
has_clock!(|self: App|self.clock);
|
||||
|
||||
has!(Clock: |self: App|self.clock);
|
||||
has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips);
|
||||
|
||||
has_editor!(|self: App|{
|
||||
editor = self.editor;
|
||||
editor_w = {
|
||||
|
|
@ -109,30 +109,6 @@ has_editor!(|self: App|{
|
|||
is_editing = self.editing.load(Relaxed);
|
||||
});
|
||||
|
||||
impl HasTracks for App {
|
||||
fn midi_ins (&self) -> &Vec<JackMidiIn> {
|
||||
&self.arranger.midi_ins
|
||||
}
|
||||
fn midi_outs (&self) -> &Vec<JackMidiOut> {
|
||||
&self.arranger.midi_outs
|
||||
}
|
||||
fn tracks (&self) -> &Vec<Track> {
|
||||
&self.arranger.tracks
|
||||
}
|
||||
fn tracks_mut (&mut self) -> &mut Vec<Track> {
|
||||
&mut self.arranger.tracks
|
||||
}
|
||||
}
|
||||
|
||||
impl HasScenes for Arrangement {
|
||||
fn scenes (&self) -> &Vec<Scene> {
|
||||
&self.arranger.scenes
|
||||
}
|
||||
fn scenes_mut (&mut self) -> &mut Vec<Scene> {
|
||||
&mut self.arranger.scenes
|
||||
}
|
||||
}if
|
||||
|
||||
#[tengri_proc::expose]
|
||||
impl App {
|
||||
fn _todo_isize_stub (&self) -> isize {
|
||||
|
|
@ -258,4 +234,27 @@ impl App {
|
|||
0
|
||||
}
|
||||
}
|
||||
fn device_add_lv2 (&mut self) -> Usually<()> {
|
||||
todo!();
|
||||
Ok(())
|
||||
}
|
||||
fn device_add_sampler (&mut self, jack: &Jack) -> Usually<usize> {
|
||||
let name = jack.with_client(|c|c.name().to_string());
|
||||
let midi = self.track().expect("no active track").sequencer.midi_outs[0].name();
|
||||
let sampler = if let Ok(sampler) = Sampler::new(
|
||||
jack,
|
||||
&format!("{}/Sampler", Has::<Track>::get(self).name),
|
||||
&[PortConnect::exact(format!("{name}:{midi}"))],
|
||||
&[&[], &[]],
|
||||
&[&[], &[]]
|
||||
) {
|
||||
self.dialog = None;
|
||||
Device::Sampler(sampler)
|
||||
} else {
|
||||
self.dialog = Some(Dialog::Message(Message::FailedToAddDevice));
|
||||
return Err("failed to add device".into())
|
||||
};
|
||||
Has::<Track>::get_mut(self).expect("no active track").devices.push(sampler);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,13 @@ macro_rules! def_sizes_iter {
|
|||
|
||||
mod arranger_api; pub use self::arranger_api::*;
|
||||
mod arranger_clip; pub use self::arranger_clip::*;
|
||||
mod arranger_device; pub use self::arranger_device::*;
|
||||
mod arranger_port; pub use self::arranger_port::*;
|
||||
mod arranger_model; pub use self::arranger_model::*;
|
||||
mod arranger_port; pub use self::arranger_port::*;
|
||||
mod arranger_scene; pub use self::arranger_scene::*;
|
||||
mod arranger_scenes; pub use self::arranger_scenes::*;
|
||||
mod arranger_select; pub use self::arranger_select::*;
|
||||
mod arranger_track; pub use self::arranger_track::*;
|
||||
mod arranger_tracks; pub use self::arranger_tracks::*;
|
||||
mod arranger_view; pub use self::arranger_view::*;
|
||||
|
||||
def_sizes_iter!(ScenesSizes => Scene);
|
||||
|
|
@ -74,13 +75,3 @@ impl HasWidth for Track {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Has<T>: Send + Sync {
|
||||
fn get (&self) -> &T;
|
||||
fn get_mut (&mut self) -> &mut T;
|
||||
}
|
||||
|
||||
pub trait HasMany<T, U>: Send + Sync {
|
||||
fn get (&self, key: U) -> &T;
|
||||
fn get_mut (&mut self, key: U) -> &mut T;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,83 @@
|
|||
use crate::*;
|
||||
|
||||
#[tengri_proc::expose]
|
||||
impl Arrangement {
|
||||
fn _todo_usize_stub_ (&self) -> usize { todo!() }
|
||||
fn _todo_arc_str_stub_ (&self) -> Arc<str> { todo!() }
|
||||
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
|
||||
fn _todo_opt_item_theme_stub (&self) -> Option<ItemTheme> { todo!() }
|
||||
}
|
||||
|
||||
#[tengri_proc::command(Arrangement)]
|
||||
impl ArrangementCommand {
|
||||
/// Set the selection
|
||||
fn select (arranger: &mut Arrangement, s: Option<Selection>) -> Perhaps<Self> {
|
||||
arranger.selected = s;
|
||||
// autoedit: load focused clip in editor.
|
||||
if let Some(ref mut editor) = arranger.editor {
|
||||
editor.set_clip(match arranger.selected {
|
||||
Some(Selection::TrackClip { track, scene })
|
||||
if let Some(Some(Some(clip))) = arranger
|
||||
.scenes.get(scene)
|
||||
.map(|s|s.clips.get(track)) => Some(clip),
|
||||
_ => None
|
||||
});
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
/// Launch a clip or scene
|
||||
fn launch (arranger: &mut Arrangement) -> Perhaps<Self> {
|
||||
use Selection::*;
|
||||
match arranger.selected {
|
||||
Some(Track(t)) => {
|
||||
arranger.tracks[t].sequencer.enqueue_next(None)
|
||||
},
|
||||
Some(TrackClip { track, scene }) => {
|
||||
arranger.tracks[track].sequencer.enqueue_next(arranger.scenes[scene].clips[track].as_ref())
|
||||
},
|
||||
Some(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
|
||||
fn set_color (arranger: &mut Arrangement, palette: Option<ItemTheme>) -> Option<ItemTheme> {
|
||||
use Selection::*;
|
||||
let palette = palette.unwrap_or_else(||ItemTheme::random());
|
||||
Some(match arranger.selected {
|
||||
Some(Mix) => {
|
||||
let old = arranger.color;
|
||||
arranger.color = palette;
|
||||
old
|
||||
},
|
||||
Some(Scene(s)) => {
|
||||
let old = arranger.scenes[s].color;
|
||||
arranger.scenes[s].color = palette;
|
||||
old
|
||||
}
|
||||
Some(Track(t)) => {
|
||||
let old = arranger.tracks[t].color;
|
||||
arranger.tracks[t].color = palette;
|
||||
old
|
||||
}
|
||||
Some(TrackClip { track, scene }) => {
|
||||
if let Some(ref clip) = arranger.scenes[scene].clips[track] {
|
||||
let mut clip = clip.write().unwrap();
|
||||
let old = clip.color;
|
||||
clip.color = palette;
|
||||
old
|
||||
} else {
|
||||
return None
|
||||
}
|
||||
},
|
||||
_ => todo!()
|
||||
})
|
||||
}
|
||||
|
||||
fn track (arranger: &mut Arrangement, track: TrackCommand) -> Perhaps<Self> {
|
||||
todo!("delegate")
|
||||
}
|
||||
|
|
@ -9,9 +85,9 @@ impl ArrangementCommand {
|
|||
let index = arranger.track_add(None, None, &[], &[])?.0;
|
||||
arranger.selected = match arranger.selected {
|
||||
Some(Selection::Track(_)) =>
|
||||
Selection::Track(index),
|
||||
Some(Selection::Track(index)),
|
||||
Some(Selection::TrackClip { track, scene }) =>
|
||||
Selection::TrackClip { track: index, scene },
|
||||
Some(Selection::TrackClip { track: index, scene }),
|
||||
_ => arranger.selected
|
||||
};
|
||||
Ok(Some(Self::TrackDelete { index }))
|
||||
|
|
@ -38,14 +114,14 @@ impl ArrangementCommand {
|
|||
Ok(None)
|
||||
//TODO:Ok(Some(Self::TrackAdd ( index, track: Some(deleted_track) })
|
||||
}
|
||||
fn midi_in (arranger: &mut Arrangement, input: InputCommand) -> Perhaps<Self> {
|
||||
fn midi_in (arranger: &mut Arrangement, input: MidiInputCommand) -> Perhaps<Self> {
|
||||
todo!("delegate")
|
||||
}
|
||||
fn midi_in_add (arranger: &mut Arrangement) -> Perhaps<Self> {
|
||||
arranger.midi_in_add()?;
|
||||
Ok(None)
|
||||
}
|
||||
fn midi_out (arranger: &mut Arrangement, input: OutputCommand) -> Perhaps<Self> {
|
||||
fn midi_out (arranger: &mut Arrangement, input: MidiOutputCommand) -> Perhaps<Self> {
|
||||
todo!("delegate")
|
||||
}
|
||||
fn midi_out_add (arranger: &mut Arrangement) -> Perhaps<Self> {
|
||||
|
|
@ -65,10 +141,10 @@ impl ArrangementCommand {
|
|||
fn scene_add (arranger: &mut Arrangement) -> Perhaps<Self> {
|
||||
let index = arranger.scene_add(None, None)?.0;
|
||||
arranger.selected = match arranger.selected {
|
||||
Selection::Scene(_) =>
|
||||
Selection::Scene(index),
|
||||
Selection::TrackClip { track, scene } =>
|
||||
Selection::TrackClip { track, scene: index },
|
||||
Some(Selection::Scene(_)) =>
|
||||
Some(Selection::Scene(index)),
|
||||
Some(Selection::TrackClip { track, scene }) =>
|
||||
Some(Selection::TrackClip { track, scene: index }),
|
||||
_ => arranger.selected
|
||||
};
|
||||
Ok(None) // TODO
|
||||
|
|
@ -80,7 +156,8 @@ impl ArrangementCommand {
|
|||
fn scene_delete (arranger: &mut Arrangement, index: usize) -> Perhaps<Self> {
|
||||
let scenes = arranger.scenes_mut();
|
||||
Ok(if scenes.get(index).is_some() {
|
||||
Some(scenes.remove(index))
|
||||
let _scene = scenes.remove(index);
|
||||
None
|
||||
} else {
|
||||
None
|
||||
})
|
||||
|
|
@ -90,6 +167,7 @@ impl ArrangementCommand {
|
|||
let clip = arranger.scenes[index].clips[track].as_ref();
|
||||
arranger.tracks[track].sequencer.enqueue_next(clip);
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
fn clip (arranger: &mut Arrangement, scene: ClipCommand) -> Perhaps<Self> {
|
||||
todo!("delegate")
|
||||
|
|
@ -120,52 +198,20 @@ impl ArrangementCommand {
|
|||
//("edit" [a: MaybeClip] Some(Self::Edit(a.unwrap())))
|
||||
todo!()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[tengri_proc::command(MidiClip)]
|
||||
impl ClipCommand {
|
||||
fn set_color (clip: &mut MidiClip, color: Option<ItemTheme>) -> Perhaps<Self> {
|
||||
//(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!()
|
||||
}
|
||||
fn set_loop (clip: &mut MidiClip, looping: Option<bool>) -> Perhaps<Self> {
|
||||
//(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!()
|
||||
impl<'state> Context<'state, TrackCommand> for Arrangement {
|
||||
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<TrackCommand> {
|
||||
Context::get(&self, iter)
|
||||
}
|
||||
}
|
||||
|
||||
#[tengri_proc::command(Device)]
|
||||
impl DeviceCommand {
|
||||
}
|
||||
|
||||
#[tengri_proc::command(JackMidiIn)]
|
||||
impl InputCommand {
|
||||
}
|
||||
|
||||
#[tengri_proc::command(JackMidiOut)]
|
||||
impl OutputCommand {
|
||||
}
|
||||
|
||||
#[tengri_proc::command(Scene)]
|
||||
impl SceneCommand {
|
||||
fn set_name (scene: &mut Scene, mut name: Arc<str>) -> Perhaps<Self> {
|
||||
std::mem::swap(&mut scene.name, &mut name);
|
||||
Ok(Some(Self::SetName { name }))
|
||||
}
|
||||
fn set_color (scene: &mut Scene, mut color: ItemTheme) -> Perhaps<Self> {
|
||||
std::mem::swap(&mut scene.color, &mut color);
|
||||
Ok(Some(Self::SetColor { color }))
|
||||
}
|
||||
fn set_size (scene: &mut Scene, size: usize) -> Perhaps<Self> {
|
||||
todo!()
|
||||
}
|
||||
fn set_zoom (scene: &mut Scene, zoom: usize) -> Perhaps<Self> {
|
||||
todo!()
|
||||
}
|
||||
#[tengri_proc::expose]
|
||||
impl Track {
|
||||
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
|
||||
fn _todo_usize_stub_ (&self) -> usize { todo!() }
|
||||
fn _todo_arc_str_stub_ (&self) -> Arc<str> { todo!() }
|
||||
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
|
||||
}
|
||||
|
||||
#[tengri_proc::command(Track)]
|
||||
|
|
@ -174,8 +220,8 @@ impl TrackCommand {
|
|||
std::mem::swap(&mut name, &mut track.name);
|
||||
Ok(Some(Self::SetName { name }))
|
||||
}
|
||||
fn set_color (track: &mut Track, color: ItemTheme) -> Perhaps<Self> {
|
||||
std::mem::swap(color, track.color);
|
||||
fn set_color (track: &mut Track, mut color: ItemTheme) -> Perhaps<Self> {
|
||||
std::mem::swap(&mut color, &mut track.color);
|
||||
Ok(Some(Self::SetColor { color }))
|
||||
}
|
||||
fn set_mute (track: &mut Track, value: Option<bool>) -> Perhaps<Self> {
|
||||
|
|
@ -205,3 +251,66 @@ impl TrackCommand {
|
|||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'state> Context<'state, SceneCommand> for Arrangement {
|
||||
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<SceneCommand> {
|
||||
Context::get(&self, iter)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'state> Context<'state, ClipCommand> for Arrangement {
|
||||
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<ClipCommand> {
|
||||
Context::get(&self, iter)
|
||||
}
|
||||
}
|
||||
|
||||
#[tengri_proc::expose]
|
||||
impl Scene {
|
||||
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
|
||||
fn _todo_usize_stub_ (&self) -> usize { todo!() }
|
||||
fn _todo_arc_str_stub_ (&self) -> Arc<str> { todo!() }
|
||||
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
|
||||
}
|
||||
|
||||
#[tengri_proc::command(Scene)]
|
||||
impl SceneCommand {
|
||||
fn set_name (scene: &mut Scene, mut name: Arc<str>) -> Perhaps<Self> {
|
||||
std::mem::swap(&mut scene.name, &mut name);
|
||||
Ok(Some(Self::SetName { name }))
|
||||
}
|
||||
fn set_color (scene: &mut Scene, mut color: ItemTheme) -> Perhaps<Self> {
|
||||
std::mem::swap(&mut scene.color, &mut color);
|
||||
Ok(Some(Self::SetColor { color }))
|
||||
}
|
||||
fn set_size (scene: &mut Scene, size: usize) -> Perhaps<Self> {
|
||||
todo!()
|
||||
}
|
||||
fn set_zoom (scene: &mut Scene, zoom: usize) -> Perhaps<Self> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[tengri_proc::expose]
|
||||
impl MidiClip {
|
||||
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
|
||||
fn _todo_bool_stub_ (&self) -> bool { todo!() }
|
||||
fn _todo_usize_stub_ (&self) -> usize { todo!() }
|
||||
fn _todo_arc_str_stub_ (&self) -> Arc<str> { todo!() }
|
||||
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
|
||||
fn _todo_opt_item_theme_stub (&self) -> Option<ItemTheme> { todo!() }
|
||||
}
|
||||
|
||||
#[tengri_proc::command(MidiClip)]
|
||||
impl ClipCommand {
|
||||
fn set_color (clip: &mut MidiClip, color: Option<ItemTheme>) -> Perhaps<Self> {
|
||||
//(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!()
|
||||
}
|
||||
fn set_loop (clip: &mut MidiClip, looping: Option<bool>) -> Perhaps<Self> {
|
||||
//(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!()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
pub trait RenderTracks {
|
||||
}
|
||||
|
||||
impl<T: HasTracks> Audio for T {
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::*;
|
||||
|
||||
|
||||
impl Arrangement {
|
||||
|
||||
/// Put a clip in a slot
|
||||
pub(crate) fn clip_put (
|
||||
&mut self, track: usize, scene: usize, clip: Option<Arc<RwLock<MidiClip>>>
|
||||
|
|
@ -10,6 +10,7 @@ impl Arrangement {
|
|||
self.scenes[scene].clips[track] = clip;
|
||||
old
|
||||
}
|
||||
|
||||
/// Change the color of a clip, returning the previous one
|
||||
pub(crate) fn clip_set_color (
|
||||
&self, track: usize, scene: usize, color: ItemTheme
|
||||
|
|
@ -22,14 +23,78 @@ impl Arrangement {
|
|||
old
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the active clip
|
||||
pub(crate) fn clip (&self) -> Option<Arc<RwLock<MidiClip>>> {
|
||||
self.scene()?.clips.get(self.selected().track()?)?.clone()
|
||||
}
|
||||
|
||||
/// Toggle looping for the active clip
|
||||
pub(crate) fn toggle_loop (&mut self) {
|
||||
if let Some(clip) = self.clip() {
|
||||
clip.write().unwrap().toggle_loop()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "pool", feature = "editor"))]
|
||||
pub trait AutoCreate:
|
||||
Has<Vec<Scene>> +
|
||||
Has<Vec<Track>> +
|
||||
Has<Option<MidiEditor>> +
|
||||
Has<Option<Selection>> +
|
||||
Has<Option<Pool>> +
|
||||
Send + Sync
|
||||
{
|
||||
// Create new clip in pool when entering empty cell
|
||||
fn clip_auto_create (&mut self) -> Option<Arc<RwLock<MidiClip>>> {
|
||||
if let Some(pool) = Has::<Option<Pool>>::get(self)
|
||||
&& let Some(Selection::TrackClip { track, scene }) = self.get()
|
||||
&& let Some(scene) = Has::<Vec<Scene>>::get_mut(self).get_mut(scene)
|
||||
&& let Some(slot) = scene.clips.get_mut(track)
|
||||
&& slot.is_none()
|
||||
&& let Some(track) = Has::<Vec<Track>>::get_mut(self).get_mut(track)
|
||||
{
|
||||
let (index, mut clip) = pool.add_new_clip();
|
||||
// autocolor: new clip colors from scene and track color
|
||||
let color = track.color.base.mix(scene.color.base, 0.5);
|
||||
clip.write().unwrap().color = ItemColor::random_near(color, 0.2).into();
|
||||
if let Some(ref mut editor) = Has::<Option<MidiEditor>>::get_mut(self) {
|
||||
editor.set_clip(Some(&clip));
|
||||
}
|
||||
*slot = Some(clip.clone());
|
||||
Some(clip)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "pool", feature = "editor"))]
|
||||
pub trait AutoRemove:
|
||||
Has<Vec<Scene>> +
|
||||
Has<Vec<Track>> +
|
||||
Has<Option<MidiEditor>> +
|
||||
Has<Option<Selection>> +
|
||||
Has<Option<Pool>> +
|
||||
Send + Sync
|
||||
{
|
||||
// Remove clip from arrangement when exiting empty clip editor
|
||||
fn clip_auto_remove (&mut self) {
|
||||
if let Some(ref mut pool) = Has::<Option<Pool>>::get(self)
|
||||
&& let Some(Selection::TrackClip { track, scene }) = self.get()
|
||||
&& let Some(scene) = Has::<Vec<Scene>>::get_mut(self).get_mut(scene)
|
||||
&& let Some(slot) = scene.clips.get_mut(track)
|
||||
&& let Some(clip) = slot.as_mut()
|
||||
{
|
||||
let mut swapped = None;
|
||||
if clip.read().unwrap().count_midi_messages() == 0 {
|
||||
std::mem::swap(&mut swapped, slot);
|
||||
}
|
||||
if let Some(clip) = swapped {
|
||||
pool.delete_clip(&clip.read().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
impl Arrangement {
|
||||
fn device_add_lv2 (&mut self) -> Usually<()> {
|
||||
todo!();
|
||||
Ok(())
|
||||
}
|
||||
fn device_add_sampler (&mut self) -> Usually<()> {
|
||||
let name = self.jack.with_client(|c|c.name().to_string());
|
||||
let midi = self.track().expect("no active track").sequencer.midi_outs[0].name();
|
||||
let sampler = if let Ok(sampler) = Sampler::new(
|
||||
&self.jack,
|
||||
&format!("{}/Sampler", &self.track().expect("no active track").name),
|
||||
&[PortConnect::exact(format!("{name}:{midi}"))],
|
||||
&[&[], &[]],
|
||||
&[&[], &[]]
|
||||
) {
|
||||
self.dialog = None;
|
||||
Device::Sampler(sampler)
|
||||
} else {
|
||||
self.dialog = Some(Dialog::Message(Message::FailedToAddDevice));
|
||||
return Err("failed to add device".into())
|
||||
};
|
||||
self.track_mut().expect("no active track").devices.push(sampler);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
#[cfg(all(feature = "pool", feature = "editor"))]
|
||||
pub trait AutoCreate:
|
||||
Has<Vec<Scene>> +
|
||||
Has<Vec<Track>> +
|
||||
Has<Option<MidiEditor>> +
|
||||
Has<Option<Selection>> +
|
||||
Has<Option<Pool>> +
|
||||
Send + Sync
|
||||
{
|
||||
// Create new clip in pool when entering empty cell
|
||||
fn clip_auto_create (&mut self) -> Option<Arc<RwLock<MidiClip>>> {
|
||||
if let Some(pool) = Has::<Pool>::get(self)
|
||||
&& let Some(Selection::TrackClip { track, scene }) = self.get()
|
||||
&& let Some(scene) = Has::<Vec<Scene>>::get_mut(self).get_mut(scene)
|
||||
&& let Some(slot) = scene.clips.get_mut(track)
|
||||
&& slot.is_none()
|
||||
&& let Some(track) = Has::<Vec<Track>>::get_mut(self).get_mut(track)
|
||||
{
|
||||
let (index, mut clip) = pool.add_new_clip();
|
||||
// autocolor: new clip colors from scene and track color
|
||||
let color = track.color.base.mix(scene.color.base, 0.5);
|
||||
clip.write().unwrap().color = ItemColor::random_near(color, 0.2).into();
|
||||
if let Some(ref mut editor) = Has::<Option<MidiEditor>>::get_mut(self) {
|
||||
editor.set_clip(Some(&clip));
|
||||
}
|
||||
*slot = Some(clip.clone());
|
||||
Some(clip)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "pool", feature = "editor"))]
|
||||
pub trait AutoRemove:
|
||||
Has<Vec<Scene>> +
|
||||
Has<Vec<Track>> +
|
||||
Has<Option<MidiEditor>> +
|
||||
Has<Option<Selection>> +
|
||||
Has<Option<Pool>> +
|
||||
Send + Sync
|
||||
{
|
||||
// Remove clip from arrangement when exiting empty clip editor
|
||||
fn clip_auto_remove (&mut self) {
|
||||
if let Some(ref mut pool) = Has::<Pool>::get(self)
|
||||
&& let Some(Selection::TrackClip { track, scene }) = self.get()
|
||||
&& let Some(scene) = Has::<Vec<Scene>>::get_mut(self).get_mut(scene)
|
||||
&& let Some(slot) = scene.clips.get_mut(track)
|
||||
&& let Some(clip) = slot.as_mut()
|
||||
{
|
||||
let mut swapped = None;
|
||||
if clip.read().unwrap().count_midi_messages() == 0 {
|
||||
std::mem::swap(&mut swapped, slot);
|
||||
}
|
||||
if let Some(clip) = swapped {
|
||||
pool.delete_clip(&clip.read().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -35,6 +35,8 @@ pub struct Arrangement {
|
|||
pub arranger: Arc<RwLock<Buffer>>,
|
||||
/// Display size
|
||||
pub size: Measure<TuiOut>,
|
||||
/// Jack client handle
|
||||
pub jack: Jack,
|
||||
}
|
||||
|
||||
has!(Option<Selection>: |self: Arrangement|self.selected);
|
||||
|
|
@ -42,33 +44,73 @@ has!(Vec<JackMidiIn>: |self: Arrangement|self.midi_ins);
|
|||
has!(Vec<JackMidiOut>: |self: Arrangement|self.midi_outs);
|
||||
has!(Vec<Scene>: |self: Arrangement|self.scenes);
|
||||
has!(Vec<Track>: |self: Arrangement|self.tracks);
|
||||
has!(Jack: |self: Arrangement|self.jack);
|
||||
|
||||
impl Has<Option<Track>> for Arrangement {
|
||||
fn get (&self) -> &Option<Track> {
|
||||
Has::<Selection>::get(self)
|
||||
.and_then(|selection|selection.track())
|
||||
.and_then(|index|Has::<Vec<Track>>::get(self).get(index))
|
||||
.flatten()
|
||||
impl Arrangement {
|
||||
/// Width of display
|
||||
pub(crate) fn w (&self) -> u16 {
|
||||
self.size.w() as u16
|
||||
}
|
||||
fn get_mut (&mut self) -> &mut Option<Track> {
|
||||
Has::<Selection>::get(self)
|
||||
.and_then(|selection|selection.track())
|
||||
.and_then(|index|Has::<Vec<Track>>::get_mut(self).get_mut(index))
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
impl Has<Option<Scene>> for Arrangement {
|
||||
fn get (&self) -> &Option<Scene> {
|
||||
Has::<Selection>::get(self)
|
||||
.and_then(|selection|selection.track())
|
||||
.and_then(|index|Has::<Vec<Scene>>::get(self).get(index))
|
||||
.flatten()
|
||||
}
|
||||
fn get_mut (&mut self) -> &mut Option<Scene> {
|
||||
Has::<Selection>::get(self)
|
||||
.and_then(|selection|selection.track())
|
||||
.and_then(|index|Has::<Vec<Scene>>::get_mut(self).get_mut(index))
|
||||
.flatten()
|
||||
/// Width allocated for sidebar.
|
||||
pub(crate) fn w_sidebar (&self, is_editing: bool) -> u16 {
|
||||
self.w() / if is_editing { 16 } else { 8 } as u16
|
||||
}
|
||||
/// Width taken by all tracks.
|
||||
pub(crate) fn w_tracks (&self) -> u16 {
|
||||
self.tracks_with_sizes().last().map(|(_, _, _, x)|x as u16).unwrap_or(0)
|
||||
}
|
||||
/// Width available to display tracks.
|
||||
pub(crate) fn w_tracks_area (&self, is_editing: bool) -> u16 {
|
||||
self.w().saturating_sub(2 * self.w_sidebar(is_editing))
|
||||
}
|
||||
/// Height of display
|
||||
pub(crate) fn h (&self) -> u16 {
|
||||
self.size.h() as u16
|
||||
}
|
||||
/// Height available to display track headers.
|
||||
pub(crate) fn h_tracks_area (&self) -> u16 {
|
||||
5 // FIXME
|
||||
//self.h().saturating_sub(self.h_inputs() + self.h_outputs())
|
||||
}
|
||||
/// Height available to display tracks.
|
||||
pub(crate) fn h_scenes_area (&self) -> u16 {
|
||||
//15
|
||||
self.h().saturating_sub(
|
||||
self.h_inputs() +
|
||||
self.h_outputs() +
|
||||
self.h_devices() +
|
||||
13 // FIXME
|
||||
)
|
||||
}
|
||||
/// Height taken by all scenes.
|
||||
pub(crate) fn h_scenes (&self, is_editing: bool) -> u16 {
|
||||
let (selected_track, selected_scene) = match Has::<Option<Selection>>::get(self) {
|
||||
Some(Selection::Track(t)) => (Some(*t), None),
|
||||
Some(Selection::Scene(s)) => (None, Some(*s)),
|
||||
Some(Selection::TrackClip { track, scene }) => (Some(*track), Some(*scene)),
|
||||
_ => (None, None)
|
||||
};
|
||||
self.scenes_with_sizes(
|
||||
is_editing,
|
||||
ArrangerView::H_SCENE,
|
||||
ArrangerView::H_EDITOR,
|
||||
selected_track,
|
||||
selected_scene
|
||||
)
|
||||
.last()
|
||||
.map(|(_, _, _, y)|y as u16).unwrap_or(0)
|
||||
}
|
||||
/// Height taken by all inputs.
|
||||
pub(crate) fn h_inputs (&self) -> u16 {
|
||||
self.midi_ins_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
|
||||
}
|
||||
/// Height taken by all outputs.
|
||||
pub(crate) fn h_outputs (&self) -> u16 {
|
||||
self.midi_outs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
|
||||
}
|
||||
/// Height taken by visible device slots.
|
||||
pub(crate) fn h_devices (&self) -> u16 {
|
||||
2
|
||||
//1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,10 @@ impl<'a> ArrangerView<'a> {
|
|||
|_, _|Tui::bg(Reset, Align::c(Bsp::s(OctaveVertical::default(), " ------ ")))))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<'a> ArrangerView<'a> {
|
||||
|
||||
pub(crate) fn output_nexts (&self) -> impl Content<TuiOut> + '_ {
|
||||
Tryptich::top(2)
|
||||
.left(self.width_side, Align::ne("From clip:"))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,16 @@
|
|||
use crate::*;
|
||||
|
||||
impl<T: Has<Option<Scene>> + Send + Sync> HasScene for T {}
|
||||
|
||||
pub trait HasScene: Has<Option<Scene>> + Send + Sync {
|
||||
fn scene (&self) -> &Option<Scene> {
|
||||
Has::<Option<Scene>>::get(self)
|
||||
}
|
||||
fn scene_mut (&mut self) -> &mut Option<Scene> {
|
||||
Has::<Option<Scene>>::get_mut(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Scene {
|
||||
/// Name of scene
|
||||
|
|
@ -39,223 +50,3 @@ impl Scene {
|
|||
match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Has<Vec<Scene>> + Send + Sync> HasScenes for T {}
|
||||
|
||||
pub trait HasScenes: Has<Vec<Scene>> + Send + Sync {
|
||||
fn scenes (&self) -> &Vec<Scene> {
|
||||
Has::<Vec<Scene>>::get(self)
|
||||
}
|
||||
fn scenes_mut (&self) -> &mut Vec<Scene> {
|
||||
Has::<Vec<Scene>>::get_mut(self)
|
||||
}
|
||||
fn scenes_with_sizes (
|
||||
&self,
|
||||
editing: bool,
|
||||
height: usize,
|
||||
larger: usize,
|
||||
selected_track: Option<usize>,
|
||||
selected_scene: Option<usize>,
|
||||
) -> impl ScenesSizes<'_> {
|
||||
let mut y = 0;
|
||||
self.scenes().iter().enumerate().map(move|(s, scene)|{
|
||||
let active = editing && selected_track.is_some() && selected_scene == Some(s);
|
||||
let height = if active { larger } else { height };
|
||||
let data = (s, scene, y, y + height);
|
||||
y += height;
|
||||
data
|
||||
})
|
||||
}
|
||||
/// Generate the default name for a new scene
|
||||
fn scene_default_name (&self) -> Arc<str> {
|
||||
format!("Sc{:3>}", self.scenes().len() + 1).into()
|
||||
}
|
||||
fn scene_longest_name (&self) -> usize {
|
||||
self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Has<Option<Scene>> + Send + Sync> HasScene for T {}
|
||||
|
||||
pub trait HasScene: Has<Option<Scene>> + Send + Sync {
|
||||
fn scene (&self) -> Option<&Scene> {
|
||||
Has::<Option<Scene>>::get(self)
|
||||
}
|
||||
fn scene_mut (&mut self) -> Option<&mut Scene> {
|
||||
Has::<Option<Scene>>::get_mut(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ArrangerView<'a> {
|
||||
/// Default scene height.
|
||||
pub(crate) const H_SCENE: usize = 2;
|
||||
/// Default editor height.
|
||||
pub(crate) const H_EDITOR: usize = 15;
|
||||
|
||||
/// Render scenes with clips
|
||||
pub(crate) fn scenes (&'a self, editor: Option<MidiEditor>) -> impl Content<TuiOut> + 'a {
|
||||
|
||||
/// A scene with size and color.
|
||||
type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option<ItemTheme>);
|
||||
|
||||
let Self {
|
||||
arrangement,
|
||||
width, width_side, width_mid,
|
||||
scenes_height, scene_last, scene_selected,
|
||||
track_selected, is_editing, ..
|
||||
} = self;
|
||||
|
||||
let selection = Has::<Option<Selection>>::get(*arrangement);
|
||||
let (selected_track, selected_scene) = match selection {
|
||||
Some(Selection::Track(t)) => (Some(*t), None),
|
||||
Some(Selection::Scene(s)) => (None, Some(*s)),
|
||||
Some(Selection::TrackClip { track, scene }) => (Some(*track), Some(*scene)),
|
||||
_ => (None, None)
|
||||
};
|
||||
|
||||
let scenes_with_scene_colors = ||arrangement.scenes_with_sizes(
|
||||
*is_editing,
|
||||
Self::H_SCENE,
|
||||
Self::H_EDITOR,
|
||||
selected_track,
|
||||
selected_scene,
|
||||
).map_while(|(s, scene, y1, y2)|if y2 as u16 > *scenes_height {
|
||||
None
|
||||
} else {
|
||||
Some((s, scene, y1, y2, if s == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(arrangement.scenes()[s-1].color)
|
||||
}))
|
||||
});
|
||||
|
||||
let scene_header = |(s, scene, y1, y2, previous): SceneWithColor, _|{
|
||||
let height = (1 + y2 - y1) as u16;
|
||||
let name = Some(scene.name.clone());
|
||||
let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⯈ ", name))));
|
||||
let same_track = true;
|
||||
let selected = same_track && *scene_selected == Some(s);
|
||||
let neighbor = same_track && s > 0 && *scene_selected == Some(s - 1);
|
||||
let is_last = *scene_last == s;
|
||||
let theme = scene.color;
|
||||
let fg = theme.lightest.rgb;
|
||||
let bg = if selected { theme.light } else { theme.base }.rgb;
|
||||
let hi = if let Some(previous) = previous {
|
||||
if neighbor {
|
||||
previous.light.rgb
|
||||
} else {
|
||||
previous.base.rgb
|
||||
}
|
||||
} else {
|
||||
Reset
|
||||
};
|
||||
let lo = if is_last {
|
||||
Reset
|
||||
} else if selected {
|
||||
theme.light.rgb
|
||||
} else {
|
||||
theme.base.rgb
|
||||
};
|
||||
Fill::x(map_south(y1 as u16, height, Fixed::y(height, Phat {
|
||||
width: 0, height: 0, content, colors: [fg, bg, hi, lo]
|
||||
})))
|
||||
};
|
||||
|
||||
let scenes_with_track_colors = |track: usize| arrangement.scenes_with_sizes(
|
||||
self.is_editing,
|
||||
Self::H_SCENE,
|
||||
Self::H_EDITOR,
|
||||
selected_track,
|
||||
selected_scene,
|
||||
).map_while(|(s, scene, y1, y2)|if y2 as u16 > self.scenes_height {
|
||||
None
|
||||
} else {
|
||||
Some((s, scene, y1, y2, if s == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(self.arrangement.scenes[s-1].clips[track].as_ref()
|
||||
.map(|c|c.read().unwrap().color)
|
||||
.unwrap_or(ItemTheme::G[32]))
|
||||
}))
|
||||
});
|
||||
|
||||
let scene_track_clips = |track_index, track|Map::new(
|
||||
||scenes_with_track_colors(track_index),
|
||||
|(s, scene, y1, y2, previous): SceneWithColor<'a>, _|{
|
||||
let (name, theme) = if let Some(clip) = &scene.clips[track_index] {
|
||||
let clip = clip.read().unwrap();
|
||||
(Some(clip.name.clone()), clip.color)
|
||||
} else {
|
||||
(None, ItemTheme::G[32])
|
||||
};
|
||||
let height = (1 + y2 - y1) as u16;
|
||||
let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⏹ ", name))));
|
||||
let same_track = *track_selected == Some(track_index);
|
||||
let selected = same_track && *scene_selected == Some(s);
|
||||
let neighbor = same_track && s > 0 && *scene_selected == Some(s - 1);
|
||||
let is_last = *scene_last == s;
|
||||
let fg = theme.lightest.rgb;
|
||||
let bg = if selected { theme.light } else { theme.base }.rgb;
|
||||
let hi = if let Some(previous) = previous {
|
||||
if neighbor {
|
||||
previous.light.rgb
|
||||
} else {
|
||||
previous.base.rgb
|
||||
}
|
||||
} else {
|
||||
Reset
|
||||
};
|
||||
let lo = if is_last {
|
||||
Reset
|
||||
} else if selected {
|
||||
theme.light.rgb
|
||||
} else {
|
||||
theme.base.rgb
|
||||
};
|
||||
map_south(y1 as u16, height, Bsp::b(Fixed::y(height, Phat {
|
||||
width: 0, height: 0, content, colors: [fg, bg, hi, lo]
|
||||
}), When(
|
||||
*is_editing && same_track && *scene_selected == Some(s),
|
||||
editor
|
||||
)))
|
||||
});
|
||||
|
||||
Tryptich::center(*scenes_height)
|
||||
.left(*width_side, Map::new(
|
||||
||scenes_with_scene_colors(),
|
||||
scene_header))
|
||||
.middle(*width_mid, per_track(
|
||||
*width_mid,
|
||||
||self.tracks_with_sizes_scrolled(),
|
||||
scene_track_clips))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Arrangement {
|
||||
/// Add multiple scenes
|
||||
pub fn scenes_add (&mut self, n: usize) -> Usually<()> {
|
||||
let scene_color_1 = ItemColor::random();
|
||||
let scene_color_2 = ItemColor::random();
|
||||
for i in 0..n {
|
||||
let _ = self.scene_add(None, Some(
|
||||
scene_color_1.mix(scene_color_2, i as f32 / n as f32).into()
|
||||
))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a scene
|
||||
pub fn scene_add (&mut self, name: Option<&str>, color: Option<ItemTheme>)
|
||||
-> Usually<(usize, &mut Scene)>
|
||||
{
|
||||
let scene = Scene {
|
||||
name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()),
|
||||
clips: vec![None;self.tracks().len()],
|
||||
color: color.unwrap_or_else(ItemTheme::random),
|
||||
};
|
||||
self.scenes_mut().push(scene);
|
||||
let index = self.scenes().len() - 1;
|
||||
Ok((index, &mut self.scenes_mut()[index]))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
65
crates/device/src/arranger/arranger_scenes.rs
Normal file
65
crates/device/src/arranger/arranger_scenes.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
use crate::*;
|
||||
|
||||
impl<T: Has<Vec<Scene>> + Send + Sync> HasScenes for T {}
|
||||
|
||||
pub trait HasScenes: Has<Vec<Scene>> + Send + Sync {
|
||||
fn scenes (&self) -> &Vec<Scene> {
|
||||
Has::<Vec<Scene>>::get(self)
|
||||
}
|
||||
fn scenes_mut (&mut self) -> &mut Vec<Scene> {
|
||||
Has::<Vec<Scene>>::get_mut(self)
|
||||
}
|
||||
fn scenes_with_sizes (
|
||||
&self,
|
||||
editing: bool,
|
||||
height: usize,
|
||||
larger: usize,
|
||||
selected_track: Option<usize>,
|
||||
selected_scene: Option<usize>,
|
||||
) -> impl ScenesSizes<'_> {
|
||||
let mut y = 0;
|
||||
self.scenes().iter().enumerate().map(move|(s, scene)|{
|
||||
let active = editing && selected_track.is_some() && selected_scene == Some(s);
|
||||
let height = if active { larger } else { height };
|
||||
let data = (s, scene, y, y + height);
|
||||
y += height;
|
||||
data
|
||||
})
|
||||
}
|
||||
/// Generate the default name for a new scene
|
||||
fn scene_default_name (&self) -> Arc<str> {
|
||||
format!("Sc{:3>}", self.scenes().len() + 1).into()
|
||||
}
|
||||
fn scene_longest_name (&self) -> usize {
|
||||
self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasScenes + HasTracks> AddScene for T {}
|
||||
|
||||
pub trait AddScene: HasScenes + HasTracks {
|
||||
/// Add multiple scenes
|
||||
fn scenes_add (&mut self, n: usize) -> Usually<()> {
|
||||
let scene_color_1 = ItemColor::random();
|
||||
let scene_color_2 = ItemColor::random();
|
||||
for i in 0..n {
|
||||
let _ = self.scene_add(None, Some(
|
||||
scene_color_1.mix(scene_color_2, i as f32 / n as f32).into()
|
||||
))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Add a scene
|
||||
fn scene_add (&mut self, name: Option<&str>, color: Option<ItemTheme>)
|
||||
-> Usually<(usize, &mut Scene)>
|
||||
{
|
||||
let scene = Scene {
|
||||
name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()),
|
||||
clips: vec![None;self.tracks().len()],
|
||||
color: color.unwrap_or_else(ItemTheme::random),
|
||||
};
|
||||
self.scenes_mut().push(scene);
|
||||
let index = self.scenes().len() - 1;
|
||||
Ok((index, &mut self.scenes_mut()[index]))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,42 @@
|
|||
use crate::*;
|
||||
|
||||
impl<T: Has<Selection>> HasSelection for T {}
|
||||
|
||||
pub trait HasSelection: Has<Selection> {
|
||||
fn selection (&self) -> &Selection {
|
||||
self.get()
|
||||
}
|
||||
fn selection_mut (&mut self) -> &mut Selection {
|
||||
self.get_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl Has<Option<Track>> for Arrangement {
|
||||
fn get (&self) -> &Option<Track> {
|
||||
Has::<Option<Selection>>::get(self)
|
||||
.and_then(|selection|selection.track())
|
||||
.and_then(|index|Has::<Vec<Track>>::get(self).get(index))
|
||||
}
|
||||
fn get_mut (&mut self) -> &mut Option<Track> {
|
||||
Has::<Option<Selection>>::get(self)
|
||||
.and_then(|selection|selection.track())
|
||||
.and_then(|index|Has::<Vec<Track>>::get_mut(self).get_mut(index))
|
||||
}
|
||||
}
|
||||
|
||||
impl Has<Option<Scene>> for Arrangement {
|
||||
fn get (&self) -> &Option<Scene> {
|
||||
Has::<Option<Selection>>::get(self)
|
||||
.and_then(|selection|selection.track())
|
||||
.and_then(|index|Has::<Vec<Scene>>::get(self).get(index))
|
||||
}
|
||||
fn get_mut (&mut self) -> &mut Option<Scene> {
|
||||
Has::<Option<Selection>>::get(self)
|
||||
.and_then(|selection|selection.track())
|
||||
.and_then(|index|Has::<Vec<Scene>>::get_mut(self).get_mut(index))
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the current user selection in the arranger
|
||||
#[derive(PartialEq, Clone, Copy, Debug, Default)]
|
||||
pub enum Selection {
|
||||
|
|
@ -147,69 +184,4 @@ impl Selection {
|
|||
}
|
||||
|
||||
impl Arrangement {
|
||||
/// Set the selection
|
||||
pub(crate) fn select (&mut self, s: Selection) {
|
||||
self.selected = s;
|
||||
// autoedit: load focused clip in editor.
|
||||
if let Some(ref mut editor) = self.editor {
|
||||
editor.set_clip(match self.selected {
|
||||
Selection::TrackClip { track, scene }
|
||||
if let Some(Some(Some(clip))) = self
|
||||
.scenes.get(scene)
|
||||
.map(|s|s.clips.get(track)) => Some(clip),
|
||||
_ => None
|
||||
});
|
||||
}
|
||||
}
|
||||
/// Launch a clip or scene
|
||||
pub(crate) fn launch (&mut self) {
|
||||
use Selection::*;
|
||||
match self.selected {
|
||||
Track(t) => {
|
||||
self.tracks[t].sequencer.enqueue_next(None)
|
||||
},
|
||||
TrackClip { track, scene } => {
|
||||
self.tracks[track].sequencer.enqueue_next(self.scenes[scene].clips[track].as_ref())
|
||||
},
|
||||
Scene(s) => {
|
||||
for t in 0..self.tracks.len() {
|
||||
self.tracks[t].sequencer.enqueue_next(self.scenes[s].clips[t].as_ref())
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
/// Set the color of the selected entity
|
||||
pub fn set_color (&mut self, palette: Option<ItemTheme>) -> Option<ItemTheme> {
|
||||
use Selection::*;
|
||||
let palette = palette.unwrap_or_else(||ItemTheme::random());
|
||||
Some(match self.selected {
|
||||
Mix => {
|
||||
let old = self.color;
|
||||
self.color = palette;
|
||||
old
|
||||
},
|
||||
Scene(s) => {
|
||||
let old = self.scenes[s].color;
|
||||
self.scenes[s].color = palette;
|
||||
old
|
||||
}
|
||||
Track(t) => {
|
||||
let old = self.tracks[t].color;
|
||||
self.tracks[t].color = palette;
|
||||
old
|
||||
}
|
||||
TrackClip { track, scene } => {
|
||||
if let Some(ref clip) = self.scenes[scene].clips[track] {
|
||||
let mut clip = clip.write().unwrap();
|
||||
let old = clip.color;
|
||||
clip.color = palette;
|
||||
old
|
||||
} else {
|
||||
return None
|
||||
}
|
||||
},
|
||||
_ => todo!()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,9 @@ pub struct Track {
|
|||
/// Device chain
|
||||
pub devices: Vec<Device>,
|
||||
}
|
||||
has_clock!(|self: Track|self.sequencer.clock);
|
||||
has_sequencer!(|self: Track|self.sequencer);
|
||||
|
||||
has!(Clock: |self: Track|self.sequencer.clock);
|
||||
has!(Sequencer: |self: Track|self.sequencer);
|
||||
|
||||
impl Track {
|
||||
/// Create a new track with only the default [Sequencer].
|
||||
|
|
@ -98,7 +99,7 @@ impl Track {
|
|||
|
||||
impl<T: Has<Vec<Track>> + Send + Sync> HasTracks for T {}
|
||||
|
||||
pub trait HasTracks: Send + Sync {
|
||||
pub trait HasTracks: Has<Vec<Track>> + Send + Sync {
|
||||
fn tracks (&self) -> &Vec<Track> {
|
||||
Has::<Vec<Track>>::get(self)
|
||||
}
|
||||
|
|
@ -107,7 +108,7 @@ pub trait HasTracks: Send + Sync {
|
|||
}
|
||||
/// Run audio callbacks for every track and every device
|
||||
fn tracks_render (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||
for track in self.tracks.iter_mut() {
|
||||
for track in self.tracks_mut().iter_mut() {
|
||||
if Control::Quit == PlayerAudio(
|
||||
track.sequencer_mut(), &mut self.note_buf, &mut self.midi_buf
|
||||
).process(client, scope) {
|
||||
|
|
@ -131,7 +132,7 @@ pub trait HasTracks: Send + Sync {
|
|||
}
|
||||
}
|
||||
/// Stop all playing clips
|
||||
fn tracks_launch (&mut self, clips: Option<Vec<Arc<RwLock<MidiClip>>>>) {
|
||||
fn tracks_launch (&mut self, clips: Option<Vec<Option<Arc<RwLock<MidiClip>>>>>) {
|
||||
if let Some(clips) = clips {
|
||||
for (clip, track) in clips.iter().zip(self.tracks_mut()) {
|
||||
track.sequencer.enqueue_next(clip);
|
||||
|
|
@ -170,11 +171,11 @@ pub trait HasTracks: Send + Sync {
|
|||
impl<T: Has<Option<Track>> + Send + Sync> HasTrack for T {}
|
||||
|
||||
pub trait HasTrack: Has<Option<Track>> + Send + Sync {
|
||||
fn scene (&self) -> Option<&Track> {
|
||||
Has::<Option<Track>>::get(self)
|
||||
fn track (&self) -> Option<&Track> {
|
||||
Has::<Option<Track>>::get(self).as_ref()
|
||||
}
|
||||
fn scene_mut (&mut self) -> Option<&mut Track> {
|
||||
Has::<Option<Track>>::get_mut(self)
|
||||
fn track_mut (&mut self) -> Option<&mut Track> {
|
||||
Has::<Option<Track>>::get_mut(self).as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -216,9 +217,11 @@ pub(crate) fn per_track <'a, T: Content<TuiOut> + 'a, U: TracksSizes<'a>> (
|
|||
)
|
||||
}
|
||||
|
||||
impl Arrangement {
|
||||
impl<T: HasTracks + HasScenes + HasClock + HasJack> AddTrack for T {}
|
||||
|
||||
pub trait AddTrack: HasTracks + HasScenes + HasClock + HasJack {
|
||||
/// Add multiple tracks
|
||||
pub fn tracks_add (
|
||||
fn tracks_add (
|
||||
&mut self,
|
||||
count: usize,
|
||||
width: Option<usize>,
|
||||
|
|
@ -239,7 +242,7 @@ impl Arrangement {
|
|||
}
|
||||
|
||||
/// Add a track
|
||||
pub fn track_add (
|
||||
fn track_add (
|
||||
&mut self,
|
||||
name: Option<&str>,
|
||||
color: Option<ItemTheme>,
|
||||
|
|
|
|||
0
crates/device/src/arranger/arranger_tracks.rs
Normal file
0
crates/device/src/arranger/arranger_tracks.rs
Normal file
|
|
@ -1,78 +1,5 @@
|
|||
use crate::*;
|
||||
|
||||
impl Arrangement {
|
||||
/// Width of display
|
||||
pub(crate) fn w (&self) -> u16 {
|
||||
self.size.w() as u16
|
||||
}
|
||||
/// Width allocated for sidebar.
|
||||
pub(crate) fn w_sidebar (&self) -> u16 {
|
||||
self.w() / if self.is_editing() { 16 } else { 8 } as u16
|
||||
}
|
||||
/// Width taken by all tracks.
|
||||
pub(crate) fn w_tracks (&self) -> u16 {
|
||||
self.tracks_with_sizes().last().map(|(_, _, _, x)|x as u16).unwrap_or(0)
|
||||
}
|
||||
/// Width available to display tracks.
|
||||
pub(crate) fn w_tracks_area (&self) -> u16 {
|
||||
self.w().saturating_sub(2 * self.w_sidebar())
|
||||
}
|
||||
/// Height of display
|
||||
pub(crate) fn h (&self) -> u16 {
|
||||
self.size.h() as u16
|
||||
}
|
||||
/// Height available to display track headers.
|
||||
pub(crate) fn h_tracks_area (&self) -> u16 {
|
||||
5 // FIXME
|
||||
//self.h().saturating_sub(self.h_inputs() + self.h_outputs())
|
||||
}
|
||||
/// Height available to display tracks.
|
||||
pub(crate) fn h_scenes_area (&self) -> u16 {
|
||||
//15
|
||||
self.h().saturating_sub(
|
||||
self.h_inputs() +
|
||||
self.h_outputs() +
|
||||
self.h_devices() +
|
||||
13 // FIXME
|
||||
)
|
||||
}
|
||||
/// Height taken by all scenes.
|
||||
pub(crate) fn h_scenes (&self) -> u16 {
|
||||
let (selected_track, selected_scene) = match Has::<Option<Selection>>::get(self) {
|
||||
Some(Selection::Track(t)) => (Some(*t), None),
|
||||
Some(Selection::Scene(s)) => (None, Some(*s)),
|
||||
Some(Selection::TrackClip { track, scene }) => (Some(*track), Some(*scene)),
|
||||
_ => (None, None)
|
||||
};
|
||||
self.scenes_with_sizes(
|
||||
self.is_editing,
|
||||
ArrangerView::H_SCENE,
|
||||
ArrangerView::H_EDITOR,
|
||||
selected_track,
|
||||
selected_scene
|
||||
)
|
||||
.last()
|
||||
.map(|(_, _, _, y)|y as u16).unwrap_or(0)
|
||||
}
|
||||
/// Height taken by all inputs.
|
||||
pub(crate) fn h_inputs (&self) -> u16 {
|
||||
self.inputs_with_sizes()
|
||||
.last()
|
||||
.map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
|
||||
}
|
||||
/// Height taken by all outputs.
|
||||
pub(crate) fn h_outputs (&self) -> u16 {
|
||||
self.outputs_with_sizes()
|
||||
.last()
|
||||
.map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
|
||||
}
|
||||
/// Height taken by visible device slots.
|
||||
pub(crate) fn h_devices (&self) -> u16 {
|
||||
2
|
||||
//1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ArrangerView<'a> {
|
||||
pub arrangement: &'a Arrangement,
|
||||
|
||||
|
|
@ -107,17 +34,18 @@ impl<'a> ArrangerView<'a> {
|
|||
arrangement: &'a Arrangement,
|
||||
editor: Option<&'a MidiEditor>
|
||||
) -> Self {
|
||||
let is_editing = editor.is_some();
|
||||
let selected = arrangement.selected;
|
||||
let h_tracks_area = arrangement.h_tracks_area();
|
||||
let h_scenes_area = arrangement.h_scenes_area();
|
||||
let h_scenes = arrangement.h_scenes();
|
||||
let h_scenes = arrangement.h_scenes(is_editing);
|
||||
Self {
|
||||
arrangement,
|
||||
is_editing: editor.is_some(),
|
||||
is_editing,
|
||||
|
||||
width: arrangement.w(),
|
||||
width_mid: arrangement.w_tracks_area(),
|
||||
width_side: arrangement.w_sidebar(),
|
||||
width_mid: arrangement.w_tracks_area(is_editing),
|
||||
width_side: arrangement.w_sidebar(is_editing),
|
||||
|
||||
inputs_height: arrangement.h_inputs(),
|
||||
inputs_count: arrangement.midi_ins.len(),
|
||||
|
|
@ -214,4 +142,144 @@ impl<'a> ArrangerView<'a> {
|
|||
track.devices.get(0).map(|device|wrap(bg.rgb, fg, device.name()))
|
||||
}))
|
||||
}
|
||||
|
||||
/// Default scene height.
|
||||
pub(crate) const H_SCENE: usize = 2;
|
||||
|
||||
/// Default editor height.
|
||||
pub(crate) const H_EDITOR: usize = 15;
|
||||
|
||||
/// Render scenes with clips
|
||||
pub(crate) fn scenes (&'a self, editor: Option<MidiEditor>) -> impl Content<TuiOut> + 'a {
|
||||
/// A scene with size and color.
|
||||
type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option<ItemTheme>);
|
||||
let Self {
|
||||
arrangement,
|
||||
width, width_side, width_mid,
|
||||
scenes_height, scene_last, scene_selected,
|
||||
track_selected, is_editing, ..
|
||||
} = self;
|
||||
|
||||
let selection = Has::<Option<Selection>>::get(self.arrangement);
|
||||
let selected_track = selection.map(|s|s.track()).flatten();
|
||||
let selected_scene = selection.map(|s|s.scene()).flatten();
|
||||
|
||||
let scenes_with_scene_colors = ||HasScenes::scenes_with_sizes(self.arrangement,
|
||||
*is_editing,
|
||||
Self::H_SCENE,
|
||||
Self::H_EDITOR,
|
||||
selected_track,
|
||||
selected_scene,
|
||||
).map_while(|(s, scene, y1, y2)|if y2 as u16 > *scenes_height {
|
||||
None
|
||||
} else {
|
||||
Some((s, scene, y1, y2, if s == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(arrangement.scenes()[s-1].color)
|
||||
}))
|
||||
});
|
||||
|
||||
let scene_header = |(s, scene, y1, y2, previous): SceneWithColor, _|{
|
||||
let height = (1 + y2 - y1) as u16;
|
||||
let name = Some(scene.name.clone());
|
||||
let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⯈ ", name))));
|
||||
let same_track = true;
|
||||
let selected = same_track && *scene_selected == Some(s);
|
||||
let neighbor = same_track && s > 0 && *scene_selected == Some(s - 1);
|
||||
let is_last = *scene_last == s;
|
||||
let theme = scene.color;
|
||||
let fg = theme.lightest.rgb;
|
||||
let bg = if selected { theme.light } else { theme.base }.rgb;
|
||||
let hi = if let Some(previous) = previous {
|
||||
if neighbor {
|
||||
previous.light.rgb
|
||||
} else {
|
||||
previous.base.rgb
|
||||
}
|
||||
} else {
|
||||
Reset
|
||||
};
|
||||
let lo = if is_last {
|
||||
Reset
|
||||
} else if selected {
|
||||
theme.light.rgb
|
||||
} else {
|
||||
theme.base.rgb
|
||||
};
|
||||
Fill::x(map_south(y1 as u16, height, Fixed::y(height, Phat {
|
||||
width: 0, height: 0, content, colors: [fg, bg, hi, lo]
|
||||
})))
|
||||
};
|
||||
|
||||
let scenes_with_track_colors = |track: usize| arrangement.scenes_with_sizes(
|
||||
self.is_editing,
|
||||
Self::H_SCENE,
|
||||
Self::H_EDITOR,
|
||||
selected_track,
|
||||
selected_scene,
|
||||
).map_while(|(s, scene, y1, y2)|if y2 as u16 > self.scenes_height {
|
||||
None
|
||||
} else {
|
||||
Some((s, scene, y1, y2, if s == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(self.arrangement.scenes[s-1].clips[track].as_ref()
|
||||
.map(|c|c.read().unwrap().color)
|
||||
.unwrap_or(ItemTheme::G[32]))
|
||||
}))
|
||||
});
|
||||
|
||||
Tryptich::center(*scenes_height)
|
||||
|
||||
.left(*width_side, Map::new(
|
||||
||scenes_with_scene_colors(),
|
||||
scene_header))
|
||||
|
||||
.middle(*width_mid, per_track(
|
||||
*width_mid,
|
||||
||self.tracks_with_sizes_scrolled(),
|
||||
|track_index, track|Map::new(
|
||||
||scenes_with_track_colors(track_index),
|
||||
|(s, scene, y1, y2, previous): SceneWithColor<'a>, _|{
|
||||
let (name, theme) = if let Some(clip) = &scene.clips[track_index] {
|
||||
let clip = clip.read().unwrap();
|
||||
(Some(clip.name.clone()), clip.color)
|
||||
} else {
|
||||
(None, ItemTheme::G[32])
|
||||
};
|
||||
let height = (1 + y2 - y1) as u16;
|
||||
let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⏹ ", name))));
|
||||
let same_track = *track_selected == Some(track_index);
|
||||
let selected = same_track && *scene_selected == Some(s);
|
||||
let neighbor = same_track && s > 0 && *scene_selected == Some(s - 1);
|
||||
let is_last = *scene_last == s;
|
||||
let fg = theme.lightest.rgb;
|
||||
let bg = if selected { theme.light } else { theme.base }.rgb;
|
||||
let hi = if let Some(previous) = previous {
|
||||
if neighbor {
|
||||
previous.light.rgb
|
||||
} else {
|
||||
previous.base.rgb
|
||||
}
|
||||
} else {
|
||||
Reset
|
||||
};
|
||||
let lo = if is_last {
|
||||
Reset
|
||||
} else if selected {
|
||||
theme.light.rgb
|
||||
} else {
|
||||
theme.base.rgb
|
||||
};
|
||||
map_south(y1 as u16, height, Bsp::b(Fixed::y(height, Phat {
|
||||
width: 0, height: 0, content, colors: [fg, bg, hi, lo]
|
||||
}), When(
|
||||
*is_editing && same_track && *scene_selected == Some(s),
|
||||
editor
|
||||
)))
|
||||
})))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use crate::*;
|
||||
|
||||
mod clock_api; pub use self::clock_api::*;
|
||||
mod clock_model; pub use self::clock_model::*;
|
||||
mod clock_view; pub use self::clock_view::*;
|
||||
|
|
@ -7,11 +9,11 @@ pub trait HasClock: Send + Sync {
|
|||
fn clock_mut (&mut self) -> &mut Clock;
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! has_clock {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasClock for $Struct $(<$($L),*$($T),*>)? {
|
||||
fn clock (&$self) -> &Clock { &$cb }
|
||||
fn clock_mut (&mut $self) -> &mut Clock { &mut $cb }
|
||||
}
|
||||
impl<T: Has<Clock>> HasClock for T {
|
||||
fn clock (&self) -> &Clock {
|
||||
self.get()
|
||||
}
|
||||
fn clock_mut (&mut self) -> &mut Clock {
|
||||
self.get_mut()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,19 @@
|
|||
use crate::*;
|
||||
|
||||
impl<T: Has<Vec<Device>> + Has<Track>> HasDevices for T {
|
||||
fn devices (&self) -> &Vec<Device> {
|
||||
self.get()
|
||||
}
|
||||
fn devices_mut (&mut self) -> &mut Vec<Device> {
|
||||
self.get_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasDevices: Has<Track> {
|
||||
fn devices (&self) -> &Vec<Device>;
|
||||
fn devices_mut (&mut self) -> &mut Vec<Device>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Device {
|
||||
#[cfg(feature = "sampler")]
|
||||
|
|
@ -49,3 +63,7 @@ audio!(|self: DeviceAudio<'a>, client, scope|{
|
|||
Sf2 => { todo!() }, // TODO
|
||||
}
|
||||
});
|
||||
|
||||
#[tengri_proc::command(Device)]
|
||||
impl DeviceCommand {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
//! MIDI sequencer
|
||||
use crate::*;
|
||||
|
||||
impl<T: Has<Sequencer>> HasSequencer for T {
|
||||
fn sequencer (&self) -> &impl MidiSequencer {
|
||||
self.get()
|
||||
}
|
||||
fn sequencer_mut (&mut self) -> &mut impl MidiSequencer {
|
||||
self.get_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasSequencer {
|
||||
fn sequencer (&self) -> &impl MidiSequencer;
|
||||
fn sequencer_mut (&mut self) -> &mut impl MidiSequencer;
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! has_sequencer {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasSequencer for $Struct $(<$($L),*$($T),*>)? {
|
||||
fn sequencer (&$self) -> &impl MidiSequencer { &$cb }
|
||||
fn sequencer_mut (&mut $self) -> &mut impl MidiSequencer { &mut$cb }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains state for playing a clip
|
||||
pub struct Sequencer {
|
||||
/// State of clock and playhead
|
||||
|
|
@ -102,7 +102,7 @@ impl std::fmt::Debug for Sequencer {
|
|||
}
|
||||
}
|
||||
|
||||
has_clock!(|self: Sequencer|self.clock);
|
||||
has!(Clock: |self: Sequencer|self.clock);
|
||||
has!(Vec<JackMidiIn>: |self:Sequencer| self.midi_ins);
|
||||
has!(Vec<JackMidiOut>: |self:Sequencer| self.midi_outs);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ version = { workspace = true }
|
|||
|
||||
[dependencies]
|
||||
tengri = { workspace = true }
|
||||
tengri_proc = { workspace = true }
|
||||
jack = { workspace = true }
|
||||
midly = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@ use crate::*;
|
|||
use super::*;
|
||||
use self::JackState::*;
|
||||
|
||||
impl<T: Has<Jack>> HasJack for T {
|
||||
fn jack (&self) -> &Jack {
|
||||
self.get()
|
||||
}
|
||||
}
|
||||
|
||||
/// Things that can provide a [jack::Client] reference.
|
||||
pub trait HasJack {
|
||||
/// Return the internal [jack::Client] handle
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ pub(crate) use std::sync::{Arc, atomic::{AtomicUsize, AtomicBool, Ordering::Rela
|
|||
pub(crate) use std::fmt::Debug;
|
||||
pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem};
|
||||
|
||||
pub(crate) use ::tengri::{from, Usually, tui::*};
|
||||
pub(crate) use ::tengri::{from, Usually, Perhaps, tui::*};
|
||||
|
||||
pub use ::atomic_float; pub(crate) use atomic_float::*;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
use crate::*;
|
||||
|
||||
#[tengri_proc::command(JackMidiIn)]
|
||||
impl MidiInputCommand {
|
||||
}
|
||||
|
||||
impl<T: Has<Vec<JackMidiIn>>> HasMidiIns for T {
|
||||
fn midi_ins (&self) -> &Vec<JackMidiIn> {
|
||||
self.get()
|
||||
|
|
@ -32,11 +36,20 @@ pub trait HasMidiIns {
|
|||
data
|
||||
})
|
||||
}
|
||||
fn midi_in_add (&mut self, jack: &Jack) -> Usually<()> {
|
||||
}
|
||||
|
||||
pub type CollectedMidiInput<'a> = Vec<Vec<(u32, Result<LiveEvent<'a>, MidiError>)>>;
|
||||
|
||||
impl<T: HasMidiIns + HasJack> AddMidiIn for T {
|
||||
fn midi_in_add (&mut self) -> Usually<()> {
|
||||
let index = self.midi_ins().len();
|
||||
self.midi_ins_mut().push(JackMidiIn::new(jack, &format!("M/{index}"), &[])?);
|
||||
let port = JackMidiIn::new(self.jack(), &format!("M/{index}"), &[])?;
|
||||
self.midi_ins_mut().push(port);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub type CollectedMidiInput<'a> = Vec<Vec<(u32, Result<LiveEvent<'a>, MidiError>)>>;
|
||||
/// May create new MIDI input ports.
|
||||
pub trait AddMidiIn {
|
||||
fn midi_in_add (&mut self) -> Usually<()>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
use crate::*;
|
||||
|
||||
#[tengri_proc::command(JackMidiOut)]
|
||||
impl MidiOutputCommand {
|
||||
}
|
||||
|
||||
impl<T: Has<Vec<JackMidiOut>>> HasMidiOuts for T {
|
||||
fn midi_outs (&self) -> &Vec<JackMidiOut> {
|
||||
self.get()
|
||||
|
|
@ -25,9 +29,19 @@ pub trait HasMidiOuts {
|
|||
data
|
||||
})
|
||||
}
|
||||
fn midi_out_add (&mut self, jack: &Jack) -> Usually<()> {
|
||||
}
|
||||
|
||||
/// Trail for thing that may gain new MIDI ports.
|
||||
impl<T: HasMidiOuts + HasJack> AddMidiOut for T {
|
||||
fn midi_out_add (&mut self) -> Usually<()> {
|
||||
let index = self.midi_outs().len();
|
||||
self.midi_outs_mut().push(JackMidiOut::new(&jack, &format!("{index}/M"), &[])?);
|
||||
let port = JackMidiOut::new(self.jack(), &format!("{index}/M"), &[])?;
|
||||
self.midi_outs_mut().push(port);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// May create new MIDI output ports.
|
||||
pub trait AddMidiOut {
|
||||
fn midi_out_add (&mut self) -> Usually<()>;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue