mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
add Arranger model
This commit is contained in:
parent
c85fa3cd06
commit
20e30cb472
14 changed files with 310 additions and 241 deletions
|
|
@ -44,8 +44,8 @@ fn handle_focused (state: &mut App, e: &AppEvent) -> Usually<bool> {
|
|||
}
|
||||
|
||||
fn handle_device (state: &mut App, e: &AppEvent) -> Usually<bool> {
|
||||
state.track_mut()
|
||||
.and_then(|(_, track)|track.device_mut())
|
||||
state.arranger.track()
|
||||
.and_then(|track|track.device_mut())
|
||||
.map(|mut device|device.handle(e))
|
||||
.transpose()
|
||||
.map(|x|x.unwrap_or(false))
|
||||
|
|
@ -58,15 +58,15 @@ pub const KEYMAP_GLOBAL: &'static [KeyBinding<App>] = keymap!(App {
|
|||
Ok(true)
|
||||
}],
|
||||
[Char('r'), NONE, "record_toggle", "toggle recording", |app: &mut App| {
|
||||
app.track_mut().map(|t|t.1.toggle_record());
|
||||
app.arranger.track_mut().map(|t|t.toggle_record());
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('o'), NONE, "overdub_toggle", "toggle overdub", |app: &mut App| {
|
||||
app.track_mut().map(|t|t.1.toggle_overdub());
|
||||
app.arranger.track_mut().map(|t|t.toggle_overdub());
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('m'), NONE, "monitor_toggle", "toggle monitor", |app: &mut App| {
|
||||
app.track_mut().map(|t|t.1.toggle_monitor());
|
||||
app.arranger.track_mut().map(|t|t.toggle_monitor());
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('+'), NONE, "quant_inc", "quantize coarser", |app: &mut App| {
|
||||
|
|
@ -86,12 +86,12 @@ pub const KEYMAP_GLOBAL: &'static [KeyBinding<App>] = keymap!(App {
|
|||
Ok(true)
|
||||
}],
|
||||
[Char('x'), NONE, "extend", "double the current clip", |app: &mut App| {
|
||||
if let Some(phrase) = app.phrase_mut() {
|
||||
app.arranger.phrase_mut().map(|phrase|{
|
||||
let mut notes = phrase.notes.clone();
|
||||
notes.extend_from_slice(&mut phrase.notes);
|
||||
phrase.notes = notes;
|
||||
phrase.length = phrase.length * 2;
|
||||
}
|
||||
});
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('l'), NONE, "loop_toggle", "toggle looping", |_app: &mut App| {
|
||||
|
|
@ -115,11 +115,11 @@ pub const KEYMAP_GLOBAL: &'static [KeyBinding<App>] = keymap!(App {
|
|||
Ok(true)
|
||||
}],
|
||||
[Char('a'), CONTROL, "scene_add", "add a new scene", |app: &mut App| {
|
||||
app.add_scene(None)?;
|
||||
app.arranger.scene_add(None)?;
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('t'), CONTROL, "track_add", "add a new track", |app: &mut App| {
|
||||
app.add_track(None)?;
|
||||
app.arranger.track_add(None)?;
|
||||
Ok(true)
|
||||
}],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,57 +3,43 @@ use crate::{core::*, model::App};
|
|||
/// Key bindings for arranger section.
|
||||
pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
|
||||
[Char('`'), NONE, "arranger_mode_switch", "switch the display mode", |app: &mut App| {
|
||||
app.arranger_mode = !app.arranger_mode;
|
||||
app.arranger.mode = !app.arranger.mode;
|
||||
Ok(true)
|
||||
}],
|
||||
[Up, NONE, "arranger_cursor_up", "move cursor up", |app: &mut App| Ok(
|
||||
match app.arranger_mode {
|
||||
false => {app.prev_scene();true},
|
||||
true => {app.prev_track();true},
|
||||
match app.arranger.mode {
|
||||
false => {app.arranger.scene_prev();true},
|
||||
true => {app.arranger.track_prev();true},
|
||||
}
|
||||
)],
|
||||
[Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| Ok(
|
||||
match app.arranger_mode {
|
||||
false => {app.next_scene();true},
|
||||
true => {app.next_track();true},
|
||||
match app.arranger.mode {
|
||||
false => {app.arranger.scene_next();true},
|
||||
true => {app.arranger.track_next();true},
|
||||
}
|
||||
)],
|
||||
[Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| Ok(
|
||||
match app.arranger_mode {
|
||||
false => {app.prev_track();true},
|
||||
true => {app.prev_scene();true},
|
||||
match app.arranger.mode {
|
||||
false => {app.arranger.track_prev();true},
|
||||
true => {app.arranger.scene_prev();true},
|
||||
}
|
||||
)],
|
||||
[Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| Ok(
|
||||
match app.arranger_mode {
|
||||
false => {app.next_track();true},
|
||||
true => {app.next_scene();true}
|
||||
}
|
||||
)],
|
||||
[Enter, NONE, "arranger_activate", "activate item at cursor", |app: &mut App| Ok(
|
||||
if app.scene_cursor == 0 {
|
||||
false
|
||||
} else {
|
||||
let scene = &app.scenes[app.scene_cursor - 1];
|
||||
if app.track_cursor == 0 {
|
||||
for (i, track) in app.tracks.iter_mut().enumerate() {
|
||||
track.sequence = scene.clips[i];
|
||||
track.reset = true;
|
||||
}
|
||||
} else {
|
||||
let track = &mut app.tracks[app.track_cursor - 1];
|
||||
track.sequence = scene.clips[app.track_cursor - 1];
|
||||
track.reset = true;
|
||||
};
|
||||
true
|
||||
match app.arranger.mode {
|
||||
false => {app.arranger.track_next();true},
|
||||
true => {app.arranger.scene_next();true}
|
||||
}
|
||||
)],
|
||||
[Char('.'), NONE, "arranger_increment", "set next clip at cursor", |app: &mut App| {
|
||||
app.next_phrase();
|
||||
app.arranger.phrase_next();
|
||||
Ok(true)
|
||||
}],
|
||||
[Char(','), NONE, "arranger_decrement", "set previous clip at cursor", |app: &mut App| {
|
||||
app.prev_phrase();
|
||||
app.arranger.phrase_prev();
|
||||
Ok(true)
|
||||
}],
|
||||
[Enter, NONE, "arranger_activate", "activate item at cursor", |app: &mut App| {
|
||||
app.arranger.activate();
|
||||
Ok(true)
|
||||
}],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,20 +9,18 @@ pub const KEYMAP_CHAIN: &'static [KeyBinding<App>] = keymap!(App {
|
|||
Ok(true)
|
||||
}],
|
||||
[Left, NONE, "chain_cursor_left", "move cursor left", |app: &mut App| {
|
||||
if let Some((_, track)) = app.track_mut() {
|
||||
if let Some(track) = app.arranger.track_mut() {
|
||||
track.device = track.device.saturating_sub(1);
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
return Ok(true)
|
||||
}
|
||||
Ok(false)
|
||||
}],
|
||||
[Right, NONE, "chain_cursor_right", "move cursor right", |app: &mut App| {
|
||||
if let Some((_, track)) = app.track_mut() {
|
||||
if let Some(track) = app.arranger.track_mut() {
|
||||
track.device = (track.device + 1).min(track.devices.len().saturating_sub(1));
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
return Ok(true)
|
||||
}
|
||||
Ok(false)
|
||||
}],
|
||||
[Char('`'), NONE, "chain_mode_switch", "switch the display mode", |app: &mut App| {
|
||||
app.chain_mode = !app.seq_mode;
|
||||
|
|
|
|||
29
src/edn.rs
29
src/edn.rs
|
|
@ -116,7 +116,7 @@ impl Scene {
|
|||
Edn::Map(map) => {
|
||||
let key = map.get(&Edn::Key(":name"));
|
||||
if let Some(Edn::Str(n)) = key {
|
||||
name = Some(String::from(*n));
|
||||
name = Some(*n);
|
||||
} else {
|
||||
panic!("unexpected key in scene '{name:?}': {key:?}")
|
||||
}
|
||||
|
|
@ -129,25 +129,23 @@ impl Scene {
|
|||
},
|
||||
_ => panic!("unexpected in scene '{name:?}': {edn:?}")
|
||||
});
|
||||
app.add_scene_with_clips(name.as_deref(), &clips)
|
||||
//for edn in args {
|
||||
//match end {
|
||||
//}
|
||||
//}
|
||||
let scene = app.arranger.scene_add(name)?;
|
||||
scene.clips = clips;
|
||||
Ok(scene)
|
||||
}
|
||||
}
|
||||
|
||||
impl Track {
|
||||
fn load_edn <'a, 'e> (app: &'a mut App, args: &[Edn<'e>]) -> Usually<&'a mut Self> {
|
||||
let ppq = app.transport.ppq();
|
||||
let mut name = app.new_track_name();
|
||||
let mut name = None;
|
||||
let mut _gain = 0.0f64;
|
||||
let mut devices: Vec<JackDevice> = vec![];
|
||||
let mut phrases: Vec<Phrase> = vec![];
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||
name = String::from(*n);
|
||||
name = Some(*n);
|
||||
}
|
||||
if let Some(Edn::Double(g)) = map.get(&Edn::Key(":gain")) {
|
||||
_gain = f64::from(*g)
|
||||
|
|
@ -163,12 +161,17 @@ impl Track {
|
|||
Some(Edn::Symbol("lv2")) => {
|
||||
devices.push(LV2Plugin::load_edn(&args[1..])?)
|
||||
},
|
||||
None => panic!("empty list track {name}"),
|
||||
_ => panic!("unexpected in track {name}: {:?}", args.get(0).unwrap())
|
||||
None => panic!("empty list track {}",
|
||||
name.unwrap_or("")
|
||||
),
|
||||
_ => panic!("unexpected in track {}: {:?}",
|
||||
name.unwrap_or(""),
|
||||
args.get(0).unwrap()
|
||||
)
|
||||
},
|
||||
_ => {}
|
||||
});
|
||||
let track = app.add_track(Some(name.as_str()))?;
|
||||
let track = app.arranger.track_add(name)?;
|
||||
for phrase in phrases { track.phrases.push(phrase); }
|
||||
for device in devices { track.add_device(device)?; }
|
||||
Ok(track)
|
||||
|
|
@ -214,8 +217,8 @@ impl Phrase {
|
|||
args.get(1),
|
||||
) {
|
||||
let (key, vel) = (
|
||||
u7::from((*key as u8).min(127)),
|
||||
u7::from((*vel as u8).min(127))
|
||||
u7::from((*key as u8).min(127)),
|
||||
u7::from((*vel as u8).min(127)),
|
||||
);
|
||||
phrase.notes[time].push(MidiMessage::NoteOn { key, vel })
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ pub fn main () -> Usually<()> {
|
|||
let app = app.read().unwrap();
|
||||
let jack = app.jack.as_ref().unwrap();
|
||||
let midi_in = jack.register_port("midi-in", MidiIn)?;
|
||||
let midi_outs = app.tracks.iter()
|
||||
let midi_outs = app.arranger.tracks.iter()
|
||||
.map(|t|Some(jack.register_port(&t.name, MidiOut).unwrap()))
|
||||
.collect::<Vec<_>>();
|
||||
(midi_in, midi_outs)
|
||||
|
|
@ -40,10 +40,10 @@ pub fn main () -> Usually<()> {
|
|||
}
|
||||
}
|
||||
app.midi_in = Some(Arc::new(midi_in));
|
||||
for (index, track) in app.tracks.iter_mut().enumerate() {
|
||||
for (index, track) in app.arranger.tracks.iter_mut().enumerate() {
|
||||
track.midi_out = midi_outs[index].take();
|
||||
}
|
||||
for track in app.tracks.iter() {
|
||||
for track in app.arranger.tracks.iter() {
|
||||
track.connect_first_device()?;
|
||||
track.connect_last_device(&app)?;
|
||||
}
|
||||
|
|
|
|||
32
src/model.rs
32
src/model.rs
|
|
@ -1,6 +1,6 @@
|
|||
//! Application state.
|
||||
|
||||
submod! { looper mixer phrase plugin sampler scene track transport }
|
||||
submod! { arranger looper mixer phrase plugin sampler scene track transport }
|
||||
|
||||
use crate::{core::*, view::*};
|
||||
|
||||
|
|
@ -13,8 +13,6 @@ pub struct App {
|
|||
pub midi_in: Option<Arc<Port<MidiIn>>>,
|
||||
/// Names of ports to connect to main MIDI IN.
|
||||
pub midi_ins: Vec<String>,
|
||||
/// Display mode of arranger section
|
||||
pub arranger_mode: bool,
|
||||
/// Display mode of chain section
|
||||
pub chain_mode: bool,
|
||||
/// Display mode of sequencer seciton
|
||||
|
|
@ -23,24 +21,12 @@ pub struct App {
|
|||
pub seq_buf: BufferedSequencerView,
|
||||
/// Optional modal dialog
|
||||
pub modal: Option<Box<dyn Exit>>,
|
||||
/// Currently focused section
|
||||
pub section: AppFocus,
|
||||
/// Whether the current focus section has input priority
|
||||
pub entered: bool,
|
||||
/// Display position of cursor within note range
|
||||
pub note_cursor: usize,
|
||||
/// Range of notes to display
|
||||
pub note_start: usize,
|
||||
/// Display position of cursor within time range
|
||||
pub time_cursor: usize,
|
||||
/// Focused scene+1, 0 is track list
|
||||
pub scene_cursor: usize,
|
||||
/// Collection of scenes
|
||||
pub scenes: Vec<Scene>,
|
||||
/// Focused track+1, 0 is scene list
|
||||
pub track_cursor: usize,
|
||||
/// Collection of tracks
|
||||
pub tracks: Vec<Track>,
|
||||
/// Paths to user directories
|
||||
xdg: Option<Arc<XdgApp>>,
|
||||
/// Main audio outputs.
|
||||
|
|
@ -48,7 +34,13 @@ pub struct App {
|
|||
/// Number of frames requested by process callback
|
||||
chunk_size: usize,
|
||||
/// Transport model and view.
|
||||
pub transport: TransportToolbar,
|
||||
pub transport: TransportToolbar,
|
||||
/// Arraneger model and view.
|
||||
pub arranger: Arranger,
|
||||
/// Currently focused section
|
||||
pub section: AppFocus,
|
||||
/// Whether the current focus section has input priority
|
||||
pub entered: bool,
|
||||
}
|
||||
|
||||
impl App {
|
||||
|
|
@ -58,7 +50,7 @@ impl App {
|
|||
let jack = JackClient::Inactive(Client::new("tek", ClientOptions::NO_START_SERVER)?.0);
|
||||
Ok(Self {
|
||||
transport: TransportToolbar::new(Some(jack.transport())),
|
||||
arranger_mode: false,
|
||||
arranger: Arranger::new(),
|
||||
audio_outs: vec![],
|
||||
chain_mode: false,
|
||||
chunk_size: 0,
|
||||
|
|
@ -68,14 +60,10 @@ impl App {
|
|||
midi_ins: vec![],
|
||||
note_cursor: 0,
|
||||
note_start: 2,
|
||||
scene_cursor: 1,
|
||||
scenes: vec![],
|
||||
section: AppFocus::default(),
|
||||
seq_mode: false,
|
||||
seq_buf: BufferedSequencerView::new(96, 16384),
|
||||
time_cursor: 0,
|
||||
track_cursor: 1,
|
||||
tracks: vec![],
|
||||
modal: first_run.then(
|
||||
||Exit::boxed(crate::config::SetupModal(Some(xdg.clone()), false))
|
||||
),
|
||||
|
|
@ -88,7 +76,7 @@ process!(App |self, _client, scope| {
|
|||
reset, current_frames, chunk_size, current_usecs, next_usecs, period_usecs
|
||||
) = self.transport.update(&scope);
|
||||
self.chunk_size = chunk_size;
|
||||
for track in self.tracks.iter_mut() {
|
||||
for track in self.arranger.tracks.iter_mut() {
|
||||
track.process(
|
||||
self.midi_in.as_ref().map(|p|p.iter(&scope)),
|
||||
&self.transport.timebase,
|
||||
|
|
|
|||
228
src/model/arranger.rs
Normal file
228
src/model/arranger.rs
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
use crate::{core::*, model::*};
|
||||
|
||||
pub struct Arranger {
|
||||
/// Display mode of arranger
|
||||
pub mode: bool,
|
||||
/// Currently selected element.
|
||||
pub selected: ArrangerFocus,
|
||||
/// Collection of tracks.
|
||||
pub tracks: Vec<Track>,
|
||||
/// Collection of scenes.
|
||||
pub scenes: Vec<Scene>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
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),
|
||||
}
|
||||
|
||||
/// Identification methods
|
||||
impl ArrangerFocus {
|
||||
pub fn is_track (&self) -> bool {
|
||||
match self { Self::Track(_) => true, _ => false }
|
||||
}
|
||||
pub fn is_scene (&self) -> bool {
|
||||
match self { Self::Scene(_) => true, _ => false }
|
||||
}
|
||||
pub fn is_clip (&self) -> bool {
|
||||
match self { Self::Clip(_, _) => true, _ => false }
|
||||
}
|
||||
}
|
||||
|
||||
/// Track methods
|
||||
impl ArrangerFocus {
|
||||
pub fn track (&self) -> Option<usize> {
|
||||
match self {
|
||||
Self::Clip(t, _) => Some(*t),
|
||||
Self::Track(t) => Some(*t),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
pub fn track_next (&mut self, last_track: usize) {
|
||||
*self = match self {
|
||||
Self::Mix => Self::Track(0),
|
||||
Self::Track(t) => Self::Track(last_track.min(*t + 1)),
|
||||
Self::Scene(s) => Self::Clip(0, *s),
|
||||
Self::Clip(t, s) => Self::Clip(last_track.min(*t + 1), *s),
|
||||
}
|
||||
}
|
||||
pub fn track_prev (&mut self) {
|
||||
*self = match self {
|
||||
Self::Mix => Self::Mix,
|
||||
Self::Scene(s) => Self::Scene(*s),
|
||||
Self::Track(0) => Self::Mix,
|
||||
Self::Track(t) => Self::Track(*t - 1),
|
||||
Self::Clip(t, s) => Self::Clip(t.saturating_sub(1), *s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Scene methods
|
||||
impl ArrangerFocus {
|
||||
pub fn scene (&self) -> Option<usize> {
|
||||
match self {
|
||||
Self::Clip(_, s) => Some(*s),
|
||||
Self::Scene(s) => Some(*s),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
pub fn scene_next (&mut self, last_scene: usize) {
|
||||
*self = match self {
|
||||
Self::Mix => Self::Scene(0),
|
||||
Self::Track(t) => Self::Scene(*t),
|
||||
Self::Scene(s) => Self::Scene(last_scene.min(*s + 1)),
|
||||
Self::Clip(t, s) => Self::Clip(*t, last_scene.min(*s + 1)),
|
||||
}
|
||||
}
|
||||
pub fn scene_prev (&mut self) {
|
||||
*self = match self {
|
||||
Self::Mix => Self::Mix,
|
||||
Self::Track(t) => Self::Track(*t),
|
||||
Self::Scene(0) => Self::Mix,
|
||||
Self::Scene(s) => Self::Scene(*s - 1),
|
||||
Self::Clip(t, s) => Self::Clip(*t, s.saturating_sub(1)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the tracks and scenes of the composition.
|
||||
impl Arranger {
|
||||
pub fn new () -> Self {
|
||||
Self {
|
||||
mode: false,
|
||||
selected: ArrangerFocus::Mix,
|
||||
scenes: vec![],
|
||||
tracks: vec![],
|
||||
}
|
||||
}
|
||||
pub fn activate (&mut self) {
|
||||
match self.selected {
|
||||
ArrangerFocus::Scene(s) => {
|
||||
for (track_index, track) in self.tracks.iter_mut().enumerate() {
|
||||
track.sequence = self.scenes[s].clips[track_index];
|
||||
track.reset = true;
|
||||
}
|
||||
},
|
||||
ArrangerFocus::Clip(t, s) => {
|
||||
self.tracks[t].sequence = self.scenes[s].clips[t];
|
||||
self.tracks[t].reset = true;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Track management methods
|
||||
impl Arranger {
|
||||
pub fn track (&self) -> Option<&Track> {
|
||||
self.selected.track().map(|t|self.tracks.get(t)).flatten()
|
||||
}
|
||||
pub fn track_mut (&mut self) -> Option<&mut Track> {
|
||||
self.selected.track().map(|t|self.tracks.get_mut(t)).flatten()
|
||||
}
|
||||
pub fn track_next (&mut self) {
|
||||
self.selected.track_next(self.tracks.len())
|
||||
}
|
||||
pub fn track_prev (&mut self) {
|
||||
self.selected.track_prev()
|
||||
}
|
||||
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Track> {
|
||||
self.tracks.push(match name {
|
||||
Some(name) => Track::new(name, None, None)?,
|
||||
None => Track::new(&self.track_default_name(), None, None)?
|
||||
});
|
||||
let index = self.tracks.len() - 1;
|
||||
Ok(&mut self.tracks[index])
|
||||
}
|
||||
pub fn track_del (&mut self) {
|
||||
unimplemented!("Arranger::track_del");
|
||||
}
|
||||
pub fn track_default_name (&self) -> String {
|
||||
format!("Track {}", self.tracks.len() + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Scene management methods
|
||||
impl Arranger {
|
||||
pub fn scene (&self) -> Option<&Scene> {
|
||||
self.selected.scene().map(|s|self.scenes.get(s)).flatten()
|
||||
}
|
||||
pub fn scene_mut (&mut self) -> Option<&mut Scene> {
|
||||
self.selected.scene().map(|s|self.scenes.get_mut(s)).flatten()
|
||||
}
|
||||
pub fn scene_next (&mut self) {
|
||||
self.selected.scene_next(self.scenes.len())
|
||||
}
|
||||
pub fn scene_prev (&mut self) {
|
||||
self.selected.scene_prev()
|
||||
}
|
||||
pub fn scene_add (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
|
||||
let clips = vec![None;self.tracks.len()];
|
||||
self.scenes.push(match name {
|
||||
Some(name) => Scene::new(name, clips),
|
||||
None => Scene::new(&self.track_default_name(), clips),
|
||||
});
|
||||
let index = self.scenes.len() - 1;
|
||||
Ok(&mut self.scenes[index])
|
||||
}
|
||||
pub fn scene_del (&mut self) {
|
||||
unimplemented!("Arranger::scene_del");
|
||||
}
|
||||
pub fn scene_default_name (&self) -> String {
|
||||
format!("Scene {}", self.scenes.len() + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Phrase management methods
|
||||
impl Arranger {
|
||||
pub fn phrase (&self) -> Option<&Phrase> {
|
||||
let track_id = self.selected.track()?;
|
||||
self.tracks.get(track_id)?.phrases.get((*self.scene()?.clips.get(track_id)?)?)
|
||||
}
|
||||
pub fn phrase_mut (&mut self) -> Option<&mut Phrase> {
|
||||
let track_id = self.selected.track()?;
|
||||
let clip = *self.scene()?.clips.get(track_id)?;
|
||||
self.tracks.get_mut(track_id)?.phrases.get_mut(clip?)
|
||||
}
|
||||
pub fn phrase_next (&mut self) {
|
||||
unimplemented!();
|
||||
//if let Some((track_index, track)) = self.track_mut() {
|
||||
//let phrases = track.phrases.len();
|
||||
//if let Some((_, scene)) = self.scene_mut() {
|
||||
//if let Some(phrase_index) = scene.clips[track_index] {
|
||||
//if phrase_index >= phrases - 1 {
|
||||
//scene.clips[track_index] = None;
|
||||
//} else {
|
||||
//scene.clips[track_index] = Some(phrase_index + 1);
|
||||
//}
|
||||
//} else if phrases > 0 {
|
||||
//scene.clips[track_index] = Some(0);
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
}
|
||||
pub fn phrase_prev (&mut self) {
|
||||
unimplemented!();
|
||||
//if let Some((track_index, track)) = self.track_mut() {
|
||||
//let phrases = track.phrases.len();
|
||||
//if let Some((_, scene)) = self.scene_mut() {
|
||||
//if let Some(phrase_index) = scene.clips[track_index] {
|
||||
//scene.clips[track_index] = if phrase_index == 0 {
|
||||
//None
|
||||
//} else {
|
||||
//Some(phrase_index - 1)
|
||||
//};
|
||||
//} else if phrases > 0 {
|
||||
//scene.clips[track_index] = Some(phrases - 1);
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +1,4 @@
|
|||
use crate::{core::*, model::App};
|
||||
|
||||
impl App {
|
||||
pub fn next_phrase (&mut self) {
|
||||
if let Some((track_index, track)) = self.track_mut() {
|
||||
let phrases = track.phrases.len();
|
||||
if let Some((_, scene)) = self.scene_mut() {
|
||||
if let Some(phrase_index) = scene.clips[track_index] {
|
||||
if phrase_index >= phrases - 1 {
|
||||
scene.clips[track_index] = None;
|
||||
} else {
|
||||
scene.clips[track_index] = Some(phrase_index + 1);
|
||||
}
|
||||
} else if phrases > 0 {
|
||||
scene.clips[track_index] = Some(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn prev_phrase (&mut self) {
|
||||
if let Some((track_index, track)) = self.track_mut() {
|
||||
let phrases = track.phrases.len();
|
||||
if let Some((_, scene)) = self.scene_mut() {
|
||||
if let Some(phrase_index) = scene.clips[track_index] {
|
||||
scene.clips[track_index] = if phrase_index == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(phrase_index - 1)
|
||||
};
|
||||
} else if phrases > 0 {
|
||||
scene.clips[track_index] = Some(phrases - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn phrase (&self) -> Option<&Phrase> {
|
||||
let (track_id, track) = self.track()?;
|
||||
let (_, scene) = self.scene()?;
|
||||
track.phrases.get((*scene.clips.get(track_id)?)?)
|
||||
}
|
||||
pub fn phrase_mut (&mut self) -> Option<&mut Phrase> {
|
||||
let (track_id, _) = self.track()?;
|
||||
let (_, scene) = self.scene()?;
|
||||
let clip = (*scene.clips.get(track_id)?)?;
|
||||
self.track_mut()?.1.phrases.get_mut(clip)
|
||||
}
|
||||
}
|
||||
use crate::core::*;
|
||||
|
||||
/// Define a MIDI phrase.
|
||||
#[macro_export] macro_rules! phrase {
|
||||
|
|
|
|||
|
|
@ -1,48 +1,3 @@
|
|||
use crate::{core::*, model::*};
|
||||
|
||||
impl App {
|
||||
pub fn next_scene (&mut self) {
|
||||
self.scene_cursor = self.scenes.len().min(self.scene_cursor + 1);
|
||||
}
|
||||
pub fn prev_scene (&mut self) {
|
||||
self.scene_cursor = self.scene_cursor.saturating_sub(1);
|
||||
}
|
||||
fn new_scene_name (&self) -> String {
|
||||
format!("Scene {}", self.scenes.len() + 1)
|
||||
}
|
||||
pub fn add_scene (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
|
||||
let clips = vec![None;self.tracks.len()];
|
||||
self.scenes.push(match name {
|
||||
Some(name) => Scene::new(name, clips),
|
||||
None => Scene::new(&self.new_scene_name(), clips)
|
||||
});
|
||||
self.scene_cursor = self.scenes.len();
|
||||
Ok(&mut self.scenes[self.scene_cursor - 1])
|
||||
}
|
||||
pub fn add_scene_with_clips (
|
||||
&mut self,
|
||||
name: Option<&str>,
|
||||
clips: &[Option<usize>]
|
||||
) -> Usually<&mut Scene> {
|
||||
let name = name.ok_or_else(||self.new_scene_name())?;
|
||||
self.scenes.push(Scene::new(&name, Vec::from(clips)));
|
||||
self.scene_cursor = self.scenes.len();
|
||||
Ok(&mut self.scenes[self.scene_cursor - 1])
|
||||
}
|
||||
pub fn scene (&self) -> Option<(usize, &Scene)> {
|
||||
match self.scene_cursor { 0 => None, _ => {
|
||||
let id = self.scene_cursor as usize - 1;
|
||||
self.scenes.get(id).map(|t|(id, t))
|
||||
} }
|
||||
}
|
||||
pub fn scene_mut (&mut self) -> Option<(usize, &mut Scene)> {
|
||||
match self.scene_cursor { 0 => None, _ => {
|
||||
let id = self.scene_cursor as usize - 1;
|
||||
self.scenes.get_mut(id).map(|t|(id, t))
|
||||
} }
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of phrases to play on each track.
|
||||
pub struct Scene {
|
||||
pub name: String,
|
||||
|
|
@ -50,7 +5,7 @@ pub struct Scene {
|
|||
}
|
||||
|
||||
impl Scene {
|
||||
fn new (name: impl AsRef<str>, clips: impl AsRef<[Option<usize>]>) -> Self {
|
||||
pub fn new (name: impl AsRef<str>, clips: impl AsRef<[Option<usize>]>) -> Self {
|
||||
Self {
|
||||
name: name.as_ref().into(),
|
||||
clips: clips.as_ref().iter().map(|x|x.clone()).collect()
|
||||
|
|
|
|||
|
|
@ -1,46 +1,5 @@
|
|||
use crate::{core::*, model::*};
|
||||
|
||||
impl App {
|
||||
pub fn next_track (&mut self) {
|
||||
self.track_cursor = self.tracks.len().min(self.track_cursor + 1);
|
||||
}
|
||||
pub fn prev_track (&mut self) {
|
||||
self.track_cursor = self.track_cursor.saturating_sub(1);
|
||||
}
|
||||
pub fn new_track_name (&self) -> String {
|
||||
format!("Track {}", self.tracks.len() + 1)
|
||||
}
|
||||
pub fn add_track (&mut self, name: Option<&str>) -> Usually<&mut Track> {
|
||||
self.tracks.push(match name {
|
||||
Some(name) => Track::new(name, None, None)?,
|
||||
None => Track::new(&self.new_track_name(), None, None)?
|
||||
});
|
||||
self.track_cursor = self.tracks.len();
|
||||
Ok(&mut self.tracks[self.track_cursor - 1])
|
||||
}
|
||||
pub fn del_track (&mut self) -> Usually<bool> {
|
||||
if self.tracks.len() > 0 {
|
||||
let track = self.tracks.remove(self.track_cursor);
|
||||
self.track_cursor = self.track_cursor.saturating_sub(1);
|
||||
track.midi_out.map(|port|self.client().unregister_port(port)).transpose()?;
|
||||
return Ok(true)
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
pub fn track (&self) -> Option<(usize, &Track)> {
|
||||
match self.track_cursor { 0 => None, _ => {
|
||||
let id = self.track_cursor as usize - 1;
|
||||
self.tracks.get(id).map(|t|(id, t))
|
||||
} }
|
||||
}
|
||||
pub fn track_mut (&mut self) -> Option<(usize, &mut Track)> {
|
||||
match self.track_cursor { 0 => None, _ => {
|
||||
let id = self.track_cursor as usize - 1;
|
||||
self.tracks.get_mut(id).map(|t|(id, t))
|
||||
} }
|
||||
}
|
||||
}
|
||||
|
||||
/// A sequencer track.
|
||||
pub struct Track {
|
||||
pub name: String,
|
||||
|
|
@ -72,7 +31,7 @@ pub struct Track {
|
|||
}
|
||||
|
||||
impl Track {
|
||||
fn new (
|
||||
pub fn new (
|
||||
name: &str,
|
||||
phrases: Option<Vec<Phrase>>,
|
||||
devices: Option<Vec<JackDevice>>,
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ submod! {
|
|||
render!(App |self, buf, area| {
|
||||
Split::down([
|
||||
&self.transport,
|
||||
&ArrangerView::new(&self, !self.arranger_mode),
|
||||
&If(self.track_cursor > 0, &Split::right([
|
||||
&ArrangerView::new(&self, !self.arranger.mode),
|
||||
&If(self.arranger.selected.is_clip(), &Split::right([
|
||||
&ChainView::vertical(&self),
|
||||
&SequencerView::new(&self),
|
||||
]))
|
||||
|
|
|
|||
|
|
@ -14,12 +14,17 @@ pub struct ArrangerView<'a> {
|
|||
impl<'a> ArrangerView<'a> {
|
||||
pub fn new (app: &'a App, vertical: bool) -> Self {
|
||||
Self {
|
||||
vertical,
|
||||
focused: app.section == AppFocus::Arranger,
|
||||
entered: app.entered,
|
||||
scenes: &app.scenes,
|
||||
tracks: &app.tracks,
|
||||
cursor: (app.track_cursor, app.scene_cursor),
|
||||
vertical
|
||||
scenes: &app.arranger.scenes,
|
||||
tracks: &app.arranger.tracks,
|
||||
cursor: match app.arranger.selected {
|
||||
ArrangerFocus::Mix => (0, 0),
|
||||
ArrangerFocus::Scene(s) => (0, s + 1),
|
||||
ArrangerFocus::Track(t) => (t + 1, 0),
|
||||
ArrangerFocus::Clip(t, s) => (t + 1, s + 1),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -100,8 +105,7 @@ impl<'a> ArrangerView<'a> {
|
|||
let hi = (track_index + 1 == self.cursor.0) && (scene_index + 1 == self.cursor.1);
|
||||
let style = Some(Nord::style_hi(self.focused, hi));
|
||||
let y = 1 + y + 2 * scene_index as u16;
|
||||
"┊".blit(buf, x, y, Some(Style::default().dim()))?;
|
||||
"┊".blit(buf, x, y + 1, Some(Style::default().dim()))?;
|
||||
"┊".blit(buf, x, y + 1, style)?;
|
||||
label.blit(buf, x, y, style)?;
|
||||
}
|
||||
if track_index + 1 == self.cursor.0 {
|
||||
|
|
|
|||
|
|
@ -19,10 +19,7 @@ impl<'a> ChainView<'a> {
|
|||
direction,
|
||||
entered: app.entered,
|
||||
focused: app.section == AppFocus::Chain,
|
||||
track: match app.track_cursor {
|
||||
0 => None,
|
||||
_ => app.tracks.get(app.track_cursor - 1)
|
||||
},
|
||||
track: app.arranger.track()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,12 +105,9 @@ pub struct SequencerView<'a> {
|
|||
|
||||
impl<'a> SequencerView<'a> {
|
||||
pub fn new (app: &'a App) -> Self {
|
||||
let track = match app.track_cursor {
|
||||
0 => None,
|
||||
_ => app.tracks.get(app.track_cursor - 1)
|
||||
};
|
||||
let track = app.arranger.track();
|
||||
Self {
|
||||
phrase: app.phrase(),
|
||||
phrase: app.arranger.phrase(),
|
||||
focused: app.section == AppFocus::Sequencer,
|
||||
entered: app.entered,
|
||||
ppq: app.transport.ppq(),
|
||||
|
|
@ -120,7 +117,7 @@ impl<'a> SequencerView<'a> {
|
|||
time_zoom: app.seq_buf.time_zoom,
|
||||
note_cursor: app.note_cursor,
|
||||
note_start: app.note_start,
|
||||
notes_in: if let Some(track) = track { &track.notes_in } else { &[false;128] },
|
||||
notes_in: if let Some(track) = track { &track.notes_in } else { &[false;128] },
|
||||
notes_out: if let Some(track) = track { &track.notes_out } else { &[false;128] },
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue