mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
wip: cleanup old code
This commit is contained in:
parent
0d7f78e74f
commit
5d3e564949
10 changed files with 1464 additions and 1512 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -149,7 +149,7 @@ pub fn main () -> Usually<()> {
|
||||||
TekMode::Arranger { scenes, tracks, track_width, .. } => engine.run(&jack.activate_with(|jack|Ok({
|
TekMode::Arranger { scenes, tracks, track_width, .. } => engine.run(&jack.activate_with(|jack|Ok({
|
||||||
App::arranger(
|
App::arranger(
|
||||||
jack,
|
jack,
|
||||||
PoolModel::default(),
|
MidiPool::default(),
|
||||||
MidiEditor::default(), &midi_froms, &midi_tos,
|
MidiEditor::default(), &midi_froms, &midi_tos,
|
||||||
default_sampler(jack)?, audio_froms, audio_tos,
|
default_sampler(jack)?, audio_froms, audio_tos,
|
||||||
scenes, tracks, track_width
|
scenes, tracks, track_width
|
||||||
|
|
@ -163,7 +163,7 @@ pub fn main () -> Usually<()> {
|
||||||
//midi_ins: vec![JackPort::<MidiIn>::new(jack, format!("M/{name}"), &midi_froms)?,],
|
//midi_ins: vec![JackPort::<MidiIn>::new(jack, format!("M/{name}"), &midi_froms)?,],
|
||||||
//midi_outs: vec![JackPort::<MidiOut>::new(jack, format!("{name}/M"), &midi_tos)?, ],
|
//midi_outs: vec![JackPort::<MidiOut>::new(jack, format!("{name}/M"), &midi_tos)?, ],
|
||||||
//clock,
|
//clock,
|
||||||
//pool: PoolModel::default(),//(&clip).into(),
|
//pool: MidiPool::default(),//(&clip).into(),
|
||||||
//editor: MidiEditor::default(),//(&clip).into(),
|
//editor: MidiEditor::default(),//(&clip).into(),
|
||||||
//selected: ArrangerSelection::Clip(0, 0),
|
//selected: ArrangerSelection::Clip(0, 0),
|
||||||
//scenes: vec![],
|
//scenes: vec![],
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ pub trait HasClips {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PoolModel {
|
pub struct MidiPool {
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
/// Collection of clips
|
/// Collection of clips
|
||||||
pub clips: Arc<RwLock<Vec<Arc<RwLock<MidiClip>>>>>,
|
pub clips: Arc<RwLock<Vec<Arc<RwLock<MidiClip>>>>>,
|
||||||
|
|
@ -43,7 +43,7 @@ pub enum PoolMode {
|
||||||
/// Save clip to disk
|
/// Save clip to disk
|
||||||
Export(usize, FileBrowser),
|
Export(usize, FileBrowser),
|
||||||
}
|
}
|
||||||
impl Default for PoolModel {
|
impl Default for MidiPool {
|
||||||
fn default () -> Self {
|
fn default () -> Self {
|
||||||
Self {
|
Self {
|
||||||
visible: true,
|
visible: true,
|
||||||
|
|
@ -53,15 +53,15 @@ impl Default for PoolModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
from!(|clip:&Arc<RwLock<MidiClip>>|PoolModel = {
|
from!(|clip:&Arc<RwLock<MidiClip>>|MidiPool = {
|
||||||
let model = Self::default();
|
let model = Self::default();
|
||||||
model.clips.write().unwrap().push(clip.clone());
|
model.clips.write().unwrap().push(clip.clone());
|
||||||
model.clip.store(1, Relaxed);
|
model.clip.store(1, Relaxed);
|
||||||
model
|
model
|
||||||
});
|
});
|
||||||
has_clips!(|self: PoolModel|self.clips);
|
has_clips!(|self: MidiPool|self.clips);
|
||||||
has_clip!(|self: PoolModel|self.clips().get(self.clip_index()).map(|c|c.clone()));
|
has_clip!(|self: MidiPool|self.clips().get(self.clip_index()).map(|c|c.clone()));
|
||||||
impl PoolModel {
|
impl MidiPool {
|
||||||
pub(crate) fn clip_index (&self) -> usize {
|
pub(crate) fn clip_index (&self) -> usize {
|
||||||
self.clip.load(Relaxed)
|
self.clip.load(Relaxed)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ pub enum PoolCommand {
|
||||||
/// Export to file
|
/// Export to file
|
||||||
Export(FileBrowserCommand),
|
Export(FileBrowserCommand),
|
||||||
}
|
}
|
||||||
impl EdnCommand<PoolModel> for PoolCommand {
|
impl EdnCommand<MidiPool> for PoolCommand {
|
||||||
fn from_edn <'a> (state: &PoolModel, head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {
|
fn from_edn <'a> (state: &MidiPool, head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +101,7 @@ impl<T: HasClips> Command<T> for PoolClipCommand {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
command!(|self:PoolCommand, state: PoolModel|{
|
command!(|self:PoolCommand, state: MidiPool|{
|
||||||
use PoolCommand::*;
|
use PoolCommand::*;
|
||||||
match self {
|
match self {
|
||||||
Show(visible) => {
|
Show(visible) => {
|
||||||
|
|
@ -153,14 +153,14 @@ command!(|self:PoolCommand, state: PoolModel|{
|
||||||
Clip(command) => command.execute(state)?.map(Clip),
|
Clip(command) => command.execute(state)?.map(Clip),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
input_to_command!(PoolCommand: |state: PoolModel, input: Event|match state.clips_mode() {
|
input_to_command!(PoolCommand: |state: MidiPool, input: Event|match state.clips_mode() {
|
||||||
Some(PoolMode::Rename(..)) => Self::Rename(ClipRenameCommand::input_to_command(state, input)?),
|
Some(PoolMode::Rename(..)) => Self::Rename(ClipRenameCommand::input_to_command(state, input)?),
|
||||||
Some(PoolMode::Length(..)) => Self::Length(ClipLengthCommand::input_to_command(state, input)?),
|
Some(PoolMode::Length(..)) => Self::Length(ClipLengthCommand::input_to_command(state, input)?),
|
||||||
Some(PoolMode::Import(..)) => Self::Import(FileBrowserCommand::input_to_command(state, input)?),
|
Some(PoolMode::Import(..)) => Self::Import(FileBrowserCommand::input_to_command(state, input)?),
|
||||||
Some(PoolMode::Export(..)) => Self::Export(FileBrowserCommand::input_to_command(state, input)?),
|
Some(PoolMode::Export(..)) => Self::Export(FileBrowserCommand::input_to_command(state, input)?),
|
||||||
_ => to_clips_command(state, input)?
|
_ => to_clips_command(state, input)?
|
||||||
});
|
});
|
||||||
fn to_clips_command (state: &PoolModel, input: &Event) -> Option<PoolCommand> {
|
fn to_clips_command (state: &MidiPool, input: &Event) -> Option<PoolCommand> {
|
||||||
use KeyCode::{Up, Down, Delete, Char};
|
use KeyCode::{Up, Down, Delete, Char};
|
||||||
use PoolCommand as Cmd;
|
use PoolCommand as Cmd;
|
||||||
let index = state.clip_index();
|
let index = state.clip_index();
|
||||||
|
|
@ -209,7 +209,7 @@ fn to_clips_command (state: &PoolModel, input: &Event) -> Option<PoolCommand> {
|
||||||
_ => return None
|
_ => return None
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
command!(|self: FileBrowserCommand, state: PoolModel|{
|
command!(|self: FileBrowserCommand, state: MidiPool|{
|
||||||
use PoolMode::*;
|
use PoolMode::*;
|
||||||
use FileBrowserCommand::*;
|
use FileBrowserCommand::*;
|
||||||
let mode = &mut state.mode;
|
let mode = &mut state.mode;
|
||||||
|
|
@ -238,7 +238,7 @@ command!(|self: FileBrowserCommand, state: PoolModel|{
|
||||||
};
|
};
|
||||||
None
|
None
|
||||||
});
|
});
|
||||||
input_to_command!(FileBrowserCommand: |state: PoolModel, input: Event|{
|
input_to_command!(FileBrowserCommand: |state: MidiPool, input: Event|{
|
||||||
use FileBrowserCommand::*;
|
use FileBrowserCommand::*;
|
||||||
use KeyCode::{Up, Down, Left, Right, Enter, Esc, Backspace, Char};
|
use KeyCode::{Up, Down, Left, Right, Enter, Esc, Backspace, Char};
|
||||||
if let Some(PoolMode::Import(_index, browser)) = &state.mode {
|
if let Some(PoolMode::Import(_index, browser)) = &state.mode {
|
||||||
|
|
@ -283,7 +283,7 @@ pub enum ClipLengthCommand {
|
||||||
Inc,
|
Inc,
|
||||||
Dec,
|
Dec,
|
||||||
}
|
}
|
||||||
command!(|self: ClipLengthCommand,state:PoolModel|{
|
command!(|self: ClipLengthCommand,state:MidiPool|{
|
||||||
use ClipLengthCommand::*;
|
use ClipLengthCommand::*;
|
||||||
use ClipLengthFocus::*;
|
use ClipLengthFocus::*;
|
||||||
match state.clips_mode_mut().clone() {
|
match state.clips_mode_mut().clone() {
|
||||||
|
|
@ -317,7 +317,7 @@ command!(|self: ClipLengthCommand,state:PoolModel|{
|
||||||
};
|
};
|
||||||
None
|
None
|
||||||
});
|
});
|
||||||
input_to_command!(ClipLengthCommand: |state: PoolModel, input: Event|{
|
input_to_command!(ClipLengthCommand: |state: MidiPool, input: Event|{
|
||||||
if let Some(PoolMode::Length(_, length, _)) = state.clips_mode() {
|
if let Some(PoolMode::Length(_, length, _)) = state.clips_mode() {
|
||||||
match input {
|
match input {
|
||||||
kpat!(Up) => Self::Inc,
|
kpat!(Up) => Self::Inc,
|
||||||
|
|
@ -332,8 +332,8 @@ input_to_command!(ClipLengthCommand: |state: PoolModel, input: Event|{
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
impl InputToCommand<Event, PoolModel> for ClipRenameCommand {
|
impl InputToCommand<Event, MidiPool> for ClipRenameCommand {
|
||||||
fn input_to_command (state: &PoolModel, input: &Event) -> Option<Self> {
|
fn input_to_command (state: &MidiPool, input: &Event) -> Option<Self> {
|
||||||
use KeyCode::{Char, Backspace, Enter, Esc};
|
use KeyCode::{Char, Backspace, Enter, Esc};
|
||||||
if let Some(PoolMode::Rename(_, ref old_name)) = state.clips_mode() {
|
if let Some(PoolMode::Rename(_, ref old_name)) = state.clips_mode() {
|
||||||
Some(match input {
|
Some(match input {
|
||||||
|
|
@ -363,8 +363,8 @@ pub enum ClipRenameCommand {
|
||||||
Confirm,
|
Confirm,
|
||||||
Set(Arc<str>),
|
Set(Arc<str>),
|
||||||
}
|
}
|
||||||
impl Command<PoolModel> for ClipRenameCommand {
|
impl Command<MidiPool> for ClipRenameCommand {
|
||||||
fn execute (self, state: &mut PoolModel) -> Perhaps<Self> {
|
fn execute (self, state: &mut MidiPool) -> Perhaps<Self> {
|
||||||
use ClipRenameCommand::*;
|
use ClipRenameCommand::*;
|
||||||
match state.clips_mode_mut().clone() {
|
match state.clips_mode_mut().clone() {
|
||||||
Some(PoolMode::Rename(clip, ref mut old_name)) => match self {
|
Some(PoolMode::Rename(clip, ref mut old_name)) => match self {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
pub struct PoolView<'a>(pub bool, pub &'a PoolModel);
|
pub struct PoolView<'a>(pub bool, pub &'a MidiPool);
|
||||||
render!(TuiOut: (self: PoolView<'a>) => {
|
render!(TuiOut: (self: PoolView<'a>) => {
|
||||||
let Self(compact, model) = self;
|
let Self(compact, model) = self;
|
||||||
let PoolModel { clips, mode, .. } = self.1;
|
let MidiPool { clips, mode, .. } = self.1;
|
||||||
let color = self.1.clip().map(|c|c.read().unwrap().color).unwrap_or_else(||TuiTheme::g(32).into());
|
let color = self.1.clip().map(|c|c.read().unwrap().color).unwrap_or_else(||TuiTheme::g(32).into());
|
||||||
let on_bg = |x|x;//Bsp::b(Repeat(" "), Tui::bg(color.darkest.rgb, x));
|
let on_bg = |x|x;//Bsp::b(Repeat(" "), Tui::bg(color.darkest.rgb, x));
|
||||||
let border = |x|x;//Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)).enclose(x);
|
let border = |x|x;//Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)).enclose(x);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,31 @@ use crate::*;
|
||||||
use EdnItem::*;
|
use EdnItem::*;
|
||||||
use ClockCommand::{Play, Pause};
|
use ClockCommand::{Play, Pause};
|
||||||
pub const TRACK_MIN_WIDTH: usize = 9;
|
pub const TRACK_MIN_WIDTH: usize = 9;
|
||||||
|
impl HasJack for App {
|
||||||
|
fn jack (&self) -> &Arc<RwLock<JackConnection>> { &self.jack }
|
||||||
|
}
|
||||||
|
/// Hosts the JACK callback for a collection of tracks
|
||||||
|
pub struct TracksAudio<'a>(
|
||||||
|
// Track collection
|
||||||
|
pub &'a mut [ArrangerTrack],
|
||||||
|
/// Note buffer
|
||||||
|
pub &'a mut Vec<u8>,
|
||||||
|
/// Note chunk buffer
|
||||||
|
pub &'a mut Vec<Vec<Vec<u8>>>,
|
||||||
|
);
|
||||||
|
impl Audio for TracksAudio<'_> {
|
||||||
|
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
|
let model = &mut self.0;
|
||||||
|
let note_buffer = &mut self.1;
|
||||||
|
let output_buffer = &mut self.2;
|
||||||
|
for track in model.iter_mut() {
|
||||||
|
if PlayerAudio(track.player_mut(), note_buffer, output_buffer).process(client, scope) == Control::Quit {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
command!(|self: ClipCommand, state: App|match self { _ => todo!("clip command") });
|
command!(|self: ClipCommand, state: App|match self { _ => todo!("clip command") });
|
||||||
command!(|self: SceneCommand, state: App|match self { _ => todo!("scene command") });
|
command!(|self: SceneCommand, state: App|match self { _ => todo!("scene command") });
|
||||||
command!(|self: TrackCommand, state: App|match self { _ => todo!("track command") });
|
command!(|self: TrackCommand, state: App|match self { _ => todo!("track command") });
|
||||||
|
|
|
||||||
232
tek/src/audio.rs
232
tek/src/audio.rs
|
|
@ -1,232 +0,0 @@
|
||||||
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
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Hosts the JACK callback for a collection of tracks
|
|
||||||
pub struct TracksAudio<'a>(
|
|
||||||
// Track collection
|
|
||||||
pub &'a mut [ArrangerTrack],
|
|
||||||
/// Note buffer
|
|
||||||
pub &'a mut Vec<u8>,
|
|
||||||
/// Note chunk buffer
|
|
||||||
pub &'a mut Vec<Vec<Vec<u8>>>,
|
|
||||||
);
|
|
||||||
|
|
||||||
impl Audio for TracksAudio<'_> {
|
|
||||||
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
|
||||||
let model = &mut self.0;
|
|
||||||
let note_buffer = &mut self.1;
|
|
||||||
let output_buffer = &mut self.2;
|
|
||||||
for track in model.iter_mut() {
|
|
||||||
if PlayerAudio(track.player_mut(), note_buffer, output_buffer).process(client, scope) == Control::Quit {
|
|
||||||
return Control::Quit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//audio!(|self: Sequencer, 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
|
|
||||||
//}
|
|
||||||
//// 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
|
|
||||||
//});
|
|
||||||
|
|
||||||
//audio!(|self: Groovebox, client, scope|{
|
|
||||||
//// Start profiling cycle
|
|
||||||
//let t0 = self.perf.get_t0();
|
|
||||||
|
|
||||||
//// Update transport clock
|
|
||||||
//if Control::Quit == ClockAudio(&mut self.player).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
|
|
||||||
//}
|
|
||||||
|
|
||||||
//// Update sampler
|
|
||||||
//if Control::Quit == SamplerAudio(&mut self.sampler).process(client, scope) {
|
|
||||||
//return Control::Quit
|
|
||||||
//}
|
|
||||||
|
|
||||||
//// TODO move these to editor and sampler:
|
|
||||||
//for RawMidi { time, bytes } in self.player.midi_ins[0].port.iter(scope) {
|
|
||||||
//if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
|
|
||||||
//match message {
|
|
||||||
//MidiMessage::NoteOn { ref key, .. } => {
|
|
||||||
//self.editor.set_note_point(key.as_int() as usize);
|
|
||||||
//},
|
|
||||||
//MidiMessage::Controller { controller, value } => {
|
|
||||||
//if let Some(sample) = &self.sampler.mapped[self.editor.note_point()] {
|
|
||||||
//sample.write().unwrap().handle_cc(controller, value)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//_ => {}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
//// End profiling cycle
|
|
||||||
//self.perf.update(t0, scope);
|
|
||||||
|
|
||||||
//Control::Continue
|
|
||||||
//});
|
|
||||||
|
|
||||||
//audio!(|self: Arranger, 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
|
|
||||||
//}
|
|
||||||
|
|
||||||
////// Update MIDI 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
|
|
||||||
////}
|
|
||||||
|
|
||||||
//// FIXME: one of these per playing track
|
|
||||||
////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);
|
|
||||||
//return Control::Continue
|
|
||||||
//});
|
|
||||||
|
|
@ -1,518 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use EdnItem::*;
|
|
||||||
use ClockCommand::{Play, Pause};
|
|
||||||
use KeyCode::{Tab, Char};
|
|
||||||
use SamplerCommand as SmplCmd;
|
|
||||||
use MidiEditCommand as EditCmd;
|
|
||||||
use PoolClipCommand as PoolCmd;
|
|
||||||
handle!(TuiIn: |self: App, input| Ok(None));
|
|
||||||
#[derive(Clone, Debug)] pub enum AppCommand {
|
|
||||||
Clear,
|
|
||||||
Clip(ClipCommand),
|
|
||||||
Clock(ClockCommand),
|
|
||||||
Color(ItemPalette),
|
|
||||||
Compact(Option<bool>),
|
|
||||||
Editor(MidiEditCommand),
|
|
||||||
Enqueue(Option<Arc<RwLock<MidiClip>>>),
|
|
||||||
History(isize),
|
|
||||||
Pool(PoolCommand),
|
|
||||||
Sampler(SamplerCommand),
|
|
||||||
Scene(SceneCommand),
|
|
||||||
Select(ArrangerSelection),
|
|
||||||
StopAll,
|
|
||||||
Track(TrackCommand),
|
|
||||||
Zoom(Option<usize>),
|
|
||||||
}
|
|
||||||
edn_command!(AppCommand: |state: App| {
|
|
||||||
("clear" [] Self::Clear)
|
|
||||||
("stop-all" [] Self::StopAll)
|
|
||||||
("compact" [c: bool ] Self::Compact(c))
|
|
||||||
("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default()))
|
|
||||||
("history" [d: isize] Self::History(d.unwrap_or(0)))
|
|
||||||
("zoom" [z: usize] Self::Zoom(z))
|
|
||||||
("clock" [a, ..b] Self::Clock(ClockCommand::from_edn(state, &a.to_ref(), b)))
|
|
||||||
("track" [a, ..b] Self::Track(TrackCommand::from_edn(state, &a.to_ref(), b)))
|
|
||||||
("scene" [a, ..b] Self::Scene(SceneCommand::from_edn(state, &a.to_ref(), b)))
|
|
||||||
("clip" [a, ..b] Self::Clip(ClipCommand::from_edn(state, &a.to_ref(), b)))
|
|
||||||
("pool" [a, ..b] Self::Pool(PoolCommand::from_edn(state.pool.as_ref().expect("no pool"), &a.to_ref(), b)))
|
|
||||||
("editor" [a, ..b] Self::Editor(MidiEditCommand::from_edn(state.editor.as_ref().expect("no editor"), &a.to_ref(), b)))
|
|
||||||
("sampler" [a, ..b] Self::Sampler(SamplerCommand::from_edn(state.sampler.as_ref().expect("no sampler"), &a.to_ref(), b)))
|
|
||||||
("select" [s: ArrangerSelection] Self::Select(s.expect("no selection")))
|
|
||||||
("enqueue" [c: Arc<RwLock<MidiClip>>] Self::Enqueue(c))
|
|
||||||
});
|
|
||||||
command!(|self: AppCommand, state: App|match self {
|
|
||||||
Self::Clear => { 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) =>
|
|
||||||
state.editor.as_mut().map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(),
|
|
||||||
Self::Sampler(cmd) =>
|
|
||||||
state.sampler.as_mut().map(|sampler|cmd.delegate(sampler, Self::Sampler)).transpose()?.flatten(),
|
|
||||||
Self::Enqueue(clip) =>
|
|
||||||
state.player.as_mut().map(|player|{player.enqueue_next(clip.as_ref());None}).flatten(),
|
|
||||||
Self::StopAll => {
|
|
||||||
for track in 0..state.tracks.len() { state.tracks[track].player.enqueue_next(None); }
|
|
||||||
None
|
|
||||||
},
|
|
||||||
Self::Color(palette) => {
|
|
||||||
let old = state.color;
|
|
||||||
state.color = palette;
|
|
||||||
Some(Self::Color(old))
|
|
||||||
},
|
|
||||||
Self::Pool(cmd) => if let Some(pool) = state.pool.as_mut() {
|
|
||||||
let undo = cmd.clone().delegate(pool, Self::Pool)?;
|
|
||||||
if let Some(editor) = state.editor.as_mut() {
|
|
||||||
match cmd {
|
|
||||||
// autoselect: automatically load selected clip in editor
|
|
||||||
// autocolor: update color in all places simultaneously
|
|
||||||
PoolCommand::Select(_) | PoolCommand::Clip(PoolCmd::SetColor(_, _)) =>
|
|
||||||
editor.set_clip(pool.clip().as_ref()),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
undo
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
Self::Compact(compact) => match compact {
|
|
||||||
Some(compact) => {
|
|
||||||
if state.compact != compact {
|
|
||||||
state.compact = compact;
|
|
||||||
Some(Self::Compact(Some(!compact)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
state.compact = !state.compact;
|
|
||||||
Some(Self::Compact(Some(!state.compact)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//handle!(TuiIn: |self: Sequencer, input|SequencerCommand::execute_with_state(self, input.event()));
|
|
||||||
//handle!(TuiIn: |self: Groovebox, input|GrooveboxCommand::execute_with_state(self, input.event()));
|
|
||||||
//handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, input.event()));
|
|
||||||
//use SequencerCommand as SeqCmd;
|
|
||||||
//use GrooveboxCommand as GrvCmd;
|
|
||||||
//use ArrangerCommand as ArrCmd;
|
|
||||||
//#[derive(Clone, Debug)] pub enum SequencerCommand {
|
|
||||||
//Compact(bool),
|
|
||||||
//History(isize),
|
|
||||||
//Clock(ClockCommand),
|
|
||||||
//Pool(PoolCommand),
|
|
||||||
//Editor(MidiEditCommand),
|
|
||||||
//Enqueue(Option<Arc<RwLock<MidiClip>>>),
|
|
||||||
//}
|
|
||||||
//#[derive(Clone, Debug)] pub enum GrooveboxCommand {
|
|
||||||
//Compact(bool),
|
|
||||||
//History(isize),
|
|
||||||
//Clock(ClockCommand),
|
|
||||||
//Pool(PoolCommand),
|
|
||||||
//Editor(MidiEditCommand),
|
|
||||||
//Enqueue(Option<Arc<RwLock<MidiClip>>>),
|
|
||||||
//Sampler(SamplerCommand),
|
|
||||||
//}
|
|
||||||
//#[derive(Clone, Debug)] pub enum ArrangerCommand {
|
|
||||||
//History(isize),
|
|
||||||
//Color(ItemPalette),
|
|
||||||
//Clock(ClockCommand),
|
|
||||||
//Scene(SceneCommand),
|
|
||||||
//Track(TrackCommand),
|
|
||||||
//Clip(ClipCommand),
|
|
||||||
//Select(ArrangerSelection),
|
|
||||||
//Zoom(usize),
|
|
||||||
//Pool(PoolCommand),
|
|
||||||
//Editor(MidiEditCommand),
|
|
||||||
//StopAll,
|
|
||||||
//Clear,
|
|
||||||
//}
|
|
||||||
|
|
||||||
//command!(|self: SequencerCommand, state: Sequencer|match self {
|
|
||||||
//Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?,
|
|
||||||
//Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?,
|
|
||||||
//Self::Enqueue(clip) => { state.player.enqueue_next(clip.as_ref()); None },
|
|
||||||
//Self::History(delta) => { todo!("undo/redo") },
|
|
||||||
|
|
||||||
//Self::Pool(cmd) => match cmd {
|
|
||||||
//// autoselect: automatically load selected clip in editor
|
|
||||||
//PoolCommand::Select(_) => {
|
|
||||||
//let undo = cmd.delegate(&mut state.pool, Self::Pool)?;
|
|
||||||
//state.editor.set_clip(state.pool.clip().as_ref());
|
|
||||||
//undo
|
|
||||||
//},
|
|
||||||
//// update color in all places simultaneously
|
|
||||||
//PoolCommand::Clip(PoolCmd::SetColor(index, _)) => {
|
|
||||||
//let undo = cmd.delegate(&mut state.pool, Self::Pool)?;
|
|
||||||
//state.editor.set_clip(state.pool.clip().as_ref());
|
|
||||||
//undo
|
|
||||||
//},
|
|
||||||
//_ => cmd.delegate(&mut state.pool, Self::Pool)?
|
|
||||||
//},
|
|
||||||
//Self::Compact(compact) => if state.compact != compact {
|
|
||||||
//state.compact = compact;
|
|
||||||
//Some(Self::Compact(!compact))
|
|
||||||
//} else {
|
|
||||||
//None
|
|
||||||
//},
|
|
||||||
//});
|
|
||||||
//command!(|self: GrooveboxCommand, state: Groovebox|match self {
|
|
||||||
//Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?,
|
|
||||||
//Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?,
|
|
||||||
//Self::Enqueue(clip) => { state.player.enqueue_next(clip.as_ref()); None },
|
|
||||||
//Self::History(delta) => { todo!("undo/redo") },
|
|
||||||
//Self::Sampler(cmd) => cmd.delegate(&mut state.sampler, Self::Sampler)?,
|
|
||||||
|
|
||||||
//Self::Pool(cmd) => match cmd {
|
|
||||||
//// autoselect: automatically load selected clip in editor
|
|
||||||
//PoolCommand::Select(_) => {
|
|
||||||
//let undo = cmd.delegate(&mut state.pool, Self::Pool)?;
|
|
||||||
//state.editor.set_clip(state.pool.clip().as_ref());
|
|
||||||
//undo
|
|
||||||
//},
|
|
||||||
//// update color in all places simultaneously
|
|
||||||
//PoolCommand::Clip(PoolCmd::SetColor(index, _)) => {
|
|
||||||
//let undo = cmd.delegate(&mut state.pool, Self::Pool)?;
|
|
||||||
//state.editor.set_clip(state.pool.clip().as_ref());
|
|
||||||
//undo
|
|
||||||
//},
|
|
||||||
//_ => cmd.delegate(&mut state.pool, Self::Pool)?
|
|
||||||
//},
|
|
||||||
//Self::Compact(compact) => if state.compact != compact {
|
|
||||||
//state.compact = compact;
|
|
||||||
//Some(Self::Compact(!compact))
|
|
||||||
//} else {
|
|
||||||
//None
|
|
||||||
//},
|
|
||||||
//});
|
|
||||||
//command!(|self: ArrangerCommand, state: Arranger|match self {
|
|
||||||
//Self::Clear => { todo!() },
|
|
||||||
//Self::Clip(cmd) => cmd.delegate(state, Self::Clip)?,
|
|
||||||
//Self::Clock(cmd) => cmd.delegate(state, Self::Clock)?,
|
|
||||||
//Self::Editor(cmd) => cmd.delegate(&mut state.editor, Self::Editor)?,
|
|
||||||
//Self::History(_) => { todo!() },
|
|
||||||
//Self::Scene(cmd) => cmd.delegate(state, Self::Scene)?,
|
|
||||||
//Self::Select(s) => { state.selected = s; None },
|
|
||||||
//Self::Track(cmd) => cmd.delegate(state, Self::Track)?,
|
|
||||||
//Self::Zoom(_) => { todo!(); },
|
|
||||||
|
|
||||||
//Self::StopAll => {
|
|
||||||
//for track in 0..state.tracks.len() { state.tracks[track].player.enqueue_next(None); }
|
|
||||||
//None
|
|
||||||
//},
|
|
||||||
//Self::Color(palette) => {
|
|
||||||
//let old = state.color;
|
|
||||||
//state.color = palette;
|
|
||||||
//Some(Self::Color(old))
|
|
||||||
//},
|
|
||||||
//Self::Pool(cmd) => {
|
|
||||||
//match cmd {
|
|
||||||
//// autoselect: automatically load selected clip in editor
|
|
||||||
//PoolCommand::Select(_) => {
|
|
||||||
//let undo = cmd.delegate(&mut state.pool, Self::Pool)?;
|
|
||||||
//state.editor.set_clip(state.pool.clip().as_ref());
|
|
||||||
//undo
|
|
||||||
//},
|
|
||||||
//// reload clip in editor to update color
|
|
||||||
//PoolCommand::Clip(PoolClipCommand::SetColor(index, _)) => {
|
|
||||||
//let undo = cmd.delegate(&mut state.pool, Self::Pool)?;
|
|
||||||
//state.editor.set_clip(state.pool.clip().as_ref());
|
|
||||||
//undo
|
|
||||||
//},
|
|
||||||
//_ => cmd.delegate(&mut state.pool, Self::Pool)?
|
|
||||||
//}
|
|
||||||
//},
|
|
||||||
//});
|
|
||||||
//command!(|self: SceneCommand, state: Arranger|match self {
|
|
||||||
//Self::Add => { state.scene_add(None, None)?; None }
|
|
||||||
//Self::Del(index) => { state.scene_del(index); None },
|
|
||||||
//Self::SetColor(index, color) => {
|
|
||||||
//let old = state.scenes[index].color;
|
|
||||||
//state.scenes[index].color = color;
|
|
||||||
//Some(Self::SetColor(index, old))
|
|
||||||
//},
|
|
||||||
//Self::Enqueue(scene) => {
|
|
||||||
//for track in 0..state.tracks.len() {
|
|
||||||
//state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref());
|
|
||||||
//}
|
|
||||||
//None
|
|
||||||
//},
|
|
||||||
//_ => None
|
|
||||||
//});
|
|
||||||
//command!(|self: TrackCommand, state: Arranger|match self {
|
|
||||||
//Self::Add => { state.track_add(None, None)?; None },
|
|
||||||
//Self::Del(index) => { state.track_del(index); None },
|
|
||||||
//Self::Stop(track) => { state.tracks[track].player.enqueue_next(None); None },
|
|
||||||
//Self::SetColor(index, color) => {
|
|
||||||
//let old = state.tracks[index].color;
|
|
||||||
//state.tracks[index].color = color;
|
|
||||||
//Some(Self::SetColor(index, old))
|
|
||||||
//},
|
|
||||||
//_ => None
|
|
||||||
//});
|
|
||||||
//command!(|self: ClipCommand, state: Arranger|match self {
|
|
||||||
//Self::Get(track, scene) => { todo!() },
|
|
||||||
//Self::Put(track, scene, clip) => {
|
|
||||||
//let old = state.scenes[scene].clips[track].clone();
|
|
||||||
//state.scenes[scene].clips[track] = clip;
|
|
||||||
//Some(Self::Put(track, scene, old))
|
|
||||||
//},
|
|
||||||
//Self::Enqueue(track, scene) => {
|
|
||||||
//state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref());
|
|
||||||
//None
|
|
||||||
//},
|
|
||||||
//_ => None
|
|
||||||
//});
|
|
||||||
//keymap!(KEYS_SEQUENCER = |state: Sequencer, input: Event| SequencerCommand {
|
|
||||||
//// TODO: k: toggle on-screen keyboard
|
|
||||||
//ctrl(key(Char('k'))) => { todo!("keyboard") },
|
|
||||||
//// Transport: Play/pause
|
|
||||||
//key(Char(' ')) => SeqCmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
|
|
||||||
//// Transport: Play from start or rewind to start
|
|
||||||
//shift(key(Char(' '))) => SeqCmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }),
|
|
||||||
//// u: undo
|
|
||||||
//key(Char('u')) => SeqCmd::History(-1),
|
|
||||||
//// Shift-U: redo
|
|
||||||
//key(Char('U')) => SeqCmd::History( 1),
|
|
||||||
//// Tab: Toggle compact mode
|
|
||||||
//key(Tab) => SeqCmd::Compact(!state.compact),
|
|
||||||
//// q: Enqueue currently edited clip
|
|
||||||
//key(Char('q')) => SeqCmd::Enqueue(state.pool.clip().clone()),
|
|
||||||
//// 0: Enqueue clip 0 (stop all)
|
|
||||||
//key(Char('0')) => SeqCmd::Enqueue(Some(state.clips()[0].clone())),
|
|
||||||
//// e: Toggle between editing currently playing or other clip
|
|
||||||
////key(Char('e')) => if let Some((_, Some(playing))) = state.player.play_clip() {
|
|
||||||
////let editing = state.editor.clip().as_ref().map(|p|p.read().unwrap().clone());
|
|
||||||
////let selected = state.pool.clip().clone();
|
|
||||||
////SeqCmd::Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing {
|
|
||||||
////selected
|
|
||||||
////} else {
|
|
||||||
////playing.clone()
|
|
||||||
////})))
|
|
||||||
////} else {
|
|
||||||
////return None
|
|
||||||
////}
|
|
||||||
//}, if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
|
|
||||||
//SeqCmd::Editor(command)
|
|
||||||
//} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
|
||||||
//SeqCmd::Pool(command)
|
|
||||||
//} else {
|
|
||||||
//return None
|
|
||||||
//});
|
|
||||||
//keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand {
|
|
||||||
//// Tab: Toggle compact mode
|
|
||||||
//key(Tab) => GrvCmd::Compact(!state.compact),
|
|
||||||
//// q: Enqueue currently edited clip
|
|
||||||
//key(Char('q')) => GrvCmd::Enqueue(state.pool.clip().clone()),
|
|
||||||
//// 0: Enqueue clip 0 (stop all)
|
|
||||||
//key(Char('0')) => GrvCmd::Enqueue(Some(state.pool.clips()[0].clone())),
|
|
||||||
//// TODO: k: toggle on-screen keyboard
|
|
||||||
//ctrl(key(Char('k'))) => todo!("keyboard"),
|
|
||||||
//// Transport: Play from start or rewind to start
|
|
||||||
//ctrl(key(Char(' '))) => GrvCmd::Clock(
|
|
||||||
//if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }
|
|
||||||
//),
|
|
||||||
//// Shift-R: toggle recording
|
|
||||||
//shift(key(Char('R'))) => GrvCmd::Sampler(if state.sampler.recording.is_some() {
|
|
||||||
//SmplCmd::RecordFinish
|
|
||||||
//} else {
|
|
||||||
//SmplCmd::RecordBegin(u7::from(state.editor.note_point() as u8))
|
|
||||||
//}),
|
|
||||||
//// Shift-Del: delete sample
|
|
||||||
//shift(key(Delete)) => GrvCmd::Sampler(
|
|
||||||
//SmplCmd::SetSample(u7::from(state.editor.note_point() as u8), None)
|
|
||||||
//),
|
|
||||||
//// e: Toggle between editing currently playing or other clip
|
|
||||||
////shift(key(Char('e'))) => if let Some((_, Some(playing))) = state.player.play_clip() {
|
|
||||||
////let editing = state.editor.clip().as_ref().map(|p|p.read().unwrap().clone());
|
|
||||||
////let selected = state.pool.clip().clone().map(|s|s.read().unwrap().clone());
|
|
||||||
////GrvCmd::Editor(Show(if selected != editing {
|
|
||||||
////selected
|
|
||||||
////} else {
|
|
||||||
////Some(playing.clone())
|
|
||||||
////}))
|
|
||||||
////} else {
|
|
||||||
////return None
|
|
||||||
////},
|
|
||||||
//}, if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
|
|
||||||
//GrvCmd::Editor(command)
|
|
||||||
//} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
|
||||||
//GrvCmd::Pool(command)
|
|
||||||
//} else {
|
|
||||||
//return None
|
|
||||||
//});
|
|
||||||
//keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand {
|
|
||||||
//key(Char('u')) => ArrCmd::History(-1),
|
|
||||||
//key(Char('U')) => ArrCmd::History(1),
|
|
||||||
//// TODO: k: toggle on-screen keyboard
|
|
||||||
//ctrl(key(Char('k'))) => { todo!("keyboard") },
|
|
||||||
//// Transport: Play/pause
|
|
||||||
//key(Char(' ')) => ArrCmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
|
|
||||||
//// Transport: Play from start or rewind to start
|
|
||||||
//shift(key(Char(' '))) => ArrCmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }),
|
|
||||||
//key(Char('e')) => ArrCmd::Editor(MidiEditCommand::Show(state.pool.clip().clone())),
|
|
||||||
//ctrl(key(Char('a'))) => ArrCmd::Scene(SceneCommand::Add),
|
|
||||||
//ctrl(key(Char('A'))) => return None,//ArrCmd::Scene(SceneCommand::Add),
|
|
||||||
//ctrl(key(Char('t'))) => ArrCmd::Track(TrackCommand::Add),
|
|
||||||
//// Tab: Toggle visibility of clip pool column
|
|
||||||
//key(Tab) => ArrCmd::Pool(PoolCommand::Show(!state.pool.visible)),
|
|
||||||
//}, {
|
|
||||||
//use ArrangerSelection as Selected;
|
|
||||||
//use SceneCommand as Scene;
|
|
||||||
//use TrackCommand as Track;
|
|
||||||
//use ClipCommand as Clip;
|
|
||||||
//let t_len = state.tracks.len();
|
|
||||||
//let s_len = state.scenes.len();
|
|
||||||
//match state.selected {
|
|
||||||
//Selected::Clip(t, s) => clip_keymap(state, input, t, s),
|
|
||||||
//Selected::Scene(s) => scene_keymap(state, input, s),
|
|
||||||
//Selected::Track(t) => track_keymap(state, input, t),
|
|
||||||
//Selected::Mix => match input {
|
|
||||||
|
|
||||||
//kpat!(Delete) => Some(ArrCmd::Clear),
|
|
||||||
//kpat!(Char('0')) => Some(ArrCmd::StopAll),
|
|
||||||
//kpat!(Char('c')) => Some(ArrCmd::Color(ItemPalette::random())),
|
|
||||||
|
|
||||||
//kpat!(Up) => return None,
|
|
||||||
//kpat!(Down) => Some(ArrCmd::Select(Selected::Scene(0))),
|
|
||||||
//kpat!(Left) => return None,
|
|
||||||
//kpat!(Right) => Some(ArrCmd::Select(Selected::Track(0))),
|
|
||||||
|
|
||||||
//_ => None
|
|
||||||
//},
|
|
||||||
//}
|
|
||||||
//}.or_else(||if let Some(command) = MidiEditCommand::input_to_command(&state.editor, input) {
|
|
||||||
//Some(ArrCmd::Editor(command))
|
|
||||||
//} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
|
|
||||||
//Some(ArrCmd::Pool(command))
|
|
||||||
//} else {
|
|
||||||
//None
|
|
||||||
//})?);
|
|
||||||
|
|
||||||
//fn clip_keymap (state: &Arranger, input: &Event, t: usize, s: usize) -> Option<ArrangerCommand> {
|
|
||||||
//use ArrangerSelection as Selected;
|
|
||||||
//use SceneCommand as Scene;
|
|
||||||
//use TrackCommand as Track;
|
|
||||||
//use ClipCommand as Clip;
|
|
||||||
//let t_len = state.tracks.len();
|
|
||||||
//let s_len = state.scenes.len();
|
|
||||||
//Some(match input {
|
|
||||||
|
|
||||||
//kpat!(Char('g')) => ArrCmd::Pool(PoolCommand::Select(0)),
|
|
||||||
//kpat!(Char('q')) => ArrCmd::Clip(Clip::Enqueue(t, s)),
|
|
||||||
//kpat!(Char('l')) => ArrCmd::Clip(Clip::SetLoop(t, s, false)),
|
|
||||||
|
|
||||||
//kpat!(Enter) => if state.scenes[s].clips[t].is_none() {
|
|
||||||
//// FIXME: get this clip from the pool (autoregister via intmut)
|
|
||||||
//let (_, clip) = state.add_clip();
|
|
||||||
//ArrCmd::Clip(Clip::Put(t, s, Some(clip)))
|
|
||||||
//} else {
|
|
||||||
//return None
|
|
||||||
//},
|
|
||||||
//kpat!(Delete) => ArrCmd::Clip(Clip::Put(t, s, None)),
|
|
||||||
//kpat!(Char('p')) => ArrCmd::Clip(Clip::Put(t, s, state.pool.clip().clone())),
|
|
||||||
//kpat!(Char(',')) => ArrCmd::Clip(Clip::Put(t, s, None)),
|
|
||||||
//kpat!(Char('.')) => ArrCmd::Clip(Clip::Put(t, s, None)),
|
|
||||||
//kpat!(Char('<')) => ArrCmd::Clip(Clip::Put(t, s, None)),
|
|
||||||
//kpat!(Char('>')) => ArrCmd::Clip(Clip::Put(t, s, None)),
|
|
||||||
|
|
||||||
//kpat!(Up) => ArrCmd::Select(if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) }),
|
|
||||||
//kpat!(Down) => ArrCmd::Select(Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1)))),
|
|
||||||
//kpat!(Left) => ArrCmd::Select(if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) }),
|
|
||||||
//kpat!(Right) => ArrCmd::Select(Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s)),
|
|
||||||
|
|
||||||
//_ => return None
|
|
||||||
//})
|
|
||||||
//}
|
|
||||||
//fn scene_keymap (state: &Arranger, input: &Event, s: usize) -> Option<ArrangerCommand> {
|
|
||||||
//use ArrangerSelection as Selected;
|
|
||||||
//use SceneCommand as Scene;
|
|
||||||
//use TrackCommand as Track;
|
|
||||||
//use ClipCommand as Clip;
|
|
||||||
//let t_len = state.tracks.len();
|
|
||||||
//let s_len = state.scenes.len();
|
|
||||||
//Some(match input {
|
|
||||||
|
|
||||||
//kpat!(Char(',')) => ArrCmd::Scene(Scene::Swap(s, s - 1)),
|
|
||||||
//kpat!(Char('.')) => ArrCmd::Scene(Scene::Swap(s, s + 1)),
|
|
||||||
//kpat!(Char('<')) => ArrCmd::Scene(Scene::Swap(s, s - 1)),
|
|
||||||
//kpat!(Char('>')) => ArrCmd::Scene(Scene::Swap(s, s + 1)),
|
|
||||||
//kpat!(Char('q')) => ArrCmd::Scene(Scene::Enqueue(s)),
|
|
||||||
//kpat!(Delete) => ArrCmd::Scene(Scene::Del(s)),
|
|
||||||
//kpat!(Char('c')) => ArrCmd::Scene(Scene::SetColor(s, ItemPalette::random())),
|
|
||||||
|
|
||||||
//kpat!(Up) => ArrCmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix }),
|
|
||||||
//kpat!(Down) => ArrCmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1)))),
|
|
||||||
//kpat!(Left) => return None,
|
|
||||||
//kpat!(Right) => ArrCmd::Select(Selected::Clip(0, s)),
|
|
||||||
|
|
||||||
//_ => return None
|
|
||||||
//})
|
|
||||||
//}
|
|
||||||
//fn track_keymap (state: &Arranger, input: &Event, t: usize) -> Option<ArrangerCommand> {
|
|
||||||
//use ArrangerSelection as Selected;
|
|
||||||
//use SceneCommand as Scene;
|
|
||||||
//use TrackCommand as Track;
|
|
||||||
//use ClipCommand as Clip;
|
|
||||||
//let t_len = state.tracks.len();
|
|
||||||
//let s_len = state.scenes.len();
|
|
||||||
//Some(match input {
|
|
||||||
|
|
||||||
//kpat!(Char(',')) => ArrCmd::Track(Track::Swap(t, t - 1)),
|
|
||||||
//kpat!(Char('.')) => ArrCmd::Track(Track::Swap(t, t + 1)),
|
|
||||||
//kpat!(Char('<')) => ArrCmd::Track(Track::Swap(t, t - 1)),
|
|
||||||
//kpat!(Char('>')) => ArrCmd::Track(Track::Swap(t, t + 1)),
|
|
||||||
//kpat!(Delete) => ArrCmd::Track(Track::Del(t)),
|
|
||||||
//kpat!(Char('c')) => ArrCmd::Track(Track::SetColor(t, ItemPalette::random())),
|
|
||||||
|
|
||||||
//kpat!(Up) => return None,
|
|
||||||
//kpat!(Down) => ArrCmd::Select(Selected::Clip(t, 0)),
|
|
||||||
//kpat!(Left) => ArrCmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix }),
|
|
||||||
//kpat!(Right) => ArrCmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1)))),
|
|
||||||
|
|
||||||
//_ => return None
|
|
||||||
//})
|
|
||||||
//}
|
|
||||||
886
tek/src/lib.rs
886
tek/src/lib.rs
|
|
@ -5,44 +5,39 @@
|
||||||
#![feature(if_let_guard)]
|
#![feature(if_let_guard)]
|
||||||
#![feature(impl_trait_in_assoc_type)]
|
#![feature(impl_trait_in_assoc_type)]
|
||||||
#![feature(type_alias_impl_trait)]
|
#![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>>;
|
||||||
/// Standard optional result type.
|
/// Standard optional result type.
|
||||||
pub type Perhaps<T> = std::result::Result<Option<T>, Box<dyn Error>>;
|
pub type Perhaps<T> = std::result::Result<Option<T>, Box<dyn Error>>;
|
||||||
|
|
||||||
pub mod model; pub use self::model::*;
|
|
||||||
pub mod view; pub use self::view::*;
|
|
||||||
pub mod control; pub use self::control::*;
|
|
||||||
pub mod audio; pub use self::audio::*;
|
|
||||||
|
|
||||||
pub mod arranger; pub use self::arranger::*;
|
pub mod arranger; pub use self::arranger::*;
|
||||||
pub mod mixer; pub use self::mixer::*;
|
pub mod mixer; pub use self::mixer::*;
|
||||||
|
|
||||||
pub use ::tek_time; pub use ::tek_time::*;
|
|
||||||
pub use ::tek_jack; pub use ::tek_jack::{*, jack::{*, contrib::*}};
|
|
||||||
pub use ::tek_midi; pub use ::tek_midi::{*, midly::{*, num::*, live::*}};
|
|
||||||
pub use ::tek_sampler::{self, *};
|
|
||||||
pub use ::tek_plugin::{self, *};
|
|
||||||
pub use ::tek_tui::{
|
pub use ::tek_tui::{
|
||||||
*,
|
*,
|
||||||
tek_edn::*,
|
tek_edn::*,
|
||||||
tek_input::*,
|
tek_input::*,
|
||||||
tek_output::*,
|
tek_output::*,
|
||||||
crossterm::{
|
crossterm,
|
||||||
self,
|
crossterm::event::{
|
||||||
event::{
|
Event, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers,
|
||||||
Event, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers,
|
KeyCode::{self, *},
|
||||||
KeyCode::{self, *},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
ratatui,
|
||||||
ratatui::{
|
ratatui::{
|
||||||
self,
|
|
||||||
prelude::{Color, Style, Stylize, Buffer, Modifier},
|
prelude::{Color, Style, Stylize, Buffer, Modifier},
|
||||||
buffer::Cell,
|
buffer::Cell,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
pub use ::tek_time::{self, *};
|
||||||
|
pub use ::tek_jack::{self, *, jack::{*, contrib::*}};
|
||||||
|
pub use ::tek_midi::{self, *, midly::{*, num::*, live::*}};
|
||||||
|
pub use ::tek_sampler::{self, *};
|
||||||
|
pub use ::tek_plugin::{self, *};
|
||||||
|
use EdnItem::*;
|
||||||
|
pub(crate) use ClockCommand::{Play, Pause};
|
||||||
|
pub(crate) use KeyCode::{Tab, Char};
|
||||||
|
pub(crate) use SamplerCommand as SmplCmd;
|
||||||
|
pub(crate) use MidiEditCommand as EditCmd;
|
||||||
|
pub(crate) use PoolClipCommand as PoolCmd;
|
||||||
pub(crate) use std::cmp::{Ord, Eq, PartialEq};
|
pub(crate) use std::cmp::{Ord, Eq, PartialEq};
|
||||||
pub(crate) use std::collections::BTreeMap;
|
pub(crate) use std::collections::BTreeMap;
|
||||||
pub(crate) use std::error::Error;
|
pub(crate) use std::error::Error;
|
||||||
|
|
@ -56,240 +51,623 @@ pub(crate) use std::thread::{spawn, JoinHandle};
|
||||||
pub(crate) use std::time::Duration;
|
pub(crate) use std::time::Duration;
|
||||||
pub(crate) use std::path::PathBuf;
|
pub(crate) use std::path::PathBuf;
|
||||||
pub(crate) use std::ffi::OsString;
|
pub(crate) use std::ffi::OsString;
|
||||||
|
#[derive(Default)] pub struct App {
|
||||||
|
pub jack: Arc<RwLock<JackConnection>>,
|
||||||
|
pub edn: String,
|
||||||
|
pub clock: Clock,
|
||||||
|
pub color: ItemPalette,
|
||||||
|
pub editing: AtomicBool,
|
||||||
|
pub pool: Option<MidiPool>,
|
||||||
|
pub editor: Option<MidiEditor>,
|
||||||
|
pub player: Option<MidiPlayer>,
|
||||||
|
pub sampler: Option<Sampler>,
|
||||||
|
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
||||||
|
pub midi_ins: Vec<JackPort<MidiIn>>,
|
||||||
|
pub midi_outs: Vec<JackPort<MidiOut>>,
|
||||||
|
pub audio_ins: Vec<JackPort<AudioIn>>,
|
||||||
|
pub audio_outs: Vec<JackPort<AudioOut>>,
|
||||||
|
pub note_buf: Vec<u8>,
|
||||||
|
pub tracks: Vec<ArrangerTrack>,
|
||||||
|
pub scenes: Vec<ArrangerScene>,
|
||||||
|
pub selected: ArrangerSelection,
|
||||||
|
pub splits: Vec<u16>,
|
||||||
|
pub size: Measure<TuiOut>,
|
||||||
|
pub perf: PerfModel,
|
||||||
|
pub compact: bool,
|
||||||
|
}
|
||||||
|
impl App {
|
||||||
|
pub fn sequencer (
|
||||||
|
jack: &Arc<RwLock<JackConnection>>,
|
||||||
|
pool: MidiPool,
|
||||||
|
editor: MidiEditor,
|
||||||
|
player: Option<MidiPlayer>,
|
||||||
|
midi_froms: &[PortConnection],
|
||||||
|
midi_tos: &[PortConnection],
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
edn: include_str!("../edn/sequencer-view.edn").to_string(),
|
||||||
|
jack: jack.clone(),
|
||||||
|
pool: Some(pool),
|
||||||
|
editor: Some(editor),
|
||||||
|
player: player,
|
||||||
|
editing: false.into(),
|
||||||
|
midi_buf: vec![vec![];65536],
|
||||||
|
color: ItemPalette::random(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn groovebox (
|
||||||
|
jack: &Arc<RwLock<JackConnection>>,
|
||||||
|
pool: MidiPool,
|
||||||
|
editor: MidiEditor,
|
||||||
|
player: Option<MidiPlayer>,
|
||||||
|
midi_froms: &[PortConnection],
|
||||||
|
midi_tos: &[PortConnection],
|
||||||
|
sampler: Sampler,
|
||||||
|
audio_froms: &[&[PortConnection]],
|
||||||
|
audio_tos: &[&[PortConnection]],
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
edn: include_str!("../edn/groovebox-view.edn").to_string(),
|
||||||
|
sampler: Some(sampler),
|
||||||
|
..Self::sequencer(
|
||||||
|
jack, pool, editor,
|
||||||
|
player, midi_froms, midi_tos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn arranger (
|
||||||
|
jack: &Arc<RwLock<JackConnection>>,
|
||||||
|
pool: MidiPool,
|
||||||
|
editor: MidiEditor,
|
||||||
|
midi_froms: &[PortConnection],
|
||||||
|
midi_tos: &[PortConnection],
|
||||||
|
sampler: Sampler,
|
||||||
|
audio_froms: &[&[PortConnection]],
|
||||||
|
audio_tos: &[&[PortConnection]],
|
||||||
|
scenes: usize,
|
||||||
|
tracks: usize,
|
||||||
|
track_width: usize,
|
||||||
|
) -> Self {
|
||||||
|
let mut arranger = Self {
|
||||||
|
edn: include_str!("../edn/arranger-view.edn").to_string(),
|
||||||
|
..Self::groovebox(
|
||||||
|
jack, pool, editor,
|
||||||
|
None, midi_froms, midi_tos,
|
||||||
|
sampler, audio_froms, audio_tos
|
||||||
|
)
|
||||||
|
};
|
||||||
|
arranger.scenes_add(scenes);
|
||||||
|
arranger.tracks_add(tracks, track_width, &[], &[]);
|
||||||
|
arranger
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
});
|
||||||
|
has_size!(<TuiOut>|self: App|&self.size);
|
||||||
|
has_clock!(|self: App|&self.clock);
|
||||||
|
has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips);
|
||||||
|
has_editor!(|self: App|self.editor.as_ref().expect("no editor"));
|
||||||
|
edn_provide!(u16: |self: App|{
|
||||||
|
":sample-h" => if self.compact() { 0 } else { 5 },
|
||||||
|
":samples-w" => if self.compact() { 4 } else { 11 },
|
||||||
|
":samples-y" => if self.compact() { 1 } else { 0 },
|
||||||
|
":pool-w" => if self.compact() { 5 } else {
|
||||||
|
let w = self.size.w();
|
||||||
|
if w > 60 { 20 } else if w > 40 { 15 } else { 10 }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
edn_provide!(Color: |self: App| { _ => return None });
|
||||||
|
edn_provide!(usize: |self: App| { _ => return None });
|
||||||
|
edn_provide!(isize: |self: App| { _ => return None });
|
||||||
|
edn_provide!(bool: |self: App| { _ => return None });
|
||||||
|
edn_provide!(ArrangerSelection: |self: App| { _ => return None });
|
||||||
|
edn_provide!(Arc<RwLock<MidiClip>>: |self: App| { _ => return None });
|
||||||
|
handle!(TuiIn: |self: App, input| Ok(None));
|
||||||
|
#[derive(Clone, Debug)] pub enum AppCommand {
|
||||||
|
Clear,
|
||||||
|
Clip(ClipCommand),
|
||||||
|
Clock(ClockCommand),
|
||||||
|
Color(ItemPalette),
|
||||||
|
Compact(Option<bool>),
|
||||||
|
Editor(MidiEditCommand),
|
||||||
|
Enqueue(Option<Arc<RwLock<MidiClip>>>),
|
||||||
|
History(isize),
|
||||||
|
Pool(PoolCommand),
|
||||||
|
Sampler(SamplerCommand),
|
||||||
|
Scene(SceneCommand),
|
||||||
|
Select(ArrangerSelection),
|
||||||
|
StopAll,
|
||||||
|
Track(TrackCommand),
|
||||||
|
Zoom(Option<usize>),
|
||||||
|
}
|
||||||
|
edn_command!(AppCommand: |state: App| {
|
||||||
|
("clear" [] Self::Clear)
|
||||||
|
("stop-all" [] Self::StopAll)
|
||||||
|
("compact" [c: bool ] Self::Compact(c))
|
||||||
|
("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default()))
|
||||||
|
("history" [d: isize] Self::History(d.unwrap_or(0)))
|
||||||
|
("zoom" [z: usize] Self::Zoom(z))
|
||||||
|
("select" [s: ArrangerSelection] Self::Select(s.expect("no selection")))
|
||||||
|
("enqueue" [c: Arc<RwLock<MidiClip>>] Self::Enqueue(c))
|
||||||
|
("clock" [a, ..b] Self::Clock(ClockCommand::from_edn(state, &a.to_ref(), b)))
|
||||||
|
("track" [a, ..b] Self::Track(TrackCommand::from_edn(state, &a.to_ref(), b)))
|
||||||
|
("scene" [a, ..b] Self::Scene(SceneCommand::from_edn(state, &a.to_ref(), b)))
|
||||||
|
("clip" [a, ..b] Self::Clip(ClipCommand::from_edn(state, &a.to_ref(), b)))
|
||||||
|
("pool" [a, ..b] Self::Pool(PoolCommand::from_edn(state.pool.as_ref().expect("no pool"), &a.to_ref(), b)))
|
||||||
|
("editor" [a, ..b] Self::Editor(MidiEditCommand::from_edn(state.editor.as_ref().expect("no editor"), &a.to_ref(), b)))
|
||||||
|
("sampler" [a, ..b] Self::Sampler(SamplerCommand::from_edn(state.sampler.as_ref().expect("no sampler"), &a.to_ref(), b)))
|
||||||
|
});
|
||||||
|
command!(|self: AppCommand, state: App|match self {
|
||||||
|
Self::Clear => { 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) =>
|
||||||
|
state.editor.as_mut().map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(),
|
||||||
|
Self::Sampler(cmd) =>
|
||||||
|
state.sampler.as_mut().map(|sampler|cmd.delegate(sampler, Self::Sampler)).transpose()?.flatten(),
|
||||||
|
Self::Enqueue(clip) =>
|
||||||
|
state.player.as_mut().map(|player|{player.enqueue_next(clip.as_ref());None}).flatten(),
|
||||||
|
Self::StopAll => {
|
||||||
|
for track in 0..state.tracks.len() { state.tracks[track].player.enqueue_next(None); }
|
||||||
|
None
|
||||||
|
},
|
||||||
|
Self::Color(palette) => {
|
||||||
|
let old = state.color;
|
||||||
|
state.color = palette;
|
||||||
|
Some(Self::Color(old))
|
||||||
|
},
|
||||||
|
Self::Pool(cmd) => if let Some(pool) = state.pool.as_mut() {
|
||||||
|
let undo = cmd.clone().delegate(pool, Self::Pool)?;
|
||||||
|
if let Some(editor) = state.editor.as_mut() {
|
||||||
|
match cmd {
|
||||||
|
// autoselect: automatically load selected clip in editor
|
||||||
|
// autocolor: update color in all places simultaneously
|
||||||
|
PoolCommand::Select(_) | PoolCommand::Clip(PoolCmd::SetColor(_, _)) =>
|
||||||
|
editor.set_clip(pool.clip().as_ref()),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
undo
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
Self::Compact(compact) => match compact {
|
||||||
|
Some(compact) => {
|
||||||
|
if state.compact != compact {
|
||||||
|
state.compact = compact;
|
||||||
|
Some(Self::Compact(Some(!compact)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
state.compact = !state.compact;
|
||||||
|
Some(Self::Compact(Some(!state.compact)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
render!(TuiOut: (self: App) => self.size.of(EdnView::from_source(self, self.edn.as_ref())));
|
||||||
|
edn_provide!('a: Box<dyn Render<TuiOut> + 'a>: |self: App|{
|
||||||
|
":editor" => (&self.editor).boxed(),
|
||||||
|
":pool" => self.pool.as_ref().map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact(), pool)))).boxed(),
|
||||||
|
":sample" => self.sample().boxed(),
|
||||||
|
":sampler" => self.sampler().boxed(),
|
||||||
|
":status" => self.editor.as_ref().map(|e|Bsp::e(e.clip_status(), e.edit_status())).boxed(),
|
||||||
|
":toolbar" => Fill::x(Fixed::y(2, Align::x(ClockView::new(true, &self.clock)))).boxed(),
|
||||||
|
":tracks" => self.row(self.w(), 3, track_header(&self), track_cells(&self)).boxed(),
|
||||||
|
":inputs" => self.row(self.w(), 3, input_header(&self), input_cells(&self)).boxed(),
|
||||||
|
":outputs" => self.row(self.w(), 3, output_header(&self), output_cells(&self)).boxed(),
|
||||||
|
":scenes" => self.scene_row(self.w(), self.size.h().saturating_sub(9) as u16).boxed(),
|
||||||
|
});
|
||||||
|
impl App {
|
||||||
|
fn compact (&self) -> bool { false }
|
||||||
|
fn editor (&self) -> impl Content<TuiOut> + '_ { &self.editor }
|
||||||
|
fn w (&self) -> u16 { self.tracks_with_sizes().last().map(|x|x.3 as u16).unwrap_or(0) }
|
||||||
|
fn pool (&self) -> impl Content<TuiOut> + use<'_> { self.pool.as_ref().map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact(), pool)))) }
|
||||||
|
fn sample <'a> (&'a self) -> impl Content<TuiOut> + 'a {
|
||||||
|
let compact = self.is_editing();
|
||||||
|
if let (Some(editor), Some(sampler)) = (&self.editor, &self.sampler) {
|
||||||
|
let note_pt = editor.note_point();
|
||||||
|
let sample_h = if compact { 0 } else { 5 };
|
||||||
|
return Some(Max::y(sample_h, Fill::x(sampler.viewer(note_pt))))
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn sampler (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
let compact = self.is_editing();
|
||||||
|
if let (Some(editor), Some(sampler)) = (&self.editor, &self.sampler) {
|
||||||
|
let note_pt = editor.note_point();
|
||||||
|
let w = if compact { 4 } else { 40 };
|
||||||
|
let y = if compact { 1 } else { 0 };
|
||||||
|
return Some(Fixed::x(w, Push::y(y, Fill::y(sampler.list(compact, editor)))))
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn row <'a> (
|
||||||
|
&'a self, w: u16, h: u16, a: impl Content<TuiOut> + 'a, b: impl Content<TuiOut> + 'a
|
||||||
|
) -> impl Content<TuiOut> + 'a {
|
||||||
|
Fixed::y(h, Bsp::e(
|
||||||
|
Fixed::xy(self.sidebar_w() as u16, h, a),
|
||||||
|
Fill::x(Align::c(Fixed::xy(w, h, b)))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
pub fn tracks_with_sizes (&self)
|
||||||
|
-> impl Iterator<Item = (usize, &ArrangerTrack, usize, usize)>
|
||||||
|
{
|
||||||
|
tracks_with_sizes(self.tracks.iter(), match self.selected {
|
||||||
|
ArrangerSelection::Track(t) if self.is_editing() => Some(t),
|
||||||
|
ArrangerSelection::Clip(t, _) if self.is_editing() => Some(t),
|
||||||
|
_ => None
|
||||||
|
}, self.editor_w())
|
||||||
|
}
|
||||||
|
pub fn scenes_with_sizes (&self, h: usize)
|
||||||
|
-> impl Iterator<Item = (usize, &ArrangerScene, usize, usize)>
|
||||||
|
{
|
||||||
|
scenes_with_sizes(self.scenes.iter(), &self.selected, self.is_editing(), 2, 15)
|
||||||
|
}
|
||||||
|
fn is_editing (&self) -> bool {
|
||||||
|
self.editing.load(Relaxed)
|
||||||
|
}
|
||||||
|
fn editor_w (&self) -> usize {
|
||||||
|
let editor = self.editor.as_ref().expect("missing editor");
|
||||||
|
(5 + (editor.time_len().get() / editor.time_zoom().get()))
|
||||||
|
.min(self.size.w().saturating_sub(20))
|
||||||
|
.max(16)
|
||||||
|
}
|
||||||
|
fn sidebar_w (&self) -> u16 {
|
||||||
|
let w = self.size.w();
|
||||||
|
let w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
|
||||||
|
let w = if self.is_editing() { 8 } else { w };
|
||||||
|
w
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn scenes_with_sizes <'a>(
|
||||||
|
scenes: impl Iterator<Item=&'a ArrangerScene> + 'a,
|
||||||
|
selected: &'a ArrangerSelection,
|
||||||
|
editing: bool,
|
||||||
|
scene_height: usize,
|
||||||
|
scene_larger: usize,
|
||||||
|
) -> impl Iterator<Item = (usize, &'a ArrangerScene, usize, usize)> + 'a {
|
||||||
|
let mut y = 0;
|
||||||
|
let (selected_track, selected_scene) = match selected {
|
||||||
|
ArrangerSelection::Clip(t, s) => (Some(t), Some(s)),
|
||||||
|
_ => (None, None)
|
||||||
|
};
|
||||||
|
scenes.enumerate().map(move|(s, scene)|{
|
||||||
|
let active = editing && selected_track.is_some() && selected_scene == Some(&s);
|
||||||
|
let height = if active { scene_larger } else { scene_height };
|
||||||
|
let data = (s, scene, y, y + height);
|
||||||
|
y += height;
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn tracks_with_sizes <'a> (
|
||||||
|
tracks: impl Iterator<Item=&'a ArrangerTrack>,
|
||||||
|
active: Option<usize>,
|
||||||
|
bigger: usize
|
||||||
|
) -> impl Iterator<Item=(usize,&'a ArrangerTrack,usize,usize)> {
|
||||||
|
let mut x = 0;
|
||||||
|
tracks.enumerate().map(move |(index, track)|{
|
||||||
|
let width = if Some(index) == active { bigger } else { track.width.max(8) };
|
||||||
|
let data = (index, track, x, x + width);
|
||||||
|
x += width;
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn track_header <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> {
|
||||||
|
(||Tui::bg(TuiTheme::g(32), Tui::bold(true, Bsp::s(
|
||||||
|
row!(
|
||||||
|
Tui::fg(TuiTheme::g(128), "add "),
|
||||||
|
Tui::fg(TuiTheme::orange(), "t"),
|
||||||
|
Tui::fg(TuiTheme::g(128), "rack"),
|
||||||
|
),
|
||||||
|
row!(
|
||||||
|
Tui::fg(TuiTheme::orange(), "a"),
|
||||||
|
Tui::fg(TuiTheme::g(128), "dd scene"),
|
||||||
|
),
|
||||||
|
))).boxed()).into()
|
||||||
|
}
|
||||||
|
pub fn track_cells <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> {
|
||||||
|
let iter = ||state.tracks_with_sizes();
|
||||||
|
(move||Align::x(Map::new(iter, move|(_, track, x1, x2), i| {
|
||||||
|
let name = Push::x(1, &track.name);
|
||||||
|
let color = track.color;
|
||||||
|
let fg = color.lightest.rgb;
|
||||||
|
let bg = color.base.rgb;
|
||||||
|
let active = state.selected.track() == Some(i);
|
||||||
|
let bfg = if active { Color::Rgb(255,255,255) } else { Color::Rgb(0,0,0) };
|
||||||
|
let border = Style::default().fg(bfg).bg(bg);
|
||||||
|
Tui::bg(bg, map_east(x1 as u16, (x2 - x1) as u16,
|
||||||
|
Outer(border).enclose(Tui::fg_bg(fg, bg, Tui::bold(true, Fill::x(Align::x(name)))))
|
||||||
|
))
|
||||||
|
})).boxed()).into()
|
||||||
|
}
|
||||||
|
fn help_tag <'a>(before: &'a str, key: &'a str, after: &'a str) -> impl Content<TuiOut> + 'a {
|
||||||
|
let lo = TuiTheme::g(128);
|
||||||
|
let hi = TuiTheme::orange();
|
||||||
|
Tui::bold(true, row!(Tui::fg(lo, before), Tui::fg(hi, key), Tui::fg(lo, after)))
|
||||||
|
}
|
||||||
|
fn input_header <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> {
|
||||||
|
let fg = TuiTheme::g(224);
|
||||||
|
let bg = TuiTheme::g(64);
|
||||||
|
(move||Bsp::s(help_tag("midi ", "I", "ns"), state.midi_ins.get(0).map(|inp|Bsp::s(
|
||||||
|
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(inp.name.clone())))),
|
||||||
|
inp.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
|
||||||
|
Tui::fg_bg(fg, bg, connect.info()))))),
|
||||||
|
))).boxed()).into()
|
||||||
|
}
|
||||||
|
fn input_cells <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> {
|
||||||
|
(move||Align::x(Map::new(||state.tracks_with_sizes(), move|(_, track, x1, x2), i| {
|
||||||
|
let w = (x2 - x1) as u16;
|
||||||
|
let color: ItemPalette = track.color.dark.into();
|
||||||
|
map_east(x1 as u16, w, Fixed::x(w, cell(color, Bsp::n(
|
||||||
|
rec_mon(color.base.rgb, false, false),
|
||||||
|
phat_hi(color.base.rgb, color.dark.rgb)
|
||||||
|
))))
|
||||||
|
})).boxed()).into()
|
||||||
|
}
|
||||||
|
fn output_header <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> {
|
||||||
|
let fg = TuiTheme::g(224);
|
||||||
|
let bg = TuiTheme::g(64);
|
||||||
|
(move||Bsp::s(help_tag("midi ", "O", "uts"), state.midi_outs.get(0).map(|out|Bsp::s(
|
||||||
|
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(out.name.clone())))),
|
||||||
|
out.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
|
||||||
|
Tui::fg_bg(fg, bg, connect.info()))))),
|
||||||
|
))).boxed()).into()
|
||||||
|
}
|
||||||
|
fn output_cells <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> {
|
||||||
|
(move||Align::x(Map::new(||state.tracks_with_sizes(), move|(_, track, x1, x2), i| {
|
||||||
|
let w = (x2 - x1) as u16;
|
||||||
|
let color: ItemPalette = track.color.dark.into();
|
||||||
|
map_east(x1 as u16, w, Fixed::x(w, cell(color, Bsp::n(
|
||||||
|
mute_solo(color.base.rgb, false, false),
|
||||||
|
phat_hi(color.dark.rgb, color.darker.rgb)
|
||||||
|
))))
|
||||||
|
})).boxed()).into()
|
||||||
|
}
|
||||||
|
fn scene_header <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> {
|
||||||
|
(||{
|
||||||
|
let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0))));
|
||||||
|
let selected = state.selected.scene();
|
||||||
|
Fill::y(Align::c(Map::new(||state.scenes_with_sizes(2), move|(_, scene, y1, y2), i| {
|
||||||
|
let h = (y2 - y1) as u16;
|
||||||
|
let name = format!("🭬{}", &scene.name);
|
||||||
|
let color = scene.color;
|
||||||
|
let active = selected == Some(i);
|
||||||
|
let mid = if active { color.light } else { color.base };
|
||||||
|
let top = Some(last_color.read().unwrap().base.rgb);
|
||||||
|
let cell = phat_sel_3(
|
||||||
|
active,
|
||||||
|
Tui::bold(true, name.clone()),
|
||||||
|
Tui::bold(true, name),
|
||||||
|
top,
|
||||||
|
mid.rgb,
|
||||||
|
Color::Rgb(0, 0, 0)
|
||||||
|
);
|
||||||
|
*last_color.write().unwrap() = color;
|
||||||
|
map_south(y1 as u16, h + 1, Fixed::y(h + 1, cell))
|
||||||
|
}))).boxed()
|
||||||
|
}).into()
|
||||||
|
}
|
||||||
|
fn scene_cells <'a> (state: &'a App) -> BoxThunk<'a, TuiOut> {
|
||||||
|
let editing = state.is_editing();
|
||||||
|
let tracks = move||state.tracks_with_sizes();
|
||||||
|
let scenes = ||state.scenes_with_sizes(2);
|
||||||
|
let selected_track = state.selected.track();
|
||||||
|
let selected_scene = state.selected.scene();
|
||||||
|
(move||Fill::y(Align::c(Map::new(tracks, move|(_, track, x1, x2), t| {
|
||||||
|
let w = (x2 - x1) as u16;
|
||||||
|
let color: ItemPalette = track.color.dark.into();
|
||||||
|
let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0))));
|
||||||
|
let cells = Map::new(scenes, move|(_, scene, y1, y2), s| {
|
||||||
|
let h = (y2 - y1) as u16;
|
||||||
|
let color = scene.color;
|
||||||
|
let (name, fg, bg) = if let Some(c) = &scene.clips[t] {
|
||||||
|
let c = c.read().unwrap();
|
||||||
|
(c.name.to_string(), c.color.lightest.rgb, c.color.base.rgb)
|
||||||
|
} else {
|
||||||
|
("⏹ ".to_string(), TuiTheme::g(64), TuiTheme::g(32))
|
||||||
|
};
|
||||||
|
let last = last_color.read().unwrap().clone();
|
||||||
|
let active = editing && selected_scene == Some(s) && selected_track == Some(t);
|
||||||
|
let editor = Thunk::new(||&state.editor);
|
||||||
|
let cell = Thunk::new(move||phat_sel_3(
|
||||||
|
selected_track == Some(t) && selected_scene == Some(s),
|
||||||
|
Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))),
|
||||||
|
Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))),
|
||||||
|
if selected_track == Some(t) && selected_scene.map(|s|s+1) == Some(s) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(bg.into())
|
||||||
|
},
|
||||||
|
bg.into(),
|
||||||
|
bg.into(),
|
||||||
|
));
|
||||||
|
let cell = Either(active, editor, cell);
|
||||||
|
*last_color.write().unwrap() = bg.into();
|
||||||
|
map_south(
|
||||||
|
y1 as u16,
|
||||||
|
h + 1,
|
||||||
|
Fill::x(Fixed::y(h + 1, cell))
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let column = Fixed::x(w, Tui::bg(Color::Reset, Align::y(cells)).boxed());
|
||||||
|
Fixed::x(w, map_east(x1 as u16, w, column))
|
||||||
|
}))).boxed()).into()
|
||||||
|
}
|
||||||
|
fn cell_clip <'a> (
|
||||||
|
scene: &'a ArrangerScene, index: usize, track: &'a ArrangerTrack, w: u16, h: u16
|
||||||
|
) -> impl Content<TuiOut> + use<'a> {
|
||||||
|
scene.clips.get(index).map(|clip|clip.as_ref().map(|clip|{
|
||||||
|
let clip = clip.read().unwrap();
|
||||||
|
let mut bg = TuiTheme::border_bg();
|
||||||
|
let name = clip.name.to_string();
|
||||||
|
let max_w = name.len().min((w as usize).saturating_sub(2));
|
||||||
|
let color = clip.color;
|
||||||
|
bg = color.dark.rgb;
|
||||||
|
if let Some((_, Some(ref playing))) = track.player.play_clip() {
|
||||||
|
if *playing.read().unwrap() == *clip {
|
||||||
|
bg = color.light.rgb
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Fixed::xy(w, h, &Tui::bg(bg, Push::x(1, Fixed::x(w, &name.as_str()[0..max_w]))));
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
fn rec_mon (bg: Color, rec: bool, mon: bool) -> impl Content<TuiOut> {
|
||||||
|
row!(
|
||||||
|
Tui::fg_bg(if rec { Color::Red } else { bg }, bg, "▐"),
|
||||||
|
Tui::fg_bg(if rec { Color::White } else { Color::Rgb(0,0,0) }, bg, "REC"),
|
||||||
|
Tui::fg_bg(if rec { Color::White } else { bg }, bg, "▐"),
|
||||||
|
Tui::fg_bg(if mon { Color::White } else { Color::Rgb(0,0,0) }, bg, "MON"),
|
||||||
|
Tui::fg_bg(if mon { Color::White } else { bg }, bg, "▌"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn mute_solo (bg: Color, mute: bool, solo: bool) -> impl Content<TuiOut> {
|
||||||
|
row!(
|
||||||
|
Tui::fg_bg(if mute { Color::White } else { Color::Rgb(0,0,0) }, bg, "MUTE"),
|
||||||
|
Tui::fg_bg(if mute { Color::White } else { bg }, bg, "▐"),
|
||||||
|
Tui::fg_bg(if solo { Color::White } else { Color::Rgb(0,0,0) }, bg, "SOLO"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn cell <T: Content<TuiOut>> (color: ItemPalette, field: T) -> impl Content<TuiOut> {
|
||||||
|
Tui::fg_bg(color.lightest.rgb, color.base.rgb, Fixed::y(1, field))
|
||||||
|
}
|
||||||
#[cfg(test)] fn test_tek () {
|
#[cfg(test)] fn test_tek () {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
//#[cfg(test)] mod test_focus {
|
|
||||||
//use super::focus::*;
|
|
||||||
//#[test] fn test_focus () {
|
|
||||||
|
|
||||||
//struct FocusTest {
|
|
||||||
//focused: char,
|
|
||||||
//cursor: (usize, usize)
|
|
||||||
//}
|
|
||||||
|
|
||||||
//impl HasFocus for FocusTest {
|
|
||||||
//type Item = char;
|
|
||||||
//fn focused (&self) -> Self::Item {
|
|
||||||
//self.focused
|
|
||||||
//}
|
|
||||||
//fn set_focused (&mut self, to: Self::Item) {
|
|
||||||
//self.focused = to
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
//impl FocusGrid for FocusTest {
|
|
||||||
//fn focus_cursor (&self) -> (usize, usize) {
|
|
||||||
//self.cursor
|
|
||||||
//}
|
|
||||||
//fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
|
|
||||||
//&mut self.cursor
|
|
||||||
//}
|
|
||||||
//fn focus_layout (&self) -> &[&[Self::Item]] {
|
|
||||||
//&[
|
|
||||||
//&['a', 'a', 'a', 'b', 'b', 'd'],
|
|
||||||
//&['a', 'a', 'a', 'b', 'b', 'd'],
|
|
||||||
//&['a', 'a', 'a', 'c', 'c', 'd'],
|
|
||||||
//&['a', 'a', 'a', 'c', 'c', 'd'],
|
|
||||||
//&['e', 'e', 'e', 'e', 'e', 'e'],
|
|
||||||
//]
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
//let mut tester = FocusTest { focused: 'a', cursor: (0, 0) };
|
|
||||||
|
|
||||||
//tester.focus_right();
|
|
||||||
//assert_eq!(tester.cursor.0, 3);
|
|
||||||
//assert_eq!(tester.focused, 'b');
|
|
||||||
|
|
||||||
//tester.focus_down();
|
|
||||||
//assert_eq!(tester.cursor.1, 2);
|
|
||||||
//assert_eq!(tester.focused, 'c');
|
|
||||||
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//use crate::*;
|
|
||||||
|
|
||||||
//struct TestEngine([u16;4], Vec<Vec<char>>);
|
|
||||||
|
|
||||||
//impl Engine for TestEngine {
|
|
||||||
//type Unit = u16;
|
|
||||||
//type Size = [Self::Unit;2];
|
|
||||||
//type Area = [Self::Unit;4];
|
|
||||||
//type Input = Self;
|
|
||||||
//type Handled = bool;
|
|
||||||
//fn exited (&self) -> bool {
|
|
||||||
//true
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
//#[derive(Copy, Clone)]
|
|
||||||
//struct TestArea(u16, u16);
|
|
||||||
|
|
||||||
//impl Render<TestEngine> for TestArea {
|
|
||||||
//fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
|
||||||
//Ok(Some([to[0], to[1], self.0, self.1]))
|
|
||||||
//}
|
|
||||||
//fn render (&self, to: &mut TestEngine) -> Perhaps<[u16;4]> {
|
|
||||||
//if let Some(layout) = self.layout(to.area())? {
|
|
||||||
//for y in layout.y()..layout.y()+layout.h()-1 {
|
|
||||||
//for x in layout.x()..layout.x()+layout.w()-1 {
|
|
||||||
//to.1[y as usize][x as usize] = '*';
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//Ok(Some(layout))
|
|
||||||
//} else {
|
|
||||||
//Ok(None)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
//#[test]
|
|
||||||
//fn test_plus_minus () -> Usually<()> {
|
|
||||||
//let area = [0, 0, 10, 10];
|
|
||||||
//let engine = TestEngine(area, vec![vec![' ';10];10]);
|
|
||||||
//let test = TestArea(4, 4);
|
|
||||||
//assert_eq!(test.layout(area)?, Some([0, 0, 4, 4]));
|
|
||||||
//assert_eq!(Push::X(1, test).layout(area)?, Some([1, 0, 4, 4]));
|
|
||||||
//Ok(())
|
|
||||||
//}
|
|
||||||
|
|
||||||
//#[test]
|
|
||||||
//fn test_outset_align () -> Usually<()> {
|
|
||||||
//let area = [0, 0, 10, 10];
|
|
||||||
//let engine = TestEngine(area, vec![vec![' ';10];10]);
|
|
||||||
//let test = TestArea(4, 4);
|
|
||||||
//assert_eq!(test.layout(area)?, Some([0, 0, 4, 4]));
|
|
||||||
//assert_eq!(Margin::X(1, test).layout(area)?, Some([0, 0, 6, 4]));
|
|
||||||
//assert_eq!(Align::X(test).layout(area)?, Some([3, 0, 4, 4]));
|
|
||||||
//assert_eq!(Align::X(Margin::X(1, test)).layout(area)?, Some([2, 0, 6, 4]));
|
|
||||||
//assert_eq!(Margin::X(1, Align::X(test)).layout(area)?, Some([2, 0, 6, 4]));
|
|
||||||
//Ok(())
|
|
||||||
//}
|
|
||||||
|
|
||||||
////#[test]
|
|
||||||
////fn test_misc () -> Usually<()> {
|
|
||||||
////let area: [u16;4] = [0, 0, 10, 10];
|
|
||||||
////let test = TestArea(4, 4);
|
|
||||||
////assert_eq!(test.layout(area)?,
|
|
||||||
////Some([0, 0, 4, 4]));
|
|
||||||
////assert_eq!(Align::Center(test).layout(area)?,
|
|
||||||
////Some([3, 3, 4, 4]));
|
|
||||||
////assert_eq!(Align::Center(Stack::down(|add|{
|
|
||||||
////add(&test)?;
|
|
||||||
////add(&test)
|
|
||||||
////})).layout(area)?,
|
|
||||||
////Some([3, 1, 4, 8]));
|
|
||||||
////assert_eq!(Align::Center(Stack::down(|add|{
|
|
||||||
////add(&Margin::XY(2, 2, test))?;
|
|
||||||
////add(&test)
|
|
||||||
////})).layout(area)?,
|
|
||||||
////Some([2, 0, 6, 10]));
|
|
||||||
////assert_eq!(Align::Center(Stack::down(|add|{
|
|
||||||
////add(&Margin::XY(2, 2, test))?;
|
|
||||||
////add(&Padding::XY(2, 2, test))
|
|
||||||
////})).layout(area)?,
|
|
||||||
////Some([2, 1, 6, 8]));
|
|
||||||
////assert_eq!(Stack::down(|add|{
|
|
||||||
////add(&Margin::XY(2, 2, test))?;
|
|
||||||
////add(&Padding::XY(2, 2, test))
|
|
||||||
////}).layout(area)?,
|
|
||||||
////Some([0, 0, 6, 8]));
|
|
||||||
////assert_eq!(Stack::right(|add|{
|
|
||||||
////add(&Stack::down(|add|{
|
|
||||||
////add(&Margin::XY(2, 2, test))?;
|
|
||||||
////add(&Padding::XY(2, 2, test))
|
|
||||||
////}))?;
|
|
||||||
////add(&Align::Center(TestArea(2 ,2)))
|
|
||||||
////}).layout(area)?,
|
|
||||||
////Some([0, 0, 8, 8]));
|
|
||||||
////Ok(())
|
|
||||||
////}
|
|
||||||
|
|
||||||
////#[test]
|
|
||||||
////fn test_offset () -> Usually<()> {
|
|
||||||
////let area: [u16;4] = [50, 50, 100, 100];
|
|
||||||
////let test = TestArea(3, 3);
|
|
||||||
////assert_eq!(Push::X(1, test).layout(area)?, Some([51, 50, 3, 3]));
|
|
||||||
////assert_eq!(Push::Y(1, test).layout(area)?, Some([50, 51, 3, 3]));
|
|
||||||
////assert_eq!(Push::XY(1, 1, test).layout(area)?, Some([51, 51, 3, 3]));
|
|
||||||
////Ok(())
|
|
||||||
////}
|
|
||||||
|
|
||||||
////#[test]
|
|
||||||
////fn test_outset () -> Usually<()> {
|
|
||||||
////let area: [u16;4] = [50, 50, 100, 100];
|
|
||||||
////let test = TestArea(3, 3);
|
|
||||||
////assert_eq!(Margin::X(1, test).layout(area)?, Some([49, 50, 5, 3]));
|
|
||||||
////assert_eq!(Margin::Y(1, test).layout(area)?, Some([50, 49, 3, 5]));
|
|
||||||
////assert_eq!(Margin::XY(1, 1, test).layout(area)?, Some([49, 49, 5, 5]));
|
|
||||||
////Ok(())
|
|
||||||
////}
|
|
||||||
|
|
||||||
////#[test]
|
|
||||||
////fn test_padding () -> Usually<()> {
|
|
||||||
////let area: [u16;4] = [50, 50, 100, 100];
|
|
||||||
////let test = TestArea(3, 3);
|
|
||||||
////assert_eq!(Padding::X(1, test).layout(area)?, Some([51, 50, 1, 3]));
|
|
||||||
////assert_eq!(Padding::Y(1, test).layout(area)?, Some([50, 51, 3, 1]));
|
|
||||||
////assert_eq!(Padding::XY(1, 1, test).layout(area)?, Some([51, 51, 1, 1]));
|
|
||||||
////Ok(())
|
|
||||||
////}
|
|
||||||
|
|
||||||
////#[test]
|
|
||||||
////fn test_stuff () -> Usually<()> {
|
|
||||||
////let area: [u16;4] = [0, 0, 100, 100];
|
|
||||||
////assert_eq!("1".layout(area)?,
|
|
||||||
////Some([0, 0, 1, 1]));
|
|
||||||
////assert_eq!("333".layout(area)?,
|
|
||||||
////Some([0, 0, 3, 1]));
|
|
||||||
////assert_eq!(Layers::new(|add|{add(&"1")?;add(&"333")}).layout(area)?,
|
|
||||||
////Some([0, 0, 3, 1]));
|
|
||||||
////assert_eq!(Stack::down(|add|{add(&"1")?;add(&"333")}).layout(area)?,
|
|
||||||
////Some([0, 0, 3, 2]));
|
|
||||||
////assert_eq!(Stack::right(|add|{add(&"1")?;add(&"333")}).layout(area)?,
|
|
||||||
////Some([0, 0, 4, 1]));
|
|
||||||
////assert_eq!(Stack::down(|add|{
|
|
||||||
////add(&Stack::right(|add|{add(&"1")?;add(&"333")}))?;
|
|
||||||
////add(&"55555")
|
|
||||||
////}).layout(area)?,
|
|
||||||
////Some([0, 0, 5, 2]));
|
|
||||||
////let area: [u16;4] = [1, 1, 100, 100];
|
|
||||||
////assert_eq!(Margin::X(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?,
|
|
||||||
////Some([0, 1, 6, 1]));
|
|
||||||
////assert_eq!(Margin::Y(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?,
|
|
||||||
////Some([1, 0, 4, 3]));
|
|
||||||
////assert_eq!(Margin::XY(1, 1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?,
|
|
||||||
////Some([0, 0, 6, 3]));
|
|
||||||
////assert_eq!(Stack::down(|add|{
|
|
||||||
////add(&Margin::XY(1, 1, "1"))?;
|
|
||||||
////add(&Margin::XY(1, 1, "333"))
|
|
||||||
////}).layout(area)?,
|
|
||||||
////Some([1, 1, 5, 6]));
|
|
||||||
////let area: [u16;4] = [1, 1, 95, 100];
|
|
||||||
////assert_eq!(Align::Center(Stack::down(|add|{
|
|
||||||
////add(&Margin::XY(1, 1, "1"))?;
|
|
||||||
////add(&Margin::XY(1, 1, "333"))
|
|
||||||
////})).layout(area)?,
|
|
||||||
////Some([46, 48, 5, 6]));
|
|
||||||
////assert_eq!(Align::Center(Stack::down(|add|{
|
|
||||||
////add(&Layers::new(|add|{
|
|
||||||
//////add(&Margin::XY(1, 1, Background(Color::Rgb(0,128,0))))?;
|
|
||||||
////add(&Margin::XY(1, 1, "1"))?;
|
|
||||||
////add(&Margin::XY(1, 1, "333"))?;
|
|
||||||
//////add(&Background(Color::Rgb(0,128,0)))?;
|
|
||||||
////Ok(())
|
|
||||||
////}))?;
|
|
||||||
////add(&Layers::new(|add|{
|
|
||||||
//////add(&Margin::XY(1, 1, Background(Color::Rgb(0,0,128))))?;
|
|
||||||
////add(&Margin::XY(1, 1, "555"))?;
|
|
||||||
////add(&Margin::XY(1, 1, "777777"))?;
|
|
||||||
//////add(&Background(Color::Rgb(0,0,128)))?;
|
|
||||||
////Ok(())
|
|
||||||
////}))
|
|
||||||
////})).layout(area)?,
|
|
||||||
////Some([46, 48, 5, 6]));
|
|
||||||
////Ok(())
|
|
||||||
////}
|
|
||||||
|
|
|
||||||
169
tek/src/model.rs
169
tek/src/model.rs
|
|
@ -1,169 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
#[derive(Default)] pub struct App {
|
|
||||||
pub jack: Arc<RwLock<JackConnection>>,
|
|
||||||
pub edn: String,
|
|
||||||
pub clock: Clock,
|
|
||||||
pub color: ItemPalette,
|
|
||||||
pub editing: AtomicBool,
|
|
||||||
pub pool: Option<PoolModel>,
|
|
||||||
pub editor: Option<MidiEditor>,
|
|
||||||
pub player: Option<MidiPlayer>,
|
|
||||||
pub sampler: Option<Sampler>,
|
|
||||||
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
|
||||||
pub midi_ins: Vec<JackPort<MidiIn>>,
|
|
||||||
pub midi_outs: Vec<JackPort<MidiOut>>,
|
|
||||||
pub audio_ins: Vec<JackPort<AudioIn>>,
|
|
||||||
pub audio_outs: Vec<JackPort<AudioOut>>,
|
|
||||||
pub note_buf: Vec<u8>,
|
|
||||||
pub tracks: Vec<ArrangerTrack>,
|
|
||||||
pub scenes: Vec<ArrangerScene>,
|
|
||||||
pub selected: ArrangerSelection,
|
|
||||||
pub splits: Vec<u16>,
|
|
||||||
pub size: Measure<TuiOut>,
|
|
||||||
pub perf: PerfModel,
|
|
||||||
pub compact: bool,
|
|
||||||
}
|
|
||||||
impl App {
|
|
||||||
pub fn sequencer (
|
|
||||||
jack: &Arc<RwLock<JackConnection>>,
|
|
||||||
pool: PoolModel,
|
|
||||||
editor: MidiEditor,
|
|
||||||
player: Option<MidiPlayer>,
|
|
||||||
midi_froms: &[PortConnection],
|
|
||||||
midi_tos: &[PortConnection],
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
edn: include_str!("../edn/sequencer-view.edn").to_string(),
|
|
||||||
jack: jack.clone(),
|
|
||||||
pool: Some(pool),
|
|
||||||
editor: Some(editor),
|
|
||||||
player: player,
|
|
||||||
editing: false.into(),
|
|
||||||
midi_buf: vec![vec![];65536],
|
|
||||||
color: ItemPalette::random(),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn groovebox (
|
|
||||||
jack: &Arc<RwLock<JackConnection>>,
|
|
||||||
pool: PoolModel,
|
|
||||||
editor: MidiEditor,
|
|
||||||
player: Option<MidiPlayer>,
|
|
||||||
midi_froms: &[PortConnection],
|
|
||||||
midi_tos: &[PortConnection],
|
|
||||||
sampler: Sampler,
|
|
||||||
audio_froms: &[&[PortConnection]],
|
|
||||||
audio_tos: &[&[PortConnection]],
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
edn: include_str!("../edn/groovebox-view.edn").to_string(),
|
|
||||||
sampler: Some(sampler),
|
|
||||||
..Self::sequencer(
|
|
||||||
jack, pool, editor,
|
|
||||||
player, midi_froms, midi_tos
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn arranger (
|
|
||||||
jack: &Arc<RwLock<JackConnection>>,
|
|
||||||
pool: PoolModel,
|
|
||||||
editor: MidiEditor,
|
|
||||||
midi_froms: &[PortConnection],
|
|
||||||
midi_tos: &[PortConnection],
|
|
||||||
sampler: Sampler,
|
|
||||||
audio_froms: &[&[PortConnection]],
|
|
||||||
audio_tos: &[&[PortConnection]],
|
|
||||||
scenes: usize,
|
|
||||||
tracks: usize,
|
|
||||||
track_width: usize,
|
|
||||||
) -> Self {
|
|
||||||
let mut arranger = Self {
|
|
||||||
edn: include_str!("../edn/arranger-view.edn").to_string(),
|
|
||||||
..Self::groovebox(
|
|
||||||
jack, pool, editor,
|
|
||||||
None, midi_froms, midi_tos,
|
|
||||||
sampler, audio_froms, audio_tos
|
|
||||||
)
|
|
||||||
};
|
|
||||||
arranger.scenes_add(scenes);
|
|
||||||
arranger.tracks_add(tracks, track_width, &[], &[]);
|
|
||||||
arranger
|
|
||||||
}
|
|
||||||
}
|
|
||||||
has_size!(<TuiOut>|self: App|&self.size);
|
|
||||||
has_clock!(|self: App|&self.clock);
|
|
||||||
has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips);
|
|
||||||
has_editor!(|self: App|self.editor.as_ref().expect("no editor"));
|
|
||||||
edn_provide!(u16: |self: App|{
|
|
||||||
":sample-h" => if self.compact() { 0 } else { 5 },
|
|
||||||
":samples-w" => if self.compact() { 4 } else { 11 },
|
|
||||||
":samples-y" => if self.compact() { 1 } else { 0 },
|
|
||||||
":pool-w" => if self.compact() { 5 } else {
|
|
||||||
let w = self.size.w();
|
|
||||||
if w > 60 { 20 } else if w > 40 { 15 } else { 10 }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
edn_provide!(Color: |self: App| { _ => return None });
|
|
||||||
edn_provide!(usize: |self: App| { _ => return None });
|
|
||||||
edn_provide!(isize: |self: App| { _ => return None });
|
|
||||||
edn_provide!(bool: |self: App| { _ => return None });
|
|
||||||
edn_provide!(ArrangerSelection: |self: App| { _ => return None });
|
|
||||||
edn_provide!(Arc<RwLock<MidiClip>>: |self: App| { _ => return None });
|
|
||||||
|
|
||||||
//#[derive(Default)] pub struct Sequencer {
|
|
||||||
//pub jack: Arc<RwLock<JackConnection>>,
|
|
||||||
//pub compact: bool,
|
|
||||||
//pub editor: MidiEditor,
|
|
||||||
//pub midi_buf: Vec<Vec<Vec<u8>>>,
|
|
||||||
//pub note_buf: Vec<u8>,
|
|
||||||
//pub perf: PerfModel,
|
|
||||||
//pub player: MidiPlayer,
|
|
||||||
//pub pool: PoolModel,
|
|
||||||
//pub selectors: bool,
|
|
||||||
//pub size: Measure<TuiOut>,
|
|
||||||
//pub status: bool,
|
|
||||||
//pub transport: bool,
|
|
||||||
//}
|
|
||||||
//has_size!(<TuiOut>|self:Sequencer|&self.size);
|
|
||||||
//has_clock!(|self:Sequencer|&self.player.clock);
|
|
||||||
//has_clips!(|self:Sequencer|self.pool.clips);
|
|
||||||
//has_editor!(|self:Sequencer|self.editor);
|
|
||||||
//has_player!(|self:Sequencer|self.player);
|
|
||||||
|
|
||||||
//#[derive(Default)] pub struct Groovebox {
|
|
||||||
//pub jack: Arc<RwLock<JackConnection>>,
|
|
||||||
//pub compact: bool,
|
|
||||||
//pub editor: MidiEditor,
|
|
||||||
//pub midi_buf: Vec<Vec<Vec<u8>>>,
|
|
||||||
//pub note_buf: Vec<u8>,
|
|
||||||
//pub perf: PerfModel,
|
|
||||||
//pub player: MidiPlayer,
|
|
||||||
//pub pool: PoolModel,
|
|
||||||
//pub sampler: Sampler,
|
|
||||||
//pub size: Measure<TuiOut>,
|
|
||||||
//pub status: bool,
|
|
||||||
//}
|
|
||||||
//has_clock!(|self: Groovebox|self.player.clock());
|
|
||||||
|
|
||||||
//#[derive(Default)] pub struct Arranger {
|
|
||||||
//pub clock: Clock,
|
|
||||||
//pub color: ItemPalette,
|
|
||||||
//pub compact: bool,
|
|
||||||
//pub editing: AtomicBool,
|
|
||||||
//pub editor: MidiEditor,
|
|
||||||
//pub jack: Arc<RwLock<JackConnection>>,
|
|
||||||
//pub midi_buf: Vec<Vec<Vec<u8>>>,
|
|
||||||
//pub midi_ins: Vec<JackPort<MidiIn>>,
|
|
||||||
//pub midi_outs: Vec<JackPort<MidiOut>>,
|
|
||||||
//pub note_buf: Vec<u8>,
|
|
||||||
//pub perf: PerfModel,
|
|
||||||
//pub pool: PoolModel,
|
|
||||||
//pub scenes: Vec<ArrangerScene>,
|
|
||||||
//pub selected: ArrangerSelection,
|
|
||||||
//pub size: Measure<TuiOut>,
|
|
||||||
//pub splits: [u16;2],
|
|
||||||
//pub tracks: Vec<ArrangerTrack>,
|
|
||||||
//}
|
|
||||||
//has_clock!(|self: Arranger|&self.clock);
|
|
||||||
//has_clips!(|self: Arranger|self.pool.clips);
|
|
||||||
//has_editor!(|self: Arranger|self.editor);
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue