rescript-audio 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,227 @@
1
+ # rescript-audio
2
+
3
+ ReScript bindings for the Web Audio API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install rescript-audio
9
+ ```
10
+
11
+ Add to your `rescript.json`:
12
+
13
+ ```json
14
+ {
15
+ "dependencies": ["rescript-audio"]
16
+ }
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```rescript
22
+ // Create an audio context
23
+ let ctx = Audio.Context.make()
24
+
25
+ // Create an oscillator with options
26
+ let osc = Audio.Oscillator.make(ctx, ~options={
27
+ waveform: Sine,
28
+ frequency: 440.0,
29
+ })
30
+
31
+ // Create a gain node
32
+ let gain = Audio.Gain.make(ctx, ~options={gain: 0.5})
33
+
34
+ // Connect oscillator -> gain -> destination
35
+ Audio.Utils.chain([
36
+ osc->Audio.Oscillator.asNode,
37
+ gain->Audio.Gain.asNode,
38
+ Audio.ContextExt.destination(ctx)->Audio.Destination.asNode,
39
+ ])
40
+
41
+ // Start the oscillator
42
+ Audio.Oscillator.start(osc)
43
+
44
+ // Schedule parameter changes
45
+ Audio.Gain.gainParam(gain)
46
+ ->Audio.Param.setValueAtTime(~value=0.5, ~startTime=Audio.Context.currentTime(ctx))
47
+ ->Audio.Param.linearRampToValueAtTime(~value=0.0, ~endTime=Audio.Context.currentTime(ctx) +. 1.0)
48
+ ->ignore
49
+ ```
50
+
51
+ ## API
52
+
53
+ ### Context
54
+
55
+ ```rescript
56
+ Audio.Context.make() // Create new AudioContext
57
+ Audio.Context.makeWithOptions(options) // Create with options
58
+ Audio.Context.sampleRate(ctx) // Get sample rate
59
+ Audio.Context.currentTime(ctx) // Get current time
60
+ Audio.Context.state(ctx) // Get state: Suspended | Running | Closed
61
+ Audio.Context.resume(ctx) // Resume (returns promise)
62
+ Audio.Context.suspend(ctx) // Suspend (returns promise)
63
+ Audio.Context.close(ctx) // Close (returns promise)
64
+ ```
65
+
66
+ ### Oscillator
67
+
68
+ ```rescript
69
+ // Waveform types: Sine | Square | Sawtooth | Triangle | Custom
70
+ Audio.Oscillator.make(ctx, ~options=?)
71
+ Audio.Oscillator.frequencyParam(osc) // Get frequency AudioParam
72
+ Audio.Oscillator.detuneParam(osc) // Get detune AudioParam
73
+ Audio.Oscillator.waveform(osc) // Get current waveform
74
+ Audio.Oscillator.setWaveform(osc, w) // Set waveform
75
+ Audio.Oscillator.start(osc) // Start immediately
76
+ Audio.Oscillator.startAt(osc, time) // Start at time
77
+ Audio.Oscillator.stop(osc) // Stop immediately
78
+ Audio.Oscillator.stopAt(osc, time) // Stop at time
79
+ ```
80
+
81
+ ### Gain
82
+
83
+ ```rescript
84
+ Audio.Gain.make(ctx, ~options=?)
85
+ Audio.Gain.gainParam(gain) // Get gain AudioParam
86
+ ```
87
+
88
+ ### BiquadFilter
89
+
90
+ ```rescript
91
+ // Filter types: Lowpass | Highpass | Bandpass | Lowshelf | Highshelf | Peaking | Notch | Allpass
92
+ Audio.BiquadFilter.make(ctx, ~options=?)
93
+ Audio.BiquadFilter.frequencyParam(f)
94
+ Audio.BiquadFilter.detuneParam(f)
95
+ Audio.BiquadFilter.qParam(f)
96
+ Audio.BiquadFilter.gainParam(f)
97
+ Audio.BiquadFilter.filterType(f)
98
+ Audio.BiquadFilter.setFilterType(f, ft)
99
+ ```
100
+
101
+ ### Delay
102
+
103
+ ```rescript
104
+ Audio.Delay.make(ctx, ~options=?)
105
+ Audio.Delay.delayTimeParam(d)
106
+ ```
107
+
108
+ ### Compressor
109
+
110
+ ```rescript
111
+ Audio.Compressor.make(ctx, ~options=?)
112
+ Audio.Compressor.thresholdParam(c)
113
+ Audio.Compressor.kneeParam(c)
114
+ Audio.Compressor.ratioParam(c)
115
+ Audio.Compressor.attackParam(c)
116
+ Audio.Compressor.releaseParam(c)
117
+ Audio.Compressor.reductionDb(c) // Current gain reduction in dB
118
+ ```
119
+
120
+ ### StereoPanner
121
+
122
+ ```rescript
123
+ Audio.StereoPanner.make(ctx, ~options=?)
124
+ Audio.StereoPanner.panParam(p) // -1.0 (left) to 1.0 (right)
125
+ ```
126
+
127
+ ### Analyser
128
+
129
+ ```rescript
130
+ Audio.Analyser.make(ctx, ~options=?)
131
+ Audio.Analyser.fftSize(a)
132
+ Audio.Analyser.setFftSize(a, size)
133
+ Audio.Analyser.frequencyBinCount(a)
134
+ Audio.Analyser.getFloatFrequencyData(a, array)
135
+ Audio.Analyser.getByteFrequencyData(a, array)
136
+ Audio.Analyser.getFloatTimeDomainData(a, array)
137
+ Audio.Analyser.getByteTimeDomainData(a, array)
138
+ ```
139
+
140
+ ### Param (AudioParam)
141
+
142
+ ```rescript
143
+ Audio.Param.value(p) // Get current value
144
+ Audio.Param.setValue(p, v) // Set value immediately
145
+ Audio.Param.setValueAtTime(p, ~value, ~startTime)
146
+ Audio.Param.linearRampToValueAtTime(p, ~value, ~endTime)
147
+ Audio.Param.exponentialRampToValueAtTime(p, ~value, ~endTime)
148
+ Audio.Param.setTargetAtTime(p, ~target, ~startTime, ~timeConstant)
149
+ Audio.Param.setValueCurveAtTime(p, ~values, ~startTime, ~duration)
150
+ Audio.Param.cancelScheduledValues(p, ~cancelTime)
151
+ Audio.Param.cancelAndHoldAtTime(p, ~cancelTime)
152
+ ```
153
+
154
+ ### Node
155
+
156
+ ```rescript
157
+ Audio.Node.connect(source, destination)
158
+ Audio.Node.connectToParam(source, param)
159
+ Audio.Node.disconnect(node)
160
+ Audio.Node.disconnectFrom(node, target)
161
+ Audio.Node.numberOfInputs(node)
162
+ Audio.Node.numberOfOutputs(node)
163
+ ```
164
+
165
+ ### Utils
166
+
167
+ ```rescript
168
+ Audio.Utils.chain(nodes) // Connect array of nodes in series
169
+ Audio.Utils.toDestination(ctx, node) // Connect node to destination
170
+ ```
171
+
172
+ ## Development
173
+
174
+ ```bash
175
+ npm install
176
+ npm run build
177
+ npm test
178
+ npm run watch # Development mode
179
+ ```
180
+
181
+ ## Contributing
182
+
183
+ This project uses [Conventional Commits](https://www.conventionalcommits.org/) for automatic versioning and changelog generation.
184
+
185
+ ### Commit Message Format
186
+
187
+ ```
188
+ <type>(<scope>): <description>
189
+
190
+ [optional body]
191
+
192
+ [optional footer(s)]
193
+ ```
194
+
195
+ ### Types
196
+
197
+ | Type | Description | Version Bump |
198
+ |------|-------------|--------------|
199
+ | `feat` | New feature | Minor |
200
+ | `fix` | Bug fix | Patch |
201
+ | `perf` | Performance improvement | Patch |
202
+ | `refactor` | Code refactoring | Patch |
203
+ | `docs` | Documentation changes | Patch (README only) |
204
+ | `chore` | Maintenance tasks | No release |
205
+ | `test` | Test changes | No release |
206
+
207
+ ### Breaking Changes
208
+
209
+ Add `BREAKING CHANGE:` in the commit footer or `!` after the type for major version bumps:
210
+
211
+ ```
212
+ feat!: remove deprecated API
213
+
214
+ BREAKING CHANGE: The old API has been removed.
215
+ ```
216
+
217
+ ### Examples
218
+
219
+ ```bash
220
+ git commit -m "feat(oscillator): add custom waveform support"
221
+ git commit -m "fix(gain): correct parameter range validation"
222
+ git commit -m "docs(README): add filter examples"
223
+ ```
224
+
225
+ ## License
226
+
227
+ MIT
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "rescript-audio",
3
+ "version": "1.0.0",
4
+ "description": "ReScript bindings for the Web Audio API",
5
+ "main": "./src/Audio.js",
6
+ "module": "./src/Audio.js",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/brnrdog/rescript-audio.git"
10
+ },
11
+ "homepage": "https://github.com/brnrdog/rescript-audio#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/brnrdog/rescript-audio/issues"
14
+ },
15
+ "files": [
16
+ "src/Audio.res",
17
+ "src/Audio.js",
18
+ "rescript.json",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "build": "rescript",
23
+ "clean": "rescript clean",
24
+ "watch": "rescript -w",
25
+ "test": "rescript && node tests/AudioTests.js"
26
+ },
27
+ "keywords": [
28
+ "rescript",
29
+ "web-audio",
30
+ "audio",
31
+ "bindings"
32
+ ],
33
+ "author": "Bernardo Gurgel <brnrdog@hey.com>",
34
+ "license": "MIT",
35
+ "dependencies": {
36
+ "@rescript/core": "^1.6.1",
37
+ "rescript": "^12.1.0"
38
+ },
39
+ "devDependencies": {
40
+ "@semantic-release/changelog": "^6.0.3",
41
+ "@semantic-release/git": "^10.0.1",
42
+ "@semantic-release/github": "^12.0.2",
43
+ "conventional-changelog-conventionalcommits": "^9.1.0",
44
+ "semantic-release": "^25.0.2",
45
+ "zekr": "^1.2.0"
46
+ }
47
+ }
package/rescript.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "rescript-audio",
3
+ "sources": [
4
+ {
5
+ "dir": "src",
6
+ "subdirs": true
7
+ },
8
+ {
9
+ "dir": "tests",
10
+ "type": "dev"
11
+ }
12
+ ],
13
+ "package-specs": {
14
+ "module": "esmodule",
15
+ "in-source": true
16
+ },
17
+ "suffix": ".js",
18
+ "dependencies": [
19
+ "@rescript/core"
20
+ ],
21
+ "dev-dependencies": [
22
+ "zekr"
23
+ ]
24
+ }
package/src/Audio.js ADDED
@@ -0,0 +1,294 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
5
+
6
+ function state(ctx) {
7
+ let match = ctx.state;
8
+ switch (match) {
9
+ case "closed" :
10
+ return "Closed";
11
+ case "running" :
12
+ return "Running";
13
+ default:
14
+ return "Suspended";
15
+ }
16
+ }
17
+
18
+ let Context = {
19
+ state: state
20
+ };
21
+
22
+ let Param = {};
23
+
24
+ let Node = {};
25
+
26
+ let Destination = {};
27
+
28
+ function make(ctx, options) {
29
+ let gainNode = ctx.createGain();
30
+ if (options !== undefined) {
31
+ Stdlib_Option.forEach(options.gain, g => {
32
+ gainNode.gain.value = g;
33
+ });
34
+ }
35
+ return gainNode;
36
+ }
37
+
38
+ let Gain = {
39
+ make: make
40
+ };
41
+
42
+ function waveformToString(w) {
43
+ switch (w) {
44
+ case "Sine" :
45
+ return "sine";
46
+ case "Square" :
47
+ return "square";
48
+ case "Sawtooth" :
49
+ return "sawtooth";
50
+ case "Triangle" :
51
+ return "triangle";
52
+ case "Custom" :
53
+ return "custom";
54
+ }
55
+ }
56
+
57
+ function waveformFromString(s) {
58
+ switch (s) {
59
+ case "custom" :
60
+ return "Custom";
61
+ case "sawtooth" :
62
+ return "Sawtooth";
63
+ case "square" :
64
+ return "Square";
65
+ case "triangle" :
66
+ return "Triangle";
67
+ default:
68
+ return "Sine";
69
+ }
70
+ }
71
+
72
+ function make$1(ctx, options) {
73
+ let osc = ctx.createOscillator();
74
+ if (options !== undefined) {
75
+ Stdlib_Option.forEach(options.waveform, w => {
76
+ osc.type = waveformToString(w);
77
+ });
78
+ Stdlib_Option.forEach(options.frequency, f => {
79
+ osc.frequency.value = f;
80
+ });
81
+ Stdlib_Option.forEach(options.detune, d => {
82
+ osc.detune.value = d;
83
+ });
84
+ }
85
+ return osc;
86
+ }
87
+
88
+ function waveform(osc) {
89
+ return waveformFromString(osc.type);
90
+ }
91
+
92
+ function setWaveform(osc, w) {
93
+ osc.type = waveformToString(w);
94
+ }
95
+
96
+ let Oscillator = {
97
+ waveformToString: waveformToString,
98
+ waveformFromString: waveformFromString,
99
+ make: make$1,
100
+ waveform: waveform,
101
+ setWaveform: setWaveform
102
+ };
103
+
104
+ function filterTypeToString(ft) {
105
+ switch (ft) {
106
+ case "Lowpass" :
107
+ return "lowpass";
108
+ case "Highpass" :
109
+ return "highpass";
110
+ case "Bandpass" :
111
+ return "bandpass";
112
+ case "Lowshelf" :
113
+ return "lowshelf";
114
+ case "Highshelf" :
115
+ return "highshelf";
116
+ case "Peaking" :
117
+ return "peaking";
118
+ case "Notch" :
119
+ return "notch";
120
+ case "Allpass" :
121
+ return "allpass";
122
+ }
123
+ }
124
+
125
+ function filterTypeFromString(s) {
126
+ switch (s) {
127
+ case "allpass" :
128
+ return "Allpass";
129
+ case "bandpass" :
130
+ return "Bandpass";
131
+ case "highpass" :
132
+ return "Highpass";
133
+ case "highshelf" :
134
+ return "Highshelf";
135
+ case "lowshelf" :
136
+ return "Lowshelf";
137
+ case "notch" :
138
+ return "Notch";
139
+ case "peaking" :
140
+ return "Peaking";
141
+ default:
142
+ return "Lowpass";
143
+ }
144
+ }
145
+
146
+ function make$2(ctx, options) {
147
+ let filter = ctx.createBiquadFilter();
148
+ if (options !== undefined) {
149
+ Stdlib_Option.forEach(options.filterType, ft => {
150
+ filter.type = filterTypeToString(ft);
151
+ });
152
+ Stdlib_Option.forEach(options.frequency, f => {
153
+ filter.frequency.value = f;
154
+ });
155
+ Stdlib_Option.forEach(options.detune, d => {
156
+ filter.detune.value = d;
157
+ });
158
+ Stdlib_Option.forEach(options.q, qVal => {
159
+ filter.Q.value = qVal;
160
+ });
161
+ Stdlib_Option.forEach(options.gain, g => {
162
+ filter.gain.value = g;
163
+ });
164
+ }
165
+ return filter;
166
+ }
167
+
168
+ function filterType(filter) {
169
+ return filterTypeFromString(filter.type);
170
+ }
171
+
172
+ function setFilterType(filter, ft) {
173
+ filter.type = filterTypeToString(ft);
174
+ }
175
+
176
+ let BiquadFilter = {
177
+ filterTypeToString: filterTypeToString,
178
+ filterTypeFromString: filterTypeFromString,
179
+ make: make$2,
180
+ filterType: filterType,
181
+ setFilterType: setFilterType
182
+ };
183
+
184
+ function make$3(ctx, options) {
185
+ let maxDelay = Stdlib_Option.flatMap(options, o => o.maxDelayTime);
186
+ let delay = ctx.createDelay(maxDelay !== undefined ? Primitive_option.valFromOption(maxDelay) : undefined);
187
+ if (options !== undefined) {
188
+ Stdlib_Option.forEach(options.delayTime, dt => {
189
+ delay.delayTime.value = dt;
190
+ });
191
+ }
192
+ return delay;
193
+ }
194
+
195
+ let Delay = {
196
+ make: make$3
197
+ };
198
+
199
+ function make$4(ctx, options) {
200
+ let comp = ctx.createDynamicsCompressor();
201
+ if (options !== undefined) {
202
+ Stdlib_Option.forEach(options.threshold, t => {
203
+ comp.threshold.value = t;
204
+ });
205
+ Stdlib_Option.forEach(options.knee, k => {
206
+ comp.knee.value = k;
207
+ });
208
+ Stdlib_Option.forEach(options.ratio, r => {
209
+ comp.ratio.value = r;
210
+ });
211
+ Stdlib_Option.forEach(options.attack, a => {
212
+ comp.attack.value = a;
213
+ });
214
+ Stdlib_Option.forEach(options.release, rel => {
215
+ comp.release.value = rel;
216
+ });
217
+ }
218
+ return comp;
219
+ }
220
+
221
+ let Compressor = {
222
+ make: make$4
223
+ };
224
+
225
+ function make$5(ctx, options) {
226
+ let panner = ctx.createStereoPanner();
227
+ if (options !== undefined) {
228
+ Stdlib_Option.forEach(options.pan, p => {
229
+ panner.pan.value = p;
230
+ });
231
+ }
232
+ return panner;
233
+ }
234
+
235
+ let StereoPanner = {
236
+ make: make$5
237
+ };
238
+
239
+ function make$6(ctx, options) {
240
+ let analyser = ctx.createAnalyser();
241
+ if (options !== undefined) {
242
+ Stdlib_Option.forEach(options.fftSize, size => {
243
+ analyser.fftSize = size;
244
+ });
245
+ Stdlib_Option.forEach(options.minDecibels, min => {
246
+ analyser.minDecibels = min;
247
+ });
248
+ Stdlib_Option.forEach(options.maxDecibels, max => {
249
+ analyser.maxDecibels = max;
250
+ });
251
+ Stdlib_Option.forEach(options.smoothingTimeConstant, s => {
252
+ analyser.smoothingTimeConstant = s;
253
+ });
254
+ }
255
+ return analyser;
256
+ }
257
+
258
+ let Analyser = {
259
+ make: make$6
260
+ };
261
+
262
+ let ContextExt = {};
263
+
264
+ function chain(nodes) {
265
+ for (let i = 0, i_finish = nodes.length - 2 | 0; i <= i_finish; ++i) {
266
+ Stdlib_Option.getOrThrow(nodes[i], undefined).connect(Stdlib_Option.getOrThrow(nodes[i + 1 | 0], undefined));
267
+ }
268
+ }
269
+
270
+ function toDestination(ctx, node) {
271
+ node.connect(ctx.destination);
272
+ }
273
+
274
+ let Utils = {
275
+ chain: chain,
276
+ toDestination: toDestination
277
+ };
278
+
279
+ export {
280
+ Context,
281
+ Param,
282
+ Node,
283
+ Destination,
284
+ Gain,
285
+ Oscillator,
286
+ BiquadFilter,
287
+ Delay,
288
+ Compressor,
289
+ StereoPanner,
290
+ Analyser,
291
+ ContextExt,
292
+ Utils,
293
+ }
294
+ /* No side effect */
package/src/Audio.res ADDED
@@ -0,0 +1,405 @@
1
+ // ReScript Web Audio API bindings
2
+ // Provides an idiomatic ReScript interface to the Web Audio API
3
+
4
+ module Context = {
5
+ type t
6
+
7
+ type state =
8
+ | Suspended
9
+ | Running
10
+ | Closed
11
+
12
+ type options = {
13
+ latencyHint?: string,
14
+ sampleRate?: float,
15
+ }
16
+
17
+ @new external make: unit => t = "AudioContext"
18
+ @new external makeWithOptions: options => t = "AudioContext"
19
+
20
+ @get external sampleRate: t => float = "sampleRate"
21
+ @get external currentTime: t => float = "currentTime"
22
+ @get external baseLatency: t => float = "baseLatency"
23
+
24
+ @get external stateString: t => string = "state"
25
+
26
+ let state = (ctx: t): state => {
27
+ switch stateString(ctx) {
28
+ | "suspended" => Suspended
29
+ | "running" => Running
30
+ | "closed" => Closed
31
+ | _ => Suspended
32
+ }
33
+ }
34
+
35
+ @send external resume: t => promise<unit> = "resume"
36
+ @send external suspend: t => promise<unit> = "suspend"
37
+ @send external close: t => promise<unit> = "close"
38
+ }
39
+
40
+ module Param = {
41
+ type t
42
+
43
+ @get external value: t => float = "value"
44
+ @set external setValue: (t, float) => unit = "value"
45
+ @get external defaultValue: t => float = "defaultValue"
46
+ @get external minValue: t => float = "minValue"
47
+ @get external maxValue: t => float = "maxValue"
48
+
49
+ @send
50
+ external setValueAtTime: (t, ~value: float, ~startTime: float) => t = "setValueAtTime"
51
+
52
+ @send
53
+ external linearRampToValueAtTime: (t, ~value: float, ~endTime: float) => t =
54
+ "linearRampToValueAtTime"
55
+
56
+ @send
57
+ external exponentialRampToValueAtTime: (t, ~value: float, ~endTime: float) => t =
58
+ "exponentialRampToValueAtTime"
59
+
60
+ @send
61
+ external setTargetAtTime: (t, ~target: float, ~startTime: float, ~timeConstant: float) => t =
62
+ "setTargetAtTime"
63
+
64
+ @send
65
+ external setValueCurveAtTime: (t, ~values: array<float>, ~startTime: float, ~duration: float) => t =
66
+ "setValueCurveAtTime"
67
+
68
+ @send external cancelScheduledValues: (t, ~cancelTime: float) => t = "cancelScheduledValues"
69
+
70
+ @send
71
+ external cancelAndHoldAtTime: (t, ~cancelTime: float) => t = "cancelAndHoldAtTime"
72
+ }
73
+
74
+ module Node = {
75
+ type t
76
+
77
+ @get external context: t => Context.t = "context"
78
+ @get external numberOfInputs: t => int = "numberOfInputs"
79
+ @get external numberOfOutputs: t => int = "numberOfOutputs"
80
+ @get external channelCount: t => int = "channelCount"
81
+ @set external setChannelCount: (t, int) => unit = "channelCount"
82
+
83
+ @send external connect: (t, t) => t = "connect"
84
+ @send external connectToParam: (t, Param.t) => unit = "connect"
85
+ @send external disconnect: t => unit = "disconnect"
86
+ @send external disconnectFrom: (t, t) => unit = "disconnect"
87
+ }
88
+
89
+ module Destination = {
90
+ type t
91
+
92
+ @get external maxChannelCount: t => int = "maxChannelCount"
93
+
94
+ external asNode: t => Node.t = "%identity"
95
+ }
96
+
97
+ module Gain = {
98
+ type t
99
+
100
+ type options = {gain?: float}
101
+
102
+ @send external _make: Context.t => t = "createGain"
103
+ @get external gainParam: t => Param.t = "gain"
104
+
105
+ let make = (ctx: Context.t, ~options: option<options>=?): t => {
106
+ let gainNode = _make(ctx)
107
+ switch options {
108
+ | Some(opts) => opts.gain->Option.forEach(g => Param.setValue(gainParam(gainNode), g))
109
+ | None => ()
110
+ }
111
+ gainNode
112
+ }
113
+
114
+ external asNode: t => Node.t = "%identity"
115
+ }
116
+
117
+ module Oscillator = {
118
+ type t
119
+
120
+ type waveform =
121
+ | Sine
122
+ | Square
123
+ | Sawtooth
124
+ | Triangle
125
+ | Custom
126
+
127
+ type options = {
128
+ waveform?: waveform,
129
+ frequency?: float,
130
+ detune?: float,
131
+ }
132
+
133
+ let waveformToString = (w: waveform): string => {
134
+ switch w {
135
+ | Sine => "sine"
136
+ | Square => "square"
137
+ | Sawtooth => "sawtooth"
138
+ | Triangle => "triangle"
139
+ | Custom => "custom"
140
+ }
141
+ }
142
+
143
+ let waveformFromString = (s: string): waveform => {
144
+ switch s {
145
+ | "sine" => Sine
146
+ | "square" => Square
147
+ | "sawtooth" => Sawtooth
148
+ | "triangle" => Triangle
149
+ | "custom" => Custom
150
+ | _ => Sine
151
+ }
152
+ }
153
+
154
+ @send external _make: Context.t => t = "createOscillator"
155
+ @get external frequencyParam: t => Param.t = "frequency"
156
+ @get external detuneParam: t => Param.t = "detune"
157
+ @get external _getType: t => string = "type"
158
+ @set external _setType: (t, string) => unit = "type"
159
+
160
+ let make = (ctx: Context.t, ~options: option<options>=?): t => {
161
+ let osc = _make(ctx)
162
+ switch options {
163
+ | Some(opts) => {
164
+ opts.waveform->Option.forEach(w => _setType(osc, waveformToString(w)))
165
+ opts.frequency->Option.forEach(f => Param.setValue(frequencyParam(osc), f))
166
+ opts.detune->Option.forEach(d => Param.setValue(detuneParam(osc), d))
167
+ }
168
+ | None => ()
169
+ }
170
+ osc
171
+ }
172
+
173
+ let waveform = (osc: t): waveform => waveformFromString(_getType(osc))
174
+
175
+ let setWaveform = (osc: t, w: waveform): unit => _setType(osc, waveformToString(w))
176
+
177
+ @send external start: t => unit = "start"
178
+ @send external startAt: (t, float) => unit = "start"
179
+ @send external stop: t => unit = "stop"
180
+ @send external stopAt: (t, float) => unit = "stop"
181
+
182
+ external asNode: t => Node.t = "%identity"
183
+ }
184
+
185
+ module BiquadFilter = {
186
+ type t
187
+
188
+ type filterType =
189
+ | Lowpass
190
+ | Highpass
191
+ | Bandpass
192
+ | Lowshelf
193
+ | Highshelf
194
+ | Peaking
195
+ | Notch
196
+ | Allpass
197
+
198
+ let filterTypeToString = (ft: filterType): string => {
199
+ switch ft {
200
+ | Lowpass => "lowpass"
201
+ | Highpass => "highpass"
202
+ | Bandpass => "bandpass"
203
+ | Lowshelf => "lowshelf"
204
+ | Highshelf => "highshelf"
205
+ | Peaking => "peaking"
206
+ | Notch => "notch"
207
+ | Allpass => "allpass"
208
+ }
209
+ }
210
+
211
+ let filterTypeFromString = (s: string): filterType => {
212
+ switch s {
213
+ | "lowpass" => Lowpass
214
+ | "highpass" => Highpass
215
+ | "bandpass" => Bandpass
216
+ | "lowshelf" => Lowshelf
217
+ | "highshelf" => Highshelf
218
+ | "peaking" => Peaking
219
+ | "notch" => Notch
220
+ | "allpass" => Allpass
221
+ | _ => Lowpass
222
+ }
223
+ }
224
+
225
+ type options = {
226
+ filterType?: filterType,
227
+ frequency?: float,
228
+ detune?: float,
229
+ q?: float,
230
+ gain?: float,
231
+ }
232
+
233
+ @send external _make: Context.t => t = "createBiquadFilter"
234
+ @get external frequencyParam: t => Param.t = "frequency"
235
+ @get external detuneParam: t => Param.t = "detune"
236
+ @get external qParam: t => Param.t = "Q"
237
+ @get external gainParam: t => Param.t = "gain"
238
+ @get external _getType: t => string = "type"
239
+ @set external _setType: (t, string) => unit = "type"
240
+
241
+ let make = (ctx: Context.t, ~options: option<options>=?): t => {
242
+ let filter = _make(ctx)
243
+ switch options {
244
+ | Some(opts) => {
245
+ opts.filterType->Option.forEach(ft => _setType(filter, filterTypeToString(ft)))
246
+ opts.frequency->Option.forEach(f => Param.setValue(frequencyParam(filter), f))
247
+ opts.detune->Option.forEach(d => Param.setValue(detuneParam(filter), d))
248
+ opts.q->Option.forEach(qVal => Param.setValue(qParam(filter), qVal))
249
+ opts.gain->Option.forEach(g => Param.setValue(gainParam(filter), g))
250
+ }
251
+ | None => ()
252
+ }
253
+ filter
254
+ }
255
+
256
+ let filterType = (filter: t): filterType => filterTypeFromString(_getType(filter))
257
+ let setFilterType = (filter: t, ft: filterType): unit => _setType(filter, filterTypeToString(ft))
258
+
259
+ external asNode: t => Node.t = "%identity"
260
+ }
261
+
262
+ module Delay = {
263
+ type t
264
+
265
+ type options = {
266
+ maxDelayTime?: float,
267
+ delayTime?: float,
268
+ }
269
+
270
+ @send external _make: (Context.t, ~maxDelayTime: float=?) => t = "createDelay"
271
+ @get external delayTimeParam: t => Param.t = "delayTime"
272
+
273
+ let make = (ctx: Context.t, ~options: option<options>=?): t => {
274
+ let maxDelay = options->Option.flatMap(o => o.maxDelayTime)
275
+ let delay = _make(ctx, ~maxDelayTime=?maxDelay)
276
+ switch options {
277
+ | Some(opts) =>
278
+ opts.delayTime->Option.forEach(dt => Param.setValue(delayTimeParam(delay), dt))
279
+ | None => ()
280
+ }
281
+ delay
282
+ }
283
+
284
+ external asNode: t => Node.t = "%identity"
285
+ }
286
+
287
+ module Compressor = {
288
+ type t
289
+
290
+ type options = {
291
+ threshold?: float,
292
+ knee?: float,
293
+ ratio?: float,
294
+ attack?: float,
295
+ release?: float,
296
+ }
297
+
298
+ @send external _make: Context.t => t = "createDynamicsCompressor"
299
+ @get external thresholdParam: t => Param.t = "threshold"
300
+ @get external kneeParam: t => Param.t = "knee"
301
+ @get external ratioParam: t => Param.t = "ratio"
302
+ @get external reductionDb: t => float = "reduction"
303
+ @get external attackParam: t => Param.t = "attack"
304
+ @get external releaseParam: t => Param.t = "release"
305
+
306
+ let make = (ctx: Context.t, ~options: option<options>=?): t => {
307
+ let comp = _make(ctx)
308
+ switch options {
309
+ | Some(opts) => {
310
+ opts.threshold->Option.forEach(t => Param.setValue(thresholdParam(comp), t))
311
+ opts.knee->Option.forEach(k => Param.setValue(kneeParam(comp), k))
312
+ opts.ratio->Option.forEach(r => Param.setValue(ratioParam(comp), r))
313
+ opts.attack->Option.forEach(a => Param.setValue(attackParam(comp), a))
314
+ opts.release->Option.forEach(rel => Param.setValue(releaseParam(comp), rel))
315
+ }
316
+ | None => ()
317
+ }
318
+ comp
319
+ }
320
+
321
+ external asNode: t => Node.t = "%identity"
322
+ }
323
+
324
+ module StereoPanner = {
325
+ type t
326
+
327
+ type options = {pan?: float}
328
+
329
+ @send external _make: Context.t => t = "createStereoPanner"
330
+ @get external panParam: t => Param.t = "pan"
331
+
332
+ let make = (ctx: Context.t, ~options: option<options>=?): t => {
333
+ let panner = _make(ctx)
334
+ switch options {
335
+ | Some(opts) => opts.pan->Option.forEach(p => Param.setValue(panParam(panner), p))
336
+ | None => ()
337
+ }
338
+ panner
339
+ }
340
+
341
+ external asNode: t => Node.t = "%identity"
342
+ }
343
+
344
+ module Analyser = {
345
+ type t
346
+
347
+ type options = {
348
+ fftSize?: int,
349
+ minDecibels?: float,
350
+ maxDecibels?: float,
351
+ smoothingTimeConstant?: float,
352
+ }
353
+
354
+ @send external _make: Context.t => t = "createAnalyser"
355
+ @get external fftSize: t => int = "fftSize"
356
+ @set external setFftSize: (t, int) => unit = "fftSize"
357
+ @get external frequencyBinCount: t => int = "frequencyBinCount"
358
+ @get external minDecibels: t => float = "minDecibels"
359
+ @set external setMinDecibels: (t, float) => unit = "minDecibels"
360
+ @get external maxDecibels: t => float = "maxDecibels"
361
+ @set external setMaxDecibels: (t, float) => unit = "maxDecibels"
362
+ @get external smoothingTimeConstant: t => float = "smoothingTimeConstant"
363
+ @set external setSmoothingTimeConstant: (t, float) => unit = "smoothingTimeConstant"
364
+
365
+ let make = (ctx: Context.t, ~options: option<options>=?): t => {
366
+ let analyser = _make(ctx)
367
+ switch options {
368
+ | Some(opts) => {
369
+ opts.fftSize->Option.forEach(size => setFftSize(analyser, size))
370
+ opts.minDecibels->Option.forEach(min => setMinDecibels(analyser, min))
371
+ opts.maxDecibels->Option.forEach(max => setMaxDecibels(analyser, max))
372
+ opts.smoothingTimeConstant->Option.forEach(s => setSmoothingTimeConstant(analyser, s))
373
+ }
374
+ | None => ()
375
+ }
376
+ analyser
377
+ }
378
+
379
+ @send external getFloatFrequencyData: (t, Float32Array.t) => unit = "getFloatFrequencyData"
380
+ @send external getByteFrequencyData: (t, Uint8Array.t) => unit = "getByteFrequencyData"
381
+ @send external getFloatTimeDomainData: (t, Float32Array.t) => unit = "getFloatTimeDomainData"
382
+ @send external getByteTimeDomainData: (t, Uint8Array.t) => unit = "getByteTimeDomainData"
383
+
384
+ external asNode: t => Node.t = "%identity"
385
+ }
386
+
387
+ // Context extensions for getting destination and creating nodes
388
+ module ContextExt = {
389
+ @get external destination: Context.t => Destination.t = "destination"
390
+ }
391
+
392
+ // Utility functions for common patterns
393
+ module Utils = {
394
+ // Connect a chain of nodes
395
+ let chain = (nodes: array<Node.t>): unit => {
396
+ for i in 0 to Array.length(nodes) - 2 {
397
+ let _ = Node.connect(nodes[i]->Option.getOrThrow, nodes[i + 1]->Option.getOrThrow)
398
+ }
399
+ }
400
+
401
+ // Connect a node to the destination
402
+ let toDestination = (ctx: Context.t, node: Node.t): unit => {
403
+ let _ = Node.connect(node, ContextExt.destination(ctx)->Destination.asNode)
404
+ }
405
+ }