mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
big border around selected scene
This commit is contained in:
parent
2e81549747
commit
69832723b3
4 changed files with 117 additions and 80 deletions
2
Justfile
2
Justfile
|
|
@ -14,7 +14,7 @@ test:
|
||||||
cargo test
|
cargo test
|
||||||
|
|
||||||
cloc:
|
cloc:
|
||||||
for src in {cli,edn/src,input/src,jack/src,midi/src,output/src,tek/src,time/src,tui/src}; do echo $src; cloc $src; done
|
for src in {cli,edn/src,input/src,jack/src,midi/src,output/src,tek/src,time/src,tui/src}; do echo; echo $src; cloc --quiet $src; done
|
||||||
|
|
||||||
status:
|
status:
|
||||||
cargo c
|
cargo c
|
||||||
|
|
|
||||||
123
cli/tek.rs
123
cli/tek.rs
|
|
@ -66,6 +66,7 @@ pub enum TekMode {
|
||||||
pub fn main () -> Usually<()> {
|
pub fn main () -> Usually<()> {
|
||||||
let cli = TekCli::parse();
|
let cli = TekCli::parse();
|
||||||
let name = cli.name.as_ref().map_or("tek", |x|x.as_str());
|
let name = cli.name.as_ref().map_or("tek", |x|x.as_str());
|
||||||
|
let color = ItemPalette::random();
|
||||||
let jack = JackConnection::new(name)?;
|
let jack = JackConnection::new(name)?;
|
||||||
let engine = Tui::new()?;
|
let engine = Tui::new()?;
|
||||||
let empty = &[] as &[&str];
|
let empty = &[] as &[&str];
|
||||||
|
|
@ -77,109 +78,113 @@ pub fn main () -> Usually<()> {
|
||||||
let right_tos = PortConnection::collect(&cli.right_to, empty, empty);
|
let right_tos = PortConnection::collect(&cli.right_to, empty, empty);
|
||||||
let perf = PerfModel::default();
|
let perf = PerfModel::default();
|
||||||
let size = Measure::new();
|
let size = Measure::new();
|
||||||
let default_clip = ||{
|
let default_clip = ||Arc::new(RwLock::new(MidiClip::new(
|
||||||
let len = 384usize;
|
"Clip", true, 384usize, None, Some(ItemColor::random().into()))));
|
||||||
let color = ItemColor::random().into();
|
let default_player = |jack: &Arc<RwLock<JackConnection>>, clip: Option<&Arc<RwLock<MidiClip>>>|
|
||||||
Arc::new(RwLock::new(MidiClip::new("Clip", true, len, None, Some(color))))
|
MidiPlayer::new(&jack, name, clip, &midi_froms, &midi_tos);
|
||||||
|
let default_sampler = |jack: &Arc<RwLock<JackConnection>>|
|
||||||
|
Sampler::new(jack, &"sampler", &midi_froms,
|
||||||
|
&[&left_froms, &right_froms], &[&left_tos, &right_tos]);
|
||||||
|
let default_bpm = |clock: Clock|{
|
||||||
|
if let Some(bpm) = cli.bpm {
|
||||||
|
clock.timebase.bpm.set(bpm);
|
||||||
|
}
|
||||||
|
clock
|
||||||
};
|
};
|
||||||
|
let default_clock = |jack: &Arc<RwLock<JackConnection>>|{
|
||||||
|
let clock = Clock::from(jack);
|
||||||
|
default_bpm(clock)
|
||||||
|
};
|
||||||
|
// TODO: enable sync master/follow
|
||||||
|
//let sync_clock = |jack: &Arc<RwLock<JackConnection>>, app|{
|
||||||
|
//if cli.sync_lead {
|
||||||
|
//jack.read().unwrap().client().register_timebase_callback(false, |mut state|{
|
||||||
|
//app.clock().playhead.update_from_sample(state.position.frame() as f64);
|
||||||
|
//state.position.bbt = Some(app.clock().bbt());
|
||||||
|
//state.position
|
||||||
|
//})
|
||||||
|
//} 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(match cli.mode {
|
Ok(match cli.mode {
|
||||||
|
|
||||||
TekMode::Clock => engine.run(&jack.activate_with(|jack|Ok(TransportTui {
|
TekMode::Clock => engine.run(&jack.activate_with(|jack|Ok(TransportTui {
|
||||||
jack: jack.clone(),
|
jack: jack.clone(), clock: default_clock(jack),
|
||||||
clock: Clock::from(jack),
|
|
||||||
}))?)?,
|
}))?)?,
|
||||||
|
|
||||||
TekMode::Sequencer => engine.run(&jack.activate_with(|jack|Ok({
|
TekMode::Sequencer => engine.run(&jack.activate_with(|jack|Ok({
|
||||||
let clip = default_clip();
|
let clip = default_clip();
|
||||||
let player = MidiPlayer::new(&jack, name, Some(&clip), &midi_froms, &midi_tos)?;
|
let mut player = default_player(jack, Some(&clip))?;
|
||||||
|
player.clock = default_bpm(player.clock);
|
||||||
Sequencer {
|
Sequencer {
|
||||||
_jack: jack.clone(),
|
_jack: jack.clone(),
|
||||||
clock: player.clock.clone(),
|
|
||||||
|
|
||||||
player,
|
player,
|
||||||
editor: MidiEditor::from(&clip),
|
pool: (&clip).into(),
|
||||||
pool: PoolModel::from(&clip),
|
editor: (&clip).into(),
|
||||||
compact: true,
|
midi_buf: vec![vec![];65536],
|
||||||
transport: true,
|
note_buf: vec![],
|
||||||
selectors: true,
|
status: true,
|
||||||
midi_buf: vec![vec![];65536],
|
|
||||||
note_buf: vec![],
|
|
||||||
status: true,
|
|
||||||
perf,
|
perf,
|
||||||
size,
|
size,
|
||||||
|
compact: true,
|
||||||
|
transport: true,
|
||||||
|
selectors: true,
|
||||||
}
|
}
|
||||||
}))?)?,
|
}))?)?,
|
||||||
|
|
||||||
TekMode::Sampler => engine.run(&jack.activate_with(|jack|Ok(
|
TekMode::Sampler => engine.run(&jack.activate_with(|jack|Ok(
|
||||||
SamplerTui {
|
SamplerTui {
|
||||||
cursor: (0, 0),
|
cursor: (0, 0),
|
||||||
editing: None,
|
editing: None,
|
||||||
mode: None,
|
mode: None,
|
||||||
note_lo: 36.into(),
|
note_lo: 36.into(),
|
||||||
note_pt: 36.into(),
|
note_pt: 36.into(),
|
||||||
color: ItemPalette::from(Color::Rgb(64, 128, 32)),
|
state: default_sampler(jack)?,
|
||||||
state: Sampler::new(jack, &"sampler", &midi_froms,
|
color,
|
||||||
&[&left_froms, &right_froms], &[&left_tos, &right_tos])?,
|
|
||||||
size,
|
size,
|
||||||
}
|
}
|
||||||
))?)?,
|
))?)?,
|
||||||
|
|
||||||
TekMode::Groovebox => engine.run(&jack.activate_with(|jack|Ok({
|
TekMode::Groovebox => engine.run(&jack.activate_with(|jack|Ok({
|
||||||
let clip = default_clip();
|
let clip = default_clip();
|
||||||
let player = MidiPlayer::new(jack, &"sequencer", Some(&clip), &midi_froms, &midi_tos)?;
|
let mut player = default_player(jack, Some(&clip))?;
|
||||||
let sampler = Sampler::new(jack, &"sampler", &midi_froms,
|
player.clock = default_bpm(player.clock);
|
||||||
&[&left_froms, &right_froms], &[&left_tos, &right_tos])?;
|
let sampler = default_sampler(jack)?;
|
||||||
jack.read().unwrap().client().connect_ports(&player.midi_outs[0].port, &sampler.midi_in.port)?;
|
jack.connect_ports(&player.midi_outs[0].port, &sampler.midi_in.port)?;
|
||||||
let app = Groovebox {
|
let app = Groovebox {
|
||||||
_jack: jack.clone(),
|
_jack: jack.clone(),
|
||||||
|
|
||||||
player,
|
player,
|
||||||
sampler,
|
sampler,
|
||||||
pool: PoolModel::from(&clip),
|
pool: (&clip).into(),
|
||||||
editor: MidiEditor::from(&clip),
|
editor: (&clip).into(),
|
||||||
compact: true,
|
|
||||||
status: true,
|
|
||||||
midi_buf: vec![vec![];65536],
|
midi_buf: vec![vec![];65536],
|
||||||
note_buf: vec![],
|
note_buf: vec![],
|
||||||
perf,
|
perf,
|
||||||
size,
|
size,
|
||||||
|
compact: true,
|
||||||
|
status: true,
|
||||||
};
|
};
|
||||||
if let Some(bpm) = cli.bpm {
|
|
||||||
app.clock().timebase.bpm.set(bpm);
|
|
||||||
}
|
|
||||||
if cli.sync_lead {
|
|
||||||
jack.read().unwrap().client().register_timebase_callback(false, |mut state|{
|
|
||||||
app.clock().playhead.update_from_sample(state.position.frame() as f64);
|
|
||||||
state.position.bbt = Some(app.clock().bbt());
|
|
||||||
state.position
|
|
||||||
})?
|
|
||||||
} 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
|
|
||||||
})?
|
|
||||||
}
|
|
||||||
app
|
app
|
||||||
}))?)?,
|
}))?)?,
|
||||||
|
|
||||||
TekMode::Arranger { scenes, tracks, track_width, .. } =>
|
TekMode::Arranger { scenes, tracks, track_width, .. } =>
|
||||||
engine.run(&jack.activate_with(|jack|Ok({
|
engine.run(&jack.activate_with(|jack|Ok({
|
||||||
let clock = Clock::from(jack);
|
|
||||||
let clip = default_clip();
|
let clip = default_clip();
|
||||||
|
let clock = default_clock(jack);
|
||||||
let mut app = Arranger {
|
let mut app = Arranger {
|
||||||
jack: jack.clone(),
|
jack: jack.clone(),
|
||||||
clock,
|
clock,
|
||||||
|
|
||||||
pool: (&clip).into(),
|
pool: (&clip).into(),
|
||||||
editor: (&clip).into(),
|
editor: (&clip).into(),
|
||||||
selected: ArrangerSelection::Clip(0, 0),
|
selected: ArrangerSelection::Scene(4),
|
||||||
scenes: vec![],
|
scenes: vec![],
|
||||||
tracks: vec![],
|
tracks: vec![],
|
||||||
color: ItemPalette::random(),
|
|
||||||
splits: [12, 20],
|
splits: [12, 20],
|
||||||
midi_buf: vec![vec![];65536],
|
midi_buf: vec![vec![];65536],
|
||||||
note_buf: vec![],
|
note_buf: vec![],
|
||||||
compact: true,
|
compact: true,
|
||||||
|
color,
|
||||||
perf,
|
perf,
|
||||||
size,
|
size,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -43,26 +43,40 @@ impl Arranger {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A cell that is 3-row on its own, but stacks, giving (N+1)*2 rows per N cells.
|
/// A cell that is 3-row on its own, but stacks, giving (N+1)*2 rows per N cells.
|
||||||
fn phat_cell <T: Content<TuiOut>> (color: ItemPalette, last: ItemPalette, field: T) -> impl Content<TuiOut> {
|
fn phat_cell <T: Content<TuiOut>> (
|
||||||
Bsp::s(
|
color: ItemPalette, last: ItemPalette, field: T
|
||||||
Self::phat_lo(color.base.rgb, last.base.rgb),
|
) -> impl Content<TuiOut> {
|
||||||
Bsp::n(
|
Bsp::s(Self::phat_lo(color.base.rgb, last.base.rgb),
|
||||||
Self::phat_hi(color.base.rgb, last.base.rgb),
|
Bsp::n(Self::phat_hi(color.base.rgb, last.base.rgb),
|
||||||
Fixed::y(1, Fill::x(Tui::fg_bg(color.lightest.rgb, color.base.rgb, field))),
|
Fixed::y(1, Fill::x(Tui::fg_bg(color.lightest.rgb, color.base.rgb, field))),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn phat_cell_3 <T: Content<TuiOut>> (top: Color, middle: Color, bottom: Color, field: T) -> impl Content<TuiOut> {
|
fn phat_cell_3 <T: Content<TuiOut>> (
|
||||||
Bsp::s(
|
field: T, top: Color, middle: Color, bottom: Color
|
||||||
Self::phat_lo(middle, top),
|
) -> impl Content<TuiOut> {
|
||||||
Bsp::n(
|
Bsp::s(Self::phat_lo(middle, top),
|
||||||
Self::phat_hi(bottom, middle),
|
Bsp::n(Self::phat_hi(middle, bottom),
|
||||||
Fixed::y(1, Fill::x(Tui::bg(middle, field))),
|
Fixed::y(1, Fill::x(Tui::bg(middle, field))),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn phat_sel_3 <T: Content<TuiOut>> (
|
||||||
|
selected: bool, field_1: T, field_2: T, top: Option<Color>, middle: Color, bottom: Color
|
||||||
|
) -> impl Content<TuiOut> {
|
||||||
|
let border = Style::default().fg(Color::Rgb(255,255,255)).bg(middle);
|
||||||
|
Either(selected,
|
||||||
|
Tui::bg(middle, Outer(border).enclose( Align::w(Bsp::s("", Bsp::s(field_1, ""))))),
|
||||||
|
Bsp::s(
|
||||||
|
Fixed::y(1, top.map(|top|Self::phat_lo(middle, top))),
|
||||||
|
Bsp::n(Self::phat_hi(middle, bottom),
|
||||||
|
Fixed::y(1, Fill::x(Tui::bg(middle, field_2))),
|
||||||
|
)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
fn output_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
fn output_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
||||||
(||Tui::bold(true, Tui::fg_bg(TuiTheme::g(0), TuiTheme::g(200), "[ ] Out 1: NI")).boxed()).into()
|
(||Tui::bold(true, Tui::fg_bg(TuiTheme::g(0), TuiTheme::g(200), "[ ] Out 1: NI")).boxed()).into()
|
||||||
}
|
}
|
||||||
|
|
@ -118,7 +132,7 @@ impl Arranger {
|
||||||
let iter = ||self.tracks_with_widths();
|
let iter = ||self.tracks_with_widths();
|
||||||
(move||Fixed::y(2, Map::new(iter, move|(_, track, x1, x2), i| {
|
(move||Fixed::y(2, Map::new(iter, move|(_, track, x1, x2), i| {
|
||||||
let color = track.color();
|
let color = track.color();
|
||||||
let name = format!(" {}", &track.name);
|
let name = Push::x(1, &track.name);
|
||||||
Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16,
|
Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16,
|
||||||
Tui::fg_bg(color.lightest.rgb, color.base.rgb,
|
Tui::fg_bg(color.lightest.rgb, color.base.rgb,
|
||||||
Self::phat_cell(color, color.darkest.rgb.into(),
|
Self::phat_cell(color, color.darkest.rgb.into(),
|
||||||
|
|
@ -141,15 +155,30 @@ impl Arranger {
|
||||||
(||{
|
(||{
|
||||||
let scenes_w = 16;//.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16);
|
let scenes_w = 16;//.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16);
|
||||||
let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0))));
|
let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0))));
|
||||||
|
let selected_scene = match self.selected {
|
||||||
|
ArrangerSelection::Scene(s) => Some(s),
|
||||||
|
_ => None
|
||||||
|
};
|
||||||
Tui::bg(Color::Rgb(0,0,0), Fill::y(Map::new(
|
Tui::bg(Color::Rgb(0,0,0), Fill::y(Map::new(
|
||||||
||self.scenes_with_heights(2),
|
||self.scenes_with_heights(2),
|
||||||
move|(_, scene, y1, y2), i| {
|
move|(_, scene, y1, y2), i| {
|
||||||
let h = (y2 - y1) as u16;
|
let h = (y2 - y1) as u16;
|
||||||
|
let name = format!("🭬{}", &scene.name);
|
||||||
let color = scene.color();
|
let color = scene.color();
|
||||||
let name = format!("🭬{}", &scene.name);
|
let cell = Self::phat_sel_3(
|
||||||
let cell = Self::phat_cell(color, *last_color.read().unwrap(), name);
|
selected_scene == Some(i),
|
||||||
|
Push::x(1, name.clone()),
|
||||||
|
Push::x(1, name),
|
||||||
|
if selected_scene.map(|s|s + 1) == Some(i) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(last_color.read().unwrap().base.rgb)
|
||||||
|
},
|
||||||
|
color.base.rgb,
|
||||||
|
Color::Rgb(0, 0, 0)
|
||||||
|
);
|
||||||
*last_color.write().unwrap() = color;
|
*last_color.write().unwrap() = color;
|
||||||
map_south(y1 as u16, 2, Fill::x(cell))
|
map_south(y1 as u16, 3, Fill::x(cell))
|
||||||
}
|
}
|
||||||
))).boxed()
|
))).boxed()
|
||||||
}).into()
|
}).into()
|
||||||
|
|
@ -160,6 +189,10 @@ impl Arranger {
|
||||||
let cell = Bsp::s("[Rec]", "[Mon]");
|
let cell = Bsp::s("[Rec]", "[Mon]");
|
||||||
let color: ItemPalette = track.color().dark.into();
|
let color: ItemPalette = track.color().dark.into();
|
||||||
let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0))));
|
let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0))));
|
||||||
|
let (selected_track, selected_scene) = match self.selected {
|
||||||
|
ArrangerSelection::Clip(t, s) => (Some(t), Some(s)),
|
||||||
|
_ => (None, None)
|
||||||
|
};
|
||||||
map_east(x1 as u16, w, Fixed::x(w, Tui::bg(Color::Rgb(0,0,0), Fill::y(Map::new(
|
map_east(x1 as u16, w, Fixed::x(w, Tui::bg(Color::Rgb(0,0,0), Fill::y(Map::new(
|
||||||
||self.scenes_with_heights(2),
|
||self.scenes_with_heights(2),
|
||||||
move|(_, scene, y1, y2), i| {
|
move|(_, scene, y1, y2), i| {
|
||||||
|
|
@ -168,11 +201,11 @@ impl Arranger {
|
||||||
let name = format!("🭬{}", &scene.name);
|
let name = format!("🭬{}", &scene.name);
|
||||||
//*last_color.write().unwrap() = color
|
//*last_color.write().unwrap() = color
|
||||||
map_south(y1 as u16, 2, Fill::x(Self::phat_cell_3(
|
map_south(y1 as u16, 2, Fill::x(Self::phat_cell_3(
|
||||||
|
Tui::fg(TuiTheme::g(64), " ⏹ "),
|
||||||
TuiTheme::g(32).into(),
|
TuiTheme::g(32).into(),
|
||||||
TuiTheme::g(32).into(),
|
TuiTheme::g(32).into(),
|
||||||
TuiTheme::g(32).into(),
|
TuiTheme::g(32).into(),
|
||||||
//Tui::fg(TuiTheme::g(64), " ⏺ ")
|
//Tui::fg(TuiTheme::g(64), " ⏺ ")
|
||||||
Tui::fg(TuiTheme::g(64), " ⏹ ")
|
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
))).boxed()
|
))).boxed()
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ pub struct Sequencer {
|
||||||
pub selectors: bool,
|
pub selectors: bool,
|
||||||
pub compact: bool,
|
pub compact: bool,
|
||||||
|
|
||||||
pub clock: Clock,
|
|
||||||
pub size: Measure<TuiOut>,
|
pub size: Measure<TuiOut>,
|
||||||
pub status: bool,
|
pub status: bool,
|
||||||
pub note_buf: Vec<u8>,
|
pub note_buf: Vec<u8>,
|
||||||
|
|
@ -95,7 +94,7 @@ audio!(|self:Sequencer, client, scope|{
|
||||||
Control::Continue
|
Control::Continue
|
||||||
});
|
});
|
||||||
has_size!(<TuiOut>|self:Sequencer|&self.size);
|
has_size!(<TuiOut>|self:Sequencer|&self.size);
|
||||||
has_clock!(|self:Sequencer|&self.clock);
|
has_clock!(|self:Sequencer|&self.player.clock);
|
||||||
has_phrases!(|self:Sequencer|self.pool.phrases);
|
has_phrases!(|self:Sequencer|self.pool.phrases);
|
||||||
has_editor!(|self:Sequencer|self.editor);
|
has_editor!(|self:Sequencer|self.editor);
|
||||||
handle!(TuiIn: |self:Sequencer,input|SequencerCommand::execute_with_state(self, input.event()));
|
handle!(TuiIn: |self:Sequencer,input|SequencerCommand::execute_with_state(self, input.event()));
|
||||||
|
|
@ -185,13 +184,13 @@ pub struct SequencerStatus {
|
||||||
pub(crate) playing: bool,
|
pub(crate) playing: bool,
|
||||||
}
|
}
|
||||||
from!(|state:&Sequencer|SequencerStatus = {
|
from!(|state:&Sequencer|SequencerStatus = {
|
||||||
let samples = state.clock.chunk.load(Relaxed);
|
let samples = state.clock().chunk.load(Relaxed);
|
||||||
let rate = state.clock.timebase.sr.get();
|
let rate = state.clock().timebase.sr.get();
|
||||||
let buffer = samples as f64 / rate;
|
let buffer = samples as f64 / rate;
|
||||||
let width = state.size.w();
|
let width = state.size.w();
|
||||||
Self {
|
Self {
|
||||||
width,
|
width,
|
||||||
playing: state.clock.is_rolling(),
|
playing: state.clock().is_rolling(),
|
||||||
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%").into()),
|
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%").into()),
|
||||||
size: format!("{}x{}│", width, state.size.h()).into(),
|
size: format!("{}x{}│", width, state.size.h()).into(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue