mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
big ass refactor (rip client)
This commit is contained in:
parent
94c1f83ef2
commit
8c3cf53c67
56 changed files with 2232 additions and 1891 deletions
46
src/model/chain.rs
Normal file
46
src/model/chain.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
use crate::{core::*, view::*};
|
||||
|
||||
pub struct Chain {
|
||||
pub name: String,
|
||||
pub focused: bool,
|
||||
pub focus: usize,
|
||||
pub items: Vec<Box<dyn Device>>,
|
||||
pub view: ChainViewMode,
|
||||
pub adding: bool,
|
||||
}
|
||||
render!(Chain = crate::view::chain::render);
|
||||
handle!(Chain = crate::control::chain::handle);
|
||||
process!(Chain);
|
||||
impl Chain {
|
||||
pub fn new (name: &str, items: Option<Vec<Box<dyn Device>>>) -> Usually<Self> {
|
||||
Ok(Self {
|
||||
name: name.into(),
|
||||
focused: false,
|
||||
focus: 0,
|
||||
items: items.unwrap_or_else(||vec![]),
|
||||
view: ChainViewMode::Column,
|
||||
adding: false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PortList for Chain {
|
||||
fn midi_ins (&self) -> Usually<Vec<String>> {
|
||||
if let Some(device) = self.items.get(0) {
|
||||
device.midi_ins()
|
||||
} else {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
fn audio_outs (&self) -> Usually<Vec<String>> {
|
||||
if let Some(device) = self.items.get(self.items.len().saturating_sub(1)) {
|
||||
device.audio_outs()
|
||||
} else {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process (_: &mut Chain, _: &Client, _: &ProcessScope) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
184
src/model/launcher.rs
Normal file
184
src/model/launcher.rs
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
use crate::{core::*, model::*};
|
||||
pub enum LauncherMode {
|
||||
Tracks,
|
||||
Sequencer,
|
||||
Chains
|
||||
}
|
||||
impl LauncherMode {
|
||||
pub fn is_chains (&self) -> bool {
|
||||
match self { Self::Chains => true, _ => false }
|
||||
}
|
||||
pub fn is_tracks (&self) -> bool {
|
||||
match self { Self::Tracks => true, _ => false }
|
||||
}
|
||||
pub fn is_sequencer (&self) -> bool {
|
||||
match self { Self::Sequencer => true, _ => false }
|
||||
}
|
||||
}
|
||||
pub struct Launcher {
|
||||
pub name: String,
|
||||
pub timebase: Arc<Timebase>,
|
||||
pub transport: ::jack::Transport,
|
||||
pub playing: TransportState,
|
||||
pub monitoring: bool,
|
||||
pub recording: bool,
|
||||
pub overdub: bool,
|
||||
pub current_frame: usize,
|
||||
pub cursor: (usize, usize),
|
||||
pub tracks: Vec<Track>,
|
||||
pub scenes: Vec<Scene>,
|
||||
pub show_help: bool,
|
||||
pub view: LauncherMode,
|
||||
}
|
||||
render!(Launcher = crate::view::launcher::render);
|
||||
handle!(Launcher = crate::control::launcher::handle);
|
||||
process!(Launcher |self, _client, _scope| {
|
||||
let transport = self.transport.query().unwrap();
|
||||
self.playing = transport.state;
|
||||
self.current_frame = transport.pos.frame() as usize;
|
||||
Control::Continue
|
||||
});
|
||||
impl PortList for Launcher {}
|
||||
impl Launcher {
|
||||
pub fn new (
|
||||
name: &str,
|
||||
timebase: &Arc<Timebase>,
|
||||
tracks: Option<Vec<Track>>,
|
||||
scenes: Option<Vec<Scene>>
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
||||
let transport = client.transport();
|
||||
let ppq = timebase.ppq() as usize;
|
||||
Ok(Self {
|
||||
name: name.into(),
|
||||
view: LauncherMode::Chains,
|
||||
playing: transport.query_state()?,
|
||||
transport,
|
||||
timebase: timebase.clone(),
|
||||
monitoring: true,
|
||||
recording: false,
|
||||
overdub: true,
|
||||
cursor: (2, 2),
|
||||
current_frame: 0,
|
||||
scenes: scenes.unwrap_or_else(||vec![Scene::new(&"Scene 1", &[None])]),
|
||||
tracks: if let Some(tracks) = tracks { tracks } else { vec![
|
||||
Track::new("Track 1", &timebase, None, Some(vec![
|
||||
Phrase::new("MIDI Clip 1", ppq * 4, Some(BTreeMap::from([
|
||||
( ppq * 0, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
|
||||
( ppq * 1, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
|
||||
( ppq * 2, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
|
||||
( ppq * 3, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
|
||||
])))
|
||||
]))?,
|
||||
] },
|
||||
show_help: true,
|
||||
})
|
||||
}
|
||||
pub fn connect (&self, midi_in: &str, audio_outs: &[&str]) -> Usually<()> {
|
||||
let (client, _status) = Client::new(
|
||||
&format!("{}-init", &self.name), ClientOptions::NO_START_SERVER
|
||||
)?;
|
||||
let midi_ins = client.ports(Some(midi_in), None, PortFlags::IS_OUTPUT);
|
||||
let audio_outs: Vec<Vec<String>> = audio_outs.iter()
|
||||
.map(|pattern|client.ports(Some(pattern), None, PortFlags::IS_INPUT))
|
||||
.collect();
|
||||
for (i, sequencer) in self.tracks.iter().enumerate() {
|
||||
for sequencer_midi_in in sequencer.midi_ins()?.iter() {
|
||||
for midi_in in midi_ins.iter() {
|
||||
client.connect_ports_by_name(&midi_in, &sequencer_midi_in)?;
|
||||
}
|
||||
}
|
||||
let chain: &Chain = &self.tracks[i].chain;
|
||||
for port in sequencer.midi_outs()?.iter() {
|
||||
for midi_in in chain.midi_ins()?.iter() {
|
||||
client.connect_ports_by_name(&port, &midi_in)?;
|
||||
}
|
||||
}
|
||||
for (j, port) in chain.audio_outs()?.iter().enumerate() {
|
||||
for audio_out in audio_outs[j % audio_outs.len()].iter() {
|
||||
client.connect_ports_by_name(&port, &audio_out)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn cols (&self) -> usize {
|
||||
(self.tracks.len() + 2) as usize
|
||||
}
|
||||
pub fn col (&self) -> usize {
|
||||
self.cursor.0 as usize
|
||||
}
|
||||
pub fn dec_col (&mut self) {
|
||||
self.cursor.0 = if self.cursor.0 > 0 {
|
||||
self.cursor.0 - 1
|
||||
} else {
|
||||
(self.cols() - 1) as usize
|
||||
}
|
||||
}
|
||||
pub fn inc_col (&mut self) {
|
||||
self.cursor.0 = if self.cursor.0 >= self.cols() - 1 {
|
||||
0
|
||||
} else {
|
||||
self.cursor.0 + 1
|
||||
}
|
||||
}
|
||||
pub fn rows (&self) -> usize {
|
||||
(self.scenes.len() + 2) as usize
|
||||
}
|
||||
pub fn row (&self) -> usize {
|
||||
self.cursor.1 as usize
|
||||
}
|
||||
pub fn dec_row (&mut self) {
|
||||
self.cursor.1 = if self.cursor.1 > 0 {
|
||||
self.cursor.1 - 1
|
||||
} else {
|
||||
self.rows() - 1
|
||||
}
|
||||
}
|
||||
pub fn inc_row (&mut self) {
|
||||
self.cursor.1 = if self.cursor.1 >= self.rows() - 1 {
|
||||
0
|
||||
} else {
|
||||
self.cursor.1 + 1
|
||||
}
|
||||
}
|
||||
|
||||
pub fn track (&self) -> Option<(usize, &Track)> {
|
||||
match self.col() { 0 => None, _ => {
|
||||
let id = self.col() as usize - 1;
|
||||
self.tracks.get(id).map(|t|(id, t))
|
||||
} }
|
||||
}
|
||||
pub fn track_mut (&mut self) -> Option<(usize, &mut Track)> {
|
||||
match self.col() { 0 => None, _ => {
|
||||
let id = self.col() as usize - 1;
|
||||
self.tracks.get_mut(id).map(|t|(id, t))
|
||||
} }
|
||||
}
|
||||
pub fn scene (&self) -> Option<(usize, &Scene)> {
|
||||
match self.row() { 0 => None, _ => {
|
||||
let id = self.row() as usize - 1;
|
||||
self.scenes.get(id).map(|t|(id, t))
|
||||
} }
|
||||
}
|
||||
pub fn scene_mut (&mut self) -> Option<(usize, &mut Scene)> {
|
||||
match self.row() { 0 => None, _ => {
|
||||
let id = self.row() as usize - 1;
|
||||
self.scenes.get_mut(id).map(|t|(id, t))
|
||||
} }
|
||||
}
|
||||
pub fn sequencer (&self) -> Option<&Sequencer> {
|
||||
Some(&self.track()?.1.sequencer)
|
||||
}
|
||||
pub fn sequencer_mut (&mut self) -> Option<&mut Sequencer> {
|
||||
Some(&mut self.track_mut()?.1.sequencer)
|
||||
}
|
||||
pub fn chain (&self) -> Option<&Chain> {
|
||||
Some(&self.track()?.1.chain)
|
||||
}
|
||||
pub fn phrase_id (&self) -> Option<usize> {
|
||||
let (track_id, _) = self.track()?;
|
||||
let (_, scene) = self.scene()?;
|
||||
*scene.clips.get(track_id)?
|
||||
}
|
||||
}
|
||||
29
src/model/looper.rs
Normal file
29
src/model/looper.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use crate::core::*;
|
||||
|
||||
pub struct Looper {
|
||||
name: String
|
||||
}
|
||||
|
||||
impl Looper {
|
||||
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
||||
Ok(DynamicDevice::new(render, handle, process, Self {
|
||||
name: name.into(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process (_: &mut Looper, _: &Client, _: &ProcessScope) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
pub fn render (_: &Looper, _: &mut Buffer, _: Rect) -> Usually<Rect> {
|
||||
Ok(Rect::default())
|
||||
}
|
||||
|
||||
pub fn handle (_: &mut Looper, _: &AppEvent) -> Usually<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub const ACTIONS: [(&'static str, &'static str);1] = [
|
||||
("Ins/Del", "Add/remove loop"),
|
||||
];
|
||||
132
src/model/mixer.rs
Normal file
132
src/model/mixer.rs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
use crate::core::*;
|
||||
|
||||
pub struct Mixer {
|
||||
pub name: String,
|
||||
pub tracks: Vec<MixerTrack>,
|
||||
pub selected_track: usize,
|
||||
pub selected_column: usize,
|
||||
}
|
||||
|
||||
impl Mixer {
|
||||
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
||||
let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
||||
Ok(DynamicDevice::new(
|
||||
crate::view::mixer::render,
|
||||
crate::control::mixer::handle,
|
||||
process,
|
||||
Self {
|
||||
name: name.into(),
|
||||
selected_column: 0,
|
||||
selected_track: 1,
|
||||
tracks: vec![
|
||||
MixerTrack::new(&client, 1, "Mono 1")?,
|
||||
MixerTrack::new(&client, 1, "Mono 2")?,
|
||||
MixerTrack::new(&client, 2, "Stereo 1")?,
|
||||
MixerTrack::new(&client, 2, "Stereo 2")?,
|
||||
MixerTrack::new(&client, 2, "Stereo 3")?,
|
||||
MixerTrack::new(&client, 2, "Bus 1")?,
|
||||
MixerTrack::new(&client, 2, "Bus 2")?,
|
||||
MixerTrack::new(&client, 2, "Mix")?,
|
||||
],
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process (
|
||||
_: &mut Mixer,
|
||||
_: &Client,
|
||||
_: &ProcessScope
|
||||
) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
pub struct MixerTrack {
|
||||
pub name: String,
|
||||
pub channels: u8,
|
||||
pub input_ports: Vec<Port<AudioIn>>,
|
||||
pub pre_gain_meter: f64,
|
||||
pub gain: f64,
|
||||
pub insert_ports: Vec<Port<AudioOut>>,
|
||||
pub return_ports: Vec<Port<AudioIn>>,
|
||||
pub post_gain_meter: f64,
|
||||
pub post_insert_meter: f64,
|
||||
pub level: f64,
|
||||
pub pan: f64,
|
||||
pub output_ports: Vec<Port<AudioOut>>,
|
||||
pub post_fader_meter: f64,
|
||||
pub route: String,
|
||||
}
|
||||
|
||||
impl MixerTrack {
|
||||
pub fn new (jack: &Client, channels: u8, name: &str) -> Result<Self, Box<dyn Error>> {
|
||||
let mut input_ports = vec![];
|
||||
let mut insert_ports = vec![];
|
||||
let mut return_ports = vec![];
|
||||
let mut output_ports = vec![];
|
||||
for channel in 1..=channels {
|
||||
input_ports.push(jack.register_port(&format!("{name} [input {channel}]"), AudioIn::default())?);
|
||||
output_ports.push(jack.register_port(&format!("{name} [out {channel}]"), AudioOut::default())?);
|
||||
let insert_port = jack.register_port(&format!("{name} [pre {channel}]"), AudioOut::default())?;
|
||||
let return_port = jack.register_port(&format!("{name} [insert {channel}]"), AudioIn::default())?;
|
||||
jack.connect_ports(&insert_port, &return_port)?;
|
||||
insert_ports.push(insert_port);
|
||||
return_ports.push(return_port);
|
||||
}
|
||||
Ok(Self {
|
||||
name: name.into(),
|
||||
channels,
|
||||
input_ports,
|
||||
pre_gain_meter: 0.0,
|
||||
gain: 0.0,
|
||||
post_gain_meter: 0.0,
|
||||
insert_ports,
|
||||
return_ports,
|
||||
post_insert_meter: 0.0,
|
||||
level: 0.0,
|
||||
pan: 0.0,
|
||||
post_fader_meter: 0.0,
|
||||
route: "---".into(),
|
||||
output_ports,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//impl<W: Write> Input<TUI<W>, bool> for Mixer {
|
||||
//fn handle (&mut self, engine: &mut TUI<W>) -> Result<Option<bool>> {
|
||||
//Ok(None)
|
||||
//}
|
||||
//
|
||||
|
||||
//impl<W: Write> Output<TUI<W>, [u16;2]> for Mixer {
|
||||
//fn render (&self, envine: &mut TUI<W>) -> Result<Option<[u16;2]>> {
|
||||
|
||||
//let tracks_table = Columns::new()
|
||||
//.add(titles)
|
||||
//.add(input_meters)
|
||||
//.add(gains)
|
||||
//.add(gain_meters)
|
||||
//.add(pres)
|
||||
//.add(pre_meters)
|
||||
//.add(levels)
|
||||
//.add(pans)
|
||||
//.add(pan_meters)
|
||||
//.add(posts)
|
||||
//.add(routes)
|
||||
|
||||
//Rows::new()
|
||||
//.add(Columns::new()
|
||||
//.add(Rows::new()
|
||||
//.add("[Arrows]".bold())
|
||||
//.add("Navigate"))
|
||||
//.add(Rows::new()
|
||||
//.add("[+/-]".bold())
|
||||
//.add("Adjust"))
|
||||
//.add(Rows::new()
|
||||
//.add("[Ins/Del]".bold())
|
||||
//.add("Add/remove track")))
|
||||
//.add(tracks_table)
|
||||
//.render(engine)
|
||||
//}
|
||||
//}
|
||||
|
||||
129
src/model/phrase.rs
Normal file
129
src/model/phrase.rs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
use crate::core::*;
|
||||
|
||||
pub struct Phrase {
|
||||
pub name: String,
|
||||
pub length: usize,
|
||||
pub notes: PhraseData,
|
||||
}
|
||||
|
||||
impl Default for Phrase {
|
||||
fn default () -> Self {
|
||||
Self::new("", 0, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Phrase {
|
||||
pub fn new (name: &str, length: usize, notes: Option<PhraseData>) -> Self {
|
||||
Self { name: name.to_string(), length, notes: notes.unwrap_or(BTreeMap::new()) }
|
||||
}
|
||||
/// Check if a range `start..end` contains MIDI Note On `k`
|
||||
pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool {
|
||||
for (_, (_, events)) in self.notes.range(start..end).enumerate() {
|
||||
for event in events.iter() {
|
||||
match event {
|
||||
MidiMessage::NoteOn {key,..} => {
|
||||
if *key == k {
|
||||
return true
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
/// Write a chunk of MIDI events to an output port.
|
||||
pub fn process_out (
|
||||
&self,
|
||||
output: &mut MIDIChunk,
|
||||
notes_on: &mut Vec<bool>,
|
||||
timebase: &Arc<Timebase>,
|
||||
frame0: usize,
|
||||
frames: usize,
|
||||
) {
|
||||
let start = frame0 as f64;
|
||||
let end = start + frames as f64;
|
||||
let repeat = timebase.pulses_frames(self.length as f64);
|
||||
let ticks = timebase.frames_to_ticks(start, end, repeat);
|
||||
//panic!("{start} {end} {repeat} {ticks:?}");
|
||||
for (time, tick) in ticks.iter() {
|
||||
let events = self.notes.get(&(*tick as usize));
|
||||
if events.is_none() {
|
||||
continue
|
||||
}
|
||||
for message in events.unwrap().iter() {
|
||||
let mut buf = vec![];
|
||||
let channel = 0.into();
|
||||
let message = *message;
|
||||
match message {
|
||||
MidiMessage::NoteOn { key, .. } => notes_on[key.as_int() as usize] = true,
|
||||
MidiMessage::NoteOff { key, .. } => notes_on[key.as_int() as usize] = false,
|
||||
_ => {}
|
||||
}
|
||||
LiveEvent::Midi { channel, message }.write(&mut buf).unwrap();
|
||||
let t = *time as usize;
|
||||
if output[t].is_none() {
|
||||
output[t] = Some(vec![]);
|
||||
}
|
||||
if let Some(Some(frame)) = output.get_mut(t) {
|
||||
frame.push(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Read a chunk of MIDI events from an input port.
|
||||
pub fn process_in (
|
||||
&mut self,
|
||||
input: ::jack::MidiIter,
|
||||
notes_on: &mut Vec<bool>,
|
||||
mut monitor: Option<&mut MIDIChunk>,
|
||||
record: bool,
|
||||
timebase: &Arc<Timebase>,
|
||||
frame0: usize,
|
||||
) {
|
||||
for RawMidi { time, bytes } in input {
|
||||
let time = time as usize;
|
||||
let pulse = timebase.frames_pulses((frame0 + time) as f64) as usize;
|
||||
if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
|
||||
if let MidiMessage::NoteOn { key, vel: _ } = message {
|
||||
notes_on[key.as_int() as usize] = true;
|
||||
if let Some(ref mut monitor) = monitor {
|
||||
if monitor[time].is_none() {
|
||||
monitor[time] = Some(vec![]);
|
||||
}
|
||||
if let Some(Some(frame)) = monitor.get_mut(time) {
|
||||
frame.push(bytes.into())
|
||||
}
|
||||
}
|
||||
if record {
|
||||
let contains = self.notes.contains_key(&pulse);
|
||||
if contains {
|
||||
self.notes.get_mut(&pulse).unwrap().push(message.clone());
|
||||
} else {
|
||||
self.notes.insert(pulse, vec![message.clone()]);
|
||||
}
|
||||
}
|
||||
} else if let midly::MidiMessage::NoteOff { key, vel: _ } = message {
|
||||
notes_on[key.as_int() as usize] = false;
|
||||
if let Some(ref mut monitor) = monitor {
|
||||
if monitor[time].is_none() {
|
||||
monitor[time] = Some(vec![]);
|
||||
}
|
||||
if let Some(Some(frame)) = monitor.get_mut(time) {
|
||||
frame.push(bytes.into())
|
||||
}
|
||||
}
|
||||
if record {
|
||||
let contains = self.notes.contains_key(&pulse);
|
||||
if contains {
|
||||
self.notes.get_mut(&pulse).unwrap().push(message.clone());
|
||||
} else {
|
||||
self.notes.insert(pulse, vec![message.clone()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
176
src/model/plugin.rs
Normal file
176
src/model/plugin.rs
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
use crate::core::*;
|
||||
|
||||
pub mod lv2;
|
||||
pub mod vst2;
|
||||
pub mod vst3;
|
||||
|
||||
use self::lv2::*;
|
||||
|
||||
pub struct Plugin {
|
||||
pub name: String,
|
||||
pub path: Option<String>,
|
||||
pub plugin: Option<PluginKind>,
|
||||
pub selected: usize,
|
||||
pub mapping: bool,
|
||||
pub midi_ins: Vec<Port<MidiIn>>,
|
||||
pub midi_outs: Vec<Port<MidiOut>>,
|
||||
pub audio_ins: Vec<Port<AudioIn>>,
|
||||
pub audio_outs: Vec<Port<AudioOut>>,
|
||||
}
|
||||
|
||||
pub enum PluginKind {
|
||||
LV2(LV2Plugin),
|
||||
VST2 {
|
||||
instance: ::vst::host::PluginInstance
|
||||
},
|
||||
VST3,
|
||||
}
|
||||
|
||||
const HELM: &'static str = "file:///nix/store/ij3sz7nqg5l7v2dygdvzy3w6cj62bd6r-helm-0.9.0/lib/lv2/helm.lv2";
|
||||
|
||||
impl Plugin {
|
||||
/// Load a LV2 plugin.
|
||||
pub fn lv2 (name: &str, path: &str) -> Usually<DynamicDevice<Self>> {
|
||||
let host = Self::new(name)?;
|
||||
let plugin = LV2Plugin::new(path)?;
|
||||
let mut state = host.state();
|
||||
let client = host.client.as_ref().unwrap().as_client();
|
||||
let (midi_ins, midi_outs, audio_ins, audio_outs) = (
|
||||
plugin.plugin.port_counts().atom_sequence_inputs,
|
||||
plugin.plugin.port_counts().atom_sequence_outputs,
|
||||
plugin.plugin.port_counts().audio_inputs,
|
||||
plugin.plugin.port_counts().audio_outputs,
|
||||
);
|
||||
state.midi_ins = {
|
||||
let mut ports = vec![];
|
||||
for i in 0..midi_ins {
|
||||
ports.push(client.register_port(&format!("midi-in-{i}"), MidiIn::default())?)
|
||||
}
|
||||
ports
|
||||
};
|
||||
state.midi_outs = {
|
||||
let mut ports = vec![];
|
||||
for i in 0..midi_outs {
|
||||
ports.push(client.register_port(&format!("midi-out-{i}"), MidiOut::default())?)
|
||||
}
|
||||
ports
|
||||
};
|
||||
state.audio_ins = {
|
||||
let mut ports = vec![];
|
||||
for i in 0..audio_ins {
|
||||
ports.push(client.register_port(&format!("audio-in-{i}"), AudioIn::default())?)
|
||||
}
|
||||
ports
|
||||
};
|
||||
state.audio_outs = {
|
||||
let mut ports = vec![];
|
||||
for i in 0..audio_outs {
|
||||
ports.push(client.register_port(&format!("audio-out-{i}"), AudioOut::default())?)
|
||||
}
|
||||
ports
|
||||
};
|
||||
state.plugin = Some(PluginKind::LV2(plugin));
|
||||
state.path = Some(String::from(path));
|
||||
std::mem::drop(state);
|
||||
Ok(host)
|
||||
}
|
||||
pub fn new (name: &str) -> Usually<DynamicDevice<Self>> {
|
||||
DynamicDevice::new(
|
||||
crate::view::plugin::render,
|
||||
crate::control::plugin::handle,
|
||||
Self::process,
|
||||
Self {
|
||||
name: name.into(),
|
||||
path: None,
|
||||
plugin: None,
|
||||
selected: 0,
|
||||
mapping: false,
|
||||
midi_ins: vec![],
|
||||
midi_outs: vec![],
|
||||
audio_ins: vec![],
|
||||
audio_outs: vec![],
|
||||
}
|
||||
).activate(Client::new(name, ClientOptions::NO_START_SERVER)?.0)
|
||||
}
|
||||
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||
match self.plugin.as_mut() {
|
||||
Some(PluginKind::LV2(LV2Plugin { features, ref mut instance, .. })) => {
|
||||
let urid = features.midi_urid();
|
||||
let mut inputs = vec![];
|
||||
for port in self.midi_ins.iter() {
|
||||
let mut atom = ::livi::event::LV2AtomSequence::new(
|
||||
&features,
|
||||
scope.n_frames() as usize
|
||||
);
|
||||
for event in port.iter(scope) {
|
||||
match event.bytes.len() {
|
||||
3 => atom.push_midi_event::<3>(
|
||||
event.time as i64,
|
||||
urid,
|
||||
&event.bytes[0..3]
|
||||
).unwrap(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
inputs.push(atom);
|
||||
}
|
||||
let mut outputs = vec![];
|
||||
for _ in self.midi_outs.iter() {
|
||||
outputs.push(::livi::event::LV2AtomSequence::new(
|
||||
&features,
|
||||
scope.n_frames() as usize
|
||||
));
|
||||
}
|
||||
let ports = ::livi::EmptyPortConnections::new()
|
||||
.with_atom_sequence_inputs(
|
||||
inputs.iter()
|
||||
)
|
||||
.with_atom_sequence_outputs(
|
||||
outputs.iter_mut()
|
||||
)
|
||||
.with_audio_inputs(
|
||||
self.audio_ins.iter().map(|o|o.as_slice(scope))
|
||||
)
|
||||
.with_audio_outputs(
|
||||
self.audio_outs.iter_mut().map(|o|o.as_mut_slice(scope))
|
||||
);
|
||||
unsafe {
|
||||
instance.run(scope.n_frames() as usize, ports).unwrap()
|
||||
};
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
|
||||
impl PortList for Plugin {
|
||||
fn audio_ins (&self) -> Usually<Vec<String>> {
|
||||
let mut ports = vec![];
|
||||
for port in self.audio_ins.iter() {
|
||||
ports.push(port.name()?);
|
||||
}
|
||||
Ok(ports)
|
||||
}
|
||||
fn audio_outs (&self) -> Usually<Vec<String>> {
|
||||
let mut ports = vec![];
|
||||
for port in self.audio_outs.iter() {
|
||||
ports.push(port.name()?);
|
||||
}
|
||||
Ok(ports)
|
||||
}
|
||||
fn midi_ins (&self) -> Usually<Vec<String>> {
|
||||
let mut ports = vec![];
|
||||
for port in self.midi_ins.iter() {
|
||||
ports.push(port.name()?);
|
||||
}
|
||||
Ok(ports)
|
||||
}
|
||||
fn midi_outs (&self) -> Usually<Vec<String>> {
|
||||
let mut ports = vec![];
|
||||
for port in self.midi_outs.iter() {
|
||||
ports.push(port.name()?);
|
||||
}
|
||||
Ok(ports)
|
||||
}
|
||||
}
|
||||
42
src/model/plugin/lv2.rs
Normal file
42
src/model/plugin/lv2.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use crate::core::*;
|
||||
|
||||
pub struct LV2Plugin {
|
||||
pub world: ::livi::World,
|
||||
pub instance: ::livi::Instance,
|
||||
pub plugin: ::livi::Plugin,
|
||||
pub features: Arc<::livi::Features>,
|
||||
pub port_list: Vec<::livi::Port>,
|
||||
}
|
||||
|
||||
impl LV2Plugin {
|
||||
pub fn new (uri: &str) -> Usually<Self> {
|
||||
// Get 1st plugin at URI
|
||||
let world = ::livi::World::with_load_bundle(&uri);
|
||||
let features = ::livi::FeaturesBuilder { min_block_length: 1, max_block_length: 65536 };
|
||||
let features = world.build_features(features);
|
||||
let mut plugin = None;
|
||||
for p in world.iter_plugins() {
|
||||
plugin = Some(p);
|
||||
break
|
||||
}
|
||||
let plugin = plugin.unwrap();
|
||||
|
||||
// Instantiate
|
||||
Ok(Self {
|
||||
world,
|
||||
instance: unsafe {
|
||||
plugin.instantiate(features.clone(), 48000.0).expect("boop")
|
||||
},
|
||||
port_list: {
|
||||
let mut port_list = vec![];
|
||||
for port in plugin.ports() {
|
||||
port_list.push(port);
|
||||
}
|
||||
port_list
|
||||
},
|
||||
plugin,
|
||||
features,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
15
src/model/plugin/vst2.rs
Normal file
15
src/model/plugin/vst2.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use crate::core::*;
|
||||
use super::*;
|
||||
|
||||
impl ::vst::host::Host for Plugin {}
|
||||
|
||||
fn set_vst_plugin (host: &Arc<Mutex<Plugin>>, _path: &str) -> Usually<PluginKind> {
|
||||
let mut loader = ::vst::host::PluginLoader::load(
|
||||
&std::path::Path::new("/nix/store/ij3sz7nqg5l7v2dygdvzy3w6cj62bd6r-helm-0.9.0/lib/lxvst/helm.so"),
|
||||
host.clone()
|
||||
)?;
|
||||
Ok(PluginKind::VST2 {
|
||||
instance: loader.instance()?
|
||||
})
|
||||
}
|
||||
|
||||
0
src/model/plugin/vst3.rs
Normal file
0
src/model/plugin/vst3.rs
Normal file
175
src/model/sampler.rs
Normal file
175
src/model/sampler.rs
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
use crate::core::*;
|
||||
|
||||
pub struct Voice {
|
||||
pub sample: Arc<Sample>,
|
||||
pub after: usize,
|
||||
pub position: usize,
|
||||
}
|
||||
|
||||
pub struct Sample {
|
||||
pub name: String,
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub channels: Vec<Vec<f32>>,
|
||||
}
|
||||
|
||||
pub struct Sampler {
|
||||
pub name: String,
|
||||
pub cursor: (usize, usize),
|
||||
pub samples: BTreeMap<u7, Arc<Sample>>,
|
||||
pub voices: Vec<Voice>,
|
||||
pub midi_in: Port<MidiIn>,
|
||||
pub audio_ins: Vec<Port<AudioIn>>,
|
||||
pub audio_outs: Vec<Port<AudioOut>>,
|
||||
}
|
||||
|
||||
impl Voice {
|
||||
pub fn chunk (&mut self, mut frames: usize) -> Option<Vec<Vec<f32>>> {
|
||||
// Create output buffer for each channel
|
||||
let mut chunk = vec![vec![];self.sample.channels.len()];
|
||||
// If it's not time to play yet, count down
|
||||
if self.after >= frames {
|
||||
self.after = self.after - frames;
|
||||
return Some(chunk)
|
||||
}
|
||||
// If the voice will start playing within the current buffer,
|
||||
// subtract the remaining number of wait frames.
|
||||
if self.after > 0 && self.after < frames {
|
||||
chunk = vec![vec![0.0;self.after];self.sample.channels.len()];
|
||||
frames = frames - self.after;
|
||||
self.after = 0;
|
||||
}
|
||||
if self.position < self.sample.end {
|
||||
let start = self.position.min(self.sample.end);
|
||||
let end = (self.position + frames).min(self.sample.end);
|
||||
for (i, channel) in self.sample.channels.iter().enumerate() {
|
||||
chunk[i].extend_from_slice(&channel[start..end]);
|
||||
};
|
||||
self.position = self.position + frames;
|
||||
Some(chunk)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sample {
|
||||
pub fn new (name: &str, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Arc<Self> {
|
||||
Arc::new(Self { name: name.to_string(), start, end, channels })
|
||||
}
|
||||
pub fn play (self: &Arc<Self>, after: usize) -> Voice {
|
||||
Voice { sample: self.clone(), after, position: self.start }
|
||||
}
|
||||
}
|
||||
|
||||
impl Sampler {
|
||||
pub fn new (
|
||||
name: &str,
|
||||
samples: Option<BTreeMap<u7, Arc<Sample>>>,
|
||||
) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
||||
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
||||
DynamicDevice::new(
|
||||
crate::view::sampler::render,
|
||||
crate::control::sampler::handle,
|
||||
Self::process,
|
||||
Self {
|
||||
name: name.into(),
|
||||
cursor: (0, 0),
|
||||
samples: samples.unwrap_or(BTreeMap::new()),
|
||||
voices: vec![],
|
||||
midi_in: client.register_port("midi", ::jack::MidiIn::default())?,
|
||||
audio_ins: vec![
|
||||
client.register_port("recL", ::jack::AudioIn::default())?,
|
||||
client.register_port("recR", ::jack::AudioIn::default())?,
|
||||
],
|
||||
audio_outs: vec![
|
||||
client.register_port("outL", ::jack::AudioOut::default())?,
|
||||
client.register_port("outR", ::jack::AudioOut::default())?,
|
||||
],
|
||||
}).activate(client)
|
||||
}
|
||||
|
||||
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||
// Output buffer: this will be copied to the audio outs.
|
||||
let channel_count = self.audio_outs.len();
|
||||
let mut mixed = vec![vec![0.0;scope.n_frames() as usize];channel_count];
|
||||
// Process MIDI input to add new voices.
|
||||
for RawMidi { time, bytes } in self.midi_in.iter(scope) {
|
||||
if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
|
||||
if let MidiMessage::NoteOn { ref key, .. } = message {
|
||||
if let Some(sample) = self.samples.get(key) {
|
||||
self.voices.push(sample.play(time as usize));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Emit next chunk of each currently playing voice,
|
||||
// dropping voices that have reached their ends.
|
||||
let mut voices = vec![];
|
||||
std::mem::swap(&mut voices, &mut self.voices);
|
||||
loop {
|
||||
if voices.len() < 1 {
|
||||
break
|
||||
}
|
||||
let mut voice = voices.swap_remove(0);
|
||||
if let Some(chunk) = voice.chunk(scope.n_frames() as usize) {
|
||||
for (i, channel) in chunk.iter().enumerate() {
|
||||
let buffer = &mut mixed[i % channel_count];
|
||||
for (i, sample) in channel.iter().enumerate() {
|
||||
buffer[i] += sample;
|
||||
}
|
||||
}
|
||||
self.voices.push(voice);
|
||||
}
|
||||
}
|
||||
// Write output buffer to output ports.
|
||||
for (i, port) in self.audio_outs.iter_mut().enumerate() {
|
||||
let buffer = &mixed[i];
|
||||
for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() {
|
||||
*value = *buffer.get(i).unwrap_or(&0.0);
|
||||
}
|
||||
}
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
fn load_sample (&mut self, _path: &str) {}
|
||||
}
|
||||
|
||||
impl PortList for Sampler {
|
||||
fn midi_ins (&self) -> Usually<Vec<String>> {
|
||||
Ok(vec![self.midi_in.name()?])
|
||||
}
|
||||
fn audio_ins (&self) -> Usually<Vec<String>> {
|
||||
let mut ports = vec![];
|
||||
for port in self.audio_ins.iter() {
|
||||
ports.push(port.name()?);
|
||||
}
|
||||
Ok(ports)
|
||||
}
|
||||
fn audio_outs (&self) -> Usually<Vec<String>> {
|
||||
let mut ports = vec![];
|
||||
for port in self.audio_outs.iter() {
|
||||
ports.push(port.name()?);
|
||||
}
|
||||
Ok(ports)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! sample {
|
||||
($note:expr, $name:expr, $src:expr) => {
|
||||
{
|
||||
let mut channels: Vec<wavers::Samples<f32>> = vec![];
|
||||
for channel in wavers::Wav::from_path($src)?.channels() {
|
||||
channels.push(channel);
|
||||
}
|
||||
let mut end = 0;
|
||||
let mut data: Vec<Vec<f32>> = vec![];
|
||||
for samples in channels.iter() {
|
||||
let channel = Vec::from(samples.as_ref());
|
||||
end = end.max(channel.len());
|
||||
data.push(channel);
|
||||
}
|
||||
(u7::from_int_lossy($note).into(), Sample::new($name, 0, end, data).into())
|
||||
}
|
||||
};
|
||||
}
|
||||
13
src/model/scene.rs
Normal file
13
src/model/scene.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
pub struct Scene {
|
||||
pub name: String,
|
||||
pub clips: Vec<Option<usize>>,
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
141
src/model/sequencer.rs
Normal file
141
src/model/sequencer.rs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
use crate::core::*;
|
||||
use crate::model::*;
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SequencerMode { Tiny, Compact, Horizontal, 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: SequencerMode,
|
||||
/// 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,
|
||||
}
|
||||
render!(Sequencer = crate::view::sequencer::render);
|
||||
handle!(Sequencer = crate::control::sequencer::handle);
|
||||
process!(Sequencer |self, _client, scope| {
|
||||
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 Sequencer {
|
||||
pub fn new (
|
||||
name: &str,
|
||||
timebase: &Arc<Timebase>,
|
||||
phrases: Option<Vec<Phrase>>,
|
||||
) -> Usually<Self> {
|
||||
let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
||||
let transport = client.transport();
|
||||
Ok(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: SequencerMode::Horizontal,
|
||||
notes_on: vec![false;128],
|
||||
note_start: 12,
|
||||
note_cursor: 0,
|
||||
time_zoom: 24,
|
||||
time_start: 0,
|
||||
time_cursor: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn phrase <'a> (&'a self) -> Option<&'a Phrase> {
|
||||
self.phrases.get(self.sequence?)
|
||||
}
|
||||
}
|
||||
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()?]) }
|
||||
}
|
||||
/// 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:?}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
48
src/model/track.rs
Normal file
48
src/model/track.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
use crate::core::*;
|
||||
use crate::model::*;
|
||||
|
||||
pub struct Track {
|
||||
pub name: String,
|
||||
pub sequencer: Sequencer,
|
||||
pub chain: Chain,
|
||||
}
|
||||
|
||||
impl Track {
|
||||
pub fn new (
|
||||
name: &str,
|
||||
tempo: &Arc<Timebase>,
|
||||
devices: Option<Vec<Box<dyn Device>>>,
|
||||
phrases: Option<Vec<Phrase>>,
|
||||
) -> Usually<Self> {
|
||||
let sequencer = Sequencer::new(&name, tempo, phrases)?;
|
||||
let chain = Chain::new(&name, devices)?;
|
||||
let (client, _status) = Client::new("init", ClientOptions::NO_START_SERVER)?;
|
||||
{
|
||||
if let (Some(output), Some(input)) = (
|
||||
sequencer.midi_outs()?.get(0).clone(),
|
||||
if let Some(item) = chain.items.get(0) {
|
||||
if let Some(port) = item.midi_ins()?.get(0) {
|
||||
Some(port.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
) {
|
||||
client.connect_ports_by_name(&output, &input)?;
|
||||
}
|
||||
}
|
||||
Ok(Self { name: name.to_string(), sequencer, chain })
|
||||
}
|
||||
}
|
||||
|
||||
impl PortList for Track {
|
||||
fn midi_ins (&self) -> Usually<Vec<String>> {
|
||||
self.sequencer.midi_ins()
|
||||
}
|
||||
fn audio_outs (&self) -> Usually<Vec<String>> {
|
||||
self.chain.audio_outs()
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue