Bläddra i källkod

Merge pull request #4 from mitchmindtree/fix_note_handling

Add dynamic constructor helper functions. Fix note handling issues. Re-export instrument and sample crates.
mitchmindtree 8 år sedan
förälder
incheckning
ec34eda0c2
6 ändrade filer med 138 tillägg och 68 borttagningar
  1. 6 0
      Cargo.toml
  2. 35 2
      src/dynamic.rs
  3. 2 2
      src/lib.rs
  4. 12 0
      src/map.rs
  5. 59 45
      src/mode.rs
  6. 24 19
      src/sampler.rs

+ 6 - 0
Cargo.toml

@@ -2,6 +2,12 @@
 name = "sampler"
 version = "0.1.0"
 authors = ["mitchmindtree <mitchell.nordine@gmail.com>"]
+description = "A polyphonic audio sampler instrument that supports unique sample mappings across both frequency and velocity ranges."
+readme = "README.md"
+keywords = ["sample", "dsp", "audio", "music", "instrument"]
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/RustAudio/sampler.git"
+homepage = "https://github.com/RustAudio/sampler"
 
 [dependencies]
 instrument = "0.1.0"

+ 35 - 2
src/dynamic.rs

@@ -1,6 +1,39 @@
 use instrument;
+use map;
 use sampler;
 
+/// An alias for a dynamic `Mode` type.
+pub type Mode = instrument::mode::Dynamic;
+
+/// An alias for a dynamic `NoteFreqGenerator` type.
+pub type NoteFreqGenerator = instrument::note_freq::DynamicGenerator;
+
 /// 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>;
+pub type Sampler<A> = sampler::Sampler<Mode, NoteFreqGenerator, A>;
+
+impl<A> Sampler<A>
+    where A: map::Audio,
+{
+
+    /// Construct a dynamic `Sampler`.
+    pub fn dynamic(mode: instrument::mode::Dynamic, map: map::Map<A>) -> Self {
+        let nfg = instrument::note_freq::DynamicGenerator::Constant;
+        Self::new(mode, nfg, map)
+    }
+
+    /// Construct a dynamic `Sampler` initialised with a `Mono::Legato` playback mode.
+    pub fn dynamic_legato(map: map::Map<A>) -> Self {
+        Self::dynamic(instrument::mode::Dynamic::legato(), map)
+    }
+
+    /// Construct a dynamic `Sampler` initialised with a `Mono::Retrigger` playback mode.
+    pub fn dynamic_retrigger(map: map::Map<A>) -> Self {
+        Self::dynamic(instrument::mode::Dynamic::retrigger(), map)
+    }
+
+    /// Construct a dynamic `Sampler` initialised with a `Poly` playback mode.
+    pub fn dynamic_poly(map: map::Map<A>) -> Self {
+        Self::dynamic(instrument::mode::Dynamic::poly(), map)
+    }
+
+}

+ 2 - 2
src/lib.rs

@@ -1,7 +1,7 @@
 #[cfg(feature="wav")] extern crate hound;
-extern crate instrument;
+pub extern crate instrument;
+pub extern crate sample;
 extern crate pitch_calc as pitch;
-extern crate sample;
 extern crate time_calc as time;
 
 pub use map::{Audio, Map, Sample};

+ 12 - 0
src/map.rs

@@ -86,6 +86,18 @@ impl<A> Sample<A> {
         }
     }
 
+    /// Maps the `Sample` with some `Audio` type `A` to a `Sample` with some `Audio` type `B`.
+    pub fn map_audio<F, B>(self, map: F) -> Sample<B>
+        where F: FnOnce(A) -> B,
+    {
+        let Sample { base_hz, base_vel, audio } = self;
+        Sample {
+            base_hz: base_hz,
+            base_vel: base_vel,
+            audio: map(audio),
+        }
+    }
+
 }
 
 impl<A> Map<A>

+ 59 - 45
src/mode.rs

@@ -1,8 +1,10 @@
 pub use instrument::mode::{Mono, MonoKind, Poly, Dynamic};
+use instrument;
 use map::{self, Map};
 use pitch;
 use sample::Frame;
 use sampler::PlayingSample;
+use std;
 use Velocity;
 
 /// The "mode" with which the Sampler will handle notes.
@@ -54,12 +56,31 @@ impl Mode for Mono {
                   voices: &mut [Option<PlayingSample<A>>])
         where A: map::Audio,
     {
-        let sample = match play_sample(note_hz, note_vel, map) {
-            Some(sample) => sample,
-            None => return,
+        let Mono(ref kind, ref note_stack) = *self;
+
+        // If we're in `Legato` mode, begin the note from the same index as the previous note's
+        // current state if there is one.
+        let sample = if let instrument::mode::MonoKind::Legato = *kind {
+            note_stack.last()
+                .and_then(|&last_hz| {
+                    voices.iter()
+                        .filter_map(|v| v.as_ref())
+                        .find(|sample| instrument::mode::does_hz_match(sample.note_on_hz.hz(), last_hz))
+                        .and_then(|sample| {
+                            let idx = sample.rate_converter.source().idx;
+                            play_sample_from_playhead_idx(idx, note_hz, note_vel, map)
+                        })
+                })
+                .or_else(|| play_sample(note_hz, note_vel, map))
+        // Otherwise, we're in `Retrigger` mode, so start from the beginning of the sample.
+        } else {
+            play_sample(note_hz, note_vel, map)
         };
-        for voice in voices {
-            *voice = Some(sample.clone());
+
+        if let Some(sample) = sample {
+            for voice in voices {
+                *voice = Some(sample.clone());
+            }
         }
     }
 
@@ -71,32 +92,33 @@ impl Mode for Mono {
     {
         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);
+        let should_reset = voices.iter()
+            .filter_map(|v| v.as_ref())
+            .any(|v| instrument::mode::does_hz_match(v.note_on_hz.hz(), note_hz.hz()));
+
+        if !should_reset {
+            return;
+        }
 
-        if should_reset {
-            let maybe_fallback_note_hz = note_stack.iter().last();
+        // If there is some note to fall back to, do so.
+        if let Some(&fallback_note_hz) = note_stack.last() {
+            let hz = fallback_note_hz.into();
             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;
-                        }
+                    let idx = match kind {
+                        MonoKind::Retrigger => 0,
+                        MonoKind::Legato => playing_sample.rate_converter.source().idx,
+                    };
+                    let vel = playing_sample.note_on_vel;
+                    if let Some(sample) = play_sample_from_playhead_idx(idx, hz, vel, map) {
+                        *playing_sample = sample;
                     }
                 }
-                // Otherwise, set the voices to `None`.
-                *voice = None;
             }
         }
+
+        // No need to manually set voices to `None` as this will be done when frames yielded by
+        // `instrument` run out.
     }
 
 }
@@ -117,20 +139,16 @@ impl Mode for Poly {
 
         // Find the right voice to play the note.
         let mut oldest = None;
-        let mut max_sample_count = 0;
+        let mut oldest_time_of_note_on = std::time::Instant::now();
         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 None = *voice {
+                *voice = Some(sample);
+                return;
+            }
+            let time_of_note_on = voice.as_ref().unwrap().time_of_note_on;
+            if time_of_note_on < oldest_time_of_note_on {
+                oldest_time_of_note_on = time_of_note_on;
+                oldest = voice.as_mut();
             }
         }
         if let Some(voice) = oldest {
@@ -139,17 +157,13 @@ impl Mode for Poly {
     }
 
     fn note_off<A>(&self,
-                   note_hz: pitch::Hz,
+                   _note_hz: pitch::Hz,
                    _map: &Map<A>,
-                   voices: &mut [Option<PlayingSample<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;
-            }
-        }
+        // No need to do anything here as voices will be set to `None` when frames yielded by
+        // `instrument` run out.
     }
 
 }

+ 24 - 19
src/sampler.rs

@@ -44,6 +44,8 @@ pub struct PlayingSample<A>
     base_vel: Velocity,
     /// Rate-adjustable interpolation of audio.
     pub rate_converter: sample::rate::Converter<Playhead<A>>,
+    /// The time at which the `PlayingSample` was constructed.
+    pub time_of_note_on: std::time::Instant,
 }
 
 /// An owned iterator that wraps an audio file but does not 
@@ -197,8 +199,8 @@ impl<M, NFG, A> Sampler<M, NFG, A>
     {
         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);
+        instrument.note_off(hz);
     }
 
     /// Stop playback and clear the current notes.
@@ -300,6 +302,7 @@ impl<A> PlayingSample<A>
             base_hz: base_hz,
             base_vel: base_vel,
             rate_converter: rate_converter,
+            time_of_note_on: std::time::Instant::now(),
         }
     }
 
@@ -351,26 +354,28 @@ impl<'a, A, NF> Frames<'a, A, NF>
         } = *self;
 
         let frame_per_voice = instrument_frames.next_frame_per_voice();
+        let equilibrium = <A::Frame as Frame>::equilibrium();
         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 => (),
-                        }
-                    },
+            .fold(equilibrium, |frame, (voice, amp_hz)| {
+                if let Some((amp, hz)) = 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 => return frame,
+                            }
+                        },
+                    }
                 }
 
                 // If we made it this far, the voice has finished playback of the note.