瀏覽代碼

Remove Audio type from map module in favour of generic Audio trait. Add wav specific Audio type to wav module. These changes should allow for serialization of sampler audio.

mitchmindtree 8 年之前
父節點
當前提交
3da95d84b2
共有 1 個文件被更改,包括 239 次插入196 次删除
  1. 239 196
      src/map.rs

+ 239 - 196
src/map.rs

@@ -6,50 +6,71 @@ use Velocity;
 
 /// A type that maps frequncy and velocity ranges to audio samples.
 #[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.
 #[derive(Clone, Debug, PartialEq)]
-pub struct Sample<F> {
+pub struct Sample<A> {
     pub base_hz: pitch::Hz,
     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.
 #[derive(Clone, Debug, PartialEq, PartialOrd)]
 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`.
 #[derive(Clone, Debug, PartialEq, PartialOrd)]
 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> {
     /// 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 {
@@ -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.
-    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 {
             base_hz: base_hz,
             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`.
@@ -97,7 +109,7 @@ impl<F> Map<F>
 
     /// 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<F>)>,
+        where I: IntoIterator<Item=(pitch::Hz, Velocity, Sample<A>)>,
     {
         const MIN_HZ: pitch::Hz = pitch::Hz(0.0);
         let (mut last_hz, mut last_vel) = (MIN_HZ, 1.0);
@@ -108,39 +120,37 @@ impl<F> Map<F>
             };
             last_hz = hz;
             last_vel = vel;
-            Pair { range: range, sample: sample }
+            SampleOverRange { range: range, sample: sample }
         }).collect();
         Map { pairs: pairs }
     }
 
     /// 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 {
             hz: Range { min: pitch::Hz(0.0), max: pitch::Hz(std::f32::MAX) },
             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 }
     }
 
     /// 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() {
             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;
             }
         }
-        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.
     ///
     /// 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) {
                 return Some(sample.clone());
             }
@@ -152,15 +162,34 @@ impl<F> Map<F>
 
 
 #[cfg(feature="wav")]
-mod wav {
-    extern crate hound;
-
+pub mod wav {
+    use hound;
     use map;
     use pitch;
     use sample;
     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,
               F::Sample: sample::Duplex<f64> + hound::Sample,
               Box<[F::Sample]>: sample::ToBoxedFrameSlice<F>,
@@ -168,180 +197,201 @@ mod wav {
 
         /// 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
         /// 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>,
         {
             use sample::Signal;
 
             let path = path.as_ref();
-
             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 /
                 // 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,
               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`.
+        /// 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`.
         ///
-        /// 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>,
         {
-            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);
+            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.
     
@@ -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 }
-// };