|
@@ -1,7 +1,6 @@
|
|
|
+use {Step, Velocity, MIN_STEP, MAX_STEP};
|
|
|
+use audio::Audio;
|
|
|
use pitch;
|
|
|
-use sample;
|
|
|
-use std;
|
|
|
-use Velocity;
|
|
|
|
|
|
|
|
|
/// A type that maps frequncy and velocity ranges to audio samples.
|
|
@@ -10,18 +9,6 @@ pub struct Map<A> {
|
|
|
pub pairs: Vec<SampleOverRange<A>>,
|
|
|
}
|
|
|
|
|
|
-/// The audio data that provides the slice of frames that are to be rendered.
|
|
|
-///
|
|
|
-/// By making this a trait instead of a hard type, we can allow users to use their own `Audio`
|
|
|
-/// types which might require other data (i.e. file paths, names, etc) for unique serialization
|
|
|
-/// implementations.
|
|
|
-pub trait Audio: Clone {
|
|
|
- /// The type of `Frame` data associated with the audio.
|
|
|
- type Frame: sample::Frame;
|
|
|
- /// A reference to the slice of frames used to play the audio.
|
|
|
- fn data(&self) -> &[Self::Frame];
|
|
|
-}
|
|
|
-
|
|
|
/// A performable `Sample` with some base playback Hz and Velocity.
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
|
pub struct Sample<A> {
|
|
@@ -32,15 +19,15 @@ pub struct Sample<A> {
|
|
|
|
|
|
/// A 2-dimensional space, represented as a frequency range and a velocity range.
|
|
|
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
|
|
-pub struct HzVelRange {
|
|
|
- pub hz: Range<pitch::Hz>,
|
|
|
+pub struct StepVelRange {
|
|
|
+ pub step: Range<Step>,
|
|
|
pub vel: Range<Velocity>,
|
|
|
}
|
|
|
|
|
|
/// A range paired with a specific sample.
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
|
pub struct SampleOverRange<A> {
|
|
|
- pub range: HzVelRange,
|
|
|
+ pub range: StepVelRange,
|
|
|
pub sample: Sample<A>,
|
|
|
}
|
|
|
|
|
@@ -52,19 +39,10 @@ pub struct Range<T> {
|
|
|
}
|
|
|
|
|
|
|
|
|
-impl<T> Audio for std::sync::Arc<T>
|
|
|
- where T: Audio,
|
|
|
-{
|
|
|
- type Frame = T::Frame;
|
|
|
- fn data(&self) -> &[Self::Frame] {
|
|
|
- T::data(self)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-impl Range<pitch::Hz> {
|
|
|
- /// Is the given hz greater than or equal to the `min` and smaller than the `max`.
|
|
|
- pub fn is_over(&self, hz: pitch::Hz) -> bool {
|
|
|
- self.min <= hz && hz < self.max
|
|
|
+impl Range<Step> {
|
|
|
+ /// Is the given step greater than or equal to the `min` and smaller than the `max`.
|
|
|
+ pub fn is_over(&self, step: Step) -> bool {
|
|
|
+ self.min <= step && step <= self.max
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -110,27 +88,26 @@ impl<A> Map<A>
|
|
|
}
|
|
|
|
|
|
/// Construct a `Map` from a series of mappings, starting from (-C2, 1.0).
|
|
|
- pub fn from_sequential_mappings<I>(mappings: I) -> Self
|
|
|
- where I: IntoIterator<Item=(pitch::Hz, Velocity, Sample<A>)>,
|
|
|
+ pub fn from_sequential_steps<I>(mappings: I) -> Self
|
|
|
+ where I: IntoIterator<Item=(Step, Velocity, Sample<A>)>,
|
|
|
{
|
|
|
- const MIN_HZ: pitch::Hz = pitch::Hz(0.0);
|
|
|
- let (mut last_hz, mut last_vel) = (MIN_HZ, 1.0);
|
|
|
- let pairs = mappings.into_iter().map(|(hz, vel, sample)| {
|
|
|
- let range = HzVelRange {
|
|
|
- hz: Range { min: last_hz, max: hz },
|
|
|
+ let (mut last_step, mut last_vel) = (0, 1.0);
|
|
|
+ let pairs = mappings.into_iter().map(|(step, vel, sample)| {
|
|
|
+ let range = StepVelRange {
|
|
|
+ step: Range { min: last_step, max: step },
|
|
|
vel: Range { min: last_vel, max: vel },
|
|
|
};
|
|
|
- last_hz = hz;
|
|
|
+ last_step = step;
|
|
|
last_vel = vel;
|
|
|
SampleOverRange { range: range, sample: sample }
|
|
|
}).collect();
|
|
|
Map { pairs: pairs }
|
|
|
}
|
|
|
|
|
|
- /// Creates a `Map` with a single sample mapped to the entire Hz and Velocity range.
|
|
|
+ /// Creates a `Map` with a single sample mapped to the entire Step and Velocity range.
|
|
|
pub fn from_single_sample(sample: Sample<A>) -> Self {
|
|
|
- let range = HzVelRange {
|
|
|
- hz: Range { min: pitch::Hz(0.0), max: pitch::Hz(std::f32::MAX) },
|
|
|
+ let range = StepVelRange {
|
|
|
+ step: Range { min: MIN_STEP, max: MAX_STEP },
|
|
|
vel: Range { min: 0.0, max: 1.0 },
|
|
|
};
|
|
|
let pairs = vec![SampleOverRange { range: range, sample: sample }];
|
|
@@ -138,7 +115,7 @@ impl<A> Map<A>
|
|
|
}
|
|
|
|
|
|
/// Inserts a range -> audio mapping into the Map.
|
|
|
- pub fn insert(&mut self, range: HzVelRange, sample: Sample<A>) {
|
|
|
+ pub fn insert(&mut self, range: StepVelRange, sample: Sample<A>) {
|
|
|
for i in 0..self.pairs.len() {
|
|
|
if self.pairs[i].range > range {
|
|
|
self.pairs.insert(i, SampleOverRange { range: range, sample: sample });
|
|
@@ -152,8 +129,9 @@ impl<A> Map<A>
|
|
|
///
|
|
|
/// TODO: This would probably be quicker with some sort of specialised RangeMap.
|
|
|
pub fn sample(&self, hz: pitch::Hz, vel: Velocity) -> Option<Sample<A>> {
|
|
|
+ let step = hz.step().round() as Step;
|
|
|
for &SampleOverRange { ref range, ref sample } in &self.pairs {
|
|
|
- if range.hz.is_over(hz) && range.vel.is_over(vel) {
|
|
|
+ if range.step.is_over(step) && range.vel.is_over(vel) {
|
|
|
return Some(sample.clone());
|
|
|
}
|
|
|
}
|
|
@@ -165,120 +143,16 @@ impl<A> Map<A>
|
|
|
|
|
|
#[cfg(feature="wav")]
|
|
|
pub mod wav {
|
|
|
- use hound;
|
|
|
+ use audio;
|
|
|
use map;
|
|
|
use pitch;
|
|
|
use sample;
|
|
|
use std;
|
|
|
|
|
|
- /// WAV data loaded into memory as a single contiguous slice of PCM frames.
|
|
|
- #[derive(Clone, Debug, PartialEq)]
|
|
|
- pub struct Audio<F> {
|
|
|
- pub path: std::path::PathBuf,
|
|
|
- pub data: Box<[F]>,
|
|
|
- pub sample_hz: f64,
|
|
|
- }
|
|
|
|
|
|
/// An alias for the `wav` `Sample` type.
|
|
|
- pub type Sample<F> = super::Sample<std::sync::Arc<Audio<F>>>;
|
|
|
-
|
|
|
- impl<F> super::Audio for Audio<F>
|
|
|
- where F: sample::Frame,
|
|
|
- {
|
|
|
- type Frame = F;
|
|
|
- fn data(&self) -> &[Self::Frame] {
|
|
|
- &self.data[..]
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- impl<F> Audio<F>
|
|
|
- where F: sample::Frame,
|
|
|
- F::Sample: sample::Duplex<f64> + sample::Duplex<i32>,
|
|
|
- Box<[F::Sample]>: sample::ToBoxedFrameSlice<F>,
|
|
|
- {
|
|
|
+ pub type Sample<F> = super::Sample<std::sync::Arc<audio::wav::Audio<F>>>;
|
|
|
|
|
|
- /// Loads a `Sample` from the `.wav` file at the given `path`.
|
|
|
- ///
|
|
|
- /// The PCM data retrieved from the file will be:
|
|
|
- /// - re-sized from its source bit rate to that of the target and
|
|
|
- /// - re-sampled upon loading (rather than at playback) to the given target sample rate for
|
|
|
- /// efficiency.
|
|
|
- pub fn from_file<P>(path: P, target_sample_hz: f64) -> Result<Self, hound::Error>
|
|
|
- where P: AsRef<std::path::Path>,
|
|
|
- {
|
|
|
- use sample::Signal;
|
|
|
-
|
|
|
- let path = path.as_ref();
|
|
|
- let mut wav_reader = try!(hound::WavReader::open(path));
|
|
|
-
|
|
|
- let spec = wav_reader.spec();
|
|
|
- // TODO: Return an error instead of panic!ing! OR do some sort of frame /
|
|
|
- // channel layout conversion.
|
|
|
- assert!(spec.channels as usize == F::n_channels(),
|
|
|
- "The number of channels in the audio file differs from the number of \
|
|
|
- channels in the frame");
|
|
|
-
|
|
|
- // Collect the samples in a loop so that we may handle any errors if necessary.
|
|
|
- let mut samples: Vec<F::Sample> = Vec::new();
|
|
|
-
|
|
|
- // Reads the samples to their correct format type, and then converts them to the target
|
|
|
- // `F::Sample` type.
|
|
|
- type WavReader = hound::WavReader<std::io::BufReader<std::fs::File>>;
|
|
|
- fn fill_samples<S, H>(samples: &mut Vec<S>, mut wav_reader: WavReader)
|
|
|
- -> Result<(), hound::Error>
|
|
|
- where S: sample::FromSample<i32>,
|
|
|
- H: sample::Sample + sample::ToSample<i32> + hound::Sample,
|
|
|
- {
|
|
|
- for sample in wav_reader.samples() {
|
|
|
- let read_sample: H = try!(sample);
|
|
|
- let i32_sample: i32 = sample::Sample::to_sample(read_sample);
|
|
|
- samples.push(sample::Sample::to_sample(i32_sample));
|
|
|
- }
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- match spec.bits_per_sample {
|
|
|
- 8 => try!(fill_samples::<_, i8>(&mut samples, wav_reader)),
|
|
|
- 16 => try!(fill_samples::<_, i16>(&mut samples, wav_reader)),
|
|
|
- 32 => try!(fill_samples::<_, i32>(&mut samples, wav_reader)),
|
|
|
- // The sample crate uses a specific type for 24 bit samples, so this 24-bit sample
|
|
|
- // conversion requires this special case.
|
|
|
- 24 => {
|
|
|
- for sample in wav_reader.samples() {
|
|
|
- let read_sample: i32 = try!(sample);
|
|
|
- let i24_sample = try!(sample::I24::new(read_sample)
|
|
|
- .ok_or(hound::Error::FormatError("Incorrectly formatted 24-bit sample \
|
|
|
- received from hound::read::WavSamples")));
|
|
|
- let i32_sample: i32 = sample::Sample::to_sample(i24_sample);
|
|
|
- samples.push(sample::Sample::to_sample(i32_sample));
|
|
|
- }
|
|
|
- },
|
|
|
- _ => return Err(hound::Error::Unsupported),
|
|
|
- }
|
|
|
-
|
|
|
- let boxed_samples = samples.into_boxed_slice();
|
|
|
- let boxed_frames: Box<[F]> = match sample::slice::to_boxed_frame_slice(boxed_samples) {
|
|
|
- Some(slice) => slice,
|
|
|
- // TODO: Return an error instead of panic!ing! OR do some sort of frame /
|
|
|
- // channel layout conversion.
|
|
|
- None => panic!("The number of samples produced from the wav file does not \
|
|
|
- match the number of channels ({}) in the given `Frame` type",
|
|
|
- F::n_channels()),
|
|
|
- };
|
|
|
-
|
|
|
- // Convert the sample rate to our target sample rate.
|
|
|
- let frames: Vec<F> = boxed_frames.iter().cloned()
|
|
|
- .from_hz_to_hz(spec.sample_rate as f64, target_sample_hz)
|
|
|
- .collect();
|
|
|
-
|
|
|
- Ok(Audio {
|
|
|
- path: path.to_path_buf(),
|
|
|
- sample_hz: target_sample_hz,
|
|
|
- data: frames.into_boxed_slice(),
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
|
|
|
impl<F> Sample<F>
|
|
|
where F: sample::Frame,
|
|
@@ -295,7 +169,7 @@ pub mod wav {
|
|
|
///
|
|
|
/// The PCM data retrieved from the file will be re-sampled upon loading (rather than at
|
|
|
/// playback) to the given target sample rate for efficiency.
|
|
|
- pub fn from_wav_file<P>(path: P, target_sample_hz: f64) -> Result<Self, hound::Error>
|
|
|
+ pub fn from_wav_file<P>(path: P, target_sample_hz: f64) -> Result<Self, audio::wav::Error>
|
|
|
where P: AsRef<std::path::Path>,
|
|
|
{
|
|
|
let path = path.as_ref();
|
|
@@ -305,141 +179,12 @@ pub mod wav {
|
|
|
let base_hz = base_letter_octave.to_hz();
|
|
|
let base_vel = 1.0;
|
|
|
|
|
|
- let audio = std::sync::Arc::new(try!(Audio::from_file(path, target_sample_hz)));
|
|
|
+ let audio = std::sync::Arc::new(try!(audio::wav::Audio::from_file(path, target_sample_hz)));
|
|
|
|
|
|
Ok(map::Sample::new(base_hz, base_vel, audio))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // impl<F> map::Map<F>
|
|
|
- // where F: sample::Frame,
|
|
|
- // F::Sample: sample::Duplex<f64> + hound::Sample,
|
|
|
- // Box<[F::Sample]>: sample::ToBoxedFrameSlice<F>,
|
|
|
- // {
|
|
|
-
|
|
|
- // /// Loads a `Map` from the given directory.
|
|
|
- // ///
|
|
|
- // /// All `.wav` files that can be successfully loaded will be loaded into the `Map`.
|
|
|
- // ///
|
|
|
- // /// If the `.wav` file has a musical note in the file name, that note's playback frequency in
|
|
|
- // /// `hz` will be used as the `base_hz`.
|
|
|
- // ///
|
|
|
- // /// For efficiency, all files will be re-sampled upon loading (rather than at playback) to the
|
|
|
- // /// given target sample rate.
|
|
|
- // pub fn from_wav_directory<P>(path: P, target_sample_hz: f64) -> Result<Self, hound::Error>
|
|
|
- // where P: AsRef<std::path::Path>,
|
|
|
- // {
|
|
|
- // use sample::Signal;
|
|
|
- // use std::cmp::Ordering;
|
|
|
-
|
|
|
- // let path = path.as_ref();
|
|
|
-
|
|
|
- // struct SampleReader {
|
|
|
- // base_letter_octave: Option<pitch::LetterOctave>,
|
|
|
- // wav_reader: hound::WavReader<std::io::BufReader<std::fs::File>>,
|
|
|
- // }
|
|
|
-
|
|
|
- // let mut sample_readers: Vec<SampleReader> = Vec::new();
|
|
|
-
|
|
|
- // // Find all .wav files in the given directory and store them as `SampleReader`s.
|
|
|
- // for entry in try!(std::fs::read_dir(path)) {
|
|
|
- // let file_name = try!(entry).file_name();
|
|
|
-
|
|
|
- // // If the entry is a wave file, add it to our list.
|
|
|
- // if has_wav_ext(file_name.as_ref()) {
|
|
|
- // let wav_reader = try!(hound::WavReader::open(&file_name));
|
|
|
- // let sample_reader = SampleReader {
|
|
|
- // base_letter_octave: read_base_letter_octave(file_name.as_ref()),
|
|
|
- // wav_reader: wav_reader,
|
|
|
- // };
|
|
|
- // sample_readers.push(sample_reader);
|
|
|
- // }
|
|
|
- // }
|
|
|
-
|
|
|
- // // Sort the readers by their base hz.
|
|
|
- // sample_readers.sort_by(|a, b| match (a.base_letter_octave, b.base_letter_octave) {
|
|
|
- // (Some(_), None) => Ordering::Less,
|
|
|
- // (None, Some(_)) => Ordering::Greater,
|
|
|
- // (Some(a), Some(b)) => a.cmp(&b),
|
|
|
- // (None, None) => Ordering::Equal,
|
|
|
- // });
|
|
|
-
|
|
|
- // const DEFAULT_LETTER_OCTAVE: pitch::LetterOctave =
|
|
|
- // pitch::LetterOctave(pitch::Letter::C, 1);
|
|
|
- // let mut maybe_last_step = None;
|
|
|
-
|
|
|
- // // We must imperatively collect the mappings so that we can handle any errors.
|
|
|
- // let mut mappings = Vec::with_capacity(sample_readers.len());
|
|
|
- // for SampleReader { base_letter_octave, mut wav_reader } in sample_readers {
|
|
|
- // let base_vel = 1.0;
|
|
|
- // let base_hz = match base_letter_octave {
|
|
|
- // Some(letter_octave) => {
|
|
|
- // maybe_last_step = Some(letter_octave.step());
|
|
|
- // letter_octave.to_hz()
|
|
|
- // },
|
|
|
- // None => {
|
|
|
- // let last_step = maybe_last_step.unwrap_or(DEFAULT_LETTER_OCTAVE.step());
|
|
|
- // let step = last_step + 1.0;
|
|
|
- // maybe_last_step = Some(step);
|
|
|
- // pitch::Step(step).to_hz()
|
|
|
- // },
|
|
|
- // };
|
|
|
-
|
|
|
- // let audio = {
|
|
|
- // let spec = wav_reader.spec();
|
|
|
-
|
|
|
- // // Collect the samples in a loop so that we may handle any errors if necessary.
|
|
|
- // let mut samples: Vec<F::Sample> = Vec::new();
|
|
|
- // for sample in wav_reader.samples() {
|
|
|
- // samples.push(try!(sample));
|
|
|
- // }
|
|
|
-
|
|
|
- // let boxed_samples = samples.into_boxed_slice();
|
|
|
- // let boxed_frames: Box<[F]> = match sample::slice::to_boxed_frame_slice(boxed_samples) {
|
|
|
- // Some(slice) => slice,
|
|
|
- // // TODO: Return an error instead of panic!ing! OR do some sort of frame /
|
|
|
- // // channel layout conversion.
|
|
|
- // None => panic!("The number of samples produced from the wav file does not \
|
|
|
- // match the number of channels ({}) in the given `Frame` type",
|
|
|
- // F::n_channels()),
|
|
|
- // };
|
|
|
-
|
|
|
- // // Convert the sample rate to our target sample rate.
|
|
|
- // let frames: Vec<F> = boxed_frames.iter().cloned()
|
|
|
- // .from_hz_to_hz(spec.sample_rate as f64, target_sample_hz)
|
|
|
- // .collect();
|
|
|
-
|
|
|
- // map::Audio::new(frames.into_boxed_slice())
|
|
|
- // };
|
|
|
-
|
|
|
- // let sample = map::Sample::new(base_hz, base_vel, audio);
|
|
|
-
|
|
|
- // // The `Hz` range that triggers this sample will span from the last sample's Hz (or
|
|
|
- // // the minimum if there is no last sample) to the following `to_hz` value.
|
|
|
- // //
|
|
|
- // // TODO: Investigate a nicer way of evenly spreading samples across the keyboard.
|
|
|
- // let to_hz = pitch::Step(base_hz.step() + 0.5).to_hz();
|
|
|
- // let to_vel = base_vel;
|
|
|
- // mappings.push((to_hz, to_vel, sample));
|
|
|
- // }
|
|
|
-
|
|
|
- // Ok(Self::from_sequential_mappings(mappings))
|
|
|
- // }
|
|
|
-
|
|
|
- // }
|
|
|
-
|
|
|
- ///// Utility functions.
|
|
|
-
|
|
|
- // /// Determines whether the given `Path` leads to a wave file.
|
|
|
- // fn has_wav_ext(path: &std::path::Path) -> bool {
|
|
|
- // let ext = path.extension()
|
|
|
- // .and_then(|s| s.to_str())
|
|
|
- // .map_or("".into(), std::ascii::AsciiExt::to_ascii_lowercase);
|
|
|
- // match &ext[..] {
|
|
|
- // "wav" | "wave" => true,
|
|
|
- // _ => false,
|
|
|
- // }
|
|
|
- // }
|
|
|
|
|
|
/// Scans the given path for an indication of its pitch.
|
|
|
fn read_base_letter_octave(path: &std::path::Path) -> Option<pitch::LetterOctave> {
|