-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
lib.rs
171 lines (143 loc) · 5.23 KB
/
lib.rs
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
extern crate wasm_bindgen;
extern crate web_sys;
use wasm_bindgen::prelude::*;
use web_sys::{
AudioContext, AudioNode, AudioScheduledSourceNode, BaseAudioContext, OscillatorType,
};
/// Converts a midi note to frequency
///
/// A midi note is an integer, generally in the range of 21 to 108
pub fn midi_to_freq(note: u8) -> f32 {
27.5 * 2f32.powf((note as f32 - 21.0) / 12.0)
}
#[wasm_bindgen]
pub struct FmOsc {
ctx: AudioContext,
/// The primary oscillator. This will be the fundamental frequency
primary: web_sys::OscillatorNode,
/// Overall gain (volume) control
gain: web_sys::GainNode,
/// Amount of frequency modulation
fm_gain: web_sys::GainNode,
/// The oscillator that will modulate the primary oscillator's frequency
fm_osc: web_sys::OscillatorNode,
/// The ratio between the primary frequency and the fm_osc frequency.
///
/// Generally fractional values like 1/2 or 1/4 sound best
fm_freq_ratio: f32,
fm_gain_ratio: f32,
}
impl Drop for FmOsc {
fn drop(&mut self) {
let _ = self.ctx.close();
}
}
#[wasm_bindgen]
impl FmOsc {
#[wasm_bindgen(constructor)]
pub fn new() -> FmOsc {
let ctx = web_sys::AudioContext::new().unwrap();
let primary;
let fm_osc;
let gain;
let fm_gain;
{
let base: &BaseAudioContext = ctx.as_ref();
// Create our web audio objects.
primary = base.create_oscillator().unwrap();
fm_osc = base.create_oscillator().unwrap();
gain = base.create_gain().unwrap();
fm_gain = base.create_gain().unwrap();
}
// Some initial settings:
primary.set_type(OscillatorType::Sine);
primary.frequency().set_value(440.0); // A4 note
gain.gain().set_value(0.0); // starts muted
fm_gain.gain().set_value(0.0); // no initial frequency modulation
fm_osc.set_type(OscillatorType::Sine);
fm_osc.frequency().set_value(0.0);
// Create base class references:
{
let primary_node: &AudioNode = primary.as_ref();
let gain_node: &AudioNode = gain.as_ref();
let fm_osc_node: &AudioNode = fm_osc.as_ref();
let fm_gain_node: &AudioNode = fm_gain.as_ref();
let base: &BaseAudioContext = ctx.as_ref();
let destination = base.destination();
let destination_node: &AudioNode = destination.as_ref();
// Connect the nodes up!
// The primary oscillator is routed through the gain node, so that
// it can control the overall output volume.
primary_node.connect_with_audio_node(gain.as_ref()).unwrap();
// Then connect the gain node to the AudioContext destination (aka
// your speakers).
gain_node.connect_with_audio_node(destination_node).unwrap();
// The FM oscillator is connected to its own gain node, so it can
// control the amount of modulation.
fm_osc_node
.connect_with_audio_node(fm_gain.as_ref())
.unwrap();
// Connect the FM oscillator to the frequency parameter of the main
// oscillator, so that the FM node can modulate its frequency.
fm_gain_node
.connect_with_audio_param(&primary.frequency())
.unwrap();
}
// Start the oscillators!
AsRef::<AudioScheduledSourceNode>::as_ref(&primary)
.start()
.unwrap();
AsRef::<AudioScheduledSourceNode>::as_ref(&fm_osc)
.start()
.unwrap();
FmOsc {
ctx,
primary,
gain,
fm_gain,
fm_osc,
fm_freq_ratio: 0.0,
fm_gain_ratio: 0.0,
}
}
/// Sets the gain for this oscillator, between 0.0 and 1.0.
#[wasm_bindgen]
pub fn set_gain(&self, mut gain: f32) {
if gain > 1.0 {
gain = 1.0;
}
if gain < 0.0 {
gain = 0.0;
}
self.gain.gain().set_value(gain);
}
#[wasm_bindgen]
pub fn set_primary_frequency(&self, freq: f32) {
self.primary.frequency().set_value(freq);
// The frequency of the FM oscillator depends on the frequency of the
// primary oscillator, so we update the frequency of both in this method.
self.fm_osc.frequency().set_value(self.fm_freq_ratio * freq);
self.fm_gain.gain().set_value(self.fm_gain_ratio * freq);
}
#[wasm_bindgen]
pub fn set_note(&self, note: u8) {
let freq = midi_to_freq(note);
self.set_primary_frequency(freq);
}
/// This should be between 0 and 1, though higher values are accepted.
#[wasm_bindgen]
pub fn set_fm_amount(&mut self, amt: f32) {
self.fm_gain_ratio = amt;
self.fm_gain
.gain()
.set_value(self.fm_gain_ratio * self.primary.frequency().value());
}
/// This should be between 0 and 1, though higher values are accepted.
#[wasm_bindgen]
pub fn set_fm_frequency(&mut self, amt: f32) {
self.fm_freq_ratio = amt;
self.fm_osc
.frequency()
.set_value(self.fm_freq_ratio * self.primary.frequency().value());
}
}