|
@@ -6,50 +6,71 @@ use Velocity;
|
|
|
|
|
|
/// A type that maps frequncy and velocity ranges to audio samples.
|
|
/// A type that maps frequncy and velocity ranges to audio samples.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
-pub struct Map<F> {
|
|
|
|
- pairs: Vec<Pair<F>>,
|
|
|
|
|
|
+pub struct Map<A> {
|
|
|
|
+ pub pairs: Vec<SampleOverRange<A>>,
|
|
}
|
|
}
|
|
|
|
|
|
-/// Some slice of PCM samples that represents a single audio sample.
|
|
|
|
|
|
+// /// Some slice of PCM samples that represents a single audio sample.
|
|
|
|
+// ///
|
|
|
|
+// /// **Note:** The `sampler` crate currently assumes that the `Audio` you give it has the same
|
|
|
|
+// /// format as the parameters with which audio is requested. We are hoping to enforce this using
|
|
|
|
+// /// types with some changes to the `sample` crate.
|
|
|
|
+// #[derive(Clone, Debug, PartialEq)]
|
|
|
|
+// pub struct Audio<F> {
|
|
|
|
+// pub data: std::sync::Arc<Box<[F]>>,
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+/// The audio data that provides the slice of frames that are to be rendered.
|
|
///
|
|
///
|
|
-/// **Note:** The `sampler` crate currently assumes that the `Audio` you give it has the same
|
|
|
|
-/// format as the parameters with which audio is requested. We are hoping to enforce this using
|
|
|
|
-/// types with some changes to the `sample` crate.
|
|
|
|
-#[derive(Clone, Debug, PartialEq)]
|
|
|
|
-pub struct Audio<F> {
|
|
|
|
- pub data: std::sync::Arc<Box<[F]>>,
|
|
|
|
|
|
+/// 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.
|
|
/// A performable `Sample` with some base playback Hz and Velocity.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
-pub struct Sample<F> {
|
|
|
|
|
|
+pub struct Sample<A> {
|
|
pub base_hz: pitch::Hz,
|
|
pub base_hz: pitch::Hz,
|
|
pub base_vel: Velocity,
|
|
pub base_vel: Velocity,
|
|
- pub audio: Audio<F>,
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/// A range paired with a specific sample.
|
|
|
|
-#[derive(Clone, Debug, PartialEq)]
|
|
|
|
-struct Pair<F> {
|
|
|
|
- range: HzVelRange,
|
|
|
|
- sample: Sample<F>,
|
|
|
|
|
|
+ pub audio: A,
|
|
}
|
|
}
|
|
|
|
|
|
/// A 2-dimensional space, represented as a frequency range and a velocity range.
|
|
/// A 2-dimensional space, represented as a frequency range and a velocity range.
|
|
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
|
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
|
pub struct HzVelRange {
|
|
pub struct HzVelRange {
|
|
- hz: Range<pitch::Hz>,
|
|
|
|
- vel: Range<Velocity>,
|
|
|
|
|
|
+ pub hz: Range<pitch::Hz>,
|
|
|
|
+ pub vel: Range<Velocity>,
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/// A range paired with a specific sample.
|
|
|
|
+#[derive(Clone, Debug, PartialEq)]
|
|
|
|
+pub struct SampleOverRange<A> {
|
|
|
|
+ pub range: HzVelRange,
|
|
|
|
+ pub sample: Sample<A>,
|
|
}
|
|
}
|
|
|
|
|
|
/// A continuous range of `T` from the `min` to the `max`.
|
|
/// A continuous range of `T` from the `min` to the `max`.
|
|
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
|
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
|
pub struct Range<T> {
|
|
pub struct Range<T> {
|
|
- min: T,
|
|
|
|
- max: T,
|
|
|
|
|
|
+ pub min: T,
|
|
|
|
+ pub max: 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> {
|
|
impl Range<pitch::Hz> {
|
|
/// Is the given hz greater than or equal to the `min` and smaller than the `max`.
|
|
/// 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 {
|
|
pub fn is_over(&self, hz: pitch::Hz) -> bool {
|
|
@@ -64,19 +85,10 @@ impl Range<Velocity> {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-impl<F> Audio<F> {
|
|
|
|
- /// Constructor for a new `Audio`.
|
|
|
|
- pub fn new<I>(samples: I) -> Self
|
|
|
|
- where I: Into<std::sync::Arc<Box<[F]>>>,
|
|
|
|
- {
|
|
|
|
- Audio { data: samples.into() }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl<F> Sample<F> {
|
|
|
|
|
|
+impl<A> Sample<A> {
|
|
|
|
|
|
/// Constructor for a new `Sample` with the given base Hz and Velocity.
|
|
/// Constructor for a new `Sample` with the given base Hz and Velocity.
|
|
- pub fn new(base_hz: pitch::Hz, base_vel: Velocity, audio: Audio<F>) -> Self {
|
|
|
|
|
|
+ pub fn new(base_hz: pitch::Hz, base_vel: Velocity, audio: A) -> Self {
|
|
Sample {
|
|
Sample {
|
|
base_hz: base_hz,
|
|
base_hz: base_hz,
|
|
base_vel: base_vel,
|
|
base_vel: base_vel,
|
|
@@ -86,8 +98,8 @@ impl<F> Sample<F> {
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
-impl<F> Map<F>
|
|
|
|
- where F: sample::Frame,
|
|
|
|
|
|
+impl<A> Map<A>
|
|
|
|
+ where A: Audio,
|
|
{
|
|
{
|
|
|
|
|
|
/// Construct an empty `Map`.
|
|
/// Construct an empty `Map`.
|
|
@@ -97,7 +109,7 @@ impl<F> Map<F>
|
|
|
|
|
|
/// Construct a `Map` from a series of mappings, starting from (-C2, 1.0).
|
|
/// Construct a `Map` from a series of mappings, starting from (-C2, 1.0).
|
|
pub fn from_sequential_mappings<I>(mappings: I) -> Self
|
|
pub fn from_sequential_mappings<I>(mappings: I) -> Self
|
|
- where I: IntoIterator<Item=(pitch::Hz, Velocity, Sample<F>)>,
|
|
|
|
|
|
+ where I: IntoIterator<Item=(pitch::Hz, Velocity, Sample<A>)>,
|
|
{
|
|
{
|
|
const MIN_HZ: pitch::Hz = pitch::Hz(0.0);
|
|
const MIN_HZ: pitch::Hz = pitch::Hz(0.0);
|
|
let (mut last_hz, mut last_vel) = (MIN_HZ, 1.0);
|
|
let (mut last_hz, mut last_vel) = (MIN_HZ, 1.0);
|
|
@@ -108,39 +120,37 @@ impl<F> Map<F>
|
|
};
|
|
};
|
|
last_hz = hz;
|
|
last_hz = hz;
|
|
last_vel = vel;
|
|
last_vel = vel;
|
|
- Pair { range: range, sample: sample }
|
|
|
|
|
|
+ SampleOverRange { range: range, sample: sample }
|
|
}).collect();
|
|
}).collect();
|
|
Map { pairs: pairs }
|
|
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 Hz and Velocity range.
|
|
- pub fn from_single_sample(sample: Sample<F>) -> Self {
|
|
|
|
|
|
+ pub fn from_single_sample(sample: Sample<A>) -> Self {
|
|
let range = HzVelRange {
|
|
let range = HzVelRange {
|
|
hz: Range { min: pitch::Hz(0.0), max: pitch::Hz(std::f32::MAX) },
|
|
hz: Range { min: pitch::Hz(0.0), max: pitch::Hz(std::f32::MAX) },
|
|
vel: Range { min: 0.0, max: 1.0 },
|
|
vel: Range { min: 0.0, max: 1.0 },
|
|
};
|
|
};
|
|
- let pairs = vec![Pair { range: range, sample: sample }];
|
|
|
|
|
|
+ let pairs = vec![SampleOverRange { range: range, sample: sample }];
|
|
Map { pairs: pairs }
|
|
Map { pairs: pairs }
|
|
}
|
|
}
|
|
|
|
|
|
/// Inserts a range -> audio mapping into the Map.
|
|
/// Inserts a range -> audio mapping into the Map.
|
|
- pub fn insert(&mut self, range: HzVelRange, sample: Sample<F>) {
|
|
|
|
|
|
+ pub fn insert(&mut self, range: HzVelRange, sample: Sample<A>) {
|
|
for i in 0..self.pairs.len() {
|
|
for i in 0..self.pairs.len() {
|
|
if self.pairs[i].range > range {
|
|
if self.pairs[i].range > range {
|
|
- self.pairs.insert(i, Pair { range: range, sample: sample });
|
|
|
|
|
|
+ self.pairs.insert(i, SampleOverRange { range: range, sample: sample });
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- self.pairs.push(Pair { range: range, sample: sample });
|
|
|
|
|
|
+ self.pairs.push(SampleOverRange { range: range, sample: sample });
|
|
}
|
|
}
|
|
|
|
|
|
/// Returns the `Audio` associated with the range within which the given hz and velocity exist.
|
|
/// Returns the `Audio` associated with the range within which the given hz and velocity exist.
|
|
///
|
|
///
|
|
/// TODO: This would probably be quicker with some sort of specialised RangeMap.
|
|
/// TODO: This would probably be quicker with some sort of specialised RangeMap.
|
|
- pub fn sample(&self, hz: pitch::Hz, vel: Velocity) -> Option<Sample<F>>
|
|
|
|
- where F: Clone,
|
|
|
|
- {
|
|
|
|
- for &Pair { ref range, ref sample } in &self.pairs {
|
|
|
|
|
|
+ pub fn sample(&self, hz: pitch::Hz, vel: Velocity) -> Option<Sample<A>> {
|
|
|
|
+ for &SampleOverRange { ref range, ref sample } in &self.pairs {
|
|
if range.hz.is_over(hz) && range.vel.is_over(vel) {
|
|
if range.hz.is_over(hz) && range.vel.is_over(vel) {
|
|
return Some(sample.clone());
|
|
return Some(sample.clone());
|
|
}
|
|
}
|
|
@@ -152,15 +162,34 @@ impl<F> Map<F>
|
|
|
|
|
|
|
|
|
|
#[cfg(feature="wav")]
|
|
#[cfg(feature="wav")]
|
|
-mod wav {
|
|
|
|
- extern crate hound;
|
|
|
|
-
|
|
|
|
|
|
+pub mod wav {
|
|
|
|
+ use hound;
|
|
use map;
|
|
use map;
|
|
use pitch;
|
|
use pitch;
|
|
use sample;
|
|
use sample;
|
|
use std;
|
|
use std;
|
|
|
|
|
|
- impl<F> map::Sample<F>
|
|
|
|
|
|
+ /// 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,
|
|
where F: sample::Frame,
|
|
F::Sample: sample::Duplex<f64> + hound::Sample,
|
|
F::Sample: sample::Duplex<f64> + hound::Sample,
|
|
Box<[F::Sample]>: sample::ToBoxedFrameSlice<F>,
|
|
Box<[F::Sample]>: sample::ToBoxedFrameSlice<F>,
|
|
@@ -168,180 +197,201 @@ mod wav {
|
|
|
|
|
|
/// Loads a `Sample` from the `.wav` file at the given `path`.
|
|
/// Loads a `Sample` from the `.wav` file at the given `path`.
|
|
///
|
|
///
|
|
- /// 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`.
|
|
|
|
- ///
|
|
|
|
- /// If a musical note cannot be determined automatically, a default `C1` will be used.
|
|
|
|
- ///
|
|
|
|
/// The PCM data retrieved from the file will be re-sampled upon loading (rather than at
|
|
/// 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.
|
|
/// 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_file<P>(path: P, target_sample_hz: f64) -> Result<Self, hound::Error>
|
|
where P: AsRef<std::path::Path>,
|
|
where P: AsRef<std::path::Path>,
|
|
{
|
|
{
|
|
use sample::Signal;
|
|
use sample::Signal;
|
|
|
|
|
|
let path = path.as_ref();
|
|
let path = path.as_ref();
|
|
-
|
|
|
|
let mut wav_reader = try!(hound::WavReader::open(path));
|
|
let mut wav_reader = try!(hound::WavReader::open(path));
|
|
- const DEFAULT_LETTER_OCTAVE: pitch::LetterOctave = pitch::LetterOctave(pitch::Letter::C, 1);
|
|
|
|
- let base_letter_octave = read_base_letter_octave(path).unwrap_or(DEFAULT_LETTER_OCTAVE);
|
|
|
|
|
|
|
|
- let base_hz = base_letter_octave.to_hz();
|
|
|
|
- let base_vel = 1.0;
|
|
|
|
|
|
+ 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();
|
|
|
|
+ for sample in wav_reader.samples() {
|
|
|
|
+ samples.push(try!(sample));
|
|
|
|
+ }
|
|
|
|
|
|
- let audio = {
|
|
|
|
- let spec = wav_reader.spec();
|
|
|
|
|
|
+ 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 /
|
|
// TODO: Return an error instead of panic!ing! OR do some sort of frame /
|
|
// channel layout conversion.
|
|
// 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();
|
|
|
|
- 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())
|
|
|
|
|
|
+ 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()),
|
|
};
|
|
};
|
|
|
|
|
|
- Ok(map::Sample::new(base_hz, base_vel, audio))
|
|
|
|
|
|
+ // 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> map::Map<F>
|
|
|
|
|
|
+ impl<F> Sample<F>
|
|
where F: sample::Frame,
|
|
where F: sample::Frame,
|
|
F::Sample: sample::Duplex<f64> + hound::Sample,
|
|
F::Sample: sample::Duplex<f64> + hound::Sample,
|
|
Box<[F::Sample]>: sample::ToBoxedFrameSlice<F>,
|
|
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`.
|
|
|
|
|
|
+ /// Loads a `Sample` from the `.wav` file at the given `path`.
|
|
///
|
|
///
|
|
/// If the `.wav` file has a musical note in the file name, that note's playback frequency in
|
|
/// 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`.
|
|
/// `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>
|
|
|
|
|
|
+ /// If a musical note cannot be determined automatically, a default `C1` will be used.
|
|
|
|
+ ///
|
|
|
|
+ /// 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>
|
|
where P: AsRef<std::path::Path>,
|
|
where P: AsRef<std::path::Path>,
|
|
{
|
|
{
|
|
- use sample::Signal;
|
|
|
|
- use std::cmp::Ordering;
|
|
|
|
-
|
|
|
|
let path = path.as_ref();
|
|
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);
|
|
|
|
|
|
+ const DEFAULT_LETTER_OCTAVE: pitch::LetterOctave = pitch::LetterOctave(pitch::Letter::C, 1);
|
|
|
|
+ let base_letter_octave = read_base_letter_octave(path).unwrap_or(DEFAULT_LETTER_OCTAVE);
|
|
|
|
+ let base_hz = base_letter_octave.to_hz();
|
|
|
|
+ let base_vel = 1.0;
|
|
|
|
|
|
- // 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));
|
|
|
|
- }
|
|
|
|
|
|
+ let audio = std::sync::Arc::new(try!(Audio::from_file(path, target_sample_hz)));
|
|
|
|
|
|
- Ok(Self::from_sequential_mappings(mappings))
|
|
|
|
|
|
+ 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.
|
|
///// Utility functions.
|
|
|
|
|
|
@@ -406,10 +456,3 @@ mod wav {
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
-
|
|
|
|
-
|
|
|
|
-// let sampler = sample_map!{
|
|
|
|
-// wav "amen_brother":
|
|
|
|
-// step { min: C 1, base: C 1, max: C 8 }
|
|
|
|
-// vel { min: 1.0, base: 1.0, max: 1.0 }
|
|
|
|
-// };
|
|
|