playout-audio-worklet-processor 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +5 -0
- package/build/es2019/interfaces/audio-worklet-processor.d.ts +6 -0
- package/build/es2019/interfaces/audio-worklet-processor.d.ts.map +1 -0
- package/build/es2019/interfaces/audio-worklet-processor.js +2 -0
- package/build/es2019/interfaces/audio-worklet-processor.js.map +1 -0
- package/build/es2019/interfaces/index.d.ts +2 -0
- package/build/es2019/interfaces/index.d.ts.map +1 -0
- package/build/es2019/interfaces/index.js +2 -0
- package/build/es2019/interfaces/index.js.map +1 -0
- package/build/es2019/module.d.ts +2 -0
- package/build/es2019/module.d.ts.map +1 -0
- package/build/es2019/module.js +3 -0
- package/build/es2019/module.js.map +1 -0
- package/build/es2019/playout-audio-worklet-processor.d.ts +18 -0
- package/build/es2019/playout-audio-worklet-processor.d.ts.map +1 -0
- package/build/es2019/playout-audio-worklet-processor.js +120 -0
- package/build/es2019/playout-audio-worklet-processor.js.map +1 -0
- package/build/es5/bundle.js +155 -0
- package/package.json +75 -0
- package/src/audio-worklet-global-scope.d.ts +198 -0
- package/src/interfaces/audio-worklet-processor.ts +3 -0
- package/src/interfaces/index.ts +1 -0
- package/src/module.ts +3 -0
- package/src/playout-audio-worklet-processor.ts +185 -0
- package/src/tsconfig.json +7 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Christoph Guttandin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# playout-audio-worklet-processor
|
|
2
|
+
|
|
3
|
+
**The AudioWorkletProcessor which is used by the playout-audio-worklet package.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/playout-audio-worklet-processor)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-worklet-processor.d.ts","sourceRoot":"","sources":["../../../src/interfaces/audio-worklet-processor.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,sBAAsB;IACnC,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,UAAU,EAAE;QAAE,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAAA;KAAE,GAAG,OAAO,CAAC;CACvH"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-worklet-processor.js","sourceRoot":"","sources":["../../../src/interfaces/audio-worklet-processor.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/interfaces/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/interfaces/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/module.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;AAEjF,iBAAiB,CAAC,iCAAiC,EAAE,4BAA4B,CAAC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { IAudioWorkletProcessor } from './interfaces';
|
|
2
|
+
export declare class PlayoutAudioWorkletProcessor extends AudioWorkletProcessor implements IAudioWorkletProcessor {
|
|
3
|
+
static parameterDescriptors: never[];
|
|
4
|
+
private _isStarted;
|
|
5
|
+
private _numberOfChannels;
|
|
6
|
+
private _numberOfSlots;
|
|
7
|
+
private _readPointerView;
|
|
8
|
+
private _slots;
|
|
9
|
+
private _startView;
|
|
10
|
+
private _stopView;
|
|
11
|
+
private _writePointerView;
|
|
12
|
+
constructor({ numberOfInputs, numberOfOutputs, outputChannelCount, processorOptions }: AudioWorkletNodeOptions);
|
|
13
|
+
process(_: Float32Array[][], [output]: Float32Array[][]): boolean;
|
|
14
|
+
private _readStart;
|
|
15
|
+
private _readStop;
|
|
16
|
+
private _readStorage;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=playout-audio-worklet-processor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playout-audio-worklet-processor.d.ts","sourceRoot":"","sources":["../../src/playout-audio-worklet-processor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAKtD,qBAAa,4BAA6B,SAAQ,qBAAsB,YAAW,sBAAsB;IACrG,OAAc,oBAAoB,UAAM;IAExC,OAAO,CAAC,UAAU,CAAS;IAE3B,OAAO,CAAC,iBAAiB,CAAS;IAElC,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,gBAAgB,CAAyC;IAEjE,OAAO,CAAC,MAAM,CAAmB;IAEjC,OAAO,CAAC,UAAU,CAAc;IAEhC,OAAO,CAAC,SAAS,CAAc;IAE/B,OAAO,CAAC,iBAAiB,CAAyC;gBAEtD,EAAE,cAAc,EAAE,eAAe,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,EAAE,uBAAuB;IA8GvG,OAAO,CAAC,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,EAAE,GAAG,OAAO;IAmBxE,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,YAAY;CAmBvB"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const RENDER_QUANTUM = 128;
|
|
2
|
+
const MASK = 524287;
|
|
3
|
+
export class PlayoutAudioWorkletProcessor extends AudioWorkletProcessor {
|
|
4
|
+
constructor({ numberOfInputs, numberOfOutputs, outputChannelCount, processorOptions }) {
|
|
5
|
+
if (numberOfInputs !== 0) {
|
|
6
|
+
throw new Error('The numberOfInputs must be 0.');
|
|
7
|
+
}
|
|
8
|
+
if (numberOfOutputs !== 1) {
|
|
9
|
+
throw new Error('The numberOfOutputs must be 1.');
|
|
10
|
+
}
|
|
11
|
+
if (outputChannelCount === undefined || outputChannelCount.length !== 1) {
|
|
12
|
+
throw new Error('The outputChannelCount must define a single output.');
|
|
13
|
+
}
|
|
14
|
+
const [numberOfChannels] = outputChannelCount;
|
|
15
|
+
if (typeof processorOptions !== 'object' || processorOptions === null) {
|
|
16
|
+
throw new Error();
|
|
17
|
+
}
|
|
18
|
+
const readPointerView = 'readPointerView' in processorOptions ? processorOptions.readPointerView : null;
|
|
19
|
+
if (!(readPointerView instanceof Uint8Array || readPointerView instanceof Uint16Array || readPointerView instanceof Uint32Array)) {
|
|
20
|
+
throw new Error('The readPointerView needs to be an instance of "Uint8Array", "Uint16Array", or "Uint32Array".');
|
|
21
|
+
}
|
|
22
|
+
if (readPointerView.length !== 1) {
|
|
23
|
+
throw new Error('The readPointerView needs to have a length of 1.');
|
|
24
|
+
}
|
|
25
|
+
const startView = 'startView' in processorOptions ? processorOptions.startView : null;
|
|
26
|
+
if (!(startView instanceof Uint16Array)) {
|
|
27
|
+
throw new Error('The startView needs to be an instance of "Uint16Array".');
|
|
28
|
+
}
|
|
29
|
+
if (startView.length !== 1) {
|
|
30
|
+
throw new Error('The startView needs to have a length of 1.');
|
|
31
|
+
}
|
|
32
|
+
const stopView = 'stopView' in processorOptions ? processorOptions.stopView : null;
|
|
33
|
+
if (!(stopView instanceof Uint16Array)) {
|
|
34
|
+
throw new Error('The stopView needs to be an instance of "Uint16Array".');
|
|
35
|
+
}
|
|
36
|
+
if (stopView.length !== 1) {
|
|
37
|
+
throw new Error('The stopView needs to have a length of 1.');
|
|
38
|
+
}
|
|
39
|
+
const storageView = 'storageView' in processorOptions ? processorOptions.storageView : null;
|
|
40
|
+
if (!(storageView instanceof Float32Array)) {
|
|
41
|
+
throw new Error('The storageView needs to be an instance of "Float32Array".');
|
|
42
|
+
}
|
|
43
|
+
const numberOfFrames = storageView.length / numberOfChannels;
|
|
44
|
+
if (!Number.isInteger(numberOfFrames)) {
|
|
45
|
+
throw new Error('The storageView needs to have a length which is a multiple of the number of channels.');
|
|
46
|
+
}
|
|
47
|
+
const numberOfSlots = numberOfFrames / RENDER_QUANTUM;
|
|
48
|
+
if (!Number.isInteger(numberOfSlots)) {
|
|
49
|
+
throw new Error('The capacity can only be a multiple of the render quantum size.');
|
|
50
|
+
}
|
|
51
|
+
const writePointerView = 'writePointerView' in processorOptions ? processorOptions.writePointerView : null;
|
|
52
|
+
if (!(writePointerView instanceof Uint8Array || writePointerView instanceof Uint16Array || writePointerView instanceof Uint32Array)) {
|
|
53
|
+
throw new Error('The writePointerView needs to be an instance of "Uint8Array", "Uint16Array", or "Uint32Array".');
|
|
54
|
+
}
|
|
55
|
+
if (writePointerView.length !== 1) {
|
|
56
|
+
throw new Error('The writePointerView needs to have a length of 1.');
|
|
57
|
+
}
|
|
58
|
+
if (readPointerView.byteLength !== writePointerView.byteLength) {
|
|
59
|
+
throw new Error('The pointer need to be of the same size.');
|
|
60
|
+
}
|
|
61
|
+
if (numberOfFrames >= 2 ** (readPointerView.byteLength * 8 - 1) /* - THE MASK */) {
|
|
62
|
+
throw new Error('The capacity exceeds the pointer range.');
|
|
63
|
+
}
|
|
64
|
+
super();
|
|
65
|
+
this._isStarted = false;
|
|
66
|
+
this._numberOfChannels = numberOfChannels;
|
|
67
|
+
this._numberOfSlots = numberOfSlots;
|
|
68
|
+
this._readPointerView = readPointerView;
|
|
69
|
+
this._slots = Array.from({ length: numberOfSlots }, (_, slotIndex) => {
|
|
70
|
+
const slotOffset = slotIndex * RENDER_QUANTUM;
|
|
71
|
+
return Array.from({ length: numberOfChannels },
|
|
72
|
+
// tslint:disable-next-line:no-shadowed-variable
|
|
73
|
+
(_, channelIndex) => {
|
|
74
|
+
const channelOffset = slotOffset + channelIndex * numberOfSlots * RENDER_QUANTUM;
|
|
75
|
+
return storageView.subarray(channelOffset, channelOffset + RENDER_QUANTUM);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
this._startView = startView;
|
|
79
|
+
this._stopView = stopView;
|
|
80
|
+
this._writePointerView = writePointerView;
|
|
81
|
+
}
|
|
82
|
+
process(_, [output]) {
|
|
83
|
+
if (!this._isStarted) {
|
|
84
|
+
if (!this._readStart()) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
this._isStarted = true;
|
|
88
|
+
}
|
|
89
|
+
const flag = !(!this._readStorage(output) && this._readStop());
|
|
90
|
+
if (!flag) {
|
|
91
|
+
this.port.postMessage(null);
|
|
92
|
+
this.port.close();
|
|
93
|
+
}
|
|
94
|
+
return flag;
|
|
95
|
+
}
|
|
96
|
+
_readStart() {
|
|
97
|
+
const start = Atomics.load(this._startView, 0);
|
|
98
|
+
return start === 1;
|
|
99
|
+
}
|
|
100
|
+
_readStop() {
|
|
101
|
+
const stop = Atomics.load(this._stopView, 0);
|
|
102
|
+
return stop === 1;
|
|
103
|
+
}
|
|
104
|
+
_readStorage(output) {
|
|
105
|
+
const readPointer = Atomics.load(this._readPointerView, 0) / RENDER_QUANTUM;
|
|
106
|
+
// tslint:disable-next-line:no-bitwise
|
|
107
|
+
const writePointer = Math.floor((Atomics.load(this._writePointerView, 0) & MASK) / RENDER_QUANTUM);
|
|
108
|
+
if (writePointer === readPointer) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
const slot = this._slots[readPointer % this._numberOfSlots];
|
|
112
|
+
for (let channelIndex = 0; channelIndex < this._numberOfChannels; channelIndex += 1) {
|
|
113
|
+
output[channelIndex].set(slot[channelIndex]);
|
|
114
|
+
}
|
|
115
|
+
Atomics.store(this._readPointerView, 0, ((readPointer + 1) % (this._numberOfSlots * 2)) * RENDER_QUANTUM);
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
PlayoutAudioWorkletProcessor.parameterDescriptors = [];
|
|
120
|
+
//# sourceMappingURL=playout-audio-worklet-processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playout-audio-worklet-processor.js","sourceRoot":"","sources":["../../src/playout-audio-worklet-processor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('@babel/runtime/helpers/typeof'), require('@babel/runtime/helpers/slicedToArray'), require('@babel/runtime/helpers/classCallCheck'), require('@babel/runtime/helpers/createClass'), require('@babel/runtime/helpers/possibleConstructorReturn'), require('@babel/runtime/helpers/getPrototypeOf'), require('@babel/runtime/helpers/inherits'), require('@babel/runtime/helpers/wrapNativeSuper')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['@babel/runtime/helpers/typeof', '@babel/runtime/helpers/slicedToArray', '@babel/runtime/helpers/classCallCheck', '@babel/runtime/helpers/createClass', '@babel/runtime/helpers/possibleConstructorReturn', '@babel/runtime/helpers/getPrototypeOf', '@babel/runtime/helpers/inherits', '@babel/runtime/helpers/wrapNativeSuper'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global._typeof, global._slicedToArray, global._classCallCheck, global._createClass, global._possibleConstructorReturn, global._getPrototypeOf, global._inherits, global._wrapNativeSuper));
|
|
5
|
+
})(this, (function (_typeof, _slicedToArray, _classCallCheck, _createClass, _possibleConstructorReturn, _getPrototypeOf, _inherits, _wrapNativeSuper) { 'use strict';
|
|
6
|
+
|
|
7
|
+
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
|
|
8
|
+
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
|
|
9
|
+
var RENDER_QUANTUM = 128;
|
|
10
|
+
var MASK = 524287;
|
|
11
|
+
var PlayoutAudioWorkletProcessor = /*#__PURE__*/function (_AudioWorkletProcesso) {
|
|
12
|
+
function PlayoutAudioWorkletProcessor(_ref) {
|
|
13
|
+
var _this;
|
|
14
|
+
var numberOfInputs = _ref.numberOfInputs,
|
|
15
|
+
numberOfOutputs = _ref.numberOfOutputs,
|
|
16
|
+
outputChannelCount = _ref.outputChannelCount,
|
|
17
|
+
processorOptions = _ref.processorOptions;
|
|
18
|
+
_classCallCheck(this, PlayoutAudioWorkletProcessor);
|
|
19
|
+
if (numberOfInputs !== 0) {
|
|
20
|
+
throw new Error('The numberOfInputs must be 0.');
|
|
21
|
+
}
|
|
22
|
+
if (numberOfOutputs !== 1) {
|
|
23
|
+
throw new Error('The numberOfOutputs must be 1.');
|
|
24
|
+
}
|
|
25
|
+
if (outputChannelCount === undefined || outputChannelCount.length !== 1) {
|
|
26
|
+
throw new Error('The outputChannelCount must define a single output.');
|
|
27
|
+
}
|
|
28
|
+
var _outputChannelCount = _slicedToArray(outputChannelCount, 1),
|
|
29
|
+
numberOfChannels = _outputChannelCount[0];
|
|
30
|
+
if (_typeof(processorOptions) !== 'object' || processorOptions === null) {
|
|
31
|
+
throw new Error();
|
|
32
|
+
}
|
|
33
|
+
var readPointerView = 'readPointerView' in processorOptions ? processorOptions.readPointerView : null;
|
|
34
|
+
if (!(readPointerView instanceof Uint8Array || readPointerView instanceof Uint16Array || readPointerView instanceof Uint32Array)) {
|
|
35
|
+
throw new Error('The readPointerView needs to be an instance of "Uint8Array", "Uint16Array", or "Uint32Array".');
|
|
36
|
+
}
|
|
37
|
+
if (readPointerView.length !== 1) {
|
|
38
|
+
throw new Error('The readPointerView needs to have a length of 1.');
|
|
39
|
+
}
|
|
40
|
+
var startView = 'startView' in processorOptions ? processorOptions.startView : null;
|
|
41
|
+
if (!(startView instanceof Uint16Array)) {
|
|
42
|
+
throw new Error('The startView needs to be an instance of "Uint16Array".');
|
|
43
|
+
}
|
|
44
|
+
if (startView.length !== 1) {
|
|
45
|
+
throw new Error('The startView needs to have a length of 1.');
|
|
46
|
+
}
|
|
47
|
+
var stopView = 'stopView' in processorOptions ? processorOptions.stopView : null;
|
|
48
|
+
if (!(stopView instanceof Uint16Array)) {
|
|
49
|
+
throw new Error('The stopView needs to be an instance of "Uint16Array".');
|
|
50
|
+
}
|
|
51
|
+
if (stopView.length !== 1) {
|
|
52
|
+
throw new Error('The stopView needs to have a length of 1.');
|
|
53
|
+
}
|
|
54
|
+
var storageView = 'storageView' in processorOptions ? processorOptions.storageView : null;
|
|
55
|
+
if (!(storageView instanceof Float32Array)) {
|
|
56
|
+
throw new Error('The storageView needs to be an instance of "Float32Array".');
|
|
57
|
+
}
|
|
58
|
+
var numberOfFrames = storageView.length / numberOfChannels;
|
|
59
|
+
if (!Number.isInteger(numberOfFrames)) {
|
|
60
|
+
throw new Error('The storageView needs to have a length which is a multiple of the number of channels.');
|
|
61
|
+
}
|
|
62
|
+
var numberOfSlots = numberOfFrames / RENDER_QUANTUM;
|
|
63
|
+
if (!Number.isInteger(numberOfSlots)) {
|
|
64
|
+
throw new Error('The capacity can only be a multiple of the render quantum size.');
|
|
65
|
+
}
|
|
66
|
+
var writePointerView = 'writePointerView' in processorOptions ? processorOptions.writePointerView : null;
|
|
67
|
+
if (!(writePointerView instanceof Uint8Array || writePointerView instanceof Uint16Array || writePointerView instanceof Uint32Array)) {
|
|
68
|
+
throw new Error('The writePointerView needs to be an instance of "Uint8Array", "Uint16Array", or "Uint32Array".');
|
|
69
|
+
}
|
|
70
|
+
if (writePointerView.length !== 1) {
|
|
71
|
+
throw new Error('The writePointerView needs to have a length of 1.');
|
|
72
|
+
}
|
|
73
|
+
if (readPointerView.byteLength !== writePointerView.byteLength) {
|
|
74
|
+
throw new Error('The pointer need to be of the same size.');
|
|
75
|
+
}
|
|
76
|
+
if (numberOfFrames >= Math.pow(2, readPointerView.byteLength * 8 - 1) /* - THE MASK */) {
|
|
77
|
+
throw new Error('The capacity exceeds the pointer range.');
|
|
78
|
+
}
|
|
79
|
+
_this = _callSuper(this, PlayoutAudioWorkletProcessor);
|
|
80
|
+
_this._isStarted = false;
|
|
81
|
+
_this._numberOfChannels = numberOfChannels;
|
|
82
|
+
_this._numberOfSlots = numberOfSlots;
|
|
83
|
+
_this._readPointerView = readPointerView;
|
|
84
|
+
_this._slots = Array.from({
|
|
85
|
+
length: numberOfSlots
|
|
86
|
+
}, function (_, slotIndex) {
|
|
87
|
+
var slotOffset = slotIndex * RENDER_QUANTUM;
|
|
88
|
+
return Array.from({
|
|
89
|
+
length: numberOfChannels
|
|
90
|
+
},
|
|
91
|
+
// tslint:disable-next-line:no-shadowed-variable
|
|
92
|
+
function (_, channelIndex) {
|
|
93
|
+
var channelOffset = slotOffset + channelIndex * numberOfSlots * RENDER_QUANTUM;
|
|
94
|
+
return storageView.subarray(channelOffset, channelOffset + RENDER_QUANTUM);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
_this._startView = startView;
|
|
98
|
+
_this._stopView = stopView;
|
|
99
|
+
_this._writePointerView = writePointerView;
|
|
100
|
+
return _this;
|
|
101
|
+
}
|
|
102
|
+
_inherits(PlayoutAudioWorkletProcessor, _AudioWorkletProcesso);
|
|
103
|
+
return _createClass(PlayoutAudioWorkletProcessor, [{
|
|
104
|
+
key: "process",
|
|
105
|
+
value: function process(_, _ref2) {
|
|
106
|
+
var _ref3 = _slicedToArray(_ref2, 1),
|
|
107
|
+
output = _ref3[0];
|
|
108
|
+
if (!this._isStarted) {
|
|
109
|
+
if (!this._readStart()) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
this._isStarted = true;
|
|
113
|
+
}
|
|
114
|
+
var flag = !(!this._readStorage(output) && this._readStop());
|
|
115
|
+
if (!flag) {
|
|
116
|
+
this.port.postMessage(null);
|
|
117
|
+
this.port.close();
|
|
118
|
+
}
|
|
119
|
+
return flag;
|
|
120
|
+
}
|
|
121
|
+
}, {
|
|
122
|
+
key: "_readStart",
|
|
123
|
+
value: function _readStart() {
|
|
124
|
+
var start = Atomics.load(this._startView, 0);
|
|
125
|
+
return start === 1;
|
|
126
|
+
}
|
|
127
|
+
}, {
|
|
128
|
+
key: "_readStop",
|
|
129
|
+
value: function _readStop() {
|
|
130
|
+
var stop = Atomics.load(this._stopView, 0);
|
|
131
|
+
return stop === 1;
|
|
132
|
+
}
|
|
133
|
+
}, {
|
|
134
|
+
key: "_readStorage",
|
|
135
|
+
value: function _readStorage(output) {
|
|
136
|
+
var readPointer = Atomics.load(this._readPointerView, 0) / RENDER_QUANTUM;
|
|
137
|
+
// tslint:disable-next-line:no-bitwise
|
|
138
|
+
var writePointer = Math.floor((Atomics.load(this._writePointerView, 0) & MASK) / RENDER_QUANTUM);
|
|
139
|
+
if (writePointer === readPointer) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
var slot = this._slots[readPointer % this._numberOfSlots];
|
|
143
|
+
for (var channelIndex = 0; channelIndex < this._numberOfChannels; channelIndex += 1) {
|
|
144
|
+
output[channelIndex].set(slot[channelIndex]);
|
|
145
|
+
}
|
|
146
|
+
Atomics.store(this._readPointerView, 0, (readPointer + 1) % (this._numberOfSlots * 2) * RENDER_QUANTUM);
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
}]);
|
|
150
|
+
}(/*#__PURE__*/_wrapNativeSuper(AudioWorkletProcessor));
|
|
151
|
+
PlayoutAudioWorkletProcessor.parameterDescriptors = [];
|
|
152
|
+
|
|
153
|
+
registerProcessor('playout-audio-worklet-processor', PlayoutAudioWorkletProcessor);
|
|
154
|
+
|
|
155
|
+
}));
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"author": "Christoph Guttandin",
|
|
3
|
+
"bugs": {
|
|
4
|
+
"url": "https://github.com/chrisguttandin/playout-audio-worklet-processor/issues"
|
|
5
|
+
},
|
|
6
|
+
"config": {
|
|
7
|
+
"commitizen": {
|
|
8
|
+
"path": "cz-conventional-changelog"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@babel/runtime": "^7.28.4",
|
|
13
|
+
"tslib": "^2.8.1"
|
|
14
|
+
},
|
|
15
|
+
"description": "The AudioWorkletProcessor which is used by the playout-audio-worklet package.",
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@babel/core": "^7.28.5",
|
|
18
|
+
"@babel/plugin-external-helpers": "^7.27.1",
|
|
19
|
+
"@babel/plugin-transform-runtime": "^7.28.5",
|
|
20
|
+
"@babel/preset-env": "^7.28.5",
|
|
21
|
+
"@commitlint/cli": "^19.8.1",
|
|
22
|
+
"@commitlint/config-angular": "^19.8.1",
|
|
23
|
+
"@rollup/plugin-babel": "^6.1.0",
|
|
24
|
+
"@vitest/browser-webdriverio": "^4.0.16",
|
|
25
|
+
"chai": "^6.2.1",
|
|
26
|
+
"commitizen": "^4.3.1",
|
|
27
|
+
"cz-conventional-changelog": "^3.3.0",
|
|
28
|
+
"eslint": "^8.57.0",
|
|
29
|
+
"eslint-config-holy-grail": "^61.0.3",
|
|
30
|
+
"husky": "^9.1.7",
|
|
31
|
+
"lint-staged": "^16.2.7",
|
|
32
|
+
"memfs": "^4.51.1",
|
|
33
|
+
"prettier": "^3.7.4",
|
|
34
|
+
"rimraf": "^6.1.2",
|
|
35
|
+
"rollup": "^4.53.5",
|
|
36
|
+
"sinon": "^21.0.0",
|
|
37
|
+
"sinon-chai": "^4.0.1",
|
|
38
|
+
"standardized-audio-context": "^25.3.77",
|
|
39
|
+
"ts-loader": "^9.5.4",
|
|
40
|
+
"tsconfig-holy-grail": "^15.0.2",
|
|
41
|
+
"tslint": "^6.1.3",
|
|
42
|
+
"tslint-config-holy-grail": "^56.0.6",
|
|
43
|
+
"typescript": "^5.9.3",
|
|
44
|
+
"vitest": "^4.0.16",
|
|
45
|
+
"webpack": "^5.104.1"
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"build/es2019/",
|
|
49
|
+
"build/es5/",
|
|
50
|
+
"src/"
|
|
51
|
+
],
|
|
52
|
+
"homepage": "https://github.com/chrisguttandin/playout-audio-worklet-processor",
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"main": "build/es5/bundle.js",
|
|
55
|
+
"module": "build/es2019/module.js",
|
|
56
|
+
"name": "playout-audio-worklet-processor",
|
|
57
|
+
"repository": {
|
|
58
|
+
"type": "git",
|
|
59
|
+
"url": "https://github.com/chrisguttandin/playout-audio-worklet-processor.git"
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "rimraf build/* && tsc --project src/tsconfig.json && rollup --config config/rollup/bundle.mjs",
|
|
63
|
+
"lint": "npm run lint:config && npm run lint:src && npm run lint:test",
|
|
64
|
+
"lint:config": "eslint --config config/eslint/config.json --ext .cjs --ext .js --ext .mjs --report-unused-disable-directives config/",
|
|
65
|
+
"lint:src": "tslint --config config/tslint/src.json --project src/tsconfig.json src/*.ts src/**/*.ts",
|
|
66
|
+
"lint:test": "eslint --config config/eslint/test.json --ext .js --report-unused-disable-directives test/",
|
|
67
|
+
"prepare": "husky",
|
|
68
|
+
"prepublishOnly": "npm run build",
|
|
69
|
+
"test": "npm run lint && npm run build && npm run test:integration && npm run test:unit",
|
|
70
|
+
"test:integration": "if [ \"$TYPE\" = \"\" -o \"$TYPE\" = \"integration\" ]; then npx vitest --config config/vitest/integration.ts; fi",
|
|
71
|
+
"test:unit": "if [ \"$TYPE\" = \"\" -o \"$TYPE\" = \"unit\" ]; then npx vitest --config config/vitest/unit.ts; fi"
|
|
72
|
+
},
|
|
73
|
+
"types": "build/es2019/module.d.ts",
|
|
74
|
+
"version": "1.0.0"
|
|
75
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @todo This is currently only a copy all the types needed to define the MessagePort. The type definitions are copied from TypeScripts
|
|
3
|
+
* webworker library. In addition to that this file also contains the definitions of the globally available AudioWorkletProcessor class and
|
|
4
|
+
* the registerProcessor function.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// tslint:disable-next-line:interface-name
|
|
8
|
+
interface Event {
|
|
9
|
+
readonly AT_TARGET: number;
|
|
10
|
+
|
|
11
|
+
readonly bubbles: boolean;
|
|
12
|
+
|
|
13
|
+
readonly BUBBLING_PHASE: number;
|
|
14
|
+
|
|
15
|
+
readonly cancelable: boolean;
|
|
16
|
+
|
|
17
|
+
cancelBubble: boolean;
|
|
18
|
+
|
|
19
|
+
readonly CAPTURING_PHASE: number;
|
|
20
|
+
|
|
21
|
+
readonly currentTarget: EventTarget | null;
|
|
22
|
+
|
|
23
|
+
readonly defaultPrevented: boolean;
|
|
24
|
+
|
|
25
|
+
readonly eventPhase: number;
|
|
26
|
+
|
|
27
|
+
readonly isTrusted: boolean;
|
|
28
|
+
|
|
29
|
+
readonly NONE: number;
|
|
30
|
+
|
|
31
|
+
returnValue: boolean;
|
|
32
|
+
|
|
33
|
+
readonly scoped: boolean;
|
|
34
|
+
|
|
35
|
+
readonly srcElement: object | null;
|
|
36
|
+
|
|
37
|
+
readonly target: EventTarget | null;
|
|
38
|
+
|
|
39
|
+
readonly timeStamp: number;
|
|
40
|
+
|
|
41
|
+
readonly type: string;
|
|
42
|
+
|
|
43
|
+
deepPath(): EventTarget[];
|
|
44
|
+
|
|
45
|
+
initEvent(type: string, bubbles?: boolean, cancelable?: boolean): void;
|
|
46
|
+
|
|
47
|
+
preventDefault(): void;
|
|
48
|
+
|
|
49
|
+
stopImmediatePropagation(): void;
|
|
50
|
+
|
|
51
|
+
stopPropagation(): void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type EventListener = (evt: Event) => void;
|
|
55
|
+
|
|
56
|
+
// tslint:disable-next-line:interface-name
|
|
57
|
+
interface EventListenerObject {
|
|
58
|
+
handleEvent(evt: Event): void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// tslint:disable-next-line:interface-name
|
|
62
|
+
interface EventListenerOptions {
|
|
63
|
+
capture?: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// tslint:disable-next-line:interface-name
|
|
67
|
+
interface AddEventListenerOptions extends EventListenerOptions {
|
|
68
|
+
once?: boolean;
|
|
69
|
+
|
|
70
|
+
passive?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type EventListenerOrEventListenerObject = EventListener | EventListenerObject;
|
|
74
|
+
|
|
75
|
+
// tslint:disable-next-line:interface-name
|
|
76
|
+
interface EventTarget {
|
|
77
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void;
|
|
78
|
+
|
|
79
|
+
dispatchEvent(evt: Event): boolean;
|
|
80
|
+
|
|
81
|
+
removeEventListener(type: string, listener?: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// tslint:disable-next-line:interface-name
|
|
85
|
+
interface MessageEvent extends Event {
|
|
86
|
+
readonly data: any;
|
|
87
|
+
|
|
88
|
+
readonly origin: string;
|
|
89
|
+
|
|
90
|
+
readonly ports: ReadonlyArray<MessagePort>;
|
|
91
|
+
|
|
92
|
+
readonly source: object | null;
|
|
93
|
+
|
|
94
|
+
initMessageEvent(
|
|
95
|
+
type: string,
|
|
96
|
+
bubbles: boolean,
|
|
97
|
+
cancelable: boolean,
|
|
98
|
+
data: any,
|
|
99
|
+
origin: string,
|
|
100
|
+
lastEventId: string,
|
|
101
|
+
source: object
|
|
102
|
+
): void;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// tslint:disable-next-line:interface-name
|
|
106
|
+
interface MessagePortEventMap {
|
|
107
|
+
message: MessageEvent;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// tslint:disable-next-line:interface-name
|
|
111
|
+
interface MessagePort extends EventTarget {
|
|
112
|
+
onmessage: ((this: MessagePort, ev: MessageEvent) => any) | null;
|
|
113
|
+
|
|
114
|
+
addEventListener<K extends keyof MessagePortEventMap>(
|
|
115
|
+
type: K,
|
|
116
|
+
listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any,
|
|
117
|
+
options?: boolean | AddEventListenerOptions
|
|
118
|
+
): void;
|
|
119
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
120
|
+
|
|
121
|
+
close(): void;
|
|
122
|
+
|
|
123
|
+
postMessage(message?: any, transfer?: any[]): void;
|
|
124
|
+
|
|
125
|
+
removeEventListener<K extends keyof MessagePortEventMap>(
|
|
126
|
+
type: K,
|
|
127
|
+
listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any,
|
|
128
|
+
options?: boolean | EventListenerOptions
|
|
129
|
+
): void;
|
|
130
|
+
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
|
131
|
+
|
|
132
|
+
start(): void;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
declare var MessagePort: {
|
|
136
|
+
prototype: MessagePort;
|
|
137
|
+
|
|
138
|
+
new (): MessagePort;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// tslint:disable-next-line:interface-name
|
|
142
|
+
interface AudioWorkletProcessor {
|
|
143
|
+
port: MessagePort;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// tslint:disable-next-line:interface-name
|
|
147
|
+
interface AudioParamDescriptor {
|
|
148
|
+
defaultValue?: number;
|
|
149
|
+
|
|
150
|
+
maxValue?: number;
|
|
151
|
+
|
|
152
|
+
minValue?: number;
|
|
153
|
+
|
|
154
|
+
name: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// tslint:disable-next-line:interface-name
|
|
158
|
+
interface AudioNodeOptions {
|
|
159
|
+
channelCount: number;
|
|
160
|
+
|
|
161
|
+
channelCountMode: 'clamped-max' | 'explicit' | 'max';
|
|
162
|
+
|
|
163
|
+
channelInterpretation: 'discrete' | 'speakers';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// tslint:disable-next-line:interface-name
|
|
167
|
+
interface AudioWorkletNodeOptions extends AudioNodeOptions {
|
|
168
|
+
numberOfInputs?: number;
|
|
169
|
+
|
|
170
|
+
numberOfOutputs?: number;
|
|
171
|
+
|
|
172
|
+
outputChannelCount: number[];
|
|
173
|
+
|
|
174
|
+
parameterData: { [name: string]: number };
|
|
175
|
+
|
|
176
|
+
processorOptions?: unknown;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// tslint:disable-next-line:interface-name
|
|
180
|
+
interface AudioWorkletProcessorConstructor {
|
|
181
|
+
parameterDescriptors: AudioParamDescriptor[];
|
|
182
|
+
|
|
183
|
+
new (options: AudioWorkletNodeOptions): AudioWorkletProcessor;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
declare var AudioWorkletProcessor: {
|
|
187
|
+
prototype: AudioWorkletProcessor;
|
|
188
|
+
|
|
189
|
+
new (): AudioWorkletProcessor;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
declare const currentFrame: number;
|
|
193
|
+
|
|
194
|
+
declare const currentTime: number;
|
|
195
|
+
|
|
196
|
+
declare const sampleRate: number;
|
|
197
|
+
|
|
198
|
+
declare function registerProcessor<T extends AudioWorkletProcessorConstructor>(name: string, processorCtor: T): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './audio-worklet-processor';
|
package/src/module.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { IAudioWorkletProcessor } from './interfaces';
|
|
2
|
+
|
|
3
|
+
const RENDER_QUANTUM = 128;
|
|
4
|
+
const MASK = 524287;
|
|
5
|
+
|
|
6
|
+
export class PlayoutAudioWorkletProcessor extends AudioWorkletProcessor implements IAudioWorkletProcessor {
|
|
7
|
+
public static parameterDescriptors = [];
|
|
8
|
+
|
|
9
|
+
private _isStarted = false;
|
|
10
|
+
|
|
11
|
+
private _numberOfChannels: number;
|
|
12
|
+
|
|
13
|
+
private _numberOfSlots: number;
|
|
14
|
+
|
|
15
|
+
private _readPointerView: Uint8Array | Uint16Array | Uint32Array;
|
|
16
|
+
|
|
17
|
+
private _slots: Float32Array[][];
|
|
18
|
+
|
|
19
|
+
private _startView: Uint16Array;
|
|
20
|
+
|
|
21
|
+
private _stopView: Uint16Array;
|
|
22
|
+
|
|
23
|
+
private _writePointerView: Uint8Array | Uint16Array | Uint32Array;
|
|
24
|
+
|
|
25
|
+
constructor({ numberOfInputs, numberOfOutputs, outputChannelCount, processorOptions }: AudioWorkletNodeOptions) {
|
|
26
|
+
if (numberOfInputs !== 0) {
|
|
27
|
+
throw new Error('The numberOfInputs must be 0.');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (numberOfOutputs !== 1) {
|
|
31
|
+
throw new Error('The numberOfOutputs must be 1.');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (outputChannelCount === undefined || outputChannelCount.length !== 1) {
|
|
35
|
+
throw new Error('The outputChannelCount must define a single output.');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const [numberOfChannels] = outputChannelCount;
|
|
39
|
+
|
|
40
|
+
if (typeof processorOptions !== 'object' || processorOptions === null) {
|
|
41
|
+
throw new Error();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const readPointerView = 'readPointerView' in processorOptions ? processorOptions.readPointerView : null;
|
|
45
|
+
|
|
46
|
+
if (!(readPointerView instanceof Uint8Array || readPointerView instanceof Uint16Array || readPointerView instanceof Uint32Array)) {
|
|
47
|
+
throw new Error('The readPointerView needs to be an instance of "Uint8Array", "Uint16Array", or "Uint32Array".');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (readPointerView.length !== 1) {
|
|
51
|
+
throw new Error('The readPointerView needs to have a length of 1.');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const startView = 'startView' in processorOptions ? processorOptions.startView : null;
|
|
55
|
+
|
|
56
|
+
if (!(startView instanceof Uint16Array)) {
|
|
57
|
+
throw new Error('The startView needs to be an instance of "Uint16Array".');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (startView.length !== 1) {
|
|
61
|
+
throw new Error('The startView needs to have a length of 1.');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const stopView = 'stopView' in processorOptions ? processorOptions.stopView : null;
|
|
65
|
+
|
|
66
|
+
if (!(stopView instanceof Uint16Array)) {
|
|
67
|
+
throw new Error('The stopView needs to be an instance of "Uint16Array".');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (stopView.length !== 1) {
|
|
71
|
+
throw new Error('The stopView needs to have a length of 1.');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const storageView = 'storageView' in processorOptions ? processorOptions.storageView : null;
|
|
75
|
+
|
|
76
|
+
if (!(storageView instanceof Float32Array)) {
|
|
77
|
+
throw new Error('The storageView needs to be an instance of "Float32Array".');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const numberOfFrames = storageView.length / numberOfChannels;
|
|
81
|
+
|
|
82
|
+
if (!Number.isInteger(numberOfFrames)) {
|
|
83
|
+
throw new Error('The storageView needs to have a length which is a multiple of the number of channels.');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const numberOfSlots = numberOfFrames / RENDER_QUANTUM;
|
|
87
|
+
|
|
88
|
+
if (!Number.isInteger(numberOfSlots)) {
|
|
89
|
+
throw new Error('The capacity can only be a multiple of the render quantum size.');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const writePointerView = 'writePointerView' in processorOptions ? processorOptions.writePointerView : null;
|
|
93
|
+
|
|
94
|
+
if (
|
|
95
|
+
!(writePointerView instanceof Uint8Array || writePointerView instanceof Uint16Array || writePointerView instanceof Uint32Array)
|
|
96
|
+
) {
|
|
97
|
+
throw new Error('The writePointerView needs to be an instance of "Uint8Array", "Uint16Array", or "Uint32Array".');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (writePointerView.length !== 1) {
|
|
101
|
+
throw new Error('The writePointerView needs to have a length of 1.');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (readPointerView.byteLength !== writePointerView.byteLength) {
|
|
105
|
+
throw new Error('The pointer need to be of the same size.');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (numberOfFrames >= 2 ** (readPointerView.byteLength * 8 - 1) /* - THE MASK */) {
|
|
109
|
+
throw new Error('The capacity exceeds the pointer range.');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
super();
|
|
113
|
+
|
|
114
|
+
this._numberOfChannels = numberOfChannels;
|
|
115
|
+
this._numberOfSlots = numberOfSlots;
|
|
116
|
+
this._readPointerView = readPointerView;
|
|
117
|
+
this._slots = Array.from({ length: numberOfSlots }, (_, slotIndex) => {
|
|
118
|
+
const slotOffset = slotIndex * RENDER_QUANTUM;
|
|
119
|
+
|
|
120
|
+
return Array.from(
|
|
121
|
+
{ length: numberOfChannels },
|
|
122
|
+
// tslint:disable-next-line:no-shadowed-variable
|
|
123
|
+
(_, channelIndex) => {
|
|
124
|
+
const channelOffset = slotOffset + channelIndex * numberOfSlots * RENDER_QUANTUM;
|
|
125
|
+
|
|
126
|
+
return storageView.subarray(channelOffset, channelOffset + RENDER_QUANTUM);
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
this._startView = startView;
|
|
131
|
+
this._stopView = stopView;
|
|
132
|
+
this._writePointerView = writePointerView;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public process(_: Float32Array[][], [output]: Float32Array[][]): boolean {
|
|
136
|
+
if (!this._isStarted) {
|
|
137
|
+
if (!this._readStart()) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this._isStarted = true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const flag = !(!this._readStorage(output) && this._readStop());
|
|
145
|
+
|
|
146
|
+
if (!flag) {
|
|
147
|
+
this.port.postMessage(null);
|
|
148
|
+
this.port.close();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return flag;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private _readStart(): boolean {
|
|
155
|
+
const start = Atomics.load(this._startView, 0);
|
|
156
|
+
|
|
157
|
+
return start === 1;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private _readStop(): boolean {
|
|
161
|
+
const stop = Atomics.load(this._stopView, 0);
|
|
162
|
+
|
|
163
|
+
return stop === 1;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private _readStorage(output: Float32Array[]): boolean {
|
|
167
|
+
const readPointer = Atomics.load(this._readPointerView, 0) / RENDER_QUANTUM;
|
|
168
|
+
// tslint:disable-next-line:no-bitwise
|
|
169
|
+
const writePointer = Math.floor((Atomics.load(this._writePointerView, 0) & MASK) / RENDER_QUANTUM);
|
|
170
|
+
|
|
171
|
+
if (writePointer === readPointer) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const slot = this._slots[readPointer % this._numberOfSlots];
|
|
176
|
+
|
|
177
|
+
for (let channelIndex = 0; channelIndex < this._numberOfChannels; channelIndex += 1) {
|
|
178
|
+
output[channelIndex].set(slot[channelIndex]);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
Atomics.store(this._readPointerView, 0, ((readPointer + 1) % (this._numberOfSlots * 2)) * RENDER_QUANTUM);
|
|
182
|
+
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
}
|