Multi Channel Distortion

A example of working with the Audio engine

Introduction

In this tutorial we will be building a unit which uses the audio engine. Its going to be a three-band distortion filter. The idea being that we want to be able to apply distortion to the High, Mid and Low frequency bands.

The audio circuit will look like this

Each gain and WaveShaper will be controllable. We will hardcode the vales for the HighMidLowFilters.

Note

We will be using the Deno runtime to run a typescript program. You can learn about how to set this up in the How To Make A Unit tutorial

The Group Description

0
1
2
3
4
{
	"printName": "Custom Audio",
	"inDevelopment": true,
	"subListWidth": 160.0
}

Pretty straightforward, though we set the "inDevelopment" entry to true while we're still developing the unit. We also set the "subListWidth" value to 160, in order to accommodate the title of the unit.

The Unit Description

Next lets move onto the unit description itself. I first designed the layout of this unit in my head, then began creating the file, recreating the unit and iterating the design as I went along before arriving at what you see below. This is a rather slow process involving many restarts of Alchemy. Alchemy's roadmap includes a better way to design units, but for now we're stuck with this process.

I recommend first designing your unit in a vector-based design program, then copying the element details over. Though for this unit I've done all the work for you.

0
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
{
	"inDevelopment": true,
	"modelName": "multi_channel_distortion",
	"printName": "Multi Channel Distortion",
	"space": [
		{"x": 0.0, "y": 0.0},
		{"x": 0.0, "y": 150.0},
		{"x": 155.0, "y": 150.0},
		{"x": 155.0, "y": 0.0}
	],
	"collisionActive": true,
	"executable": {
		"mac": "../deno",
		"win": "../deno.exe",
		"args": ["run", "--allow-read", "main.ts"]
	},
	"base": [
		{
			"name": "backing",
			"attribute": {
				"type": "rectangle",
				"content": {
					"width": 155.0,
					"height": 150.0,
					"colour": {"r": 0.25, "g": 0.25, "b": 0.25}
				}
			}
		},
		{
			"name": "frame",
			"attribute": {
				"type": "rectangle",
				"content": {
					"x": 5.0,
					"y": 5.0,
					"width": 145.0,
					"height": 125.0,
					"colour": {"r": 0.35, "g": 0.35, "b": 0.35}
				}
			}
		},
		{
			"name": "join_in",
			"attribute": {
				"type": "path",
				"content": {
					"x": 10.0,
					"y": 25.0,
					"points": [{"x":0.0, "y":0.0}, {"x":0.0, "y":70.0}],
					"thickness": 2.5,
					"colour": {"r": 0.45, "g": 0.45, "b": 0.45}
				}
			}
		},
		{
			"name": "join_out",
			"attribute": {
				"type": "path",
				"content": {
					"x": 145.0,
					"y": 25.0,
					"points": [{"x":0.0, "y":0.0}, {"x":0.0, "y":70.0}],
					"thickness": 2.5,
					"colour": {"r": 0.45, "g": 0.45, "b": 0.45}
				}
			}
		},
		{
			"name": "channel_1",
			"attribute": {
				"type": "path",
				"content": {
					"x": 10.0,
					"y": 25.0,
					"points": [{"x":0.0, "y":0.0}, {"x":135.0, "y":0.0}],
					"thickness": 2.5,
					"colour": {"r": 0.45, "g": 0.45, "b": 0.45}
				}
			}
		},
		{
			"name": "channel_2",
			"attribute": {
				"type": "path",
				"content": {
					"x": 0.0,
					"y": 60.0,
					"points": [{"x":0.0, "y":0.0}, {"x":155.0, "y":0.0}],
					"thickness": 2.5,
					"colour": {"r": 0.45, "g": 0.45, "b": 0.45}
				}
			}
		},
		{
			"name": "channel_3",
			"attribute": {
				"type": "path",
				"content": {
					"x": 10.0,
					"y": 95.0,
					"points": [{"x":0.0, "y":0.0}, {"x":135.0, "y":0.0}],
					"thickness": 2.5,
					"colour": {"r": 0.45, "g": 0.45, "b": 0.45}
				}
			}
		},
		{
			"name": "group",
			"attribute": {
				"type": "rectangle",
				"content": {
					"x": 40.0,
					"y": 7.5,
					"width": 75.0,
					"height": 120.0,
					"colour": {"r": 1, "g": 1, "b": 1, "a": 0.25}
				}
			}
		},
		{
			"name": "logo",
			"attribute": {
				"type": "characterString",
				"content": {
					"x": 5.0,
					"y": 145.0,
					"scale": 0.6,
					"characterString": "Custom Audio",
					"interCharacterSpacing": 1.0,
					"colour": {"r": 1, "g": 1, "b": 1}
				}
			}
		},
		{
			"name": "model",
			"attribute": {
				"type": "characterString",
				"content": {
					"x": 150.0,
					"y": 145.0,
					"scale": 0.4,
					"characterString": "Multi Channel Distortion",
					"interCharacterSpacing": 1.0,
					"printingModeHorizontal": "right",
					"colour": {"r": 1, "g": 1, "b": 1}
				}
			}
		},
		{
			"name": "out_gain_text",
			"attribute": {
				"type": "characterString",
				"content": {
					"x": 25.0,
					"y": 117.5,
					"scale": 0.4,
					"characterString": "out gain",
					"printingModeHorizontal": "middle",
					"printingModeVertical": "calculatedTop",
					"interCharacterSpacing": 1.0,
					"colour": {"r": 0, "g": 0, "b": 0}
				}
			}
		},
		{
			"name": "distortion",
			"attribute": {
				"type": "characterString",
				"content": {
					"x": 60.0,
					"y": 117.5,
					"scale": 0.4,
					"characterString": "distortion",
					"printingModeHorizontal": "middle",
					"printingModeVertical": "calculatedTop",
					"interCharacterSpacing": 1.0,
					"colour": {"r": 0, "g": 0, "b": 0}
				}
			}
		},
		{
			"name": "resolution",
			"attribute": {
				"type": "characterString",
				"content": {
					"x": 95.0,
					"y": 117.5,
					"scale": 0.4,
					"characterString": "resolution",
					"printingModeHorizontal": "middle",
					"printingModeVertical": "calculatedTop",
					"interCharacterSpacing": 1.0,
					"colour": {"r": 0, "g": 0, "b": 0}
				}
			}
		},
		{
			"name": "in_gain_text",
			"attribute": {
				"type": "characterString",
				"content": {
					"x": 130.0,
					"y": 117.5,
					"scale": 0.4,
					"characterString": "in gain",
					"printingModeHorizontal": "middle",
					"printingModeVertical": "calculatedTop",
					"interCharacterSpacing": 1.0,
					"colour": {"r": 0, "g": 0, "b": 0}
				}
			}
		}
	],
	"parts": {
		"back": [
			{
				"name": "audio_in",
				"attribute": {
					"type": "audioPort",
					"content": {
						"x": 0.0,
						"y": 60.0,
						"height": 15.0,
						"width": 5.0,
						"angle": 3.1415927,
						"anchor": {"x":0, "y":0.5},
						"mode": "producer"
					}
				}
			},
			{
				"name": "audio_out",
				"attribute": {
					"type": "audioPort",
					"content": {
						"x": 155.0,
						"y": 60.0,
						"height": 15.0,
						"width": 5.0,
						"anchor": {"x":0, "y":0.5},
						"mode": "consumer"
					}
				}
			}
		],
		"front": [
			{
				"name": "gain_out_1",
				"attribute": {
					"type": "dialContinuousDesign1",
					"content": {
						"x": 25.0,
						"y": 25.0,
						"radius": 10.0,
						"resetValue": 0.5,
						"styleSlot": {"r": 0.25, "g": 0.02, "b": 0.02},
						"styleHandle": {"r": 0.65, "g": 0.65, "b": 0.65},
						"styleNeedle": {"r": 0.95, "g": 0.95, "b": 0.95},
						"styleInteractionDisk": {"r": 0.25, "g": 0.25, "b": 0.25, "a": 0.25}
					}
				}
			},
			{
				"name": "gain_out_2",
				"attribute": {
					"type": "dialContinuousDesign1",
					"content": {
						"x": 25.0,
						"y": 60.0,
						"radius": 10.0,
						"resetValue": 0.5,
						"styleSlot": {"r": 0.25, "g": 0.02, "b": 0.02},
						"styleHandle": {"r": 0.65, "g": 0.65, "b": 0.65},
						"styleNeedle": {"r": 0.95, "g": 0.95, "b": 0.95},
						"styleInteractionDisk": {"r": 0.25, "g": 0.25, "b": 0.25, "a": 0.25}
					}
				}
			},
			{
				"name": "gain_out_3",
				"attribute": {
					"type": "dialContinuousDesign1",
					"content": {
						"x": 25.0,
						"y": 95.0,
						"radius": 10.0,
						"resetValue": 0.5,
						"styleSlot": {"r": 0.25, "g": 0.02, "b": 0.02},
						"styleHandle": {"r": 0.65, "g": 0.65, "b": 0.65},
						"styleNeedle": {"r": 0.95, "g": 0.95, "b": 0.95},
						"styleInteractionDisk": {"r": 0.25, "g": 0.25, "b": 0.25, "a": 0.25}
					}
				}
			},
			{
				"name": "distortion_1",
				"attribute": {
					"type": "dialContinuousDesign1",
					"content": {
						"x": 60.0,
						"y": 25.0,
						"styleSlot": {"r": 0.25, "g": 0.02, "b": 0.02},
						"styleHandle": {"r": 0.65, "g": 0.65, "b": 0.65},
						"styleNeedle": {"r": 0.95, "g": 0.95, "b": 0.95},
						"styleInteractionDisk": {"r": 0.25, "g": 0.25, "b": 0.25, "a": 0.25}
					}
				}
			},
			{
				"name": "distortion_2",
				"attribute": {
					"type": "dialContinuousDesign1",
					"content": {
						"x": 60.0,
						"y": 60.0,
						"styleSlot": {"r": 0.25, "g": 0.02, "b": 0.02},
						"styleHandle": {"r": 0.65, "g": 0.65, "b": 0.65},
						"styleNeedle": {"r": 0.95, "g": 0.95, "b": 0.95},
						"styleInteractionDisk": {"r": 0.25, "g": 0.25, "b": 0.25, "a": 0.25}
					}
				}
			},
			{
				"name": "distortion_3",
				"attribute": {
					"type": "dialContinuousDesign1",
					"content": {
						"x": 60.0,
						"y": 95.0,
						"styleSlot": {"r": 0.25, "g": 0.02, "b": 0.02},
						"styleHandle": {"r": 0.65, "g": 0.65, "b": 0.65},
						"styleNeedle": {"r": 0.95, "g": 0.95, "b": 0.95},
						"styleInteractionDisk": {"r": 0.25, "g": 0.25, "b": 0.25, "a": 0.25}
					}
				}
			},
			{
				"name": "resolution_1",
				"attribute": {
					"type": "dialContinuousDesign1",
					"content": {
						"x": 95.0,
						"y": 25.0,
						"styleSlot": {"r": 0.25, "g": 0.02, "b": 0.02},
						"styleHandle": {"r": 0.65, "g": 0.65, "b": 0.65},
						"styleNeedle": {"r": 0.95, "g": 0.95, "b": 0.95},
						"styleInteractionDisk": {"r": 0.25, "g": 0.25, "b": 0.25, "a": 0.25}
					}
				}
			},
			{
				"name": "resolution_2",
				"attribute": {
					"type": "dialContinuousDesign1",
					"content": {
						"x": 95.0,
						"y": 60.0,
						"styleSlot": {"r": 0.25, "g": 0.02, "b": 0.02},
						"styleHandle": {"r": 0.65, "g": 0.65, "b": 0.65},
						"styleNeedle": {"r": 0.95, "g": 0.95, "b": 0.95},
						"styleInteractionDisk": {"r": 0.25, "g": 0.25, "b": 0.25, "a": 0.25}
					}
				}
			},
			{
				"name": "resolution_3",
				"attribute": {
					"type": "dialContinuousDesign1",
					"content": {
						"x": 95.0,
						"y": 95.0,
						"styleSlot": {"r": 0.25, "g": 0.02, "b": 0.02},
						"styleHandle": {"r": 0.65, "g": 0.65, "b": 0.65},
						"styleNeedle": {"r": 0.95, "g": 0.95, "b": 0.95},
						"styleInteractionDisk": {"r": 0.25, "g": 0.25, "b": 0.25, "a": 0.25}
					}
				}
			},
			{
				"name": "gain_in_1",
				"attribute": {
					"type": "dialContinuousDesign1",
					"content": {
						"x": 130.0,
						"y": 25.0,
						"radius": 10.0,
						"resetValue": 0.5,
						"styleSlot": {"r": 0.25, "g": 0.02, "b": 0.02},
						"styleHandle": {"r": 0.65, "g": 0.65, "b": 0.65},
						"styleNeedle": {"r": 0.95, "g": 0.95, "b": 0.95},
						"styleInteractionDisk": {"r": 0.25, "g": 0.25, "b": 0.25, "a": 0.25}
					}
				}
			},
			{
				"name": "gain_in_2",
				"attribute": {
					"type": "dialContinuousDesign1",
					"content": {
						"x": 130.0,
						"y": 60.0,
						"radius": 10.0,
						"resetValue": 0.5,
						"styleSlot": {"r": 0.25, "g": 0.02, "b": 0.02},
						"styleHandle": {"r": 0.65, "g": 0.65, "b": 0.65},
						"styleNeedle": {"r": 0.95, "g": 0.95, "b": 0.95},
						"styleInteractionDisk": {"r": 0.25, "g": 0.25, "b": 0.25, "a": 0.25}
					}
				}
			},
			{
				"name": "gain_in_3",
				"attribute": {
					"type": "dialContinuousDesign1",
					"content": {
						"x": 130.0,
						"y": 95.0,
						"radius": 10.0,
						"resetValue": 0.5,
						"styleSlot": {"r": 0.25, "g": 0.02, "b": 0.02},
						"styleHandle": {"r": 0.65, "g": 0.65, "b": 0.65},
						"styleNeedle": {"r": 0.95, "g": 0.95, "b": 0.95},
						"styleInteractionDisk": {"r": 0.25, "g": 0.25, "b": 0.25, "a": 0.25}
					}
				}
			}
		]
	}
}

Show More

This description should produce a unit that looks like this

There are no special requirements for enabling the audio engine. Audio ports are defined like any other port, except that they require a "mode" entry, set to either "consumer" or "producer". As the names suggest, "consumer" ports received audio streams while "producer" ports send it.

We now should have a file structure that looks like this.

custom_audio
├─ deno
├─ multi_channel_distortion
│  └─ index.unit
└─ typescript-deno-x.x.x-y.y.y

The Inner Workings

Unlike the graphical design, audio nodes are created at run time using the API. One consequence of this is that we must send a request for an audio node to be created then await a response containing the nodeId of this newly created node. We then use this nodeId to set up connections between nodes, or to modify the settings of a particular node.

For the state, we will be storing the value of the dials instead of the values those dials represent. This will make reading and writing those values easier when they come from the import listener, or from the undo/redo listeners. We will then be setting up a series of conversion functions which will be used to convert dial values to the actual functioning values (eg. to distortion amount, to gain amount, etc.)

This is set to be a rather large Typescript program, so lets try to split a few things out to help. First, lets set up a map of part names to their index numbers. We'll put this in a "constants.ts" file.

0
1
2
3
4
5
6
7
8
9
10
11
12
13
export const part = {
	gainOut1: 0,
	gainOut2: 1,
	gainOut3: 2,
	distortion1: 3,
	distortion2: 4,
	distortion3: 5,
	resolution1: 6,
	resolution2: 7,
	resolution3: 8,
	gainIn1: 9,
	gainIn2: 10,
	gainIn3: 11
};

Next we'll create a "types.ts" file to hold the interface definitions for the audio circuit nodes and the unit state itself. Note how the AudioCircuit's values are all optional. These will be filled in at runtime when the nodeIds of the requested nodes come in.

0
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
import type { NodeId } from "../typescript-deno-0.3.3-0.1.0/types/platform/audio/alpa/library/id/nodeId/index.ts";
 
export interface State {
	gainIn1: number,
	gainIn2: number,
	gainIn3: number,
	distortion1: number,
	distortion2: number,
	distortion3: number,
	resolution1: number,
	resolution2: number,
	resolution3: number,
	gainOut1: number,
	gainOut2: number,
	gainOut3: number
}
 
export interface AudioCircuit {
	streamDuplicator1: NodeId | undefined,
	streamDuplicator2: NodeId | undefined,
	highMidLowFilter1: NodeId | undefined,
	highMidLowFilter2: NodeId | undefined,
	highMidLowFilter3: NodeId | undefined,
	gainIn1: NodeId | undefined,
	gainIn2: NodeId | undefined,
	gainIn3: NodeId | undefined,
	waveShaper1: NodeId | undefined,
	waveShaper2: NodeId | undefined,
	waveShaper3: NodeId | undefined,
	gainOut1: NodeId | undefined,
	gainOut2: NodeId | undefined,
	gainOut3: NodeId | undefined,
	streamAdder3: NodeId | undefined
}

Show More

Finally, lets round things out with a "utils.ts" file. This will export two thing; a function to update the Exportable Data store, and object of functions which will be used to convert dial values to the values they represent.

0
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
import apiConsole from "../typescript-deno-0.3.3-0.1.0/index.ts";
 
import { State } from './types.ts';
 
export function updateExportableData(state:State) {
	apiConsole.unit.updateExportableData(
		JSON.stringify(state)
	);
}
 
function clamp(num:number, min:number, max:number): number {
	return num <= min ? min : num >= max ? max : num;
}
 
function normalizeStretchArray(array:number[]): number[] {
	//check for an empty array
		if(array.length === 0) { return array; }
 
	//discover the largest number
		let biggestValue = Math.max(
			...array.map((a) => Math.abs(a))
		);
 
	//divide everything by this largest number, making everything a ratio of this value 
		const outputArray = array.map((a) => a / biggestValue);
 
	//stretch the other side of the array to meet 0 or 1
		if(outputArray[0] === 0 && array[array.length-1] === 1){ return outputArray; }
		let pertinentValue = outputArray[0] !== 0 ? outputArray[0] : outputArray[array.length-1];
		for(let a = 0; a < outputArray.length; a++) {
			outputArray[a] = (outputArray[a] - pertinentValue) / (1.0 - pertinentValue);
		}
 
	return outputArray;
}
 
function sCurve(resolution:number, distortion:number): number[] {
	resolution = Math.round(resolution);
 
	if(distortion === 0) {
		return new Array(resolution).fill(0).map((_, index) => (index/resolution) * 2 - 1);
	}
 
	let curve = new Array(resolution)
		.fill(0)
		.map((_, index) => {
			const step = index/resolution;
			return 1/(
				1 + Math.exp(-distortion*(step-0.5))
			);
		}
	);
 
	curve = normalizeStretchArray(curve);
 
	for(let a = 0; a < curve.length; a++) {
		curve[a] = curve[a] * 2 - 1;
	}
 
	return curve;
}
 
export const interfaceTo = {
	gain: (gainDial:number):number => gainDial * 2,
	distortion: (distortionDial:number):number => distortionDial * 100.0,
	resolution: (resolutionDial:number):number => clamp((resolutionDial * 1000.0), 2, 1000),
	waveShaperCurve: (distortionDial:number, resolutionDial:number) => sCurve(interfaceTo.resolution(distortionDial), interfaceTo.distortion(resolutionDial)),
};

Show More

We now should have a file structure that looks like this.

custom_audio
├─ deno
├─ multi_channel_distortion
│  ├ constants.ts
│  ├ index.unit
│  ├ types.ts
│  └ utils.ts
└─ typescript-deno-x.x.x-y.y.y

Now, on to the main program.

The Main Program

The main.ts file has 5 sections.

  1. Defining the state
  2. Setup
  3. Mouse event handlers
  4. The import hander
  5. Undo / Redo handlers

Before all that though, lets import everything we need.

import apiConsole from "../typescript-deno-0.3.3-0.1.0/index.ts";
import { PartEvent } from '../typescript-deno-0.3.3-0.1.0/types/platform/interface/part/event/partEvent.ts';
import { Modification } from "../typescript-deno-0.3.3-0.1.0/types/platform/unit/unit/modification/index.ts";
 
import { part } from './constants.ts';
import { AudioCircuit, State } from './types.ts';
import { interfaceTo, updateExportableData } from './utils.ts';

Defining the state

This short section crates the "audioCircuit" and "state" variables using the types defined in the "types.ts" file. All the values of "audioCircuit" are set to undefined for now as we will populate them in the 'Setup' section. The "state" is set to the initial 'dial-values' of the design. Here that means that all the 'gain' and 'resolution' dials are set to 0.5

0
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
const audioCircuit:AudioCircuit = {
	streamDuplicator1: undefined,
	streamDuplicator2: undefined,
	highMidLowFilter1: undefined,
	highMidLowFilter2: undefined,
	highMidLowFilter3: undefined,
	gainIn1: undefined,
	gainIn2: undefined,
	gainIn3: undefined,
	waveShaper1: undefined,
	waveShaper2: undefined,
	waveShaper3: undefined,
	gainOut1: undefined,
	gainOut2: undefined,
	gainOut3: undefined,
	streamAdder3: undefined
}
 
let state:State = {
	gainIn1: 0.5,
	gainIn2: 0.5,
	gainIn3: 0.5,
	distortion1: 0.0,
	distortion2: 0.0,
	distortion3: 0.0,
	resolution1: 0.5,
	resolution2: 0.5,
	resolution3: 0.5,
	gainOut1: 0.5,
	gainOut2: 0.5,
	gainOut3: 0.5
};

Show More

Setup

Here we create the audio nodes, connect them together, give them their initial settings and initialise the dials.

The ApiConsole's "alpa.createNode" command comes with a callback function which will be run when the nodeId for the requested audio node is returned from Alchemy. So, we can use this to populate the appropriate entry in the "audioCircuit" constant. We also run a "updateAudioConnections" function. This function will only allow itself to be run when all the entries of the "audioCircuit" constant have been populated. The function will then set up all the audio routing and set the values of the audio nodes. You will notice in that second section where the values are applied, that we use the conversion functions defined in the "utils.ts" file.

Under the "updateAudioConnections" function, we can see all the dials being initialised with the values from the "state" variable.

0
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
//audio circuit
	apiConsole.alpa.createNode({ "type": "StreamDuplicator" }, (node_id) => {audioCircuit.streamDuplicator1 = node_id; updateAudioConnections();});
	apiConsole.alpa.createNode({ "type": "StreamDuplicator" }, (node_id) => {audioCircuit.streamDuplicator2 = node_id; updateAudioConnections();});
	apiConsole.alpa.createNode({ "type": "HighMidLowFilter" }, (node_id) => {audioCircuit.highMidLowFilter1 = node_id; updateAudioConnections();});
	apiConsole.alpa.createNode({ "type": "HighMidLowFilter" }, (node_id) => {audioCircuit.highMidLowFilter2 = node_id; updateAudioConnections();});
	apiConsole.alpa.createNode({ "type": "HighMidLowFilter" }, (node_id) => {audioCircuit.highMidLowFilter3 = node_id; updateAudioConnections();});
	apiConsole.alpa.createNode({ "type": "Gain" }, (node_id) => {audioCircuit.gainIn1 = node_id; updateAudioConnections();});
	apiConsole.alpa.createNode({ "type": "Gain" }, (node_id) => {audioCircuit.gainIn2 = node_id; updateAudioConnections();});
	apiConsole.alpa.createNode({ "type": "Gain" }, (node_id) => {audioCircuit.gainIn3 = node_id; updateAudioConnections();});
	apiConsole.alpa.createNode({ "type": "WaveShaper" }, (node_id) => {audioCircuit.waveShaper1 = node_id; updateAudioConnections();});
	apiConsole.alpa.createNode({ "type": "WaveShaper" }, (node_id) => {audioCircuit.waveShaper2 = node_id; updateAudioConnections();});
	apiConsole.alpa.createNode({ "type": "WaveShaper" }, (node_id) => {audioCircuit.waveShaper3 = node_id; updateAudioConnections();});
	apiConsole.alpa.createNode({ "type": "Gain" }, (node_id) => {audioCircuit.gainOut1 = node_id; updateAudioConnections();});
	apiConsole.alpa.createNode({ "type": "Gain" }, (node_id) => {audioCircuit.gainOut2 = node_id; updateAudioConnections();});
	apiConsole.alpa.createNode({ "type": "Gain" }, (node_id) => {audioCircuit.gainOut3 = node_id; updateAudioConnections();});
	apiConsole.alpa.createNode({ "type": "StreamAdder3" }, (node_id) => {audioCircuit.streamAdder3 = node_id; updateAudioConnections();});
 
	function updateAudioConnections() {
		if(
			audioCircuit.streamDuplicator1 === undefined ||
			audioCircuit.streamDuplicator2 === undefined ||
			audioCircuit.highMidLowFilter1 === undefined ||
			audioCircuit.highMidLowFilter2 === undefined ||
			audioCircuit.highMidLowFilter3 === undefined ||
			audioCircuit.gainIn1 === undefined ||
			audioCircuit.gainIn2 === undefined ||
			audioCircuit.gainIn3 === undefined ||
			audioCircuit.waveShaper1 === undefined ||
			audioCircuit.waveShaper2 === undefined ||
			audioCircuit.waveShaper3 === undefined ||
			audioCircuit.gainOut1 === undefined ||
			audioCircuit.gainOut2 === undefined ||
			audioCircuit.gainOut3 === undefined ||
			audioCircuit.streamAdder3 === undefined
		) { return; }
 
		const audioPortNodeIDs = apiConsole.alpa.getAudioPortNodeIds();
		if(audioPortNodeIDs === undefined) { return; }
 
		//routing
			apiConsole.alpa.createRouteNodeToNode(audioPortNodeIDs[0], 0, audioCircuit.streamDuplicator1, 0, () => {});
			apiConsole.alpa.createRouteNodeToNode(audioCircuit.streamDuplicator1, 1, audioCircuit.streamDuplicator2, 0, () => {});
 
			apiConsole.alpa.createRouteNodeToNode(audioCircuit.streamDuplicator1, 0, audioCircuit.gainIn1, 0, () => {});
			apiConsole.alpa.createRouteNodeToNode(audioCircuit.streamDuplicator2, 0, audioCircuit.gainIn2, 0, () => {});
			apiConsole.alpa.createRouteNodeToNode(audioCircuit.streamDuplicator2, 1, audioCircuit.gainIn3, 0, () => {});
 
			apiConsole.alpa.createRouteNodeToNode(audioCircuit.gainIn1, 0, audioCircuit.waveShaper1, 0, () => {});
			apiConsole.alpa.createRouteNodeToNode(audioCircuit.gainIn2, 0, audioCircuit.waveShaper2, 0, () => {});
			apiConsole.alpa.createRouteNodeToNode(audioCircuit.gainIn3, 0, audioCircuit.waveShaper3, 0, () => {});
 
			apiConsole.alpa.createRouteNodeToNode(audioCircuit.waveShaper1, 0, audioCircuit.highMidLowFilter1, 0, () => {});
			apiConsole.alpa.createRouteNodeToNode(audioCircuit.waveShaper2, 0, audioCircuit.highMidLowFilter2, 0, () => {});
			apiConsole.alpa.createRouteNodeToNode(audioCircuit.waveShaper3, 0, audioCircuit.highMidLowFilter3, 0, () => {});
 
			apiConsole.alpa.createRouteNodeToNode(audioCircuit.highMidLowFilter1, 0, audioCircuit.gainOut1, 0, () => {});
			apiConsole.alpa.createRouteNodeToNode(audioCircuit.highMidLowFilter2, 0, audioCircuit.gainOut2, 0, () => {});
			apiConsole.alpa.createRouteNodeToNode(audioCircuit.highMidLowFilter3, 0, audioCircuit.gainOut3, 0, () => {});
 
			apiConsole.alpa.createRouteNodeToNode(audioCircuit.gainOut1, 0, audioCircuit.streamAdder3, 0, () => {});
			apiConsole.alpa.createRouteNodeToNode(audioCircuit.gainOut2, 0, audioCircuit.streamAdder3, 1, () => {});
			apiConsole.alpa.createRouteNodeToNode(audioCircuit.gainOut3, 0, audioCircuit.streamAdder3, 2, () => {});
 
			apiConsole.alpa.createRouteNodeToNode(audioCircuit.streamAdder3, 0, audioPortNodeIDs[1], 0, () => {});
 
		//set up
			apiConsole.alpa.highMidLowFilter.setHighBandQ(audioCircuit.highMidLowFilter1, 1);
			apiConsole.alpa.highMidLowFilter.setMidBandQ(audioCircuit.highMidLowFilter1, 1);
			apiConsole.alpa.highMidLowFilter.setLowBandQ(audioCircuit.highMidLowFilter1, 1);
 
			apiConsole.alpa.highMidLowFilter.setHighBandGain(audioCircuit.highMidLowFilter1, 0);
			apiConsole.alpa.highMidLowFilter.setMidBandGain(audioCircuit.highMidLowFilter1, -2.5);
			apiConsole.alpa.highMidLowFilter.setLowBandGain(audioCircuit.highMidLowFilter1, -10);
 
			apiConsole.alpa.highMidLowFilter.setHighBandGain(audioCircuit.highMidLowFilter2, -2.5);
			apiConsole.alpa.highMidLowFilter.setMidBandGain(audioCircuit.highMidLowFilter2, 0);
			apiConsole.alpa.highMidLowFilter.setLowBandGain(audioCircuit.highMidLowFilter2, -2.5);
 
			apiConsole.alpa.highMidLowFilter.setHighBandGain(audioCircuit.highMidLowFilter3, -10);
			apiConsole.alpa.highMidLowFilter.setMidBandGain(audioCircuit.highMidLowFilter3, -2.5);
			apiConsole.alpa.highMidLowFilter.setLowBandGain(audioCircuit.highMidLowFilter3, 0);
 
			apiConsole.alpa.gain.setValue(audioCircuit.gainOut1, interfaceTo.gain(state.gainOut1));
			apiConsole.alpa.gain.setValue(audioCircuit.gainOut2, interfaceTo.gain(state.gainOut2));
			apiConsole.alpa.gain.setValue(audioCircuit.gainOut3, interfaceTo.gain(state.gainOut3));
			apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution1, state.distortion1));
			apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution2, state.distortion2));
			apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution3, state.distortion3));
			apiConsole.alpa.gain.setValue(audioCircuit.gainIn1, interfaceTo.gain(state.gainIn1));
			apiConsole.alpa.gain.setValue(audioCircuit.gainIn2, interfaceTo.gain(state.gainIn2));
			apiConsole.alpa.gain.setValue(audioCircuit.gainIn3, interfaceTo.gain(state.gainIn3));
	}
 
//dials
	apiConsole.interface.dialContinuous.setValue(part.gainOut1, true, state.gainOut1);
	apiConsole.interface.dialContinuous.setValue(part.gainOut2, true, state.gainOut2);
	apiConsole.interface.dialContinuous.setValue(part.gainOut3, true, state.gainOut3);
	apiConsole.interface.dialContinuous.setValue(part.distortion1, true, state.distortion1);
	apiConsole.interface.dialContinuous.setValue(part.distortion2, true, state.distortion2);
	apiConsole.interface.dialContinuous.setValue(part.distortion3, true, state.distortion3);
	apiConsole.interface.dialContinuous.setValue(part.resolution1, true, state.resolution1);
	apiConsole.interface.dialContinuous.setValue(part.resolution2, true, state.resolution2);
	apiConsole.interface.dialContinuous.setValue(part.resolution3, true, state.resolution3);
	apiConsole.interface.dialContinuous.setValue(part.gainIn1, true, state.gainIn1);
	apiConsole.interface.dialContinuous.setValue(part.gainIn2, true, state.gainIn2);
	apiConsole.interface.dialContinuous.setValue(part.gainIn3, true, state.gainIn3);

Show More

Mouse event handlers

We need to handle both regular mouse events and mouse wheel events. Luckily these two have the same structure so we can write a single handler function and use it for both. After checking to see if the dial is on the front layer (which it should be, as there's no dials on the back layer) and that all the audio nodes exist (a requirement of Typescript) we check to see if the event is from a "continuous dial" then move on to handle two of the event types; "change" and "release".

"Change" events appear when the dial changes its value at all, thus you can expect a lot of these to appear for any adjustment of the dial. "Release" events only appear when the user releases the mouse. "Release" events also come with the value that the dial had before any change was made.

So, for "change" events we update the unit state and update the audio node. For "release" events we do the same, but also populate a "modification" value which is returned at the end of the function. Additionally at the end of the function we update the Exportable data state.

0
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
function handleMouseEvent(partIndex:number, isOnFrontLayer:boolean, event:PartEvent) {
	if(!isOnFrontLayer) { return; }
 
	if(
		audioCircuit.streamDuplicator1 === undefined ||
		audioCircuit.streamDuplicator2 === undefined ||
		audioCircuit.highMidLowFilter1 === undefined ||
		audioCircuit.highMidLowFilter2 === undefined ||
		audioCircuit.highMidLowFilter3 === undefined ||
		audioCircuit.gainIn1 === undefined ||
		audioCircuit.gainIn2 === undefined ||
		audioCircuit.gainIn3 === undefined ||
		audioCircuit.waveShaper1 === undefined ||
		audioCircuit.waveShaper2 === undefined ||
		audioCircuit.waveShaper3 === undefined ||
		audioCircuit.gainOut1 === undefined ||
		audioCircuit.gainOut2 === undefined ||
		audioCircuit.gainOut3 === undefined ||
		audioCircuit.streamAdder3 === undefined
	) { return; }
 
	if(event.type === "continuousDial") {
		let modification:Modification | undefined;
 
		if(event.content.type === "change") {
			switch (partIndex) {
				case part.gainOut1:
					state.gainOut1 = event.content.content.currentLocation;
					apiConsole.alpa.gain.setValue(audioCircuit.gainOut1, interfaceTo.gain(state.gainOut1));
				break;
				case part.gainOut2:
					state.gainOut2 = event.content.content.currentLocation;
					apiConsole.alpa.gain.setValue(audioCircuit.gainOut2, interfaceTo.gain(state.gainOut2));
				break;
				case part.gainOut3:
					state.gainOut3 = event.content.content.currentLocation;
					apiConsole.alpa.gain.setValue(audioCircuit.gainOut3, interfaceTo.gain(state.gainOut3));
				break;
				case part.distortion1:
					state.distortion1 = event.content.content.currentLocation;
					apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution1, state.distortion1));
				break;
				case part.distortion2:
					state.distortion2 = event.content.content.currentLocation;
					apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution2, state.distortion2));
				break;
				case part.distortion3:
					state.distortion3 = event.content.content.currentLocation;
					apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution3, state.distortion3));
				break;
				case part.resolution1:
					state.resolution1 = event.content.content.currentLocation;
					apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution1, state.distortion1));
				break;
				case part.resolution2:
					state.resolution2 = event.content.content.currentLocation;
					apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution2, state.distortion2));
				break;
				case part.resolution3:
					state.resolution3 = event.content.content.currentLocation;
					apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution3, state.distortion3));
				break;
				case part.gainIn1:
					state.gainIn1 = event.content.content.currentLocation;
					apiConsole.alpa.gain.setValue(audioCircuit.gainIn1, interfaceTo.gain(state.gainIn1));
				break;
				case part.gainIn2:
					state.gainIn2 = event.content.content.currentLocation;
					apiConsole.alpa.gain.setValue(audioCircuit.gainIn2, interfaceTo.gain(state.gainIn2));
				break;
				case part.gainIn3:
					state.gainIn3 = event.content.content.currentLocation;
					apiConsole.alpa.gain.setValue(audioCircuit.gainIn3, interfaceTo.gain(state.gainIn3));
				break;
			}
		} else if(event.content.type === "release") {
			switch (partIndex) {
				case part.gainOut1:
					state.gainOut1 = event.content.content.finishLocation;
					apiConsole.alpa.gain.setValue(audioCircuit.gainOut1, interfaceTo.gain(state.gainOut1));
					modification = apiConsole.createModification("gainOut1", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
				break;
				case part.gainOut2:
					state.gainOut2 = event.content.content.finishLocation;
					apiConsole.alpa.gain.setValue(audioCircuit.gainOut2, interfaceTo.gain(state.gainOut2));
					modification = apiConsole.createModification("gainOut2", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
				break;
				case part.gainOut3:
					state.gainOut3 = event.content.content.finishLocation;
					apiConsole.alpa.gain.setValue(audioCircuit.gainOut3, interfaceTo.gain(state.gainOut3));
					modification = apiConsole.createModification("gainOut3", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
				break;
				case part.distortion1:
					state.distortion1 = event.content.content.finishLocation;
					apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution1, state.distortion1));
					modification = apiConsole.createModification("distortion1", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
				break;
				case part.distortion2:
					state.distortion2 = event.content.content.finishLocation;
					apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper2, interfaceTo.waveShaperCurve(state.resolution2, state.distortion2));
					modification = apiConsole.createModification("distortion2", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
				break;
				case part.distortion3:
					state.distortion3 = event.content.content.finishLocation;
					apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper3, interfaceTo.waveShaperCurve(state.resolution3, state.distortion3));
					modification = apiConsole.createModification("distortion3", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
				break;
				case part.resolution1: {
					state.resolution1 = event.content.content.finishLocation;
					apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution1, state.distortion1));
					modification = apiConsole.createModification("resolution1", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
				} break;
				case part.resolution2: {
					state.resolution2 = event.content.content.finishLocation;
					apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper2, interfaceTo.waveShaperCurve(state.resolution2, state.distortion2));
					modification = apiConsole.createModification("resolution2", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
				} break;
				case part.resolution3: {
					state.resolution3 = event.content.content.finishLocation;
					apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper3, interfaceTo.waveShaperCurve(state.resolution3, state.distortion3));
					modification = apiConsole.createModification("resolution3", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
				} break;
				case part.gainIn1:
					state.gainIn1 = event.content.content.finishLocation;
					apiConsole.alpa.gain.setValue(audioCircuit.gainIn1, interfaceTo.gain(state.gainIn1));
					modification = apiConsole.createModification("gainIn1", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
				break;
				case part.gainIn2:
					state.gainIn2 = event.content.content.finishLocation;
					apiConsole.alpa.gain.setValue(audioCircuit.gainIn2, interfaceTo.gain(state.gainIn2));
					modification = apiConsole.createModification("gainIn2", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
				break;
				case part.gainIn3:
					state.gainIn3 = event.content.content.finishLocation;
					apiConsole.alpa.gain.setValue(audioCircuit.gainIn3, interfaceTo.gain(state.gainIn3));
					modification = apiConsole.createModification("gainIn3", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
				break;
			}
		}
 
		updateExportableData(state);
 
		return modification;
	}
}
 
apiConsole.unit.setMouseEventListener(({ partIndex, isOnFrontLayer, event }) => handleMouseEvent(partIndex, isOnFrontLayer, event));
 
apiConsole.unit.setMouseWheelEventListener(({ partIndex, isOnFrontLayer, event }) => handleMouseEvent(partIndex, isOnFrontLayer, event));

Show More

The import hander

The import handler's job is simple; update everything to suit whatever data is provided. This includes updating the state value, changing the dials to suit and setting the audio node settings. As with the mouse event handler, we must check that all the audio nodes exist. Luckily however, we don't have to worry about update conflicts between the handler and the setup code above; if the audio nodes aren't present we can assume that they're being created, after which point the setup will use the state to populate their values. The very state that was updated in this function.

0
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
apiConsole.unit.setImportListener(({dataString}) => {
	state = JSON.parse(dataString) as State;
 
	//dials
		apiConsole.interface.dialContinuous.setValue(part.gainOut1, true, state.gainOut1);
		apiConsole.interface.dialContinuous.setValue(part.gainOut2, true, state.gainOut2);
		apiConsole.interface.dialContinuous.setValue(part.gainOut3, true, state.gainOut3);
		apiConsole.interface.dialContinuous.setValue(part.distortion1, true, state.distortion1);
		apiConsole.interface.dialContinuous.setValue(part.distortion2, true, state.distortion2);
		apiConsole.interface.dialContinuous.setValue(part.distortion3, true, state.distortion3);
		apiConsole.interface.dialContinuous.setValue(part.resolution1, true, state.resolution1);
		apiConsole.interface.dialContinuous.setValue(part.resolution2, true, state.resolution2);
		apiConsole.interface.dialContinuous.setValue(part.resolution3, true, state.resolution3);
		apiConsole.interface.dialContinuous.setValue(part.gainIn1, true, state.gainIn1);
		apiConsole.interface.dialContinuous.setValue(part.gainIn2, true, state.gainIn2);
		apiConsole.interface.dialContinuous.setValue(part.gainIn3, true, state.gainIn3);
 
	//audio circuit
		if(
			audioCircuit.streamDuplicator1 === undefined ||
			audioCircuit.streamDuplicator2 === undefined ||
			audioCircuit.highMidLowFilter1 === undefined ||
			audioCircuit.highMidLowFilter2 === undefined ||
			audioCircuit.highMidLowFilter3 === undefined ||
			audioCircuit.gainIn1 === undefined ||
			audioCircuit.gainIn2 === undefined ||
			audioCircuit.gainIn3 === undefined ||
			audioCircuit.waveShaper1 === undefined ||
			audioCircuit.waveShaper2 === undefined ||
			audioCircuit.waveShaper3 === undefined ||
			audioCircuit.gainOut1 === undefined ||
			audioCircuit.gainOut2 === undefined ||
			audioCircuit.gainOut3 === undefined ||
			audioCircuit.streamAdder3 === undefined
		) { return; }
 
		apiConsole.alpa.gain.setValue(audioCircuit.gainOut1, interfaceTo.gain(state.gainOut1));
		apiConsole.alpa.gain.setValue(audioCircuit.gainOut2, interfaceTo.gain(state.gainOut2));
		apiConsole.alpa.gain.setValue(audioCircuit.gainOut3, interfaceTo.gain(state.gainOut3));
		apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution1, state.distortion1));
		apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution2, state.distortion2));
		apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution3, state.distortion3));
		apiConsole.alpa.gain.setValue(audioCircuit.gainIn1, interfaceTo.gain(state.gainIn1));
		apiConsole.alpa.gain.setValue(audioCircuit.gainIn2, interfaceTo.gain(state.gainIn2));
		apiConsole.alpa.gain.setValue(audioCircuit.gainIn3, interfaceTo.gain(state.gainIn3));
});

Show More

Undo / Redo handlers

For simpler units such as this one, undo/redo can often be combined as the only real difference between the two is whether to use the "before" or "after" values. Thus we can combine the handler into one function and have the specifics decided in the undo and redo listeners. The rest of the function should be pretty familiar as it follows what has been done in previous sections; check the audio nodes exist, update the state, update the dials and update the audio nodes.

