mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
autoedit
This commit is contained in:
parent
968441850f
commit
c08d1bee5d
7 changed files with 177 additions and 150 deletions
|
|
@ -94,11 +94,11 @@ impl Default for MidiEditor {
|
|||
}
|
||||
}
|
||||
impl MidiEditor {
|
||||
pub fn clip_length (&self) -> usize {
|
||||
self.clip().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1)
|
||||
}
|
||||
//fn clip_length (&self) -> usize {
|
||||
//self.clip().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1)
|
||||
//}
|
||||
/// Put note at current position
|
||||
pub fn put_note (&mut self, advance: bool) {
|
||||
fn put_note (&mut self, advance: bool) {
|
||||
let mut redraw = false;
|
||||
if let Some(clip) = self.clip() {
|
||||
let mut clip = clip.write().unwrap();
|
||||
|
|
@ -127,6 +127,34 @@ impl MidiEditor {
|
|||
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 {
|
||||
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 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| {
|
||||
("note/put" [a: bool] Self::PutNote)
|
||||
("note/del" [a: bool] Self::PutNote)
|
||||
("note/put" [_a: bool] Self::PutNote)
|
||||
("note/del" [_a: bool] Self::PutNote)
|
||||
("note/pos" [a: usize] Self::SetNoteCursor(a.expect("no note cursor")))
|
||||
("note/len" [a: usize] Self::SetNoteLength(a.expect("no note length")))
|
||||
("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>>>),
|
||||
}
|
||||
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::*;
|
||||
let edns = EdnItem::<&str>::read_all(KEYS_EDIT)?;
|
||||
for item in edns.iter() {
|
||||
|
|
|
|||
|
|
@ -144,10 +144,10 @@ impl PianoHorizontal {
|
|||
}
|
||||
fn cursor (&self) -> impl Content<TuiOut> {
|
||||
let style = Some(Style::default().fg(self.color.lightest.rgb));
|
||||
let note_hi = self.note_hi();
|
||||
let note_len = self.note_len();
|
||||
let note_lo = self.note_lo().get();
|
||||
let note_hi = self.note_hi();
|
||||
let note_lo = self.note_lo().get();
|
||||
let note_pos = self.note_pos();
|
||||
let note_len = self.note_len();
|
||||
let time_pos = self.time_pos();
|
||||
let time_start = self.time_start().get();
|
||||
let time_zoom = self.time_zoom().get();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
(@shift-u redo 1)
|
||||
(@space clock toggle)
|
||||
(@shift-space clock toggle 0)
|
||||
(@e editor show :pool-clip)
|
||||
(@ctrl-a scene add)
|
||||
(@ctrl-t track add)
|
||||
(@tab edit)
|
||||
(@c color)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
(@left select :track-prev :scene)
|
||||
(@right select :track-next :scene)
|
||||
|
||||
(@q clip launch)
|
||||
(@q enqueue :clip)
|
||||
(@c clip color)
|
||||
(@g clip get)
|
||||
(@p clip put)
|
||||
|
|
|
|||
235
tek/src/lib.rs
235
tek/src/lib.rs
|
|
@ -98,17 +98,22 @@ impl TekCli {
|
|||
Ok(match self.mode {
|
||||
TekMode::Clock =>
|
||||
engine.run(&jack.activate_with(|jack|Tek::new_clock(
|
||||
jack, self.bpm))?)?,
|
||||
jack, self.bpm, self.sync_lead, self.sync_follow))?)?,
|
||||
TekMode::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 =>
|
||||
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, .. } =>
|
||||
engine.run(&jack.activate_with(|jack|Tek::new_arranger(
|
||||
jack, self.bpm, &midi_froms, &midi_tos, &audio_froms, &audio_tos,
|
||||
scenes, tracks, track_width,
|
||||
jack, self.bpm, self.sync_lead, self.sync_follow,
|
||||
&midi_froms, &midi_tos,
|
||||
&audio_froms, &audio_tos,
|
||||
scenes, tracks, track_width
|
||||
))?)?,
|
||||
_ => todo!()
|
||||
})
|
||||
|
|
@ -198,6 +203,8 @@ impl Tek {
|
|||
fn new_arranger (
|
||||
jack: &Arc<RwLock<JackConnection>>,
|
||||
bpm: Option<f64>,
|
||||
sync_lead: bool,
|
||||
sync_follow: bool,
|
||||
midi_froms: &[PortConnection],
|
||||
midi_tos: &[PortConnection],
|
||||
audio_froms: &[&[PortConnection];2],
|
||||
|
|
@ -208,7 +215,10 @@ impl Tek {
|
|||
) -> Usually<Self> {
|
||||
let mut arranger = Self {
|
||||
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.tracks_add(tracks, track_width, &[], &[]);
|
||||
|
|
@ -217,6 +227,8 @@ impl Tek {
|
|||
fn new_groovebox (
|
||||
jack: &Arc<RwLock<JackConnection>>,
|
||||
bpm: Option<f64>,
|
||||
sync_lead: bool,
|
||||
sync_follow: bool,
|
||||
midi_froms: &[PortConnection],
|
||||
midi_tos: &[PortConnection],
|
||||
audio_froms: &[&[PortConnection];2],
|
||||
|
|
@ -225,7 +237,7 @@ impl Tek {
|
|||
let app = Self {
|
||||
edn: include_str!("./view_groovebox.edn").to_string(),
|
||||
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() {
|
||||
jack.connect_ports(&app.player.as_ref().unwrap().midi_outs[0].port, &sampler.port)?;
|
||||
|
|
@ -235,6 +247,8 @@ impl Tek {
|
|||
fn new_sequencer (
|
||||
jack: &Arc<RwLock<JackConnection>>,
|
||||
bpm: Option<f64>,
|
||||
sync_lead: bool,
|
||||
sync_follow: bool,
|
||||
midi_froms: &[PortConnection],
|
||||
midi_tos: &[PortConnection],
|
||||
) -> Usually<Self> {
|
||||
|
|
@ -247,32 +261,25 @@ impl Tek {
|
|||
editing: false.into(),
|
||||
midi_buf: vec![vec![];65536],
|
||||
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 (
|
||||
jack: &Arc<RwLock<JackConnection>>,
|
||||
bpm: Option<f64>,
|
||||
sync_lead: bool,
|
||||
sync_follow: bool,
|
||||
) -> Usually<Self> {
|
||||
// TODO: enable sync master/follow
|
||||
//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 {
|
||||
let tek = Self {
|
||||
edn: include_str!("./view_transport.edn").to_string(),
|
||||
jack: jack.clone(),
|
||||
color: ItemPalette::random(),
|
||||
clock: Clock::new(jack, bpm),
|
||||
..Default::default()
|
||||
})
|
||||
};
|
||||
tek.sync_lead(sync_lead);
|
||||
tek.sync_follow(sync_follow);
|
||||
Ok(tek)
|
||||
}
|
||||
fn sync_lead (&self, enable: bool) -> Usually<()> {
|
||||
if enable {
|
||||
|
|
@ -285,8 +292,9 @@ impl Tek {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
fn sync_follow (&self) {
|
||||
// TODO
|
||||
fn sync_follow (&self, enable: bool) -> Usually<()> {
|
||||
// TODO: sync follow
|
||||
Ok(())
|
||||
}
|
||||
fn editor (&self) -> impl Content<TuiOut> + '_ { &self.editor }
|
||||
fn view_clock (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
|
|
@ -297,6 +305,7 @@ impl Tek {
|
|||
))
|
||||
}
|
||||
fn view_beat_stats (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
let compact = self.size.w() > 80;
|
||||
let clock = self.clock();
|
||||
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)))
|
||||
|
|
@ -304,29 +313,29 @@ impl Tek {
|
|||
let time = ||now.map(|now|format!("{:.3}s", now/1000000.))
|
||||
.unwrap_or("-.---s".into());
|
||||
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()),
|
||||
FieldV(TuiTheme::g(128).into(), "Beat", beat()),
|
||||
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()))))
|
||||
FieldV(TuiTheme::g(128).into(), "Time", time())))
|
||||
}
|
||||
fn view_engine_stats (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
let clock = self.clock();
|
||||
let compact = self.is_editing();
|
||||
let rate = clock.timebase.sr.get();
|
||||
let chunk = clock.chunk.load(Relaxed);
|
||||
let sr = move||format!("{}", if compact {format!("{:.1}kHz", rate / 1000.)} else {format!("{:.0}Hz", rate)});
|
||||
let buffer_size = move||format!("{chunk}");
|
||||
let latency = move||format!("{:.1}ms", chunk as f64 / rate * 1000.);
|
||||
let compact = self.size.w() > 80;
|
||||
let clock = self.clock();
|
||||
let rate = clock.timebase.sr.get();
|
||||
let chunk = clock.chunk.load(Relaxed);
|
||||
let sr = move||format!("{}", if compact {format!("{:.1}kHz", rate / 1000.)} else {format!("{:.0}Hz", rate)});
|
||||
let buf = move||format!("{chunk}");
|
||||
let latency = move||format!("{:.1}ms", chunk as f64 / rate * 1000.);
|
||||
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()),
|
||||
FieldV(TuiTheme::g(128).into(), "Buf", buffer_size()),
|
||||
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")))
|
||||
FieldV(TuiTheme::g(128).into(), "Buf", buf()),
|
||||
FieldV(TuiTheme::g(128).into(), "Lat", latency())))
|
||||
}
|
||||
fn view_meter <'a> (&'a self, label: &'a str, value: f32) -> impl Content<TuiOut> + 'a {
|
||||
col!(
|
||||
|
|
@ -356,9 +365,10 @@ impl Tek {
|
|||
}
|
||||
fn view_play_pause (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
let playing = self.clock.is_rolling();
|
||||
let compact = self.is_editing();
|
||||
Tui::bg(
|
||||
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,
|
||||
Tui::fg(Color::Rgb(0, 255, 0), " PLAYING "),
|
||||
Tui::fg(Color::Rgb(255, 128, 0), " STOPPED ")))),
|
||||
|
|
@ -404,12 +414,6 @@ impl Tek {
|
|||
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 (
|
||||
&mut self,
|
||||
name: Option<&str>,
|
||||
|
|
@ -435,8 +439,8 @@ impl Tek {
|
|||
}
|
||||
fn tracks_add (
|
||||
&mut self,
|
||||
count: usize,
|
||||
width: usize,
|
||||
count: usize,
|
||||
width: usize,
|
||||
midi_from: &[PortConnection],
|
||||
midi_to: &[PortConnection],
|
||||
) -> Usually<()> {
|
||||
|
|
@ -508,7 +512,9 @@ impl Tek {
|
|||
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(
|
||||
active,
|
||||
Thunk::new(||self.editor()),
|
||||
Thunk::new(||Bsp::a(
|
||||
Fill::xy(Align::nw(button(" Tab ".into(), "".into()))),
|
||||
self.editor())),
|
||||
Thunk::new(move||Bsp::a(
|
||||
When::new(selected, Fill::y(Align::n(button(" Tab ".into(), "edit".into())))),
|
||||
phat_sel_3(
|
||||
|
|
@ -549,6 +555,37 @@ impl Tek {
|
|||
}
|
||||
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_CLIP: &str = include_str!("keys_clip.edn");
|
||||
|
|
@ -600,17 +637,13 @@ handle!(TuiIn: |self: Tek, input|Ok({
|
|||
Zoom(Option<usize>),
|
||||
}
|
||||
edn_command!(TekCommand: |app: Tek| {
|
||||
("edit" [] Self::Edit(None))
|
||||
("edit" [c: bool] Self::Edit(c))
|
||||
|
||||
("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default()))
|
||||
|
||||
("stop-all" [] Self::StopAll)
|
||||
("undo" [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))
|
||||
|
||||
("stop-all" [] Self::StopAll)
|
||||
("edit" [] Self::Edit(None))
|
||||
("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))
|
||||
("select" [t: usize, s: usize] match (t.expect("no track"), s.expect("no scene")) {
|
||||
(0, 0) => Self::Select(Selection::Mix),
|
||||
|
|
@ -618,7 +651,6 @@ edn_command!(TekCommand: |app: Tek| {
|
|||
(0, s) => Self::Select(Selection::Scene(s)),
|
||||
(t, s) => Self::Select(Selection::Clip(t, s)),
|
||||
})
|
||||
|
||||
("clip" [a, ..b] Self::Clip(ClipCommand::from_edn(app, &a.to_ref(), b)
|
||||
.expect("invalid command")))
|
||||
("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")))
|
||||
});
|
||||
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::Select(s) => { app.selected = s; 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()
|
||||
Self::Select(s) => {
|
||||
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
|
||||
},
|
||||
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(),
|
||||
Self::Sampler(cmd) => app.sampler.as_mut()
|
||||
Self::Sampler(cmd) => app.sampler.as_mut()
|
||||
.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(),
|
||||
Self::Color(palette) => {
|
||||
let old = app.color;
|
||||
|
|
@ -672,17 +728,6 @@ command!(|self: TekCommand, app: Tek|match self {
|
|||
} else {
|
||||
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
|
||||
#[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection {
|
||||
|
|
@ -736,19 +781,19 @@ trait HasSelection {
|
|||
}
|
||||
#[derive(Debug, Default)] struct Track {
|
||||
/// Name of track
|
||||
name: Arc<str>,
|
||||
name: Arc<str>,
|
||||
/// Preferred width of track column
|
||||
width: usize,
|
||||
width: usize,
|
||||
/// Identifying color of track
|
||||
color: ItemPalette,
|
||||
color: ItemPalette,
|
||||
/// MIDI player state
|
||||
player: MidiPlayer,
|
||||
player: MidiPlayer,
|
||||
/// Device chain
|
||||
devices: Vec<Box<dyn Device>>,
|
||||
/// Inputs of 1st device
|
||||
audio_ins: Vec<JackPort<AudioIn>>,
|
||||
/// Outputs of last device
|
||||
audio_outs: Vec<JackPort<AudioOut>>,
|
||||
/// Device chain
|
||||
devices: Vec<Box<dyn Device>>,
|
||||
}
|
||||
has_clock!(|self: Track|self.player.clock);
|
||||
has_player!(|self: Track|self.player);
|
||||
|
|
@ -840,15 +885,6 @@ trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
|
|||
))
|
||||
})).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> {
|
||||
(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;
|
||||
|
|
@ -868,15 +904,6 @@ trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
|
|||
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> {
|
||||
(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;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
(bsp/s (fixed/y 2 :toolbar)
|
||||
(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)))))))
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ impl Content<TuiOut> for Repeat<'_> {
|
|||
fn render (&self, to: &mut TuiOut) {
|
||||
let [x, y, w, h] = to.area().xywh();
|
||||
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() {
|
||||
if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((x, y))) {
|
||||
let u = u % a;
|
||||
|
|
@ -56,7 +56,7 @@ impl Content<TuiOut> for RepeatV<'_> {
|
|||
to
|
||||
}
|
||||
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 {
|
||||
if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((x, y))) {
|
||||
cell.set_symbol(&self.0);
|
||||
|
|
@ -72,7 +72,7 @@ impl Content<TuiOut> for RepeatH<'_> {
|
|||
to
|
||||
}
|
||||
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 {
|
||||
if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((x, y))) {
|
||||
cell.set_symbol(&self.0);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue