refactor sampler module

This commit is contained in:
🪞👃🪞 2024-12-28 18:12:43 +01:00
parent 97920d7063
commit df00fedfd6
6 changed files with 293 additions and 306 deletions

View file

@ -110,9 +110,11 @@ render!(<Tui>|self: GrooveboxSamples<'a>|{
bg = Color::Rgb(64,16,0); bg = Color::Rgb(64,16,0);
fg = Color::Rgb(224,64,32) fg = Color::Rgb(224,64,32)
} }
} else if self.0.sampler.mapped[note].is_some() {
fg = TuiTheme::g(224);
} }
Tui::bg(bg, if let Some(sample) = &self.0.sampler.mapped[note] { Tui::bg(bg, if let Some(sample) = &self.0.sampler.mapped[note] {
Tui::fg(fg, format!("{note:3} ????? ")) Tui::fg(fg, format!("{note:3} ?????? "))
} else { } else {
Tui::fg(fg, format!("{note:3} (none) ")) Tui::fg(fg, format!("{note:3} (none) "))
}) })

View file

@ -21,14 +21,9 @@ pub mod voice;
pub(crate) use self::voice::*; pub(crate) use self::voice::*;
pub use self::voice::Voice; pub use self::voice::Voice;
pub mod sampler_control; pub mod sampler_tui;
pub(crate) use self::sampler_control::*; pub(crate) use self::sampler_tui::*;
pub use self::sampler_tui::SamplerTui;
pub mod sampler_audio;
pub(crate) use self::sampler_audio::*;
pub mod sampler_keys;
pub(crate) use self::sampler_keys::*;
pub mod sample_import; pub mod sample_import;
pub(crate) use self::sample_import::*; pub(crate) use self::sample_import::*;
@ -102,88 +97,138 @@ impl Sampler {
} }
} }
} }
pub struct SamplerTui {
pub state: Sampler, pub enum SamplerCommand {
pub cursor: (usize, usize), RecordBegin(u7),
pub editing: Option<Arc<RwLock<Sample>>>, RecordCancel,
pub mode: Option<SamplerMode>, RecordFinish,
/// Size of actual notes area SetSample(u7, Option<Arc<RwLock<Sample>>>),
pub size: Measure<Tui>, SetGain(f32),
/// Lowest note displayed NoteOn(u7, u7),
pub note_lo: AtomicUsize, NoteOff(u7),
pub note_pt: AtomicUsize,
color: ItemPalette
}
impl SamplerTui {
/// Immutable reference to sample at cursor.
pub fn sample (&self) -> Option<&Arc<RwLock<Sample>>> {
for (i, sample) in self.state.mapped.iter().enumerate() {
if i == self.cursor.0 {
return sample.as_ref()
}
}
for (i, sample) in self.state.unmapped.iter().enumerate() {
if i + self.state.mapped.len() == self.cursor.0 {
return Some(sample)
}
}
None
}
}
from_jack!(|jack|SamplerTui{
Self {
cursor: (0, 0),
editing: None,
mode: None,
size: Measure::new(),
note_lo: 36.into(),
note_pt: 36.into(),
color: ItemPalette::from(Color::Rgb(64, 128, 32)),
state: Sampler::new(jack, "sampler")?,
}
});
render!(<Tui>|self: SamplerTui|{
let keys_width = 5;
let keys = move||SamplerKeys(self);
let fg = self.color.base.rgb;
let bg = self.color.darkest.rgb;
let border = Fill::wh(Outer(Style::default().fg(fg).bg(bg)));
let with_border = |x|lay!([border, Fill::wh(&x)]);
let with_size = |x|lay!([self.size, x]);
Tui::bg(bg, Fill::wh(with_border(Bsp::s(
Tui::fg(self.color.light.rgb, Tui::bold(true, "Sampler")),
with_size(Tui::shrink_y(1, Bsp::e(
Fixed::w(keys_width, keys()),
Fill::wh(render(|to: &mut TuiOutput|Ok({
let x = to.area.x();
let bg_base = self.color.darkest.rgb;
let bg_selected = self.color.darker.rgb;
let style_empty = Style::default().fg(self.color.base.rgb);
let style_full = Style::default().fg(self.color.lighter.rgb);
let note_hi = self.note_hi();
let note_pt = self.note_point();
for y in 0..self.size.h() {
let note = note_hi - y as usize;
let bg = if note == note_pt { bg_selected } else { bg_base };
let style = Some(style_empty.bg(bg));
to.blit(&" (no sample) ", x, to.area.y() + y as u16, style)
}
})))
))),
))))
});
impl NoteRange for SamplerTui {
fn note_lo (&self) -> &AtomicUsize { &self.note_lo }
fn note_axis (&self) -> &AtomicUsize { &self.size.y }
}
impl NotePoint for SamplerTui {
fn note_len (&self) -> usize {0/*TODO*/}
fn set_note_len (&self, x: usize) {}
fn note_point (&self) -> usize { self.note_pt.load(Relaxed) }
fn set_note_point (&self, x: usize) { self.note_pt.store(x, Relaxed); }
} }
pub enum SamplerMode { command!(|self: SamplerCommand, state: Sampler|match self {
// Load sample from path Self::SetSample(index, sample) => {
Import(usize, FileBrowser), let i = index.as_int() as usize;
let old = state.mapped[i].clone();
state.mapped[i] = sample;
Some(Self::SetSample(index, old))
},
Self::RecordBegin(index) => {
state.begin_recording(index.as_int() as usize);
None
},
Self::RecordCancel => {
state.cancel_recording();
None
},
Self::RecordFinish => {
state.finish_recording();
None
},
_ => todo!()
});
audio!(|self: SamplerTui, client, scope|{
SamplerAudio(&mut self.state).process(client, scope)
});
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);
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 {
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.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;
} else {
for (input, meter) in audio_ins.iter().zip(input_meter) {
let slice = input.as_slice(scope);
let total: f32 = slice.iter().map(|x|x.abs()).sum();
let count = slice.len() as f32;
*meter = 10. * (total / count).log10();
}
}
}
/// Create [Voice]s from [Sample]s in response to MIDI input.
pub fn process_midi_in (&mut self, scope: &ProcessScope) {
let Sampler { midi_in, mapped, voices, .. } = self;
for RawMidi { time, bytes } in midi_in.iter(scope) {
if let LiveEvent::Midi {
message: MidiMessage::NoteOn { ref key, ref vel }, ..
} = LiveEvent::parse(bytes).unwrap() {
if let Some(ref sample) = mapped[key.as_int() as usize] {
voices.write().unwrap().push(Sample::play(sample, time as usize, vel));
}
}
}
}
/// Zero the output buffer.
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.
pub fn process_audio_out (&mut self, scope: &ProcessScope) {
let Sampler { ref mut buffer, voices, output_gain, .. } = 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
});
}
/// Write output buffer to output ports.
pub 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];
for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() {
*value = *buffer.get(i).unwrap_or(&0.0);
}
}
}
} }

