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