summaryrefslogtreecommitdiff
path: root/klangfarbrs/src
diff options
context:
space:
mode:
authorGrant Shangreaux <grant@unabridgedsoftware.com>2021-10-16 22:26:00 -0500
committerGrant Shangreaux <grant@unabridgedsoftware.com>2021-10-16 22:26:00 -0500
commit3aa16685da4b62a0281a2816c9fcc234232611d1 (patch)
tree66c17fc9648c48334049bbb59425cb5ff031575a /klangfarbrs/src
parenta0ef84b0474219bedf603a293a2ee8d4fab32ec0 (diff)
WIP: Make sound with rust
Diffstat (limited to 'klangfarbrs/src')
-rw-r--r--klangfarbrs/src/lib.rs103
1 files changed, 103 insertions, 0 deletions
diff --git a/klangfarbrs/src/lib.rs b/klangfarbrs/src/lib.rs
index 4843910..f116430 100644
--- a/klangfarbrs/src/lib.rs
+++ b/klangfarbrs/src/lib.rs
@@ -1,4 +1,10 @@
+use gdnative::api::Resource;
use gdnative::prelude::*;
+use cpal;
+use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
+use dasp::{Sample};
+use dasp_signal::{self as signal, Signal};
+use std::sync::mpsc;
/// The HelloWorld "class"
#[derive(NativeClass)]
@@ -19,6 +25,103 @@ impl HelloWorld {
}
}
+#[derive(NativeClass)]
+#[inherit(Resource)]
+pub struct Synth {
+ frequency: f32
+}
+
+#[methods]
+impl Synth {
+ #[export]
+ fn set_freq(
+ &mut self,
+ _owner: &Resource,
+ freq: f32
+ ) {
+ self.frequency = freq;
+ }
+
+ #[export]
+ fn _ready(&self, _owner: &Resource) {
+ let host = cpal::default_host();
+ let device = host
+ .default_output_device()
+ .expect("failed to find a default output device");
+ let config = device.default_output_config();
+
+ match config.sample_format() {
+ cpal::SampleFormat::F32 => run::<f32>(&device, &config.into()),
+ cpal::SampleFormat::I16 => run::<i16>(&device, &config.into()),
+ cpal::SampleFormat::U16 => run::<u16>(&device, &config.into()),
+ };
+
+ ();
+ }
+}
+
+fn run<T>(device: &cpal::Device, config: &cpal::StreamConfig) -> Result<(), anyhow::Error>
+where
+ T: cpal::Sample,
+{
+ // Create a signal chain to play back 1 second of each oscillator at A4.
+ let hz = signal::rate(config.sample_rate.0 as f64).const_hz(440.0);
+ let one_sec = config.sample_rate.0 as usize;
+ let mut synth = hz
+ .clone()
+ .sine()
+ .take(one_sec)
+ .chain(hz.clone().saw().take(one_sec))
+ .chain(hz.clone().square().take(one_sec))
+ .chain(hz.clone().noise_simplex().take(one_sec))
+ .chain(signal::noise(0).take(one_sec))
+ .map(|s| s.to_sample::<f32>() * 0.2);
+
+ // A channel for indicating when playback has completed.
+ let (complete_tx, complete_rx) = mpsc::sync_channel(1);
+
+ // Create and run the stream.
+ let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
+ let channels = config.channels as usize;
+ let stream = device.build_output_stream(
+ config,
+ move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
+ write_data(data, channels, &complete_tx, &mut synth)
+ },
+ err_fn,
+ )?;
+ stream.play()?;
+
+ // Wait for playback to complete.
+ complete_rx.recv().unwrap();
+ stream.pause()?;
+
+ Ok(())
+}
+
+fn write_data<T>(
+ output: &mut [T],
+ channels: usize,
+ complete_tx: &mpsc::SyncSender<()>,
+ signal: &mut dyn Iterator<Item = f32>,
+) where
+ T: cpal::Sample,
+{
+ for frame in output.chunks_mut(channels) {
+ let sample = match signal.next() {
+ None => {
+ complete_tx.try_send(()).ok();
+ 0.0
+ }
+ Some(sample) => sample,
+ };
+ let value: T = cpal::Sample::from::<f32>(&sample);
+ for sample in frame.iter_mut() {
+ *sample = value;
+ }
+ }
+}
+
// Function that registers all exposed classes to Godot
fn init(handle: InitHandle) {
// Register the new `HelloWorld` type we just declared.