1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
//! # Rust audio oscillator for the Godot game engine
//!
//! This crate contains logic for generating samples for audio wave forms which are then
//! used to fill Godot's `AudioStreamPlayback` buffers. Scripts using this code as a dynamic
//! library will be able to request a certain number of frames (represented as a `Vector2`)
//! at a specific frequency. Because of how the Godot bindings work, the wave structs will
//! have a default sample rate at 48kHz. You'll want to set it in your script's `_ready`
//! function to match the sample rate in Godot.
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 Osc {
pub waveform: Waveform,
pub sample_rate: f32,
phase: f32,
}
/// The various waveforms the `Osc` can generate.
pub 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()
},
Waveform::Square => {
if osc.phase < 0.5 {
-1.0
} else {
1.0
}
}
}
}
/// 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::Osc;
/// let mut wave = Osc { sample_rate: 24000.0, phase: 0.0 };
/// assert_eq!(wave.sample_rate, 24000.0);
/// ```
#[methods]
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 { waveform: Waveform::Sine, sample_rate: 48000.0, phase: 0.0 }
}
#[export]
fn _ready(&self, _owner: &Node) {
godot_print!("DAS IST KLANGFARBRS.")
}
#[export]
fn sine(&mut self, _owner: &Node) {
self.waveform = Waveform::Sine
}
#[export]
fn square(&mut self, _owner: &Node) {
self.waveform = Waveform::Square
}
#[export]
pub fn set_sample_rate(&mut self, _owner: &Node, sample_rate: f32) {
self.sample_rate = sample_rate;
}
#[export]
pub fn frames(&mut self, _owner: &Node, frequency: f32, duration: i32) -> TypedArray<Vector2> {
let mut frames = TypedArray::new();
for _i in 0..duration {
let sample = generate_sample(&self);
frames.push(Vector2::new(sample, sample));
self.phase = calculate_phase(&self, frequency);
}
return frames
}
}
// Function that registers all exposed classes to Godot
fn init(handle: InitHandle) {
// Register the `Osc` type we declared.
handle.add_class::<Osc>();
}
// Macro that creates the entry-points of the dynamic library.
godot_init!(init);
|