PhraseList -> Pool

This commit is contained in:
🪞👃🪞 2024-12-25 02:10:44 +01:00
parent ac0ee26b7c
commit 85cfb43e82
7 changed files with 52 additions and 54 deletions

View file

@ -41,7 +41,7 @@ mod phrase_editor; pub(crate) use phrase_editor::*;
mod piano_horizontal; pub(crate) use piano_horizontal::*; mod piano_horizontal; pub(crate) use piano_horizontal::*;
mod phrase_length; pub(crate) use phrase_length::*; mod phrase_length; pub(crate) use phrase_length::*;
mod phrase_rename; pub(crate) use phrase_rename::*; mod phrase_rename; pub(crate) use phrase_rename::*;
mod phrase_list; pub(crate) use phrase_list::*; mod pool; pub(crate) use pool::*;
mod port_select; mod port_select;
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////

View file

@ -3,7 +3,7 @@ use crate::*;
pub struct ArrangerTui { pub struct ArrangerTui {
jack: Arc<RwLock<JackClient>>, jack: Arc<RwLock<JackClient>>,
pub clock: ClockModel, pub clock: ClockModel,
pub phrases: PhraseListModel, pub phrases: PoolModel,
pub tracks: Vec<ArrangerTrack>, pub tracks: Vec<ArrangerTrack>,
pub scenes: Vec<ArrangerScene>, pub scenes: Vec<ArrangerScene>,
pub splits: [u16;2], pub splits: [u16;2],
@ -97,7 +97,7 @@ render!(<Tui>|self: ArrangerTui|{
let transport = TransportView::from((self, Some(ItemPalette::from(TuiTheme::g(96))), true)); let transport = TransportView::from((self, Some(ItemPalette::from(TuiTheme::g(96))), true));
let with_transport = |x|col!([row!(![&play, &transport]), &x]); let with_transport = |x|col!([row!(![&play, &transport]), &x]);
let pool_size = if self.show_pool { self.splits[1] } else { 0 }; let pool_size = if self.show_pool { self.splits[1] } else { 0 };
let with_pool = |x|Split::left(false, pool_size, PhraseListView(&self.phrases), x); let with_pool = |x|Split::left(false, pool_size, PoolView(&self.phrases), x);
let status = ArrangerStatus::from(self); let status = ArrangerStatus::from(self);
let with_editbar = |x|Tui::split_n(false, 3, PhraseEditStatus(&self.editor), x); let with_editbar = |x|Tui::split_n(false, 3, PhraseEditStatus(&self.editor), x);
let with_status = |x|Tui::split_n(false, 2, status, x); let with_status = |x|Tui::split_n(false, 2, status, x);

View file

@ -8,7 +8,7 @@ use PhrasePoolCommand::*;
pub struct SequencerTui { pub struct SequencerTui {
_jack: Arc<RwLock<JackClient>>, _jack: Arc<RwLock<JackClient>>,
pub(crate) clock: ClockModel, pub(crate) clock: ClockModel,
pub(crate) phrases: PhraseListModel, pub(crate) phrases: PoolModel,
pub(crate) player: PhrasePlayerModel, pub(crate) player: PhrasePlayerModel,
pub(crate) editor: PhraseEditorModel, pub(crate) editor: PhraseEditorModel,
pub(crate) size: Measure<Tui>, pub(crate) size: Measure<Tui>,
@ -26,7 +26,7 @@ from_jack!(|jack|SequencerTui {
))); )));
Self { Self {
_jack: jack.clone(), _jack: jack.clone(),
phrases: PhraseListModel::from(&phrase), phrases: PoolModel::from(&phrase),
editor: PhraseEditorModel::from(&phrase), editor: PhraseEditorModel::from(&phrase),
player: PhrasePlayerModel::from((&clock, &phrase)), player: PhrasePlayerModel::from((&clock, &phrase)),
size: Measure::new(), size: Measure::new(),
@ -42,7 +42,7 @@ render!(<Tui>|self: SequencerTui|{
let w = self.size.w(); let w = self.size.w();
let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
let pool_w = if self.show_pool { phrase_w } else { 0 }; let pool_w = if self.show_pool { phrase_w } else { 0 };
let pool = Fill::h(Align::e(PhraseListView(&self.phrases))); let pool = Fill::h(Align::e(PoolView(&self.phrases)));
let with_pool = move|x|Tui::split_w(false, pool_w, pool, x); let with_pool = move|x|Tui::split_w(false, pool_w, pool, x);
let status = SequencerStatus::from(self); let status = SequencerStatus::from(self);
let with_status = |x|Tui::split_n(false, if self.status { 2 } else { 0 }, status, x); let with_status = |x|Tui::split_n(false, if self.status { 2 } else { 0 }, status, x);

View file

@ -47,10 +47,8 @@ pub enum ArrangerClipCommand {
} }
input_to_command!(ArrangerCommand: <Tui>|state: ArrangerTui, input|match input.event() { input_to_command!(ArrangerCommand: <Tui>|state: ArrangerTui, input|match input.event() {
// TODO: u: undo key_pat!(Char('u')) => Self::History(-1),
key_pat!(Char('u')) => { todo!("undo") }, key_pat!(Char('U')) => Self::History(1),
// TODO: Shift-U: redo
key_pat!(Char('U')) => { todo!("redo") },
// TODO: k: toggle on-screen keyboard // TODO: k: toggle on-screen keyboard
key_pat!(Ctrl-Char('k')) => { todo!("keyboard") }, key_pat!(Ctrl-Char('k')) => { todo!("keyboard") },
// Transport: Play/pause // Transport: Play/pause

View file

@ -1,5 +1,5 @@
use crate::*; use crate::*;
use super::phrase_list::{PhraseListModel, PhraseListMode}; use super::pool::{PoolModel, PoolMode};
use PhraseLengthFocus::*; use PhraseLengthFocus::*;
use PhraseLengthCommand::*; use PhraseLengthCommand::*;
/// Displays and edits phrase length. /// Displays and edits phrase length.
@ -88,9 +88,9 @@ pub enum PhraseLengthCommand {
Inc, Inc,
Dec, Dec,
} }
command!(|self:PhraseLengthCommand,state:PhraseListModel|{ command!(|self:PhraseLengthCommand,state:PoolModel|{
match state.phrases_mode_mut().clone() { match state.phrases_mode_mut().clone() {
Some(PhraseListMode::Length(phrase, ref mut length, ref mut focus)) => match self { Some(PoolMode::Length(phrase, ref mut length, ref mut focus)) => match self {
Cancel => { *state.phrases_mode_mut() = None; }, Cancel => { *state.phrases_mode_mut() = None; },
Prev => { focus.prev() }, Prev => { focus.prev() },
Next => { focus.next() }, Next => { focus.next() },
@ -118,8 +118,8 @@ command!(|self:PhraseLengthCommand,state:PhraseListModel|{
}; };
None None
}); });
input_to_command!(PhraseLengthCommand:<Tui>|state:PhraseListModel,from|{ input_to_command!(PhraseLengthCommand:<Tui>|state:PoolModel,from|{
if let Some(PhraseListMode::Length(_, length, _)) = state.phrases_mode() { if let Some(PoolMode::Length(_, length, _)) = state.phrases_mode() {
match from.event() { match from.event() {
key_pat!(Up) => Self::Inc, key_pat!(Up) => Self::Inc,
key_pat!(Down) => Self::Dec, key_pat!(Down) => Self::Dec,

View file

@ -8,11 +8,11 @@ pub enum PhraseRenameCommand {
Set(String), Set(String),
} }
impl Command<PhraseListModel> for PhraseRenameCommand { impl Command<PoolModel> for PhraseRenameCommand {
fn execute (self, state: &mut PhraseListModel) -> Perhaps<Self> { fn execute (self, state: &mut PoolModel) -> Perhaps<Self> {
use PhraseRenameCommand::*; use PhraseRenameCommand::*;
match state.phrases_mode_mut().clone() { match state.phrases_mode_mut().clone() {
Some(PhraseListMode::Rename(phrase, ref mut old_name)) => match self { Some(PoolMode::Rename(phrase, ref mut old_name)) => match self {
Set(s) => { Set(s) => {
state.phrases()[phrase].write().unwrap().name = s.into(); state.phrases()[phrase].write().unwrap().name = s.into();
return Ok(Some(Self::Set(old_name.clone()))) return Ok(Some(Self::Set(old_name.clone())))
@ -33,10 +33,10 @@ impl Command<PhraseListModel> for PhraseRenameCommand {
} }
} }
impl InputToCommand<Tui, PhraseListModel> for PhraseRenameCommand { impl InputToCommand<Tui, PoolModel> for PhraseRenameCommand {
fn input_to_command (state: &PhraseListModel, from: &TuiInput) -> Option<Self> { fn input_to_command (state: &PoolModel, from: &TuiInput) -> Option<Self> {
use KeyCode::{Char, Backspace, Enter, Esc}; use KeyCode::{Char, Backspace, Enter, Esc};
if let Some(PhraseListMode::Rename(_, ref old_name)) = state.phrases_mode() { if let Some(PoolMode::Rename(_, ref old_name)) = state.phrases_mode() {
Some(match from.event() { Some(match from.event() {
key_pat!(Char(c)) => { key_pat!(Char(c)) => {
let mut new_name = old_name.clone(); let mut new_name = old_name.clone();

View file

@ -7,14 +7,14 @@ use crate::{
}; };
#[derive(Debug)] #[derive(Debug)]
pub struct PhraseListModel { pub struct PoolModel {
pub(crate) visible: bool, pub(crate) visible: bool,
/// Collection of phrases /// Collection of phrases
pub(crate) phrases: Vec<Arc<RwLock<Phrase>>>, pub(crate) phrases: Vec<Arc<RwLock<Phrase>>>,
/// Selected phrase /// Selected phrase
pub(crate) phrase: AtomicUsize, pub(crate) phrase: AtomicUsize,
/// Mode switch /// Mode switch
pub(crate) mode: Option<PhraseListMode>, pub(crate) mode: Option<PoolMode>,
/// Rendered size /// Rendered size
size: Measure<Tui>, size: Measure<Tui>,
/// Scroll offset /// Scroll offset
@ -23,7 +23,7 @@ pub struct PhraseListModel {
/// Modes for phrase pool /// Modes for phrase pool
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum PhraseListMode { pub enum PoolMode {
/// Renaming a pattern /// Renaming a pattern
Rename(usize, String), Rename(usize, String),
/// Editing the length of a pattern /// Editing the length of a pattern
@ -51,7 +51,7 @@ pub enum PhrasesCommand {
Export(Browse), Export(Browse),
} }
command!(|self:PhrasesCommand, state:PhraseListModel|{ command!(|self:PhrasesCommand, state: PoolModel|{
use PhrasesCommand::*; use PhrasesCommand::*;
match self { match self {
Show(visible) => { Show(visible) => {
@ -62,7 +62,7 @@ command!(|self:PhrasesCommand, state:PhraseListModel|{
PhraseRenameCommand::Begin => { PhraseRenameCommand::Begin => {
let length = state.phrases()[state.phrase_index()].read().unwrap().length; let length = state.phrases()[state.phrase_index()].read().unwrap().length;
*state.phrases_mode_mut() = Some( *state.phrases_mode_mut() = Some(
PhraseListMode::Length(state.phrase_index(), length, PhraseLengthFocus::Bar) PoolMode::Length(state.phrase_index(), length, PhraseLengthFocus::Bar)
); );
None None
}, },
@ -72,7 +72,7 @@ command!(|self:PhrasesCommand, state:PhraseListModel|{
PhraseLengthCommand::Begin => { PhraseLengthCommand::Begin => {
let name = state.phrases()[state.phrase_index()].read().unwrap().name.clone(); let name = state.phrases()[state.phrase_index()].read().unwrap().name.clone();
*state.phrases_mode_mut() = Some( *state.phrases_mode_mut() = Some(
PhraseListMode::Rename(state.phrase_index(), name) PoolMode::Rename(state.phrase_index(), name)
); );
None None
}, },
@ -81,7 +81,7 @@ command!(|self:PhrasesCommand, state:PhraseListModel|{
Import(command) => match command { Import(command) => match command {
FileBrowserCommand::Begin => { FileBrowserCommand::Begin => {
*state.phrases_mode_mut() = Some( *state.phrases_mode_mut() = Some(
PhraseListMode::Import(state.phrase_index(), FileBrowser::new(None)?) PoolMode::Import(state.phrase_index(), FileBrowser::new(None)?)
); );
None None
}, },
@ -90,7 +90,7 @@ command!(|self:PhrasesCommand, state:PhraseListModel|{
Export(command) => match command { Export(command) => match command {
FileBrowserCommand::Begin => { FileBrowserCommand::Begin => {
*state.phrases_mode_mut() = Some( *state.phrases_mode_mut() = Some(
PhraseListMode::Export(state.phrase_index(), FileBrowser::new(None)?) PoolMode::Export(state.phrase_index(), FileBrowser::new(None)?)
); );
None None
}, },
@ -104,15 +104,15 @@ command!(|self:PhrasesCommand, state:PhraseListModel|{
} }
}); });
input_to_command!(PhrasesCommand:<Tui>|state:PhraseListModel,input|match state.phrases_mode() { input_to_command!(PhrasesCommand:<Tui>|state: PoolModel,input|match state.phrases_mode() {
Some(PhraseListMode::Rename(..)) => Self::Rename(Rename::input_to_command(state, input)?), Some(PoolMode::Rename(..)) => Self::Rename(Rename::input_to_command(state, input)?),
Some(PhraseListMode::Length(..)) => Self::Length(Length::input_to_command(state, input)?), Some(PoolMode::Length(..)) => Self::Length(Length::input_to_command(state, input)?),
Some(PhraseListMode::Import(..)) => Self::Import(Browse::input_to_command(state, input)?), Some(PoolMode::Import(..)) => Self::Import(Browse::input_to_command(state, input)?),
Some(PhraseListMode::Export(..)) => Self::Export(Browse::input_to_command(state, input)?), Some(PoolMode::Export(..)) => Self::Export(Browse::input_to_command(state, input)?),
_ => to_phrases_command(state, input)? _ => to_phrases_command(state, input)?
}); });
fn to_phrases_command (state: &PhraseListModel, input: &TuiInput) -> Option<PhrasesCommand> { fn to_phrases_command (state: &PoolModel, input: &TuiInput) -> Option<PhrasesCommand> {
use KeyCode::{Up, Down, Delete, Char}; use KeyCode::{Up, Down, Delete, Char};
use PhrasesCommand as Cmd; use PhrasesCommand as Cmd;
let index = state.phrase_index(); let index = state.phrase_index();
@ -161,7 +161,7 @@ fn to_phrases_command (state: &PhraseListModel, input: &TuiInput) -> Option<Phra
_ => return None _ => return None
}) })
} }
impl Default for PhraseListModel { impl Default for PoolModel {
fn default () -> Self { fn default () -> Self {
Self { Self {
visible: true, visible: true,
@ -173,32 +173,32 @@ impl Default for PhraseListModel {
} }
} }
} }
from!(|phrase:&Arc<RwLock<Phrase>>|PhraseListModel = { from!(|phrase:&Arc<RwLock<Phrase>>|PoolModel = {
let mut model = Self::default(); let mut model = Self::default();
model.phrases.push(phrase.clone()); model.phrases.push(phrase.clone());
model.phrase.store(1, Relaxed); model.phrase.store(1, Relaxed);
model model
}); });
has_phrases!(|self:PhraseListModel|self.phrases); has_phrases!(|self: PoolModel|self.phrases);
has_phrase!(|self:PhraseListModel|self.phrases[self.phrase_index()]); has_phrase!(|self: PoolModel|self.phrases[self.phrase_index()]);
impl PhraseListModel { impl PoolModel {
pub(crate) fn phrase_index (&self) -> usize { pub(crate) fn phrase_index (&self) -> usize {
self.phrase.load(Relaxed) self.phrase.load(Relaxed)
} }
pub(crate) fn set_phrase_index (&self, value: usize) { pub(crate) fn set_phrase_index (&self, value: usize) {
self.phrase.store(value, Relaxed); self.phrase.store(value, Relaxed);
} }
pub(crate) fn phrases_mode (&self) -> &Option<PhraseListMode> { pub(crate) fn phrases_mode (&self) -> &Option<PoolMode> {
&self.mode &self.mode
} }
pub(crate) fn phrases_mode_mut (&mut self) -> &mut Option<PhraseListMode> { pub(crate) fn phrases_mode_mut (&mut self) -> &mut Option<PoolMode> {
&mut self.mode &mut self.mode
} }
} }
pub struct PhraseListView<'a>(pub(crate) &'a PhraseListModel); pub struct PoolView<'a>(pub(crate) &'a PoolModel);
// TODO: Display phrases always in order of appearance // TODO: Display phrases always in order of appearance
render!(<Tui>|self: PhraseListView<'a>|{ render!(<Tui>|self: PoolView<'a>|{
let PhraseListModel { phrases, mode, .. } = self.0; let PoolModel { phrases, mode, .. } = self.0;
let bg = TuiTheme::g(32); let bg = TuiTheme::g(32);
let title_color = TuiTheme::ti1(); let title_color = TuiTheme::ti1();
let upper_left = "Pool:"; let upper_left = "Pool:";
@ -208,13 +208,13 @@ render!(<Tui>|self: PhraseListView<'a>|{
add(&Fill::wh(Outer(Style::default().fg(color.base.rgb).bg(bg))))?; add(&Fill::wh(Outer(Style::default().fg(color.base.rgb).bg(bg))))?;
//add(&Lozenge(Style::default().bg(border_bg).fg(border_color)))?; //add(&Lozenge(Style::default().bg(border_bg).fg(border_color)))?;
add(&Tui::inset_xy(0, 1, Fill::wh(col!(move|add|match mode { add(&Tui::inset_xy(0, 1, Fill::wh(col!(move|add|match mode {
Some(PhraseListMode::Import(_, ref file_picker)) => add(file_picker), Some(PoolMode::Import(_, ref file_picker)) => add(file_picker),
Some(PhraseListMode::Export(_, ref file_picker)) => add(file_picker), Some(PoolMode::Export(_, ref file_picker)) => add(file_picker),
_ => Ok(for (i, phrase) in phrases.iter().enumerate() { _ => Ok(for (i, phrase) in phrases.iter().enumerate() {
add(&lay!(|add|{ add(&lay!(|add|{
let Phrase { ref name, color, length, .. } = *phrase.read().unwrap(); let Phrase { ref name, color, length, .. } = *phrase.read().unwrap();
let mut length = PhraseLength::new(length, None); let mut length = PhraseLength::new(length, None);
if let Some(PhraseListMode::Length(phrase, new_length, focus)) = mode { if let Some(PoolMode::Length(phrase, new_length, focus)) = mode {
if i == *phrase { if i == *phrase {
length.pulses = *new_length; length.pulses = *new_length;
length.focus = Some(*focus); length.focus = Some(*focus);
@ -227,7 +227,7 @@ render!(<Tui>|self: PhraseListView<'a>|{
})), })),
Tui::bold(true, { Tui::bold(true, {
let mut row2 = format!(" {name}"); let mut row2 = format!(" {name}");
if let Some(PhraseListMode::Rename(phrase, _)) = mode { if let Some(PoolMode::Rename(phrase, _)) = mode {
if i == *phrase { if i == *phrase {
row2 = format!("{row2}"); row2 = format!("{row2}");
} }
@ -305,8 +305,8 @@ impl PhraseSelector {
Self { title: " Next|", time, name, color, } Self { title: " Next|", time, name, color, }
} }
} }
command!(|self: FileBrowserCommand, state: PhraseListModel|{ command!(|self: FileBrowserCommand, state: PoolModel|{
use PhraseListMode::*; use PoolMode::*;
use FileBrowserCommand::*; use FileBrowserCommand::*;
let mode = &mut state.mode; let mode = &mut state.mode;
match mode { match mode {
@ -334,11 +334,11 @@ command!(|self: FileBrowserCommand, state: PhraseListModel|{
}; };
None None
}); });
input_to_command!(FileBrowserCommand:<Tui>|state:PhraseListModel,from|{ input_to_command!(FileBrowserCommand:<Tui>|state: PoolModel,from|{
use FileBrowserCommand::*; use FileBrowserCommand::*;
use KeyCode::{Up, Down, Left, Right, Enter, Esc, Backspace, Char}; use KeyCode::{Up, Down, Left, Right, Enter, Esc, Backspace, Char};
if let Some(PhraseListMode::Import(_index, browser)) = &state.mode { if let Some(PoolMode::Import(_index, browser)) = &state.mode {
match from.event() { match from.event() {
key_pat!(Up) => Select(browser.index.overflowing_sub(1).0 key_pat!(Up) => Select(browser.index.overflowing_sub(1).0
.min(browser.len().saturating_sub(1))), .min(browser.len().saturating_sub(1))),
@ -352,7 +352,7 @@ input_to_command!(FileBrowserCommand:<Tui>|state:PhraseListModel,from|{
key_pat!(Esc) => Cancel, key_pat!(Esc) => Cancel,
_ => return None _ => return None
} }
} else if let Some(PhraseListMode::Export(_index, browser)) = &state.mode { } else if let Some(PoolMode::Export(_index, browser)) = &state.mode {
match from.event() { match from.event() {
key_pat!(Up) => Select(browser.index.overflowing_sub(1).0 key_pat!(Up) => Select(browser.index.overflowing_sub(1).0
.min(browser.len())), .min(browser.len())),