simplify sequencer init

This commit is contained in:
🪞👃🪞 2024-12-12 23:04:55 +01:00
parent 2795c05275
commit 9619ef9739
3 changed files with 198 additions and 195 deletions

View file

@ -8,31 +8,22 @@ use PhraseCommand::*;
impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui { impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui {
type Error = Box<dyn std::error::Error>; type Error = Box<dyn std::error::Error>;
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> { fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
let clock = ClockModel::from(jack); let clock = ClockModel::from(jack);
let phrase = Arc::new(RwLock::new(Phrase::new(
let mut phrase = Phrase::default(); "New",
phrase.name = "New".into(); true,
phrase.color = ItemColor::random().into(); 4 * clock.timebase.ppq.get() as usize,
phrase.set_length(384); None,
Some(ItemColor::random().into())
let mut phrases = PhraseListModel::default(); )));
let phrase = Arc::new(RwLock::new(phrase));
phrases.phrases.push(phrase.clone());
phrases.phrase.store(1, Ordering::Relaxed);
let mut player = PhrasePlayerModel::from(&clock);
player.play_phrase = Some((Moment::zero(&clock.timebase), Some(phrase.clone())));
Ok(Self { Ok(Self {
jack: jack.clone(), jack: jack.clone(),
clock, phrases: PhraseListModel::from(&phrase),
phrases,
player,
editor: PhraseEditorModel::from(&phrase), editor: PhraseEditorModel::from(&phrase),
player: PhrasePlayerModel::from((&clock, &phrase)),
clock,
size: Measure::new(), size: Measure::new(),
cursor: (0, 0), cursor: (0, 0),
entered: false,
split: 20, split: 20,
midi_buf: vec![vec![];65536], midi_buf: vec![vec![];65536],
note_buf: vec![], note_buf: vec![],
@ -53,7 +44,6 @@ pub struct SequencerTui {
pub size: Measure<Tui>, pub size: Measure<Tui>,
pub cursor: (usize, usize), pub cursor: (usize, usize),
pub split: u16, pub split: u16,
pub entered: bool,
pub note_buf: Vec<u8>, pub note_buf: Vec<u8>,
pub midi_buf: Vec<Vec<Vec<u8>>>, pub midi_buf: Vec<Vec<Vec<u8>>>,
pub focus: SequencerFocus, pub focus: SequencerFocus,
@ -105,181 +95,6 @@ impl Audio for SequencerTui {
} }
} }
render!(|self: SequencerTui|{
let content = lay!([self.size, Tui::split_up(false, 1,
Tui::fill_xy(SequencerStatusBar::from(self)),
Tui::split_right(false, if self.size.w() > 60 { 20 } else if self.size.w() > 40 { 15 } else { 10 },
Tui::fixed_x(20, Tui::split_down(false, 4, col!([
PhraseSelector::play_phrase(&self.player),
PhraseSelector::next_phrase(&self.player),
]), Tui::split_up(false, 2,
PhraseSelector::edit_phrase(&self.editor.phrase.read().unwrap()),
PhraseListView::from(self),
))),
col!([
Tui::fixed_y(2, TransportView::from((
self,
self.player.play_phrase().as_ref().map(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)).flatten(),
if let SequencerFocus::Transport(_) = self.focus {
true
} else {
false
}
))),
self.editor
]),
)
)]);
pub struct PhraseSelector {
pub(crate) title: &'static str,
pub(crate) name: String,
pub(crate) color: ItemPalette,
pub(crate) time: String,
}
// TODO: Display phrases always in order of appearance
render!(|self: PhraseSelector|Tui::fixed_y(2, col!([
lay!(move|add|{
add(&Tui::push_x(1, Tui::fg(TuiTheme::g(240), self.title)))?;
add(&Tui::bg(self.color.base.rgb, Tui::fill_x(Tui::inset_x(1, Tui::fill_x(Tui::at_e(
Tui::fg(self.color.lightest.rgb, &self.time)))))))?;
Ok(())
}),
Tui::bg(self.color.base.rgb,
Tui::fg(self.color.lightest.rgb,
Tui::bold(true, self.name.clone()))),
])));
impl PhraseSelector {
// beats elapsed
pub fn play_phrase <T: HasPlayPhrase + HasClock> (state: &T) -> Self {
let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() {
let Phrase { ref name, color, .. } = *phrase.read().unwrap();
(name.clone(), color)
} else {
("".to_string(), ItemPalette::from(TuiTheme::g(64)))
};
let time = if let Some(elapsed) = state.pulses_since_start_looped() {
format!("+{:>}", state.clock().timebase.format_beats_0(elapsed))
} else {
String::from("")
};
Self { title: "Now:", time, name, color, }
}
// beats until switchover
pub fn next_phrase <T: HasPlayPhrase> (state: &T) -> Self {
let (time, name, color) = if let Some((t, Some(phrase))) = state.next_phrase() {
let Phrase { ref name, color, .. } = *phrase.read().unwrap();
let time = {
let target = t.pulse.get();
let current = state.clock().playhead.pulse.get();
if target > current {
let remaining = target - current;
format!("-{:>}", state.clock().timebase.format_beats_0(remaining))
} else {
String::new()
}
};
(time, name.clone(), color)
} else {
("".into(), "".into(), TuiTheme::g(64).into())
};
Self { title: "Next:", time, name, color, }
}
pub fn edit_phrase (phrase: &Option<Arc<RwLock<Phrase>>>) -> Self {
let (time, name, color) = if let Some(phrase) = phrase {
let phrase = phrase.read().unwrap();
(format!("{}", phrase.length), phrase.name.clone(), phrase.color)
} else {
("".to_string(), "".to_string(), ItemPalette::from(TuiTheme::g(64)))
};
Self { title: "Editing:", time, name, color }
}
}
content
});
impl HasClock for SequencerTui {
fn clock (&self) -> &ClockModel {
&self.clock
}
}
impl HasPhrases for SequencerTui {
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
&self.phrases.phrases
}
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
&mut self.phrases.phrases
}
}
impl HasPhraseList for SequencerTui {
fn phrases_focused (&self) -> bool {
true
}
fn phrases_entered (&self) -> bool {
true
}
fn phrases_mode (&self) -> &Option<PhraseListMode> {
&self.phrases.mode
}
fn phrase_index (&self) -> usize {
self.phrases.phrase.load(Ordering::Relaxed)
}
}
impl HasEditor for SequencerTui {
fn editor (&self) -> &PhraseEditorModel {
&self.editor
}
fn editor_focused (&self) -> bool {
false
}
fn editor_entered (&self) -> bool {
true
}
}
impl HasFocus for SequencerTui {
type Item = SequencerFocus;
/// Get the currently focused item.
fn focused (&self) -> Self::Item {
self.focus
}
/// Get the currently focused item.
fn set_focused (&mut self, to: Self::Item) {
self.focus = to
}
}
impl Into<Option<TransportFocus>> for SequencerFocus {
fn into (self) -> Option<TransportFocus> {
if let Self::Transport(transport) = self {
Some(transport)
} else {
None
}
}
}
impl From<&SequencerTui> for Option<TransportFocus> {
fn from (state: &SequencerTui) -> Self {
match state.focus {
Transport(focus) => Some(focus),
_ => None
}
}
}
impl Handle<Tui> for SequencerTui {
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
SequencerCommand::execute_with_state(self, i)
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum SequencerCommand { pub enum SequencerCommand {
Focus(FocusCommand<SequencerFocus>), Focus(FocusCommand<SequencerFocus>),
@ -378,6 +193,98 @@ pub fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option<S
}) })
} }
render!(|self: SequencerTui|lay!([self.size, Tui::split_up(false, 1,
Tui::fill_xy(SequencerStatusBar::from(self)),
Tui::split_right(false, if self.size.w() > 60 { 20 } else if self.size.w() > 40 { 15 } else { 10 },
Tui::fixed_x(20, Tui::split_down(false, 4, col!([
PhraseSelector::play_phrase(&self.player),
PhraseSelector::next_phrase(&self.player),
]), Tui::split_up(false, 2,
PhraseSelector::edit_phrase(&self.editor.phrase.read().unwrap()),
PhraseListView::from(self),
))),
col!([
Tui::fixed_y(2, TransportView::from((
self,
self.player.play_phrase().as_ref().map(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)).flatten(),
if let SequencerFocus::Transport(_) = self.focus {
true
} else {
false
}
))),
self.editor
]),
)
)]));
pub struct PhraseSelector {
pub(crate) title: &'static str,
pub(crate) name: String,
pub(crate) color: ItemPalette,
pub(crate) time: String,
}
// TODO: Display phrases always in order of appearance
render!(|self: PhraseSelector|Tui::fixed_y(2, col!([
lay!(move|add|{
add(&Tui::push_x(1, Tui::fg(TuiTheme::g(240), self.title)))?;
add(&Tui::bg(self.color.base.rgb, Tui::fill_x(Tui::inset_x(1, Tui::fill_x(Tui::at_e(
Tui::fg(self.color.lightest.rgb, &self.time)))))))?;
Ok(())
}),
Tui::bg(self.color.base.rgb,
Tui::fg(self.color.lightest.rgb,
Tui::bold(true, self.name.clone()))),
])));
impl PhraseSelector {
// beats elapsed
pub fn play_phrase <T: HasPlayPhrase + HasClock> (state: &T) -> Self {
let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() {
let Phrase { ref name, color, .. } = *phrase.read().unwrap();
(name.clone(), color)
} else {
("".to_string(), ItemPalette::from(TuiTheme::g(64)))
};
let time = if let Some(elapsed) = state.pulses_since_start_looped() {
format!("+{:>}", state.clock().timebase.format_beats_0(elapsed))
} else {
String::from("")
};
Self { title: "Now:", time, name, color, }
}
// beats until switchover
pub fn next_phrase <T: HasPlayPhrase> (state: &T) -> Self {
let (time, name, color) = if let Some((t, Some(phrase))) = state.next_phrase() {
let Phrase { ref name, color, .. } = *phrase.read().unwrap();
let time = {
let target = t.pulse.get();
let current = state.clock().playhead.pulse.get();
if target > current {
let remaining = target - current;
format!("-{:>}", state.clock().timebase.format_beats_0(remaining))
} else {
String::new()
}
};
(time, name.clone(), color)
} else {
("".into(), "".into(), TuiTheme::g(64).into())
};
Self { title: "Next:", time, name, color, }
}
pub fn edit_phrase (phrase: &Option<Arc<RwLock<Phrase>>>) -> Self {
let (time, name, color) = if let Some(phrase) = phrase {
let phrase = phrase.read().unwrap();
(format!("{}", phrase.length), phrase.name.clone(), phrase.color)
} else {
("".to_string(), "".to_string(), ItemPalette::from(TuiTheme::g(64)))
};
Self { title: "Editing:", time, name, color }
}
}
impl TransportControl<SequencerFocus> for SequencerTui { impl TransportControl<SequencerFocus> for SequencerTui {
fn transport_focused (&self) -> Option<TransportFocus> { fn transport_focused (&self) -> Option<TransportFocus> {
match self.focus { match self.focus {
@ -386,3 +293,82 @@ impl TransportControl<SequencerFocus> for SequencerTui {
} }
} }
} }
impl HasClock for SequencerTui {
fn clock (&self) -> &ClockModel {
&self.clock
}
}
impl HasPhrases for SequencerTui {
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
&self.phrases.phrases
}
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
&mut self.phrases.phrases
}
}
impl HasPhraseList for SequencerTui {
fn phrases_focused (&self) -> bool {
true
}
fn phrases_entered (&self) -> bool {
true
}
fn phrases_mode (&self) -> &Option<PhraseListMode> {
&self.phrases.mode
}
fn phrase_index (&self) -> usize {
self.phrases.phrase.load(Ordering::Relaxed)
}
}
impl HasEditor for SequencerTui {
fn editor (&self) -> &PhraseEditorModel {
&self.editor
}
fn editor_focused (&self) -> bool {
false
}
fn editor_entered (&self) -> bool {
true
}
}
impl HasFocus for SequencerTui {
type Item = SequencerFocus;
/// Get the currently focused item.
fn focused (&self) -> Self::Item {
self.focus
}
/// Get the currently focused item.
fn set_focused (&mut self, to: Self::Item) {
self.focus = to
}
}
impl Into<Option<TransportFocus>> for SequencerFocus {
fn into (self) -> Option<TransportFocus> {
if let Self::Transport(transport) = self {
Some(transport)
} else {
None
}
}
}
impl From<&SequencerTui> for Option<TransportFocus> {
fn from (state: &SequencerTui) -> Self {
match state.focus {
Transport(focus) => Some(focus),
_ => None
}
}
}
impl Handle<Tui> for SequencerTui {
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
SequencerCommand::execute_with_state(self, i)
}
}

View file

@ -42,6 +42,15 @@ impl Default for PhraseListModel {
} }
} }
impl From<&Arc<RwLock<Phrase>>> for PhraseListModel {
fn from (phrase: &Arc<RwLock<Phrase>>) -> Self {
let mut model = Self::default();
model.phrases.push(phrase.clone());
model.phrase.store(1, Ordering::Relaxed);
model
}
}
impl HasPhrases for PhraseListModel { impl HasPhrases for PhraseListModel {
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> { fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
&self.phrases &self.phrases

View file

@ -57,6 +57,14 @@ impl From<&ClockModel> for PhrasePlayerModel {
} }
} }
impl From<(&ClockModel, &Arc<RwLock<Phrase>>)> for PhrasePlayerModel {
fn from ((clock, phrase): (&ClockModel, &Arc<RwLock<Phrase>>)) -> Self {
let mut model = Self::from(clock);
model.play_phrase = Some((Moment::zero(&clock.timebase), Some(phrase.clone())));
model
}
}
impl HasClock for PhrasePlayerModel { impl HasClock for PhrasePlayerModel {
fn clock (&self) -> &ClockModel { fn clock (&self) -> &ClockModel {
&self.clock &self.clock