mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
wip: unify apps
This commit is contained in:
parent
a9cad18891
commit
cff87657b9
12 changed files with 291 additions and 247 deletions
110
cli/tek.rs
110
cli/tek.rs
|
|
@ -76,6 +76,8 @@ pub fn main () -> Usually<()> {
|
|||
let left_tos = PortConnection::collect(&cli.left_to, empty, empty);
|
||||
let right_froms = PortConnection::collect(&cli.right_from, empty, empty);
|
||||
let right_tos = PortConnection::collect(&cli.right_to, empty, empty);
|
||||
let audio_froms = &[&left_froms, &right_froms];
|
||||
let audio_tos = &[&left_tos, &right_tos ];
|
||||
let perf = PerfModel::default();
|
||||
let size = Measure::new();
|
||||
let default_clip = ||Arc::new(RwLock::new(MidiClip::new(
|
||||
|
|
@ -116,25 +118,6 @@ pub fn main () -> Usually<()> {
|
|||
TekMode::Clock => engine.run(&jack.activate_with(|jack|Ok(TransportTui {
|
||||
jack: jack.clone(), clock: default_clock(jack),
|
||||
}))?)?,
|
||||
TekMode::Sequencer => engine.run(&jack.activate_with(|jack|Ok({
|
||||
let clip = default_clip();
|
||||
let mut player = default_player(jack, Some(&clip))?;
|
||||
player.clock = default_bpm(player.clock);
|
||||
Sequencer {
|
||||
_jack: jack.clone(),
|
||||
player,
|
||||
pool: (&clip).into(),
|
||||
editor: (&clip).into(),
|
||||
midi_buf: vec![vec![];65536],
|
||||
note_buf: vec![],
|
||||
status: true,
|
||||
perf,
|
||||
size,
|
||||
compact: true,
|
||||
transport: true,
|
||||
selectors: true,
|
||||
}
|
||||
}))?)?,
|
||||
TekMode::Sampler => engine.run(&jack.activate_with(|jack|Ok(
|
||||
SamplerTui {
|
||||
cursor: (0, 0),
|
||||
|
|
@ -147,53 +130,62 @@ pub fn main () -> Usually<()> {
|
|||
size,
|
||||
}
|
||||
))?)?,
|
||||
TekMode::Sequencer => engine.run(&jack.activate_with(|jack|Ok({
|
||||
let clip = default_clip();
|
||||
let mut player = default_player(jack, Some(&clip))?;
|
||||
player.clock = default_bpm(player.clock);
|
||||
App::sequencer(
|
||||
jack, (&clip).into(), (&clip).into(),
|
||||
Some(player), &midi_froms, &midi_tos,
|
||||
)
|
||||
}))?)?,
|
||||
TekMode::Groovebox => engine.run(&jack.activate_with(|jack|Ok({
|
||||
let clip = default_clip();
|
||||
let mut player = default_player(jack, Some(&clip))?;
|
||||
player.clock = default_bpm(player.clock);
|
||||
let sampler = default_sampler(jack)?;
|
||||
jack.connect_ports(&player.midi_outs[0].port, &sampler.midi_in.port)?;
|
||||
let app = Groovebox {
|
||||
_jack: jack.clone(),
|
||||
player,
|
||||
sampler,
|
||||
pool: (&clip).into(),
|
||||
editor: (&clip).into(),
|
||||
midi_buf: vec![vec![];65536],
|
||||
note_buf: vec![],
|
||||
perf,
|
||||
size,
|
||||
compact: true,
|
||||
status: true,
|
||||
};
|
||||
app
|
||||
App::groovebox(
|
||||
jack, (&clip).into(), (&clip).into(),
|
||||
Some(player), &midi_froms, &midi_tos,
|
||||
sampler, &audio_froms, &audio_tos,
|
||||
)
|
||||
}))?)?,
|
||||
TekMode::Arranger { scenes, tracks, track_width, .. } =>
|
||||
engine.run(&jack.activate_with(|jack|Ok({
|
||||
let clock = default_clock(jack);
|
||||
let mut app = Arranger {
|
||||
jack: jack.clone(),
|
||||
midi_ins: vec![JackPort::<MidiIn>::new(jack, format!("M/{name}"), &midi_froms)?,],
|
||||
midi_outs: vec![JackPort::<MidiOut>::new(jack, format!("{name}/M"), &midi_tos)?, ],
|
||||
clock,
|
||||
pool: PoolModel::default(),//(&clip).into(),
|
||||
editor: MidiEditor::default(),//(&clip).into(),
|
||||
selected: ArrangerSelection::Clip(0, 0),
|
||||
scenes: vec![],
|
||||
tracks: vec![],
|
||||
splits: [12, 20],
|
||||
midi_buf: vec![vec![];65536],
|
||||
note_buf: vec![],
|
||||
compact: false,
|
||||
editing: true.into(),
|
||||
color,
|
||||
perf,
|
||||
size,
|
||||
};
|
||||
app.tracks_add(tracks, track_width, &midi_froms, &midi_tos)?;
|
||||
app.scenes_add(scenes)?;
|
||||
app
|
||||
}))?)?,
|
||||
TekMode::Arranger { scenes, tracks, track_width, .. } => engine.run(&jack.activate_with(|jack|Ok({
|
||||
App::arranger(
|
||||
jack,
|
||||
PoolModel::default(),
|
||||
MidiEditor::default(), &midi_froms, &midi_tos,
|
||||
default_sampler(jack)?, &audio_froms, &audio_tos,
|
||||
scenes, tracks, track_width
|
||||
)
|
||||
}))?)?,
|
||||
|
||||
|
||||
//let clock = default_clock(jack);
|
||||
//let mut app = Arranger {
|
||||
//jack: jack.clone(),
|
||||
//midi_ins: vec![JackPort::<MidiIn>::new(jack, format!("M/{name}"), &midi_froms)?,],
|
||||
//midi_outs: vec![JackPort::<MidiOut>::new(jack, format!("{name}/M"), &midi_tos)?, ],
|
||||
//clock,
|
||||
//pool: PoolModel::default(),//(&clip).into(),
|
||||
//editor: MidiEditor::default(),//(&clip).into(),
|
||||
//selected: ArrangerSelection::Clip(0, 0),
|
||||
//scenes: vec![],
|
||||
//tracks: vec![],
|
||||
//splits: [12, 20],
|
||||
//midi_buf: vec![vec![];65536],
|
||||
//note_buf: vec![],
|
||||
//compact: false,
|
||||
//editing: true.into(),
|
||||
//color,
|
||||
//perf,
|
||||
//size,
|
||||
//};
|
||||
//app.tracks_add(tracks, track_width, &midi_froms, &midi_tos)?;
|
||||
//app.scenes_add(scenes)?;
|
||||
//app
|
||||
//}))?)?,
|
||||
|
||||
_ => todo!()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@ pub type DynamicAsyncClient = AsyncClient<DynamicNotifications, DynamicAudioH
|
|||
/// This is a connection which may be `Inactive`, `Activating`, or `Active`.
|
||||
/// In the `Active` and `Inactive` states, its `client` method returns a
|
||||
/// [Client] which you can use to talk to the JACK API.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub enum JackConnection {
|
||||
#[default]
|
||||
Inert,
|
||||
/// Before activation.
|
||||
Inactive(Client),
|
||||
/// During activation.
|
||||
|
|
@ -35,6 +37,7 @@ impl From<JackConnection> for Client {
|
|||
fn from (jack: JackConnection) -> Self {
|
||||
match jack {
|
||||
JackConnection::Inactive(client) => client,
|
||||
JackConnection::Inert => panic!("jack client not activated"),
|
||||
JackConnection::Activating => panic!("jack client still activating"),
|
||||
JackConnection::Active(_) => panic!("jack client already activated"),
|
||||
}
|
||||
|
|
@ -49,6 +52,7 @@ impl JackConnection {
|
|||
/// Return the internal [Client] handle that lets you call the JACK API.
|
||||
pub fn client (&self) -> &Client {
|
||||
match self {
|
||||
Self::Inert => panic!("jack client not activated"),
|
||||
Self::Inactive(ref client) => client,
|
||||
Self::Activating => panic!("jack client has not finished activation"),
|
||||
Self::Active(ref client) => client.as_client(),
|
||||
|
|
|
|||
|
|
@ -25,7 +25,10 @@ pub trait EdnViewData<E: Output> {
|
|||
}
|
||||
|
||||
/// Renders from EDN source and context.
|
||||
#[derive(Default)]
|
||||
pub enum EdnView<E: Output, T: EdnViewData<E>> {
|
||||
#[default]
|
||||
Inert,
|
||||
_Unused(PhantomData<E>),
|
||||
Ok(T, EdnItem<String>),
|
||||
//render: Box<dyn Fn(&'a T)->Box<dyn Render<E> + Send + Sync + 'a> + Send + Sync + 'a>
|
||||
|
|
|
|||
|
|
@ -77,38 +77,33 @@ impl NotePoint for SamplerTui {
|
|||
fn note_point (&self) -> usize { self.note_pt.load(Relaxed) }
|
||||
fn set_note_point (&self, x: usize) { self.note_pt.store(x, Relaxed); }
|
||||
}
|
||||
pub struct SampleList<'a> {
|
||||
compact: bool,
|
||||
sampler: &'a Sampler,
|
||||
editor: &'a MidiEditor
|
||||
}
|
||||
impl<'a> SampleList<'a> {
|
||||
pub fn new (compact: bool, sampler: &'a Sampler, editor: &'a MidiEditor) -> Self {
|
||||
Self { compact, sampler, editor }
|
||||
}
|
||||
}
|
||||
render!(TuiOut: (self: SampleList<'a>) => {
|
||||
let Self { compact, sampler, editor } = self;
|
||||
let note_lo = editor.note_lo().load(Relaxed);
|
||||
let note_pt = editor.note_point();
|
||||
let note_hi = editor.note_hi();
|
||||
Outer(Style::default().fg(TuiTheme::g(96))).enclose(Map::new(move||(note_lo..=note_hi).rev(), move|note, i| {
|
||||
let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a))));
|
||||
let mut bg = if note == note_pt { TuiTheme::g(64) } else { Color::Reset };
|
||||
let mut fg = TuiTheme::g(160);
|
||||
if sampler.mapped[note].is_some() {
|
||||
fg = TuiTheme::g(224);
|
||||
bg = Color::Rgb(0, if note == note_pt { 96 } else { 64 }, 0);
|
||||
}
|
||||
if let Some((index, _)) = sampler.recording {
|
||||
if note == index {
|
||||
bg = if note == note_pt { Color::Rgb(96,24,0) } else { Color::Rgb(64,16,0) };
|
||||
fg = Color::Rgb(224,64,32)
|
||||
impl Sampler {
|
||||
const EMPTY: &[(f64, f64)] = &[(0., 0.), (1., 1.), (2., 2.), (0., 2.), (2., 0.)];
|
||||
pub fn list <'a> (&'a self, compact: bool, editor: &MidiEditor) -> impl Content<TuiOut> + 'a {
|
||||
let note_lo = editor.note_lo().load(Relaxed);
|
||||
let note_pt = editor.note_point();
|
||||
let note_hi = editor.note_hi();
|
||||
Outer(Style::default().fg(TuiTheme::g(96))).enclose(Map::new(move||(note_lo..=note_hi).rev(), move|note, i| {
|
||||
let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a))));
|
||||
let mut bg = if note == note_pt { TuiTheme::g(64) } else { Color::Reset };
|
||||
let mut fg = TuiTheme::g(160);
|
||||
if self.mapped[note].is_some() {
|
||||
fg = TuiTheme::g(224);
|
||||
bg = Color::Rgb(0, if note == note_pt { 96 } else { 64 }, 0);
|
||||
}
|
||||
}
|
||||
let label = if *compact {
|
||||
if let Some((index, _)) = self.recording {
|
||||
if note == index {
|
||||
bg = if note == note_pt { Color::Rgb(96,24,0) } else { Color::Rgb(64,16,0) };
|
||||
fg = Color::Rgb(224,64,32)
|
||||
}
|
||||
}
|
||||
offset(Tui::fg_bg(fg, bg, format!("{note:3} {}", self.list_item(note, compact))))
|
||||
}))
|
||||
}
|
||||
pub fn list_item (&self, note: usize, compact: bool) -> String {
|
||||
if compact {
|
||||
String::default()
|
||||
} else if let Some(sample) = &sampler.mapped[note] {
|
||||
} else if let Some(sample) = &self.mapped[note] {
|
||||
let sample = sample.read().unwrap();
|
||||
format!("{:8} {:3} {:6}-{:6}/{:6}",
|
||||
sample.name,
|
||||
|
|
@ -119,12 +114,8 @@ render!(TuiOut: (self: SampleList<'a>) => {
|
|||
)
|
||||
} else {
|
||||
String::from("(none)")
|
||||
};
|
||||
offset(Tui::fg_bg(fg, bg, format!("{note:3} {}", label)))
|
||||
}))
|
||||
});
|
||||
impl Sampler {
|
||||
const EMPTY: &[(f64, f64)] = &[(0., 0.), (1., 1.), (2., 2.), (0., 2.), (2., 0.)];
|
||||
}
|
||||
}
|
||||
pub fn viewer (&self, note_pt: usize) -> impl Content<TuiOut> {
|
||||
let sample = if let Some((_, sample)) = &self.recording {
|
||||
Some(sample.clone())
|
||||
|
|
|
|||
157
tek/src/app.rs
Normal file
157
tek/src/app.rs
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
use crate::*;
|
||||
#[derive(Default)]
|
||||
pub struct App {
|
||||
//jack: Arc<RwLock<JackConnection>>,
|
||||
//view: EdnView<TuiOut, &'a Self>,
|
||||
//pool: Option<PoolModel>,
|
||||
//editor: Option<MidiEditor>,
|
||||
//player: Option<MidiPlayer>,
|
||||
//compact: AtomicBool,
|
||||
//size: Measure<TuiOut>,
|
||||
//perf: PerfModel,
|
||||
//note_buf: Vec<u8>,
|
||||
//midi_buf: Vec<Vec<Vec<u8>>>
|
||||
|
||||
pub jack: Arc<RwLock<JackConnection>>,
|
||||
pub edn: String,
|
||||
pub clock: Clock,
|
||||
pub color: ItemPalette,
|
||||
pub editing: AtomicBool,
|
||||
pub pool: Option<PoolModel>,
|
||||
pub editor: Option<MidiEditor>,
|
||||
pub player: Option<MidiPlayer>,
|
||||
pub sampler: Option<Sampler>,
|
||||
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
||||
pub midi_ins: Vec<JackPort<MidiIn>>,
|
||||
pub midi_outs: Vec<JackPort<MidiOut>>,
|
||||
pub audio_ins: Vec<JackPort<AudioIn>>,
|
||||
pub audio_outs: Vec<JackPort<AudioOut>>,
|
||||
pub note_buf: Vec<u8>,
|
||||
pub tracks: Vec<ArrangerTrack>,
|
||||
pub scenes: Vec<ArrangerScene>,
|
||||
pub selected: ArrangerSelection,
|
||||
pub splits: Vec<u16>,
|
||||
pub size: Measure<TuiOut>,
|
||||
pub perf: PerfModel,
|
||||
}
|
||||
impl EdnViewData<TuiOut> for &App {}
|
||||
impl App {
|
||||
pub fn sequencer (
|
||||
jack: &Arc<RwLock<JackConnection>>,
|
||||
pool: PoolModel,
|
||||
editor: MidiEditor,
|
||||
player: Option<MidiPlayer>,
|
||||
midi_froms: &[PortConnection],
|
||||
midi_tos: &[PortConnection],
|
||||
) -> Self {
|
||||
Self {
|
||||
edn: include_str!("sequencer.edn").to_string(),
|
||||
jack: jack.clone(),
|
||||
pool: Some(pool),
|
||||
editor: Some(editor),
|
||||
player: player,
|
||||
editing: false.into(),
|
||||
midi_buf: vec![vec![];65536],
|
||||
color: ItemPalette::random(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
pub fn groovebox (
|
||||
jack: &Arc<RwLock<JackConnection>>,
|
||||
pool: PoolModel,
|
||||
editor: MidiEditor,
|
||||
player: Option<MidiPlayer>,
|
||||
midi_froms: &[PortConnection],
|
||||
midi_tos: &[PortConnection],
|
||||
sampler: Sampler,
|
||||
audio_froms: &[&[PortConnection]],
|
||||
audio_tos: &[&[PortConnection]],
|
||||
) -> Self {
|
||||
Self {
|
||||
edn: include_str!("groovebox.edn").to_string(),
|
||||
sampler: Some(sampler),
|
||||
..Self::sequencer(
|
||||
jack, pool, editor,
|
||||
player, midi_froms, midi_tos
|
||||
)
|
||||
}
|
||||
}
|
||||
pub fn arranger (
|
||||
jack: &Arc<RwLock<JackConnection>>,
|
||||
pool: PoolModel,
|
||||
editor: MidiEditor,
|
||||
midi_froms: &[PortConnection],
|
||||
midi_tos: &[PortConnection],
|
||||
sampler: Sampler,
|
||||
audio_froms: &[&[PortConnection]],
|
||||
audio_tos: &[&[PortConnection]],
|
||||
scenes: usize,
|
||||
tracks: usize,
|
||||
track_width: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
edn: include_str!("arranger.edn").to_string(),
|
||||
..Self::groovebox(
|
||||
jack, pool, editor,
|
||||
None, midi_froms, midi_tos,
|
||||
sampler, audio_froms, audio_tos
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
render!(TuiOut: (self: App) => self.size.of(EdnView::from_source(self, self.edn.as_ref())));
|
||||
|
||||
/// Root view for standalone `tek_sequencer`.
|
||||
pub struct Sequencer {
|
||||
pub _jack: Arc<RwLock<JackConnection>>,
|
||||
|
||||
pub pool: PoolModel,
|
||||
pub editor: MidiEditor,
|
||||
pub player: MidiPlayer,
|
||||
|
||||
pub transport: bool,
|
||||
pub selectors: bool,
|
||||
pub compact: bool,
|
||||
|
||||
pub size: Measure<TuiOut>,
|
||||
pub status: bool,
|
||||
pub note_buf: Vec<u8>,
|
||||
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
||||
pub perf: PerfModel,
|
||||
}
|
||||
|
||||
pub struct Groovebox {
|
||||
pub _jack: Arc<RwLock<JackConnection>>,
|
||||
pub player: MidiPlayer,
|
||||
pub pool: PoolModel,
|
||||
pub editor: MidiEditor,
|
||||
pub sampler: Sampler,
|
||||
|
||||
pub compact: bool,
|
||||
pub size: Measure<TuiOut>,
|
||||
pub status: bool,
|
||||
pub note_buf: Vec<u8>,
|
||||
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
||||
pub perf: PerfModel,
|
||||
}
|
||||
|
||||
/// Root view for standalone `tek_arranger`
|
||||
pub struct Arranger {
|
||||
pub jack: Arc<RwLock<JackConnection>>,
|
||||
pub midi_ins: Vec<JackPort<MidiIn>>,
|
||||
pub midi_outs: Vec<JackPort<MidiOut>>,
|
||||
pub clock: Clock,
|
||||
pub pool: PoolModel,
|
||||
pub tracks: Vec<ArrangerTrack>,
|
||||
pub scenes: Vec<ArrangerScene>,
|
||||
pub splits: [u16;2],
|
||||
pub selected: ArrangerSelection,
|
||||
pub color: ItemPalette,
|
||||
pub size: Measure<TuiOut>,
|
||||
pub note_buf: Vec<u8>,
|
||||
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
||||
pub editor: MidiEditor,
|
||||
pub editing: AtomicBool,
|
||||
pub perf: PerfModel,
|
||||
pub compact: bool,
|
||||
}
|
||||
|
|
@ -5,26 +5,6 @@ mod arranger_track; pub use self::arranger_track::*;
|
|||
mod arranger_h;
|
||||
use ClockCommand::{Play, Pause};
|
||||
use self::ArrangerCommand as Cmd;
|
||||
/// Root view for standalone `tek_arranger`
|
||||
pub struct Arranger {
|
||||
pub jack: Arc<RwLock<JackConnection>>,
|
||||
pub midi_ins: Vec<JackPort<MidiIn>>,
|
||||
pub midi_outs: Vec<JackPort<MidiOut>>,
|
||||
pub clock: Clock,
|
||||
pub pool: PoolModel,
|
||||
pub tracks: Vec<ArrangerTrack>,
|
||||
pub scenes: Vec<ArrangerScene>,
|
||||
pub splits: [u16;2],
|
||||
pub selected: ArrangerSelection,
|
||||
pub color: ItemPalette,
|
||||
pub size: Measure<TuiOut>,
|
||||
pub note_buf: Vec<u8>,
|
||||
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
||||
pub editor: MidiEditor,
|
||||
pub editing: AtomicBool,
|
||||
pub perf: PerfModel,
|
||||
pub compact: bool,
|
||||
}
|
||||
render!(TuiOut: (self: Arranger) => self.size.of(EdnView::from_source(self, Self::EDN)));
|
||||
impl EdnViewData<TuiOut> for &Arranger {
|
||||
fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use crate::*;
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
#[derive(PartialEq, Clone, Copy, Debug, Default)]
|
||||
/// Represents the current user selection in the arranger
|
||||
pub enum ArrangerSelection {
|
||||
/// The whole mix is selected
|
||||
Mix,
|
||||
#[default] Mix,
|
||||
/// A track is selected.
|
||||
Track(usize),
|
||||
/// A scene is selected.
|
||||
|
|
|
|||
|
|
@ -7,28 +7,7 @@ use MidiEditCommand::*;
|
|||
use MidiPoolCommand::*;
|
||||
use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right};
|
||||
use std::marker::ConstParamTy;
|
||||
pub struct Groovebox {
|
||||
pub _jack: Arc<RwLock<JackConnection>>,
|
||||
pub player: MidiPlayer,
|
||||
pub pool: PoolModel,
|
||||
pub editor: MidiEditor,
|
||||
pub sampler: Sampler,
|
||||
|
||||
pub compact: bool,
|
||||
pub size: Measure<TuiOut>,
|
||||
pub status: bool,
|
||||
pub note_buf: Vec<u8>,
|
||||
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
||||
pub perf: PerfModel,
|
||||
}
|
||||
render!(TuiOut: (self: Groovebox) => self.size.of(EdnView::from_source(self, Self::EDN)));
|
||||
// this works:
|
||||
//render!(TuiOut: (self: Groovebox) => self.size.of(
|
||||
//Bsp::s(self.toolbar_view(),
|
||||
//Bsp::n(self.selector_view(),
|
||||
//Bsp::n(self.sample_view(),
|
||||
//Bsp::n(self.status_view(),
|
||||
//Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor)))))))));
|
||||
impl EdnViewData<TuiOut> for &Groovebox {
|
||||
fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> {
|
||||
use EdnItem::*;
|
||||
|
|
@ -36,11 +15,11 @@ impl EdnViewData<TuiOut> for &Groovebox {
|
|||
Nil => Box::new(()),
|
||||
Exp(items) => Box::new(EdnView::from_items(*self, items.as_slice())),
|
||||
Sym(":editor") => (&self.editor).boxed(),
|
||||
Sym(":pool") => self.pool_view().boxed(),
|
||||
Sym(":status") => self.status_view().boxed(),
|
||||
Sym(":toolbar") => self.toolbar_view().boxed(),
|
||||
Sym(":sampler") => self.sampler_view().boxed(),
|
||||
Sym(":sample") => self.sample_view().boxed(),
|
||||
Sym(":pool") => self.pool().boxed(),
|
||||
Sym(":status") => self.status().boxed(),
|
||||
Sym(":toolbar") => self.toolbar().boxed(),
|
||||
Sym(":sampler") => self.sampler().boxed(),
|
||||
Sym(":sample") => self.sample().boxed(),
|
||||
_ => panic!("no content for {item:?}")
|
||||
}
|
||||
}
|
||||
|
|
@ -60,14 +39,14 @@ impl EdnViewData<TuiOut> for &Groovebox {
|
|||
}
|
||||
impl Groovebox {
|
||||
const EDN: &'static str = include_str!("groovebox.edn");
|
||||
fn toolbar_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
fn toolbar (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
Fill::x(Fixed::y(2, lay!(
|
||||
Fill::x(Align::w(Meter("L/", self.sampler.input_meter[0]))),
|
||||
Fill::x(Align::e(Meter("R/", self.sampler.input_meter[1]))),
|
||||
Align::x(TransportView::new(true, &self.player.clock)),
|
||||
)))
|
||||
}
|
||||
fn status_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
fn status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
row!(
|
||||
self.player.play_status(),
|
||||
self.player.next_status(),
|
||||
|
|
@ -75,7 +54,7 @@ impl Groovebox {
|
|||
self.editor.edit_status(),
|
||||
)
|
||||
}
|
||||
fn sample_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
fn sample (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
let note_pt = self.editor.note_point();
|
||||
let sample_h = if self.compact { 0 } else { 5 };
|
||||
Max::y(sample_h, Fill::xy(
|
||||
|
|
@ -83,21 +62,19 @@ impl Groovebox {
|
|||
Fill::x(Align::w(Fixed::y(1, self.sampler.status(note_pt)))),
|
||||
self.sampler.viewer(note_pt))))
|
||||
}
|
||||
fn pool_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
fn pool (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
let w = self.size.w();
|
||||
let pool_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
|
||||
Fixed::x(if self.compact { 5 } else { pool_w },
|
||||
PoolView(self.compact, &self.pool))
|
||||
}
|
||||
fn sampler_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
fn sampler (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
let note_pt = self.editor.note_point();
|
||||
let sampler_w = if self.compact { 4 } else { 40 };
|
||||
let sampler_y = if self.compact { 1 } else { 0 };
|
||||
Fixed::x(sampler_w, Push::y(sampler_y, Fill::y(
|
||||
SampleList::new(self.compact, &self.sampler, &self.editor))))
|
||||
Fixed::x(sampler_w, Push::y(sampler_y, Fill::y(self.sampler.list(self.compact, &self.editor))))
|
||||
}
|
||||
}
|
||||
|
||||
audio!(|self: Groovebox, client, scope|{
|
||||
let t0 = self.perf.get_t0();
|
||||
if Control::Quit == ClockAudio(&mut self.player).process(client, scope) {
|
||||
|
|
@ -130,7 +107,6 @@ audio!(|self: Groovebox, client, scope|{
|
|||
self.perf.update(t0, scope);
|
||||
Control::Continue
|
||||
});
|
||||
|
||||
has_clock!(|self: Groovebox|self.player.clock());
|
||||
pub enum GrooveboxCommand {
|
||||
Compact(bool),
|
||||
|
|
@ -141,7 +117,6 @@ pub enum GrooveboxCommand {
|
|||
Enqueue(Option<Arc<RwLock<MidiClip>>>),
|
||||
Sampler(SamplerCommand),
|
||||
}
|
||||
|
||||
command!(|self: GrooveboxCommand, state: Groovebox|match self {
|
||||
Self::Enqueue(clip) => {
|
||||
state.player.enqueue_next(clip.as_ref());
|
||||
|
|
@ -173,10 +148,7 @@ command!(|self: GrooveboxCommand, state: Groovebox|match self {
|
|||
None
|
||||
},
|
||||
});
|
||||
|
||||
handle!(TuiIn: |self: Groovebox, input|
|
||||
GrooveboxCommand::execute_with_state(self, input.event()));
|
||||
|
||||
handle!(TuiIn: |self: Groovebox, input|GrooveboxCommand::execute_with_state(self, input.event()));
|
||||
keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand {
|
||||
// Tab: Toggle compact mode
|
||||
key(Tab) => Cmd::Compact(!state.compact),
|
||||
|
|
@ -268,21 +240,6 @@ keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand
|
|||
//row!(&self.cpu, &self.size)
|
||||
//}
|
||||
//}
|
||||
//render!(TuiOut: (self: Groovebox) => self.size.of(
|
||||
//Bsp::s(self.toolbar_view(),
|
||||
//Bsp::n(self.selector_view(),
|
||||
//Bsp::n(self.sample_view(),
|
||||
//Bsp::n(self.status_view(),
|
||||
//Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor)))))))));
|
||||
|
||||
//const GROOVEBOX_EDN: &'static str = include_str!("groovebox.edn");
|
||||
|
||||
//impl Content<TuiOut> for Groovebox {
|
||||
//fn content (&self) -> impl Content<TuiOut> {
|
||||
//EdnView::parse(self.edn.as_slice())
|
||||
//}
|
||||
//}
|
||||
|
||||
//macro_rules! edn_context {
|
||||
//($Struct:ident |$l:lifetime, $state:ident| {
|
||||
//$($key:literal = $field:ident: $Type:ty => $expr:expr,)*
|
||||
|
|
@ -328,17 +285,17 @@ keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand
|
|||
//}
|
||||
|
||||
////impl Groovebox {
|
||||
////fn status_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
////fn status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
////let note_pt = self.editor.note_point();
|
||||
////Align::w(Fixed::y(1, ))
|
||||
////}
|
||||
////fn pool_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
////fn pool (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
////let w = self.size.w();
|
||||
////let pool_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
|
||||
////Fixed::x(if self.compact { 5 } else { pool_w },
|
||||
////)
|
||||
////}
|
||||
////fn sampler_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
////fn sampler (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
////let sampler_w = if self.compact { 4 } else { 11 };
|
||||
////let sampler_y = if self.compact { 1 } else { 0 };
|
||||
////Fixed::x(sampler_w, Push::y(sampler_y, Fill::y(
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ pub type Usually<T> = std::result::Result<T, Box<dyn Error>>;
|
|||
/// Standard optional result type.
|
||||
pub type Perhaps<T> = std::result::Result<Option<T>, Box<dyn Error>>;
|
||||
|
||||
pub mod app; pub use self::app::*;
|
||||
pub mod arranger; pub use self::arranger::*;
|
||||
pub mod groovebox; pub use self::groovebox::*;
|
||||
pub mod mixer; pub use self::mixer::*;
|
||||
|
|
|
|||
|
|
@ -4,24 +4,6 @@ use KeyCode::{Tab, Char};
|
|||
use SequencerCommand as Cmd;
|
||||
use MidiEditCommand::*;
|
||||
use MidiPoolCommand::*;
|
||||
/// Root view for standalone `tek_sequencer`.
|
||||
pub struct Sequencer {
|
||||
pub _jack: Arc<RwLock<JackConnection>>,
|
||||
|
||||
pub pool: PoolModel,
|
||||
pub editor: MidiEditor,
|
||||
pub player: MidiPlayer,
|
||||
|
||||
pub transport: bool,
|
||||
pub selectors: bool,
|
||||
pub compact: bool,
|
||||
|
||||
pub size: Measure<TuiOut>,
|
||||
pub status: bool,
|
||||
pub note_buf: Vec<u8>,
|
||||
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
||||
pub perf: PerfModel,
|
||||
}
|
||||
render!(TuiOut: (self: Sequencer) => self.size.of(EdnView::from_source(self, Self::EDN)));
|
||||
impl EdnViewData<TuiOut> for &Sequencer {
|
||||
fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> {
|
||||
|
|
@ -179,34 +161,3 @@ command!(|self: SequencerCommand, state: Sequencer|match self {
|
|||
None
|
||||
},
|
||||
});
|
||||
|
||||
/// Status bar for sequencer app
|
||||
#[derive(Clone)]
|
||||
pub struct SequencerStatus {
|
||||
pub(crate) width: usize,
|
||||
pub(crate) cpu: Option<Arc<str>>,
|
||||
pub(crate) size: Arc<str>,
|
||||
pub(crate) playing: bool,
|
||||
}
|
||||
from!(|state:&Sequencer|SequencerStatus = {
|
||||
let samples = state.clock().chunk.load(Relaxed);
|
||||
let rate = state.clock().timebase.sr.get();
|
||||
let buffer = samples as f64 / rate;
|
||||
let width = state.size.w();
|
||||
Self {
|
||||
width,
|
||||
playing: state.clock().is_rolling(),
|
||||
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%").into()),
|
||||
size: format!("{}x{}│", width, state.size.h()).into(),
|
||||
}
|
||||
});
|
||||
render!(TuiOut: (self: SequencerStatus) => Fixed::y(2, lay!(
|
||||
Sequencer::help(),
|
||||
Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))),
|
||||
)));
|
||||
impl SequencerStatus {
|
||||
fn stats (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
row!(&self.cpu, &self.size)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,10 +72,10 @@ impl<T: HasClock> Command<T> for ClockCommand {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Clock {
|
||||
/// JACK transport handle.
|
||||
pub transport: Arc<Transport>,
|
||||
pub transport: Arc<Option<Transport>>,
|
||||
/// Global temporal resolution (shared by [Moment] fields)
|
||||
pub timebase: Arc<Timebase>,
|
||||
/// Current global sample and usec (monotonic from JACK clock)
|
||||
|
|
@ -102,7 +102,7 @@ from!(|jack: &Arc<RwLock<JackConnection>>| Clock = {
|
|||
Self {
|
||||
quant: Arc::new(24.into()),
|
||||
sync: Arc::new(384.into()),
|
||||
transport: Arc::new(transport),
|
||||
transport: Arc::new(Some(transport)),
|
||||
chunk: Arc::new((chunk as usize).into()),
|
||||
global: Arc::new(Moment::zero(&timebase)),
|
||||
playhead: Arc::new(Moment::zero(&timebase)),
|
||||
|
|
@ -154,17 +154,21 @@ impl Clock {
|
|||
}
|
||||
/// Start playing, optionally seeking to a given location beforehand
|
||||
pub fn play_from (&self, start: Option<u32>) -> Usually<()> {
|
||||
if let Some(start) = start {
|
||||
self.transport.locate(start)?;
|
||||
if let Some(transport) = self.transport.as_ref() {
|
||||
if let Some(start) = start {
|
||||
transport.locate(start)?;
|
||||
}
|
||||
transport.start()?;
|
||||
}
|
||||
self.transport.start()?;
|
||||
Ok(())
|
||||
}
|
||||
/// Pause, optionally seeking to a given location afterwards
|
||||
pub fn pause_at (&self, pause: Option<u32>) -> Usually<()> {
|
||||
self.transport.stop()?;
|
||||
if let Some(pause) = pause {
|
||||
self.transport.locate(pause)?;
|
||||
if let Some(transport) = self.transport.as_ref() {
|
||||
transport.stop()?;
|
||||
if let Some(pause) = pause {
|
||||
transport.locate(pause)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -189,21 +193,24 @@ impl Clock {
|
|||
self.global.sample.set(current_frames as f64);
|
||||
self.global.usec.set(current_usecs as f64);
|
||||
|
||||
let mut started = self.started.write().unwrap();
|
||||
|
||||
// If transport has just started or just stopped,
|
||||
// update starting point:
|
||||
let mut started = self.started.write().unwrap();
|
||||
match (self.transport.query_state()?, started.as_ref()) {
|
||||
(TransportState::Rolling, None) => {
|
||||
let moment = Moment::zero(&self.timebase);
|
||||
moment.sample.set(current_frames as f64);
|
||||
moment.usec.set(current_usecs as f64);
|
||||
*started = Some(moment);
|
||||
},
|
||||
(TransportState::Stopped, Some(_)) => {
|
||||
*started = None;
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
if let Some(transport) = self.transport.as_ref() {
|
||||
match (transport.query_state()?, started.as_ref()) {
|
||||
(TransportState::Rolling, None) => {
|
||||
let moment = Moment::zero(&self.timebase);
|
||||
moment.sample.set(current_frames as f64);
|
||||
moment.usec.set(current_usecs as f64);
|
||||
*started = Some(moment);
|
||||
},
|
||||
(TransportState::Stopped, Some(_)) => {
|
||||
*started = None;
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
self.playhead.update_from_sample(started.as_ref()
|
||||
.map(|started|current_frames as f64 - started.sample.get())
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TuiOut {
|
||||
pub buffer: Buffer,
|
||||
pub area: [u16;4]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue