@aptre/v86 0.5.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.
Files changed (111) hide show
  1. package/LICENSE +22 -0
  2. package/LICENSE.MIT +22 -0
  3. package/Readme.md +237 -0
  4. package/dist/v86.browser.js +26666 -0
  5. package/dist/v86.browser.js.map +7 -0
  6. package/dist/v86.js +26632 -0
  7. package/dist/v86.js.map +7 -0
  8. package/gen/generate_analyzer.ts +512 -0
  9. package/gen/generate_interpreter.ts +522 -0
  10. package/gen/generate_jit.ts +624 -0
  11. package/gen/rust_ast.ts +107 -0
  12. package/gen/util.ts +35 -0
  13. package/gen/x86_table.ts +1836 -0
  14. package/lib/9p.ts +1547 -0
  15. package/lib/filesystem.ts +1879 -0
  16. package/lib/marshall.ts +168 -0
  17. package/lib/softfloat/softfloat.c +32501 -0
  18. package/lib/zstd/zstddeclib.c +13520 -0
  19. package/package.json +75 -0
  20. package/src/acpi.ts +267 -0
  21. package/src/browser/dummy_screen.ts +106 -0
  22. package/src/browser/fake_network.ts +1771 -0
  23. package/src/browser/fetch_network.ts +361 -0
  24. package/src/browser/filestorage.ts +124 -0
  25. package/src/browser/inbrowser_network.ts +57 -0
  26. package/src/browser/keyboard.ts +564 -0
  27. package/src/browser/main.ts +3415 -0
  28. package/src/browser/mouse.ts +255 -0
  29. package/src/browser/network.ts +142 -0
  30. package/src/browser/print_stats.ts +336 -0
  31. package/src/browser/screen.ts +978 -0
  32. package/src/browser/serial.ts +316 -0
  33. package/src/browser/speaker.ts +1223 -0
  34. package/src/browser/starter.ts +1688 -0
  35. package/src/browser/wisp_network.ts +332 -0
  36. package/src/browser/worker_bus.ts +64 -0
  37. package/src/buffer.ts +652 -0
  38. package/src/bus.ts +78 -0
  39. package/src/const.ts +128 -0
  40. package/src/cpu.ts +2891 -0
  41. package/src/dma.ts +474 -0
  42. package/src/elf.ts +251 -0
  43. package/src/floppy.ts +1778 -0
  44. package/src/ide.ts +3455 -0
  45. package/src/io.ts +504 -0
  46. package/src/iso9660.ts +317 -0
  47. package/src/kernel.ts +250 -0
  48. package/src/lib.ts +645 -0
  49. package/src/log.ts +149 -0
  50. package/src/main.ts +199 -0
  51. package/src/ne2k.ts +1589 -0
  52. package/src/pci.ts +815 -0
  53. package/src/pit.ts +406 -0
  54. package/src/ps2.ts +820 -0
  55. package/src/rtc.ts +537 -0
  56. package/src/rust/analysis.rs +101 -0
  57. package/src/rust/codegen.rs +2660 -0
  58. package/src/rust/config.rs +3 -0
  59. package/src/rust/control_flow.rs +425 -0
  60. package/src/rust/cpu/apic.rs +658 -0
  61. package/src/rust/cpu/arith.rs +1207 -0
  62. package/src/rust/cpu/call_indirect.rs +2 -0
  63. package/src/rust/cpu/cpu.rs +4501 -0
  64. package/src/rust/cpu/fpu.rs +923 -0
  65. package/src/rust/cpu/global_pointers.rs +112 -0
  66. package/src/rust/cpu/instructions.rs +2486 -0
  67. package/src/rust/cpu/instructions_0f.rs +5261 -0
  68. package/src/rust/cpu/ioapic.rs +316 -0
  69. package/src/rust/cpu/memory.rs +351 -0
  70. package/src/rust/cpu/misc_instr.rs +613 -0
  71. package/src/rust/cpu/mod.rs +16 -0
  72. package/src/rust/cpu/modrm.rs +133 -0
  73. package/src/rust/cpu/pic.rs +402 -0
  74. package/src/rust/cpu/sse_instr.rs +361 -0
  75. package/src/rust/cpu/string.rs +701 -0
  76. package/src/rust/cpu/vga.rs +175 -0
  77. package/src/rust/cpu_context.rs +69 -0
  78. package/src/rust/dbg.rs +98 -0
  79. package/src/rust/gen/analyzer.rs +3807 -0
  80. package/src/rust/gen/analyzer0f.rs +3992 -0
  81. package/src/rust/gen/interpreter.rs +4447 -0
  82. package/src/rust/gen/interpreter0f.rs +5404 -0
  83. package/src/rust/gen/jit.rs +5080 -0
  84. package/src/rust/gen/jit0f.rs +5547 -0
  85. package/src/rust/gen/mod.rs +14 -0
  86. package/src/rust/jit.rs +2443 -0
  87. package/src/rust/jit_instructions.rs +7881 -0
  88. package/src/rust/js_api.rs +6 -0
  89. package/src/rust/leb.rs +46 -0
  90. package/src/rust/lib.rs +29 -0
  91. package/src/rust/modrm.rs +330 -0
  92. package/src/rust/opstats.rs +249 -0
  93. package/src/rust/page.rs +15 -0
  94. package/src/rust/paging.rs +25 -0
  95. package/src/rust/prefix.rs +15 -0
  96. package/src/rust/profiler.rs +155 -0
  97. package/src/rust/regs.rs +38 -0
  98. package/src/rust/softfloat.rs +286 -0
  99. package/src/rust/state_flags.rs +27 -0
  100. package/src/rust/wasmgen/mod.rs +2 -0
  101. package/src/rust/wasmgen/wasm_builder.rs +1047 -0
  102. package/src/rust/wasmgen/wasm_opcodes.rs +221 -0
  103. package/src/rust/zstd.rs +105 -0
  104. package/src/sb16.ts +1928 -0
  105. package/src/state.ts +359 -0
  106. package/src/uart.ts +472 -0
  107. package/src/vga.ts +2791 -0
  108. package/src/virtio.ts +1756 -0
  109. package/src/virtio_balloon.ts +273 -0
  110. package/src/virtio_console.ts +372 -0
  111. package/src/virtio_net.ts +326 -0
