-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implemented background noise. Fixes #1.
Continuously samples background noise in chunks of 1023 samples. If a chunk is interrupted, it will be discarded. 20 last chunks are stored and when needed, they are played back by the background looper one by one, by choosing from the ten quietest clips. Seems to work surprisingly fine; no cross fading between samples is implemented yet, and the choise mechasnim for samples could better than just its RMS (e.g. start/end RMS difference?).
- Loading branch information
Showing
9 changed files
with
241 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
use crate::sampler::Sampler; | ||
use rand::rngs::StdRng; | ||
use rand::{Rng, SeedableRng}; | ||
use std::collections::BTreeMap; | ||
|
||
#[derive(Clone)] | ||
pub struct Clip { | ||
pub sample_a: Sampler, | ||
pub sample_b: Sampler, | ||
pub rms: f32, | ||
} | ||
|
||
type ClipId = usize; | ||
|
||
pub struct BackgroundSampler { | ||
current_clip: Option<Clip>, | ||
clip_length: usize, | ||
num_clips: usize, | ||
clip_id_gen: ClipId, | ||
clips: BTreeMap<ClipId, Clip>, | ||
rng: StdRng, | ||
} | ||
|
||
pub type StereoSample = (f32, f32); | ||
|
||
// BackgroundSampler periodically (or maybe randomly) samples short samples, keeping at most num_clips latest | ||
// ones. Then it orders the samples by their volume and the user may pick n quietest samples from them to use as | ||
// background noise. If sampling a sample is interrupted by a pause, then that sample is discarded. | ||
impl BackgroundSampler { | ||
pub fn new(num_clips: usize, clip_length: usize) -> BackgroundSampler { | ||
let mut bg_sampler = BackgroundSampler { | ||
current_clip: None, | ||
clip_length, | ||
num_clips, | ||
clip_id_gen: 0, | ||
clips: BTreeMap::new(), | ||
rng: StdRng::from_entropy(), | ||
}; | ||
bg_sampler.resume(); | ||
bg_sampler | ||
} | ||
|
||
fn new_clip_id(&mut self) -> usize { | ||
let id = self.clip_id_gen; | ||
self.clip_id_gen += 1; | ||
id | ||
} | ||
|
||
pub fn sample(&mut self, sample: StereoSample) { | ||
let full = match self.current_clip { | ||
None => false, | ||
Some(ref mut clip) => { | ||
clip.sample_a.sample(sample.0); | ||
clip.sample_b.sample(sample.1); | ||
clip.sample_a.is_full() | ||
} | ||
}; | ||
if full { | ||
let id = self.new_clip_id(); | ||
let mut clip = self.current_clip.take().unwrap(); | ||
clip.rms = clip.sample_a.rms().max(clip.sample_b.rms()); | ||
self.clips.insert(id, clip); | ||
if self.clips.len() > self.num_clips { | ||
// we could do: self.clips.pop_first(); | ||
// but let's avoid unstable features for now | ||
if let Some((&key, _)) = self.clips.iter().next() { | ||
self.clips.remove(&key); | ||
} | ||
} | ||
self.resume(); | ||
} | ||
} | ||
|
||
// pick a random clip from the n least-rms clips | ||
pub fn choose_clip(&mut self, limit: usize) -> Option<Clip> { | ||
if self.clips.is_empty() { | ||
None | ||
} else { | ||
let mut clips: Vec<&Clip> = self.clips.iter().map(|(_, v)| v).collect(); | ||
clips.sort_unstable_by(|a, b| { | ||
if a.rms < b.rms { | ||
std::cmp::Ordering::Less | ||
} else if a.rms > b.rms { | ||
std::cmp::Ordering::Greater | ||
} else { | ||
std::cmp::Ordering::Equal | ||
} | ||
}); | ||
Some(clips[self.rng.gen_range(0..limit.min(clips.len()))].clone()) | ||
} | ||
} | ||
|
||
pub fn pause(&mut self) { | ||
self.current_clip = None; | ||
} | ||
|
||
pub fn resume(&mut self) { | ||
if self.current_clip.is_none() { | ||
self.current_clip = Some(Clip { | ||
sample_a: Sampler::new(self.clip_length, true), | ||
sample_b: Sampler::new(self.clip_length, true), | ||
rms: 0.0, | ||
}); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
pub struct CrossFader { | ||
value: f32, | ||
step_per_sample: f32, | ||
} | ||
|
||
impl CrossFader { | ||
pub fn new(value: f32) -> CrossFader { | ||
CrossFader { | ||
value, | ||
step_per_sample: 0.0, | ||
} | ||
} | ||
|
||
// fades to sample_a | ||
pub fn fade_in(&mut self, samples: usize) { | ||
self.step_per_sample = 1.0 / (samples as f32); | ||
} | ||
|
||
// fades to sample_b | ||
pub fn fade_out(&mut self, samples: usize) { | ||
self.step_per_sample = -1.0 / (samples as f32); | ||
} | ||
|
||
pub fn process(&mut self, sample_a: f32, sample_b: f32) -> f32 { | ||
self.value = f32::clamp(self.value + self.step_per_sample, 0.0, 1.0); | ||
sample_a * self.value + sample_b * (1.0 - self.value) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
use crate::background_sampler; | ||
use crate::background_sampler::BackgroundSampler; | ||
|
||
struct PlayClip { | ||
sample_a: Vec<f32>, | ||
sample_b: Vec<f32>, | ||
playhead: usize, | ||
} | ||
|
||
pub struct Looper { | ||
current_clip: Option<PlayClip>, | ||
} | ||
|
||
impl Looper { | ||
pub fn new() -> Looper { | ||
Looper { current_clip: None } | ||
} | ||
|
||
pub fn produce( | ||
&mut self, | ||
background_sampler: &mut BackgroundSampler, | ||
) -> background_sampler::StereoSample { | ||
if self.current_clip.is_none() { | ||
if let Some(clip) = background_sampler.choose_clip(10) { | ||
self.current_clip = Some(PlayClip { | ||
sample_a: clip.sample_a.get(), | ||
sample_b: clip.sample_b.get(), | ||
playhead: 0, | ||
}) | ||
} | ||
} | ||
if let Some(ref mut clip) = self.current_clip { | ||
if clip.playhead >= clip.sample_a.len() { | ||
self.current_clip = None; | ||
(0.0, 0.0) | ||
} else { | ||
let sample = (clip.sample_a[clip.playhead], clip.sample_b[clip.playhead]); | ||
clip.playhead += 1; | ||
sample | ||
} | ||
} else { | ||
(0.0, 0.0) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.