mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
wip: refresh mixer components
This commit is contained in:
parent
1dbc5d1bb7
commit
bc679ef8bd
16 changed files with 296 additions and 287 deletions
|
|
@ -2,20 +2,38 @@ use crate::*;
|
|||
use jack::*;
|
||||
/// Trait for things that wrap a JACK client.
|
||||
pub trait AudioEngine {
|
||||
fn activate(
|
||||
fn activate (
|
||||
self,
|
||||
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 client (&self) -> &Client;
|
||||
fn transport (&self) -> Transport {
|
||||
self.client().transport()
|
||||
}
|
||||
fn port_by_name(&self, name: &str) -> Option<Port<Unowned>> {
|
||||
fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
|
||||
self.client().port_by_name(name)
|
||||
}
|
||||
fn register_port<PS: PortSpec>(&self, name: &str, spec: PS) -> Usually<Port<PS>> {
|
||||
fn register_port <PS: PortSpec> (&self, name: &str, spec: PS) -> Usually<Port<PS>> {
|
||||
Ok(self.client().register_port(name, spec)?)
|
||||
}
|
||||
fn thread_init (&self, _: &Client) {}
|
||||
unsafe fn shutdown (&mut self, status: ClientStatus, reason: &str) {}
|
||||
fn freewheel (&mut self, _: &Client, enabled: bool) {}
|
||||
fn client_registration (&mut self, _: &Client, name: &str, reg: bool) {}
|
||||
fn port_registration (&mut self, _: &Client, id: PortId, reg: bool) {}
|
||||
fn ports_connected (&mut self, _: &Client, a: PortId, b: PortId, are: bool) {}
|
||||
fn sample_rate (&mut self, _: &Client, frames: Frames) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
fn graph_reorder (&mut self, _: &Client) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
fn xrun (&mut self, _: &Client) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
/// Wraps [Client] or [DynamicAsyncClient] in place.
|
||||
pub enum JackClient {
|
||||
|
|
|
|||
|
|
@ -10,13 +10,8 @@ pub(crate) use std::ffi::OsString;
|
|||
pub(crate) use std::fs::read_dir;
|
||||
|
||||
submod! {
|
||||
mixer
|
||||
plugin
|
||||
plugin_lv2
|
||||
plugin_lv2_gui
|
||||
plugin_vst2
|
||||
plugin_vst3
|
||||
sampler
|
||||
track
|
||||
voice
|
||||
mixer mixer_snd
|
||||
track track_edn
|
||||
plugin plugin_snd plugin_edn plugin_lv2 plugin_lv2_gui plugin_vst2 plugin_vst3
|
||||
sampler sampler_snd sampler_edn
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,11 +27,6 @@ impl<E: Engine> Mixer<E> {
|
|||
self.tracks.get(self.selected_track)
|
||||
}
|
||||
}
|
||||
impl<E: Engine> Audio for Mixer<E> {
|
||||
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
impl Content for Mixer<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
|
|
|
|||
6
crates/tek_mixer/src/mixer_snd.rs
Normal file
6
crates/tek_mixer/src/mixer_snd.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
use crate::*;
|
||||
impl<E: Engine> Audio for Mixer<E> {
|
||||
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
|
|
@ -32,82 +32,23 @@ impl<E> Plugin<E> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Plugin<Tui> {
|
||||
impl<E: Engine> Plugin<E> {
|
||||
pub fn new_lv2 (
|
||||
jack: &Arc<RwLock<JackClient>>,
|
||||
name: &str,
|
||||
path: &str,
|
||||
) -> Usually<JackDevice<Tui>> {
|
||||
) -> Usually<JackDevice<E>> {
|
||||
let plugin = LV2Plugin::new(path)?;
|
||||
jack_from_lv2(name, &plugin.plugin)?
|
||||
.run(|ports|Box::new(Self {
|
||||
_engine: Default::default(),
|
||||
jack: jack.clone(),
|
||||
name: name.into(),
|
||||
path: Some(String::from(path)),
|
||||
plugin: Some(PluginKind::LV2(plugin)),
|
||||
selected: 0,
|
||||
mapping: false,
|
||||
ports
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Send + Sync> Audio for Plugin<E> {
|
||||
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||
match self.plugin.as_mut() {
|
||||
Some(PluginKind::LV2(LV2Plugin {
|
||||
features,
|
||||
ref mut instance,
|
||||
ref mut input_buffer,
|
||||
..
|
||||
})) => {
|
||||
let urid = features.midi_urid();
|
||||
input_buffer.clear();
|
||||
for port in self.ports.midi_ins.values() {
|
||||
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(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
input_buffer.push(atom);
|
||||
}
|
||||
let mut outputs = vec![];
|
||||
for _ in self.ports.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_audio_inputs(
|
||||
self.ports.audio_ins.values().map(|o|o.as_slice(scope))
|
||||
)
|
||||
.with_audio_outputs(
|
||||
self.ports.audio_outs.values_mut().map(|o|o.as_mut_slice(scope))
|
||||
);
|
||||
unsafe {
|
||||
instance.run(scope.n_frames() as usize, ports).unwrap()
|
||||
};
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
Control::Continue
|
||||
jack_from_lv2(name, &plugin.plugin)?.run(|ports|Box::new(Self {
|
||||
_engine: Default::default(),
|
||||
jack: jack.clone(),
|
||||
name: name.into(),
|
||||
path: Some(String::from(path)),
|
||||
plugin: Some(PluginKind::LV2(plugin)),
|
||||
selected: 0,
|
||||
mapping: false,
|
||||
ports
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
20
crates/tek_mixer/src/plugin_edn.rs
Normal file
20
crates/tek_mixer/src/plugin_edn.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use crate::*;
|
||||
|
||||
impl LV2Plugin {
|
||||
pub fn from_edn <'e, E: Engine> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<JackDevice<E>> {
|
||||
let mut name = String::new();
|
||||
let mut path = String::new();
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||
name = String::from(*n);
|
||||
}
|
||||
if let Some(Edn::Str(p)) = map.get(&Edn::Key(":path")) {
|
||||
path = String::from(*p);
|
||||
}
|
||||
},
|
||||
_ => panic!("unexpected in lv2 '{name}'"),
|
||||
});
|
||||
Plugin::new_lv2(jack, &name, &path)
|
||||
}
|
||||
}
|
||||
|
|
@ -22,53 +22,25 @@ pub struct LV2Plugin {
|
|||
}
|
||||
|
||||
impl LV2Plugin {
|
||||
pub fn from_edn <'e> (args: &[Edn<'e>]) -> Usually<JackDevice<Tui>> {
|
||||
let mut name = String::new();
|
||||
let mut path = String::new();
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||
name = String::from(*n);
|
||||
}
|
||||
if let Some(Edn::Str(p)) = map.get(&Edn::Key(":path")) {
|
||||
path = String::from(*p);
|
||||
}
|
||||
},
|
||||
_ => panic!("unexpected in lv2 '{name}'"),
|
||||
});
|
||||
Plugin::new_lv2(&name, &path)
|
||||
}
|
||||
const INPUT_BUFFER: usize = 1024;
|
||||
pub fn new (uri: &str) -> Usually<Self> {
|
||||
// Get 1st plugin at URI
|
||||
let world = World::with_load_bundle(&uri);
|
||||
let features = FeaturesBuilder { min_block_length: 1, max_block_length: 65536 };
|
||||
let features = world.build_features(features);
|
||||
let mut plugin = None;
|
||||
if let Some(p) = world.iter_plugins().next() {
|
||||
plugin = Some(p);
|
||||
if let Some(p) = world.iter_plugins().next() { plugin = Some(p); }
|
||||
let plugin = plugin.expect("plugin not found");
|
||||
let err = &format!("init {uri}");
|
||||
let instance = unsafe { plugin.instantiate(features.clone(), 48000.0).expect(&err) };
|
||||
let mut port_list = vec![];
|
||||
for port in plugin.ports() {
|
||||
port_list.push(port);
|
||||
}
|
||||
let plugin = plugin.unwrap();
|
||||
let err = &format!("init {uri}");
|
||||
|
||||
let input_buffer = Vec::with_capacity(Self::INPUT_BUFFER);
|
||||
// Instantiate
|
||||
Ok(Self {
|
||||
world,
|
||||
instance: unsafe {
|
||||
plugin
|
||||
.instantiate(features.clone(), 48000.0)
|
||||
.expect(&err)
|
||||
},
|
||||
port_list: {
|
||||
let mut port_list = vec![];
|
||||
for port in plugin.ports() {
|
||||
port_list.push(port);
|
||||
}
|
||||
port_list
|
||||
},
|
||||
plugin,
|
||||
features,
|
||||
input_buffer: Vec::with_capacity(1024),
|
||||
ui_thread: None
|
||||
world, instance, port_list, plugin, features, input_buffer, ui_thread: None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
58
crates/tek_mixer/src/plugin_snd.rs
Normal file
58
crates/tek_mixer/src/plugin_snd.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use crate::*;
|
||||
impl<E: Engine> Audio for Plugin<E> {
|
||||
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||
match self.plugin.as_mut() {
|
||||
Some(PluginKind::LV2(LV2Plugin {
|
||||
features,
|
||||
ref mut instance,
|
||||
ref mut input_buffer,
|
||||
..
|
||||
})) => {
|
||||
let urid = features.midi_urid();
|
||||
input_buffer.clear();
|
||||
for port in self.ports.midi_ins.values() {
|
||||
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(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
input_buffer.push(atom);
|
||||
}
|
||||
let mut outputs = vec![];
|
||||
for _ in self.ports.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_audio_inputs(
|
||||
self.ports.audio_ins.values().map(|o|o.as_slice(scope))
|
||||
)
|
||||
.with_audio_outputs(
|
||||
self.ports.audio_outs.values_mut().map(|o|o.as_mut_slice(scope))
|
||||
);
|
||||
unsafe {
|
||||
instance.run(scope.n_frames() as usize, ports).unwrap()
|
||||
};
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
|
|
@ -17,38 +17,9 @@ pub struct Sampler<E: Engine> {
|
|||
}
|
||||
|
||||
impl<E: Engine> Sampler<E> {
|
||||
pub fn from_edn <'e> (args: &[Edn<'e>]) -> Usually<JackDevice<Tui>> {
|
||||
let mut name = String::new();
|
||||
let mut dir = String::new();
|
||||
let mut samples = BTreeMap::new();
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||
name = String::from(*n);
|
||||
}
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":dir")) {
|
||||
dir = String::from(*n);
|
||||
}
|
||||
},
|
||||
Edn::List(args) => match args.get(0) {
|
||||
Some(Edn::Symbol("sample")) => {
|
||||
let (midi, sample) = Sample::from_edn(&dir, &args[1..])?;
|
||||
if let Some(midi) = midi {
|
||||
samples.insert(midi, sample);
|
||||
} else {
|
||||
panic!("sample without midi binding: {}", sample.read().unwrap().name);
|
||||
}
|
||||
},
|
||||
_ => panic!("unexpected in sampler {name}: {args:?}")
|
||||
},
|
||||
_ => panic!("unexpected in sampler {name}: {edn:?}")
|
||||
});
|
||||
Self::new(&name, Some(samples))
|
||||
}
|
||||
|
||||
pub fn new (
|
||||
name: &str, mapped: Option<BTreeMap<u7, Arc<RwLock<Sample>>>>
|
||||
) -> Usually<JackDevice<Tui>> {
|
||||
) -> Usually<JackDevice<E>> {
|
||||
Jack::new(name)?
|
||||
.midi_in("midi")
|
||||
.audio_in("recL")
|
||||
|
|
@ -57,6 +28,7 @@ impl<E: Engine> Sampler<E> {
|
|||
.audio_out("outR")
|
||||
.run(|ports|Box::new(Self {
|
||||
_engine: Default::default(),
|
||||
jack: (),
|
||||
name: name.into(),
|
||||
cursor: (0, 0),
|
||||
editing: None,
|
||||
|
|
@ -69,7 +41,6 @@ impl<E: Engine> Sampler<E> {
|
|||
modal: Default::default()
|
||||
}))
|
||||
}
|
||||
|
||||
/// Immutable reference to sample at cursor.
|
||||
pub fn sample (&self) -> Option<&Arc<RwLock<Sample>>> {
|
||||
for (i, sample) in self.mapped.values().enumerate() {
|
||||
|
|
@ -84,9 +55,8 @@ impl<E: Engine> Sampler<E> {
|
|||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Create [Voice]s from [Sample]s in response to MIDI input.
|
||||
fn process_midi_in (&mut self, scope: &ProcessScope) {
|
||||
pub fn process_midi_in (&mut self, scope: &ProcessScope) {
|
||||
for RawMidi { time, bytes } in self.ports.midi_ins.get("midi").unwrap().iter(scope) {
|
||||
if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
|
||||
if let MidiMessage::NoteOn { ref key, ref vel } = message {
|
||||
|
|
@ -97,16 +67,14 @@ impl<E: Engine> Sampler<E> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Zero the output buffer.
|
||||
fn clear_output_buffer (&mut self) {
|
||||
pub fn clear_output_buffer (&mut self) {
|
||||
for buffer in self.buffer.iter_mut() {
|
||||
buffer.fill(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Mix all currently playing samples into the output.
|
||||
fn process_audio_out (&mut self, scope: &ProcessScope) {
|
||||
pub fn process_audio_out (&mut self, scope: &ProcessScope) {
|
||||
let channel_count = self.buffer.len();
|
||||
self.voices.write().unwrap().retain_mut(|voice|{
|
||||
for index in 0..scope.n_frames() as usize {
|
||||
|
|
@ -126,9 +94,8 @@ impl<E: Engine> Sampler<E> {
|
|||
return true
|
||||
});
|
||||
}
|
||||
|
||||
/// Write output buffer to output ports.
|
||||
fn write_output_buffer (&mut self, scope: &ProcessScope) {
|
||||
pub fn write_output_buffer (&mut self, scope: &ProcessScope) {
|
||||
for (i, port) in self.ports.audio_outs.values_mut().enumerate() {
|
||||
let buffer = &self.buffer[i];
|
||||
for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() {
|
||||
|
|
@ -137,16 +104,6 @@ impl<E: Engine> Sampler<E> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> Audio for Sampler<E> {
|
||||
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||
self.process_midi_in(scope);
|
||||
self.clear_output_buffer();
|
||||
self.process_audio_out(scope);
|
||||
self.write_output_buffer(scope);
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
impl Handle<Tui> for Sampler<Tui> {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
match from.event() {
|
||||
|
|
@ -257,31 +214,6 @@ pub struct Sample {
|
|||
}
|
||||
|
||||
impl Sample {
|
||||
pub fn from_edn <'e> (dir: &str, args: &[Edn<'e>]) -> Usually<(Option<u7>, Arc<RwLock<Self>>)> {
|
||||
let mut name = String::new();
|
||||
let mut file = String::new();
|
||||
let mut midi = None;
|
||||
let mut start = 0usize;
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||
name = String::from(*n);
|
||||
}
|
||||
if let Some(Edn::Str(f)) = map.get(&Edn::Key(":file")) {
|
||||
file = String::from(*f);
|
||||
}
|
||||
if let Some(Edn::Int(i)) = map.get(&Edn::Key(":start")) {
|
||||
start = *i as usize;
|
||||
}
|
||||
if let Some(Edn::Int(m)) = map.get(&Edn::Key(":midi")) {
|
||||
midi = Some(u7::from(*m as u8));
|
||||
}
|
||||
},
|
||||
_ => panic!("unexpected in sample {name}"),
|
||||
});
|
||||
let (end, data) = read_sample_data(&format!("{dir}/{file}"))?;
|
||||
Ok((midi, Arc::new(RwLock::new(Self::new(&name, start, end, data)))))
|
||||
}
|
||||
pub fn new (name: &str, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Self {
|
||||
Self { name: name.to_string(), start, end, channels, rate: None }
|
||||
}
|
||||
|
|
@ -615,3 +547,31 @@ impl Sample {
|
|||
Ok(sample)
|
||||
}
|
||||
}
|
||||
|
||||
/// A currently playing instance of a sample.
|
||||
pub struct Voice {
|
||||
pub sample: Arc<RwLock<Sample>>,
|
||||
pub after: usize,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ impl SamplerCli {
|
|||
let mut plugin = Sampler::new(
|
||||
jack,
|
||||
self.name.as_ref().map(|x|x.as_str()).unwrap_or("mixer"),
|
||||
None,
|
||||
)?;
|
||||
Ok(plugin)
|
||||
})?)?;
|
||||
|
|
|
|||
60
crates/tek_mixer/src/sampler_edn.rs
Normal file
60
crates/tek_mixer/src/sampler_edn.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
use crate::*;
|
||||
|
||||
impl<E: Engine> Sampler<E> {
|
||||
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<JackDevice<E>> {
|
||||
let mut name = String::new();
|
||||
let mut dir = String::new();
|
||||
let mut samples = BTreeMap::new();
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||
name = String::from(*n);
|
||||
}
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":dir")) {
|
||||
dir = String::from(*n);
|
||||
}
|
||||
},
|
||||
Edn::List(args) => match args.get(0) {
|
||||
Some(Edn::Symbol("sample")) => {
|
||||
let (midi, sample) = Sample::from_edn(jack, &dir, &args[1..])?;
|
||||
if let Some(midi) = midi {
|
||||
samples.insert(midi, sample);
|
||||
} else {
|
||||
panic!("sample without midi binding: {}", sample.read().unwrap().name);
|
||||
}
|
||||
},
|
||||
_ => panic!("unexpected in sampler {name}: {args:?}")
|
||||
},
|
||||
_ => panic!("unexpected in sampler {name}: {edn:?}")
|
||||
});
|
||||
Self::new(&name, Some(samples))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sample {
|
||||
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, dir: &str, args: &[Edn<'e>]) -> Usually<(Option<u7>, Arc<RwLock<Self>>)> {
|
||||
let mut name = String::new();
|
||||
let mut file = String::new();
|
||||
let mut midi = None;
|
||||
let mut start = 0usize;
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||
name = String::from(*n);
|
||||
}
|
||||
if let Some(Edn::Str(f)) = map.get(&Edn::Key(":file")) {
|
||||
file = String::from(*f);
|
||||
}
|
||||
if let Some(Edn::Int(i)) = map.get(&Edn::Key(":start")) {
|
||||
start = *i as usize;
|
||||
}
|
||||
if let Some(Edn::Int(m)) = map.get(&Edn::Key(":midi")) {
|
||||
midi = Some(u7::from(*m as u8));
|
||||
}
|
||||
},
|
||||
_ => panic!("unexpected in sample {name}"),
|
||||
});
|
||||
let (end, data) = read_sample_data(&format!("{dir}/{file}"))?;
|
||||
Ok((midi, Arc::new(RwLock::new(Self::new(&name, start, end, data)))))
|
||||
}
|
||||
}
|
||||
10
crates/tek_mixer/src/sampler_snd.rs
Normal file
10
crates/tek_mixer/src/sampler_snd.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
use crate::*;
|
||||
impl<E: Engine> Audio for Sampler<E> {
|
||||
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||
self.process_midi_in(scope);
|
||||
self.clear_output_buffer();
|
||||
self.process_audio_out(scope);
|
||||
self.write_output_buffer(scope);
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,9 @@ impl<E: Engine> Track<E> {
|
|||
let index = self.devices.len() - 1;
|
||||
Ok(&mut self.devices[index])
|
||||
}
|
||||
pub fn add_device (&mut self, device: JackDevice<E>) {
|
||||
self.devices.push(device);
|
||||
}
|
||||
//pub fn connect_first_device (&self) -> Usually<()> {
|
||||
//if let (Some(port), Some(device)) = (&self.midi_out, self.devices.get(0)) {
|
||||
//device.client.as_client().connect_ports(&port, &device.midi_ins()?[0])?;
|
||||
|
|
@ -50,61 +53,6 @@ impl<E: Engine> Track<E> {
|
|||
//})
|
||||
//}
|
||||
}
|
||||
impl Track<Tui> {
|
||||
pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually<Track<Tui>> {
|
||||
let mut _gain = 0.0f64;
|
||||
let mut track = Self::new("")?;
|
||||
#[allow(unused_mut)]
|
||||
let mut devices: Vec<JackDevice<Tui>> = vec![];
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(SYM_NAME)) {
|
||||
track.name = n.to_string();
|
||||
}
|
||||
if let Some(Edn::Double(g)) = map.get(&Edn::Key(SYM_GAIN)) {
|
||||
_gain = f64::from(*g);
|
||||
}
|
||||
},
|
||||
Edn::List(args) => match args.get(0) {
|
||||
// Add a sampler device to the track
|
||||
Some(Edn::Symbol(SYM_SAMPLER)) => {
|
||||
devices.push(Sampler::from_edn(&args[1..])?);
|
||||
panic!(
|
||||
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"",
|
||||
&track.name,
|
||||
args.get(0).unwrap()
|
||||
)
|
||||
},
|
||||
// Add a LV2 plugin to the track.
|
||||
Some(Edn::Symbol(SYM_LV2)) => {
|
||||
devices.push(LV2Plugin::from_edn(&args[1..])?);
|
||||
panic!(
|
||||
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"",
|
||||
&track.name,
|
||||
args.get(0).unwrap()
|
||||
)
|
||||
},
|
||||
None =>
|
||||
panic!("empty list track {}", &track.name),
|
||||
_ =>
|
||||
panic!("unexpected in track {}: {:?}", &track.name, args.get(0).unwrap())
|
||||
},
|
||||
_ => {}
|
||||
});
|
||||
for device in devices {
|
||||
track.add_device(device);
|
||||
}
|
||||
Ok(track)
|
||||
}
|
||||
pub fn add_device (&mut self, device: JackDevice<Tui>) {
|
||||
self.devices.push(device);
|
||||
}
|
||||
}
|
||||
|
||||
const SYM_NAME: &'static str = ":name";
|
||||
const SYM_GAIN: &'static str = ":gain";
|
||||
const SYM_SAMPLER: &'static str = "sampler";
|
||||
const SYM_LV2: &'static str = "lv2";
|
||||
|
||||
impl Handle<Tui> for Track<Tui> {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
|
|
|
|||
54
crates/tek_mixer/src/track_edn.rs
Normal file
54
crates/tek_mixer/src/track_edn.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
use crate::*;
|
||||
|
||||
const SYM_NAME: &'static str = ":name";
|
||||
const SYM_GAIN: &'static str = ":gain";
|
||||
const SYM_SAMPLER: &'static str = "sampler";
|
||||
const SYM_LV2: &'static str = "lv2";
|
||||
|
||||
impl<E: Engine> Track<E> {
|
||||
pub fn from_edn <'a, 'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<Self> {
|
||||
let mut _gain = 0.0f64;
|
||||
let mut track = Self::new("")?;
|
||||
#[allow(unused_mut)]
|
||||
let mut devices: Vec<JackDevice<E>> = vec![];
|
||||
edn!(edn in args {
|
||||
Edn::Map(map) => {
|
||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(SYM_NAME)) {
|
||||
track.name = n.to_string();
|
||||
}
|
||||
if let Some(Edn::Double(g)) = map.get(&Edn::Key(SYM_GAIN)) {
|
||||
_gain = f64::from(*g);
|
||||
}
|
||||
},
|
||||
Edn::List(args) => match args.get(0) {
|
||||
// Add a sampler device to the track
|
||||
Some(Edn::Symbol(SYM_SAMPLER)) => {
|
||||
devices.push(Sampler::from_edn(jack, &args[1..])?);
|
||||
panic!(
|
||||
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"",
|
||||
&track.name,
|
||||
args.get(0).unwrap()
|
||||
)
|
||||
},
|
||||
// Add a LV2 plugin to the track.
|
||||
Some(Edn::Symbol(SYM_LV2)) => {
|
||||
devices.push(LV2Plugin::from_edn(jack, &args[1..])?);
|
||||
panic!(
|
||||
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"",
|
||||
&track.name,
|
||||
args.get(0).unwrap()
|
||||
)
|
||||
},
|
||||
None =>
|
||||
panic!("empty list track {}", &track.name),
|
||||
_ =>
|
||||
panic!("unexpected in track {}: {:?}", &track.name, args.get(0).unwrap())
|
||||
},
|
||||
_ => {}
|
||||
});
|
||||
for device in devices {
|
||||
track.add_device(device);
|
||||
}
|
||||
Ok(track)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
/// A currently playing instance of a sample.
|
||||
pub struct Voice {
|
||||
pub sample: Arc<RwLock<Sample>>,
|
||||
pub after: usize,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -157,7 +157,7 @@ impl<E: Engine> Arranger<E> {
|
|||
if let ArrangementFocus::Clip(track, scene) = self.arrangement.selected {
|
||||
let track_color = self.arrangement.tracks[track].color;
|
||||
let scene_color = self.arrangement.scenes[scene].color;
|
||||
track_color.mix(scene_color, 0.5).mix(ItemColor::random(), 0.5)
|
||||
track_color.mix(scene_color, 0.5).mix(ItemColor::random(), 0.25)
|
||||
} else {
|
||||
panic!("could not compute next color")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue