├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── README.md ├── index.js ├── package.json └── src ├── AudioContext.js ├── index.js └── nodes ├── index.js ├── inputs ├── Oscillator.js ├── index.js └── shared │ ├── AudioNodeChain.js │ ├── AudioNodeChain.test.js │ └── AudioSource.js ├── outputs ├── Destination.js └── index.js ├── processors ├── Analyser.js ├── BiquadFilter.js ├── ConvolverNode.js ├── Delay.js ├── DynamicsCompressor.js ├── Gain.js ├── PeriodicWave.js ├── StereoPanner.js ├── WaveShaper.js ├── index.js └── shared │ └── AudioNode.js └── shared └── update.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015", "stage-0"] 3 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('linter') -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | dist 5 | lib 6 | yarn.lock -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | yarn.lock -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-audio 2 | 3 | This is a low-level React wrapper around the [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API). 4 | 5 | ## Usage 6 | ```javascript 7 | import { 8 | Oscillator, 9 | BiquadFilter, 10 | Delay, 11 | DynamicsCompressor, 12 | StereoPanner, 13 | Gain, 14 | WaveShaper, 15 | Analyser, 16 | Destination 17 | } from 'react-audio' 18 | 19 | // in a React render() 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ``` 33 | The components set the AudioNode properties via props. See the Mozilla documentation for details. 34 | 35 | The default props are set to the Web Audio API defaults, with the exception of WaveShaper, which provides the example curve shown in the Mozilla documentation. 36 | 37 | ## Analyser 38 | The Analyser provides exposes thinly wrapped ``getFloatFrequencyData``, ``getByteFrequencyData``, ``getFloatTimeDomainData``, and ``getByteTimeDomainData`` from the Web Audio API AnalyserNode as props to its children. These functions return the data in the proper array types as follows: 39 | 40 | * getFloatFrequencyData: Float32Array 41 | * getByteFrequencyData: Uint8Array 42 | * getFloatTimeDomainData: Float32Array 43 | * getByteTimeDomainData: Uint8Array 44 | 45 | ``frequencyBinCount`` is also exposed from the AnalyserNode via props. 46 | 47 | ## Roadmap 48 | 49 | * Support dynamic AudioNode ordering - currently AudioNodes are pushed to the end of the AudioNode chain when added. This works as expected with a static AudioNode chain, but may give unexpected results when changed at runtime. 50 | * Support more of the Web Audio API. Right now only oscillators, filters, the analyser node, and a simple output are supported. 51 | * ConvolverNode is not currently supported. -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib') -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-audio", 3 | "version": "0.0.1", 4 | "description": "HTML 5 Audio API in React", 5 | "main": "index.js", 6 | "files": [ 7 | "lib", 8 | "src" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/chrbala/react-audio.git" 13 | }, 14 | "scripts": { 15 | "build": "rm -rf lib && babel src --out-dir lib", 16 | "upload": "npm run build && npm publish", 17 | "test": "tape -r babel-register ./src/**/*.test.js", 18 | "lint": "eslint src" 19 | }, 20 | "keywords": [ 21 | "audio", 22 | "html5", 23 | "react" 24 | ], 25 | "author": "Chris Bala", 26 | "license": "ISC", 27 | "devDependencies": { 28 | "babel-cli": "6.16.0", 29 | "babel-core": "6.16.0", 30 | "babel-preset-es2015": "6.16.0", 31 | "babel-preset-react": "6.16.0", 32 | "babel-preset-stage-0": "6.16.0", 33 | "eslint": "1.10.3", 34 | "linter": "git+https://github.com/chrbala/linter.git#332fcad", 35 | "tape": "4.4.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/AudioContext.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | var Context = window.AudioContext || window.webkitAudioContext 4 | 5 | export default class AudioContextComponent extends Component { 6 | componentWillMount() { 7 | if (this.props.audioContext) 8 | return 9 | 10 | if (Context) 11 | this.audioContext = new Context() 12 | else { 13 | console.error('AudioContext not supported in this browser') 14 | this.audioContext = {} 15 | } 16 | } 17 | 18 | componentWillUnmount() { 19 | if (this.audioContext) 20 | this.audioContext.close() 21 | } 22 | 23 | getChildContext() { 24 | return { audioContext: this.props.audioContext || this.audioContext } 25 | } 26 | 27 | render() { 28 | return
{this.props.children}
29 | } 30 | } 31 | 32 | AudioContextComponent.childContextTypes = { 33 | audioContext: React.PropTypes.any.isRequired 34 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import AudioContextComponent from './AudioContext' 2 | import nodes from './nodes' 3 | 4 | module.exports = { 5 | AudioContextComponent, 6 | ...nodes 7 | } -------------------------------------------------------------------------------- /src/nodes/index.js: -------------------------------------------------------------------------------- 1 | import inputs from './inputs' 2 | import processors from './processors' 3 | import outputs from './outputs' 4 | 5 | const nodes = { 6 | ...inputs, 7 | ...processors, 8 | ...outputs 9 | } 10 | 11 | export default nodes -------------------------------------------------------------------------------- /src/nodes/inputs/Oscillator.js: -------------------------------------------------------------------------------- 1 | import AudioSource from './shared/AudioSource' 2 | 3 | export default class Oscillator extends AudioSource { 4 | componentWillMount() { 5 | var { audioContext } = this.context 6 | if (audioContext.createOscillator) 7 | this.node = audioContext.createOscillator() 8 | else 9 | console.error('Oscillator not supported in this browser') 10 | 11 | super.componentWillMount() 12 | } 13 | 14 | componentDidMount() { 15 | if (this.node) 16 | this.node.start() 17 | } 18 | } 19 | 20 | module.exports.defaultProps = { 21 | frequency: 440, 22 | detune: 0, 23 | type: 'sine' 24 | } -------------------------------------------------------------------------------- /src/nodes/inputs/index.js: -------------------------------------------------------------------------------- 1 | import Oscillator from './Oscillator' 2 | 3 | export default { 4 | Oscillator 5 | } -------------------------------------------------------------------------------- /src/nodes/inputs/shared/AudioNodeChain.js: -------------------------------------------------------------------------------- 1 | export default class AudioNodeChain { 2 | constructor() { 3 | this.chain = [] 4 | this.hasSource = false 5 | this.hasDestination = false 6 | } 7 | 8 | _nodeExists(node, method) { 9 | if (!node) 10 | throw new Error(`Nodes must be provided to ${method}`) 11 | } 12 | 13 | source() { 14 | if (!this.hasSource) 15 | return null 16 | 17 | return this.chain[0] 18 | } 19 | 20 | destination() { 21 | if (!this.hasDestination) 22 | return null 23 | 24 | return this.chain[this.chain.length] 25 | } 26 | 27 | setSource(node) { 28 | this._nodeExists(node, 'setSource') 29 | 30 | if (this.hasSource) 31 | throw new Error('Only one source allowed per AudioNodeChain') 32 | 33 | this.unshift(node) 34 | this.hasSource = true 35 | } 36 | 37 | setDestination(node) { 38 | this._nodeExists(node, 'setDestination') 39 | 40 | if (this.hasDestination) 41 | throw new Error('Only one destination allowed per AudioNodeChain') 42 | 43 | this.push(node) 44 | this.hasSource = true 45 | } 46 | 47 | first() { 48 | var index 49 | 50 | if (this.hasSource) 51 | index = 1 52 | index = 0 53 | 54 | return this.chain[index] 55 | } 56 | 57 | last() { 58 | var index 59 | 60 | if (this.hasDestination) 61 | index = this.chain.length - 2 62 | index = this.chain.length - 1 63 | 64 | return this.chain[index] 65 | } 66 | 67 | push(node) { 68 | this._nodeExists(node, 'push') 69 | 70 | var last = this.last() 71 | if (last) 72 | last.connect(node) 73 | return this.chain.push(node) 74 | } 75 | 76 | pop() { 77 | var node = this.chain.pop() 78 | if (node) 79 | node.disconnect() 80 | return node 81 | } 82 | 83 | shift() { 84 | var node = this.chain.shift() 85 | if (node) 86 | node.disconnect() 87 | return node 88 | } 89 | 90 | unshift(node) { 91 | this._nodeExists(node, 'unshift') 92 | 93 | var first = this.first() 94 | var source = this.source() 95 | 96 | if (source) { 97 | source.disconnect() 98 | source.connect(node) 99 | } 100 | if (first) 101 | node.connect(first) 102 | 103 | return this.chain.unshift(node) 104 | } 105 | 106 | _insert(node, position) { 107 | var max = this.chain.length 108 | if (this.hasSource) 109 | position++ 110 | if (this.hasDestination) 111 | max-- 112 | 113 | if (!Number.isInteger(position) || position < 0) 114 | throw new Error(`Node can not be inserted in position ${position}`) 115 | if (position > max) 116 | throw new Error(`Node inserted at ${position}, which is above the maximum of ${max}`) 117 | 118 | var previous = this.chain[position - 1] 119 | var next = this.chain[position] 120 | 121 | if (next) { 122 | next.disconnect(previous) 123 | node.connect(next) 124 | } 125 | if (previous) 126 | previous.connect(node) 127 | 128 | this.chain.splice(position, 0, node) 129 | } 130 | 131 | move(node, toIndex) { 132 | this.remove(node) 133 | this._insert(node, toIndex) 134 | } 135 | 136 | _moveByRelativePosition(node, delta) { 137 | var index = this._getNodeIndex(node) 138 | this._removeByIndex(index) 139 | this._insert(node, index + delta) 140 | } 141 | 142 | moveUp(node) { 143 | this._moveByRelativePosition(node, -1) 144 | } 145 | 146 | moveDown(node) { 147 | this._moveByRelativePosition(node, 1) 148 | } 149 | 150 | _removeByIndex(index) { 151 | if (!this.chain[index]) 152 | throw new Error(`No node at index ${index}`) 153 | if (index == 0 && this.hasSource) 154 | this.hasSource = false 155 | if (index == this.chain.length && this.hasDestination) 156 | this.hasDestination = false 157 | 158 | var previous = this.chain[index - 1] 159 | var next = this.chain[index + 1] 160 | 161 | var [removed] = this.chain.splice(index, 1) 162 | removed.disconnect() 163 | 164 | if (previous && next) 165 | previous.connect(next) 166 | 167 | return removed 168 | } 169 | 170 | _getNodeIndex(node) { 171 | var index = this.chain.findIndex(_node => _node === node) 172 | if (index == -1) 173 | throw new Error('Node not found!') 174 | return index 175 | } 176 | 177 | _removeByNode(node) { 178 | this._nodeExists(node, 'remove') 179 | 180 | var index = this._getNodeIndex(node) 181 | return this._removeByIndex(index) 182 | } 183 | 184 | remove(arg) { 185 | if (typeof arg == 'number') 186 | return this._removeByIndex(arg) 187 | return this._removeByNode(arg) 188 | } 189 | } -------------------------------------------------------------------------------- /src/nodes/inputs/shared/AudioNodeChain.test.js: -------------------------------------------------------------------------------- 1 | import test from 'tape' 2 | 3 | import AudioNodeChain from './AudioNodeChain' 4 | 5 | class Node { 6 | constructor(number) { 7 | if (!Number.isInteger(number)) 8 | throw new Error('Node requires an integer parameter!') 9 | 10 | this.previous = null 11 | this.next = null 12 | this.number = number 13 | } 14 | 15 | connect(nextNode) { 16 | if (this.next) 17 | throw new Error('Nodes can only be connected to one child at a time!') 18 | 19 | this.next = nextNode 20 | nextNode.previous = this 21 | } 22 | 23 | disconnect(node) { 24 | if (!this.previous && !this.next) 25 | throw new Error('Can not disconnect when not connected') 26 | 27 | if (this.previous && (!node || this.previous == node)) { 28 | this.previous.next = null 29 | this.previous = null 30 | } if (this.next && (!node || this.next == node)) { 31 | this.next.previous = null 32 | this.next = null 33 | } 34 | } 35 | } 36 | 37 | const init = count => { 38 | var audioNodeChain = new AudioNodeChain() 39 | for (var i = 0; i < count; i++) 40 | audioNodeChain.push(new Node(i)) 41 | 42 | return audioNodeChain 43 | } 44 | 45 | const checkChain = (t, audioNodeChain, expected) => { 46 | const chain = type => { 47 | var actual = [] 48 | var otherType = type == 'next' ? 'previous' : 'next' 49 | var node = audioNodeChain.chain[type == 'next' ? 0 : audioNodeChain.chain.length - 1] 50 | if (node) 51 | t.equal(node[otherType], null, `Check start point`) 52 | 53 | while (node) { 54 | actual[type == 'next' ? 'push' : 'unshift'](node.number) 55 | node = node[type] 56 | } 57 | 58 | if (node) 59 | t.equal(node[otherType], null, 'Check end point') 60 | 61 | return actual 62 | } 63 | 64 | const array = audioNodeChain.chain.map(({number}) => number) 65 | 66 | t.deepEqual(chain('next'), expected, 'Check next connections') 67 | t.deepEqual(chain('previous'), expected, 'Check previous connections') 68 | t.deepEqual(array, expected, 'Check chain') 69 | } 70 | 71 | test('Can connect nodes', t => { 72 | var one = new Node(1) 73 | var two = new Node(2) 74 | 75 | t.equal(one.number, 1) 76 | t.equal(two.number, 2) 77 | 78 | t.equal(one.next, null) 79 | one.connect(two) 80 | 81 | t.equal(one.next, two) 82 | t.equal(two.previous, one) 83 | 84 | t.end() 85 | }) 86 | 87 | test('Can get the first and last last nodes', t => { 88 | var audioNodeChain = new AudioNodeChain() 89 | var first = new Node(0) 90 | var last = new Node(1) 91 | 92 | audioNodeChain.chain = [ first, last ] 93 | t.equal(audioNodeChain.first(), first) 94 | t.equal(audioNodeChain.last(), last) 95 | 96 | t.end() 97 | }) 98 | 99 | test('Can initialize a node chain', t => { 100 | var count = 3 101 | var audioNodeChain = init(count) 102 | checkChain(t, audioNodeChain, [0, 1, 2]) 103 | 104 | t.end() 105 | }) 106 | 107 | test('Can insert nodes', t => { 108 | var count = 5 109 | var audioNodeChain = init(count) 110 | var node = new Node(99) 111 | audioNodeChain._insert(node, 2) 112 | 113 | var expected = [0, 1, 99, 2, 3, 4] 114 | checkChain(t, audioNodeChain, expected) 115 | t.end() 116 | }) 117 | 118 | test('Can remove nodes by position', t => { 119 | var count = 5 120 | var audioNodeChain = init(count) 121 | var node = audioNodeChain.remove(2) 122 | 123 | var expected = [0, 1, 3, 4] 124 | checkChain(t, audioNodeChain, expected) 125 | t.equal(node.number, 2) 126 | t.end() 127 | }) 128 | 129 | test('Can remove nodes by reference', t => { 130 | var count = 5 131 | var index = 2 132 | var audioNodeChain = init(count) 133 | var node = audioNodeChain.chain[index] 134 | var _node = audioNodeChain.remove(node) 135 | 136 | var expected = [0, 1, 3, 4] 137 | checkChain(t, audioNodeChain, expected) 138 | t.equal(node, _node) 139 | t.equal(node.number, index) 140 | t.end() 141 | }) 142 | 143 | test('Can move nodes', t => { 144 | var count = 5 145 | var audioNodeChain = init(count) 146 | var { chain } = audioNodeChain 147 | var zero = chain[0] 148 | audioNodeChain.move(zero, chain.length - 1) 149 | 150 | var expected = [1, 2, 3, 4, 0] 151 | checkChain(t, audioNodeChain, expected) 152 | t.end() 153 | }) 154 | 155 | test('Can move nodes up', t => { 156 | var count = 5 157 | var audioNodeChain = init(count) 158 | var { chain } = audioNodeChain 159 | var three = chain[3] 160 | audioNodeChain.moveUp(three) 161 | 162 | var expected = [0, 1, 3, 2, 4] 163 | checkChain(t, audioNodeChain, expected) 164 | t.end() 165 | }) 166 | 167 | test('Can move nodes down', t => { 168 | var count = 5 169 | var audioNodeChain = init(count) 170 | var { chain } = audioNodeChain 171 | var two = chain[2] 172 | audioNodeChain.moveDown(two) 173 | 174 | var expected = [0, 1, 3, 2, 4] 175 | checkChain(t, audioNodeChain, expected) 176 | t.end() 177 | }) 178 | -------------------------------------------------------------------------------- /src/nodes/inputs/shared/AudioSource.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | import update from '../../shared/update' 4 | 5 | import AudioNodeChain from './AudioNodeChain' 6 | 7 | export default class AudioSource extends Component { 8 | constructor() { 9 | super() 10 | this.audioNodeChain = new AudioNodeChain() 11 | } 12 | 13 | getChildContext() { 14 | var { audioNodeChain } = this 15 | return { audioNodeChain } 16 | } 17 | 18 | componentWillMount() { 19 | var { node, audioNodeChain } = this 20 | audioNodeChain.setSource(node) 21 | update.call(this) 22 | } 23 | 24 | componentDidUpdate() { 25 | update.call(this) 26 | } 27 | 28 | componentWillUnmount() { 29 | var { node } = this 30 | this.audioNodeChain.remove(node) 31 | } 32 | 33 | render() { 34 | return
{this.props.children}
35 | } 36 | } 37 | 38 | AudioSource.childContextTypes = { 39 | audioNodeChain: React.PropTypes.any.isRequired 40 | } 41 | 42 | AudioSource.contextTypes = { 43 | audioContext: React.PropTypes.any.isRequired 44 | } -------------------------------------------------------------------------------- /src/nodes/outputs/Destination.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | export default class AudioNode extends Component { 4 | componentWillMount() { 5 | var { destination } = this.context.audioContext 6 | this.node = destination 7 | } 8 | 9 | componentDidMount() { 10 | var { node } = this 11 | var { audioNodeChain } = this.context 12 | 13 | audioNodeChain.setDestination(node) 14 | } 15 | 16 | componentWillUnmount() { 17 | var { node } = this 18 | var { audioNodeChain } = this.context 19 | 20 | audioNodeChain.remove(node) 21 | 22 | node.disconnect() 23 | } 24 | 25 | render() { 26 | return
27 | } 28 | } 29 | 30 | AudioNode.contextTypes = { 31 | audioContext: React.PropTypes.any.isRequired, 32 | audioNodeChain: React.PropTypes.any.isRequired 33 | } -------------------------------------------------------------------------------- /src/nodes/outputs/index.js: -------------------------------------------------------------------------------- 1 | import Destination from './Destination' 2 | 3 | export default { 4 | Destination 5 | } -------------------------------------------------------------------------------- /src/nodes/processors/Analyser.js: -------------------------------------------------------------------------------- 1 | import AudioNode from './shared/AudioNode' 2 | 3 | export default class Analyser extends AudioNode { 4 | componentWillMount() { 5 | var { audioContext } = this.context 6 | if (audioContext.createAnalyser) { 7 | var node = this.node = audioContext.createAnalyser() 8 | 9 | this.getFloatFrequencyData = () => { 10 | if (!this.floatFrequencyData) 11 | this.floatFrequencyData = new Float32Array(node.frequencyBinCount) 12 | 13 | var data = this.floatFrequencyData 14 | node.getFloatFrequencyData(data) 15 | return data 16 | } 17 | 18 | this.getByteFrequencyData = () => { 19 | if (!this.byteFrequencyData) 20 | this.byteFrequencyData = new Uint8Array(node.frequencyBinCount) 21 | 22 | var data = this.byteFrequencyData 23 | node.getByteFrequencyData(data) 24 | return data 25 | } 26 | 27 | this.getFloatTimeDomainData = () => { 28 | if (!this.floatTimeDomainData) 29 | this.floatTimeDomainData = new Float32Array(node.fftSize) 30 | 31 | var data = this.floatTimeDomainData 32 | node.getFloatTimeDomainData(data) 33 | return data 34 | } 35 | 36 | this.getByteTimeDomainData = () => { 37 | if (!this.byteTimeDomainData) 38 | this.byteTimeDomainData = new Uint8Array(node.fftSize) 39 | 40 | var data = this.byteTimeDomainData 41 | node.getByteTimeDomainData(data) 42 | return data 43 | } 44 | } 45 | else 46 | console.error('Analyser not supported in this browser') 47 | } 48 | 49 | render() { 50 | var { frequencyBinCount } = this.node 51 | 52 | var props = { 53 | getFloatFrequencyData: this.getFloatFrequencyData, 54 | getByteFrequencyData: this.getByteFrequencyData, 55 | getFloatTimeDomainData: this.getFloatTimeDomainData, 56 | getByteTimeDomainData: this.getByteTimeDomainData, 57 | frequencyBinCount 58 | } 59 | 60 | return
{React.Children.map(this.props.children, m => 61 | React.cloneElement(m, props) 62 | )}
63 | } 64 | } 65 | 66 | module.exports.defaultProps = { 67 | fftSize: 2048, 68 | minDecibels: -100, 69 | maxDecibels: -30, 70 | smoothingTimeConstant: 0.8 71 | } -------------------------------------------------------------------------------- /src/nodes/processors/BiquadFilter.js: -------------------------------------------------------------------------------- 1 | import AudioNode from './shared/AudioNode' 2 | 3 | export default class BiquadFilter extends AudioNode { 4 | componentWillMount() { 5 | var { audioContext } = this.context 6 | if (audioContext.createBiquadFilter) 7 | this.node = audioContext.createBiquadFilter() 8 | else 9 | console.error('BiquadFilter not supported in this browser') 10 | } 11 | } 12 | 13 | module.exports.defaultProps = { 14 | frequency: 350, 15 | detune: 0, 16 | Q: 1, 17 | gain: 0, 18 | type: 'lowpass' 19 | } -------------------------------------------------------------------------------- /src/nodes/processors/ConvolverNode.js: -------------------------------------------------------------------------------- 1 | import AudioNode from './shared/AudioNode' 2 | 3 | export default class ConvolverNode extends AudioNode { 4 | constructor() { 5 | super() 6 | throw new Error('UnsupportedOperation') 7 | } 8 | } -------------------------------------------------------------------------------- /src/nodes/processors/Delay.js: -------------------------------------------------------------------------------- 1 | import AudioNode from './shared/AudioNode' 2 | 3 | export default class Delay extends AudioNode { 4 | componentWillMount() { 5 | var { audioContext } = this.context 6 | if (audioContext.createDelay) 7 | this.node = audioContext.createDelay() 8 | else 9 | console.error('Delay not supported in this browser') 10 | } 11 | } 12 | 13 | module.exports.defaultProps = { 14 | delayTime: 0 15 | } -------------------------------------------------------------------------------- /src/nodes/processors/DynamicsCompressor.js: -------------------------------------------------------------------------------- 1 | import AudioNode from './shared/AudioNode' 2 | 3 | export default class DynamicsCompressor extends AudioNode { 4 | componentWillMount() { 5 | var { audioContext } = this.context 6 | if (audioContext.createDynamicsCompressor) 7 | this.node = audioContext.createDynamicsCompressor() 8 | else 9 | console.error('DynamicsCompressor not supported in this browser') 10 | } 11 | } 12 | 13 | module.exports.defaultProps = { 14 | threshold: -24, 15 | knee: 30, 16 | ratio: 12, 17 | reduction: 0, 18 | attack: 0.003, 19 | release: 0.25 20 | } -------------------------------------------------------------------------------- /src/nodes/processors/Gain.js: -------------------------------------------------------------------------------- 1 | import AudioNode from './shared/AudioNode' 2 | 3 | export default class Gain extends AudioNode { 4 | componentWillMount() { 5 | var { audioContext } = this.context 6 | if (audioContext.createGain) 7 | this.node = audioContext.createGain() 8 | else 9 | console.error('Gain not supported in this browser') 10 | } 11 | } 12 | 13 | module.exports.defaultProps = { 14 | value: 1 15 | } -------------------------------------------------------------------------------- /src/nodes/processors/PeriodicWave.js: -------------------------------------------------------------------------------- 1 | import AudioNode from './shared/AudioNode' 2 | 3 | export default class PeriodicWave extends AudioNode { 4 | constructor() { 5 | super() 6 | throw new Error('UnsupportedOperation') 7 | } 8 | } -------------------------------------------------------------------------------- /src/nodes/processors/StereoPanner.js: -------------------------------------------------------------------------------- 1 | import AudioNode from './shared/AudioNode' 2 | 3 | export default class StereoPanner extends AudioNode { 4 | componentWillMount() { 5 | var { audioContext } = this.context 6 | if (audioContext.createStereoPanner) 7 | this.node = audioContext.createStereoPanner() 8 | else 9 | console.error('StereoPanner not supported in this browser') 10 | } 11 | } 12 | 13 | module.exports.defaultProps = { 14 | pan: 0 15 | } -------------------------------------------------------------------------------- /src/nodes/processors/WaveShaper.js: -------------------------------------------------------------------------------- 1 | import AudioNode from './shared/AudioNode' 2 | 3 | export default class WaveShaper extends AudioNode { 4 | componentWillMount() { 5 | var { audioContext } = this.context 6 | if (audioContext.createWaveShaper) 7 | this.node = audioContext.createWaveShaper() 8 | else 9 | console.error('WaveShaper not supported in this browser') 10 | } 11 | } 12 | 13 | /* eslint-disable */ 14 | const defaultCurve = (amount => { 15 | var k = typeof amount === 'number' ? amount : 50, 16 | n_samples = 44100, 17 | curve = new Float32Array(n_samples), 18 | deg = Math.PI / 180, 19 | i = 0, 20 | x; 21 | for ( ; i < n_samples; ++i ) { 22 | x = i * 2 / n_samples - 1; 23 | curve[i] = ( 3 + k ) * x * 20 * deg / ( Math.PI + k * Math.abs(x) ); 24 | } 25 | return curve; 26 | })(400) 27 | /* eslint-enable */ 28 | 29 | WaveShaper.defaultProps = { 30 | curve: defaultCurve, 31 | oversample: 'none' 32 | } -------------------------------------------------------------------------------- /src/nodes/processors/index.js: -------------------------------------------------------------------------------- 1 | import BiquadFilter from './BiquadFilter' 2 | import ConvolverNode from './ConvolverNode' 3 | import Delay from './Delay' 4 | import DynamicsCompressor from './DynamicsCompressor' 5 | import Gain from './Gain' 6 | import StereoPanner from './StereoPanner' 7 | import WaveShaper from './WaveShaper' 8 | import Analyser from './Analyser' 9 | 10 | export default { 11 | BiquadFilter, 12 | ConvolverNode, 13 | Delay, 14 | DynamicsCompressor, 15 | Gain, 16 | StereoPanner, 17 | WaveShaper, 18 | Analyser 19 | } -------------------------------------------------------------------------------- /src/nodes/processors/shared/AudioNode.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | import update from '../../shared/update' 4 | 5 | export default class AudioNode extends Component { 6 | componentDidMount() { 7 | var { node } = this 8 | var { audioNodeChain } = this.context 9 | 10 | audioNodeChain.push(node) 11 | 12 | update.call(this) 13 | } 14 | 15 | componentDidUpdate() { 16 | update.call(this) 17 | } 18 | 19 | componentWillUnmount() { 20 | var { node } = this 21 | var { audioNodeChain } = this.context 22 | 23 | audioNodeChain.remove(node) 24 | 25 | node.disconnect() 26 | } 27 | 28 | render() { 29 | return
30 | } 31 | } 32 | 33 | AudioNode.contextTypes = { 34 | audioContext: React.PropTypes.any.isRequired, 35 | audioNodeChain: React.PropTypes.any.isRequired 36 | } -------------------------------------------------------------------------------- /src/nodes/shared/update.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | var { node } = this 3 | var { 4 | type, 5 | curve, 6 | oversample, 7 | fftSize, 8 | minDecibels, 9 | maxDecibels, 10 | smoothingTimeConstant, 11 | ...rest 12 | } = this.props 13 | 14 | if (!node) 15 | return 16 | 17 | Object.assign(node, {type, curve, oversample}) 18 | 19 | for (var prop in rest) 20 | if (node[prop]) 21 | node[prop].value = rest[prop] 22 | } --------------------------------------------------------------------------------