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
pub(crate) clock: ClockModel,
/// 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
pub(crate) next_phrase: Option<(Moment, Option<Arc<RwLock<Phrase>>>)>,
pub(crate) next_phrase: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
/// Play input through output.
pub(crate) monitoring: bool,
/// Write input to sequence.
@ -125,7 +125,7 @@ from!(|clock: &ClockModel| MidiPlayer = Self {
notes_in: 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 mut model = Self::from(clock);
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 {
&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
}
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
}
fn next_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<Phrase>>>)> {
fn next_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<MidiClip>>>)> {
&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
}
}

View file

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

View file

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

View file

@ -66,7 +66,7 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts {
note_buf: &mut Vec<u8>,
out: &mut [Vec<Vec<u8>>],
started: &Moment,
phrase: &Option<Arc<RwLock<Phrase>>>
phrase: &Option<Arc<RwLock<MidiClip>>>
) -> bool {
// First sample to populate. Greater than 0 means that the first
// 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 (
phrase: &RwLock<Phrase>,
phrase: &RwLock<MidiClip>,
pulse: usize,
sample: usize,
note_buf: &mut Vec<u8>,

View file

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

View file

@ -13,7 +13,7 @@ pub struct ArrangerTui {
pub size: Measure<Tui>,
pub note_buf: Vec<u8>,
pub midi_buf: Vec<Vec<Vec<u8>>>,
pub editor: PhraseEditorModel,
pub editor: MidiEditorModel,
pub perf: PerfModel,
}
impl ArrangerTui {
@ -40,7 +40,7 @@ impl ArrangerTui {
};
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()
}
pub fn toggle_loop (&mut self) {
@ -61,7 +61,7 @@ impl ArrangerTui {
}
from_jack!(|jack| ArrangerTui {
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,
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 with_pool = |x|Split::left(false, pool_size, PoolView(&self.phrases), x);
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_size = |x|lay!([&self.size, x]);
let arranger = ||lay!(|add|{

View file

@ -10,7 +10,7 @@ pub struct SequencerTui {
pub(crate) clock: ClockModel,
pub(crate) phrases: PoolModel,
pub(crate) player: MidiPlayer,
pub(crate) editor: PhraseEditorModel,
pub(crate) editor: MidiEditorModel,
pub(crate) size: Measure<Tui>,
pub(crate) status: bool,
pub(crate) note_buf: Vec<u8>,
@ -19,14 +19,14 @@ pub struct SequencerTui {
}
from_jack!(|jack|SequencerTui {
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,
None, Some(ItemColor::random().into())
)));
Self {
_jack: jack.clone(),
phrases: PoolModel::from(&phrase),
editor: PhraseEditorModel::from(&phrase),
editor: MidiEditorModel::from(&phrase),
player: MidiPlayer::from((&clock, &phrase)),
size: Measure::new(),
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 status = SequencerStatus::from(self);
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 editor = with_editbar(with_pool(Fill::wh(&self.editor)));
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),
Phrases(PoolCommand),
Editor(PhraseCommand),
Enqueue(Option<Arc<RwLock<Phrase>>>),
Enqueue(Option<Arc<RwLock<MidiClip>>>),
}
input_to_command!(SequencerCommand: <Tui>|state: SequencerTui, input|match input.event() {
// TODO: k: toggle on-screen keyboard

View file

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

View file

@ -30,7 +30,7 @@ impl ArrangerTui {
/// Name of scene
pub(crate) name: Arc<RwLock<String>>,
/// 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
pub(crate) color: ItemPalette,
}
@ -38,7 +38,7 @@ impl ArrangerScene {
pub fn name (&self) -> &Arc<RwLock<String>> {
&self.name
}
pub fn clips (&self) -> &Vec<Option<Arc<RwLock<Phrase>>>> {
pub fn clips (&self) -> &Vec<Option<Arc<RwLock<MidiClip>>>> {
&self.clips
}
pub fn color (&self) -> ItemPalette {
@ -85,7 +85,7 @@ impl ArrangerScene {
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 }
}
}

View file

@ -41,7 +41,7 @@ impl<'a> ArrangerVClips<'a> {
Fixed::wh(w, h, Layers::new(move |add|{
let mut bg = TuiTheme::border_bg();
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 max_w = name.len().min((w as usize).saturating_sub(2));
let color = phrase.read().unwrap().color;

View file

@ -3,13 +3,13 @@ use KeyCode::{Char, Up, Down, Left, Right, Enter};
use PhraseCommand::*;
pub trait HasEditor {
fn editor (&self) -> &PhraseEditorModel;
fn editor (&self) -> &MidiEditorModel;
}
#[macro_export] macro_rules! has_editor {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
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),
SetTimeZoom(usize),
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] = [
(kexp!(Ctrl-Alt-Up), &|s: &Self|SetNoteScroll(s.note_point() + 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 {
fn execute (self, state: &mut PhraseEditorModel) -> Perhaps<Self> {
impl Command<MidiEditorModel> for PhraseCommand {
fn execute (self, state: &mut MidiEditorModel) -> Perhaps<Self> {
use PhraseCommand::*;
match self {
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
pub struct PhraseEditorModel {
pub struct MidiEditorModel {
/// Renders the phrase
pub mode: Box<dyn PhraseViewMode>,
pub size: Measure<Tui>
}
impl Default for PhraseEditorModel {
impl Default for MidiEditorModel {
fn default () -> Self {
let mut mode = Box::new(PianoHorizontal::new(None));
mode.redraw();
@ -106,28 +106,28 @@ impl Default for PhraseEditorModel {
}
}
has_size!(<Tui>|self:PhraseEditorModel|&self.size);
render!(<Tui>|self: PhraseEditorModel|{
has_size!(<Tui>|self:MidiEditorModel|&self.size);
render!(<Tui>|self: MidiEditorModel|{
self.autoscroll();
self.autozoom();
&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 {
fn buffer_size (&self, phrase: &Phrase) -> (usize, usize);
fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize);
fn redraw (&mut self);
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>>;
fn phrase_mut (&mut self) -> &mut Option<Arc<RwLock<Phrase>>>;
fn set_phrase (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
fn phrase (&self) -> &Option<Arc<RwLock<MidiClip>>>;
fn phrase_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>>;
fn set_phrase (&mut self, phrase: Option<&Arc<RwLock<MidiClip>>>) {
*self.phrase_mut() = phrase.map(|p|p.clone());
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_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() }
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() }
}
impl MidiPoint for PhraseEditorModel {
impl MidiPoint for MidiEditorModel {
fn note_len (&self) -> usize { self.mode.note_len()}
fn set_note_len (&self, x: usize) { self.mode.set_note_len(x) }
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) }
}
impl PhraseViewMode for PhraseEditorModel {
fn buffer_size (&self, phrase: &Phrase) -> (usize, usize) {
impl PhraseViewMode for MidiEditorModel {
fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize) {
self.mode.buffer_size(phrase)
}
fn redraw (&mut self) {
self.mode.redraw()
}
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>> {
fn phrase (&self) -> &Option<Arc<RwLock<MidiClip>>> {
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()
}
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)
}
}
impl PhraseEditorModel {
impl MidiEditorModel {
/// Put note at current position
pub fn put_note (&mut self, advance: bool) {
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()));
model.redraw();
model
});
from!(|phrase: Option<Arc<RwLock<Phrase>>>|PhraseEditorModel = {
from!(|phrase: Option<Arc<RwLock<MidiClip>>>|MidiEditorModel = {
let mut model = Self::default();
*model.phrase_mut() = phrase;
model.redraw();
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> {
f.debug_struct("PhraseEditorModel")
f.debug_struct("MidiEditorModel")
.field("mode", &self.mode)
.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.
pub struct PianoHorizontal {
phrase: Option<Arc<RwLock<Phrase>>>,
phrase: Option<Arc<RwLock<MidiClip>>>,
/// Buffer where the whole phrase is rerendered on change
buffer: BigBuffer,
/// Size of actual notes area
@ -26,7 +26,7 @@ pub struct 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 mut range = MidiRangeModel::from((24, true));
range.time_axis = size.x.clone();
@ -64,7 +64,7 @@ render!(<Tui>|self: PianoHorizontal|{
impl PianoHorizontal {
/// 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 (x, time) in (0..buf.width).map(|x|(x, x*zoom)) {
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: █▄ █▄ █▄
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 mut notes_on = [false;128];
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) }
}
impl PhraseViewMode for PianoHorizontal {
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>> {
fn phrase (&self) -> &Option<Arc<RwLock<MidiClip>>> {
&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
}
/// 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)
}
fn redraw (&mut self) {
@ -168,7 +168,7 @@ impl PhraseViewMode for PianoHorizontal {
};
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.color = phrase.map(|p|p.read().unwrap().color.clone())
.unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64))));

View file

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

View file

@ -1,7 +1,7 @@
use crate::*;
pub struct PhraseEditStatus<'a>(pub &'a PhraseEditorModel);
render!(<Tui>|self:PhraseEditStatus<'a>|{
pub struct MidiEditStatus<'a>(pub &'a MidiEditorModel);
render!(<Tui>|self:MidiEditStatus<'a>|{
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)
} else {