wip: down to 25 errors woo

This commit is contained in:
🪞👃🪞 2025-05-14 02:13:13 +03:00
parent 89288f2920
commit 6ce83fb27a
25 changed files with 688 additions and 620 deletions

1
Cargo.lock generated
View file

@ -2315,6 +2315,7 @@ dependencies = [
"jack",
"midly",
"tengri",
"tengri_proc",
"uuid",
]

View file

@ -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(())
}
}

View file

@ -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;
}

View file

@ -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!()
}
}

View file

@ -1,7 +0,0 @@
use crate::*;
pub trait RenderTracks {
}
impl<T: HasTracks> Audio for T {
}

View file

@ -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());
}
}
}
}

View file

@ -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(())
}
}

View file

@ -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());
}
}
}
}

View file

@ -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)
}
}

View file

@ -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:"))

View file

@ -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]))
}
}

View 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]))
}
}

View file

@ -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!()
})
}
}

View file

@ -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>,

View 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
)))
})))
}
}

View file

@ -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()
}
}

View file

@ -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 {
}

View file

@ -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);

View file

@ -5,6 +5,7 @@ version = { workspace = true }
[dependencies]
tengri = { workspace = true }
tengri_proc = { workspace = true }
jack = { workspace = true }
midly = { workspace = true }
uuid = { workspace = true }

View file

@ -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

View file

@ -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::*;

View file

@ -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<()>;
}

View file

@ -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<()>;
}