flatten workspace into 1 crate

This commit is contained in:
🪞👃🪞 2024-12-29 00:10:30 +01:00
parent 7c4e1e2166
commit d926422c67
147 changed files with 66 additions and 126 deletions

144
src/pool/phrase_length.rs Normal file
View file

@ -0,0 +1,144 @@
use crate::*;
use super::*;
use PhraseLengthFocus::*;
use PhraseLengthCommand::*;
use KeyCode::{Up, Down, Left, Right, Enter, Esc};
/// Displays and edits phrase length.
#[derive(Clone)]
pub struct PhraseLength {
/// 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 PhraseLength {
pub fn new (pulses: usize, focus: Option<PhraseLengthFocus>) -> Self {
Self { 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())
}
}
/// Focused field of `PhraseLength`
#[derive(Copy, Clone, Debug)]
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,
}
}
}
render!(<Tui>|self: PhraseLength|{
let bars = ||self.bars_string();
let beats = ||self.beats_string();
let ticks = ||self.ticks_string();
row!(move|add|match self.focus {
None =>
add(&row!([" ", bars(), ".", beats(), ".", ticks()])),
Some(PhraseLengthFocus::Bar) =>
add(&row!(["[", bars(), "]", beats(), ".", ticks()])),
Some(PhraseLengthFocus::Beat) =>
add(&row!([" ", bars(), "[", beats(), "]", ticks()])),
Some(PhraseLengthFocus::Tick) =>
add(&row!([" ", bars(), ".", beats(), "[", ticks()])),
})
});
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum PhraseLengthCommand {
Begin,
Cancel,
Set(usize),
Next,
Prev,
Inc,
Dec,
}
command!(|self:PhraseLengthCommand,state:PoolModel|{
match state.phrases_mode_mut().clone() {
Some(PoolMode::Length(phrase, ref mut length, ref mut focus)) => match self {
Cancel => { *state.phrases_mode_mut() = None; },
Prev => { focus.prev() },
Next => { focus.next() },
Inc => match focus {
Bar => { *length += 4 * PPQ },
Beat => { *length += PPQ },
Tick => { *length += 1 },
},
Dec => match focus {
Bar => { *length = length.saturating_sub(4 * PPQ) },
Beat => { *length = length.saturating_sub(PPQ) },
Tick => { *length = length.saturating_sub(1) },
},
Set(length) => {
let mut phrase = state.phrases()[phrase].write().unwrap();
let old_length = phrase.length;
phrase.length = length;
std::mem::drop(phrase);
*state.phrases_mode_mut() = None;
return Ok(Some(Self::Set(old_length)))
},
_ => unreachable!()
},
_ => unreachable!()
};
None
});
input_to_command!(PhraseLengthCommand:<Tui>|state:PoolModel,from|{
if let Some(PoolMode::Length(_, length, _)) = state.phrases_mode() {
match from.event() {
key_pat!(Up) => Self::Inc,
key_pat!(Down) => Self::Dec,
key_pat!(Right) => Self::Next,
key_pat!(Left) => Self::Prev,
key_pat!(Enter) => Self::Set(*length),
key_pat!(Esc) => Self::Cancel,
_ => return None
}
} else {
unreachable!()
}
});

60
src/pool/phrase_rename.rs Normal file
View file

@ -0,0 +1,60 @@
use crate::*;
use super::*;
#[derive(Clone, Debug, PartialEq)]
pub enum PhraseRenameCommand {
Begin,
Cancel,
Confirm,
Set(String),
}
impl Command<PoolModel> for PhraseRenameCommand {
fn execute (self, state: &mut PoolModel) -> Perhaps<Self> {
use PhraseRenameCommand::*;
match state.phrases_mode_mut().clone() {
Some(PoolMode::Rename(phrase, ref mut old_name)) => match self {
Set(s) => {
state.phrases()[phrase].write().unwrap().name = s;
return Ok(Some(Self::Set(old_name.clone())))
},
Confirm => {
let old_name = old_name.clone();
*state.phrases_mode_mut() = None;
return Ok(Some(Self::Set(old_name)))
},
Cancel => {
state.phrases()[phrase].write().unwrap().name = old_name.clone();
},
_ => unreachable!()
},
_ => unreachable!()
};
Ok(None)
}
}
impl InputToCommand<Tui, PoolModel> for PhraseRenameCommand {
fn input_to_command (state: &PoolModel, from: &TuiInput) -> Option<Self> {
use KeyCode::{Char, Backspace, Enter, Esc};
if let Some(PoolMode::Rename(_, ref old_name)) = state.phrases_mode() {
Some(match from.event() {
key_pat!(Char(c)) => {
let mut new_name = old_name.clone();
new_name.push(*c);
Self::Set(new_name)
},
key_pat!(Backspace) => {
let mut new_name = old_name.clone();
new_name.pop();
Self::Set(new_name)
},
key_pat!(Enter) => Self::Confirm,
key_pat!(Esc) => Self::Cancel,
_ => return None
})
} else {
unreachable!()
}
}
}

View file

@ -0,0 +1,65 @@
use crate::*;
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!(<Tui>|self: PhraseSelector|Fixed::wh(24, 1, row!([
Tui::fg(self.color.lightest.rgb, Tui::bold(true, &self.title)),
Tui::fg_bg(self.color.lighter.rgb, self.color.base.rgb, row!([
format!("{:8}", &self.name[0..8.min(self.name.len())]),
Tui::bg(self.color.dark.rgb, &self.time),
])),
])));
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 MidiClip { 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 MidiClip { 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 if let Some((_, Some(phrase))) = state.play_phrase() {
let phrase = phrase.read().unwrap();
if phrase.looped {
(" ".into(), phrase.name.clone(), phrase.color)
} else {
(" ".into(), " ".into(), TuiTheme::g(64).into())
}
} else {
(" ".into(), " ".into(), TuiTheme::g(64).into())
};
Self { title: " Next|", time, name, color, }
}
}