mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-10 13:46:42 +01:00
put all of arranger/sequencer/transport in 1 file
This commit is contained in:
parent
3042e9e3a8
commit
6f988e5072
12 changed files with 1877 additions and 1847 deletions
|
|
@ -1,487 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
const CORNERS: CornersTall = CornersTall(NOT_DIM_GREEN);
|
||||
|
||||
/// Stores and displays time-related state.
|
||||
pub struct TransportToolbar<E: Engine> {
|
||||
/// Enable metronome?
|
||||
pub metronome: bool,
|
||||
/// Current sample rate, tempo, and PPQ.
|
||||
pub timebase: Arc<Timebase>,
|
||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
||||
pub jack: Option<JackClient>,
|
||||
/// JACK transport handle.
|
||||
pub transport: Option<Transport>,
|
||||
/// Quantization factor
|
||||
/// Global frame and usec at which playback started
|
||||
pub started: Option<(usize, usize)>,
|
||||
|
||||
pub focused: bool,
|
||||
pub focus: usize,
|
||||
pub playing: TransportPlayPauseButton<E>,
|
||||
pub bpm: TransportBPM<E>,
|
||||
pub quant: TransportQuantize<E>,
|
||||
pub sync: TransportSync<E>,
|
||||
pub clock: TransportClock<E>,
|
||||
}
|
||||
impl<E: Engine> TransportToolbar<E> {
|
||||
pub fn standalone () -> Usually<Arc<RwLock<Self>>> where Self: 'static {
|
||||
let mut transport = Self::new(None);
|
||||
transport.focused = true;
|
||||
let jack = JackClient::Inactive(
|
||||
Client::new("tek_transport", ClientOptions::NO_START_SERVER)?.0
|
||||
);
|
||||
transport.transport = Some(jack.transport());
|
||||
transport.playing.transport = Some(jack.transport());
|
||||
let transport = Arc::new(RwLock::new(transport));
|
||||
transport.write().unwrap().jack = Some(
|
||||
jack.activate(
|
||||
&transport.clone(),
|
||||
|state: &Arc<RwLock<TransportToolbar<E>>>, client, scope| {
|
||||
state.write().unwrap().process(client, scope)
|
||||
}
|
||||
)?
|
||||
);
|
||||
Ok(transport)
|
||||
}
|
||||
pub fn new (transport: Option<Transport>) -> Self {
|
||||
let timebase = Arc::new(Timebase::default());
|
||||
Self {
|
||||
focused: false,
|
||||
focus: 0,
|
||||
|
||||
playing: TransportPlayPauseButton {
|
||||
_engine: Default::default(),
|
||||
transport: None,
|
||||
value: Some(TransportState::Stopped),
|
||||
focused: true
|
||||
},
|
||||
bpm: TransportBPM {
|
||||
_engine: Default::default(),
|
||||
value: timebase.bpm(),
|
||||
focused: false
|
||||
},
|
||||
quant: TransportQuantize {
|
||||
_engine: Default::default(),
|
||||
value: 24,
|
||||
focused: false
|
||||
},
|
||||
sync: TransportSync {
|
||||
_engine: Default::default(),
|
||||
value: timebase.ppq() as usize * 4,
|
||||
focused: false
|
||||
},
|
||||
clock: TransportClock {
|
||||
_engine: Default::default(),
|
||||
frame: 0,
|
||||
pulse: 0,
|
||||
ppq: 0,
|
||||
usecs: 0,
|
||||
focused: false
|
||||
},
|
||||
|
||||
transport,
|
||||
timebase,
|
||||
metronome: false,
|
||||
started: None,
|
||||
jack: None,
|
||||
}
|
||||
}
|
||||
pub fn toggle_play (&mut self) -> Usually<()> {
|
||||
self.playing.toggle()?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn update (&mut self, scope: &ProcessScope) -> (bool, usize, usize, usize, usize, f64) {
|
||||
let CycleTimes {
|
||||
current_frames,
|
||||
current_usecs,
|
||||
next_usecs,
|
||||
period_usecs
|
||||
} = scope.cycle_times().unwrap();
|
||||
let chunk_size = scope.n_frames() as usize;
|
||||
let transport = self.transport.as_ref().unwrap().query().unwrap();
|
||||
self.clock.frame = transport.pos.frame() as usize;
|
||||
let mut reset = false;
|
||||
if self.playing.value != Some(transport.state) {
|
||||
match transport.state {
|
||||
TransportState::Rolling => {
|
||||
self.started = Some((
|
||||
current_frames as usize,
|
||||
current_usecs as usize,
|
||||
));
|
||||
},
|
||||
TransportState::Stopped => {
|
||||
self.started = None;
|
||||
reset = true;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
self.playing.value = Some(transport.state);
|
||||
(
|
||||
reset,
|
||||
current_frames as usize,
|
||||
chunk_size as usize,
|
||||
current_usecs as usize,
|
||||
next_usecs as usize,
|
||||
period_usecs as f64
|
||||
)
|
||||
}
|
||||
pub fn bpm (&self) -> usize {
|
||||
self.timebase.bpm() as usize
|
||||
}
|
||||
pub fn ppq (&self) -> usize {
|
||||
self.timebase.ppq() as usize
|
||||
}
|
||||
pub fn pulse (&self) -> usize {
|
||||
self.timebase.frame_to_pulse(self.clock.frame as f64) as usize
|
||||
}
|
||||
pub fn usecs (&self) -> usize {
|
||||
self.timebase.frame_to_usec(self.clock.frame as f64) as usize
|
||||
}
|
||||
pub fn quant (&self) -> usize {
|
||||
self.quant.value
|
||||
}
|
||||
pub fn sync (&self) -> usize {
|
||||
self.sync.value
|
||||
}
|
||||
}
|
||||
impl<E: Engine> Audio for TransportToolbar<E> {
|
||||
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||
self.update(&scope);
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
impl Handle<Tui> for TransportToolbar<Tui> {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
match from.event() {
|
||||
key!(KeyCode::Left) => { self.focus_prev(); },
|
||||
key!(KeyCode::Right) => { self.focus_next(); },
|
||||
_ => return self.focused_mut().handle(from)
|
||||
}
|
||||
Ok(Some(true))
|
||||
}
|
||||
}
|
||||
impl Content for TransportToolbar<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
Split::right(|add|{
|
||||
let focus_wrap = |focused, component|Layers::new(move |add|{
|
||||
if focused {
|
||||
add(&CORNERS)?;
|
||||
add(&Background(COLOR_BG1))?;
|
||||
}
|
||||
add(component)
|
||||
});
|
||||
add(&focus_wrap(self.focused && self.playing.focused, &self.playing))?;
|
||||
add(&focus_wrap(self.focused && self.bpm.focused, &self.bpm))?;
|
||||
add(&focus_wrap(self.focused && self.quant.focused, &self.quant))?;
|
||||
add(&focus_wrap(self.focused && self.sync.focused, &self.sync))?;
|
||||
add(&focus_wrap(self.focused && self.clock.focused, &self.clock))?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Focus<5, Tui> for TransportToolbar<Tui> {
|
||||
fn focus (&self) -> usize {
|
||||
self.focus
|
||||
}
|
||||
fn focus_mut (&mut self) -> &mut usize {
|
||||
&mut self.focus
|
||||
}
|
||||
fn focusable (&self) -> [&dyn Focusable<Tui>;5] {
|
||||
[
|
||||
&self.playing as &dyn Focusable<Tui>,
|
||||
&self.bpm as &dyn Focusable<Tui>,
|
||||
&self.quant as &dyn Focusable<Tui>,
|
||||
&self.sync as &dyn Focusable<Tui>,
|
||||
&self.clock as &dyn Focusable<Tui>,
|
||||
]
|
||||
}
|
||||
fn focusable_mut (&mut self) -> [&mut dyn Focusable<Tui>;5] {
|
||||
[
|
||||
&mut self.playing as &mut dyn Focusable<Tui>,
|
||||
&mut self.bpm as &mut dyn Focusable<Tui>,
|
||||
&mut self.quant as &mut dyn Focusable<Tui>,
|
||||
&mut self.sync as &mut dyn Focusable<Tui>,
|
||||
&mut self.clock as &mut dyn Focusable<Tui>,
|
||||
]
|
||||
}
|
||||
}
|
||||
impl Focusable<Tui> for TransportToolbar<Tui> {
|
||||
fn is_focused (&self) -> bool {
|
||||
self.focused
|
||||
}
|
||||
fn set_focused (&mut self, focused: bool) {
|
||||
self.focused = focused
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub struct TransportPlayPauseButton<E: Engine> {
|
||||
pub _engine: PhantomData<E>,
|
||||
pub transport: Option<Transport>,
|
||||
pub value: Option<TransportState>,
|
||||
pub focused: bool
|
||||
}
|
||||
impl<E: Engine> TransportPlayPauseButton<E> {
|
||||
fn toggle (&mut self) -> Usually<()> {
|
||||
let transport = self.transport.as_ref().unwrap();
|
||||
self.value = match self.value.expect("1st frame has not been processed yet") {
|
||||
TransportState::Stopped => {
|
||||
transport.start()?;
|
||||
Some(TransportState::Starting)
|
||||
},
|
||||
_ => {
|
||||
transport.stop()?;
|
||||
transport.locate(0)?;
|
||||
Some(TransportState::Stopped)
|
||||
},
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl Focusable<Tui> for TransportPlayPauseButton<Tui> {
|
||||
fn is_focused (&self) -> bool {
|
||||
self.focused
|
||||
}
|
||||
fn set_focused (&mut self, focused: bool) {
|
||||
self.focused = focused
|
||||
}
|
||||
}
|
||||
impl Handle<Tui> for TransportPlayPauseButton<Tui> {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
match from.event() {
|
||||
key!(KeyCode::Enter) => self.toggle().map(|_|Some(true)),
|
||||
_ => Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Content for TransportPlayPauseButton<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
Layers::new(|add|{
|
||||
add(&Plus::X(1, Min::XY(11, 2, Styled(match self.value {
|
||||
Some(TransportState::Stopped) => Some(GRAY_DIM.bold()),
|
||||
Some(TransportState::Starting) => Some(GRAY_NOT_DIM_BOLD),
|
||||
Some(TransportState::Rolling) => Some(WHITE_NOT_DIM_BOLD),
|
||||
_ => unreachable!(),
|
||||
}, match self.value {
|
||||
Some(TransportState::Rolling) => "▶ PLAYING",
|
||||
Some(TransportState::Starting) => "READY ...",
|
||||
Some(TransportState::Stopped) => "⏹ STOPPED",
|
||||
_ => unreachable!(),
|
||||
}))))?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub struct TransportBPM<E: Engine> {
|
||||
pub _engine: PhantomData<E>,
|
||||
pub value: f64,
|
||||
pub focused: bool
|
||||
}
|
||||
impl Focusable<Tui> for TransportBPM<Tui> {
|
||||
fn is_focused (&self) -> bool {
|
||||
self.focused
|
||||
}
|
||||
fn set_focused (&mut self, focused: bool) {
|
||||
self.focused = focused
|
||||
}
|
||||
}
|
||||
impl Handle<Tui> for TransportBPM<Tui> {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
match from.event() {
|
||||
key!(KeyCode::Char(',')) => { self.value -= 1.0; },
|
||||
key!(KeyCode::Char('.')) => { self.value += 1.0; },
|
||||
key!(KeyCode::Char('<')) => { self.value -= 0.001; },
|
||||
key!(KeyCode::Char('>')) => { self.value += 0.001; },
|
||||
_ => return Ok(None)
|
||||
}
|
||||
Ok(Some(true))
|
||||
}
|
||||
}
|
||||
impl Content for TransportBPM<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
let Self { value, .. } = self;
|
||||
Outset::X(1u16, Split::down(move |add|{
|
||||
add(&"BPM")?;
|
||||
add(&format!("{}.{:03}", *value as usize, (value * 1000.0) % 1000.0).as_str())
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub struct TransportQuantize<E: Engine> {
|
||||
pub _engine: PhantomData<E>,
|
||||
pub value: usize,
|
||||
pub focused: bool
|
||||
}
|
||||
impl Focusable<Tui> for TransportQuantize<Tui> {
|
||||
fn is_focused (&self) -> bool {
|
||||
self.focused
|
||||
}
|
||||
fn set_focused (&mut self, focused: bool) {
|
||||
self.focused = focused
|
||||
}
|
||||
}
|
||||
impl Handle<Tui> for TransportQuantize<Tui> {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
match from.event() {
|
||||
key!(KeyCode::Char(',')) => {
|
||||
self.value = prev_note_length(self.value);
|
||||
Ok(Some(true))
|
||||
},
|
||||
key!(KeyCode::Char('.')) => {
|
||||
self.value = next_note_length(self.value);
|
||||
Ok(Some(true))
|
||||
},
|
||||
_ => Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Content for TransportQuantize<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
let Self { value, .. } = self;
|
||||
Outset::X(1u16, Split::down(|add|{
|
||||
add(&"QUANT")?;
|
||||
add(&ppq_to_name(*value as usize))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub struct TransportSync<E: Engine> {
|
||||
pub _engine: PhantomData<E>,
|
||||
pub value: usize,
|
||||
pub focused: bool
|
||||
}
|
||||
impl Focusable<Tui> for TransportSync<Tui> {
|
||||
fn is_focused (&self) -> bool {
|
||||
self.focused
|
||||
}
|
||||
fn set_focused (&mut self, focused: bool) {
|
||||
self.focused = focused
|
||||
}
|
||||
}
|
||||
impl Handle<Tui> for TransportSync<Tui> {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
match from.event() {
|
||||
key!(KeyCode::Char(',')) => {
|
||||
self.value = prev_note_length(self.value);
|
||||
Ok(Some(true))
|
||||
},
|
||||
key!(KeyCode::Char('.')) => {
|
||||
self.value = next_note_length(self.value);
|
||||
Ok(Some(true))
|
||||
},
|
||||
_ => Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Content for TransportSync<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
let Self { value, .. } = self;
|
||||
Outset::X(1u16, Split::down(|add|{
|
||||
add(&"SYNC")?;
|
||||
add(&ppq_to_name(*value as usize))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub struct TransportClock<E: Engine> {
|
||||
pub _engine: PhantomData<E>,
|
||||
pub frame: usize,
|
||||
pub pulse: usize,
|
||||
pub ppq: usize,
|
||||
pub usecs: usize,
|
||||
pub focused: bool,
|
||||
}
|
||||
impl Focusable<Tui> for TransportClock<Tui> {
|
||||
fn is_focused (&self) -> bool {
|
||||
self.focused
|
||||
}
|
||||
fn set_focused (&mut self, focused: bool) {
|
||||
self.focused = focused
|
||||
}
|
||||
}
|
||||
impl Handle<Tui> for TransportClock<Tui> {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
impl Content for TransportClock<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
let Self { frame: _frame, pulse, ppq, usecs, focused, .. } = self;
|
||||
Layers::new(move|add|{
|
||||
let (beats, pulses) = if *ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
|
||||
let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1);
|
||||
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
|
||||
let (minutes, seconds) = (seconds / 60, seconds % 60);
|
||||
add(&Outset::X(1u16, Split::down(|add|{
|
||||
add(&format!("{bars}.{beats}.{pulses:02}").as_str())?;
|
||||
add(&format!("{minutes}:{seconds:02}:{msecs:03}").as_str())
|
||||
})))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub const GRAY_DIM: Style = Style {
|
||||
fg: Some(Color::Gray),
|
||||
bg: None,
|
||||
underline_color: None,
|
||||
add_modifier: Modifier::DIM,
|
||||
sub_modifier: Modifier::empty(),
|
||||
};
|
||||
|
||||
pub const GRAY_NOT_DIM_BOLD: Style = Style {
|
||||
fg: Some(Color::Gray),
|
||||
bg: None,
|
||||
underline_color: None,
|
||||
add_modifier: Modifier::BOLD,
|
||||
sub_modifier: Modifier::DIM,
|
||||
};
|
||||
|
||||
pub const NOT_DIM_BOLD: Style = Style {
|
||||
fg: None,
|
||||
bg: None,
|
||||
underline_color: None,
|
||||
add_modifier: Modifier::BOLD,
|
||||
sub_modifier: Modifier::DIM,
|
||||
};
|
||||
|
||||
pub const NOT_DIM_GREEN: Style = Style {
|
||||
fg: Some(Color::Rgb(96, 255, 32)),
|
||||
bg: Some(COLOR_BG1),
|
||||
underline_color: None,
|
||||
add_modifier: Modifier::empty(),
|
||||
sub_modifier: Modifier::DIM,
|
||||
};
|
||||
|
||||
pub const NOT_DIM: Style = Style {
|
||||
fg: None,
|
||||
bg: None,
|
||||
underline_color: None,
|
||||
add_modifier: Modifier::empty(),
|
||||
sub_modifier: Modifier::DIM,
|
||||
};
|
||||
|
||||
pub const WHITE_NOT_DIM_BOLD: Style = Style {
|
||||
fg: Some(Color::White),
|
||||
bg: None,
|
||||
underline_color: None,
|
||||
add_modifier: Modifier::BOLD,
|
||||
sub_modifier: Modifier::DIM,
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue