wip: refactor pt.6, fixed tek_api

This commit is contained in:
🪞👃🪞 2024-11-10 14:35:20 +01:00
parent 5df08409e5
commit 869d92110d
29 changed files with 1678 additions and 1679 deletions

View file

@ -1,5 +1,5 @@
use crate::*;
use tek_core::jack::*;
use tek_core::jack::{*, Transport as JackTransport};
/// Trait for things that have a JACK process callback.
pub trait Audio: Send + Sync {
@ -38,7 +38,7 @@ pub trait AudioEngine {
process: impl FnMut(&Arc<RwLock<Self>>, &Client, &ProcessScope) -> Control + Send + 'static
) -> Usually<Arc<RwLock<Self>>> where Self: Send + Sync + 'static;
fn client (&self) -> &Client;
fn transport (&self) -> Transport {
fn transport (&self) -> JackTransport {
self.client().transport()
}
fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {

View file

@ -10,11 +10,22 @@ pub struct Arrangement {
/// Collection of phrases.
pub phrases: Arc<RwLock<Vec<Phrase>>>,
/// Collection of tracks.
pub tracks: Vec<SequencerTrack>,
pub tracks: Vec<ArrangementTrack>,
/// Collection of scenes.
pub scenes: Vec<Scene>,
}
pub struct ArrangementTrack {
/// Name of track
pub name: Arc<RwLock<String>>,
/// Preferred width of track column
pub width: usize,
/// Identifying color of track
pub color: ItemColor,
/// The MIDI player for the track
pub player: MIDIPlayer
}
impl Audio for Arrangement {
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
for track in self.tracks.iter_mut() {
@ -24,6 +35,12 @@ impl Audio for Arrangement {
}
}
impl Arrangement {
fn is_stopped (&self) -> bool {
*self.clock.playing.read().unwrap() == Some(TransportState::Stopped)
}
}
#[derive(Clone)]
pub enum ArrangementCommand {
New,
@ -58,8 +75,8 @@ pub enum ArrangementTrackCommand {
#[derive(Clone)]
pub enum ArrangementClipCommand {
SetLoop(bool),
Get(usize, usize),
Put(usize, usize, Option<Arc<RwLock<Phrase>>>),
Edit(Option<Arc<RwLock<Phrase>>>),
SetLoop(bool),
}

View file

@ -18,7 +18,7 @@ submod! {
pool
sampler
sample
scene
scene scene_cmd
sequencer
track
transport transport_cmd

View file

@ -4,12 +4,16 @@ use crate::*;
#[derive(Debug)]
pub struct Plugin {
/// JACK client handle (needs to not be dropped for standalone mode to work).
pub jack: Arc<RwLock<JackClient>>,
pub name: String,
pub path: Option<String>,
pub plugin: Option<PluginKind>,
pub selected: usize,
pub mapping: bool,
pub jack: Arc<RwLock<JackClient>>,
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>>,
}
impl Plugin {
pub fn new_lv2 (
@ -24,6 +28,10 @@ impl Plugin {
plugin: Some(PluginKind::LV2(LV2Plugin::new(path)?)),
selected: 0,
mapping: false,
midi_ins: vec![],
midi_outs: vec![],
audio_ins: vec![],
audio_outs: vec![],
})
}
@ -57,7 +65,7 @@ impl Audio for Plugin {
})) => {
let urid = features.midi_urid();
input_buffer.clear();
for port in self.ports.midi_ins.values() {
for port in self.midi_ins.iter() {
let mut atom = ::livi::event::LV2AtomSequence::new(
&features,
scope.n_frames() as usize
@ -75,24 +83,20 @@ impl Audio for Plugin {
input_buffer.push(atom);
}
let mut outputs = vec![];
for _ in self.ports.midi_outs.iter() {
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(
input_buffer.iter()
)
.with_atom_sequence_outputs(
outputs.iter_mut()
)
.with_atom_sequence_inputs(input_buffer.iter())
.with_atom_sequence_outputs(outputs.iter_mut())
.with_audio_inputs(
self.ports.audio_ins.values().map(|o|o.as_slice(scope))
self.audio_ins.iter().map(|o|o.as_slice(scope))
)
.with_audio_outputs(
self.ports.audio_outs.values_mut().map(|o|o.as_mut_slice(scope))
self.audio_outs.iter_mut().map(|o|o.as_mut_slice(scope))
);
unsafe {
instance.run(scope.n_frames() as usize, ports).unwrap()

View file

@ -42,7 +42,7 @@ impl LV2Plugin {
}
impl LV2Plugin {
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<Self> {
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<Plugin> {
let mut name = String::new();
let mut path = String::new();
edn!(edn in args {

View file

@ -9,7 +9,7 @@ pub struct Sampler {
pub unmapped: Vec<Arc<RwLock<Sample>>>,
pub voices: Arc<RwLock<Vec<Voice>>>,
pub midi_in: Port<MidiIn>,
pub audio_outs: Vec<Port<MidiOut>>,
pub audio_outs: Vec<Port<AudioOut>>,
pub buffer: Vec<Vec<f32>>,
pub output_gain: f32
}
@ -52,33 +52,20 @@ impl Sampler {
_ => panic!("unexpected in sampler {name}: {edn:?}")
});
Ok(Sampler {
jack: jack.clone(),
name: name.into(),
mapped: samples,
unmapped: Default::default(),
voices: Default::default(),
ports: Default::default(),
buffer: Default::default(),
jack: jack.clone(),
name: name.into(),
mapped: samples,
unmapped: Default::default(),
voices: Default::default(),
buffer: Default::default(),
midi_in: jack.read().unwrap().register_port("in", MidiIn::default())?,
audio_outs: vec![],
output_gain: 0.
})
}
/// Immutable reference to sample at cursor.
pub fn sample (&self) -> Option<&Arc<RwLock<Sample>>> {
for (i, sample) in self.mapped.values().enumerate() {
if i == self.cursor.0 {
return Some(sample)
}
}
for (i, sample) in self.unmapped.iter().enumerate() {
if i + self.mapped.len() == self.cursor.0 {
return Some(sample)
}
}
None
}
/// Create [Voice]s from [Sample]s in response to MIDI input.
pub fn process_midi_in (&mut self, scope: &ProcessScope) {
for RawMidi { time, bytes } in self.ports.midi_ins.get("midi").unwrap().iter(scope) {
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, ref vel } = message {
if let Some(sample) = self.mapped.get(key) {
@ -117,7 +104,7 @@ impl Sampler {
}
/// Write output buffer to output ports.
pub fn write_output_buffer (&mut self, scope: &ProcessScope) {
for (i, port) in self.ports.audio_outs.values_mut().enumerate() {
for (i, port) in self.audio_outs.iter_mut().enumerate() {
let buffer = &self.buffer[i];
for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() {
*value = *buffer.get(i).unwrap_or(&0.0);

View file

@ -11,43 +11,60 @@ pub struct Scene {
}
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:?}")
});
Ok(Scene {
name: Arc::new(name.unwrap_or("").to_string().into()),
color: ItemColor::random(),
clips,
//TODO
//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:?}")
//});
//Ok(Scene {
//name: Arc::new(name.unwrap_or("").to_string().into()),
//color: ItemColor::random(),
//clips,
//})
//}
/// Returns the pulse length of the longest phrase in the scene
pub fn pulses (&self) -> usize {
self.clips.iter().fold(0, |a, p|{
a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0))
})
}
}
#[derive(Clone)]
pub enum SceneCommand {
Next,
Prev,
Add,
Delete,
MoveForward,
MoveBack,
RandomColor,
SetSize(usize),
SetZoom(usize),
/// Returns true if all phrases in the scene are
/// currently playing on the given collection of tracks.
pub fn is_playing (&self, tracks: &[MIDIPlayer]) -> bool {
self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate()
.all(|(track_index, clip)|match clip {
Some(clip) => tracks
.get(track_index)
.map(|track|if let Some((_, Some(phrase))) = &track.phrase {
*phrase.read().unwrap() == *clip.read().unwrap()
} else {
false
})
.unwrap_or(false),
None => true
})
}
pub fn clip (&self, index: usize) -> Option<&Arc<RwLock<Phrase>>> {
match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None }
}
}

View file

@ -0,0 +1,14 @@
use crate::*;
#[derive(Clone)]
pub enum SceneCommand {
Next,
Prev,
Add,
Delete,
MoveForward,
MoveBack,
RandomColor,
SetSize(usize),
SetZoom(usize),
}

View file

@ -1,38 +1,10 @@
use crate::*;
#[derive(Clone, PartialEq)]
pub enum SequencerCommand {
Focus(FocusCommand),
Transport(TransportCommand),
Phrases(PhrasePoolCommand),
Editor(PhraseEditorCommand),
}
#[derive(Clone, PartialEq)]
pub enum PhraseEditorCommand {
// TODO: 1-9 seek markers that by default start every 8th of the phrase
ToggleDirection,
EnterEditMode,
ExitEditMode,
NoteAppend,
NoteSet,
NoteCursorSet(usize),
NoteLengthSet(usize),
NoteScrollSet(usize),
TimeCursorSet(usize),
TimeScrollSet(usize),
TimeZoomSet(usize),
Go(Direction),
}
/// MIDI message structural
pub type PhraseData = Vec<Vec<MidiMessage>>;
#[derive(Debug)]
pub struct SequencerTrack {
/// Name of track
pub name: Arc<RwLock<String>>,
/// Preferred width of track column
pub width: usize,
/// Identifying color of track
pub color: ItemColor,
pub struct MIDIPlayer {
/// Global timebase
pub clock: Arc<Clock>,
/// Start time and phrase being played
@ -62,7 +34,7 @@ pub struct SequencerTrack {
}
/// JACK process callback for a sequencer's phrase player/recorder.
impl Audio for SequencerTrack {
impl Audio for MIDIPlayer {
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
let has_midi_outputs = self.has_midi_outputs();
let has_midi_inputs = self.has_midi_inputs();
@ -88,7 +60,33 @@ impl Audio for SequencerTrack {
}
/// Methods used primarily by the process callback
impl SequencerTrack {
impl MIDIPlayer {
pub fn new (
jack: &Arc<RwLock<JackClient>>,
clock: &Arc<Clock>,
name: &str
) -> Usually<Self> {
let jack = jack.read().unwrap();
Ok(Self {
clock: clock.clone(),
phrase: None,
next_phrase: None,
notes_in: Arc::new(RwLock::new([false;128])),
notes_out: Arc::new(RwLock::new([false;128])),
monitoring: false,
recording: false,
overdub: true,
reset: true,
midi_note: Vec::with_capacity(8),
midi_chunk: vec![Vec::with_capacity(16);16384],
midi_outputs: vec![
jack.client().register_port(format!("{name}_out0").as_str(), MidiOut::default())?
],
midi_inputs: vec![
jack.client().register_port(format!("{name}_in0").as_str(), MidiIn::default())?
],
})
}
fn is_rolling (&self) -> bool {
*self.clock.playing.read().unwrap() == Some(TransportState::Rolling)
}
@ -251,10 +249,28 @@ impl SequencerTrack {
}
}
}
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 enqueue_next (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
let start = self.clock.next_launch_pulse();
self.next_phrase = Some((
Instant::from_pulse(&self.clock.timebase(), start as f64),
phrase.map(|p|p.clone())
));
self.reset = true;
}
pub fn pulses_since_start (&self) -> Option<f64> {
if let Some((started, Some(_))) = self.phrase.as_ref() {
Some(self.clock.current.pulse.get() - started.pulse.get())
} else {
None
}
}
}
/// Add "all notes off" to the start of a buffer.
pub fn all_notes_off (output: &mut PhraseChunk) {
pub fn all_notes_off (output: &mut [Vec<Vec<u8>>]) {
let mut buf = vec![];
let msg = MidiMessage::Controller { controller: 123.into(), value: 0.into() };
let evt = LiveEvent::Midi { channel: 0.into(), message: msg };

View file

@ -22,7 +22,9 @@ pub trait MixerTrackDevice: Audio + Debug {
impl MixerTrackDevice for Sampler {}
impl MixerTrackDevice for LV2Plugin {}
impl MixerTrackDevice for Plugin {}
//impl MixerTrackDevice for LV2Plugin {}
impl MixerTrack {
const SYM_NAME: &'static str = ":name";
@ -37,8 +39,6 @@ impl MixerTrack {
audio_outs: vec![],
devices: vec![],
};
#[allow(unused_mut)]
let mut devices: Vec<JackClient> = vec![];
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(Self::SYM_NAME)) {
@ -51,8 +51,8 @@ impl MixerTrack {
Edn::List(args) => match args.get(0) {
// Add a sampler device to the track
Some(Edn::Symbol(Self::SYM_SAMPLER)) => {
track.add_device(
Sampler::from_edn(jack, &args[1..])?
track.devices.push(
Box::new(Sampler::from_edn(jack, &args[1..])?) as Box<dyn MixerTrackDevice>
);
panic!(
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"",
@ -62,8 +62,8 @@ impl MixerTrack {
},
// Add a LV2 plugin to the track.
Some(Edn::Symbol(Self::SYM_LV2)) => {
track.add_device(
LV2Plugin::from_edn(jack, &args[1..])?
track.devices.push(
Box::new(LV2Plugin::from_edn(jack, &args[1..])?) as Box<dyn MixerTrackDevice>
);
panic!(
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"",
@ -80,7 +80,4 @@ impl MixerTrack {
});
Ok(track)
}
pub fn add_device (&mut self, device: impl MixerTrackDevice) {
self.devices.push(device.boxed())
}
}

View file

@ -8,3 +8,23 @@ pub struct Voice {
pub position: usize,
pub velocity: f32,
}
impl Iterator for Voice {
type Item = [f32;2];
fn next (&mut self) -> Option<Self::Item> {
if self.after > 0 {
self.after = self.after - 1;
return Some([0.0, 0.0])
}
let sample = self.sample.read().unwrap();
if self.position < sample.end {
let position = self.position;
self.position = self.position + 1;
return sample.channels[0].get(position).map(|_amplitude|[
sample.channels[0][position] * self.velocity,
sample.channels[0][position] * self.velocity,
])
}
None
}
}