mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 12:46:42 +01:00
569 lines
18 KiB
Rust
569 lines
18 KiB
Rust
use crate::*;
|
|
|
|
view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); {
|
|
":nil" =>
|
|
Box::new("nil"),
|
|
":transport" =>
|
|
self.view_transport().boxed(),
|
|
":arranger" =>
|
|
ArrangerView::new(self).boxed(),
|
|
":editor" =>
|
|
self.editor.as_ref()
|
|
.map(|e|Bsp::s(Bsp::e(e.clip_status(), e.edit_status()), e))
|
|
.boxed(),
|
|
":sample" =>
|
|
().boxed(),//self.view_sample(self.is_editing()).boxed(),
|
|
":sampler" =>
|
|
().boxed(),//self.view_sampler(self.is_editing(), &self.editor).boxed(),
|
|
":samples-grid" =>
|
|
self.tracks[0].sampler(0).map(|s|s.view_grid()).boxed(),
|
|
":status" =>
|
|
self.view_status().boxed(),
|
|
":pool" => self.pool.as_ref()
|
|
.map(|pool|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), pool)))
|
|
.boxed(),
|
|
});
|
|
|
|
expose!([self: Tek] {
|
|
[bool] => {}
|
|
[u16] => {
|
|
":h-ins" => self.h_inputs(),
|
|
":h-outs" => self.h_outputs(),
|
|
":h-sample" => if self.is_editing() { 0 } else { 5 },
|
|
":w-samples" => if self.is_editing() { 4 } else { 11 },
|
|
":w-sidebar" => self.w_sidebar(),
|
|
":y-ins" => (self.size.h() as u16).saturating_sub(self.h_inputs() + 1),
|
|
":y-outs" => (self.size.h() as u16).saturating_sub(self.h_outputs() + 1),
|
|
":y-samples" => if self.is_editing() { 1 } else { 0 },
|
|
}
|
|
[usize] => {
|
|
":scene-last" => self.scenes.len(),
|
|
":track-last" => self.tracks.len(),
|
|
}
|
|
[isize] => {}
|
|
[Option<usize>] => {
|
|
":scene" => self.selected.scene(),
|
|
":track" => self.selected.track(),
|
|
}
|
|
[Color] => {}
|
|
[Arc<RwLock<MidiClip>>] => {}
|
|
[Option<Arc<RwLock<MidiClip>>>] => {
|
|
":clip" => match self.selected {
|
|
Selection::Clip(t, s) => self.scenes[s].clips[t].clone(),
|
|
_ => None
|
|
}
|
|
}
|
|
[Selection] => {
|
|
":scene-next" => match self.selected {
|
|
Selection::Mix => Selection::Scene(0),
|
|
Selection::Track(t) => Selection::Clip(t, 0),
|
|
Selection::Scene(s) if s + 1 < self.scenes.len() => Selection::Scene(s + 1),
|
|
Selection::Scene(s) => Selection::Mix,
|
|
Selection::Clip(t, s) if s + 1 < self.scenes.len() => Selection::Clip(t, s + 1),
|
|
Selection::Clip(t, s) => Selection::Track(t),
|
|
},
|
|
":scene-prev" => match self.selected {
|
|
Selection::Mix => Selection::Mix,
|
|
Selection::Track(t) => Selection::Track(t),
|
|
Selection::Scene(0) => Selection::Mix,
|
|
Selection::Scene(s) => Selection::Scene(s - 1),
|
|
Selection::Clip(t, 0) => Selection::Track(t),
|
|
Selection::Clip(t, s) => Selection::Clip(t, s - 1),
|
|
},
|
|
":track-next" => match self.selected {
|
|
Selection::Mix => Selection::Track(0),
|
|
Selection::Track(t) if t + 1 < self.tracks.len() => Selection::Track(t + 1),
|
|
Selection::Track(t) => Selection::Mix,
|
|
Selection::Scene(s) => Selection::Clip(0, s),
|
|
Selection::Clip(t, s) if t + 1 < self.tracks.len() => Selection::Clip(t + 1, s),
|
|
Selection::Clip(t, s) => Selection::Scene(s),
|
|
},
|
|
":track-prev" => match self.selected {
|
|
Selection::Mix => Selection::Mix,
|
|
Selection::Scene(s) => Selection::Scene(s),
|
|
Selection::Track(0) => Selection::Mix,
|
|
Selection::Track(t) => Selection::Track(t - 1),
|
|
Selection::Clip(0, s) => Selection::Scene(s),
|
|
Selection::Clip(t, s) => Selection::Clip(t - 1, s),
|
|
},
|
|
}
|
|
});
|
|
|
|
defcom! { |self, app: Tek|
|
|
|
|
TekCommand {
|
|
|
|
Sampler(cmd: SamplerCommand) => {
|
|
println!("\n\rtodo: {cmd:?}");
|
|
None
|
|
}
|
|
|
|
Enqueue(clip: Option<Arc<RwLock<MidiClip>>>) => {
|
|
println!("\n\rtodo: enqueue {clip:?}");
|
|
None
|
|
}
|
|
|
|
History(delta: isize) => {
|
|
println!("\n\rtodo: {self:?}");
|
|
None
|
|
}
|
|
|
|
Zoom(zoom: Option<usize>) => {
|
|
println!("\n\rtodo: {self:?}");
|
|
None
|
|
}
|
|
|
|
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) => 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>) => {
|
|
if let Some(value) = value {
|
|
if app.is_editing() != value {
|
|
app.editing.store(value, Relaxed);
|
|
}
|
|
} else {
|
|
app.editing.store(!app.is_editing(), Relaxed);
|
|
};
|
|
// autocreate: create new clip from pool when entering empty cell
|
|
if let Some(ref pool) = app.pool
|
|
&& app.is_editing()
|
|
&& let Selection::Clip(t, s) = app.selected
|
|
&& let Some(scene) = app.scenes.get_mut(s)
|
|
&& let Some(slot) = scene.clips.get_mut(t)
|
|
&& slot.is_none()
|
|
{
|
|
let (index, mut clip) = pool.add_new_clip();
|
|
// autocolor: new clip colors from scene and track color
|
|
clip.write().unwrap().color = ItemColor::random_near(
|
|
app.tracks[t].color.base.mix(
|
|
scene.color.base,
|
|
0.5
|
|
),
|
|
0.2
|
|
).into();
|
|
if let Some(ref mut editor) = app.editor {
|
|
editor.set_clip(Some(&clip));
|
|
}
|
|
*slot = Some(clip);
|
|
}
|
|
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)
|
|
})
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
impose!([app: Tek] {
|
|
|
|
TekCommand => {
|
|
("stop" []
|
|
Some(Self::StopAll))
|
|
("undo" [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)))
|
|
("edit" []
|
|
Some(Self::Edit(None)))
|
|
("edit" [c: bool]
|
|
Some(Self::Edit(c)))
|
|
("color" []
|
|
Some(Self::Color(ItemPalette::random())))
|
|
("color" [c: Color]
|
|
Some(Self::Color(c.map(ItemPalette::from).expect("no color"))))
|
|
("enqueue" [c: Arc<RwLock<MidiClip>>]
|
|
Some(Self::Enqueue(c)))
|
|
("launch" []
|
|
Some(Self::Launch))
|
|
("clip" [,..a]
|
|
ClipCommand::try_from_expr(app, a).map(Self::Clip))
|
|
("clock" [,..a]
|
|
ClockCommand::try_from_expr(app.clock(), a).map(Self::Clock))
|
|
("editor" [,..a]
|
|
MidiEditCommand::try_from_expr(app.editor.as_ref().expect("no editor"), a).map(Self::Editor))
|
|
("pool" [,..a]
|
|
PoolCommand::try_from_expr(app.pool.as_ref().expect("no pool"), a).map(Self::Pool))
|
|
//("sampler" [,..a]
|
|
// Self::Sampler( //SamplerCommand::try_from_expr(app.sampler().as_ref().expect("no sampler"), a).expect("invalid command")))
|
|
("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: usize, s: usize]
|
|
Some(match (t.expect("no track"), s.expect("no scene")) {
|
|
(0, 0) => Self::Select(Selection::Mix),
|
|
(t, 0) => Self::Select(Selection::Track(t)),
|
|
(0, s) => Self::Select(Selection::Scene(s)),
|
|
(t, s) => Self::Select(Selection::Clip(t, s)),
|
|
}))
|
|
}
|
|
|
|
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())))
|
|
("edit" [a: Option<Arc<RwLock<MidiClip>>>]
|
|
Some(Self::Edit(a.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())))
|
|
}
|
|
|
|
InputCommand => {
|
|
("add" [] Some(Self::Add))
|
|
}
|
|
|
|
OutputCommand => {
|
|
("add" [] Some(Self::Add))
|
|
}
|
|
|
|
SceneCommand => {
|
|
("add" []
|
|
Some(Self::Add))
|
|
("del" [a: usize]
|
|
Some(Self::Del(0)))
|
|
("zoom" [a: usize]
|
|
Some(Self::SetZoom(a.unwrap())))
|
|
("color" [a: usize]
|
|
Some(Self::SetColor(a.unwrap(), ItemPalette::G[128])))
|
|
("enqueue" [a: usize]
|
|
Some(Self::Enqueue(a.unwrap())))
|
|
("swap" [a: usize, b: usize]
|
|
Some(Self::Swap(a.unwrap(), b.unwrap())))
|
|
}
|
|
|
|
TrackCommand => {
|
|
("add" []
|
|
Some(Self::Add))
|
|
("size" [a: usize]
|
|
Some(Self::SetSize(a.unwrap())))
|
|
("zoom" [a: usize]
|
|
Some(Self::SetZoom(a.unwrap())))
|
|
("color" [a: usize]
|
|
Some(Self::SetColor(a.unwrap(), ItemPalette::random())))
|
|
("del" [a: usize]
|
|
Some(Self::Del(a.unwrap())))
|
|
("stop" [a: usize]
|
|
Some(Self::Stop(a.unwrap())))
|
|
("swap" [a: usize, b: usize]
|
|
Some(Self::Swap(a.unwrap(), b.unwrap())))
|
|
("play" []
|
|
Some(Self::TogglePlay))
|
|
("solo" []
|
|
Some(Self::ToggleSolo))
|
|
("rec" []
|
|
Some(Self::ToggleRecord))
|
|
("mon" []
|
|
Some(Self::ToggleMonitor))
|
|
}
|
|
|
|
});
|
|
|
|
handle!(TuiIn: |self: Tek, input|Ok({
|
|
// If editing, editor keys take priority
|
|
if self.is_editing() {
|
|
if self.editor.handle(input)? == Some(true) {
|
|
return Ok(Some(true))
|
|
}
|
|
}
|
|
// Handle from root keymap
|
|
if let Some(command) = self.keys.command::<_, TekCommand, _>(self, input) {
|
|
if let Some(undo) = command.execute(self)? { self.history.push(undo); }
|
|
return Ok(Some(true))
|
|
}
|
|
// Handle from selection-dependent keymaps
|
|
if let Some(command) = match self.selected() {
|
|
Selection::Clip(_, _) => self.keys_clip,
|
|
Selection::Track(_) => self.keys_track,
|
|
Selection::Scene(_) => self.keys_scene,
|
|
Selection::Mix => self.keys_mix,
|
|
}.command::<_, TekCommand, _>(self, input) {
|
|
if let Some(undo) = command.execute(self)? { self.history.push(undo); }
|
|
return Ok(Some(true))
|
|
}
|
|
None
|
|
}));
|