mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56: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),
|
||||
Duplicate(usize),
|
||||
Swap(usize, usize),
|
||||
RandomColor(usize),
|
||||
Color(usize, ItemColor),
|
||||
Import(usize, String),
|
||||
Export(usize, String),
|
||||
SetName(usize, String),
|
||||
|
|
@ -38,10 +38,9 @@ impl<T: HasPhrases> Command<T> for PhrasePoolCommand {
|
|||
//view.phrase += 1;
|
||||
},
|
||||
Self::Swap(index, other) => {
|
||||
//Self::MoveUp => { view.move_up() },
|
||||
//Self::MoveDown => { view.move_down() },
|
||||
model.phrases_mut().swap(index, other);
|
||||
},
|
||||
Self::RandomColor(index) => {
|
||||
Self::Color(index, color) => {
|
||||
//view.phrase().write().unwrap().color = ItemColorTriplet::random();
|
||||
},
|
||||
Self::Import(index, path) => {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ pub trait HasScenes<S: ArrangerSceneApi> {
|
|||
fn scene_default_name (&self) -> String {
|
||||
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)]
|
||||
|
|
@ -86,6 +92,7 @@ pub trait ArrangerSceneApi: Sized {
|
|||
fn clip (&self, index: usize) -> Option<&Arc<RwLock<Phrase>>> {
|
||||
match self.clips().get(index) { Some(Some(clip)) => Some(clip), _ => None }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//impl ArrangerScene {
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ use rand::{thread_rng, distributions::uniform::UniformSampler};
|
|||
pub use palette::{*, convert::*, okhsl::*};
|
||||
|
||||
/// A color in OKHSL and RGB representations.
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct ItemColor {
|
||||
pub okhsl: Okhsl<f32>,
|
||||
pub rgb: Color,
|
||||
}
|
||||
/// 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 base: ItemColor,
|
||||
pub light: ItemColor,
|
||||
|
|
|
|||
|
|
@ -29,38 +29,3 @@ submod! {
|
|||
tui_view
|
||||
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::*;
|
||||
|
||||
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 {
|
||||
pub fn new (phrases: Vec<Arc<RwLock<Phrase>>>) -> Self {
|
||||
Self {
|
||||
|
|
@ -329,71 +11,37 @@ impl PhrasesTui {
|
|||
phrases,
|
||||
}
|
||||
}
|
||||
pub fn len (&self) -> usize {
|
||||
self.phrases.len()
|
||||
}
|
||||
pub fn phrase (&self) -> &Arc<RwLock<Phrase>> {
|
||||
&self.phrases[self.phrase]
|
||||
}
|
||||
pub fn index_before (&self, index: usize) -> usize {
|
||||
index.overflowing_sub(1).0.min(self.len() - 1)
|
||||
}
|
||||
pub fn index_after (&self, index: usize) -> usize {
|
||||
(index + 1) % self.len()
|
||||
}
|
||||
pub 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 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
|
||||
)))
|
||||
}
|
||||
pub fn delete_selected (&mut self) {
|
||||
if self.phrase > 0 {
|
||||
self.phrases.remove(self.phrase);
|
||||
self.phrase = self.phrase.min(self.phrases.len().saturating_sub(1));
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
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 track_next (&mut self, last_track: usize) {
|
||||
//use ArrangerSelection::*;
|
||||
//*self = match self {
|
||||
|
|
@ -572,142 +220,23 @@ impl PhrasesTui {
|
|||
//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) {
|
||||
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);
|
||||
}
|
||||
//pub fn is_first_row (&self) -> bool {
|
||||
//let selected = self.selected;
|
||||
//selected.is_mix() || selected.is_track()
|
||||
//}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//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 index_before (&self, index: usize) -> usize {
|
||||
//index.overflowing_sub(1).0.min(self.len() - 1)
|
||||
//}
|
||||
//pub fn index_after (&self, index: usize) -> usize {
|
||||
//(index + 1) % self.len()
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -7,79 +7,6 @@ pub enum TransportCommand {
|
|||
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 {
|
||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||
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
|
||||
where
|
||||
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
|
||||
where
|
||||
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 {
|
||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||
use PhraseRenameCommand as Rename;
|
||||
|
|
@ -169,8 +136,8 @@ impl<T: PhrasesControl> Command<T> for PhrasesCommand {
|
|||
Self::Select(phrase) => {
|
||||
state.phrase = phrase
|
||||
},
|
||||
Self::Edit(command) => {
|
||||
return Ok(command.execute(&mut state)?.map(Self::Edit))
|
||||
Self::Phrase(command) => {
|
||||
return Ok(command.execute(&mut state)?.map(Self::Phrase))
|
||||
}
|
||||
Self::Rename(command) => match command {
|
||||
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 {
|
||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||
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> {
|
||||
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 {
|
||||
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())))
|
||||
},
|
||||
Confirm => {
|
||||
let old_name = old_name.clone();
|
||||
state.mode = None;
|
||||
*state.phrases_mode_mut() = None;
|
||||
return Ok(Some(Self::Set(old_name)))
|
||||
},
|
||||
Cancel => {
|
||||
let mut phrase = state.phrases[phrase].write().unwrap();
|
||||
phrase.name = old_name.clone();
|
||||
state.phrases()[*phrase].write().unwrap().name = old_name.clone();
|
||||
},
|
||||
_ => unreachable!()
|
||||
};
|
||||
Ok(None)
|
||||
} else if self == Begin {
|
||||
self.phrases_rename_begin();
|
||||
self.phrase_rename_begin();
|
||||
Ok(None)
|
||||
} else {
|
||||
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 {
|
||||
//use PhraseCommand::*;
|
||||
//match self {
|
||||
|
|
@ -277,10 +284,10 @@ impl<T: PhraseControl> Command<T> for PhraseCommand {
|
|||
state.mode = !state.mode;
|
||||
},
|
||||
EnterEditMode => {
|
||||
state.entered = true;
|
||||
state.focus_enter();
|
||||
},
|
||||
ExitEditMode => {
|
||||
state.entered = false;
|
||||
state.focus_exit();
|
||||
},
|
||||
TimeZoomOut => {
|
||||
let scale = state.time_axis().read().unwrap().scale;
|
||||
|
|
@ -338,14 +345,12 @@ impl<T: PhraseControl> Command<T> for PhraseCommand {
|
|||
axis.start_inc(3);
|
||||
axis.point_inc(3);
|
||||
},
|
||||
NoteAppend => {
|
||||
if state.entered {
|
||||
state.put();
|
||||
state.time_cursor_advance();
|
||||
}
|
||||
NoteAppend => if state.focus_entered() {
|
||||
state.put();
|
||||
state.time_cursor_advance();
|
||||
},
|
||||
NoteSet => {
|
||||
if state.entered { state.put(); }
|
||||
NoteSet => if state.focus_entered() {
|
||||
state.put();
|
||||
},
|
||||
_ => unreachable!()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,33 +14,109 @@ impl SequencerControl for SequencerTui {}
|
|||
|
||||
pub trait ArrangerControl {
|
||||
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 {
|
||||
fn selected (&self) -> ArrangerSelection {
|
||||
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_mut (&mut self) -> &mut Option<PhrasesMode>;
|
||||
fn phrase_rename_begin (&mut self) {
|
||||
*self.phrases_mode_mut() = Some(PhrasesMode::Rename(
|
||||
self.phrase,
|
||||
self.phrases[self.phrase].read().unwrap().name.clone()
|
||||
))
|
||||
let name = self.phrases()[self.phrase_index()].read().unwrap().name.clone();
|
||||
*self.phrases_mode_mut() = Some(
|
||||
PhrasesMode::Rename(self.phrase_index(), name)
|
||||
)
|
||||
}
|
||||
fn phrase_length_begin (&mut self) {
|
||||
*self.phrases_mode_mut() = Some(PhrasesMode::Length(
|
||||
self.phrase,
|
||||
self.phrases[self.phrase].read().unwrap().length,
|
||||
PhraseLengthFocus::Bar
|
||||
))
|
||||
let length = self.phrases()[self.phrase_index()].read().unwrap().length;
|
||||
*self.phrases_mode_mut() = Some(
|
||||
PhrasesMode::Length(self.phrase_index(), length, 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 {
|
||||
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> {
|
||||
&self.phrases_mode
|
||||
}
|
||||
|
|
@ -50,6 +126,12 @@ impl PhrasesControl for SequencerTui {
|
|||
}
|
||||
|
||||
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> {
|
||||
&self.phrases_mode
|
||||
}
|
||||
|
|
@ -59,6 +141,12 @@ impl PhrasesControl for ArrangerTui {
|
|||
}
|
||||
|
||||
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> {
|
||||
&self.mode
|
||||
}
|
||||
|
|
@ -73,6 +161,12 @@ pub trait PhraseControl {
|
|||
fn note_axis (&self) -> &RwLock<FixedAxis<usize>>;
|
||||
fn note_len (&self) -> 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 {
|
||||
|
|
|
|||
|
|
@ -251,19 +251,42 @@ where
|
|||
|
||||
impl<T: PhrasesControl> InputToCommand<Tui, T> for PhrasesCommand {
|
||||
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
|
||||
use PhrasePoolCommand as Edit;
|
||||
use PhrasePoolCommand as Phrase;
|
||||
use PhraseRenameCommand as Rename;
|
||||
use PhraseLengthCommand as Length;
|
||||
let index = state.phrase();
|
||||
let count = state.phrases().len();
|
||||
match input.event() {
|
||||
key!(KeyCode::Up) => Some(Self::Select(0)),
|
||||
key!(KeyCode::Down) => Some(Self::Select(0)),
|
||||
key!(KeyCode::Char(',')) => Some(Self::Edit(Edit::Swap(0, 0))),
|
||||
key!(KeyCode::Char('.')) => Some(Self::Edit(Edit::Swap(0, 0))),
|
||||
key!(KeyCode::Delete) => Some(Self::Edit(Edit::Delete(0))),
|
||||
key!(KeyCode::Char('a')) => Some(Self::Edit(Edit::Add(0))),
|
||||
key!(KeyCode::Char('i')) => Some(Self::Edit(Edit::Add(0))),
|
||||
key!(KeyCode::Char('d')) => Some(Self::Edit(Edit::Duplicate(0))),
|
||||
key!(KeyCode::Char('c')) => Some(Self::Edit(Edit::RandomColor(0))),
|
||||
key!(KeyCode::Char(',')) => {
|
||||
if index > 1 {
|
||||
Some(Self::Phrase(Phrase::Swap(index - 1, index)))
|
||||
//index -= 1;
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
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('t')) => Some(Self::Length(Length::Begin)),
|
||||
_ => match state.phrases_mode() {
|
||||
|
|
|
|||
|
|
@ -1 +1,36 @@
|
|||
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>,
|
||||
}
|
||||
|
||||
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`
|
||||
pub struct ArrangerTui {
|
||||
pub(crate) jack: Arc<RwLock<JackClient>>,
|
||||
|
|
@ -109,6 +139,43 @@ pub struct ArrangerTui {
|
|||
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)]
|
||||
pub struct ArrangerScene {
|
||||
/// Name of scene
|
||||
|
|
@ -119,6 +186,59 @@ pub struct ArrangerScene {
|
|||
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)]
|
||||
pub struct ArrangerTrack {
|
||||
/// Name of track
|
||||
|
|
@ -159,6 +279,120 @@ pub struct ArrangerTrack {
|
|||
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 {
|
||||
/// Collection of phrases
|
||||
pub(crate) phrases: Vec<Arc<RwLock<Phrase>>>,
|
||||
|
|
@ -174,6 +408,15 @@ pub struct PhrasesTui {
|
|||
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
|
||||
pub enum PhrasesMode {
|
||||
/// Renaming a pattern
|
||||
|
|
|
|||
|
|
@ -767,3 +767,108 @@ pub(crate) fn keys_vert () -> Buffer {
|
|||
const NTH_OCTAVE: [&'static str; 11] = [
|
||||
"-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