View file

@ -1,104 +0,0 @@
use crate::*;
audio!(|self: SamplerTui, client, scope|{
SamplerAudio(&mut self.state).process(client, scope)
});
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);
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 {
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.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;
} else {
for (input, meter) in audio_ins.iter().zip(input_meter) {
let slice = input.as_slice(scope);
let total: f32 = slice.iter().map(|x|x.abs()).sum();
let count = slice.len() as f32;
*meter = 10. * (total / count).log10();
}
}
}
/// Create [Voice]s from [Sample]s in response to MIDI input.
pub fn process_midi_in (&mut self, scope: &ProcessScope) {
let Sampler { midi_in, mapped, voices, .. } = self;
for RawMidi { time, bytes } in midi_in.iter(scope) {
if let LiveEvent::Midi {
message: MidiMessage::NoteOn { ref key, ref vel }, ..
} = LiveEvent::parse(bytes).unwrap() {
if let Some(ref sample) = mapped[key.as_int() as usize] {
voices.write().unwrap().push(Sample::play(sample, time as usize, vel));
}
}
}
}
/// Zero the output buffer.
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.
pub fn process_audio_out (&mut self, scope: &ProcessScope) {
let Sampler { ref mut buffer, voices, output_gain, .. } = 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
});
}
/// Write output buffer to output ports.
pub 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];
for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() {
*value = *buffer.get(i).unwrap_or(&0.0);
}
}
}
}

View file

@ -1,96 +0,0 @@
use crate::*;
use KeyCode::Char;
handle!(<Tui>|self: SamplerTui, input|SamplerTuiCommand::execute_with_state(self, input));
pub enum SamplerTuiCommand {
Import(FileBrowserCommand),
SelectNote(usize),
SelectField(usize),
Sample(SamplerCommand),
}
pub enum SamplerCommand {
RecordBegin(u7),
RecordCancel,
RecordFinish,
SetSample(u7, Option<Arc<RwLock<Sample>>>),
SetGain(f32),
NoteOn(u7, u7),
NoteOff(u7),
}
input_to_command!(SamplerTuiCommand: <Tui>|state: SamplerTui, input|match state.mode {
Some(SamplerMode::Import(..)) => Self::Import(
FileBrowserCommand::input_to_command(state, input)?
),
_ => match input.event() {
// load sample
key_pat!(Shift-Char('L')) => {
Self::Import(FileBrowserCommand::Begin)
},
key_pat!(KeyCode::Up) => {
Self::SelectNote(state.note_point().overflowing_add(1).0.min(127))
},
key_pat!(KeyCode::Down) => {
Self::SelectNote(state.note_point().overflowing_sub(1).0.min(127))
},
_ => return None
}
//key_pat!(KeyCode::Char('p')) => if let Some(sample) = self.sample() {
//voices.write().unwrap().push(Sample::play(sample, 0, &100.into()));
//},
//key_pat!(KeyCode::Char('a')) => {
//let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![])));
//self.mode = None;//Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?));
//unmapped.push(sample);
//},
//key_pat!(KeyCode::Char('r')) => if let Some(sample) = self.sample() {
//self.mode = None;//Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?));
//},
//key_pat!(KeyCode::Enter) => if let Some(sample) = self.sample() {
//self.editing = Some(sample.clone());
//},
//_ => {
//return Ok(None)
//}
//}
});
command!(|self: SamplerTuiCommand, state: SamplerTui|match self {
Self::Import(FileBrowserCommand::Begin) => {
let voices = &state.state.voices;
let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![])));
state.mode = Some(SamplerMode::Import(0, FileBrowser::new(None)?));
None
},
Self::SelectNote(index) => {
let old = state.note_point();
state.set_note_point(index);
Some(Self::SelectNote(old))
},
Self::Sample(cmd) => cmd.execute(&mut state.state)?.map(Self::Sample),
_ => todo!()
});
command!(|self: SamplerCommand, state: Sampler|match self {
Self::SetSample(index, sample) => {
let i = index.as_int() as usize;
let old = state.mapped[i].clone();
state.mapped[i] = sample;
Some(Self::SetSample(index, old))
},
Self::RecordBegin(index) => {
state.begin_recording(index.as_int() as usize);
None
},
Self::RecordCancel => {
state.cancel_recording();
None
},
Self::RecordFinish => {
state.finish_recording();
None
},
_ => todo!()
});

View file

