├── .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 | }
--------------------------------------------------------------------------------