mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-10-08 18:56:42 +02:00
642 lines
21 KiB
Rust
642 lines
21 KiB
Rust
#![allow(unused)]
|
|
use crate::*;
|
|
pub(crate) use symphonia::{
|
|
default::get_codecs,
|
|
core::{//errors::Error as SymphoniaError,
|
|
audio::SampleBuffer, formats::Packet, io::MediaSourceStream, probe::Hint,
|
|
codecs::{Decoder, CODEC_TYPE_NULL},
|
|
},
|
|
};
|
|
|
|
/// 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: Sizer,
|
|
/// 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<T: AsRef<Sampler> + AsMut<Sampler>> HasSampler for T {}
|
|
|
|
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.1
|
|
}
|
|
}
|
|
|
|
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.try_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.try_write().unwrap().retain_mut(|voice|{
|
|
mix_summing(buffer.as_mut_slice(), *output_gain, frames, ||voice.next())
|
|
}),
|
|
MixingMode::Average => voices.try_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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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.try_write().unwrap().push(Sample::play(sample, time as usize, vel));
|
|
}
|
|
},
|
|
MidiMessage::Controller { controller: _, value: _ } => {
|
|
// TODO
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get the first sampler of the active track
|
|
impl Project {
|
|
pub fn sampler (&self) -> Option<&Sampler> {
|
|
self.selected_track()?.sampler(0)
|
|
}
|
|
|
|
/// Get the first sampler of the active track
|
|
pub fn sampler_mut (&mut self) -> Option<&mut Sampler> {
|
|
self.selected_track_mut()?.sampler_mut(0)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "track")]
|
|
impl Track {
|
|
/// Create a new track connecting the [Sequencer] to a [Sampler].
|
|
pub fn new_with_sampler (
|
|
name: &impl AsRef<str>,
|
|
color: Option<ItemTheme>,
|
|
jack: &Jack<'static>,
|
|
clock: Option<&Clock>,
|
|
clip: Option<&Arc<RwLock<MidiClip>>>,
|
|
midi_from: &[Connect],
|
|
midi_to: &[Connect],
|
|
audio_from: &[&[Connect];2],
|
|
audio_to: &[&[Connect];2],
|
|
) -> Usually<Self> {
|
|
let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?;
|
|
let client_name = jack.with_client(|c|c.name().to_string());
|
|
let port_name = track.sequencer.midi_outs[0].port_name();
|
|
let connect = [Connect::exact(format!("{client_name}:{}", port_name))];
|
|
track.devices.push(Device::Sampler(Sampler::new(
|
|
jack, &format!("{}/sampler", name.as_ref()), &connect, audio_from, audio_to
|
|
)?));
|
|
Ok(track)
|
|
}
|
|
|
|
pub fn sampler (&self, mut nth: usize) -> Option<&Sampler> {
|
|
for device in self.devices.iter() {
|
|
match device {
|
|
Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; },
|
|
_ => {}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
pub fn sampler_mut (&mut self, mut nth: usize) -> Option<&mut Sampler> {
|
|
for device in self.devices.iter_mut() {
|
|
match device {
|
|
Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; },
|
|
_ => {}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
}
|
|
|
|
/// 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 <const N: usize> (
|
|
pub [Option<Arc<RwLock<Sample>>>;N]
|
|
);
|
|
|
|
impl<const N: usize> Default for SampleKit<N> {
|
|
fn default () -> Self { Self([const { None }; N]) }
|
|
}
|
|
|
|
impl<const N: usize> SampleKit<N> {
|
|
pub fn get (&self, index: usize) -> &Option<Arc<RwLock<Sample>>> {
|
|
if index < self.0.len() {
|
|
&self.0[index]
|
|
} else {
|
|
&None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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<str>,
|
|
pub start: usize,
|
|
pub end: usize,
|
|
pub channels: Vec<Vec<f32>>,
|
|
pub rate: Option<usize>,
|
|
pub gain: f32,
|
|
pub color: ItemTheme,
|
|
}
|
|
|
|
impl Sample {
|
|
pub fn new (name: impl AsRef<str>, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Self {
|
|
Self {
|
|
name: name.as_ref().into(),
|
|
start,
|
|
end,
|
|
channels,
|
|
rate: None,
|
|
gain: 1.0,
|
|
color: ItemTheme::random(),
|
|
}
|
|
}
|
|
pub fn play (sample: &Arc<RwLock<Self>>, after: usize, velocity: &u7) -> Voice {
|
|
Voice {
|
|
sample: sample.clone(),
|
|
after,
|
|
position: sample.try_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<Vec<f32>>)> {
|
|
let mut channels: Vec<wavers::Samples<f32>> = vec![];
|
|
for channel in wavers::Wav::from_path(src)?.channels() {
|
|
channels.push(channel);
|
|
}
|
|
let mut end = 0;
|
|
let mut data: Vec<Vec<f32>> = 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<Self> {
|
|
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<dyn Decoder>, packet: Packet
|
|
) -> Usually<()> {
|
|
// Decode a packet
|
|
let decoded = decoder
|
|
.decode(&packet)
|
|
.map_err(|e|Box::<dyn std::error::Error>::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(())
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug)] pub struct SampleAdd {
|
|
pub exited: bool,
|
|
pub dir: PathBuf,
|
|
pub subdirs: Vec<OsString>,
|
|
pub files: Vec<OsString>,
|
|
pub cursor: usize,
|
|
pub offset: usize,
|
|
pub sample: Arc<RwLock<Sample>>,
|
|
pub voices: Arc<RwLock<Vec<Voice>>>,
|
|
pub _search: Option<String>,
|
|
}
|
|
|
|
impl_draw!(|self: SampleAdd, to: Tui|{ todo!() });
|
|
|
|
#[allow(unused)]
|
|
impl SampleAdd {
|
|
fn exited (&self) -> bool {
|
|
self.exited
|
|
}
|
|
fn exit (&mut self) {
|
|
self.exited = true
|
|
}
|
|
pub fn new (
|
|
sample: &Arc<RwLock<Sample>>,
|
|
voices: &Arc<RwLock<Vec<Voice>>>
|
|
) -> Usually<Self> {
|
|
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.try_write().unwrap() = sample;
|
|
self.voices.try_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<PathBuf> {
|
|
if self.cursor < self.subdirs.len() {
|
|
Some(self.dir.join(&self.subdirs[self.cursor]))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
fn cursor_file (&self) -> Option<PathBuf> {
|
|
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<bool> {
|
|
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.try_write().unwrap();
|
|
sample.name = path.file_name().unwrap().to_string_lossy().into();
|
|
sample.end = end;
|
|
sample.channels = channels;
|
|
return Ok(true)
|
|
}
|
|
return Ok(false)
|
|
}
|
|
}
|
|
|
|
#[allow(unused)]
|
|
fn read_sample_data (_: &str) -> Usually<(usize, Vec<Vec<f32>>)> {
|
|
todo!();
|
|
}
|
|
|
|
/// A currently playing instance of a sample.
|
|
#[derive(Default, Debug, Clone)] pub struct Voice {
|
|
pub sample: Arc<RwLock<Sample>>,
|
|
pub after: usize,
|
|
pub position: usize,
|
|
pub velocity: f32,
|
|
}
|
|
|
|
impl Iterator for Voice {
|
|
type Item = [f32;2];
|
|
fn next (&mut self) -> Option<Self::Item> {
|
|
if self.after > 0 {
|
|
self.after -= 1;
|
|
return Some([0.0, 0.0])
|
|
}
|
|
let sample = self.sample.try_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
|
|
}
|
|
}
|
|
|