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 +227 -0
- package/package.json +47 -0
- package/rescript.json +24 -0
- package/src/Audio.js +294 -0
- package/src/Audio.res +405 -0
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
|
+
}
|