Phrase -> MidiClip, PhraseEdit -> MidiEdit

This commit is contained in:
🪞👃🪞 2024-12-27 09:05:33 +01:00
parent 63550fabcf
commit 0530e43a2f
14 changed files with 94 additions and 94 deletions

View file

@ -80,9 +80,9 @@ pub struct MidiPlayer {
/// State of clock and playhead /// State of clock and playhead
pub(crate) clock: ClockModel, pub(crate) clock: ClockModel,
/// Start time and phrase being played /// Start time and phrase being played
pub(crate) play_phrase: Option<(Moment, Option<Arc<RwLock<Phrase>>>)>, pub(crate) play_phrase: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
/// Start time and next phrase /// Start time and next phrase
pub(crate) next_phrase: Option<(Moment, Option<Arc<RwLock<Phrase>>>)>, pub(crate) next_phrase: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
/// Play input through output. /// Play input through output.
pub(crate) monitoring: bool, pub(crate) monitoring: bool,
/// Write input to sequence. /// Write input to sequence.
@ -125,7 +125,7 @@ from!(|clock: &ClockModel| MidiPlayer = Self {
notes_in: RwLock::new([false;128]).into(), notes_in: RwLock::new([false;128]).into(),
notes_out: RwLock::new([false;128]).into(), notes_out: RwLock::new([false;128]).into(),
}); });
from!(|state: (&ClockModel, &Arc<RwLock<Phrase>>)|MidiPlayer = { from!(|state: (&ClockModel, &Arc<RwLock<MidiClip>>)|MidiPlayer = {
let (clock, phrase) = state; let (clock, phrase) = state;
let mut model = Self::from(clock); let mut model = Self::from(clock);
model.play_phrase = Some((Moment::zero(&clock.timebase), Some(phrase.clone()))); model.play_phrase = Some((Moment::zero(&clock.timebase), Some(phrase.clone())));
@ -228,16 +228,16 @@ impl HasPlayPhrase for MidiPlayer {
fn reset_mut (&mut self) -> &mut bool { fn reset_mut (&mut self) -> &mut bool {
&mut self.reset &mut self.reset
} }
fn play_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<Phrase>>>)> { fn play_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<MidiClip>>>)> {
&self.play_phrase &self.play_phrase
} }
fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<Phrase>>>)> { fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)> {
&mut self.play_phrase &mut self.play_phrase
} }
fn next_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<Phrase>>>)> { fn next_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<MidiClip>>>)> {
&self.next_phrase &self.next_phrase
} }
fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<Phrase>>>)> { fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)> {
&mut self.next_phrase &mut self.next_phrase
} }
} }

View file