0
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
function handleUndoRedo(meta:string, modificationValue:string) {
	if(
		audioCircuit.streamDuplicator1 === undefined ||
		audioCircuit.streamDuplicator2 === undefined ||
		audioCircuit.highMidLowFilter1 === undefined ||
		audioCircuit.highMidLowFilter2 === undefined ||
		audioCircuit.highMidLowFilter3 === undefined ||
		audioCircuit.gainIn1 === undefined ||
		audioCircuit.gainIn2 === undefined ||
		audioCircuit.gainIn3 === undefined ||
		audioCircuit.waveShaper1 === undefined ||
		audioCircuit.waveShaper2 === undefined ||
		audioCircuit.waveShaper3 === undefined ||
		audioCircuit.gainOut1 === undefined ||
		audioCircuit.gainOut2 === undefined ||
		audioCircuit.gainOut3 === undefined ||
		audioCircuit.streamAdder3 === undefined
	) { return; }
 
	state[meta] = JSON.parse(modificationValue);
	apiConsole.interface.dialContinuous.setValue(part[meta], true, state[meta]);
 
	switch(meta) {
		case "gainOut1": apiConsole.alpa.gain.setValue(audioCircuit.gainOut1, interfaceTo.gain(state[meta])); break;
		case "gainOut2": apiConsole.alpa.gain.setValue(audioCircuit.gainOut2, interfaceTo.gain(state[meta])); break;
		case "gainOut3": apiConsole.alpa.gain.setValue(audioCircuit.gainOut3, interfaceTo.gain(state[meta])); break;
		case "gainIn1": apiConsole.alpa.gain.setValue(audioCircuit.gainIn1, interfaceTo.gain(state[meta])); break;
		case "gainIn2": apiConsole.alpa.gain.setValue(audioCircuit.gainIn2, interfaceTo.gain(state[meta])); break;
		case "gainIn3": apiConsole.alpa.gain.setValue(audioCircuit.gainIn3, interfaceTo.gain(state[meta])); break;
 
		case "distortion1":
		case "resolution1":
			apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution1, state.distortion1));
		break;
 
		case "distortion2":
		case "resolution2":
			apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper2, interfaceTo.waveShaperCurve(state.resolution2, state.distortion2));
		break;
 
		case "distortion3":
		case "resolution3":
			apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper3, interfaceTo.waveShaperCurve(state.resolution3, state.distortion3));
		break;
	}
}
 
apiConsole.unit.setUndoListener(({modification}) => {
	handleUndoRedo(modification.meta, modification.before);
	updateExportableData(state);
});
 
apiConsole.unit.setRedoListener(({modification}) => {
	handleUndoRedo(modification.meta, modification.after);
	updateExportableData(state);
});

Show More

In the end, we should have a file that looks like this.

0
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
import apiConsole from "../typescript-deno-0.3.3-0.1.0/index.ts";
import { PartEvent } from '../typescript-deno-0.3.3-0.1.0/types/platform/interface/part/event/partEvent.ts';
import { Modification } from "../typescript-deno-0.3.3-0.1.0/types/platform/unit/unit/modification/index.ts";
 
import { part } from './constants.ts';
import { AudioCircuit, State } from './types.ts';
import { interfaceTo, updateExportableData } from './utils.ts';
 
//state
	const audioCircuit:AudioCircuit = {
		streamDuplicator1: undefined,
		streamDuplicator2: undefined,
		highMidLowFilter1: undefined,
		highMidLowFilter2: undefined,
		highMidLowFilter3: undefined,
		gainIn1: undefined,
		gainIn2: undefined,
		gainIn3: undefined,
		waveShaper1: undefined,
		waveShaper2: undefined,
		waveShaper3: undefined,
		gainOut1: undefined,
		gainOut2: undefined,
		gainOut3: undefined,
		streamAdder3: undefined
	}
 
	let state:State = {
		gainIn1: 0.5,
		gainIn2: 0.5,
		gainIn3: 0.5,
		distortion1: 0.0,
		distortion2: 0.0,
		distortion3: 0.0,
		resolution1: 0.5,
		resolution2: 0.5,
		resolution3: 0.5,
		gainOut1: 0.5,
		gainOut2: 0.5,
		gainOut3: 0.5
	};
 
//setup
	//audio circuit
		apiConsole.alpa.createNode({ "type": "StreamDuplicator" }, (node_id) => {audioCircuit.streamDuplicator1 = node_id; updateAudioConnections();});
		apiConsole.alpa.createNode({ "type": "StreamDuplicator" }, (node_id) => {audioCircuit.streamDuplicator2 = node_id; updateAudioConnections();});
		apiConsole.alpa.createNode({ "type": "HighMidLowFilter" }, (node_id) => {audioCircuit.highMidLowFilter1 = node_id; updateAudioConnections();});
		apiConsole.alpa.createNode({ "type": "HighMidLowFilter" }, (node_id) => {audioCircuit.highMidLowFilter2 = node_id; updateAudioConnections();});
		apiConsole.alpa.createNode({ "type": "HighMidLowFilter" }, (node_id) => {audioCircuit.highMidLowFilter3 = node_id; updateAudioConnections();});
		apiConsole.alpa.createNode({ "type": "Gain" }, (node_id) => {audioCircuit.gainIn1 = node_id; updateAudioConnections();});
		apiConsole.alpa.createNode({ "type": "Gain" }, (node_id) => {audioCircuit.gainIn2 = node_id; updateAudioConnections();});
		apiConsole.alpa.createNode({ "type": "Gain" }, (node_id) => {audioCircuit.gainIn3 = node_id; updateAudioConnections();});
		apiConsole.alpa.createNode({ "type": "WaveShaper" }, (node_id) => {audioCircuit.waveShaper1 = node_id; updateAudioConnections();});
		apiConsole.alpa.createNode({ "type": "WaveShaper" }, (node_id) => {audioCircuit.waveShaper2 = node_id; updateAudioConnections();});
		apiConsole.alpa.createNode({ "type": "WaveShaper" }, (node_id) => {audioCircuit.waveShaper3 = node_id; updateAudioConnections();});
		apiConsole.alpa.createNode({ "type": "Gain" }, (node_id) => {audioCircuit.gainOut1 = node_id; updateAudioConnections();});
		apiConsole.alpa.createNode({ "type": "Gain" }, (node_id) => {audioCircuit.gainOut2 = node_id; updateAudioConnections();});
		apiConsole.alpa.createNode({ "type": "Gain" }, (node_id) => {audioCircuit.gainOut3 = node_id; updateAudioConnections();});
		apiConsole.alpa.createNode({ "type": "StreamAdder3" }, (node_id) => {audioCircuit.streamAdder3 = node_id; updateAudioConnections();});
 
		function updateAudioConnections() {
			if(
				audioCircuit.streamDuplicator1 === undefined ||
				audioCircuit.streamDuplicator2 === undefined ||
				audioCircuit.highMidLowFilter1 === undefined ||
				audioCircuit.highMidLowFilter2 === undefined ||
				audioCircuit.highMidLowFilter3 === undefined ||
				audioCircuit.gainIn1 === undefined ||
				audioCircuit.gainIn2 === undefined ||
				audioCircuit.gainIn3 === undefined ||
				audioCircuit.waveShaper1 === undefined ||
				audioCircuit.waveShaper2 === undefined ||
				audioCircuit.waveShaper3 === undefined ||
				audioCircuit.gainOut1 === undefined ||
				audioCircuit.gainOut2 === undefined ||
				audioCircuit.gainOut3 === undefined ||
				audioCircuit.streamAdder3 === undefined
			) { return; }
 
			const audioPortNodeIDs = apiConsole.alpa.getAudioPortNodeIds();
			if(audioPortNodeIDs === undefined) { return; }
 
			//routing
				apiConsole.alpa.createRouteNodeToNode(audioPortNodeIDs[0], 0, audioCircuit.streamDuplicator1, 0, () => {});
				apiConsole.alpa.createRouteNodeToNode(audioCircuit.streamDuplicator1, 1, audioCircuit.streamDuplicator2, 0, () => {});
 
				apiConsole.alpa.createRouteNodeToNode(audioCircuit.streamDuplicator1, 0, audioCircuit.gainIn1, 0, () => {});
				apiConsole.alpa.createRouteNodeToNode(audioCircuit.streamDuplicator2, 0, audioCircuit.gainIn2, 0, () => {});
				apiConsole.alpa.createRouteNodeToNode(audioCircuit.streamDuplicator2, 1, audioCircuit.gainIn3, 0, () => {});
 
				apiConsole.alpa.createRouteNodeToNode(audioCircuit.gainIn1, 0, audioCircuit.waveShaper1, 0, () => {});
				apiConsole.alpa.createRouteNodeToNode(audioCircuit.gainIn2, 0, audioCircuit.waveShaper2, 0, () => {});
				apiConsole.alpa.createRouteNodeToNode(audioCircuit.gainIn3, 0, audioCircuit.waveShaper3, 0, () => {});
 
				apiConsole.alpa.createRouteNodeToNode(audioCircuit.waveShaper1, 0, audioCircuit.highMidLowFilter1, 0, () => {});
				apiConsole.alpa.createRouteNodeToNode(audioCircuit.waveShaper2, 0, audioCircuit.highMidLowFilter2, 0, () => {});
				apiConsole.alpa.createRouteNodeToNode(audioCircuit.waveShaper3, 0, audioCircuit.highMidLowFilter3, 0, () => {});
 
				apiConsole.alpa.createRouteNodeToNode(audioCircuit.highMidLowFilter1, 0, audioCircuit.gainOut1, 0, () => {});
				apiConsole.alpa.createRouteNodeToNode(audioCircuit.highMidLowFilter2, 0, audioCircuit.gainOut2, 0, () => {});
				apiConsole.alpa.createRouteNodeToNode(audioCircuit.highMidLowFilter3, 0, audioCircuit.gainOut3, 0, () => {});
 
				apiConsole.alpa.createRouteNodeToNode(audioCircuit.gainOut1, 0, audioCircuit.streamAdder3, 0, () => {});
				apiConsole.alpa.createRouteNodeToNode(audioCircuit.gainOut2, 0, audioCircuit.streamAdder3, 1, () => {});
				apiConsole.alpa.createRouteNodeToNode(audioCircuit.gainOut3, 0, audioCircuit.streamAdder3, 2, () => {});
 
				apiConsole.alpa.createRouteNodeToNode(audioCircuit.streamAdder3, 0, audioPortNodeIDs[1], 0, () => {});
 
			//set up
				apiConsole.alpa.highMidLowFilter.setHighBandQ(audioCircuit.highMidLowFilter1, 1);
				apiConsole.alpa.highMidLowFilter.setMidBandQ(audioCircuit.highMidLowFilter1, 1);
				apiConsole.alpa.highMidLowFilter.setLowBandQ(audioCircuit.highMidLowFilter1, 1);
 
				apiConsole.alpa.highMidLowFilter.setHighBandGain(audioCircuit.highMidLowFilter1, 0);
				apiConsole.alpa.highMidLowFilter.setMidBandGain(audioCircuit.highMidLowFilter1, -2.5);
				apiConsole.alpa.highMidLowFilter.setLowBandGain(audioCircuit.highMidLowFilter1, -10);
 
				apiConsole.alpa.highMidLowFilter.setHighBandGain(audioCircuit.highMidLowFilter2, -2.5);
				apiConsole.alpa.highMidLowFilter.setMidBandGain(audioCircuit.highMidLowFilter2, 0);
				apiConsole.alpa.highMidLowFilter.setLowBandGain(audioCircuit.highMidLowFilter2, -2.5);
 
				apiConsole.alpa.highMidLowFilter.setHighBandGain(audioCircuit.highMidLowFilter3, -10);
				apiConsole.alpa.highMidLowFilter.setMidBandGain(audioCircuit.highMidLowFilter3, -2.5);
				apiConsole.alpa.highMidLowFilter.setLowBandGain(audioCircuit.highMidLowFilter3, 0);
 
				apiConsole.alpa.gain.setValue(audioCircuit.gainOut1, interfaceTo.gain(state.gainOut1));
				apiConsole.alpa.gain.setValue(audioCircuit.gainOut2, interfaceTo.gain(state.gainOut2));
				apiConsole.alpa.gain.setValue(audioCircuit.gainOut3, interfaceTo.gain(state.gainOut3));
				apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution1, state.distortion1));
				apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution2, state.distortion2));
				apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution3, state.distortion3));
				apiConsole.alpa.gain.setValue(audioCircuit.gainIn1, interfaceTo.gain(state.gainIn1));
				apiConsole.alpa.gain.setValue(audioCircuit.gainIn2, interfaceTo.gain(state.gainIn2));
				apiConsole.alpa.gain.setValue(audioCircuit.gainIn3, interfaceTo.gain(state.gainIn3));
		}
 
	//dials
		apiConsole.interface.dialContinuous.setValue(part.gainOut1, true, state.gainOut1);
		apiConsole.interface.dialContinuous.setValue(part.gainOut2, true, state.gainOut2);
		apiConsole.interface.dialContinuous.setValue(part.gainOut3, true, state.gainOut3);
		apiConsole.interface.dialContinuous.setValue(part.distortion1, true, state.distortion1);
		apiConsole.interface.dialContinuous.setValue(part.distortion2, true, state.distortion2);
		apiConsole.interface.dialContinuous.setValue(part.distortion3, true, state.distortion3);
		apiConsole.interface.dialContinuous.setValue(part.resolution1, true, state.resolution1);
		apiConsole.interface.dialContinuous.setValue(part.resolution2, true, state.resolution2);
		apiConsole.interface.dialContinuous.setValue(part.resolution3, true, state.resolution3);
		apiConsole.interface.dialContinuous.setValue(part.gainIn1, true, state.gainIn1);
		apiConsole.interface.dialContinuous.setValue(part.gainIn2, true, state.gainIn2);
		apiConsole.interface.dialContinuous.setValue(part.gainIn3, true, state.gainIn3);
 
