summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrant Shangreaux <grant@unabridgedsoftware.com>2021-10-30 23:28:56 -0500
committerGrant Shangreaux <grant@unabridgedsoftware.com>2021-10-30 23:28:56 -0500
commitb9559897c05b8c2f67e34372685952d586af5c3d (patch)
tree60a5c8c21749e3de811002692e797f73f102f518
parent6d9bfcbc4ab0575e6cbf5a79d072431d7c7e712e (diff)
Add: restructured Osc class preparing for more wave forms
-rw-r--r--klangfarb/Osc.gdns (renamed from klangfarb/SineWave.gdns)4
-rw-r--r--klangfarb/main.gd4
-rw-r--r--klangfarbrs/src/lib.rs63
3 files changed, 56 insertions, 15 deletions
diff --git a/klangfarb/SineWave.gdns b/klangfarb/Osc.gdns
index e220a2e..65d44ef 100644
--- a/klangfarb/SineWave.gdns
+++ b/klangfarb/Osc.gdns
@@ -3,6 +3,6 @@
[ext_resource path="res://klangfarbrs.gdnlib" type="GDNativeLibrary" id=1]
[resource]
-resource_name = "SineWave"
-class_name = "SineWave"
+resource_name = "Osc"
+class_name = "Osc"
library = ExtResource( 1 )
diff --git a/klangfarb/main.gd b/klangfarb/main.gd
index fb29c8c..f91dbb7 100644
--- a/klangfarb/main.gd
+++ b/klangfarb/main.gd
@@ -4,9 +4,9 @@ extends AudioStreamPlayer
export(float, 20, 8000, 10) var freq = 440.0
# load the GDNative script connected to the rust lib
-var SineWave = preload("res://SineWave.gdns")
+var Osc = preload("res://Osc.gdns")
# make an instance of our one "class" in rust lib
-var wave = SineWave.new()
+var wave = Osc.new()
# initialize the Godot stream we fill up with samples
var playback: AudioStreamPlayback = null
diff --git a/klangfarbrs/src/lib.rs b/klangfarbrs/src/lib.rs
index ba201f0..a418995 100644
--- a/klangfarbrs/src/lib.rs
+++ b/klangfarbrs/src/lib.rs
@@ -11,25 +11,66 @@ use gdnative::prelude::*;
use gdnative::core_types::TypedArray;
use std::f32::consts::TAU;
+/// This struct is used as a class in Godot. It is a "numerically controlled oscillator"
+/// which is driven by a phasor. The sample rate and waveform should be set after you
+/// create a new instance in GDScript.
#[derive(NativeClass)]
#[inherit(Node)]
-pub struct SineWave {
+pub struct Osc {
+ pub waveform: Waveform,
pub sample_rate: f32,
- pub phase: f32
+ phase: f32,
+}
+
+/// The various waveforms the `Osc` can generate.
+enum Waveform {
+ Sine,
+ // Square,
+ // Triangle,
+ // Saw,
+ // Noise,
+}
+
+/// Generates the next sample for an oscillator based on its waveform.
+fn generate_sample(osc: &Osc) -> f32 {
+ match osc.waveform {
+ Waveform::Sine => {
+ (TAU * osc.phase).sin()
+ }
+ }
+}
+
+/// Phase stays between 0.0 and 1.0 and represents position on the axis of time
+/// for a given wave form. Since audio signals are periodic, we can just calculate
+/// the first cycle of a wave repeatedly. This also prevents pitch drift caused by
+/// floating point errors over time.
+fn calculate_phase(osc: &Osc, frequency: f32) -> f32 {
+ (osc.phase + (frequency / osc.sample_rate)) % 1.0
}
/// # Examples
///
+/// It is more work than benefit to figure out how to instantiate a Godot object (Node)
+/// that does not behave as typical Rust. However, I wanted to try out the feature of
+/// examples in the documentation that run as automated tests. :galaxy-brain:
+///
/// ```
-/// use klangfarbrs::SineWave;
-/// let mut wave = SineWave { sample_rate: 24000.0, phase: 0.0 };
+/// use klangfarbrs::Osc;
+/// let mut wave = Osc { sample_rate: 24000.0, phase: 0.0 };
/// assert_eq!(wave.sample_rate, 24000.0);
/// ```
-
#[methods]
-impl SineWave {
+impl Osc {
+ /// # Examples
+ ///
+ /// ```gdscript
+ /// var Osc = preload("res://Osc.gdns")
+ /// var wave = Osc.new()
+ /// wave.set_sample_rate(24000.0)
+ /// wave.square() # changes to a square wave
+ /// ```
pub fn new(_owner: &Node) -> Self {
- Self { sample_rate: 48000.0, phase: 0.0 }
+ Self { waveform: Waveform::Sine, sample_rate: 48000.0, phase: 0.0 }
}
#[export]
@@ -47,9 +88,9 @@ impl SineWave {
let mut frames = TypedArray::new();
for _i in 0..duration {
- let sample = (TAU * self.phase).sin().clamp(-1.0, 1.0);
+ let sample = generate_sample(&self);
frames.push(Vector2::new(sample, sample));
- self.phase = (self.phase + (frequency / self.sample_rate)) % 1.0;
+ self.phase = calculate_phase(&self, frequency);
}
return frames
@@ -58,8 +99,8 @@ impl SineWave {
// Function that registers all exposed classes to Godot
fn init(handle: InitHandle) {
- // Register the `Sine` type we declared.
- handle.add_class::<SineWave>();
+ // Register the `Osc` type we declared.
+ handle.add_class::<Osc>();
}
// Macro that creates the entry-points of the dynamic library.