mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 20:26:42 +01:00
177 lines
5.4 KiB
Rust
177 lines
5.4 KiB
Rust
use crate::*;
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
pub enum PhraseLengthCommand {
|
|
Begin,
|
|
Next,
|
|
Prev,
|
|
Inc,
|
|
Dec,
|
|
Set(usize),
|
|
Cancel,
|
|
}
|
|
|
|
impl InputToCommand<Tui, PhrasePoolView<Tui>> for PhraseLengthCommand {
|
|
fn input_to_command (view: &PhrasePoolView<Tui>, from: &TuiInput) -> Option<Self> {
|
|
if let Some(PhrasePoolMode::Length(_, length, _)) = view.mode {
|
|
Some(match from.event() {
|
|
key!(KeyCode::Up) => Self::Inc,
|
|
key!(KeyCode::Down) => Self::Dec,
|
|
key!(KeyCode::Right) => Self::Next,
|
|
key!(KeyCode::Left) => Self::Prev,
|
|
key!(KeyCode::Enter) => Self::Set(length),
|
|
key!(KeyCode::Esc) => Self::Cancel,
|
|
_ => return None
|
|
})
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<E: Engine> Command<PhrasePoolView<E>> for PhraseLengthCommand {
|
|
fn execute (self, view: &mut PhrasePoolView<E>) -> Perhaps<Self> {
|
|
use PhraseLengthFocus::*;
|
|
use PhraseLengthCommand::*;
|
|
if let Some(PhrasePoolMode::Length(phrase, ref mut length, ref mut focus)) = view.mode {
|
|
match self {
|
|
Self::Cancel => {
|
|
view.mode = None;
|
|
},
|
|
Self::Prev => {
|
|
focus.prev()
|
|
},
|
|
Self::Next => {
|
|
focus.next()
|
|
},
|
|
Self::Inc => match focus {
|
|
Bar => { *length += 4 * PPQ },
|
|
Beat => { *length += PPQ },
|
|
Tick => { *length += 1 },
|
|
},
|
|
Self::Dec => match focus {
|
|
Bar => { *length = length.saturating_sub(4 * PPQ) },
|
|
Beat => { *length = length.saturating_sub(PPQ) },
|
|
Tick => { *length = length.saturating_sub(1) },
|
|
},
|
|
Self::Set(length) => {
|
|
let mut phrase = view.model.phrases[phrase].write().unwrap();
|
|
let old_length = phrase.length;
|
|
phrase.length = length;
|
|
view.mode = None;
|
|
return Ok(Some(Self::Set(old_length)))
|
|
},
|
|
_ => unreachable!()
|
|
}
|
|
Ok(None)
|
|
} else if self == Begin {
|
|
view.mode = Some(PhrasePoolMode::Length(
|
|
view.phrase,
|
|
view.model.phrases[view.phrase].read().unwrap().length,
|
|
PhraseLengthFocus::Bar
|
|
));
|
|
Ok(None)
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Displays and edits phrase length.
|
|
pub struct PhraseLength<E: Engine> {
|
|
_engine: PhantomData<E>,
|
|
/// Pulses per beat (quaver)
|
|
pub ppq: usize,
|
|
/// Beats per bar
|
|
pub bpb: usize,
|
|
/// Length of phrase in pulses
|
|
pub pulses: usize,
|
|
/// Selected subdivision
|
|
pub focus: Option<PhraseLengthFocus>,
|
|
}
|
|
|
|
impl<E: Engine> PhraseLength<E> {
|
|
pub fn new (pulses: usize, focus: Option<PhraseLengthFocus>) -> Self {
|
|
Self { _engine: Default::default(), ppq: PPQ, bpb: 4, pulses, focus }
|
|
}
|
|
pub fn bars (&self) -> usize {
|
|
self.pulses / (self.bpb * self.ppq)
|
|
}
|
|
pub fn beats (&self) -> usize {
|
|
(self.pulses % (self.bpb * self.ppq)) / self.ppq
|
|
}
|
|
pub fn ticks (&self) -> usize {
|
|
self.pulses % self.ppq
|
|
}
|
|
pub fn bars_string (&self) -> String {
|
|
format!("{}", self.bars())
|
|
}
|
|
pub fn beats_string (&self) -> String {
|
|
format!("{}", self.beats())
|
|
}
|
|
pub fn ticks_string (&self) -> String {
|
|
format!("{:>02}", self.ticks())
|
|
}
|
|
}
|
|
|
|
impl Content for PhraseLength<Tui> {
|
|
type Engine = Tui;
|
|
fn content (&self) -> impl Widget<Engine = Tui> {
|
|
Layers::new(move|add|{
|
|
match self.focus {
|
|
None => add(&row!(
|
|
" ", self.bars_string(),
|
|
".", self.beats_string(),
|
|
".", self.ticks_string(),
|
|
" "
|
|
)),
|
|
Some(PhraseLengthFocus::Bar) => add(&row!(
|
|
"[", self.bars_string(),
|
|
"]", self.beats_string(),
|
|
".", self.ticks_string(),
|
|
" "
|
|
)),
|
|
Some(PhraseLengthFocus::Beat) => add(&row!(
|
|
" ", self.bars_string(),
|
|
"[", self.beats_string(),
|
|
"]", self.ticks_string(),
|
|
" "
|
|
)),
|
|
Some(PhraseLengthFocus::Tick) => add(&row!(
|
|
" ", self.bars_string(),
|
|
".", self.beats_string(),
|
|
"[", self.ticks_string(),
|
|
"]"
|
|
)),
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Focused field of `PhraseLength`
|
|
#[derive(Copy, Clone)]
|
|
pub enum PhraseLengthFocus {
|
|
/// Editing the number of bars
|
|
Bar,
|
|
/// Editing the number of beats
|
|
Beat,
|
|
/// Editing the number of ticks
|
|
Tick,
|
|
}
|
|
|
|
impl PhraseLengthFocus {
|
|
pub fn next (&mut self) {
|
|
*self = match self {
|
|
Self::Bar => Self::Beat,
|
|
Self::Beat => Self::Tick,
|
|
Self::Tick => Self::Bar,
|
|
}
|
|
}
|
|
pub fn prev (&mut self) {
|
|
*self = match self {
|
|
Self::Bar => Self::Tick,
|
|
Self::Beat => Self::Bar,
|
|
Self::Tick => Self::Beat,
|
|
}
|
|
}
|
|
}
|