Browse Source

initial commit

master
Thomas Johnson 2 months ago
commit
20168891a8
  1. 11
      Cargo.toml
  2. 99
      src/main.rs
  3. 68
      src/self_sum_output.rs
  4. 17
      src/sync_diff.rs

11
Cargo.toml

@ -0,0 +1,11 @@
[package]
name = "doa"
version = "0.1.0"
edition = "2018"
authors = [ "thajohns" ]
[dependencies]
cpal = "0.13"
[features]
jack = [ "cpal/jack" ]

99
src/main.rs

@ -0,0 +1,99 @@
#![feature(map_first_last)]
mod sync_diff;
mod self_sum_output;
use std::io::{Write, Read};
use self_sum_output::SelfSumOutput;
use cpal::SampleRate;
use cpal::traits::{HostTrait, DeviceTrait, StreamTrait};
fn main() {
#[cfg(feature="jack")]
let host = if let Ok(host) = cpal::host_from_id(cpal::HostId::Jack) {
host
} else {
cpal::default_host()
};
#[cfg(not(feature="jack"))]
let host = cpal::default_host();
let device = host.default_output_device().expect("no default host audio device!");
let mut conf_ranges = device.supported_output_configs().expect("could not query audio device capabilities -- audio device disconnected?");
let conf_range = conf_ranges.next().expect("audio device has no configurations!");
let desired_sample_rate = conf_range.max_sample_rate().0;
//let desired_sample_rate = u32::clamp(44100, conf_range.min_sample_rate().0, conf_range.max_sample_rate().0);
let conf = conf_range.with_sample_rate(SampleRate(desired_sample_rate)).config();
println!("playing at sample rate {}", conf.sample_rate.0);
let lower_freq = 300.0;
let upper_freq = 400.0;
let window_freq = upper_freq - lower_freq;
let window_freq_length = (conf.sample_rate.0 as f64) / window_freq;
let origin = -20.0 / lower_freq;
let step = (conf.sample_rate.0 as f64).recip();
let num_steps = (origin * -2.0 / step) as usize;
let mut samps = vec![0.0f64; num_steps];
sync_diff::sync_diff_buff(origin, step, lower_freq, upper_freq, &mut samps);
// for i in 0..num_steps {
// println!("{} {}", origin + step * i as f64, samps[i]);
// }
let exit = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let mut file = std::fs::File::open(std::env::args().nth(1).expect("give a file name")).expect("could not open file");
let mut bytes = Vec::new();
file.read_to_end(&mut bytes).expect("could not read file");
let mut sig = SelfSumOutput::new(samps);
for i in 0..bytes.len() {
for j in 0..8 {
if (bytes[i] << j) & 0x80 == 0 {
sig.add_interstitial_source(window_freq_length * ((i * 8 + j) * 2) as f64, 1.0);
sig.add_interstitial_source(window_freq_length * ((i * 8 + j) * 2 + 1) as f64, -1.0);
} else {
sig.add_interstitial_source(window_freq_length * ((i * 8 + j) * 2) as f64, -1.0);
sig.add_interstitial_source(window_freq_length * ((i * 8 + j) * 2 + 1) as f64, 1.0);
}
}
}
let mut gen_samples;
{
let exit = exit.clone();
gen_samples = move |data: &mut [f32]| {
let mut tmp = vec![0.0f64; data.len()];
sig.advance(&mut tmp);
for i in 0..data.len() {
data[i] = 0.25 * tmp[i] as f32;
}
if sig.num_sources() == 0 {
exit.store(true, std::sync::atomic::Ordering::Relaxed);
}
};
}
if std::env::args().count() < 3 {
let stream = device.build_output_stream(
&conf,
move |buf, _: &cpal::OutputCallbackInfo| gen_samples(buf),
move |err| {
println!("audio stream error: {}", err);
}
).expect("could not create audio stream");
stream.play();
while !exit.load(std::sync::atomic::Ordering::Relaxed) {}
} else {
// FIXME: this whole section should not be like this
let mut file = std::fs::File::create(std::env::args().nth(2).unwrap()).expect("could not open output file");
let mut buf = vec![0.0f32; 256];
while !exit.load(std::sync::atomic::Ordering::Relaxed) {
gen_samples(&mut buf);
for samp in &buf {
file.write(&samp.to_le_bytes()).expect("could not write output file");
}
}
}
}

68
src/self_sum_output.rs

@ -0,0 +1,68 @@
use std::collections::BTreeMap;
// Provides a stream of samples, produced by adding copied versions of a buffer to itself.
#[derive(Debug, Clone)]
pub struct SelfSumOutput {
// The buffer to source from
samples: Vec<f64>,
sources: BTreeMap<usize, f64>,
next_sample: usize,
}
impl SelfSumOutput {
pub fn new(samples: Vec<f64>) -> Self {
Self {
samples,
sources: BTreeMap::new(),
next_sample: 0,
}
}
// advances the stream, adding the result to the buffer
pub fn advance(&mut self, buff: &mut [f64]) {
for (&start, &mag) in &self.sources {
// since they're sorted by ascending start time, ignore those which are too new
if start >= self.next_sample + buff.len() {
break;
}
let lower = usize::max(start, self.next_sample);
let upper = usize::min(start + self.samples.len(), self.next_sample + buff.len());
for sample in lower..upper {
buff[sample - self.next_sample] += self.samples[sample - start] * mag;
}
}
self.next_sample += buff.len();
// Remove sources that aren't contributing anymore
while let Some(ent) = self.sources.first_entry() {
if ent.key() + self.samples.len() < self.next_sample {
ent.remove_entry();
} else {
break;
}
}
}
pub fn num_sources(&self) -> usize {
self.sources.len()
}
pub fn add_source(&mut self, start: usize, mag: f64) {
if let Some(v) = self.sources.get_mut(&start) {
*v += mag;
} else {
self.sources.insert(start, mag);
}
}
pub fn add_interstitial_source(&mut self, start: f64, mag: f64) {
let lower = start.floor();
let ratio = start - lower;
self.add_source(lower as usize, mag - ratio * mag);
self.add_source(lower as usize + 1, ratio * mag);
}
pub fn get_next_sample_idx(&self) -> usize {
self.next_sample
}
}

17
src/sync_diff.rs

@ -0,0 +1,17 @@
use std::f64::consts::TAU;
pub fn sync_diff(t: f64, l: f64, u: f64) -> f64 {
let peak = (u - l) * TAU;
if t == 0.0 {
1.0
} else {
((u * t * TAU).sin() - (l * t * TAU).sin()) / t / peak
}
}
pub fn sync_diff_buff(origin: f64, step: f64, l: f64, u: f64, buff: &mut [f64]) {
for i in 0..buff.len() {
buff[i] = sync_diff(origin + (i as f64) * step, l, u);
}
}
Loading…
Cancel
Save