Browse Source

Merge pull request #1 from mitchmindtree/master

Sampler now usable. Includes wav feature for loading samples from WAV files and serde_serialization feature.
Mitchell Nordine 8 years ago
parent
commit
c13327ca2d
13 changed files with 2127 additions and 138 deletions
  1. 2 0
      .travis.yml
  2. 22 4
      Cargo.toml
  3. BIN
      assets/amen_brother.wav
  4. BIN
      assets/thumbpiano A#3.wav
  5. 39 20
      examples/wav.rs
  6. 6 0
      src/dynamic.rs
  7. 12 3
      src/lib.rs
  8. 433 36
      src/map.rs
  9. 184 0
      src/mode.rs
  10. 368 15
      src/sampler.rs
  11. 1061 0
      src/serde.rs
  12. 0 12
      src/source.rs
  13. 0 48
      src/voice.rs

+ 2 - 0
.travis.yml

@@ -9,5 +9,7 @@ os:
 
 script:
     - cargo build --verbose
+    - cargo build --no-default-features
     - cargo test --verbose
+    - cargo test --features="serde_serialization" --verbose
     - cargo doc --verbose

+ 22 - 4
Cargo.toml

@@ -4,11 +4,29 @@ version = "0.1.0"
 authors = ["mitchmindtree <mitchell.nordine@gmail.com>"]
 
 [dependencies]
-pitch_calc = "0.10.0"
-sample = "0.3.0"
-time_calc = "0.10.1"
+instrument = "0.1.0"
+pitch_calc = "0.11.0"
+time_calc = "0.11.0"
+sample = "0.6.0"
+
+# optional dependencies
+hound = { optional = true, version = "1.1.0" }
+serde = { optional = true, version = "0.7.0" }
+serde_json = { optional = true, version = "0.7.0" }
+find_folder = { optional = true, version = "0.3.0" }
+
+[features]
+default = ["wav"]
+wav = ["hound"]
+serde_serialization = [
+    "serde",
+    "serde_json",
+    "time_calc/serde_serialization",
+    "pitch_calc/serde_serialization",
+    "instrument/serde_serialization",
+    "find_folder",
+]
 
 [dev-dependencies]
 find_folder = "0.3.0"
-hound = "1.1.0"
 portaudio = "0.6.2"

BIN
assets/amen_brother.wav


BIN
assets/thumbpiano A#3.wav


+ 39 - 20
examples/wav.rs

@@ -1,6 +1,16 @@
-extern crate find_folder;
-extern crate hound;
-extern crate portaudio as pa;
+extern crate find_folder; // For easily finding the assets folder.
+extern crate portaudio as pa; // For audio I/O
+extern crate pitch_calc as pitch; // To work with musical notes.
+extern crate sample; // To convert portaudio sample buffers to frames.
+extern crate sampler;
+
+use sampler::Sampler;
+
+const CHANNELS: i32 = 2;
+const SAMPLE_RATE: f64 = 44_100.0;
+const FRAMES_PER_BUFFER: u32 = 64;
+const THUMB_PIANO: &'static str = "thumbpiano A#3.wav";
+
 
 fn main() {
     run().unwrap();
@@ -8,27 +18,34 @@ fn main() {
 
 fn run() -> Result<(), pa::Error> {
 
-    let mut samples = {
-        let assets = find_folder::Search::ParentsThenKids(5, 5).for_folder("assets").unwrap();
-        let sample_file = assets.join("amen_brother.wav");
-        let mut reader = hound::WavReader::open(&sample_file).unwrap();
-        reader.samples().collect::<Result<Vec<i16>, _>>().unwrap().into_iter()
-    };
+    // We'll create a sample map that maps a single sample to the entire note range.
+    let assets = find_folder::Search::ParentsThenKids(5, 5).for_folder("assets").unwrap();
+    let sample = sampler::Sample::from_wav_file(assets.join(THUMB_PIANO), SAMPLE_RATE).unwrap();
+    let sample_map = sampler::Map::from_single_sample(sample);
 
-    let pa = try!(pa::PortAudio::new());
+    // Create a polyphonic sampler.
+    let mut sampler = Sampler::poly((), sample_map).num_voices(4);
 
-    const CHANNELS: i32 = 1;
-    const SAMPLE_RATE: f64 = 44_100.0;
-    const FRAMES_PER_BUFFER: u32 = 64;
+    // Initialise PortAudio and create an output stream.
+    let pa = try!(pa::PortAudio::new());
+    let settings = 
+        try!(pa.default_output_stream_settings::<f32>(CHANNELS, SAMPLE_RATE, FRAMES_PER_BUFFER));
 
-    let settings = try!(pa.default_output_stream_settings(CHANNELS, SAMPLE_RATE, FRAMES_PER_BUFFER));
     let callback = move |pa::OutputStreamCallbackArgs { buffer, .. }| {
-        for sample in buffer.iter_mut() {
-            *sample = match samples.next() {
-                Some(s) => s,
-                None => return pa::Complete,
-            };
+        let buffer: &mut [[f32; CHANNELS as usize]] =
+            sample::slice::to_frame_slice_mut(buffer).unwrap();
+        sample::slice::equilibrium(buffer);
+
+        // If the sampler is not currently active, play a note.
+        if !sampler.is_active() {
+            let vel = 0.3;
+            sampler.note_on(pitch::LetterOctave(pitch::Letter::Ash, 3).to_hz(), vel);
+            sampler.note_on(pitch::LetterOctave(pitch::Letter::Dsh, 3).to_hz(), vel);
+            sampler.note_on(pitch::LetterOctave(pitch::Letter::Gsh, 1).to_hz(), vel);
         }
+
+        sampler.fill_slice(buffer, SAMPLE_RATE);
+
         pa::Continue
     };
 
@@ -36,7 +53,9 @@ fn run() -> Result<(), pa::Error> {
 
     try!(stream.start());
 
-    while let Ok(true) = stream.is_active() {}
+    while let Ok(true) = stream.is_active() {
+        std::thread::sleep(std::time::Duration::from_millis(16));
+    }
 
     try!(stream.stop());
     try!(stream.close());

+ 6 - 0
src/dynamic.rs

@@ -0,0 +1,6 @@
+use instrument;
+use sampler;
+
+/// An alias for a `Sampler` type that uses a dynamic instrument and note frequency mode.
+pub type Sampler<F> =
+    sampler::Sampler<instrument::mode::Dynamic, instrument::note_freq::DynamicGenerator, F>;

+ 12 - 3
src/lib.rs

@@ -1,10 +1,19 @@
+#[cfg(feature="wav")] extern crate hound;
+extern crate instrument;
 extern crate pitch_calc as pitch;
 extern crate sample;
 extern crate time_calc as time;
 
-mod map;
+pub use map::{Audio, Map, Sample};
+pub use mode::Mode;
+pub use sampler::{Frames, Sampler};
+
+pub mod dynamic;
+pub mod map;
+mod mode;
 mod sampler;
-mod source;
-mod voice;
+
+#[cfg(feature="serde_serialization")]
+mod serde;
 
 pub type Velocity = f32;

+ 433 - 36
src/map.rs

@@ -1,83 +1,480 @@
 use pitch;
+use sample;
 use std;
 use Velocity;
 
+
+/// A type that maps frequncy and velocity ranges to audio samples.
 #[derive(Clone, Debug, PartialEq)]
-pub struct SampleMap<S> {
-    pairs: Vec<Pair<S>>,
+pub struct Map<A> {
+    pub pairs: Vec<SampleOverRange<A>>,
 }
 
-/// Some slice of PCM samples that represents a single audio sample.
-#[derive(Clone, Debug, PartialEq)]
-pub struct Audio<S> {
-    data: std::sync::Arc<[S]>,
+/// 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 range paired with a specific sample.
+/// A performable `Sample` with some base playback Hz and Velocity.
 #[derive(Clone, Debug, PartialEq)]
-struct Pair<S> {
-    range: HzVelRange,
-    audio: Audio<S>,
+pub struct Sample<A> {
+    pub base_hz: pitch::Hz,
+    pub base_vel: Velocity,
+    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> Range<T> {
-    /// Is the given `T` greater than or equal to the `min` and smaller than the `max`.
-    pub fn is_over(&self, t: T) -> bool
-        where T: PartialOrd,
-    {
-        self.min <= t && t < self.max
+
+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<S> SampleMap<S> {
+impl Range<Velocity> {
+    /// Is the given velocity greater than or equal to the `min` and smaller than the `max`.
+    pub fn is_over(&self, vel: Velocity) -> bool {
+        self.min <= vel && vel <= self.max
+    }
+}
+
+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: A) -> Self {
+        Sample {
+            base_hz: base_hz,
+            base_vel: base_vel,
+            audio: audio,
+        }
+    }
 
-    /// Construct a `SampleMap` from a series of mappings, starting from (-C2, 1.0).
+}
+
+impl<A> Map<A>
+    where A: Audio,
+{
+
+    /// Construct an empty `Map`.
+    pub fn empty() -> Self {
+        Map { pairs: vec![] }
+    }
+
+    /// 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, Audio<S>)>,
+        where I: IntoIterator<Item=(pitch::Hz, Velocity, Sample<A>)>,
     {
-        let (mut last_hz, mut last_vel) = (pitch::Step(0.0).to_hz(), 1.0);
-        let pairs = mappings.into_iter().map(|(hz, vel, audio)| {
+        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 },
                 vel: Range { min: last_vel, max: vel },
             };
             last_hz = hz;
             last_vel = vel;
-            Pair { range: range, audio: audio }
+            SampleOverRange { range: range, sample: sample }
         }).collect();
-        SampleMap { pairs: pairs }
+        Map { pairs: pairs }
     }
 
-    /// Inserts a range -> audio mapping into the SampleMap.
-    pub fn insert(&mut self, range: HzVelRange, audio: Audio<S>) {
+    /// Creates a `Map` with a single sample mapped to the entire Hz 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) },
+            vel: Range { min: 0.0, max: 1.0 },
+        };
+        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<A>) {
         for i in 0..self.pairs.len() {
             if self.pairs[i].range > range {
-                self.pairs.insert(i, Pair { range: range, audio: audio });
+                self.pairs.insert(i, SampleOverRange { range: range, sample: sample });
                 return;
             }
         }
-        self.pairs.push(Pair { range: range, audio: audio });
+        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<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());
+            }
+        }
+        None
     }
 
 }
 
 
-// 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 }
-// };
+#[cfg(feature="wav")]
+pub mod wav {
+    use hound;
+    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>,
+    {
+
+        /// 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,
+              F::Sample: sample::Duplex<f64> + sample::Duplex<i32>,
+              Box<[F::Sample]>: sample::ToBoxedFrameSlice<F>,
+    {
+
+        /// 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>
+            where P: AsRef<std::path::Path>,
+        {
+            let path = path.as_ref();
+
+            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 audio = std::sync::Arc::new(try!(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> {
+        use pitch::Letter::*;
+        use std::ascii::AsciiExt;
+
+        let s = path.to_str().map_or("".into(), |s| s.to_ascii_lowercase());
+
+        // Check to see if the path contains a note for the given `letter` for any octave
+        // between -8 and 24. If so, return the `LetterOctave`.
+        let contains_letter = |letter: &str| -> Option<pitch::LetterOctave> {
+            for i in -8i8..24 {
+                let pattern = format!("{}{}", letter, i);
+                if s.contains(&pattern) {
+                    let letter = match letter {
+                        "c" => C,
+                        "c#" | "csh" => Csh,
+                        "d" => D,
+                        "d#" | "dsh" => Dsh,
+                        "e" => E,
+                        "f" => F,
+                        "f#" | "fsh" => Fsh,
+                        "g" => G,
+                        "g#" | "gsh" => Gsh,
+                        "a" => A,
+                        "a#" | "ash" => Ash,
+                        "b" => B,
+                        _ => unreachable!(),
+                    };
+                    return Some(pitch::LetterOctave(letter, i as pitch::Octave));
+                }
+            }
+            None
+        };
+
+        let list = [
+            "c", "c#", "csh", "d", "d#", "dsh", "e", "f", "f#", "fsh", "g", "g#", "gsh",
+            "a", "a#", "ash", "b",
+        ];
+
+        for letter in &list[..] {
+            if let Some(letter_octave) = contains_letter(letter) {
+                return Some(letter_octave);
+            }
+        }
+
+        None
+    }
+
+}

+ 184 - 0
src/mode.rs

@@ -0,0 +1,184 @@
+pub use instrument::mode::{Mono, MonoKind, Poly, Dynamic};
+use map::{self, Map};
+use pitch;
+use sample::Frame;
+use sampler::PlayingSample;
+use Velocity;
+
+/// The "mode" with which the Sampler will handle notes.
+pub trait Mode {
+
+    /// Handle a `note_on` event.
+    ///
+    /// Is called immediately following `instrument::Mode::note_on`.
+    fn note_on<A>(&self,
+                  note_hz: pitch::Hz,
+                  note_velocity: Velocity,
+                  map: &Map<A>,
+                  voices: &mut [Option<PlayingSample<A>>])
+        where A: map::Audio;
+
+    /// Handle a `note_off` event.
+    fn note_off<A>(&self,
+                   note_hz: pitch::Hz,
+                   map: &Map<A>,
+                   voices: &mut [Option<PlayingSample<A>>])
+        where A: map::Audio;
+}
+
+
+// Helper function for constructing a `PlayingSample`.
+fn play_sample<A>(hz: pitch::Hz, vel: Velocity, map: &Map<A>) -> Option<PlayingSample<A>>
+    where A: map::Audio,
+{
+    play_sample_from_playhead_idx(0, hz, vel, map)
+}
+
+// Helper function for constructing a `PlayingSample` with a given playhead index.
+fn play_sample_from_playhead_idx<A>(idx: usize,
+                                    hz: pitch::Hz,
+                                    vel: Velocity,
+                                    map: &Map<A>) -> Option<PlayingSample<A>>
+    where A: map::Audio,
+{
+    map.sample(hz, vel).map(|sample| PlayingSample::from_playhead_idx(idx, hz, vel, sample))
+}
+
+
+impl Mode for Mono {
+
+    fn note_on<A>(&self,
+                  note_hz: pitch::Hz,
+                  note_vel: Velocity,
+                  map: &Map<A>,
+                  voices: &mut [Option<PlayingSample<A>>])
+        where A: map::Audio,
+    {
+        let sample = match play_sample(note_hz, note_vel, map) {
+            Some(sample) => sample,
+            None => return,
+        };
+        for voice in voices {
+            *voice = Some(sample.clone());
+        }
+    }
+
+    fn note_off<A>(&self,
+                   note_hz: pitch::Hz,
+                   map: &Map<A>,
+                   voices: &mut [Option<PlayingSample<A>>])
+        where A: map::Audio,
+    {
+        let Mono(kind, ref note_stack) = *self;
+
+        let should_reset = voices.iter().next()
+            .and_then(|v| v.as_ref().map(|v| v.note_on_hz == note_hz))
+            .unwrap_or(false);
+
+        if should_reset {
+            let maybe_fallback_note_hz = note_stack.iter().last();
+            for voice in voices {
+                // If there's some fallback note in the note stack, play it.
+                if let Some(ref mut playing_sample) = *voice {
+                    if let Some(&hz) = maybe_fallback_note_hz {
+                        let hz = pitch::Hz(hz.into());
+                        let idx = match kind {
+                            MonoKind::Legato => playing_sample.rate_converter.source().idx,
+                            MonoKind::Retrigger => 0,
+                        };
+                        let vel = playing_sample.note_on_vel;
+                        if let Some(sample) = play_sample_from_playhead_idx(idx, hz, vel, map) {
+                            *playing_sample = sample;
+                            continue;
+                        }
+                    }
+                }
+                // Otherwise, set the voices to `None`.
+                *voice = None;
+            }
+        }
+    }
+
+}
+
+impl Mode for Poly {
+
+    fn note_on<A>(&self,
+                  note_hz: pitch::Hz,
+                  note_vel: Velocity,
+                  map: &Map<A>,
+                  voices: &mut [Option<PlayingSample<A>>])
+        where A: map::Audio,
+    {
+        let sample = match play_sample(note_hz, note_vel, map) {
+            Some(sample) => sample,
+            None => return,
+        };
+
+        // Find the right voice to play the note.
+        let mut oldest = None;
+        let mut max_sample_count = 0;
+        for voice in voices.iter_mut() {
+            match *voice {
+                None => {
+                    *voice = Some(sample);
+                    return;
+                },
+                Some(ref mut playing_sample) => {
+                    let playhead = playing_sample.rate_converter.source().idx;
+                    if playhead >= max_sample_count {
+                        max_sample_count = playhead;
+                        oldest = Some(playing_sample);
+                    }
+                },
+            }
+        }
+        if let Some(voice) = oldest {
+            *voice = sample;
+        }
+    }
+
+    fn note_off<A>(&self,
+                   note_hz: pitch::Hz,
+                   _map: &Map<A>,
+                   voices: &mut [Option<PlayingSample<A>>])
+        where A: map::Audio,
+    {
+        for voice in voices {
+            let should_reset = voice.as_ref().map(|v| v.note_on_hz == note_hz).unwrap_or(false);
+            if should_reset {
+                *voice = None;
+            }
+        }
+    }
+
+}
+
+impl Mode for Dynamic {
+
+    fn note_on<A>(&self,
+                  note_hz: pitch::Hz,
+                  note_vel: Velocity,
+                  map: &Map<A>,
+                  voices: &mut [Option<PlayingSample<A>>])
+        where A: map::Audio,
+    {
+        match *self {
+            Dynamic::Mono(ref mono) => mono.note_on(note_hz, note_vel, map, voices),
+            Dynamic::Poly(ref poly) => poly.note_on(note_hz, note_vel, map, voices),
+        }
+    }
+
+    fn note_off<A>(&self,
+                   note_hz: pitch::Hz,
+                   map: &Map<A>,
+                   voices: &mut [Option<PlayingSample<A>>])
+        where A: map::Audio,
+    {
+        match *self {
+            Dynamic::Mono(ref mono) => mono.note_off(note_hz, map, voices),
+            Dynamic::Poly(ref poly) => poly.note_off(note_hz, map, voices),
+        }
+    }
+
+}

+ 368 - 15
src/sampler.rs

@@ -1,40 +1,393 @@
-use map::SampleMap;
+use instrument::{self, Instrument};
+use map::{self, Map};
 use pitch;
-use sample::Sample as PcmSample;
+use sample::{self, Frame, Sample as PcmSample};
+use std;
+use time;
 use Velocity;
-use voice::Voice;
 
-#[derive(Clone, Debug, PartialEq)]
-pub struct Sampler<S> {
-    map: SampleMap<S>,
-    voices: Vec<Voice>,
+/// A Sampler instrument.
+#[derive(Clone, Debug)]
+pub struct Sampler<M, NFG, A>
+    where NFG: instrument::NoteFreqGenerator,
+          A: map::Audio,
+{
+    pub instrument: Instrument<M, NFG>,
+    pub map: Map<A>,
+    voices: Voices<A>,
 }
 
-impl<S> Sampler<S> {
+/// Samples that are currently active along with the `Hz` with which they were triggered.
+///
+/// A new pair is pushed on each `note_on`, and pairs are removed on their associated `note_off`.
+///
+/// In `Mono` mode, the sampler always fills the buffer using the last pair on the stack.
+///
+/// In `Poly` mode, each pair is mapped directly to each of the `Instrument`'s `voices` via their
+/// `Vec` indices.
+#[derive(Clone)]
+pub struct Voices<A>
+    where A: map::Audio,
+{
+    map: Vec<Option<PlayingSample<A>>>,
+}
+
+/// A sample that is currently being played back.
+#[derive(Clone)]
+pub struct PlayingSample<A>
+    where A: map::Audio,
+{
+    /// The pitch in hz at which the `note_on` was triggered.
+    pub note_on_hz: pitch::Hz,
+    pub note_on_vel: Velocity,
+    base_hz: pitch::Hz,
+    base_vel: Velocity,
+    /// Rate-adjustable interpolation of audio.
+    pub rate_converter: sample::rate::Converter<Playhead<A>>,
+}
+
+/// An owned iterator that wraps an audio file but does not 
+#[derive(Clone)]
+pub struct Playhead<A>
+    where A: map::Audio,
+{
+    /// The position of the playhead over the `Sample`.
+    pub idx: usize,
+    audio: A,
+}
+
+/// An iterator yielding one frame from the `Sampler` at a time.
+pub struct Frames<'a, A: 'a, NF: 'a>
+    where A: map::Audio,
+{
+    voices: &'a mut Voices<A>,
+    instrument_frames: instrument::Frames<'a, NF>,
+}
+
+
+impl<A> std::fmt::Debug for Voices<A>
+    where A: map::Audio,
+{
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
+        write!(f, "Voices {{ num: {:?} }}", self.map.len())
+    }
+}
+
+impl<NFG, A> Sampler<instrument::mode::Mono, NFG, A>
+    where NFG: instrument::NoteFreqGenerator,
+          A: map::Audio,
+{
+    /// Construct a `Sampler` with a `Mono::Legato` playback mode.
+    pub fn legato(nfg: NFG, map: Map<A>) -> Self {
+        Self::new(instrument::mode::Mono::legato(), nfg, map)
+    }
+}
+
+impl<NFG, A> Sampler<instrument::mode::Mono, NFG, A>
+    where NFG: instrument::NoteFreqGenerator,
+          A: map::Audio,
+{
+    /// Construct a `Sampler` with a `Mono::Retrigger` playback mode.
+    pub fn retrigger(nfg: NFG, map: Map<A>) -> Self {
+        Self::new(instrument::mode::Mono::retrigger(), nfg, map)
+    }
+}
+
+impl<NFG, A> Sampler<instrument::mode::Poly, NFG, A>
+    where NFG: instrument::NoteFreqGenerator,
+          A: map::Audio,
+{
+    /// Construct a `Sampler` with a `Poly` playback mode.
+    pub fn poly(nfg: NFG, map: Map<A>) -> Self {
+        Self::new(instrument::mode::Poly, nfg, map)
+    }
+}
+
+
+impl<M, NFG, A> Sampler<M, NFG, A>
+    where NFG: instrument::NoteFreqGenerator,
+          A: map::Audio,
+{
 
     /// Construct a new `Sampler`.
-    pub fn new(map: SampleMap<S>) -> Self {
+    pub fn new(mode: M, note_freq_gen: NFG, map: Map<A>) -> Self {
+        let instrument = Instrument::new(mode, note_freq_gen);
+        let n_voices = instrument.voices.len();
         Sampler {
             map: map,
-            voices: vec![],
+            voices: Voices { map: vec![None; n_voices] },
+            instrument: instrument,
         }
     }
 
-    /// Begin playback of a note.
+    /// Map the `Instrument` to a new `Instrument` in place.
     ///
-    /// `Sampler` will try to use a free `Voice` to do this. If no `Voice`s are free, the one
-    /// playing the oldest note will be chosen to play the new note instead.
+    /// This is useful for providing wrapper builder methods for the Synth.
+    #[inline]
+    pub fn map_instrument<Map, NewM, NewNFG>(self, f: Map) -> Sampler<NewM, NewNFG, A>
+        where Map: FnOnce(Instrument<M, NFG>) -> Instrument<NewM, NewNFG>,
+              NewNFG: instrument::NoteFreqGenerator,
+    {
+        let Sampler {
+            map,
+            voices,
+            instrument,
+        } = self;
+
+        Sampler {
+            map: map,
+            voices: voices,
+            instrument: f(instrument),
+        }
+    }
+
+    /// Build the `Sampler` with the given number of voices.
+    pub fn num_voices(mut self, n: usize) -> Self {
+        self.set_num_voices(n);
+        self
+    }
+
+    /// Return the number of voices for use within the `Sampler`.
+    pub fn voice_count(&self) -> usize {
+        self.voices.map.len()
+    }
+
+    /// Detune the `note_on` hz by the given amount.
+    pub fn detune(self, detune: f32) -> Self {
+        self.map_instrument(|inst| inst.detune(detune))
+    }
+
+    /// Set the attack.
+    pub fn attack<Attack>(self, attack: Attack) -> Self
+        where Attack: Into<time::Ms>,
+    {
+        self.map_instrument(|inst| inst.attack(attack))
+    }
+
+    /// Set the release.
+    pub fn release<Release>(self, release: Release) -> Self
+        where Release: Into<time::Ms>,
+    {
+        self.map_instrument(|inst| inst.release(release))
+    }
+
+    /// Set the number of voices to use for 
+    pub fn set_num_voices(&mut self, n: usize) {
+        self.instrument.set_num_voices(n);
+        self.voices.map.resize(n, None);
+    }
+
+    /// Begin playback of a note.
     #[inline]
     pub fn note_on<T>(&mut self, note_hz: T, note_vel: Velocity)
-        where T: Into<pitch::Hz>
+        where M: instrument::Mode + super::Mode,
+              T: Into<pitch::Hz>
     {
+        let Sampler { ref mut instrument, ref mut voices, ref map, .. } = *self;
+        let hz = note_hz.into();
+        instrument.note_on(hz, note_vel);
+        super::Mode::note_on(&mut instrument.mode, hz, note_vel, map, &mut voices.map);
     }
 
     /// Stop playback of the note that was triggered with the matching frequency.
     #[inline]
     pub fn note_off<T>(&mut self, note_hz: T)
-        where T: Into<pitch::Hz>
+        where M: instrument::Mode + super::Mode,
+              T: Into<pitch::Hz>
+    {
+        let Sampler { ref mut instrument, ref mut voices, ref map, .. } = *self;
+        let hz = note_hz.into();
+        instrument.note_off(hz);
+        super::Mode::note_off(&mut instrument.mode, hz, map, &mut voices.map);
+    }
+
+    /// Stop playback and clear the current notes.
+    #[inline]
+    pub fn stop(&mut self)
+        where M: instrument::Mode,
     {
+        let Sampler { ref mut instrument, ref mut voices, .. } = *self;
+        instrument.stop();
+        voices.map.clear();
     }
 
+    /// Produces an iterator that yields `Frame`s of audio data.
+    pub fn frames(&mut self, sample_hz: f64) -> Frames<A, NFG::NoteFreq>
+        where A: map::Audio,
+              <A::Frame as Frame>::Sample: sample::Duplex<f64>,
+              <<A::Frame as Frame>::Sample as PcmSample>::Float: sample::FromSample<f32>,
+    {
+        Frames {
+            voices: &mut self.voices,
+            instrument_frames: self.instrument.frames(sample_hz),
+        }
+    }
+
+    /// Returns whether or not the `Sampler` is currently playing any notes.
+    pub fn is_active(&self) -> bool {
+        for voice in &self.voices.map {
+            if voice.is_some() {
+                return true;
+            }
+        }
+        false
+    }
+
+    /// Fills the given slice of frames with the `Sampler::frames` iterator.
+    pub fn fill_slice<F>(&mut self, output: &mut [F], sample_hz: f64)
+        where F: Frame,
+              F::Sample: sample::Duplex<f64>,
+              <F::Sample as PcmSample>::Float: sample::FromSample<f32>,
+              A: map::Audio<Frame=F>,
+    {
+        let mut frames = self.frames(sample_hz);
+        sample::slice::map_in_place(output, |f| {
+            f.zip_map(frames.next_frame(), |a, b| {
+                a.add_amp(b.to_sample::<<F::Sample as PcmSample>::Signed>())
+            })
+        });
+    }
+
+}
+
+
+#[cfg(feature="serde_serialization")]
+pub mod private {
+    use instrument::{self, Instrument};
+    use map::{self, Map};
+
+    /// A private constructor for use within serde.rs.
+    pub fn new<M, NFG, A>(instrument: Instrument<M, NFG>,
+                          map: Map<A>,
+                          num_voices: usize) -> super::Sampler<M, NFG, A>
+        where NFG: instrument::NoteFreqGenerator,
+              A: map::Audio,
+    {
+        super::Sampler {
+            instrument: instrument,
+            map: map,
+            voices: super::Voices { map: vec![None; num_voices] },
+        }
+    }
+}
+
+
+impl<A> PlayingSample<A>
+    where A: map::Audio,
+{
+
+    /// Construct a new `PlayingSample` from the given note hz, velocity and the associated
+    /// `Sample` from the `Map`.
+    pub fn new(hz: pitch::Hz, vel: Velocity, sample: map::Sample<A>) -> Self {
+        Self::from_playhead_idx(0, hz, vel, sample)
+    }
+
+    /// Construct a new `PlayingSample` from the given note hz, velocity and the associated
+    /// `Sample` from the `Map`.
+    ///
+    /// The given `Sample`'s audio will begin playing from the given `idx`.
+    pub fn from_playhead_idx(idx: usize,
+                             hz: pitch::Hz,
+                             vel: Velocity,
+                             sample: map::Sample<A>) -> Self
+    {
+        let map::Sample { base_hz, base_vel, audio } = sample;
+        let playhead = Playhead::from_idx(idx, audio);
+        let rate_converter = sample::rate::Converter::scale_playback_hz(playhead, 1.0);
+        PlayingSample {
+            note_on_hz: hz,
+            note_on_vel: vel,
+            base_hz: base_hz,
+            base_vel: base_vel,
+            rate_converter: rate_converter,
+        }
+    }
+
+}
+
+
+impl<A> Playhead<A>
+    where A: map::Audio,
+{
+    /// Wrap the given `Audio` with a `Playhead` starting from 0.
+    pub fn new(audio: A) -> Self {
+        Self::from_idx(0, audio)
+    }
+
+    /// Wrap the given `Audio` with a `Playhead` starting from the given playhead index.
+    pub fn from_idx(idx: usize, audio: A) -> Self {
+        Playhead {
+            idx: idx,
+            audio: audio,
+        }
+    }
+}
+
+impl<A> Iterator for Playhead<A>
+    where A: map::Audio,
+{
+    type Item = A::Frame;
+    #[inline]
+    fn next(&mut self) -> Option<Self::Item> {
+        let idx = self.idx;
+        self.idx += 1;
+        map::Audio::data(&self.audio).get(idx).map(|&f| f)
+    }
+}
+
+
+impl<'a, A, NF> Frames<'a, A, NF>
+    where A: map::Audio,
+          <A::Frame as Frame>::Sample: sample::Duplex<f64>,
+          <<A::Frame as Frame>::Sample as PcmSample>::Float: sample::FromSample<f32>,
+          NF: instrument::NoteFreq,
+{
+    /// Yields the next audio `Frame`.
+    #[inline]
+    pub fn next_frame(&mut self) -> A::Frame {
+        let Frames {
+            ref mut voices,
+            ref mut instrument_frames,
+        } = *self;
+
+        let frame_per_voice = instrument_frames.next_frame_per_voice();
+        voices.map.iter_mut()
+            .zip(frame_per_voice)
+            .filter_map(|(v, amp_hz)| amp_hz.map(|amp_hz| (v, amp_hz)))
+            .fold(<A::Frame as Frame>::equilibrium(), |frame, (voice, (amp, hz))| {
+                match *voice {
+                    None => return frame,
+                    Some(ref mut voice) => {
+                        let playback_hz_scale = hz / voice.base_hz.hz();
+                        voice.rate_converter.set_playback_hz_scale(playback_hz_scale as f64);
+                        match voice.rate_converter.next_frame() {
+                            Some(wave) => {
+                                let amp = amp * voice.base_vel;
+                                let scaled = wave.scale_amp(amp.to_sample());
+                                return frame.zip_map(scaled, |f, s| {
+                                    f.add_amp(s.to_sample::<<<A::Frame as Frame>::Sample as PcmSample>::Signed>())
+                                });
+                            },
+                            None => (),
+                        }
+                    },
+                }
+
+                // If we made it this far, the voice has finished playback of the note.
+                *voice = None;
+                frame
+            })
+    }
+}
+
+impl<'a, A, NF> Iterator for Frames<'a, A, NF>
+    where A: map::Audio,
+          <A::Frame as Frame>::Sample: sample::Duplex<f64>,
+          <<A::Frame as Frame>::Sample as PcmSample>::Float: sample::FromSample<f32>,
+          NF: instrument::NoteFreq,
+{
+    type Item = A::Frame;
+    fn next(&mut self) -> Option<Self::Item> {
+        Some(self.next_frame())
+    }
 }

File diff suppressed because it is too large
+ 1061 - 0
src/serde.rs


+ 0 - 12
src/source.rs

@@ -1,12 +0,0 @@
-
-/// The source of samples, which may be either dynamic or static.
-pub trait Source {}
-
-pub struct Dynamic;
-
-pub struct Static;
-
-
-pub trait PcmSampleSource {
-    fn samples<I, S>(&self) -> I where I: Iterator<Item=S>;
-}

+ 0 - 48
src/voice.rs

@@ -1,48 +0,0 @@
-use {pitch, time, Velocity};
-
-pub type Playhead = time::Samples;
-
-/// The current state of the Voice's note playback.
-#[derive(Copy, Clone, Debug)]
-pub enum NoteState {
-    /// The note is current playing.
-    Playing,
-    /// The note has been released and is fading out.
-    Released(Playhead),
-}
-
-/// A single monophonic voice of a `Sampler`.
-#[derive(Clone, Debug, PartialEq)]
-pub struct Voice {
-    note: Option<(NoteState, pitch::Hz, Velocity)>,
-    playhead: Playhead,
-}
-
-impl Voice {
-
-    /// Construct a new `Voice`.
-    pub fn new() -> Self {
-        Voice {
-            note: None,
-            playhead: 0,
-        }
-    }
-
-    /// Trigger playback with the given note.
-    #[inline]
-    pub fn note_on(&mut self, hz: NoteHz, vel: NoteVelocity) {
-        self.maybe_note = Some((NoteState::Playing, hz, vel));
-    }
-
-    /// Release playback of the current note if there is one.
-    #[inline]
-    pub fn note_off(&mut self) {
-        if let Some(&mut(ref mut state, _, _)) = self.note.as_mut() {
-            *state = NoteState::Released(0);
-        }
-    }
-
-    pub fn fill_buffer(&mut self, buffer: &mut [S]) {
-    }
-
-}