mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
add handle! macro and enable groovebox
This commit is contained in:
parent
5c630cc51b
commit
a352141dde
13 changed files with 350 additions and 309 deletions
19
Justfile
19
Justfile
|
|
@ -1,9 +1,11 @@
|
||||||
default:
|
default:
|
||||||
just -l
|
just -l
|
||||||
|
|
||||||
status:
|
status:
|
||||||
cargo c
|
cargo c
|
||||||
cloc --by-file src/
|
cloc --by-file src/
|
||||||
git status
|
git status
|
||||||
|
|
||||||
push:
|
push:
|
||||||
git push -u codeberg main
|
git push -u codeberg main
|
||||||
git push -u origin main
|
git push -u origin main
|
||||||
|
|
@ -16,18 +18,35 @@ fpush:
|
||||||
ftpush:
|
ftpush:
|
||||||
git push --tags -fu codeberg
|
git push --tags -fu codeberg
|
||||||
git push --tags -fu origin
|
git push --tags -fu origin
|
||||||
|
|
||||||
transport:
|
transport:
|
||||||
reset
|
reset
|
||||||
cargo run --bin tek_transport
|
cargo run --bin tek_transport
|
||||||
|
transport-release:
|
||||||
|
reset
|
||||||
|
cargo run --release --bin tek_transport
|
||||||
|
|
||||||
arranger:
|
arranger:
|
||||||
reset
|
reset
|
||||||
cargo run --bin tek_arranger
|
cargo run --bin tek_arranger
|
||||||
|
arranger-release:
|
||||||
|
reset
|
||||||
|
cargo run --release --bin tek_arranger
|
||||||
|
|
||||||
|
groovebox:
|
||||||
|
reset
|
||||||
|
cargo run --bin tek_groovebox
|
||||||
|
groovebox-release:
|
||||||
|
reset
|
||||||
|
cargo run --release --bin tek_groovebox
|
||||||
|
|
||||||
sequencer:
|
sequencer:
|
||||||
reset
|
reset
|
||||||
cargo run --bin tek_sequencer
|
cargo run --bin tek_sequencer
|
||||||
sequencer-release:
|
sequencer-release:
|
||||||
reset
|
reset
|
||||||
cargo run --release --bin tek_sequencer
|
cargo run --release --bin tek_sequencer
|
||||||
|
|
||||||
mixer:
|
mixer:
|
||||||
reset
|
reset
|
||||||
cargo run --bin tek_mixer
|
cargo run --bin tek_mixer
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,10 @@ path = "src/cli/cli_arranger.rs"
|
||||||
name = "tek_sequencer"
|
name = "tek_sequencer"
|
||||||
path = "src/cli/cli_sequencer.rs"
|
path = "src/cli/cli_sequencer.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "tek_groovebox"
|
||||||
|
path = "src/cli/cli_groovebox.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "tek_transport"
|
name = "tek_transport"
|
||||||
path = "src/cli/cli_transport.rs"
|
path = "src/cli/cli_transport.rs"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
include!("../lib.rs");
|
||||||
|
pub fn main () -> Usually<()> {
|
||||||
|
GrooveboxCli::parse().run()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
pub struct GrooveboxCli;
|
||||||
|
|
||||||
|
impl GrooveboxCli {
|
||||||
|
fn run (&self) -> Usually<()> {
|
||||||
|
Tui::run(JackClient::new("tek_groovebox")?.activate_with(|jack|{
|
||||||
|
let midi_in_1 = jack.read().unwrap().register_port("in1", MidiIn::default())?;
|
||||||
|
let midi_out = jack.read().unwrap().register_port("out", MidiOut::default())?;
|
||||||
|
let midi_in_2 = jack.read().unwrap().register_port("in2", MidiIn::default())?;
|
||||||
|
let audio_in_1 = jack.read().unwrap().register_port("inL", AudioIn::default())?;
|
||||||
|
let audio_in_2 = jack.read().unwrap().register_port("inR", AudioIn::default())?;
|
||||||
|
let audio_out_1 = jack.read().unwrap().register_port("out1", AudioOut::default())?;
|
||||||
|
let audio_out_2 = jack.read().unwrap().register_port("out2", AudioOut::default())?;
|
||||||
|
let mut app = GrooveboxTui::try_from(jack)?;
|
||||||
|
Ok(app)
|
||||||
|
})?)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,16 @@ pub trait Handle<E: Engine>: Send + Sync {
|
||||||
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled>;
|
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! handle {
|
||||||
|
(<$E:ty>|$self:ident:$Struct:ty,$input:ident|$handler:expr) => {
|
||||||
|
impl Handle<$E> for $Struct {
|
||||||
|
fn handle (&mut $self, $input: &<$E as Engine>::Input) -> Perhaps<<$E as Engine>::Handled> {
|
||||||
|
$handler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<E: Engine, H: Handle<E>> Handle<E> for &mut H {
|
impl<E: Engine, H: Handle<E>> Handle<E> for &mut H {
|
||||||
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
|
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
|
||||||
(*self).handle(context)
|
(*self).handle(context)
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,10 @@ pub(crate) use ratatui::{
|
||||||
|
|
||||||
pub(crate) use jack;
|
pub(crate) use jack;
|
||||||
pub(crate) use jack::{
|
pub(crate) use jack::{
|
||||||
Client, ProcessScope, Control, CycleTimes,
|
|
||||||
Port, PortSpec, MidiIn, MidiOut, AudioOut, Unowned,
|
|
||||||
Transport, TransportState, MidiIter, RawMidi,
|
|
||||||
contrib::ClosureProcessHandler,
|
contrib::ClosureProcessHandler,
|
||||||
|
Client, ProcessScope, Control, CycleTimes,
|
||||||
|
Port, PortSpec, MidiIn, MidiOut, AudioIn, AudioOut, Unowned,
|
||||||
|
Transport, TransportState, MidiIter, RawMidi,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) use midly;
|
pub(crate) use midly;
|
||||||
|
|
|
||||||
|
|
@ -164,88 +164,85 @@ impl Content<Tui> for Track<Tui> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handle<Tui> for Mixer<Tui> {
|
handle!(<Tui>|self:Mixer,engine|{
|
||||||
fn handle (&mut self, engine: &TuiInput) -> Perhaps<bool> {
|
if let TuiEvent::Input(crossterm::event::Event::Key(event)) = engine.event() {
|
||||||
if let TuiEvent::Input(crossterm::event::Event::Key(event)) = engine.event() {
|
|
||||||
|
|
||||||
match event.code {
|
match event.code {
|
||||||
//KeyCode::Char('c') => {
|
//KeyCode::Char('c') => {
|
||||||
//if event.modifiers == KeyModifiers::CONTROL {
|
//if event.modifiers == KeyModifiers::CONTROL {
|
||||||
//self.exit();
|
//self.exit();
|
||||||
//}
|
//}
|
||||||
//},
|
//},
|
||||||
KeyCode::Down => {
|
KeyCode::Down => {
|
||||||
self.selected_track = (self.selected_track + 1) % self.tracks.len();
|
self.selected_track = (self.selected_track + 1) % self.tracks.len();
|
||||||
println!("{}", self.selected_track);
|
println!("{}", self.selected_track);
|
||||||
return Ok(Some(true))
|
return Ok(Some(true))
|
||||||
},
|
},
|
||||||
KeyCode::Up => {
|
KeyCode::Up => {
|
||||||
if self.selected_track == 0 {
|
if self.selected_track == 0 {
|
||||||
self.selected_track = self.tracks.len() - 1;
|
self.selected_track = self.tracks.len() - 1;
|
||||||
} else {
|
} else {
|
||||||
self.selected_track -= 1;
|
self.selected_track -= 1;
|
||||||
}
|
|
||||||
println!("{}", self.selected_track);
|
|
||||||
return Ok(Some(true))
|
|
||||||
},
|
|
||||||
KeyCode::Left => {
|
|
||||||
if self.selected_column == 0 {
|
|
||||||
self.selected_column = 6
|
|
||||||
} else {
|
|
||||||
self.selected_column -= 1;
|
|
||||||
}
|
|
||||||
return Ok(Some(true))
|
|
||||||
},
|
|
||||||
KeyCode::Right => {
|
|
||||||
if self.selected_column == 6 {
|
|
||||||
self.selected_column = 0
|
|
||||||
} else {
|
|
||||||
self.selected_column += 1;
|
|
||||||
}
|
|
||||||
return Ok(Some(true))
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
println!("\n{event:?}");
|
|
||||||
}
|
}
|
||||||
|
println!("{}", self.selected_track);
|
||||||
|
return Ok(Some(true))
|
||||||
|
},
|
||||||
|
KeyCode::Left => {
|
||||||
|
if self.selected_column == 0 {
|
||||||
|
self.selected_column = 6
|
||||||
|
} else {
|
||||||
|
self.selected_column -= 1;
|
||||||
|
}
|
||||||
|
return Ok(Some(true))
|
||||||
|
},
|
||||||
|
KeyCode::Right => {
|
||||||
|
if self.selected_column == 6 {
|
||||||
|
self.selected_column = 0
|
||||||
|
} else {
|
||||||
|
self.selected_column += 1;
|
||||||
|
}
|
||||||
|
return Ok(Some(true))
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
println!("\n{event:?}");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
}
|
||||||
}
|
Ok(None)
|
||||||
impl Handle<Tui> for Track<Tui> {
|
});
|
||||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
|
||||||
match from.event() {
|
handle!(<Tui>|self:Track<Tui>,from|{
|
||||||
//, NONE, "chain_cursor_up", "move cursor up", || {
|
match from.event() {
|
||||||
key!(KeyCode::Up) => {
|
//, NONE, "chain_cursor_up", "move cursor up", || {
|
||||||
Ok(Some(true))
|
key!(KeyCode::Up) => {
|
||||||
},
|
Ok(Some(true))
|
||||||
// , NONE, "chain_cursor_down", "move cursor down", || {
|
},
|
||||||
key!(KeyCode::Down) => {
|
// , NONE, "chain_cursor_down", "move cursor down", || {
|
||||||
Ok(Some(true))
|
key!(KeyCode::Down) => {
|
||||||
},
|
Ok(Some(true))
|
||||||
// Left, NONE, "chain_cursor_left", "move cursor left", || {
|
},
|
||||||
key!(KeyCode::Left) => {
|
// Left, NONE, "chain_cursor_left", "move cursor left", || {
|
||||||
//if let Some(track) = app.arranger.track_mut() {
|
key!(KeyCode::Left) => {
|
||||||
//track.device = track.device.saturating_sub(1);
|
//if let Some(track) = app.arranger.track_mut() {
|
||||||
//return Ok(true)
|
//track.device = track.device.saturating_sub(1);
|
||||||
//}
|
//return Ok(true)
|
||||||
Ok(Some(true))
|
//}
|
||||||
},
|
Ok(Some(true))
|
||||||
// , NONE, "chain_cursor_right", "move cursor right", || {
|
},
|
||||||
key!(KeyCode::Right) => {
|
// , NONE, "chain_cursor_right", "move cursor right", || {
|
||||||
//if let Some(track) = app.arranger.track_mut() {
|
key!(KeyCode::Right) => {
|
||||||
//track.device = (track.device + 1).min(track.devices.len().saturating_sub(1));
|
//if let Some(track) = app.arranger.track_mut() {
|
||||||
//return Ok(true)
|
//track.device = (track.device + 1).min(track.devices.len().saturating_sub(1));
|
||||||
//}
|
//return Ok(true)
|
||||||
Ok(Some(true))
|
//}
|
||||||
},
|
Ok(Some(true))
|
||||||
// , NONE, "chain_mode_switch", "switch the display mode", || {
|
},
|
||||||
key!(KeyCode::Char('`')) => {
|
// , NONE, "chain_mode_switch", "switch the display mode", || {
|
||||||
//app.chain_mode = !app.chain_mode;
|
key!(KeyCode::Char('`')) => {
|
||||||
Ok(Some(true))
|
//app.chain_mode = !app.chain_mode;
|
||||||
},
|
Ok(Some(true))
|
||||||
_ => Ok(None)
|
},
|
||||||
}
|
_ => Ok(None)
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
|
||||||
|
|
@ -83,66 +83,65 @@ fn draw_header <E> (state: &Plugin<E>, to: &mut TuiOutput, x: u16, y: u16, w: u1
|
||||||
Ok(Rect { x, y, width: w, height: 1 })
|
Ok(Rect { x, y, width: w, height: 1 })
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handle<Tui> for Plugin<Tui> {
|
handle!(<Tui>|self:Plugin<tui>,from|{
|
||||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
match from.event() {
|
||||||
match from.event() {
|
key!(KeyCode::Up) => {
|
||||||
key!(KeyCode::Up) => {
|
self.selected = self.selected.saturating_sub(1);
|
||||||
self.selected = self.selected.saturating_sub(1);
|
Ok(Some(true))
|
||||||
Ok(Some(true))
|
},
|
||||||
},
|
key!(KeyCode::Down) => {
|
||||||
key!(KeyCode::Down) => {
|
self.selected = (self.selected + 1).min(match &self.plugin {
|
||||||
self.selected = (self.selected + 1).min(match &self.plugin {
|
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
_ => unimplemented!()
|
||||||
_ => unimplemented!()
|
});
|
||||||
});
|
Ok(Some(true))
|
||||||
Ok(Some(true))
|
},
|
||||||
},
|
key!(KeyCode::PageUp) => {
|
||||||
key!(KeyCode::PageUp) => {
|
self.selected = self.selected.saturating_sub(8);
|
||||||
self.selected = self.selected.saturating_sub(8);
|
Ok(Some(true))
|
||||||
Ok(Some(true))
|
},
|
||||||
},
|
key!(KeyCode::PageDown) => {
|
||||||
key!(KeyCode::PageDown) => {
|
self.selected = (self.selected + 10).min(match &self.plugin {
|
||||||
self.selected = (self.selected + 10).min(match &self.plugin {
|
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
_ => unimplemented!()
|
||||||
_ => unimplemented!()
|
});
|
||||||
});
|
Ok(Some(true))
|
||||||
Ok(Some(true))
|
},
|
||||||
},
|
key!(KeyCode::Char(',')) => {
|
||||||
key!(KeyCode::Char(',')) => {
|
match self.plugin.as_mut() {
|
||||||
match self.plugin.as_mut() {
|
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
let index = port_list[self.selected].index;
|
||||||
let index = port_list[self.selected].index;
|
if let Some(value) = instance.control_input(index) {
|
||||||
if let Some(value) = instance.control_input(index) {
|
instance.set_control_input(index, value - 0.01);
|
||||||
instance.set_control_input(index, value - 0.01);
|
}
|
||||||
}
|
},
|
||||||
},
|
_ => {}
|
||||||
_ => {}
|
}
|
||||||
}
|
Ok(Some(true))
|
||||||
Ok(Some(true))
|
},
|
||||||
},
|
key!(KeyCode::Char('.')) => {
|
||||||
key!(KeyCode::Char('.')) => {
|
match self.plugin.as_mut() {
|
||||||
match self.plugin.as_mut() {
|
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
let index = port_list[self.selected].index;
|
||||||
let index = port_list[self.selected].index;
|
if let Some(value) = instance.control_input(index) {
|
||||||
if let Some(value) = instance.control_input(index) {
|
instance.set_control_input(index, value + 0.01);
|
||||||
instance.set_control_input(index, value + 0.01);
|
}
|
||||||
}
|
},
|
||||||
},
|
_ => {}
|
||||||
_ => {}
|
}
|
||||||
}
|
Ok(Some(true))
|
||||||
Ok(Some(true))
|
},
|
||||||
},
|
key!(KeyCode::Char('g')) => {
|
||||||
key!(KeyCode::Char('g')) => {
|
match self.plugin {
|
||||||
match self.plugin {
|
Some(PluginKind::LV2(ref mut plugin)) => {
|
||||||
Some(PluginKind::LV2(ref mut plugin)) => {
|
plugin.ui_thread = Some(run_lv2_ui(LV2PluginUI::new()?)?);
|
||||||
plugin.ui_thread = Some(run_lv2_ui(LV2PluginUI::new()?)?);
|
},
|
||||||
},
|
Some(_) => unreachable!(),
|
||||||
Some(_) => unreachable!(),
|
None => {}
|
||||||
None => {}
|
}
|
||||||
}
|
Ok(Some(true))
|
||||||
Ok(Some(true))
|
},
|
||||||
},
|
_ => Ok(None)
|
||||||
_ => Ok(None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,94 @@ pub struct ArrangerTui {
|
||||||
pub perf: PerfModel,
|
pub perf: PerfModel,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handle<Tui> for ArrangerTui {
|
has_clock!(|self:ArrangerTui|&self.clock);
|
||||||
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
has_phrases!(|self:ArrangerTui|self.phrases.phrases);
|
||||||
ArrangerCommand::execute_with_state(self, i)
|
has_editor!(|self:ArrangerTui|self.editor);
|
||||||
|
handle!(<Tui>|self: ArrangerTui, input|ArrangerCommand::execute_with_state(self, input));
|
||||||
|
render!(|self: ArrangerTui|{
|
||||||
|
let arranger_focused = self.arranger_focused();
|
||||||
|
let transport_focused = if let ArrangerFocus::Transport(_) = self.focus.inner() {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
let transport = TransportView::from((self, None, transport_focused));
|
||||||
|
let with_transport = move|x|col!([transport, x]);
|
||||||
|
let border = Lozenge(Style::default()
|
||||||
|
.bg(TuiTheme::border_bg())
|
||||||
|
.fg(TuiTheme::border_fg(arranger_focused)));
|
||||||
|
let arranger = move||border.wrap(Tui::grow_y(1, lay!(|add|{
|
||||||
|
match self.mode {
|
||||||
|
ArrangerMode::Horizontal => add(&arranger_content_horizontal(self))?,
|
||||||
|
ArrangerMode::Vertical(factor) => add(&arranger_content_vertical(self, factor))?
|
||||||
|
};
|
||||||
|
add(&self.size)
|
||||||
|
})));
|
||||||
|
with_transport(col!([
|
||||||
|
Tui::fixed_y(self.splits[0], lay!([
|
||||||
|
arranger(),
|
||||||
|
Tui::push_x(1, Tui::fg(
|
||||||
|
TuiTheme::title_fg(arranger_focused),
|
||||||
|
format!("[{}] Arranger", if self.entered {
|
||||||
|
"■"
|
||||||
|
} else {
|
||||||
|
" "
|
||||||
|
})
|
||||||
|
))
|
||||||
|
])),
|
||||||
|
Split::right(false, self.splits[1], PhraseListView(&self.phrases), &self.editor),
|
||||||
|
]))
|
||||||
|
});
|
||||||
|
audio!(|self: ArrangerTui, client, scope|{
|
||||||
|
// Start profiling cycle
|
||||||
|
let t0 = self.perf.get_t0();
|
||||||
|
// Update transport clock
|
||||||
|
if ClockAudio(self).process(client, scope) == Control::Quit {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
// Update MIDI sequencers
|
||||||
|
let tracks = &mut self.tracks;
|
||||||
|
let note_buf = &mut self.note_buf;
|
||||||
|
let midi_buf = &mut self.midi_buf;
|
||||||
|
if TracksAudio(tracks, note_buf, midi_buf, Default::default())
|
||||||
|
.process(client, scope) == Control::Quit {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
// FIXME: one of these per playing track
|
||||||
|
//self.now.set(0.);
|
||||||
|
//if let ArrangerSelection::Clip(t, s) = self.selected {
|
||||||
|
//let phrase = self.scenes().get(s).map(|scene|scene.clips.get(t));
|
||||||
|
//if let Some(Some(Some(phrase))) = phrase {
|
||||||
|
//if let Some(track) = self.tracks().get(t) {
|
||||||
|
//if let Some((ref started_at, Some(ref playing))) = track.player.play_phrase {
|
||||||
|
//let phrase = phrase.read().unwrap();
|
||||||
|
//if *playing.read().unwrap() == *phrase {
|
||||||
|
//let pulse = self.current().pulse.get();
|
||||||
|
//let start = started_at.pulse.get();
|
||||||
|
//let now = (pulse - start) % phrase.length as f64;
|
||||||
|
//self.now.set(now);
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
// End profiling cycle
|
||||||
|
self.perf.update(t0, scope);
|
||||||
|
return Control::Continue
|
||||||
|
});
|
||||||
|
|
||||||
|
impl HasPhraseList for ArrangerTui {
|
||||||
|
fn phrases_focused (&self) -> bool {
|
||||||
|
self.focused() == ArrangerFocus::Phrases
|
||||||
|
}
|
||||||
|
fn phrases_entered (&self) -> bool {
|
||||||
|
self.entered() && self.phrases_focused()
|
||||||
|
}
|
||||||
|
fn phrases_mode (&self) -> &Option<PhraseListMode> {
|
||||||
|
&self.phrases.mode
|
||||||
|
}
|
||||||
|
fn phrase_index (&self) -> usize {
|
||||||
|
self.phrases.phrase.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -320,97 +405,8 @@ impl TransportControl<ArrangerFocus> for ArrangerTui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
has_clock!(|self:ArrangerTui|&self.clock);
|
|
||||||
has_clock!(|self:ArrangerTrack|self.player.clock());
|
has_clock!(|self:ArrangerTrack|self.player.clock());
|
||||||
has_phrases!(|self:ArrangerTui|self.phrases.phrases);
|
|
||||||
has_editor!(|self:ArrangerTui|self.editor);
|
|
||||||
has_player!(|self:ArrangerTrack|self.player);
|
has_player!(|self:ArrangerTrack|self.player);
|
||||||
render!(|self: ArrangerTui|{
|
|
||||||
let arranger_focused = self.arranger_focused();
|
|
||||||
let transport_focused = if let ArrangerFocus::Transport(_) = self.focus.inner() {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
let transport = TransportView::from((self, None, transport_focused));
|
|
||||||
let with_transport = move|x|col!([transport, x]);
|
|
||||||
let border = Lozenge(Style::default()
|
|
||||||
.bg(TuiTheme::border_bg())
|
|
||||||
.fg(TuiTheme::border_fg(arranger_focused)));
|
|
||||||
let arranger = move||border.wrap(Tui::grow_y(1, lay!(|add|{
|
|
||||||
match self.mode {
|
|
||||||
ArrangerMode::Horizontal => add(&arranger_content_horizontal(self))?,
|
|
||||||
ArrangerMode::Vertical(factor) => add(&arranger_content_vertical(self, factor))?
|
|
||||||
};
|
|
||||||
add(&self.size)
|
|
||||||
})));
|
|
||||||
with_transport(col!([
|
|
||||||
Tui::fixed_y(self.splits[0], lay!([
|
|
||||||
arranger(),
|
|
||||||
Tui::push_x(1, Tui::fg(
|
|
||||||
TuiTheme::title_fg(arranger_focused),
|
|
||||||
format!("[{}] Arranger", if self.entered {
|
|
||||||
"■"
|
|
||||||
} else {
|
|
||||||
" "
|
|
||||||
})
|
|
||||||
))
|
|
||||||
])),
|
|
||||||
Split::right(false, self.splits[1], PhraseListView(&self.phrases), &self.editor),
|
|
||||||
]))
|
|
||||||
});
|
|
||||||
audio!(|self: ArrangerTui, client, scope|{
|
|
||||||
// Start profiling cycle
|
|
||||||
let t0 = self.perf.get_t0();
|
|
||||||
// Update transport clock
|
|
||||||
if ClockAudio(self).process(client, scope) == Control::Quit {
|
|
||||||
return Control::Quit
|
|
||||||
}
|
|
||||||
// Update MIDI sequencers
|
|
||||||
let tracks = &mut self.tracks;
|
|
||||||
let note_buf = &mut self.note_buf;
|
|
||||||
let midi_buf = &mut self.midi_buf;
|
|
||||||
if TracksAudio(tracks, note_buf, midi_buf, Default::default())
|
|
||||||
.process(client, scope) == Control::Quit {
|
|
||||||
return Control::Quit
|
|
||||||
}
|
|
||||||
// FIXME: one of these per playing track
|
|
||||||
//self.now.set(0.);
|
|
||||||
//if let ArrangerSelection::Clip(t, s) = self.selected {
|
|
||||||
//let phrase = self.scenes().get(s).map(|scene|scene.clips.get(t));
|
|
||||||
//if let Some(Some(Some(phrase))) = phrase {
|
|
||||||
//if let Some(track) = self.tracks().get(t) {
|
|
||||||
//if let Some((ref started_at, Some(ref playing))) = track.player.play_phrase {
|
|
||||||
//let phrase = phrase.read().unwrap();
|
|
||||||
//if *playing.read().unwrap() == *phrase {
|
|
||||||
//let pulse = self.current().pulse.get();
|
|
||||||
//let start = started_at.pulse.get();
|
|
||||||
//let now = (pulse - start) % phrase.length as f64;
|
|
||||||
//self.now.set(now);
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
// End profiling cycle
|
|
||||||
self.perf.update(t0, scope);
|
|
||||||
return Control::Continue
|
|
||||||
});
|
|
||||||
|
|
||||||
impl HasPhraseList for ArrangerTui {
|
|
||||||
fn phrases_focused (&self) -> bool {
|
|
||||||
self.focused() == ArrangerFocus::Phrases
|
|
||||||
}
|
|
||||||
fn phrases_entered (&self) -> bool {
|
|
||||||
self.entered() && self.phrases_focused()
|
|
||||||
}
|
|
||||||
fn phrases_mode (&self) -> &Option<PhraseListMode> {
|
|
||||||
&self.phrases.mode
|
|
||||||
}
|
|
||||||
fn phrase_index (&self) -> usize {
|
|
||||||
self.phrases.phrase.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sections in the arranger app that may be focused
|
/// Sections in the arranger app that may be focused
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for GrooveboxTui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GrooveboxTui {
|
pub struct GrooveboxTui {
|
||||||
pub sequencer: SequencerTui,
|
pub sequencer: SequencerTui,
|
||||||
pub sampler: SamplerTui,
|
pub sampler: SamplerTui,
|
||||||
pub focus: GrooveboxFocus,
|
pub focus: GrooveboxFocus,
|
||||||
|
|
@ -30,3 +30,7 @@ pub enum GrooveboxFocus {
|
||||||
/// The sample player is focused
|
/// The sample player is focused
|
||||||
Sampler
|
Sampler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render!(|self:GrooveboxTui|"are we groovy yet?");
|
||||||
|
audio!(|self:GrooveboxTui,_client,_process|Control::Continue);
|
||||||
|
handle!(<Tui>|self:GrooveboxTui,input|Ok(None));
|
||||||
|
|
|
||||||
|
|
@ -221,42 +221,40 @@ fn read_sample_data (_: &str) -> Usually<(usize, Vec<Vec<f32>>)> {
|
||||||
todo!();
|
todo!();
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handle<Tui> for SamplerTui {
|
handle!(<Tui>|self:SamplerTui,from|{
|
||||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
let cursor = &mut self.cursor;
|
||||||
let cursor = &mut self.cursor;
|
let unmapped = &mut self.state.unmapped;
|
||||||
let unmapped = &mut self.state.unmapped;
|
let mapped = &self.state.mapped;
|
||||||
let mapped = &self.state.mapped;
|
let voices = &self.state.voices;
|
||||||
let voices = &self.state.voices;
|
match from.event() {
|
||||||
match from.event() {
|
key_pat!(KeyCode::Up) => cursor.0 = if cursor.0 == 0 {
|
||||||
key_pat!(KeyCode::Up) => cursor.0 = if cursor.0 == 0 {
|
mapped.len() + unmapped.len() - 1
|
||||||
mapped.len() + unmapped.len() - 1
|
} else {
|
||||||
} else {
|
cursor.0 - 1
|
||||||
cursor.0 - 1
|
},
|
||||||
},
|
key_pat!(KeyCode::Down) => {
|
||||||
key_pat!(KeyCode::Down) => {
|
cursor.0 = (cursor.0 + 1) % (mapped.len() + unmapped.len());
|
||||||
cursor.0 = (cursor.0 + 1) % (mapped.len() + unmapped.len());
|
},
|
||||||
},
|
key_pat!(KeyCode::Char('p')) => if let Some(sample) = self.sample() {
|
||||||
key_pat!(KeyCode::Char('p')) => if let Some(sample) = self.sample() {
|
voices.write().unwrap().push(Sample::play(sample, 0, &100.into()));
|
||||||
voices.write().unwrap().push(Sample::play(sample, 0, &100.into()));
|
},
|
||||||
},
|
key_pat!(KeyCode::Char('a')) => {
|
||||||
key_pat!(KeyCode::Char('a')) => {
|
let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![])));
|
||||||
let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![])));
|
*self.modal.lock().unwrap() = Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?));
|
||||||
*self.modal.lock().unwrap() = Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?));
|
unmapped.push(sample);
|
||||||
unmapped.push(sample);
|
},
|
||||||
},
|
key_pat!(KeyCode::Char('r')) => if let Some(sample) = self.sample() {
|
||||||
key_pat!(KeyCode::Char('r')) => if let Some(sample) = self.sample() {
|
*self.modal.lock().unwrap() = Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?));
|
||||||
*self.modal.lock().unwrap() = Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?));
|
},
|
||||||
},
|
key_pat!(KeyCode::Enter) => if let Some(sample) = self.sample() {
|
||||||
key_pat!(KeyCode::Enter) => if let Some(sample) = self.sample() {
|
self.editing = Some(sample.clone());
|
||||||
self.editing = Some(sample.clone());
|
},
|
||||||
},
|
_ => {
|
||||||
_ => {
|
return Ok(None)
|
||||||
return Ok(None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(Some(true))
|
|
||||||
}
|
}
|
||||||
}
|
Ok(Some(true))
|
||||||
|
});
|
||||||
|
|
||||||
fn scan (dir: &PathBuf) -> Usually<(Vec<OsString>, Vec<OsString>)> {
|
fn scan (dir: &PathBuf) -> Usually<(Vec<OsString>, Vec<OsString>)> {
|
||||||
let (mut subdirs, mut files) = std::fs::read_dir(dir)?
|
let (mut subdirs, mut files) = std::fs::read_dir(dir)?
|
||||||
|
|
|
||||||
|
|
@ -144,24 +144,11 @@ impl InputToCommand<Tui, SequencerTui> for SequencerCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
audio!(|self:SequencerTui, client, scope|{
|
has_size!(<Tui>|self:SequencerTui|&self.size);
|
||||||
// Start profiling cycle
|
has_clock!(|self:SequencerTui|&self.clock);
|
||||||
let t0 = self.perf.get_t0();
|
has_phrases!(|self:SequencerTui|self.phrases.phrases);
|
||||||
// Update transport clock
|
has_editor!(|self:SequencerTui|self.editor);
|
||||||
if Control::Quit == ClockAudio(self).process(client, scope) {
|
handle!(<Tui>|self:SequencerTui,i|SequencerCommand::execute_with_state(self, i));
|
||||||
return Control::Quit
|
|
||||||
}
|
|
||||||
// Update MIDI sequencer
|
|
||||||
if Control::Quit == PlayerAudio(
|
|
||||||
&mut self.player, &mut self.note_buf, &mut self.midi_buf
|
|
||||||
).process(client, scope) {
|
|
||||||
return Control::Quit
|
|
||||||
}
|
|
||||||
// End profiling cycle
|
|
||||||
self.perf.update(t0, scope);
|
|
||||||
Control::Continue
|
|
||||||
});
|
|
||||||
|
|
||||||
render!(|self: SequencerTui|{
|
render!(|self: SequencerTui|{
|
||||||
let w = self.size.w();
|
let w = self.size.w();
|
||||||
let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
|
let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
|
||||||
|
|
@ -181,11 +168,23 @@ render!(|self: SequencerTui|{
|
||||||
]), transport]);
|
]), transport]);
|
||||||
with_size(with_status(col!([ toolbar, editor, ])))
|
with_size(with_status(col!([ toolbar, editor, ])))
|
||||||
});
|
});
|
||||||
|
audio!(|self:SequencerTui, client, scope|{
|
||||||
has_size!(<Tui>|self:SequencerTui|&self.size);
|
// Start profiling cycle
|
||||||
has_clock!(|self:SequencerTui|&self.clock);
|
let t0 = self.perf.get_t0();
|
||||||
has_phrases!(|self:SequencerTui|self.phrases.phrases);
|
// Update transport clock
|
||||||
has_editor!(|self:SequencerTui|self.editor);
|
if Control::Quit == ClockAudio(self).process(client, scope) {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
// Update MIDI sequencer
|
||||||
|
if Control::Quit == PlayerAudio(
|
||||||
|
&mut self.player, &mut self.note_buf, &mut self.midi_buf
|
||||||
|
).process(client, scope) {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
// End profiling cycle
|
||||||
|
self.perf.update(t0, scope);
|
||||||
|
Control::Continue
|
||||||
|
});
|
||||||
|
|
||||||
impl HasPhraseList for SequencerTui {
|
impl HasPhraseList for SequencerTui {
|
||||||
fn phrases_focused (&self) -> bool {
|
fn phrases_focused (&self) -> bool {
|
||||||
|
|
@ -202,12 +201,6 @@ impl HasPhraseList for SequencerTui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handle<Tui> for SequencerTui {
|
|
||||||
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
|
||||||
SequencerCommand::execute_with_state(self, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Status bar for sequencer app
|
/// Status bar for sequencer app
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SequencerStatusBar {
|
pub struct SequencerStatusBar {
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ impl std::fmt::Debug for TransportTui {
|
||||||
|
|
||||||
has_clock!(|self:TransportTui|&self.clock);
|
has_clock!(|self:TransportTui|&self.clock);
|
||||||
audio!(|self:TransportTui,client,scope|ClockAudio(self).process(client, scope));
|
audio!(|self:TransportTui,client,scope|ClockAudio(self).process(client, scope));
|
||||||
|
handle!(<Tui>|self:TransportTui,from|TransportCommand::execute_with_state(self, from));
|
||||||
render!(|self: TransportTui|TransportView::from((self, None, true)));
|
render!(|self: TransportTui|TransportView::from((self, None, true)));
|
||||||
|
|
||||||
pub struct TransportView {
|
pub struct TransportView {
|
||||||
|
|
@ -201,12 +202,6 @@ impl StatusBar for TransportStatusBar {
|
||||||
|
|
||||||
render!(|self: TransportStatusBar|"todo");
|
render!(|self: TransportStatusBar|"todo");
|
||||||
|
|
||||||
impl Handle<Tui> for TransportTui {
|
|
||||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
|
||||||
TransportCommand::execute_with_state(self, from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait TransportControl<T>: HasClock + {
|
pub trait TransportControl<T>: HasClock + {
|
||||||
fn transport_focused (&self) -> Option<TransportFocus>;
|
fn transport_focused (&self) -> Option<TransportFocus>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -239,10 +239,11 @@ impl PianoHorizontal {
|
||||||
for (x, time_start) in (0..phrase.length).step_by(zoom).enumerate() {
|
for (x, time_start) in (0..phrase.length).step_by(zoom).enumerate() {
|
||||||
|
|
||||||
for (y, note) in (0..127).rev().enumerate() {
|
for (y, note) in (0..127).rev().enumerate() {
|
||||||
let cell = buf.get_mut(x, note).unwrap();
|
if let Some(cell) = buf.get_mut(x, note) {
|
||||||
if notes_on[note] {
|
if notes_on[note] {
|
||||||
cell.set_char('▂');
|
cell.set_char('▂');
|
||||||
cell.set_style(style);
|
cell.set_style(style);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue