tek/src/device/launcher/mod.rs
2024-07-04 01:36:32 +03:00

323 lines
12 KiB
Rust

use crate::prelude::*;
mod grid;
pub use self::grid::*;
mod handle;
pub use self::handle::*;
pub struct Launcher {
name: String,
timebase: Arc<Timebase>,
transport: Transport,
playing: TransportState,
monitoring: bool,
recording: bool,
overdub: bool,
position: usize,
cursor: (usize, usize),
pub tracks: Vec<Track>,
scenes: Vec<Scene>,
show_help: bool,
view: LauncherView,
}
pub enum LauncherView {
Tracks,
Sequencer,
Chains,
Modal(Box<dyn Device>),
}
impl LauncherView {
fn is_tracks (&self) -> bool {
match self { Self::Tracks => true, _ => false }
}
}
pub struct Scene {
name: String,
clips: Vec<Option<usize>>,
}
impl Scene {
pub fn new (name: impl AsRef<str>, clips: impl AsRef<[Option<usize>]>) -> Self {
Self {
name: name.as_ref().into(),
clips: clips.as_ref().iter().map(|x|x.clone()).collect()
}
}
}
impl Launcher {
pub fn new (
name: &str,
timebase: &Arc<Timebase>,
tracks: Option<Vec<Track>>,
scenes: Option<Vec<Scene>>
) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
let transport = client.transport();
let ppq = timebase.ppq() as u32;
DynamicDevice::new(render, handle, process, Self {
name: name.into(),
view: LauncherView::Chains,
playing: transport.query_state()?,
transport,
timebase: timebase.clone(),
monitoring: true,
recording: false,
overdub: true,
cursor: (2, 2),
position: 0,
scenes: scenes.unwrap_or_else(||vec![Scene::new(&"Scene 1", &[None])]),
tracks: if let Some(tracks) = tracks { tracks } else { vec![
Track::new("Track 1", &timebase, None, Some(vec![
Phrase::new("MIDI Clip 1", ppq * 4, Some(BTreeMap::from([
( ppq * 0, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
( ppq * 1, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
( ppq * 2, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
( ppq * 3, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
])))
]))?,
] },
show_help: true,
}).activate(client)
}
fn cols (&self) -> usize {
(self.tracks.len() + 2) as usize
}
fn col (&self) -> usize {
self.cursor.0 as usize
}
fn dec_col (&mut self) {
self.cursor.0 = if self.cursor.0 > 0 {
self.cursor.0 - 1
} else {
(self.cols() - 1) as usize
}
}
fn inc_col (&mut self) {
self.cursor.0 = if self.cursor.0 >= self.cols() - 1 {
0
} else {
self.cursor.0 + 1
}
}
fn rows (&self) -> usize {
(self.scenes.len() + 2) as usize
}
fn row (&self) -> usize {
self.cursor.1 as usize
}
fn dec_row (&mut self) {
self.cursor.1 = if self.cursor.1 > 0 {
self.cursor.1 - 1
} else {
self.rows() - 1
}
}
fn inc_row (&mut self) {
self.cursor.1 = if self.cursor.1 >= self.rows() - 1 {
0
} else {
self.cursor.1 + 1
}
}
fn track <'a> (&'a self) -> Option<(usize, &'a Track)> {
match self.col() { 0 => None, _ => {
let id = self.col() as usize - 1;
self.tracks.get(id).map(|t|(id, t))
} }
}
fn scene <'a> (&'a self) -> Option<(usize, &'a Scene)> {
match self.row() { 0 => None, _ => {
let id = self.row() as usize - 1;
self.scenes.get(id).map(|t|(id, t))
} }
}
fn sequencer <'a> (&'a self) -> Option<MutexGuard<Sequencer>> {
self.track().map(|t|t.1.sequencer.state())
}
fn chain <'a> (&'a self) -> Option<MutexGuard<Chain>> {
self.track().map(|t|t.1.chain.state())
}
fn phrase_id (&self) -> Option<usize> {
let (track_id, _) = self.track()?;
let (_, scene) = self.scene()?;
*scene.clips.get(track_id)?
}
}
impl DynamicDevice<Launcher> {
pub fn connect (self, midi_in: &str, audio_outs: &[&str]) -> Usually<Self> {
{
let state = &self.state();
let (client, _status) = Client::new(
&format!("{}-init", &state.name), ClientOptions::NO_START_SERVER
)?;
let midi_ins = client.ports(Some(midi_in), None, PortFlags::IS_OUTPUT);
let audio_outs: Vec<Vec<String>> = audio_outs.iter()
.map(|pattern|client.ports(Some(pattern), None, PortFlags::IS_INPUT))
.collect();
for (i, sequencer) in state.tracks.iter().enumerate() {
for sequencer_midi_in in sequencer.midi_ins()?.iter() {
for midi_in in midi_ins.iter() {
client.connect_ports_by_name(&midi_in, &sequencer_midi_in)?;
}
}
let chain: &DynamicDevice<Chain> = &state.tracks[i].chain;
for port in sequencer.midi_outs()?.iter() {
for midi_in in chain.midi_ins()?.iter() {
client.connect_ports_by_name(&port, &midi_in)?;
}
}
for (j, port) in chain.audio_outs()?.iter().enumerate() {
for audio_out in audio_outs[j % audio_outs.len()].iter() {
client.connect_ports_by_name(&port, &audio_out)?;
}
}
}
}
Ok(self)
}
}
impl PortList for Launcher {}
pub fn process (state: &mut Launcher, _: &Client, _: &ProcessScope) -> Control {
let transport = state.transport.query().unwrap();
state.playing = transport.state;
state.position = transport.pos.frame() as usize;
Control::Continue
}
pub fn render (state: &Launcher, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
//area.width = 80; // DOS mode
//area.height = 25;
let Rect { x, y, width, height } = area;
crate::device::sequencer::draw_play_stop(buf, x + 1, y, &state.playing);
crate::device::sequencer::draw_rec(buf, x + 12, y, state.recording);
crate::device::sequencer::draw_mon(buf, x + 19, y, state.monitoring);
crate::device::sequencer::draw_dub(buf, x + 26, y, state.overdub);
draw_bpm(buf, x + 33, y, state.timebase.tempo());
draw_timer(buf, x + width - 1, y, &state.timebase, state.position);
let mut y = y + 1;
y = y + LauncherGridView::new(
state, buf, Rect { x, y, width, height: height / 3 }, state.view.is_tracks()
).draw()?.height;
y = y + draw_section_sequencer(state, buf, Rect { x, y, width, height: height / 3 })?.height;
y = y + draw_section_chains(state, buf, Rect { x, y, width, height: height /3 })?.height;
area.height = y;
if state.show_help {
let style = Some(Style::default().bold().white().not_dim().on_black().italic());
let hide = "[Left Right] Track [Up Down] Scene [, .] Value [F1] Toggle help ";
hide.blit(buf, width - hide.len() as u16, height - 1, style);
}
Ok(area)
}
fn draw_bpm (buf: &mut Buffer, x: u16, y: u16, tempo: usize) {
let style = Style::default().not_dim();
"BPM"
.blit(buf, x, y, Some(style));
format!("{:03}.{:03}", tempo / 1000, tempo % 1000)
.blit(buf, x + 4, y, Some(style.bold()));
"SYNC"
.blit(buf, x + 13, y, Some(style));
"4/4"
.blit(buf, x + 18, y, Some(style.bold()));
"QUANT"
.blit(buf, x + 23, y, Some(style));
"1/16"
.blit(buf, x + 29, y, Some(style.bold()));
}
fn draw_timer (buf: &mut Buffer, x: u16, y: u16, timebase: &Arc<Timebase>, frame: usize) {
let tick = (frame as f64 / timebase.frames_per_tick()) as usize;
let (beats, ticks) = (tick / timebase.ppq(), tick % timebase.ppq());
let (bars, beats) = (beats / 4, beats % 4);
let timer = format!("{}.{}.{ticks:02}", bars + 1, beats + 1);
timer.blit(buf, x - timer.len() as u16, y, Some(Style::default().not_dim()));
}
fn draw_section_sequencer (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { x, y, width, height } = area;
let style = Some(Style::default().green().dim());
let view = &state.view;
match view {
LauncherView::Sequencer => {
lozenge_left(buf, x, y, height, style);
lozenge_right(buf, x + width - 1, y, height, style);
},
_ => {},
};
if let Some((_, track)) = state.track() {
let frame = state.position;
let timebase = &state.timebase;
let tick = (frame as f64 / timebase.frames_per_tick()) as usize;
let sequencer = track.sequencer.state();
let zoom = sequencer.resolution;
let steps = if let Some(_phrase) = sequencer.phrase() { 0 } else { 0 } / 4; // TODO
crate::device::sequencer::horizontal::timer(buf, x+5, y,
steps, sequencer.steps * zoom, sequencer.time_axis.0, sequencer.time_axis.1
);
crate::device::sequencer::horizontal::keys(buf, Rect { x, y: y + 1, width, height },
sequencer.note_axis.1
)?;
if let Some(id) = state.phrase_id() {
if let Some(phrase) = sequencer.phrases.get(id) {
crate::device::sequencer::horizontal::lanes(buf, x, y + 1,
&phrase,
sequencer.timebase.ppq() as u32,
sequencer.resolution as u32,
sequencer.time_axis.0 as u32,
sequencer.time_axis.1 as u32,
sequencer.note_axis.0 as u32,
sequencer.note_axis.1 as u32,
);
}
}
let cursor_style = match view {
LauncherView::Sequencer => Style::default().green().not_dim(),
_ => Style::default().green().dim(),
};
crate::device::sequencer::horizontal::cursor(buf, x, y + 1, cursor_style,
sequencer.time_cursor,
sequencer.note_cursor
);
}
Ok(area)
}
fn draw_highlight (buf: &mut Buffer, highlight: &Option<Rect>, style: Style) {
if let Some(area) = highlight {
draw_box_styled(buf, *area, Some(style));
}
}
fn draw_section_chains (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let style = Some(Style::default().green().dim());
match state.view {
LauncherView::Chains => {
let Rect { x, y, width, height} = area;
lozenge_left(buf, x, y, height, style);
lozenge_right(buf, x + width - 1, y, height, style);
},
_ => {},
};
let chain = state.chain();
let _ = if let Some(chain) = &chain {
let (_, plugins) = crate::device::chain::draw_as_row(
&*chain, buf, area, style
)?;
plugins
} else {
vec![]
};
//match state.view {
//LauncherView::Chains => {
//draw_box_styled(buf, Rect { height: 18, ..area }, style);
//},
//_ => {},
//};
//draw_highlight(buf, &Some(area), match state.view {
//LauncherView::Chains => Style::default().green().dim(),
//_ => Style::default().dim()
//});
//if let Some(chain) = &chain {
//if let Some(plugin) = plugins.get(chain.focus) {
//draw_highlight(buf, &Some(*plugin), match state.view {
//LauncherView::Chains => Style::default().green().not_dim(),
//_ => Style::default().green().dim()
//});
//}
//}
Ok(area)
}