mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
extract midi_pool.rs
This commit is contained in:
parent
1408c0c3ce
commit
e80b9419ae
7 changed files with 380 additions and 388 deletions
191
.scratch.rs
191
.scratch.rs
|
|
@ -120,3 +120,194 @@
|
||||||
//PhraseView,
|
//PhraseView,
|
||||||
//PhraseEdit,
|
//PhraseEdit,
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
//let focused = true;
|
||||||
|
//let _tracks = view.tracks();
|
||||||
|
//lay!(
|
||||||
|
//focused.then_some(Background(TuiTheme::border_bg())),
|
||||||
|
//row!(
|
||||||
|
//// name
|
||||||
|
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
||||||
|
//todo!()
|
||||||
|
////let Self(tracks, selected) = self;
|
||||||
|
////let yellow = Some(Style::default().yellow().bold().not_dim());
|
||||||
|
////let white = Some(Style::default().white().bold().not_dim());
|
||||||
|
////let area = to.area();
|
||||||
|
////let area = [area.x(), area.y(), 3 + 5.max(track_name_max_len(tracks)) as u16, area.h()];
|
||||||
|
////let offset = 0; // track scroll offset
|
||||||
|
////for y in 0..area.h() {
|
||||||
|
////if y == 0 {
|
||||||
|
////to.blit(&"Mixer", area.x() + 1, area.y() + y, Some(DIM))?;
|
||||||
|
////} else if y % 2 == 0 {
|
||||||
|
////let index = (y as usize - 2) / 2 + offset;
|
||||||
|
////if let Some(track) = tracks.get(index) {
|
||||||
|
////let selected = selected.track() == Some(index);
|
||||||
|
////let style = if selected { yellow } else { white };
|
||||||
|
////to.blit(&format!(" {index:>02} "), area.x(), area.y() + y, style)?;
|
||||||
|
////to.blit(&*track.name.read().unwrap(), area.x() + 4, area.y() + y, style)?;
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////Ok(Some(area))
|
||||||
|
//}),
|
||||||
|
//// monitor
|
||||||
|
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
||||||
|
//todo!()
|
||||||
|
////let Self(tracks) = self;
|
||||||
|
////let mut area = to.area();
|
||||||
|
////let on = Some(Style::default().not_dim().green().bold());
|
||||||
|
////let off = Some(DIM);
|
||||||
|
////area.x += 1;
|
||||||
|
////for y in 0..area.h() {
|
||||||
|
////if y == 0 {
|
||||||
|
//////" MON ".blit(to.buffer, area.x, area.y + y, style2)?;
|
||||||
|
////} else if y % 2 == 0 {
|
||||||
|
////let index = (y as usize - 2) / 2;
|
||||||
|
////if let Some(track) = tracks.get(index) {
|
||||||
|
////let style = if track.monitoring { on } else { off };
|
||||||
|
////to.blit(&" MON ", area.x(), area.y() + y, style)?;
|
||||||
|
////} else {
|
||||||
|
////area.height = y;
|
||||||
|
////break
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////area.width = 4;
|
||||||
|
////Ok(Some(area))
|
||||||
|
//}),
|
||||||
|
//// record
|
||||||
|
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
||||||
|
//todo!()
|
||||||
|
////let Self(tracks) = self;
|
||||||
|
////let mut area = to.area();
|
||||||
|
////let on = Some(Style::default().not_dim().red().bold());
|
||||||
|
////let off = Some(Style::default().dim());
|
||||||
|
////area.x += 1;
|
||||||
|
////for y in 0..area.h() {
|
||||||
|
////if y == 0 {
|
||||||
|
//////" REC ".blit(to.buffer, area.x, area.y + y, style2)?;
|
||||||
|
////} else if y % 2 == 0 {
|
||||||
|
////let index = (y as usize - 2) / 2;
|
||||||
|
////if let Some(track) = tracks.get(index) {
|
||||||
|
////let style = if track.recording { on } else { off };
|
||||||
|
////to.blit(&" REC ", area.x(), area.y() + y, style)?;
|
||||||
|
////} else {
|
||||||
|
////area.height = y;
|
||||||
|
////break
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////area.width = 4;
|
||||||
|
////Ok(Some(area))
|
||||||
|
//}),
|
||||||
|
//// overdub
|
||||||
|
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
||||||
|
//todo!()
|
||||||
|
////let Self(tracks) = self;
|
||||||
|
////let mut area = to.area();
|
||||||
|
////let on = Some(Style::default().not_dim().yellow().bold());
|
||||||
|
////let off = Some(Style::default().dim());
|
||||||
|
////area.x = area.x + 1;
|
||||||
|
////for y in 0..area.h() {
|
||||||
|
////if y == 0 {
|
||||||
|
//////" OVR ".blit(to.buffer, area.x, area.y + y, style2)?;
|
||||||
|
////} else if y % 2 == 0 {
|
||||||
|
////let index = (y as usize - 2) / 2;
|
||||||
|
////if let Some(track) = tracks.get(index) {
|
||||||
|
////to.blit(&" OVR ", area.x(), area.y() + y, if track.overdub {
|
||||||
|
////on
|
||||||
|
////} else {
|
||||||
|
////off
|
||||||
|
////})?;
|
||||||
|
////} else {
|
||||||
|
////area.height = y;
|
||||||
|
////break
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////area.width = 4;
|
||||||
|
////Ok(Some(area))
|
||||||
|
//}),
|
||||||
|
//// erase
|
||||||
|
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
||||||
|
//todo!()
|
||||||
|
////let Self(tracks) = self;
|
||||||
|
////let mut area = to.area();
|
||||||
|
////let off = Some(Style::default().dim());
|
||||||
|
////area.x = area.x + 1;
|
||||||
|
////for y in 0..area.h() {
|
||||||
|
////if y == 0 {
|
||||||
|
//////" DEL ".blit(to.buffer, area.x, area.y + y, style2)?;
|
||||||
|
////} else if y % 2 == 0 {
|
||||||
|
////let index = (y as usize - 2) / 2;
|
||||||
|
////if let Some(_) = tracks.get(index) {
|
||||||
|
////to.blit(&" DEL ", area.x(), area.y() + y, off)?;
|
||||||
|
////} else {
|
||||||
|
////area.height = y;
|
||||||
|
////break
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////area.width = 4;
|
||||||
|
////Ok(Some(area))
|
||||||
|
//}),
|
||||||
|
//// gain
|
||||||
|
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
||||||
|
//todo!()
|
||||||
|
////let Self(tracks) = self;
|
||||||
|
////let mut area = to.area();
|
||||||
|
////let off = Some(Style::default().dim());
|
||||||
|
////area.x = area.x() + 1;
|
||||||
|
////for y in 0..area.h() {
|
||||||
|
////if y == 0 {
|
||||||
|
//////" GAIN ".blit(to.buffer, area.x, area.y + y, style2)?;
|
||||||
|
////} else if y % 2 == 0 {
|
||||||
|
////let index = (y as usize - 2) / 2;
|
||||||
|
////if let Some(_) = tracks.get(index) {
|
||||||
|
////to.blit(&" +0.0 ", area.x(), area.y() + y, off)?;
|
||||||
|
////} else {
|
||||||
|
////area.height = y;
|
||||||
|
////break
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////}
|
||||||
|
////area.width = 7;
|
||||||
|
////Ok(Some(area))
|
||||||
|
//}),
|
||||||
|
//// scenes
|
||||||
|
//Widget::new(|_|{todo!()}, |to: &mut TuiOutput|{
|
||||||
|
//let [x, y, _, height] = to.area();
|
||||||
|
//let mut x2 = 0;
|
||||||
|
//Ok(for (scene_index, scene) in view.scenes().iter().enumerate() {
|
||||||
|
//let active_scene = view.selected.scene() == Some(scene_index);
|
||||||
|
//let sep = Some(if active_scene {
|
||||||
|
//Style::default().yellow().not_dim()
|
||||||
|
//} else {
|
||||||
|
//Style::default().dim()
|
||||||
|
//});
|
||||||
|
//for y in y+1..y+height {
|
||||||
|
//to.blit(&"│", x + x2, y, sep);
|
||||||
|
//}
|
||||||
|
//let name = scene.name.read().unwrap();
|
||||||
|
//let mut x3 = name.len() as u16;
|
||||||
|
//to.blit(&*name, x + x2, y, sep);
|
||||||
|
//for (i, clip) in scene.clips.iter().enumerate() {
|
||||||
|
//let active_track = view.selected.track() == Some(i);
|
||||||
|
//if let Some(clip) = clip {
|
||||||
|
//let y2 = y + 2 + i as u16 * 2;
|
||||||
|
//let label = format!("{}", clip.read().unwrap().name);
|
||||||
|
//to.blit(&label, x + x2, y2, Some(if active_track && active_scene {
|
||||||
|
//Style::default().not_dim().yellow().bold()
|
||||||
|
//} else {
|
||||||
|
//Style::default().not_dim()
|
||||||
|
//}));
|
||||||
|
//x3 = x3.max(label.len() as u16)
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//x2 = x2 + x3 + 1;
|
||||||
|
//})
|
||||||
|
//}),
|
||||||
|
//)
|
||||||
|
//)
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ pub(crate) mod midi_note; pub(crate) use midi_note::*;
|
||||||
pub(crate) mod midi_out; pub(crate) use midi_out::*;
|
pub(crate) mod midi_out; pub(crate) use midi_out::*;
|
||||||
pub(crate) mod midi_phrase; pub(crate) use midi_phrase::*;
|
pub(crate) mod midi_phrase; pub(crate) use midi_phrase::*;
|
||||||
pub(crate) mod midi_play; pub(crate) use midi_play::*;
|
pub(crate) mod midi_play; pub(crate) use midi_play::*;
|
||||||
|
pub(crate) mod midi_pool; pub(crate) use midi_pool::*;
|
||||||
pub(crate) mod midi_rec; pub(crate) use midi_rec::*;
|
pub(crate) mod midi_rec; pub(crate) use midi_rec::*;
|
||||||
|
|
||||||
/// Add "all notes off" to the start of a buffer.
|
/// Add "all notes off" to the start of a buffer.
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub trait HasPhrases {
|
|
||||||
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>>;
|
|
||||||
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! has_phrases {
|
|
||||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
|
||||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasPhrases for $Struct $(<$($L),*$($T),*>)? {
|
|
||||||
fn phrases (&$self) -> &Vec<Arc<RwLock<Phrase>>> { &$cb }
|
|
||||||
fn phrases_mut (&mut $self) -> &mut Vec<Arc<RwLock<Phrase>>> { &mut$cb }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HasPhrase {
|
pub trait HasPhrase {
|
||||||
fn phrase (&self) -> &Arc<RwLock<Phrase>>;
|
fn phrase (&self) -> &Arc<RwLock<Phrase>>;
|
||||||
}
|
}
|
||||||
|
|
@ -26,84 +12,6 @@ pub trait HasPhrase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum PhrasePoolCommand {
|
|
||||||
Add(usize, Phrase),
|
|
||||||
Delete(usize),
|
|
||||||
Swap(usize, usize),
|
|
||||||
Import(usize, PathBuf),
|
|
||||||
Export(usize, PathBuf),
|
|
||||||
SetName(usize, String),
|
|
||||||
SetLength(usize, usize),
|
|
||||||
SetColor(usize, ItemColor),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: HasPhrases> Command<T> for PhrasePoolCommand {
|
|
||||||
fn execute (self, model: &mut T) -> Perhaps<Self> {
|
|
||||||
use PhrasePoolCommand::*;
|
|
||||||
Ok(match self {
|
|
||||||
Add(mut index, phrase) => {
|
|
||||||
let phrase = Arc::new(RwLock::new(phrase));
|
|
||||||
let phrases = model.phrases_mut();
|
|
||||||
if index >= phrases.len() {
|
|
||||||
index = phrases.len();
|
|
||||||
phrases.push(phrase)
|
|
||||||
} else {
|
|
||||||
phrases.insert(index, phrase);
|
|
||||||
}
|
|
||||||
Some(Self::Delete(index))
|
|
||||||
},
|
|
||||||
Delete(index) => {
|
|
||||||
let phrase = model.phrases_mut().remove(index).read().unwrap().clone();
|
|
||||||
Some(Self::Add(index, phrase))
|
|
||||||
},
|
|
||||||
Swap(index, other) => {
|
|
||||||
model.phrases_mut().swap(index, other);
|
|
||||||
Some(Self::Swap(index, other))
|
|
||||||
},
|
|
||||||
Import(index, path) => {
|
|
||||||
let bytes = std::fs::read(&path)?;
|
|
||||||
let smf = Smf::parse(bytes.as_slice())?;
|
|
||||||
let mut t = 0u32;
|
|
||||||
let mut events = vec![];
|
|
||||||
for track in smf.tracks.iter() {
|
|
||||||
for event in track.iter() {
|
|
||||||
t += event.delta.as_int();
|
|
||||||
if let TrackEventKind::Midi { channel, message } = event.kind {
|
|
||||||
events.push((t, channel.as_int(), message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut phrase = Phrase::new("imported", true, t as usize + 1, None, None);
|
|
||||||
for event in events.iter() {
|
|
||||||
phrase.notes[event.0 as usize].push(event.2);
|
|
||||||
}
|
|
||||||
Self::Add(index, phrase).execute(model)?
|
|
||||||
},
|
|
||||||
Export(_index, _path) => {
|
|
||||||
todo!("export phrase to midi file");
|
|
||||||
},
|
|
||||||
SetName(index, name) => {
|
|
||||||
let mut phrase = model.phrases()[index].write().unwrap();
|
|
||||||
let old_name = phrase.name.clone();
|
|
||||||
phrase.name = name;
|
|
||||||
Some(Self::SetName(index, old_name))
|
|
||||||
},
|
|
||||||
SetLength(index, length) => {
|
|
||||||
let mut phrase = model.phrases()[index].write().unwrap();
|
|
||||||
let old_len = phrase.length;
|
|
||||||
phrase.length = length;
|
|
||||||
Some(Self::SetLength(index, old_len))
|
|
||||||
},
|
|
||||||
SetColor(index, color) => {
|
|
||||||
let mut color = ItemPalette::from(color);
|
|
||||||
std::mem::swap(&mut color, &mut model.phrases()[index].write().unwrap().color);
|
|
||||||
Some(Self::SetColor(index, color.base))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A MIDI sequence.
|
/// A MIDI sequence.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Phrase {
|
pub struct Phrase {
|
||||||
|
|
|
||||||
93
crates/tek/src/midi/midi_pool.rs
Normal file
93
crates/tek/src/midi/midi_pool.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub trait HasPhrases {
|
||||||
|
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>>;
|
||||||
|
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! has_phrases {
|
||||||
|
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||||
|
impl $(<$($L),*$($T $(: $U)?),*>)? HasPhrases for $Struct $(<$($L),*$($T),*>)? {
|
||||||
|
fn phrases (&$self) -> &Vec<Arc<RwLock<Phrase>>> { &$cb }
|
||||||
|
fn phrases_mut (&mut $self) -> &mut Vec<Arc<RwLock<Phrase>>> { &mut$cb }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum PhrasePoolCommand {
|
||||||
|
Add(usize, Phrase),
|
||||||
|
Delete(usize),
|
||||||
|
Swap(usize, usize),
|
||||||
|
Import(usize, PathBuf),
|
||||||
|
Export(usize, PathBuf),
|
||||||
|
SetName(usize, String),
|
||||||
|
SetLength(usize, usize),
|
||||||
|
SetColor(usize, ItemColor),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HasPhrases> Command<T> for PhrasePoolCommand {
|
||||||
|
fn execute (self, model: &mut T) -> Perhaps<Self> {
|
||||||
|
use PhrasePoolCommand::*;
|
||||||
|
Ok(match self {
|
||||||
|
Add(mut index, phrase) => {
|
||||||
|
let phrase = Arc::new(RwLock::new(phrase));
|
||||||
|
let phrases = model.phrases_mut();
|
||||||
|
if index >= phrases.len() {
|
||||||
|
index = phrases.len();
|
||||||
|
phrases.push(phrase)
|
||||||
|
} else {
|
||||||
|
phrases.insert(index, phrase);
|
||||||
|
}
|
||||||
|
Some(Self::Delete(index))
|
||||||
|
},
|
||||||
|
Delete(index) => {
|
||||||
|
let phrase = model.phrases_mut().remove(index).read().unwrap().clone();
|
||||||
|
Some(Self::Add(index, phrase))
|
||||||
|
},
|
||||||
|
Swap(index, other) => {
|
||||||
|
model.phrases_mut().swap(index, other);
|
||||||
|
Some(Self::Swap(index, other))
|
||||||
|
},
|
||||||
|
Import(index, path) => {
|
||||||
|
let bytes = std::fs::read(&path)?;
|
||||||
|
let smf = Smf::parse(bytes.as_slice())?;
|
||||||
|
let mut t = 0u32;
|
||||||
|
let mut events = vec![];
|
||||||
|
for track in smf.tracks.iter() {
|
||||||
|
for event in track.iter() {
|
||||||
|
t += event.delta.as_int();
|
||||||
|
if let TrackEventKind::Midi { channel, message } = event.kind {
|
||||||
|
events.push((t, channel.as_int(), message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut phrase = Phrase::new("imported", true, t as usize + 1, None, None);
|
||||||
|
for event in events.iter() {
|
||||||
|
phrase.notes[event.0 as usize].push(event.2);
|
||||||
|
}
|
||||||
|
Self::Add(index, phrase).execute(model)?
|
||||||
|
},
|
||||||
|
Export(_index, _path) => {
|
||||||
|
todo!("export phrase to midi file");
|
||||||
|
},
|
||||||
|
SetName(index, name) => {
|
||||||
|
let mut phrase = model.phrases()[index].write().unwrap();
|
||||||
|
let old_name = phrase.name.clone();
|
||||||
|
phrase.name = name;
|
||||||
|
Some(Self::SetName(index, old_name))
|
||||||
|
},
|
||||||
|
SetLength(index, length) => {
|
||||||
|
let mut phrase = model.phrases()[index].write().unwrap();
|
||||||
|
let old_len = phrase.length;
|
||||||
|
phrase.length = length;
|
||||||
|
Some(Self::SetLength(index, old_len))
|
||||||
|
},
|
||||||
|
SetColor(index, color) => {
|
||||||
|
let mut color = ItemPalette::from(color);
|
||||||
|
std::mem::swap(&mut color, &mut model.phrases()[index].write().unwrap().color);
|
||||||
|
Some(Self::SetColor(index, color.base))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -192,58 +192,55 @@ input_to_command!(ArrangerCommand: <Tui>|state: ArrangerTui, input|{
|
||||||
key_pat!(Ctrl-Char('k')) => { todo!("keyboard") },
|
key_pat!(Ctrl-Char('k')) => { todo!("keyboard") },
|
||||||
// Transport: Play/pause
|
// Transport: Play/pause
|
||||||
key_pat!(Char(' ')) =>
|
key_pat!(Char(' ')) =>
|
||||||
Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
|
Self::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
|
||||||
// Transport: Play from start or rewind to start
|
// Transport: Play from start or rewind to start
|
||||||
key_pat!(Shift-Char(' ')) =>
|
key_pat!(Shift-Char(' ')) =>
|
||||||
Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }),
|
Self::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }),
|
||||||
key_pat!(Char('e')) =>
|
key_pat!(Char('e')) =>
|
||||||
Editor(PhraseCommand::Show(Some(state.phrases.phrase().clone()))),
|
Self::Editor(PhraseCommand::Show(Some(state.phrases.phrase().clone()))),
|
||||||
key_pat!(Char('l')) =>
|
key_pat!(Char('l')) =>
|
||||||
Clip(ArrangerClipCommand::SetLoop(false)),
|
Self::Clip(ArrangerClipCommand::SetLoop(false)),
|
||||||
key_pat!(Ctrl-Char('a')) =>
|
key_pat!(Ctrl-Char('a')) =>
|
||||||
Scene(ArrangerSceneCommand::Add),
|
Self::Scene(ArrangerSceneCommand::Add),
|
||||||
key_pat!(Ctrl-Char('t')) =>
|
key_pat!(Ctrl-Char('t')) =>
|
||||||
Track(ArrangerTrackCommand::Add),
|
Self::Track(ArrangerTrackCommand::Add),
|
||||||
key_pat!(Char('0')) => match state.selected() {
|
key_pat!(Char('0')) => match state.selected() {
|
||||||
Selected::Mix => StopAll,
|
Selected::Mix => StopAll,
|
||||||
Selected::Track(t) => return None,
|
Selected::Track(t) => return None,
|
||||||
Selected::Scene(s) => return None,
|
Selected::Scene(s) => return None,
|
||||||
Selected::Clip(t, s) => return None,
|
Selected::Clip(t, s) => return None,
|
||||||
},
|
},
|
||||||
key_pat!(Char('q')) => match state.selected() {
|
key_pat!(Char('g')) => if let Selected::Clip(t, s) = state.selected() {
|
||||||
Selected::Mix => return None,
|
Self::Phrases(PhrasesCommand::Select(0))
|
||||||
Selected::Track(t) => return None,
|
} else {
|
||||||
Selected::Scene(s) => return None,
|
return None
|
||||||
Selected::Clip(t, s) => return None,
|
|
||||||
},
|
},
|
||||||
key_pat!(Char('g')) => match state.selected() {
|
key_pat!(Char('p')) => if let Selected::Clip(t, s) = state.selected() {
|
||||||
Selected::Mix => return None,
|
Self::Clip(ArrangerClipCommand::Select(0))
|
||||||
Selected::Track(t) => return None,
|
} else {
|
||||||
Selected::Scene(s) => return None,
|
return None
|
||||||
Selected::Clip(t, s) => return None,
|
|
||||||
},
|
},
|
||||||
key_pat!(Char('p')) => match state.selected() {
|
key_pat!(Char('q')) => if let Selected::Clip(t, s) = state.selected() {
|
||||||
Selected::Mix => return None,
|
todo!("enqueue clip")
|
||||||
Selected::Track(t) => return None,
|
} else {
|
||||||
Selected::Scene(s) => return None,
|
return None
|
||||||
Selected::Clip(t, s) => return None,
|
|
||||||
},
|
},
|
||||||
// Tab: Toggle visibility of phrase pool column
|
// Tab: Toggle visibility of phrase pool column
|
||||||
key_pat!(Tab) =>
|
key_pat!(Tab) =>
|
||||||
ShowPool(!state.show_pool),
|
Self::ShowPool(!state.show_pool),
|
||||||
_ => match state.selected() {
|
_ => {
|
||||||
Selected::Mix => to_arranger_mix_command(input),
|
let t_len = state.tracks.len();
|
||||||
Selected::Track(t) => to_arranger_track_command(input,
|
let s_len = state.scenes.len();
|
||||||
t, state.tracks.len()),
|
match state.selected() {
|
||||||
Selected::Scene(s) => to_arranger_scene_command(input,
|
Selected::Mix => to_arranger_mix_command(input),
|
||||||
s, state.scenes.len()),
|
Selected::Track(t) => to_arranger_track_command(input, t, t_len),
|
||||||
Selected::Clip(t, s) => to_arranger_clip_command(input,
|
Selected::Scene(s) => to_arranger_scene_command(input, s, s_len),
|
||||||
t, state.tracks.len(),
|
Selected::Clip(t, s) => to_arranger_clip_command(input, t, t_len, s, s_len),
|
||||||
s, state.scenes.len()),
|
}
|
||||||
}.or_else(||if let Some(command) = PhraseCommand::input_to_command(&state.editor, input) {
|
}.or_else(||if let Some(command) = PhraseCommand::input_to_command(&state.editor, input) {
|
||||||
Some(Editor(command))
|
Some(Self::Editor(command))
|
||||||
} else if let Some(command) = PhrasesCommand::input_to_command(&state.phrases, input) {
|
} else if let Some(command) = PhrasesCommand::input_to_command(&state.phrases, input) {
|
||||||
Some(Phrases(command))
|
Some(Self::Phrases(command))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
})?
|
})?
|
||||||
|
|
@ -368,264 +365,3 @@ impl ArrangerMode {
|
||||||
fn any_size <E: Engine> (_: E::Size) -> Perhaps<E::Size>{
|
fn any_size <E: Engine> (_: E::Size) -> Perhaps<E::Size>{
|
||||||
Ok(Some([0.into(),0.into()].into()))
|
Ok(Some([0.into(),0.into()].into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
//impl<T: ArrangerApi> Command<T> for ArrangerClipCommand {
|
|
||||||
//fn execute (self, state: &mut T) -> Perhaps<Self> {
|
|
||||||
//match self {
|
|
||||||
//_ => todo!()
|
|
||||||
//}
|
|
||||||
//Ok(None)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//let focused = true;
|
|
||||||
//let _tracks = view.tracks();
|
|
||||||
//lay!(
|
|
||||||
//focused.then_some(Background(TuiTheme::border_bg())),
|
|
||||||
//row!(
|
|
||||||
//// name
|
|
||||||
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
|
||||||
//todo!()
|
|
||||||
////let Self(tracks, selected) = self;
|
|
||||||
////let yellow = Some(Style::default().yellow().bold().not_dim());
|
|
||||||
////let white = Some(Style::default().white().bold().not_dim());
|
|
||||||
////let area = to.area();
|
|
||||||
////let area = [area.x(), area.y(), 3 + 5.max(track_name_max_len(tracks)) as u16, area.h()];
|
|
||||||
////let offset = 0; // track scroll offset
|
|
||||||
////for y in 0..area.h() {
|
|
||||||
////if y == 0 {
|
|
||||||
////to.blit(&"Mixer", area.x() + 1, area.y() + y, Some(DIM))?;
|
|
||||||
////} else if y % 2 == 0 {
|
|
||||||
////let index = (y as usize - 2) / 2 + offset;
|
|
||||||
////if let Some(track) = tracks.get(index) {
|
|
||||||
////let selected = selected.track() == Some(index);
|
|
||||||
////let style = if selected { yellow } else { white };
|
|
||||||
////to.blit(&format!(" {index:>02} "), area.x(), area.y() + y, style)?;
|
|
||||||
////to.blit(&*track.name.read().unwrap(), area.x() + 4, area.y() + y, style)?;
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////Ok(Some(area))
|
|
||||||
//}),
|
|
||||||
//// monitor
|
|
||||||
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
|
||||||
//todo!()
|
|
||||||
////let Self(tracks) = self;
|
|
||||||
////let mut area = to.area();
|
|
||||||
////let on = Some(Style::default().not_dim().green().bold());
|
|
||||||
////let off = Some(DIM);
|
|
||||||
////area.x += 1;
|
|
||||||
////for y in 0..area.h() {
|
|
||||||
////if y == 0 {
|
|
||||||
//////" MON ".blit(to.buffer, area.x, area.y + y, style2)?;
|
|
||||||
////} else if y % 2 == 0 {
|
|
||||||
////let index = (y as usize - 2) / 2;
|
|
||||||
////if let Some(track) = tracks.get(index) {
|
|
||||||
////let style = if track.monitoring { on } else { off };
|
|
||||||
////to.blit(&" MON ", area.x(), area.y() + y, style)?;
|
|
||||||
////} else {
|
|
||||||
////area.height = y;
|
|
||||||
////break
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////area.width = 4;
|
|
||||||
////Ok(Some(area))
|
|
||||||
//}),
|
|
||||||
//// record
|
|
||||||
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
|
||||||
//todo!()
|
|
||||||
////let Self(tracks) = self;
|
|
||||||
////let mut area = to.area();
|
|
||||||
////let on = Some(Style::default().not_dim().red().bold());
|
|
||||||
////let off = Some(Style::default().dim());
|
|
||||||
////area.x += 1;
|
|
||||||
////for y in 0..area.h() {
|
|
||||||
////if y == 0 {
|
|
||||||
//////" REC ".blit(to.buffer, area.x, area.y + y, style2)?;
|
|
||||||
////} else if y % 2 == 0 {
|
|
||||||
////let index = (y as usize - 2) / 2;
|
|
||||||
////if let Some(track) = tracks.get(index) {
|
|
||||||
////let style = if track.recording { on } else { off };
|
|
||||||
////to.blit(&" REC ", area.x(), area.y() + y, style)?;
|
|
||||||
////} else {
|
|
||||||
////area.height = y;
|
|
||||||
////break
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////area.width = 4;
|
|
||||||
////Ok(Some(area))
|
|
||||||
//}),
|
|
||||||
//// overdub
|
|
||||||
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
|
||||||
//todo!()
|
|
||||||
////let Self(tracks) = self;
|
|
||||||
////let mut area = to.area();
|
|
||||||
////let on = Some(Style::default().not_dim().yellow().bold());
|
|
||||||
////let off = Some(Style::default().dim());
|
|
||||||
////area.x = area.x + 1;
|
|
||||||
////for y in 0..area.h() {
|
|
||||||
////if y == 0 {
|
|
||||||
//////" OVR ".blit(to.buffer, area.x, area.y + y, style2)?;
|
|
||||||
////} else if y % 2 == 0 {
|
|
||||||
////let index = (y as usize - 2) / 2;
|
|
||||||
////if let Some(track) = tracks.get(index) {
|
|
||||||
////to.blit(&" OVR ", area.x(), area.y() + y, if track.overdub {
|
|
||||||
////on
|
|
||||||
////} else {
|
|
||||||
////off
|
|
||||||
////})?;
|
|
||||||
////} else {
|
|
||||||
////area.height = y;
|
|
||||||
////break
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////area.width = 4;
|
|
||||||
////Ok(Some(area))
|
|
||||||
//}),
|
|
||||||
//// erase
|
|
||||||
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
|
||||||
//todo!()
|
|
||||||
////let Self(tracks) = self;
|
|
||||||
////let mut area = to.area();
|
|
||||||
////let off = Some(Style::default().dim());
|
|
||||||
////area.x = area.x + 1;
|
|
||||||
////for y in 0..area.h() {
|
|
||||||
////if y == 0 {
|
|
||||||
//////" DEL ".blit(to.buffer, area.x, area.y + y, style2)?;
|
|
||||||
////} else if y % 2 == 0 {
|
|
||||||
////let index = (y as usize - 2) / 2;
|
|
||||||
////if let Some(_) = tracks.get(index) {
|
|
||||||
////to.blit(&" DEL ", area.x(), area.y() + y, off)?;
|
|
||||||
////} else {
|
|
||||||
////area.height = y;
|
|
||||||
////break
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////area.width = 4;
|
|
||||||
////Ok(Some(area))
|
|
||||||
//}),
|
|
||||||
//// gain
|
|
||||||
//Widget::new(|_|{todo!()}, |_: &mut TuiOutput|{
|
|
||||||
//todo!()
|
|
||||||
////let Self(tracks) = self;
|
|
||||||
////let mut area = to.area();
|
|
||||||
////let off = Some(Style::default().dim());
|
|
||||||
////area.x = area.x() + 1;
|
|
||||||
////for y in 0..area.h() {
|
|
||||||
////if y == 0 {
|
|
||||||
//////" GAIN ".blit(to.buffer, area.x, area.y + y, style2)?;
|
|
||||||
////} else if y % 2 == 0 {
|
|
||||||
////let index = (y as usize - 2) / 2;
|
|
||||||
////if let Some(_) = tracks.get(index) {
|
|
||||||
////to.blit(&" +0.0 ", area.x(), area.y() + y, off)?;
|
|
||||||
////} else {
|
|
||||||
////area.height = y;
|
|
||||||
////break
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////area.width = 7;
|
|
||||||
////Ok(Some(area))
|
|
||||||
//}),
|
|
||||||
//// scenes
|
|
||||||
//Widget::new(|_|{todo!()}, |to: &mut TuiOutput|{
|
|
||||||
//let [x, y, _, height] = to.area();
|
|
||||||
//let mut x2 = 0;
|
|
||||||
//Ok(for (scene_index, scene) in view.scenes().iter().enumerate() {
|
|
||||||
//let active_scene = view.selected.scene() == Some(scene_index);
|
|
||||||
//let sep = Some(if active_scene {
|
|
||||||
//Style::default().yellow().not_dim()
|
|
||||||
//} else {
|
|
||||||
//Style::default().dim()
|
|
||||||
//});
|
|
||||||
//for y in y+1..y+height {
|
|
||||||
//to.blit(&"│", x + x2, y, sep);
|
|
||||||
//}
|
|
||||||
//let name = scene.name.read().unwrap();
|
|
||||||
//let mut x3 = name.len() as u16;
|
|
||||||
//to.blit(&*name, x + x2, y, sep);
|
|
||||||
//for (i, clip) in scene.clips.iter().enumerate() {
|
|
||||||
//let active_track = view.selected.track() == Some(i);
|
|
||||||
//if let Some(clip) = clip {
|
|
||||||
//let y2 = y + 2 + i as u16 * 2;
|
|
||||||
//let label = format!("{}", clip.read().unwrap().name);
|
|
||||||
//to.blit(&label, x + x2, y2, Some(if active_track && active_scene {
|
|
||||||
//Style::default().not_dim().yellow().bold()
|
|
||||||
//} else {
|
|
||||||
//Style::default().not_dim()
|
|
||||||
//}));
|
|
||||||
//x3 = x3.max(label.len() as u16)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//x2 = x2 + x3 + 1;
|
|
||||||
//})
|
|
||||||
//}),
|
|
||||||
//)
|
|
||||||
//)
|
|
||||||
//}
|
|
||||||
|
|
||||||
//impl Command<ArrangerModel> for ArrangerSceneCommand {
|
|
||||||
//}
|
|
||||||
//Edit(phrase) => { state.state.phrase = phrase.clone() },
|
|
||||||
//ToggleViewMode => { state.state.mode.to_next(); },
|
|
||||||
//Delete => { state.state.delete(); },
|
|
||||||
//Activate => { state.state.activate(); },
|
|
||||||
//ZoomIn => { state.state.zoom_in(); },
|
|
||||||
//ZoomOut => { state.state.zoom_out(); },
|
|
||||||
//MoveBack => { state.state.move_back(); },
|
|
||||||
//MoveForward => { state.state.move_forward(); },
|
|
||||||
//RandomColor => { state.state.randomize_color(); },
|
|
||||||
//Put => { state.state.phrase_put(); },
|
|
||||||
//Get => { state.state.phrase_get(); },
|
|
||||||
//AddScene => { state.state.scene_add(None, None)?; },
|
|
||||||
//AddTrack => { state.state.track_add(None, None)?; },
|
|
||||||
//ToggleLoop => { state.state.toggle_loop() },
|
|
||||||
//pub fn zoom_in (&mut self) {
|
|
||||||
//if let ArrangerEditorMode::V(factor) = self.mode {
|
|
||||||
//self.mode = ArrangerEditorMode::V(factor + 1)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//pub fn zoom_out (&mut self) {
|
|
||||||
//if let ArrangerEditorMode::V(factor) = self.mode {
|
|
||||||
//self.mode = ArrangerEditorMode::V(factor.saturating_sub(1))
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//pub fn move_back (&mut self) {
|
|
||||||
//match self.selected {
|
|
||||||
//ArrangerEditorFocus::Scene(s) => {
|
|
||||||
//if s > 0 {
|
|
||||||
//self.scenes.swap(s, s - 1);
|
|
||||||
//self.selected = ArrangerEditorFocus::Scene(s - 1);
|
|
||||||
//}
|
|
||||||
//},
|
|
||||||
//ArrangerEditorFocus::Track(t) => {
|
|
||||||
//if t > 0 {
|
|
||||||
//self.tracks.swap(t, t - 1);
|
|
||||||
//self.selected = ArrangerEditorFocus::Track(t - 1);
|
|
||||||
//// FIXME: also swap clip order in scenes
|
|
||||||
//}
|
|
||||||
//},
|
|
||||||
//_ => todo!("arrangement: move forward")
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//pub fn move_forward (&mut self) {
|
|
||||||
//match self.selected {
|
|
||||||
//ArrangerEditorFocus::Scene(s) => {
|
|
||||||
//if s < self.scenes.len().saturating_sub(1) {
|
|
||||||
//self.scenes.swap(s, s + 1);
|
|
||||||
//self.selected = ArrangerEditorFocus::Scene(s + 1);
|
|
||||||
//}
|
|
||||||
//},
|
|
||||||
//ArrangerEditorFocus::Track(t) => {
|
|
||||||
//if t < self.tracks.len().saturating_sub(1) {
|
|
||||||
//self.tracks.swap(t, t + 1);
|
|
||||||
//self.selected = ArrangerEditorFocus::Track(t + 1);
|
|
||||||
//// FIXME: also swap clip order in scenes
|
|
||||||
//}
|
|
||||||
//},
|
|
||||||
//_ => todo!("arrangement: move forward")
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
|
||||||
|
|
@ -123,3 +123,65 @@ impl ArrangerTui {
|
||||||
self.selected.scene().map(|s|self.scenes_mut().get_mut(s)).flatten()
|
self.selected.scene().map(|s|self.scenes_mut().get_mut(s)).flatten()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//impl Command<ArrangerModel> for ArrangerSceneCommand {
|
||||||
|
//}
|
||||||
|
//Edit(phrase) => { state.state.phrase = phrase.clone() },
|
||||||
|
//ToggleViewMode => { state.state.mode.to_next(); },
|
||||||
|
//Delete => { state.state.delete(); },
|
||||||
|
//Activate => { state.state.activate(); },
|
||||||
|
//ZoomIn => { state.state.zoom_in(); },
|
||||||
|
//ZoomOut => { state.state.zoom_out(); },
|
||||||
|
//MoveBack => { state.state.move_back(); },
|
||||||
|
//MoveForward => { state.state.move_forward(); },
|
||||||
|
//RandomColor => { state.state.randomize_color(); },
|
||||||
|
//Put => { state.state.phrase_put(); },
|
||||||
|
//Get => { state.state.phrase_get(); },
|
||||||
|
//AddScene => { state.state.scene_add(None, None)?; },
|
||||||
|
//AddTrack => { state.state.track_add(None, None)?; },
|
||||||
|
//ToggleLoop => { state.state.toggle_loop() },
|
||||||
|
//pub fn zoom_in (&mut self) {
|
||||||
|
//if let ArrangerEditorMode::V(factor) = self.mode {
|
||||||
|
//self.mode = ArrangerEditorMode::V(factor + 1)
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//pub fn zoom_out (&mut self) {
|
||||||
|
//if let ArrangerEditorMode::V(factor) = self.mode {
|
||||||
|
//self.mode = ArrangerEditorMode::V(factor.saturating_sub(1))
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//pub fn move_back (&mut self) {
|
||||||
|
//match self.selected {
|
||||||
|
//ArrangerEditorFocus::Scene(s) => {
|
||||||
|
//if s > 0 {
|
||||||
|
//self.scenes.swap(s, s - 1);
|
||||||
|
//self.selected = ArrangerEditorFocus::Scene(s - 1);
|
||||||
|
//}
|
||||||
|
//},
|
||||||
|
//ArrangerEditorFocus::Track(t) => {
|
||||||
|
//if t > 0 {
|
||||||
|
//self.tracks.swap(t, t - 1);
|
||||||
|
//self.selected = ArrangerEditorFocus::Track(t - 1);
|
||||||
|
//// FIXME: also swap clip order in scenes
|
||||||
|
//}
|
||||||
|
//},
|
||||||
|
//_ => todo!("arrangement: move forward")
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//pub fn move_forward (&mut self) {
|
||||||
|
//match self.selected {
|
||||||
|
//ArrangerEditorFocus::Scene(s) => {
|
||||||
|
//if s < self.scenes.len().saturating_sub(1) {
|
||||||
|
//self.scenes.swap(s, s + 1);
|
||||||
|
//self.selected = ArrangerEditorFocus::Scene(s + 1);
|
||||||
|
//}
|
||||||
|
//},
|
||||||
|
//ArrangerEditorFocus::Track(t) => {
|
||||||
|
//if t < self.tracks.len().saturating_sub(1) {
|
||||||
|
//self.tracks.swap(t, t + 1);
|
||||||
|
//self.selected = ArrangerEditorFocus::Track(t + 1);
|
||||||
|
//// FIXME: also swap clip order in scenes
|
||||||
|
//}
|
||||||
|
//},
|
||||||
|
//_ => todo!("arrangement: move forward")
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ from!(|state:&ArrangerTui|ArrangerStatus = {
|
||||||
});
|
});
|
||||||
render!(<Tui>|self: ArrangerStatus|Fixed::h(2, lay!([
|
render!(<Tui>|self: ArrangerStatus|Fixed::h(2, lay!([
|
||||||
Self::help(),
|
Self::help(),
|
||||||
Fill::wh(Align::se({Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats())})),
|
Fill::wh(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))),
|
||||||
])));
|
])));
|
||||||
impl ArrangerStatus {
|
impl ArrangerStatus {
|
||||||
fn help () -> impl Render<Tui> {
|
fn help () -> impl Render<Tui> {
|
||||||
|
|
@ -120,9 +120,10 @@ impl ArrangerStatus {
|
||||||
single("SPACE", "play/pause"),
|
single("SPACE", "play/pause"),
|
||||||
single(" Ctrl", " scroll"),
|
single(" Ctrl", " scroll"),
|
||||||
single(" wsad", " cell"),
|
single(" wsad", " cell"),
|
||||||
|
double(("p", "put"), ("g", "get")),
|
||||||
double(("q", "enqueue"), ("e", "edit")),
|
double(("q", "enqueue"), ("e", "edit")),
|
||||||
single(" ▲▼▶◀", " note"),
|
single(" ▲▼▶◀", " note"),
|
||||||
double(("a", "append"), ("s", "set note"),),
|
double(("a", "append"), ("s", "set"),),
|
||||||
double((",.", "length"), ("<>", "triplet"),),
|
double((",.", "length"), ("<>", "triplet"),),
|
||||||
double(("[]", "phrase"), ("{}", "order"),),
|
double(("[]", "phrase"), ("{}", "order"),),
|
||||||
]))
|
]))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue