mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-08-07 22:17:07 +02:00
restruct: 25e
This commit is contained in:
parent
c737c7d839
commit
3bbe52093e
11 changed files with 454 additions and 432 deletions
399
src/device/sampler.rs
Normal file
399
src/device/sampler.rs
Normal file
|
|
@ -0,0 +1,399 @@
|
|||
use crate::{*, device::*, browse::*, mix::*};
|
||||
|
||||
mod voice; pub use self::voice::*;
|
||||
mod sample; pub use self::sample::*;
|
||||
mod sample_add; pub use self::sample_add::*;
|
||||
mod sample_kit; pub use self::sample_kit::*;
|
||||
|
||||
/// Plays [Voice]s from [Sample]s.
|
||||
///
|
||||
/// ```
|
||||
/// let sampler = tek::Sampler::default();
|
||||
/// ```
|
||||
#[derive(Debug, Default)] pub struct Sampler {
|
||||
/// Name of sampler.
|
||||
pub name: Arc<str>,
|
||||
/// Device color.
|
||||
pub color: ItemTheme,
|
||||
/// Sample currently being recorded.
|
||||
pub recording: Option<(usize, Option<Arc<RwLock<Sample>>>)>,
|
||||
/// Recording buffer.
|
||||
pub buffer: Vec<Vec<f32>>,
|
||||
/// Samples mapped to MIDI notes.
|
||||
pub samples: SampleKit<128>,
|
||||
/// Collection of currently playing instances of samples.
|
||||
pub voices: Arc<RwLock<Vec<Voice>>>,
|
||||
/// Samples that are not mapped to MIDI notes.
|
||||
pub unmapped: Vec<Arc<RwLock<Sample>>>,
|
||||
/// Sample currently being edited.
|
||||
pub editing: Option<Arc<RwLock<Sample>>>,
|
||||
/// How to mix the voices.
|
||||
pub mixing_mode: MixingMode,
|
||||
/// How to meter the inputs and outputs.
|
||||
pub metering_mode: MeteringMode,
|
||||
/// Fixed gain applied to all output.
|
||||
pub output_gain: f32,
|
||||
/// Currently active modal, if any.
|
||||
pub mode: Option<SamplerMode>,
|
||||
/// Size of rendered sampler.
|
||||
pub size: Size,
|
||||
/// Lowest note displayed.
|
||||
pub note_lo: AtomicUsize,
|
||||
/// Currently selected note.
|
||||
pub note_pt: AtomicUsize,
|
||||
/// Selected note as row/col.
|
||||
pub cursor: (AtomicUsize, AtomicUsize),
|
||||
/// Audio input meters.
|
||||
#[cfg(feature = "meter")] pub input_meters: Vec<f32>,
|
||||
/// Audio input ports. Samples are recorded from here.
|
||||
#[cfg(feature = "port")] pub audio_ins: Vec<AudioInput>,
|
||||
/// MIDI input port. Sampler are triggered from here.
|
||||
#[cfg(feature = "port")] pub midi_in: Option<MidiInput>,
|
||||
/// Audio output ports. Voices are played into here.
|
||||
#[cfg(feature = "port")] pub audio_outs: Vec<AudioOutput>,
|
||||
/// Audio output meters.
|
||||
#[cfg(feature = "meter")] pub output_meters: Vec<f32>,
|
||||
}
|
||||
|
||||
impl_audio!(Sampler: sampler_jack_process);
|
||||
|
||||
pub(crate) fn sampler_jack_process (state: &mut Sampler, _: &Client, scope: &ProcessScope) -> Control {
|
||||
if let Some(midi_in) = &state.midi_in {
|
||||
for midi in midi_in.port().iter(scope) {
|
||||
sampler_midi_in(&state.samples, &state.voices, midi)
|
||||
}
|
||||
}
|
||||
state.process_audio_out(scope);
|
||||
state.process_audio_in(scope);
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
#[derive(Debug)] pub enum SamplerMode {
|
||||
// Load sample from path
|
||||
Import(usize, Browse),
|
||||
}
|
||||
|
||||
pub type MidiSample =
|
||||
(Option<u7>, Arc<RwLock<crate::Sample>>);
|
||||
|
||||
impl NoteRange for Sampler {
|
||||
fn note_lo (&self) -> &AtomicUsize {
|
||||
&self.note_lo
|
||||
}
|
||||
fn note_axis (&self) -> &AtomicUsize {
|
||||
&self.size.y
|
||||
}
|
||||
}
|
||||
|
||||
impl NotePoint for Sampler {
|
||||
fn note_len (&self) -> &AtomicUsize {
|
||||
unreachable!();
|
||||
}
|
||||
fn get_note_len (&self) -> usize {
|
||||
0
|
||||
}
|
||||
fn set_note_len (&self, _x: usize) -> usize {
|
||||
0 /*TODO?*/
|
||||
}
|
||||
fn note_pos (&self) -> &AtomicUsize {
|
||||
&self.note_pt
|
||||
}
|
||||
fn get_note_pos (&self) -> usize {
|
||||
self.note_pt.load(Relaxed)
|
||||
}
|
||||
fn set_note_pos (&self, x: usize) -> usize {
|
||||
let old = self.note_pt.swap(x, Relaxed);
|
||||
self.cursor.0.store(x % 8, Relaxed);
|
||||
self.cursor.1.store(x / 8, Relaxed);
|
||||
old
|
||||
}
|
||||
}
|
||||
|
||||
impl Sampler {
|
||||
pub fn new (
|
||||
jack: &Jack<'static>,
|
||||
name: impl AsRef<str>,
|
||||
#[cfg(feature = "port")] midi_from: &[Connect],
|
||||
#[cfg(feature = "port")] audio_from: &[&[Connect];2],
|
||||
#[cfg(feature = "port")] audio_to: &[&[Connect];2],
|
||||
) -> Usually<Self> {
|
||||
let name = name.as_ref();
|
||||
Ok(Self {
|
||||
name: name.into(),
|
||||
input_meters: vec![0.0;2],
|
||||
output_meters: vec![0.0;2],
|
||||
output_gain: 1.,
|
||||
buffer: vec![vec![0.0;16384];2],
|
||||
#[cfg(feature = "port")] midi_in: Some(
|
||||
MidiInput::new(jack, &format!("M/{name}"), midi_from)?
|
||||
),
|
||||
#[cfg(feature = "port")] audio_ins: vec![
|
||||
AudioInput::new(jack, &format!("L/{name}"), audio_from[0])?,
|
||||
AudioInput::new(jack, &format!("R/{name}"), audio_from[1])?,
|
||||
],
|
||||
#[cfg(feature = "port")] audio_outs: vec![
|
||||
AudioOutput::new(jack, &format!("{name}/L"), audio_to[0])?,
|
||||
AudioOutput::new(jack, &format!("{name}/R"), audio_to[1])?,
|
||||
],
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
/// Value of cursor
|
||||
pub fn cursor (&self) -> (usize, usize) {
|
||||
(self.cursor.0.load(Relaxed), self.cursor.1.load(Relaxed))
|
||||
}
|
||||
fn sample_selected (&self) -> usize {
|
||||
(self.get_note_pos() as u8).into()
|
||||
}
|
||||
fn sample_selected_pitch (&self) -> u7 {
|
||||
(self.get_note_pos() as u8).into()
|
||||
}
|
||||
pub fn process_audio_in (&mut self, scope: &ProcessScope) {
|
||||
self.reset_input_meters();
|
||||
if self.recording.is_some() {
|
||||
self.record_into(scope);
|
||||
} else {
|
||||
self.update_input_meters(scope);
|
||||
}
|
||||
}
|
||||
/// Make sure that input meter count corresponds to input channel count
|
||||
fn reset_input_meters (&mut self) {
|
||||
let channels = self.audio_ins.len();
|
||||
if self.input_meters.len() != channels {
|
||||
self.input_meters = vec![f32::MIN;channels];
|
||||
}
|
||||
}
|
||||
/// Record from inputs to sample
|
||||
fn record_into (&mut self, scope: &ProcessScope) {
|
||||
if let Some(ref sample) = self.recording.as_ref().expect("no recording sample").1 {
|
||||
let mut sample = sample.write().unwrap();
|
||||
if sample.channels.len() != self.audio_ins.len() {
|
||||
panic!("channel count mismatch");
|
||||
}
|
||||
let samples_with_meters = self.audio_ins.iter()
|
||||
.zip(self.input_meters.iter_mut())
|
||||
.zip(sample.channels.iter_mut());
|
||||
let mut length = 0;
|
||||
for ((input, meter), channel) in samples_with_meters {
|
||||
let slice = input.port().as_slice(scope);
|
||||
length = length.max(slice.len());
|
||||
*meter = to_rms(slice);
|
||||
channel.extend_from_slice(slice);
|
||||
}
|
||||
sample.end += length;
|
||||
} else {
|
||||
panic!("tried to record into the void")
|
||||
}
|
||||
}
|
||||
/// Update input meters
|
||||
fn update_input_meters (&mut self, scope: &ProcessScope) {
|
||||
for (input, meter) in self.audio_ins.iter().zip(self.input_meters.iter_mut()) {
|
||||
let slice = input.port().as_slice(scope);
|
||||
*meter = to_rms(slice);
|
||||
}
|
||||
}
|
||||
/// Make sure that output meter count corresponds to input channel count
|
||||
fn reset_output_meters (&mut self) {
|
||||
let channels = self.audio_outs.len();
|
||||
if self.output_meters.len() != channels {
|
||||
self.output_meters = vec![f32::MIN;channels];
|
||||
}
|
||||
}
|
||||
/// Mix all currently playing samples into the output.
|
||||
pub fn process_audio_out (&mut self, scope: &ProcessScope) {
|
||||
self.clear_output_buffer();
|
||||
self.populate_output_buffer(scope.n_frames() as usize);
|
||||
self.write_output_buffer(scope);
|
||||
}
|
||||
/// Zero the output buffer.
|
||||
fn clear_output_buffer (&mut self) {
|
||||
for buffer in self.buffer.iter_mut() {
|
||||
buffer.fill(0.0);
|
||||
}
|
||||
}
|
||||
/// Write playing voices to output buffer
|
||||
fn populate_output_buffer (&mut self, frames: usize) {
|
||||
let Sampler { buffer, voices, output_gain, mixing_mode, .. } = self;
|
||||
let _channel_count = buffer.len();
|
||||
match mixing_mode {
|
||||
MixingMode::Summing => voices.write().unwrap().retain_mut(|voice|{
|
||||
mix_summing(buffer.as_mut_slice(), *output_gain, frames, ||voice.next())
|
||||
}),
|
||||
MixingMode::Average => voices.write().unwrap().retain_mut(|voice|{
|
||||
mix_average(buffer.as_mut_slice(), *output_gain, frames, ||voice.next())
|
||||
}),
|
||||
}
|
||||
}
|
||||
/// Write output buffer to output ports.
|
||||
fn write_output_buffer (&mut self, scope: &ProcessScope) {
|
||||
let Sampler { audio_outs, buffer, .. } = self;
|
||||
for (i, port) in audio_outs.iter_mut().enumerate() {
|
||||
let buffer = &buffer[i];
|
||||
for (i, value) in port.port_mut().as_mut_slice(scope).iter_mut().enumerate() {
|
||||
*value = *buffer.get(i).unwrap_or(&0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_list_item (sample: &Option<Arc<RwLock<Sample>>>) -> String {
|
||||
if let Some(sample) = sample {
|
||||
let sample = sample.read().unwrap();
|
||||
format!("{:8}", sample.name)
|
||||
//format!("{:8} {:3} {:6}-{:6}/{:6}",
|
||||
//sample.name,
|
||||
//sample.gain,
|
||||
//sample.start,
|
||||
//sample.end,
|
||||
//sample.channels[0].len()
|
||||
//)
|
||||
} else {
|
||||
String::from("........")
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_viewer (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
|
||||
let min_db = -64.0;
|
||||
thunk(move|to: &mut Tui|{
|
||||
let xywh = to.area().into();
|
||||
let XYWH(x, y, width, height) = xywh;
|
||||
let area = Rect { x, y, width, height };
|
||||
if let Some(sample) = &sample {
|
||||
let sample = sample.read().unwrap();
|
||||
let start = sample.start as f64;
|
||||
let end = sample.end as f64;
|
||||
let length = end - start;
|
||||
let step = length / width as f64;
|
||||
let mut t = start;
|
||||
let mut lines = vec![];
|
||||
while t < end {
|
||||
let chunk = &sample.channels[0][t as usize..((t + step) as usize).min(sample.end)];
|
||||
let total: f32 = chunk.iter().map(|x|x.abs()).sum();
|
||||
let count = chunk.len() as f32;
|
||||
let meter = 10. * (total / count).log10();
|
||||
let x = t as f64;
|
||||
let y = meter as f64;
|
||||
lines.push(Line::new(x, min_db, x, y, Color::Green));
|
||||
t += step / 2.;
|
||||
}
|
||||
Canvas::default()
|
||||
.x_bounds([sample.start as f64, sample.end as f64])
|
||||
.y_bounds([min_db, 0.])
|
||||
.paint(|ctx| {
|
||||
for line in lines.iter() {
|
||||
ctx.draw(line);
|
||||
}
|
||||
//FIXME: proportions
|
||||
//let text = "press record to finish sampling";
|
||||
//ctx.print(
|
||||
//(width - text.len() as u16) as f64 / 2.0,
|
||||
//height as f64 / 2.0,
|
||||
//text.red()
|
||||
//);
|
||||
})
|
||||
.render(area, to.as_mut());
|
||||
} else {
|
||||
Canvas::default()
|
||||
.x_bounds([0.0, width as f64])
|
||||
.y_bounds([0.0, height as f64])
|
||||
.paint(|_ctx| {
|
||||
//let text = "press record to begin sampling";
|
||||
//ctx.print(
|
||||
//(width - text.len() as u16) as f64 / 2.0,
|
||||
//height as f64 / 2.0,
|
||||
//text.red()
|
||||
//);
|
||||
})
|
||||
.render(area, to.as_mut());
|
||||
}
|
||||
Ok(xywh)
|
||||
})
|
||||
}
|
||||
|
||||
/// Create [Voice]s from [Sample]s in response to MIDI input.
|
||||
fn sampler_midi_in (
|
||||
samples: &SampleKit<128>, voices: &Arc<RwLock<Vec<Voice>>>, RawMidi { time, bytes }: RawMidi
|
||||
) {
|
||||
if let Ok(LiveEvent::Midi { message, .. }) = LiveEvent::parse(bytes) {
|
||||
match message {
|
||||
MidiMessage::NoteOn { ref key, ref vel } => {
|
||||
if let Some(sample) = samples.get(key.as_int() as usize) {
|
||||
voices.write().unwrap().push(Sample::play(sample, time as usize, vel));
|
||||
}
|
||||
},
|
||||
MidiMessage::Controller { controller: _, value: _ } => {
|
||||
// TODO
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_sample (
|
||||
to: &mut Tui, x: u16, y: u16, note: Option<&u7>, sample: &Sample, focus: bool
|
||||
) -> Usually<usize> {
|
||||
let style = if focus { Style::default().green() } else { Style::default() };
|
||||
if focus {
|
||||
to.blit(&"🬴", x+1, y, Some(style.bold()));
|
||||
}
|
||||
let label1 = format!("{:3} {:12}",
|
||||
note.map(|n|n.to_string()).unwrap_or(String::default()),
|
||||
sample.name);
|
||||
let label2 = format!("{:>6} {:>6} +0.0",
|
||||
sample.start,
|
||||
sample.end);
|
||||
to.blit(&label1, x+2, y, Some(style.bold()));
|
||||
to.blit(&label2, x+3+label1.len()as u16, y, Some(style));
|
||||
Ok(label1.len() + label2.len() + 4)
|
||||
}
|
||||
|
||||
fn read_sample_data (_: &str) -> Usually<(usize, Vec<Vec<f32>>)> {
|
||||
todo!();
|
||||
}
|
||||
|
||||
def_command!(SamplerCommand: |sampler: Sampler| {
|
||||
RecordToggle { slot: usize } => {
|
||||
let slot = *slot;
|
||||
let recording = sampler.recording.as_ref().map(|x|x.0);
|
||||
let _ = Self::RecordFinish.act(sampler)?;
|
||||
// autoslice: continue recording at next slot
|
||||
if recording != Some(slot) {
|
||||
Self::RecordBegin { slot }.act(sampler)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
},
|
||||
RecordBegin { slot: usize } => {
|
||||
let slot = *slot;
|
||||
sampler.recording = Some((
|
||||
slot,
|
||||
Some(Arc::new(RwLock::new(Sample::new(
|
||||
"Sample", 0, 0, vec![vec![];sampler.audio_ins.len()]
|
||||
))))
|
||||
));
|
||||
Ok(None)
|
||||
},
|
||||
RecordFinish => {
|
||||
let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{
|
||||
std::mem::swap(sample, &mut sampler.samples.0[*index]);
|
||||
sample
|
||||
}); // TODO: undo
|
||||
Ok(None)
|
||||
},
|
||||
RecordCancel => {
|
||||
sampler.recording = None;
|
||||
Ok(None)
|
||||
},
|
||||
PlaySample { slot: usize } => {
|
||||
let slot = *slot;
|
||||
if let Some(ref sample) = sampler.samples.0[slot] {
|
||||
sampler.voices.write().unwrap().push(Sample::play(sample, 0, &u7::from(128)));
|
||||
}
|
||||
Ok(None)
|
||||
},
|
||||
StopSample { slot: usize } => {
|
||||
let _slot = *slot;
|
||||
todo!();
|
||||
//Ok(None)
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue