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 {
|
||||
type Error = Box<dyn std::error::Error>;
|
||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
||||
|
||||
let clock = ClockModel::from(jack);
|
||||
|
||||
let mut phrase = Phrase::default();
|
||||
phrase.name = "New".into();
|
||||
phrase.color = ItemColor::random().into();
|
||||
phrase.set_length(384);
|
||||
|
||||
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())));
|
||||
|
||||
let phrase = Arc::new(RwLock::new(Phrase::new(
|
||||
"New",
|
||||
true,
|
||||
4 * clock.timebase.ppq.get() as usize,
|
||||
None,
|
||||
Some(ItemColor::random().into())
|
||||
)));
|
||||
Ok(Self {
|
||||
jack: jack.clone(),
|
||||
clock,
|
||||
phrases,
|
||||
player,
|
||||
phrases: PhraseListModel::from(&phrase),
|
||||
editor: PhraseEditorModel::from(&phrase),
|
||||
player: PhrasePlayerModel::from((&clock, &phrase)),
|
||||
clock,
|
||||
size: Measure::new(),
|
||||
cursor: (0, 0),
|
||||
entered: false,
|
||||
split: 20,
|
||||
midi_buf: vec![vec![];65536],
|
||||
note_buf: vec![],
|
||||
|
|
@ -53,7 +44,6 @@ pub struct SequencerTui {
|
|||
pub size: Measure<Tui>,
|
||||
pub cursor: (usize, usize),
|
||||
pub split: u16,
|
||||
pub entered: bool,
|
||||
pub note_buf: Vec<u8>,
|
||||
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
||||
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)]
|
||||
pub enum SequencerCommand {
|
||||
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 {
|
||||
fn transport_focused (&self) -> Option<TransportFocus> {
|
||||
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 {
|
||||
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
||||
&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 {
|
||||
fn clock (&self) -> &ClockModel {
|
||||
&self.clock
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue