Bläddra i källkod

Initial commit

mitchmindtree 8 år sedan
incheckning
20904efb81
13 ändrade filer med 324 tillägg och 0 borttagningar
  1. 24 0
      .gitignore
  2. 13 0
      .travis.yml
  3. 14 0
      Cargo.toml
  4. 13 0
      LICENSE-APACHE
  5. 21 0
      LICENSE-MIT
  6. 1 0
      README.md
  7. BIN
      assets/amen_brother.wav
  8. 45 0
      examples/wav.rs
  9. 10 0
      src/lib.rs
  10. 83 0
      src/map.rs
  11. 40 0
      src/sampler.rs
  12. 12 0
      src/source.rs
  13. 48 0
      src/voice.rs

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+.DS_Store
+*~
+*#
+*.o
+*.so
+*.swp
+*.dylib
+*.dSYM
+*.dll
+*.rlib
+*.dummy
+*.exe
+*-test
+/bin/main
+/bin/test-internal
+/bin/test-external
+/doc/
+/target/
+/build/
+/.rust/
+rusti.sh
+/examples/**/target
+
+Cargo.lock

+ 13 - 0
.travis.yml

@@ -0,0 +1,13 @@
+language: rust
+
+rust:
+    - stable
+    - nightly
+
+os:
+    - linux
+
+script:
+    - cargo build --verbose
+    - cargo test --verbose
+    - cargo doc --verbose

+ 14 - 0
Cargo.toml

@@ -0,0 +1,14 @@
+[package]
+name = "sampler"
+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"
+
+[dev-dependencies]
+find_folder = "0.3.0"
+hound = "1.1.0"
+portaudio = "0.6.2"

+ 13 - 0
LICENSE-APACHE

@@ -0,0 +1,13 @@
+Copyright 2016 Mitchell Nordine
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.

+ 21 - 0
LICENSE-MIT

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Mitchell Nordine
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 1 - 0
README.md

@@ -0,0 +1 @@
+# sampler [![Build Status](https://travis-ci.org/RustAudio/sampler.svg?branch=master)](https://travis-ci.org/RustAudio/sampler) [![Crates.io](https://img.shields.io/crates/v/sampler.svg)](https://crates.io/crates/sampler) [![Crates.io](https://img.shields.io/crates/l/sampler.svg)](https://github.com/RustAudio/sampler/blob/master/LICENSE)

BIN
assets/amen_brother.wav


+ 45 - 0
examples/wav.rs

@@ -0,0 +1,45 @@
+extern crate find_folder;
+extern crate hound;
+extern crate portaudio as pa;
+
+fn main() {
+    run().unwrap();
+}
+
+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()
+    };
+
+    let pa = try!(pa::PortAudio::new());
+
+    const CHANNELS: i32 = 1;
+    const SAMPLE_RATE: f64 = 44_100.0;
+    const FRAMES_PER_BUFFER: u32 = 64;
+
+    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,
+            };
+        }
+        pa::Continue
+    };
+
+    let mut stream = try!(pa.open_non_blocking_stream(settings, callback));
+
+    try!(stream.start());
+
+    while let Ok(true) = stream.is_active() {}
+
+    try!(stream.stop());
+    try!(stream.close());
+
+    Ok(())
+}

+ 10 - 0
src/lib.rs

@@ -0,0 +1,10 @@
+extern crate pitch_calc as pitch;
+extern crate sample;
+extern crate time_calc as time;
+
+mod map;
+mod sampler;
+mod source;
+mod voice;
+
+pub type Velocity = f32;

+ 83 - 0
src/map.rs

@@ -0,0 +1,83 @@
+use pitch;
+use std;
+use Velocity;
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct SampleMap<S> {
+    pairs: Vec<Pair<S>>,
+}
+
+/// Some slice of PCM samples that represents a single audio sample.
+#[derive(Clone, Debug, PartialEq)]
+pub struct Audio<S> {
+    data: std::sync::Arc<[S]>,
+}
+
+/// A range paired with a specific sample.
+#[derive(Clone, Debug, PartialEq)]
+struct Pair<S> {
+    range: HzVelRange,
+    audio: Audio<S>,
+}
+
+/// 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>,
+}
+
+/// A continuous range of `T` from the `min` to the `max`.
+#[derive(Clone, Debug, PartialEq, PartialOrd)]
+pub struct Range<T> {
+    min: T,
+    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<S> SampleMap<S> {
+
+    /// Construct a `SampleMap` 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>)>,
+    {
+        let (mut last_hz, mut last_vel) = (pitch::Step(0.0).to_hz(), 1.0);
+        let pairs = mappings.into_iter().map(|(hz, vel, audio)| {
+            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 }
+        }).collect();
+        SampleMap { pairs: pairs }
+    }
+
+    /// Inserts a range -> audio mapping into the SampleMap.
+    pub fn insert(&mut self, range: HzVelRange, audio: Audio<S>) {
+        for i in 0..self.pairs.len() {
+            if self.pairs[i].range > range {
+                self.pairs.insert(i, Pair { range: range, audio: audio });
+                return;
+            }
+        }
+        self.pairs.push(Pair { range: range, audio: audio });
+    }
+
+}
+
+
+// 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 }
+// };

+ 40 - 0
src/sampler.rs

@@ -0,0 +1,40 @@
+use map::SampleMap;
+use pitch;
+use sample::Sample as PcmSample;
+use Velocity;
+use voice::Voice;
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct Sampler<S> {
+    map: SampleMap<S>,
+    voices: Vec<Voice>,
+}
+
+impl<S> Sampler<S> {
+
+    /// Construct a new `Sampler`.
+    pub fn new(map: SampleMap<S>) -> Self {
+        Sampler {
+            map: map,
+            voices: vec![],
+        }
+    }
+
+    /// Begin playback of a note.
+    ///
+    /// `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.
+    #[inline]
+    pub fn note_on<T>(&mut self, note_hz: T, note_vel: Velocity)
+        where T: Into<pitch::Hz>
+    {
+    }
+
+    /// 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>
+    {
+    }
+
+}

+ 12 - 0
src/source.rs

@@ -0,0 +1,12 @@
+
+/// 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>;
+}

+ 48 - 0
src/voice.rs

@@ -0,0 +1,48 @@
+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]) {
+    }
+
+}