@ -1,20 +1,20 @@
use crate::*; use crate::*;
pub trait HasPhrase { pub trait HasMidiClip {
fn phrase (&self) -> &Arc<RwLock<Phrase>>; fn phrase (&self) -> &Arc<RwLock<MidiClip>>;
} }
#[macro_export] macro_rules! has_phrase { #[macro_export] macro_rules! has_phrase {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasPhrase for $Struct $(<$($L),*$($T),*>)? { impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? {
fn phrase (&$self) -> &Arc<RwLock<Phrase>> { &$cb } fn phrase (&$self) -> &Arc<RwLock<MidiClip>> { &$cb }
} }
} }
} }
/// A MIDI sequence. /// A MIDI sequence.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Phrase { pub struct MidiClip {
pub uuid: uuid::Uuid, pub uuid: uuid::Uuid,
/// Name of phrase /// Name of phrase
pub name: String, pub name: String,
@ -23,7 +23,7 @@ pub struct Phrase {
/// Length of phrase in pulses /// Length of phrase in pulses
pub length: usize, pub length: usize,
/// Notes in phrase /// Notes in phrase
pub notes: PhraseData, pub notes: MidiData,
/// Whether to loop the phrase or play it once /// Whether to loop the phrase or play it once
pub looped: bool, pub looped: bool,
/// Start of loop /// Start of loop
@ -37,14 +37,14 @@ pub struct Phrase {
} }
/// MIDI message structural /// MIDI message structural
pub type PhraseData = Vec<Vec<MidiMessage>>; pub type MidiData = Vec<Vec<MidiMessage>>;
impl Phrase { impl MidiClip {
pub fn new ( pub fn new (
name: impl AsRef<str>, name: impl AsRef<str>,
looped: bool, looped: bool,
length: usize, length: usize,
notes: Option<PhraseData>, notes: Option<MidiData>,
color: Option<ItemPalette>, color: Option<ItemPalette>,
) -> Self { ) -> Self {
Self { Self {
@ -86,7 +86,7 @@ impl Phrase {
} }
} }
impl Default for Phrase { impl Default for MidiClip {
fn default () -> Self { fn default () -> Self {
Self::new( Self::new(
"Stop", "Stop",
@ -101,10 +101,10 @@ impl Default for Phrase {
} }
} }
impl PartialEq for Phrase { impl PartialEq for MidiClip {
fn eq (&self, other: &Self) -> bool { fn eq (&self, other: &Self) -> bool {
self.uuid == other.uuid self.uuid == other.uuid
} }
} }
impl Eq for Phrase {} impl Eq for MidiClip {}

View file

@ -3,10 +3,10 @@ use crate::*;
pub trait HasPlayPhrase: HasClock { pub trait HasPlayPhrase: HasClock {
fn reset (&self) -> bool; fn reset (&self) -> bool;
fn reset_mut (&mut self) -> &mut bool; fn reset_mut (&mut self) -> &mut bool;
fn play_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<Phrase>>>)>; fn play_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<Phrase>>>)>; fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
fn next_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<Phrase>>>)>; fn next_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<Phrase>>>)>; fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
fn pulses_since_start (&self) -> Option<f64> { fn pulses_since_start (&self) -> Option<f64> {
if let Some((started, Some(_))) = self.play_phrase().as_ref() { if let Some((started, Some(_))) = self.play_phrase().as_ref() {
let elapsed = self.clock().playhead.pulse.get() - started.pulse.get(); let elapsed = self.clock().playhead.pulse.get() - started.pulse.get();
@ -25,7 +25,7 @@ pub trait HasPlayPhrase: HasClock {
None None
} }
} }
fn enqueue_next (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) { fn enqueue_next (&mut self, phrase: Option<&Arc<RwLock<MidiClip>>>) {
let start = self.clock().next_launch_pulse() as f64; let start = self.clock().next_launch_pulse() as f64;
let instant = Moment::from_pulse(&self.clock().timebase(), start); let instant = Moment::from_pulse(&self.clock().timebase(), start);
let phrase = phrase.map(|p|p.clone()); let phrase = phrase.map(|p|p.clone());

View file

@ -66,7 +66,7 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts {
note_buf: &mut Vec<u8>, note_buf: &mut Vec<u8>,
out: &mut [Vec<Vec<u8>>], out: &mut [Vec<Vec<u8>>],
started: &Moment, started: &Moment,
phrase: &Option<Arc<RwLock<Phrase>>> phrase: &Option<Arc<RwLock<MidiClip>>>
) -> bool { ) -> bool {
// First sample to populate. Greater than 0 means that the first // First sample to populate. Greater than 0 means that the first
// pulse of the phrase falls somewhere in the middle of the chunk. // pulse of the phrase falls somewhere in the middle of the chunk.
@ -97,7 +97,7 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts {
} }
fn play_pulse ( fn play_pulse (
phrase: &RwLock<Phrase>, phrase: &RwLock<MidiClip>,
pulse: usize, pulse: usize,
sample: usize, sample: usize,
note_buf: &mut Vec<u8>, note_buf: &mut Vec<u8>,

View file

@ -1,22 +1,22 @@
use crate::*; use crate::*;
pub trait HasPhrases { pub trait HasPhrases {
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>>; fn phrases (&self) -> &Vec<Arc<RwLock<MidiClip>>>;
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>>; fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<MidiClip>>>;
} }
#[macro_export] macro_rules! has_phrases { #[macro_export] macro_rules! has_phrases {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasPhrases for $Struct $(<$($L),*$($T),*>)? { impl $(<$($L),*$($T $(: $U)?),*>)? HasPhrases for $Struct $(<$($L),*$($T),*>)? {
fn phrases (&$self) -> &Vec<Arc<RwLock<Phrase>>> { &$cb } fn phrases (&$self) -> &Vec<Arc<RwLock<MidiClip>>> { &$cb }
fn phrases_mut (&mut $self) -> &mut Vec<Arc<RwLock<Phrase>>> { &mut$cb } fn phrases_mut (&mut $self) -> &mut Vec<Arc<RwLock<MidiClip>>> { &mut$cb }
} }
} }
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum PhrasePoolCommand { pub enum PhrasePoolCommand {
Add(usize, Phrase), Add(usize, MidiClip),
Delete(usize), Delete(usize),
Swap(usize, usize), Swap(usize, usize),
Import(usize, PathBuf), Import(usize, PathBuf),
@ -62,7 +62,7 @@ impl<T: HasPhrases> Command<T> for PhrasePoolCommand {
} }
} }
} }
let mut phrase = Phrase::new("imported", true, t as usize + 1, None, None); let mut phrase = MidiClip::new("imported", true, t as usize + 1, None, None);
for event in events.iter() { for event in events.iter() {
phrase.notes[event.0 as usize].push(event.2); phrase.notes[event.0 as usize].push(event.2);
} }

View file

@ -13,7 +13,7 @@ pub struct ArrangerTui {
pub size: Measure<Tui>, pub size: Measure<Tui>,
pub note_buf: Vec<u8>, pub note_buf: Vec<u8>,
pub midi_buf: Vec<Vec<Vec<u8>>>, pub midi_buf: Vec<Vec<Vec<u8>>>,
pub editor: PhraseEditorModel, pub editor: MidiEditorModel,
pub perf: PerfModel, pub perf: PerfModel,
} }
impl ArrangerTui { impl ArrangerTui {
@ -40,7 +40,7 @@ impl ArrangerTui {
}; };
Ok(()) Ok(())
} }
pub fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>> { pub fn selected_phrase (&self) -> Option<Arc<RwLock<MidiClip>>> {
self.selected_scene()?.clips.get(self.selected.track()?)?.clone() self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
} }
pub fn toggle_loop (&mut self) { pub fn toggle_loop (&mut self) {
@ -61,7 +61,7 @@ impl ArrangerTui {
} }
from_jack!(|jack| ArrangerTui { from_jack!(|jack| ArrangerTui {
let clock = ClockModel::from(jack); let clock = ClockModel::from(jack);
let phrase = Arc::new(RwLock::new(Phrase::new( let phrase = Arc::new(RwLock::new(MidiClip::new(
"New", true, 4 * clock.timebase.ppq.get() as usize, "New", true, 4 * clock.timebase.ppq.get() as usize,
None, Some(ItemColor::random().into()) None, Some(ItemColor::random().into())
))); )));
@ -97,7 +97,7 @@ render!(<Tui>|self: ArrangerTui|{
let pool_size = if self.phrases.visible { self.splits[1] } else { 0 }; let pool_size = if self.phrases.visible { self.splits[1] } else { 0 };
let with_pool = |x|Split::left(false, pool_size, PoolView(&self.phrases), x); let with_pool = |x|Split::left(false, pool_size, PoolView(&self.phrases), x);
let status = ArrangerStatus::from(self); let status = ArrangerStatus::from(self);
let with_editbar = |x|Tui::split_n(false, 3, PhraseEditStatus(&self.editor), x); let with_editbar = |x|Tui::split_n(false, 3, MidiEditStatus(&self.editor), x);
let with_status = |x|Tui::split_n(false, 2, status, x); let with_status = |x|Tui::split_n(false, 2, status, x);
let with_size = |x|lay!([&self.size, x]); let with_size = |x|lay!([&self.size, x]);
let arranger = ||lay!(|add|{ let arranger = ||lay!(|add|{

View file

@ -10,7 +10,7 @@ pub struct SequencerTui {
pub(crate) clock: ClockModel, pub(crate) clock: ClockModel,
pub(crate) phrases: PoolModel, pub(crate) phrases: PoolModel,
pub(crate) player: MidiPlayer, pub(crate) player: MidiPlayer,
pub(crate) editor: PhraseEditorModel, pub(crate) editor: MidiEditorModel,
pub(crate) size: Measure<Tui>, pub(crate) size: Measure<Tui>,
pub(crate) status: bool, pub(crate) status: bool,
pub(crate) note_buf: Vec<u8>, pub(crate) note_buf: Vec<u8>,
@ -19,14 +19,14 @@ pub struct SequencerTui {
} }
from_jack!(|jack|SequencerTui { from_jack!(|jack|SequencerTui {
let clock = ClockModel::from(jack); let clock = ClockModel::from(jack);
let phrase = Arc::new(RwLock::new(Phrase::new( let phrase = Arc::new(RwLock::new(MidiClip::new(
"New", true, 4 * clock.timebase.ppq.get() as usize, "New", true, 4 * clock.timebase.ppq.get() as usize,
None, Some(ItemColor::random().into()) None, Some(ItemColor::random().into())
))); )));
Self { Self {
_jack: jack.clone(), _jack: jack.clone(),
phrases: PoolModel::from(&phrase), phrases: PoolModel::from(&phrase),
editor: PhraseEditorModel::from(&phrase), editor: MidiEditorModel::from(&phrase),
player: MidiPlayer::from((&clock, &phrase)), player: MidiPlayer::from((&clock, &phrase)),
size: Measure::new(), size: Measure::new(),
midi_buf: vec![vec![];65536], midi_buf: vec![vec![];65536],
@ -44,7 +44,7 @@ render!(<Tui>|self: SequencerTui|{
let with_pool = move|x|Tui::split_w(false, pool_w, pool, x); let with_pool = move|x|Tui::split_w(false, pool_w, pool, x);
let status = SequencerStatus::from(self); let status = SequencerStatus::from(self);
let with_status = |x|Tui::split_n(false, if self.status { 2 } else { 0 }, status, x); let with_status = |x|Tui::split_n(false, if self.status { 2 } else { 0 }, status, x);
let with_editbar = |x|Tui::split_n(false, 3, PhraseEditStatus(&self.editor), x); let with_editbar = |x|Tui::split_n(false, 3, MidiEditStatus(&self.editor), x);
let with_size = |x|lay!([self.size, x]); let with_size = |x|lay!([self.size, x]);
let editor = with_editbar(with_pool(Fill::wh(&self.editor))); let editor = with_editbar(with_pool(Fill::wh(&self.editor)));
let color = self.player.play_phrase().as_ref().map(|(_,p)| let color = self.player.play_phrase().as_ref().map(|(_,p)|
@ -85,7 +85,7 @@ handle!(<Tui>|self:SequencerTui,input|SequencerCommand::execute_with_state(self,
Clock(ClockCommand), Clock(ClockCommand),
Phrases(PoolCommand), Phrases(PoolCommand),
Editor(PhraseCommand), Editor(PhraseCommand),
Enqueue(Option<Arc<RwLock<Phrase>>>), Enqueue(Option<Arc<RwLock<MidiClip>>>),
} }
input_to_command!(SequencerCommand: <Tui>|state: SequencerTui, input|match input.event() { input_to_command!(SequencerCommand: <Tui>|state: SequencerTui, input|match input.event() {
// TODO: k: toggle on-screen keyboard // TODO: k: toggle on-screen keyboard

View file

@ -39,9 +39,9 @@ pub enum ArrangerSceneCommand {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum ArrangerClipCommand { pub enum ArrangerClipCommand {
Get(usize, usize), Get(usize, usize),
Put(usize, usize, Option<Arc<RwLock<Phrase>>>), Put(usize, usize, Option<Arc<RwLock<MidiClip>>>),
Enqueue(usize, usize), Enqueue(usize, usize),
Edit(Option<Arc<RwLock<Phrase>>>), Edit(Option<Arc<RwLock<MidiClip>>>),
SetLoop(usize, usize, bool), SetLoop(usize, usize, bool),
SetColor(usize, usize, ItemPalette), SetColor(usize, usize, ItemPalette),
} }

View file

@ -30,7 +30,7 @@ impl ArrangerTui {
/// Name of scene /// Name of scene
pub(crate) name: Arc<RwLock<String>>, pub(crate) name: Arc<RwLock<String>>,
/// Clips in scene, one per track /// Clips in scene, one per track
pub(crate) clips: Vec<Option<Arc<RwLock<Phrase>>>>, pub(crate) clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
/// Identifying color of scene /// Identifying color of scene
pub(crate) color: ItemPalette, pub(crate) color: ItemPalette,
} }
@ -38,7 +38,7 @@ impl ArrangerScene {
pub fn name (&self) -> &Arc<RwLock<String>> { pub fn name (&self) -> &Arc<RwLock<String>> {
&self.name &self.name
} }
pub fn clips (&self) -> &Vec<Option<Arc<RwLock<Phrase>>>> { pub fn clips (&self) -> &Vec<Option<Arc<RwLock<MidiClip>>>> {
&self.clips &self.clips
} }
pub fn color (&self) -> ItemPalette { pub fn color (&self) -> ItemPalette {
@ -85,7 +85,7 @@ impl ArrangerScene {
None => true None => true
}) })
} }
pub fn clip (&self, index: usize) -> Option<&Arc<RwLock<Phrase>>> { pub fn clip (&self, index: usize) -> Option<&Arc<RwLock<MidiClip>>> {
match self.clips().get(index) { Some(Some(clip)) => Some(clip), _ => None } match self.clips().get(index) { Some(Some(clip)) => Some(clip), _ => None }
} }
} }

View file

@ -41,7 +41,7 @@ impl<'a> ArrangerVClips<'a> {
Fixed::wh(w, h, Layers::new(move |add|{ Fixed::wh(w, h, Layers::new(move |add|{
let mut bg = TuiTheme::border_bg(); let mut bg = TuiTheme::border_bg();
if let Some(Some(phrase)) = scene.clips.get(index) { if let Some(Some(phrase)) = scene.clips.get(index) {
let name = &(phrase as &Arc<RwLock<Phrase>>).read().unwrap().name; let name = &(phrase as &Arc<RwLock<MidiClip>>).read().unwrap().name;
let name = format!("{}", name); let name = format!("{}", name);
let max_w = name.len().min((w as usize).saturating_sub(2)); let max_w = name.len().min((w as usize).saturating_sub(2));
let color = phrase.read().unwrap().color; let color = phrase.read().unwrap().color;

View file

@ -3,13 +3,13 @@ use KeyCode::{Char, Up, Down, Left, Right, Enter};
use PhraseCommand::*; use PhraseCommand::*;
pub trait HasEditor { pub trait HasEditor {
fn editor (&self) -> &PhraseEditorModel; fn editor (&self) -> &MidiEditorModel;
} }
#[macro_export] macro_rules! has_editor { #[macro_export] macro_rules! has_editor {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasEditor for $Struct $(<$($L),*$($T),*>)? { impl $(<$($L),*$($T $(: $U)?),*>)? HasEditor for $Struct $(<$($L),*$($T),*>)? {
fn editor (&$self) -> &PhraseEditorModel { &$cb } fn editor (&$self) -> &MidiEditorModel { &$cb }
} }
} }
} }
@ -26,11 +26,11 @@ pub enum PhraseCommand {
SetTimeScroll(usize), SetTimeScroll(usize),
SetTimeZoom(usize), SetTimeZoom(usize),
SetTimeLock(bool), SetTimeLock(bool),
Show(Option<Arc<RwLock<Phrase>>>), Show(Option<Arc<RwLock<MidiClip>>>),
} }
event_map_input_to_command!(Tui: PhraseEditorModel: PhraseCommand: PhraseEditorModel::KEYS); event_map_input_to_command!(Tui: MidiEditorModel: PhraseCommand: MidiEditorModel::KEYS);
impl PhraseEditorModel { impl MidiEditorModel {
const KEYS: [(TuiEvent, &'static dyn Fn(&Self)->PhraseCommand);31] = [ const KEYS: [(TuiEvent, &'static dyn Fn(&Self)->PhraseCommand);31] = [
(kexp!(Ctrl-Alt-Up), &|s: &Self|SetNoteScroll(s.note_point() + 3)), (kexp!(Ctrl-Alt-Up), &|s: &Self|SetNoteScroll(s.note_point() + 3)),
(kexp!(Ctrl-Alt-Down), &|s: &Self|SetNoteScroll(s.note_point().saturating_sub(3))), (kexp!(Ctrl-Alt-Down), &|s: &Self|SetNoteScroll(s.note_point().saturating_sub(3))),
@ -71,8 +71,8 @@ impl PhraseEditorModel {
} }
} }
impl Command<PhraseEditorModel> for PhraseCommand { impl Command<MidiEditorModel> for PhraseCommand {
fn execute (self, state: &mut PhraseEditorModel) -> Perhaps<Self> { fn execute (self, state: &mut MidiEditorModel) -> Perhaps<Self> {
use PhraseCommand::*; use PhraseCommand::*;
match self { match self {
Show(phrase) => { state.set_phrase(phrase.as_ref()); }, Show(phrase) => { state.set_phrase(phrase.as_ref()); },
@ -92,13 +92,13 @@ impl Command<PhraseEditorModel> for PhraseCommand {
} }
/// Contains state for viewing and editing a phrase /// Contains state for viewing and editing a phrase
pub struct PhraseEditorModel { pub struct MidiEditorModel {
/// Renders the phrase /// Renders the phrase
pub mode: Box<dyn PhraseViewMode>, pub mode: Box<dyn PhraseViewMode>,
pub size: Measure<Tui> pub size: Measure<Tui>
} }
impl Default for PhraseEditorModel { impl Default for MidiEditorModel {
fn default () -> Self { fn default () -> Self {
let mut mode = Box::new(PianoHorizontal::new(None)); let mut mode = Box::new(PianoHorizontal::new(None));
mode.redraw(); mode.redraw();
@ -106,28 +106,28 @@ impl Default for PhraseEditorModel {
} }
} }
has_size!(<Tui>|self:PhraseEditorModel|&self.size); has_size!(<Tui>|self:MidiEditorModel|&self.size);
render!(<Tui>|self: PhraseEditorModel|{ render!(<Tui>|self: MidiEditorModel|{
self.autoscroll(); self.autoscroll();
self.autozoom(); self.autozoom();
&self.mode &self.mode
}); });
//render!(<Tui>|self: PhraseEditorModel|lay!(|add|{add(&self.size)?;add(self.mode)}));//bollocks //render!(<Tui>|self: MidiEditorModel|lay!(|add|{add(&self.size)?;add(self.mode)}));//bollocks
pub trait PhraseViewMode: Render<Tui> + HasSize<Tui> + MidiRange + MidiPoint + Debug + Send + Sync { pub trait PhraseViewMode: Render<Tui> + HasSize<Tui> + MidiRange + MidiPoint + Debug + Send + Sync {
fn buffer_size (&self, phrase: &Phrase) -> (usize, usize); fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize);
fn redraw (&mut self); fn redraw (&mut self);
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>>; fn phrase (&self) -> &Option<Arc<RwLock<MidiClip>>>;
fn phrase_mut (&mut self) -> &mut Option<Arc<RwLock<Phrase>>>; fn phrase_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>>;
fn set_phrase (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) { fn set_phrase (&mut self, phrase: Option<&Arc<RwLock<MidiClip>>>) {
*self.phrase_mut() = phrase.map(|p|p.clone()); *self.phrase_mut() = phrase.map(|p|p.clone());
self.redraw(); self.redraw();
} }
} }
impl MidiView<Tui> for PhraseEditorModel {} impl MidiView<Tui> for MidiEditorModel {}
impl MidiRange for PhraseEditorModel { impl MidiRange for MidiEditorModel {
fn time_len (&self) -> &AtomicUsize { self.mode.time_len() } fn time_len (&self) -> &AtomicUsize { self.mode.time_len() }
fn time_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() } fn time_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() }
fn time_lock (&self) -> &AtomicBool { self.mode.time_lock() } fn time_lock (&self) -> &AtomicBool { self.mode.time_lock() }
@ -137,7 +137,7 @@ impl MidiRange for PhraseEditorModel {
fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() } fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() }
} }
impl MidiPoint for PhraseEditorModel { impl MidiPoint for MidiEditorModel {
fn note_len (&self) -> usize { self.mode.note_len()} fn note_len (&self) -> usize { self.mode.note_len()}
fn set_note_len (&self, x: usize) { self.mode.set_note_len(x) } fn set_note_len (&self, x: usize) { self.mode.set_note_len(x) }
fn note_point (&self) -> usize { self.mode.note_point() } fn note_point (&self) -> usize { self.mode.note_point() }
@ -146,25 +146,25 @@ impl MidiPoint for PhraseEditorModel {
fn set_time_point (&self, x: usize) { self.mode.set_time_point(x) } fn set_time_point (&self, x: usize) { self.mode.set_time_point(x) }
} }
impl PhraseViewMode for PhraseEditorModel { impl PhraseViewMode for MidiEditorModel {
fn buffer_size (&self, phrase: &Phrase) -> (usize, usize) { fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize) {
self.mode.buffer_size(phrase) self.mode.buffer_size(phrase)
} }
fn redraw (&mut self) { fn redraw (&mut self) {
self.mode.redraw() self.mode.redraw()
} }
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>> { fn phrase (&self) -> &Option<Arc<RwLock<MidiClip>>> {
self.mode.phrase() self.mode.phrase()
} }
fn phrase_mut (&mut self) -> &mut Option<Arc<RwLock<Phrase>>> { fn phrase_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>> {
self.mode.phrase_mut() self.mode.phrase_mut()
} }
fn set_phrase (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) { fn set_phrase (&mut self, phrase: Option<&Arc<RwLock<MidiClip>>>) {
self.mode.set_phrase(phrase) self.mode.set_phrase(phrase)
} }
} }
impl PhraseEditorModel { impl MidiEditorModel {
/// Put note at current position /// Put note at current position
pub fn put_note (&mut self, advance: bool) { pub fn put_note (&mut self, advance: bool) {
let mut redraw = false; let mut redraw = false;
@ -197,22 +197,22 @@ impl PhraseEditorModel {
} }
} }
from!(|phrase: &Arc<RwLock<Phrase>>|PhraseEditorModel = { from!(|phrase: &Arc<RwLock<MidiClip>>|MidiEditorModel = {
let mut model = Self::from(Some(phrase.clone())); let mut model = Self::from(Some(phrase.clone()));
model.redraw(); model.redraw();
model model
}); });
from!(|phrase: Option<Arc<RwLock<Phrase>>>|PhraseEditorModel = { from!(|phrase: Option<Arc<RwLock<MidiClip>>>|MidiEditorModel = {
let mut model = Self::default(); let mut model = Self::default();
*model.phrase_mut() = phrase; *model.phrase_mut() = phrase;
model.redraw(); model.redraw();
model model
}); });
impl std::fmt::Debug for PhraseEditorModel { impl std::fmt::Debug for MidiEditorModel {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("PhraseEditorModel") f.debug_struct("MidiEditorModel")
.field("mode", &self.mode) .field("mode", &self.mode)
.finish() .finish()
} }

View file

@ -12,7 +12,7 @@ pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iter
/// A phrase, rendered as a horizontal piano roll. /// A phrase, rendered as a horizontal piano roll.
pub struct PianoHorizontal { pub struct PianoHorizontal {
phrase: Option<Arc<RwLock<Phrase>>>, phrase: Option<Arc<RwLock<MidiClip>>>,
/// Buffer where the whole phrase is rerendered on change /// Buffer where the whole phrase is rerendered on change
buffer: BigBuffer, buffer: BigBuffer,
/// Size of actual notes area /// Size of actual notes area
@ -26,7 +26,7 @@ pub struct PianoHorizontal {
} }
impl PianoHorizontal { impl PianoHorizontal {
pub fn new (phrase: Option<&Arc<RwLock<Phrase>>>) -> Self { pub fn new (phrase: Option<&Arc<RwLock<MidiClip>>>) -> Self {
let size = Measure::new(); let size = Measure::new();
let mut range = MidiRangeModel::from((24, true)); let mut range = MidiRangeModel::from((24, true));
range.time_axis = size.x.clone(); range.time_axis = size.x.clone();
@ -64,7 +64,7 @@ render!(<Tui>|self: PianoHorizontal|{
impl PianoHorizontal { impl PianoHorizontal {
/// Draw the piano roll foreground using full blocks on note on and half blocks on legato: █▄ █▄ █▄ /// Draw the piano roll foreground using full blocks on note on and half blocks on legato: █▄ █▄ █▄
fn draw_bg (buf: &mut BigBuffer, phrase: &Phrase, zoom: usize, note_len: usize) { fn draw_bg (buf: &mut BigBuffer, phrase: &MidiClip, zoom: usize, note_len: usize) {
for (y, note) in (0..127).rev().enumerate() { for (y, note) in (0..127).rev().enumerate() {
for (x, time) in (0..buf.width).map(|x|(x, x*zoom)) { for (x, time) in (0..buf.width).map(|x|(x, x*zoom)) {
let cell = buf.get_mut(x, y).unwrap(); let cell = buf.get_mut(x, y).unwrap();
@ -85,7 +85,7 @@ impl PianoHorizontal {
} }
} }
/// Draw the piano roll background using full blocks on note on and half blocks on legato: █▄ █▄ █▄ /// Draw the piano roll background using full blocks on note on and half blocks on legato: █▄ █▄ █▄
fn draw_fg (buf: &mut BigBuffer, phrase: &Phrase, zoom: usize) { fn draw_fg (buf: &mut BigBuffer, phrase: &MidiClip, zoom: usize) {
let style = Style::default().fg(phrase.color.base.rgb);//.bg(Color::Rgb(0, 0, 0)); let style = Style::default().fg(phrase.color.base.rgb);//.bg(Color::Rgb(0, 0, 0));
let mut notes_on = [false;128]; let mut notes_on = [false;128];
for (x, time_start) in (0..phrase.length).step_by(zoom).enumerate() { for (x, time_start) in (0..phrase.length).step_by(zoom).enumerate() {
@ -142,14 +142,14 @@ impl MidiPoint for PianoHorizontal {
fn set_time_point (&self, x: usize) { self.point.set_time_point(x) } fn set_time_point (&self, x: usize) { self.point.set_time_point(x) }
} }
impl PhraseViewMode for PianoHorizontal { impl PhraseViewMode for PianoHorizontal {
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>> { fn phrase (&self) -> &Option<Arc<RwLock<MidiClip>>> {
&self.phrase &self.phrase
} }
fn phrase_mut (&mut self) -> &mut Option<Arc<RwLock<Phrase>>> { fn phrase_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>> {
&mut self.phrase &mut self.phrase
} }
/// Determine the required space to render the phrase. /// Determine the required space to render the phrase.
fn buffer_size (&self, phrase: &Phrase) -> (usize, usize) { fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize) {
(phrase.length / self.range.time_zoom().get(), 128) (phrase.length / self.range.time_zoom().get(), 128)
} }
fn redraw (&mut self) { fn redraw (&mut self) {
@ -168,7 +168,7 @@ impl PhraseViewMode for PianoHorizontal {
}; };
self.buffer = buffer self.buffer = buffer
} }
fn set_phrase (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) { fn set_phrase (&mut self, phrase: Option<&Arc<RwLock<MidiClip>>>) {
*self.phrase_mut() = phrase.map(|p|p.clone()); *self.phrase_mut() = phrase.map(|p|p.clone());
self.color = phrase.map(|p|p.read().unwrap().color.clone()) self.color = phrase.map(|p|p.read().unwrap().color.clone())
.unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64)))); .unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64))));

View file

@ -12,7 +12,7 @@ use FileBrowserCommand as Browse;
pub struct PoolModel { pub struct PoolModel {
pub(crate) visible: bool, pub(crate) visible: bool,
/// Collection of phrases /// Collection of phrases
pub(crate) phrases: Vec<Arc<RwLock<Phrase>>>, pub(crate) phrases: Vec<Arc<RwLock<MidiClip>>>,
/// Selected phrase /// Selected phrase
pub(crate) phrase: AtomicUsize, pub(crate) phrase: AtomicUsize,
/// Mode switch /// Mode switch
@ -149,10 +149,10 @@ fn to_phrases_command (state: &PoolModel, input: &TuiInput) -> Option<PoolComman
} else { } else {
return None return None
}, },
key_pat!(Char('a')) | key_pat!(Shift-Char('A')) => Cmd::Phrase(Pool::Add(count, Phrase::new( key_pat!(Char('a')) | key_pat!(Shift-Char('A')) => Cmd::Phrase(Pool::Add(count, MidiClip::new(
String::from("(new)"), true, 4 * PPQ, None, Some(ItemPalette::random()) String::from("(new)"), true, 4 * PPQ, None, Some(ItemPalette::random())
))), ))),
key_pat!(Char('i')) => Cmd::Phrase(Pool::Add(index + 1, Phrase::new( key_pat!(Char('i')) => Cmd::Phrase(Pool::Add(index + 1, MidiClip::new(
String::from("(new)"), true, 4 * PPQ, None, Some(ItemPalette::random()) String::from("(new)"), true, 4 * PPQ, None, Some(ItemPalette::random())
))), ))),
key_pat!(Char('d')) | key_pat!(Shift-Char('D')) => { key_pat!(Char('d')) | key_pat!(Shift-Char('D')) => {
@ -167,7 +167,7 @@ impl Default for PoolModel {
fn default () -> Self { fn default () -> Self {
Self { Self {
visible: true, visible: true,
phrases: vec![RwLock::new(Phrase::default()).into()], phrases: vec![RwLock::new(MidiClip::default()).into()],
phrase: 0.into(), phrase: 0.into(),
scroll: 0, scroll: 0,
mode: None, mode: None,
@ -175,7 +175,7 @@ impl Default for PoolModel {
} }
} }
} }
from!(|phrase:&Arc<RwLock<Phrase>>|PoolModel = { from!(|phrase:&Arc<RwLock<MidiClip>>|PoolModel = {
let mut model = Self::default(); let mut model = Self::default();
model.phrases.push(phrase.clone()); model.phrases.push(phrase.clone());
model.phrase.store(1, Relaxed); model.phrase.store(1, Relaxed);
@ -214,7 +214,7 @@ render!(<Tui>|self: PoolView<'a>|{
Some(PoolMode::Export(_, ref file_picker)) => add(file_picker), Some(PoolMode::Export(_, ref file_picker)) => add(file_picker),
_ => Ok(for (i, phrase) in phrases.iter().enumerate() { _ => Ok(for (i, phrase) in phrases.iter().enumerate() {
add(&lay!(|add|{ add(&lay!(|add|{
let Phrase { ref name, color, length, .. } = *phrase.read().unwrap(); let MidiClip { ref name, color, length, .. } = *phrase.read().unwrap();
let mut length = PhraseLength::new(length, None); let mut length = PhraseLength::new(length, None);
if let Some(PoolMode::Length(phrase, new_length, focus)) = mode { if let Some(PoolMode::Length(phrase, new_length, focus)) = mode {
if i == *phrase { if i == *phrase {
@ -267,7 +267,7 @@ impl PhraseSelector {
// beats elapsed // beats elapsed
pub fn play_phrase <T: HasPlayPhrase + HasClock> (state: &T) -> Self { pub fn play_phrase <T: HasPlayPhrase + HasClock> (state: &T) -> Self {
let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() { let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() {
let Phrase { ref name, color, .. } = *phrase.read().unwrap(); let MidiClip { ref name, color, .. } = *phrase.read().unwrap();
(name.clone(), color) (name.clone(), color)
} else { } else {
("".to_string(), ItemPalette::from(TuiTheme::g(64))) ("".to_string(), ItemPalette::from(TuiTheme::g(64)))
@ -282,7 +282,7 @@ impl PhraseSelector {
// beats until switchover // beats until switchover
pub fn next_phrase <T: HasPlayPhrase> (state: &T) -> Self { pub fn next_phrase <T: HasPlayPhrase> (state: &T) -> Self {
let (time, name, color) = if let Some((t, Some(phrase))) = state.next_phrase() { let (time, name, color) = if let Some((t, Some(phrase))) = state.next_phrase() {
let Phrase { ref name, color, .. } = *phrase.read().unwrap(); let MidiClip { ref name, color, .. } = *phrase.read().unwrap();
let time = { let time = {
let target = t.pulse.get(); let target = t.pulse.get();
let current = state.clock().playhead.pulse.get(); let current = state.clock().playhead.pulse.get();

View file

@ -1,7 +1,7 @@
use crate::*; use crate::*;
pub struct PhraseEditStatus<'a>(pub &'a PhraseEditorModel); pub struct MidiEditStatus<'a>(pub &'a MidiEditorModel);
render!(<Tui>|self:PhraseEditStatus<'a>|{ render!(<Tui>|self:MidiEditStatus<'a>|{
let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) { let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) {
(phrase.color, phrase.name.clone(), phrase.length, phrase.looped) (phrase.color, phrase.name.clone(), phrase.length, phrase.looped)
} else { } else {