@@ -0,0 +1,1223 @@
1
+ declare let DEBUG: boolean
2
+
3
+ // AudioWorkletProcessor is available in AudioWorklet scope but not in main thread scope.
4
+
5
+ declare let AudioWorkletProcessor: any
6
+
7
+ import {
8
+ MIXER_CHANNEL_BOTH,
9
+ MIXER_CHANNEL_LEFT,
10
+ MIXER_CHANNEL_RIGHT,
11
+ MIXER_SRC_PCSPEAKER,
12
+ MIXER_SRC_DAC,
13
+ MIXER_SRC_MASTER,
14
+ } from '../const.js'
15
+ import { dbg_assert, dbg_log } from '../log.js'
16
+ import { OSCILLATOR_FREQ } from '../pit.js'
17
+ import { dump_file } from '../lib.js'
18
+ import { BusConnector } from '../bus.js'
19
+
20
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
21
+ declare const registerProcessor: any
22
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
23
+ declare const sampleRate: number
24
+
25
+ const DAC_QUEUE_RESERVE = 0.2
26
+
27
+ const AUDIOBUFFER_MINIMUM_SAMPLING_RATE = 8000
28
+
29
+ interface _SpeakerDACInterface {
30
+ node_processor: AudioWorkletNode | null
31
+ pump(): void
32
+ }
33
+
34
+ export class SpeakerAdapter {
35
+ bus: BusConnector
36
+ audio_context: AudioContext | null
37
+ mixer: SpeakerMixer
38
+ pcspeaker: PCSpeaker
39
+ dac: SpeakerWorkletDAC | SpeakerBufferSourceDAC
40
+
41
+ constructor(bus: BusConnector) {
42
+ if (typeof window === 'undefined') {
43
+ this.bus = bus
44
+ this.audio_context = null
45
+ this.mixer = undefined!
46
+ this.pcspeaker = undefined!
47
+ this.dac = undefined!
48
+ return
49
+ }
50
+ if (!window.AudioContext) {
51
+ console.warn("Web browser doesn't support Web Audio API")
52
+ this.bus = bus
53
+ this.audio_context = null
54
+ this.mixer = undefined!
55
+ this.pcspeaker = undefined!
56
+ this.dac = undefined!
57
+ return
58
+ }
59
+
60
+ const SpeakerDAC = window.AudioWorklet
61
+ ? SpeakerWorkletDAC
62
+ : SpeakerBufferSourceDAC
63
+
64
+ this.bus = bus
65
+
66
+ this.audio_context = new AudioContext()
67
+
68
+ this.mixer = new SpeakerMixer(bus, this.audio_context)
69
+
70
+ this.pcspeaker = new PCSpeaker(bus, this.audio_context, this.mixer)
71
+
72
+ this.dac = new SpeakerDAC(bus, this.audio_context, this.mixer)
73
+
74
+ this.pcspeaker.start()
75
+
76
+ bus.register(
77
+ 'emulator-stopped',
78
+ function (this: SpeakerAdapter) {
79
+ this.audio_context?.suspend()
80
+ },
81
+ this,
82
+ )
83
+
84
+ bus.register(
85
+ 'emulator-started',
86
+ function (this: SpeakerAdapter) {
87
+ this.audio_context?.resume()
88
+ },
89
+ this,
90
+ )
91
+
92
+ bus.register(
93
+ 'speaker-confirm-initialized',
94
+ function () {
95
+ bus.send('speaker-has-initialized')
96
+ },
97
+ this,
98
+ )
99
+ bus.send('speaker-has-initialized')
100
+ }
101
+
102
+ destroy(): void {
103
+ if (this.audio_context) {
104
+ this.audio_context.close()
105
+ }
106
+ this.audio_context = null
107
+ if (this.dac && this.dac.node_processor) {
108
+ this.dac.node_processor.port.close()
109
+ }
110
+ this.dac = undefined!
111
+ }
112
+ }
113
+
114
+ class SpeakerMixer {
115
+ audio_context: AudioContext
116
+ sources: Map<number, SpeakerMixerSource> = new Map()
117
+
118
+ volume_both = 1
119
+ volume_left = 1
120
+ volume_right = 1
121
+ gain_left = 1
122
+ gain_right = 1
123
+
124
+ node_treble_left: BiquadFilterNode
125
+ node_treble_right: BiquadFilterNode
126
+ node_bass_left: BiquadFilterNode
127
+ node_bass_right: BiquadFilterNode
128
+ node_gain_left: GainNode
129
+ node_gain_right: GainNode
130
+ node_merger: ChannelMergerNode
131
+
132
+ input_left: BiquadFilterNode
133
+ input_right: BiquadFilterNode
134
+
135
+ constructor(bus: BusConnector, audio_context: AudioContext) {
136
+ this.audio_context = audio_context
137
+
138
+ this.node_treble_left = this.audio_context.createBiquadFilter()
139
+ this.node_treble_right = this.audio_context.createBiquadFilter()
140
+ this.node_treble_left.type = 'highshelf'
141
+ this.node_treble_right.type = 'highshelf'
142
+ this.node_treble_left.frequency.setValueAtTime(
143
+ 2000,
144
+ this.audio_context.currentTime,
145
+ )
146
+ this.node_treble_right.frequency.setValueAtTime(
147
+ 2000,
148
+ this.audio_context.currentTime,
149
+ )
150
+
151
+ this.node_bass_left = this.audio_context.createBiquadFilter()
152
+ this.node_bass_right = this.audio_context.createBiquadFilter()
153
+ this.node_bass_left.type = 'lowshelf'
154
+ this.node_bass_right.type = 'lowshelf'
155
+ this.node_bass_left.frequency.setValueAtTime(
156
+ 200,
157
+ this.audio_context.currentTime,
158
+ )
159
+ this.node_bass_right.frequency.setValueAtTime(
160
+ 200,
161
+ this.audio_context.currentTime,
162
+ )
163
+
164
+ this.node_gain_left = this.audio_context.createGain()
165
+ this.node_gain_right = this.audio_context.createGain()
166
+
167
+ this.node_merger = this.audio_context.createChannelMerger(2)
168
+
169
+ this.input_left = this.node_treble_left
170
+ this.input_right = this.node_treble_right
171
+
172
+ this.node_treble_left.connect(this.node_bass_left)
173
+ this.node_bass_left.connect(this.node_gain_left)
174
+ this.node_gain_left.connect(this.node_merger, 0, 0)
175
+
176
+ this.node_treble_right.connect(this.node_bass_right)
177
+ this.node_bass_right.connect(this.node_gain_right)
178
+ this.node_gain_right.connect(this.node_merger, 0, 1)
179
+
180
+ this.node_merger.connect(this.audio_context.destination)
181
+
182
+ bus.register(
183
+ 'mixer-connect',
184
+ function (this: SpeakerMixer, data: [number, number]) {
185
+ const source_id = data[0]
186
+ const channel = data[1]
187
+ this.connect_source(source_id, channel)
188
+ },
189
+ this,
190
+ )
191
+
192
+ bus.register(
193
+ 'mixer-disconnect',
194
+ function (this: SpeakerMixer, data: [number, number]) {
195
+ const source_id = data[0]
196
+ const channel = data[1]
197
+ this.disconnect_source(source_id, channel)
198
+ },
199
+ this,
200
+ )
201
+
202
+ bus.register(
203
+ 'mixer-volume',
204
+ function (this: SpeakerMixer, data: [number, number, number]) {
205
+ const source_id = data[0]
206
+ const channel = data[1]
207
+ const decibels = data[2]
208
+
209
+ const gain = Math.pow(10, decibels / 20)
210
+
211
+ const source: SpeakerMixer | SpeakerMixerSource | undefined =
212
+ source_id === MIXER_SRC_MASTER
213
+ ? this
214
+ : this.sources.get(source_id)
215
+
216
+ if (source === undefined) {
217
+ dbg_assert(
218
+ false,
219
+ 'Mixer set volume - cannot set volume for undefined source: ' +
220
+ source_id,
221
+ )
222
+ return
223
+ }
224
+
225
+ source.set_volume(gain, channel)
226
+ },
227
+ this,
228
+ )
229
+
230
+ bus.register(
231
+ 'mixer-gain-left',
232
+ function (this: SpeakerMixer, decibels: number) {
233
+ this.gain_left = Math.pow(10, decibels / 20)
234
+ this.update()
235
+ },
236
+ this,
237
+ )
238
+
239
+ bus.register(
240
+ 'mixer-gain-right',
241
+ function (this: SpeakerMixer, decibels: number) {
242
+ this.gain_right = Math.pow(10, decibels / 20)
243
+ this.update()
244
+ },
245
+ this,
246
+ )
247
+
248
+ const create_gain_handler = (audio_node: BiquadFilterNode) => {
249
+ return function (this: SpeakerMixer, decibels: number) {
250
+ audio_node.gain.setValueAtTime(
251
+ decibels,
252
+ this.audio_context.currentTime,
253
+ )
254
+ }
255
+ }
256
+ bus.register(
257
+ 'mixer-treble-left',
258
+ create_gain_handler(this.node_treble_left),
259
+ this,
260
+ )
261
+ bus.register(
262
+ 'mixer-treble-right',
263
+ create_gain_handler(this.node_treble_right),
264
+ this,
265
+ )
266
+ bus.register(
267
+ 'mixer-bass-left',
268
+ create_gain_handler(this.node_bass_left),
269
+ this,
270
+ )
271
+ bus.register(
272
+ 'mixer-bass-right',
273
+ create_gain_handler(this.node_bass_right),
274
+ this,
275
+ )
276
+ }
277
+
278
+ add_source(source_node: AudioNode, source_id: number): SpeakerMixerSource {
279
+ const source = new SpeakerMixerSource(
280
+ this.audio_context,
281
+ source_node,
282
+ this.input_left,
283
+ this.input_right,
284
+ )
285
+
286
+ dbg_assert(
287
+ !this.sources.has(source_id),
288
+ 'Mixer add source - overwritting source: ' + source_id,
289
+ )
290
+
291
+ this.sources.set(source_id, source)
292
+ return source
293
+ }
294
+
295
+ connect_source(source_id: number, channel?: number): void {
296
+ const source = this.sources.get(source_id)
297
+
298
+ if (source === undefined) {
299
+ dbg_assert(
300
+ false,
301
+ 'Mixer connect - cannot connect undefined source: ' + source_id,
302
+ )
303
+ return
304
+ }
305
+
306
+ source.connect(channel)
307
+ }
308
+
309
+ disconnect_source(source_id: number, channel?: number): void {
310
+ const source = this.sources.get(source_id)
311
+
312
+ if (source === undefined) {
313
+ dbg_assert(
314
+ false,
315
+ 'Mixer disconnect - cannot disconnect undefined source: ' +
316
+ source_id,
317
+ )
318
+ return
319
+ }
320
+
321
+ source.disconnect(channel)
322
+ }
323
+
324
+ set_volume(value: number, channel?: number): void {
325
+ if (channel === undefined) {
326
+ channel = MIXER_CHANNEL_BOTH
327
+ }
328
+
329
+ switch (channel) {
330
+ case MIXER_CHANNEL_LEFT:
331
+ this.volume_left = value
332
+ break
333
+ case MIXER_CHANNEL_RIGHT:
334
+ this.volume_right = value
335
+ break
336
+ case MIXER_CHANNEL_BOTH:
337
+ this.volume_both = value
338
+ break
339
+ default:
340
+ dbg_assert(
341
+ false,
342
+ 'Mixer set master volume - unknown channel: ' + channel,
343
+ )
344
+ return
345
+ }
346
+
347
+ this.update()
348
+ }
349
+
350
+ update(): void {
351
+ const net_gain_left =
352
+ this.volume_both * this.volume_left * this.gain_left
353
+ const net_gain_right =
354
+ this.volume_both * this.volume_right * this.gain_right
355
+
356
+ this.node_gain_left.gain.setValueAtTime(
357
+ net_gain_left,
358
+ this.audio_context.currentTime,
359
+ )
360
+ this.node_gain_right.gain.setValueAtTime(
361
+ net_gain_right,
362
+ this.audio_context.currentTime,
363
+ )
364
+ }
365
+ }
366
+
367
+ class SpeakerMixerSource {
368
+ audio_context: AudioContext
369
+
370
+ connected_left = true
371
+ connected_right = true
372
+ gain_hidden = 1
373
+ volume_both = 1
374
+ volume_left = 1
375
+ volume_right = 1
376
+
377
+ node_splitter: ChannelSplitterNode
378
+ node_gain_left: GainNode
379
+ node_gain_right: GainNode
380
+
381
+ constructor(
382
+ audio_context: AudioContext,
383
+ source_node: AudioNode,
384
+ destination_left: AudioNode,
385
+ destination_right: AudioNode,
386
+ ) {
387
+ this.audio_context = audio_context
388
+
389
+ this.node_splitter = audio_context.createChannelSplitter(2)
390
+ this.node_gain_left = audio_context.createGain()
391
+ this.node_gain_right = audio_context.createGain()
392
+
393
+ source_node.connect(this.node_splitter)
394
+
395
+ this.node_splitter.connect(this.node_gain_left, 0)
396
+ this.node_gain_left.connect(destination_left)
397
+
398
+ this.node_splitter.connect(this.node_gain_right, 1)
399
+ this.node_gain_right.connect(destination_right)
400
+ }
401
+
402
+ update(): void {
403
+ const net_gain_left =
404
+ +this.connected_left *
405
+ this.gain_hidden *
406
+ this.volume_both *
407
+ this.volume_left
408
+ const net_gain_right =
409
+ +this.connected_right *
410
+ this.gain_hidden *
411
+ this.volume_both *
412
+ this.volume_right
413
+
414
+ this.node_gain_left.gain.setValueAtTime(
415
+ net_gain_left,
416
+ this.audio_context.currentTime,
417
+ )
418
+ this.node_gain_right.gain.setValueAtTime(
419
+ net_gain_right,
420
+ this.audio_context.currentTime,
421
+ )
422
+ }
423
+
424
+ connect(channel?: number): void {
425
+ const both = !channel || channel === MIXER_CHANNEL_BOTH
426
+ if (both || channel === MIXER_CHANNEL_LEFT) {
427
+ this.connected_left = true
428
+ }
429
+ if (both || channel === MIXER_CHANNEL_RIGHT) {
430
+ this.connected_right = true
431
+ }
432
+ this.update()
433
+ }
434
+
435
+ disconnect(channel?: number): void {
436
+ const both = !channel || channel === MIXER_CHANNEL_BOTH
437
+ if (both || channel === MIXER_CHANNEL_LEFT) {
438
+ this.connected_left = false
439
+ }
440
+ if (both || channel === MIXER_CHANNEL_RIGHT) {
441
+ this.connected_right = false
442
+ }
443
+ this.update()
444
+ }
445
+
446
+ set_volume(value: number, channel?: number): void {
447
+ if (channel === undefined) {
448
+ channel = MIXER_CHANNEL_BOTH
449
+ }
450
+
451
+ switch (channel) {
452
+ case MIXER_CHANNEL_LEFT:
453
+ this.volume_left = value
454
+ break
455
+ case MIXER_CHANNEL_RIGHT:
456
+ this.volume_right = value
457
+ break
458
+ case MIXER_CHANNEL_BOTH:
459
+ this.volume_both = value
460
+ break
461
+ default:
462
+ dbg_assert(
463
+ false,
464
+ 'Mixer set volume - unknown channel: ' + channel,
465
+ )
466
+ return
467
+ }
468
+
469
+ this.update()
470
+ }
471
+
472
+ set_gain_hidden(value: number): void {
473
+ this.gain_hidden = value
474
+ }
475
+ }
476
+
477
+ class PCSpeaker {
478
+ node_oscillator: OscillatorNode
479
+ mixer_connection: SpeakerMixerSource
480
+
481
+ constructor(
482
+ bus: BusConnector,
483
+ audio_context: AudioContext,
484
+ mixer: SpeakerMixer,
485
+ ) {
486
+ this.node_oscillator = audio_context.createOscillator()
487
+ this.node_oscillator.type = 'square'
488
+ this.node_oscillator.frequency.setValueAtTime(
489
+ 440,
490
+ audio_context.currentTime,
491
+ )
492
+
493
+ this.mixer_connection = mixer.add_source(
494
+ this.node_oscillator,
495
+ MIXER_SRC_PCSPEAKER,
496
+ )
497
+ this.mixer_connection.disconnect()
498
+
499
+ bus.register(
500
+ 'pcspeaker-enable',
501
+ function () {
502
+ mixer.connect_source(MIXER_SRC_PCSPEAKER)
503
+ },
504
+ this,
505
+ )
506
+
507
+ bus.register(
508
+ 'pcspeaker-disable',
509
+ function () {
510
+ mixer.disconnect_source(MIXER_SRC_PCSPEAKER)
511
+ },
512
+ this,
513
+ )
514
+
515
+ bus.register(
516
+ 'pcspeaker-update',
517
+ function (this: PCSpeaker, data: [number, number]) {
518
+ const counter_mode = data[0]
519
+ const counter_reload = data[1]
520
+
521
+ let frequency = 0
522
+ const beep_enabled = counter_mode === 3
523
+
524
+ if (beep_enabled) {
525
+ frequency = (OSCILLATOR_FREQ * 1000) / counter_reload
526
+ frequency = Math.min(
527
+ frequency,
528
+ this.node_oscillator.frequency.maxValue,
529
+ )
530
+ frequency = Math.max(frequency, 0)
531
+ }
532
+
533
+ this.node_oscillator.frequency.setValueAtTime(
534
+ frequency,
535
+ audio_context.currentTime,
536
+ )
537
+ },
538
+ this,
539
+ )
540
+ }
541
+
542
+ start(): void {
543
+ this.node_oscillator.start()
544
+ }
545
+ }
546
+
547
+ class SpeakerWorkletDAC {
548
+ bus: BusConnector
549
+ audio_context: AudioContext
550
+ enabled = false
551
+ sampling_rate = 48000
552
+ node_processor: AudioWorkletNode | null = null
553
+ node_output: GainNode
554
+ mixer_connection: SpeakerMixerSource
555
+ debugger: SpeakerDACDebugger | undefined
556
+
557
+ constructor(
558
+ bus: BusConnector,
559
+ audio_context: AudioContext,
560
+ mixer: SpeakerMixer,
561
+ ) {
562
+ this.bus = bus
563
+ this.audio_context = audio_context
564
+
565
+ // The worklet function body is extracted as a string and loaded as a Blob URL.
566
+ // It runs in a separate AudioWorklet scope, not in the main thread.
567
+ function worklet() {
568
+ const RENDER_QUANTUM = 128
569
+ const MINIMUM_BUFFER_SIZE = 2 * RENDER_QUANTUM
570
+ const QUEUE_RESERVE = 1024
571
+
572
+ function sinc(x: number): number {
573
+ if (x === 0) return 1
574
+ x *= Math.PI
575
+ return Math.sin(x) / x
576
+ }
577
+
578
+ const EMPTY_BUFFER: [Float32Array, Float32Array] = [
579
+ new Float32Array(MINIMUM_BUFFER_SIZE),
580
+ new Float32Array(MINIMUM_BUFFER_SIZE),
581
+ ]
582
+
583
+ function DACProcessor(this: any) {
584
+ const self = Reflect.construct(
585
+ AudioWorkletProcessor,
586
+ [],
587
+ DACProcessor,
588
+ )
589
+
590
+ self.kernel_size = 3
591
+
592
+ self.queue_data = new Array(1024)
593
+ self.queue_start = 0
594
+ self.queue_end = 0
595
+ self.queue_length = 0
596
+ self.queue_size = self.queue_data.length
597
+ self.queued_samples = 0
598
+
599
+ self.source_buffer_previous = EMPTY_BUFFER
600
+ self.source_buffer_current = EMPTY_BUFFER
601
+
602
+ self.source_samples_per_destination = 1.0
603
+
604
+ self.source_block_start = 0
605
+
606
+ self.source_time = 0.0
607
+
608
+ self.source_offset = 0
609
+
610
+ self.port.onmessage = (event: any) => {
611
+ switch (event.data.type) {
612
+ case 'queue':
613
+ self.queue_push(event.data.value)
614
+ break
615
+ case 'sampling-rate':
616
+ self.source_samples_per_destination =
617
+ event.data.value /
618
+ (globalThis as any).sampleRate
619
+ break
620
+ }
621
+ }
622
+
623
+ return self
624
+ }
625
+
626
+ Reflect.setPrototypeOf(
627
+ DACProcessor.prototype,
628
+ AudioWorkletProcessor.prototype,
629
+ )
630
+ Reflect.setPrototypeOf(DACProcessor, AudioWorkletProcessor)
631
+
632
+ DACProcessor.prototype['process'] = DACProcessor.prototype.process =
633
+ function (
634
+ this: any,
635
+ _inputs: any,
636
+ outputs: any,
637
+ _parameters: any,
638
+ ) {
639
+ for (let i = 0; i < outputs[0][0].length; i++) {
640
+ let sum0 = 0
641
+ let sum1 = 0
642
+
643
+ const start = this.source_offset - this.kernel_size + 1
644
+ const end = this.source_offset + this.kernel_size
645
+
646
+ for (let j = start; j <= end; j++) {
647
+ const convolute_index = this.source_block_start + j
648
+ sum0 +=
649
+ this.get_sample(convolute_index, 0) *
650
+ this.kernel(this.source_time - j)
651
+ sum1 +=
652
+ this.get_sample(convolute_index, 1) *
653
+ this.kernel(this.source_time - j)
654
+ }
655
+
656
+ if (isNaN(sum0) || isNaN(sum1)) {
657
+ sum0 = sum1 = 0
658
+ this.dbg_log('ERROR: NaN values! Ignoring for now.')
659
+ }
660
+
661
+ outputs[0][0][i] = sum0
662
+ outputs[0][1][i] = sum1
663
+
664
+ this.source_time += this.source_samples_per_destination
665
+ this.source_offset = Math.floor(this.source_time)
666
+ }
667
+
668
+ let samples_needed_per_block = this.source_offset
669
+ samples_needed_per_block += this.kernel_size + 2
670
+
671
+ this.source_time -= this.source_offset
672
+ this.source_block_start += this.source_offset
673
+ this.source_offset = 0
674
+
675
+ this.ensure_enough_data(samples_needed_per_block)
676
+
677
+ return true
678
+ }
679
+
680
+ DACProcessor.prototype.kernel = function (this: any, x: number) {
681
+ return sinc(x) * sinc(x / this.kernel_size)
682
+ }
683
+
684
+ DACProcessor.prototype.get_sample = function (
685
+ this: any,
686
+ index: number,
687
+ channel: number,
688
+ ) {
689
+ if (index < 0) {
690
+ index += this.source_buffer_previous[0].length
691
+ return this.source_buffer_previous[channel][index]
692
+ }
693
+ return this.source_buffer_current[channel][index]
694
+ }
695
+
696
+ DACProcessor.prototype.ensure_enough_data = function (
697
+ this: any,
698
+ needed: number,
699
+ ) {
700
+ const current_length = this.source_buffer_current[0].length
701
+ const remaining = current_length - this.source_block_start
702
+
703
+ if (remaining < needed) {
704
+ this.prepare_next_buffer()
705
+ this.source_block_start -= current_length
706
+ }
707
+ }
708
+
709
+ DACProcessor.prototype.prepare_next_buffer = function (this: any) {
710
+ if (
711
+ this.queued_samples < MINIMUM_BUFFER_SIZE &&
712
+ this.queue_length
713
+ ) {
714
+ this.dbg_log(
715
+ 'Not enough samples - should not happen during midway of playback',
716
+ )
717
+ }
718
+
719
+ this.source_buffer_previous = this.source_buffer_current
720
+ this.source_buffer_current = this.queue_shift()
721
+
722
+ let sample_count = this.source_buffer_current[0].length
723
+
724
+ if (sample_count < MINIMUM_BUFFER_SIZE) {
725
+ let queue_pos = this.queue_start
726
+ let buffer_count = 0
727
+
728
+ while (
729
+ sample_count < MINIMUM_BUFFER_SIZE &&
730
+ buffer_count < this.queue_length
731
+ ) {
732
+ sample_count += this.queue_data[queue_pos][0].length
733
+
734
+ queue_pos = (queue_pos + 1) & (this.queue_size - 1)
735
+ buffer_count++
736
+ }
737
+
738
+ const new_big_buffer_size = Math.max(
739
+ sample_count,
740
+ MINIMUM_BUFFER_SIZE,
741
+ )
742
+ const new_big_buffer = [
743
+ new Float32Array(new_big_buffer_size),
744
+ new Float32Array(new_big_buffer_size),
745
+ ]
746
+
747
+ new_big_buffer[0].set(this.source_buffer_current[0])
748
+ new_big_buffer[1].set(this.source_buffer_current[1])
749
+ let new_big_buffer_pos =
750
+ this.source_buffer_current[0].length
751
+
752
+ for (let i = 0; i < buffer_count; i++) {
753
+ const small_buffer = this.queue_shift()
754
+ new_big_buffer[0].set(
755
+ small_buffer[0],
756
+ new_big_buffer_pos,
757
+ )
758
+ new_big_buffer[1].set(
759
+ small_buffer[1],
760
+ new_big_buffer_pos,
761
+ )
762
+ new_big_buffer_pos += small_buffer[0].length
763
+ }
764
+
765
+ this.source_buffer_current = new_big_buffer
766
+ }
767
+
768
+ this.pump()
769
+ }
770
+
771
+ DACProcessor.prototype.pump = function (this: any) {
772
+ if (
773
+ this.queued_samples / this.source_samples_per_destination <
774
+ QUEUE_RESERVE
775
+ ) {
776
+ this.port.postMessage({
777
+ type: 'pump',
778
+ })
779
+ }
780
+ }
781
+
782
+ DACProcessor.prototype.queue_push = function (
783
+ this: any,
784
+ item: [Float32Array, Float32Array],
785
+ ) {
786
+ if (this.queue_length < this.queue_size) {
787
+ this.queue_data[this.queue_end] = item
788
+ this.queue_end =
789
+ (this.queue_end + 1) & (this.queue_size - 1)
790
+ this.queue_length++
791
+
792
+ this.queued_samples += item[0].length
793
+
794
+ this.pump()
795
+ }
796
+ }
797
+
798
+ DACProcessor.prototype.queue_shift = function (this: any) {
799
+ if (!this.queue_length) {
800
+ return EMPTY_BUFFER
801
+ }
802
+
803
+ const item = this.queue_data[this.queue_start]
804
+
805
+ this.queue_data[this.queue_start] = null
806
+ this.queue_start =
807
+ (this.queue_start + 1) & (this.queue_size - 1)
808
+ this.queue_length--
809
+
810
+ this.queued_samples -= item[0].length
811
+
812
+ return item
813
+ }
814
+
815
+ DACProcessor.prototype.dbg_log = function (
816
+ this: any,
817
+ message: string,
818
+ ) {
819
+ if (DEBUG) {
820
+ this.port.postMessage({
821
+ type: 'debug-log',
822
+ value: message,
823
+ })
824
+ }
825
+ }
826
+ ;(globalThis as any).registerProcessor(
827
+ 'dac-processor',
828
+ DACProcessor,
829
+ )
830
+ }
831
+
832
+ const worklet_string = worklet.toString()
833
+
834
+ const worklet_code_start = worklet_string.indexOf('{') + 1
835
+ const worklet_code_end = worklet_string.lastIndexOf('}')
836
+ let worklet_code = worklet_string.substring(
837
+ worklet_code_start,
838
+ worklet_code_end,
839
+ )
840
+
841
+ if (DEBUG) {
842
+ worklet_code = 'var DEBUG = true;\n' + worklet_code
843
+ }
844
+
845
+ const worklet_blob = new Blob([worklet_code], {
846
+ type: 'application/javascript',
847
+ })
848
+ const worklet_url = URL.createObjectURL(worklet_blob)
849
+
850
+ this.node_output = this.audio_context.createGain()
851
+
852
+ this.audio_context.audioWorklet.addModule(worklet_url).then(() => {
853
+ URL.revokeObjectURL(worklet_url)
854
+
855
+ this.node_processor = new AudioWorkletNode(
856
+ this.audio_context,
857
+ 'dac-processor',
858
+ {
859
+ numberOfInputs: 0,
860
+ numberOfOutputs: 1,
861
+ outputChannelCount: [2],
862
+ parameterData: {},
863
+ processorOptions: {},
864
+ },
865
+ )
866
+
867
+ this.node_processor.port.postMessage({
868
+ type: 'sampling-rate',
869
+ value: this.sampling_rate,
870
+ })
871
+
872
+ this.node_processor.port.onmessage = (event) => {
873
+ switch (event.data.type) {
874
+ case 'pump':
875
+ this.pump()
876
+ break
877
+ case 'debug-log':
878
+ dbg_log(
879
+ 'SpeakerWorkletDAC - Worklet: ' + event.data.value,
880
+ )
881
+ break
882
+ }
883
+ }
884
+
885
+ this.node_processor.connect(this.node_output)
886
+ })
887
+
888
+ this.mixer_connection = mixer.add_source(
889
+ this.node_output,
890
+ MIXER_SRC_DAC,
891
+ )
892
+ this.mixer_connection.set_gain_hidden(3)
893
+
894
+ bus.register(
895
+ 'dac-send-data',
896
+ function (
897
+ this: SpeakerWorkletDAC,
898
+ data: [Float32Array, Float32Array],
899
+ ) {
900
+ this.queue(data)
901
+ },
902
+ this,
903
+ )
904
+
905
+ bus.register(
906
+ 'dac-enable',
907
+ function (this: SpeakerWorkletDAC) {
908
+ this.enabled = true
909
+ },
910
+ this,
911
+ )
912
+
913
+ bus.register(
914
+ 'dac-disable',
915
+ function (this: SpeakerWorkletDAC) {
916
+ this.enabled = false
917
+ },
918
+ this,
919
+ )
920
+
921
+ bus.register(
922
+ 'dac-tell-sampling-rate',
923
+ function (this: SpeakerWorkletDAC, rate: number) {
924
+ dbg_assert(rate > 0, 'Sampling rate should be nonzero')
925
+ this.sampling_rate = rate
926
+
927
+ if (!this.node_processor) {
928
+ return
929
+ }
930
+
931
+ this.node_processor.port.postMessage({
932
+ type: 'sampling-rate',
933
+ value: rate,
934
+ })
935
+ },
936
+ this,
937
+ )
938
+
939
+ if (DEBUG) {
940
+ this.debugger = new SpeakerDACDebugger(
941
+ this.audio_context,
942
+ this.node_output,
943
+ )
944
+ }
945
+ }
946
+
947
+ queue(data: [Float32Array, Float32Array]): void {
948
+ if (!this.node_processor) {
949
+ return
950
+ }
951
+
952
+ if (DEBUG) {
953
+ this.debugger!.push_queued_data(data)
954
+ }
955
+
956
+ this.node_processor.port.postMessage(
957
+ {
958
+ type: 'queue',
959
+ value: data,
960
+ },
961
+ [data[0].buffer, data[1].buffer],
962
+ )
963
+ }
964
+
965
+ pump(): void {
966
+ if (!this.enabled) {
967
+ return
968
+ }
969
+ this.bus.send('dac-request-data')
970
+ }
971
+ }
972
+
973
+ class SpeakerBufferSourceDAC {
974
+ bus: BusConnector
975
+ audio_context: AudioContext
976
+ enabled = false
977
+ sampling_rate = 22050
978
+ buffered_time = 0
979
+ rate_ratio = 1
980
+ node_lowpass: BiquadFilterNode
981
+ node_output: BiquadFilterNode
982
+ node_processor: AudioWorkletNode | null = null
983
+ mixer_connection: SpeakerMixerSource
984
+ debugger: SpeakerDACDebugger | undefined
985
+
986
+ constructor(
987
+ bus: BusConnector,
988
+ audio_context: AudioContext,
989
+ mixer: SpeakerMixer,
990
+ ) {
991
+ this.bus = bus
992
+ this.audio_context = audio_context
993
+
994
+ this.node_lowpass = this.audio_context.createBiquadFilter()
995
+ this.node_lowpass.type = 'lowpass'
996
+
997
+ this.node_output = this.node_lowpass
998
+
999
+ this.mixer_connection = mixer.add_source(
1000
+ this.node_output,
1001
+ MIXER_SRC_DAC,
1002
+ )
1003
+ this.mixer_connection.set_gain_hidden(3)
1004
+
1005
+ bus.register(
1006
+ 'dac-send-data',
1007
+ function (
1008
+ this: SpeakerBufferSourceDAC,
1009
+ data: [Float32Array, Float32Array],
1010
+ ) {
1011
+ this.queue(data)
1012
+ },
1013
+ this,
1014
+ )
1015
+
1016
+ bus.register(
1017
+ 'dac-enable',
1018
+ function (this: SpeakerBufferSourceDAC) {
1019
+ this.enabled = true
1020
+ this.pump()
1021
+ },
1022
+ this,
1023
+ )
1024
+
1025
+ bus.register(
1026
+ 'dac-disable',
1027
+ function (this: SpeakerBufferSourceDAC) {
1028
+ this.enabled = false
1029
+ },
1030
+ this,
1031
+ )
1032
+
1033
+ bus.register(
1034
+ 'dac-tell-sampling-rate',
1035
+ function (this: SpeakerBufferSourceDAC, rate: number) {
1036
+ dbg_assert(rate > 0, 'Sampling rate should be nonzero')
1037
+ this.sampling_rate = rate
1038
+ this.rate_ratio = Math.ceil(
1039
+ AUDIOBUFFER_MINIMUM_SAMPLING_RATE / rate,
1040
+ )
1041
+ this.node_lowpass.frequency.setValueAtTime(
1042
+ rate / 2,
1043
+ this.audio_context.currentTime,
1044
+ )
1045
+ },
1046
+ this,
1047
+ )
1048
+
1049
+ if (DEBUG) {
1050
+ this.debugger = new SpeakerDACDebugger(
1051
+ this.audio_context,
1052
+ this.node_output,
1053
+ )
1054
+ }
1055
+ }
1056
+
1057
+ queue(data: [Float32Array, Float32Array]): void {
1058
+ if (DEBUG) {
1059
+ this.debugger!.push_queued_data(data)
1060
+ }
1061
+
1062
+ const sample_count = data[0].length
1063
+ const block_duration = sample_count / this.sampling_rate
1064
+
1065
+ let buffer: AudioBuffer
1066
+ if (this.rate_ratio > 1) {
1067
+ const new_sample_count = sample_count * this.rate_ratio
1068
+ const new_sampling_rate = this.sampling_rate * this.rate_ratio
1069
+ buffer = this.audio_context.createBuffer(
1070
+ 2,
1071
+ new_sample_count,
1072
+ new_sampling_rate,
1073
+ )
1074
+ const buffer_data0 = buffer.getChannelData(0)
1075
+ const buffer_data1 = buffer.getChannelData(1)
1076
+
1077
+ let buffer_index = 0
1078
+ for (let i = 0; i < sample_count; i++) {
1079
+ for (let j = 0; j < this.rate_ratio; j++, buffer_index++) {
1080
+ buffer_data0[buffer_index] = data[0][i]
1081
+ buffer_data1[buffer_index] = data[1][i]
1082
+ }
1083
+ }
1084
+ } else {
1085
+ buffer = this.audio_context.createBuffer(
1086
+ 2,
1087
+ sample_count,
1088
+ this.sampling_rate,
1089
+ )
1090
+ if (buffer.copyToChannel) {
1091
+ buffer.copyToChannel(new Float32Array(data[0]), 0)
1092
+ buffer.copyToChannel(new Float32Array(data[1]), 1)
1093
+ } else {
1094
+ buffer.getChannelData(0).set(data[0])
1095
+ buffer.getChannelData(1).set(data[1])
1096
+ }
1097
+ }
1098
+
1099
+ const source = this.audio_context.createBufferSource()
1100
+ source.buffer = buffer
1101
+ source.connect(this.node_lowpass)
1102
+ source.addEventListener('ended', this.pump.bind(this))
1103
+
1104
+ const current_time = this.audio_context.currentTime
1105
+
1106
+ if (this.buffered_time < current_time) {
1107
+ dbg_log(
1108
+ "Speaker DAC - Creating/Recreating reserve - shouldn't occur frequently during playback",
1109
+ )
1110
+
1111
+ this.buffered_time = current_time
1112
+ const target_silence_duration = DAC_QUEUE_RESERVE - block_duration
1113
+ let current_silence_duration = 0
1114
+ while (current_silence_duration <= target_silence_duration) {
1115
+ current_silence_duration += block_duration
1116
+ this.buffered_time += block_duration
1117
+ setTimeout(() => this.pump(), current_silence_duration * 1000)
1118
+ }
1119
+ }
1120
+
1121
+ source.start(this.buffered_time)
1122
+ this.buffered_time += block_duration
1123
+
1124
+ setTimeout(() => this.pump(), 0)
1125
+ }
1126
+
1127
+ pump(): void {
1128
+ if (!this.enabled) {
1129
+ return
1130
+ }
1131
+ if (
1132
+ this.buffered_time - this.audio_context.currentTime >
1133
+ DAC_QUEUE_RESERVE
1134
+ ) {
1135
+ return
1136
+ }
1137
+ this.bus.send('dac-request-data')
1138
+ }
1139
+ }
1140
+
1141
+ class SpeakerDACDebugger {
1142
+ audio_context: AudioContext
1143
+ node_source: AudioNode
1144
+ node_processor: ScriptProcessorNode | null = null
1145
+ node_gain: GainNode
1146
+ is_active = false
1147
+ queued_history: [Float32Array[], Float32Array[]][] = []
1148
+ output_history: [Float32Array[], Float32Array[]][] = []
1149
+ queued: [Float32Array[], Float32Array[]] = [[], []]
1150
+ output: [Float32Array[], Float32Array[]] = [[], []]
1151
+
1152
+ constructor(audio_context: AudioContext, source_node: AudioNode) {
1153
+ this.audio_context = audio_context
1154
+ this.node_source = source_node
1155
+
1156
+ this.node_gain = this.audio_context.createGain()
1157
+ this.node_gain.gain.setValueAtTime(0, this.audio_context.currentTime)
1158
+
1159
+ this.node_gain.connect(this.audio_context.destination)
1160
+ }
1161
+
1162
+ start(duration_ms: number): void {
1163
+ this.is_active = true
1164
+ this.queued = [[], []]
1165
+ this.output = [[], []]
1166
+ this.queued_history.push(this.queued)
1167
+ this.output_history.push(this.output)
1168
+
1169
+ this.node_processor = this.audio_context.createScriptProcessor(
1170
+ 1024,
1171
+ 2,
1172
+ 2,
1173
+ )
1174
+ this.node_processor.onaudioprocess = (event) => {
1175
+ this.output[0].push(event.inputBuffer.getChannelData(0).slice())
1176
+ this.output[1].push(event.inputBuffer.getChannelData(1).slice())
1177
+ }
1178
+
1179
+ this.node_source.connect(this.node_processor)
1180
+ this.node_processor.connect(this.node_gain)
1181
+
1182
+ setTimeout(() => {
1183
+ this.stop()
1184
+ }, duration_ms)
1185
+ }
1186
+
1187
+ stop(): void {
1188
+ this.is_active = false
1189
+ if (this.node_processor) {
1190
+ this.node_source.disconnect(this.node_processor)
1191
+ this.node_processor.disconnect()
1192
+ this.node_processor = null
1193
+ }
1194
+ }
1195
+
1196
+ push_queued_data(data: [Float32Array, Float32Array]): void {
1197
+ if (this.is_active) {
1198
+ this.queued[0].push(data[0].slice())
1199
+ this.queued[1].push(data[1].slice())
1200
+ }
1201
+ }
1202
+
1203
+ download_txt(history_id: number, channel: number): void {
1204
+ const txt = this.output_history[history_id][channel]
1205
+ .map((v) => v.join(' '))
1206
+ .join(' ')
1207
+
1208
+ dump_file(txt, 'dacdata.txt')
1209
+ }
1210
+
1211
+ download_csv(history_id: number): void {
1212
+ const buffers = this.output_history[history_id]
1213
+ const csv_rows: string[] = []
1214
+ for (let buffer_id = 0; buffer_id < buffers[0].length; buffer_id++) {
1215
+ for (let i = 0; i < buffers[0][buffer_id].length; i++) {
1216
+ csv_rows.push(
1217
+ `${buffers[0][buffer_id][i]},${buffers[1][buffer_id][i]}`,
1218
+ )
1219
+ }
1220
+ }
1221
+ dump_file(csv_rows.join('\n'), 'dacdata.csv')
1222
+ }
1223
+ }