mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
sequencer: merge Sequencer and SequencerTrack
This commit is contained in:
parent
1d3d3875fe
commit
6206a43b4a
8 changed files with 171 additions and 190 deletions
|
|
@ -11,13 +11,12 @@ pub struct Arranger {
|
|||
/// Currently selected element.
|
||||
pub selected: ArrangerFocus,
|
||||
/// Collection of tracks.
|
||||
pub tracks: Vec<SequencerTrack>,
|
||||
pub tracks: Vec<Sequencer>,
|
||||
/// Collection of scenes.
|
||||
pub scenes: Vec<Scene>,
|
||||
pub focused: bool,
|
||||
pub entered: bool,
|
||||
pub fixed_height: bool,
|
||||
pub sequencer: Sequencer,
|
||||
pub transport: Option<Arc<RwLock<TransportToolbar>>>,
|
||||
}
|
||||
|
||||
|
|
@ -32,7 +31,6 @@ impl Arranger {
|
|||
entered: true,
|
||||
focused: true,
|
||||
fixed_height: false,
|
||||
sequencer: Sequencer::new(),
|
||||
transport: None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,12 +42,10 @@ pub const KEYMAP_ARRANGER: &'static [KeyBinding<Arranger>] = keymap!(Arranger {
|
|||
}],
|
||||
[Char('.'), NONE, "arranger_increment", "set next clip at cursor", |arranger: &mut Arranger| {
|
||||
arranger.phrase_next();
|
||||
arranger.sequencer.phrase = arranger.phrase().map(Clone::clone);
|
||||
Ok(true)
|
||||
}],
|
||||
[Char(','), NONE, "arranger_decrement", "set previous clip at cursor", |arranger: &mut Arranger| {
|
||||
arranger.phrase_prev();
|
||||
arranger.sequencer.phrase = arranger.phrase().map(Clone::clone);
|
||||
Ok(true)
|
||||
}],
|
||||
[Enter, NONE, "arranger_activate", "activate item at cursor", |arranger: &mut Arranger| {
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ use super::Arranger;
|
|||
|
||||
/// Track management methods
|
||||
impl Arranger {
|
||||
pub fn track (&self) -> Option<&SequencerTrack> {
|
||||
pub fn track (&self) -> Option<&Sequencer> {
|
||||
self.selected.track().map(|t|self.tracks.get(t)).flatten()
|
||||
}
|
||||
pub fn track_mut (&mut self) -> Option<&mut SequencerTrack> {
|
||||
pub fn track_mut (&mut self) -> Option<&mut Sequencer> {
|
||||
self.selected.track().map(|t|self.tracks.get_mut(t)).flatten()
|
||||
}
|
||||
pub fn track_next (&mut self) {
|
||||
|
|
@ -16,11 +16,11 @@ impl Arranger {
|
|||
pub fn track_prev (&mut self) {
|
||||
self.selected.track_prev()
|
||||
}
|
||||
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut SequencerTrack> {
|
||||
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Sequencer> {
|
||||
self.tracks.push(name.map_or_else(
|
||||
|| SequencerTrack::new(&self.track_default_name()),
|
||||
|name| SequencerTrack::new(name),
|
||||
)?);
|
||||
|| Sequencer::new(&self.track_default_name()),
|
||||
|name| Sequencer::new(name),
|
||||
));
|
||||
let index = self.tracks.len() - 1;
|
||||
Ok(&mut self.tracks[index])
|
||||
}
|
||||
|
|
@ -32,13 +32,13 @@ impl Arranger {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn track_name_max_len (tracks: &[SequencerTrack]) -> usize {
|
||||
pub fn track_name_max_len (tracks: &[Sequencer]) -> usize {
|
||||
tracks.iter()
|
||||
.map(|s|s.name.len())
|
||||
.fold(0, usize::max)
|
||||
}
|
||||
|
||||
pub fn track_clip_name_lengths (tracks: &[SequencerTrack]) -> Vec<(usize, usize)> {
|
||||
pub fn track_clip_name_lengths (tracks: &[Sequencer]) -> Vec<(usize, usize)> {
|
||||
let mut total = 0;
|
||||
let mut lengths: Vec<(usize, usize)> = tracks.iter().map(|track|{
|
||||
let len = 2 + track.phrases
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ submod! {
|
|||
sequencer_cli
|
||||
sequencer_handle
|
||||
sequencer_render
|
||||
sequencer_track
|
||||
arranger
|
||||
arranger_cli
|
||||
arranger_focus
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::*;
|
||||
|
||||
/// A collection of phrases to play on each track.
|
||||
#[derive(Default)]
|
||||
pub struct Scene {
|
||||
pub name: String,
|
||||
pub clips: Vec<Option<usize>>,
|
||||
|
|
@ -13,14 +14,14 @@ impl Scene {
|
|||
Self { name, clips, }
|
||||
}
|
||||
/// Returns the pulse length of the longest phrase in the scene
|
||||
pub fn pulses (&self, tracks: &[SequencerTrack]) -> usize {
|
||||
pub fn pulses (&self, tracks: &[Sequencer]) -> usize {
|
||||
self.clips.iter().enumerate()
|
||||
.filter_map(|(i, c)|c.map(|c|tracks[i].phrases.get(c)))
|
||||
.filter_map(|p|p)
|
||||
.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 (&self, tracks: &[SequencerTrack]) -> bool {
|
||||
pub fn is_playing (&self, tracks: &[Sequencer]) -> bool {
|
||||
self.clips.iter().enumerate()
|
||||
.all(|(track_index, phrase_index)|match phrase_index {
|
||||
Some(i) => tracks[track_index].sequence == Some(*i),
|
||||
|
|
@ -35,7 +36,7 @@ pub fn scene_name_max_len (scenes: &[Scene]) -> usize {
|
|||
.fold(0, usize::max)
|
||||
}
|
||||
|
||||
pub fn scene_ppqs (tracks: &[SequencerTrack], scenes: &[Scene]) -> Vec<(usize, usize)> {
|
||||
pub fn scene_ppqs (tracks: &[Sequencer], scenes: &[Scene]) -> Vec<(usize, usize)> {
|
||||
let mut total = 0;
|
||||
let mut scenes: Vec<(usize, usize)> = scenes.iter().map(|scene|{
|
||||
let pulses = scene.pulses(tracks);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::*;
|
||||
use tek_core::Direction;
|
||||
|
||||
/// Phrase editor.
|
||||
pub struct Sequencer {
|
||||
|
|
@ -20,12 +21,42 @@ pub struct Sequencer {
|
|||
pub ppq: usize,
|
||||
pub note_axis: FixedAxis<usize>,
|
||||
pub time_axis: ScaledAxis<usize>,
|
||||
/// Play input through output.
|
||||
pub monitoring: bool,
|
||||
/// Write input to sequence.
|
||||
pub recording: bool,
|
||||
/// Overdub input to sequence.
|
||||
pub overdub: bool,
|
||||
/// Map: tick -> MIDI events at tick
|
||||
pub phrases: Vec<Arc<RwLock<Phrase>>>,
|
||||
/// Phrase selector
|
||||
pub sequence: Option<usize>,
|
||||
/// Output from current sequence.
|
||||
pub midi_out: Option<Port<MidiOut>>,
|
||||
/// MIDI output buffer
|
||||
midi_out_buf: Vec<Vec<Vec<u8>>>,
|
||||
/// Send all notes off
|
||||
pub reset: bool, // TODO?: after Some(nframes)
|
||||
/// Highlight keys on piano roll.
|
||||
pub notes_in: [bool;128],
|
||||
/// Highlight keys on piano roll.
|
||||
pub notes_out: [bool;128],
|
||||
}
|
||||
|
||||
impl Sequencer {
|
||||
pub fn new () -> Self {
|
||||
pub fn new (name: &str) -> Self {
|
||||
Self {
|
||||
name: "".into(),
|
||||
name: name.into(),
|
||||
monitoring: false,
|
||||
recording: false,
|
||||
overdub: true,
|
||||
phrases: vec![],
|
||||
sequence: None,
|
||||
midi_out: None,
|
||||
midi_out_buf: vec![Vec::with_capacity(16);16384],
|
||||
reset: true,
|
||||
notes_in: [false;128],
|
||||
notes_out: [false;128],
|
||||
buffer: Default::default(),
|
||||
keys: keys_vert(),
|
||||
entered: false,
|
||||
|
|
@ -48,4 +79,128 @@ impl Sequencer {
|
|||
},
|
||||
}
|
||||
}
|
||||
pub fn toggle_monitor (&mut self) {
|
||||
self.monitoring = !self.monitoring;
|
||||
}
|
||||
pub fn toggle_record (&mut self) {
|
||||
self.recording = !self.recording;
|
||||
}
|
||||
pub fn toggle_overdub (&mut self) {
|
||||
self.overdub = !self.overdub;
|
||||
}
|
||||
pub fn process (
|
||||
&mut self,
|
||||
input: Option<MidiIter>,
|
||||
timebase: &Arc<Timebase>,
|
||||
playing: Option<TransportState>,
|
||||
started: Option<(usize, usize)>,
|
||||
quant: usize,
|
||||
reset: bool,
|
||||
scope: &ProcessScope,
|
||||
(frame0, frames): (usize, usize),
|
||||
(_usec0, _usecs): (usize, usize),
|
||||
period: f64,
|
||||
) {
|
||||
if self.midi_out.is_some() {
|
||||
// Clear the section of the output buffer that we will be using
|
||||
for frame in &mut self.midi_out_buf[0..frames] {
|
||||
frame.clear();
|
||||
}
|
||||
// Emit "all notes off" at start of buffer if requested
|
||||
if self.reset {
|
||||
all_notes_off(&mut self.midi_out_buf);
|
||||
self.reset = false;
|
||||
} else if reset {
|
||||
all_notes_off(&mut self.midi_out_buf);
|
||||
}
|
||||
}
|
||||
if let (
|
||||
Some(TransportState::Rolling), Some((start_frame, _)), Some(phrase)
|
||||
) = (
|
||||
playing, started, self.sequence.and_then(|id|self.phrases.get_mut(id))
|
||||
) {
|
||||
phrase.read().map(|phrase|{
|
||||
if self.midi_out.is_some() {
|
||||
phrase.process_out(
|
||||
&mut self.midi_out_buf,
|
||||
&mut self.notes_out,
|
||||
timebase,
|
||||
(frame0.saturating_sub(start_frame), frames, period)
|
||||
);
|
||||
}
|
||||
}).unwrap();
|
||||
let mut phrase = phrase.write().unwrap();
|
||||
let length = phrase.length;
|
||||
// Monitor and record input
|
||||
if input.is_some() && (self.recording || self.monitoring) {
|
||||
// For highlighting keys and note repeat
|
||||
for (frame, event, bytes) in parse_midi_input(input.unwrap()) {
|
||||
match event {
|
||||
LiveEvent::Midi { message, .. } => {
|
||||
if self.monitoring {
|
||||
self.midi_out_buf[frame].push(bytes.to_vec())
|
||||
}
|
||||
if self.recording {
|
||||
phrase.record_event({
|
||||
let pulse = timebase.frame_to_pulse(
|
||||
(frame0 + frame - start_frame) as f64
|
||||
);
|
||||
let quantized = (
|
||||
pulse / quant as f64
|
||||
).round() as usize * quant;
|
||||
let looped = quantized % length;
|
||||
looped
|
||||
}, message);
|
||||
}
|
||||
match message {
|
||||
MidiMessage::NoteOn { key, .. } => {
|
||||
self.notes_in[key.as_int() as usize] = true;
|
||||
}
|
||||
MidiMessage::NoteOff { key, .. } => {
|
||||
self.notes_in[key.as_int() as usize] = false;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if input.is_some() && self.midi_out.is_some() && self.monitoring {
|
||||
for (frame, event, bytes) in parse_midi_input(input.unwrap()) {
|
||||
self.process_monitor_event(frame, &event, bytes)
|
||||
}
|
||||
}
|
||||
if let Some(out) = &mut self.midi_out {
|
||||
write_midi_output(&mut out.writer(scope), &self.midi_out_buf, frames);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn process_monitor_event (&mut self, frame: usize, event: &LiveEvent, bytes: &[u8]) {
|
||||
match event {
|
||||
LiveEvent::Midi { message, .. } => {
|
||||
self.write_to_output_buffer(frame, bytes);
|
||||
self.process_monitor_message(&message);
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline] fn write_to_output_buffer (&mut self, frame: usize, bytes: &[u8]) {
|
||||
self.midi_out_buf[frame].push(bytes.to_vec());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn process_monitor_message (&mut self, message: &MidiMessage) {
|
||||
match message {
|
||||
MidiMessage::NoteOn { key, .. } => {
|
||||
self.notes_in[key.as_int() as usize] = true;
|
||||
}
|
||||
MidiMessage::NoteOff { key, .. } => {
|
||||
self.notes_in[key.as_int() as usize] = false;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ pub struct SequencerCli {
|
|||
impl Sequencer {
|
||||
pub fn from_args () -> Self {
|
||||
let args = SequencerCli::parse();
|
||||
let mut seq = Self::new();
|
||||
let mut seq = Self::new("");
|
||||
if let Some(name) = args.name {
|
||||
seq.name = name.clone();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,170 +0,0 @@
|
|||
use crate::*;
|
||||
use tek_core::Direction;
|
||||
|
||||
/// A sequencer track.
|
||||
#[derive(Debug)]
|
||||
pub struct SequencerTrack {
|
||||
pub name: String,
|
||||
/// Play input through output.
|
||||
pub monitoring: bool,
|
||||
/// Write input to sequence.
|
||||
pub recording: bool,
|
||||
/// Overdub input to sequence.
|
||||
pub overdub: bool,
|
||||
/// Map: tick -> MIDI events at tick
|
||||
pub phrases: Vec<Arc<RwLock<Phrase>>>,
|
||||
/// Phrase selector
|
||||
pub sequence: Option<usize>,
|
||||
/// Output from current sequence.
|
||||
pub midi_out: Option<Port<MidiOut>>,
|
||||
/// MIDI output buffer
|
||||
midi_out_buf: Vec<Vec<Vec<u8>>>,
|
||||
/// Send all notes off
|
||||
pub reset: bool, // TODO?: after Some(nframes)
|
||||
/// Highlight keys on piano roll.
|
||||
pub notes_in: [bool;128],
|
||||
/// Highlight keys on piano roll.
|
||||
pub notes_out: [bool;128],
|
||||
}
|
||||
|
||||
impl SequencerTrack {
|
||||
pub fn new (name: &str) -> Usually<Self> {
|
||||
Ok(Self {
|
||||
name: name.to_string(),
|
||||
monitoring: false,
|
||||
recording: false,
|
||||
overdub: true,
|
||||
phrases: vec![],
|
||||
sequence: None,
|
||||
midi_out: None,
|
||||
midi_out_buf: vec![Vec::with_capacity(16);16384],
|
||||
reset: true,
|
||||
notes_in: [false;128],
|
||||
notes_out: [false;128],
|
||||
})
|
||||
}
|
||||
pub fn toggle_monitor (&mut self) {
|
||||
self.monitoring = !self.monitoring;
|
||||
}
|
||||
pub fn toggle_record (&mut self) {
|
||||
self.recording = !self.recording;
|
||||
}
|
||||
pub fn toggle_overdub (&mut self) {
|
||||
self.overdub = !self.overdub;
|
||||
}
|
||||
pub fn process (
|
||||
&mut self,
|
||||
input: Option<MidiIter>,
|
||||
timebase: &Arc<Timebase>,
|
||||
playing: Option<TransportState>,
|
||||
started: Option<(usize, usize)>,
|
||||
quant: usize,
|
||||
reset: bool,
|
||||
scope: &ProcessScope,
|
||||
(frame0, frames): (usize, usize),
|
||||
(_usec0, _usecs): (usize, usize),
|
||||
period: f64,
|
||||
) {
|
||||
if self.midi_out.is_some() {
|
||||
// Clear the section of the output buffer that we will be using
|
||||
for frame in &mut self.midi_out_buf[0..frames] {
|
||||
frame.clear();
|
||||
}
|
||||
// Emit "all notes off" at start of buffer if requested
|
||||
if self.reset {
|
||||
all_notes_off(&mut self.midi_out_buf);
|
||||
self.reset = false;
|
||||
} else if reset {
|
||||
all_notes_off(&mut self.midi_out_buf);
|
||||
}
|
||||
}
|
||||
if let (
|
||||
Some(TransportState::Rolling), Some((start_frame, _)), Some(phrase)
|
||||
) = (
|
||||
playing, started, self.sequence.and_then(|id|self.phrases.get_mut(id))
|
||||
) {
|
||||
phrase.read().map(|phrase|{
|
||||
if self.midi_out.is_some() {
|
||||
phrase.process_out(
|
||||
&mut self.midi_out_buf,
|
||||
&mut self.notes_out,
|
||||
timebase,
|
||||
(frame0.saturating_sub(start_frame), frames, period)
|
||||
);
|
||||
}
|
||||
}).unwrap();
|
||||
let mut phrase = phrase.write().unwrap();
|
||||
let length = phrase.length;
|
||||
// Monitor and record input
|
||||
if input.is_some() && (self.recording || self.monitoring) {
|
||||
// For highlighting keys and note repeat
|
||||
for (frame, event, bytes) in parse_midi_input(input.unwrap()) {
|
||||
match event {
|
||||
LiveEvent::Midi { message, .. } => {
|
||||
if self.monitoring {
|
||||
self.midi_out_buf[frame].push(bytes.to_vec())
|
||||
}
|
||||
if self.recording {
|
||||
phrase.record_event({
|
||||
let pulse = timebase.frame_to_pulse(
|
||||
(frame0 + frame - start_frame) as f64
|
||||
);
|
||||
let quantized = (
|
||||
pulse / quant as f64
|
||||
).round() as usize * quant;
|
||||
let looped = quantized % length;
|
||||
looped
|
||||
}, message);
|
||||
}
|
||||
match message {
|
||||
MidiMessage::NoteOn { key, .. } => {
|
||||
self.notes_in[key.as_int() as usize] = true;
|
||||
}
|
||||
MidiMessage::NoteOff { key, .. } => {
|
||||
self.notes_in[key.as_int() as usize] = false;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if input.is_some() && self.midi_out.is_some() && self.monitoring {
|
||||
for (frame, event, bytes) in parse_midi_input(input.unwrap()) {
|
||||
self.process_monitor_event(frame, &event, bytes)
|
||||
}
|
||||
}
|
||||
if let Some(out) = &mut self.midi_out {
|
||||
write_midi_output(&mut out.writer(scope), &self.midi_out_buf, frames);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn process_monitor_event (&mut self, frame: usize, event: &LiveEvent, bytes: &[u8]) {
|
||||
match event {
|
||||
LiveEvent::Midi { message, .. } => {
|
||||
self.write_to_output_buffer(frame, bytes);
|
||||
self.process_monitor_message(&message);
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline] fn write_to_output_buffer (&mut self, frame: usize, bytes: &[u8]) {
|
||||
self.midi_out_buf[frame].push(bytes.to_vec());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn process_monitor_message (&mut self, message: &MidiMessage) {
|
||||
match message {
|
||||
MidiMessage::NoteOn { key, .. } => {
|
||||
self.notes_in[key.as_int() as usize] = true;
|
||||
}
|
||||
MidiMessage::NoteOff { key, .. } => {
|
||||
self.notes_in[key.as_int() as usize] = false;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue