mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
groovebox: display input meters!
This commit is contained in:
parent
e5752ea4b0
commit
7690549bdc
12 changed files with 239 additions and 117 deletions
|
|
@ -20,8 +20,9 @@ default = [ "clock", "editor", "sequencer", "sampler", "lv2" ]
|
|||
clock = []
|
||||
editor = []
|
||||
meter = []
|
||||
mixer = []
|
||||
sequencer = [ "clock", "uuid" ]
|
||||
sampler = [ "meter", "symphonia", "wavers" ]
|
||||
sampler = [ "meter", "mixer", "symphonia", "wavers" ]
|
||||
lv2 = [ "livi", "winit" ]
|
||||
vst2 = []
|
||||
vst3 = []
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ audio!(|self: DeviceAudio<'a>, client, scope|{
|
|||
use Device::*;
|
||||
match self.0 {
|
||||
#[cfg(feature = "sampler")]
|
||||
Sampler(sampler) => SamplerAudio(sampler).process(client, scope),
|
||||
Sampler(sampler) => sampler.process(client, scope),
|
||||
|
||||
#[cfg(feature = "lv2")]
|
||||
Lv2(lv2) => lv2.process(client, scope),
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ pub use self::device::*;
|
|||
#[cfg(feature = "meter")] mod meter;
|
||||
#[cfg(feature = "meter")] pub use self::meter::*;
|
||||
|
||||
#[cfg(feature = "mixer")] mod mixer;
|
||||
#[cfg(feature = "mixer")] pub use self::mixer::*;
|
||||
|
||||
#[cfg(feature = "lv2")] mod lv2;
|
||||
#[cfg(feature = "lv2")] pub use self::lv2::*;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,36 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct RMSMeter(f32);
|
||||
|
||||
impl RMSMeter {
|
||||
pub fn set (&mut self, samples: &[u32]) {
|
||||
let sum: usize = samples.iter().map(|s|*s as usize).reduce(|sum, sample|sum + sample)
|
||||
.unwrap_or(0);
|
||||
self.0 = (sum as f32 / samples.len() as f32).sqrt();
|
||||
}
|
||||
#[derive(Debug, Default)]
|
||||
pub enum MeteringMode {
|
||||
#[default]
|
||||
Rms,
|
||||
Log10,
|
||||
}
|
||||
|
||||
render!(TuiOut: |self: RMSMeter, to| {
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Meter(pub f32);
|
||||
|
||||
render!(TuiOut: |self: Meter, to| {
|
||||
let [x, y, w, h] = to.area();
|
||||
let signal = 100.0 - f32::max(0.0, f32::min(100.0, self.0.abs()));
|
||||
let v = (signal * h as f32 / 100.0).ceil() as u16;
|
||||
let y2 = y + h;
|
||||
//to.blit(&format!("\r{v} {} {signal}", self.0), x * 20, y, None);
|
||||
for y in y..(y + v) {
|
||||
for x in x..(x + w) {
|
||||
to.blit(&"▌", x, y2 - y, Some(Style::default().green()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
pub fn to_rms (samples: &[u32]) -> f32 {
|
||||
let sum: usize = samples.iter()
|
||||
.map(|s|*s as usize)
|
||||
.reduce(|sum, sample|sum + sample)
|
||||
.unwrap_or(0);
|
||||
(sum as f32 / samples.len() as f32).sqrt()
|
||||
}
|
||||
|
||||
pub fn to_log10 (samples: &[u32]) -> f32 {
|
||||
0.0
|
||||
}
|
||||
|
|
|
|||
41
crates/device/src/mixer.rs
Normal file
41
crates/device/src/mixer.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#[derive(Debug, Default)]
|
||||
pub enum MixingMode {
|
||||
#[default]
|
||||
Summing,
|
||||
Average,
|
||||
}
|
||||
|
||||
pub fn mix_summing <const N: usize> (
|
||||
buffer: &mut [Vec<f32>], gain: f32, frames: usize, mut next: impl FnMut()->Option<[f32;N]>,
|
||||
) -> bool {
|
||||
let channels = buffer.len();
|
||||
for index in 0..frames {
|
||||
if let Some(frame) = next() {
|
||||
for (channel, sample) in frame.iter().enumerate() {
|
||||
let channel = channel % channels;
|
||||
buffer[channel][index] += sample * gain;
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn mix_average <const N: usize> (
|
||||
buffer: &mut [Vec<f32>], gain: f32, frames: usize, mut next: impl FnMut()->Option<[f32;N]>,
|
||||
) -> bool {
|
||||
let channels = buffer.len();
|
||||
for index in 0..frames {
|
||||
if let Some(frame) = next() {
|
||||
for (channel, sample) in frame.iter().enumerate() {
|
||||
let channel = channel % channels;
|
||||
let value = buffer[channel][index];
|
||||
buffer[channel][index] = (value + sample * gain) / 2.0;
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ impl Sampler {
|
|||
//todo!();
|
||||
//}
|
||||
///// Immutable reference to sample at cursor.
|
||||
//fn sample_selected (&self) -> MaybeSample {
|
||||
//fn sample_selected (&self) -> Option<Arc<RwLock<Sample>>> {
|
||||
//for (i, sample) in self.mapped.iter().enumerate() {
|
||||
//if i == self.cursor().0 {
|
||||
//return sample.as_ref()
|
||||
|
|
@ -88,7 +88,7 @@ impl SamplerCommand {
|
|||
//Self::Select(state.set_note_pos(i))
|
||||
//}
|
||||
///// Assign sample to pitch
|
||||
//fn set (&self, pitch: u7, sample: MaybeSample) -> Option<Self> {
|
||||
//fn set (&self, pitch: u7, sample: Option<Arc<RwLock<Sample>>>) -> Option<Self> {
|
||||
//let i = pitch.as_int() as usize;
|
||||
//let old = self.mapped[i].clone();
|
||||
//self.mapped[i] = sample;
|
||||
|
|
@ -106,7 +106,7 @@ impl SamplerCommand {
|
|||
//fn note_off (&self, state: &mut Sampler, pitch: u7) -> Option<Self> {
|
||||
//todo!()
|
||||
//}
|
||||
//fn set_sample (&self, state: &mut Sampler, pitch: u7, s: MaybeSample) -> Option<Self> {
|
||||
//fn set_sample (&self, state: &mut Sampler, pitch: u7, s: Option<Arc<RwLock<Sample>>>) -> Option<Self> {
|
||||
//Some(Self::SetSample(p, state.set_sample(p, s)))
|
||||
//}
|
||||
//fn import (&self, state: &mut Sampler, c: FileBrowserCommand) -> Option<Self> {
|
||||
|
|
@ -131,7 +131,7 @@ impl SamplerCommand {
|
|||
////(SetGain [p: u7, gain: f32] cmd_todo!("\n\rtodo: {self:?}"))
|
||||
////(NoteOn [p: u7, velocity: u7] cmd_todo!("\n\rtodo: {self:?}"))
|
||||
////(NoteOff [p: u7] cmd_todo!("\n\rtodo: {self:?}"))
|
||||
////(SetSample [p: u7, s: MaybeSample] Some(Self::SetSample(p, state.set_sample(p, s))))
|
||||
////(SetSample [p: u7, s: Option<Arc<RwLock<Sample>>>] Some(Self::SetSample(p, state.set_sample(p, s))))
|
||||
////(Import [c: FileBrowserCommand] match c {
|
||||
////FileBrowserCommand::Begin => {
|
||||
//////let voices = &state.state.voices;
|
||||
|
|
@ -154,7 +154,7 @@ impl SamplerCommand {
|
|||
////Some(Self::RecordCancel))
|
||||
////("record/finish" []
|
||||
////Some(Self::RecordFinish))
|
||||
////("set/sample" [i: u7, s: MaybeSample]
|
||||
////("set/sample" [i: u7, s: Option<Arc<RwLock<Sample>>>]
|
||||
////Some(Self::SetSample(i.expect("no index"), s.expect("no sampler"))))
|
||||
////("set/start" [i: u7, s: usize]
|
||||
////Some(Self::SetStart(i.expect("no index"), s.expect("no start"))))
|
||||
|
|
|
|||
|
|
@ -1,80 +1,102 @@
|
|||
use crate::*;
|
||||
|
||||
pub struct SamplerAudio<'a>(pub &'a mut Sampler);
|
||||
|
||||
audio!(|self: SamplerAudio<'a>, _client, scope|{
|
||||
self.0.process_midi_in(scope);
|
||||
self.0.clear_output_buffer();
|
||||
self.0.process_audio_out(scope);
|
||||
self.0.write_output_buffer(scope);
|
||||
self.0.process_audio_in(scope);
|
||||
audio!(|self: Sampler, _client, scope|{
|
||||
self.process_midi_in(scope);
|
||||
self.process_audio_out(scope);
|
||||
self.process_audio_in(scope);
|
||||
Control::Continue
|
||||
});
|
||||
|
||||
impl Sampler {
|
||||
|
||||
pub fn process_audio_in (&mut self, scope: &ProcessScope) {
|
||||
let Sampler { audio_ins, input_meter, recording, .. } = self;
|
||||
if audio_ins.len() != input_meter.len() {
|
||||
*input_meter = vec![0.0;audio_ins.len()];
|
||||
}
|
||||
if let Some((_, sample)) = recording {
|
||||
let mut sample = sample.write().unwrap();
|
||||
if sample.channels.len() != audio_ins.len() {
|
||||
panic!("channel count mismatch");
|
||||
}
|
||||
let iterator = audio_ins.iter().zip(input_meter).zip(sample.channels.iter_mut());
|
||||
let mut length = 0;
|
||||
for ((input, meter), channel) in iterator {
|
||||
let slice = input.port().as_slice(scope);
|
||||
length = length.max(slice.len());
|
||||
let total: f32 = slice.iter().map(|x|x.abs()).sum();
|
||||
let count = slice.len() as f32;
|
||||
*meter = 10. * (total / count).log10();
|
||||
channel.extend_from_slice(slice);
|
||||
}
|
||||
sample.end += length;
|
||||
self.reset_input_meters();
|
||||
if self.recording.is_some() {
|
||||
self.record_into(scope);
|
||||
} else {
|
||||
for (input, meter) in audio_ins.iter().zip(input_meter) {
|
||||
let slice = input.port().as_slice(scope);
|
||||
let total: f32 = slice.iter().map(|x|x.abs()).sum();
|
||||
let count = slice.len() as f32;
|
||||
*meter = 10. * (total / count).log10();
|
||||
}
|
||||
self.update_input_meters(scope);
|
||||
}
|
||||
}
|
||||
|
||||
/// Zero the output buffer.
|
||||
pub fn clear_output_buffer (&mut self) {
|
||||
for buffer in self.buffer.iter_mut() {
|
||||
buffer.fill(0.0);
|
||||
/// 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) {
|
||||
let mut sample = self.recording
|
||||
.as_mut().expect("no recording sample").1
|
||||
.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());
|
||||
let total: f32 = slice.iter().map(|x|x.abs()).sum();
|
||||
let count = slice.len() as f32;
|
||||
*meter = 10. * (total / count).log10();
|
||||
channel.extend_from_slice(slice);
|
||||
}
|
||||
sample.end += length;
|
||||
}
|
||||
|
||||
/// 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);
|
||||
let total: f32 = slice.iter().map(|x|x.abs()).sum();
|
||||
let count = slice.len() as f32;
|
||||
*meter = 10. * (total / count).log10();
|
||||
}
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
let Sampler { ref mut buffer, voices, output_gain, .. } = self;
|
||||
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 { ref mut buffer, voices, output_gain, mixing_mode, .. } = self;
|
||||
let channel_count = buffer.len();
|
||||
voices.write().unwrap().retain_mut(|voice|{
|
||||
for index in 0..scope.n_frames() as usize {
|
||||
if let Some(frame) = voice.next() {
|
||||
for (channel, sample) in frame.iter().enumerate() {
|
||||
// Averaging mixer:
|
||||
//self.buffer[channel % channel_count][index] = (
|
||||
//(self.buffer[channel % channel_count][index] + sample * self.output_gain) / 2.0
|
||||
//);
|
||||
buffer[channel % channel_count][index] += sample * *output_gain;
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
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.
|
||||
pub fn write_output_buffer (&mut self, scope: &ProcessScope) {
|
||||
fn write_output_buffer (&mut self, scope: &ProcessScope) {
|
||||
let Sampler { ref mut audio_outs, buffer, .. } = self;
|
||||
for (i, port) in audio_outs.iter_mut().enumerate() {
|
||||
let buffer = &buffer[i];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::*;
|
||||
|
||||
impl Sample {
|
||||
|
||||
/// Read WAV from file
|
||||
pub fn read_data (src: &str) -> Usually<(usize, Vec<Vec<f32>>)> {
|
||||
let mut channels: Vec<wavers::Samples<f32>> = vec![];
|
||||
|
|
@ -16,6 +17,7 @@ impl Sample {
|
|||
}
|
||||
Ok((end, data))
|
||||
}
|
||||
|
||||
pub fn from_file (path: &PathBuf) -> Usually<Self> {
|
||||
let name = path.file_name().unwrap().to_string_lossy().into();
|
||||
let mut sample = Self { name, ..Default::default() };
|
||||
|
|
@ -49,6 +51,7 @@ impl Sample {
|
|||
sample.end = sample.channels.iter().fold(0, |l, c|l + c.len());
|
||||
Ok(sample)
|
||||
}
|
||||
|
||||
fn decode_packet (
|
||||
&mut self, decoder: &mut Box<dyn Decoder>, packet: Packet
|
||||
) -> Usually<()> {
|
||||
|
|
@ -84,4 +87,5 @@ impl Sample {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,55 +1,76 @@
|
|||
use crate::*;
|
||||
|
||||
pub type MaybeSample = Option<Arc<RwLock<Sample>>>;
|
||||
|
||||
/// The sampler device plays sounds in response to MIDI notes.
|
||||
#[derive(Debug)]
|
||||
pub struct Sampler {
|
||||
pub name: String,
|
||||
pub mapped: [MaybeSample;128],
|
||||
pub recording: Option<(usize, Arc<RwLock<Sample>>)>,
|
||||
pub unmapped: Vec<Arc<RwLock<Sample>>>,
|
||||
pub voices: Arc<RwLock<Vec<Voice>>>,
|
||||
pub midi_in: Option<JackMidiIn>,
|
||||
pub audio_ins: Vec<JackAudioIn>,
|
||||
pub input_meter: Vec<f32>,
|
||||
pub audio_outs: Vec<JackAudioOut>,
|
||||
pub buffer: Vec<Vec<f32>>,
|
||||
pub output_gain: f32,
|
||||
pub editing: MaybeSample,
|
||||
pub mode: Option<SamplerMode>,
|
||||
/// Size of actual notes area
|
||||
pub size: Measure<TuiOut>,
|
||||
/// Lowest note displayed
|
||||
pub note_lo: AtomicUsize,
|
||||
/// Selected note
|
||||
pub note_pt: AtomicUsize,
|
||||
/// Selected note as row/col
|
||||
pub cursor: (AtomicUsize, AtomicUsize),
|
||||
pub color: ItemTheme
|
||||
/// Name of sampler.
|
||||
pub name: String,
|
||||
/// Device color.
|
||||
pub color: ItemTheme,
|
||||
/// Audio input ports. Samples get recorded here.
|
||||
pub audio_ins: Vec<JackAudioIn>,
|
||||
/// Audio input meters.
|
||||
pub input_meters: Vec<f32>,
|
||||
/// Sample currently being recorded.
|
||||
pub recording: Option<(usize, Arc<RwLock<Sample>>)>,
|
||||
/// Recording buffer.
|
||||
pub buffer: Vec<Vec<f32>>,
|
||||
/// Samples mapped to MIDI notes.
|
||||
pub mapped: [Option<Arc<RwLock<Sample>>>;128],
|
||||
/// Samples that are not mapped to MIDI notes.
|
||||
pub unmapped: Vec<Arc<RwLock<Sample>>>,
|
||||
/// Sample currently being edited.
|
||||
pub editing: Option<Arc<RwLock<Sample>>>,
|
||||
/// MIDI input port. Triggers sample playback.
|
||||
pub midi_in: Option<JackMidiIn>,
|
||||
/// Collection of currently playing instances of samples.
|
||||
pub voices: Arc<RwLock<Vec<Voice>>>,
|
||||
/// Audio output ports. Voices get played here.
|
||||
pub audio_outs: Vec<JackAudioOut>,
|
||||
/// Audio output meters.
|
||||
pub output_meters: Vec<f32>,
|
||||
/// 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: Measure<TuiOut>,
|
||||
/// Lowest note displayed.
|
||||
pub note_lo: AtomicUsize,
|
||||
/// Currently selected note.
|
||||
pub note_pt: AtomicUsize,
|
||||
/// Selected note as row/col.
|
||||
pub cursor: (AtomicUsize, AtomicUsize),
|
||||
}
|
||||
|
||||
impl Default for Sampler {
|
||||
fn default () -> Self {
|
||||
Self {
|
||||
midi_in: None,
|
||||
audio_ins: vec![],
|
||||
input_meter: vec![0.0;2],
|
||||
audio_outs: vec![],
|
||||
name: "tek_sampler".to_string(),
|
||||
mapped: [const { None };128],
|
||||
unmapped: vec![],
|
||||
voices: Arc::new(RwLock::new(vec![])),
|
||||
buffer: vec![vec![0.0;16384];2],
|
||||
output_gain: 1.,
|
||||
recording: None,
|
||||
mode: None,
|
||||
editing: None,
|
||||
size: Default::default(),
|
||||
note_lo: 0.into(),
|
||||
note_pt: 0.into(),
|
||||
cursor: (0.into(), 0.into()),
|
||||
color: Default::default(),
|
||||
midi_in: None,
|
||||
audio_ins: vec![],
|
||||
input_meters: vec![0.0;2],
|
||||
output_meters: vec![0.0;2],
|
||||
audio_outs: vec![],
|
||||
name: "tek_sampler".to_string(),
|
||||
mapped: [const { None };128],
|
||||
unmapped: vec![],
|
||||
voices: Arc::new(RwLock::new(vec![])),
|
||||
buffer: vec![vec![0.0;16384];2],
|
||||
output_gain: 1.,
|
||||
recording: None,
|
||||
mode: None,
|
||||
editing: None,
|
||||
size: Default::default(),
|
||||
note_lo: 0.into(),
|
||||
note_pt: 0.into(),
|
||||
cursor: (0.into(), 0.into()),
|
||||
color: Default::default(),
|
||||
mixing_mode: Default::default(),
|
||||
metering_mode: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,12 @@ impl Sampler {
|
|||
pub fn status (&self, index: usize) -> impl Content<TuiOut> {
|
||||
draw_status(self.mapped[index].as_ref())
|
||||
}
|
||||
|
||||
pub fn view_meters_input (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
Tui::bg(Black, Fixed::x(2, Map::east(1, ||self.input_meters.iter(), |value, _index|{
|
||||
Fill::y(Meter(*value))
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_list_item (sample: &Option<Arc<RwLock<Sample>>>) -> String {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue