-
Notifications
You must be signed in to change notification settings - Fork 20
/
index.js
124 lines (111 loc) · 3.94 KB
/
index.js
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
/**
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
import { Realm } from './realm';
const PARAMS = [];
let nextPort;
if (typeof AudioWorkletNode !== 'function') {
self.AudioWorkletNode = function AudioWorkletNode (context, name, options) {
const processor = getProcessorsForContext(context)[name];
const outputChannels = options && options.outputChannelCount ? options.outputChannelCount[0] : 2;
const scriptProcessor = context.createScriptProcessor(undefined, 2, outputChannels);
scriptProcessor.parameters = new Map();
if (processor.properties) {
for (let i = 0; i < processor.properties.length; i++) {
const prop = processor.properties[i];
const node = context.createGain().gain;
node.value = prop.defaultValue;
// @TODO there's no good way to construct the proxy AudioParam here
scriptProcessor.parameters.set(prop.name, node);
}
}
const mc = new MessageChannel();
nextPort = mc.port2;
const inst = new processor.Processor(options || {});
nextPort = null;
scriptProcessor.port = mc.port1;
scriptProcessor.processor = processor;
scriptProcessor.instance = inst;
scriptProcessor.onaudioprocess = onAudioProcess;
return scriptProcessor;
};
Object.defineProperty((self.AudioContext || self.webkitAudioContext).prototype, 'audioWorklet', {
get () {
return this.$$audioWorklet || (this.$$audioWorklet = new self.AudioWorklet(this));
}
});
self.AudioWorklet = class AudioWorklet {
constructor (audioContext) {
this.$$context = audioContext;
}
addModule (url, options) {
return fetch(url).then(r => {
if (!r.ok) throw Error(r.status);
return r.text();
}).then(code => {
const context = {
sampleRate: 0,
currentTime: 0,
AudioWorkletProcessor () {
this.port = nextPort;
},
registerProcessor: (name, Processor) => {
const processors = getProcessorsForContext(this.$$context);
processors[name] = {
realm,
context,
Processor,
properties: Processor.parameterDescriptors || []
};
}
};
context.self = context;
const realm = new Realm(context, document.documentElement);
realm.exec(((options && options.transpile) || String)(code));
return null;
});
}
};
}
function onAudioProcess (e) {
const parameters = {};
let index = -1;
this.parameters.forEach((value, key) => {
const arr = PARAMS[++index] || (PARAMS[index] = new Float32Array(this.bufferSize));
// @TODO proper values here if possible
arr.fill(value.value);
parameters[key] = arr;
});
this.processor.realm.exec(
'self.sampleRate=sampleRate=' + this.context.sampleRate + ';' +
'self.currentTime=currentTime=' + this.context.currentTime
);
const inputs = channelToArray(e.inputBuffer);
const outputs = channelToArray(e.outputBuffer);
this.instance.process([inputs], [outputs], parameters);
// @todo - keepalive
// let ret = this.instance.process([inputs], [outputs], parameters);
// if (ret === true) { }
}
function channelToArray (ch) {
const out = [];
for (let i = 0; i < ch.numberOfChannels; i++) {
out[i] = ch.getChannelData(i);
}
return out;
}
function getProcessorsForContext (audioContext) {
return audioContext.$$processors || (audioContext.$$processors = {});
}