mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-09 05:06:43 +01:00
wip: separate PhrasePlayer vs PhraseEditor
This commit is contained in:
parent
7668a6f339
commit
25e54eba4e
8 changed files with 491 additions and 636 deletions
|
|
@ -3,23 +3,20 @@ use crate::*;
|
|||
/// Root level object for standalone `tek_arranger`
|
||||
pub struct Arranger<E: Engine> {
|
||||
/// Controls the JACK transport.
|
||||
pub transport: Option<Arc<RwLock<TransportToolbar<E>>>>,
|
||||
pub transport: Option<Arc<RwLock<TransportToolbar<E>>>>,
|
||||
/// Contains all the sequencers.
|
||||
pub arrangement: Arrangement<E>,
|
||||
pub arrangement: Arrangement<E>,
|
||||
/// Pool of all phrases in the arrangement
|
||||
pub phrases: Arc<RwLock<PhrasePool<E>>>,
|
||||
/// Phrase editor view
|
||||
pub editor: PhraseEditor<E>,
|
||||
/// This allows the sequencer view to be moved or hidden.
|
||||
pub show_sequencer: Option<tek_core::Direction>,
|
||||
pub show_sequencer: Option<tek_core::Direction>,
|
||||
/// Index of currently focused component
|
||||
pub focus: usize,
|
||||
/// Focus target that passes events down to sequencer
|
||||
pub sequencer_proxy: SequencerProxy<E>,
|
||||
pub focus: usize,
|
||||
/// Slot for modal dialog displayed on top of app.
|
||||
pub modal: Option<Box<dyn ContentComponent<E>>>,
|
||||
|
||||
|
||||
pub phrases: Arc<RwLock<PhrasePool<E>>>,
|
||||
pub editor: PhraseEditor<E>,
|
||||
pub modal: Option<Box<dyn ContentComponent<E>>>,
|
||||
}
|
||||
|
||||
/// Represents the tracks and scenes of the composition.
|
||||
pub struct Arrangement<E: Engine> {
|
||||
/// Name of arranger
|
||||
|
|
@ -27,23 +24,77 @@ pub struct Arrangement<E: Engine> {
|
|||
/// Collection of phrases.
|
||||
pub phrases: Arc<RwLock<PhrasePool<E>>>,
|
||||
/// Collection of tracks.
|
||||
pub tracks: Vec<Sequencer<E>>,
|
||||
pub tracks: Vec<ArrangementTrack<E>>,
|
||||
/// Collection of scenes.
|
||||
pub scenes: Vec<Scene>,
|
||||
/// Currently selected element.
|
||||
pub selected: ArrangerFocus,
|
||||
pub selected: ArrangementFocus,
|
||||
/// Display mode of arranger
|
||||
pub mode: ArrangerViewMode,
|
||||
pub mode: ArrangementViewMode,
|
||||
/// Whether the arranger is currently focused
|
||||
pub focused: bool
|
||||
}
|
||||
|
||||
/// Represents a track in the arrangement
|
||||
pub struct ArrangementTrack<E: Engine> {
|
||||
/// Name of track
|
||||
pub name: Arc<RwLock<String>>,
|
||||
/// Inputs
|
||||
pub inputs: Vec<()>,
|
||||
/// MIDI player/recorder
|
||||
pub player: PhrasePlayer<E>,
|
||||
/// Outputs
|
||||
pub outputs: Vec<()>,
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct Scene {
|
||||
pub name: Arc<RwLock<String>>,
|
||||
pub clips: Vec<Option<usize>>,
|
||||
}
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
/// Represents the current user selection in the arranger
|
||||
pub enum ArrangementFocus {
|
||||
/** The whole mix is selected */
|
||||
Mix,
|
||||
/// A track is selected.
|
||||
Track(usize),
|
||||
/// A scene is selected.
|
||||
Scene(usize),
|
||||
/// A clip (track × scene) is selected.
|
||||
Clip(usize, usize),
|
||||
}
|
||||
/// Display mode of arranger
|
||||
#[derive(PartialEq)]
|
||||
pub enum ArrangementViewMode {
|
||||
Horizontal,
|
||||
Vertical(usize),
|
||||
}
|
||||
/// A collection of phrases to play on each track.
|
||||
pub struct VerticalArranger<'a, E: Engine>(
|
||||
pub &'a Arrangement<E>, pub usize
|
||||
);
|
||||
pub struct VerticalArrangerGrid<'a>(
|
||||
pub u16, pub &'a [(usize, usize)], pub &'a [(usize, usize)]
|
||||
);
|
||||
pub struct VerticalArrangerCursor<'a>(
|
||||
pub bool, pub ArrangementFocus, pub u16, pub &'a [(usize, usize)], pub &'a [(usize, usize)],
|
||||
);
|
||||
pub struct HorizontalArranger<'a, E: Engine>(
|
||||
pub &'a Arrangement<E>
|
||||
);
|
||||
pub struct ArrangerRenameModal<E: Engine> {
|
||||
_engine: std::marker::PhantomData<E>,
|
||||
pub done: bool,
|
||||
pub target: ArrangementFocus,
|
||||
pub value: String,
|
||||
pub result: Arc<RwLock<String>>,
|
||||
pub cursor: usize
|
||||
}
|
||||
impl<E: Engine> Arrangement<E> {
|
||||
pub fn new (name: &str, phrases: &Arc<RwLock<PhrasePool<E>>>) -> Self {
|
||||
Self {
|
||||
name: Arc::new(RwLock::new(name.into())),
|
||||
mode: ArrangerViewMode::Vertical(2),
|
||||
selected: ArrangerFocus::Clip(0, 0),
|
||||
mode: ArrangementViewMode::Vertical(2),
|
||||
selected: ArrangementFocus::Clip(0, 0),
|
||||
phrases: phrases.clone(),
|
||||
scenes: vec![],
|
||||
tracks: vec![],
|
||||
|
|
@ -52,25 +103,25 @@ impl<E: Engine> Arrangement<E> {
|
|||
}
|
||||
pub fn activate (&mut self) {
|
||||
match self.selected {
|
||||
ArrangerFocus::Scene(s) => {
|
||||
ArrangementFocus::Scene(s) => {
|
||||
for (track_index, track) in self.tracks.iter_mut().enumerate() {
|
||||
track.playing_phrase = self.scenes[s].clips[track_index];
|
||||
track.reset = true;
|
||||
track.player.phrase = self.scenes[s].clips[track_index];
|
||||
track.player.reset = true;
|
||||
}
|
||||
},
|
||||
ArrangerFocus::Clip(t, s) => {
|
||||
self.tracks[t].playing_phrase = self.scenes[s].clips[t];
|
||||
self.tracks[t].reset = true;
|
||||
ArrangementFocus::Clip(t, s) => {
|
||||
self.tracks[t].player.phrase = self.scenes[s].clips[t];
|
||||
self.tracks[t].player.reset = true;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
pub fn sequencer (&self) -> Option<&Sequencer<E>> {
|
||||
pub fn sequencer (&self) -> Option<&ArrangementTrack<E>> {
|
||||
self.selected.track()
|
||||
.map(|track|self.tracks.get(track))
|
||||
.flatten()
|
||||
}
|
||||
pub fn sequencer_mut (&mut self) -> Option<&mut Sequencer<E>> {
|
||||
pub fn sequencer_mut (&mut self) -> Option<&mut ArrangementTrack<E>> {
|
||||
self.selected.track()
|
||||
.map(|track|self.tracks.get_mut(track))
|
||||
.flatten()
|
||||
|
|
@ -81,7 +132,7 @@ impl<E: Engine> Arrangement<E> {
|
|||
let scene = self.scenes.get(scene_index);
|
||||
let track = self.tracks.get_mut(track_index);
|
||||
if let (Some(scene), Some(track)) = (scene, track) {
|
||||
track.viewing_phrase = scene.clips[track_index]
|
||||
track.player.phrase = scene.clips[track_index]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -92,17 +143,17 @@ impl<E: Engine> Arrangement<E> {
|
|||
pub fn is_last_row (&self) -> bool {
|
||||
let selected = self.selected;
|
||||
(self.scenes.len() == 0 && (selected.is_mix() || selected.is_track())) || match selected {
|
||||
ArrangerFocus::Scene(s) =>
|
||||
ArrangementFocus::Scene(s) =>
|
||||
s == self.scenes.len() - 1,
|
||||
ArrangerFocus::Clip(_, s) =>
|
||||
ArrangementFocus::Clip(_, s) =>
|
||||
s == self.scenes.len() - 1,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
pub fn track (&self) -> Option<&Sequencer<E>> {
|
||||
pub fn track (&self) -> Option<&PhrasePlayer<E>> {
|
||||
self.selected.track().map(|t|self.tracks.get(t)).flatten()
|
||||
}
|
||||
pub fn track_mut (&mut self) -> Option<&mut Sequencer<E>> {
|
||||
pub fn track_mut (&mut self) -> Option<&mut PhrasePlayer<E>> {
|
||||
self.selected.track().map(|t|self.tracks.get_mut(t)).flatten()
|
||||
}
|
||||
pub fn track_next (&mut self) {
|
||||
|
|
@ -111,10 +162,10 @@ impl<E: Engine> Arrangement<E> {
|
|||
pub fn track_prev (&mut self) {
|
||||
self.selected.track_prev()
|
||||
}
|
||||
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Sequencer<E>> {
|
||||
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut ArrangementTrack<E>> {
|
||||
self.tracks.push(name.map_or_else(
|
||||
|| Sequencer::new(&self.track_default_name()),
|
||||
|name| Sequencer::new(name),
|
||||
|| PhrasePlayer::new(&self.track_default_name()),
|
||||
|name| PhrasePlayer::new(name),
|
||||
));
|
||||
let index = self.tracks.len() - 1;
|
||||
Ok(&mut self.tracks[index])
|
||||
|
|
@ -214,27 +265,32 @@ impl<E: Engine> Arrangement<E> {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
/// Represents the current user selection in the arranger
|
||||
pub enum ArrangerFocus {
|
||||
/** The whole mix is selected */
|
||||
Mix,
|
||||
/// A track is selected.
|
||||
Track(usize),
|
||||
/// A scene is selected.
|
||||
Scene(usize),
|
||||
/// A clip (track × scene) is selected.
|
||||
Clip(usize, usize),
|
||||
impl<E: Engine> ArrangementTrack<E> {
|
||||
pub fn longest_name (tracks: &[Self]) -> usize {
|
||||
tracks.iter()
|
||||
.map(|s|s.name.read().unwrap().len())
|
||||
.fold(0, usize::max)
|
||||
}
|
||||
pub fn clip_name_lengths (tracks: &[Self]) -> Vec<(usize, usize)> {
|
||||
let mut total = 0;
|
||||
let mut lengths: Vec<(usize, usize)> = tracks.iter().map(|track|{
|
||||
let len = 4 + track.phrases
|
||||
.iter()
|
||||
.fold(track.name.read().unwrap().len(), |len, phrase|{
|
||||
len.max(phrase.read().unwrap().name.read().unwrap().len())
|
||||
});
|
||||
total = total + len;
|
||||
(len, total - len)
|
||||
}).collect();
|
||||
lengths.push((0, total));
|
||||
lengths
|
||||
}
|
||||
}
|
||||
|
||||
/// Focus identification methods
|
||||
impl ArrangerFocus {
|
||||
impl ArrangementFocus {
|
||||
pub fn description <E: Engine> (
|
||||
&self,
|
||||
tracks: &Vec<Sequencer<E>>,
|
||||
tracks: &Vec<ArrangementTrack<E>>,
|
||||
scenes: &Vec<Scene>,
|
||||
) -> String {
|
||||
format!("Selected: {}", match self {
|
||||
|
|
@ -342,14 +398,8 @@ impl ArrangerFocus {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Display mode of arranger
|
||||
#[derive(PartialEq)]
|
||||
pub enum ArrangerViewMode { Horizontal, Vertical(usize) }
|
||||
/// Arranger display mode can be cycled
|
||||
impl ArrangerViewMode {
|
||||
impl ArrangementViewMode {
|
||||
/// Cycle arranger display mode
|
||||
pub fn to_next (&mut self) {
|
||||
*self = match self {
|
||||
|
|
@ -361,39 +411,8 @@ impl ArrangerViewMode {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub struct VerticalArranger<'a, E: Engine>(
|
||||
pub &'a Arrangement<E>, pub usize
|
||||
);
|
||||
pub struct VerticalArrangerGrid<'a>(
|
||||
pub u16, pub &'a [(usize, usize)], pub &'a [(usize, usize)]
|
||||
);
|
||||
pub struct VerticalArrangerCursor<'a>(
|
||||
pub bool, pub ArrangerFocus, pub u16, pub &'a [(usize, usize)], pub &'a [(usize, usize)],
|
||||
);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub struct HorizontalArranger<'a, E: Engine>(
|
||||
pub &'a Arrangement<E>
|
||||
);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Appears on first run (i.e. if state dir is missing).
|
||||
pub struct ArrangerRenameModal<E: Engine> {
|
||||
_engine: std::marker::PhantomData<E>,
|
||||
pub done: bool,
|
||||
pub target: ArrangerFocus,
|
||||
pub value: String,
|
||||
pub result: Arc<RwLock<String>>,
|
||||
pub cursor: usize
|
||||
}
|
||||
|
||||
impl<E: Engine> ArrangerRenameModal<E> {
|
||||
pub fn new (target: ArrangerFocus, value: &Arc<RwLock<String>>) -> Self {
|
||||
pub fn new (target: ArrangementFocus, value: &Arc<RwLock<String>>) -> Self {
|
||||
Self {
|
||||
_engine: Default::default(),
|
||||
done: false,
|
||||
|
|
@ -404,52 +423,19 @@ impl<E: Engine> ArrangerRenameModal<E> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine + Send> Exit for ArrangerRenameModal<E> {
|
||||
fn exited (&self) -> bool { self.done }
|
||||
fn exit (&mut self) { self.done = true }
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// A collection of phrases to play on each track.
|
||||
#[derive(Default)]
|
||||
pub struct Scene {
|
||||
pub name: Arc<RwLock<String>>,
|
||||
pub clips: Vec<Option<usize>>,
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually<Self> {
|
||||
let mut name = None;
|
||||
let mut clips = vec![];
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
let key = map.get(&Edn::Key(":name"));
|
||||
if let Some(Edn::Str(n)) = key {
|
||||
name = Some(*n);
|
||||
} else {
|
||||
panic!("unexpected key in scene '{name:?}': {key:?}")
|
||||
}
|
||||
},
|
||||
Edn::Symbol("_") => {
|
||||
clips.push(None);
|
||||
},
|
||||
Edn::Int(i) => {
|
||||
clips.push(Some(*i as usize));
|
||||
},
|
||||
_ => panic!("unexpected in scene '{name:?}': {edn:?}")
|
||||
});
|
||||
let scene = Self::new(name.unwrap_or(""), clips);
|
||||
Ok(scene)
|
||||
}
|
||||
pub fn new (name: impl AsRef<str>, clips: impl AsRef<[Option<usize>]>) -> Self {
|
||||
let name = Arc::new(RwLock::new(name.as_ref().into()));
|
||||
let clips = clips.as_ref().iter().map(|x|x.clone()).collect();
|
||||
Self { name, clips, }
|
||||
Self {
|
||||
name: Arc::new(RwLock::new(name.as_ref().into())),
|
||||
clips: clips.as_ref().iter().map(|x|x.clone()).collect(),
|
||||
}
|
||||
}
|
||||
/// Returns the pulse length of the longest phrase in the scene
|
||||
pub fn pulses <E: Engine> (&self, tracks: &[Sequencer<E>]) -> usize {
|
||||
pub fn pulses <E: Engine> (&self, tracks: &[ArrangementTrack<E>]) -> usize {
|
||||
self.clips.iter().enumerate()
|
||||
.filter_map(|(i, c)|c
|
||||
.map(|c|tracks
|
||||
|
|
@ -462,14 +448,29 @@ impl Scene {
|
|||
.fold(0, |a, p|a.max(p.read().unwrap().length))
|
||||
}
|
||||
/// Returns true if all phrases in the scene are currently playing
|
||||
pub fn is_playing <E: Engine> (&self, tracks: &[Sequencer<E>]) -> bool {
|
||||
pub fn is_playing <E: Engine> (&self, tracks: &[ArrangementTrack<E>]) -> bool {
|
||||
self.clips.iter().enumerate()
|
||||
.all(|(track_index, phrase_index)|match phrase_index {
|
||||
Some(i) => tracks
|
||||
.get(track_index)
|
||||
.map(|track|track.playing_phrase == Some(*i))
|
||||
.map(|track|track.player.phrase == Some(*i))
|
||||
.unwrap_or(false),
|
||||
None => true
|
||||
})
|
||||
}
|
||||
pub fn ppqs <E: Engine> (tracks: &[ArrangementTrack<E>], scenes: &[Self]) -> Vec<(usize, usize)> {
|
||||
let mut total = 0;
|
||||
let mut scenes: Vec<(usize, usize)> = scenes.iter().map(|scene|{
|
||||
let pulses = scene.pulses(tracks).max(PPQ);
|
||||
total = total + pulses;
|
||||
(pulses, total - pulses)
|
||||
}).collect();
|
||||
scenes.push((0, total));
|
||||
scenes
|
||||
}
|
||||
pub fn longest_name (scenes: &[Self]) -> usize {
|
||||
scenes.iter()
|
||||
.map(|s|s.name.read().unwrap().len())
|
||||
.fold(0, usize::max)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue