mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
wip: p.57, e=81
This commit is contained in:
parent
0964ad3be4
commit
0c94c2af8f
11 changed files with 672 additions and 667 deletions
|
|
@ -11,7 +11,7 @@ pub enum PhrasePoolCommand {
|
||||||
Delete(usize),
|
Delete(usize),
|
||||||
Duplicate(usize),
|
Duplicate(usize),
|
||||||
Swap(usize, usize),
|
Swap(usize, usize),
|
||||||
RandomColor(usize),
|
Color(usize, ItemColor),
|
||||||
Import(usize, String),
|
Import(usize, String),
|
||||||
Export(usize, String),
|
Export(usize, String),
|
||||||
SetName(usize, String),
|
SetName(usize, String),
|
||||||
|
|
@ -38,10 +38,9 @@ impl<T: HasPhrases> Command<T> for PhrasePoolCommand {
|
||||||
//view.phrase += 1;
|
//view.phrase += 1;
|
||||||
},
|
},
|
||||||
Self::Swap(index, other) => {
|
Self::Swap(index, other) => {
|
||||||
//Self::MoveUp => { view.move_up() },
|
model.phrases_mut().swap(index, other);
|
||||||
//Self::MoveDown => { view.move_down() },
|
|
||||||
},
|
},
|
||||||
Self::RandomColor(index) => {
|
Self::Color(index, color) => {
|
||||||
//view.phrase().write().unwrap().color = ItemColorTriplet::random();
|
//view.phrase().write().unwrap().color = ItemColorTriplet::random();
|
||||||
},
|
},
|
||||||
Self::Import(index, path) => {
|
Self::Import(index, path) => {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,12 @@ pub trait HasScenes<S: ArrangerSceneApi> {
|
||||||
fn scene_default_name (&self) -> String {
|
fn scene_default_name (&self) -> String {
|
||||||
format!("Scene {}", self.scenes().len() + 1)
|
format!("Scene {}", self.scenes().len() + 1)
|
||||||
}
|
}
|
||||||
|
fn selected_scene (&self) -> Option<&S> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn selected_scene_mut (&mut self) -> Option<&mut S> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -86,6 +92,7 @@ pub trait ArrangerSceneApi: Sized {
|
||||||
fn clip (&self, index: usize) -> Option<&Arc<RwLock<Phrase>>> {
|
fn clip (&self, index: usize) -> Option<&Arc<RwLock<Phrase>>> {
|
||||||
match self.clips().get(index) { Some(Some(clip)) => Some(clip), _ => None }
|
match self.clips().get(index) { Some(Some(clip)) => Some(clip), _ => None }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//impl ArrangerScene {
|
//impl ArrangerScene {
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@ use rand::{thread_rng, distributions::uniform::UniformSampler};
|
||||||
pub use palette::{*, convert::*, okhsl::*};
|
pub use palette::{*, convert::*, okhsl::*};
|
||||||
|
|
||||||
/// A color in OKHSL and RGB representations.
|
/// A color in OKHSL and RGB representations.
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||||
pub struct ItemColor {
|
pub struct ItemColor {
|
||||||
pub okhsl: Okhsl<f32>,
|
pub okhsl: Okhsl<f32>,
|
||||||
pub rgb: Color,
|
pub rgb: Color,
|
||||||
}
|
}
|
||||||
/// A color in OKHSL and RGB with lighter and darker variants.
|
/// A color in OKHSL and RGB with lighter and darker variants.
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||||
pub struct ItemColorTriplet {
|
pub struct ItemColorTriplet {
|
||||||
pub base: ItemColor,
|
pub base: ItemColor,
|
||||||
pub light: ItemColor,
|
pub light: ItemColor,
|
||||||
|
|
|
||||||
|
|
@ -29,38 +29,3 @@ submod! {
|
||||||
tui_view
|
tui_view
|
||||||
tui_widget
|
tui_widget
|
||||||
}
|
}
|
||||||
|
|
||||||
fn content_with_menu_and_status <'a, A, S, C> (
|
|
||||||
content: &'a A,
|
|
||||||
menu_bar: &'a Option<MenuBar<Tui, S, C>>,
|
|
||||||
status_bar: &'a Option<impl StatusBar>
|
|
||||||
) -> impl Widget<Engine = Tui> + 'a
|
|
||||||
where
|
|
||||||
A: Widget<Engine = Tui>,
|
|
||||||
S: Send + Sync + 'a,
|
|
||||||
C: Command<S>
|
|
||||||
{
|
|
||||||
let menus = menu_bar.as_ref().map_or_else(
|
|
||||||
||&[] as &[Menu<_, _, _>],
|
|
||||||
|m|m.menus.as_slice()
|
|
||||||
);
|
|
||||||
Either(
|
|
||||||
menu_bar.is_none(),
|
|
||||||
Either(
|
|
||||||
status_bar.is_none(),
|
|
||||||
widget(content),
|
|
||||||
Split::up(
|
|
||||||
1,
|
|
||||||
widget(status_bar.as_ref().unwrap()),
|
|
||||||
widget(content)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Split::down(
|
|
||||||
1,
|
|
||||||
row!(menu in menus.iter() => {
|
|
||||||
row!(" ", menu.title.as_str(), " ")
|
|
||||||
}),
|
|
||||||
widget(content)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,323 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
impl HasPhrases for ArrangerTui {
|
|
||||||
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
|
||||||
&self.phrases
|
|
||||||
}
|
|
||||||
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
|
|
||||||
&mut self.phrases
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasPhrases for SequencerTui {
|
|
||||||
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
|
||||||
&self.phrases
|
|
||||||
}
|
|
||||||
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
|
|
||||||
&mut self.phrases
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasPhrases for PhrasesTui {
|
|
||||||
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
|
||||||
&self.phrases
|
|
||||||
}
|
|
||||||
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
|
|
||||||
&mut self.phrases
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasPhrase for SequencerTui {
|
|
||||||
fn reset (&self) -> bool {
|
|
||||||
self.reset
|
|
||||||
}
|
|
||||||
fn reset_mut (&mut self) -> &mut bool {
|
|
||||||
&mut self.reset
|
|
||||||
}
|
|
||||||
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 ArrangerSceneApi for ArrangerScene {
|
|
||||||
fn name (&self) -> &Arc<RwLock<String>> {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
fn clips (&self) -> &Vec<Option<Arc<RwLock<Phrase>>>> {
|
|
||||||
&self.clips
|
|
||||||
}
|
|
||||||
fn color (&self) -> ItemColor {
|
|
||||||
self.color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasScenes<ArrangerScene> for ArrangerTui {
|
|
||||||
fn scenes (&self) -> &Vec<ArrangerScene> {
|
|
||||||
&self.scenes
|
|
||||||
}
|
|
||||||
fn scenes_mut (&mut self) -> &mut Vec<ArrangerScene> {
|
|
||||||
&mut self.scenes
|
|
||||||
}
|
|
||||||
fn scene_add (&mut self, name: Option<&str>, color: Option<ItemColor>)
|
|
||||||
-> Usually<&mut ArrangerScene>
|
|
||||||
{
|
|
||||||
let name = name.map_or_else(||self.scene_default_name(), |x|x.to_string());
|
|
||||||
let scene = ArrangerScene {
|
|
||||||
name: Arc::new(name.into()),
|
|
||||||
clips: vec![None;self.tracks().len()],
|
|
||||||
color: color.unwrap_or_else(||ItemColor::random()),
|
|
||||||
};
|
|
||||||
self.scenes_mut().push(scene);
|
|
||||||
let index = self.scenes().len() - 1;
|
|
||||||
Ok(&mut self.scenes_mut()[index])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasTracks<ArrangerTrack> for ArrangerTui {
|
|
||||||
fn tracks (&self) -> &Vec<ArrangerTrack> {
|
|
||||||
&self.tracks
|
|
||||||
}
|
|
||||||
fn tracks_mut (&mut self) -> &mut Vec<ArrangerTrack> {
|
|
||||||
&mut self.tracks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArrangerTracksApi<ArrangerTrack> for ArrangerTui {
|
|
||||||
fn track_add (&mut self, name: Option<&str>, color: Option<ItemColor>)
|
|
||||||
-> Usually<&mut ArrangerTrack>
|
|
||||||
{
|
|
||||||
let name = name.map_or_else(||self.track_default_name(), |x|x.to_string());
|
|
||||||
let track = ArrangerTrack {
|
|
||||||
width: name.len() + 2,
|
|
||||||
name: Arc::new(name.into()),
|
|
||||||
color: color.unwrap_or_else(||ItemColor::random()),
|
|
||||||
midi_ins: vec![],
|
|
||||||
midi_outs: vec![],
|
|
||||||
reset: true,
|
|
||||||
recording: false,
|
|
||||||
monitoring: false,
|
|
||||||
overdub: false,
|
|
||||||
play_phrase: None,
|
|
||||||
next_phrase: None,
|
|
||||||
notes_in: RwLock::new([false;128]).into(),
|
|
||||||
notes_out: RwLock::new([false;128]).into(),
|
|
||||||
};
|
|
||||||
self.tracks_mut().push(track);
|
|
||||||
let index = self.tracks().len() - 1;
|
|
||||||
Ok(&mut self.tracks_mut()[index])
|
|
||||||
}
|
|
||||||
fn track_del (&mut self, index: usize) {
|
|
||||||
self.tracks_mut().remove(index);
|
|
||||||
for scene in self.scenes_mut().iter_mut() {
|
|
||||||
scene.clips.remove(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArrangerTrackApi for ArrangerTrack {
|
|
||||||
/// Name of track
|
|
||||||
fn name (&self) -> &Arc<RwLock<String>> {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
/// Preferred width of track column
|
|
||||||
fn width (&self) -> usize {
|
|
||||||
self.width
|
|
||||||
}
|
|
||||||
/// Preferred width of track column
|
|
||||||
fn width_mut (&mut self) -> &mut usize {
|
|
||||||
&mut self.width
|
|
||||||
}
|
|
||||||
/// Identifying color of track
|
|
||||||
fn color (&self) -> ItemColor {
|
|
||||||
self.color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasPhrase for ArrangerTrack {
|
|
||||||
fn reset (&self) -> bool {
|
|
||||||
self.reset
|
|
||||||
}
|
|
||||||
fn reset_mut (&mut self) -> &mut bool {
|
|
||||||
&mut self.reset
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
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 {
|
|
||||||
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 {
|
|
||||||
fn timebase (&self) -> &Arc<Timebase> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn quant (&self) -> &Quantize {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn sync (&self) -> &LaunchSync {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {}
|
|
||||||
|
|
||||||
|
|
||||||
/// General methods for arranger
|
|
||||||
impl ArrangerTui {
|
|
||||||
pub fn selected_scene (&self) -> Option<&ArrangerScene> {
|
|
||||||
self.selected.scene().map(|s|self.scenes().get(s)).flatten()
|
|
||||||
}
|
|
||||||
pub fn selected_scene_mut (&mut self) -> Option<&mut ArrangerScene> {
|
|
||||||
self.selected.scene().map(|s|self.scenes_mut().get_mut(s)).flatten()
|
|
||||||
}
|
|
||||||
pub fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>> {
|
|
||||||
self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Focus the editor with the current phrase
|
|
||||||
pub fn show_phrase (&mut self) {
|
|
||||||
self.editor.show(self.selected_phrase().as_ref());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn activate (&mut self) {
|
|
||||||
let selected = self.selected;
|
|
||||||
match selected {
|
|
||||||
ArrangerSelection::Scene(s) => {
|
|
||||||
for (t, track) in self.tracks_mut().iter_mut().enumerate() {
|
|
||||||
let clip = self.scenes()[s].clips[t].as_ref();
|
|
||||||
if track.play_phrase.is_some() || clip.is_some() {
|
|
||||||
track.enqueue_next(clip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO make transport available here, so that
|
|
||||||
// activating a scene when stopped starts playback
|
|
||||||
//if self.is_stopped() {
|
|
||||||
//self.transport.toggle_play()
|
|
||||||
//}
|
|
||||||
},
|
|
||||||
ArrangerSelection::Clip(t, s) => {
|
|
||||||
self.tracks_mut()[t].enqueue_next(self.scenes()[s].clips[t].as_ref());
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_first_row (&self) -> bool {
|
|
||||||
let selected = self.selected;
|
|
||||||
selected.is_mix() || selected.is_track()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_last_row (&self) -> bool {
|
|
||||||
let selected = self.selected;
|
|
||||||
(self.scenes().len() == 0 && (selected.is_mix() || selected.is_track())) || match selected {
|
|
||||||
ArrangerSelection::Scene(s) => s == self.scenes().len() - 1,
|
|
||||||
ArrangerSelection::Clip(_, s) => s == self.scenes().len() - 1,
|
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toggle_loop (&mut self) {
|
|
||||||
if let Some(phrase) = self.selected_phrase() {
|
|
||||||
phrase.write().unwrap().toggle_loop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn randomize_color (&mut self) {
|
|
||||||
match self.selected {
|
|
||||||
ArrangerSelection::Mix => {
|
|
||||||
self.color = ItemColor::random_dark()
|
|
||||||
},
|
|
||||||
ArrangerSelection::Track(t) => {
|
|
||||||
self.tracks_mut()[t].color = ItemColor::random()
|
|
||||||
},
|
|
||||||
ArrangerSelection::Scene(s) => {
|
|
||||||
self.scenes_mut()[s].color = ItemColor::random()
|
|
||||||
},
|
|
||||||
ArrangerSelection::Clip(t, s) => {
|
|
||||||
if let Some(phrase) = &self.scenes_mut()[s].clips[t] {
|
|
||||||
phrase.write().unwrap().color = ItemColorTriplet::random();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PhrasesTui {
|
impl PhrasesTui {
|
||||||
pub fn new (phrases: Vec<Arc<RwLock<Phrase>>>) -> Self {
|
pub fn new (phrases: Vec<Arc<RwLock<Phrase>>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -329,71 +11,37 @@ impl PhrasesTui {
|
||||||
phrases,
|
phrases,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn len (&self) -> usize {
|
|
||||||
self.phrases.len()
|
|
||||||
}
|
}
|
||||||
pub fn phrase (&self) -> &Arc<RwLock<Phrase>> {
|
impl PhraseTui {
|
||||||
&self.phrases[self.phrase]
|
pub fn new () -> Self {
|
||||||
}
|
Self {
|
||||||
pub fn index_before (&self, index: usize) -> usize {
|
phrase: None,
|
||||||
index.overflowing_sub(1).0.min(self.len() - 1)
|
note_len: 24,
|
||||||
}
|
notes_in: Arc::new(RwLock::new([false;128])),
|
||||||
pub fn index_after (&self, index: usize) -> usize {
|
notes_out: Arc::new(RwLock::new([false;128])),
|
||||||
(index + 1) % self.len()
|
keys: keys_vert(),
|
||||||
}
|
buffer: Default::default(),
|
||||||
pub fn index_of (&self, phrase: &Phrase) -> Option<usize> {
|
focused: false,
|
||||||
for i in 0..self.phrases.len() {
|
entered: false,
|
||||||
if *self.phrases[i].read().unwrap() == *phrase { return Some(i) }
|
mode: false,
|
||||||
}
|
now: Arc::new(0.into()),
|
||||||
return None
|
width: 0.into(),
|
||||||
}
|
height: 0.into(),
|
||||||
fn new_phrase (name: Option<&str>, color: Option<ItemColorTriplet>) -> Arc<RwLock<Phrase>> {
|
note_axis: RwLock::new(FixedAxis {
|
||||||
Arc::new(RwLock::new(Phrase::new(
|
start: 12,
|
||||||
String::from(name.unwrap_or("(new)")), true, 4 * PPQ, None, color
|
point: Some(36),
|
||||||
)))
|
clamp: Some(127)
|
||||||
}
|
}),
|
||||||
pub fn delete_selected (&mut self) {
|
time_axis: RwLock::new(ScaledAxis {
|
||||||
if self.phrase > 0 {
|
start: 00,
|
||||||
self.phrases.remove(self.phrase);
|
point: Some(00),
|
||||||
self.phrase = self.phrase.min(self.phrases.len().saturating_sub(1));
|
clamp: Some(000),
|
||||||
}
|
scale: 24
|
||||||
}
|
}),
|
||||||
pub fn append_new (&mut self, name: Option<&str>, color: Option<ItemColorTriplet>) {
|
|
||||||
self.phrases.push(Self::new_phrase(name, color));
|
|
||||||
self.phrase = self.phrases.len() - 1;
|
|
||||||
}
|
|
||||||
pub fn insert_new (&mut self, name: Option<&str>, color: Option<ItemColorTriplet>) {
|
|
||||||
self.phrases.insert(self.phrase + 1, Self::new_phrase(name, color));
|
|
||||||
self.phrase += 1;
|
|
||||||
}
|
|
||||||
pub fn insert_dup (&mut self) {
|
|
||||||
let mut phrase = self.phrases[self.phrase].read().unwrap().duplicate();
|
|
||||||
phrase.color = ItemColorTriplet::random_near(phrase.color, 0.25);
|
|
||||||
self.phrases.insert(self.phrase + 1, Arc::new(RwLock::new(phrase)));
|
|
||||||
self.phrase += 1;
|
|
||||||
}
|
|
||||||
pub fn move_up (&mut self) {
|
|
||||||
if self.phrase > 1 {
|
|
||||||
self.phrases.swap(self.phrase - 1, self.phrase);
|
|
||||||
self.phrase -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn move_down (&mut self) {
|
|
||||||
if self.phrase < self.phrases.len().saturating_sub(1) {
|
|
||||||
self.phrases.swap(self.phrase + 1, self.phrase);
|
|
||||||
self.phrase += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//pub fn track_next (&mut self, last_track: usize) {
|
//pub fn track_next (&mut self, last_track: usize) {
|
||||||
//use ArrangerSelection::*;
|
//use ArrangerSelection::*;
|
||||||
//*self = match self {
|
//*self = match self {
|
||||||
|
|
@ -572,142 +220,23 @@ impl PhrasesTui {
|
||||||
//self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
|
//self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
|
||||||
//}
|
//}
|
||||||
|
|
||||||
impl PhraseTui {
|
|
||||||
pub fn new () -> Self {
|
|
||||||
Self {
|
|
||||||
phrase: None,
|
|
||||||
note_len: 24,
|
|
||||||
notes_in: Arc::new(RwLock::new([false;128])),
|
|
||||||
notes_out: Arc::new(RwLock::new([false;128])),
|
|
||||||
keys: keys_vert(),
|
|
||||||
buffer: Default::default(),
|
|
||||||
focused: false,
|
|
||||||
entered: false,
|
|
||||||
mode: false,
|
|
||||||
now: Arc::new(0.into()),
|
|
||||||
width: 0.into(),
|
|
||||||
height: 0.into(),
|
|
||||||
note_axis: RwLock::new(FixedAxis {
|
|
||||||
start: 12,
|
|
||||||
point: Some(36),
|
|
||||||
clamp: Some(127)
|
|
||||||
}),
|
|
||||||
time_axis: RwLock::new(ScaledAxis {
|
|
||||||
start: 00,
|
|
||||||
point: Some(00),
|
|
||||||
clamp: Some(000),
|
|
||||||
scale: 24
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn time_cursor_advance (&self) {
|
//pub fn is_first_row (&self) -> bool {
|
||||||
let point = self.time_axis.read().unwrap().point;
|
//let selected = self.selected;
|
||||||
let length = self.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
|
//selected.is_mix() || selected.is_track()
|
||||||
let forward = |time|(time + self.note_len) % length;
|
//}
|
||||||
self.time_axis.write().unwrap().point = point.map(forward);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn put (&mut self) {
|
//pub fn is_last_row (&self) -> bool {
|
||||||
if let (Some(phrase), Some(time), Some(note)) = (
|
//let selected = self.selected;
|
||||||
&self.phrase,
|
//(self.scenes().len() == 0 && (selected.is_mix() || selected.is_track())) || match selected {
|
||||||
self.time_axis.read().unwrap().point,
|
//ArrangerSelection::Scene(s) => s == self.scenes().len() - 1,
|
||||||
self.note_axis.read().unwrap().point,
|
//ArrangerSelection::Clip(_, s) => s == self.scenes().len() - 1,
|
||||||
) {
|
//_ => false
|
||||||
let mut phrase = phrase.write().unwrap();
|
//}
|
||||||
let key: u7 = u7::from((127 - note) as u8);
|
//}
|
||||||
let vel: u7 = 100.into();
|
//pub fn index_before (&self, index: usize) -> usize {
|
||||||
let start = time;
|
//index.overflowing_sub(1).0.min(self.len() - 1)
|
||||||
let end = (start + self.note_len) % phrase.length;
|
//}
|
||||||
phrase.notes[time].push(MidiMessage::NoteOn { key, vel });
|
//pub fn index_after (&self, index: usize) -> usize {
|
||||||
phrase.notes[end].push(MidiMessage::NoteOff { key, vel });
|
//(index + 1) % self.len()
|
||||||
self.buffer = Self::redraw(&phrase);
|
//}
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.
|
|
||||||
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
|
|
||||||
if let Some(phrase) = phrase {
|
|
||||||
self.phrase = Some(phrase.clone());
|
|
||||||
self.time_axis.write().unwrap().clamp = Some(phrase.read().unwrap().length);
|
|
||||||
self.buffer = Self::redraw(&*phrase.read().unwrap());
|
|
||||||
} else {
|
|
||||||
self.phrase = None;
|
|
||||||
self.time_axis.write().unwrap().clamp = Some(0);
|
|
||||||
self.buffer = Default::default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn redraw (phrase: &Phrase) -> BigBuffer {
|
|
||||||
let mut buf = BigBuffer::new(usize::MAX.min(phrase.length), 65);
|
|
||||||
Self::fill_seq_bg(&mut buf, phrase.length, phrase.ppq);
|
|
||||||
Self::fill_seq_fg(&mut buf, &phrase);
|
|
||||||
buf
|
|
||||||
}
|
|
||||||
fn fill_seq_bg (buf: &mut BigBuffer, length: usize, ppq: usize) {
|
|
||||||
for x in 0..buf.width {
|
|
||||||
// Only fill as far as phrase length
|
|
||||||
if x as usize >= length { break }
|
|
||||||
// Fill each row with background characters
|
|
||||||
for y in 0 .. buf.height {
|
|
||||||
buf.get_mut(x, y).map(|cell|{
|
|
||||||
cell.set_char(if ppq == 0 {
|
|
||||||
'·'
|
|
||||||
} else if x % (4 * ppq) == 0 {
|
|
||||||
'│'
|
|
||||||
} else if x % ppq == 0 {
|
|
||||||
'╎'
|
|
||||||
} else {
|
|
||||||
'·'
|
|
||||||
});
|
|
||||||
cell.set_fg(Color::Rgb(48, 64, 56));
|
|
||||||
cell.modifier = Modifier::DIM;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn fill_seq_fg (buf: &mut BigBuffer, phrase: &Phrase) {
|
|
||||||
let mut notes_on = [false;128];
|
|
||||||
for x in 0..buf.width {
|
|
||||||
if x as usize >= phrase.length {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if let Some(notes) = phrase.notes.get(x as usize) {
|
|
||||||
if phrase.percussive {
|
|
||||||
for note in notes {
|
|
||||||
match note {
|
|
||||||
MidiMessage::NoteOn { key, .. } =>
|
|
||||||
notes_on[key.as_int() as usize] = true,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for note in notes {
|
|
||||||
match note {
|
|
||||||
MidiMessage::NoteOn { key, .. } =>
|
|
||||||
notes_on[key.as_int() as usize] = true,
|
|
||||||
MidiMessage::NoteOff { key, .. } =>
|
|
||||||
notes_on[key.as_int() as usize] = false,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for y in 0..buf.height {
|
|
||||||
if y >= 64 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if let Some(block) = half_block(
|
|
||||||
notes_on[y as usize * 2],
|
|
||||||
notes_on[y as usize * 2 + 1],
|
|
||||||
) {
|
|
||||||
buf.get_mut(x, y).map(|cell|{
|
|
||||||
cell.set_char(block);
|
|
||||||
cell.set_fg(Color::White);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if phrase.percussive {
|
|
||||||
notes_on.fill(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -7,79 +7,6 @@ pub enum TransportCommand {
|
||||||
Playhead(PlayheadCommand),
|
Playhead(PlayheadCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum SequencerCommand {
|
|
||||||
Focus(FocusCommand),
|
|
||||||
Undo,
|
|
||||||
Redo,
|
|
||||||
Clear,
|
|
||||||
Clock(ClockCommand),
|
|
||||||
Playhead(PlayheadCommand),
|
|
||||||
Phrases(PhrasesCommand),
|
|
||||||
Editor(PhraseCommand),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum ArrangerCommand {
|
|
||||||
Focus(FocusCommand),
|
|
||||||
Undo,
|
|
||||||
Redo,
|
|
||||||
Clear,
|
|
||||||
Clock(ClockCommand),
|
|
||||||
Playhead(PlayheadCommand),
|
|
||||||
Scene(ArrangerSceneCommand),
|
|
||||||
Track(ArrangerTrackCommand),
|
|
||||||
Clip(ArrangerClipCommand),
|
|
||||||
Select(ArrangerSelection),
|
|
||||||
Zoom(usize),
|
|
||||||
Phrases(PhrasePoolCommand),
|
|
||||||
Editor(PhraseCommand),
|
|
||||||
EditPhrase(Option<Arc<RwLock<Phrase>>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
|
||||||
pub enum PhrasesCommand {
|
|
||||||
Select(usize),
|
|
||||||
Edit(PhrasePoolCommand),
|
|
||||||
Rename(PhraseRenameCommand),
|
|
||||||
Length(PhraseLengthCommand),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum PhraseRenameCommand {
|
|
||||||
Begin,
|
|
||||||
Set(String),
|
|
||||||
Confirm,
|
|
||||||
Cancel,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
||||||
pub enum PhraseLengthCommand {
|
|
||||||
Begin,
|
|
||||||
Next,
|
|
||||||
Prev,
|
|
||||||
Inc,
|
|
||||||
Dec,
|
|
||||||
Set(usize),
|
|
||||||
Cancel,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
|
||||||
pub enum PhraseCommand {
|
|
||||||
// TODO: 1-9 seek markers that by default start every 8th of the phrase
|
|
||||||
ToggleDirection,
|
|
||||||
EnterEditMode,
|
|
||||||
ExitEditMode,
|
|
||||||
NoteAppend,
|
|
||||||
NoteSet,
|
|
||||||
NoteCursorSet(usize),
|
|
||||||
NoteLengthSet(usize),
|
|
||||||
NoteScrollSet(usize),
|
|
||||||
TimeCursorSet(usize),
|
|
||||||
TimeScrollSet(usize),
|
|
||||||
TimeZoomSet(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: TransportControl> Command<T> for TransportCommand {
|
impl<T: TransportControl> Command<T> for TransportCommand {
|
||||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||||
use TransportCommand::{Focus, Clock, Playhead};
|
use TransportCommand::{Focus, Clock, Playhead};
|
||||||
|
|
@ -97,6 +24,18 @@ impl<T: TransportControl> Command<T> for TransportCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum SequencerCommand {
|
||||||
|
Focus(FocusCommand),
|
||||||
|
Undo,
|
||||||
|
Redo,
|
||||||
|
Clear,
|
||||||
|
Clock(ClockCommand),
|
||||||
|
Playhead(PlayheadCommand),
|
||||||
|
Phrases(PhrasesCommand),
|
||||||
|
Editor(PhraseCommand),
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> Command<T> for SequencerCommand
|
impl<T> Command<T> for SequencerCommand
|
||||||
where
|
where
|
||||||
T: FocusGrid + PhrasesControl + PhraseControl + ClockApi + PlayheadApi
|
T: FocusGrid + PhrasesControl + PhraseControl + ClockApi + PlayheadApi
|
||||||
|
|
@ -113,6 +52,24 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum ArrangerCommand {
|
||||||
|
Focus(FocusCommand),
|
||||||
|
Undo,
|
||||||
|
Redo,
|
||||||
|
Clear,
|
||||||
|
Clock(ClockCommand),
|
||||||
|
Playhead(PlayheadCommand),
|
||||||
|
Scene(ArrangerSceneCommand),
|
||||||
|
Track(ArrangerTrackCommand),
|
||||||
|
Clip(ArrangerClipCommand),
|
||||||
|
Select(ArrangerSelection),
|
||||||
|
Zoom(usize),
|
||||||
|
Phrases(PhrasePoolCommand),
|
||||||
|
Editor(PhraseCommand),
|
||||||
|
EditPhrase(Option<Arc<RwLock<Phrase>>>),
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> Command<T> for ArrangerCommand
|
impl<T> Command<T> for ArrangerCommand
|
||||||
where
|
where
|
||||||
T: FocusGrid + ArrangerControl + HasPhrases + PhraseControl + ClockApi + PlayheadApi
|
T: FocusGrid + ArrangerControl + HasPhrases + PhraseControl + ClockApi + PlayheadApi
|
||||||
|
|
@ -161,6 +118,16 @@ impl<T: ArrangerControl> Command<T> for ArrangerClipCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub enum PhrasesCommand {
|
||||||
|
Select(usize),
|
||||||
|
Phrase(PhrasePoolCommand),
|
||||||
|
Rename(PhraseRenameCommand),
|
||||||
|
Length(PhraseLengthCommand),
|
||||||
|
MoveUp,
|
||||||
|
MoveDown,
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: PhrasesControl> Command<T> for PhrasesCommand {
|
impl<T: PhrasesControl> Command<T> for PhrasesCommand {
|
||||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||||
use PhraseRenameCommand as Rename;
|
use PhraseRenameCommand as Rename;
|
||||||
|
|
@ -169,8 +136,8 @@ impl<T: PhrasesControl> Command<T> for PhrasesCommand {
|
||||||
Self::Select(phrase) => {
|
Self::Select(phrase) => {
|
||||||
state.phrase = phrase
|
state.phrase = phrase
|
||||||
},
|
},
|
||||||
Self::Edit(command) => {
|
Self::Phrase(command) => {
|
||||||
return Ok(command.execute(&mut state)?.map(Self::Edit))
|
return Ok(command.execute(&mut state)?.map(Self::Phrase))
|
||||||
}
|
}
|
||||||
Self::Rename(command) => match command {
|
Self::Rename(command) => match command {
|
||||||
Rename::Begin => self.phrases_rename_begin(),
|
Rename::Begin => self.phrases_rename_begin(),
|
||||||
|
|
@ -185,6 +152,17 @@ impl<T: PhrasesControl> Command<T> for PhrasesCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
pub enum PhraseLengthCommand {
|
||||||
|
Begin,
|
||||||
|
Next,
|
||||||
|
Prev,
|
||||||
|
Inc,
|
||||||
|
Dec,
|
||||||
|
Set(usize),
|
||||||
|
Cancel,
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: PhrasesControl> Command<T> for PhraseLengthCommand {
|
impl<T: PhrasesControl> Command<T> for PhraseLengthCommand {
|
||||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||||
use PhraseLengthFocus::*;
|
use PhraseLengthFocus::*;
|
||||||
|
|
@ -229,29 +207,39 @@ impl<T: PhrasesControl> Command<T> for PhraseLengthCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: PhrasesControl> Command<T> for PhraseRenameCommand {
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum PhraseRenameCommand {
|
||||||
|
Begin,
|
||||||
|
Set(String),
|
||||||
|
Confirm,
|
||||||
|
Cancel,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Command<T> for PhraseRenameCommand
|
||||||
|
where
|
||||||
|
T: PhrasesControl
|
||||||
|
{
|
||||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||||
use PhraseRenameCommand::*;
|
use PhraseRenameCommand::*;
|
||||||
if let Some(PhrasesMode::Rename(phrase, ref mut old_name)) = state.mode {
|
if let Some(PhrasesMode::Rename(phrase, ref mut old_name)) = state.phrases_mode() {
|
||||||
match self {
|
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())))
|
||||||
},
|
},
|
||||||
Confirm => {
|
Confirm => {
|
||||||
let old_name = old_name.clone();
|
let old_name = old_name.clone();
|
||||||
state.mode = None;
|
*state.phrases_mode_mut() = None;
|
||||||
return Ok(Some(Self::Set(old_name)))
|
return Ok(Some(Self::Set(old_name)))
|
||||||
},
|
},
|
||||||
Cancel => {
|
Cancel => {
|
||||||
let mut phrase = state.phrases[phrase].write().unwrap();
|
state.phrases()[*phrase].write().unwrap().name = old_name.clone();
|
||||||
phrase.name = old_name.clone();
|
|
||||||
},
|
},
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
};
|
};
|
||||||
Ok(None)
|
Ok(None)
|
||||||
} else if self == Begin {
|
} else if self == Begin {
|
||||||
self.phrases_rename_begin();
|
self.phrase_rename_begin();
|
||||||
Ok(None)
|
Ok(None)
|
||||||
} else {
|
} else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
|
|
@ -259,7 +247,26 @@ impl<T: PhrasesControl> Command<T> for PhraseRenameCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: PhraseControl> Command<T> for PhraseCommand {
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub enum PhraseCommand {
|
||||||
|
// TODO: 1-9 seek markers that by default start every 8th of the phrase
|
||||||
|
ToggleDirection,
|
||||||
|
EnterEditMode,
|
||||||
|
ExitEditMode,
|
||||||
|
NoteAppend,
|
||||||
|
NoteSet,
|
||||||
|
NoteCursorSet(usize),
|
||||||
|
NoteLengthSet(usize),
|
||||||
|
NoteScrollSet(usize),
|
||||||
|
TimeCursorSet(usize),
|
||||||
|
TimeScrollSet(usize),
|
||||||
|
TimeZoomSet(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Command<T> for PhraseCommand
|
||||||
|
where
|
||||||
|
T: PhraseControl + FocusEnter
|
||||||
|
{
|
||||||
//fn translate (self, state: &PhraseTui<E>) -> Self {
|
//fn translate (self, state: &PhraseTui<E>) -> Self {
|
||||||
//use PhraseCommand::*;
|
//use PhraseCommand::*;
|
||||||
//match self {
|
//match self {
|
||||||
|
|
@ -277,10 +284,10 @@ impl<T: PhraseControl> Command<T> for PhraseCommand {
|
||||||
state.mode = !state.mode;
|
state.mode = !state.mode;
|
||||||
},
|
},
|
||||||
EnterEditMode => {
|
EnterEditMode => {
|
||||||
state.entered = true;
|
state.focus_enter();
|
||||||
},
|
},
|
||||||
ExitEditMode => {
|
ExitEditMode => {
|
||||||
state.entered = false;
|
state.focus_exit();
|
||||||
},
|
},
|
||||||
TimeZoomOut => {
|
TimeZoomOut => {
|
||||||
let scale = state.time_axis().read().unwrap().scale;
|
let scale = state.time_axis().read().unwrap().scale;
|
||||||
|
|
@ -338,14 +345,12 @@ impl<T: PhraseControl> Command<T> for PhraseCommand {
|
||||||
axis.start_inc(3);
|
axis.start_inc(3);
|
||||||
axis.point_inc(3);
|
axis.point_inc(3);
|
||||||
},
|
},
|
||||||
NoteAppend => {
|
NoteAppend => if state.focus_entered() {
|
||||||
if state.entered {
|
|
||||||
state.put();
|
state.put();
|
||||||
state.time_cursor_advance();
|
state.time_cursor_advance();
|
||||||
}
|
|
||||||
},
|
},
|
||||||
NoteSet => {
|
NoteSet => if state.focus_entered() {
|
||||||
if state.entered { state.put(); }
|
state.put();
|
||||||
},
|
},
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,33 +14,109 @@ impl SequencerControl for SequencerTui {}
|
||||||
|
|
||||||
pub trait ArrangerControl {
|
pub trait ArrangerControl {
|
||||||
fn selected (&self) -> ArrangerSelection;
|
fn selected (&self) -> ArrangerSelection;
|
||||||
|
fn show_phrase (&mut self);
|
||||||
|
fn activate (&mut self);
|
||||||
|
fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>>;
|
||||||
|
fn toggle_loop (&mut self);
|
||||||
|
fn randomize_color (&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArrangerControl for ArrangerTui {
|
impl ArrangerControl for ArrangerTui {
|
||||||
fn selected (&self) -> ArrangerSelection {
|
fn selected (&self) -> ArrangerSelection {
|
||||||
self.selected
|
self.selected
|
||||||
}
|
}
|
||||||
|
fn show_phrase (&mut self) {
|
||||||
|
self.editor.show(self.selected_phrase().as_ref());
|
||||||
|
}
|
||||||
|
fn activate (&mut self) {
|
||||||
|
if let ArrangerSelection::Scene(s) = self.selected {
|
||||||
|
for (t, track) in self.tracks_mut().iter_mut().enumerate() {
|
||||||
|
let clip = self.scenes()[s].clips[t].as_ref();
|
||||||
|
if track.play_phrase.is_some() || clip.is_some() {
|
||||||
|
track.enqueue_next(clip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO make transport available here, so that
|
||||||
|
// activating a scene when stopped starts playback
|
||||||
|
//if self.is_stopped() {
|
||||||
|
//self.transport.toggle_play()
|
||||||
|
//}
|
||||||
|
} else if let ArrangerSelection::Clip(t, s) = self.selected {
|
||||||
|
self.tracks_mut()[t].enqueue_next(self.scenes()[s].clips[t].as_ref());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>> {
|
||||||
|
self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
|
||||||
|
}
|
||||||
|
fn toggle_loop (&mut self) {
|
||||||
|
if let Some(phrase) = self.selected_phrase() {
|
||||||
|
phrase.write().unwrap().toggle_loop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn randomize_color (&mut self) {
|
||||||
|
match self.selected {
|
||||||
|
ArrangerSelection::Mix => {
|
||||||
|
self.color = ItemColor::random_dark()
|
||||||
|
},
|
||||||
|
ArrangerSelection::Track(t) => {
|
||||||
|
self.tracks_mut()[t].color = ItemColor::random()
|
||||||
|
},
|
||||||
|
ArrangerSelection::Scene(s) => {
|
||||||
|
self.scenes_mut()[s].color = ItemColor::random()
|
||||||
|
},
|
||||||
|
ArrangerSelection::Clip(t, s) => {
|
||||||
|
if let Some(phrase) = &self.scenes_mut()[s].clips[t] {
|
||||||
|
phrase.write().unwrap().color = ItemColorTriplet::random();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait PhrasesControl {
|
pub trait PhrasesControl: HasPhrases {
|
||||||
|
fn phrase_index (&self) -> usize;
|
||||||
|
fn phrase_index_mut (&mut self) -> &mut usize;
|
||||||
fn phrases_mode (&self) -> &Option<PhrasesMode>;
|
fn phrases_mode (&self) -> &Option<PhrasesMode>;
|
||||||
fn phrases_mode_mut (&mut self) -> &mut Option<PhrasesMode>;
|
fn phrases_mode_mut (&mut self) -> &mut Option<PhrasesMode>;
|
||||||
fn phrase_rename_begin (&mut self) {
|
fn phrase_rename_begin (&mut self) {
|
||||||
*self.phrases_mode_mut() = Some(PhrasesMode::Rename(
|
let name = self.phrases()[self.phrase_index()].read().unwrap().name.clone();
|
||||||
self.phrase,
|
*self.phrases_mode_mut() = Some(
|
||||||
self.phrases[self.phrase].read().unwrap().name.clone()
|
PhrasesMode::Rename(self.phrase_index(), name)
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
fn phrase_length_begin (&mut self) {
|
fn phrase_length_begin (&mut self) {
|
||||||
*self.phrases_mode_mut() = Some(PhrasesMode::Length(
|
let length = self.phrases()[self.phrase_index()].read().unwrap().length;
|
||||||
self.phrase,
|
*self.phrases_mode_mut() = Some(
|
||||||
self.phrases[self.phrase].read().unwrap().length,
|
PhrasesMode::Length(self.phrase_index(), length, PhraseLengthFocus::Bar)
|
||||||
PhraseLengthFocus::Bar
|
)
|
||||||
))
|
}
|
||||||
|
fn new_phrase (name: Option<&str>, color: Option<ItemColorTriplet>) -> Arc<RwLock<Phrase>> {
|
||||||
|
Arc::new(RwLock::new(Phrase::new(
|
||||||
|
String::from(name.unwrap_or("(new)")), true, 4 * PPQ, None, color
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
fn index_of (&self, phrase: &Phrase) -> Option<usize> {
|
||||||
|
for i in 0..self.phrases().len() {
|
||||||
|
if *self.phrases()[i].read().unwrap() == *phrase { return Some(i) }
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
fn insert_dup (&mut self) {
|
||||||
|
let mut phrase = self.phrases()[self.phrase_index()].read().unwrap().duplicate();
|
||||||
|
phrase.color = ItemColorTriplet::random_near(phrase.color, 0.25);
|
||||||
|
let index = self.phrase_index() + 1;
|
||||||
|
self.phrases_mut().insert(index, Arc::new(RwLock::new(phrase)));
|
||||||
|
*self.phrase_index_mut() += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhrasesControl for SequencerTui {
|
impl PhrasesControl for SequencerTui {
|
||||||
|
fn phrase_index (&self) -> usize {
|
||||||
|
self.view_phrase
|
||||||
|
}
|
||||||
|
fn phrase_index_mut (&mut self) -> &mut usize {
|
||||||
|
&mut self.view_phrase
|
||||||
|
}
|
||||||
fn phrases_mode (&self) -> &Option<PhrasesMode> {
|
fn phrases_mode (&self) -> &Option<PhrasesMode> {
|
||||||
&self.phrases_mode
|
&self.phrases_mode
|
||||||
}
|
}
|
||||||
|
|
@ -50,6 +126,12 @@ impl PhrasesControl for SequencerTui {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhrasesControl for ArrangerTui {
|
impl PhrasesControl for ArrangerTui {
|
||||||
|
fn phrase_index (&self) -> usize {
|
||||||
|
self.phrase
|
||||||
|
}
|
||||||
|
fn phrase_index_mut (&mut self) -> &mut usize {
|
||||||
|
&mut self.phrase
|
||||||
|
}
|
||||||
fn phrases_mode (&self) -> &Option<PhrasesMode> {
|
fn phrases_mode (&self) -> &Option<PhrasesMode> {
|
||||||
&self.phrases_mode
|
&self.phrases_mode
|
||||||
}
|
}
|
||||||
|
|
@ -59,6 +141,12 @@ impl PhrasesControl for ArrangerTui {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhrasesControl for PhrasesTui {
|
impl PhrasesControl for PhrasesTui {
|
||||||
|
fn phrase_index (&self) -> usize {
|
||||||
|
self.phrase
|
||||||
|
}
|
||||||
|
fn phrase_index_mut (&mut self) -> &mut usize {
|
||||||
|
&mut self.phrase
|
||||||
|
}
|
||||||
fn phrases_mode (&self) -> &Option<PhrasesMode> {
|
fn phrases_mode (&self) -> &Option<PhrasesMode> {
|
||||||
&self.mode
|
&self.mode
|
||||||
}
|
}
|
||||||
|
|
@ -73,6 +161,12 @@ pub trait PhraseControl {
|
||||||
fn note_axis (&self) -> &RwLock<FixedAxis<usize>>;
|
fn note_axis (&self) -> &RwLock<FixedAxis<usize>>;
|
||||||
fn note_len (&self) -> usize;
|
fn note_len (&self) -> usize;
|
||||||
fn note_len_mut (&mut self) -> &mut usize;
|
fn note_len_mut (&mut self) -> &mut usize;
|
||||||
|
fn time_cursor_advance (&self) {
|
||||||
|
let point = self.time_axis().read().unwrap().point;
|
||||||
|
let length = self.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
|
||||||
|
let forward = |time|(time + self.note_len()) % length;
|
||||||
|
self.time_axis().write().unwrap().point = point.map(forward);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhraseControl for SequencerTui {
|
impl PhraseControl for SequencerTui {
|
||||||
|
|
|
||||||
|
|
@ -251,19 +251,42 @@ where
|
||||||
|
|
||||||
impl<T: PhrasesControl> InputToCommand<Tui, T> for PhrasesCommand {
|
impl<T: PhrasesControl> InputToCommand<Tui, T> for PhrasesCommand {
|
||||||
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
|
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
|
||||||
use PhrasePoolCommand as Edit;
|
use PhrasePoolCommand as Phrase;
|
||||||
use PhraseRenameCommand as Rename;
|
use PhraseRenameCommand as Rename;
|
||||||
use PhraseLengthCommand as Length;
|
use PhraseLengthCommand as Length;
|
||||||
|
let index = state.phrase();
|
||||||
|
let count = state.phrases().len();
|
||||||
match input.event() {
|
match input.event() {
|
||||||
key!(KeyCode::Up) => Some(Self::Select(0)),
|
key!(KeyCode::Up) => Some(Self::Select(0)),
|
||||||
key!(KeyCode::Down) => Some(Self::Select(0)),
|
key!(KeyCode::Down) => Some(Self::Select(0)),
|
||||||
key!(KeyCode::Char(',')) => Some(Self::Edit(Edit::Swap(0, 0))),
|
key!(KeyCode::Char(',')) => {
|
||||||
key!(KeyCode::Char('.')) => Some(Self::Edit(Edit::Swap(0, 0))),
|
if index > 1 {
|
||||||
key!(KeyCode::Delete) => Some(Self::Edit(Edit::Delete(0))),
|
Some(Self::Phrase(Phrase::Swap(index - 1, index)))
|
||||||
key!(KeyCode::Char('a')) => Some(Self::Edit(Edit::Add(0))),
|
//index -= 1;
|
||||||
key!(KeyCode::Char('i')) => Some(Self::Edit(Edit::Add(0))),
|
} else {
|
||||||
key!(KeyCode::Char('d')) => Some(Self::Edit(Edit::Duplicate(0))),
|
None
|
||||||
key!(KeyCode::Char('c')) => Some(Self::Edit(Edit::RandomColor(0))),
|
}
|
||||||
|
},
|
||||||
|
key!(KeyCode::Char('.')) => {
|
||||||
|
if index < count.saturating_sub(1) {
|
||||||
|
Some(Self::Phrase(Phrase::Swap(index + 1, index)))
|
||||||
|
//index += 1;
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key!(KeyCode::Delete) => {
|
||||||
|
if index > 0 {
|
||||||
|
Some(Self::Delete(index))
|
||||||
|
//index = index.min(count.saturating_sub(1));
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key!(KeyCode::Char('a')) => Some(Self::Phrase(Phrase::Add(count))),
|
||||||
|
key!(KeyCode::Char('i')) => Some(Self::Phrase(Phrase::Add(index + 1))),
|
||||||
|
key!(KeyCode::Char('d')) => Some(Self::Phrase(Phrase::Duplicate(index))),
|
||||||
|
key!(KeyCode::Char('c')) => Some(Self::Phrase(Phrase::RandomColor(index))),
|
||||||
key!(KeyCode::Char('n')) => Some(Self::Rename(Rename::Begin)),
|
key!(KeyCode::Char('n')) => Some(Self::Rename(Rename::Begin)),
|
||||||
key!(KeyCode::Char('t')) => Some(Self::Length(Length::Begin)),
|
key!(KeyCode::Char('t')) => Some(Self::Length(Length::Begin)),
|
||||||
_ => match state.phrases_mode() {
|
_ => match state.phrases_mode() {
|
||||||
|
|
|
||||||
|
|
@ -1 +1,36 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
fn content_with_menu_and_status <'a, A, S, C> (
|
||||||
|
content: &'a A,
|
||||||
|
menu_bar: &'a Option<MenuBar<Tui, S, C>>,
|
||||||
|
status_bar: &'a Option<impl StatusBar>
|
||||||
|
) -> impl Widget<Engine = Tui> + 'a
|
||||||
|
where
|
||||||
|
A: Widget<Engine = Tui>,
|
||||||
|
S: Send + Sync + 'a,
|
||||||
|
C: Command<S>
|
||||||
|
{
|
||||||
|
let menus = menu_bar.as_ref().map_or_else(
|
||||||
|
||&[] as &[Menu<_, _, _>],
|
||||||
|
|m|m.menus.as_slice()
|
||||||
|
);
|
||||||
|
Either(
|
||||||
|
menu_bar.is_none(),
|
||||||
|
Either(
|
||||||
|
status_bar.is_none(),
|
||||||
|
widget(content),
|
||||||
|
Split::up(
|
||||||
|
1,
|
||||||
|
widget(status_bar.as_ref().unwrap()),
|
||||||
|
widget(content)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Split::down(
|
||||||
|
1,
|
||||||
|
row!(menu in menus.iter() => {
|
||||||
|
row!(" ", menu.title.as_str(), " ")
|
||||||
|
}),
|
||||||
|
widget(content)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,36 @@ pub struct SequencerTui {
|
||||||
pub(crate) phrases_mode: Option<PhrasesMode>,
|
pub(crate) phrases_mode: Option<PhrasesMode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HasPhrases for SequencerTui {
|
||||||
|
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
||||||
|
&self.phrases
|
||||||
|
}
|
||||||
|
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
|
||||||
|
&mut self.phrases
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasPhrase for SequencerTui {
|
||||||
|
fn reset (&self) -> bool {
|
||||||
|
self.reset
|
||||||
|
}
|
||||||
|
fn reset_mut (&mut self) -> &mut bool {
|
||||||
|
&mut self.reset
|
||||||
|
}
|
||||||
|
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!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Root view for standalone `tek_arranger`
|
/// Root view for standalone `tek_arranger`
|
||||||
pub struct ArrangerTui {
|
pub struct ArrangerTui {
|
||||||
pub(crate) jack: Arc<RwLock<JackClient>>,
|
pub(crate) jack: Arc<RwLock<JackClient>>,
|
||||||
|
|
@ -109,6 +139,43 @@ pub struct ArrangerTui {
|
||||||
pub(crate) phrases_mode: Option<PhrasesMode>,
|
pub(crate) phrases_mode: Option<PhrasesMode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HasPhrases for ArrangerTui {
|
||||||
|
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
||||||
|
&self.phrases
|
||||||
|
}
|
||||||
|
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
|
||||||
|
&mut self.phrases
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasScenes<ArrangerScene> for ArrangerTui {
|
||||||
|
fn scenes (&self) -> &Vec<ArrangerScene> {
|
||||||
|
&self.scenes
|
||||||
|
}
|
||||||
|
fn scenes_mut (&mut self) -> &mut Vec<ArrangerScene> {
|
||||||
|
&mut self.scenes
|
||||||
|
}
|
||||||
|
fn scene_add (&mut self, name: Option<&str>, color: Option<ItemColor>)
|
||||||
|
-> Usually<&mut ArrangerScene>
|
||||||
|
{
|
||||||
|
let name = name.map_or_else(||self.scene_default_name(), |x|x.to_string());
|
||||||
|
let scene = ArrangerScene {
|
||||||
|
name: Arc::new(name.into()),
|
||||||
|
clips: vec![None;self.tracks().len()],
|
||||||
|
color: color.unwrap_or_else(||ItemColor::random()),
|
||||||
|
};
|
||||||
|
self.scenes_mut().push(scene);
|
||||||
|
let index = self.scenes().len() - 1;
|
||||||
|
Ok(&mut self.scenes_mut()[index])
|
||||||
|
}
|
||||||
|
fn selected_scene (&self) -> Option<&ArrangerScene> {
|
||||||
|
self.selected.scene().map(|s|self.scenes().get(s)).flatten()
|
||||||
|
}
|
||||||
|
fn selected_scene_mut (&mut self) -> Option<&mut ArrangerScene> {
|
||||||
|
self.selected.scene().map(|s|self.scenes_mut().get_mut(s)).flatten()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
pub struct ArrangerScene {
|
pub struct ArrangerScene {
|
||||||
/// Name of scene
|
/// Name of scene
|
||||||
|
|
@ -119,6 +186,59 @@ pub struct ArrangerScene {
|
||||||
pub(crate) color: ItemColor,
|
pub(crate) color: ItemColor,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ArrangerSceneApi for ArrangerScene {
|
||||||
|
fn name (&self) -> &Arc<RwLock<String>> {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
fn clips (&self) -> &Vec<Option<Arc<RwLock<Phrase>>>> {
|
||||||
|
&self.clips
|
||||||
|
}
|
||||||
|
fn color (&self) -> ItemColor {
|
||||||
|
self.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasTracks<ArrangerTrack> for ArrangerTui {
|
||||||
|
fn tracks (&self) -> &Vec<ArrangerTrack> {
|
||||||
|
&self.tracks
|
||||||
|
}
|
||||||
|
fn tracks_mut (&mut self) -> &mut Vec<ArrangerTrack> {
|
||||||
|
&mut self.tracks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArrangerTracksApi<ArrangerTrack> for ArrangerTui {
|
||||||
|
fn track_add (&mut self, name: Option<&str>, color: Option<ItemColor>)
|
||||||
|
-> Usually<&mut ArrangerTrack>
|
||||||
|
{
|
||||||
|
let name = name.map_or_else(||self.track_default_name(), |x|x.to_string());
|
||||||
|
let track = ArrangerTrack {
|
||||||
|
width: name.len() + 2,
|
||||||
|
name: Arc::new(name.into()),
|
||||||
|
color: color.unwrap_or_else(||ItemColor::random()),
|
||||||
|
midi_ins: vec![],
|
||||||
|
midi_outs: vec![],
|
||||||
|
reset: true,
|
||||||
|
recording: false,
|
||||||
|
monitoring: false,
|
||||||
|
overdub: false,
|
||||||
|
play_phrase: None,
|
||||||
|
next_phrase: None,
|
||||||
|
notes_in: RwLock::new([false;128]).into(),
|
||||||
|
notes_out: RwLock::new([false;128]).into(),
|
||||||
|
};
|
||||||
|
self.tracks_mut().push(track);
|
||||||
|
let index = self.tracks().len() - 1;
|
||||||
|
Ok(&mut self.tracks_mut()[index])
|
||||||
|
}
|
||||||
|
fn track_del (&mut self, index: usize) {
|
||||||
|
self.tracks_mut().remove(index);
|
||||||
|
for scene in self.scenes_mut().iter_mut() {
|
||||||
|
scene.clips.remove(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ArrangerTrack {
|
pub struct ArrangerTrack {
|
||||||
/// Name of track
|
/// Name of track
|
||||||
|
|
@ -159,6 +279,120 @@ pub struct ArrangerTrack {
|
||||||
pub(crate) phrases_mode: Option<PhrasesMode>,
|
pub(crate) phrases_mode: Option<PhrasesMode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ArrangerTrackApi for ArrangerTrack {
|
||||||
|
/// Name of track
|
||||||
|
fn name (&self) -> &Arc<RwLock<String>> {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
/// Preferred width of track column
|
||||||
|
fn width (&self) -> usize {
|
||||||
|
self.width
|
||||||
|
}
|
||||||
|
/// Preferred width of track column
|
||||||
|
fn width_mut (&mut self) -> &mut usize {
|
||||||
|
&mut self.width
|
||||||
|
}
|
||||||
|
/// Identifying color of track
|
||||||
|
fn color (&self) -> ItemColor {
|
||||||
|
self.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasPhrase for ArrangerTrack {
|
||||||
|
fn reset (&self) -> bool {
|
||||||
|
self.reset
|
||||||
|
}
|
||||||
|
fn reset_mut (&mut self) -> &mut bool {
|
||||||
|
&mut self.reset
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
fn timebase (&self) -> &Arc<Timebase> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn quant (&self) -> &Quantize {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn sync (&self) -> &LaunchSync {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {}
|
||||||
|
|
||||||
pub struct PhrasesTui {
|
pub struct PhrasesTui {
|
||||||
/// Collection of phrases
|
/// Collection of phrases
|
||||||
pub(crate) phrases: Vec<Arc<RwLock<Phrase>>>,
|
pub(crate) phrases: Vec<Arc<RwLock<Phrase>>>,
|
||||||
|
|
@ -174,6 +408,15 @@ pub struct PhrasesTui {
|
||||||
pub(crate) entered: bool,
|
pub(crate) entered: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HasPhrases for PhrasesTui {
|
||||||
|
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
||||||
|
&self.phrases
|
||||||
|
}
|
||||||
|
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
|
||||||
|
&mut self.phrases
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Modes for phrase pool
|
/// Modes for phrase pool
|
||||||
pub enum PhrasesMode {
|
pub enum PhrasesMode {
|
||||||
/// Renaming a pattern
|
/// Renaming a pattern
|
||||||
|
|
|
||||||
|
|
@ -767,3 +767,108 @@ pub(crate) fn keys_vert () -> Buffer {
|
||||||
const NTH_OCTAVE: [&'static str; 11] = [
|
const NTH_OCTAVE: [&'static str; 11] = [
|
||||||
"-2", "-1", "0", "1", "2", "3", "4", "5", "6", "7", "8",
|
"-2", "-1", "0", "1", "2", "3", "4", "5", "6", "7", "8",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
impl PhraseTui {
|
||||||
|
pub fn put (&mut self) {
|
||||||
|
if let (Some(phrase), Some(time), Some(note)) = (
|
||||||
|
&self.phrase,
|
||||||
|
self.time_axis.read().unwrap().point,
|
||||||
|
self.note_axis.read().unwrap().point,
|
||||||
|
) {
|
||||||
|
let mut phrase = phrase.write().unwrap();
|
||||||
|
let key: u7 = u7::from((127 - note) as u8);
|
||||||
|
let vel: u7 = 100.into();
|
||||||
|
let start = time;
|
||||||
|
let end = (start + self.note_len) % phrase.length;
|
||||||
|
phrase.notes[time].push(MidiMessage::NoteOn { key, vel });
|
||||||
|
phrase.notes[end].push(MidiMessage::NoteOff { key, vel });
|
||||||
|
self.buffer = Self::redraw(&phrase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.
|
||||||
|
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
|
||||||
|
if let Some(phrase) = phrase {
|
||||||
|
self.phrase = Some(phrase.clone());
|
||||||
|
self.time_axis.write().unwrap().clamp = Some(phrase.read().unwrap().length);
|
||||||
|
self.buffer = Self::redraw(&*phrase.read().unwrap());
|
||||||
|
} else {
|
||||||
|
self.phrase = None;
|
||||||
|
self.time_axis.write().unwrap().clamp = Some(0);
|
||||||
|
self.buffer = Default::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn redraw (phrase: &Phrase) -> BigBuffer {
|
||||||
|
let mut buf = BigBuffer::new(usize::MAX.min(phrase.length), 65);
|
||||||
|
Self::fill_seq_bg(&mut buf, phrase.length, phrase.ppq);
|
||||||
|
Self::fill_seq_fg(&mut buf, &phrase);
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
fn fill_seq_bg (buf: &mut BigBuffer, length: usize, ppq: usize) {
|
||||||
|
for x in 0..buf.width {
|
||||||
|
// Only fill as far as phrase length
|
||||||
|
if x as usize >= length { break }
|
||||||
|
// Fill each row with background characters
|
||||||
|
for y in 0 .. buf.height {
|
||||||
|
buf.get_mut(x, y).map(|cell|{
|
||||||
|
cell.set_char(if ppq == 0 {
|
||||||
|
'·'
|
||||||
|
} else if x % (4 * ppq) == 0 {
|
||||||
|
'│'
|
||||||
|
} else if x % ppq == 0 {
|
||||||
|
'╎'
|
||||||
|
} else {
|
||||||
|
'·'
|
||||||
|
});
|
||||||
|
cell.set_fg(Color::Rgb(48, 64, 56));
|
||||||
|
cell.modifier = Modifier::DIM;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn fill_seq_fg (buf: &mut BigBuffer, phrase: &Phrase) {
|
||||||
|
let mut notes_on = [false;128];
|
||||||
|
for x in 0..buf.width {
|
||||||
|
if x as usize >= phrase.length {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if let Some(notes) = phrase.notes.get(x as usize) {
|
||||||
|
if phrase.percussive {
|
||||||
|
for note in notes {
|
||||||
|
match note {
|
||||||
|
MidiMessage::NoteOn { key, .. } =>
|
||||||
|
notes_on[key.as_int() as usize] = true,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for note in notes {
|
||||||
|
match note {
|
||||||
|
MidiMessage::NoteOn { key, .. } =>
|
||||||
|
notes_on[key.as_int() as usize] = true,
|
||||||
|
MidiMessage::NoteOff { key, .. } =>
|
||||||
|
notes_on[key.as_int() as usize] = false,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for y in 0..buf.height {
|
||||||
|
if y >= 64 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if let Some(block) = half_block(
|
||||||
|
notes_on[y as usize * 2],
|
||||||
|
notes_on[y as usize * 2 + 1],
|
||||||
|
) {
|
||||||
|
buf.get_mut(x, y).map(|cell|{
|
||||||
|
cell.set_char(block);
|
||||||
|
cell.set_fg(Color::White);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if phrase.percussive {
|
||||||
|
notes_on.fill(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue