mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
wip: refactor pt.42 (84e) lotta todo!
This commit is contained in:
parent
bf0e14c252
commit
638298ad32
11 changed files with 536 additions and 335 deletions
|
|
@ -19,16 +19,13 @@ impl<T: ClockApi> Command<T> for ClockCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ClockApi: Send + Sync {
|
pub trait ClockApi: Send + Sync {
|
||||||
/// Current moment in time
|
/// Temporal resolution in all units
|
||||||
fn current (&self) -> &Instant;
|
fn timebase (&self) -> &Arc<Timebase>;
|
||||||
/// Note quantization factor
|
/// Note quantization factor
|
||||||
fn quant (&self) -> &Quantize;
|
fn quant (&self) -> &Quantize;
|
||||||
/// Launch quantization factor
|
/// Launch quantization factor
|
||||||
fn sync (&self) -> &LaunchSync;
|
fn sync (&self) -> &LaunchSync;
|
||||||
|
|
||||||
fn timebase (&self) -> &Arc<Timebase> {
|
|
||||||
&self.current().timebase
|
|
||||||
}
|
|
||||||
fn sr (&self) -> &SampleRate {
|
fn sr (&self) -> &SampleRate {
|
||||||
&self.timebase().sr
|
&self.timebase().sr
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ pub trait HasPhrases {
|
||||||
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>>;
|
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum PhrasePoolCommand {
|
pub enum PhrasePoolCommand {
|
||||||
Add(usize),
|
Add(usize),
|
||||||
Delete(usize),
|
Delete(usize),
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ pub trait HasMidiBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait HasPhrase: ClockApi + PlayheadApi + HasMidiBuffer {
|
pub trait HasPhrase: PlayheadApi + HasMidiBuffer {
|
||||||
fn phrase (&self)
|
fn phrase (&self)
|
||||||
-> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)>;
|
-> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)>;
|
||||||
fn phrase_mut (&self)
|
fn phrase_mut (&self)
|
||||||
|
|
|
||||||
|
|
@ -34,12 +34,24 @@ impl<T: PlayheadApi> Command<T> for PlayheadCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait PlayheadApi: ClockApi {
|
pub trait PlayheadApi: ClockApi {
|
||||||
|
/// Current moment in time
|
||||||
|
fn current (&self) -> &Instant;
|
||||||
|
/// Handle to JACK transport
|
||||||
fn transport (&self) -> &jack::Transport;
|
fn transport (&self) -> &jack::Transport;
|
||||||
/// Playback state
|
/// Playback state
|
||||||
fn playing (&self) -> &RwLock<Option<TransportState>>;
|
fn playing (&self) -> &RwLock<Option<TransportState>>;
|
||||||
/// Global sample and usec at which playback started
|
/// Global sample and usec at which playback started
|
||||||
fn started (&self) -> &RwLock<Option<(usize, usize)>>;
|
fn started (&self) -> &RwLock<Option<(usize, usize)>>;
|
||||||
|
|
||||||
|
fn is_stopped (&self) -> bool {
|
||||||
|
*self.playing().read().unwrap() == Some(TransportState::Stopped)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_rolling (&self) -> bool {
|
||||||
|
*self.playing().read().unwrap() == Some(TransportState::Rolling)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Current pulse
|
||||||
fn pulse (&self) -> f64 {
|
fn pulse (&self) -> f64 {
|
||||||
self.current().pulse.get()
|
self.current().pulse.get()
|
||||||
}
|
}
|
||||||
|
|
@ -67,13 +79,6 @@ pub trait PlayheadApi: ClockApi {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_stopped (&self) -> bool {
|
|
||||||
*self.playing().read().unwrap() == Some(TransportState::Stopped)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_rolling (&self) -> bool {
|
|
||||||
*self.playing().read().unwrap() == Some(TransportState::Rolling)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hosts the JACK callback for updating the temporal pointer and playback status.
|
/// Hosts the JACK callback for updating the temporal pointer and playback status.
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,6 @@ submod! {
|
||||||
//tui_plugin_vst2
|
//tui_plugin_vst2
|
||||||
//tui_plugin_vst3
|
//tui_plugin_vst3
|
||||||
tui_pool
|
tui_pool
|
||||||
tui_pool_length
|
|
||||||
tui_pool_rename
|
|
||||||
//tui_sampler // TODO
|
//tui_sampler // TODO
|
||||||
//tui_sampler_cmd
|
//tui_sampler_cmd
|
||||||
tui_sequencer
|
tui_sequencer
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ pub type ArrangerApp<E: Engine> = AppView<
|
||||||
ArrangerStatusBar
|
ArrangerStatusBar
|
||||||
>;
|
>;
|
||||||
|
|
||||||
impl Audio for ArrangerApp {
|
impl<E: Engine> Audio for ArrangerApp<E> {
|
||||||
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
ArrangerRefAudio(self.app).process(client, scope)
|
ArrangerRefAudio(self.app).process(client, scope)
|
||||||
}
|
}
|
||||||
|
|
@ -78,9 +78,19 @@ impl InputToCommand<Tui, ArrangerApp<Tui>> for ArrangerAppCommand {
|
||||||
Self::App(Playhead(PlayheadCommand::Play(None)))
|
Self::App(Playhead(PlayheadCommand::Play(None)))
|
||||||
},
|
},
|
||||||
_ => Self::App(match view.focused() {
|
_ => Self::App(match view.focused() {
|
||||||
Content(ArrangerViewFocus::Transport) => Transport(
|
Content(ArrangerViewFocus::Transport) => {
|
||||||
TransportCommand::input_to_command(&view.app.transport, input)?
|
match TransportCommand::input_to_command(&view.app.transport, input)? {
|
||||||
),
|
Focus(command) => {
|
||||||
|
todo!()
|
||||||
|
},
|
||||||
|
App(Clock(command)) => {
|
||||||
|
todo!()
|
||||||
|
},
|
||||||
|
App(Playhead(command)) => {
|
||||||
|
todo!()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
Content(ArrangerViewFocus::PhraseEditor) => Editor(
|
Content(ArrangerViewFocus::PhraseEditor) => Editor(
|
||||||
PhraseEditorCommand::input_to_command(&view.app.editor, input)?
|
PhraseEditorCommand::input_to_command(&view.app.editor, input)?
|
||||||
),
|
),
|
||||||
|
|
@ -280,8 +290,8 @@ impl HasJack for ArrangerView<Tui> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClockApi for ArrangerView<Tui> {
|
impl ClockApi for ArrangerView<Tui> {
|
||||||
fn current (&self) -> &Instant {
|
fn timebase (&self) -> &Arc<Timebase> {
|
||||||
&self.current
|
&self.current.timebase
|
||||||
}
|
}
|
||||||
fn quant (&self) -> &Quantize {
|
fn quant (&self) -> &Quantize {
|
||||||
&self.quant
|
&self.quant
|
||||||
|
|
@ -291,6 +301,21 @@ impl ClockApi for ArrangerView<Tui> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PlayheadApi for ArrangerView<Tui> {
|
||||||
|
fn current (&self) -> &Instant {
|
||||||
|
&self.current
|
||||||
|
}
|
||||||
|
fn transport (&self) -> &jack::Transport {
|
||||||
|
&self.transport
|
||||||
|
}
|
||||||
|
fn playing (&self) -> &RwLock<Option<TransportState>> {
|
||||||
|
&self.playing
|
||||||
|
}
|
||||||
|
fn started (&self) -> &RwLock<Option<(usize, usize)>> {
|
||||||
|
&self.started
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl HasPhrases for ArrangerView<Tui> {
|
impl HasPhrases for ArrangerView<Tui> {
|
||||||
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
||||||
&self.phrases
|
&self.phrases
|
||||||
|
|
@ -986,16 +1011,17 @@ pub fn arranger_content_vertical (
|
||||||
view: &ArrangerView<Tui>,
|
view: &ArrangerView<Tui>,
|
||||||
factor: usize
|
factor: usize
|
||||||
) -> impl Widget<Engine = Tui> + use<'_> {
|
) -> impl Widget<Engine = Tui> + use<'_> {
|
||||||
let clock = view.clock();
|
let timebase = view.timebase();
|
||||||
let tracks = view.tracks();
|
let current = view.current();
|
||||||
let scenes = view.scenes();
|
let tracks = view.tracks();
|
||||||
let cols = track_widths(tracks);
|
let scenes = view.scenes();
|
||||||
let rows = ArrangerScene::ppqs(scenes, factor);
|
let cols = track_widths(tracks);
|
||||||
let bg = view.color;
|
let rows = ArrangerScene::ppqs(scenes, factor);
|
||||||
let clip_bg = TuiTheme::border_bg();
|
let bg = view.color;
|
||||||
let sep_fg = TuiTheme::separator_fg(false);
|
let clip_bg = TuiTheme::border_bg();
|
||||||
let header_h = 3u16;//5u16;
|
let sep_fg = TuiTheme::separator_fg(false);
|
||||||
let scenes_w = 3 + ArrangerScene::longest_name(scenes) as u16; // x of 1st track
|
let header_h = 3u16;//5u16;
|
||||||
|
let scenes_w = 3 + ArrangerScene::longest_name(scenes) as u16; // x of 1st track
|
||||||
let arrangement = Layers::new(move |add|{
|
let arrangement = Layers::new(move |add|{
|
||||||
let rows: &[(usize, usize)] = rows.as_ref();
|
let rows: &[(usize, usize)] = rows.as_ref();
|
||||||
let cols: &[(usize, usize)] = cols.as_ref();
|
let cols: &[(usize, usize)] = cols.as_ref();
|
||||||
|
|
@ -1033,7 +1059,7 @@ pub fn arranger_content_vertical (
|
||||||
let elapsed = if let Some((_, Some(phrase))) = track.phrase().as_ref() {
|
let elapsed = if let Some((_, Some(phrase))) = track.phrase().as_ref() {
|
||||||
let length = phrase.read().unwrap().length;
|
let length = phrase.read().unwrap().length;
|
||||||
let elapsed = track.pulses_since_start().unwrap();
|
let elapsed = track.pulses_since_start().unwrap();
|
||||||
let elapsed = clock.timebase().format_beats_1_short(
|
let elapsed = timebase.format_beats_1_short(
|
||||||
(elapsed as usize % length) as f64
|
(elapsed as usize % length) as f64
|
||||||
);
|
);
|
||||||
format!("▎+{elapsed:>}")
|
format!("▎+{elapsed:>}")
|
||||||
|
|
@ -1043,21 +1069,21 @@ pub fn arranger_content_vertical (
|
||||||
// beats until switchover
|
// beats until switchover
|
||||||
let until_next = track.next_phrase().as_ref().map(|(t, _)|{
|
let until_next = track.next_phrase().as_ref().map(|(t, _)|{
|
||||||
let target = t.pulse.get();
|
let target = t.pulse.get();
|
||||||
let current = clock.current.pulse.get();
|
let current = current.pulse.get();
|
||||||
if target > current {
|
if target > current {
|
||||||
let remaining = target - current;
|
let remaining = target - current;
|
||||||
format!("▎-{:>}", clock.timebase().format_beats_0_short(remaining))
|
format!("▎-{:>}", timebase.format_beats_0_short(remaining))
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
}
|
}
|
||||||
}).unwrap_or(String::from("▎"));
|
}).unwrap_or(String::from("▎"));
|
||||||
// name of active MIDI input
|
// name of active MIDI input
|
||||||
let input = format!("▎>{}", track.midi_inputs().get(0)
|
let input = format!("▎>{}", track.midi_ins().get(0)
|
||||||
.map(|port|port.short_name())
|
.map(|port|port.short_name())
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or("(none)".into()));
|
.unwrap_or("(none)".into()));
|
||||||
// name of active MIDI output
|
// name of active MIDI output
|
||||||
let output = format!("▎<{}", track.midi_outputs().get(0)
|
let output = format!("▎<{}", track.midi_outs().get(0)
|
||||||
.map(|port|port.short_name())
|
.map(|port|port.short_name())
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or("(none)".into()));
|
.unwrap_or("(none)".into()));
|
||||||
|
|
@ -1437,22 +1463,105 @@ impl ArrangerTrackApi for ArrangerTrack {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasMidiBuffer for ArrangerTrack {
|
impl HasMidiBuffer for ArrangerTrack {
|
||||||
|
fn midi_buffer (&self) -> &Vec<Vec<Vec<u8>>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn midi_buffer_mut (&self) -> &mut Vec<Vec<Vec<u8>>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn reset (&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn reset_mut (&mut self) -> &mut bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasPhrase for ArrangerTrack {
|
impl HasPhrase for ArrangerTrack {
|
||||||
|
fn phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn phrase_mut (&self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn next_phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MidiInputApi for ArrangerTrack {
|
impl MidiInputApi for ArrangerTrack {
|
||||||
|
fn midi_ins(&self) -> &Vec<Port<jack::MidiIn>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn midi_ins_mut(&self) -> &mut Vec<Port<jack::MidiIn>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn recording(&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn recording_mut(&mut self) -> &mut bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn monitoring(&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn monitoring_mut(&mut self) -> &mut bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn overdub(&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn overdub_mut(&mut self) -> &mut bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn notes_in(&self) -> &Arc<RwLock<[bool; 128]>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MidiOutputApi for ArrangerTrack {
|
impl MidiOutputApi for ArrangerTrack {
|
||||||
|
fn midi_outs (&self) -> &Vec<Port<jack::MidiOut>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn midi_outs_mut (&mut self) -> &mut Vec<Port<jack::MidiOut>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn midi_note (&mut self) -> &mut Vec<u8> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClockApi for ArrangerTrack {
|
impl ClockApi for ArrangerTrack {
|
||||||
|
fn timebase (&self) -> &Arc<Timebase> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn quant (&self) -> &Quantize {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn sync (&self) -> &LaunchSync {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayheadApi for ArrangerTrack {
|
impl PlayheadApi for ArrangerTrack {
|
||||||
|
fn current(&self) -> &Instant {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn transport(&self) -> &Transport {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn playing(&self) -> &RwLock<Option<TransportState>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn started(&self) -> &RwLock<Option<(usize, usize)>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayerApi for ArrangerTrack {
|
impl PlayerApi for ArrangerTrack {}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use crate::*;
|
||||||
pub struct PhrasePoolView<E: Engine> {
|
pub struct PhrasePoolView<E: Engine> {
|
||||||
_engine: PhantomData<E>,
|
_engine: PhantomData<E>,
|
||||||
/// Collection of phrases
|
/// Collection of phrases
|
||||||
pub model: PhrasePoolModel,
|
pub phrases: Vec<Arc<RwLock<Phrase>>>,
|
||||||
/// Selected phrase
|
/// Selected phrase
|
||||||
pub phrase: usize,
|
pub phrase: usize,
|
||||||
/// Scroll offset
|
/// Scroll offset
|
||||||
|
|
@ -25,7 +25,7 @@ pub enum PhrasePoolMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> PhrasePoolView<E> {
|
impl<E: Engine> PhrasePoolView<E> {
|
||||||
pub fn new (model: PhrasePoolModel) -> Self {
|
pub fn new (phrases: Vec<Arc<RwLock<Phrase>>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
_engine: Default::default(),
|
_engine: Default::default(),
|
||||||
scroll: 0,
|
scroll: 0,
|
||||||
|
|
@ -33,14 +33,14 @@ impl<E: Engine> PhrasePoolView<E> {
|
||||||
mode: None,
|
mode: None,
|
||||||
focused: false,
|
focused: false,
|
||||||
entered: false,
|
entered: false,
|
||||||
model,
|
phrases,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn len (&self) -> usize {
|
pub fn len (&self) -> usize {
|
||||||
self.model.phrases.len()
|
self.phrases.len()
|
||||||
}
|
}
|
||||||
pub fn phrase (&self) -> &Arc<RwLock<Phrase>> {
|
pub fn phrase (&self) -> &Arc<RwLock<Phrase>> {
|
||||||
&self.model.phrases[self.phrase]
|
&self.phrases[self.phrase]
|
||||||
}
|
}
|
||||||
pub fn index_before (&self, index: usize) -> usize {
|
pub fn index_before (&self, index: usize) -> usize {
|
||||||
index.overflowing_sub(1).0.min(self.len() - 1)
|
index.overflowing_sub(1).0.min(self.len() - 1)
|
||||||
|
|
@ -49,8 +49,8 @@ impl<E: Engine> PhrasePoolView<E> {
|
||||||
(index + 1) % self.len()
|
(index + 1) % self.len()
|
||||||
}
|
}
|
||||||
pub fn index_of (&self, phrase: &Phrase) -> Option<usize> {
|
pub fn index_of (&self, phrase: &Phrase) -> Option<usize> {
|
||||||
for i in 0..self.model.phrases.len() {
|
for i in 0..self.phrases.len() {
|
||||||
if *self.model.phrases[i].read().unwrap() == *phrase { return Some(i) }
|
if *self.phrases[i].read().unwrap() == *phrase { return Some(i) }
|
||||||
}
|
}
|
||||||
return None
|
return None
|
||||||
}
|
}
|
||||||
|
|
@ -61,33 +61,33 @@ impl<E: Engine> PhrasePoolView<E> {
|
||||||
}
|
}
|
||||||
pub fn delete_selected (&mut self) {
|
pub fn delete_selected (&mut self) {
|
||||||
if self.phrase > 0 {
|
if self.phrase > 0 {
|
||||||
self.model.phrases.remove(self.phrase);
|
self.phrases.remove(self.phrase);
|
||||||
self.phrase = self.phrase.min(self.model.phrases.len().saturating_sub(1));
|
self.phrase = self.phrase.min(self.phrases.len().saturating_sub(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn append_new (&mut self, name: Option<&str>, color: Option<ItemColorTriplet>) {
|
pub fn append_new (&mut self, name: Option<&str>, color: Option<ItemColorTriplet>) {
|
||||||
self.model.phrases.push(Self::new_phrase(name, color));
|
self.phrases.push(Self::new_phrase(name, color));
|
||||||
self.phrase = self.model.phrases.len() - 1;
|
self.phrase = self.phrases.len() - 1;
|
||||||
}
|
}
|
||||||
pub fn insert_new (&mut self, name: Option<&str>, color: Option<ItemColorTriplet>) {
|
pub fn insert_new (&mut self, name: Option<&str>, color: Option<ItemColorTriplet>) {
|
||||||
self.model.phrases.insert(self.phrase + 1, Self::new_phrase(name, color));
|
self.phrases.insert(self.phrase + 1, Self::new_phrase(name, color));
|
||||||
self.phrase += 1;
|
self.phrase += 1;
|
||||||
}
|
}
|
||||||
pub fn insert_dup (&mut self) {
|
pub fn insert_dup (&mut self) {
|
||||||
let mut phrase = self.model.phrases[self.phrase].read().unwrap().duplicate();
|
let mut phrase = self.phrases[self.phrase].read().unwrap().duplicate();
|
||||||
phrase.color = ItemColorTriplet::random_near(phrase.color, 0.25);
|
phrase.color = ItemColorTriplet::random_near(phrase.color, 0.25);
|
||||||
self.model.phrases.insert(self.phrase + 1, Arc::new(RwLock::new(phrase)));
|
self.phrases.insert(self.phrase + 1, Arc::new(RwLock::new(phrase)));
|
||||||
self.phrase += 1;
|
self.phrase += 1;
|
||||||
}
|
}
|
||||||
pub fn move_up (&mut self) {
|
pub fn move_up (&mut self) {
|
||||||
if self.phrase > 1 {
|
if self.phrase > 1 {
|
||||||
self.model.phrases.swap(self.phrase - 1, self.phrase);
|
self.phrases.swap(self.phrase - 1, self.phrase);
|
||||||
self.phrase -= 1;
|
self.phrase -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn move_down (&mut self) {
|
pub fn move_down (&mut self) {
|
||||||
if self.phrase < self.model.phrases.len().saturating_sub(1) {
|
if self.phrase < self.phrases.len().saturating_sub(1) {
|
||||||
self.model.phrases.swap(self.phrase + 1, self.phrase);
|
self.phrases.swap(self.phrase + 1, self.phrase);
|
||||||
self.phrase += 1;
|
self.phrase += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -97,9 +97,9 @@ impl<E: Engine> PhrasePoolView<E> {
|
||||||
impl Content for PhrasePoolView<Tui> {
|
impl Content for PhrasePoolView<Tui> {
|
||||||
type Engine = Tui;
|
type Engine = Tui;
|
||||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||||
let Self { focused, model, mode, .. } = self;
|
let Self { focused, phrases, mode, .. } = self;
|
||||||
let content = col!(
|
let content = col!(
|
||||||
(i, phrase) in model.phrases.iter().enumerate() => Layers::new(|add|{
|
(i, phrase) in phrases.iter().enumerate() => Layers::new(|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(PhrasePoolMode::Length(phrase, new_length, focus)) = mode {
|
if let Some(PhrasePoolMode::Length(phrase, new_length, focus)) = mode {
|
||||||
|
|
@ -124,7 +124,7 @@ impl Content for PhrasePoolView<Tui> {
|
||||||
let content = content.fill_xy().bg(Color::Rgb(28, 35, 25)).border(border);
|
let content = content.fill_xy().bg(Color::Rgb(28, 35, 25)).border(border);
|
||||||
let title_color = if *focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)};
|
let title_color = if *focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)};
|
||||||
let upper_left = format!("[{}] Phrases", if self.entered {"■"} else {" "});
|
let upper_left = format!("[{}] Phrases", if self.entered {"■"} else {" "});
|
||||||
let upper_right = format!("({})", model.phrases.len());
|
let upper_right = format!("({})", phrases.len());
|
||||||
lay!(
|
lay!(
|
||||||
content,
|
content,
|
||||||
TuiStyle::fg(upper_left.to_string(), title_color).push_x(1).align_nw().fill_xy(),
|
TuiStyle::fg(upper_left.to_string(), title_color).push_x(1).align_nw().fill_xy(),
|
||||||
|
|
@ -187,13 +187,13 @@ impl<E: Engine> Command<PhrasePoolView<E>> for PhrasePoolViewCommand {
|
||||||
view.phrase = phrase
|
view.phrase = phrase
|
||||||
},
|
},
|
||||||
Self::Edit(command) => {
|
Self::Edit(command) => {
|
||||||
return Ok(command.execute(&mut view.model)?.map(Self::Edit))
|
return Ok(command.execute(&mut view)?.map(Self::Edit))
|
||||||
}
|
}
|
||||||
Self::Rename(command) => match command {
|
Self::Rename(command) => match command {
|
||||||
Rename::Begin => {
|
Rename::Begin => {
|
||||||
view.mode = Some(PhrasePoolMode::Rename(
|
view.mode = Some(PhrasePoolMode::Rename(
|
||||||
view.phrase,
|
view.phrase,
|
||||||
view.model.phrases[view.phrase].read().unwrap().name.clone()
|
view.phrases[view.phrase].read().unwrap().name.clone()
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
|
|
@ -204,7 +204,7 @@ impl<E: Engine> Command<PhrasePoolView<E>> for PhrasePoolViewCommand {
|
||||||
Length::Begin => {
|
Length::Begin => {
|
||||||
view.mode = Some(PhrasePoolMode::Length(
|
view.mode = Some(PhrasePoolMode::Length(
|
||||||
view.phrase,
|
view.phrase,
|
||||||
view.model.phrases[view.phrase].read().unwrap().length,
|
view.phrases[view.phrase].read().unwrap().length,
|
||||||
PhraseLengthFocus::Bar
|
PhraseLengthFocus::Bar
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
|
|
@ -216,3 +216,244 @@ impl<E: Engine> Command<PhrasePoolView<E>> for PhrasePoolViewCommand {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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.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.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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum PhraseRenameCommand {
|
||||||
|
Begin,
|
||||||
|
Set(String),
|
||||||
|
Confirm,
|
||||||
|
Cancel,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputToCommand<Tui, PhrasePoolView<Tui>> for PhraseRenameCommand {
|
||||||
|
fn input_to_command (view: &PhrasePoolView<Tui>, from: &TuiInput) -> Option<Self> {
|
||||||
|
if let Some(PhrasePoolMode::Rename(_, ref old_name)) = view.mode {
|
||||||
|
Some(match from.event() {
|
||||||
|
key!(KeyCode::Char(c)) => {
|
||||||
|
let mut new_name = old_name.clone();
|
||||||
|
new_name.push(*c);
|
||||||
|
Self::Set(new_name)
|
||||||
|
},
|
||||||
|
key!(KeyCode::Backspace) => {
|
||||||
|
let mut new_name = old_name.clone();
|
||||||
|
new_name.pop();
|
||||||
|
Self::Set(new_name)
|
||||||
|
},
|
||||||
|
key!(KeyCode::Enter) => Self::Confirm,
|
||||||
|
key!(KeyCode::Esc) => Self::Cancel,
|
||||||
|
_ => return None
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine> Command<PhrasePoolView<E>> for PhraseRenameCommand {
|
||||||
|
fn execute (self, view: &mut PhrasePoolView<E>) -> Perhaps<Self> {
|
||||||
|
use PhraseRenameCommand::*;
|
||||||
|
if let Some(PhrasePoolMode::Rename(phrase, ref mut old_name)) = view.mode {
|
||||||
|
match self {
|
||||||
|
Set(s) => {
|
||||||
|
view.phrases[phrase].write().unwrap().name = s.into();
|
||||||
|
return Ok(Some(Self::Set(old_name.clone())))
|
||||||
|
},
|
||||||
|
Confirm => {
|
||||||
|
let old_name = old_name.clone();
|
||||||
|
view.mode = None;
|
||||||
|
return Ok(Some(Self::Set(old_name)))
|
||||||
|
},
|
||||||
|
Cancel => {
|
||||||
|
let mut phrase = view.phrases[phrase].write().unwrap();
|
||||||
|
phrase.name = old_name.clone();
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
};
|
||||||
|
Ok(None)
|
||||||
|
} else if self == Begin {
|
||||||
|
view.mode = Some(PhrasePoolMode::Rename(
|
||||||
|
view.phrase,
|
||||||
|
view.phrases[view.phrase].read().unwrap().name.clone()
|
||||||
|
));
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,177 +0,0 @@
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum PhraseRenameCommand {
|
|
||||||
Begin,
|
|
||||||
Set(String),
|
|
||||||
Confirm,
|
|
||||||
Cancel,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InputToCommand<Tui, PhrasePoolView<Tui>> for PhraseRenameCommand {
|
|
||||||
fn input_to_command (view: &PhrasePoolView<Tui>, from: &TuiInput) -> Option<Self> {
|
|
||||||
if let Some(PhrasePoolMode::Rename(_, ref old_name)) = view.mode {
|
|
||||||
Some(match from.event() {
|
|
||||||
key!(KeyCode::Char(c)) => {
|
|
||||||
let mut new_name = old_name.clone();
|
|
||||||
new_name.push(*c);
|
|
||||||
Self::Set(new_name)
|
|
||||||
},
|
|
||||||
key!(KeyCode::Backspace) => {
|
|
||||||
let mut new_name = old_name.clone();
|
|
||||||
new_name.pop();
|
|
||||||
Self::Set(new_name)
|
|
||||||
},
|
|
||||||
key!(KeyCode::Enter) => Self::Confirm,
|
|
||||||
key!(KeyCode::Esc) => Self::Cancel,
|
|
||||||
_ => return None
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Engine> Command<PhrasePoolView<E>> for PhraseRenameCommand {
|
|
||||||
fn execute (self, view: &mut PhrasePoolView<E>) -> Perhaps<Self> {
|
|
||||||
use PhraseRenameCommand::*;
|
|
||||||
if let Some(PhrasePoolMode::Rename(phrase, ref mut old_name)) = view.mode {
|
|
||||||
match self {
|
|
||||||
Set(s) => {
|
|
||||||
view.model.phrases[phrase].write().unwrap().name = s.into();
|
|
||||||
return Ok(Some(Self::Set(old_name.clone())))
|
|
||||||
},
|
|
||||||
Confirm => {
|
|
||||||
let old_name = old_name.clone();
|
|
||||||
view.mode = None;
|
|
||||||
return Ok(Some(Self::Set(old_name)))
|
|
||||||
},
|
|
||||||
Cancel => {
|
|
||||||
let mut phrase = view.model.phrases[phrase].write().unwrap();
|
|
||||||
phrase.name = old_name.clone();
|
|
||||||
},
|
|
||||||
_ => unreachable!()
|
|
||||||
};
|
|
||||||
Ok(None)
|
|
||||||
} else if self == Begin {
|
|
||||||
view.mode = Some(PhrasePoolMode::Rename(
|
|
||||||
view.phrase,
|
|
||||||
view.model.phrases[view.phrase].read().unwrap().name.clone()
|
|
||||||
));
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -9,7 +9,6 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerApp<Tui> {
|
||||||
metronome: false,
|
metronome: false,
|
||||||
transport: jack.read().unwrap().transport(),
|
transport: jack.read().unwrap().transport(),
|
||||||
jack: jack.clone(),
|
jack: jack.clone(),
|
||||||
clock: Arc::new(Clock::from(Instant::default())),
|
|
||||||
focused: false,
|
focused: false,
|
||||||
focus: TransportViewFocus::PlayPause,
|
focus: TransportViewFocus::PlayPause,
|
||||||
size: Measure::new(),
|
size: Measure::new(),
|
||||||
|
|
@ -246,22 +245,105 @@ impl<E: Engine> HasPhrases for SequencerView<E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> HasMidiBuffer for SequencerView<E> {
|
impl<E: Engine> HasMidiBuffer for SequencerView<E> {
|
||||||
|
fn midi_buffer (&self) -> &Vec<Vec<Vec<u8>>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn midi_buffer_mut (&self) -> &mut Vec<Vec<Vec<u8>>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn reset (&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn reset_mut (&mut self) -> &mut bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> HasPhrase for SequencerView<E> {
|
impl<E: Engine> HasPhrase for SequencerView<E> {
|
||||||
|
fn phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn phrase_mut (&self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn next_phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> MidiInputApi for SequencerView<E> {
|
impl<E: Engine> MidiInputApi for SequencerView<E> {
|
||||||
|
fn midi_ins(&self) -> &Vec<Port<jack::MidiIn>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn midi_ins_mut(&self) -> &mut Vec<Port<jack::MidiIn>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn recording(&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn recording_mut(&mut self) -> &mut bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn monitoring(&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn monitoring_mut(&mut self) -> &mut bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn overdub(&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn overdub_mut(&mut self) -> &mut bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn notes_in(&self) -> &Arc<RwLock<[bool; 128]>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> MidiOutputApi for SequencerView<E> {
|
impl<E: Engine> MidiOutputApi for SequencerView<E> {
|
||||||
|
fn midi_outs (&self) -> &Vec<Port<jack::MidiOut>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn midi_outs_mut (&mut self) -> &mut Vec<Port<jack::MidiOut>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn midi_note (&mut self) -> &mut Vec<u8> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> ClockApi for SequencerView<E> {
|
impl<E: Engine> ClockApi for SequencerView<E> {
|
||||||
|
fn timebase (&self) -> &Arc<Timebase> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn quant (&self) -> &Quantize {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn sync (&self) -> &LaunchSync {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> PlayheadApi for SequencerView<E> {
|
impl<E: Engine> PlayheadApi for SequencerView<E> {
|
||||||
|
fn current(&self) -> &Instant {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn transport(&self) -> &Transport {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn playing(&self) -> &RwLock<Option<TransportState>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn started(&self) -> &RwLock<Option<(usize, usize)>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> PlayerApi for SequencerView<E> {
|
impl<E: Engine> PlayerApi for SequencerView<E> {}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for TransportApp<Tui> {
|
||||||
metronome: false,
|
metronome: false,
|
||||||
transport: jack.read().unwrap().transport(),
|
transport: jack.read().unwrap().transport(),
|
||||||
jack: jack.clone(),
|
jack: jack.clone(),
|
||||||
clock: Arc::new(Clock::from(Instant::default())),
|
|
||||||
focused: false,
|
focused: false,
|
||||||
focus: TransportViewFocus::PlayPause,
|
focus: TransportViewFocus::PlayPause,
|
||||||
size: Measure::new(),
|
size: Measure::new(),
|
||||||
|
|
@ -57,39 +56,40 @@ impl InputToCommand<Tui, TransportApp<Tui>> for TransportCommand {
|
||||||
use KeyCode::Char;
|
use KeyCode::Char;
|
||||||
use AppViewFocus::Content;
|
use AppViewFocus::Content;
|
||||||
use ClockCommand::{SetBpm, SetQuant, SetSync};
|
use ClockCommand::{SetBpm, SetQuant, SetSync};
|
||||||
use TransportViewFocus::{Bpm, Quant, Sync, PlayPause, Clock};
|
use TransportViewFocus as Focus;
|
||||||
let clock = app.app.clock();
|
use TransportCommand::{Clock, Playhead};
|
||||||
|
let timebase = app.app.timebase();
|
||||||
Some(match input.event() {
|
Some(match input.event() {
|
||||||
key!(Char('.')) => match app.focused() {
|
key!(Char('.')) => match app.focused() {
|
||||||
Content(Bpm) => SetBpm(clock.timebase().bpm.get() + 1.0),
|
Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() + 1.0)),
|
||||||
Content(Quant) => SetQuant(next_note_length(clock.quant.get()as usize)as f64),
|
Content(Focus::Quant) => Clock(SetQuant(next_note_length(app.app.quant().get()as usize)as f64)),
|
||||||
Content(Sync) => SetSync(next_note_length(clock.sync.get()as usize)as f64+1.),
|
Content(Focus::Sync) => Clock(SetSync(next_note_length(app.app.sync().get()as usize)as f64+1.)),
|
||||||
Content(PlayPause) => {todo!()},
|
Content(Focus::PlayPause) => Playhead(todo!()),
|
||||||
Content(Clock) => {todo!()},
|
Content(Focus::Clock) => Playhead(todo!()),
|
||||||
_ => {todo!()}
|
_ => {todo!()}
|
||||||
},
|
},
|
||||||
key!(KeyCode::Char(',')) => match app.focused() {
|
key!(KeyCode::Char(',')) => match app.focused() {
|
||||||
Content(Bpm) => SetBpm(clock.timebase().bpm.get() - 1.0),
|
Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() - 1.0)),
|
||||||
Content(Quant) => SetQuant(prev_note_length(clock.quant.get()as usize)as f64),
|
Content(Focus::Quant) => Clock(SetQuant(prev_note_length(app.app.quant().get()as usize)as f64)),
|
||||||
Content(Sync) => SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.),
|
Content(Focus::Sync) => Clock(SetSync(prev_note_length(app.app.sync().get()as usize)as f64+1.)),
|
||||||
Content(PlayPause) => {todo!()},
|
Content(Focus::PlayPause) => Playhead(todo!()),
|
||||||
Content(Clock) => {todo!()}
|
Content(Focus::Clock) => Playhead(todo!()),
|
||||||
_ => {todo!()}
|
_ => {todo!()}
|
||||||
},
|
},
|
||||||
key!(KeyCode::Char('>')) => match app.focused() {
|
key!(KeyCode::Char('>')) => match app.focused() {
|
||||||
Content(Bpm) => SetBpm(clock.timebase().bpm.get() + 0.001),
|
Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() + 0.001)),
|
||||||
Content(Quant) => SetQuant(next_note_length(clock.quant.get()as usize)as f64),
|
Content(Focus::Quant) => Clock(SetQuant(next_note_length(app.app.quant().get()as usize)as f64)),
|
||||||
Content(Sync) => SetSync(next_note_length(clock.sync.get()as usize)as f64+1.),
|
Content(Focus::Sync) => Clock(SetSync(next_note_length(app.app.sync().get()as usize)as f64+1.)),
|
||||||
Content(PlayPause) => {todo!()},
|
Content(Focus::PlayPause) => Playhead(todo!()),
|
||||||
Content(Clock) => {todo!()}
|
Content(Focus::Clock) => Playhead(todo!()),
|
||||||
_ => {todo!()}
|
_ => {todo!()}
|
||||||
},
|
},
|
||||||
key!(KeyCode::Char('<')) => match app.focused() {
|
key!(KeyCode::Char('<')) => match app.focused() {
|
||||||
Content(Bpm) => SetBpm(clock.timebase().bpm.get() - 0.001),
|
Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() - 0.001)),
|
||||||
Content(Quant) => SetQuant(prev_note_length(clock.quant.get()as usize)as f64),
|
Content(Focus::Quant) => Clock(SetQuant(prev_note_length(app.app.quant().get()as usize)as f64)),
|
||||||
Content(Sync) => SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.),
|
Content(Focus::Sync) => Clock(SetSync(prev_note_length(app.app.sync().get()as usize)as f64+1.)),
|
||||||
Content(PlayPause) => {todo!()},
|
Content(Focus::PlayPause) => Playhead(todo!()),
|
||||||
Content(Clock) => {todo!()}
|
Content(Focus::Clock) => Playhead(todo!()),
|
||||||
_ => {todo!()}
|
_ => {todo!()}
|
||||||
},
|
},
|
||||||
_ => return None
|
_ => return None
|
||||||
|
|
@ -310,8 +310,8 @@ impl<E: Engine> HasJack for TransportView<E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> ClockApi for TransportView<E> {
|
impl<E: Engine> ClockApi for TransportView<E> {
|
||||||
fn current (&self) -> &Instant {
|
fn timebase (&self) -> &Arc<Timebase> {
|
||||||
&self.current
|
&self.current.timebase
|
||||||
}
|
}
|
||||||
fn quant (&self) -> &Quantize {
|
fn quant (&self) -> &Quantize {
|
||||||
&self.quant
|
&self.quant
|
||||||
|
|
@ -322,6 +322,18 @@ impl<E: Engine> ClockApi for TransportView<E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> PlayheadApi for TransportView<E> {
|
impl<E: Engine> PlayheadApi for TransportView<E> {
|
||||||
|
fn current (&self) -> &Instant {
|
||||||
|
&self.current
|
||||||
|
}
|
||||||
|
fn transport (&self) -> &jack::Transport {
|
||||||
|
&self.transport
|
||||||
|
}
|
||||||
|
fn playing (&self) -> &RwLock<Option<TransportState>> {
|
||||||
|
&self.playing
|
||||||
|
}
|
||||||
|
fn started (&self) -> &RwLock<Option<(usize, usize)>> {
|
||||||
|
&self.started
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> std::fmt::Debug for TransportView<E> {
|
impl<E: Engine> std::fmt::Debug for TransportView<E> {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue