mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
groovebox: display input meters!
This commit is contained in:
parent
e5752ea4b0
commit
7690549bdc
12 changed files with 239 additions and 117 deletions
|
|
@ -8,8 +8,9 @@
|
||||||
(bsp/n (fixed/y 1 :view-status)
|
(bsp/n (fixed/y 1 :view-status)
|
||||||
(bsp/n (fixed/y 5 :view-sample-viewer)
|
(bsp/n (fixed/y 5 :view-sample-viewer)
|
||||||
(bsp/w (fixed/x :w-sidebar :view-pool)
|
(bsp/w (fixed/x :w-sidebar :view-pool)
|
||||||
|
(bsp/e :view-meters-input
|
||||||
(bsp/e :view-samples-keys
|
(bsp/e :view-samples-keys
|
||||||
(fill/y :view-editor))))))))
|
(fill/y :view-editor)))))))))
|
||||||
|
|
||||||
(keys
|
(keys
|
||||||
(layer-if :focus-pool-import "./keys_pool_file.edn")
|
(layer-if :focus-pool-import "./keys_pool_file.edn")
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,9 @@ impl App {
|
||||||
)))
|
)))
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
pub fn view_meters_input (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
self.sampler().map(|s|s.view_meters_input())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,9 @@ default = [ "clock", "editor", "sequencer", "sampler", "lv2" ]
|
||||||
clock = []
|
clock = []
|
||||||
editor = []
|
editor = []
|
||||||
meter = []
|
meter = []
|
||||||
|
mixer = []
|
||||||
sequencer = [ "clock", "uuid" ]
|
sequencer = [ "clock", "uuid" ]
|
||||||
sampler = [ "meter", "symphonia", "wavers" ]
|
sampler = [ "meter", "mixer", "symphonia", "wavers" ]
|
||||||
lv2 = [ "livi", "winit" ]
|
lv2 = [ "livi", "winit" ]
|
||||||
vst2 = []
|
vst2 = []
|
||||||
vst3 = []
|
vst3 = []
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ audio!(|self: DeviceAudio<'a>, client, scope|{
|
||||||
use Device::*;
|
use Device::*;
|
||||||
match self.0 {
|
match self.0 {
|
||||||
#[cfg(feature = "sampler")]
|
#[cfg(feature = "sampler")]
|
||||||
Sampler(sampler) => SamplerAudio(sampler).process(client, scope),
|
Sampler(sampler) => sampler.process(client, scope),
|
||||||
|
|
||||||
#[cfg(feature = "lv2")]
|
#[cfg(feature = "lv2")]
|
||||||
Lv2(lv2) => lv2.process(client, scope),
|
Lv2(lv2) => lv2.process(client, scope),
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ pub use self::device::*;
|
||||||
#[cfg(feature = "meter")] mod meter;
|
#[cfg(feature = "meter")] mod meter;
|
||||||
#[cfg(feature = "meter")] pub use self::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")] mod lv2;
|
||||||
#[cfg(feature = "lv2")] pub use self::lv2::*;
|
#[cfg(feature = "lv2")] pub use self::lv2::*;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,36 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub enum MeteringMode {
|
||||||
|
#[default]
|
||||||
|
Rms,
|
||||||
|
Log10,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct RMSMeter(f32);
|
pub struct Meter(pub f32);
|
||||||
|
|
||||||
impl RMSMeter {
|
render!(TuiOut: |self: Meter, to| {
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render!(TuiOut: |self: RMSMeter, to| {
|
|
||||||
let [x, y, w, h] = to.area();
|
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!();
|
//todo!();
|
||||||
//}
|
//}
|
||||||
///// Immutable reference to sample at cursor.
|
///// 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() {
|
//for (i, sample) in self.mapped.iter().enumerate() {
|
||||||
//if i == self.cursor().0 {
|
//if i == self.cursor().0 {
|
||||||
//return sample.as_ref()
|
//return sample.as_ref()
|
||||||
|
|
@ -88,7 +88,7 @@ impl SamplerCommand {
|
||||||
//Self::Select(state.set_note_pos(i))
|
//Self::Select(state.set_note_pos(i))
|
||||||
//}
|
//}
|
||||||
///// Assign sample to pitch
|
///// 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 i = pitch.as_int() as usize;
|
||||||
//let old = self.mapped[i].clone();
|
//let old = self.mapped[i].clone();
|
||||||
//self.mapped[i] = sample;
|
//self.mapped[i] = sample;
|
||||||
|
|
@ -106,7 +106,7 @@ impl SamplerCommand {
|
||||||
//fn note_off (&self, state: &mut Sampler, pitch: u7) -> Option<Self> {
|
//fn note_off (&self, state: &mut Sampler, pitch: u7) -> Option<Self> {
|
||||||
//todo!()
|
//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)))
|
//Some(Self::SetSample(p, state.set_sample(p, s)))
|
||||||
//}
|
//}
|
||||||
//fn import (&self, state: &mut Sampler, c: FileBrowserCommand) -> Option<Self> {
|
//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:?}"))
|
////(SetGain [p: u7, gain: f32] cmd_todo!("\n\rtodo: {self:?}"))
|
||||||
////(NoteOn [p: u7, velocity: u7] cmd_todo!("\n\rtodo: {self:?}"))
|
////(NoteOn [p: u7, velocity: u7] cmd_todo!("\n\rtodo: {self:?}"))
|
||||||
////(NoteOff [p: 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 {
|
////(Import [c: FileBrowserCommand] match c {
|
||||||
////FileBrowserCommand::Begin => {
|
////FileBrowserCommand::Begin => {
|
||||||
//////let voices = &state.state.voices;
|
//////let voices = &state.state.voices;
|
||||||
|
|
@ -154,7 +154,7 @@ impl SamplerCommand {
|
||||||
////Some(Self::RecordCancel))
|
////Some(Self::RecordCancel))
|
||||||
////("record/finish" []
|
////("record/finish" []
|
||||||
////Some(Self::RecordFinish))
|
////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"))))
|
////Some(Self::SetSample(i.expect("no index"), s.expect("no sampler"))))
|
||||||
////("set/start" [i: u7, s: usize]
|
////("set/start" [i: u7, s: usize]
|
||||||
////Some(Self::SetStart(i.expect("no index"), s.expect("no start"))))
|
////Some(Self::SetStart(i.expect("no index"), s.expect("no start"))))
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,44 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub struct SamplerAudio<'a>(pub &'a mut Sampler);
|
audio!(|self: Sampler, _client, scope|{
|
||||||
|
self.process_midi_in(scope);
|
||||||
audio!(|self: SamplerAudio<'a>, _client, scope|{
|
self.process_audio_out(scope);
|
||||||
self.0.process_midi_in(scope);
|
self.process_audio_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);
|
|
||||||
Control::Continue
|
Control::Continue
|
||||||
});
|
});
|
||||||
|
|
||||||
impl Sampler {
|
impl Sampler {
|
||||||
|
|
||||||
pub fn process_audio_in (&mut self, scope: &ProcessScope) {
|
pub fn process_audio_in (&mut self, scope: &ProcessScope) {
|
||||||
let Sampler { audio_ins, input_meter, recording, .. } = self;
|
self.reset_input_meters();
|
||||||
if audio_ins.len() != input_meter.len() {
|
if self.recording.is_some() {
|
||||||
*input_meter = vec![0.0;audio_ins.len()];
|
self.record_into(scope);
|
||||||
|
} else {
|
||||||
|
self.update_input_meters(scope);
|
||||||
}
|
}
|
||||||
if let Some((_, sample)) = recording {
|
}
|
||||||
let mut sample = sample.write().unwrap();
|
|
||||||
if sample.channels.len() != audio_ins.len() {
|
/// 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");
|
panic!("channel count mismatch");
|
||||||
}
|
}
|
||||||
let iterator = audio_ins.iter().zip(input_meter).zip(sample.channels.iter_mut());
|
let samples_with_meters = self.audio_ins.iter()
|
||||||
|
.zip(self.input_meters.iter_mut())
|
||||||
|
.zip(sample.channels.iter_mut());
|
||||||
let mut length = 0;
|
let mut length = 0;
|
||||||
for ((input, meter), channel) in iterator {
|
for ((input, meter), channel) in samples_with_meters {
|
||||||
let slice = input.port().as_slice(scope);
|
let slice = input.port().as_slice(scope);
|
||||||
length = length.max(slice.len());
|
length = length.max(slice.len());
|
||||||
let total: f32 = slice.iter().map(|x|x.abs()).sum();
|
let total: f32 = slice.iter().map(|x|x.abs()).sum();
|
||||||
|
|
@ -34,47 +47,56 @@ impl Sampler {
|
||||||
channel.extend_from_slice(slice);
|
channel.extend_from_slice(slice);
|
||||||
}
|
}
|
||||||
sample.end += length;
|
sample.end += length;
|
||||||
} else {
|
}
|
||||||
for (input, meter) in audio_ins.iter().zip(input_meter) {
|
|
||||||
|
/// 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 slice = input.port().as_slice(scope);
|
||||||
let total: f32 = slice.iter().map(|x|x.abs()).sum();
|
let total: f32 = slice.iter().map(|x|x.abs()).sum();
|
||||||
let count = slice.len() as f32;
|
let count = slice.len() as f32;
|
||||||
*meter = 10. * (total / count).log10();
|
*meter = 10. * (total / count).log10();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Zero the output buffer.
|
/// Make sure that output meter count corresponds to input channel count
|
||||||
pub fn clear_output_buffer (&mut self) {
|
fn reset_output_meters (&mut self) {
|
||||||
for buffer in self.buffer.iter_mut() {
|
let channels = self.audio_outs.len();
|
||||||
buffer.fill(0.0);
|
if self.output_meters.len() != channels {
|
||||||
|
self.output_meters = vec![f32::MIN;channels];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mix all currently playing samples into the output.
|
/// Mix all currently playing samples into the output.
|
||||||
pub fn process_audio_out (&mut self, scope: &ProcessScope) {
|
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();
|
let channel_count = buffer.len();
|
||||||
voices.write().unwrap().retain_mut(|voice|{
|
match mixing_mode {
|
||||||
for index in 0..scope.n_frames() as usize {
|
MixingMode::Summing => voices.write().unwrap().retain_mut(|voice|{
|
||||||
if let Some(frame) = voice.next() {
|
mix_summing(buffer.as_mut_slice(), *output_gain, frames, ||voice.next())
|
||||||
for (channel, sample) in frame.iter().enumerate() {
|
}),
|
||||||
// Averaging mixer:
|
MixingMode::Average => voices.write().unwrap().retain_mut(|voice|{
|
||||||
//self.buffer[channel % channel_count][index] = (
|
mix_average(buffer.as_mut_slice(), *output_gain, frames, ||voice.next())
|
||||||
//(self.buffer[channel % channel_count][index] + sample * self.output_gain) / 2.0
|
}),
|
||||||
//);
|
|
||||||
buffer[channel % channel_count][index] += sample * *output_gain;
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write output buffer to output ports.
|
/// 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;
|
let Sampler { ref mut audio_outs, buffer, .. } = self;
|
||||||
for (i, port) in audio_outs.iter_mut().enumerate() {
|
for (i, port) in audio_outs.iter_mut().enumerate() {
|
||||||
let buffer = &buffer[i];
|
let buffer = &buffer[i];
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
impl Sample {
|
impl Sample {
|
||||||
|
|
||||||
/// Read WAV from file
|
/// Read WAV from file
|
||||||
pub fn read_data (src: &str) -> Usually<(usize, Vec<Vec<f32>>)> {
|
pub fn read_data (src: &str) -> Usually<(usize, Vec<Vec<f32>>)> {
|
||||||
let mut channels: Vec<wavers::Samples<f32>> = vec![];
|
let mut channels: Vec<wavers::Samples<f32>> = vec![];
|
||||||
|
|
@ -16,6 +17,7 @@ impl Sample {
|
||||||
}
|
}
|
||||||
Ok((end, data))
|
Ok((end, data))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_file (path: &PathBuf) -> Usually<Self> {
|
pub fn from_file (path: &PathBuf) -> Usually<Self> {
|
||||||
let name = path.file_name().unwrap().to_string_lossy().into();
|
let name = path.file_name().unwrap().to_string_lossy().into();
|
||||||
let mut sample = Self { name, ..Default::default() };
|
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());
|
sample.end = sample.channels.iter().fold(0, |l, c|l + c.len());
|
||||||
Ok(sample)
|
Ok(sample)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_packet (
|
fn decode_packet (
|
||||||
&mut self, decoder: &mut Box<dyn Decoder>, packet: Packet
|
&mut self, decoder: &mut Box<dyn Decoder>, packet: Packet
|
||||||
) -> Usually<()> {
|
) -> Usually<()> {
|
||||||
|
|
@ -84,4 +87,5 @@ impl Sample {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,50 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub type MaybeSample = Option<Arc<RwLock<Sample>>>;
|
|
||||||
|
|
||||||
/// The sampler device plays sounds in response to MIDI notes.
|
/// The sampler device plays sounds in response to MIDI notes.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Sampler {
|
pub struct Sampler {
|
||||||
|
/// Name of sampler.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub mapped: [MaybeSample;128],
|
/// Device color.
|
||||||
pub recording: Option<(usize, Arc<RwLock<Sample>>)>,
|
pub color: ItemTheme,
|
||||||
pub unmapped: Vec<Arc<RwLock<Sample>>>,
|
/// Audio input ports. Samples get recorded here.
|
||||||
pub voices: Arc<RwLock<Vec<Voice>>>,
|
|
||||||
pub midi_in: Option<JackMidiIn>,
|
|
||||||
pub audio_ins: Vec<JackAudioIn>,
|
pub audio_ins: Vec<JackAudioIn>,
|
||||||
pub input_meter: Vec<f32>,
|
/// Audio input meters.
|
||||||
pub audio_outs: Vec<JackAudioOut>,
|
pub input_meters: Vec<f32>,
|
||||||
|
/// Sample currently being recorded.
|
||||||
|
pub recording: Option<(usize, Arc<RwLock<Sample>>)>,
|
||||||
|
/// Recording buffer.
|
||||||
pub buffer: Vec<Vec<f32>>,
|
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,
|
pub output_gain: f32,
|
||||||
pub editing: MaybeSample,
|
/// Currently active modal, if any.
|
||||||
pub mode: Option<SamplerMode>,
|
pub mode: Option<SamplerMode>,
|
||||||
/// Size of actual notes area
|
/// Size of rendered sampler.
|
||||||
pub size: Measure<TuiOut>,
|
pub size: Measure<TuiOut>,
|
||||||
/// Lowest note displayed
|
/// Lowest note displayed.
|
||||||
pub note_lo: AtomicUsize,
|
pub note_lo: AtomicUsize,
|
||||||
/// Selected note
|
/// Currently selected note.
|
||||||
pub note_pt: AtomicUsize,
|
pub note_pt: AtomicUsize,
|
||||||
/// Selected note as row/col
|
/// Selected note as row/col.
|
||||||
pub cursor: (AtomicUsize, AtomicUsize),
|
pub cursor: (AtomicUsize, AtomicUsize),
|
||||||
pub color: ItemTheme
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Sampler {
|
impl Default for Sampler {
|
||||||
|
|
@ -34,7 +52,8 @@ impl Default for Sampler {
|
||||||
Self {
|
Self {
|
||||||
midi_in: None,
|
midi_in: None,
|
||||||
audio_ins: vec![],
|
audio_ins: vec![],
|
||||||
input_meter: vec![0.0;2],
|
input_meters: vec![0.0;2],
|
||||||
|
output_meters: vec![0.0;2],
|
||||||
audio_outs: vec![],
|
audio_outs: vec![],
|
||||||
name: "tek_sampler".to_string(),
|
name: "tek_sampler".to_string(),
|
||||||
mapped: [const { None };128],
|
mapped: [const { None };128],
|
||||||
|
|
@ -50,6 +69,8 @@ impl Default for Sampler {
|
||||||
note_pt: 0.into(),
|
note_pt: 0.into(),
|
||||||
cursor: (0.into(), 0.into()),
|
cursor: (0.into(), 0.into()),
|
||||||
color: Default::default(),
|
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> {
|
pub fn status (&self, index: usize) -> impl Content<TuiOut> {
|
||||||
draw_status(self.mapped[index].as_ref())
|
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 {
|
fn draw_list_item (sample: &Option<Arc<RwLock<Sample>>>) -> String {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue