use crate::{*, device::*, browse::*, mix::*}; def_command!(SamplerCommand: |sampler: Sampler| { RecordToggle { slot: usize } => { let slot = *slot; let recording = sampler.recording.as_ref().map(|x|x.0); let _ = Self::RecordFinish.execute(sampler)?; // autoslice: continue recording at next slot if recording != Some(slot) { Self::RecordBegin { slot }.execute(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) }, }); /// Plays [Voice]s from [Sample]s. /// /// ``` /// let sampler = tek::Sampler::default(); /// ``` #[derive(Debug, Default)] pub struct Sampler { /// Name of sampler. pub name: Arc, /// Device color. pub color: ItemTheme, /// Sample currently being recorded. pub recording: Option<(usize, Option>>)>, /// Recording buffer. pub buffer: Vec>, /// Samples mapped to MIDI notes. pub samples: SampleKit<128>, /// Collection of currently playing instances of samples. pub voices: Arc>>, /// Samples that are not mapped to MIDI notes. pub unmapped: Vec>>, /// Sample currently being edited. pub editing: Option>>, /// 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, /// Size of rendered sampler. pub size: Measure, /// 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, /// Audio input ports. Samples are recorded from here. #[cfg(feature = "port")] pub audio_ins: Vec, /// MIDI input port. Sampler are triggered from here. #[cfg(feature = "port")] pub midi_in: Option, /// Audio output ports. Voices are played into here. #[cfg(feature = "port")] pub audio_outs: Vec, /// Audio output meters. #[cfg(feature = "meter")] pub output_meters: Vec, } /// Collection of samples, one per slot, fixed number of slots. /// /// History: Separated to cleanly implement [Default]. /// /// ``` /// let samples = tek::SampleKit([None, None, None, None]); /// ``` #[derive(Debug)] pub struct SampleKit( pub [Option>>;N] ); /// A sound cut. /// /// ``` /// let sample = tek::Sample::default(); /// let sample = tek::Sample::new("test", 0, 0, vec![]); /// ``` #[derive(Default, Debug)] pub struct Sample { pub name: Arc, pub start: usize, pub end: usize, pub channels: Vec>, pub rate: Option, pub gain: f32, pub color: ItemTheme, } /// A currently playing instance of a sample. #[derive(Default, Debug, Clone)] pub struct Voice { pub sample: Arc>, pub after: usize, pub position: usize, pub velocity: f32, } #[derive(Default, Debug)] pub struct SampleAdd { pub exited: bool, pub dir: PathBuf, pub subdirs: Vec, pub files: Vec, pub cursor: usize, pub offset: usize, pub sample: Arc>, pub voices: Arc>>, pub _search: Option, } #[derive(Debug)] pub enum SamplerMode { // Load sample from path Import(usize, Browse), } pub type MidiSample = (Option, Arc>); impl Default for SampleKit { fn default () -> Self { Self([const { None }; N]) } } impl Iterator for Voice { type Item = [f32;2]; fn next (&mut self) -> Option { if self.after > 0 { 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 += 1; return sample.channels[0].get(position).map(|_amplitude|[ sample.channels[0][position] * self.velocity * sample.gain, sample.channels[0][position] * self.velocity * sample.gain, ]) } None } } 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, #[cfg(feature = "port")] midi_from: &[Connect], #[cfg(feature = "port")] audio_from: &[&[Connect];2], #[cfg(feature = "port")] audio_to: &[&[Connect];2], ) -> Usually { 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); } } } } impl SampleAdd { fn exited (&self) -> bool { self.exited } fn exit (&mut self) { self.exited = true } pub fn new ( sample: &Arc>, voices: &Arc>> ) -> Usually { let dir = std::env::current_dir()?; let (subdirs, files) = scan(&dir)?; Ok(Self { exited: false, dir, subdirs, files, cursor: 0, offset: 0, sample: sample.clone(), voices: voices.clone(), _search: None }) } fn rescan (&mut self) -> Usually<()> { scan(&self.dir).map(|(subdirs, files)|{ self.subdirs = subdirs; self.files = files; }) } fn prev (&mut self) { self.cursor = self.cursor.saturating_sub(1); } fn next (&mut self) { self.cursor = self.cursor + 1; } fn try_preview (&mut self) -> Usually<()> { if let Some(path) = self.cursor_file() { if let Ok(sample) = Sample::from_file(&path) { *self.sample.write().unwrap() = sample; self.voices.write().unwrap().push( Sample::play(&self.sample, 0, &u7::from(100u8)) ); } //load_sample(&path)?; //let src = std::fs::File::open(&path)?; //let mss = MediaSourceStream::new(Box::new(src), Default::default()); //let mut hint = Hint::new(); //if let Some(ext) = path.extension() { //hint.with_extension(&ext.to_string_lossy()); //} //let meta_opts: MetadataOptions = Default::default(); //let fmt_opts: FormatOptions = Default::default(); //if let Ok(mut probed) = symphonia::default::get_probe() //.format(&hint, mss, &fmt_opts, &meta_opts) //{ //panic!("{:?}", probed.format.metadata()); //}; } Ok(()) } fn cursor_dir (&self) -> Option { if self.cursor < self.subdirs.len() { Some(self.dir.join(&self.subdirs[self.cursor])) } else { None } } fn cursor_file (&self) -> Option { if self.cursor < self.subdirs.len() { return None } let index = self.cursor.saturating_sub(self.subdirs.len()); if index < self.files.len() { Some(self.dir.join(&self.files[index])) } else { None } } fn pick (&mut self) -> Usually { if self.cursor == 0 { if let Some(parent) = self.dir.parent() { self.dir = parent.into(); self.rescan()?; self.cursor = 0; return Ok(false) } } if let Some(dir) = self.cursor_dir() { self.dir = dir; self.rescan()?; self.cursor = 0; return Ok(false) } if let Some(path) = self.cursor_file() { let (end, channels) = read_sample_data(&path.to_string_lossy())?; let mut sample = self.sample.write().unwrap(); sample.name = path.file_name().unwrap().to_string_lossy().into(); sample.end = end; sample.channels = channels; return Ok(true) } return Ok(false) } } impl SampleKit { pub fn get (&self, index: usize) -> &Option>> { if index < self.0.len() { &self.0[index] } else { &None } } } impl Sample { pub fn new (name: impl AsRef, start: usize, end: usize, channels: Vec>) -> Self { Self { name: name.as_ref().into(), start, end, channels, rate: None, gain: 1.0, color: ItemTheme::random(), } } pub fn play (sample: &Arc>, after: usize, velocity: &u7) -> Voice { Voice { sample: sample.clone(), after, position: sample.read().unwrap().start, velocity: velocity.as_int() as f32 / 127.0, } } pub fn handle_cc (&mut self, controller: u7, value: u7) { let percentage = value.as_int() as f64 / 127.; match controller.as_int() { 20 => { self.start = (percentage * self.end as f64) as usize; }, 21 => { let length = self.channels[0].len(); self.end = length.min( self.start + (percentage * (length as f64 - self.start as f64)) as usize ); }, 22 => { /*attack*/ }, 23 => { /*decay*/ }, 24 => { self.gain = percentage as f32 * 2.0; }, 26 => { /* pan */ } 25 => { /* pitch */ } _ => {} } } /// Read WAV from file pub fn read_data (src: &str) -> Usually<(usize, Vec>)> { let mut channels: Vec> = vec![]; for channel in wavers::Wav::from_path(src)?.channels() { channels.push(channel); } let mut end = 0; let mut data: Vec> = vec![]; for samples in channels.iter() { let channel = Vec::from(samples.as_ref()); end = end.max(channel.len()); data.push(channel); } Ok((end, data)) } pub fn from_file (path: &PathBuf) -> Usually { let name = path.file_name().unwrap().to_string_lossy().into(); let mut sample = Self { name, ..Default::default() }; // Use file extension if present let mut hint = Hint::new(); if let Some(ext) = path.extension() { hint.with_extension(&ext.to_string_lossy()); } let probed = symphonia::default::get_probe().format( &hint, MediaSourceStream::new( Box::new(File::open(path)?), Default::default(), ), &Default::default(), &Default::default() )?; let mut format = probed.format; let params = &format.tracks().iter() .find(|t| t.codec_params.codec != CODEC_TYPE_NULL) .expect("no tracks found") .codec_params; let mut decoder = get_codecs().make(params, &Default::default())?; loop { match format.next_packet() { Ok(packet) => sample.decode_packet(&mut decoder, packet)?, Err(symphonia::core::errors::Error::IoError(_)) => break decoder.last_decoded(), Err(err) => return Err(err.into()), }; }; sample.end = sample.channels.iter().fold(0, |l, c|l + c.len()); Ok(sample) } fn decode_packet ( &mut self, decoder: &mut Box, packet: Packet ) -> Usually<()> { // Decode a packet let decoded = decoder .decode(&packet) .map_err(|e|Box::::from(e))?; // Determine sample rate let spec = *decoded.spec(); if let Some(rate) = self.rate { if rate != spec.rate as usize { panic!("sample rate changed"); } } else { self.rate = Some(spec.rate as usize); } // Determine channel count while self.channels.len() < spec.channels.count() { self.channels.push(vec![]); } // Load sample let mut samples = SampleBuffer::new( decoded.frames() as u64, spec ); if samples.capacity() > 0 { samples.copy_interleaved_ref(decoded); for frame in samples.samples().chunks(spec.channels.count()) { for (chan, frame) in frame.iter().enumerate() { self.channels[chan].push(*frame) } } } Ok(()) } } impl Draw for SampleAdd { fn draw (self, _to: &mut Tui) -> Usually> { todo!() } } fn draw_list_item (sample: &Option>>) -> 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>>) -> impl Draw + use<'_> { let min_db = -64.0; Thunk::new(move|to: &mut Tui|{ let XYWH(x, y, width, height) = to.area(); 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, &mut to.buffer); } 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, &mut to.buffer); } }) } 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 } /// Create [Voice]s from [Sample]s in response to MIDI input. fn sampler_midi_in ( samples: &SampleKit<128>, voices: &Arc>>, 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 { 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>)> { todo!(); }