├── .eslintrc.js ├── .gitattributes ├── .github └── workflows │ ├── nodejs.yml │ └── npmpublish.yml ├── .gitignore ├── .idea ├── .gitignore ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── inspectionProfiles │ └── Project_Default.xml ├── modules.xml ├── prettier.xml ├── vcs.xml └── web-audio-js.iml ├── .prettierrc ├── .travis.yml ├── README.md ├── benchmark ├── browser-benchmark │ ├── noop.js │ ├── sched-param.js │ ├── sched-sine.js │ ├── simple-gain.js │ ├── simple-saw.js │ └── simple-sine.js ├── index.html ├── main.js ├── package.json └── suites │ ├── AudioBufferSourceNode-params-fixed-0.js │ ├── AudioBufferSourceNode-params-fixed-1.js │ ├── AudioBufferSourceNode-params-fixed-x.js │ ├── AudioBufferSourceNode-params-sched.js │ ├── BiquadFilterNode-params-fixed-x.js │ ├── BiquadFilterNode-params-sched.js │ ├── BiquadFilterNode-silent-input.js │ ├── DelayNode-params-fixed-x.js │ ├── DelayNode-params-sched.js │ ├── DelayNode-silent-input.js │ ├── GainNode-params-fixed-0.js │ ├── GainNode-params-fixed-1.js │ ├── GainNode-params-fixed-x.js │ ├── GainNode-params-sched.js │ ├── GainNode-silent-input.js │ ├── OfflineAudioContext-noop.js │ ├── OfflineAudioContext-sine.js │ ├── OscillatorNode-sine-params-fixed-x.js │ ├── OscillatorNode-sine-params-sched.js │ ├── OscillatorNode-tri-params-fixed-x.js │ ├── OscillatorNode-tri-params-sched.js │ ├── StereoPannerNode-params-fixed-x.js │ ├── StereoPannerNode-params-sched.js │ ├── StereoPannerNode-silent-input.js │ ├── WaveShaperNode-curve.js │ ├── WaveShaperNode-none-curve.js │ └── WaveShaperNode-slient-input.js ├── demo ├── .gitignore ├── assets │ └── sound │ │ ├── a11wlk01.wav │ │ ├── amen.wav │ │ ├── hihat1.wav │ │ ├── hihat2.wav │ │ ├── kick.wav │ │ ├── snare.wav │ │ ├── tom1.wav │ │ └── tom2.wav ├── demo.js ├── index.html ├── package.json └── sources │ ├── chorus.js │ ├── delay.js │ ├── drum.js │ ├── filter.js │ ├── metronome.js │ ├── pan.js │ ├── sines.js │ ├── test_delay-modulation.js │ ├── test_filter-modulation.js │ ├── test_iir-filter.js │ └── test_loop.js ├── package.json ├── rollup.config.js ├── src ├── __tests__ │ ├── .eslintrc.json │ ├── api │ │ ├── AnalyserNode.js │ │ ├── AudioBuffer.js │ │ ├── AudioBufferSourceNode.js │ │ ├── AudioDestinationNode.js │ │ ├── AudioListener.js │ │ ├── AudioNode.js │ │ ├── AudioParam.js │ │ ├── BaseAudioContext.js │ │ ├── BiquadFilterNode.js │ │ ├── ChannelMergerNode.js │ │ ├── ChannelSplitterNode.js │ │ ├── ConstantSourceNode.js │ │ ├── ConvolverNode.js │ │ ├── DelayNode.js │ │ ├── DynamicsCompressorNode.js │ │ ├── EventTarget.js │ │ ├── GainNode.js │ │ ├── IIRFilterNode.js │ │ ├── OscillatorNode.js │ │ ├── PannerNode.js │ │ ├── PeriodicWave.js │ │ ├── ScriptProcessorNode.js │ │ ├── SpatialPannerNode.js │ │ ├── StereoPannerNode.js │ │ └── WaveShaperNode.js │ ├── context │ │ ├── OfflineAudioContext.js │ │ ├── RenderingAudioContext.js │ │ └── StreamAudioContext.js │ ├── decoder.js │ ├── encoder.js │ ├── impl │ │ ├── AnalyserNode.js │ │ ├── AudioBuffer.js │ │ ├── AudioBufferSourceNode.js │ │ ├── AudioContext.js │ │ ├── AudioDestinationNode.js │ │ ├── AudioNode.js │ │ ├── AudioParam.js │ │ ├── BasePannerNode.js │ │ ├── BiquadFilterNode.js │ │ ├── ChannelMergerNode.js │ │ ├── ChannelSplitterNode.js │ │ ├── ConstantSourceNode.js │ │ ├── ConvolverNode.js │ │ ├── DelayNode.js │ │ ├── DynamicsCompressorNode.js │ │ ├── GainNode.js │ │ ├── IIRFilterNode.js │ │ ├── OscillatorNode.js │ │ ├── PannerNode.js │ │ ├── PeriodicWave.js │ │ ├── ScriptProcessorNode.js │ │ ├── SpatialListener.js │ │ ├── SpatialPannerNode.js │ │ ├── StereoPannerNode.js │ │ ├── WaveShaperNode.js │ │ ├── core │ │ │ ├── AudioBus.js │ │ │ ├── AudioBusMixing.js │ │ │ ├── AudioData.js │ │ │ ├── AudioNodeChannelPropagation.js │ │ │ ├── AudioNodeConnection.js │ │ │ ├── AudioNodeDSP.js │ │ │ ├── AudioNodeInput.js │ │ │ └── AudioNodeOutput.js │ │ └── dsp │ │ │ ├── AnalyserNode.js │ │ │ ├── AudioContext.js │ │ │ ├── AudioDestinationNode.js │ │ │ ├── AudioNode.js │ │ │ ├── AudioParam.js │ │ │ ├── BiquadFilterNode.js │ │ │ ├── ChannelMergerNode.js │ │ │ ├── ChannelSplitterNode.js │ │ │ ├── DynamicsCompressorNode.js │ │ │ ├── GainNode.js │ │ │ ├── IIRFilterNode.js │ │ │ ├── ScriptProcessorNode.js │ │ │ ├── StereoPannerNode.js │ │ │ └── WaveShaperNode.js │ ├── mocha.opts │ └── utils │ │ ├── AudioDataUtils.js │ │ ├── AudioParamUtils.js │ │ ├── DecoderUtils.js │ │ ├── EncoderUtils.js │ │ ├── PCMArrayBufferWriter.js │ │ ├── PCMBufferWriter.js │ │ ├── PCMEncoder.js │ │ ├── setImmediate.js │ │ └── utils │ │ ├── clamp.js │ │ ├── defaults.js │ │ ├── defineProp.js │ │ ├── fill.js │ │ ├── fillRange.js │ │ ├── normalize.js │ │ ├── toArrayIfNeeded.js │ │ ├── toAudioTime.js │ │ ├── toDecibel.js │ │ ├── toGain.js │ │ ├── toImpl.js │ │ ├── toNumber.js │ │ ├── toPowerOfTwo.js │ │ ├── toValidBitDepth.js │ │ ├── toValidBlockSize.js │ │ ├── toValidNumberOfChannels.js │ │ └── toValidSampleRate.js ├── __tests_helpers │ ├── DynamicsCompressorData.js │ ├── np.js │ └── paramTester.js ├── api │ ├── AnalyserNode.js │ ├── AudioBuffer.js │ ├── AudioBufferSourceNode.js │ ├── AudioDestinationNode.js │ ├── AudioListener.js │ ├── AudioNode.js │ ├── AudioParam.js │ ├── AudioScheduledSourceNode.js │ ├── AudioWorkletNode.ts │ ├── AudioWorkletProcessor.ts │ ├── BaseAudioContext.js │ ├── BiquadFilterNode.js │ ├── ChannelMergerNode.js │ ├── ChannelSplitterNode.js │ ├── ConstantSourceNode.js │ ├── ConvolverNode.js │ ├── DelayNode.js │ ├── DynamicsCompressorNode.js │ ├── EventTarget.js │ ├── GainNode.js │ ├── IIRFilterNode.js │ ├── OscillatorNode.js │ ├── PannerNode.js │ ├── PeriodicWave.js │ ├── README.md │ ├── ScriptProcessorNode.js │ ├── SpatialListener.js │ ├── SpatialPannerNode.js │ ├── StereoPannerNode.js │ ├── WaveShaperNode.js │ └── index.js ├── config.js ├── constants │ ├── AudioContextState.js │ ├── AudioParamEvent.js │ ├── AudioParamRate.js │ ├── BiquadFilterType.js │ ├── ChannelCountMode.js │ ├── ChannelInterpretation.js │ ├── OscillatorType.js │ ├── PlaybackState.js │ └── index.js ├── context │ ├── OfflineAudioContext.js │ ├── RawDataAudioContext.ts │ ├── RenderingAudioContext.js │ ├── StreamAudioContext.js │ └── WebAudioContext.js ├── decoder.js ├── encoder.js ├── impl │ ├── AnalyserNode.js │ ├── AudioBuffer.js │ ├── AudioBufferSourceNode.js │ ├── AudioContext.js │ ├── AudioDestinationNode.js │ ├── AudioListener.js │ ├── AudioNode.js │ ├── AudioParam.js │ ├── AudioScheduledSourceNode.js │ ├── AudioSourceNode.js │ ├── AudioWorklet.ts │ ├── AudioWorkletNode.ts │ ├── AudioWorkletPort.ts │ ├── AudioWorkletProcessor.ts │ ├── BasePannerNode.js │ ├── BiquadFilterNode.js │ ├── ChannelMergerNode.js │ ├── ChannelSplitterNode.js │ ├── ConstantSourceNode.js │ ├── ConvolverNode.js │ ├── DelayNode.js │ ├── DynamicsCompressorNode.js │ ├── EventTarget.js │ ├── GainNode.js │ ├── IIRFilterNode.js │ ├── OscillatorNode.js │ ├── PannerNode.js │ ├── PeriodicWave.js │ ├── ScriptProcessorNode.js │ ├── SpatialListener.js │ ├── SpatialPannerNode.js │ ├── StereoPannerNode.js │ ├── WaveShaperNode.js │ ├── core │ │ ├── AudioBus.js │ │ ├── AudioData.js │ │ ├── AudioNodeInput.js │ │ └── AudioNodeOutput.js │ ├── dsp │ │ ├── AnalyserNode.js │ │ ├── AudioBufferSourceNode.js │ │ ├── AudioParam.js │ │ ├── BiquadFilterKernel.js │ │ ├── BiquadFilterNode.js │ │ ├── ChannelMergerNode.js │ │ ├── ChannelSplitterNode.js │ │ ├── ConstantSourceNode.js │ │ ├── ConvolverNode.js │ │ ├── DelayNode.js │ │ ├── DynamicsCompressor.js │ │ ├── DynamicsCompressorKernel.js │ │ ├── GainNode.js │ │ ├── IIRFilterKernel.js │ │ ├── IIRFilterNode.js │ │ ├── OscillatorNode.js │ │ ├── PannerNode.js │ │ ├── PeriodicWave.js │ │ ├── ScriptProcessorNode.js │ │ ├── SpatialPannerNode.js │ │ ├── StereoPannerNode.js │ │ └── WaveShaperNode.js │ └── index.js ├── index.js └── utils │ ├── AudioDataUtils.js │ ├── AudioParamUtils.js │ ├── DecoderUtils.js │ ├── Encoder.ts │ ├── EncoderUtils.js │ ├── FilterUtils.js │ ├── Format.ts │ ├── PCMArrayBufferWriter.js │ ├── PCMBufferWriter.js │ ├── PCMEncoder.js │ ├── index.js │ ├── nmap.ts │ ├── setImmediate.js │ └── utils │ ├── clamp.js │ ├── defaults.js │ ├── defineProp.js │ ├── fill.js │ ├── fillRange.js │ ├── flushDenormalFloatToZero.js │ ├── normalize.js │ ├── toArrayIfNeeded.js │ ├── toAudioTime.js │ ├── toDecibel.js │ ├── toGain.js │ ├── toImpl.js │ ├── toLinear.js │ ├── toNumber.js │ ├── toPowerOfTwo.js │ ├── toValidBitDepth.js │ ├── toValidBlockSize.js │ ├── toValidNumberOfChannels.js │ └── toValidSampleRate.js ├── tsconfig.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'plugin:@typescript-eslint/recommended', 4 | 'prettier', 5 | 'prettier/@typescript-eslint', 6 | ], 7 | rules: { 8 | '@typescript-eslint/no-inferrable-types': 'off', 9 | '@typescript-eslint/no-use-before-define': 'off', 10 | '@typescript-eslint/no-parameter-properties': 'off', 11 | '@typescript-eslint/explicit-function-return-type': 'off', 12 | '@typescript-eslint/no-var-requires': 'off', 13 | '@typescript-eslint/no-empty-function': 'warn', 14 | '@typescript-eslint/camelcase': 'warn', 15 | '@typescript-eslint/no-this-alias': 'warn', 16 | 'prefer-rest-params': 'warn', 17 | 'prefer-spread': 'warn', 18 | 'prefer-const': 'warn', 19 | 'no-var': 'warn', 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.wav binary 2 | build/*.js -diff 3 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | node-version: [12.x, 14.x, 16.x] 14 | env: 15 | CI: true 16 | steps: 17 | - uses: actions/checkout@v1 18 | - name: Use Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | - run: yarn install --frozen-lockfile 23 | - run: yarn lint 24 | - run: yarn test-ci 25 | - run: yarn build 26 | -------------------------------------------------------------------------------- /.github/workflows/npmpublish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NPM 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | publish-npm: 10 | runs-on: ubuntu-latest 11 | env: 12 | CI: true 13 | steps: 14 | - uses: actions/checkout@v1 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: 12 18 | registry-url: https://registry.npmjs.org/ 19 | - run: yarn install --frozen-lockfile 20 | - run: yarn run lint 21 | - run: yarn run test-ci 22 | - run: yarn run build 23 | - run: npm publish --access public 24 | env: 25 | NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | .nyc_output 3 | npm-debug.log 4 | yarn-debug.log 5 | yarn-error.log 6 | node_modules/ 7 | coverage/ 8 | lib/ 9 | build/ 10 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/prettier.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/web-audio-js.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "singleQuote": true, 4 | "arrowParens": "always" 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "6.0" 5 | cache: 6 | directories: 7 | - node_modules 8 | script: 9 | - npm run travis 10 | -------------------------------------------------------------------------------- /benchmark/browser-benchmark/noop.js: -------------------------------------------------------------------------------- 1 | module.exports = function() {}; 2 | -------------------------------------------------------------------------------- /benchmark/browser-benchmark/sched-param.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context) { 2 | var osc = context.createOscillator(); 3 | 4 | for (var i = 0; i < 1200; i++) { 5 | osc.detune.setValueAtTime(i, i / 1200); 6 | } 7 | 8 | osc.start(0.25); 9 | osc.stop(0.75); 10 | osc.connect(context.destination); 11 | }; 12 | -------------------------------------------------------------------------------- /benchmark/browser-benchmark/sched-sine.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context) { 2 | for (var i = 0; i < 500; i++) { 3 | var osc = context.createOscillator(); 4 | 5 | osc.start(i * 0.01); 6 | osc.stop(i * 0.01 + 0.005); 7 | osc.connect(context.destination); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /benchmark/browser-benchmark/simple-gain.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context) { 2 | var osc = context.createOscillator(); 3 | var amp = context.createGain(); 4 | 5 | osc.start(0); 6 | osc.stop(1); 7 | osc.connect(amp); 8 | 9 | amp.gain.setValueAtTime(1, 0); 10 | amp.gain.linearRampToValueAtTime(0, 1); 11 | amp.connect(context.destination); 12 | }; 13 | -------------------------------------------------------------------------------- /benchmark/browser-benchmark/simple-saw.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context) { 2 | var osc = context.createOscillator(); 3 | 4 | osc.type = "sawtooth"; 5 | osc.start(0.25); 6 | osc.stop(0.75); 7 | osc.connect(context.destination); 8 | }; 9 | -------------------------------------------------------------------------------- /benchmark/browser-benchmark/simple-sine.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context) { 2 | var osc = context.createOscillator(); 3 | 4 | osc.start(0.25); 5 | osc.stop(0.75); 6 | osc.connect(context.destination); 7 | }; 8 | -------------------------------------------------------------------------------- /benchmark/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs"); 4 | const vm = require("vm"); 5 | const readline = require("readline"); 6 | 7 | const files = fs.readdirSync(`${ __dirname }/suites`) 8 | .filter(filename => /\.js$/.test(filename)) 9 | .sort(); 10 | 11 | function showList() { 12 | files.forEach((filename, index) => { 13 | console.log(`[${ index }] ${ filename.replace(/\.js$/, "") }`) 14 | }); 15 | } 16 | 17 | function chooseBenchmark(callback) { 18 | const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); 19 | 20 | showList(); 21 | 22 | rl.question("choose benchmark? ", (indices) => { 23 | run(indices); 24 | rl.close(); 25 | }); 26 | } 27 | 28 | function run(indices) { 29 | const names = Array.prototype.concat.apply([], indices.split(/\s+/g).map(collectBenchmark)); 30 | 31 | return names.reduce((promise, name) => { 32 | return promise.then(() => { 33 | const func = require(`${ __dirname }/suites/${ name }`); 34 | 35 | console.log("-- " + name + " " + "-".repeat(Math.max(0, 71 - (name.length)))); 36 | 37 | return new Promise((resolve) => { func(resolve); }); 38 | }); 39 | }, Promise.resolve()); 40 | } 41 | 42 | function collectBenchmark(index) { 43 | if (/^\d+$/.test(index)) { 44 | return [ files[index] ]; 45 | } 46 | return files.filter(name => name.indexOf(index) !== -1); 47 | } 48 | 49 | if (process.argv[2]) { 50 | run(process.argv.slice(2).join(" ")); 51 | } else { 52 | chooseBenchmark(); 53 | } 54 | -------------------------------------------------------------------------------- /benchmark/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@web-audio-engine/benchnmark", 3 | "description": "benchnmark", 4 | "version": "0.0.0", 5 | "dependencies": { 6 | "benchmark": "^2.1.0" 7 | }, 8 | "license": "MIT", 9 | "main": "main.js", 10 | "private": true 11 | } 12 | -------------------------------------------------------------------------------- /benchmark/suites/AudioBufferSourceNode-params-fixed-0.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const buffer = new wae.impl.AudioBuffer(context, { 18 | numberOfChannels: this.numberOfChannels, length: 1024, sampleRate: context.sampleRate 19 | }); 20 | const bufSrc = new wae.impl.AudioBufferSourceNode(context); 21 | 22 | bufSrc.setBuffer(buffer); 23 | bufSrc.start(0); 24 | bufSrc.getPlaybackRate().value = 0; 25 | }, 26 | fn() { 27 | bufSrc.dspProcess(); 28 | } 29 | }; 30 | } 31 | 32 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 33 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 34 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 35 | 36 | suite.on("cycle", (e) => { 37 | console.log(e.target.toString()); 38 | }); 39 | 40 | suite.on("complete", () => { 41 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 42 | console.log(); 43 | }); 44 | 45 | suite.on("error", (e) => { 46 | console.error(e); 47 | }); 48 | 49 | suite.run(); 50 | } 51 | 52 | module.exports = (done) => { 53 | run(1); 54 | done(); 55 | }; 56 | 57 | if (module.parent === null) { 58 | module.exports(process.exit); 59 | } 60 | -------------------------------------------------------------------------------- /benchmark/suites/AudioBufferSourceNode-params-fixed-1.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const buffer = new wae.impl.AudioBuffer(context, { 18 | numberOfChannels: this.numberOfChannels, length: 1024, sampleRate: context.sampleRate 19 | }); 20 | const bufSrc = new wae.impl.AudioBufferSourceNode(context); 21 | 22 | bufSrc.setBuffer(buffer); 23 | bufSrc.start(0); 24 | bufSrc.getPlaybackRate().value = 1; 25 | }, 26 | fn() { 27 | bufSrc.dspProcess(); 28 | } 29 | }; 30 | } 31 | 32 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 33 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 34 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 35 | 36 | suite.on("cycle", (e) => { 37 | console.log(e.target.toString()); 38 | }); 39 | 40 | suite.on("complete", () => { 41 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 42 | console.log(); 43 | }); 44 | 45 | suite.on("error", (e) => { 46 | console.error(e); 47 | }); 48 | 49 | suite.run(); 50 | } 51 | 52 | module.exports = (done) => { 53 | run(1); 54 | done(); 55 | }; 56 | 57 | if (module.parent === null) { 58 | module.exports(process.exit); 59 | } 60 | -------------------------------------------------------------------------------- /benchmark/suites/AudioBufferSourceNode-params-fixed-x.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const buffer = new wae.impl.AudioBuffer(context, { 18 | numberOfChannels: this.numberOfChannels, length: 1024, sampleRate: context.sampleRate 19 | }); 20 | const bufSrc = new wae.impl.AudioBufferSourceNode(context); 21 | 22 | bufSrc.setBuffer(buffer); 23 | bufSrc.start(0); 24 | bufSrc.getPlaybackRate().value = 0.5; 25 | }, 26 | fn() { 27 | bufSrc.dspProcess(); 28 | } 29 | }; 30 | } 31 | 32 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 33 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 34 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 35 | 36 | suite.on("cycle", (e) => { 37 | console.log(e.target.toString()); 38 | }); 39 | 40 | suite.on("complete", () => { 41 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 42 | console.log(); 43 | }); 44 | 45 | suite.on("error", (e) => { 46 | console.error(e); 47 | }); 48 | 49 | suite.run(); 50 | } 51 | 52 | module.exports = (done) => { 53 | run(1); 54 | done(); 55 | }; 56 | 57 | if (module.parent === null) { 58 | module.exports(process.exit); 59 | } 60 | -------------------------------------------------------------------------------- /benchmark/suites/AudioBufferSourceNode-params-sched.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const buffer = new wae.impl.AudioBuffer(context, { 18 | numberOfChannels: this.numberOfChannels, length: 1024, sampleRate: context.sampleRate 19 | }); 20 | const bufSrc = new wae.impl.AudioBufferSourceNode(context); 21 | 22 | bufSrc.setBuffer(buffer); 23 | bufSrc.start(0); 24 | bufSrc.getPlaybackRate().setValueAtTime(0.5, 0); 25 | bufSrc.getPlaybackRate().linearRampToValueAtTime(1.0, 1); 26 | bufSrc.getPlaybackRate().dspProcess(); 27 | }, 28 | fn() { 29 | bufSrc.dspProcess(); 30 | } 31 | }; 32 | } 33 | 34 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 35 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 36 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 37 | 38 | suite.on("cycle", (e) => { 39 | console.log(e.target.toString()); 40 | }); 41 | 42 | suite.on("complete", () => { 43 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 44 | console.log(); 45 | }); 46 | 47 | suite.on("error", (e) => { 48 | console.error(e); 49 | }); 50 | 51 | suite.run(); 52 | } 53 | 54 | module.exports = (done) => { 55 | run(1); 56 | run(2); 57 | run(4); 58 | done(); 59 | }; 60 | 61 | if (module.parent === null) { 62 | module.exports(process.exit); 63 | } 64 | -------------------------------------------------------------------------------- /benchmark/suites/BiquadFilterNode-params-fixed-x.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const biquad = new wae.impl.BiquadFilterNode(context); 18 | 19 | biquad.inputs[0].bus.setNumberOfChannels(this.numberOfChannels); 20 | biquad.inputs[0].bus.getMutableData(); 21 | biquad.outputs[0].bus.setNumberOfChannels(this.numberOfChannels); 22 | 23 | biquad.getFrequency().value = 350; 24 | }, 25 | fn() { 26 | biquad.dspProcess(); 27 | } 28 | }; 29 | } 30 | 31 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 32 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 33 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 34 | 35 | suite.on("cycle", (e) => { 36 | console.log(e.target.toString()); 37 | }); 38 | 39 | suite.on("complete", () => { 40 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 41 | console.log(); 42 | }); 43 | 44 | suite.on("error", (e) => { 45 | console.error(e); 46 | }); 47 | 48 | suite.run(); 49 | } 50 | 51 | module.exports = (done) => { 52 | run(1); 53 | run(2); 54 | run(4); 55 | done(); 56 | }; 57 | 58 | if (module.parent === null) { 59 | module.exports(process.exit); 60 | } 61 | -------------------------------------------------------------------------------- /benchmark/suites/BiquadFilterNode-params-sched.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const biquad = new wae.impl.BiquadFilterNode(context); 18 | 19 | biquad.inputs[0].bus.setNumberOfChannels(this.numberOfChannels); 20 | biquad.inputs[0].bus.getMutableData(); 21 | biquad.outputs[0].bus.setNumberOfChannels(this.numberOfChannels); 22 | 23 | biquad.getFrequency().setValueAtTime(350.0, 0); 24 | biquad.getFrequency().linearRampToValueAtTime(700.0, 1); 25 | biquad.getFrequency().dspProcess(); 26 | }, 27 | fn() { 28 | biquad.dspProcess(); 29 | } 30 | }; 31 | } 32 | 33 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 34 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 35 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 36 | 37 | suite.on("cycle", (e) => { 38 | console.log(e.target.toString()); 39 | }); 40 | 41 | suite.on("complete", () => { 42 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 43 | console.log(); 44 | }); 45 | 46 | suite.on("error", (e) => { 47 | console.error(e); 48 | }); 49 | 50 | suite.run(); 51 | } 52 | 53 | module.exports = (done) => { 54 | run(1); 55 | run(2); 56 | run(4); 57 | done(); 58 | }; 59 | 60 | if (module.parent === null) { 61 | module.exports(process.exit); 62 | } 63 | -------------------------------------------------------------------------------- /benchmark/suites/BiquadFilterNode-silent-input.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const biquad = new wae.impl.BiquadFilterNode(context); 18 | 19 | biquad.inputs[0].bus.setNumberOfChannels(this.numberOfChannels); 20 | biquad.inputs[0].bus.zeros(); 21 | biquad.outputs[0].bus.setNumberOfChannels(this.numberOfChannels); 22 | 23 | biquad.getFrequency().setValueAtTime(350.0, 0); 24 | biquad.getFrequency().linearRampToValueAtTime(700.0, 1); 25 | biquad.getFrequency().dspProcess(); 26 | }, 27 | fn() { 28 | biquad.dspProcess(); 29 | } 30 | }; 31 | } 32 | 33 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 34 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 35 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 36 | 37 | suite.on("cycle", (e) => { 38 | console.log(e.target.toString()); 39 | }); 40 | 41 | suite.on("complete", () => { 42 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 43 | console.log(); 44 | }); 45 | 46 | suite.on("error", (e) => { 47 | console.error(e); 48 | }); 49 | 50 | suite.run(); 51 | } 52 | 53 | module.exports = (done) => { 54 | run(1); 55 | run(2); 56 | run(4); 57 | done(); 58 | }; 59 | 60 | if (module.parent === null) { 61 | module.exports(process.exit); 62 | } 63 | -------------------------------------------------------------------------------- /benchmark/suites/DelayNode-params-fixed-x.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const delay = new wae.impl.DelayNode(context); 18 | 19 | delay.inputs[0].bus.setNumberOfChannels(this.numberOfChannels); 20 | delay.inputs[0].bus.getMutableData(); 21 | delay.outputs[0].bus.setNumberOfChannels(this.numberOfChannels); 22 | 23 | delay.getDelayTime().value = 8 / context.sampleRate; 24 | }, 25 | fn() { 26 | delay.dspProcess(); 27 | } 28 | }; 29 | } 30 | 31 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 32 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 33 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 34 | 35 | suite.on("cycle", (e) => { 36 | console.log(e.target.toString()); 37 | }); 38 | 39 | suite.on("complete", () => { 40 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 41 | console.log(); 42 | }); 43 | 44 | suite.on("error", (e) => { 45 | console.error(e); 46 | }); 47 | 48 | suite.run(); 49 | } 50 | 51 | module.exports = (done) => { 52 | run(1); 53 | run(2); 54 | run(4); 55 | done(); 56 | }; 57 | 58 | if (module.parent === null) { 59 | module.exports(process.exit); 60 | } 61 | -------------------------------------------------------------------------------- /benchmark/suites/DelayNode-params-sched.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const delay = new wae.impl.DelayNode(context); 18 | 19 | delay.inputs[0].bus.setNumberOfChannels(this.numberOfChannels); 20 | delay.inputs[0].bus.getMutableData(); 21 | delay.outputs[0].bus.setNumberOfChannels(this.numberOfChannels); 22 | 23 | delay.getDelayTime().setValueAtTime(0.0, 0); 24 | delay.getDelayTime().linearRampToValueAtTime(1.0, 1); 25 | delay.getDelayTime().dspProcess(); 26 | }, 27 | fn() { 28 | delay.dspProcess(); 29 | } 30 | }; 31 | } 32 | 33 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 34 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 35 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 36 | 37 | suite.on("cycle", (e) => { 38 | console.log(e.target.toString()); 39 | }); 40 | 41 | suite.on("complete", () => { 42 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 43 | console.log(); 44 | }); 45 | 46 | suite.on("error", (e) => { 47 | console.error(e); 48 | }); 49 | 50 | suite.run(); 51 | } 52 | 53 | module.exports = (done) => { 54 | run(1); 55 | run(2); 56 | run(4); 57 | done(); 58 | }; 59 | 60 | if (module.parent === null) { 61 | module.exports(process.exit); 62 | } 63 | -------------------------------------------------------------------------------- /benchmark/suites/DelayNode-silent-input.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const delay = new wae.impl.DelayNode(context); 18 | 19 | delay.inputs[0].bus.setNumberOfChannels(this.numberOfChannels); 20 | delay.inputs[0].bus.zeros(); 21 | delay.outputs[0].bus.setNumberOfChannels(this.numberOfChannels); 22 | 23 | delay.getDelayTime().setValueAtTime(0.0, 0); 24 | delay.getDelayTime().linearRampToValueAtTime(1.0, 1); 25 | delay.getDelayTime().dspProcess(); 26 | }, 27 | fn() { 28 | delay.dspProcess(); 29 | } 30 | }; 31 | } 32 | 33 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 34 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 35 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 36 | 37 | suite.on("cycle", (e) => { 38 | console.log(e.target.toString()); 39 | }); 40 | 41 | suite.on("complete", () => { 42 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 43 | console.log(); 44 | }); 45 | 46 | suite.on("error", (e) => { 47 | console.error(e); 48 | }); 49 | 50 | suite.run(); 51 | } 52 | 53 | module.exports = (done) => { 54 | run(1); 55 | run(2); 56 | run(4); 57 | done(); 58 | }; 59 | 60 | if (module.parent === null) { 61 | module.exports(process.exit); 62 | } 63 | -------------------------------------------------------------------------------- /benchmark/suites/GainNode-params-fixed-0.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const gain = new wae.impl.GainNode(context); 18 | 19 | gain.inputs[0].bus.setNumberOfChannels(this.numberOfChannels); 20 | gain.inputs[0].bus.getMutableData(); 21 | gain.outputs[0].bus.setNumberOfChannels(this.numberOfChannels); 22 | 23 | gain.getGain().value = 0; 24 | }, 25 | fn() { 26 | gain.dspProcess(); 27 | } 28 | }; 29 | } 30 | 31 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 32 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 33 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 34 | 35 | suite.on("cycle", (e) => { 36 | console.log(e.target.toString()); 37 | }); 38 | 39 | suite.on("complete", () => { 40 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 41 | console.log(); 42 | }); 43 | 44 | suite.on("error", (e) => { 45 | console.error(e); 46 | }); 47 | 48 | suite.run(); 49 | } 50 | 51 | module.exports = (done) => { 52 | run(1); 53 | run(2); 54 | run(4); 55 | done(); 56 | }; 57 | 58 | if (module.parent === null) { 59 | module.exports(process.exit); 60 | } 61 | -------------------------------------------------------------------------------- /benchmark/suites/GainNode-params-fixed-1.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const gain = new wae.impl.GainNode(context); 18 | 19 | gain.inputs[0].bus.setNumberOfChannels(this.numberOfChannels); 20 | gain.inputs[0].bus.getMutableData(); 21 | gain.outputs[0].bus.setNumberOfChannels(this.numberOfChannels); 22 | 23 | gain.getGain().value = 1; 24 | }, 25 | fn() { 26 | gain.dspProcess(); 27 | } 28 | }; 29 | } 30 | 31 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 32 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 33 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 34 | 35 | suite.on("cycle", (e) => { 36 | console.log(e.target.toString()); 37 | }); 38 | 39 | suite.on("complete", () => { 40 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 41 | console.log(); 42 | }); 43 | 44 | suite.on("error", (e) => { 45 | console.error(e); 46 | }); 47 | 48 | suite.run(); 49 | } 50 | 51 | module.exports = (done) => { 52 | run(1); 53 | run(2); 54 | run(4); 55 | done(); 56 | }; 57 | 58 | if (module.parent === null) { 59 | module.exports(process.exit); 60 | } 61 | -------------------------------------------------------------------------------- /benchmark/suites/GainNode-params-fixed-x.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const gain = new wae.impl.GainNode(context); 18 | 19 | gain.inputs[0].bus.setNumberOfChannels(this.numberOfChannels); 20 | gain.inputs[0].bus.getMutableData(); 21 | gain.outputs[0].bus.setNumberOfChannels(this.numberOfChannels); 22 | 23 | gain.getGain().value = 0.5; 24 | }, 25 | fn() { 26 | gain.dspProcess(); 27 | } 28 | }; 29 | } 30 | 31 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 32 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 33 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 34 | 35 | suite.on("cycle", (e) => { 36 | console.log(e.target.toString()); 37 | }); 38 | 39 | suite.on("complete", () => { 40 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 41 | console.log(); 42 | }); 43 | 44 | suite.on("error", (e) => { 45 | console.error(e); 46 | }); 47 | 48 | suite.run(); 49 | } 50 | 51 | module.exports = (done) => { 52 | run(1); 53 | run(2); 54 | run(4); 55 | done(); 56 | }; 57 | 58 | if (module.parent === null) { 59 | module.exports(process.exit); 60 | } 61 | -------------------------------------------------------------------------------- /benchmark/suites/GainNode-params-sched.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const gain = new wae.impl.GainNode(context); 18 | 19 | gain.inputs[0].bus.setNumberOfChannels(this.numberOfChannels); 20 | gain.inputs[0].bus.getMutableData(); 21 | gain.outputs[0].bus.setNumberOfChannels(this.numberOfChannels); 22 | 23 | gain.getGain().setValueAtTime(0.0, 0); 24 | gain.getGain().linearRampToValueAtTime(1.0, 1); 25 | gain.getGain().dspProcess(); 26 | }, 27 | fn() { 28 | gain.dspProcess(); 29 | } 30 | }; 31 | } 32 | 33 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 34 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 35 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 36 | 37 | suite.on("cycle", (e) => { 38 | console.log(e.target.toString()); 39 | }); 40 | 41 | suite.on("complete", () => { 42 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 43 | console.log(); 44 | }); 45 | 46 | suite.on("error", (e) => { 47 | console.error(e); 48 | }); 49 | 50 | suite.run(); 51 | } 52 | 53 | module.exports = (done) => { 54 | run(1); 55 | run(2); 56 | run(4); 57 | done(); 58 | }; 59 | 60 | if (module.parent === null) { 61 | module.exports(process.exit); 62 | } 63 | -------------------------------------------------------------------------------- /benchmark/suites/GainNode-silent-input.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const gain = new wae.impl.GainNode(context); 18 | 19 | gain.inputs[0].bus.setNumberOfChannels(this.numberOfChannels); 20 | gain.inputs[0].bus.zeros(); 21 | gain.outputs[0].bus.setNumberOfChannels(this.numberOfChannels); 22 | 23 | gain.getGain().setValueAtTime(0.0, 0); 24 | gain.getGain().linearRampToValueAtTime(1.0, 1); 25 | gain.getGain().dspProcess(); 26 | }, 27 | fn() { 28 | gain.dspProcess(); 29 | } 30 | }; 31 | } 32 | 33 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 34 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 35 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 36 | 37 | suite.on("cycle", (e) => { 38 | console.log(e.target.toString()); 39 | }); 40 | 41 | suite.on("complete", () => { 42 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 43 | console.log(); 44 | }); 45 | 46 | suite.on("error", (e) => { 47 | console.error(e); 48 | }); 49 | 50 | suite.run(); 51 | } 52 | 53 | module.exports = (done) => { 54 | run(1); 55 | run(2); 56 | run(4); 57 | done(); 58 | }; 59 | 60 | if (module.parent === null) { 61 | module.exports(process.exit); 62 | } 63 | -------------------------------------------------------------------------------- /benchmark/suites/OfflineAudioContext-noop.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(done) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, 14 | defer: true, 15 | setup() { 16 | const wae = this.wae; 17 | }, 18 | fn(deffered) { 19 | const context = new wae.OfflineAudioContext(2, 44100, 44100); 20 | 21 | context.startRendering().then(() => { 22 | deffered.resolve(); 23 | }); 24 | } 25 | }; 26 | } 27 | 28 | suite.add("src", setup(waeSrc)); 29 | suite.add("lib", setup(waeLib)); 30 | suite.add("bld", setup(waeBld)); 31 | 32 | suite.on("cycle", (e) => { 33 | console.log(e.target.toString()); 34 | }); 35 | 36 | suite.on("complete", () => { 37 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 38 | console.log(); 39 | done(); 40 | }); 41 | 42 | suite.run({ async: true }); 43 | } 44 | 45 | module.exports = (done) => { 46 | run(done); 47 | }; 48 | 49 | if (module.parent === null) { 50 | module.exports(process.exit); 51 | } 52 | -------------------------------------------------------------------------------- /benchmark/suites/OfflineAudioContext-sine.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(done) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, 14 | defer: true, 15 | setup() { 16 | const wae = this.wae; 17 | }, 18 | fn(deffered) { 19 | const context = new wae.OfflineAudioContext(2, 44100, 44100); 20 | const osc = context.createOscillator(); 21 | 22 | osc.start(0.25); 23 | osc.stop(0.75); 24 | osc.connect(context.destination); 25 | 26 | context.startRendering().then(() => { 27 | deffered.resolve(); 28 | }); 29 | } 30 | }; 31 | } 32 | 33 | suite.add("src", setup(waeSrc)); 34 | suite.add("lib", setup(waeLib)); 35 | suite.add("bld", setup(waeBld)); 36 | 37 | suite.on("cycle", (e) => { 38 | console.log(e.target.toString()); 39 | }); 40 | 41 | suite.on("complete", () => { 42 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 43 | console.log(); 44 | done(); 45 | }); 46 | 47 | suite.run({ async: true }); 48 | } 49 | 50 | module.exports = (done) => { 51 | run(done); 52 | }; 53 | 54 | if (module.parent === null) { 55 | module.exports(process.exit); 56 | } 57 | -------------------------------------------------------------------------------- /benchmark/suites/OscillatorNode-sine-params-fixed-x.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const osc = new wae.impl.OscillatorNode(context); 18 | 19 | osc.setType("sine"); 20 | osc.start(0); 21 | osc.getFrequency().value = 440; 22 | }, 23 | fn() { 24 | osc.dspProcess(); 25 | } 26 | }; 27 | } 28 | 29 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 30 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 31 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 32 | 33 | suite.on("cycle", (e) => { 34 | console.log(e.target.toString()); 35 | }); 36 | 37 | suite.on("complete", () => { 38 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 39 | console.log(); 40 | }); 41 | 42 | suite.on("error", (e) => { 43 | console.error(e); 44 | }); 45 | 46 | suite.run(); 47 | } 48 | 49 | module.exports = (done) => { 50 | run(1); 51 | done(); 52 | }; 53 | 54 | if (module.parent === null) { 55 | module.exports(process.exit); 56 | } 57 | -------------------------------------------------------------------------------- /benchmark/suites/OscillatorNode-sine-params-sched.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const osc = new wae.impl.OscillatorNode(context); 18 | 19 | osc.setType("triangle"); 20 | osc.start(0); 21 | osc.getFrequency().setValueAtTime(440, 0); 22 | osc.getFrequency().linearRampToValueAtTime(880, 1); 23 | osc.getFrequency().dspProcess(); 24 | }, 25 | fn() { 26 | osc.dspProcess(); 27 | } 28 | }; 29 | } 30 | 31 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 32 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 33 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 34 | 35 | suite.on("cycle", (e) => { 36 | console.log(e.target.toString()); 37 | }); 38 | 39 | suite.on("complete", () => { 40 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 41 | console.log(); 42 | }); 43 | 44 | suite.on("error", (e) => { 45 | console.error(e); 46 | }); 47 | 48 | suite.run(); 49 | } 50 | 51 | module.exports = (done) => { 52 | run(1); 53 | done(); 54 | }; 55 | 56 | if (module.parent === null) { 57 | module.exports(process.exit); 58 | } 59 | -------------------------------------------------------------------------------- /benchmark/suites/OscillatorNode-tri-params-fixed-x.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const osc = new wae.impl.OscillatorNode(context); 18 | 19 | osc.setType("triangle"); 20 | osc.start(0); 21 | osc.getFrequency().value = 440; 22 | }, 23 | fn() { 24 | osc.dspProcess(); 25 | } 26 | }; 27 | } 28 | 29 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 30 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 31 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 32 | 33 | suite.on("cycle", (e) => { 34 | console.log(e.target.toString()); 35 | }); 36 | 37 | suite.on("complete", () => { 38 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 39 | console.log(); 40 | }); 41 | 42 | suite.on("error", (e) => { 43 | console.error(e); 44 | }); 45 | 46 | suite.run(); 47 | } 48 | 49 | module.exports = (done) => { 50 | run(1); 51 | done(); 52 | }; 53 | 54 | if (module.parent === null) { 55 | module.exports(process.exit); 56 | } 57 | -------------------------------------------------------------------------------- /benchmark/suites/OscillatorNode-tri-params-sched.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const osc = new wae.impl.OscillatorNode(context); 18 | 19 | osc.setType("triangle"); 20 | osc.start(0); 21 | osc.getFrequency().setValueAtTime(440, 0); 22 | osc.getFrequency().linearRampToValueAtTime(880, 1); 23 | osc.getFrequency().dspProcess(); 24 | }, 25 | fn() { 26 | osc.dspProcess(); 27 | } 28 | }; 29 | } 30 | 31 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 32 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 33 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 34 | 35 | suite.on("cycle", (e) => { 36 | console.log(e.target.toString()); 37 | }); 38 | 39 | suite.on("complete", () => { 40 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 41 | console.log(); 42 | }); 43 | 44 | suite.on("error", (e) => { 45 | console.error(e); 46 | }); 47 | 48 | suite.run(); 49 | } 50 | 51 | module.exports = (done) => { 52 | run(1); 53 | done(); 54 | }; 55 | 56 | if (module.parent === null) { 57 | module.exports(process.exit); 58 | } 59 | -------------------------------------------------------------------------------- /benchmark/suites/StereoPannerNode-params-fixed-x.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const panner = new wae.impl.StereoPannerNode(context); 18 | 19 | panner.inputs[0].bus.setNumberOfChannels(this.numberOfChannels); 20 | panner.inputs[0].bus.getMutableData(); 21 | 22 | panner.getPan().value = 0; 23 | }, 24 | fn() { 25 | panner.dspProcess(); 26 | } 27 | }; 28 | } 29 | 30 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 31 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 32 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 33 | 34 | suite.on("cycle", (e) => { 35 | console.log(e.target.toString()); 36 | }); 37 | 38 | suite.on("complete", () => { 39 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 40 | console.log(); 41 | }); 42 | 43 | suite.on("error", (e) => { 44 | console.error(e); 45 | }); 46 | 47 | suite.run(); 48 | } 49 | 50 | module.exports = (done) => { 51 | run(1); 52 | run(2); 53 | run(4); 54 | done(); 55 | }; 56 | 57 | if (module.parent === null) { 58 | module.exports(process.exit); 59 | } 60 | -------------------------------------------------------------------------------- /benchmark/suites/StereoPannerNode-params-sched.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const panner = new wae.impl.StereoPannerNode(context); 18 | 19 | panner.inputs[0].bus.setNumberOfChannels(this.numberOfChannels); 20 | panner.inputs[0].bus.getMutableData(); 21 | 22 | panner.getPan().setValueAtTime(0.0, 0); 23 | panner.getPan().linearRampToValueAtTime(1.0, 1); 24 | panner.getPan().dspProcess(); 25 | }, 26 | fn() { 27 | panner.dspProcess(); 28 | } 29 | }; 30 | } 31 | 32 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 33 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 34 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 35 | 36 | suite.on("cycle", (e) => { 37 | console.log(e.target.toString()); 38 | }); 39 | 40 | suite.on("complete", () => { 41 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 42 | console.log(); 43 | }); 44 | 45 | suite.on("error", (e) => { 46 | console.error(e); 47 | }); 48 | 49 | suite.run(); 50 | } 51 | 52 | module.exports = (done) => { 53 | run(1); 54 | run(2); 55 | run(4); 56 | done(); 57 | }; 58 | 59 | if (module.parent === null) { 60 | module.exports(process.exit); 61 | } 62 | -------------------------------------------------------------------------------- /benchmark/suites/StereoPannerNode-silent-input.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const panner = new wae.impl.StereoPannerNode(context); 18 | 19 | panner.inputs[0].bus.setNumberOfChannels(this.numberOfChannels); 20 | panner.inputs[0].bus.zeros(); 21 | 22 | panner.getPan().setValueAtTime(0.0, 0); 23 | panner.getPan().linearRampToValueAtTime(1.0, 1); 24 | panner.getPan().dspProcess(); 25 | }, 26 | fn() { 27 | panner.dspProcess(); 28 | } 29 | }; 30 | } 31 | 32 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 33 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 34 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 35 | 36 | suite.on("cycle", (e) => { 37 | console.log(e.target.toString()); 38 | }); 39 | 40 | suite.on("complete", () => { 41 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 42 | console.log(); 43 | }); 44 | 45 | suite.on("error", (e) => { 46 | console.error(e); 47 | }); 48 | 49 | suite.run(); 50 | } 51 | 52 | module.exports = (done) => { 53 | run(1); 54 | run(2); 55 | run(4); 56 | done(); 57 | }; 58 | 59 | if (module.parent === null) { 60 | module.exports(process.exit); 61 | } 62 | -------------------------------------------------------------------------------- /benchmark/suites/WaveShaperNode-curve.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const shaper = new wae.impl.WaveShaperNode(context); 18 | const curve = new Float32Array(1024).map((_, i) => Math.sin(i / 1024 * Math.PI * 2)); 19 | 20 | shaper.inputs[0].bus.setNumberOfChannels(this.numberOfChannels); 21 | shaper.inputs[0].bus.getMutableData(); 22 | shaper.outputs[0].bus.setNumberOfChannels(this.numberOfChannels); 23 | 24 | shaper.setCurve(curve); 25 | }, 26 | fn() { 27 | shaper.dspProcess(); 28 | } 29 | }; 30 | } 31 | 32 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 33 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 34 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 35 | 36 | suite.on("cycle", (e) => { 37 | console.log(e.target.toString()); 38 | }); 39 | 40 | suite.on("complete", () => { 41 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 42 | console.log(); 43 | }); 44 | 45 | suite.on("error", (e) => { 46 | console.error(e); 47 | }); 48 | 49 | suite.run(); 50 | } 51 | 52 | module.exports = (done) => { 53 | run(1); 54 | run(2); 55 | run(4); 56 | done(); 57 | }; 58 | 59 | if (module.parent === null) { 60 | module.exports(process.exit); 61 | } 62 | -------------------------------------------------------------------------------- /benchmark/suites/WaveShaperNode-none-curve.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const shaper = new wae.impl.WaveShaperNode(context); 18 | 19 | shaper.inputs[0].bus.setNumberOfChannels(this.numberOfChannels); 20 | shaper.inputs[0].bus.getMutableData(); 21 | shaper.outputs[0].bus.setNumberOfChannels(this.numberOfChannels); 22 | 23 | shaper.setCurve(null); 24 | }, 25 | fn() { 26 | shaper.dspProcess(); 27 | } 28 | }; 29 | } 30 | 31 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 32 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 33 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 34 | 35 | suite.on("cycle", (e) => { 36 | console.log(e.target.toString()); 37 | }); 38 | 39 | suite.on("complete", () => { 40 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 41 | console.log(); 42 | }); 43 | 44 | suite.on("error", (e) => { 45 | console.error(e); 46 | }); 47 | 48 | suite.run(); 49 | } 50 | 51 | module.exports = (done) => { 52 | run(1); 53 | run(2); 54 | run(4); 55 | done(); 56 | }; 57 | 58 | if (module.parent === null) { 59 | module.exports(process.exit); 60 | } 61 | -------------------------------------------------------------------------------- /benchmark/suites/WaveShaperNode-slient-input.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const benchmark = require("benchmark"); 4 | const waeSrc = require("../../src"); 5 | const waeLib = require("../../lib"); 6 | const waeBld = require("../../build/web-audio-engine"); 7 | 8 | function run(numberOfChannels) { 9 | const suite = new benchmark.Suite(); 10 | 11 | function setup(wae) { 12 | return { 13 | wae, numberOfChannels, 14 | setup() { 15 | const wae = this.wae; 16 | const context = new wae.impl.AudioContext(); 17 | const shaper = new wae.impl.WaveShaperNode(context); 18 | const curve = new Float32Array(1024).map((_, i) => Math.sin(i / 1024 * Math.PI * 2)); 19 | 20 | shaper.inputs[0].bus.setNumberOfChannels(this.numberOfChannels); 21 | shaper.inputs[0].bus.zeros(); 22 | shaper.outputs[0].bus.setNumberOfChannels(this.numberOfChannels); 23 | 24 | shaper.setCurve(curve); 25 | }, 26 | fn() { 27 | shaper.dspProcess(); 28 | } 29 | }; 30 | } 31 | 32 | suite.add(`src ${ numberOfChannels }ch`, setup(waeSrc)); 33 | suite.add(`lib ${ numberOfChannels }ch`, setup(waeLib)); 34 | suite.add(`bld ${ numberOfChannels }ch`, setup(waeBld)); 35 | 36 | suite.on("cycle", (e) => { 37 | console.log(e.target.toString()); 38 | }); 39 | 40 | suite.on("complete", () => { 41 | console.log("* Fastest is " + suite.filter("fastest").map("name")); 42 | console.log(); 43 | }); 44 | 45 | suite.on("error", (e) => { 46 | console.error(e); 47 | }); 48 | 49 | suite.run(); 50 | } 51 | 52 | module.exports = (done) => { 53 | run(1); 54 | run(2); 55 | run(4); 56 | done(); 57 | }; 58 | 59 | if (module.parent === null) { 60 | module.exports(process.exit); 61 | } 62 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | out.wav 2 | -------------------------------------------------------------------------------- /demo/assets/sound/a11wlk01.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/descriptinc/web-audio-js/12f6da53aadc6166f2e3179e6b014c9dda859d00/demo/assets/sound/a11wlk01.wav -------------------------------------------------------------------------------- /demo/assets/sound/amen.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/descriptinc/web-audio-js/12f6da53aadc6166f2e3179e6b014c9dda859d00/demo/assets/sound/amen.wav -------------------------------------------------------------------------------- /demo/assets/sound/hihat1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/descriptinc/web-audio-js/12f6da53aadc6166f2e3179e6b014c9dda859d00/demo/assets/sound/hihat1.wav -------------------------------------------------------------------------------- /demo/assets/sound/hihat2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/descriptinc/web-audio-js/12f6da53aadc6166f2e3179e6b014c9dda859d00/demo/assets/sound/hihat2.wav -------------------------------------------------------------------------------- /demo/assets/sound/kick.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/descriptinc/web-audio-js/12f6da53aadc6166f2e3179e6b014c9dda859d00/demo/assets/sound/kick.wav -------------------------------------------------------------------------------- /demo/assets/sound/snare.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/descriptinc/web-audio-js/12f6da53aadc6166f2e3179e6b014c9dda859d00/demo/assets/sound/snare.wav -------------------------------------------------------------------------------- /demo/assets/sound/tom1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/descriptinc/web-audio-js/12f6da53aadc6166f2e3179e6b014c9dda859d00/demo/assets/sound/tom1.wav -------------------------------------------------------------------------------- /demo/assets/sound/tom2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/descriptinc/web-audio-js/12f6da53aadc6166f2e3179e6b014c9dda859d00/demo/assets/sound/tom2.wav -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@web-audio-engine/demo", 3 | "description": "sound test", 4 | "version": "0.0.0", 5 | "cliOptions": { 6 | "prepend": "Usage: node demo [options] demo-name", 7 | "options": [ 8 | { 9 | "option": "help", 10 | "alias": "h", 11 | "type": "Boolean", 12 | "description": "display help" 13 | }, 14 | { 15 | "option": "list", 16 | "alias": "l", 17 | "type": "Boolean", 18 | "description": "display demo name" 19 | }, 20 | { 21 | "option": "type", 22 | "alias": "t", 23 | "type": "String", 24 | "description": "File type of audio", 25 | "default": "s16" 26 | }, 27 | { 28 | "option": "rate", 29 | "alias": "r", 30 | "type": "Number", 31 | "description": "Sample rate of audio", 32 | "default": "44100" 33 | }, 34 | { 35 | "option": "channels", 36 | "alias": "c", 37 | "type": "Number", 38 | "description": "Number of channels of audio data; e.g. 2 = stereo", 39 | "default": "2" 40 | }, 41 | { 42 | "option": "duration", 43 | "alias": "d", 44 | "type": "Number", 45 | "description": "Number of duration for rendering" 46 | }, 47 | { 48 | "option": "out", 49 | "alias": "o", 50 | "type": "String", 51 | "description": "Write output to " 52 | }, 53 | { 54 | "option": "verbose", 55 | "alias": "V", 56 | "type": "Boolean", 57 | "description": "Run in verbose mode", 58 | "default": "false" 59 | } 60 | ], 61 | "append": "AUDIO FILE FORMATS: s16 s32 u8 raw cd" 62 | }, 63 | "dependencies": { 64 | "optionator": "^0.8.1", 65 | "wae-cli": "^0.6.0", 66 | "web-audio-scheduler": "^1.1.0" 67 | }, 68 | "license": "MIT", 69 | "main": "demo.js", 70 | "private": true 71 | } 72 | -------------------------------------------------------------------------------- /demo/sources/chorus.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context, util) { 2 | return util.fetchAudioBuffer("a11wlk01.wav").then(function(instruments) { 3 | var bufSrc = context.createBufferSource(); 4 | var delay = context.createDelay(); 5 | var lfo1 = context.createOscillator(); 6 | var lfo2 = context.createGain(); 7 | var amp1 = context.createGain(); 8 | var amp2 = context.createGain(); 9 | 10 | bufSrc.buffer = instruments; 11 | bufSrc.loop = true; 12 | bufSrc.start(context.currentTime); 13 | bufSrc.connect(amp1); 14 | bufSrc.connect(delay); 15 | 16 | lfo1.frequency.value = 0.125; 17 | lfo1.start(context.currentTime); 18 | lfo1.connect(lfo2); 19 | 20 | lfo2.gain.value = 0.015; 21 | lfo2.connect(delay.delayTime); 22 | 23 | delay.delayTime.value = 0.03; 24 | delay.connect(amp2); 25 | 26 | amp1.gain.value = 0.6; 27 | amp1.connect(context.destination); 28 | 29 | amp2.gain.value = 0.4; 30 | amp2.connect(context.destination); 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /demo/sources/delay.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context, util) { 2 | var sched = new util.WebAudioScheduler({ context: context, timerAPI: global }); 3 | 4 | function sample(list) { 5 | return list[(Math.random() * list.length)|0]; 6 | } 7 | 8 | function mtof(value) { 9 | return 440 * Math.pow(2, (value - 69) / 12); 10 | } 11 | 12 | function synth(t0, midi, dur) { 13 | var osc = context.createOscillator(); 14 | var amp = context.createGain(); 15 | var delay = context.createDelay(0.1); 16 | var out = context.createGain(); 17 | var t1 = t0 + dur; 18 | 19 | osc.type = "triangle"; 20 | osc.frequency.value = mtof(midi); 21 | osc.start(t0); 22 | osc.stop(t1); 23 | osc.connect(amp); 24 | 25 | amp.gain.setValueAtTime(0.075, t0); 26 | amp.gain.linearRampToValueAtTime(0, t1); 27 | amp.connect(out); 28 | amp.connect(delay); 29 | 30 | delay.delayTime.value = 0.09375; 31 | delay.connect(out); 32 | 33 | return out; 34 | } 35 | 36 | function createFeedbackDelay() { 37 | var input = context.createGain(); 38 | var delay = context.createDelay(); 39 | var feedback = context.createGain(); 40 | var output = context.createGain(); 41 | 42 | input.connect(delay); 43 | input.connect(output); 44 | 45 | delay.delayTime.value = 0.125; 46 | delay.connect(feedback); 47 | 48 | feedback.gain.value = 0.925; 49 | feedback.connect(input); 50 | 51 | return { input: input, output: output, feedback: feedback.gain }; 52 | } 53 | 54 | var efx = createFeedbackDelay(); 55 | 56 | function compose(e) { 57 | var t0 = e.playbackTime; 58 | var midi = sample([ 64, 65, 65, 65, 69, 69, 72, 76 ]) + 12; 59 | var dur = sample([ 0.125, 0.25, 0.25, 0.5 ]); 60 | var feedback = sample([ 0.4, 0.6, 0.8, 0.9, 0.975 ]); 61 | var nextTime = dur * sample([ 0.5, 1, 1, 1.5, 1.5, 2, 2, 4 ]); 62 | 63 | efx.feedback.setTargetAtTime(feedback, t0, 1); 64 | 65 | synth(t0, midi, dur).connect(efx.input); 66 | 67 | sched.insert(t0 + nextTime, compose); 68 | } 69 | 70 | efx.output.connect(context.destination); 71 | 72 | sched.start(compose); 73 | }; 74 | -------------------------------------------------------------------------------- /demo/sources/filter.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context, util) { 2 | var sched = new util.WebAudioScheduler({ context: context, timerAPI: global }); 3 | 4 | function sample(list) { 5 | return list[(Math.random() * list.length)|0]; 6 | } 7 | 8 | function mtof(value) { 9 | return 440 * Math.pow(2, (value - 69) / 12); 10 | } 11 | 12 | function synth(t0, midi, dur) { 13 | var filter = context.createBiquadFilter(); 14 | var gain = context.createGain(); 15 | var t1 = t0 + dur; 16 | var freq = mtof(midi); 17 | 18 | [ 1, 3/2, 15/8 ].forEach(function(ratio, index) { 19 | [ -12, +12 ].forEach(function(detune) { 20 | var osc = context.createOscillator(); 21 | 22 | osc.type = "sawtooth"; 23 | osc.frequency.value = freq * ratio; 24 | osc.detune.value = detune; 25 | osc.start(t0); 26 | osc.stop(t1); 27 | osc.connect(filter); 28 | }); 29 | }); 30 | 31 | var cutoff1 = freq * sample([ 0.25, 0.5, 1, 2, 2, 4, 4, 4, 6, 6, 8 ]); 32 | var cutoff2 = freq * sample([ 0.25, 0.5, 1, 2, 2, 4, 4, 4, 6, 6, 8 ]); 33 | 34 | filter.type = "bandpass"; 35 | filter.frequency.setValueAtTime(cutoff1, t0); 36 | filter.frequency.exponentialRampToValueAtTime(cutoff2, t1); 37 | filter.Q.value = 4; 38 | filter.connect(gain); 39 | 40 | gain.gain.setValueAtTime(0.1, t0); 41 | gain.gain.linearRampToValueAtTime(0, t1); 42 | gain.connect(context.destination); 43 | } 44 | 45 | function compose(e) { 46 | var t0 = e.playbackTime; 47 | var midi = sample([ 60, 60, 62, 64, 64, 67, 69 ]); 48 | var dur = sample([ 2, 4, 8, 16 ]); 49 | var nextTime = dur * sample([ 0.25, 0.25, 0.5, 0.5, 0.5, 0.5, 1, 1.25 ]); 50 | 51 | synth(t0, midi, dur); 52 | 53 | sched.insert(t0 + nextTime, compose); 54 | } 55 | 56 | sched.start(compose); 57 | }; 58 | -------------------------------------------------------------------------------- /demo/sources/metronome.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context, util) { 2 | var sched = new util.WebAudioScheduler({ context: context, timerAPI: global }); 3 | 4 | function metronome(e) { 5 | var t0 = e.playbackTime; 6 | 7 | sched.insert(t0 + 0.000, ticktack, { frequency: 880, amp: 1.0, duration: 1.00 }); 8 | sched.insert(t0 + 0.500, ticktack, { frequency: 440, amp: 0.4, duration: 0.25 }); 9 | sched.insert(t0 + 1.000, ticktack, { frequency: 440, amp: 0.5, duration: 0.25 }); 10 | sched.insert(t0 + 1.500, ticktack, { frequency: 440, amp: 0.4, duration: 0.25 }); 11 | sched.insert(t0 + 2.000, metronome); 12 | } 13 | 14 | function ticktack(e) { 15 | var t0 = e.playbackTime; 16 | var t1 = t0 + e.args.duration; 17 | var osc = context.createOscillator(); 18 | var amp = context.createGain(); 19 | 20 | osc.frequency.value = e.args.frequency; 21 | osc.start(t0); 22 | osc.stop(t1); 23 | osc.connect(amp); 24 | 25 | amp.gain.setValueAtTime(0.5 * e.args.amp, t0); 26 | amp.gain.exponentialRampToValueAtTime(1e-6, t1); 27 | amp.connect(context.destination); 28 | } 29 | 30 | sched.start(metronome); 31 | }; 32 | -------------------------------------------------------------------------------- /demo/sources/pan.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context, util) { 2 | var sched = new util.WebAudioScheduler({ context: context, timerAPI: global }); 3 | 4 | function sample(list) { 5 | return list[(Math.random() * list.length)|0]; 6 | } 7 | 8 | function mtof(value) { 9 | return 440 * Math.pow(2, (value - 69) / 12); 10 | } 11 | 12 | function synth(t0, midi, dur, amp, pos) { 13 | var t1 = t0 + dur; 14 | var osc = context.createOscillator(); 15 | var pan = context.createStereoPanner(); 16 | var gain = context.createGain(); 17 | 18 | osc.frequency.value = mtof(midi); 19 | osc.start(t0); 20 | osc.stop(t1); 21 | osc.connect(pan); 22 | 23 | pan.pan.value = pos; 24 | pan.connect(gain); 25 | 26 | gain.gain.setValueAtTime(amp, t0); 27 | gain.gain.linearRampToValueAtTime(0, t1); 28 | gain.connect(context.destination); 29 | } 30 | 31 | function compose(e) { 32 | var t0 = e.playbackTime; 33 | var midi = 60 + sample([ 1, 2, 2, 2, 3, 4, 4, 5 ]) * 12; 34 | var dur = sample([ 0.125, 0.125, 0.125, 0.250, 0.250, 0.5 ]); 35 | var amp = sample([ 0.05, 0.1, 0.2, 0.4 ]); 36 | var pos = sample([ 0.2, 0.4, 0.6, 0.8, 1.0 ]) * sample([ -1, +1 ]); 37 | var iterations = sample([ 1, 1, 2, 2, 2, 3, 4, 8 ]); 38 | var interval = 0.250 / iterations; 39 | 40 | dur = dur / iterations; 41 | 42 | for (var i = 0; i < iterations; i++) { 43 | synth(t0 + interval * i, midi, dur, amp, pos); 44 | } 45 | 46 | sched.insert(t0 + 0.125, compose); 47 | } 48 | 49 | sched.start(compose); 50 | }; 51 | -------------------------------------------------------------------------------- /demo/sources/sines.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context, util) { 2 | var sched = new util.WebAudioScheduler({ context: context, timerAPI: global }); 3 | 4 | function sample(list) { 5 | return list[(Math.random() * list.length)|0]; 6 | } 7 | 8 | function mtof(value) { 9 | return 440 * Math.pow(2, (value - 69) / 12); 10 | } 11 | 12 | function synth(t0, midi, dur) { 13 | var osc1 = context.createOscillator(); 14 | var osc2 = context.createOscillator(); 15 | var gain = context.createGain(); 16 | var t1 = t0 + dur * 0.25; 17 | var t2 = t1 + dur * 0.75; 18 | 19 | osc1.frequency.value = mtof(midi); 20 | osc1.detune.setValueAtTime(+4, t0); 21 | osc1.detune.linearRampToValueAtTime(+12, t2); 22 | osc1.start(t0); 23 | osc1.stop(t2); 24 | osc1.connect(gain); 25 | 26 | osc2.frequency.value = mtof(midi); 27 | osc2.detune.setValueAtTime(-4, t0); 28 | osc2.detune.linearRampToValueAtTime(-12, t2); 29 | osc2.start(t0); 30 | osc2.stop(t2); 31 | osc2.connect(gain); 32 | 33 | gain.gain.setValueAtTime(0, t0); 34 | gain.gain.linearRampToValueAtTime(0.125, t1); 35 | gain.gain.linearRampToValueAtTime(0, t2); 36 | gain.connect(context.destination); 37 | } 38 | 39 | function compose(e) { 40 | var t0 = e.playbackTime; 41 | var midi = sample([ 72, 72, 74, 76, 76, 79, 81 ]); 42 | var dur = sample([ 2, 2, 4, 4, 4, 4, 8 ]); 43 | var nextTime = dur * sample([ 0.125, 0.125, 0.25, 0.25, 0.25, 0.25, 0.5, 0.75 ]); 44 | 45 | synth(t0, midi, dur); 46 | 47 | sched.insert(t0 + nextTime, compose); 48 | } 49 | 50 | sched.start(compose); 51 | }; 52 | -------------------------------------------------------------------------------- /demo/sources/test_delay-modulation.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context) { 2 | // +---------------+ +---------------+ 3 | // | oscillator1 | | oscillator2 | 4 | // +---------------+ +---------------+ 5 | // | | 6 | // | +---------------+ 7 | // | | gain2 | 8 | // | +---------------+ 9 | // | | 10 | // +---------------+ | 11 | // | delay | | 12 | // | * delayTime <-------------+ 13 | // +---------------+ 14 | // | 15 | // +---------------+ 16 | // | gain1 | 17 | // +---------------+ 18 | var oscillator1 = context.createOscillator(); 19 | var delay = context.createDelay(); 20 | var gain1 = context.createGain(); 21 | var oscillator2 = context.createOscillator(); 22 | var gain2 = context.createGain(); 23 | 24 | oscillator1.type = "sine"; 25 | oscillator1.frequency.value = 880; 26 | oscillator1.start(); 27 | oscillator1.connect(delay); 28 | 29 | delay.delayTime.value = 0.5; 30 | delay.connect(gain1); 31 | 32 | gain1.gain.value = 0.25; 33 | gain1.connect(context.destination); 34 | 35 | oscillator2.type = "sine"; 36 | oscillator2.frequency.value = 20; 37 | oscillator2.start(); 38 | oscillator2.connect(gain2); 39 | 40 | gain2.gain.value = 0.005; 41 | gain2.connect(delay.delayTime); 42 | }; 43 | -------------------------------------------------------------------------------- /demo/sources/test_filter-modulation.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context) { 2 | // +---------------+ +---------------+ 3 | // | oscillator1 | | oscillator2 | 4 | // +---------------+ +---------------+ 5 | // | | 6 | // | +---------------+ 7 | // | | gain2 | 8 | // | +---------------+ 9 | // | | 10 | // +---------------+ | 11 | // | biquadFilter | | 12 | // | * frequency <-------------+ 13 | // +---------------+ 14 | // | 15 | // +---------------+ 16 | // | gain1 | 17 | // +---------------+ 18 | var oscillator1 = context.createOscillator(); 19 | var biquadFilter = context.createBiquadFilter(); 20 | var gain1 = context.createGain(); 21 | var oscillator2 = context.createOscillator(); 22 | var gain2 = context.createGain(); 23 | 24 | oscillator1.type = "sawtooth"; 25 | oscillator1.frequency.value = 880; 26 | oscillator1.start(); 27 | oscillator1.connect(biquadFilter); 28 | 29 | biquadFilter.type = "lowpass"; 30 | biquadFilter.frequency.value = 1000; 31 | biquadFilter.connect(gain1); 32 | 33 | gain1.gain.value = 0.25; 34 | gain1.connect(context.destination); 35 | 36 | oscillator2.type = "sine"; 37 | oscillator2.frequency.value = 4; 38 | oscillator2.start(); 39 | oscillator2.connect(gain2); 40 | 41 | gain2.gain.value = 400; 42 | gain2.connect(biquadFilter.frequency); 43 | }; 44 | -------------------------------------------------------------------------------- /demo/sources/test_iir-filter.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context, util) { 2 | return util.fetchAudioBuffer("amen.wav").then(function(instruments) { 3 | var bufSrc = context.createBufferSource(); 4 | var iir = context.createIIRFilter([ 0.182377, 0, -0.182377 ], [ 1, -1.956784, 0.979231 ]); 5 | 6 | bufSrc.buffer = instruments; 7 | bufSrc.loop = true; 8 | bufSrc.start(context.currentTime); 9 | bufSrc.connect(iir); 10 | 11 | iir.connect(context.destination); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /demo/sources/test_loop.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context, util) { 2 | return util.fetchAudioBuffer("amen.wav").then(function(instruments) { 3 | var bufSrc = context.createBufferSource(); 4 | 5 | bufSrc.buffer = instruments; 6 | bufSrc.loop = true; 7 | bufSrc.loopStart = 0.2; 8 | bufSrc.loopEnd = 0.4; 9 | bufSrc.start(0, 0); 10 | bufSrc.connect(context.destination); 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | // rollup.config.js 2 | import pkg from './package.json'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | import resolve from '@rollup/plugin-node-resolve'; 5 | import typescript from 'rollup-plugin-typescript2'; 6 | 7 | export default { 8 | input: 'src', 9 | plugins: [ 10 | commonjs(), 11 | resolve({ preferBuiltins: true }), 12 | typescript({ exclude: '**/*.test.ts' }), 13 | ], 14 | external: [ 15 | 'assert', 16 | 'events', 17 | 'worker_threads', 18 | ...Object.keys(pkg.dependencies), 19 | ], 20 | output: [ 21 | { 22 | sourcemap: true, 23 | sourcemapExcludeSources: true, 24 | file: pkg.main, 25 | format: 'cjs', 26 | }, 27 | { 28 | sourcemap: true, 29 | sourcemapExcludeSources: true, 30 | file: pkg.module, 31 | format: 'esm', 32 | }, 33 | ], 34 | }; 35 | -------------------------------------------------------------------------------- /src/__tests__/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/__tests__/api/AudioDestinationNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as api from '../../api'; 4 | import AudioContext from '../../api/BaseAudioContext'; 5 | 6 | describe('api/AudioDestinationNode', () => { 7 | it('context.destination', () => { 8 | const context = new AudioContext(); 9 | const target = context.destination; 10 | 11 | expect(target instanceof api.AudioDestinationNode).toBeTruthy(); 12 | }); 13 | 14 | describe('attributes', () => { 15 | it('.maxChannelCount', () => { 16 | const context = new AudioContext(); 17 | const target = context.destination; 18 | const maxChannelCount = 2; 19 | 20 | target._impl.getMaxChannelCount = jest.fn(() => maxChannelCount); 21 | 22 | expect(target.maxChannelCount).toBe(maxChannelCount); 23 | expect(target._impl.getMaxChannelCount).toHaveBeenCalledTimes(1); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/__tests__/api/AudioListener.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as api from '../../api'; 4 | import AudioContext from '../../api/BaseAudioContext'; 5 | 6 | describe('api/AudioListener', () => { 7 | it('context.listener', () => { 8 | const context = new AudioContext(); 9 | const target = context.listener; 10 | 11 | expect(target instanceof api.AudioListener).toBeTruthy(); 12 | }); 13 | 14 | describe('methods', () => { 15 | it('.setPosition(x, y, z)', () => { 16 | const context = new AudioContext(); 17 | const target = context.listener; 18 | const x = 0; 19 | const y = 1; 20 | const z = 2; 21 | 22 | target._impl.setPosition = jest.fn(); 23 | 24 | target.setPosition(x, y, z); 25 | expect(target._impl.setPosition).toHaveBeenCalledTimes(1); 26 | expect(target._impl.setPosition.mock.calls[0][0]).toBe(x); 27 | expect(target._impl.setPosition.mock.calls[0][1]).toBe(y); 28 | expect(target._impl.setPosition.mock.calls[0][2]).toBe(z); 29 | }); 30 | 31 | it('.setOrientation(x, y, z, xUp, yUp, zUp)', () => { 32 | const context = new AudioContext(); 33 | const target = context.listener; 34 | const x = 0; 35 | const y = 1; 36 | const z = 2; 37 | const xUp = 3; 38 | const yUp = 4; 39 | const zUp = 5; 40 | 41 | target._impl.setOrientation = jest.fn(); 42 | 43 | target.setOrientation(x, y, z, xUp, yUp, zUp); 44 | expect(target._impl.setOrientation).toHaveBeenCalledTimes(1); 45 | expect(target._impl.setOrientation.mock.calls[0][0]).toBe(x); 46 | expect(target._impl.setOrientation.mock.calls[0][1]).toBe(y); 47 | expect(target._impl.setOrientation.mock.calls[0][2]).toBe(z); 48 | expect(target._impl.setOrientation.mock.calls[0][3]).toBe(xUp); 49 | expect(target._impl.setOrientation.mock.calls[0][4]).toBe(yUp); 50 | expect(target._impl.setOrientation.mock.calls[0][5]).toBe(zUp); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/__tests__/api/ChannelMergerNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as api from '../../api'; 4 | import AudioContext from '../../api/BaseAudioContext'; 5 | 6 | describe('api/ChannelMergerNode', () => { 7 | it('context.createChannelMerger(numberOfInputs)', () => { 8 | const context = new AudioContext(); 9 | const target = context.createChannelMerger(2); 10 | 11 | expect(target instanceof api.ChannelMergerNode).toBeTruthy(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/__tests__/api/ChannelSplitterNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as api from '../../api'; 4 | import AudioContext from '../../api/BaseAudioContext'; 5 | 6 | describe('api/ChannelSplitterNode', () => { 7 | it('context.createChannelSplitter(numberOfOutputs)', () => { 8 | const context = new AudioContext(); 9 | const target = context.createChannelSplitter(2); 10 | 11 | expect(target instanceof api.ChannelSplitterNode).toBeTruthy(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/__tests__/api/ConstantSourceNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as api from '../../api'; 4 | import AudioContext from '../../api/BaseAudioContext'; 5 | import AudioParam from '../../api/AudioParam'; 6 | 7 | describe('api/ConstantSourceNode', () => { 8 | it('context.createConstantSource()', () => { 9 | const context = new AudioContext(); 10 | const target = context.createConstantSource(); 11 | 12 | expect(target instanceof api.ConstantSourceNode).toBeTruthy(); 13 | expect(target instanceof api.AudioScheduledSourceNode).toBeTruthy(); 14 | }); 15 | 16 | describe('attributes', () => { 17 | it('.offset=', () => { 18 | const context = new AudioContext(); 19 | const target = context.createConstantSource(); 20 | 21 | expect(target.offset instanceof AudioParam).toBeTruthy(); 22 | expect(target.offset).toBe(target._impl.$offset); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/__tests__/api/ConvolverNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as api from '../../api'; 4 | import AudioContext from '../../api/BaseAudioContext'; 5 | 6 | describe('api/ConvolverNode', () => { 7 | it('context.createConvolver()', () => { 8 | const context = new AudioContext(); 9 | const target = context.createConvolver(); 10 | 11 | expect(target instanceof api.ConvolverNode).toBeTruthy(); 12 | }); 13 | 14 | describe('attributes', () => { 15 | it('.buffer=', () => { 16 | const context = new AudioContext(); 17 | const target = context.createConvolver(); 18 | const buffer = context.createBuffer(1, 16, 8000); 19 | 20 | target._impl.setBuffer = jest.fn(); 21 | 22 | target.buffer = buffer; 23 | expect(target.buffer).toBe(buffer); 24 | expect(target._impl.setBuffer).toHaveBeenCalledTimes(1); 25 | expect(target._impl.setBuffer.mock.calls[0][0]).toBe(buffer); 26 | }); 27 | 28 | it('.normalize=', () => { 29 | const context = new AudioContext(); 30 | const target = context.createConvolver(); 31 | const normalize1 = true; 32 | const normalize2 = false; 33 | 34 | target._impl.getNormalize = jest.fn(() => normalize1); 35 | target._impl.setNormalize = jest.fn(); 36 | 37 | expect(target.normalize).toBe(normalize1); 38 | expect(target._impl.getNormalize).toHaveBeenCalledTimes(1); 39 | 40 | target.normalize = normalize2; 41 | expect(target._impl.setNormalize).toHaveBeenCalledTimes(1); 42 | expect(target._impl.setNormalize.mock.calls[0][0]).toBe(normalize2); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/__tests__/api/DelayNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as api from '../../api'; 4 | import AudioContext from '../../api/BaseAudioContext'; 5 | import AudioParam from '../../api/AudioParam'; 6 | 7 | describe('api/DelayNode', () => { 8 | it('context.createDelay(maxDelayTime)', () => { 9 | const context = new AudioContext(); 10 | const target = context.createDelay(1); 11 | 12 | expect(target instanceof api.DelayNode).toBeTruthy(); 13 | }); 14 | 15 | describe('attributes', () => { 16 | it('.delayTime', () => { 17 | const context = new AudioContext(); 18 | const target = context.createDelay(); 19 | 20 | expect(target.delayTime instanceof AudioParam).toBeTruthy(); 21 | expect(target.delayTime).toBe(target._impl.$delayTime); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/__tests__/api/EventTarget.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioContext from '../../api/BaseAudioContext'; 4 | 5 | describe('api/EventTarget', () => { 6 | describe('methods', () => { 7 | it('.addEventListener(type, listener)', () => { 8 | const context = new AudioContext(); 9 | const target = context.createOscillator(); 10 | const type = 'ended'; 11 | const listener = () => {}; 12 | 13 | target._impl.addEventListener = jest.fn(); 14 | 15 | target.addEventListener(type, listener); 16 | expect(target._impl.addEventListener).toHaveBeenCalledTimes(1); 17 | expect(target._impl.addEventListener.mock.calls[0][0]).toBe(type); 18 | expect(target._impl.addEventListener.mock.calls[0][1]).toBe(listener); 19 | }); 20 | 21 | it('.removeEventListener()', () => { 22 | const context = new AudioContext(); 23 | const target = context.createOscillator(); 24 | const type = 'ended'; 25 | const listener = jest.fn(); 26 | 27 | target._impl.removeEventListener = jest.fn(); 28 | 29 | target.removeEventListener(type, listener); 30 | expect(target._impl.removeEventListener).toHaveBeenCalledTimes(1); 31 | expect(target._impl.removeEventListener.mock.calls[0][0]).toBe(type); 32 | expect(target._impl.removeEventListener.mock.calls[0][1]).toBe(listener); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/__tests__/api/GainNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as api from '../../api'; 4 | import AudioContext from '../../api/BaseAudioContext'; 5 | import AudioParam from '../../api/AudioParam'; 6 | 7 | describe('api/GainNode', () => { 8 | it('context.createGain()', () => { 9 | const context = new AudioContext(); 10 | const target = context.createGain(); 11 | 12 | expect(target instanceof api.GainNode).toBeTruthy(); 13 | }); 14 | 15 | describe('attributes', () => { 16 | it('.gain', () => { 17 | const context = new AudioContext(); 18 | const target = context.createGain(); 19 | 20 | expect(target.gain instanceof AudioParam).toBeTruthy(); 21 | expect(target.gain).toBe(target._impl.$gain); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/__tests__/api/IIRFilterNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as api from '../../api'; 4 | import AudioContext from '../../api/BaseAudioContext'; 5 | 6 | describe('api/IIRFilterNode', () => { 7 | it('context.createIIRFilter(feedforward, feedback)', () => { 8 | const context = new AudioContext(); 9 | const feedforward = new Float32Array(4); 10 | const feedback = new Float32Array(4); 11 | const target = context.createIIRFilter(feedforward, feedback); 12 | 13 | expect(target instanceof api.IIRFilterNode).toBeTruthy(); 14 | }); 15 | 16 | describe('methods', () => { 17 | it('.getFrequencyResponse(frequencyHz, magResponse, phaseResponse)', () => { 18 | const context = new AudioContext(); 19 | const feedforward = new Float32Array(4); 20 | const feedback = new Float32Array(4); 21 | const target = context.createIIRFilter(feedforward, feedback); 22 | const frequencyHz = new Float32Array(128); 23 | const magResponse = new Float32Array(128); 24 | const phaseResponse = new Float32Array(128); 25 | 26 | target._impl.getFrequencyResponse = jest.fn(); 27 | 28 | target.getFrequencyResponse(frequencyHz, magResponse, phaseResponse); 29 | expect(target._impl.getFrequencyResponse).toHaveBeenCalledTimes(1); 30 | expect(target._impl.getFrequencyResponse.mock.calls[0][0]).toBe( 31 | frequencyHz, 32 | ); 33 | expect(target._impl.getFrequencyResponse.mock.calls[0][1]).toBe( 34 | magResponse, 35 | ); 36 | expect(target._impl.getFrequencyResponse.mock.calls[0][2]).toBe( 37 | phaseResponse, 38 | ); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/__tests__/api/PeriodicWave.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as api from '../../api'; 4 | import AudioContext from '../../api/BaseAudioContext'; 5 | 6 | describe('api/PeriodicWave', () => { 7 | it('context.createPeriodicWave(real, imag)', () => { 8 | const context = new AudioContext(); 9 | const real = new Float32Array(16); 10 | const imag = new Float32Array(16); 11 | const target = context.createPeriodicWave(real, imag); 12 | 13 | expect(target instanceof api.PeriodicWave).toBeTruthy(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/__tests__/api/ScriptProcessorNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as api from '../../api'; 4 | import AudioContext from '../../api/BaseAudioContext'; 5 | 6 | describe('api/ScriptProcessorNode', () => { 7 | it('context.createScriptProcessor(bufferSize, numberOfInputChannels, numberOfOutputChannels)', () => { 8 | const context = new AudioContext(); 9 | const target = context.createScriptProcessor(256, 1, 1); 10 | 11 | expect(target instanceof api.ScriptProcessorNode).toBeTruthy(); 12 | }); 13 | 14 | describe('attributes', () => { 15 | it('.bufferSize', () => { 16 | const context = new AudioContext(); 17 | const target = context.createScriptProcessor(256, 1, 1); 18 | const bufferSize = 256; 19 | 20 | target._impl.getBufferSize = jest.fn(() => bufferSize); 21 | 22 | expect(target.bufferSize).toBe(bufferSize); 23 | expect(target._impl.getBufferSize).toHaveBeenCalledTimes(1); 24 | }); 25 | 26 | it('.onaudioprocess=', () => { 27 | const context = new AudioContext(); 28 | const target = context.createScriptProcessor(256, 1, 1); 29 | const callback1 = jest.fn(); 30 | const callback2 = jest.fn(); 31 | const callback3 = jest.fn(); 32 | const event = { type: 'audioprocess' }; 33 | 34 | target.onaudioprocess = callback1; 35 | target.onaudioprocess = callback2; 36 | target.addEventListener('audioprocess', callback3); 37 | target._impl.dispatchEvent(event); 38 | 39 | expect(target.onaudioprocess).toBe(callback2); 40 | expect(callback1).toHaveBeenCalledTimes(0); 41 | expect(callback2).toHaveBeenCalledTimes(1); 42 | expect(callback3).toHaveBeenCalledTimes(1); 43 | expect(callback2.mock.calls[0][0]).toBe(event); 44 | expect(callback3.mock.calls[0][0]).toBe(event); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/__tests__/api/StereoPannerNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as api from '../../api'; 4 | import AudioContext from '../../api/BaseAudioContext'; 5 | import AudioParam from '../../api/AudioParam'; 6 | 7 | describe('api/StereoPannerNode', () => { 8 | it('context.createStereoPanner()', () => { 9 | const context = new AudioContext(); 10 | const target = context.createStereoPanner(); 11 | 12 | expect(target instanceof api.StereoPannerNode).toBeTruthy(); 13 | }); 14 | 15 | describe('atrributes', () => { 16 | it('.pan', () => { 17 | const context = new AudioContext(); 18 | const target = context.createStereoPanner(); 19 | 20 | expect(target.pan instanceof AudioParam).toBeTruthy(); 21 | expect(target.pan).toBe(target._impl.$pan); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/__tests__/api/WaveShaperNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as api from '../../api'; 4 | import AudioContext from '../../api/BaseAudioContext'; 5 | 6 | describe('api/WaveShaperNode', () => { 7 | it('context.createWaveShaper()', () => { 8 | const context = new AudioContext(); 9 | const target = context.createWaveShaper(); 10 | 11 | expect(target instanceof api.WaveShaperNode).toBeTruthy(); 12 | }); 13 | 14 | describe('atrributes', () => { 15 | it('.curve=', () => { 16 | const context = new AudioContext(); 17 | const target = context.createWaveShaper(); 18 | const curve1 = null; 19 | const curve2 = new Float32Array(128); 20 | 21 | target._impl.getCurve = jest.fn(() => curve1); 22 | target._impl.setCurve = jest.fn(); 23 | 24 | expect(target.curve).toBe(curve1); 25 | expect(target._impl.getCurve).toHaveBeenCalledTimes(1); 26 | 27 | target.curve = curve2; 28 | expect(target._impl.setCurve).toHaveBeenCalledTimes(1); 29 | expect(target._impl.setCurve.mock.calls[0][0]).toBe(curve2); 30 | }); 31 | 32 | it('.oversample=', () => { 33 | const context = new AudioContext(); 34 | const target = context.createWaveShaper(); 35 | const oversample1 = 'none'; 36 | const oversample2 = '2x'; 37 | 38 | target._impl.getOversample = jest.fn(() => oversample1); 39 | target._impl.setOversample = jest.fn(); 40 | 41 | expect(target.oversample).toBe(oversample1); 42 | expect(target._impl.getOversample).toHaveBeenCalledTimes(1); 43 | 44 | target.oversample = oversample2; 45 | expect(target._impl.setOversample).toHaveBeenCalledTimes(1); 46 | expect(target._impl.setOversample.mock.calls[0][0]).toBe(oversample2); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /src/__tests__/context/RenderingAudioContext.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import RenderingAudioContext from '../../context/RenderingAudioContext'; 4 | 5 | describe('RenderingAudioContext', () => { 6 | it('should return a RenderingAudioContext instance', () => { 7 | const context = new RenderingAudioContext(); 8 | 9 | expect(context instanceof RenderingAudioContext).toBeTruthy(); 10 | }); 11 | 12 | it('should return a RenderingAudioContext instance with options', () => { 13 | const context = new RenderingAudioContext({ 14 | sampleRate: 8000, 15 | numberOfChannels: 1, 16 | blockSize: 256, 17 | bitDepth: 8, 18 | }); 19 | 20 | expect(context instanceof RenderingAudioContext).toBeTruthy(); 21 | expect(context.sampleRate).toBe(8000); 22 | expect(context.numberOfChannels).toBe(1); 23 | expect(context.blockSize).toBe(256); 24 | expect(context.format).toEqual({ 25 | sampleRate: 8000, 26 | channels: 1, 27 | bitDepth: 8, 28 | float: false, 29 | }); 30 | }); 31 | 32 | it('should advance current time when rendered', () => { 33 | const context = new RenderingAudioContext(); 34 | 35 | expect(context.currentTime).toBe(0); 36 | 37 | context.processTo('00:00:10.000'); 38 | expect(context.currentTime | 0).toBe(10); 39 | 40 | context.processTo('00:00:15.000'); 41 | expect(context.currentTime | 0).toBe(15); 42 | }); 43 | 44 | it('should export AudioData', () => { 45 | const context = new RenderingAudioContext(); 46 | 47 | context.processTo('00:00:10.000'); 48 | 49 | const audioData = context.exportAsAudioData(); 50 | 51 | expect(audioData.numberOfChannels).toBe(2); 52 | expect((audioData.length / audioData.sampleRate) | 0).toBe(10); 53 | expect(audioData.sampleRate).toBe(44100); 54 | }); 55 | 56 | it('should encode AudioData', () => { 57 | const context = new RenderingAudioContext(); 58 | 59 | const audioData = { 60 | sampleRate: 44100, 61 | channelData: [new Float32Array(16)], 62 | }; 63 | 64 | return context.encodeAudioData(audioData).then((arrayBuffer) => { 65 | expect(arrayBuffer instanceof ArrayBuffer).toBeTruthy(); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /src/__tests__/impl/ConstantSourceNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioContext from '../../impl/AudioContext'; 4 | import ConstantSourceNode from '../../impl/ConstantSourceNode'; 5 | import AudioParam from '../../impl/AudioParam'; 6 | import AudioNode from '../../impl/AudioNode'; 7 | 8 | describe('impl/ConstantSourceNode', () => { 9 | let context; 10 | 11 | beforeEach(() => { 12 | context = new AudioContext({ sampleRate: 8000, blockSize: 32 }); 13 | }); 14 | 15 | it('constructor', () => { 16 | const node = new ConstantSourceNode(context); 17 | 18 | expect(node instanceof ConstantSourceNode).toBeTruthy(); 19 | expect(node instanceof AudioNode).toBeTruthy(); 20 | }); 21 | 22 | describe('attributes', () => { 23 | it('.numberOfInputs', () => { 24 | const node = new ConstantSourceNode(context); 25 | 26 | expect(node.getNumberOfInputs()).toBe(0); 27 | }); 28 | 29 | it('.numberOfOutputs', () => { 30 | const node = new ConstantSourceNode(context); 31 | 32 | expect(node.getNumberOfOutputs()).toBe(1); 33 | }); 34 | 35 | it('.offset', () => { 36 | const node = new ConstantSourceNode(context); 37 | 38 | expect(node.getOffset() instanceof AudioParam).toBeTruthy(); 39 | expect(node.getOffset().getValue()).toBe(1); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/__tests__/impl/PannerNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioContext from '../../impl/AudioContext'; 4 | import PannerNode from '../../impl/PannerNode'; 5 | import BasePannerNode from '../../impl/BasePannerNode'; 6 | 7 | describe('impl/PannerNode', () => { 8 | let context; 9 | 10 | beforeEach(() => { 11 | context = new AudioContext({ sampleRate: 8000, blockSize: 32 }); 12 | }); 13 | 14 | it('constructor', () => { 15 | const node = new PannerNode(context); 16 | 17 | expect(node instanceof PannerNode).toBeTruthy(); 18 | expect(node instanceof BasePannerNode).toBeTruthy(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/__tests__/impl/PeriodicWave.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioContext from '../../impl/AudioContext'; 4 | import PeriodicWave from '../../impl/PeriodicWave'; 5 | 6 | const real = new Float32Array([0, 0]); 7 | const imag = new Float32Array([0, 1]); 8 | 9 | describe('impl/PeriodicWave', () => { 10 | let context; 11 | 12 | beforeEach(() => { 13 | context = new AudioContext({ sampleRate: 8000, blockSize: 32 }); 14 | }); 15 | 16 | it('constructor', () => { 17 | const node = new PeriodicWave(context, { real, imag }); 18 | 19 | expect(node instanceof PeriodicWave).toBeTruthy(); 20 | }); 21 | 22 | describe('attributes', () => { 23 | it('.constraints', () => { 24 | const node = new PeriodicWave(context, { real, imag }); 25 | 26 | expect(node.getConstraints()).toBe(false); 27 | }); 28 | 29 | it('.real', () => { 30 | const node = new PeriodicWave(context, { real, imag }); 31 | 32 | expect(node.getReal()).toBe(real); 33 | }); 34 | 35 | it('.imag', () => { 36 | const node = new PeriodicWave(context, { real, imag }); 37 | 38 | expect(node.getImag()).toBe(imag); 39 | }); 40 | }); 41 | 42 | describe('generate basic waveform', () => { 43 | const periodicWave = new PeriodicWave(context, { 44 | real: [0, 0], 45 | imag: [0, 1], 46 | }); 47 | 48 | [ 49 | { type: 'sine', expected: 'sine' }, 50 | { type: 'sawtooth', expected: 'sawtooth' }, 51 | { type: 'triangle', expected: 'triangle' }, 52 | { type: 'square', expected: 'square' }, 53 | { type: 'unknown', expected: 'custom' }, 54 | ].forEach(({ type, expected }) => { 55 | it(type, () => { 56 | periodicWave.generateBasicWaveform(type); 57 | expect(periodicWave.getName()).toBe(expected); 58 | }); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/__tests__/impl/SpatialPannerNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioContext from '../../impl/AudioContext'; 4 | import SpatialPannerNode from '../../impl/SpatialPannerNode'; 5 | import BasePannerNode from '../../impl/BasePannerNode'; 6 | import AudioParam from '../../impl/AudioParam'; 7 | 8 | describe('impl/SpatialPannerNode', () => { 9 | let context; 10 | 11 | beforeEach(() => { 12 | context = new AudioContext({ sampleRate: 8000, blockSize: 32 }); 13 | }); 14 | 15 | it('constructor', () => { 16 | const node = new SpatialPannerNode(context); 17 | 18 | expect(node instanceof SpatialPannerNode).toBeTruthy(); 19 | expect(node instanceof BasePannerNode).toBeTruthy(); 20 | }); 21 | 22 | describe('attributes', () => { 23 | it('.positionX', () => { 24 | const node = new SpatialPannerNode(context); 25 | 26 | expect(node.getPositionX() instanceof AudioParam).toBeTruthy(); 27 | expect(node.getPositionX().getValue()).toBe(0); 28 | }); 29 | 30 | it('.positionY', () => { 31 | const node = new SpatialPannerNode(context); 32 | 33 | expect(node.getPositionY() instanceof AudioParam).toBeTruthy(); 34 | expect(node.getPositionY().getValue()).toBe(0); 35 | }); 36 | 37 | it('.positionZ', () => { 38 | const node = new SpatialPannerNode(context); 39 | 40 | expect(node.getPositionZ() instanceof AudioParam).toBeTruthy(); 41 | expect(node.getPositionZ().getValue()).toBe(0); 42 | }); 43 | 44 | it('.orientationX', () => { 45 | const node = new SpatialPannerNode(context); 46 | 47 | expect(node.getOrientationX() instanceof AudioParam).toBeTruthy(); 48 | expect(node.getOrientationX().getValue()).toBe(0); 49 | }); 50 | 51 | it('.orientationY', () => { 52 | const node = new SpatialPannerNode(context); 53 | 54 | expect(node.getOrientationY() instanceof AudioParam).toBeTruthy(); 55 | expect(node.getOrientationY().getValue()).toBe(0); 56 | }); 57 | 58 | it('.orientationZ', () => { 59 | const node = new SpatialPannerNode(context); 60 | 61 | expect(node.getOrientationZ() instanceof AudioParam).toBeTruthy(); 62 | expect(node.getOrientationZ().getValue()).toBe(0); 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /src/__tests__/impl/StereoPannerNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioContext from '../../impl/AudioContext'; 4 | import StereoPannerNode from '../../impl/StereoPannerNode'; 5 | import BasePannerNode from '../../impl/BasePannerNode'; 6 | import AudioParam from '../../impl/AudioParam'; 7 | 8 | describe('impl/StereoPannerNode', () => { 9 | let context; 10 | 11 | beforeEach(() => { 12 | context = new AudioContext({ sampleRate: 8000, blockSize: 32 }); 13 | }); 14 | 15 | it('constructor', () => { 16 | const node = new StereoPannerNode(context); 17 | 18 | expect(node instanceof StereoPannerNode).toBeTruthy(); 19 | expect(node instanceof BasePannerNode).toBeTruthy(); 20 | }); 21 | 22 | describe('attributes', () => { 23 | it('.numberOfInputs', () => { 24 | const node = new StereoPannerNode(context); 25 | 26 | expect(node.getNumberOfInputs()).toBe(1); 27 | }); 28 | 29 | it('.numberOfOutputs', () => { 30 | const node = new StereoPannerNode(context); 31 | 32 | expect(node.getNumberOfOutputs()).toBe(1); 33 | }); 34 | 35 | it('.pan', () => { 36 | const node = new StereoPannerNode(context); 37 | 38 | expect(node.getPan() instanceof AudioParam).toBeTruthy(); 39 | expect(node.getPan().getValue()).toBe(0); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/__tests__/impl/core/AudioData.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as np from '../../../__tests_helpers/np'; 4 | import AudioData from '../../../impl/core/AudioData'; 5 | 6 | describe('impl/core/AudioData', () => { 7 | it('constructor(numberOfChannels, length, sampleRate)', () => { 8 | const data = new AudioData(2, 128, 44100); 9 | 10 | expect(data instanceof AudioData).toBeTruthy(); 11 | }); 12 | 13 | describe('attributes', () => { 14 | it('.numberOfChannels', () => { 15 | const data = new AudioData(2, 128, 44100); 16 | 17 | expect(data.numberOfChannels).toBe(2); 18 | }); 19 | 20 | it('.length', () => { 21 | const data = new AudioData(2, 128, 44100); 22 | 23 | expect(data.length).toBe(128); 24 | }); 25 | 26 | it('.sampleRate', () => { 27 | const data = new AudioData(2, 128, 44100); 28 | 29 | expect(data.sampleRate).toBe(44100); 30 | }); 31 | 32 | it('.channelData', () => { 33 | const data = new AudioData(2, 128, 44100); 34 | 35 | expect(data.channelData.length).toBe(2); 36 | expect(data.channelData[0]).toEqual(np.zeros(128)); 37 | expect(data.channelData[1]).toEqual(np.zeros(128)); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/__tests__/impl/dsp/AudioContext.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioContext from '../../../impl/AudioContext'; 4 | 5 | const contextOpts = { sampleRate: 8000, blockSize: 16 }; 6 | 7 | describe('impl/dsp/AudioContext', () => { 8 | let context, destination; 9 | 10 | beforeAll(() => { 11 | context = new AudioContext(contextOpts); 12 | destination = context.getDestination(); 13 | 14 | context.resume(); 15 | }); 16 | 17 | it('1: time advances', () => { 18 | const channelData = [new Float32Array(16), new Float32Array(16)]; 19 | 20 | expect(context.getCurrentTime()).toBe(0); 21 | destination.process = jest.fn(); 22 | 23 | context.process(channelData, 0); 24 | 25 | expect(destination.process).toHaveBeenCalledTimes(1); 26 | expect(destination.process).toBeCalledWith(channelData, 0); 27 | expect(context.getCurrentTime()).toBe(16 / 8000); 28 | }); 29 | 30 | it('2: do post process and reserve pre process (for next process)', () => { 31 | const channelData = [new Float32Array(16), new Float32Array(16)]; 32 | const callOrder = []; 33 | const immediateSpy = jest.fn(() => callOrder.push('immediateSpy')); 34 | 35 | expect(context.getCurrentTime()).toBe(16 / 8000); 36 | destination.process = jest.fn(() => { 37 | callOrder.push('destination.process'); 38 | context.addPostProcess(immediateSpy); 39 | }); 40 | 41 | context.process(channelData, 0); 42 | 43 | expect(destination.process).toHaveBeenCalledTimes(1); 44 | expect(destination.process).toBeCalledWith(channelData, 0); 45 | expect(context.getCurrentTime()).toBe(32 / 8000); 46 | expect(immediateSpy).toHaveBeenCalledTimes(1); 47 | expect(callOrder).toEqual(['destination.process', 'immediateSpy']); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /src/__tests__/impl/dsp/AudioDestinationNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as np from '../../../__tests_helpers/np'; 4 | import AudioContext from '../../../impl/AudioContext'; 5 | import AudioDestinationNode from '../../../impl/AudioDestinationNode'; 6 | import AudioNode from '../../../impl/AudioNode'; 7 | 8 | describe('impl/dsp/AudioDestinationNode', () => { 9 | it('silent', () => { 10 | const channelData = [new Float32Array(16), new Float32Array(16)]; 11 | const context = new AudioContext({ sampleRate: 8000, blockSize: 16 }); 12 | const node1 = new AudioNode(context, {}, { outputs: [2] }); 13 | const node2 = new AudioDestinationNode(context, { numberOfChannels: 2 }); 14 | // const outputBus = node2.output.bus; 15 | 16 | node1.outputs[0].bus.zeros(); 17 | node1.enableOutputsIfNecessary(); 18 | node1.connect(node2); 19 | 20 | node2.process(channelData, 0); 21 | 22 | expect(channelData[0]).toEqual(np.zeros(16)); 23 | expect(channelData[1]).toEqual(np.zeros(16)); 24 | }); 25 | 26 | it('noise', () => { 27 | const channelData = [new Float32Array(16), new Float32Array(16)]; 28 | const context = new AudioContext({ sampleRate: 8000, blockSize: 16 }); 29 | const node1 = new AudioNode(context, {}, { outputs: [2] }); 30 | const node2 = new AudioDestinationNode(context, { numberOfChannels: 2 }); 31 | const noise1 = np.random_sample(16); 32 | const noise2 = np.random_sample(16); 33 | 34 | node1.outputs[0].bus.getMutableData()[0].set(noise1); 35 | node1.outputs[0].bus.getMutableData()[1].set(noise2); 36 | node1.enableOutputsIfNecessary(); 37 | node1.connect(node2); 38 | 39 | node2.process(channelData, 0); 40 | 41 | expect(channelData[0]).toEqual(noise1); 42 | expect(channelData[1]).toEqual(noise2); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /src/__tests__/impl/dsp/AudioNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioContext from '../../../impl/AudioContext'; 4 | import GainNode from '../../../impl/GainNode'; 5 | 6 | const context = new AudioContext({ sampleRate: 8000, blockSize: 16 }); 7 | 8 | describe('impl/dsp/AudioNode', () => { 9 | it('propagation', () => { 10 | const node1 = new GainNode(context); 11 | const node2 = new GainNode(context); 12 | const node3 = new GainNode(context); 13 | const param = node1.getGain(); 14 | 15 | node1.outputs[0].enable(); 16 | node2.outputs[0].enable(); 17 | node1.connect(node2); 18 | node2.connect(node3); 19 | 20 | node1.dspProcess = jest.fn(); 21 | param.dspProcess = jest.fn(); 22 | 23 | node3.processIfNecessary(); 24 | 25 | expect(param.dspProcess).toHaveBeenCalledTimes(1); 26 | }); 27 | 28 | it('feedback loop', () => { 29 | const node1 = new GainNode(context); 30 | const node2 = new GainNode(context); 31 | const node3 = new GainNode(context); 32 | 33 | node1.outputs[0].enable(); 34 | node2.outputs[0].enable(); 35 | node1.connect(node2); 36 | node2.connect(node3); 37 | node3.connect(node1); 38 | 39 | node1.dspProcess = jest.fn(); 40 | 41 | node3.processIfNecessary(); 42 | 43 | expect(node1.dspProcess).toHaveBeenCalledTimes(1); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/__tests__/impl/dsp/ChannelMergerNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as np from '../../../__tests_helpers/np'; 4 | import AudioContext from '../../../impl/AudioContext'; 5 | import ChannelMergerNode from '../../../impl/ChannelMergerNode'; 6 | import AudioNode from '../../../impl/AudioNode'; 7 | 8 | describe('impl/dsp/ChannelMergerNode', () => { 9 | it('works', () => { 10 | const channelData = [new Float32Array(16), new Float32Array(16)]; 11 | const context = new AudioContext({ sampleRate: 8000, blockSize: 16 }); 12 | const node1 = new AudioNode(context, {}, { inputs: [1], outputs: [1] }); 13 | const node2 = new AudioNode(context, {}, { inputs: [1], outputs: [1] }); 14 | const node3 = new ChannelMergerNode(context, { numberOfInputs: 4 }); 15 | const noise1 = np.random_sample(16); 16 | const noise2 = np.random_sample(16); 17 | 18 | context.resume(); 19 | node1.connect(node3, 0, 0); 20 | node2.connect(node3, 0, 1); 21 | node3.connect(context.getDestination()); 22 | node1.enableOutputsIfNecessary(); 23 | node2.enableOutputsIfNecessary(); 24 | node1.outputs[0].bus.getMutableData()[0].set(noise1); 25 | node2.outputs[0].bus.getMutableData()[0].set(noise2); 26 | 27 | context.process(channelData, 0); 28 | 29 | const actual = node3.outputs[0].bus.getChannelData(); 30 | const expected = [noise1, noise2, np.zeros(16), np.zeros(16)]; 31 | 32 | expect(actual).toEqual(expected); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/__tests__/impl/dsp/DynamicsCompressorNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioContext from '../../../api/BaseAudioContext'; 4 | import DynamicsCompressorNode from '../../../api/DynamicsCompressorNode'; 5 | import DynamicsCompressorData from '../../../__tests_helpers/DynamicsCompressorData'; 6 | 7 | describe('impl/dsp/DynamicsCompressor', () => { 8 | it('works', () => { 9 | const sampleRate = 44100; 10 | const length = 44100; 11 | const blockSize = 1024; 12 | const context = new AudioContext({ 13 | sampleRate, 14 | blockSize, 15 | numberOfChannels: 1, 16 | }); 17 | const node = new DynamicsCompressorNode(context); 18 | 19 | // Testing the "Classic Voiceover" preset 20 | // { threshold: -24, ratio: 1.5, attack: 0.15, release: 0.4, knee: 10 } 21 | 22 | node.attack.value = 0.15; 23 | node.ratio.value = 1.5; 24 | node.threshold.value = -24; 25 | node.release.value = 0.4; 26 | node.knee.value = 10; 27 | 28 | const buffer = context.createBuffer(1, length, sampleRate); 29 | const bufSrc = context.createBufferSource(); 30 | 31 | const freq = 440; 32 | 33 | function val(t) { 34 | return Math.sin(2 * Math.PI * freq * t); 35 | } 36 | 37 | for (let i = 0; i < length; i++) { 38 | buffer.getChannelData(0)[i] = val(i / sampleRate); 39 | } 40 | 41 | bufSrc.buffer = buffer; 42 | 43 | bufSrc.connect(node); 44 | node.connect(context.destination); 45 | const iterations = Math.ceil(length / blockSize); 46 | const iterLength = iterations * blockSize; 47 | const channelData = [new Float32Array(iterLength)]; 48 | bufSrc.start(); 49 | context.resume(); 50 | 51 | for (let i = 0; i < iterations; i++) { 52 | context._impl.process(channelData, i * blockSize); 53 | } 54 | 55 | const out = channelData[0].slice(0, length); 56 | 57 | expect(out.length).toBe(length); 58 | for (let i = 0; i < out.length; i++) { 59 | const a = out[i]; 60 | const b = DynamicsCompressorData[i]; 61 | expect(Math.abs(a - b) <= 1e-4).toBeTruthy(); 62 | } 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /src/__tests__/impl/dsp/IIRFilterNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioContext from '../../../impl/AudioContext'; 4 | import IIRFilterNode from '../../../impl/IIRFilterNode'; 5 | 6 | function closeTo(a, b, delta) { 7 | return Math.abs(a - b) <= delta; 8 | } 9 | 10 | describe('impl/dsp/IIRFilterNode', () => { 11 | describe('getFrequencyResponse(frequencyHz, magResponse, phaseResponse)', () => { 12 | it('works', () => { 13 | const context = new AudioContext({ sampleRate: 44100, blockSize: 16 }); 14 | const node = new IIRFilterNode(context, { 15 | feedforward: [0.135784, 0.0, -0.135784], 16 | feedback: [1, -1.854196, 0.932108], 17 | }); 18 | const frequencyHz = new Float32Array([1000, 2000, 3000]); 19 | const magResponse = new Float32Array(3); 20 | const phaseResponse = new Float32Array(3); 21 | 22 | node.getFrequencyResponse(frequencyHz, magResponse, phaseResponse); 23 | 24 | // computed using Chrome 54 25 | // a=a||new AudioContext(); b=a.createIIRFilter([ 0.135784, 0.000000, -0.135784 ], [ 1, -1.854196, 0.932108 ]); c=new Float32Array([ 1000, 2000, 3000 ]); d=new Float32Array(3); e=new Float32Array(3); b.getFrequencyResponse(c, d, e); console.log(d); console.log(e); 26 | const magExpected = new Float32Array([ 27 | 0.6521847248077393, 28 | 4, 29 | 1.1262547969818115, 30 | ]); 31 | const phaseExpected = new Float32Array([ 32 | 1.4070188999176025, 33 | 0.00000937021650315728, 34 | -1.2853729724884033, 35 | ]); 36 | 37 | expect( 38 | magResponse.every((mag, i) => closeTo(mag, magExpected[i], 1e-6)), 39 | ).toBeTruthy(); 40 | expect( 41 | phaseResponse.every((phase, i) => 42 | closeTo(phase, phaseExpected[i], 1e-6), 43 | ), 44 | ).toBeTruthy(); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/__tests__/mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive 2 | --require babel-register 3 | -------------------------------------------------------------------------------- /src/__tests__/utils/EncoderUtils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as EncoderUtils from '../../utils/EncoderUtils'; 4 | 5 | describe('utils/EncoderUtils.encode(encodeFn: function, audioData: AudioData, opts?: object): Promise', () => { 6 | it('should return promise and resolve - from AudioData', () => { 7 | const source = new Uint8Array(128); 8 | const sampleRate = 44100; 9 | const channelData = [new Float32Array(128), new Float32Array(128)]; 10 | const audioData = { sampleRate, channelData }; 11 | const opts = {}; 12 | const encodeFn = jest.fn(() => { 13 | return Promise.resolve(source.buffer); 14 | }); 15 | 16 | return EncoderUtils.encode(encodeFn, audioData, opts).then( 17 | (arrayBuffer) => { 18 | expect(encodeFn).toHaveBeenCalledTimes(1); 19 | expect(encodeFn).toBeCalledWith(audioData, opts); 20 | expect(arrayBuffer instanceof ArrayBuffer).toBeTruthy(); 21 | }, 22 | ); 23 | }); 24 | 25 | it('should return peomise and resolve - from AudioBuffer', () => { 26 | const source = new Uint8Array(128); 27 | const numberOfChannels = 2; 28 | const sampleRate = 44100; 29 | const channelData = [new Float32Array(128), new Float32Array(128)]; 30 | const audioData = { 31 | numberOfChannels, 32 | sampleRate, 33 | getChannelData(ch) { 34 | return channelData[ch]; 35 | }, 36 | }; 37 | const opts = {}; 38 | const encodeFn = jest.fn(() => { 39 | return Promise.resolve(source.buffer); 40 | }); 41 | 42 | return EncoderUtils.encode(encodeFn, audioData, opts).then( 43 | (arrayBuffer) => { 44 | expect(encodeFn).toHaveBeenCalledTimes(1); 45 | 46 | const _audioData = encodeFn.mock.calls[0][0]; 47 | 48 | expect(_audioData.numberOfChannels).toBe(numberOfChannels); 49 | expect(_audioData.length).toBe(128); 50 | expect(_audioData.sampleRate).toBe(44100); 51 | expect(_audioData.channelData[0]).toBe(channelData[0]); 52 | expect(_audioData.channelData[1]).toBe(channelData[1]); 53 | 54 | expect(arrayBuffer instanceof ArrayBuffer).toBeTruthy(); 55 | }, 56 | ); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/__tests__/utils/setImmediate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import setImmediate from '../../utils/setImmediate'; 4 | 5 | describe('utils/setImmediate(fn)', () => { 6 | it('works', (done) => { 7 | setImmediate(done); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /src/__tests__/utils/utils/clamp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import clamp from '../../../utils/utils/clamp'; 4 | 5 | describe('utils/clamp(value, minValue, maxValue)', () => { 6 | it('return clamped value in the range [minValue, maxValue]', () => { 7 | expect(clamp(0, 2, 4)).toBe(2); 8 | expect(clamp(1, 2, 4)).toBe(2); 9 | expect(clamp(2, 2, 4)).toBe(2); 10 | expect(clamp(3, 2, 4)).toBe(3); 11 | expect(clamp(4, 2, 4)).toBe(4); 12 | expect(clamp(5, 2, 4)).toBe(4); 13 | expect(clamp(6, 2, 4)).toBe(4); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/__tests__/utils/utils/defaults.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import defaults from '../../../utils/utils/defaults'; 4 | 5 | describe('utils/defaults(value, defaultValue)', () => { 6 | it('works', () => { 7 | expect(defaults(0, 1)).toBe(0); 8 | expect(defaults(null, 1)).toBe(null); 9 | expect(defaults(undefined, 1)).toBe(1); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/__tests__/utils/utils/defineProp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import defineProp from '../../../utils/utils/defineProp'; 4 | 5 | describe('utils/defineProp', () => { 6 | it('define property', () => { 7 | const a = {}; 8 | 9 | defineProp(a, 'value', 100); 10 | 11 | expect(a.value).toBe(100); 12 | }); 13 | 14 | it('not enumerable', () => { 15 | const a = {}; 16 | 17 | defineProp(a, 'value', 100); 18 | 19 | expect(Object.keys(a).length).toBe(0); 20 | }); 21 | 22 | it('writable', () => { 23 | const a = {}; 24 | 25 | defineProp(a, 'value', 100); 26 | 27 | a.value = 200; 28 | expect(a.value).toBe(200); 29 | }); 30 | 31 | it('configurable', () => { 32 | const a = {}; 33 | 34 | defineProp(a, 'value', 100); 35 | defineProp(a, 'value', 300); 36 | 37 | expect(a.value).toBe(300); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/__tests__/utils/utils/fill.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import fill from '../../../utils/utils/fill'; 4 | 5 | describe('utils/fill(list, value)', () => { 6 | it('fill value', () => { 7 | const list = new Float32Array(8); 8 | const actual = fill(list, 1); 9 | const expected = new Float32Array([1, 1, 1, 1, 1, 1, 1, 1]); 10 | 11 | expect(actual).toEqual(expected); 12 | expect(list).toEqual(expected); 13 | }); 14 | 15 | it('fill value - polyfill ver', () => { 16 | const list = new Float32Array(8); 17 | 18 | // kill native function 19 | Object.defineProperty(list, 'fill', { value: null }); 20 | 21 | const actual = fill(list, 1); 22 | const expected = new Float32Array([1, 1, 1, 1, 1, 1, 1, 1]); 23 | 24 | expect(actual).toEqual(expected); 25 | expect(list).toEqual(expected); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/__tests__/utils/utils/fillRange.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import fillRange from '../../../utils/utils/fillRange'; 4 | 5 | describe('utils/fillRange(list, value, start, end)', () => { 6 | it('fill value', () => { 7 | const list = new Float32Array(8); 8 | const actual = fillRange(list, 1, 2, 6); 9 | const expected = new Float32Array([0, 0, 1, 1, 1, 1, 0, 0]); 10 | 11 | expect(actual).toEqual(expected); 12 | expect(list).toEqual(expected); 13 | }); 14 | 15 | it('fill value - polyfill ver', () => { 16 | const list = new Float32Array(8); 17 | 18 | // kill native function 19 | Object.defineProperty(list, 'fill', { value: null }); 20 | 21 | const actual = fillRange(list, 1, 2, 6); 22 | const expected = new Float32Array([0, 0, 1, 1, 1, 1, 0, 0]); 23 | 24 | expect(actual).toEqual(expected); 25 | expect(list).toEqual(expected); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/__tests__/utils/utils/normalize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import normalize from '../../../utils/utils/normalize'; 4 | 5 | describe('utils/normalize(val, min, max)', () => { 6 | it('normalize a value to something between 0 - 1', () => { 7 | expect(normalize(0, -100, 100)).toBe(0.5); 8 | expect(normalize(-100, -100, 100)).toBe(0); 9 | expect(normalize(-200, -100, 100)).toBe(0); 10 | expect(normalize(100, -100, 100)).toBe(1); 11 | expect(normalize(200, -100, 100)).toBe(1); 12 | expect(normalize(50, -100, 100)).toBe(0.75); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/__tests__/utils/utils/toArrayIfNeeded.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import toArrayIfNeeded from '../../../utils/utils/toArrayIfNeeded'; 4 | 5 | describe('utils/toArrayIfNeeded(value)', () => { 6 | it('convert to array if not array', () => { 7 | const value = 1; 8 | const actual = toArrayIfNeeded(value); 9 | const expected = [value]; 10 | 11 | expect(actual).toEqual(expected); 12 | }); 13 | 14 | it('nothing to do if array', () => { 15 | const value = [1]; 16 | const actual = toArrayIfNeeded(value); 17 | const expected = value; 18 | 19 | expect(actual).toBe(expected); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/__tests__/utils/utils/toAudioTime.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import toAudioTime from '../../../utils/utils/toAudioTime'; 4 | 5 | describe('utils/toAudioTime()', () => { 6 | it('return the provided value when provide positive number', () => { 7 | expect(toAudioTime(10)).toBe(10); 8 | }); 9 | 10 | it('return 0 when provide a negative number', () => { 11 | expect(toAudioTime(-1)).toBe(0); 12 | }); 13 | 14 | it('return 0 when provided infinite number', () => { 15 | expect(toAudioTime(Infinity)).toBe(0); 16 | }); 17 | 18 | it("convert to number when provided format of 'ss.SSS'", () => { 19 | expect(toAudioTime('23.456')).toBe(23.456); 20 | }); 21 | 22 | it("convert to number when provided format of 'mm:ss.SSS'", () => { 23 | expect(toAudioTime('01:23.456')).toBe(83.456); 24 | }); 25 | 26 | it("convert to number when provided format of 'hh:mm:ss.SSS'", () => { 27 | expect(toAudioTime('00:01:23.456')).toBe(83.456); 28 | }); 29 | 30 | it('return 0 when provided case', () => { 31 | expect(toAudioTime('UNKNOWN')).toBe(0); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/__tests__/utils/utils/toDecibel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import toDecibel from '../../../utils/utils/toDecibel'; 4 | 5 | describe('utils/toDecibel(gainValue)', () => { 6 | it('convert gainValue to decibel', () => { 7 | expect(Math.round(toDecibel(3.162))).toBe(10); 8 | expect(Math.round(toDecibel(1.995))).toBe(6); 9 | expect(Math.round(toDecibel(1.413))).toBe(3); 10 | expect(Math.round(toDecibel(1.122))).toBe(1); 11 | expect(Math.round(toDecibel(1.0))).toBe(0); 12 | expect(Math.round(toDecibel(0.891))).toBe(-1); 13 | expect(Math.round(toDecibel(0.708))).toBe(-3); 14 | expect(Math.round(toDecibel(0.501))).toBe(-6); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/__tests__/utils/utils/toGain.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import toGain from '../../../utils/utils/toGain'; 4 | 5 | function closeTo(a, b, delta) { 6 | return Math.abs(a - b) <= delta; 7 | } 8 | 9 | describe('utils/toGain(decibel)', () => { 10 | it('convert decibel to gain', () => { 11 | expect(closeTo(toGain(10), 3.1622776985168457, 1e-6)).toBeTruthy(); 12 | expect(closeTo(toGain(6), 1.9952622652053833, 1e-6)).toBeTruthy(); 13 | expect(closeTo(toGain(3), 1.4125375747680664, 1e-6)).toBeTruthy(); 14 | expect(closeTo(toGain(1), 1.1220184564590454, 1e-6)).toBeTruthy(); 15 | expect(toGain(0)).toBe(1); 16 | expect(closeTo(toGain(-1), 0.8912509083747864, 1e-6)).toBeTruthy(); 17 | expect(closeTo(toGain(-3), 0.7079457640647888, 1e-6)).toBeTruthy(); 18 | expect(closeTo(toGain(-6), 0.5011872053146362, 1e-6)).toBeTruthy(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/__tests__/utils/utils/toImpl.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import toImpl from '../../../utils/utils/toImpl'; 4 | 5 | describe('utils/toImpl(value)', () => { 6 | it('convert to impl', () => { 7 | const impl = {}; 8 | const value = { _impl: impl }; 9 | const actual = toImpl(value); 10 | const expected = impl; 11 | 12 | expect(actual).toBe(expected); 13 | }); 14 | 15 | it('nothing to do', () => { 16 | const impl = {}; 17 | const actual = toImpl(impl); 18 | const expected = impl; 19 | 20 | expect(actual).toBe(expected); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/__tests__/utils/utils/toNumber.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import toNumber from '../../../utils/utils/toNumber'; 4 | 5 | describe('utils/toNumber(value)', () => { 6 | it('convert to number', () => { 7 | expect(toNumber(1)).toBe(1); 8 | expect(toNumber(Infinity)).toBe(Infinity); 9 | expect(toNumber('1')).toBe(1); 10 | expect(toNumber(NaN)).toBe(0); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/__tests__/utils/utils/toPowerOfTwo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import toPowerOfTwo from '../../../utils/utils/toPowerOfTwo'; 4 | 5 | describe('utils/toPowerOfTwo(value)', () => { 6 | it('convert to 2^n', () => { 7 | expect(toPowerOfTwo(1)).toBe(1); 8 | expect(toPowerOfTwo(2)).toBe(2); 9 | expect(toPowerOfTwo(3)).toBe(4); 10 | expect(toPowerOfTwo(3, Math.floor)).toBe(2); 11 | expect(toPowerOfTwo(3, Math.ceil)).toBe(4); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/__tests__/utils/utils/toValidBitDepth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import toValidBitDepth from '../../../utils/utils/toValidBitDepth'; 4 | 5 | describe('utils/toValidBitDepth()', () => { 6 | it('return valid bit depth', () => { 7 | expect(toValidBitDepth(8)).toBe(8); 8 | expect(toValidBitDepth(16)).toBe(16); 9 | expect(toValidBitDepth(32)).toBe(32); 10 | }); 11 | 12 | it('return the default bit depth 16 when provided an invalid bit depth', () => { 13 | expect(toValidBitDepth(0)).toBe(16); 14 | expect(toValidBitDepth(NaN)).toBe(16); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/__tests__/utils/utils/toValidBlockSize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import toValidBlockSize from '../../../utils/utils/toValidBlockSize'; 4 | 5 | describe('utils/toValidBlockSize()', () => { 6 | it('return valid block size', () => { 7 | expect(toValidBlockSize(0)).toBe(8); 8 | expect(toValidBlockSize(8)).toBe(8); 9 | expect(toValidBlockSize(128)).toBe(128); 10 | expect(toValidBlockSize(500)).toBe(512); 11 | expect(toValidBlockSize(2000)).toBe(1024); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/__tests__/utils/utils/toValidNumberOfChannels.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import toValidNumberOfChannels from '../../../utils/utils/toValidNumberOfChannels'; 4 | 5 | describe('utils/toValidNumberOfChannels()', () => { 6 | it('return valid number of channels', () => { 7 | expect(toValidNumberOfChannels(0)).toBe(1); 8 | expect(toValidNumberOfChannels(2)).toBe(2); 9 | expect(toValidNumberOfChannels(8)).toBe(8); 10 | expect(toValidNumberOfChannels(2000)).toBe(32); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/__tests__/utils/utils/toValidSampleRate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import toValidSampleRate from '../../../utils/utils/toValidSampleRate'; 4 | 5 | describe('utils/toValidSampleRate()', () => { 6 | it('return valid sampleRate', () => { 7 | expect(toValidSampleRate(0)).toBe(3000); 8 | expect(toValidSampleRate(5512.5)).toBe(5512); 9 | expect(toValidSampleRate(44100)).toBe(44100); 10 | expect(toValidSampleRate(48000)).toBe(48000); 11 | expect(toValidSampleRate(Infinity)).toBe(192000); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/__tests_helpers/np.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function zeros(size, constructor = Float32Array) { 4 | return new constructor(size); 5 | } 6 | 7 | function full(size, value, constructor = Float32Array) { 8 | return new constructor(size).fill(value); 9 | } 10 | 11 | function random_sample(size, constructor = Float32Array) { 12 | return new constructor(size).map(Math.random); 13 | } 14 | 15 | export { zeros, full, random_sample }; 16 | -------------------------------------------------------------------------------- /src/api/AnalyserNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as impl from '../impl'; 4 | import AudioNode from './AudioNode'; 5 | 6 | class AnalyserNode extends AudioNode { 7 | constructor(context, opts) { 8 | super(context); 9 | 10 | this._impl = new impl.AnalyserNode(context._impl, opts); 11 | } 12 | 13 | get fftSize() { 14 | return this._impl.getFftSize(); 15 | } 16 | 17 | set fftSize(value) { 18 | this._impl.setFftSize(value); 19 | } 20 | 21 | get frequencyBinCount() { 22 | return this._impl.getFrequencyBinCount(); 23 | } 24 | 25 | get minDecibels() { 26 | return this._impl.getMinDecibels(); 27 | } 28 | 29 | set minDecibels(value) { 30 | this._impl.setMinDecibels(value); 31 | } 32 | 33 | get maxDecibels() { 34 | return this._impl.getMaxDecibels(); 35 | } 36 | 37 | set maxDecibels(value) { 38 | this._impl.setMaxDecibels(value); 39 | } 40 | 41 | get smoothingTimeConstant() { 42 | return this._impl.getSmoothingTimeConstant(); 43 | } 44 | 45 | set smoothingTimeConstant(value) { 46 | this._impl.setSmoothingTimeConstant(value); 47 | } 48 | 49 | getFloatFrequencyData(array) { 50 | this._impl.getFloatFrequencyData(array); 51 | } 52 | 53 | getByteFrequencyData(array) { 54 | this._impl.getByteFrequencyData(array); 55 | } 56 | 57 | getFloatTimeDomainData(array) { 58 | this._impl.getFloatTimeDomainData(array); 59 | } 60 | 61 | getByteTimeDomainData(array) { 62 | this._impl.getByteTimeDomainData(array); 63 | } 64 | } 65 | 66 | export default AnalyserNode; 67 | -------------------------------------------------------------------------------- /src/api/AudioBuffer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as impl from '../impl'; 4 | import { defineProp } from '../utils'; 5 | 6 | class AudioBuffer { 7 | constructor(opts) { 8 | defineProp(this, '_impl', new impl.AudioBuffer(opts)); 9 | } 10 | 11 | get sampleRate() { 12 | return this._impl.getSampleRate(); 13 | } 14 | 15 | get length() { 16 | return this._impl.getLength(); 17 | } 18 | 19 | get duration() { 20 | return this._impl.getDuration(); 21 | } 22 | 23 | get numberOfChannels() { 24 | return this._impl.getNumberOfChannels(); 25 | } 26 | 27 | getChannelData(channel) { 28 | return this._impl.getChannelData(channel); 29 | } 30 | 31 | copyFromChannel(destination, channelNumber, startInChannel) { 32 | this._impl.copyFromChannel(destination, channelNumber, startInChannel); 33 | } 34 | 35 | copyToChannel(source, channelNumber, startInChannel) { 36 | this._impl.copyToChannel(source, channelNumber, startInChannel); 37 | } 38 | } 39 | 40 | export default AudioBuffer; 41 | -------------------------------------------------------------------------------- /src/api/AudioBufferSourceNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as impl from '../impl'; 4 | import AudioScheduledSourceNode from './AudioScheduledSourceNode'; 5 | import AudioParam from './AudioParam'; 6 | 7 | class AudioBufferSourceNode extends AudioScheduledSourceNode { 8 | constructor(context, opts) { 9 | super(context); 10 | 11 | this._impl = new impl.AudioBufferSourceNode(context._impl, opts); 12 | this._impl.$playbackRate = new AudioParam( 13 | context, 14 | this._impl.getPlaybackRate(), 15 | ); 16 | this._impl.$detune = new AudioParam(context, this._impl.getDetune()); 17 | this._impl.$buffer = null; 18 | this._impl.$onended = null; 19 | 20 | if (opts && opts.buffer) { 21 | this.buffer = opts.buffer; 22 | } 23 | } 24 | 25 | get buffer() { 26 | return this._impl.$buffer; 27 | } 28 | 29 | set buffer(value) { 30 | this._impl.$buffer = value; 31 | this._impl.setBuffer(value); 32 | } 33 | 34 | get playbackRate() { 35 | return this._impl.$playbackRate; 36 | } 37 | 38 | get detune() { 39 | return this._impl.$detune; 40 | } 41 | 42 | get loop() { 43 | return this._impl.getLoop(); 44 | } 45 | 46 | set loop(value) { 47 | this._impl.setLoop(value); 48 | } 49 | 50 | get loopStart() { 51 | return this._impl.getLoopStart(); 52 | } 53 | 54 | set loopStart(value) { 55 | this._impl.setLoopStart(value); 56 | } 57 | 58 | get loopEnd() { 59 | return this._impl.getLoopEnd(); 60 | } 61 | 62 | set loopEnd(value) { 63 | this._impl.setLoopEnd(value); 64 | } 65 | 66 | start(when, offset, duration) { 67 | this._impl.start(when, offset, duration); 68 | } 69 | } 70 | 71 | export default AudioBufferSourceNode; 72 | -------------------------------------------------------------------------------- /src/api/AudioDestinationNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioNode from './AudioNode'; 4 | 5 | class AudioDestinationNode extends AudioNode { 6 | constructor(context, impl) { 7 | super(context); 8 | 9 | this._impl = impl; 10 | } 11 | 12 | get maxChannelCount() { 13 | return this._impl.getMaxChannelCount(); 14 | } 15 | } 16 | 17 | export default AudioDestinationNode; 18 | -------------------------------------------------------------------------------- /src/api/AudioListener.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { defineProp } from '../utils'; 4 | 5 | class AudioListener { 6 | constructor(context, impl) { 7 | defineProp(this, '_impl', impl); 8 | } 9 | 10 | setPosition(x, y, z) { 11 | this._impl.setPosition(x, y, z); 12 | } 13 | 14 | setOrientation(x, y, z, xUp, yUp, zUp) { 15 | this._impl.setOrientation(x, y, z, xUp, yUp, zUp); 16 | } 17 | } 18 | 19 | export default AudioListener; 20 | -------------------------------------------------------------------------------- /src/api/AudioNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import EventTarget from './EventTarget'; 4 | import { defineProp } from '../utils'; 5 | 6 | class AudioNode extends EventTarget { 7 | constructor(context) { 8 | super(); 9 | 10 | defineProp(this, '_context', context); 11 | defineProp(this, '_impl', null); 12 | } 13 | 14 | get context() { 15 | return this._context; 16 | } 17 | 18 | get numberOfInputs() { 19 | return this._impl.getNumberOfInputs(); 20 | } 21 | 22 | get numberOfOutputs() { 23 | return this._impl.getNumberOfOutputs(); 24 | } 25 | 26 | get channelCount() { 27 | return this._impl.getChannelCount(); 28 | } 29 | 30 | set channelCount(value) { 31 | this._impl.setChannelCount(value); 32 | } 33 | 34 | get channelCountMode() { 35 | return this._impl.getChannelCountMode(); 36 | } 37 | 38 | set channelCountMode(value) { 39 | this._impl.setChannelCountMode(value); 40 | } 41 | 42 | get channelInterpretation() { 43 | return this._impl.getChannelInterpretation(); 44 | } 45 | 46 | set channelInterpretation(value) { 47 | return this._impl.setChannelInterpretation(value); 48 | } 49 | 50 | connect(destination, input, output) { 51 | this._impl.connect(destination._impl, input, output); 52 | 53 | /* istanbul ignore else */ 54 | if (destination instanceof AudioNode) { 55 | return destination; 56 | } 57 | } 58 | 59 | disconnect() { 60 | this._impl.disconnect.apply(this._impl, arguments); 61 | } 62 | } 63 | 64 | export default AudioNode; 65 | -------------------------------------------------------------------------------- /src/api/AudioParam.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { defineProp } from '../utils'; 4 | 5 | class AudioParam { 6 | constructor(context, impl) { 7 | defineProp(this, '_context', context); 8 | defineProp(this, '_impl', impl); 9 | } 10 | 11 | get value() { 12 | return this._impl.getValue(); 13 | } 14 | 15 | set value(value) { 16 | this._impl.setValue(value); 17 | } 18 | 19 | get automationRate() { 20 | return this._impl.getRate(); 21 | } 22 | 23 | get defaultValue() { 24 | return this._impl.getDefaultValue(); 25 | } 26 | 27 | setValueAtTime(value, startTime) { 28 | this._impl.setValueAtTime(value, startTime); 29 | return this; 30 | } 31 | 32 | linearRampToValueAtTime(value, endTime) { 33 | this._impl.linearRampToValueAtTime(value, endTime); 34 | return this; 35 | } 36 | 37 | exponentialRampToValueAtTime(value, endTime) { 38 | this._impl.exponentialRampToValueAtTime(value, endTime); 39 | return this; 40 | } 41 | 42 | setTargetAtTime(target, startTime, timeConstant) { 43 | this._impl.setTargetAtTime(target, startTime, timeConstant); 44 | return this; 45 | } 46 | 47 | setValueCurveAtTime(values, startTime, duration) { 48 | this._impl.setValueCurveAtTime(values, startTime, duration); 49 | return this; 50 | } 51 | 52 | cancelScheduledValues(startTime) { 53 | this._impl.cancelScheduledValues(startTime); 54 | return this; 55 | } 56 | } 57 | 58 | export default AudioParam; 59 | -------------------------------------------------------------------------------- /src/api/AudioScheduledSourceNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioNode from './AudioNode'; 4 | 5 | class AudioScheduledSourceNode extends AudioNode { 6 | get onended() { 7 | return this._impl.$onended; 8 | } 9 | 10 | set onended(callback) { 11 | this._impl.replaceEventListener('ended', this._impl.$onended, callback); 12 | this._impl.$onended = callback; 13 | } 14 | 15 | /** 16 | * 17 | * @param {number} when 18 | * @param {number} [offset] 19 | * @param {number} [duration] 20 | */ 21 | start(when, offset, duration) { 22 | this._impl.start(when, offset, duration); 23 | } 24 | 25 | stop(when) { 26 | this._impl.stop(when); 27 | } 28 | } 29 | 30 | export default AudioScheduledSourceNode; 31 | -------------------------------------------------------------------------------- /src/api/AudioWorkletNode.ts: -------------------------------------------------------------------------------- 1 | import { AudioWorkletNode } from '../impl'; 2 | 3 | export default AudioWorkletNode; 4 | -------------------------------------------------------------------------------- /src/api/AudioWorkletProcessor.ts: -------------------------------------------------------------------------------- 1 | import { AudioWorkletProcessor } from '../impl'; 2 | 3 | export default AudioWorkletProcessor; 4 | -------------------------------------------------------------------------------- /src/api/BiquadFilterNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as impl from '../impl'; 4 | import AudioNode from './AudioNode'; 5 | import AudioParam from './AudioParam'; 6 | 7 | class BiquadFilterNode extends AudioNode { 8 | constructor(context, opts) { 9 | super(context); 10 | 11 | this._impl = new impl.BiquadFilterNode(context._impl, opts); 12 | this._impl.$frequency = new AudioParam(context, this._impl.getFrequency()); 13 | this._impl.$detune = new AudioParam(context, this._impl.getDetune()); 14 | this._impl.$Q = new AudioParam(context, this._impl.getQ()); 15 | this._impl.$gain = new AudioParam(context, this._impl.getGain()); 16 | } 17 | 18 | get type() { 19 | return this._impl.getType(); 20 | } 21 | 22 | set type(value) { 23 | this._impl.setType(value); 24 | } 25 | 26 | get frequency() { 27 | return this._impl.$frequency; 28 | } 29 | 30 | get detune() { 31 | return this._impl.$detune; 32 | } 33 | 34 | get Q() { 35 | return this._impl.$Q; 36 | } 37 | 38 | get gain() { 39 | return this._impl.$gain; 40 | } 41 | 42 | getFrequencyResponse(frequencyHz, magResponse, phaseResponse) { 43 | this._impl.getFrequencyResponse(frequencyHz, magResponse, phaseResponse); 44 | } 45 | } 46 | 47 | export default BiquadFilterNode; 48 | -------------------------------------------------------------------------------- /src/api/ChannelMergerNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as impl from '../impl'; 4 | import AudioNode from './AudioNode'; 5 | 6 | class ChannelMergerNode extends AudioNode { 7 | constructor(context, opts) { 8 | super(context); 9 | 10 | this._impl = new impl.ChannelMergerNode(context._impl, opts); 11 | } 12 | } 13 | 14 | export default ChannelMergerNode; 15 | -------------------------------------------------------------------------------- /src/api/ChannelSplitterNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as impl from '../impl'; 4 | import AudioNode from './AudioNode'; 5 | 6 | class ChannelSplitterNode extends AudioNode { 7 | constructor(context, opts) { 8 | super(context); 9 | 10 | this._impl = new impl.ChannelSplitterNode(context._impl, opts); 11 | } 12 | } 13 | 14 | export default ChannelSplitterNode; 15 | -------------------------------------------------------------------------------- /src/api/ConstantSourceNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as impl from '../impl'; 4 | import AudioScheduledSourceNode from './AudioScheduledSourceNode'; 5 | import AudioParam from './AudioParam'; 6 | 7 | class ConstantSourceNode extends AudioScheduledSourceNode { 8 | constructor(context, opts) { 9 | super(context); 10 | 11 | this._impl = new impl.ConstantSourceNode(context._impl, opts); 12 | this._impl.$offset = new AudioParam(context, this._impl.getOffset()); 13 | this._impl.$onended = null; 14 | } 15 | 16 | get offset() { 17 | return this._impl.$offset; 18 | } 19 | } 20 | 21 | export default ConstantSourceNode; 22 | -------------------------------------------------------------------------------- /src/api/ConvolverNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as impl from '../impl'; 4 | import AudioNode from './AudioNode'; 5 | 6 | class ConvolverNode extends AudioNode { 7 | constructor(context, opts) { 8 | super(context); 9 | 10 | this._impl = new impl.ConvolverNode(context._impl, opts); 11 | this._impl.$buffer = null; 12 | 13 | if (opts && opts.buffer) { 14 | this.buffer = opts.buffer; 15 | } 16 | } 17 | 18 | get buffer() { 19 | return this._impl.$buffer; 20 | } 21 | 22 | set buffer(value) { 23 | this._impl.$buffer = value; 24 | this._impl.setBuffer(value); 25 | } 26 | 27 | get normalize() { 28 | return this._impl.getNormalize(); 29 | } 30 | 31 | set normalize(value) { 32 | this._impl.setNormalize(value); 33 | } 34 | } 35 | 36 | export default ConvolverNode; 37 | -------------------------------------------------------------------------------- /src/api/DelayNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as impl from '../impl'; 4 | import AudioNode from './AudioNode'; 5 | import AudioParam from './AudioParam'; 6 | 7 | class DelayNode extends AudioNode { 8 | constructor(context, opts) { 9 | super(context); 10 | 11 | this._impl = new impl.DelayNode(context._impl, opts); 12 | this._impl.$delayTime = new AudioParam(context, this._impl.getDelayTime()); 13 | } 14 | 15 | get delayTime() { 16 | return this._impl.$delayTime; 17 | } 18 | } 19 | 20 | export default DelayNode; 21 | -------------------------------------------------------------------------------- /src/api/DynamicsCompressorNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as impl from '../impl'; 4 | import AudioNode from './AudioNode'; 5 | import AudioParam from './AudioParam'; 6 | 7 | class DynamicsCompressorNode extends AudioNode { 8 | constructor(context, opts) { 9 | super(context); 10 | 11 | this._impl = new impl.DynamicsCompressorNode(context._impl, opts); 12 | this._impl.$threshold = new AudioParam(context, this._impl.getThreshold()); 13 | this._impl.$knee = new AudioParam(context, this._impl.getKnee()); 14 | this._impl.$ratio = new AudioParam(context, this._impl.getRatio()); 15 | this._impl.$attack = new AudioParam(context, this._impl.getAttack()); 16 | this._impl.$release = new AudioParam(context, this._impl.getRelease()); 17 | } 18 | 19 | get threshold() { 20 | return this._impl.$threshold; 21 | } 22 | 23 | get knee() { 24 | return this._impl.$knee; 25 | } 26 | 27 | get ratio() { 28 | return this._impl.$ratio; 29 | } 30 | 31 | get reduction() { 32 | return this._impl.getReduction(); 33 | } 34 | 35 | get attack() { 36 | return this._impl.$attack; 37 | } 38 | 39 | get release() { 40 | return this._impl.$release; 41 | } 42 | } 43 | 44 | export default DynamicsCompressorNode; 45 | -------------------------------------------------------------------------------- /src/api/EventTarget.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class EventTarget { 4 | addEventListener(type, listener) { 5 | this._impl.addEventListener(type, listener); 6 | } 7 | 8 | removeEventListener(type, listener) { 9 | this._impl.removeEventListener(type, listener); 10 | } 11 | } 12 | 13 | export default EventTarget; 14 | -------------------------------------------------------------------------------- /src/api/GainNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as impl from '../impl'; 4 | import AudioNode from './AudioNode'; 5 | import AudioParam from './AudioParam'; 6 | 7 | class GainNode extends AudioNode { 8 | constructor(context, opts) { 9 | super(context); 10 | 11 | this._impl = new impl.GainNode(context._impl, opts); 12 | this._impl.$gain = new AudioParam(context, this._impl.getGain()); 13 | } 14 | 15 | get gain() { 16 | return this._impl.$gain; 17 | } 18 | } 19 | 20 | export default GainNode; 21 | -------------------------------------------------------------------------------- /src/api/IIRFilterNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as impl from '../impl'; 4 | import AudioNode from './AudioNode'; 5 | 6 | class IIRFilterNode extends AudioNode { 7 | constructor(context, opts) { 8 | super(context); 9 | 10 | this._impl = new impl.IIRFilterNode(context._impl, opts); 11 | } 12 | 13 | getFrequencyResponse(frequencyHz, magResponse, phaseResponse) { 14 | this._impl.getFrequencyResponse(frequencyHz, magResponse, phaseResponse); 15 | } 16 | } 17 | 18 | export default IIRFilterNode; 19 | -------------------------------------------------------------------------------- /src/api/OscillatorNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as impl from '../impl'; 4 | import AudioScheduledSourceNode from './AudioScheduledSourceNode'; 5 | import AudioParam from './AudioParam'; 6 | 7 | class OscillatorNode extends AudioScheduledSourceNode { 8 | constructor(context, opts) { 9 | super(context); 10 | 11 | this._impl = new impl.OscillatorNode(context._impl, opts); 12 | this._impl.$frequency = new AudioParam(context, this._impl.getFrequency()); 13 | this._impl.$detune = new AudioParam(context, this._impl.getDetune()); 14 | this._impl.$onended = null; 15 | 16 | if (opts && opts.periodicWave) { 17 | this.setPeriodicWave(opts.periodicWave); 18 | } 19 | } 20 | 21 | get type() { 22 | return this._impl.getType(); 23 | } 24 | 25 | set type(value) { 26 | this._impl.setType(value); 27 | } 28 | 29 | get frequency() { 30 | return this._impl.$frequency; 31 | } 32 | 33 | get detune() { 34 | return this._impl.$detune; 35 | } 36 | 37 | setPeriodicWave(periodicWave) { 38 | this._impl.setPeriodicWave(periodicWave); 39 | } 40 | } 41 | 42 | export default OscillatorNode; 43 | -------------------------------------------------------------------------------- /src/api/PannerNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as impl from '../impl'; 4 | import AudioNode from './AudioNode'; 5 | 6 | class PannerNode extends AudioNode { 7 | constructor(context, opts) { 8 | super(context); 9 | 10 | this._impl = new impl.PannerNode(context._impl, opts); 11 | } 12 | 13 | get panningModel() { 14 | return this._impl.getPanningModel(); 15 | } 16 | 17 | set panningModel(value) { 18 | this._impl.setPanningModel(value); 19 | } 20 | 21 | get distanceModel() { 22 | return this._impl.getDistanceModel(); 23 | } 24 | 25 | set distanceModel(value) { 26 | this._impl.setDistanceModel(value); 27 | } 28 | 29 | get refDistance() { 30 | return this._impl.getRefDistance(); 31 | } 32 | 33 | set refDistance(value) { 34 | this._impl.setRefDistance(value); 35 | } 36 | 37 | get maxDistance() { 38 | return this._impl.getMaxDistance(); 39 | } 40 | 41 | set maxDistance(value) { 42 | this._impl.setMaxDistance(value); 43 | } 44 | 45 | get rolloffFactor() { 46 | return this._impl.getRolloffFactor(); 47 | } 48 | 49 | set rolloffFactor(value) { 50 | this._impl.setRolloffFactor(value); 51 | } 52 | 53 | get coneInnerAngle() { 54 | return this._impl.getConeInnerAngle(); 55 | } 56 | 57 | set coneInnerAngle(value) { 58 | this._impl.setConeInnerAngle(value); 59 | } 60 | 61 | get coneOuterAngle() { 62 | return this._impl.getConeOuterAngle(); 63 | } 64 | 65 | set coneOuterAngle(value) { 66 | this._impl.setConeOuterAngle(value); 67 | } 68 | 69 | get coneOuterGain() { 70 | return this._impl.getConeOuterGain(); 71 | } 72 | 73 | set coneOuterGain(value) { 74 | this._impl.setConeOuterGain(value); 75 | } 76 | 77 | setPosition(x, y, z) { 78 | this._impl.setPosition(x, y, z); 79 | } 80 | 81 | setOrientation(x, y, z) { 82 | this._impl.setOrientation(x, y, z); 83 | } 84 | 85 | setVelocity(x, y, z) { 86 | this._impl.setVelocity(x, y, z); 87 | } 88 | } 89 | 90 | export default PannerNode; 91 | -------------------------------------------------------------------------------- /src/api/PeriodicWave.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as impl from '../impl'; 4 | import { defineProp } from '../utils'; 5 | 6 | class PeriodicWave { 7 | constructor(context, opts) { 8 | defineProp(this, '_impl', new impl.PeriodicWave(context._impl, opts)); 9 | } 10 | } 11 | 12 | export default PeriodicWave; 13 | -------------------------------------------------------------------------------- /src/api/README.md: -------------------------------------------------------------------------------- 1 | These files provide public interfaces of the web audio. The implementations are put on [src/impl](../impl). 2 | -------------------------------------------------------------------------------- /src/api/ScriptProcessorNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as impl from '../impl'; 4 | import AudioNode from './AudioNode'; 5 | import AudioBuffer from './AudioBuffer'; 6 | 7 | class ScriptProcessorNode extends AudioNode { 8 | constructor(context, opts) { 9 | super(context); 10 | 11 | this._impl = new impl.ScriptProcessorNode(context._impl, opts); 12 | this._impl.$onaudioprocess = null; 13 | this._impl.setEventItem({ 14 | type: 'audioprocess', 15 | playbackTime: 0, 16 | inputBuffer: new AudioBuffer(), 17 | outputBuffer: new AudioBuffer(), 18 | }); 19 | } 20 | 21 | get bufferSize() { 22 | return this._impl.getBufferSize(); 23 | } 24 | 25 | get onaudioprocess() { 26 | return this._impl.$onaudioprocess; 27 | } 28 | 29 | set onaudioprocess(callback) { 30 | this._impl.replaceEventListener( 31 | 'audioprocess', 32 | this._impl.$onaudioprocess, 33 | callback, 34 | ); 35 | this._impl.$onaudioprocess = callback; 36 | } 37 | } 38 | 39 | export default ScriptProcessorNode; 40 | -------------------------------------------------------------------------------- /src/api/SpatialListener.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioParam from './AudioParam'; 4 | import { defineProp } from '../utils'; 5 | 6 | class SpatialListener { 7 | constructor(context, impl) { 8 | defineProp(this, '_context', context); 9 | defineProp(this, '_impl', impl); 10 | 11 | this._impl.$positionX = new AudioParam(context, this._impl.getPositionX()); 12 | this._impl.$positionY = new AudioParam(context, this._impl.getPositionY()); 13 | this._impl.$positionZ = new AudioParam(context, this._impl.getPositionZ()); 14 | this._impl.$forwardX = new AudioParam(context, this._impl.getForwardX()); 15 | this._impl.$forwardY = new AudioParam(context, this._impl.getForwardY()); 16 | this._impl.$forwardZ = new AudioParam(context, this._impl.getForwardZ()); 17 | this._impl.$upX = new AudioParam(context, this._impl.getUpX()); 18 | this._impl.$upY = new AudioParam(context, this._impl.getUpY()); 19 | this._impl.$upZ = new AudioParam(context, this._impl.getUpZ()); 20 | } 21 | 22 | get positionX() { 23 | return this._impl.$positionX; 24 | } 25 | 26 | get positionY() { 27 | return this._impl.$positionY; 28 | } 29 | 30 | get positionZ() { 31 | return this._impl.$positionZ; 32 | } 33 | 34 | get forwardX() { 35 | return this._impl.$forwardX; 36 | } 37 | 38 | get forwardY() { 39 | return this._impl.$forwardY; 40 | } 41 | 42 | get forwardZ() { 43 | return this._impl.$forwardZ; 44 | } 45 | 46 | get upX() { 47 | return this._impl.$upX; 48 | } 49 | 50 | get upY() { 51 | return this._impl.$upY; 52 | } 53 | 54 | get upZ() { 55 | return this._impl.$upZ; 56 | } 57 | } 58 | 59 | export default SpatialListener; 60 | -------------------------------------------------------------------------------- /src/api/StereoPannerNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as impl from '../impl'; 4 | import AudioNode from './AudioNode'; 5 | import AudioParam from './AudioParam'; 6 | 7 | class StereoPannerNode extends AudioNode { 8 | constructor(context, opts) { 9 | super(context); 10 | 11 | this._impl = new impl.StereoPannerNode(context._impl, opts); 12 | this._impl.$pan = new AudioParam(context, this._impl.getPan()); 13 | } 14 | 15 | get pan() { 16 | return this._impl.$pan; 17 | } 18 | } 19 | 20 | export default StereoPannerNode; 21 | -------------------------------------------------------------------------------- /src/api/WaveShaperNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as impl from '../impl'; 4 | import AudioNode from './AudioNode'; 5 | 6 | class WaveShaperNode extends AudioNode { 7 | constructor(context, opts) { 8 | super(context); 9 | 10 | this._impl = new impl.WaveShaperNode(context._impl, opts); 11 | } 12 | 13 | get curve() { 14 | return this._impl.getCurve(); 15 | } 16 | 17 | set curve(value) { 18 | this._impl.setCurve(value); 19 | } 20 | 21 | get oversample() { 22 | return this._impl.getOversample(); 23 | } 24 | 25 | set oversample(value) { 26 | this._impl.setOversample(value); 27 | } 28 | } 29 | 30 | export default WaveShaperNode; 31 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export { default as AnalyserNode } from './AnalyserNode'; 4 | export { default as AudioBuffer } from './AudioBuffer'; 5 | export { default as AudioBufferSourceNode } from './AudioBufferSourceNode'; 6 | export { default as AudioDestinationNode } from './AudioDestinationNode'; 7 | export { default as AudioListener } from './AudioListener'; 8 | export { default as AudioNode } from './AudioNode'; 9 | export { default as AudioParam } from './AudioParam'; 10 | export { default as AudioScheduledSourceNode } from './AudioScheduledSourceNode'; 11 | export { default as AudioWorkletNode } from './AudioWorkletNode'; 12 | export { default as AudioWorkletProcessor } from './AudioWorkletProcessor'; 13 | export { default as BaseAudioContext } from './BaseAudioContext'; 14 | export { default as BiquadFilterNode } from './BiquadFilterNode'; 15 | export { default as ChannelMergerNode } from './ChannelMergerNode'; 16 | export { default as ChannelSplitterNode } from './ChannelSplitterNode'; 17 | export { default as ConstantSourceNode } from './ConstantSourceNode'; 18 | export { default as ConvolverNode } from './ConvolverNode'; 19 | export { default as DelayNode } from './DelayNode'; 20 | export { default as DynamicsCompressorNode } from './DynamicsCompressorNode'; 21 | export { default as EventTarget } from './EventTarget'; 22 | export { default as GainNode } from './GainNode'; 23 | export { default as IIRFilterNode } from './IIRFilterNode'; 24 | export { default as OscillatorNode } from './OscillatorNode'; 25 | export { default as PannerNode } from './PannerNode'; 26 | export { default as PeriodicWave } from './PeriodicWave'; 27 | export { default as ScriptProcessorNode } from './ScriptProcessorNode'; 28 | export { default as SpatialListener } from './SpatialListener'; 29 | export { default as SpatialPannerNode } from './SpatialPannerNode'; 30 | export { default as StereoPannerNode } from './StereoPannerNode'; 31 | export { default as WaveShaperNode } from './WaveShaperNode'; 32 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default { 4 | sampleRate: 44100, 5 | numberOfChannels: 2, 6 | blockSize: 128, 7 | bitDepth: 16, 8 | }; 9 | -------------------------------------------------------------------------------- /src/constants/AudioContextState.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const RUNNING = 'running'; 4 | export const SUSPENDED = 'suspended'; 5 | export const CLOSED = 'closed'; 6 | -------------------------------------------------------------------------------- /src/constants/AudioParamEvent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const SET_VALUE_AT_TIME = 'setValueAtTime'; 4 | export const LINEAR_RAMP_TO_VALUE_AT_TIME = 'linearRampToValueAtTime'; 5 | export const EXPONENTIAL_RAMP_TO_VALUE_AT_TIME = 'exponentialRampToValueAtTime'; 6 | export const SET_TARGET_AT_TIME = 'setTargetAtTime'; 7 | export const SET_VALUE_CURVE_AT_TIME = 'setValueCurveAtTime'; 8 | -------------------------------------------------------------------------------- /src/constants/AudioParamRate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const CONTROL_RATE = 'control'; 4 | export const AUDIO_RATE = 'audio'; 5 | -------------------------------------------------------------------------------- /src/constants/BiquadFilterType.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const LOWPASS = 'lowpass'; 4 | export const HIGHPASS = 'highpass'; 5 | export const BANDPASS = 'bandpass'; 6 | export const LOWSHELF = 'lowshelf'; 7 | export const HIGHSHELF = 'highshelf'; 8 | export const PEAKING = 'peaking'; 9 | export const NOTCH = 'notch'; 10 | export const ALLPASS = 'allpass'; 11 | -------------------------------------------------------------------------------- /src/constants/ChannelCountMode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const MAX = 'max'; 4 | export const CLAMPED_MAX = 'clamped-max'; 5 | export const EXPLICIT = 'explicit'; 6 | -------------------------------------------------------------------------------- /src/constants/ChannelInterpretation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const SPEAKERS = 'speakers'; 4 | export const DISCRETE = 'discrete'; 5 | -------------------------------------------------------------------------------- /src/constants/OscillatorType.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const SINE = 'sine'; 4 | export const SAWTOOTH = 'sawtooth'; 5 | export const TRIANGLE = 'triangle'; 6 | export const SQUARE = 'square'; 7 | export const CUSTOM = 'custom'; 8 | -------------------------------------------------------------------------------- /src/constants/PlaybackState.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const UNSCHEDULED = 'unscheduled'; 4 | export const SCHEDULED = 'scheduled'; 5 | export const PLAYING = 'playing'; 6 | export const FINISHED = 'finished'; 7 | -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const MIN_SAMPLERATE = 3000; 4 | export const MAX_SAMPLERATE = 192000; 5 | export const MIN_NUMBER_OF_CHANNELS = 1; 6 | export const MAX_NUMBER_OF_CHANNELS = 32; 7 | export const MIN_BLOCK_SIZE = 8; 8 | export const MAX_BLOCK_SIZE = 1024; 9 | -------------------------------------------------------------------------------- /src/context/RawDataAudioContext.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import config from '../config'; 4 | import BaseAudioContext from '../api/BaseAudioContext'; 5 | import { 6 | toValidBlockSize, 7 | toValidNumberOfChannels, 8 | toValidSampleRate, 9 | } from '../utils'; 10 | import AudioContext from '../impl/AudioContext'; 11 | import AudioData from '../impl/core/AudioData'; 12 | import AudioBuffer from '../api/AudioBuffer'; 13 | 14 | export class RawDataAudioContext extends BaseAudioContext { 15 | readonly _impl!: AudioContext; 16 | public readonly blockSize: number; 17 | public readonly numberOfChannels: number; 18 | 19 | constructor({ 20 | sampleRate = config.sampleRate, 21 | blockSize = config.blockSize, 22 | numberOfChannels = config.numberOfChannels, 23 | }: { 24 | sampleRate?: number; 25 | blockSize?: number; 26 | numberOfChannels?: number; 27 | } = {}) { 28 | sampleRate = toValidSampleRate(sampleRate); 29 | blockSize = toValidBlockSize(blockSize); 30 | numberOfChannels = toValidNumberOfChannels(numberOfChannels); 31 | 32 | super({ sampleRate, blockSize, numberOfChannels }); 33 | 34 | this.blockSize = blockSize; 35 | this.numberOfChannels = numberOfChannels; 36 | } 37 | 38 | suspend() { 39 | return this._impl.suspend(); 40 | } 41 | 42 | createAudioBuffer( 43 | length: number, 44 | sampleRate: number, 45 | channels: Float32Array[], 46 | ): AudioBuffer { 47 | return new AudioBuffer( 48 | new AudioData(channels.length, length, sampleRate, channels), 49 | ); 50 | } 51 | 52 | process(channelBuffers: Float32Array[], offset: number = 0): void { 53 | this._impl.process(channelBuffers, offset); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/decoder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import audioType from 'audio-type'; 4 | import WavDecoder from 'wav-decoder'; 5 | import * as DecoderUtils from './utils/DecoderUtils'; 6 | import * as AudioDataUtils from './utils/AudioDataUtils'; 7 | import AudioBuffer from './api/AudioBuffer'; 8 | 9 | const decoders = {}; 10 | 11 | /** 12 | * @param {string} type 13 | * @return {function} 14 | */ 15 | function get(type) { 16 | return decoders[type] || null; 17 | } 18 | 19 | /** 20 | * @param {string} type 21 | * @param {function} fn 22 | */ 23 | function set(type, fn) { 24 | /* istanbul ignore else */ 25 | if (typeof fn === 'function') { 26 | decoders[type] = fn; 27 | } 28 | } 29 | 30 | /** 31 | * @param {ArrayBuffer} AudioBuffer 32 | * @param {object} opts 33 | * @return {Promise} 34 | */ 35 | function decode(audioData, opts) { 36 | const type = toAudioType(audioData); 37 | const decodeFn = decoders[type]; 38 | 39 | if (typeof decodeFn !== 'function') { 40 | return Promise.reject( 41 | new TypeError( 42 | `Decoder does not support the audio format: ${type || 'unknown'}`, 43 | ), 44 | ); 45 | } 46 | 47 | return DecoderUtils.decode(decodeFn, audioData, opts).then((audioData) => { 48 | return AudioDataUtils.toAudioBuffer(audioData, AudioBuffer); 49 | }); 50 | } 51 | 52 | function toAudioType(audioData) { 53 | if (!(audioData instanceof Uint8Array)) { 54 | audioData = new Uint8Array(audioData, 0, 16); 55 | } 56 | return audioType(audioData) || ''; 57 | } 58 | 59 | set('wav', WavDecoder.decode); 60 | 61 | export { get, set, decode }; 62 | -------------------------------------------------------------------------------- /src/encoder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import WavEncoder from 'wav-encoder'; 4 | import * as EncoderUtils from './utils/EncoderUtils'; 5 | 6 | const encoders = {}; 7 | 8 | /** 9 | * @param {string} type 10 | * @return {function} 11 | */ 12 | function get(type) { 13 | return encoders[type] || null; 14 | } 15 | 16 | /** 17 | * @param {string} type 18 | * @param {function} fn 19 | */ 20 | function set(type, fn) { 21 | /* istanbul ignore else */ 22 | if (typeof fn === 'function') { 23 | encoders[type] = fn; 24 | } 25 | } 26 | 27 | /** 28 | * @param {AudioData} audioData 29 | * @param {object} opts 30 | * @return {Promise} 31 | */ 32 | function encode(audioData, /* istanbul ignore next */ opts = {}) { 33 | const type = opts.type || 'wav'; 34 | const encodeFn = encoders[type]; 35 | 36 | if (typeof encodeFn !== 'function') { 37 | return Promise.reject( 38 | new TypeError(`Encoder does not support the audio format: ${type}`), 39 | ); 40 | } 41 | 42 | return EncoderUtils.encode(encodeFn, audioData, opts); 43 | } 44 | 45 | set('wav', WavEncoder.encode); 46 | 47 | export { get, set, encode }; 48 | -------------------------------------------------------------------------------- /src/impl/AudioDestinationNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioNode from './AudioNode'; 4 | import { toValidNumberOfChannels } from '../utils'; 5 | 6 | import { EXPLICIT } from '../constants/ChannelCountMode'; 7 | 8 | /** 9 | * @prop {AudioNodeOutput} output 10 | * @prop {AudioBus} outputBus 11 | */ 12 | class AudioDestinationNode extends AudioNode { 13 | /** 14 | * @param {AudioContext} context 15 | * @param {object} opts 16 | * @param {number} opts.numberOfChannels 17 | */ 18 | constructor(context, opts = {}) { 19 | let numberOfChannels = opts.numberOfChannels; 20 | 21 | numberOfChannels = toValidNumberOfChannels(numberOfChannels); 22 | 23 | super(context, opts, { 24 | inputs: [numberOfChannels], 25 | outputs: [], 26 | channelCount: numberOfChannels, 27 | channelCountMode: EXPLICIT, 28 | allowedMaxChannelCount: numberOfChannels, 29 | }); 30 | 31 | this._numberOfChannels = numberOfChannels | 0; 32 | this._destinationChannelData = this.inputs[0].bus.getChannelData(); 33 | } 34 | 35 | /** 36 | * @return {number} 37 | */ 38 | getMaxChannelCount() { 39 | return this._numberOfChannels; 40 | } 41 | 42 | /** 43 | * @param {Float32Array[]} channelData 44 | * @param {number} offset 45 | */ 46 | process(channelData, offset) { 47 | const inputs = this.inputs; 48 | const destinationChannelData = this._destinationChannelData; 49 | const numberOfChannels = channelData.length; 50 | 51 | for (let i = 0, imax = inputs.length; i < imax; i++) { 52 | inputs[i].pull(); 53 | } 54 | 55 | for (let ch = 0; ch < numberOfChannels; ch++) { 56 | channelData[ch].set(destinationChannelData[ch], offset); 57 | } 58 | } 59 | } 60 | 61 | export default AudioDestinationNode; 62 | -------------------------------------------------------------------------------- /src/impl/AudioListener.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class AudioListener { 4 | /** 5 | * @param {AudioContext} context 6 | */ 7 | constructor(context) { 8 | this.context = context; 9 | } 10 | 11 | /** 12 | * @param {number} x 13 | * @param {number} y 14 | * @param {number} z 15 | */ 16 | /* istanbul ignore next */ 17 | setPosition() { 18 | throw new TypeError('NOT YET IMPLEMENTED'); 19 | } 20 | 21 | /** 22 | * @param {number} x 23 | * @param {number} y 24 | * @param {number} z 25 | * @param {number} xUp 26 | * @param {number} yUp 27 | * @param {number} zUp 28 | */ 29 | /* istanbul ignore next */ 30 | setOrientation() { 31 | throw new TypeError('NOT YET IMPLEMENTED'); 32 | } 33 | } 34 | 35 | export default AudioListener; 36 | -------------------------------------------------------------------------------- /src/impl/AudioSourceNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import assert from 'assert'; 4 | import AudioNode from './AudioNode'; 5 | 6 | /* istanbul ignore next */ 7 | class AudioSourceNode extends AudioNode { 8 | /** 9 | * @param {AudioContext} context 10 | */ 11 | constructor(context, opts) { 12 | super(context, opts, { 13 | inputs: [], 14 | outputs: [1], 15 | }); 16 | } 17 | 18 | enableOutputsIfNecessary() { 19 | assert(!'SHOULD NOT BE CALLED'); 20 | } 21 | 22 | disableOutputsIfNecessary() { 23 | assert(!'SHOULD NOT BE CALLED'); 24 | } 25 | } 26 | 27 | export default AudioSourceNode; 28 | -------------------------------------------------------------------------------- /src/impl/AudioWorklet.ts: -------------------------------------------------------------------------------- 1 | // adapted from https://github.com/GoogleChromeLabs/audioworklet-polyfill/blob/master/src/index.js 2 | 3 | import { BaseAudioContext } from '../api'; 4 | import AudioWorkletProcessor from './AudioWorkletProcessor'; 5 | 6 | export interface AudioWorkletOptions { 7 | outputChannelCount: number[]; 8 | numberOfInputs: number; 9 | numberOfOutputs: number; 10 | 11 | // for web-audio-js only, register via a processor constructor rather than a url 12 | name: string; 13 | processorCtor: typeof AudioWorkletProcessor; 14 | } 15 | 16 | const workletProcessors = new WeakMap< 17 | BaseAudioContext, 18 | Map 19 | >(); 20 | 21 | export function getWorkletProcessor( 22 | audioContext: BaseAudioContext, 23 | name: string, 24 | ): typeof AudioWorkletProcessor | undefined { 25 | return workletProcessors.get(audioContext)?.get(name); 26 | } 27 | 28 | function registerProcessor( 29 | audioContext: BaseAudioContext, 30 | name: string, 31 | Processor: typeof AudioWorkletProcessor, 32 | ): void { 33 | let contextProcessors = workletProcessors.get(audioContext); 34 | if (!contextProcessors) { 35 | contextProcessors = new Map(); 36 | workletProcessors.set(audioContext, contextProcessors); 37 | } 38 | contextProcessors.set(name, Processor); 39 | } 40 | 41 | export default class AudioWorklet { 42 | constructor(private readonly audioContext: BaseAudioContext) { 43 | // NOP 44 | } 45 | 46 | async addModule(_url: string, options?: AudioWorkletOptions): Promise { 47 | if (options?.processorCtor && options?.name) { 48 | registerProcessor(this.audioContext, options.name, options.processorCtor); 49 | return null; 50 | } 51 | 52 | throw new Error( 53 | 'Cannot load audio worklet via url in web-audio-js. Pass name and processorCtr in options', 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/impl/AudioWorkletPort.ts: -------------------------------------------------------------------------------- 1 | import type { MessagePort } from 'worker_threads'; 2 | 3 | let MC: (new () => { port1: MessagePort; port2: MessagePort }) | undefined; 4 | 5 | export function makeMessageChannel() { 6 | if (!MC) { 7 | MC = 8 | // eslint-disable-next-line @typescript-eslint/ban-ts-ignore 9 | // @ts-ignore 10 | typeof MessageChannel === 'function' 11 | ? // eslint-disable-next-line @typescript-eslint/ban-ts-ignore 12 | // @ts-ignore 13 | MessageChannel 14 | : require('worker_threads')?.MessageChannel; 15 | } 16 | if (!MC) { 17 | throw new Error('cannot create message channel'); 18 | } 19 | return new MC(); 20 | } 21 | 22 | let nextPort: MessagePort | undefined; 23 | 24 | export function setNextPort(port: MessagePort | undefined): void { 25 | nextPort = port; 26 | } 27 | 28 | export function getNextPort(): MessagePort { 29 | if (!nextPort) { 30 | throw new Error('no port available for AudioWorkletProcessor'); 31 | } 32 | return nextPort; 33 | } 34 | -------------------------------------------------------------------------------- /src/impl/AudioWorkletProcessor.ts: -------------------------------------------------------------------------------- 1 | import type { MessagePort } from 'worker_threads'; 2 | import { getNextPort } from './AudioWorkletPort'; 3 | import { AudioWorkletOptions } from './AudioWorklet'; 4 | 5 | export interface AudioParamDescriptor { 6 | name: string; 7 | automationRate?: 'a-rate' | 'k-rate'; 8 | minValue?: number; 9 | maxValue?: number; 10 | defaultValue?: number; 11 | } 12 | 13 | export default class AudioWorkletProcessor { 14 | port: MessagePort; 15 | 16 | sampleRate = 0; 17 | currentTime = 0; 18 | 19 | constructor(_options: Partial) { 20 | this.port = getNextPort(); 21 | } 22 | 23 | process( 24 | _input: Float32Array[][], 25 | _output: Float32Array[][], 26 | _parameters: Record, 27 | ): void { 28 | // subclass should implement 29 | } 30 | 31 | static parameterDescriptors: AudioParamDescriptor[]; 32 | } 33 | -------------------------------------------------------------------------------- /src/impl/ChannelMergerNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioNode from './AudioNode'; 4 | import ChannelMergerNodeDSP from './dsp/ChannelMergerNode'; 5 | import { defaults, fill, toValidNumberOfChannels } from '../utils'; 6 | 7 | import { EXPLICIT } from '../constants/ChannelCountMode'; 8 | 9 | const DEFAULT_NUMBER_OF_INPUTS = 6; 10 | 11 | class ChannelMergerNode extends AudioNode { 12 | /** 13 | * @param {AudioContext} context 14 | * @param {object} opts 15 | * @param {number} opts.numberOfInputs 16 | */ 17 | constructor(context, opts = {}) { 18 | let numberOfInputs = defaults( 19 | opts.numberOfInputs, 20 | DEFAULT_NUMBER_OF_INPUTS, 21 | ); 22 | 23 | numberOfInputs = toValidNumberOfChannels(numberOfInputs); 24 | 25 | super(context, opts, { 26 | inputs: fill(new Array(numberOfInputs), 1), 27 | outputs: [numberOfInputs], 28 | channelCount: 1, 29 | channelCountMode: EXPLICIT, 30 | allowedMaxChannelCount: 1, 31 | allowedChannelCountMode: [EXPLICIT], 32 | }); 33 | } 34 | 35 | disableOutputsIfNecessary() { 36 | // disable if all inputs are disabled 37 | 38 | /* istanbul ignore else */ 39 | if (this.isEnabled()) { 40 | const inputs = this.inputs; 41 | 42 | for (let i = 0, imax = inputs.length; i < imax; i++) { 43 | if (inputs[i].isEnabled()) { 44 | return; 45 | } 46 | } 47 | 48 | super.disableOutputsIfNecessary(); 49 | } 50 | } 51 | } 52 | 53 | Object.assign(ChannelMergerNode.prototype, ChannelMergerNodeDSP); 54 | 55 | export default ChannelMergerNode; 56 | -------------------------------------------------------------------------------- /src/impl/ChannelSplitterNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioNode from './AudioNode'; 4 | import ChannelSplitterNodeDSP from './dsp/ChannelSplitterNode'; 5 | import { defaults, fill, toValidNumberOfChannels } from '../utils'; 6 | 7 | import { MAX } from '../constants/ChannelCountMode'; 8 | 9 | const DEFAULT_NUMBER_OF_OUTPUTS = 6; 10 | 11 | class ChannelSplitterNode extends AudioNode { 12 | /** 13 | * @param {AudioContext} context 14 | * @param {object} opts 15 | * @param {number} opts.numberOfOutputs 16 | */ 17 | constructor(context, opts = {}) { 18 | let numberOfOutputs = defaults( 19 | opts.numberOfOutputs, 20 | DEFAULT_NUMBER_OF_OUTPUTS, 21 | ); 22 | 23 | numberOfOutputs = toValidNumberOfChannels(numberOfOutputs); 24 | 25 | super(context, opts, { 26 | inputs: [1], 27 | outputs: fill(new Array(numberOfOutputs), 1), 28 | channelCount: 2, 29 | channelCountMode: MAX, 30 | }); 31 | } 32 | } 33 | 34 | Object.assign(ChannelSplitterNode.prototype, ChannelSplitterNodeDSP); 35 | 36 | export default ChannelSplitterNode; 37 | -------------------------------------------------------------------------------- /src/impl/ConstantSourceNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioScheduledSourceNode from './AudioScheduledSourceNode'; 4 | import ConstantSourceNodeDSP from './dsp/ConstantSourceNode'; 5 | import { defaults } from '../utils'; 6 | 7 | import { MAX } from '../constants/ChannelCountMode'; 8 | 9 | import { AUDIO_RATE } from '../constants/AudioParamRate'; 10 | 11 | const DEFAULT_OFFSET = 1; 12 | 13 | class ConstantSourceNode extends AudioScheduledSourceNode { 14 | /** 15 | * @param {AudioContext} context 16 | * @param {object} opts 17 | * @param {number} opts.offset 18 | */ 19 | constructor(context, opts = {}) { 20 | const offset = defaults(opts.offset, DEFAULT_OFFSET); 21 | 22 | super(context, opts, { 23 | inputs: [1], 24 | outputs: [1], 25 | channelCount: 2, 26 | channelCountMode: MAX, 27 | }); 28 | 29 | this._offset = this.addParam(AUDIO_RATE, offset); 30 | } 31 | 32 | /** 33 | * @return {AudioParam} 34 | */ 35 | getOffset() { 36 | return this._offset; 37 | } 38 | } 39 | 40 | Object.assign(ConstantSourceNode.prototype, ConstantSourceNodeDSP); 41 | 42 | export default ConstantSourceNode; 43 | -------------------------------------------------------------------------------- /src/impl/ConvolverNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioNode from './AudioNode'; 4 | import AudioBuffer from './AudioBuffer'; 5 | import ConvolverNodeDSP from './dsp/ConvolverNode'; 6 | import { defaults, toImpl } from '../utils'; 7 | 8 | import { CLAMPED_MAX, EXPLICIT } from '../constants/ChannelCountMode'; 9 | 10 | const DEFAULT_DISABLE_NORMALIZATION = false; 11 | 12 | class ConvolverNode extends AudioNode { 13 | /** 14 | * @param {AudioContext} context 15 | * @param {object} opts 16 | * @param {boolean} opts.disableNormalization 17 | */ 18 | constructor(context, opts = {}) { 19 | const disableNormalization = defaults( 20 | opts.disableNormalization, 21 | DEFAULT_DISABLE_NORMALIZATION, 22 | ); 23 | 24 | super(context, opts, { 25 | inputs: [1], 26 | outputs: [1], 27 | channelCount: 2, 28 | channelCountMode: CLAMPED_MAX, 29 | allowedMaxChannelCount: 2, 30 | allowedChannelCountMode: [CLAMPED_MAX, EXPLICIT], 31 | }); 32 | 33 | this._buffer = null; 34 | this._audioData = null; 35 | this._normalize = !disableNormalization; 36 | } 37 | 38 | /** 39 | * @return {AudioBuffer} 40 | */ 41 | getBuffer() { 42 | return this._buffer; 43 | } 44 | 45 | /** 46 | * @param {AudioBuffer} value 47 | */ 48 | setBuffer(value) { 49 | value = toImpl(value); 50 | 51 | /* istanbul ignore else */ 52 | if (value instanceof AudioBuffer) { 53 | this._buffer = value; 54 | this._audioData = this._buffer.audioData; 55 | } 56 | } 57 | 58 | /** 59 | * @return {boolean} 60 | */ 61 | getNormalize() { 62 | return this._normalize; 63 | } 64 | 65 | /** 66 | * @param {boolean} value 67 | */ 68 | setNormalize(value) { 69 | this._normalize = !!value; 70 | } 71 | 72 | /** 73 | * @param {number} numberOfChannels 74 | */ 75 | channelDidUpdate(numberOfChannels) { 76 | numberOfChannels = Math.min(numberOfChannels, 2); 77 | 78 | this.outputs[0].setNumberOfChannels(numberOfChannels); 79 | } 80 | } 81 | 82 | Object.assign(ConvolverNode.prototype, ConvolverNodeDSP); 83 | 84 | export default ConvolverNode; 85 | -------------------------------------------------------------------------------- /src/impl/DelayNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioNode from './AudioNode'; 4 | import DelayNodeDSP from './dsp/DelayNode'; 5 | import { defaults, toNumber } from '../utils'; 6 | 7 | import { MAX } from '../constants/ChannelCountMode'; 8 | 9 | import { AUDIO_RATE } from '../constants/AudioParamRate'; 10 | 11 | const DEFAULT_MAX_DELAY_TIME = 1; 12 | const DEFAULT_DELAY_TIME = 0; 13 | 14 | class DelayNode extends AudioNode { 15 | /** 16 | * @param {AudioContext} context 17 | * @param {object} opts 18 | * @param {number} opts.maxDelayTime 19 | * @param {number} opts.delayTime 20 | */ 21 | constructor(context, opts = {}) { 22 | let maxDelayTime = defaults(opts.maxDelayTime, DEFAULT_MAX_DELAY_TIME); 23 | let delayTime = defaults(opts.delayTime, DEFAULT_DELAY_TIME); 24 | 25 | maxDelayTime = Math.max(0, toNumber(maxDelayTime)); 26 | delayTime = Math.min(delayTime, maxDelayTime); 27 | 28 | super(context, opts, { 29 | inputs: [1], 30 | outputs: [1], 31 | channelCount: 2, 32 | channelCountMode: MAX, 33 | }); 34 | 35 | this._maxDelayTime = maxDelayTime; 36 | this._delayTime = this.addParam(AUDIO_RATE, delayTime); 37 | 38 | this.dspInit(this._maxDelayTime); 39 | this.dspUpdateKernel(1); 40 | } 41 | 42 | /** 43 | * @return {number} 44 | */ 45 | getDelayTime() { 46 | return this._delayTime; 47 | } 48 | 49 | /** 50 | * @return {number} 51 | */ 52 | getMaxDelayTime() { 53 | return this._maxDelayTime; 54 | } 55 | 56 | /** 57 | * @param {number} numberOfChannels 58 | */ 59 | channelDidUpdate(numberOfChannels) { 60 | this.dspUpdateKernel(numberOfChannels); 61 | this.outputs[0].setNumberOfChannels(numberOfChannels); 62 | } 63 | 64 | /** 65 | * @return {number} 66 | */ 67 | getTailTime() { 68 | return this._maxDelayTime; 69 | } 70 | } 71 | 72 | Object.assign(DelayNode.prototype, DelayNodeDSP); 73 | 74 | export default DelayNode; 75 | -------------------------------------------------------------------------------- /src/impl/EventTarget.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import events from 'events'; 4 | 5 | class EventTarget { 6 | constructor() { 7 | this._emitter = new events.EventEmitter(); 8 | } 9 | 10 | /** 11 | * @param {string} type 12 | * @param {function} listener 13 | */ 14 | addEventListener(type, listener) { 15 | /* istanbul ignore else */ 16 | if (typeof listener === 'function') { 17 | this._emitter.addListener(type, listener); 18 | } 19 | } 20 | 21 | /** 22 | * @param {string} type 23 | * @param {function} listener 24 | */ 25 | removeEventListener(type, listener) { 26 | /* istanbul ignore else */ 27 | if (typeof listener === 'function') { 28 | this._emitter.removeListener(type, listener); 29 | } 30 | } 31 | 32 | /** 33 | * @param {string} type 34 | * @param {function} oldListener 35 | * @param {function} newListener 36 | */ 37 | replaceEventListener(type, oldListener, newListener) { 38 | this.removeEventListener(type, oldListener); 39 | this.addEventListener(type, newListener); 40 | } 41 | 42 | /** 43 | * @param {object} event 44 | * @param {string} event.type 45 | */ 46 | dispatchEvent(event) { 47 | this._emitter.emit(event.type, event); 48 | } 49 | } 50 | 51 | export default EventTarget; 52 | -------------------------------------------------------------------------------- /src/impl/GainNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioNode from './AudioNode'; 4 | import GainNodeDSP from './dsp/GainNode'; 5 | import { defaults } from '../utils'; 6 | 7 | import { MAX } from '../constants/ChannelCountMode'; 8 | 9 | import { AUDIO_RATE } from '../constants/AudioParamRate'; 10 | 11 | const DEFAULT_GAIN = 1; 12 | 13 | class GainNode extends AudioNode { 14 | /** 15 | * @param {AudioContext} context 16 | * @param {object} opts 17 | * @param {number} opts.gain 18 | */ 19 | constructor(context, opts = {}) { 20 | const gain = defaults(opts.gain, DEFAULT_GAIN); 21 | 22 | super(context, opts, { 23 | inputs: [1], 24 | outputs: [1], 25 | channelCount: 2, 26 | channelCountMode: MAX, 27 | }); 28 | 29 | this._gain = this.addParam(AUDIO_RATE, gain); 30 | } 31 | 32 | /** 33 | * @return {AudioParam} 34 | */ 35 | getGain() { 36 | return this._gain; 37 | } 38 | 39 | /** 40 | * @param {number} numberOfChannels 41 | */ 42 | channelDidUpdate(numberOfChannels) { 43 | this.outputs[0].setNumberOfChannels(numberOfChannels); 44 | } 45 | } 46 | 47 | Object.assign(GainNode.prototype, GainNodeDSP); 48 | 49 | export default GainNode; 50 | -------------------------------------------------------------------------------- /src/impl/IIRFilterNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioNode from './AudioNode'; 4 | import IIRFilterNodeDSP from './dsp/IIRFilterNode'; 5 | import { defaults } from '../utils'; 6 | 7 | import { MAX } from '../constants/ChannelCountMode'; 8 | 9 | class IIRFilterNode extends AudioNode { 10 | /** 11 | * @param {AudioContext} context 12 | * @param {object} opts 13 | * @param {Float32Array} opts.feedforward 14 | * @param {Float32Array} opts.feedback 15 | */ 16 | constructor(context, opts = {}) { 17 | const feedforward = defaults(opts.feedforward, [0]); 18 | const feedback = defaults(opts.feedback, [1]); 19 | 20 | super(context, opts, { 21 | inputs: [1], 22 | outputs: [1], 23 | channelCount: 2, 24 | channelCountMode: MAX, 25 | }); 26 | 27 | this._feedforward = feedforward; 28 | this._feedback = feedback; 29 | 30 | this.dspInit(); 31 | this.dspUpdateKernel(1); 32 | } 33 | 34 | /** 35 | * @param {Float32Array} frequencyHz 36 | * @param {Float32Array} magResponse 37 | * @param {Float32Array} phaseResponse 38 | */ 39 | getFrequencyResponse(frequencyHz, magResponse, phaseResponse) { 40 | this.dspGetFrequencyResponse(frequencyHz, magResponse, phaseResponse); 41 | } 42 | 43 | /** 44 | * @return {Float32Array} 45 | */ 46 | getFeedforward() { 47 | return this._feedforward; 48 | } 49 | 50 | /** 51 | * @return {Float32Array} 52 | */ 53 | getFeedback() { 54 | return this._feedback; 55 | } 56 | 57 | /** 58 | * @param {number} numberOfChannels 59 | */ 60 | channelDidUpdate(numberOfChannels) { 61 | this.dspUpdateKernel(numberOfChannels); 62 | this.outputs[0].setNumberOfChannels(numberOfChannels); 63 | } 64 | } 65 | 66 | Object.assign(IIRFilterNode.prototype, IIRFilterNodeDSP); 67 | 68 | export default IIRFilterNode; 69 | -------------------------------------------------------------------------------- /src/impl/PannerNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import BasePannerNode from './BasePannerNode'; 4 | import PannerNodeDSP from './dsp/PannerNode'; 5 | 6 | class PannerNode extends BasePannerNode { 7 | /** 8 | * @param {AudioContext} context 9 | */ 10 | constructor(context, opts) { 11 | super(context, opts); 12 | } 13 | 14 | /** 15 | * @param {number} x 16 | * @param {number} y 17 | * @param {number} z 18 | */ 19 | /* istanbul ignore next */ 20 | setPosition() { 21 | throw new TypeError('NOT YET IMPLEMENTED'); 22 | } 23 | 24 | /** 25 | * @param {number} x 26 | * @param {number} y 27 | * @param {number} z 28 | */ 29 | /* istanbul ignore next */ 30 | setOrientation() { 31 | throw new TypeError('NOT YET IMPLEMENTED'); 32 | } 33 | 34 | /** 35 | * @param {number} x 36 | * @param {number} y 37 | * @param {number} z 38 | */ 39 | /* istanbul ignore next */ 40 | setVelocity() { 41 | throw new TypeError('NOT YET IMPLEMENTED'); 42 | } 43 | } 44 | 45 | Object.assign(PannerNode.prototype, PannerNodeDSP); 46 | 47 | export default PannerNode; 48 | -------------------------------------------------------------------------------- /src/impl/SpatialPannerNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import BasePannerNode from './BasePannerNode'; 4 | import SpatialPannerNodeDSP from './dsp/SpatialPannerNode'; 5 | import { AUDIO_RATE } from '../constants/AudioParamRate'; 6 | 7 | class SpatialPannerNode extends BasePannerNode { 8 | /** 9 | * @param {AudioContext} 10 | */ 11 | constructor(context, opts) { 12 | super(context, opts); 13 | 14 | this._positionX = this.addParam(AUDIO_RATE, 0); 15 | this._positionY = this.addParam(AUDIO_RATE, 0); 16 | this._positionZ = this.addParam(AUDIO_RATE, 0); 17 | this._orientationX = this.addParam(AUDIO_RATE, 0); 18 | this._orientationY = this.addParam(AUDIO_RATE, 0); 19 | this._orientationZ = this.addParam(AUDIO_RATE, 0); 20 | } 21 | 22 | /** 23 | * @param {AudioParam} 24 | */ 25 | getPositionX() { 26 | return this._positionX; 27 | } 28 | 29 | /** 30 | * @param {AudioParam} 31 | */ 32 | getPositionY() { 33 | return this._positionY; 34 | } 35 | 36 | /** 37 | * @param {AudioParam} 38 | */ 39 | getPositionZ() { 40 | return this._positionZ; 41 | } 42 | 43 | /** 44 | * @param {AudioParam} 45 | */ 46 | getOrientationX() { 47 | return this._positionX; 48 | } 49 | 50 | /** 51 | * @param {AudioParam} 52 | */ 53 | getOrientationY() { 54 | return this._positionY; 55 | } 56 | 57 | /** 58 | * @param {AudioParam} 59 | */ 60 | getOrientationZ() { 61 | return this._positionZ; 62 | } 63 | } 64 | 65 | Object.assign(SpatialPannerNode.prototype, SpatialPannerNodeDSP); 66 | 67 | export default SpatialPannerNode; 68 | -------------------------------------------------------------------------------- /src/impl/StereoPannerNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import BasePannerNode from './BasePannerNode'; 4 | import StereoPannerNodeDSP from './dsp/StereoPannerNode'; 5 | import { defaults } from '../utils'; 6 | 7 | import { AUDIO_RATE } from '../constants/AudioParamRate'; 8 | 9 | const DEFAULT_PAN = 0; 10 | 11 | class StereoPannerNode extends BasePannerNode { 12 | /** 13 | * @param {AudioContext} context 14 | * @param {object} opts 15 | * @param {number} opts.pan 16 | */ 17 | constructor(context, opts = {}) { 18 | const pan = defaults(opts.pan, DEFAULT_PAN); 19 | 20 | super(context, opts); 21 | 22 | this._pan = this.addParam(AUDIO_RATE, pan); 23 | } 24 | 25 | /** 26 | * @param {AudioParam} 27 | */ 28 | getPan() { 29 | return this._pan; 30 | } 31 | } 32 | 33 | Object.assign(StereoPannerNode.prototype, StereoPannerNodeDSP); 34 | 35 | export default StereoPannerNode; 36 | -------------------------------------------------------------------------------- /src/impl/WaveShaperNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import AudioNode from './AudioNode'; 4 | import WaveShaperNodeDSP from './dsp/WaveShaperNode'; 5 | import { defaults } from '../utils'; 6 | 7 | import { MAX } from '../constants/ChannelCountMode'; 8 | 9 | const OverSampleTypes = ['none', '2x', '4x']; 10 | 11 | const DEFAULT_CURVE = null; 12 | const DEFAULT_OVERSAMPLE = 'none'; 13 | 14 | class WaveShaperNode extends AudioNode { 15 | /** 16 | * @param {AudioContext} context 17 | * @param {object} opts 18 | * @param {Float32Arrat} opts.curve 19 | * @param {string} opts.overSample 20 | */ 21 | constructor(context, opts = {}) { 22 | const curve = defaults(opts.curve, DEFAULT_CURVE); 23 | const overSample = defaults(opts.overSample, DEFAULT_OVERSAMPLE); 24 | 25 | super(context, opts, { 26 | inputs: [1], 27 | outputs: [1], 28 | channelCount: 2, 29 | channelCountMode: MAX, 30 | }); 31 | 32 | this._curve = curve; 33 | this._overSample = overSample; 34 | 35 | this.dspInit(); 36 | this.dspUpdateKernel(null, 1); 37 | } 38 | 39 | /** 40 | * @return {Float32Array} 41 | */ 42 | getCurve() { 43 | return this._curve; 44 | } 45 | 46 | /** 47 | * @param {Float32Array} value 48 | */ 49 | setCurve(value) { 50 | /* istanbul ignore else */ 51 | if (value === null || value instanceof Float32Array) { 52 | this._curve = value; 53 | this.dspUpdateKernel(this._curve, this.outputs[0].getNumberOfChannels()); 54 | } 55 | } 56 | 57 | /** 58 | * @return {boolean} 59 | */ 60 | getOversample() { 61 | return this._overSample; 62 | } 63 | 64 | /** 65 | * @param {boolean} value 66 | */ 67 | setOversample(value) { 68 | /* istanbul ignore else */ 69 | if (OverSampleTypes.indexOf(value) !== -1) { 70 | this._overSample = value; 71 | } 72 | } 73 | 74 | /** 75 | * @param {number} numberOfChannels 76 | */ 77 | channelDidUpdate(numberOfChannels) { 78 | this.dspUpdateKernel(this._curve, numberOfChannels); 79 | this.outputs[0].setNumberOfChannels(numberOfChannels); 80 | } 81 | } 82 | 83 | Object.assign(WaveShaperNode.prototype, WaveShaperNodeDSP); 84 | 85 | export default WaveShaperNode; 86 | -------------------------------------------------------------------------------- /src/impl/core/AudioData.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { nmap } from '../../utils/nmap'; 4 | 5 | /** 6 | * AudioData is struct like AudioBuffer. 7 | * This instance has no methods. 8 | * The channel data of this instance are taken via property accessor. 9 | * @prop {number} numberOfChannels 10 | * @prop {number} length 11 | * @prop {number} sampleRate 12 | * @prop {Float32Array[]} channelData 13 | */ 14 | class AudioData { 15 | /** 16 | * @param {number} numberOfChannels 17 | * @param {number} length 18 | * @param {number} sampleRate 19 | * @param {Float32Array[]} [channelData] 20 | */ 21 | constructor(numberOfChannels, length, sampleRate, channelData) { 22 | this.numberOfChannels = numberOfChannels | 0; 23 | this.length = length | 0; 24 | this.sampleRate = sampleRate | 0; 25 | this.channelData = 26 | channelData || 27 | nmap(this.numberOfChannels, () => new Float32Array(this.length)); 28 | } 29 | } 30 | 31 | export default AudioData; 32 | -------------------------------------------------------------------------------- /src/impl/dsp/ChannelMergerNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ChannelMergerNodeDSP = { 4 | dspProcess() { 5 | const outputBus = this.outputs[0].bus; 6 | const inputBuses = this.inputs.map((input) => input.bus); 7 | const allSilent = inputBuses.every((inputBus) => inputBus.isSilent); 8 | 9 | outputBus.zeros(); 10 | 11 | if (!allSilent) { 12 | const outputChannelData = outputBus.getMutableData(); 13 | 14 | for (let i = 0, imax = inputBuses.length; i < imax; i++) { 15 | outputChannelData[i].set(inputBuses[i].getChannelData()[0]); 16 | } 17 | } 18 | }, 19 | }; 20 | 21 | export default ChannelMergerNodeDSP; 22 | -------------------------------------------------------------------------------- /src/impl/dsp/ChannelSplitterNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ChannelSplitterNodeDSP = { 4 | dspProcess() { 5 | const inputBus = this.inputs[0].bus; 6 | const outputs = this.outputs; 7 | 8 | if (inputBus.isSilent) { 9 | for (let i = 0, imax = outputs.length; i < imax; i++) { 10 | outputs[i].bus.zeros(); 11 | } 12 | } else { 13 | const inputChannelData = inputBus.getChannelData(); 14 | 15 | for (let i = 0, imax = outputs.length; i < imax; i++) { 16 | const outputBus = outputs[i].bus; 17 | 18 | if (inputChannelData[i]) { 19 | outputBus.getMutableData()[0].set(inputChannelData[i]); 20 | } else { 21 | outputBus.zeros(); 22 | } 23 | } 24 | } 25 | }, 26 | }; 27 | 28 | export default ChannelSplitterNodeDSP; 29 | -------------------------------------------------------------------------------- /src/impl/dsp/ConstantSourceNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { fill } from '../../utils'; 4 | 5 | const ConstantSourceNode = { 6 | dspInit() {}, 7 | 8 | dspProcess() { 9 | const offsetParam = this._offset; 10 | const outputBus = this.outputs[0].bus; 11 | const outputs = outputBus.getMutableData(); 12 | 13 | if (offsetParam.hasSampleAccurateValues()) { 14 | outputs[0].set(offsetParam.getSampleAccurateValues()); 15 | } else { 16 | fill(outputs[0], offsetParam.getValue()); 17 | } 18 | }, 19 | }; 20 | 21 | export default ConstantSourceNode; 22 | -------------------------------------------------------------------------------- /src/impl/dsp/ConvolverNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ConvolverNodeDSP = { 4 | dspProcess() { 5 | const outputBus = this.outputs[0].bus; 6 | 7 | outputBus.zeros(); 8 | outputBus.sumFrom(this.inputs[0].bus); 9 | }, 10 | }; 11 | 12 | export default ConvolverNodeDSP; 13 | -------------------------------------------------------------------------------- /src/impl/dsp/IIRFilterKernel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class IIRFilterKernel { 4 | constructor(feedforward, feedback) { 5 | this.a = toCoefficient(feedback, feedback[0]); 6 | this.b = toCoefficient(feedforward, feedback[0]); 7 | this.x = new Float32Array(this.b.length); 8 | this.y = new Float32Array(this.a.length); 9 | } 10 | 11 | process(input, output, inNumSamples) { 12 | const a = this.a; 13 | const b = this.b; 14 | const x = this.x; 15 | const y = this.y; 16 | const alen = this.a.length - 1; 17 | const blen = this.b.length; 18 | 19 | for (let i = 0; i < inNumSamples; i++) { 20 | x[blen - 1] = input[i]; 21 | y[alen] = 0; 22 | 23 | for (let j = 0; j < blen; j++) { 24 | y[alen] += b[j] * x[j]; 25 | x[j] = flushDenormalFloatToZero(x[j + 1]); 26 | } 27 | 28 | for (let j = 0; j < alen; j++) { 29 | y[alen] -= a[j] * y[j]; 30 | y[j] = flushDenormalFloatToZero(y[j + 1]); 31 | } 32 | 33 | output[i] = y[alen]; 34 | } 35 | } 36 | } 37 | 38 | function toCoefficient(filter, a0) { 39 | const coeff = new Float32Array(filter.length); 40 | 41 | for (let i = 0, imax = coeff.length; i < imax; i++) { 42 | coeff[i] = filter[imax - i - 1] / a0; 43 | } 44 | 45 | return coeff; 46 | } 47 | 48 | function flushDenormalFloatToZero(f) { 49 | return Math.abs(f) < 1.175494e-38 ? 0.0 : f; 50 | } 51 | 52 | export default IIRFilterKernel; 53 | -------------------------------------------------------------------------------- /src/impl/dsp/PannerNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const PannerNodeDSP = { 4 | dspProcess() { 5 | const outputBus = this.outputs[0].bus; 6 | 7 | outputBus.zeros(); 8 | outputBus.sumFrom(this.inputs[0].bus); 9 | }, 10 | }; 11 | 12 | export default PannerNodeDSP; 13 | -------------------------------------------------------------------------------- /src/impl/dsp/PeriodicWave.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const WAVE_TABLE_LENGTH = 8192; 4 | 5 | const PeriodicWaveDSP = { 6 | dspInit() { 7 | this._waveTable = null; 8 | }, 9 | 10 | dspBuildWaveTable() { 11 | if (this._waveTable !== null) { 12 | return this._waveTable; 13 | } 14 | 15 | const waveTable = new Float32Array(WAVE_TABLE_LENGTH + 1); 16 | const real = this._real; 17 | const imag = this._imag; 18 | 19 | let maxAbsValue = 0; 20 | const periodicWaveLength = Math.min(real.length, 16); 21 | 22 | for (let i = 0; i < WAVE_TABLE_LENGTH; i++) { 23 | const x = (i / WAVE_TABLE_LENGTH) * Math.PI * 2; 24 | 25 | for (let n = 1; n < periodicWaveLength; n++) { 26 | waveTable[i] += real[n] * Math.cos(n * x) + imag[n] * Math.sin(n * x); 27 | } 28 | 29 | maxAbsValue = Math.max(maxAbsValue, Math.abs(waveTable[i])); 30 | } 31 | 32 | if (!this._constants && maxAbsValue !== 1) { 33 | for (let i = 0; i < WAVE_TABLE_LENGTH; i++) { 34 | waveTable[i] *= maxAbsValue; 35 | } 36 | } 37 | waveTable[WAVE_TABLE_LENGTH] = waveTable[0]; 38 | 39 | this._waveTable = waveTable; 40 | 41 | return waveTable; 42 | }, 43 | }; 44 | 45 | export default PeriodicWaveDSP; 46 | -------------------------------------------------------------------------------- /src/impl/dsp/SpatialPannerNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const SpatialPannerNodeDSP = { 4 | dspProcess() { 5 | const outputBus = this.outputs[0].bus; 6 | 7 | outputBus.zeros(); 8 | outputBus.sumFrom(this.inputs[0].bus); 9 | }, 10 | }; 11 | 12 | export default SpatialPannerNodeDSP; 13 | -------------------------------------------------------------------------------- /src/impl/index.js: -------------------------------------------------------------------------------- 1 | export { default as AnalyserNode } from './AnalyserNode'; 2 | export { default as AudioBuffer } from './AudioBuffer'; 3 | export { default as AudioBufferSourceNode } from './AudioBufferSourceNode'; 4 | export { default as AudioContext } from './AudioContext'; 5 | export { default as AudioDestinationNode } from './AudioDestinationNode'; 6 | export { default as AudioListener } from './AudioListener'; 7 | export { default as AudioNode } from './AudioNode'; 8 | export { default as AudioParam } from './AudioParam'; 9 | export { default as AudioWorklet } from './AudioWorklet'; 10 | export { default as AudioWorkletNode } from './AudioWorkletNode'; 11 | export { default as AudioWorkletProcessor } from './AudioWorkletProcessor'; 12 | export { default as BiquadFilterNode } from './BiquadFilterNode'; 13 | export { default as ChannelMergerNode } from './ChannelMergerNode'; 14 | export { default as ChannelSplitterNode } from './ChannelSplitterNode'; 15 | export { default as ConstantSourceNode } from './ConstantSourceNode'; 16 | export { default as ConvolverNode } from './ConvolverNode'; 17 | export { default as DelayNode } from './DelayNode'; 18 | export { default as DynamicsCompressorNode } from './DynamicsCompressorNode'; 19 | export { default as GainNode } from './GainNode'; 20 | export { default as IIRFilterNode } from './IIRFilterNode'; 21 | export { default as OscillatorNode } from './OscillatorNode'; 22 | export { default as PannerNode } from './PannerNode'; 23 | export { default as PeriodicWave } from './PeriodicWave'; 24 | export { default as ScriptProcessorNode } from './ScriptProcessorNode'; 25 | export { default as SpatialPannerNode } from './SpatialPannerNode'; 26 | export { default as StereoPannerNode } from './StereoPannerNode'; 27 | export { default as WaveShaperNode } from './WaveShaperNode'; 28 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export { default as OfflineAudioContext } from './context/OfflineAudioContext'; 4 | export { default as StreamAudioContext } from './context/StreamAudioContext'; 5 | export { default as RenderingAudioContext } from './context/RenderingAudioContext'; 6 | export { default as WebAudioContext } from './context/WebAudioContext'; 7 | export { RawDataAudioContext } from './context/RawDataAudioContext'; 8 | import * as api from './api'; 9 | import * as impl from './impl'; 10 | import * as decoder from './decoder'; 11 | import * as encoder from './encoder'; 12 | 13 | export { api, impl, decoder, encoder }; 14 | -------------------------------------------------------------------------------- /src/utils/DecoderUtils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { nmap } from '../utils/nmap'; 4 | import * as AudioDataUtils from './AudioDataUtils'; 5 | 6 | /** 7 | * @param {function} decodeFn 8 | * @param {ArrayBuffer} audioData 9 | * @param {object} opts 10 | * @return {Promise} 11 | */ 12 | function decode(decodeFn, audioData, /* istanbul ignore next */ opts = {}) { 13 | return new Promise((resolve, reject) => { 14 | return decodeFn(audioData, opts).then((result) => { 15 | if (AudioDataUtils.isAudioData(result)) { 16 | if (typeof opts.sampleRate === 'number') { 17 | result = resample(result, opts.sampleRate); 18 | } 19 | return resolve(result); 20 | } 21 | return reject(new TypeError('Failed to decode audio data')); 22 | }, reject); 23 | }); 24 | } 25 | 26 | /** 27 | * @param {AudioData} audioData 28 | * @param {number} sampleRate 29 | */ 30 | function resample(audioData, sampleRate) { 31 | if (audioData.sampleRate === sampleRate) { 32 | return audioData; 33 | } 34 | 35 | const rate = audioData.sampleRate / sampleRate; 36 | const numberOfChannels = audioData.channelData.length; 37 | const length = Math.round(audioData.channelData[0].length / rate); 38 | const channelData = nmap(numberOfChannels, () => new Float32Array(length)); 39 | 40 | for (let i = 0; i < length; i++) { 41 | const ix = i * rate; 42 | const i0 = ix | 0; 43 | const i1 = i0 + 1; 44 | const ia = ix % 1; 45 | 46 | for (let ch = 0; ch < numberOfChannels; ch++) { 47 | const v0 = audioData.channelData[ch][i0]; 48 | const v1 = audioData.channelData[ch][i1]; 49 | 50 | channelData[ch][i] = v0 + ia * (v1 - v0); 51 | } 52 | } 53 | 54 | return { numberOfChannels, length, sampleRate, channelData }; 55 | } 56 | 57 | export { decode, resample }; 58 | -------------------------------------------------------------------------------- /src/utils/Encoder.ts: -------------------------------------------------------------------------------- 1 | export type Encoder = { 2 | encode( 3 | channelData: Float32Array[], 4 | offset?: number, 5 | len?: number, 6 | ): ArrayBuffer; 7 | }; 8 | -------------------------------------------------------------------------------- /src/utils/EncoderUtils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as AudioDataUtils from './AudioDataUtils'; 4 | 5 | /** 6 | * @param {function} encodeFn 7 | * @param {AudioData} audioData 8 | * @param {object} opts 9 | */ 10 | function encode(encodeFn, audioData, /* istanbul ignore next */ opts = {}) { 11 | if (!AudioDataUtils.isAudioData(audioData)) { 12 | audioData = AudioDataUtils.toAudioData(audioData); 13 | } 14 | return encodeFn(audioData, opts); 15 | } 16 | 17 | export { encode }; 18 | -------------------------------------------------------------------------------- /src/utils/FilterUtils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function getFilterResponse( 4 | b, 5 | a, 6 | frequencyHz, 7 | magResponse, 8 | phaseResponse, 9 | sampleRate, 10 | ) { 11 | for (let i = 0, imax = frequencyHz.length; i < imax; i++) { 12 | const w0 = 2 * Math.PI * (frequencyHz[i] / sampleRate); 13 | const ca = compute(a, Math.cos, w0); 14 | const sa = compute(a, Math.sin, w0); 15 | const cb = compute(b, Math.cos, w0); 16 | const sb = compute(b, Math.sin, w0); 17 | 18 | magResponse[i] = Math.sqrt((cb * cb + sb * sb) / (ca * ca + sa * sa)); 19 | phaseResponse[i] = Math.atan2(sa, ca) - Math.atan2(sb, cb); 20 | } 21 | } 22 | 23 | function compute(values, fn, w0) { 24 | let result = 0; 25 | 26 | for (let i = 0, imax = values.length; i < imax; i++) { 27 | result += values[i] * fn(w0 * i); 28 | } 29 | 30 | return result; 31 | } 32 | 33 | export { getFilterResponse }; 34 | -------------------------------------------------------------------------------- /src/utils/Format.ts: -------------------------------------------------------------------------------- 1 | export type Format = { 2 | sampleRate: number; 3 | channels: number; 4 | bitDepth: number; 5 | float: boolean; 6 | }; 7 | -------------------------------------------------------------------------------- /src/utils/PCMArrayBufferWriter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class PCMArrayBufferWriter { 4 | constructor(buffer) { 5 | this._view = new DataView(buffer); 6 | this._pos = 0; 7 | } 8 | 9 | pcm8(value) { 10 | value = Math.max(-1, Math.min(value, +1)); 11 | value = (value * 0.5 + 0.5) * 128; 12 | this._view.setUint8(this._pos, value | 0); 13 | this._pos += 1; 14 | } 15 | 16 | pcm16(value) { 17 | value = Math.max(-1, Math.min(value, +1)); 18 | value = value < 0 ? value * 32768 : value * 32767; 19 | this._view.setInt16(this._pos, value | 0, true); 20 | this._pos += 2; 21 | } 22 | 23 | pcm32(value) { 24 | value = Math.max(-1, Math.min(value, +1)); 25 | value = value < 0 ? value * 2147483648 : value * 2147483647; 26 | this._view.setInt32(this._pos, value | 0, true); 27 | this._pos += 4; 28 | } 29 | 30 | pcm32f(value) { 31 | this._view.setFloat32(this._pos, value, true); 32 | this._pos += 4; 33 | } 34 | } 35 | 36 | export default PCMArrayBufferWriter; 37 | -------------------------------------------------------------------------------- /src/utils/PCMBufferWriter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class PCMBufferWriter { 4 | constructor(buffer) { 5 | this._buffer = buffer; 6 | this._pos = 0; 7 | } 8 | 9 | pcm8(value) { 10 | value = Math.max(-1, Math.min(value, +1)); 11 | value = (value * 0.5 + 0.5) * 128; 12 | this._buffer.writeUInt8(value | 0, this._pos); 13 | this._pos += 1; 14 | } 15 | 16 | pcm16(value) { 17 | value = Math.max(-1, Math.min(value, +1)); 18 | value = value < 0 ? value * 32768 : value * 32767; 19 | this._buffer.writeInt16LE(value | 0, this._pos); 20 | this._pos += 2; 21 | } 22 | 23 | pcm32(value) { 24 | value = Math.max(-1, Math.min(value, +1)); 25 | value = value < 0 ? value * 2147483648 : value * 2147483647; 26 | this._buffer.writeInt32LE(value | 0, this._pos); 27 | this._pos += 4; 28 | } 29 | 30 | pcm32f(value) { 31 | this._buffer.writeFloatLE(value, this._pos); 32 | this._pos += 4; 33 | } 34 | } 35 | 36 | export default PCMBufferWriter; 37 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export { default as clamp } from './utils/clamp'; 4 | export { default as defaults } from './utils/defaults'; 5 | export { default as defineProp } from './utils/defineProp'; 6 | export { default as fill } from './utils/fill'; 7 | export { default as fillRange } from './utils/fillRange'; 8 | export { default as normalize } from './utils/normalize'; 9 | export { default as toArrayIfNeeded } from './utils/toArrayIfNeeded'; 10 | export { default as toAudioTime } from './utils/toAudioTime'; 11 | export { default as toDecibel } from './utils/toDecibel'; 12 | export { default as toLinear } from './utils/toLinear'; 13 | export { default as flushDenormalFloatToZero } from './utils/flushDenormalFloatToZero'; 14 | export { default as toGain } from './utils/toGain'; 15 | export { default as toImpl } from './utils/toImpl'; 16 | export { default as toNumber } from './utils/toNumber'; 17 | export { default as toPowerOfTwo } from './utils/toPowerOfTwo'; 18 | export { default as toValidBitDepth } from './utils/toValidBitDepth'; 19 | export { default as toValidBlockSize } from './utils/toValidBlockSize'; 20 | export { default as toValidNumberOfChannels } from './utils/toValidNumberOfChannels'; 21 | export { default as toValidSampleRate } from './utils/toValidSampleRate'; 22 | -------------------------------------------------------------------------------- /src/utils/nmap.ts: -------------------------------------------------------------------------------- 1 | export function nmap( 2 | n: number, 3 | map: (i: number, i2: number, arr: T[]) => T, 4 | ) { 5 | const result = new Array(n); 6 | for (let i = 0; i < n; i++) { 7 | result[i] = map(i, i, result); 8 | } 9 | return result; 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/setImmediate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default global.setImmediate /* istanbul ignore next */ || 4 | ((fn) => setTimeout(fn, 0)); 5 | -------------------------------------------------------------------------------- /src/utils/utils/clamp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {number} value 5 | * @param {number} minValue 6 | * @param {number} maxValue 7 | */ 8 | function clamp(value, minValue, maxValue) { 9 | return Math.max(minValue, Math.min(value, maxValue)); 10 | } 11 | 12 | export default clamp; 13 | -------------------------------------------------------------------------------- /src/utils/utils/defaults.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {*} value 5 | * @param {*) defaultValue 6 | */ 7 | function defaults(value, defaultValue) { 8 | return typeof value !== 'undefined' ? value : defaultValue; 9 | } 10 | 11 | export default defaults; 12 | -------------------------------------------------------------------------------- /src/utils/utils/defineProp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {object} target 5 | * @param {string} name 6 | * @param {*} value 7 | */ 8 | function defineProp(target, name, value) { 9 | Object.defineProperty(target, name, { 10 | value: value, 11 | enumerable: false, 12 | writable: true, 13 | configurable: true, 14 | }); 15 | } 16 | 17 | export default defineProp; 18 | -------------------------------------------------------------------------------- /src/utils/utils/fill.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {number[]} list 5 | * @param {number} value 6 | * @return {number[]} 7 | */ 8 | function fill(list, value) { 9 | if (list.fill) { 10 | return list.fill(value); 11 | } 12 | 13 | for (let i = 0, imax = list.length; i < imax; i++) { 14 | list[i] = value; 15 | } 16 | 17 | return list; 18 | } 19 | 20 | export default fill; 21 | -------------------------------------------------------------------------------- /src/utils/utils/fillRange.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {number[]} list 5 | * @param {number} value 6 | * @param {number} start 7 | * @param {number} end 8 | * @return {number[]} 9 | */ 10 | function fillRange(list, value, start, end) { 11 | if (list.fill) { 12 | return list.fill(value, start, end); 13 | } 14 | 15 | for (let i = start; i < end; i++) { 16 | list[i] = value; 17 | } 18 | 19 | return list; 20 | } 21 | 22 | export default fillRange; 23 | -------------------------------------------------------------------------------- /src/utils/utils/flushDenormalFloatToZero.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {number} f 5 | * @returns {number} 6 | */ 7 | function flushDenormalFloatToZero(f) { 8 | return Math.abs(f) < 1.175494e-38 ? 0.0 : f; 9 | } 10 | 11 | export default flushDenormalFloatToZero; 12 | -------------------------------------------------------------------------------- /src/utils/utils/normalize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import clamp from './clamp'; 4 | 5 | /** 6 | * normalize - returns a number between 0 - 1 7 | * @param {number} value 8 | * @param {number} minValue 9 | * @param {number} maxValue 10 | */ 11 | function normalize(value, minValue, maxValue) { 12 | const val = (value - minValue) / (maxValue - minValue); 13 | return clamp(val, 0, 1); 14 | } 15 | 16 | export default normalize; 17 | -------------------------------------------------------------------------------- /src/utils/utils/toArrayIfNeeded.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {*} value 5 | * @return {Array} 6 | */ 7 | function toArrayIfNeeded(value) { 8 | return Array.isArray(value) ? value : [value]; 9 | } 10 | 11 | export default toArrayIfNeeded; 12 | -------------------------------------------------------------------------------- /src/utils/utils/toAudioTime.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {number|string} str 5 | * @return {number} 6 | */ 7 | function toAudioTime(str) { 8 | if (Number.isFinite(+str)) { 9 | return Math.max(0, +str); 10 | } 11 | 12 | const matched = ('' + str).match(/^(?:(\d\d+):)?(\d\d?):(\d\d?(?:\.\d+)?)$/); 13 | 14 | if (matched) { 15 | const hours = +matched[1] | 0; 16 | const minutes = +matched[2]; 17 | const seconds = +matched[3]; 18 | 19 | return hours * 3600 + minutes * 60 + seconds; 20 | } 21 | 22 | return 0; 23 | } 24 | 25 | export default toAudioTime; 26 | -------------------------------------------------------------------------------- /src/utils/utils/toDecibel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {*} value 5 | * @return {number} 6 | */ 7 | function toDecibel(value) { 8 | return 20 * Math.log10(value); 9 | } 10 | 11 | export default toDecibel; 12 | -------------------------------------------------------------------------------- /src/utils/utils/toGain.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {*} value 5 | * @return {number} 6 | */ 7 | function toGain(value) { 8 | return Math.sqrt(Math.pow(10, value / 10)); 9 | } 10 | 11 | export default toGain; 12 | -------------------------------------------------------------------------------- /src/utils/utils/toImpl.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {object} value 5 | * @return {object} 6 | */ 7 | function toImpl(value) { 8 | return value._impl || value; 9 | } 10 | 11 | export default toImpl; 12 | -------------------------------------------------------------------------------- /src/utils/utils/toLinear.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {*} decibels 5 | * @return {number} 6 | */ 7 | function toLinear(decibels) { 8 | return Math.pow(10, 0.05 * decibels); 9 | } 10 | 11 | export default toLinear; 12 | -------------------------------------------------------------------------------- /src/utils/utils/toNumber.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {*} value 5 | * @return {number} 6 | */ 7 | function toNumber(value) { 8 | return +value || 0; 9 | } 10 | 11 | export default toNumber; 12 | -------------------------------------------------------------------------------- /src/utils/utils/toPowerOfTwo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {number} value 5 | * @param {function} round 6 | * @return {number} 7 | */ 8 | function toPowerOfTwo(value, round) { 9 | round = round || Math.round; 10 | return 1 << round(Math.log(value) / Math.log(2)); 11 | } 12 | 13 | export default toPowerOfTwo; 14 | -------------------------------------------------------------------------------- /src/utils/utils/toValidBitDepth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {number} value 5 | * @return {number} 6 | */ 7 | function toValidBitDepth(value) { 8 | value = value | 0; 9 | if (value === 8 || value === 16 || value === 32) { 10 | return value; 11 | } 12 | return 16; 13 | } 14 | 15 | export default toValidBitDepth; 16 | -------------------------------------------------------------------------------- /src/utils/utils/toValidBlockSize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import clamp from './clamp'; 4 | import toPowerOfTwo from './toPowerOfTwo'; 5 | import { MAX_BLOCK_SIZE, MIN_BLOCK_SIZE } from '../../constants'; 6 | 7 | /** 8 | * @param {number} value 9 | * @return {number} 10 | */ 11 | function toValidBlockSize(value) { 12 | return clamp(toPowerOfTwo(value), MIN_BLOCK_SIZE, MAX_BLOCK_SIZE); 13 | } 14 | 15 | export default toValidBlockSize; 16 | -------------------------------------------------------------------------------- /src/utils/utils/toValidNumberOfChannels.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import toNumber from './toNumber'; 4 | import clamp from './clamp'; 5 | import { MAX_NUMBER_OF_CHANNELS } from '../../constants'; 6 | 7 | /** 8 | * @param {number} value 9 | * @return {number} 10 | */ 11 | function toValidNumberOfChannels(value) { 12 | return clamp(toNumber(value), 1, MAX_NUMBER_OF_CHANNELS) | 0; 13 | } 14 | 15 | export default toValidNumberOfChannels; 16 | -------------------------------------------------------------------------------- /src/utils/utils/toValidSampleRate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import toNumber from './toNumber'; 4 | import clamp from './clamp'; 5 | import { MAX_SAMPLERATE, MIN_SAMPLERATE } from '../../constants'; 6 | 7 | /** 8 | * @param {number} value 9 | * @return {number} 10 | */ 11 | function toValidSampleRate(value) { 12 | return clamp(toNumber(value), MIN_SAMPLERATE, MAX_SAMPLERATE) | 0; 13 | } 14 | 15 | export default toValidSampleRate; 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es2015", 5 | "moduleResolution": "node", 6 | "lib": ["es2015"], 7 | 8 | "rootDir": "src", 9 | "outDir": "lib", 10 | 11 | "strict": true, 12 | "alwaysStrict": true, 13 | "strictFunctionTypes": true, 14 | "strictNullChecks": true, 15 | "strictPropertyInitialization": true, 16 | "esModuleInterop": true, 17 | 18 | "allowJs": true, 19 | 20 | "forceConsistentCasingInFileNames": true, 21 | "noImplicitAny": true, 22 | "noImplicitReturns": true, 23 | "noImplicitThis": true, 24 | "noFallthroughCasesInSwitch": true, 25 | "noUnusedLocals": true, 26 | "noUnusedParameters": true, 27 | 28 | "declaration": true, 29 | 30 | "pretty": true 31 | }, 32 | "include": ["src/**/*"] 33 | } 34 | --------------------------------------------------------------------------------