mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +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 {
|
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() {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
235
tek/src/lib.rs
235
tek/src/lib.rs
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)))))))
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue