mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
simplify sequencer init
This commit is contained in:
parent
2795c05275
commit
9619ef9739
3 changed files with 198 additions and 195 deletions
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue