mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
api: compact
This commit is contained in:
parent
e4808f8fc1
commit
3ef3d5eb6f
22 changed files with 441 additions and 558 deletions
|
|
@ -1,14 +1,14 @@
|
||||||
(@esc menu)
|
(@esc menu)
|
||||||
(@f1 help)
|
(@f1 help)
|
||||||
|
|
||||||
(@u undo 1)
|
(@u undo 1)
|
||||||
(@shift-u redo 1)
|
(@shift-u redo 1)
|
||||||
|
|
||||||
(@space clock toggle)
|
(@space clock toggle)
|
||||||
(@shift-space clock toggle 0)
|
(@shift-space clock toggle 0)
|
||||||
|
|
||||||
(@c color)
|
(@c color)
|
||||||
|
|
||||||
(@q launch)
|
(@q launch)
|
||||||
|
|
||||||
(@r record/begin :note-pos)
|
(@r sampler record/begin :pitch)
|
||||||
|
|
|
||||||
|
|
@ -1,437 +1,192 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
expose!([self: Tek] {
|
type MaybeClip = Option<Arc<RwLock<MidiClip>>>;
|
||||||
[bool] => {}
|
|
||||||
[isize] => {}
|
macro_rules! ns { ($C:ty, $s:expr, $a:expr, $W:expr) => { <$C>::try_from_expr($s, $a).map($W) } }
|
||||||
[Color] => {}
|
macro_rules! cmd { ($cmd:expr) => {{ $cmd; None }}; }
|
||||||
[Arc<RwLock<MidiClip>>] => {}
|
macro_rules! cmd_todo { ($msg:literal) => {{ println!($msg); None }}; }
|
||||||
[u16] => { ":w-sidebar" => self.w_sidebar(), }
|
|
||||||
[usize] => { ":scene-last" => self.scenes.len(),
|
view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); {
|
||||||
":track-last" => self.tracks.len(), }
|
":nil" => Box::new("nil"),
|
||||||
[Option<usize>] => { ":scene" => self.selected.scene(),
|
":modal" => self.view_modal(),
|
||||||
":track" => self.selected.track(), }
|
":status" => self.view_status(),
|
||||||
[Option<Arc<RwLock<MidiClip>>>] => {
|
":transport" => self.view_transport(),
|
||||||
":clip" => match self.selected {
|
":arranger" => self.view_arranger(),
|
||||||
Selection::Clip(t, s) => self.scenes[s].clips[t].clone(),
|
":pool" => self.view_pool(),
|
||||||
_ => None
|
":editor" => self.editor().map(|e|Bsp::n(Bsp::e(e.clip_status(), e.edit_status()), e)),
|
||||||
}
|
":samples-keys" => self.sampler().map(|s|s.view_list(false, self.editor().unwrap())),
|
||||||
}
|
":samples-grid" => self.sampler().map(|s|s.view_grid()),
|
||||||
[Selection] => {
|
":sample-viewer" => self.sampler().map(|s|s.view_sample(self.editor().unwrap().note_pos())),
|
||||||
":scene-next" => self.selected.scene_next(self.scenes.len()),
|
|
||||||
":scene-prev" => self.selected.scene_prev(),
|
|
||||||
":track-next" => self.selected.track_next(self.tracks.len()),
|
|
||||||
":track-prev" => self.selected.track_prev(),
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
impose!([app: Tek] {
|
expose!([self: Tek]
|
||||||
|
([bool])
|
||||||
|
([isize])
|
||||||
|
([Color])
|
||||||
|
([Arc<RwLock<MidiClip>>])
|
||||||
|
([u7] (":pitch" (self.editor().map(|e|e.note_pos()).unwrap() as u8).into()))
|
||||||
|
([u16] (":w-sidebar" self.w_sidebar()))
|
||||||
|
([usize] (":scene-last" self.scenes.len())
|
||||||
|
(":track-last" self.tracks.len()))
|
||||||
|
([Option<usize>] (":scene" self.selected.scene())
|
||||||
|
(":track" self.selected.track()))
|
||||||
|
([MaybeClip]
|
||||||
|
(":clip" match self.selected {
|
||||||
|
Selection::Clip(t, s) => self.scenes[s].clips[t].clone(),
|
||||||
|
_ => None
|
||||||
|
}))
|
||||||
|
([Selection]
|
||||||
|
(":scene-next" self.selected.scene_next(self.scenes.len()))
|
||||||
|
(":scene-prev" self.selected.scene_prev())
|
||||||
|
(":track-next" self.selected.track_next(self.tracks.len()))
|
||||||
|
(":track-prev" self.selected.track_prev())));
|
||||||
|
|
||||||
TekCommand => {
|
impose!([app: Tek]
|
||||||
|
(TekCommand:
|
||||||
("menu" [] Some(Self::ToggleMenu))
|
("menu" [] Some(Self::ToggleMenu))
|
||||||
("help" [] Some(Self::ToggleHelp))
|
("help" [] Some(Self::ToggleHelp))
|
||||||
("stop" [] Some(Self::StopAll))
|
("stop" [] Some(Self::StopAll))
|
||||||
("undo" [d: usize] Some(Self::History(-(d.unwrap_or(0)as isize))))
|
("undo" [d: usize] Some(Self::History(-(d.unwrap_or(0) as isize))))
|
||||||
("redo" [d: usize] Some(Self::History(d.unwrap_or(0) as isize)))
|
("redo" [d: usize] Some(Self::History(d.unwrap_or(0) as isize)))
|
||||||
("zoom" [z: usize] Some(Self::Zoom(z)))
|
("zoom" [z: usize] Some(Self::Zoom(z)))
|
||||||
("edit" [] Some(Self::Edit(None)))
|
("edit" [] Some(Self::Edit(None)))
|
||||||
("edit" [c: bool] Some(Self::Edit(c)))
|
("edit" [c: bool] Some(Self::Edit(c)))
|
||||||
("color" [] Some(Self::Color(ItemPalette::random())))
|
("color" [] Some(Self::Color(ItemTheme::random())))
|
||||||
("color" [c: Color] Some(Self::Color(c.map(ItemPalette::from).expect("no color"))))
|
("color" [c: Color] Some(Self::Color(c.map(ItemTheme::from).expect("no color"))))
|
||||||
("enqueue" [c: Arc<RwLock<MidiClip>>] Some(Self::Enqueue(c)))
|
("enqueue" [c: Arc<RwLock<MidiClip>>] Some(Self::Enqueue(c)))
|
||||||
("launch" [] Some(Self::Launch))
|
("launch" [] Some(Self::Launch))
|
||||||
("scene" [,..a] SceneCommand::try_from_expr(app, a).map(Self::Scene))
|
|
||||||
("track" [,..a] TrackCommand::try_from_expr(app, a).map(Self::Track))
|
|
||||||
("input" [,..a] InputCommand::try_from_expr(app, a).map(Self::Input))
|
|
||||||
("output" [,..a] OutputCommand::try_from_expr(app, a).map(Self::Output))
|
|
||||||
("select" [t: Selection] Some(t.map(Self::Select).expect("no selection")))
|
("select" [t: Selection] Some(t.map(Self::Select).expect("no selection")))
|
||||||
("clip" [,..a] ClipCommand::try_from_expr(app, a)
|
("clock" [,..a] ns!(ClockCommand, app.clock(), a, Self::Clock))
|
||||||
.map(Self::Clip))
|
("scene" [,..a] ns!(SceneCommand, app, a, Self::Scene))
|
||||||
("clock" [,..a] ClockCommand::try_from_expr(app.clock(), a)
|
("track" [,..a] ns!(TrackCommand, app, a, Self::Track))
|
||||||
.map(Self::Clock))
|
("input" [,..a] ns!(InputCommand, app, a, Self::Input))
|
||||||
("editor" [,..a] MidiEditCommand::try_from_expr(app.editor.as_ref().expect("no editor"), a)
|
("output" [,..a] ns!(OutputCommand, app, a, Self::Output))
|
||||||
.map(Self::Editor))
|
("clip" [,..a] ns!(ClipCommand, app, a, Self::Clip))
|
||||||
("pool" [,..a] PoolCommand::try_from_expr(app.pool.as_ref().expect("no pool"), a)
|
("pool" [,..a] app.pool.as_ref().map(|p|ns!(PoolCommand, p, a, Self::Pool)).flatten())
|
||||||
.map(Self::Pool))
|
("editor" [,..a] app.editor().map(|e|ns!(MidiEditCommand, e, a, Self::Editor)).flatten())
|
||||||
|
("sampler" [,..a] app.sampler().map(|s|ns!(SamplerCommand, s, a, Self::Sampler)).flatten())
|
||||||
("select" [t: usize, s: usize] Some(match (t.expect("no track"), s.expect("no scene")) {
|
("select" [t: usize, s: usize] Some(match (t.expect("no track"), s.expect("no scene")) {
|
||||||
(0, 0) => Self::Select(Selection::Mix),
|
(0, 0) => Self::Select(Selection::Mix),
|
||||||
(t, 0) => Self::Select(Selection::Track(t)),
|
(t, 0) => Self::Select(Selection::Track(t)),
|
||||||
(0, s) => Self::Select(Selection::Scene(s)),
|
(0, s) => Self::Select(Selection::Scene(s)),
|
||||||
(t, s) => Self::Select(Selection::Clip(t, s)),
|
(t, s) => Self::Select(Selection::Clip(t, s)) })))
|
||||||
}))
|
(ClipCommand:
|
||||||
//("sampler" [,..a]
|
("edit" [a: MaybeClip] Some(Self::Edit(a.unwrap())))
|
||||||
// Self::Sampler( //SamplerCommand::try_from_expr(app.sampler().as_ref().expect("no sampler"), a).expect("invalid command")))
|
("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random())))
|
||||||
}
|
|
||||||
|
|
||||||
ClipCommand => {
|
|
||||||
("get" [a: usize, b: usize] Some(Self::Get(a.unwrap(), b.unwrap())))
|
|
||||||
("put" [a: usize, b: usize, c: Option<Arc<RwLock<MidiClip>>>] Some(Self::Put(a.unwrap(), b.unwrap(), c.unwrap())))
|
|
||||||
("enqueue" [a: usize, b: usize] Some(Self::Enqueue(a.unwrap(), b.unwrap())))
|
("enqueue" [a: usize, b: usize] Some(Self::Enqueue(a.unwrap(), b.unwrap())))
|
||||||
("edit" [a: Option<Arc<RwLock<MidiClip>>>] Some(Self::Edit(a.unwrap())))
|
("get" [a: usize, b: usize] Some(Self::Get(a.unwrap(), b.unwrap())))
|
||||||
("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap())))
|
("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap())))
|
||||||
("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemPalette::random())))
|
("put" [a: usize, b: usize, c: MaybeClip] Some(Self::Put(a.unwrap(), b.unwrap(), c.unwrap()))))
|
||||||
}
|
|
||||||
|
|
||||||
InputCommand => {
|
(InputCommand:
|
||||||
("add" [] Some(Self::Add))
|
("add" [] Some(Self::Add)))
|
||||||
}
|
|
||||||
|
|
||||||
OutputCommand => {
|
(OutputCommand:
|
||||||
("add" [] Some(Self::Add))
|
("add" [] Some(Self::Add)))
|
||||||
}
|
|
||||||
|
|
||||||
SceneCommand => {
|
(SceneCommand:
|
||||||
("add" [] Some(Self::Add))
|
("add" [] Some(Self::Add))
|
||||||
("del" [a: usize] Some(Self::Del(0)))
|
("del" [a: usize] Some(Self::Del(0)))
|
||||||
("zoom" [a: usize] Some(Self::SetZoom(a.unwrap())))
|
("zoom" [a: usize] Some(Self::SetZoom(a.unwrap())))
|
||||||
("color" [a: usize] Some(Self::SetColor(a.unwrap(), ItemPalette::G[128])))
|
("color" [a: usize] Some(Self::SetColor(a.unwrap(), ItemTheme::G[128])))
|
||||||
("enqueue" [a: usize] Some(Self::Enqueue(a.unwrap())))
|
("enqueue" [a: usize] Some(Self::Enqueue(a.unwrap())))
|
||||||
("swap" [a: usize, b: usize] Some(Self::Swap(a.unwrap(), b.unwrap())))
|
("swap" [a: usize, b: usize] Some(Self::Swap(a.unwrap(), b.unwrap()))))
|
||||||
}
|
|
||||||
|
|
||||||
TrackCommand => {
|
(TrackCommand:
|
||||||
("add" [] Some(Self::Add))
|
("add" [] Some(Self::Add))
|
||||||
("size" [a: usize] Some(Self::SetSize(a.unwrap())))
|
("size" [a: usize] Some(Self::SetSize(a.unwrap())))
|
||||||
("zoom" [a: usize] Some(Self::SetZoom(a.unwrap())))
|
("zoom" [a: usize] Some(Self::SetZoom(a.unwrap())))
|
||||||
("color" [a: usize] Some(Self::SetColor(a.unwrap(), ItemPalette::random())))
|
("color" [a: usize] Some(Self::SetColor(a.unwrap(), ItemTheme::random())))
|
||||||
("del" [a: usize] Some(Self::Del(a.unwrap())))
|
("del" [a: usize] Some(Self::Del(a.unwrap())))
|
||||||
("stop" [a: usize] Some(Self::Stop(a.unwrap())))
|
("stop" [a: usize] Some(Self::Stop(a.unwrap())))
|
||||||
("swap" [a: usize, b: usize] Some(Self::Swap(a.unwrap(), b.unwrap())))
|
("swap" [a: usize, b: usize] Some(Self::Swap(a.unwrap(), b.unwrap())))
|
||||||
("play" [] Some(Self::TogglePlay))
|
("play" [] Some(Self::TogglePlay))
|
||||||
("solo" [] Some(Self::ToggleSolo))
|
("solo" [] Some(Self::ToggleSolo))
|
||||||
("rec" [] Some(Self::ToggleRecord))
|
("rec" [] Some(Self::ToggleRec))
|
||||||
("mon" [] Some(Self::ToggleMonitor))
|
("mon" [] Some(Self::ToggleMon))));
|
||||||
}
|
|
||||||
|
|
||||||
});
|
defcom!([self, app: Tek]
|
||||||
|
|
||||||
defcom! { |self, app: Tek|
|
(TekCommand
|
||||||
|
(Sampler [cmd: SamplerCommand] cmd_todo!("\n\rtodo: sampler {cmd:?}"))
|
||||||
|
(Scene [cmd: SceneCommand] cmd.delegate(app, Self::Scene)?)
|
||||||
|
(Track [cmd: TrackCommand] cmd.delegate(app, Self::Track)?)
|
||||||
|
(Output [cmd: OutputCommand] cmd.delegate(app, Self::Output)?)
|
||||||
|
(Input [cmd: InputCommand] cmd.delegate(app, Self::Input)?)
|
||||||
|
(Clip [cmd: ClipCommand] cmd.delegate(app, Self::Clip)?)
|
||||||
|
(Clock [cmd: ClockCommand] cmd.delegate(app, Self::Clock)?)
|
||||||
|
(Editor [cmd: MidiEditCommand] delegate_to_editor(app, cmd)?)
|
||||||
|
(Pool [cmd: PoolCommand] delegate_to_pool(app, cmd)?)
|
||||||
|
(ToggleHelp [] cmd!({ app.modal = match app.modal { Some(Modal::Help) => None, _ => Some(Modal::Help) }}))
|
||||||
|
(ToggleMenu [] cmd!({ app.modal = match app.modal { Some(Modal::Menu) => None, _ => Some(Modal::Menu) }}))
|
||||||
|
(Color [p: ItemTheme] app.set_color(Some(p)).map(Self::Color))
|
||||||
|
(Enqueue [c: MaybeClip] cmd_todo!("\n\rtodo: enqueue {c:?}"))
|
||||||
|
(History [d: isize] cmd_todo!("\n\rtodo: history {d:?}"))
|
||||||
|
(Zoom [z: Option<usize>] cmd_todo!("\n\rtodo: zoom {z:?}"))
|
||||||
|
(Edit [value: Option<bool>] cmd!(app.toggle_editor(value)))
|
||||||
|
(Launch [] cmd!(app.launch()))
|
||||||
|
(Select [s: Selection] cmd!(app.select(s)))
|
||||||
|
(StopAll [] cmd!(app.stop_all())))
|
||||||
|
|
||||||
TekCommand {
|
(InputCommand
|
||||||
|
(Add [] cmd!(app.add_midi_in()?)))
|
||||||
|
|
||||||
ToggleHelp => {
|
(OutputCommand
|
||||||
app.modal = match app.modal {
|
(Add [] cmd!(app.add_midi_out()?)))
|
||||||
Some(Modal::Help) => None,
|
|
||||||
_ => Some(Modal::Help)
|
|
||||||
};
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
ToggleMenu => {
|
(TrackCommand
|
||||||
app.modal = match app.modal {
|
(TogglePlay [] Some(Self::TogglePlay))
|
||||||
Some(Modal::Menu) => None,
|
(ToggleSolo [] Some(Self::ToggleSolo))
|
||||||
_ => Some(Modal::Menu)
|
(SetSize [t: usize] cmd_todo!("\n\rtodo: {self:?}"))
|
||||||
};
|
(SetZoom [z: usize] cmd_todo!("\n\rtodo: {self:?}"))
|
||||||
None
|
(Swap [a: usize, b: usize] cmd_todo!("\n\rtodo: {self:?}"))
|
||||||
}
|
(Del [index: usize] cmd!(app.track_del(index)))
|
||||||
|
(Stop [index: usize] cmd!(app.tracks[index].player.enqueue_next(None)))
|
||||||
|
(Add [] Some(Self::Del(app.track_add_focus()?)))
|
||||||
|
(SetColor [i: usize, c: ItemTheme] Some(Self::SetColor(i, app.track_set_color(i, c))))
|
||||||
|
(ToggleRec [] { app.track_toggle_record(); Some(Self::ToggleRec) })
|
||||||
|
(ToggleMon [] { app.track_toggle_monitor(); Some(Self::ToggleMon) }))
|
||||||
|
|
||||||
Sampler(cmd: SamplerCommand) => {
|
(SceneCommand
|
||||||
println!("\n\rtodo: {cmd:?}");
|
(Swap [a: usize, b: usize] cmd_todo!("\n\rtodo: {self:?}"))
|
||||||
None
|
(SetSize [index: usize] cmd_todo!("\n\rtodo: {self:?}"))
|
||||||
}
|
(SetZoom [zoom: usize] cmd_todo!("\n\rtodo: {self:?}"))
|
||||||
|
(Enqueue [scene: usize] cmd!(app.scene_enqueue(scene)))
|
||||||
|
(Del [index: usize] cmd!(app.scene_del(index)))
|
||||||
|
(Add [] Some(Self::Del(app.scene_add_focus()?)))
|
||||||
|
(SetColor [i: usize, c: ItemTheme] Some(Self::SetColor(i, app.scene_set_color(i, c)))))
|
||||||
|
|
||||||
Enqueue(clip: Option<Arc<RwLock<MidiClip>>>) => {
|
(ClipCommand
|
||||||
println!("\n\rtodo: enqueue {clip:?}");
|
(Get [a: usize, b: usize] cmd_todo!("\n\rtodo: clip: get: {a} {b}"))
|
||||||
None
|
(Edit [clip: MaybeClip] cmd_todo!("\n\rtodo: clip: edit: {clip:?}"))
|
||||||
}
|
(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}"))
|
||||||
|
(Put [t: usize, s: usize, c: MaybeClip]
|
||||||
|
Some(Self::Put(t, s, app.clip_put(t, s, c))))
|
||||||
|
(Enqueue [t: usize, s: usize]
|
||||||
|
cmd!(app.tracks[t].player.enqueue_next(app.scenes[s].clips[t].as_ref())))
|
||||||
|
(SetColor [t: usize, s: usize, c: ItemTheme]
|
||||||
|
app.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o)))));
|
||||||
|
|
||||||
History(delta: isize) => {
|
fn delegate_to_editor (app: &mut Tek, cmd: MidiEditCommand) -> Perhaps<TekCommand> {
|
||||||
println!("\n\rtodo: {self:?}");
|
Ok(app.editor.as_mut().map(|editor|cmd.delegate(editor, TekCommand::Editor))
|
||||||
None
|
.transpose()?
|
||||||
}
|
.flatten())
|
||||||
|
}
|
||||||
Zoom(zoom: Option<usize>) => {
|
|
||||||
println!("\n\rtodo: {self:?}");
|
fn delegate_to_pool (app: &mut Tek, cmd: PoolCommand) -> Perhaps<TekCommand> {
|
||||||
None
|
Ok(if let Some(pool) = app.pool.as_mut() {
|
||||||
}
|
let undo = cmd.clone().delegate(pool, TekCommand::Pool)?;
|
||||||
|
if let Some(editor) = app.editor.as_mut() {
|
||||||
Scene(cmd: SceneCommand) =>
|
match cmd {
|
||||||
cmd.delegate(app, Self::Scene)?
|
// autoselect: automatically load selected clip in editor
|
||||||
|
// autocolor: update color in all places simultaneously
|
||||||
Track(cmd: TrackCommand) =>
|
PoolCommand::Select(_) | PoolCommand::Clip(PoolClipCommand::SetColor(_, _)) =>
|
||||||
cmd.delegate(app, Self::Track)?
|
editor.set_clip(pool.clip().as_ref()),
|
||||||
|
_ => {}
|
||||||
Output(cmd: OutputCommand) =>
|
}
|
||||||
cmd.delegate(app, Self::Output)?
|
};
|
||||||
|
undo
|
||||||
Input(cmd: InputCommand) =>
|
} else {
|
||||||
cmd.delegate(app, Self::Input)?
|
None
|
||||||
|
})
|
||||||
Clip(cmd: ClipCommand) =>
|
|
||||||
cmd.delegate(app, Self::Clip)?
|
|
||||||
|
|
||||||
Clock(cmd: ClockCommand) =>
|
|
||||||
cmd.delegate(app, Self::Clock)?
|
|
||||||
|
|
||||||
Editor(cmd: MidiEditCommand) => app.editor.as_mut()
|
|
||||||
.map(|editor|cmd.delegate(editor, Self::Editor))
|
|
||||||
.transpose()?
|
|
||||||
.flatten()
|
|
||||||
|
|
||||||
Pool(cmd: PoolCommand) => if let Some(pool) = app.pool.as_mut() {
|
|
||||||
let undo = cmd.clone().delegate(pool, Self::Pool)?;
|
|
||||||
if let Some(editor) = app.editor.as_mut() {
|
|
||||||
match cmd {
|
|
||||||
// autoselect: automatically load selected clip in editor
|
|
||||||
// autocolor: update color in all places simultaneously
|
|
||||||
PoolCommand::Select(_) | PoolCommand::Clip(PoolClipCommand::SetColor(_, _)) =>
|
|
||||||
editor.set_clip(pool.clip().as_ref()),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
undo
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
Color(palette: ItemPalette) => {
|
|
||||||
use Selection::*;
|
|
||||||
Some(Self::Color(match app.selected {
|
|
||||||
Mix => {
|
|
||||||
let old = app.color;
|
|
||||||
app.color = palette;
|
|
||||||
old
|
|
||||||
},
|
|
||||||
Track(t) => {
|
|
||||||
let old = app.tracks[t].color;
|
|
||||||
app.tracks[t].color = palette;
|
|
||||||
old
|
|
||||||
}
|
|
||||||
Scene(s) => {
|
|
||||||
let old = app.scenes[s].color;
|
|
||||||
app.scenes[s].color = palette;
|
|
||||||
old
|
|
||||||
}
|
|
||||||
Clip(t, s) => {
|
|
||||||
if let Some(ref clip) = app.scenes[s].clips[t] {
|
|
||||||
let mut clip = clip.write().unwrap();
|
|
||||||
let old = clip.color;
|
|
||||||
clip.color = palette;
|
|
||||||
old
|
|
||||||
} else {
|
|
||||||
return Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
Edit(value: Option<bool>) => {
|
|
||||||
let editing = app.is_editing();
|
|
||||||
let value = value.unwrap_or_else(||!app.is_editing());
|
|
||||||
app.editing.store(value, Relaxed);
|
|
||||||
if value {
|
|
||||||
app.clip_auto_create();
|
|
||||||
} else {
|
|
||||||
app.clip_auto_remove();
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
Launch => {
|
|
||||||
use Selection::*;
|
|
||||||
match app.selected {
|
|
||||||
Track(t) => app.tracks[t].player.enqueue_next(None),
|
|
||||||
Clip(t, s) => app.tracks[t].player.enqueue_next(app.scenes[s].clips[t].as_ref()),
|
|
||||||
Scene(s) => {
|
|
||||||
for t in 0..app.tracks.len() {
|
|
||||||
app.tracks[t].player.enqueue_next(app.scenes[s].clips[t].as_ref())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
Select(s: Selection) => {
|
|
||||||
app.selected = s;
|
|
||||||
// autoedit: load focused clip in editor.
|
|
||||||
if let Some(ref mut editor) = app.editor {
|
|
||||||
editor.set_clip(match app.selected {
|
|
||||||
Selection::Clip(t, s) if let Some(Some(Some(clip))) = app
|
|
||||||
.scenes.get(s).map(|s|s.clips.get(t)) => Some(clip),
|
|
||||||
_ => None
|
|
||||||
});
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
StopAll => {
|
|
||||||
for track in 0..app.tracks.len(){app.tracks[track].player.enqueue_next(None);}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
InputCommand {
|
|
||||||
|
|
||||||
Add => {
|
|
||||||
app.midi_ins.push(JackMidiIn::new(&app.jack, &format!("M/{}", app.midi_ins.len()), &[])?);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
OutputCommand {
|
|
||||||
|
|
||||||
Add => {
|
|
||||||
app.midi_outs.push(JackMidiOut::new(&app.jack, &format!("{}/M", app.midi_outs.len()), &[])?);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackCommand {
|
|
||||||
|
|
||||||
Swap(a: usize, b: usize) => {
|
|
||||||
println!("\n\rtodo: {self:?}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
SetSize(t: usize) => {
|
|
||||||
println!("\n\rtodo: {self:?}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
SetZoom(z: usize) => {
|
|
||||||
println!("\n\rtodo: {self:?}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
Add => {
|
|
||||||
use Selection::*;
|
|
||||||
let index = app.track_add(None, None, &[], &[])?.0;
|
|
||||||
app.selected = match app.selected {
|
|
||||||
Track(t) => Track(index),
|
|
||||||
Clip(t, s) => Clip(index, s),
|
|
||||||
_ => app.selected
|
|
||||||
};
|
|
||||||
Some(Self::Del(index))
|
|
||||||
}
|
|
||||||
|
|
||||||
Del(index: usize) => {
|
|
||||||
app.track_del(index);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
Stop(index: usize) => {
|
|
||||||
app.tracks[index].player.enqueue_next(None);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
SetColor(index: usize, color: ItemPalette) => {
|
|
||||||
let old = app.tracks[index].color;
|
|
||||||
app.tracks[index].color = color;
|
|
||||||
Some(Self::SetColor(index, old))
|
|
||||||
}
|
|
||||||
|
|
||||||
TogglePlay => {
|
|
||||||
Some(Self::TogglePlay)
|
|
||||||
}
|
|
||||||
|
|
||||||
ToggleSolo => {
|
|
||||||
Some(Self::ToggleSolo)
|
|
||||||
}
|
|
||||||
|
|
||||||
ToggleRecord => {
|
|
||||||
if let Some(t) = app.selected.track() {
|
|
||||||
app.tracks[t-1].player.recording = !app.tracks[t-1].player.recording;
|
|
||||||
}
|
|
||||||
Some(Self::ToggleRecord)
|
|
||||||
}
|
|
||||||
|
|
||||||
ToggleMonitor => {
|
|
||||||
if let Some(t) = app.selected.track() {
|
|
||||||
app.tracks[t-1].player.monitoring = !app.tracks[t-1].player.monitoring;
|
|
||||||
}
|
|
||||||
Some(Self::ToggleMonitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
SceneCommand {
|
|
||||||
|
|
||||||
Swap(a: usize, b: usize) => {
|
|
||||||
println!("\n\rtodo: {self:?}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
SetSize(index: usize) => {
|
|
||||||
println!("\n\rtodo: {self:?}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
SetZoom(zoom: usize) => {
|
|
||||||
println!("\n\rtodo: {self:?}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
Add => {
|
|
||||||
use Selection::*;
|
|
||||||
let index = app.scene_add(None, None)?.0;
|
|
||||||
app.selected = match app.selected {
|
|
||||||
Scene(s) => Scene(index),
|
|
||||||
Clip(t, s) => Clip(t, index),
|
|
||||||
_ => app.selected
|
|
||||||
};
|
|
||||||
Some(Self::Del(index))
|
|
||||||
}
|
|
||||||
|
|
||||||
Del(index: usize) => {
|
|
||||||
app.scene_del(index);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
SetColor(index: usize, color: ItemPalette) => {
|
|
||||||
let old = app.scenes[index].color;
|
|
||||||
app.scenes[index].color = color;
|
|
||||||
Some(Self::SetColor(index, old))
|
|
||||||
}
|
|
||||||
|
|
||||||
Enqueue(scene: usize) => {
|
|
||||||
for track in 0..app.tracks.len() {
|
|
||||||
app.tracks[track].player.enqueue_next(app.scenes[scene].clips[track].as_ref());
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ClipCommand {
|
|
||||||
|
|
||||||
Get(a: usize, b: usize) => {
|
|
||||||
println!("\n\rtodo: {self:?}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
Edit(clip: Option<Arc<RwLock<MidiClip>>>) => {
|
|
||||||
println!("\n\rtodo: edit {clip:?}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
SetLoop(track: usize, scene: usize, looped: bool) => {
|
|
||||||
println!("\n\rtodo: {self:?}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
Put(track: usize, scene: usize, clip: Option<Arc<RwLock<MidiClip>>>) => {
|
|
||||||
let old = app.scenes[scene].clips[track].clone();
|
|
||||||
app.scenes[scene].clips[track] = clip;
|
|
||||||
Some(Self::Put(track, scene, old))
|
|
||||||
}
|
|
||||||
|
|
||||||
Enqueue(track: usize, scene: usize) => {
|
|
||||||
app.tracks[track].player.enqueue_next(app.scenes[scene].clips[track].as_ref());
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
SetColor(track: usize, scene: usize, color: ItemPalette) => {
|
|
||||||
app.scenes[scene].clips[track].as_ref().map(|clip|{
|
|
||||||
let mut clip = clip.write().unwrap();
|
|
||||||
let old = clip.color.clone();
|
|
||||||
clip.color = color.clone();
|
|
||||||
panic!("{color:?} {old:?}");
|
|
||||||
Self::SetColor(track, scene, old)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
@ -38,7 +38,6 @@ pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::Relaxed}}
|
||||||
|
|
||||||
mod api; pub use self::api::*;
|
mod api; pub use self::api::*;
|
||||||
mod audio; pub use self::audio::*;
|
mod audio; pub use self::audio::*;
|
||||||
mod device; pub use self::device::*;
|
|
||||||
mod model; pub use self::model::*;
|
mod model; pub use self::model::*;
|
||||||
mod view; pub use self::view::*;
|
mod view; pub use self::view::*;
|
||||||
mod keys; pub use self::keys::*;
|
mod keys; pub use self::keys::*;
|
||||||
|
|
@ -47,7 +46,6 @@ mod keys; pub use self::keys::*;
|
||||||
let mut tek = Tek::default();
|
let mut tek = Tek::default();
|
||||||
let _ = tek.clip();
|
let _ = tek.clip();
|
||||||
let _ = tek.toggle_loop();
|
let _ = tek.toggle_loop();
|
||||||
let _ = tek.activate();
|
|
||||||
}
|
}
|
||||||
#[cfg(test)] #[test] fn test_model_scene () {
|
#[cfg(test)] #[test] fn test_model_scene () {
|
||||||
let mut app = Tek::default();
|
let mut app = Tek::default();
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ pub struct Tek {
|
||||||
/// Source of time
|
/// Source of time
|
||||||
pub clock: Clock,
|
pub clock: Clock,
|
||||||
/// Theme
|
/// Theme
|
||||||
pub color: ItemPalette,
|
pub color: ItemTheme,
|
||||||
/// Contains all clips in the project
|
/// Contains all clips in the project
|
||||||
pub pool: Option<MidiPool>,
|
pub pool: Option<MidiPool>,
|
||||||
/// Contains the currently edited MIDI clip
|
/// Contains the currently edited MIDI clip
|
||||||
|
|
@ -63,15 +63,15 @@ impl Tek {
|
||||||
&mut self,
|
&mut self,
|
||||||
count: usize,
|
count: usize,
|
||||||
width: Option<usize>,
|
width: Option<usize>,
|
||||||
midi_from: &[PortConnect],
|
mins: &[PortConnect],
|
||||||
midi_to: &[PortConnect],
|
mouts: &[PortConnect],
|
||||||
) -> Usually<()> {
|
) -> Usually<()> {
|
||||||
let jack = self.jack().clone();
|
let jack = self.jack().clone();
|
||||||
let track_color_1 = ItemColor::random();
|
let track_color_1 = ItemColor::random();
|
||||||
let track_color_2 = ItemColor::random();
|
let track_color_2 = ItemColor::random();
|
||||||
for i in 0..count {
|
for i in 0..count {
|
||||||
let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into();
|
let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into();
|
||||||
let mut track = self.track_add(None, Some(color), midi_from, midi_to)?.1;
|
let mut track = self.track_add(None, Some(color), mins, mouts)?.1;
|
||||||
if let Some(width) = width {
|
if let Some(width) = width {
|
||||||
track.width = width;
|
track.width = width;
|
||||||
}
|
}
|
||||||
|
|
@ -82,22 +82,17 @@ impl Tek {
|
||||||
/// Add a track
|
/// Add a track
|
||||||
pub fn track_add (
|
pub fn track_add (
|
||||||
&mut self,
|
&mut self,
|
||||||
name: Option<&str>,
|
name: Option<&str>,
|
||||||
color: Option<ItemPalette>,
|
color: Option<ItemTheme>,
|
||||||
midi_froms: &[PortConnect],
|
mins: &[PortConnect],
|
||||||
midi_tos: &[PortConnect],
|
mouts: &[PortConnect],
|
||||||
) -> Usually<(usize, &mut Track)> {
|
) -> Usually<(usize, &mut Track)> {
|
||||||
let name = name.map_or_else(||self.track_next_name(), |x|x.to_string().into());
|
let name = name.map_or_else(||self.track_next_name(), |x|x.to_string().into());
|
||||||
let mut track = Track {
|
let mut track = Track {
|
||||||
width: (name.len() + 2).max(12),
|
width: (name.len() + 2).max(12),
|
||||||
color: color.unwrap_or_else(ItemPalette::random),
|
color: color.unwrap_or_else(ItemTheme::random),
|
||||||
player: MidiPlayer::new(
|
player: MidiPlayer::new(
|
||||||
&format!("{name}"),
|
&format!("{name}"), self.jack(), Some(self.clock()), None, mins, mouts
|
||||||
self.jack(),
|
|
||||||
Some(self.clock()),
|
|
||||||
None,
|
|
||||||
midi_froms,
|
|
||||||
midi_tos
|
|
||||||
)?,
|
)?,
|
||||||
name,
|
name,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -113,6 +108,17 @@ impl Tek {
|
||||||
Ok((index, &mut self.tracks_mut()[index]))
|
Ok((index, &mut self.tracks_mut()[index]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add and focus a track
|
||||||
|
pub(crate) fn track_add_focus (&mut self) -> Usually<usize> {
|
||||||
|
let index = self.track_add(None, None, &[], &[])?.0;
|
||||||
|
self.selected = match self.selected {
|
||||||
|
Selection::Track(t) => Selection::Track(index),
|
||||||
|
Selection::Clip(t, s) => Selection::Clip(index, s),
|
||||||
|
_ => self.selected
|
||||||
|
};
|
||||||
|
Ok(index)
|
||||||
|
}
|
||||||
|
|
||||||
/// Delete a track
|
/// Delete a track
|
||||||
pub fn track_del (&mut self, index: usize) {
|
pub fn track_del (&mut self, index: usize) {
|
||||||
self.tracks_mut().remove(index);
|
self.tracks_mut().remove(index);
|
||||||
|
|
@ -134,22 +140,46 @@ impl Tek {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a scene
|
/// Add a scene
|
||||||
pub fn scene_add (&mut self, name: Option<&str>, color: Option<ItemPalette>)
|
pub fn scene_add (&mut self, name: Option<&str>, color: Option<ItemTheme>)
|
||||||
-> Usually<(usize, &mut Scene)>
|
-> Usually<(usize, &mut Scene)>
|
||||||
{
|
{
|
||||||
let scene = Scene {
|
let scene = Scene {
|
||||||
name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()),
|
name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()),
|
||||||
clips: vec![None;self.tracks().len()],
|
clips: vec![None;self.tracks().len()],
|
||||||
color: color.unwrap_or_else(ItemPalette::random),
|
color: color.unwrap_or_else(ItemTheme::random),
|
||||||
};
|
};
|
||||||
self.scenes_mut().push(scene);
|
self.scenes_mut().push(scene);
|
||||||
let index = self.scenes().len() - 1;
|
let index = self.scenes().len() - 1;
|
||||||
Ok((index, &mut self.scenes_mut()[index]))
|
Ok((index, &mut self.scenes_mut()[index]))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate the default name for a new scene
|
/// Add and focus an empty scene
|
||||||
pub fn scene_default_name (&self) -> Arc<str> {
|
pub fn scene_add_focus (&mut self) -> Usually<usize> {
|
||||||
format!("Sc{:3>}", self.scenes().len() + 1).into()
|
let index = self.scene_add(None, None)?.0;
|
||||||
|
self.selected = match self.selected {
|
||||||
|
Selection::Scene(s) => Selection::Scene(index),
|
||||||
|
Selection::Clip(t, s) => Selection::Clip(t, index),
|
||||||
|
_ => self.selected
|
||||||
|
};
|
||||||
|
Ok(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enqueue clips from a scene across all tracks
|
||||||
|
pub fn scene_enqueue (&mut self, scene: usize) {
|
||||||
|
for track in 0..self.tracks.len() {
|
||||||
|
self.tracks[track].player.enqueue_next(self.scenes[scene].clips[track].as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_editor (&mut self, value: Option<bool>) {
|
||||||
|
let editing = self.is_editing();
|
||||||
|
let value = value.unwrap_or_else(||!self.is_editing());
|
||||||
|
self.editing.store(value, Relaxed);
|
||||||
|
if value {
|
||||||
|
self.clip_auto_create();
|
||||||
|
} else {
|
||||||
|
self.clip_auto_remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new clip in pool when entering empty cell
|
// Create new clip in pool when entering empty cell
|
||||||
|
|
@ -194,11 +224,43 @@ impl Tek {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Put a clip in a slot
|
||||||
|
pub(crate) fn clip_put (
|
||||||
|
&mut self, track: usize, scene: usize, clip: Option<Arc<RwLock<MidiClip>>>
|
||||||
|
) -> Option<Arc<RwLock<MidiClip>>> {
|
||||||
|
let old = self.scenes[scene].clips[track].clone();
|
||||||
|
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
|
||||||
|
) -> Option<ItemTheme> {
|
||||||
|
self.scenes[scene].clips[track].as_ref().map(|clip|{
|
||||||
|
let mut clip = clip.write().unwrap();
|
||||||
|
let old = clip.color.clone();
|
||||||
|
clip.color = color.clone();
|
||||||
|
panic!("{color:?} {old:?}");
|
||||||
|
old
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the clip pool, if present
|
||||||
|
pub(crate) fn pool (&self) -> Option<&MidiPool> {
|
||||||
|
self.pool.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the active clip
|
/// Get the active clip
|
||||||
pub(crate) fn clip (&self) -> Option<Arc<RwLock<MidiClip>>> {
|
pub(crate) fn clip (&self) -> Option<Arc<RwLock<MidiClip>>> {
|
||||||
self.scene()?.clips.get(self.selected().track()?)?.clone()
|
self.scene()?.clips.get(self.selected().track()?)?.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the active editor
|
||||||
|
pub(crate) fn editor (&self) -> Option<&MidiEditor> {
|
||||||
|
self.editor.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
/// Toggle looping for the active clip
|
/// Toggle looping for the active clip
|
||||||
pub(crate) fn toggle_loop (&mut self) {
|
pub(crate) fn toggle_loop (&mut self) {
|
||||||
if let Some(clip) = self.clip() {
|
if let Some(clip) = self.clip() {
|
||||||
|
|
@ -206,30 +268,88 @@ impl Tek {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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::Clip(t, s) if let Some(Some(Some(clip))) = self
|
||||||
|
.scenes.get(s).map(|s|s.clips.get(t)) => Some(clip),
|
||||||
|
_ => None
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop all playing clips
|
||||||
|
pub(crate) fn stop_all (&mut self) {
|
||||||
|
for track in 0..self.tracks.len() {
|
||||||
|
self.tracks[track].player.enqueue_next(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Launch a clip or scene
|
/// Launch a clip or scene
|
||||||
pub(crate) fn activate (&mut self) -> Usually<()> {
|
pub(crate) fn launch (&mut self) {
|
||||||
let selected = self.selected().clone();
|
match self.selected {
|
||||||
match selected {
|
Selection::Track(t) => {
|
||||||
Selection::Scene(s) => {
|
self.tracks[t].player.enqueue_next(None)
|
||||||
let mut clips = vec![];
|
|
||||||
for (t, _) in self.tracks().iter().enumerate() {
|
|
||||||
clips.push(self.scenes()[s].clips[t].clone());
|
|
||||||
}
|
|
||||||
for (t, track) in self.tracks_mut().iter_mut().enumerate() {
|
|
||||||
if track.player.play_clip.is_some() || clips[t].is_some() {
|
|
||||||
track.player.enqueue_next(clips[t].as_ref());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.clock().is_stopped() {
|
|
||||||
self.clock().play_from(Some(0))?;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Selection::Clip(t, s) => {
|
Selection::Clip(t, s) => {
|
||||||
let clip = self.scenes()[s].clips[t].clone();
|
self.tracks[t].player.enqueue_next(self.scenes[s].clips[t].as_ref())
|
||||||
self.tracks_mut()[t].player.enqueue_next(clip.as_ref());
|
},
|
||||||
|
Selection::Scene(s) => {
|
||||||
|
for t in 0..self.tracks.len() {
|
||||||
|
self.tracks[t].player.enqueue_next(self.scenes[s].clips[t].as_ref())
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the first sampler of the active track
|
||||||
|
pub fn sampler (&self) -> Option<&Sampler> {
|
||||||
|
self.tracks.get(0).map(|t|t.sampler(0)).flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the color of the selected entity
|
||||||
|
pub fn set_color (&mut self, palette: Option<ItemTheme>) -> Option<ItemTheme> {
|
||||||
|
let palette = palette.unwrap_or_else(||ItemTheme::random());
|
||||||
|
Some(match self.selected {
|
||||||
|
Selection::Mix => {
|
||||||
|
let old = self.color;
|
||||||
|
self.color = palette;
|
||||||
|
old
|
||||||
|
},
|
||||||
|
Selection::Track(t) => {
|
||||||
|
let old = self.tracks[t].color;
|
||||||
|
self.tracks[t].color = palette;
|
||||||
|
old
|
||||||
|
}
|
||||||
|
Selection::Scene(s) => {
|
||||||
|
let old = self.scenes[s].color;
|
||||||
|
self.scenes[s].color = palette;
|
||||||
|
old
|
||||||
|
}
|
||||||
|
Selection::Clip(t, s) => {
|
||||||
|
if let Some(ref clip) = self.scenes[s].clips[t] {
|
||||||
|
let mut clip = clip.write().unwrap();
|
||||||
|
let old = clip.color;
|
||||||
|
clip.color = palette;
|
||||||
|
old
|
||||||
|
} else {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_midi_in (&mut self) -> Usually<()> {
|
||||||
|
self.midi_ins.push(JackMidiIn::new(&self.jack, &format!("M/{}", self.midi_ins.len()), &[])?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_midi_out (&mut self) -> Usually<()> {
|
||||||
|
self.midi_outs.push(JackMidiOut::new(&self.jack, &format!("{}/M", self.midi_outs.len()), &[])?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -379,6 +499,17 @@ pub trait HasScenes: HasSelection + HasEditor + Send + Sync {
|
||||||
fn scene_del (&mut self, index: usize) {
|
fn scene_del (&mut self, index: usize) {
|
||||||
self.selected().scene().and_then(|s|Some(self.scenes_mut().remove(index)));
|
self.selected().scene().and_then(|s|Some(self.scenes_mut().remove(index)));
|
||||||
}
|
}
|
||||||
|
/// Set the color of a scene, returning the previous one.
|
||||||
|
fn scene_set_color (&mut self, index: usize, color: ItemTheme) -> ItemTheme {
|
||||||
|
let scenes = self.scenes_mut();
|
||||||
|
let old = scenes[index].color;
|
||||||
|
scenes[index].color = color;
|
||||||
|
old
|
||||||
|
}
|
||||||
|
/// Generate the default name for a new scene
|
||||||
|
fn scene_default_name (&self) -> Arc<str> {
|
||||||
|
format!("Sc{:3>}", self.scenes().len() + 1).into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)] pub struct Scene {
|
#[derive(Debug, Default)] pub struct Scene {
|
||||||
|
|
@ -387,7 +518,7 @@ pub trait HasScenes: HasSelection + HasEditor + Send + Sync {
|
||||||
/// Clips in scene, one per track
|
/// Clips in scene, one per track
|
||||||
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
|
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
|
||||||
/// Identifying color of scene
|
/// Identifying color of scene
|
||||||
pub color: ItemPalette,
|
pub color: ItemTheme,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
|
|
@ -443,6 +574,27 @@ pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync
|
||||||
fn track_mut (&mut self) -> Option<&mut Track> {
|
fn track_mut (&mut self) -> Option<&mut Track> {
|
||||||
self.selected().track().and_then(|s|self.tracks_mut().get_mut(s))
|
self.selected().track().and_then(|s|self.tracks_mut().get_mut(s))
|
||||||
}
|
}
|
||||||
|
/// Set the color of a track
|
||||||
|
fn track_set_color (&mut self, index: usize, color: ItemTheme) -> ItemTheme {
|
||||||
|
let tracks = self.tracks_mut();
|
||||||
|
let old = tracks[index].color;
|
||||||
|
tracks[index].color = color;
|
||||||
|
old
|
||||||
|
}
|
||||||
|
/// Toggle track recording
|
||||||
|
fn track_toggle_record (&mut self) {
|
||||||
|
if let Some(t) = self.selected().track() {
|
||||||
|
let tracks = self.tracks_mut();
|
||||||
|
tracks[t-1].player.recording = !tracks[t-1].player.recording;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Toggle track monitoring
|
||||||
|
fn track_toggle_monitor (&mut self) {
|
||||||
|
if let Some(t) = self.selected().track() {
|
||||||
|
let tracks = self.tracks_mut();
|
||||||
|
tracks[t-1].player.monitoring = !tracks[t-1].player.monitoring;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)] pub struct Track {
|
#[derive(Debug, Default)] pub struct Track {
|
||||||
|
|
@ -451,7 +603,7 @@ pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync
|
||||||
/// Preferred width of track column
|
/// Preferred width of track column
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
/// Identifying color of track
|
/// Identifying color of track
|
||||||
pub color: ItemPalette,
|
pub color: ItemTheme,
|
||||||
/// MIDI player state
|
/// MIDI player state
|
||||||
pub player: MidiPlayer,
|
pub player: MidiPlayer,
|
||||||
/// Device chain
|
/// Device chain
|
||||||
|
|
|
||||||
|
|
@ -2,37 +2,6 @@ use crate::*;
|
||||||
pub(crate) use std::fmt::Write;
|
pub(crate) use std::fmt::Write;
|
||||||
pub(crate) use ::tengri::tui::ratatui::prelude::Position;
|
pub(crate) use ::tengri::tui::ratatui::prelude::Position;
|
||||||
|
|
||||||
view!(TuiOut: |self: Tek| {
|
|
||||||
self.size.of(View(self, self.view))
|
|
||||||
}; {
|
|
||||||
":nil" =>
|
|
||||||
Box::new("nil"),
|
|
||||||
":modal" =>
|
|
||||||
When::new(self.modal.is_some(), Bsp::b(
|
|
||||||
Fill::xy(Tui::fg_bg(Color::Rgb(64,64,64), Color::Rgb(32,32,32), "")),
|
|
||||||
Fixed::xy(30, 15, Tui::fg_bg(Color::Rgb(255,255,255), Color::Rgb(16,16,16), self.modal))
|
|
||||||
)).boxed(),
|
|
||||||
":transport" =>
|
|
||||||
self.view_transport().boxed(),
|
|
||||||
":arranger" =>
|
|
||||||
ArrangerView::new(self).boxed(),
|
|
||||||
":editor" =>
|
|
||||||
self.editor.as_ref()
|
|
||||||
.map(|e|Bsp::n(Bsp::e(e.clip_status(), e.edit_status()), e))
|
|
||||||
.boxed(),
|
|
||||||
":samples-keys" =>
|
|
||||||
self.tracks[0].sampler(0).map(|s|s.view_list(false, self.editor.as_ref().unwrap())).boxed(),
|
|
||||||
":samples-grid" =>
|
|
||||||
self.tracks[0].sampler(0).map(|s|s.view_grid()).boxed(),
|
|
||||||
":sample-viewer" =>
|
|
||||||
self.tracks[0].sampler(0).map(|s|s.view_sample(self.editor.as_ref().unwrap().note_pos())).boxed(),
|
|
||||||
":status" =>
|
|
||||||
self.view_status().boxed(),
|
|
||||||
":pool" => self.pool.as_ref()
|
|
||||||
.map(|pool|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), pool)))
|
|
||||||
.boxed(),
|
|
||||||
});
|
|
||||||
|
|
||||||
pub(crate) struct ArrangerView<'a> {
|
pub(crate) struct ArrangerView<'a> {
|
||||||
app: &'a Tek,
|
app: &'a Tek,
|
||||||
|
|
||||||
|
|
@ -317,7 +286,7 @@ impl<'a> ArrangerView<'a> {
|
||||||
let clip = clip.read().unwrap();
|
let clip = clip.read().unwrap();
|
||||||
(Some(clip.name.clone()), clip.color)
|
(Some(clip.name.clone()), clip.color)
|
||||||
} else {
|
} else {
|
||||||
(None, ItemPalette::G[32])
|
(None, ItemTheme::G[32])
|
||||||
};
|
};
|
||||||
let height = (1 + y2 - y1) as u16;
|
let height = (1 + y2 - y1) as u16;
|
||||||
let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⏹ ", name))));
|
let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⏹ ", name))));
|
||||||
|
|
@ -392,7 +361,7 @@ impl<'a> ArrangerView<'a> {
|
||||||
} else {
|
} else {
|
||||||
Some(self.app.scenes[s-1].clips[track].as_ref()
|
Some(self.app.scenes[s-1].clips[track].as_ref()
|
||||||
.map(|c|c.read().unwrap().color)
|
.map(|c|c.read().unwrap().color)
|
||||||
.unwrap_or(ItemPalette::G[32]))
|
.unwrap_or(ItemTheme::G[32]))
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -402,7 +371,7 @@ impl<'a> ArrangerView<'a> {
|
||||||
|
|
||||||
trait ScenesColors<'a> = Iterator<Item=SceneWithColor<'a>>;
|
trait ScenesColors<'a> = Iterator<Item=SceneWithColor<'a>>;
|
||||||
|
|
||||||
type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option<ItemPalette>);
|
type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option<ItemTheme>);
|
||||||
|
|
||||||
impl Tek {
|
impl Tek {
|
||||||
/// Spacing between tracks.
|
/// Spacing between tracks.
|
||||||
|
|
@ -537,6 +506,21 @@ impl Tek {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn view_modal (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
When::new(self.modal.is_some(), Bsp::b(
|
||||||
|
Fill::xy(Tui::fg_bg(Color::Rgb(64,64,64), Color::Rgb(32,32,32), "")),
|
||||||
|
Fixed::xy(30, 15, Tui::fg_bg(Color::Rgb(255,255,255), Color::Rgb(16,16,16), self.modal))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_arranger (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
ArrangerView::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_pool (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
self.pool().map(|p|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), p)))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a type alias for iterators of sized items (columns).
|
/// Define a type alias for iterators of sized items (columns).
|
||||||
|
|
@ -563,7 +547,7 @@ fn view_transport (
|
||||||
beat: Arc<RwLock<String>>,
|
beat: Arc<RwLock<String>>,
|
||||||
time: Arc<RwLock<String>>,
|
time: Arc<RwLock<String>>,
|
||||||
) -> impl Content<TuiOut> {
|
) -> impl Content<TuiOut> {
|
||||||
let theme = ItemPalette::G[96];
|
let theme = ItemTheme::G[96];
|
||||||
Tui::bg(Black, row!(Bsp::a(
|
Tui::bg(Black, row!(Bsp::a(
|
||||||
Fill::xy(Align::w(button_play_pause(play))),
|
Fill::xy(Align::w(button_play_pause(play))),
|
||||||
Fill::xy(Align::e(row!(
|
Fill::xy(Align::e(row!(
|
||||||
|
|
@ -580,7 +564,7 @@ fn view_status (
|
||||||
buf: Arc<RwLock<String>>,
|
buf: Arc<RwLock<String>>,
|
||||||
lat: Arc<RwLock<String>>,
|
lat: Arc<RwLock<String>>,
|
||||||
) -> impl Content<TuiOut> {
|
) -> impl Content<TuiOut> {
|
||||||
let theme = ItemPalette::G[96];
|
let theme = ItemTheme::G[96];
|
||||||
Tui::bg(Black, row!(Bsp::a(
|
Tui::bg(Black, row!(Bsp::a(
|
||||||
Fill::xy(Align::w(FieldH(theme, "Selected", sel))),
|
Fill::xy(Align::w(FieldH(theme, "Selected", sel))),
|
||||||
Fill::xy(Align::e(row!(
|
Fill::xy(Align::e(row!(
|
||||||
|
|
@ -610,7 +594,7 @@ pub(crate) fn button_play_pause (playing: bool) -> impl Content<TuiOut> {
|
||||||
|
|
||||||
pub (crate) fn view_meter <'a> (label: &'a str, value: f32) -> impl Content<TuiOut> + 'a {
|
pub (crate) fn view_meter <'a> (label: &'a str, value: f32) -> impl Content<TuiOut> + 'a {
|
||||||
col!(
|
col!(
|
||||||
FieldH(ItemPalette::G[128], label, format!("{:>+9.3}", value)),
|
FieldH(ItemTheme::G[128], label, format!("{:>+9.3}", value)),
|
||||||
Fixed::xy(if value >= 0.0 { 13 }
|
Fixed::xy(if value >= 0.0 { 13 }
|
||||||
else if value >= -1.0 { 12 }
|
else if value >= -1.0 { 12 }
|
||||||
else if value >= -2.0 { 11 }
|
else if value >= -2.0 { 11 }
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ impl Cli {
|
||||||
}
|
}
|
||||||
let mut app = Tek {
|
let mut app = Tek {
|
||||||
jack: jack.clone(),
|
jack: jack.clone(),
|
||||||
color: ItemPalette::random(),
|
color: ItemTheme::random(),
|
||||||
clock: Clock::new(jack, self.bpm)?,
|
clock: Clock::new(jack, self.bpm)?,
|
||||||
view: SourceIter(match mode {
|
view: SourceIter(match mode {
|
||||||
LaunchMode::Clock => include_str!("./edn/transport.edn"),
|
LaunchMode::Clock => include_str!("./edn/transport.edn"),
|
||||||
|
|
|
||||||
|
|
@ -35,14 +35,14 @@
|
||||||
//return None
|
//return None
|
||||||
//},
|
//},
|
||||||
//kpat!(Char('a')) | kpat!(Shift-Char('A')) => Cmd::Clip(PoolClipCommand::Add(count, MidiClip::new(
|
//kpat!(Char('a')) | kpat!(Shift-Char('A')) => Cmd::Clip(PoolClipCommand::Add(count, MidiClip::new(
|
||||||
//"Clip", true, 4 * PPQ, None, Some(ItemPalette::random())
|
//"Clip", true, 4 * PPQ, None, Some(ItemTheme::random())
|
||||||
//))),
|
//))),
|
||||||
//kpat!(Char('i')) => Cmd::Clip(PoolClipCommand::Add(index + 1, MidiClip::new(
|
//kpat!(Char('i')) => Cmd::Clip(PoolClipCommand::Add(index + 1, MidiClip::new(
|
||||||
//"Clip", true, 4 * PPQ, None, Some(ItemPalette::random())
|
//"Clip", true, 4 * PPQ, None, Some(ItemTheme::random())
|
||||||
//))),
|
//))),
|
||||||
//kpat!(Char('d')) | kpat!(Shift-Char('D')) => {
|
//kpat!(Char('d')) | kpat!(Shift-Char('D')) => {
|
||||||
//let mut clip = state.clips()[index].read().unwrap().duplicate();
|
//let mut clip = state.clips()[index].read().unwrap().duplicate();
|
||||||
//clip.color = ItemPalette::random_near(clip.color, 0.25);
|
//clip.color = ItemTheme::random_near(clip.color, 0.25);
|
||||||
//Cmd::Clip(PoolClipCommand::Add(index + 1, clip))
|
//Cmd::Clip(PoolClipCommand::Add(index + 1, clip))
|
||||||
//},
|
//},
|
||||||
//_ => return None
|
//_ => return None
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
mod clip_editor; pub use self::clip_editor::*;
|
mod clip_editor; pub use self::clip_editor::*;
|
||||||
mod clip_launch; pub use self::clip_launch::*;
|
mod clip_launch; pub use self::clip_launch::*;
|
||||||
mod clip_model; pub use self::clip_model::*;
|
mod clip_model; pub use self::clip_model::*;
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ impl MidiEditor {
|
||||||
pub fn clip_status (&self) -> impl Content<TuiOut> + '_ {
|
pub fn clip_status (&self) -> impl Content<TuiOut> + '_ {
|
||||||
let (color, name, length, looped) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
|
let (color, name, length, looped) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
|
||||||
(clip.color, clip.name.clone(), clip.length, clip.looped)
|
(clip.color, clip.name.clone(), clip.length, clip.looped)
|
||||||
} else { (ItemPalette::G[64], String::new().into(), 0, false) };
|
} else { (ItemTheme::G[64], String::new().into(), 0, false) };
|
||||||
Bsp::e(
|
Bsp::e(
|
||||||
FieldH(color, "Edit", format!("{name} ({length})")),
|
FieldH(color, "Edit", format!("{name} ({length})")),
|
||||||
FieldH(color, "Loop", looped.to_string())
|
FieldH(color, "Loop", looped.to_string())
|
||||||
|
|
@ -125,7 +125,7 @@ impl MidiEditor {
|
||||||
pub fn edit_status (&self) -> impl Content<TuiOut> + '_ {
|
pub fn edit_status (&self) -> impl Content<TuiOut> + '_ {
|
||||||
let (color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
|
let (color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
|
||||||
(clip.color, clip.length)
|
(clip.color, clip.length)
|
||||||
} else { (ItemPalette::G[64], 0) };
|
} else { (ItemTheme::G[64], 0) };
|
||||||
let time_pos = self.time_pos();
|
let time_pos = self.time_pos();
|
||||||
let time_zoom = self.time_zoom().get();
|
let time_zoom = self.time_zoom().get();
|
||||||
let time_lock = if self.time_lock().get() { "[lock]" } else { " " };
|
let time_lock = if self.time_lock().get() { "[lock]" } else { " " };
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ pub trait HasPlayClip: HasClock {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn play_status (&self) -> impl Content<TuiOut> {
|
fn play_status (&self) -> impl Content<TuiOut> {
|
||||||
let (name, color): (Arc<str>, ItemPalette) = if let Some((_, Some(clip))) = self.play_clip() {
|
let (name, color): (Arc<str>, ItemTheme) = if let Some((_, Some(clip))) = self.play_clip() {
|
||||||
let MidiClip { ref name, color, .. } = *clip.read().unwrap();
|
let MidiClip { ref name, color, .. } = *clip.read().unwrap();
|
||||||
(name.clone(), color)
|
(name.clone(), color)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -54,7 +54,7 @@ pub trait HasPlayClip: HasClock {
|
||||||
fn next_status (&self) -> impl Content<TuiOut> {
|
fn next_status (&self) -> impl Content<TuiOut> {
|
||||||
let mut time: Arc<str> = String::from("--.-.--").into();
|
let mut time: Arc<str> = String::from("--.-.--").into();
|
||||||
let mut name: Arc<str> = String::from("").into();
|
let mut name: Arc<str> = String::from("").into();
|
||||||
let mut color = ItemPalette::G[64];
|
let mut color = ItemTheme::G[64];
|
||||||
let clock = self.clock();
|
let clock = self.clock();
|
||||||
if let Some((t, Some(clip))) = self.next_clip() {
|
if let Some((t, Some(clip))) = self.next_clip() {
|
||||||
let clip = clip.read().unwrap();
|
let clip = clip.read().unwrap();
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ pub struct MidiClip {
|
||||||
/// All notes are displayed with minimum length
|
/// All notes are displayed with minimum length
|
||||||
pub percussive: bool,
|
pub percussive: bool,
|
||||||
/// Identifying color of clip
|
/// Identifying color of clip
|
||||||
pub color: ItemPalette,
|
pub color: ItemTheme,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// MIDI message structural
|
/// MIDI message structural
|
||||||
|
|
@ -46,7 +46,7 @@ impl MidiClip {
|
||||||
looped: bool,
|
looped: bool,
|
||||||
length: usize,
|
length: usize,
|
||||||
notes: Option<MidiData>,
|
notes: Option<MidiData>,
|
||||||
color: Option<ItemPalette>,
|
color: Option<ItemTheme>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
uuid: uuid::Uuid::new_v4(),
|
uuid: uuid::Uuid::new_v4(),
|
||||||
|
|
@ -58,7 +58,7 @@ impl MidiClip {
|
||||||
loop_start: 0,
|
loop_start: 0,
|
||||||
loop_length: length,
|
loop_length: length,
|
||||||
percussive: true,
|
percussive: true,
|
||||||
color: color.unwrap_or_else(ItemPalette::random)
|
color: color.unwrap_or_else(ItemTheme::random)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn count_midi_messages (&self) -> usize {
|
pub fn count_midi_messages (&self) -> usize {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
mod mode_browse; pub use self::mode_browse::*;
|
|
||||||
mod mode_length; pub use self::mode_length::*;
|
mod mode_length; pub use self::mode_length::*;
|
||||||
mod mode_rename; pub use self::mode_rename::*;
|
mod mode_rename; pub use self::mode_rename::*;
|
||||||
|
mod mode_browse;
|
||||||
|
|
||||||
/// Modes for clip pool
|
/// Modes for clip pool
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ pub struct PianoHorizontal {
|
||||||
/// The note cursor
|
/// The note cursor
|
||||||
pub point: MidiPointModel,
|
pub point: MidiPointModel,
|
||||||
/// The highlight color palette
|
/// The highlight color palette
|
||||||
pub color: ItemPalette,
|
pub color: ItemTheme,
|
||||||
/// Width of the keyboard
|
/// Width of the keyboard
|
||||||
pub keys_width: u16,
|
pub keys_width: u16,
|
||||||
}
|
}
|
||||||
|
|
@ -31,7 +31,7 @@ impl PianoHorizontal {
|
||||||
buffer: RwLock::new(Default::default()).into(),
|
buffer: RwLock::new(Default::default()).into(),
|
||||||
point: MidiPointModel::default(),
|
point: MidiPointModel::default(),
|
||||||
clip: clip.cloned(),
|
clip: clip.cloned(),
|
||||||
color: clip.as_ref().map(|p|p.read().unwrap().color).unwrap_or(ItemPalette::G[64]),
|
color: clip.as_ref().map(|p|p.read().unwrap().color).unwrap_or(ItemTheme::G[64]),
|
||||||
};
|
};
|
||||||
piano.redraw();
|
piano.redraw();
|
||||||
piano
|
piano
|
||||||
|
|
@ -281,7 +281,7 @@ impl MidiViewer for PianoHorizontal {
|
||||||
fn set_clip (&mut self, clip: Option<&Arc<RwLock<MidiClip>>>) {
|
fn set_clip (&mut self, clip: Option<&Arc<RwLock<MidiClip>>>) {
|
||||||
*self.clip_mut() = clip.cloned();
|
*self.clip_mut() = clip.cloned();
|
||||||
self.color = clip.map(|p|p.read().unwrap().color)
|
self.color = clip.map(|p|p.read().unwrap().color)
|
||||||
.unwrap_or(ItemPalette::G[64]);
|
.unwrap_or(ItemTheme::G[64]);
|
||||||
self.redraw();
|
self.redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ impl<T: HasClips> Command<T> for PoolClipCommand {
|
||||||
Some(Self::SetLength(index, old_len))
|
Some(Self::SetLength(index, old_len))
|
||||||
},
|
},
|
||||||
SetColor(index, color) => {
|
SetColor(index, color) => {
|
||||||
let mut color = ItemPalette::from(color);
|
let mut color = ItemTheme::from(color);
|
||||||
std::mem::swap(&mut color, &mut model.clips()[index].write().unwrap().color);
|
std::mem::swap(&mut color, &mut model.clips()[index].write().unwrap().color);
|
||||||
Some(Self::SetColor(index, color.base))
|
Some(Self::SetColor(index, color.base))
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -85,12 +85,12 @@ impl MidiPool {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn new_clip (&self) -> MidiClip {
|
pub fn new_clip (&self) -> MidiClip {
|
||||||
MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemPalette::random()))
|
MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemTheme::random()))
|
||||||
}
|
}
|
||||||
pub fn cloned_clip (&self) -> MidiClip {
|
pub fn cloned_clip (&self) -> MidiClip {
|
||||||
let index = self.clip_index();
|
let index = self.clip_index();
|
||||||
let mut clip = self.clips()[index].read().unwrap().duplicate();
|
let mut clip = self.clips()[index].read().unwrap().duplicate();
|
||||||
clip.color = ItemPalette::random_near(clip.color, 0.25);
|
clip.color = ItemTheme::random_near(clip.color, 0.25);
|
||||||
clip
|
clip
|
||||||
}
|
}
|
||||||
pub fn add_new_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) {
|
pub fn add_new_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) {
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@
|
||||||
//});
|
//});
|
||||||
|
|
||||||
//struct SamplesTui {
|
//struct SamplesTui {
|
||||||
//color: ItemPalette,
|
//color: ItemTheme,
|
||||||
//note_hi: usize,
|
//note_hi: usize,
|
||||||
//note_pt: usize,
|
//note_pt: usize,
|
||||||
//height: usize,
|
//height: usize,
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,13 @@ pub(crate) use symphonia::{
|
||||||
pub(crate) use ratatui::{prelude::Rect, widgets::{Widget, canvas::{Canvas, Line}}};
|
pub(crate) use ratatui::{prelude::Rect, widgets::{Widget, canvas::{Canvas, Line}}};
|
||||||
|
|
||||||
mod sampler_api; pub use self::sampler_api::*;
|
mod sampler_api; pub use self::sampler_api::*;
|
||||||
mod sampler_data; pub use self::sampler_data::*;
|
|
||||||
mod sampler_audio; pub use self::sampler_audio::*;
|
mod sampler_audio; pub use self::sampler_audio::*;
|
||||||
mod sampler_browse; pub use self::sampler_browse::*;
|
mod sampler_browse; pub use self::sampler_browse::*;
|
||||||
mod sampler_midi; pub use self::sampler_midi::*;
|
mod sampler_midi; pub use self::sampler_midi::*;
|
||||||
mod sampler_model; pub use self::sampler_model::*;
|
mod sampler_model; pub use self::sampler_model::*;
|
||||||
mod sampler_view; pub use self::sampler_view::*;
|
|
||||||
|
mod sampler_data;
|
||||||
|
mod sampler_view;
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_sampler () {
|
#[cfg(test)] #[test] fn test_sampler () {
|
||||||
// TODO!
|
// TODO!
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,48 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
expose! {
|
expose!([self: Sampler]
|
||||||
[self: Sampler] {
|
([Arc<str>])
|
||||||
[Arc<str>] => {}
|
([Option<Arc<RwLock<Sample>>>])
|
||||||
[Option<Arc<RwLock<Sample>>>] => {}
|
([PathBuf])
|
||||||
[PathBuf] => {}
|
([f32])
|
||||||
[f32] => {}
|
([u7] (":pitch" (self.note_pos() as u8).into()) // TODO
|
||||||
[u7] => {
|
(":sample" (self.note_pos() as u8).into()))
|
||||||
":sample" => (self.note_pos() as u8).into(),
|
([usize] (":sample-up" self.note_pos().min(119) + 8)
|
||||||
}
|
(":sample-down" self.note_pos().max(8) - 8)
|
||||||
[usize] => {
|
(":sample-left" self.note_pos().min(126) + 1)
|
||||||
":sample-up" => self.note_pos().min(119) + 8,
|
(":sample-right" self.note_pos().max(1) - 1)));
|
||||||
":sample-down" => self.note_pos().max(8) - 8,
|
|
||||||
":sample-left" => self.note_pos().min(126) + 1,
|
impose!([state: Sampler]
|
||||||
":sample-right" => self.note_pos().max(1) - 1,
|
(FileBrowserCommand:
|
||||||
}
|
("begin" [] Some(Self::Begin))
|
||||||
}
|
("cancel" [] Some(Self::Cancel))
|
||||||
}
|
("confirm" [] Some(Self::Confirm))
|
||||||
|
("select" [i: usize] Some(Self::Select(i.expect("no index"))))
|
||||||
|
("chdir" [p: PathBuf] Some(Self::Chdir(p.expect("no path"))))
|
||||||
|
("filter" [f: Arc<str>] Some(Self::Filter(f.expect("no filter")))))
|
||||||
|
(SamplerCommand:
|
||||||
|
("import" [,..a]
|
||||||
|
FileBrowserCommand::try_from_expr(state, a).map(Self::Import))
|
||||||
|
("select" [i: usize]
|
||||||
|
Some(Self::Select(i.expect("no index"))))
|
||||||
|
("record/begin" [i: u7]
|
||||||
|
Some(Self::RecordBegin(i.expect("no index"))))
|
||||||
|
("record/cancel" []
|
||||||
|
Some(Self::RecordCancel))
|
||||||
|
("record/finish" []
|
||||||
|
Some(Self::RecordFinish))
|
||||||
|
("set/sample" [i: u7, s: Option<Arc<RwLock<Sample>>>]
|
||||||
|
Some(Self::SetSample(i.expect("no index"), s.expect("no sampler"))))
|
||||||
|
("set/start" [i: u7, s: usize]
|
||||||
|
Some(Self::SetStart(i.expect("no index"), s.expect("no start"))))
|
||||||
|
("set/gain" [i: u7, g: f32]
|
||||||
|
Some(Self::SetGain(i.expect("no index"), g.expect("no gain"))))
|
||||||
|
("note/on" [p: u7, v: u7]
|
||||||
|
Some(Self::NoteOn(p.expect("no pitch"), v.expect("no velocity"))))
|
||||||
|
("note/off" [p: u7]
|
||||||
|
Some(Self::NoteOff(p.expect("no pitch"))))));
|
||||||
|
|
||||||
defcom! { |self, state: Sampler|
|
defcom! { |self, state: Sampler|
|
||||||
|
|
||||||
SamplerCommand {
|
SamplerCommand {
|
||||||
|
|
||||||
Import(cmd: FileBrowserCommand) => match cmd {
|
Import(cmd: FileBrowserCommand) => match cmd {
|
||||||
|
|
@ -86,28 +109,3 @@ defcom! { |self, state: Sampler|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
atom_command!(FileBrowserCommand: |state: Sampler| {
|
|
||||||
("begin" [] Some(Self::Begin))
|
|
||||||
("cancel" [] Some(Self::Cancel))
|
|
||||||
("confirm" [] Some(Self::Confirm))
|
|
||||||
("select" [i: usize] Some(Self::Select(i.expect("no index"))))
|
|
||||||
("chdir" [p: PathBuf] Some(Self::Chdir(p.expect("no path"))))
|
|
||||||
("filter" [f: Arc<str>] Some(Self::Filter(f.expect("no filter")))) });
|
|
||||||
|
|
||||||
atom_command!(SamplerCommand: |state: Sampler| {
|
|
||||||
("import" [,..a] FileBrowserCommand::try_from_expr(state, a).map(Self::Import))
|
|
||||||
("select" [i: usize] Some(Self::Select(i.expect("no index"))))
|
|
||||||
("record/begin" [i: u7] Some(Self::RecordBegin(i.expect("no index"))))
|
|
||||||
("record/cancel" [] Some(Self::RecordCancel))
|
|
||||||
("record/finish" [] Some(Self::RecordFinish))
|
|
||||||
("set/sample" [i: u7, s: Option<Arc<RwLock<Sample>>>]
|
|
||||||
Some(Self::SetSample(i.expect("no index"), s.expect("no sampler"))))
|
|
||||||
("set/start" [i: u7, s: usize]
|
|
||||||
Some(Self::SetStart(i.expect("no index"), s.expect("no start"))))
|
|
||||||
("set/gain" [i: u7, g: f32]
|
|
||||||
Some(Self::SetGain(i.expect("no index"), g.expect("no garin"))))
|
|
||||||
("note/on" [p: u7, v: u7]
|
|
||||||
Some(Self::NoteOn(p.expect("no pitch"), v.expect("no velocity"))))
|
|
||||||
("note/off" [p: u7]
|
|
||||||
Some(Self::NoteOff(p.expect("no pitch")))) });
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ pub struct Sampler {
|
||||||
pub note_pt: AtomicUsize,
|
pub note_pt: AtomicUsize,
|
||||||
/// Selected note as row/col
|
/// Selected note as row/col
|
||||||
pub cursor: (AtomicUsize, AtomicUsize),
|
pub cursor: (AtomicUsize, AtomicUsize),
|
||||||
pub color: ItemPalette
|
pub color: ItemTheme
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Sampler {
|
impl Default for Sampler {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
mod clock_api; pub use self::clock_api::*;
|
mod clock_api; pub use self::clock_api::*;
|
||||||
mod clock_model; pub use self::clock_model::*;
|
mod clock_model; pub use self::clock_model::*;
|
||||||
|
|
||||||
|
|
|
||||||
2
deps/tengri
vendored
2
deps/tengri
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 844681d6add6838a173ddc2a5c3bb9d51451d2e1
|
Subproject commit 7ba37f3f0255c2c61ed3371e2c437514f27f6d6d
|
||||||
Loading…
Add table
Add a link
Reference in a new issue