125 lines
4.3 KiB
Rust
125 lines
4.3 KiB
Rust
use std::{f32::consts::PI, sync::Arc};
|
|
|
|
use cpal::{
|
|
traits::{DeviceTrait, HostTrait, StreamTrait},
|
|
SampleRate, Stream,
|
|
};
|
|
use oddio::{Frames, Handle, Mixer};
|
|
use rand::Rng;
|
|
|
|
use super::sound_effects::{SoundEffect, SoundType};
|
|
|
|
pub type SoundSamples = Arc<Frames<f32>>;
|
|
|
|
pub struct SoundOutput {
|
|
mixer_handle: Handle<Mixer<[f32; 2]>>,
|
|
sample_rate: SampleRate,
|
|
_stream: Stream,
|
|
}
|
|
|
|
impl Default for SoundOutput {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl SoundOutput {
|
|
pub fn new() -> Self {
|
|
let host = cpal::default_host();
|
|
let device = host
|
|
.default_output_device()
|
|
.expect("no output device available");
|
|
let sample_rate = device.default_output_config().unwrap().sample_rate();
|
|
let config = cpal::StreamConfig {
|
|
channels: 2,
|
|
sample_rate,
|
|
buffer_size: cpal::BufferSize::Default,
|
|
};
|
|
|
|
let (mixer_handle, mixer) = oddio::split(oddio::Mixer::new());
|
|
|
|
let stream = device
|
|
.build_output_stream(
|
|
&config,
|
|
move |out_flat: &mut [f32], _: &cpal::OutputCallbackInfo| {
|
|
let out_stereo: &mut [[f32; 2]] = oddio::frame_stereo(out_flat);
|
|
oddio::run(&mixer, sample_rate.0, out_stereo);
|
|
},
|
|
move |err| {
|
|
eprintln!("{}", err);
|
|
},
|
|
)
|
|
.unwrap();
|
|
stream.play().unwrap();
|
|
|
|
Self {
|
|
mixer_handle,
|
|
sample_rate,
|
|
_stream: stream,
|
|
}
|
|
}
|
|
|
|
pub fn render_sound_effect(&self, effect: &SoundEffect) -> SoundSamples {
|
|
let effect_buffer: Vec<f32> = effect
|
|
.sounds
|
|
.iter()
|
|
.flat_map(|sound| match sound.sound_type {
|
|
SoundType::Silence => (0
|
|
..(self.sample_rate.0 as f32 * sound.duration.as_secs_f32()) as usize)
|
|
.map(|_| 0f32)
|
|
.collect::<Vec<f32>>(),
|
|
SoundType::Tone(freq) => {
|
|
if freq == 0 {
|
|
return (0..(self.sample_rate.0 as f32 * sound.duration.as_secs_f32())
|
|
as usize)
|
|
.map(|_| 0f32)
|
|
.collect::<Vec<f32>>();
|
|
}
|
|
let num_harmonics = self.sample_rate.0 / (freq as u32 * 2);
|
|
let coefficients = (0..=num_harmonics)
|
|
.map(|i| {
|
|
if i == 0 {
|
|
return 0.0;
|
|
}
|
|
(i as f32 * 0.5 * PI).sin() * 2.0 / (i as f32 * PI)
|
|
})
|
|
.collect::<Vec<f32>>();
|
|
let scaler = freq as f32 * PI * 2.0 / self.sample_rate.0 as f32;
|
|
(0..(self.sample_rate.0 as f32 * sound.duration.as_secs_f32()) as usize)
|
|
.map(|i| {
|
|
let temp = scaler * i as f32;
|
|
coefficients
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(j, coef)| coef * (j as f32 * temp).cos())
|
|
.sum::<f32>()
|
|
})
|
|
.collect::<Vec<f32>>()
|
|
}
|
|
SoundType::Noise(min, max) => {
|
|
(0..(self.sample_rate.0 as f32 * sound.duration.as_secs_f32()) as usize)
|
|
.map(|i| {
|
|
let t = i as f32 / self.sample_rate.0 as f32;
|
|
(t * (rand::thread_rng().gen_range(min as f32..max as f32))
|
|
* 2.0
|
|
* std::f32::consts::PI)
|
|
.sin()
|
|
.signum()
|
|
})
|
|
.collect::<Vec<f32>>()
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
oddio::Frames::from_iter(self.sample_rate.0, effect_buffer.iter().copied())
|
|
}
|
|
|
|
pub fn play_sound(&mut self, samples: SoundSamples) {
|
|
self.mixer_handle
|
|
.control::<oddio::Mixer<_>, _>()
|
|
.play(oddio::MonoToStereo::new(oddio::Gain::new(
|
|
oddio::FramesSignal::from(samples),
|
|
0.20,
|
|
)));
|
|
}
|
|
}
|