mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
210 lines
7.6 KiB
Rust
210 lines
7.6 KiB
Rust
use crate::core::*;
|
|
use crate::layout::*;
|
|
|
|
mod keys; use self::keys::*;
|
|
mod handle; pub use self::handle::*;
|
|
mod phrase; pub use self::phrase::*;
|
|
|
|
pub mod horizontal;
|
|
pub mod vertical;
|
|
|
|
pub struct Sequencer {
|
|
pub name: String,
|
|
/// JACK transport handle.
|
|
pub transport: ::jack::Transport,
|
|
/// JACK MIDI input port that will be created.
|
|
pub midi_in: Port<MidiIn>,
|
|
/// JACK MIDI output port that will be created.
|
|
pub midi_out: Port<MidiOut>,
|
|
/// Holds info about tempo
|
|
pub timebase: Arc<Timebase>,
|
|
/// Phrase selector
|
|
pub sequence: Option<usize>,
|
|
/// Map: tick -> MIDI events at tick
|
|
pub phrases: Vec<Phrase>,
|
|
/// Red keys on piano roll.
|
|
pub notes_on: Vec<bool>,
|
|
/// Play sequence to output.
|
|
pub playing: TransportState,
|
|
/// Play input through output.
|
|
pub monitoring: bool,
|
|
/// Write input to sequence.
|
|
pub recording: bool,
|
|
/// Don't delete when recording.
|
|
pub overdub: bool,
|
|
/// Display mode
|
|
pub view: SequencerView,
|
|
/// Range of notes to display
|
|
pub note_start: usize,
|
|
/// Position of cursor within note range
|
|
pub note_cursor: usize,
|
|
/// PPM per display unit
|
|
pub time_zoom: usize,
|
|
/// Range of time steps to display
|
|
pub time_start: usize,
|
|
/// Position of cursor within time range
|
|
pub time_cursor: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum SequencerView { Tiny, Compact, Horizontal, Vertical, }
|
|
|
|
impl Sequencer {
|
|
pub fn new (
|
|
name: &str,
|
|
timebase: &Arc<Timebase>,
|
|
phrases: Option<Vec<Phrase>>,
|
|
) -> Usually<DynamicDevice<Self>> {
|
|
let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
|
let transport = client.transport();
|
|
DynamicDevice::new(render, handle, Self::process, Self {
|
|
name: name.into(),
|
|
timebase: timebase.clone(),
|
|
phrases: phrases.unwrap_or_else(||vec![Phrase::default()]),
|
|
sequence: Some(0),
|
|
|
|
transport,
|
|
midi_in: client.register_port("in", MidiIn::default())?,
|
|
monitoring: true,
|
|
recording: true,
|
|
midi_out: client.register_port("out", MidiOut::default())?,
|
|
playing: TransportState::Starting,
|
|
overdub: true,
|
|
|
|
view: SequencerView::Horizontal,
|
|
notes_on: vec![false;128],
|
|
note_start: 12,
|
|
note_cursor: 0,
|
|
time_zoom: 24,
|
|
time_start: 0,
|
|
time_cursor: 0,
|
|
}).activate(client)
|
|
}
|
|
|
|
pub fn phrase <'a> (&'a self) -> Option<&'a Phrase> {
|
|
self.phrases.get(self.sequence?)
|
|
}
|
|
|
|
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
|
if self.sequence.is_none() { return Control::Continue }
|
|
let phrase = self.phrases.get_mut(self.sequence.unwrap());
|
|
if phrase.is_none() { return Control::Continue }
|
|
let phrase = phrase.unwrap();
|
|
let frame = scope.last_frame_time() as usize;
|
|
let frames = scope.n_frames() as usize;
|
|
let mut output: Vec<Option<Vec<Vec<u8>>>> = vec![None;frames];
|
|
let transport = self.transport.query().unwrap();
|
|
if transport.state != self.playing {
|
|
all_notes_off(&mut output);
|
|
}
|
|
self.playing = transport.state;
|
|
// Play from phrase into output buffer
|
|
if self.playing == TransportState::Rolling {
|
|
phrase.process_out(
|
|
&mut output,
|
|
&mut self.notes_on,
|
|
&self.timebase,
|
|
frame,
|
|
frames
|
|
);
|
|
}
|
|
// Play from input to monitor, and record into phrase.
|
|
phrase.process_in(
|
|
self.midi_in.iter(scope),
|
|
&mut self.notes_on,
|
|
if self.monitoring { Some(&mut output) } else { None },
|
|
self.recording && self.playing == TransportState::Rolling,
|
|
&self.timebase,
|
|
frame,
|
|
);
|
|
write_output(&mut self.midi_out.writer(scope), &mut output, frames);
|
|
Control::Continue
|
|
}
|
|
}
|
|
|
|
impl PortList for Sequencer {
|
|
fn midi_ins (&self) -> Usually<Vec<String>> { Ok(vec![self.midi_in.name()?]) }
|
|
fn midi_outs (&self) -> Usually<Vec<String>> { Ok(vec![self.midi_out.name()?]) }
|
|
}
|
|
fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
let Rect { x, y, width, height } = area;
|
|
let header = draw_header(s, buf, area)?;
|
|
let piano = match s.view {
|
|
SequencerView::Tiny => Rect { x, y, width, height: 0 },
|
|
SequencerView::Compact => Rect { x, y, width, height: 0 },
|
|
SequencerView::Vertical => self::vertical::draw(s, buf, Rect {
|
|
x, y: y + header.height, width, height,
|
|
})?,
|
|
SequencerView::Horizontal => self::horizontal::draw(
|
|
buf,
|
|
Rect { x, y: y + header.height, width, height, },
|
|
s.phrase(),
|
|
s.timebase.ppq() as usize,
|
|
s.time_cursor,
|
|
s.time_start,
|
|
s.time_zoom,
|
|
s.note_cursor,
|
|
s.note_start,
|
|
None
|
|
)?,
|
|
};
|
|
Ok(draw_box(buf, Rect {
|
|
x, y,
|
|
width: header.width.max(piano.width),
|
|
height: header.height + piano.height
|
|
}))
|
|
}
|
|
pub fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
let Rect { x, y, width, .. } = area;
|
|
let style = Style::default().gray();
|
|
crate::device::transport::draw_play_stop(buf, x + 2, y + 1, &s.playing);
|
|
let separator = format!("├{}┤", "-".repeat((width - 2).into()));
|
|
separator.blit(buf, x, y + 2, Some(style.dim()));
|
|
crate::device::transport::draw_rec(buf, x + 13, y + 1, s.recording);
|
|
crate::device::transport::draw_dub(buf, x + 20, y + 1, s.overdub);
|
|
crate::device::transport::draw_mon(buf, x + 27, y + 1, s.monitoring);
|
|
let _ = draw_clips(s, buf, area)?;
|
|
Ok(Rect { x, y, width, height: 3 })
|
|
}
|
|
pub fn draw_timer (buf: &mut Buffer, x: u16, y: u16, timer: &str) {
|
|
let style = Some(Style::default().gray().bold().not_dim());
|
|
timer.blit(buf, x - timer.len() as u16, y, style);
|
|
}
|
|
pub fn draw_clips (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
let Rect { x, y, .. } = area;
|
|
let style = Style::default().gray();
|
|
for (i, sequence) in s.phrases.iter().enumerate() {
|
|
let label = format!("▶ {}", &sequence.name);
|
|
label.blit(buf, x + 2, y + 3 + (i as u16)*2, Some(if Some(i) == s.sequence {
|
|
match s.playing {
|
|
TransportState::Rolling => style.white().bold(),
|
|
_ => style.not_dim().bold()
|
|
}
|
|
} else {
|
|
style.dim()
|
|
}));
|
|
}
|
|
Ok(Rect { x, y, width: 14, height: 14 })
|
|
}
|
|
/// Add "all notes off" to the start of a buffer.
|
|
pub fn all_notes_off (output: &mut MIDIChunk) {
|
|
output[0] = Some(vec![]);
|
|
if let Some(Some(frame)) = output.get_mut(0) {
|
|
let mut buf = vec![];
|
|
let msg = MidiMessage::Controller { controller: 123.into(), value: 0.into() };
|
|
let evt = LiveEvent::Midi { channel: 0.into(), message: msg };
|
|
evt.write(&mut buf).unwrap();
|
|
frame.push(buf);
|
|
}
|
|
}
|
|
/// Write to JACK port from output buffer (containing notes from sequence and/or monitor)
|
|
fn write_output (writer: &mut ::jack::MidiWriter, output: &mut MIDIChunk, frames: usize) {
|
|
for time in 0..frames {
|
|
if let Some(Some(frame)) = output.get_mut(time ) {
|
|
for event in frame.iter() {
|
|
writer.write(&::jack::RawMidi { time: time as u32, bytes: &event })
|
|
.expect(&format!("{event:?}"));
|
|
}
|
|
}
|
|
}
|
|
}
|