@ -1,15 +0,0 @@
use crate::*;
pub struct SamplerKeys<'a>(pub &'a SamplerTui);
has_color!(|self: SamplerKeys<'a>|self.0.color.base);
render!(<Tui>|self: SamplerKeys<'a>|render(|to|Ok(render_keys_v(to, self))));
impl<'a> NoteRange for SamplerKeys<'a> {
fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo }
fn note_axis (&self) -> &AtomicUsize { &self.0.size.y }
}
impl<'a> NotePoint for SamplerKeys<'a> {
fn note_len (&self) -> usize {0/*TODO*/}
fn set_note_len (&self, x: usize) {}
fn note_point (&self) -> usize { self.0.note_point() }
fn set_note_point (&self, x: usize) { self.0.set_note_point(x); }
}

View file

@ -0,0 +1,155 @@
use crate::*;
use KeyCode::Char;
pub struct SamplerTui {
pub state: Sampler,
pub cursor: (usize, usize),
pub editing: Option<Arc<RwLock<Sample>>>,
pub mode: Option<SamplerMode>,
/// Size of actual notes area
pub size: Measure<Tui>,
/// Lowest note displayed
pub note_lo: AtomicUsize,
pub note_pt: AtomicUsize,
color: ItemPalette
}
impl SamplerTui {
/// Immutable reference to sample at cursor.
pub fn sample (&self) -> Option<&Arc<RwLock<Sample>>> {
for (i, sample) in self.state.mapped.iter().enumerate() {
if i == self.cursor.0 {
return sample.as_ref()
}
}
for (i, sample) in self.state.unmapped.iter().enumerate() {
if i + self.state.mapped.len() == self.cursor.0 {
return Some(sample)
}
}
None
}
}
from_jack!(|jack|SamplerTui{
Self {
cursor: (0, 0),
editing: None,
mode: None,
size: Measure::new(),
note_lo: 36.into(),
note_pt: 36.into(),
color: ItemPalette::from(Color::Rgb(64, 128, 32)),
state: Sampler::new(jack, "sampler")?,
}
});
render!(<Tui>|self: SamplerTui|{
let keys_width = 5;
let keys = move||"";//SamplerKeys(self);
let fg = self.color.base.rgb;
let bg = self.color.darkest.rgb;
let border = Fill::wh(Outer(Style::default().fg(fg).bg(bg)));
let with_border = |x|lay!([border, Fill::wh(&x)]);
let with_size = |x|lay!([self.size, x]);
Tui::bg(bg, Fill::wh(with_border(Bsp::s(
Tui::fg(self.color.light.rgb, Tui::bold(true, "Sampler")),
with_size(Tui::shrink_y(1, Bsp::e(
Fixed::w(keys_width, keys()),
Fill::wh(render(|to: &mut TuiOutput|Ok({
let x = to.area.x();
let bg_base = self.color.darkest.rgb;
let bg_selected = self.color.darker.rgb;
let style_empty = Style::default().fg(self.color.base.rgb);
let style_full = Style::default().fg(self.color.lighter.rgb);
let note_hi = self.note_hi();
let note_pt = self.note_point();
for y in 0..self.size.h() {
let note = note_hi - y as usize;
let bg = if note == note_pt { bg_selected } else { bg_base };
let style = Some(style_empty.bg(bg));
to.blit(&" (no sample) ", x, to.area.y() + y as u16, style)
}
})))
))),
))))
});
impl NoteRange for SamplerTui {
fn note_lo (&self) -> &AtomicUsize { &self.note_lo }
fn note_axis (&self) -> &AtomicUsize { &self.size.y }
}
impl NotePoint for SamplerTui {
fn note_len (&self) -> usize {0/*TODO*/}
fn set_note_len (&self, x: usize) {}
fn note_point (&self) -> usize { self.note_pt.load(Relaxed) }
fn set_note_point (&self, x: usize) { self.note_pt.store(x, Relaxed); }
}
pub enum SamplerMode {
// Load sample from path
Import(usize, FileBrowser),
}
handle!(<Tui>|self: SamplerTui, input|SamplerTuiCommand::execute_with_state(self, input));
pub enum SamplerTuiCommand {
Import(FileBrowserCommand),
SelectNote(usize),
SelectField(usize),
Sample(SamplerCommand),
}
input_to_command!(SamplerTuiCommand: <Tui>|state: SamplerTui, input|match state.mode {
Some(SamplerMode::Import(..)) => Self::Import(
FileBrowserCommand::input_to_command(state, input)?
),
_ => match input.event() {
// load sample
key_pat!(Shift-Char('L')) => {
Self::Import(FileBrowserCommand::Begin)
},
key_pat!(KeyCode::Up) => {
Self::SelectNote(state.note_point().overflowing_add(1).0.min(127))
},
key_pat!(KeyCode::Down) => {
Self::SelectNote(state.note_point().overflowing_sub(1).0.min(127))
},
_ => return None
}
//key_pat!(KeyCode::Char('p')) => if let Some(sample) = self.sample() {
//voices.write().unwrap().push(Sample::play(sample, 0, &100.into()));
//},
//key_pat!(KeyCode::Char('a')) => {
//let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![])));
//self.mode = None;//Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?));
//unmapped.push(sample);
//},
//key_pat!(KeyCode::Char('r')) => if let Some(sample) = self.sample() {
//self.mode = None;//Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?));
//},
//key_pat!(KeyCode::Enter) => if let Some(sample) = self.sample() {
//self.editing = Some(sample.clone());
//},
//_ => {
//return Ok(None)
//}
//}
});
command!(|self: SamplerTuiCommand, state: SamplerTui|match self {
Self::Import(FileBrowserCommand::Begin) => {
let voices = &state.state.voices;
let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![])));
state.mode = Some(SamplerMode::Import(0, FileBrowser::new(None)?));
None
},
Self::SelectNote(index) => {
let old = state.note_point();
state.set_note_point(index);
Some(Self::SelectNote(old))
},
Self::Sample(cmd) => cmd.execute(&mut state.state)?.map(Self::Sample),
_ => todo!()
});