mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
add HasJack; Arrangement
This commit is contained in:
parent
744ce21e24
commit
e73c31d494
12 changed files with 565 additions and 387 deletions
188
.old/from_arranger.rs
Normal file
188
.old/from_arranger.rs
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
|
||||||
|
|
||||||
|
//pub struct ArrangerVCursor {
|
||||||
|
//cols: Vec<(usize, usize)>,
|
||||||
|
//rows: Vec<(usize, usize)>,
|
||||||
|
//color: ItemPalette,
|
||||||
|
//reticle: Reticle,
|
||||||
|
//selected: ArrangerSelection,
|
||||||
|
//scenes_w: u16,
|
||||||
|
//}
|
||||||
|
|
||||||
|
//pub(crate) const HEADER_H: u16 = 0; // 5
|
||||||
|
//pub(crate) const SCENES_W_OFFSET: u16 = 0;
|
||||||
|
//from!(|args:(&Arranger, usize)|ArrangerVCursor = Self {
|
||||||
|
//cols: Arranger::track_widths(&args.0.tracks),
|
||||||
|
//rows: Arranger::scene_heights(&args.0.scenes, args.1),
|
||||||
|
//selected: args.0.selected(),
|
||||||
|
//scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&args.0.scenes) as u16,
|
||||||
|
//color: args.0.color,
|
||||||
|
//reticle: Reticle(Style {
|
||||||
|
//fg: Some(args.0.color.lighter.rgb),
|
||||||
|
//bg: None,
|
||||||
|
//underline_color: None,
|
||||||
|
//add_modifier: Modifier::empty(),
|
||||||
|
//sub_modifier: Modifier::DIM
|
||||||
|
//}),
|
||||||
|
//});
|
||||||
|
//impl Content<TuiOut> for ArrangerVCursor {
|
||||||
|
//fn render (&self, to: &mut TuiOut) {
|
||||||
|
//let area = to.area();
|
||||||
|
//let focused = true;
|
||||||
|
//let selected = self.selected;
|
||||||
|
//let get_track_area = |t: usize| [
|
||||||
|
//self.scenes_w + area.x() + self.cols[t].1 as u16, area.y(),
|
||||||
|
//self.cols[t].0 as u16, area.h(),
|
||||||
|
//];
|
||||||
|
//let get_scene_area = |s: usize| [
|
||||||
|
//area.x(), HEADER_H + area.y() + (self.rows[s].1 / PPQ) as u16,
|
||||||
|
//area.w(), (self.rows[s].0 / PPQ) as u16
|
||||||
|
//];
|
||||||
|
//let get_clip_area = |t: usize, s: usize| [
|
||||||
|
//(self.scenes_w + area.x() + self.cols[t].1 as u16).saturating_sub(1),
|
||||||
|
//HEADER_H + area.y() + (self.rows[s].1/PPQ) as u16,
|
||||||
|
//self.cols[t].0 as u16 + 2,
|
||||||
|
//(self.rows[s].0 / PPQ) as u16
|
||||||
|
//];
|
||||||
|
//let mut track_area: Option<[u16;4]> = None;
|
||||||
|
//let mut scene_area: Option<[u16;4]> = None;
|
||||||
|
//let mut clip_area: Option<[u16;4]> = None;
|
||||||
|
//let area = match selected {
|
||||||
|
//ArrangerSelection::Mix => area,
|
||||||
|
//ArrangerSelection::Track(t) => {
|
||||||
|
//track_area = Some(get_track_area(t));
|
||||||
|
//area
|
||||||
|
//},
|
||||||
|
//ArrangerSelection::Scene(s) => {
|
||||||
|
//scene_area = Some(get_scene_area(s));
|
||||||
|
//area
|
||||||
|
//},
|
||||||
|
//ArrangerSelection::Clip(t, s) => {
|
||||||
|
//track_area = Some(get_track_area(t));
|
||||||
|
//scene_area = Some(get_scene_area(s));
|
||||||
|
//clip_area = Some(get_clip_area(t, s));
|
||||||
|
//area
|
||||||
|
//},
|
||||||
|
//};
|
||||||
|
//let bg = self.color.lighter.rgb;//Color::Rgb(0, 255, 0);
|
||||||
|
//if let Some([x, y, width, height]) = track_area {
|
||||||
|
//to.fill_fg([x, y, 1, height], bg);
|
||||||
|
//to.fill_fg([x + width, y, 1, height], bg);
|
||||||
|
//}
|
||||||
|
//if let Some([_, y, _, height]) = scene_area {
|
||||||
|
//to.fill_ul([area.x(), y - 1, area.w(), 1], bg);
|
||||||
|
//to.fill_ul([area.x(), y + height - 1, area.w(), 1], bg);
|
||||||
|
//}
|
||||||
|
//if focused {
|
||||||
|
//to.place(if let Some(clip_area) = clip_area {
|
||||||
|
//clip_area
|
||||||
|
//} else if let Some(track_area) = track_area {
|
||||||
|
//track_area.clip_h(HEADER_H)
|
||||||
|
//} else if let Some(scene_area) = scene_area {
|
||||||
|
//scene_area.clip_w(self.scenes_w)
|
||||||
|
//} else {
|
||||||
|
//area.clip_w(self.scenes_w).clip_h(HEADER_H)
|
||||||
|
//}, &self.reticle)
|
||||||
|
//};
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//impl Arranger {
|
||||||
|
//fn render_mode (state: &Self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
//match state.mode {
|
||||||
|
//ArrangerMode::H => todo!("horizontal arranger"),
|
||||||
|
//ArrangerMode::V(factor) => Self::render_mode_v(state, factor),
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//render!(TuiOut: (self: Arranger) => {
|
||||||
|
//let pool_w = if self.pool.visible { self.splits[1] } else { 0 };
|
||||||
|
//let color = self.color;
|
||||||
|
//let layout = Bsp::a(Fill::xy(ArrangerStatus::from(self)),
|
||||||
|
//Bsp::n(Fixed::x(pool_w, PoolView(self.pool.visible, &self.pool)),
|
||||||
|
//Bsp::n(TransportView::new(true, &self.clock),
|
||||||
|
//Bsp::s(Fixed::y(1, MidiEditStatus(&self.editor)),
|
||||||
|
//Bsp::n(Fill::x(Fixed::y(20,
|
||||||
|
//Bsp::a(Fill::xy(Tui::bg(color.darkest.rgb, "background")),
|
||||||
|
//Bsp::a(
|
||||||
|
//Fill::xy(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))),
|
||||||
|
//Self::render_mode(self))))), Fill::y(&"fixme: self.editor"))))));
|
||||||
|
//self.size.of(layout)
|
||||||
|
//});
|
||||||
|
//Align::n(Fill::xy(lay!(
|
||||||
|
//Align::n(Fill::xy(Tui::bg(self.color.darkest.rgb, " "))),
|
||||||
|
//Align::n(Fill::xy(ArrangerVRowSep::from((self, 1)))),
|
||||||
|
//Align::n(Fill::xy(ArrangerVColSep::from(self))),
|
||||||
|
//Align::n(Fill::xy(ArrangerVClips::new(self, 1))),
|
||||||
|
//Align::n(Fill::xy(ArrangerVCursor::from((self, 1))))))))))))))));
|
||||||
|
//Align::n(Fill::xy(":")))))))))))));
|
||||||
|
//"todo:"))))))));
|
||||||
|
//Bsp::s(
|
||||||
|
//Align::n(Fixed::y(1, Fill::x(ArrangerVIns::from(self)))),
|
||||||
|
//Bsp::s(
|
||||||
|
//Fixed::y(20, Align::n(ArrangerVClips::new(self, 1))),
|
||||||
|
//Fill::x(Fixed::y(1, ArrangerVOuts::from(self)))))))))))));
|
||||||
|
//Bsp::s(
|
||||||
|
//Bsp::s(
|
||||||
|
//Bsp::s(
|
||||||
|
//Fill::xy(ArrangerVClips::new(self, 1)),
|
||||||
|
//Fill::x(ArrangerVOuts::from(self)))))
|
||||||
|
|
||||||
|
//let cell = phat_sel_3(
|
||||||
|
//selected_track == Some(i) && selected_scene == Some(j),
|
||||||
|
//Tui::fg(TuiTheme::g(64), Push::x(1, name)),
|
||||||
|
//Tui::fg(TuiTheme::g(64), Push::x(1, name)),
|
||||||
|
//if selected_track == Some(i) && selected_scene.map(|s|s+1) == Some(j) {
|
||||||
|
//None
|
||||||
|
//} else {
|
||||||
|
//Some(TuiTheme::g(32).into())
|
||||||
|
//},
|
||||||
|
//TuiTheme::g(32).into(),
|
||||||
|
//TuiTheme::g(32).into(),
|
||||||
|
//);
|
||||||
|
// TODO: port per track:
|
||||||
|
//for connection in midi_from.iter() {
|
||||||
|
//let mut split = connection.as_ref().split("=");
|
||||||
|
//let number = split.next().unwrap().trim();
|
||||||
|
//if let Ok(track) = number.parse::<usize>() {
|
||||||
|
//if track < 1 {
|
||||||
|
//panic!("Tracks start from 1")
|
||||||
|
//}
|
||||||
|
//if track > count {
|
||||||
|
//panic!("Tried to connect track {track} or {count}. Pass -t {track} to increase number of tracks.")
|
||||||
|
//}
|
||||||
|
//if let Some(port) = split.next() {
|
||||||
|
//if let Some(port) = jack.read().unwrap().client().port_by_name(port).as_ref() {
|
||||||
|
////jack.read().unwrap().client().connect_ports(port, &self.tracks[track-1].player.midi_ins[0])?;
|
||||||
|
//} else {
|
||||||
|
//panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names.");
|
||||||
|
//}
|
||||||
|
//} else {
|
||||||
|
//panic!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME")
|
||||||
|
//}
|
||||||
|
//} else {
|
||||||
|
//panic!("Failed to parse track number: {number}")
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//for connection in midi_to.iter() {
|
||||||
|
//let mut split = connection.as_ref().split("=");
|
||||||
|
//let number = split.next().unwrap().trim();
|
||||||
|
//if let Ok(track) = number.parse::<usize>() {
|
||||||
|
//if track < 1 {
|
||||||
|
//panic!("Tracks start from 1")
|
||||||
|
//}
|
||||||
|
//if track > count {
|
||||||
|
//panic!("Tried to connect track {track} or {count}. Pass -t {track} to increase number of tracks.")
|
||||||
|
//}
|
||||||
|
//if let Some(port) = split.next() {
|
||||||
|
//if let Some(port) = jack.read().unwrap().client().port_by_name(port).as_ref() {
|
||||||
|
////jack.read().unwrap().client().connect_ports(&self.tracks[track-1].player.midi_outs[0], port)?;
|
||||||
|
//} else {
|
||||||
|
//panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names.");
|
||||||
|
//}
|
||||||
|
//} else {
|
||||||
|
//panic!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME")
|
||||||
|
//}
|
||||||
|
//} else {
|
||||||
|
//panic!("Failed to parse track number: {number}")
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
@ -112,7 +112,7 @@ pub fn main () -> Usually<()> {
|
||||||
//}
|
//}
|
||||||
//};
|
//};
|
||||||
Ok(match cli.mode {
|
Ok(match cli.mode {
|
||||||
TekMode::Clock => engine.run(&jack.activate_with(|jack|Ok(TransportTui {
|
TekMode::Clock => engine.run(&jack.activate_with(|jack|Ok(ClockTui {
|
||||||
jack: jack.clone(), clock: default_clock(jack),
|
jack: jack.clone(), clock: default_clock(jack),
|
||||||
}))?)?,
|
}))?)?,
|
||||||
TekMode::Sampler => engine.run(&jack.activate_with(|jack|Ok(
|
TekMode::Sampler => engine.run(&jack.activate_with(|jack|Ok(
|
||||||
|
|
|
||||||
5
jack/src/has_jack.rs
Normal file
5
jack/src/has_jack.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub trait HasJack {
|
||||||
|
fn jack (&self) -> &Arc<RwLock<JackConnection>>;
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,7 @@ pub(crate) use ::jack::{
|
||||||
};
|
};
|
||||||
|
|
||||||
mod from_jack; pub use self::from_jack::*;
|
mod from_jack; pub use self::from_jack::*;
|
||||||
|
mod has_jack; pub use self::has_jack::*;
|
||||||
mod jack_audio; pub use self::jack_audio::*;
|
mod jack_audio; pub use self::jack_audio::*;
|
||||||
mod jack_connect; pub use self::jack_connect::*;
|
mod jack_connect; pub use self::jack_connect::*;
|
||||||
mod jack_event; pub use self::jack_event::*;
|
mod jack_event; pub use self::jack_event::*;
|
||||||
|
|
|
||||||
|
|
@ -1,81 +1,74 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use ClockCommand::{Play, Pause};
|
use ClockCommand::{Play, Pause};
|
||||||
use self::ArrangerCommand as Cmd;
|
use self::ArrangerCommand as Cmd;
|
||||||
impl Arranger {
|
|
||||||
pub fn activate (&mut self) -> Usually<()> {
|
impl Arrangement for App {
|
||||||
if let ArrangerSelection::Scene(s) = self.selected {
|
fn tracks (&self) -> &Vec<ArrangerTrack> { &self.tracks }
|
||||||
for (t, track) in self.tracks.iter_mut().enumerate() {
|
fn tracks_mut (&mut self) -> &mut Vec<ArrangerTrack> { &mut self.tracks }
|
||||||
let clip = self.scenes[s].clips[t].clone();
|
fn scenes (&self) -> &Vec<ArrangerScene> { &self.scenes }
|
||||||
if track.player.play_clip.is_some() || clip.is_some() {
|
fn scenes_mut (&mut self) -> &mut Vec<ArrangerScene> { &mut self.scenes }
|
||||||
track.player.enqueue_next(clip.as_ref());
|
fn selected (&self) -> &ArrangerSelection { &self.selected }
|
||||||
}
|
fn selected_mut (&mut self) -> &mut ArrangerSelection { &mut self.selected }
|
||||||
}
|
|
||||||
if self.clock().is_stopped() {
|
|
||||||
self.clock().play_from(Some(0))?;
|
|
||||||
}
|
|
||||||
} else if let ArrangerSelection::Clip(t, s) = self.selected {
|
|
||||||
let clip = self.scenes[s].clips[t].clone();
|
|
||||||
self.tracks[t].player.enqueue_next(clip.as_ref());
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn selected_clip (&self) -> Option<Arc<RwLock<MidiClip>>> {
|
|
||||||
self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
|
|
||||||
}
|
|
||||||
pub fn toggle_loop (&mut self) {
|
|
||||||
if let Some(clip) = self.selected_clip() {
|
|
||||||
clip.write().unwrap().toggle_loop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn randomize_color (&mut self) {
|
|
||||||
match self.selected {
|
|
||||||
ArrangerSelection::Mix => { self.color = ItemPalette::random() },
|
|
||||||
ArrangerSelection::Track(t) => { self.tracks[t].color = ItemPalette::random() },
|
|
||||||
ArrangerSelection::Scene(s) => { self.scenes[s].color = ItemPalette::random() },
|
|
||||||
ArrangerSelection::Clip(t, s) => if let Some(clip) = &self.scenes[s].clips[t] {
|
|
||||||
clip.write().unwrap().color = ItemPalette::random();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl Arranger {
|
impl Arrangement for Arranger {
|
||||||
pub fn track_next_name (&self) -> Arc<str> {
|
fn tracks (&self) -> &Vec<ArrangerTrack> { &self.tracks }
|
||||||
format!("Trk{:02}", self.tracks.len() + 1).into()
|
fn tracks_mut (&mut self) -> &mut Vec<ArrangerTrack> { &mut self.tracks }
|
||||||
|
fn scenes (&self) -> &Vec<ArrangerScene> { &self.scenes }
|
||||||
|
fn scenes_mut (&mut self) -> &mut Vec<ArrangerScene> { &mut self.scenes }
|
||||||
|
fn selected (&self) -> &ArrangerSelection { &self.selected }
|
||||||
|
fn selected_mut (&mut self) -> &mut ArrangerSelection { &mut self.selected }
|
||||||
|
}
|
||||||
|
pub trait Arrangement: HasClock + HasJack {
|
||||||
|
fn tracks (&self) -> &Vec<ArrangerTrack>;
|
||||||
|
fn tracks_mut (&mut self) -> &mut Vec<ArrangerTrack>;
|
||||||
|
fn scenes (&self) -> &Vec<ArrangerScene>;
|
||||||
|
fn scenes_mut (&mut self) -> &mut Vec<ArrangerScene>;
|
||||||
|
fn selected (&self) -> &ArrangerSelection;
|
||||||
|
fn selected_mut (&mut self) -> &mut ArrangerSelection;
|
||||||
|
|
||||||
|
fn track_next_name (&self) -> Arc<str> {
|
||||||
|
format!("Trk{:02}", self.tracks().len() + 1).into()
|
||||||
}
|
}
|
||||||
pub fn track_add (&mut self, name: Option<&str>, color: Option<ItemPalette>)
|
fn track (&self) -> Option<&ArrangerTrack> {
|
||||||
|
self.selected().track().and_then(|s|self.tracks().get(s))
|
||||||
|
}
|
||||||
|
fn track_mut (&mut self) -> Option<&mut ArrangerTrack> {
|
||||||
|
self.selected().track().and_then(|s|self.tracks_mut().get_mut(s))
|
||||||
|
}
|
||||||
|
fn track_del (&mut self, index: usize) {
|
||||||
|
self.tracks_mut().remove(index);
|
||||||
|
for scene in self.scenes_mut().iter_mut() {
|
||||||
|
scene.clips.remove(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn track_add (&mut self, name: Option<&str>, color: Option<ItemPalette>)
|
||||||
-> Usually<&mut ArrangerTrack>
|
-> Usually<&mut ArrangerTrack>
|
||||||
{
|
{
|
||||||
let name = name.map_or_else(||self.track_next_name(), |x|x.to_string().into());
|
let name = name.map_or_else(||self.track_next_name(), |x|x.to_string().into());
|
||||||
let track = ArrangerTrack {
|
let track = ArrangerTrack {
|
||||||
width: (name.len() + 2).max(9),
|
width: (name.len() + 2).max(9),
|
||||||
color: color.unwrap_or_else(ItemPalette::random),
|
color: color.unwrap_or_else(ItemPalette::random),
|
||||||
player: MidiPlayer::from(&self.clock),
|
player: MidiPlayer::from(self.clock()),
|
||||||
name,
|
name,
|
||||||
};
|
};
|
||||||
self.tracks.push(track);
|
self.tracks_mut().push(track);
|
||||||
let len = self.tracks.len();
|
let len = self.tracks().len();
|
||||||
let index = len - 1;
|
let index = len - 1;
|
||||||
for scene in self.scenes.iter_mut() {
|
for scene in self.scenes_mut().iter_mut() {
|
||||||
while scene.clips.len() < len {
|
while scene.clips.len() < len {
|
||||||
scene.clips.push(None);
|
scene.clips.push(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(&mut self.tracks[index])
|
Ok(&mut self.tracks_mut()[index])
|
||||||
}
|
}
|
||||||
pub fn track_del (&mut self, index: usize) {
|
fn tracks_add (
|
||||||
self.tracks.remove(index);
|
|
||||||
for scene in self.scenes.iter_mut() {
|
|
||||||
scene.clips.remove(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn tracks_add (
|
|
||||||
&mut self,
|
&mut self,
|
||||||
count: usize,
|
count: usize,
|
||||||
width: usize,
|
width: usize,
|
||||||
midi_from: &[PortConnection],
|
midi_from: &[PortConnection],
|
||||||
midi_to: &[PortConnection],
|
midi_to: &[PortConnection],
|
||||||
) -> Usually<()> {
|
) -> Usually<()> {
|
||||||
let jack = self.jack.clone();
|
let jack = self.jack().clone();
|
||||||
let track_color_1 = ItemColor::random();
|
let track_color_1 = ItemColor::random();
|
||||||
let track_color_2 = ItemColor::random();
|
let track_color_2 = ItemColor::random();
|
||||||
for i in 0..count {
|
for i in 0..count {
|
||||||
|
|
@ -89,33 +82,32 @@ impl Arranger {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
impl Arranger {
|
fn scene_default_name (&self) -> Arc<str> {
|
||||||
pub fn scene_add (&mut self, name: Option<&str>, color: Option<ItemPalette>)
|
format!("Sc{:3>}", self.scenes().len() + 1).into()
|
||||||
|
}
|
||||||
|
fn scene (&self) -> Option<&ArrangerScene> {
|
||||||
|
self.selected().scene().and_then(|s|self.scenes().get(s))
|
||||||
|
}
|
||||||
|
fn scene_mut (&mut self) -> Option<&mut ArrangerScene> {
|
||||||
|
self.selected().scene().and_then(|s|self.scenes_mut().get_mut(s))
|
||||||
|
}
|
||||||
|
fn scene_del (&mut self, index: usize) {
|
||||||
|
todo!("delete scene");
|
||||||
|
}
|
||||||
|
fn scene_add (&mut self, name: Option<&str>, color: Option<ItemPalette>)
|
||||||
-> Usually<&mut ArrangerScene>
|
-> Usually<&mut ArrangerScene>
|
||||||
{
|
{
|
||||||
let scene = ArrangerScene {
|
let scene = ArrangerScene {
|
||||||
name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()),
|
name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()),
|
||||||
clips: vec![None;self.tracks.len()],
|
clips: vec![None;self.tracks().len()],
|
||||||
color: color.unwrap_or_else(ItemPalette::random),
|
color: color.unwrap_or_else(ItemPalette::random),
|
||||||
};
|
};
|
||||||
self.scenes.push(scene);
|
self.scenes_mut().push(scene);
|
||||||
let index = self.scenes.len() - 1;
|
let index = self.scenes().len() - 1;
|
||||||
Ok(&mut self.scenes[index])
|
Ok(&mut self.scenes_mut()[index])
|
||||||
}
|
}
|
||||||
pub fn scene_del (&mut self, index: usize) {
|
fn scenes_add (&mut self, n: usize) -> Usually<()> {
|
||||||
todo!("delete scene");
|
|
||||||
}
|
|
||||||
fn scene_default_name (&self) -> Arc<str> {
|
|
||||||
format!("Sc{:3>}", self.scenes.len() + 1).into()
|
|
||||||
}
|
|
||||||
pub fn selected_scene (&self) -> Option<&ArrangerScene> {
|
|
||||||
self.selected.scene().and_then(|s|self.scenes.get(s))
|
|
||||||
}
|
|
||||||
pub fn selected_scene_mut (&mut self) -> Option<&mut ArrangerScene> {
|
|
||||||
self.selected.scene().and_then(|s|self.scenes.get_mut(s))
|
|
||||||
}
|
|
||||||
pub fn scenes_add (&mut self, n: usize) -> Usually<()> {
|
|
||||||
let scene_color_1 = ItemColor::random();
|
let scene_color_1 = ItemColor::random();
|
||||||
let scene_color_2 = ItemColor::random();
|
let scene_color_2 = ItemColor::random();
|
||||||
for i in 0..n {
|
for i in 0..n {
|
||||||
|
|
@ -125,19 +117,16 @@ impl Arranger {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
impl ArrangerTrack {
|
|
||||||
fn longest_name (tracks: &[Self]) -> usize {
|
#[derive(Default)] pub struct ArrangerScene {
|
||||||
tracks.iter().map(|s|s.name.len()).fold(0, usize::max)
|
/// Name of scene
|
||||||
}
|
pub(crate) name: Arc<str>,
|
||||||
fn width_inc (&mut self) {
|
/// Clips in scene, one per track
|
||||||
self.width += 1;
|
pub(crate) clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
|
||||||
}
|
/// Identifying color of scene
|
||||||
fn width_dec (&mut self) {
|
pub(crate) color: ItemPalette,
|
||||||
if self.width > Arranger::TRACK_MIN_WIDTH {
|
|
||||||
self.width -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl ArrangerScene {
|
impl ArrangerScene {
|
||||||
pub fn longest_name (scenes: &[Self]) -> usize {
|
pub fn longest_name (scenes: &[Self]) -> usize {
|
||||||
|
|
@ -171,190 +160,120 @@ impl ArrangerScene {
|
||||||
match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None }
|
match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[derive(Debug)] pub struct ArrangerTrack {
|
||||||
//pub struct ArrangerVCursor {
|
/// Name of track
|
||||||
//cols: Vec<(usize, usize)>,
|
pub name: Arc<str>,
|
||||||
//rows: Vec<(usize, usize)>,
|
/// Preferred width of track column
|
||||||
//color: ItemPalette,
|
pub width: usize,
|
||||||
//reticle: Reticle,
|
/// Identifying color of track
|
||||||
//selected: ArrangerSelection,
|
pub color: ItemPalette,
|
||||||
//scenes_w: u16,
|
/// MIDI player state
|
||||||
//}
|
pub player: MidiPlayer,
|
||||||
|
}
|
||||||
//pub(crate) const HEADER_H: u16 = 0; // 5
|
has_clock!(|self:ArrangerTrack|self.player.clock());
|
||||||
//pub(crate) const SCENES_W_OFFSET: u16 = 0;
|
has_player!(|self:ArrangerTrack|self.player);
|
||||||
//from!(|args:(&Arranger, usize)|ArrangerVCursor = Self {
|
impl ArrangerTrack {
|
||||||
//cols: Arranger::track_widths(&args.0.tracks),
|
fn longest_name (tracks: &[Self]) -> usize {
|
||||||
//rows: Arranger::scene_heights(&args.0.scenes, args.1),
|
tracks.iter().map(|s|s.name.len()).fold(0, usize::max)
|
||||||
//selected: args.0.selected(),
|
}
|
||||||
//scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&args.0.scenes) as u16,
|
fn width_inc (&mut self) {
|
||||||
//color: args.0.color,
|
self.width += 1;
|
||||||
//reticle: Reticle(Style {
|
}
|
||||||
//fg: Some(args.0.color.lighter.rgb),
|
fn width_dec (&mut self) {
|
||||||
//bg: None,
|
if self.width > Arranger::TRACK_MIN_WIDTH {
|
||||||
//underline_color: None,
|
self.width -= 1;
|
||||||
//add_modifier: Modifier::empty(),
|
}
|
||||||
//sub_modifier: Modifier::DIM
|
}
|
||||||
//}),
|
}
|
||||||
//});
|
#[derive(PartialEq, Clone, Copy, Debug, Default)]
|
||||||
//impl Content<TuiOut> for ArrangerVCursor {
|
/// Represents the current user selection in the arranger
|
||||||
//fn render (&self, to: &mut TuiOut) {
|
pub enum ArrangerSelection {
|
||||||
//let area = to.area();
|
/// The whole mix is selected
|
||||||
//let focused = true;
|
#[default] Mix,
|
||||||
//let selected = self.selected;
|
/// A track is selected.
|
||||||
//let get_track_area = |t: usize| [
|
Track(usize),
|
||||||
//self.scenes_w + area.x() + self.cols[t].1 as u16, area.y(),
|
/// A scene is selected.
|
||||||
//self.cols[t].0 as u16, area.h(),
|
Scene(usize),
|
||||||
//];
|
/// A clip (track × scene) is selected.
|
||||||
//let get_scene_area = |s: usize| [
|
Clip(usize, usize),
|
||||||
//area.x(), HEADER_H + area.y() + (self.rows[s].1 / PPQ) as u16,
|
}
|
||||||
//area.w(), (self.rows[s].0 / PPQ) as u16
|
/// Focus identification methods
|
||||||
//];
|
impl ArrangerSelection {
|
||||||
//let get_clip_area = |t: usize, s: usize| [
|
pub fn track (&self) -> Option<usize> {
|
||||||
//(self.scenes_w + area.x() + self.cols[t].1 as u16).saturating_sub(1),
|
use ArrangerSelection::*;
|
||||||
//HEADER_H + area.y() + (self.rows[s].1/PPQ) as u16,
|
match self {
|
||||||
//self.cols[t].0 as u16 + 2,
|
Clip(t, _) => Some(*t),
|
||||||
//(self.rows[s].0 / PPQ) as u16
|
Track(t) => Some(*t),
|
||||||
//];
|
_ => None
|
||||||
//let mut track_area: Option<[u16;4]> = None;
|
}
|
||||||
//let mut scene_area: Option<[u16;4]> = None;
|
}
|
||||||
//let mut clip_area: Option<[u16;4]> = None;
|
pub fn scene (&self) -> Option<usize> {
|
||||||
//let area = match selected {
|
use ArrangerSelection::*;
|
||||||
//ArrangerSelection::Mix => area,
|
match self {
|
||||||
//ArrangerSelection::Track(t) => {
|
Clip(_, s) => Some(*s),
|
||||||
//track_area = Some(get_track_area(t));
|
Scene(s) => Some(*s),
|
||||||
//area
|
_ => None
|
||||||
//},
|
}
|
||||||
//ArrangerSelection::Scene(s) => {
|
}
|
||||||
//scene_area = Some(get_scene_area(s));
|
pub fn is_mix (&self) -> bool { matches!(self, Self::Mix) }
|
||||||
//area
|
pub fn is_track (&self) -> bool { matches!(self, Self::Track(_)) }
|
||||||
//},
|
pub fn is_scene (&self) -> bool { matches!(self, Self::Scene(_)) }
|
||||||
//ArrangerSelection::Clip(t, s) => {
|
pub fn is_clip (&self) -> bool { matches!(self, Self::Clip(_, _)) }
|
||||||
//track_area = Some(get_track_area(t));
|
pub fn description (
|
||||||
//scene_area = Some(get_scene_area(s));
|
&self,
|
||||||
//clip_area = Some(get_clip_area(t, s));
|
tracks: &[ArrangerTrack],
|
||||||
//area
|
scenes: &[ArrangerScene],
|
||||||
//},
|
) -> Arc<str> {
|
||||||
//};
|
format!("Selected: {}", match self {
|
||||||
//let bg = self.color.lighter.rgb;//Color::Rgb(0, 255, 0);
|
Self::Mix => "Everything".to_string(),
|
||||||
//if let Some([x, y, width, height]) = track_area {
|
Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name))
|
||||||
//to.fill_fg([x, y, 1, height], bg);
|
.unwrap_or_else(||"T??".into()),
|
||||||
//to.fill_fg([x + width, y, 1, height], bg);
|
Self::Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name))
|
||||||
//}
|
.unwrap_or_else(||"S??".into()),
|
||||||
//if let Some([_, y, _, height]) = scene_area {
|
Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) {
|
||||||
//to.fill_ul([area.x(), y - 1, area.w(), 1], bg);
|
(Some(_), Some(scene)) => match scene.clip(*t) {
|
||||||
//to.fill_ul([area.x(), y + height - 1, area.w(), 1], bg);
|
Some(clip) => format!("T{t} S{s} C{}", &clip.read().unwrap().name),
|
||||||
//}
|
None => format!("T{t} S{s}: Empty")
|
||||||
//if focused {
|
},
|
||||||
//to.place(if let Some(clip_area) = clip_area {
|
_ => format!("T{t} S{s}: Empty"),
|
||||||
//clip_area
|
}
|
||||||
//} else if let Some(track_area) = track_area {
|
}).into()
|
||||||
//track_area.clip_h(HEADER_H)
|
}
|
||||||
//} else if let Some(scene_area) = scene_area {
|
}
|
||||||
//scene_area.clip_w(self.scenes_w)
|
impl Arranger {
|
||||||
//} else {
|
pub fn activate (&mut self) -> Usually<()> {
|
||||||
//area.clip_w(self.scenes_w).clip_h(HEADER_H)
|
if let ArrangerSelection::Scene(s) = self.selected {
|
||||||
//}, &self.reticle)
|
for (t, track) in self.tracks.iter_mut().enumerate() {
|
||||||
//};
|
let clip = self.scenes[s].clips[t].clone();
|
||||||
//}
|
if track.player.play_clip.is_some() || clip.is_some() {
|
||||||
//}
|
track.player.enqueue_next(clip.as_ref());
|
||||||
//impl Arranger {
|
}
|
||||||
//fn render_mode (state: &Self) -> impl Content<TuiOut> + use<'_> {
|
}
|
||||||
//match state.mode {
|
if self.clock().is_stopped() {
|
||||||
//ArrangerMode::H => todo!("horizontal arranger"),
|
self.clock().play_from(Some(0))?;
|
||||||
//ArrangerMode::V(factor) => Self::render_mode_v(state, factor),
|
}
|
||||||
//}
|
} else if let ArrangerSelection::Clip(t, s) = self.selected {
|
||||||
//}
|
let clip = self.scenes[s].clips[t].clone();
|
||||||
//}
|
self.tracks[t].player.enqueue_next(clip.as_ref());
|
||||||
//render!(TuiOut: (self: Arranger) => {
|
};
|
||||||
//let pool_w = if self.pool.visible { self.splits[1] } else { 0 };
|
Ok(())
|
||||||
//let color = self.color;
|
}
|
||||||
//let layout = Bsp::a(Fill::xy(ArrangerStatus::from(self)),
|
pub fn clip (&self) -> Option<Arc<RwLock<MidiClip>>> {
|
||||||
//Bsp::n(Fixed::x(pool_w, PoolView(self.pool.visible, &self.pool)),
|
self.scene()?.clips.get(self.selected.track()?)?.clone()
|
||||||
//Bsp::n(TransportView::new(true, &self.clock),
|
}
|
||||||
//Bsp::s(Fixed::y(1, MidiEditStatus(&self.editor)),
|
pub fn toggle_loop (&mut self) {
|
||||||
//Bsp::n(Fill::x(Fixed::y(20,
|
if let Some(clip) = self.clip() {
|
||||||
//Bsp::a(Fill::xy(Tui::bg(color.darkest.rgb, "background")),
|
clip.write().unwrap().toggle_loop()
|
||||||
//Bsp::a(
|
}
|
||||||
//Fill::xy(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))),
|
}
|
||||||
//Self::render_mode(self))))), Fill::y(&"fixme: self.editor"))))));
|
pub fn randomize_color (&mut self) {
|
||||||
//self.size.of(layout)
|
match self.selected {
|
||||||
//});
|
ArrangerSelection::Mix => { self.color = ItemPalette::random() },
|
||||||
//Align::n(Fill::xy(lay!(
|
ArrangerSelection::Track(t) => { self.tracks[t].color = ItemPalette::random() },
|
||||||
//Align::n(Fill::xy(Tui::bg(self.color.darkest.rgb, " "))),
|
ArrangerSelection::Scene(s) => { self.scenes[s].color = ItemPalette::random() },
|
||||||
//Align::n(Fill::xy(ArrangerVRowSep::from((self, 1)))),
|
ArrangerSelection::Clip(t, s) => if let Some(clip) = &self.scenes[s].clips[t] {
|
||||||
//Align::n(Fill::xy(ArrangerVColSep::from(self))),
|
clip.write().unwrap().color = ItemPalette::random();
|
||||||
//Align::n(Fill::xy(ArrangerVClips::new(self, 1))),
|
}
|
||||||
//Align::n(Fill::xy(ArrangerVCursor::from((self, 1))))))))))))))));
|
}
|
||||||
//Align::n(Fill::xy(":")))))))))))));
|
}
|
||||||
//"todo:"))))))));
|
}
|
||||||
//Bsp::s(
|
|
||||||
//Align::n(Fixed::y(1, Fill::x(ArrangerVIns::from(self)))),
|
|
||||||
//Bsp::s(
|
|
||||||
//Fixed::y(20, Align::n(ArrangerVClips::new(self, 1))),
|
|
||||||
//Fill::x(Fixed::y(1, ArrangerVOuts::from(self)))))))))))));
|
|
||||||
//Bsp::s(
|
|
||||||
//Bsp::s(
|
|
||||||
//Bsp::s(
|
|
||||||
//Fill::xy(ArrangerVClips::new(self, 1)),
|
|
||||||
//Fill::x(ArrangerVOuts::from(self)))))
|
|
||||||
|
|
||||||
//let cell = phat_sel_3(
|
|
||||||
//selected_track == Some(i) && selected_scene == Some(j),
|
|
||||||
//Tui::fg(TuiTheme::g(64), Push::x(1, name)),
|
|
||||||
//Tui::fg(TuiTheme::g(64), Push::x(1, name)),
|
|
||||||
//if selected_track == Some(i) && selected_scene.map(|s|s+1) == Some(j) {
|
|
||||||
//None
|
|
||||||
//} else {
|
|
||||||
//Some(TuiTheme::g(32).into())
|
|
||||||
//},
|
|
||||||
//TuiTheme::g(32).into(),
|
|
||||||
//TuiTheme::g(32).into(),
|
|
||||||
//);
|
|
||||||
// TODO: port per track:
|
|
||||||
//for connection in midi_from.iter() {
|
|
||||||
//let mut split = connection.as_ref().split("=");
|
|
||||||
//let number = split.next().unwrap().trim();
|
|
||||||
//if let Ok(track) = number.parse::<usize>() {
|
|
||||||
//if track < 1 {
|
|
||||||
//panic!("Tracks start from 1")
|
|
||||||
//}
|
|
||||||
//if track > count {
|
|
||||||
//panic!("Tried to connect track {track} or {count}. Pass -t {track} to increase number of tracks.")
|
|
||||||
//}
|
|
||||||
//if let Some(port) = split.next() {
|
|
||||||
//if let Some(port) = jack.read().unwrap().client().port_by_name(port).as_ref() {
|
|
||||||
////jack.read().unwrap().client().connect_ports(port, &self.tracks[track-1].player.midi_ins[0])?;
|
|
||||||
//} else {
|
|
||||||
//panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names.");
|
|
||||||
//}
|
|
||||||
//} else {
|
|
||||||
//panic!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME")
|
|
||||||
//}
|
|
||||||
//} else {
|
|
||||||
//panic!("Failed to parse track number: {number}")
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//for connection in midi_to.iter() {
|
|
||||||
//let mut split = connection.as_ref().split("=");
|
|
||||||
//let number = split.next().unwrap().trim();
|
|
||||||
//if let Ok(track) = number.parse::<usize>() {
|
|
||||||
//if track < 1 {
|
|
||||||
//panic!("Tracks start from 1")
|
|
||||||
//}
|
|
||||||
//if track > count {
|
|
||||||
//panic!("Tried to connect track {track} or {count}. Pass -t {track} to increase number of tracks.")
|
|
||||||
//}
|
|
||||||
//if let Some(port) = split.next() {
|
|
||||||
//if let Some(port) = jack.read().unwrap().client().port_by_name(port).as_ref() {
|
|
||||||
////jack.read().unwrap().client().connect_ports(&self.tracks[track-1].player.midi_outs[0], port)?;
|
|
||||||
//} else {
|
|
||||||
//panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names.");
|
|
||||||
//}
|
|
||||||
//} else {
|
|
||||||
//panic!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME")
|
|
||||||
//}
|
|
||||||
//} else {
|
|
||||||
//panic!("Failed to parse track number: {number}")
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
|
||||||
102
tek/src/audio.rs
102
tek/src/audio.rs
|
|
@ -1,4 +1,105 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
impl HasJack for App {
|
||||||
|
fn jack (&self) -> &Arc<RwLock<JackConnection>> { &self.jack }
|
||||||
|
}
|
||||||
|
impl HasJack for Arranger {
|
||||||
|
fn jack (&self) -> &Arc<RwLock<JackConnection>> { &self.jack }
|
||||||
|
}
|
||||||
|
audio!(|self: App, client, scope|{
|
||||||
|
// Start profiling cycle
|
||||||
|
let t0 = self.perf.get_t0();
|
||||||
|
// Update transport clock
|
||||||
|
if Control::Quit == ClockAudio(self).process(client, scope) {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
// Collect MIDI input (TODO preallocate)
|
||||||
|
let midi_in = self.midi_ins.iter()
|
||||||
|
.map(|port|port.port.iter(scope)
|
||||||
|
.map(|RawMidi { time, bytes }|(time, LiveEvent::parse(bytes)))
|
||||||
|
.collect::<Vec<_>>())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
// Update standalone MIDI sequencer
|
||||||
|
if let Some(player) = self.player.as_mut() {
|
||||||
|
if Control::Quit == PlayerAudio(
|
||||||
|
player,
|
||||||
|
&mut self.note_buf,
|
||||||
|
&mut self.midi_buf,
|
||||||
|
).process(client, scope) {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Update standalone sampler
|
||||||
|
if let Some(sampler) = self.sampler.as_mut() {
|
||||||
|
if Control::Quit == SamplerAudio(sampler).process(client, scope) {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
//for port in midi_in.iter() {
|
||||||
|
//for message in port.iter() {
|
||||||
|
//match message {
|
||||||
|
//Ok(M
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
// TODO move these to editor and sampler?:
|
||||||
|
for port in midi_in.iter() {
|
||||||
|
for event in port.iter() {
|
||||||
|
match event {
|
||||||
|
(time, Ok(LiveEvent::Midi {message, ..})) => match message {
|
||||||
|
MidiMessage::NoteOn {ref key, ..} if let Some(editor) = self.editor.as_ref() => {
|
||||||
|
editor.set_note_point(key.as_int() as usize);
|
||||||
|
},
|
||||||
|
MidiMessage::Controller {controller, value} if let (Some(editor), Some(sampler)) = (
|
||||||
|
self.editor.as_ref(),
|
||||||
|
self.sampler.as_ref(),
|
||||||
|
) => {
|
||||||
|
// TODO: give sampler its own cursor
|
||||||
|
if let Some(sample) = &sampler.mapped[editor.note_point()] {
|
||||||
|
sample.write().unwrap().handle_cc(*controller, *value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ =>{}
|
||||||
|
},
|
||||||
|
_ =>{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update track sequencers
|
||||||
|
let tracks = &mut self.tracks;
|
||||||
|
let note_buf = &mut self.note_buf;
|
||||||
|
let midi_buf = &mut self.midi_buf;
|
||||||
|
if Control::Quit == TracksAudio(tracks, note_buf, midi_buf).process(client, scope) {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: update timeline position in editor.
|
||||||
|
// must be in sync with clip's playback. since
|
||||||
|
// a clip can be on multiple tracks and launched
|
||||||
|
// at different times, add a playhead with the
|
||||||
|
// playing track's color.
|
||||||
|
//self.now.set(0.);
|
||||||
|
//if let ArrangerSelection::Clip(t, s) = self.selected {
|
||||||
|
//let clip = self.scenes.get(s).map(|scene|scene.clips.get(t));
|
||||||
|
//if let Some(Some(Some(clip))) = clip {
|
||||||
|
//if let Some(track) = self.tracks().get(t) {
|
||||||
|
//if let Some((ref started_at, Some(ref playing))) = track.player.play_clip {
|
||||||
|
//let clip = clip.read().unwrap();
|
||||||
|
//if *playing.read().unwrap() == *clip {
|
||||||
|
//let pulse = self.current().pulse.get();
|
||||||
|
//let start = started_at.pulse.get();
|
||||||
|
//let now = (pulse - start) % clip.length as f64;
|
||||||
|
//self.now.set(now);
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
// End profiling cycle
|
||||||
|
self.perf.update(t0, scope);
|
||||||
|
Control::Continue
|
||||||
|
});
|
||||||
|
|
||||||
audio!(|self: Sequencer, client, scope|{
|
audio!(|self: Sequencer, client, scope|{
|
||||||
// Start profiling cycle
|
// Start profiling cycle
|
||||||
|
|
@ -8,7 +109,6 @@ audio!(|self: Sequencer, client, scope|{
|
||||||
if Control::Quit == ClockAudio(self).process(client, scope) {
|
if Control::Quit == ClockAudio(self).process(client, scope) {
|
||||||
return Control::Quit
|
return Control::Quit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update MIDI sequencer
|
// Update MIDI sequencer
|
||||||
if Control::Quit == PlayerAudio(
|
if Control::Quit == PlayerAudio(
|
||||||
&mut self.player, &mut self.note_buf, &mut self.midi_buf
|
&mut self.player, &mut self.note_buf, &mut self.midi_buf
|
||||||
|
|
|
||||||
|
|
@ -153,12 +153,51 @@ impl TrackCommand {
|
||||||
}
|
}
|
||||||
command!(|self: AppCommand, state: App|match self {
|
command!(|self: AppCommand, state: App|match self {
|
||||||
Self::Clear => { todo!() },
|
Self::Clear => { todo!() },
|
||||||
Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?,
|
|
||||||
Self::History(delta) => { todo!("undo/redo") },
|
|
||||||
Self::Select(s) => { state.selected = s; None },
|
|
||||||
Self::Scene(cmd) => cmd.delegate(state, Self::Scene)?,
|
|
||||||
Self::Track(cmd) => cmd.delegate(state, Self::Track)?,
|
|
||||||
Self::Zoom(_) => { todo!(); },
|
Self::Zoom(_) => { todo!(); },
|
||||||
|
Self::History(delta) => { todo!("undo/redo") },
|
||||||
|
|
||||||
|
Self::Select(s) => { state.selected = s; None },
|
||||||
|
Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?,
|
||||||
|
Self::Scene(cmd) => match cmd {
|
||||||
|
SceneCommand::Add => { state.scene_add(None, None)?; None }
|
||||||
|
SceneCommand::Del(index) => { state.scene_del(index); None },
|
||||||
|
SceneCommand::SetColor(index, color) => {
|
||||||
|
let old = state.scenes[index].color;
|
||||||
|
state.scenes[index].color = color;
|
||||||
|
Some(SceneCommand::SetColor(index, old))
|
||||||
|
},
|
||||||
|
SceneCommand::Enqueue(scene) => {
|
||||||
|
for track in 0..state.tracks.len() {
|
||||||
|
state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref());
|
||||||
|
}
|
||||||
|
None
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
}.map(Self::Scene),
|
||||||
|
Self::Track(cmd) => match cmd {
|
||||||
|
TrackCommand::Add => { state.track_add(None, None)?; None },
|
||||||
|
TrackCommand::Del(index) => { state.track_del(index); None },
|
||||||
|
TrackCommand::Stop(track) => { state.tracks[track].player.enqueue_next(None); None },
|
||||||
|
TrackCommand::SetColor(index, color) => {
|
||||||
|
let old = state.tracks[index].color;
|
||||||
|
state.tracks[index].color = color;
|
||||||
|
Some(TrackCommand::SetColor(index, old))
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
}.map(Self::Track),
|
||||||
|
Self::Clip(cmd) => match cmd {
|
||||||
|
ClipCommand::Get(track, scene) => { todo!() },
|
||||||
|
ClipCommand::Put(track, scene, clip) => {
|
||||||
|
let old = state.scenes[scene].clips[track].clone();
|
||||||
|
state.scenes[scene].clips[track] = clip;
|
||||||
|
Some(ClipCommand::Put(track, scene, old))
|
||||||
|
},
|
||||||
|
ClipCommand::Enqueue(track, scene) => {
|
||||||
|
state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref());
|
||||||
|
None
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
}.map(Self::Clip),
|
||||||
|
|
||||||
Self::Editor(cmd) =>
|
Self::Editor(cmd) =>
|
||||||
state.editor.as_mut().map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(),
|
state.editor.as_mut().map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(),
|
||||||
|
|
@ -176,20 +215,20 @@ command!(|self: AppCommand, state: App|match self {
|
||||||
Some(Self::Color(old))
|
Some(Self::Color(old))
|
||||||
},
|
},
|
||||||
|
|
||||||
Self::Pool(cmd) => match cmd {
|
Self::Pool(cmd) => if let Some(pool) = state.pool.as_mut() {
|
||||||
// autoselect: automatically load selected clip in editor
|
let undo = cmd.clone().delegate(pool, Self::Pool)?;
|
||||||
PoolCommand::Select(_) => {
|
if let Some(editor) = state.editor.as_mut() {
|
||||||
let undo = cmd.delegate(&mut state.pool, Self::Pool)?;
|
match cmd {
|
||||||
state.editor.set_clip(state.pool.clip().as_ref());
|
// autoselect: automatically load selected clip in editor
|
||||||
undo
|
// autocolor: update color in all places simultaneously
|
||||||
},
|
PoolCommand::Select(_) | PoolCommand::Clip(PoolCmd::SetColor(_, _)) =>
|
||||||
// update color in all places simultaneously
|
editor.set_clip(pool.clip().as_ref()),
|
||||||
PoolCommand::Clip(PoolCmd::SetColor(index, _)) => {
|
_ => {}
|
||||||
let undo = cmd.delegate(&mut state.pool, Self::Pool)?;
|
}
|
||||||
state.editor.set_clip(state.pool.clip().as_ref());
|
};
|
||||||
undo
|
undo
|
||||||
},
|
} else {
|
||||||
_ => cmd.delegate(&mut state.pool, Self::Pool)?
|
None
|
||||||
},
|
},
|
||||||
|
|
||||||
Self::Compact(compact) => if state.compact != compact {
|
Self::Compact(compact) => if state.compact != compact {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
#![allow(clippy::unit_arg)]
|
#![allow(clippy::unit_arg)]
|
||||||
#![feature(adt_const_params)]
|
#![feature(adt_const_params)]
|
||||||
#![feature(type_alias_impl_trait)]
|
|
||||||
#![feature(impl_trait_in_assoc_type)]
|
|
||||||
#![feature(associated_type_defaults)]
|
#![feature(associated_type_defaults)]
|
||||||
|
#![feature(if_let_guard)]
|
||||||
|
#![feature(impl_trait_in_assoc_type)]
|
||||||
|
#![feature(type_alias_impl_trait)]
|
||||||
|
|
||||||
/// Standard result type.
|
/// Standard result type.
|
||||||
pub type Usually<T> = std::result::Result<T, Box<dyn Error>>;
|
pub type Usually<T> = std::result::Result<T, Box<dyn Error>>;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ use crate::*;
|
||||||
pub splits: Vec<u16>,
|
pub splits: Vec<u16>,
|
||||||
pub size: Measure<TuiOut>,
|
pub size: Measure<TuiOut>,
|
||||||
pub perf: PerfModel,
|
pub perf: PerfModel,
|
||||||
|
pub compact: bool,
|
||||||
}
|
}
|
||||||
impl App {
|
impl App {
|
||||||
pub fn sequencer (
|
pub fn sequencer (
|
||||||
|
|
@ -88,84 +89,8 @@ impl App {
|
||||||
}
|
}
|
||||||
has_size!(<TuiOut>|self: App|&self.size);
|
has_size!(<TuiOut>|self: App|&self.size);
|
||||||
has_clock!(|self: App|&self.clock);
|
has_clock!(|self: App|&self.clock);
|
||||||
has_clips!(|self: App|self.pool.clips);
|
has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips);
|
||||||
has_editor!(|self: App|self.editor);
|
has_editor!(|self: App|self.editor.as_ref().expect("no editor"));
|
||||||
|
|
||||||
#[derive(Debug)] pub struct ArrangerTrack {
|
|
||||||
/// Name of track
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Preferred width of track column
|
|
||||||
pub width: usize,
|
|
||||||
/// Identifying color of track
|
|
||||||
pub color: ItemPalette,
|
|
||||||
/// MIDI player state
|
|
||||||
pub player: MidiPlayer,
|
|
||||||
}
|
|
||||||
has_clock!(|self:ArrangerTrack|self.player.clock());
|
|
||||||
has_player!(|self:ArrangerTrack|self.player);
|
|
||||||
#[derive(Default)] pub struct ArrangerScene {
|
|
||||||
/// Name of scene
|
|
||||||
pub(crate) name: Arc<str>,
|
|
||||||
/// Clips in scene, one per track
|
|
||||||
pub(crate) clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
|
|
||||||
/// Identifying color of scene
|
|
||||||
pub(crate) color: ItemPalette,
|
|
||||||
}
|
|
||||||
#[derive(PartialEq, Clone, Copy, Debug, Default)]
|
|
||||||
/// Represents the current user selection in the arranger
|
|
||||||
pub enum ArrangerSelection {
|
|
||||||
/// The whole mix is selected
|
|
||||||
#[default] Mix,
|
|
||||||
/// A track is selected.
|
|
||||||
Track(usize),
|
|
||||||
/// A scene is selected.
|
|
||||||
Scene(usize),
|
|
||||||
/// A clip (track × scene) is selected.
|
|
||||||
Clip(usize, usize),
|
|
||||||
}
|
|
||||||
/// Focus identification methods
|
|
||||||
impl ArrangerSelection {
|
|
||||||
pub fn is_mix (&self) -> bool { matches!(self, Self::Mix) }
|
|
||||||
pub fn is_track (&self) -> bool { matches!(self, Self::Track(_)) }
|
|
||||||
pub fn is_scene (&self) -> bool { matches!(self, Self::Scene(_)) }
|
|
||||||
pub fn is_clip (&self) -> bool { matches!(self, Self::Clip(_, _)) }
|
|
||||||
pub fn description (
|
|
||||||
&self,
|
|
||||||
tracks: &[ArrangerTrack],
|
|
||||||
scenes: &[ArrangerScene],
|
|
||||||
) -> Arc<str> {
|
|
||||||
format!("Selected: {}", match self {
|
|
||||||
Self::Mix => "Everything".to_string(),
|
|
||||||
Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name))
|
|
||||||
.unwrap_or_else(||"T??".into()),
|
|
||||||
Self::Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name))
|
|
||||||
.unwrap_or_else(||"S??".into()),
|
|
||||||
Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) {
|
|
||||||
(Some(_), Some(scene)) => match scene.clip(*t) {
|
|
||||||
Some(clip) => format!("T{t} S{s} C{}", &clip.read().unwrap().name),
|
|
||||||
None => format!("T{t} S{s}: Empty")
|
|
||||||
},
|
|
||||||
_ => format!("T{t} S{s}: Empty"),
|
|
||||||
}
|
|
||||||
}).into()
|
|
||||||
}
|
|
||||||
pub fn track (&self) -> Option<usize> {
|
|
||||||
use ArrangerSelection::*;
|
|
||||||
match self {
|
|
||||||
Clip(t, _) => Some(*t),
|
|
||||||
Track(t) => Some(*t),
|
|
||||||
_ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn scene (&self) -> Option<usize> {
|
|
||||||
use ArrangerSelection::*;
|
|
||||||
match self {
|
|
||||||
Clip(_, s) => Some(*s),
|
|
||||||
Scene(s) => Some(*s),
|
|
||||||
_ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)] pub struct Sequencer {
|
#[derive(Default)] pub struct Sequencer {
|
||||||
pub jack: Arc<RwLock<JackConnection>>,
|
pub jack: Arc<RwLock<JackConnection>>,
|
||||||
|
|
|
||||||
1
tek/src/select.rs
Normal file
1
tek/src/select.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
use crate::*;
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
render!(TuiOut: (self: App) => self.size.of(EdnView::from_source(self, self.edn.as_ref())));
|
render!(TuiOut: (self: App) => self.size.of(EdnView::from_source(self, self.edn.as_ref())));
|
||||||
audio!(|self: App, _client, _scope|Control::Continue);
|
|
||||||
impl EdnViewData<TuiOut> for &App {
|
impl EdnViewData<TuiOut> for &App {
|
||||||
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::*;
|
||||||
|
|
@ -38,7 +37,7 @@ impl EdnViewData<TuiOut> for &App {
|
||||||
impl App {
|
impl App {
|
||||||
fn compact (&self) -> bool { false }
|
fn compact (&self) -> bool { false }
|
||||||
fn toolbar (&self) -> impl Content<TuiOut> + use<'_> {
|
fn toolbar (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.clock))))
|
Fill::x(Fixed::y(2, Align::x(ClockView::new(true, &self.clock))))
|
||||||
}
|
}
|
||||||
fn status (&self, note_pt: usize) -> impl Content<TuiOut> + use<'_> {
|
fn status (&self, note_pt: usize) -> impl Content<TuiOut> + use<'_> {
|
||||||
self.editor.as_ref()
|
self.editor.as_ref()
|
||||||
|
|
@ -356,7 +355,7 @@ impl EdnViewData<TuiOut> for &Sequencer {
|
||||||
impl Sequencer {
|
impl Sequencer {
|
||||||
const EDN: &'static str = include_str!("../edn/sequencer.edn");
|
const EDN: &'static str = include_str!("../edn/sequencer.edn");
|
||||||
fn toolbar_view (&self) -> impl Content<TuiOut> + use<'_> {
|
fn toolbar_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.player.clock))))
|
Fill::x(Fixed::y(2, Align::x(ClockView::new(true, &self.player.clock))))
|
||||||
}
|
}
|
||||||
fn status_view (&self) -> impl Content<TuiOut> + use<'_> {
|
fn status_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
Bsp::e(
|
Bsp::e(
|
||||||
|
|
@ -434,7 +433,7 @@ impl Groovebox {
|
||||||
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(ClockView::new(true, &self.player.clock)),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
fn status (&self) -> impl Content<TuiOut> + use<'_> {
|
fn status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
|
@ -604,7 +603,7 @@ impl Arranger {
|
||||||
pub const TRACK_MIN_WIDTH: usize = 9;
|
pub const TRACK_MIN_WIDTH: usize = 9;
|
||||||
|
|
||||||
fn toolbar (&self) -> impl Content<TuiOut> + use<'_> {
|
fn toolbar (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.clock))))
|
Fill::x(Fixed::y(2, Align::x(ClockView::new(true, &self.clock))))
|
||||||
}
|
}
|
||||||
fn pool (&self) -> impl Content<TuiOut> + use<'_> {
|
fn pool (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact, &self.pool)))
|
Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact, &self.pool)))
|
||||||
|
|
|
||||||
|
|
@ -2,31 +2,31 @@ use crate::*;
|
||||||
use KeyCode::*;
|
use KeyCode::*;
|
||||||
use ClockCommand::{Play, Pause};
|
use ClockCommand::{Play, Pause};
|
||||||
/// Transport clock app.
|
/// Transport clock app.
|
||||||
pub struct TransportTui {
|
pub struct ClockTui {
|
||||||
pub jack: Arc<RwLock<JackConnection>>,
|
pub jack: Arc<RwLock<JackConnection>>,
|
||||||
pub clock: Clock,
|
pub clock: Clock,
|
||||||
}
|
}
|
||||||
handle!(TuiIn: |self: TransportTui, input|ClockCommand::execute_with_state(self, input.event()));
|
handle!(TuiIn: |self: ClockTui, input|ClockCommand::execute_with_state(self, input.event()));
|
||||||
keymap!(TRANSPORT_KEYS = |state: TransportTui, input: Event| ClockCommand {
|
keymap!(TRANSPORT_KEYS = |state: ClockTui, input: Event| ClockCommand {
|
||||||
key(Char(' ')) =>
|
key(Char(' ')) =>
|
||||||
if state.clock().is_stopped() { Play(None) } else { Pause(None) },
|
if state.clock().is_stopped() { Play(None) } else { Pause(None) },
|
||||||
shift(key(Char(' '))) =>
|
shift(key(Char(' '))) =>
|
||||||
if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }
|
if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }
|
||||||
});
|
});
|
||||||
has_clock!(|self: TransportTui|&self.clock);
|
has_clock!(|self: ClockTui|&self.clock);
|
||||||
audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope));
|
audio!(|self: ClockTui, client, scope|ClockAudio(self).process(client, scope));
|
||||||
render!(TuiOut: (self: TransportTui) => TransportView {
|
render!(TuiOut: (self: ClockTui) => ClockView {
|
||||||
compact: false,
|
compact: false,
|
||||||
clock: &self.clock
|
clock: &self.clock
|
||||||
});
|
});
|
||||||
|
|
||||||
pub struct TransportView<'a> { pub compact: bool, pub clock: &'a Clock }
|
pub struct ClockView<'a> { pub compact: bool, pub clock: &'a Clock }
|
||||||
impl<'a> TransportView<'a> {
|
impl<'a> ClockView<'a> {
|
||||||
pub fn new (compact: bool, clock: &'a Clock) -> Self {
|
pub fn new (compact: bool, clock: &'a Clock) -> Self {
|
||||||
Self { compact, clock }
|
Self { compact, clock }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
render!(TuiOut: (self: TransportView<'a>) => Outer(
|
render!(TuiOut: (self: ClockView<'a>) => Outer(
|
||||||
Style::default().fg(TuiTheme::g(255))
|
Style::default().fg(TuiTheme::g(255))
|
||||||
).enclose(row!(
|
).enclose(row!(
|
||||||
OutputStats::new(self.compact, self.clock),
|
OutputStats::new(self.compact, self.clock),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue