This commit is contained in:
🪞👃🪞 2025-01-16 16:49:53 +01:00
parent 968441850f
commit c08d1bee5d
7 changed files with 177 additions and 150 deletions

View file

@ -94,11 +94,11 @@ impl Default for MidiEditor {
} }
} }
impl MidiEditor { impl MidiEditor {
pub fn clip_length (&self) -> usize { //fn clip_length (&self) -> usize {
self.clip().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1) //self.clip().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1)
} //}
/// Put note at current position /// Put note at current position
pub fn put_note (&mut self, advance: bool) { fn put_note (&mut self, advance: bool) {
let mut redraw = false; let mut redraw = false;
if let Some(clip) = self.clip() { if let Some(clip) = self.clip() {
let mut clip = clip.write().unwrap(); let mut clip = clip.write().unwrap();
@ -127,6 +127,34 @@ impl MidiEditor {
self.mode.redraw(); self.mode.redraw();
} }
} }
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()) {
(clip.color, clip.name.clone(), clip.length, clip.looped)
} else {
(ItemPalette::from(TuiTheme::g(64)), String::new().into(), 0, false)
};
row!(
FieldV(color, "Edit", format!("{name} ({length})")),
FieldV(color, "Loop", looped.to_string())
)
}
pub fn edit_status (&self) -> impl Content<TuiOut> + '_ {
let (color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
(clip.color, clip.length)
} else {
(ItemPalette::from(TuiTheme::g(64)), 0)
};
let time_pos = self.time_pos();
let time_zoom = self.time_zoom().get();
let time_lock = if self.time_lock().get() { "[lock]" } else { " " };
let note_pos = format!("{:>3}", self.note_pos());
let note_name = format!("{:4}", Note::pitch_to_name(self.note_pos()));
let note_len = format!("{:>4}", self.note_len());
Bsp::e(
FieldV(color, "Time", format!("{length}/{time_zoom}+{time_pos} {time_lock}")),
FieldV(color, "Note", format!("{note_name} {note_pos} {note_len}")),
)
}
} }
impl TimeRange for MidiEditor { impl TimeRange for MidiEditor {
fn time_len (&self) -> &AtomicUsize { self.mode.time_len() } fn time_len (&self) -> &AtomicUsize { self.mode.time_len() }
@ -156,39 +184,9 @@ impl MidiViewer for MidiEditor {
fn clip_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>> { self.mode.clip_mut() } fn clip_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>> { self.mode.clip_mut() }
fn set_clip (&mut self, p: Option<&Arc<RwLock<MidiClip>>>) { self.mode.set_clip(p) } fn set_clip (&mut self, p: Option<&Arc<RwLock<MidiClip>>>) { self.mode.set_clip(p) }
} }
impl MidiEditor {
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()) {
(clip.color, clip.name.clone(), clip.length, clip.looped)
} else {
(ItemPalette::from(TuiTheme::g(64)), String::new().into(), 0, false)
};
row!(
FieldV(color, "Edit", format!("{name} ({length})")),
FieldV(color, "Loop", looped.to_string())
)
}
pub fn edit_status (&self) -> impl Content<TuiOut> + '_ {
let (color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
(clip.color, clip.length)
} else {
(ItemPalette::from(TuiTheme::g(64)), 0)
};
let time_pos = self.time_pos();
let time_zoom = self.time_zoom().get();
let time_lock = if self.time_lock().get() { "[lock]" } else { " " };
let note_pos = format!("{:>3}", self.note_pos());
let note_name = format!("{:4}", Note::pitch_to_name(self.note_pos()));
let note_len = format!("{:>4}", self.note_len());
Bsp::e(
FieldV(color, "Time", format!("{length}/{time_zoom}+{time_pos} {time_lock}")),
FieldV(color, "Note", format!("{note_name} {note_pos} {note_len}")),
)
}
}
edn_command!(MidiEditCommand: |state: MidiEditor| { edn_command!(MidiEditCommand: |state: MidiEditor| {
("note/put" [a: bool] Self::PutNote) ("note/put" [_a: bool] Self::PutNote)
("note/del" [a: bool] Self::PutNote) ("note/del" [_a: bool] Self::PutNote)
("note/pos" [a: usize] Self::SetNoteCursor(a.expect("no note cursor"))) ("note/pos" [a: usize] Self::SetNoteCursor(a.expect("no note cursor")))
("note/len" [a: usize] Self::SetNoteLength(a.expect("no note length"))) ("note/len" [a: usize] Self::SetNoteLength(a.expect("no note length")))
("time/pos" [a: usize] Self::SetTimeCursor(a.expect("no time cursor"))) ("time/pos" [a: usize] Self::SetTimeCursor(a.expect("no time cursor")))
@ -210,7 +208,7 @@ edn_command!(MidiEditCommand: |state: MidiEditor| {
Show(Option<Arc<RwLock<MidiClip>>>), Show(Option<Arc<RwLock<MidiClip>>>),
} }
impl MidiEditCommand { impl MidiEditCommand {
pub fn from_tui_event (state: &MidiEditor, input: &impl EdnInput) -> Usually<Option<Self>> { fn from_tui_event (state: &MidiEditor, input: &impl EdnInput) -> Usually<Option<Self>> {
use EdnItem::*; use EdnItem::*;
let edns = EdnItem::<&str>::read_all(KEYS_EDIT)?; let edns = EdnItem::<&str>::read_all(KEYS_EDIT)?;
for item in edns.iter() { for item in edns.iter() {

View file

@ -144,10 +144,10 @@ impl PianoHorizontal {
} }
fn cursor (&self) -> impl Content<TuiOut> { fn cursor (&self) -> impl Content<TuiOut> {
let style = Some(Style::default().fg(self.color.lightest.rgb)); let style = Some(Style::default().fg(self.color.lightest.rgb));
let note_hi = self.note_hi(); let note_hi = self.note_hi();
let note_len = self.note_len(); let note_lo = self.note_lo().get();
let note_lo = self.note_lo().get();
let note_pos = self.note_pos(); let note_pos = self.note_pos();
let note_len = self.note_len();
let time_pos = self.time_pos(); let time_pos = self.time_pos();
let time_start = self.time_start().get(); let time_start = self.time_start().get();
let time_zoom = self.time_zoom().get(); let time_zoom = self.time_zoom().get();

View file

@ -2,7 +2,7 @@
(@shift-u redo 1) (@shift-u redo 1)
(@space clock toggle) (@space clock toggle)
(@shift-space clock toggle 0) (@shift-space clock toggle 0)
(@e editor show :pool-clip)
(@ctrl-a scene add) (@ctrl-a scene add)
(@ctrl-t track add) (@ctrl-t track add)
(@tab edit) (@tab edit)
(@c color)

View file

@ -3,7 +3,7 @@
(@left select :track-prev :scene) (@left select :track-prev :scene)
(@right select :track-next :scene) (@right select :track-next :scene)
(@q clip launch) (@q enqueue :clip)
(@c clip color) (@c clip color)
(@g clip get) (@g clip get)
(@p clip put) (@p clip put)

View file

@ -98,17 +98,22 @@ impl TekCli {
Ok(match self.mode { Ok(match self.mode {
TekMode::Clock => TekMode::Clock =>
engine.run(&jack.activate_with(|jack|Tek::new_clock( engine.run(&jack.activate_with(|jack|Tek::new_clock(
jack, self.bpm))?)?, jack, self.bpm, self.sync_lead, self.sync_follow))?)?,
TekMode::Sequencer => TekMode::Sequencer =>
engine.run(&jack.activate_with(|jack|Tek::new_sequencer( engine.run(&jack.activate_with(|jack|Tek::new_sequencer(
jack, self.bpm, &midi_froms, &midi_tos))?)?, jack, self.bpm, self.sync_lead, self.sync_follow,
&midi_froms, &midi_tos))?)?,
TekMode::Groovebox => TekMode::Groovebox =>
engine.run(&jack.activate_with(|jack|Tek::new_groovebox( engine.run(&jack.activate_with(|jack|Tek::new_groovebox(
jack, self.bpm, &midi_froms, &midi_tos, &audio_froms, &audio_tos))?)?, jack, self.bpm, self.sync_lead, self.sync_follow,
&midi_froms, &midi_tos,
&audio_froms, &audio_tos))?)?,
TekMode::Arranger { scenes, tracks, track_width, .. } => TekMode::Arranger { scenes, tracks, track_width, .. } =>
engine.run(&jack.activate_with(|jack|Tek::new_arranger( engine.run(&jack.activate_with(|jack|Tek::new_arranger(
jack, self.bpm, &midi_froms, &midi_tos, &audio_froms, &audio_tos, jack, self.bpm, self.sync_lead, self.sync_follow,
scenes, tracks, track_width, &midi_froms, &midi_tos,
&audio_froms, &audio_tos,
scenes, tracks, track_width
))?)?, ))?)?,
_ => todo!() _ => todo!()
}) })
@ -198,6 +203,8 @@ impl Tek {
fn new_arranger ( fn new_arranger (
jack: &Arc<RwLock<JackConnection>>, jack: &Arc<RwLock<JackConnection>>,
bpm: Option<f64>, bpm: Option<f64>,
sync_lead: bool,
sync_follow: bool,
midi_froms: &[PortConnection], midi_froms: &[PortConnection],
midi_tos: &[PortConnection], midi_tos: &[PortConnection],
audio_froms: &[&[PortConnection];2], audio_froms: &[&[PortConnection];2],
@ -208,7 +215,10 @@ impl Tek {
) -> Usually<Self> { ) -> Usually<Self> {
let mut arranger = Self { let mut arranger = Self {
edn: include_str!("./view_arranger.edn").to_string(), edn: include_str!("./view_arranger.edn").to_string(),
..Self::new_groovebox(jack, bpm, midi_froms, midi_tos, audio_froms, audio_tos)? ..Self::new_groovebox(
jack, bpm, sync_lead, sync_follow,
midi_froms, midi_tos, audio_froms, audio_tos,
)?
}; };
arranger.scenes_add(scenes); arranger.scenes_add(scenes);
arranger.tracks_add(tracks, track_width, &[], &[]); arranger.tracks_add(tracks, track_width, &[], &[]);
@ -217,6 +227,8 @@ impl Tek {
fn new_groovebox ( fn new_groovebox (
jack: &Arc<RwLock<JackConnection>>, jack: &Arc<RwLock<JackConnection>>,
bpm: Option<f64>, bpm: Option<f64>,
sync_lead: bool,
sync_follow: bool,
midi_froms: &[PortConnection], midi_froms: &[PortConnection],
midi_tos: &[PortConnection], midi_tos: &[PortConnection],
audio_froms: &[&[PortConnection];2], audio_froms: &[&[PortConnection];2],
@ -225,7 +237,7 @@ impl Tek {
let app = Self { let app = Self {
edn: include_str!("./view_groovebox.edn").to_string(), edn: include_str!("./view_groovebox.edn").to_string(),
sampler: Some(Sampler::new(jack, &"sampler", midi_froms, audio_froms, audio_tos)?), sampler: Some(Sampler::new(jack, &"sampler", midi_froms, audio_froms, audio_tos)?),
..Self::new_sequencer(jack, bpm, midi_froms, midi_tos)? ..Self::new_sequencer(jack, bpm, sync_lead, sync_follow, midi_froms, midi_tos)?
}; };
if let Some(sampler) = app.sampler.as_ref().unwrap().midi_in.as_ref() { if let Some(sampler) = app.sampler.as_ref().unwrap().midi_in.as_ref() {
jack.connect_ports(&app.player.as_ref().unwrap().midi_outs[0].port, &sampler.port)?; jack.connect_ports(&app.player.as_ref().unwrap().midi_outs[0].port, &sampler.port)?;
@ -235,6 +247,8 @@ impl Tek {
fn new_sequencer ( fn new_sequencer (
jack: &Arc<RwLock<JackConnection>>, jack: &Arc<RwLock<JackConnection>>,
bpm: Option<f64>, bpm: Option<f64>,
sync_lead: bool,
sync_follow: bool,
midi_froms: &[PortConnection], midi_froms: &[PortConnection],
midi_tos: &[PortConnection], midi_tos: &[PortConnection],
) -> Usually<Self> { ) -> Usually<Self> {
@ -247,32 +261,25 @@ impl Tek {
editing: false.into(), editing: false.into(),
midi_buf: vec![vec![];65536], midi_buf: vec![vec![];65536],
player: Some(MidiPlayer::new(&jack, "sequencer", Some(&clip), &midi_froms, &midi_tos)?), player: Some(MidiPlayer::new(&jack, "sequencer", Some(&clip), &midi_froms, &midi_tos)?),
..Self::new_clock(jack, bpm)? ..Self::new_clock(jack, bpm, sync_lead, sync_follow)?
}) })
} }
fn new_clock ( fn new_clock (
jack: &Arc<RwLock<JackConnection>>, jack: &Arc<RwLock<JackConnection>>,
bpm: Option<f64>, bpm: Option<f64>,
sync_lead: bool,
sync_follow: bool,
) -> Usually<Self> { ) -> Usually<Self> {
// TODO: enable sync master/follow let tek = Self {
//let sync_clock = |jack: &Arc<RwLock<JackConnection>>, app|{
//if cli.sync_lead {
//} else if cli.sync_follow {
//jack.read().unwrap().client().register_timebase_callback(false, |state|{
//app.clock().playhead.update_from_sample(state.position.frame() as f64);
//state.position
//})
//} else {
//Ok(())
//}
//};
Ok(Self {
edn: include_str!("./view_transport.edn").to_string(), edn: include_str!("./view_transport.edn").to_string(),
jack: jack.clone(), jack: jack.clone(),
color: ItemPalette::random(), color: ItemPalette::random(),
clock: Clock::new(jack, bpm), clock: Clock::new(jack, bpm),
..Default::default() ..Default::default()
}) };
tek.sync_lead(sync_lead);
tek.sync_follow(sync_follow);
Ok(tek)
} }
fn sync_lead (&self, enable: bool) -> Usually<()> { fn sync_lead (&self, enable: bool) -> Usually<()> {
if enable { if enable {
@ -285,8 +292,9 @@ impl Tek {
} }
Ok(()) Ok(())
} }
fn sync_follow (&self) { fn sync_follow (&self, enable: bool) -> Usually<()> {
// TODO // TODO: sync follow
Ok(())
} }
fn editor (&self) -> impl Content<TuiOut> + '_ { &self.editor } fn editor (&self) -> impl Content<TuiOut> + '_ { &self.editor }
fn view_clock (&self) -> impl Content<TuiOut> + use<'_> { fn view_clock (&self) -> impl Content<TuiOut> + use<'_> {
@ -297,6 +305,7 @@ impl Tek {
)) ))
} }
fn view_beat_stats (&self) -> impl Content<TuiOut> + use<'_> { fn view_beat_stats (&self) -> impl Content<TuiOut> + use<'_> {
let compact = self.size.w() > 80;
let clock = self.clock(); let clock = self.clock();
let now = clock.started.read().unwrap().as_ref().map(|start|clock.global.usec.get() - start.usec.get()); let now = clock.started.read().unwrap().as_ref().map(|start|clock.global.usec.get() - start.usec.get());
let beat = ||now.map(|now|clock.timebase.format_beats_1(clock.timebase.usecs_to_pulse(now))) let beat = ||now.map(|now|clock.timebase.format_beats_1(clock.timebase.usecs_to_pulse(now)))
@ -304,29 +313,29 @@ impl Tek {
let time = ||now.map(|now|format!("{:.3}s", now/1000000.)) let time = ||now.map(|now|format!("{:.3}s", now/1000000.))
.unwrap_or("-.---s".into()); .unwrap_or("-.---s".into());
let bpm = ||format!("{:.3}", clock.timebase.bpm.get()); let bpm = ||format!("{:.3}", clock.timebase.bpm.get());
Either::new(self.is_editing(), Either::new(compact,
row!(Field(TuiTheme::g(128).into(), "BPM", bpm()),
Field(TuiTheme::g(128).into(), "Beat", beat()),
Field(TuiTheme::g(128).into(), "Time", time())),
row!(FieldV(TuiTheme::g(128).into(), "BPM", bpm()), row!(FieldV(TuiTheme::g(128).into(), "BPM", bpm()),
FieldV(TuiTheme::g(128).into(), "Beat", beat()), FieldV(TuiTheme::g(128).into(), "Beat", beat()),
FieldV(TuiTheme::g(128).into(), "Time", time()),), FieldV(TuiTheme::g(128).into(), "Time", time())))
col!(Bsp::e(Tui::fg(TuiTheme::g(255), bpm()), " BPM"),
Bsp::e("Beat ", Tui::fg(TuiTheme::g(255), beat())),
Bsp::e("Time ", Tui::fg(TuiTheme::g(255), time()))))
} }
fn view_engine_stats (&self) -> impl Content<TuiOut> + use<'_> { fn view_engine_stats (&self) -> impl Content<TuiOut> + use<'_> {
let clock = self.clock(); let compact = self.size.w() > 80;
let compact = self.is_editing(); let clock = self.clock();
let rate = clock.timebase.sr.get(); let rate = clock.timebase.sr.get();
let chunk = clock.chunk.load(Relaxed); let chunk = clock.chunk.load(Relaxed);
let sr = move||format!("{}", if compact {format!("{:.1}kHz", rate / 1000.)} else {format!("{:.0}Hz", rate)}); let sr = move||format!("{}", if compact {format!("{:.1}kHz", rate / 1000.)} else {format!("{:.0}Hz", rate)});
let buffer_size = move||format!("{chunk}"); let buf = move||format!("{chunk}");
let latency = move||format!("{:.1}ms", chunk as f64 / rate * 1000.); let latency = move||format!("{:.1}ms", chunk as f64 / rate * 1000.);
Either::new(compact, Either::new(compact,
row!(Field(TuiTheme::g(128).into(), "SR", sr()),
Field(TuiTheme::g(128).into(), "Buf", buf()),
Field(TuiTheme::g(128).into(), "Lat", latency())),
row!(FieldV(TuiTheme::g(128).into(), "SR", sr()), row!(FieldV(TuiTheme::g(128).into(), "SR", sr()),
FieldV(TuiTheme::g(128).into(), "Buf", buffer_size()), FieldV(TuiTheme::g(128).into(), "Buf", buf()),
FieldV(TuiTheme::g(128).into(), "Lat", latency())), FieldV(TuiTheme::g(128).into(), "Lat", latency())))
col!(Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", sr())), " sample rate"),
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", buffer_size())), " sample buffer"),
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{:.3}ms", latency())), " latency")))
} }
fn view_meter <'a> (&'a self, label: &'a str, value: f32) -> impl Content<TuiOut> + 'a { fn view_meter <'a> (&'a self, label: &'a str, value: f32) -> impl Content<TuiOut> + 'a {
col!( col!(
@ -356,9 +365,10 @@ impl Tek {
} }
fn view_play_pause (&self) -> impl Content<TuiOut> + use<'_> { fn view_play_pause (&self) -> impl Content<TuiOut> + use<'_> {
let playing = self.clock.is_rolling(); let playing = self.clock.is_rolling();
let compact = self.is_editing();
Tui::bg( Tui::bg(
if playing{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)}, if playing{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
Either::new(self.is_editing(), Either::new(compact,
Thunk::new(move||Fixed::x(9, Either::new(playing, Thunk::new(move||Fixed::x(9, Either::new(playing,
Tui::fg(Color::Rgb(0, 255, 0), " PLAYING "), Tui::fg(Color::Rgb(0, 255, 0), " PLAYING "),
Tui::fg(Color::Rgb(255, 128, 0), " STOPPED ")))), Tui::fg(Color::Rgb(255, 128, 0), " STOPPED ")))),
@ -404,12 +414,6 @@ impl Tek {
clip.write().unwrap().toggle_loop() clip.write().unwrap().toggle_loop()
} }
} }
fn track_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(||Tui::bg(TuiTheme::g(32), Bsp::s(
Fill::x(Align::w(button(" C-a ".to_string(), format!(" add scene ({})", self.scenes().len())))),
Fill::x(Align::w(button(" C-t ".to_string(), format!(" add track ({})", self.tracks().len())))),
)).boxed()).into()
}
fn track_add ( fn track_add (
&mut self, &mut self,
name: Option<&str>, name: Option<&str>,
@ -435,8 +439,8 @@ impl Tek {
} }
fn tracks_add ( fn tracks_add (
&mut self, &mut self,
count: usize, count: usize,
width: usize, width: usize,
midi_from: &[PortConnection], midi_from: &[PortConnection],
midi_to: &[PortConnection], midi_to: &[PortConnection],
) -> Usually<()> { ) -> Usually<()> {
@ -508,7 +512,9 @@ impl Tek {
let neighbor = selected_track == Some(t) && selected_scene.map(|s|s+1) == Some(s); let neighbor = selected_track == Some(t) && selected_scene.map(|s|s+1) == Some(s);
map_south(y1 as u16, h, Push::y(1, Fixed::y(h, Either::new( map_south(y1 as u16, h, Push::y(1, Fixed::y(h, Either::new(
active, active,
Thunk::new(||self.editor()), Thunk::new(||Bsp::a(
Fill::xy(Align::nw(button(" Tab ".into(), "".into()))),
self.editor())),
Thunk::new(move||Bsp::a( Thunk::new(move||Bsp::a(
When::new(selected, Fill::y(Align::n(button(" Tab ".into(), "edit".into())))), When::new(selected, Fill::y(Align::n(button(" Tab ".into(), "edit".into())))),
phat_sel_3( phat_sel_3(
@ -549,6 +555,37 @@ impl Tek {
} }
Ok(()) Ok(())
} }
fn button (&self, key: String, label: String) -> impl Content<TuiOut> {
let compact = !self.is_editing();
Tui::bold(true, Bsp::e(
Margin::x(1, Tui::fg_bg(TuiTheme::g(0), TuiTheme::orange(), key)),
When::new(compact, Margin::x(1, Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(96), label))),
))
}
fn input_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let fg = TuiTheme::g(224);
let bg = TuiTheme::g(64);
(move||Bsp::s(Fill::x(Align::w(self.button(" I ".to_string(), format!(" midi ins ({})", self.midi_ins().len())))), self.midi_ins().get(0).map(|inp|Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(inp.name.clone())))),
inp.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
Tui::fg_bg(fg, bg, connect.info()))))),
))).boxed()).into()
}
fn output_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let fg = TuiTheme::g(224);
let bg = TuiTheme::g(64);
(move||Bsp::s(Fill::x(Align::w(self.button(" O ".to_string(), format!(" midi outs ({}) ", self.midi_outs().len())))), self.midi_outs().get(0).map(|out|Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(out.name.clone())))),
out.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
Tui::fg_bg(fg, bg, connect.info()))))),
))).boxed()).into()
}
fn track_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(||Tui::bg(TuiTheme::g(32), Bsp::s(
Fill::x(Align::w(self.button(" C-a ".to_string(), format!(" add scene ({})", self.scenes().len())))),
Fill::x(Align::w(self.button(" C-t ".to_string(), format!(" add track ({})", self.tracks().len())))),
)).boxed()).into()
}
} }
const KEYS_APP: &str = include_str!("keys.edn"); const KEYS_APP: &str = include_str!("keys.edn");
const KEYS_CLIP: &str = include_str!("keys_clip.edn"); const KEYS_CLIP: &str = include_str!("keys_clip.edn");
@ -600,17 +637,13 @@ handle!(TuiIn: |self: Tek, input|Ok({
Zoom(Option<usize>), Zoom(Option<usize>),
} }
edn_command!(TekCommand: |app: Tek| { edn_command!(TekCommand: |app: Tek| {
("edit" [] Self::Edit(None)) ("stop-all" [] Self::StopAll)
("edit" [c: bool] Self::Edit(c))
("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default()))
("undo" [d: usize] Self::History(-(d.unwrap_or(0)as isize))) ("undo" [d: usize] Self::History(-(d.unwrap_or(0)as isize)))
("redo" [d: usize] Self::History(d.unwrap_or(0) as isize)) ("redo" [d: usize] Self::History(d.unwrap_or(0) as isize))
("zoom" [z: usize] Self::Zoom(z)) ("zoom" [z: usize] Self::Zoom(z))
("edit" [] Self::Edit(None))
("stop-all" [] Self::StopAll) ("edit" [c: bool] Self::Edit(c))
("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default()))
("enqueue" [c: Arc<RwLock<MidiClip>>] Self::Enqueue(c)) ("enqueue" [c: Arc<RwLock<MidiClip>>] Self::Enqueue(c))
("select" [t: usize, s: usize] match (t.expect("no track"), s.expect("no scene")) { ("select" [t: usize, s: usize] match (t.expect("no track"), s.expect("no scene")) {
(0, 0) => Self::Select(Selection::Mix), (0, 0) => Self::Select(Selection::Mix),
@ -618,7 +651,6 @@ edn_command!(TekCommand: |app: Tek| {
(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)),
}) })
("clip" [a, ..b] Self::Clip(ClipCommand::from_edn(app, &a.to_ref(), b) ("clip" [a, ..b] Self::Clip(ClipCommand::from_edn(app, &a.to_ref(), b)
.expect("invalid command"))) .expect("invalid command")))
("clock" [a, ..b] Self::Clock(ClockCommand::from_edn(app.clock(), &a.to_ref(), b) ("clock" [a, ..b] Self::Clock(ClockCommand::from_edn(app.clock(), &a.to_ref(), b)
@ -635,18 +667,42 @@ edn_command!(TekCommand: |app: Tek| {
.expect("invalid command"))) .expect("invalid command")))
}); });
command!(|self: TekCommand, app: Tek|match self { command!(|self: TekCommand, app: Tek|match self {
Self::Zoom(_) => { println!("\n\rtodo: global zoom"); None }, Self::Zoom(_) => { println!("\n\rtodo: global zoom"); None },
Self::History(delta) => { println!("\n\rtodo: undo/redo"); None }, Self::History(delta) => { println!("\n\rtodo: undo/redo"); None },
Self::Select(s) => { app.selected = s; None }, Self::Select(s) => {
Self::Clock(cmd) => cmd.delegate(app, Self::Clock)?, app.selected = s;
Self::Scene(cmd) => cmd.delegate(app, Self::Scene)?, // autoedit: load focused clip in editor.
Self::Track(cmd) => cmd.delegate(app, Self::Track)?, if let Some(ref mut editor) = app.editor {
Self::Clip(cmd) => cmd.delegate(app, Self::Clip)?, editor.set_clip(match app.selected {
Self::Editor(cmd) => app.editor.as_mut() Selection::Clip(t, s) if let Some(Some(Some(clip))) = app
.scenes
.get(s)
.map(|s|s.clips.get(t)) => Some(clip),
_ => None
});
}
None
},
Self::Edit(value) => {
// TODO: autocreate: create new clip when entering empty cell
if let Some(value) = value {
if app.is_editing() != value {
app.editing.store(value, Relaxed);
}
} else {
app.editing.store(!app.is_editing(), Relaxed);
};
None
},
Self::Clock(cmd) => cmd.delegate(app, Self::Clock)?,
Self::Scene(cmd) => cmd.delegate(app, Self::Scene)?,
Self::Track(cmd) => cmd.delegate(app, Self::Track)?,
Self::Clip(cmd) => cmd.delegate(app, Self::Clip)?,
Self::Editor(cmd) => app.editor.as_mut()
.map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(), .map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(),
Self::Sampler(cmd) => app.sampler.as_mut() Self::Sampler(cmd) => app.sampler.as_mut()
.map(|sampler|cmd.delegate(sampler, Self::Sampler)).transpose()?.flatten(), .map(|sampler|cmd.delegate(sampler, Self::Sampler)).transpose()?.flatten(),
Self::Enqueue(clip) => app.player.as_mut() Self::Enqueue(clip) => app.player.as_mut()
.map(|player|{player.enqueue_next(clip.as_ref());None}).flatten(), .map(|player|{player.enqueue_next(clip.as_ref());None}).flatten(),
Self::Color(palette) => { Self::Color(palette) => {
let old = app.color; let old = app.color;
@ -672,17 +728,6 @@ command!(|self: TekCommand, app: Tek|match self {
} else { } else {
None None
}, },
Self::Edit(value) => if let Some(value) = value {
if app.is_editing() != value {
app.editing.store(value, Relaxed);
Some(Self::Edit(Some(!value)))
} else {
None
}
} else {
app.editing.store(!app.is_editing(), Relaxed);
Some(Self::Edit(Some(!app.is_editing())))
}
}); });
/// Represents the current user selection in the arranger /// Represents the current user selection in the arranger
#[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection { #[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection {
@ -736,19 +781,19 @@ trait HasSelection {
} }
#[derive(Debug, Default)] struct Track { #[derive(Debug, Default)] struct Track {
/// Name of track /// Name of track
name: Arc<str>, name: Arc<str>,
/// Preferred width of track column /// Preferred width of track column
width: usize, width: usize,
/// Identifying color of track /// Identifying color of track
color: ItemPalette, color: ItemPalette,
/// MIDI player state /// MIDI player state
player: MidiPlayer, player: MidiPlayer,
/// Device chain
devices: Vec<Box<dyn Device>>,
/// Inputs of 1st device /// Inputs of 1st device
audio_ins: Vec<JackPort<AudioIn>>, audio_ins: Vec<JackPort<AudioIn>>,
/// Outputs of last device /// Outputs of last device
audio_outs: Vec<JackPort<AudioOut>>, audio_outs: Vec<JackPort<AudioOut>>,
/// Device chain
devices: Vec<Box<dyn Device>>,
} }
has_clock!(|self: Track|self.player.clock); has_clock!(|self: Track|self.player.clock);
has_player!(|self: Track|self.player); has_player!(|self: Track|self.player);
@ -840,15 +885,6 @@ trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
)) ))
})).boxed()).into() })).boxed()).into()
} }
fn input_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let fg = TuiTheme::g(224);
let bg = TuiTheme::g(64);
(move||Bsp::s(Fill::x(Align::w(button(" I ".to_string(), format!(" midi ins ({})", self.midi_ins().len())))), self.midi_ins().get(0).map(|inp|Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(inp.name.clone())))),
inp.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
Tui::fg_bg(fg, bg, connect.info()))))),
))).boxed()).into()
}
fn input_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { fn input_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(move||Align::x(Map::new(||self.tracks_sizes(self.is_editing(), self.editor_w()), move|(_, track, x1, x2), i| { (move||Align::x(Map::new(||self.tracks_sizes(self.is_editing(), self.editor_w()), move|(_, track, x1, x2), i| {
let w = (x2 - x1) as u16; let w = (x2 - x1) as u16;
@ -868,15 +904,6 @@ trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
Tui::fg_bg(if mon { Color::White } else { bg }, bg, ""), Tui::fg_bg(if mon { Color::White } else { bg }, bg, ""),
) )
} }
fn output_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let fg = TuiTheme::g(224);
let bg = TuiTheme::g(64);
(move||Bsp::s(Fill::x(Align::w(button(" O ".to_string(), format!(" midi outs ({}) ", self.midi_outs().len())))), self.midi_outs().get(0).map(|out|Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(out.name.clone())))),
out.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
Tui::fg_bg(fg, bg, connect.info()))))),
))).boxed()).into()
}
fn output_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> { fn output_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(move||Align::x(Map::new(||self.tracks_sizes(self.is_editing(), self.editor_w()), move|(_, track, x1, x2), i| { (move||Align::x(Map::new(||self.tracks_sizes(self.is_editing(), self.editor_w()), move|(_, track, x1, x2), i| {
let w = (x2 - x1) as u16; let w = (x2 - x1) as u16;

View file

@ -1,3 +1,5 @@
(bsp/s (fixed/y 2 :toolbar) (bsp/s (fixed/y 2 :toolbar)
(fill/x (align/c (bsp/w (fixed/x :pool-w :pool) (fill/x (align/c (bsp/w (fixed/x :pool-w :pool)
(bsp/n (fixed/y 3 :outputs) (bsp/n (fixed/y 3 :inputs) (bsp/n (fixed/y 3 :tracks) :scenes))))))) (bsp/n (fixed/y 3 :outputs)
(bsp/n (fixed/y 3 :inputs)
(bsp/n (fixed/y 3 :tracks) :scenes)))))))

View file

@ -38,7 +38,7 @@ impl Content<TuiOut> for Repeat<'_> {
fn render (&self, to: &mut TuiOut) { fn render (&self, to: &mut TuiOut) {
let [x, y, w, h] = to.area().xywh(); let [x, y, w, h] = to.area().xywh();
let a = self.0.len(); let a = self.0.len();
for (v, y) in (y..y+h).enumerate() { for (_v, y) in (y..y+h).enumerate() {
for (u, x) in (x..x+w).enumerate() { for (u, x) in (x..x+w).enumerate() {
if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((x, y))) { if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((x, y))) {
let u = u % a; let u = u % a;
@ -56,7 +56,7 @@ impl Content<TuiOut> for RepeatV<'_> {
to to
} }
fn render (&self, to: &mut TuiOut) { fn render (&self, to: &mut TuiOut) {
let [x, y, w, h] = to.area().xywh(); let [x, y, _w, h] = to.area().xywh();
for y in y..y+h { for y in y..y+h {
if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((x, y))) { if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((x, y))) {
cell.set_symbol(&self.0); cell.set_symbol(&self.0);
@ -72,7 +72,7 @@ impl Content<TuiOut> for RepeatH<'_> {
to to
} }
fn render (&self, to: &mut TuiOut) { fn render (&self, to: &mut TuiOut) {
let [x, y, w, h] = to.area().xywh(); let [x, y, w, _h] = to.area().xywh();
for x in x..x+w { for x in x..x+w {
if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((x, y))) { if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((x, y))) {
cell.set_symbol(&self.0); cell.set_symbol(&self.0);