//mouse events
	function handleMouseEvent(partIndex:number, isOnFrontLayer:boolean, event:PartEvent) {
		if(!isOnFrontLayer) { return; }
 
		if(
			audioCircuit.streamDuplicator1 === undefined ||
			audioCircuit.streamDuplicator2 === undefined ||
			audioCircuit.highMidLowFilter1 === undefined ||
			audioCircuit.highMidLowFilter2 === undefined ||
			audioCircuit.highMidLowFilter3 === undefined ||
			audioCircuit.gainIn1 === undefined ||
			audioCircuit.gainIn2 === undefined ||
			audioCircuit.gainIn3 === undefined ||
			audioCircuit.waveShaper1 === undefined ||
			audioCircuit.waveShaper2 === undefined ||
			audioCircuit.waveShaper3 === undefined ||
			audioCircuit.gainOut1 === undefined ||
			audioCircuit.gainOut2 === undefined ||
			audioCircuit.gainOut3 === undefined ||
			audioCircuit.streamAdder3 === undefined
		) { return; }
 
		if(event.type === "continuousDial") {
			let modification:Modification | undefined;
 
			if(event.content.type === "change") {
				switch (partIndex) {
					case part.gainOut1:
						state.gainOut1 = event.content.content.currentLocation;
						apiConsole.alpa.gain.setValue(audioCircuit.gainOut1, interfaceTo.gain(state.gainOut1));
					break;
					case part.gainOut2:
						state.gainOut2 = event.content.content.currentLocation;
						apiConsole.alpa.gain.setValue(audioCircuit.gainOut2, interfaceTo.gain(state.gainOut2));
					break;
					case part.gainOut3:
						state.gainOut3 = event.content.content.currentLocation;
						apiConsole.alpa.gain.setValue(audioCircuit.gainOut3, interfaceTo.gain(state.gainOut3));
					break;
					case part.distortion1:
						state.distortion1 = event.content.content.currentLocation;
						apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution1, state.distortion1));
					break;
					case part.distortion2:
						state.distortion2 = event.content.content.currentLocation;
						apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution2, state.distortion2));
					break;
					case part.distortion3:
						state.distortion3 = event.content.content.currentLocation;
						apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution3, state.distortion3));
					break;
					case part.resolution1:
						state.resolution1 = event.content.content.currentLocation;
						apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution1, state.distortion1));
					break;
					case part.resolution2:
						state.resolution2 = event.content.content.currentLocation;
						apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution2, state.distortion2));
					break;
					case part.resolution3:
						state.resolution3 = event.content.content.currentLocation;
						apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution3, state.distortion3));
					break;
					case part.gainIn1:
						state.gainIn1 = event.content.content.currentLocation;
						apiConsole.alpa.gain.setValue(audioCircuit.gainIn1, interfaceTo.gain(state.gainIn1));
					break;
					case part.gainIn2:
						state.gainIn2 = event.content.content.currentLocation;
						apiConsole.alpa.gain.setValue(audioCircuit.gainIn2, interfaceTo.gain(state.gainIn2));
					break;
					case part.gainIn3:
						state.gainIn3 = event.content.content.currentLocation;
						apiConsole.alpa.gain.setValue(audioCircuit.gainIn3, interfaceTo.gain(state.gainIn3));
					break;
				}
			} else if(event.content.type === "release") {
				switch (partIndex) {
					case part.gainOut1:
						state.gainOut1 = event.content.content.finishLocation;
						apiConsole.alpa.gain.setValue(audioCircuit.gainOut1, interfaceTo.gain(state.gainOut1));
						modification = apiConsole.createModification("gainOut1", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
					break;
					case part.gainOut2:
						state.gainOut2 = event.content.content.finishLocation;
						apiConsole.alpa.gain.setValue(audioCircuit.gainOut2, interfaceTo.gain(state.gainOut2));
						modification = apiConsole.createModification("gainOut2", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
					break;
					case part.gainOut3:
						state.gainOut3 = event.content.content.finishLocation;
						apiConsole.alpa.gain.setValue(audioCircuit.gainOut3, interfaceTo.gain(state.gainOut3));
						modification = apiConsole.createModification("gainOut3", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
					break;
					case part.distortion1:
						state.distortion1 = event.content.content.finishLocation;
						apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution1, state.distortion1));
						modification = apiConsole.createModification("distortion1", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
					break;
					case part.distortion2:
						state.distortion2 = event.content.content.finishLocation;
						apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper2, interfaceTo.waveShaperCurve(state.resolution2, state.distortion2));
						modification = apiConsole.createModification("distortion2", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
					break;
					case part.distortion3:
						state.distortion3 = event.content.content.finishLocation;
						apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper3, interfaceTo.waveShaperCurve(state.resolution3, state.distortion3));
						modification = apiConsole.createModification("distortion3", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
					break;
					case part.resolution1: {
						state.resolution1 = event.content.content.finishLocation;
						apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution1, state.distortion1));
						modification = apiConsole.createModification("resolution1", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
					} break;
					case part.resolution2: {
						state.resolution2 = event.content.content.finishLocation;
						apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper2, interfaceTo.waveShaperCurve(state.resolution2, state.distortion2));
						modification = apiConsole.createModification("resolution2", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
					} break;
					case part.resolution3: {
						state.resolution3 = event.content.content.finishLocation;
						apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper3, interfaceTo.waveShaperCurve(state.resolution3, state.distortion3));
						modification = apiConsole.createModification("resolution3", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
					} break;
					case part.gainIn1:
						state.gainIn1 = event.content.content.finishLocation;
						apiConsole.alpa.gain.setValue(audioCircuit.gainIn1, interfaceTo.gain(state.gainIn1));
						modification = apiConsole.createModification("gainIn1", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
					break;
					case part.gainIn2:
						state.gainIn2 = event.content.content.finishLocation;
						apiConsole.alpa.gain.setValue(audioCircuit.gainIn2, interfaceTo.gain(state.gainIn2));
						modification = apiConsole.createModification("gainIn2", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
					break;
					case part.gainIn3:
						state.gainIn3 = event.content.content.finishLocation;
						apiConsole.alpa.gain.setValue(audioCircuit.gainIn3, interfaceTo.gain(state.gainIn3));
						modification = apiConsole.createModification("gainIn3", JSON.stringify(event.content.content.initialLocation), JSON.stringify(event.content.content.finishLocation));
					break;
				}
			}
 
			updateExportableData(state);
 
			return modification;
		}
	}
 
	apiConsole.unit.setMouseEventListener(({ partIndex, isOnFrontLayer, event }) => handleMouseEvent(partIndex, isOnFrontLayer, event));
 
	apiConsole.unit.setMouseWheelEventListener(({ partIndex, isOnFrontLayer, event }) => handleMouseEvent(partIndex, isOnFrontLayer, event));
 
//import
	apiConsole.unit.setImportListener(({dataString}) => {
		state = JSON.parse(dataString) as State;
 
		//dials
			apiConsole.interface.dialContinuous.setValue(part.gainOut1, true, state.gainOut1);
			apiConsole.interface.dialContinuous.setValue(part.gainOut2, true, state.gainOut2);
			apiConsole.interface.dialContinuous.setValue(part.gainOut3, true, state.gainOut3);
			apiConsole.interface.dialContinuous.setValue(part.distortion1, true, state.distortion1);
			apiConsole.interface.dialContinuous.setValue(part.distortion2, true, state.distortion2);
			apiConsole.interface.dialContinuous.setValue(part.distortion3, true, state.distortion3);
			apiConsole.interface.dialContinuous.setValue(part.resolution1, true, state.resolution1);
			apiConsole.interface.dialContinuous.setValue(part.resolution2, true, state.resolution2);
			apiConsole.interface.dialContinuous.setValue(part.resolution3, true, state.resolution3);
			apiConsole.interface.dialContinuous.setValue(part.gainIn1, true, state.gainIn1);
			apiConsole.interface.dialContinuous.setValue(part.gainIn2, true, state.gainIn2);
			apiConsole.interface.dialContinuous.setValue(part.gainIn3, true, state.gainIn3);
 
		//audio circuit
			if(
				audioCircuit.streamDuplicator1 === undefined ||
				audioCircuit.streamDuplicator2 === undefined ||
				audioCircuit.highMidLowFilter1 === undefined ||
				audioCircuit.highMidLowFilter2 === undefined ||
				audioCircuit.highMidLowFilter3 === undefined ||
				audioCircuit.gainIn1 === undefined ||
				audioCircuit.gainIn2 === undefined ||
				audioCircuit.gainIn3 === undefined ||
				audioCircuit.waveShaper1 === undefined ||
				audioCircuit.waveShaper2 === undefined ||
				audioCircuit.waveShaper3 === undefined ||
				audioCircuit.gainOut1 === undefined ||
				audioCircuit.gainOut2 === undefined ||
				audioCircuit.gainOut3 === undefined ||
				audioCircuit.streamAdder3 === undefined
			) { return; }
 
			apiConsole.alpa.gain.setValue(audioCircuit.gainOut1, interfaceTo.gain(state.gainOut1));
			apiConsole.alpa.gain.setValue(audioCircuit.gainOut2, interfaceTo.gain(state.gainOut2));
			apiConsole.alpa.gain.setValue(audioCircuit.gainOut3, interfaceTo.gain(state.gainOut3));
			apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution1, state.distortion1));
			apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution2, state.distortion2));
			apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution3, state.distortion3));
			apiConsole.alpa.gain.setValue(audioCircuit.gainIn1, interfaceTo.gain(state.gainIn1));
			apiConsole.alpa.gain.setValue(audioCircuit.gainIn2, interfaceTo.gain(state.gainIn2));
			apiConsole.alpa.gain.setValue(audioCircuit.gainIn3, interfaceTo.gain(state.gainIn3));
	});
 
//undo / redo
	function handleUndoRedo(meta:string, modificationValue:string) {
		if(
			audioCircuit.streamDuplicator1 === undefined ||
			audioCircuit.streamDuplicator2 === undefined ||
			audioCircuit.highMidLowFilter1 === undefined ||
			audioCircuit.highMidLowFilter2 === undefined ||
			audioCircuit.highMidLowFilter3 === undefined ||
			audioCircuit.gainIn1 === undefined ||
			audioCircuit.gainIn2 === undefined ||
			audioCircuit.gainIn3 === undefined ||
			audioCircuit.waveShaper1 === undefined ||
			audioCircuit.waveShaper2 === undefined ||
			audioCircuit.waveShaper3 === undefined ||
			audioCircuit.gainOut1 === undefined ||
			audioCircuit.gainOut2 === undefined ||
			audioCircuit.gainOut3 === undefined ||
			audioCircuit.streamAdder3 === undefined
		) { return; }
 
		state[meta] = JSON.parse(modificationValue);
		apiConsole.interface.dialContinuous.setValue(part[meta], true, state[meta]);
 
		switch(meta) {
			case "gainOut1": apiConsole.alpa.gain.setValue(audioCircuit.gainOut1, interfaceTo.gain(state[meta])); break;
			case "gainOut2": apiConsole.alpa.gain.setValue(audioCircuit.gainOut2, interfaceTo.gain(state[meta])); break;
			case "gainOut3": apiConsole.alpa.gain.setValue(audioCircuit.gainOut3, interfaceTo.gain(state[meta])); break;
			case "gainIn1": apiConsole.alpa.gain.setValue(audioCircuit.gainIn1, interfaceTo.gain(state[meta])); break;
			case "gainIn2": apiConsole.alpa.gain.setValue(audioCircuit.gainIn2, interfaceTo.gain(state[meta])); break;
			case "gainIn3": apiConsole.alpa.gain.setValue(audioCircuit.gainIn3, interfaceTo.gain(state[meta])); break;
 
			case "distortion1":
			case "resolution1":
				apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper1, interfaceTo.waveShaperCurve(state.resolution1, state.distortion1));
			break;
 
			case "distortion2":
			case "resolution2":
				apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper2, interfaceTo.waveShaperCurve(state.resolution2, state.distortion2));
			break;
 
			case "distortion3":
			case "resolution3":
				apiConsole.alpa.waveShaper.setCurve(audioCircuit.waveShaper3, interfaceTo.waveShaperCurve(state.resolution3, state.distortion3));
			break;
		}
	}
 
	apiConsole.unit.setUndoListener(({modification}) => {
		handleUndoRedo(modification.meta, modification.before);
		updateExportableData(state);
	});
 
	apiConsole.unit.setRedoListener(({modification}) => {
		handleUndoRedo(modification.meta, modification.after);
		updateExportableData(state);
	});

Show More

And this should leave us finally with a file structure like so

custom_audio
├─ deno
├─ multi_channel_distortion
│  ├ constants.ts
│  ├ index.unit
│  ├ types.ts
│  └ utils.ts
└─ typescript-deno-x.x.x-y.y.y

Done!

Congratulations! You've just created a new unit which uses the audio engine. You can now play around with it in whatever way you like. Personally, I find it sounds great as a distortion box for an electric guitar. Having the ability to chose how much distortion I have for High or Low frequencies really gives lots of freedom in what sounds you can create.