[
  {
    "path": ".eslintrc.json",
    "content": "{\n  \"extends\": [\n    \"standard\",\n    \"eslint:recommended\",\n    \"plugin:react/recommended\"\n  ],\n  \"env\": {\n    \"browser\": true\n  },\n  \"rules\": {\n    \"react/prop-types\": \"off\",\n    \"react/no-deprecated\": \"off\",\n    \"no-unused-vars\": \"off\",\n    \"space-before-function-paren\": \"off\",\n    \"no-return-assign\": \"off\",\n    \"semi\": [\n      \"error\",\n      \"always\"\n    ]\n  }\n}\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n*.tex\nevaluation\nnative_test*\nnode_modules\ndist\n"
  },
  {
    "path": ".npmignore",
    "content": "examples/\nevaluation/\n.eslintrc.json\n.DS_Store\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2018-present British Broadcasting Corporation\n\nAll rights reserved\n\n(http://www.bbc.co.uk) and r-audio Contributors\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# r-audio\nA library of React components for building [Web Audio](https://www.w3.org/TR/webaudio/) graphs.\n\n## Objectives\n👉 make Web Audio graph code more readable and representative of the graph shape\n\n👉 make it easier to create reusable graphs\n\n👉 make state management easier with React's one-way data bindings and single source of state\n\n👉 represent any arbitrary directed graphs in JSX\n\n👉 support all non-deprecated audio nodes including `AudioWorklet`\n\n👉 allow interspersed HTML components in audio components\n\n## Installation\n\n```bash\nnpm install r-audio\n```\n\n## Usage example\n\nStereo waveshaper + amplitude modulation on a WAV loop\n```jsx\n<RAudioContext debug={true} onInit={ctx => this.audioContext = ctx}>\n  <RPipeline>\n    <RBufferSource buffer={this.state.buffer} loop/>\n    <RSplitChannels channelCount={2}>\n      <RPipeline>\n        <RWaveShaper curve={this.makeDistortionCurve(200)} />\n        <RConvolver buffer={this.state.buffer} />\n        <RDynamicsCompressor threshold={-50} knee={40}/>\n        <RGain gain={.5} />\n      </RPipeline>\n      <RPipeline>\n        <ROscillator frequency={1} type=\"sine\" detune={0} connectToParam=\"gain\" />\n        <RGain gain={1} />\n      </RPipeline>\n    </RSplitChannels>\n  </RPipeline>\n</RAudioContext>\n```\n\n## Useful links\n- [Full usage examples](https://github.com/bbc/r-audio/tree/master/examples)\n- [API Reference](https://github.com/bbc/r-audio/wiki/API-Reference)\n\n## Development setup\n\n```bash\nnpm install\nnpm run dev\n```\n\nThe demo page will be served at `localhost:8080`. Use a recent version of Chrome or Firefox for the best experience.\n\nFirefox Web Audio developer tool is especially handy (bear in mind Firefox does not support AudioWorklet as of 17 April 2018).\n\n"
  },
  {
    "path": "examples/README.md",
    "content": "# r-audio examples\n\nThe files in this directory constitute a demo web app where you can test the examples and experiment. To launch the demo app, run `npm run dev`.\n\nEvery example has notes/explanations embedded as HTML along the `r-audio` components.\n"
  },
  {
    "path": "examples/assets/js/bit-crusher.js",
    "content": "// Copyright (c) 2017 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n/**\n * A AudioWorklet-based BitCrusher demo from the spec example.\n *\n * @class BitCrusher\n * @extends AudioWorkletProcessor\n * @see https://webaudio.github.io/web-audio-api/#the-bitcrusher-node\n */\nclass BitCrusher extends AudioWorkletProcessor {\n  static get parameterDescriptors() {\n    return [\n      {name: 'bitDepth', defaultValue: 12, minValue: 1, maxValue: 16}, {\n        name: 'frequencyReduction',\n        defaultValue: 0.5,\n        minValue: 0,\n        maxValue: 1,\n      },\n    ];\n  }\n\n  constructor(options) {\n    super(options);\n    this.phase_ = 0;\n    this.lastSampleValue_ = 0;\n  }\n\n  process(inputs, outputs, parameters) {\n    let input = inputs[0];\n    let output = outputs[0];\n    let bitDepth = parameters.bitDepth;\n    let frequencyReduction = parameters.frequencyReduction;\n    for (let channel = 0; channel < input.length; ++channel) {\n      let inputChannel = input[channel];\n      let outputChannel = output[channel];\n      for (let i = 0; i < inputChannel.length; ++i) {\n        let step = Math.pow(0.5, bitDepth[i]);\n        this.phase_ += frequencyReduction[i];\n        if (this.phase_ >= 1.0) {\n          this.phase_ -= 1.0;\n          this.lastSampleValue_ =\n              step * Math.floor(inputChannel[i] / step + 0.5);\n        }\n        outputChannel[i] = this.lastSampleValue_;\n      }\n    }\n\n    return true;\n  }\n}\n\nregisterProcessor('bit-crusher', BitCrusher);\n"
  },
  {
    "path": "examples/audio-worklet.js",
    "content": "import React from 'react';\nimport { render } from 'react-dom';\n\nimport {\n  RAnalyser,\n  RAudioContext,\n  RAudioWorklet,\n  RDelay,\n  RGain,\n  RMediaStreamSource,\n  RPipeline,\n  RSplitChannels\n} from '../index.js';\n\nexport default class AudioWorkletExample extends React.Component {\n  constructor() {\n    super();\n    this.state = { stream: null, ready: false };\n  }\n\n  loadWorkletAndStream(ctx) {\n    const streamPromise = navigator.mediaDevices.getUserMedia({ audio: true, video: false })\n      .then(stream => this.setState({ stream }));\n\n    const workletPromise = ctx.audioWorklet\n      .addModule('/assets/js/bit-crusher.js');\n\n    Promise.all([ streamPromise, workletPromise ])\n      .then(() => this.setState({ ready: true }));\n  }\n\n  render() {\n    return (\n      <RAudioContext debug={true} onInit={ctx => this.loadWorkletAndStream.bind(this)(ctx)}>\n        <article>\n          <h1>Buffers and Channels</h1>\n          <p>This example demonstrates how to use an <code>AudioWorklet</code> in <em>r-audio</em>. It also shows a RAnalyser in action.</p>\n          <p>Notice that the graph only renders after both the media stream and the worklet have been initialised.</p>\n        </article>\n        {\n          this.state.ready ? (\n            <RPipeline>\n              <RMediaStreamSource stream={this.state.stream} />\n              <RAnalyser fftSize={2048}>\n                {\n                  proxy => {\n                    const data = new Float32Array(proxy.frequencyBinCount);\n                    // when this function first runs, there will be no data yet\n                    // so wait a bit\n                    // in reality one might want to save the `proxy` object and call it independently\n                    // for instance, inside a `requestAnimationFrame` call\n                    setTimeout(() => {\n                      proxy.getFloatFrequencyData(data);\n                      console.log(data); // eslint-disable-line no-console\n                    }, 3000);\n                  }\n                }\n              </RAnalyser>\n              <RDelay delayTime={0.3} bitDepth={4} />\n              <RSplitChannels channelCount={2}>\n                <RAudioWorklet worklet=\"bit-crusher\" bitDepth={4} frequencyReduction={0.5}/>\n                <RAudioWorklet worklet=\"bit-crusher\" bitDepth={4} frequencyReduction={0.5}/>\n              </RSplitChannels>\n              <RGain gain={0.4} />\n            </RPipeline>\n          ) : null\n        }\n      </RAudioContext>\n    );\n  }\n}\n"
  },
  {
    "path": "examples/buffers-channels.js",
    "content": "import React from 'react';\nimport { render } from 'react-dom';\n\nimport {\n  RAudioContext,\n  RBufferSource,\n  RConstantSource,\n  RConvolver,\n  RDynamicsCompressor,\n  RGain,\n  ROscillator,\n  RPipeline,\n  RSplitChannels,\n  RWaveShaper\n} from '../index.js';\n\nexport default class BuffersAndChannels extends React.Component {\n  constructor() {\n    super();\n\n    this.state = { buffer: null };\n  }\n\n  componentDidMount() {\n    fetch('/assets/audio/b.wav')\n      .then(res => res.arrayBuffer())\n      .then(ab => this.audioContext.decodeAudioData(ab))\n      .then(buffer => this.setState({ buffer }));\n  }\n\n  makeDistortionCurve(amount) {\n    var k = typeof amount === 'number' ? amount : 50,\n      n_samples = 44100,\n      curve = new Float32Array(n_samples),\n      deg = Math.PI / 180,\n      i = 0,\n      x;\n    for (; i < n_samples; ++i) {\n      x = i * 2 / n_samples - 1;\n      curve[i] = (3 + k) * x * 20 * deg / (Math.PI + k * Math.abs(x));\n    }\n    return curve;\n  }\n\n  render() {\n    return (\n      <RAudioContext debug={true} onInit={ctx => this.audioContext = ctx}>\n        <article>\n          <h1>Buffers and Channels</h1>\n          <p>This example demonstrates initialising a <code>RBufferSource</code> with a decoded <code>AudioBuffer</code>.</p>\n          <p>It also shows how to process channels separately.</p>\n        </article>\n        <RPipeline>\n          <RBufferSource buffer={this.state.buffer} loop start={0}/>\n          <RSplitChannels channelCount={2}>\n            <RPipeline>\n              <RWaveShaper curve={this.makeDistortionCurve(200)} />\n              <RConvolver buffer={this.state.buffer} />\n              <RDynamicsCompressor threshold={-50} knee={40}/>\n              <RConstantSource offset={0} connectToParam=\"gain\" start={0}/>\n              <RGain gain={0.5} />\n            </RPipeline>\n            <RPipeline>\n              <ROscillator frequency={1} type=\"sine\" detune={0} connectToParam=\"gain\" start={0}/>\n              <RGain gain={1} />\n            </RPipeline>\n          </RSplitChannels>\n        </RPipeline>\n      </RAudioContext>\n    );\n  }\n}\n"
  },
  {
    "path": "examples/complex-effects-graph.js",
    "content": "import React from 'react';\nimport { render } from 'react-dom';\n\nimport {\n  RAudioContext,\n  RBiquadFilter,\n  RGain,\n  ROscillator,\n  RPipeline,\n  RSplit,\n  RStereoPanner\n} from '../index.js';\n\nconst pipeline = (detune, gain, filterFreq, pan) => (\n  <RAudioContext debug={true}>\n    <article>\n      <h1>Complex effects graph</h1>\n      <p>This example demonstrates how <em>r-audio</em> handles various graph configurations,\n      including non-connectable nodes in pipelines and deeply nested parallel/serial connections.</p>\n\n      <p>It also shows how to create &lsquo;dead-end&rsquo; branches using the <code>disconnected</code> attribute.</p>\n    </article>\n    <RPipeline>\n      <ROscillator start={0} frequency={440} type=\"triangle\" detune={0}/>\n      <ROscillator start={0} frequency={220} type=\"triangle\" detune={detune} transitionTime={0.5}/>\n      <RGain gain={gain} transitionTime={1} name='gainToSplit'/>\n      <RSplit>\n        <ROscillator start={0} frequency={330} type=\"triangle\" detune={detune + 3} transitionTime={0.5} />\n        <RBiquadFilter frequency={1000} gain={gain} Q={1} type=\"lowpass\" detune={detune}\n          transitionTime={{ gain: 5, detune: 10 }}\n          transitionCurve={{ gain: 'exponential', detune: 'linear' }} />\n        <RPipeline>\n          <RBiquadFilter frequency={1000} gain={1} Q={1} type=\"lowpass\" detune={5} transitionTime={0.8}/>\n          <RBiquadFilter frequency={1000} gain={1} Q={1} type=\"lowpass\" detune={5} transitionTime={0.8}/>\n          <RBiquadFilter frequency={1000} gain={1} Q={1} type=\"lowpass\" detune={5} transitionTime={0.8}/>\n          <ROscillator start={0} frequency={1} type=\"sine\" detune={0} connectToParam='pan' />\n          <RStereoPanner />\n          <RBiquadFilter frequency={1000} gain={1} Q={1} type=\"lowpass\" detune={3} transitionTime={0.8} />\n        </RPipeline>\n        <RPipeline>\n          <RBiquadFilter frequency={1000} gain={1} Q={1} type=\"lowpass\" detune={3} transitionTime={0.8} disconnected />\n        </RPipeline>\n      </RSplit>\n      <RPipeline>\n        <ROscillator start={0} frequency={110} type=\"sawtooth\" detune={0}/>\n        <ROscillator start={0} frequency={1} type=\"sine\" detune={0} connectToParam='pan' />\n        <RStereoPanner />\n      </RPipeline>\n      <RGain gain={0.8} transitionTime={1}/>\n      <RBiquadFilter frequency={filterFreq} gain={1.5} Q={10.1} type=\"lowpass\" detune={0} transitionTime={0.8}/>\n    </RPipeline>\n  </RAudioContext>\n);\n\nexport default class ComplexGraph extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      detune: 50,\n      gain: 0.4,\n      filterFreq: 600,\n      pan: 0\n    };\n\n    setInterval(() => {\n      this.setState({\n        detune: Math.random() * 100,\n        gain: Math.random() / 2 + 0.5,\n        filterFreq: Math.random() * 3000 + 200,\n        pan: Math.random() * 2 - 1\n      });\n    }, 2000);\n  }\n\n  render() {\n    return pipeline(this.state.detune, this.state.gain, this.state.filterFreq, this.state.pan);\n  }\n}\n"
  },
  {
    "path": "examples/custom-nodes.js",
    "content": "import React from 'react';\nimport { render } from 'react-dom';\n\nimport {\n  RAudioContext,\n  RCycle,\n  RDelay,\n  RExtensible,\n  RGain,\n  RMediaElementSource,\n  RPipeline,\n  RSplit\n} from '../index.js';\n\nclass DelayLine extends RExtensible {\n  renderGraph() {\n    return (\n      <RCycle>\n        <RPipeline>\n          <RGain gain={this.props.gain}/>\n          <RDelay delayTime={this.props.delayTime}/>\n        </RPipeline>\n      </RCycle>\n    );\n  }\n}\n\nexport default class CustomNodeExample extends React.Component {\n  constructor(props) {\n    super(props);\n    this.audio = new Audio('/assets/audio/clarinet.mp3');\n    this.audio.autoplay = true;\n    this.audio.loop = true;\n  }\n\n  render() {\n    return (\n      <RAudioContext debug={true}>\n        <article>\n          <h1>Creating custom nodes</h1>\n          <p>This example demonstrates how to create custom <em>r-audio</em> nodes.\n          This can be done by extending <code>RExtensible</code>,\n          which is itself an extension of <em>RPipeline</em>.\n          We define the contents of our custom node by overriding the <code>renderGraph</code> method,\n          which simply returns a bit of JSX, just like React components&apos;\n          <code>render</code> method.</p>\n        </article>\n        <RPipeline>\n          <RMediaElementSource element={this.audio} />\n          <DelayLine gain={0.7} delayTime={0.3} />\n          <RGain gain={2} />\n        </RPipeline>\n      </RAudioContext>\n    );\n  }\n}\n"
  },
  {
    "path": "examples/delay-lines.js",
    "content": "/**\n\n**/\nimport React from 'react';\nimport { render } from 'react-dom';\n\nimport {\n  RAudioContext,\n  RBiquadFilter,\n  RCycle,\n  RDelay,\n  RGain,\n  ROscillator,\n  RPipeline,\n  RSplit,\n  RStereoPanner\n} from '../index.js';\n\nexport default class DelayLineExample extends React.Component {\n  constructor(props) {\n    super(props);\n\n    this.state = { periodicWave: null, start: 0, stop: 3 };\n    // a simple waveform can be created with a series of periodically repeating numbers\n    const realComponents = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1];\n    // imaginary components can all be 0 for demo purposes\n    const imagComponents = realComponents.map(() => 0);\n\n    this.onContextInit = ctx => {\n      this.setState({\n        periodicWave: ctx.createPeriodicWave(\n          Float32Array.from(realComponents),\n          Float32Array.from(imagComponents),\n          { disableNormalization: true }\n        )\n      });\n\n      // schedule restart of the oscillator after 6 seconds\n      setInterval(() => this.setState({ start: ctx.currentTime, stop: ctx.currentTime + 3 }), 6000);\n    };\n  }\n\n  render() {\n    return (\n      <RAudioContext debug={true} onInit={this.onContextInit}>\n        <article>\n          <h1>Delay lines &amp; scheduling</h1>\n          <p>\n            This example demonstrates how one can create feedback delay lines using the <code>RCycle</code> component.\n            It also shows how scheduling works.\n          </p>\n          <p>Make sure to always include a <code>RGain</code> with <code>gain</code> &lt; 1 to avoid infinite feedback.</p>\n        </article>\n        <RPipeline>\n          <ROscillator\n            frequency={220} type=\"triangle\" detune={0}\n            periodicWave={this.state.periodicWave}\n            start={this.state.start}\n            stop={this.state.stop}/>\n          <ROscillator frequency={1} type=\"square\" detune={0} connectToParam=\"gain\" start={0}/>\n          <RGain gain={1} />\n          <RSplit>\n            <RGain gain={0.5} />\n            <RCycle>\n              <RPipeline>\n                <RDelay delayTime={0.1} />\n                <RGain gain={0.4} />\n                <RStereoPanner pan={-1}/>\n              </RPipeline>\n            </RCycle>\n            <RCycle>\n              <RPipeline>\n                <RDelay delayTime={0.3} />\n                <RGain gain={0.4} />\n                <RStereoPanner pan={1}/>\n              </RPipeline>\n            </RCycle>\n          </RSplit>\n        </RPipeline>\n      </RAudioContext>\n    );\n  }\n}\n"
  },
  {
    "path": "examples/examples.js",
    "content": "import React from 'react';\n\nimport AudioWorkletExample from './audio-worklet.js';\nimport DelayLineExample from './delay-lines.js';\nimport ComplexGraph from './complex-effects-graph.js';\nimport BuffersAndChannels from './buffers-channels.js';\nimport MediaElementSourceExample from './media-element.js';\nimport MediaStreamSourceExample from './media-stream.js';\nimport Mutation from './mutation.js';\nimport GainMatrixExample from './gain-matrix.js';\nimport CustomNodeExample from './custom-nodes.js';\n\nconst examples = {\n  'audio-worklet': <AudioWorkletExample/>,\n  'delay-lines-scheduling': <DelayLineExample />,\n  'complex-effects-graph': <ComplexGraph/>,\n  'buffers-channels': <BuffersAndChannels/>,\n  'media-element': <MediaElementSourceExample/>,\n  'media-stream': <MediaStreamSourceExample/>,\n  'mutation': <Mutation/>,\n  'gain-matrix': <GainMatrixExample/>,\n  'custom-node': <CustomNodeExample />,\n};\n\nexport default examples;\n"
  },
  {
    "path": "examples/gain-matrix.js",
    "content": "import React from 'react';\nimport { render } from 'react-dom';\n\nimport {\n  RAudioContext,\n  RBufferSource,\n  RExtensible,\n  RGain,\n  RPipeline,\n  RSplit,\n  RSplitChannels\n} from '../index.js';\n\nclass GainMatrix extends RExtensible {\n  constructor(props) {\n    super(props);\n\n    const gains = (new Array(props.channelCount || 2))\n      .fill((new Array(props.channelCount || 2)).fill(1));\n\n    this.state = { gains };\n    this.makeRow = this.makeRow.bind(this);\n  }\n\n  onGainInput(e) {\n    const [x, y] = e.target.name.split('').map(v => parseInt(v));\n\n    const gains = this.state.gains.slice().map(arr => arr.slice());\n    gains[x][y] = e.target.value;\n\n    this.setState({ gains });\n  }\n\n  makeRow(row, rowIndex) {\n    return (\n      <RSplit key={rowIndex}>\n        {\n          row.map((cellGain, columnIndex) => (\n            <RPipeline key={columnIndex}>\n              <RGain name={`gain${rowIndex}${columnIndex}`} gain={cellGain}\n                connectToChannel={columnIndex}/>\n              <form>\n                <label htmlFor={`label-${rowIndex}${columnIndex}`}>\n                  {`Row: ${rowIndex} - Column: ${columnIndex}`}\n                </label>\n                <input type=\"range\" min=\"0\" max=\"1\" step=\"any\" defaultValue=\"1\"\n                  id={`label-${rowIndex}${columnIndex}`}\n                  name={`${rowIndex}${columnIndex}`}\n                  onChange={this.onGainInput.bind(this)}/>\n                <hr/>\n              </form>\n            </RPipeline>\n          ))\n        }\n      </RSplit>\n    );\n  }\n\n  renderGraph() {\n    return (\n      <RSplitChannels channelCount={this.props.channelCount}>\n        { this.state.gains.map(this.makeRow) }\n      </RSplitChannels>\n    );\n  }\n}\n\nexport default class GainMatrixExample extends React.Component {\n  constructor() {\n    super();\n    this.state = {\n      buffer: null\n    };\n  }\n\n  componentDidMount() {\n    // In Safari decodeAudioData doesn't return a promise\n    // so we need to run this as both a callback and a promise handler\n    const loadBuffer = buffer => buffer && this.setState({ buffer });\n\n    fetch('/assets/audio/clarinet.mp3')\n      .then(res => res.arrayBuffer())\n      .then(ab => this.audioContext.decodeAudioData(ab, loadBuffer, null))\n      .then(loadBuffer);\n  }\n\n  render() {\n    return (\n      <RAudioContext debug={false} onInit={ctx => this.audioContext = ctx}>\n        <article>\n          <h1>Gain Matrix</h1>\n          <p>\n            This example (courtesy of <a href=\"http://github.com/tomjnixon\">Tom Nixon</a> from\n            BBC R&amp;D) shows how we can create complex multichannel graphs\n            using <code>RSplitChannels</code> and explicit <code>connectToChannel</code> props.\n          </p>\n          <p>\n            Each channel of the stereo input signal is routed to both channels of the output signal\n            and each branch is processed by a separate <code>RGain</code>.\n            This kind of graph is particularly useful when binauralising audio.\n          </p>\n          <p>\n            Stereo audio recording by Freesound user <a href=\"https://freesound.org/people/debudding/\">debudding</a> (Public Domain).\n          </p>\n        </article>\n        <RPipeline>\n          <RBufferSource buffer={this.state.buffer} loop start={0}/>\n          <GainMatrix channelCount={2}/>\n        </RPipeline>\n      </RAudioContext>\n    );\n  }\n}\n"
  },
  {
    "path": "examples/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>r-audio</title>\n  <script defer type=\"text/javascript\" src=\"/r-audio.min.js\"></script>\n  <style type=\"text/css\">\n    body {\n      font-family: sans-serif;\n      font-size: 17px;\n      line-height: 1.3;\n    }\n  </style>\n</head>\n<body>\n  <div id=\"app\">\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "examples/index.js",
    "content": "import React from 'react';\nimport { render } from 'react-dom';\nimport examples from './examples.js';\n\nconst example = location.hash.slice(1);\n\nconst onExampleChange = e => {\n  location.hash = e.target.value;\n  location.reload();\n};\n\nrender(\n  (\n    <main>\n      <header>\n        <label htmlFor=\"example-select\">Select an example: </label>\n        <select id=\"example-select\" onChange={onExampleChange} value={example}>\n          <option value=\"\" disabled>Choose an example</option>\n          {\n            Object.keys(examples).map((ex, ei) => <option key={ei} value={ex}>{ex}</option>)\n          }\n        </select>\n      </header>\n      <hr/>\n      { examples[example] || null }\n    </main>\n  ),\n  document.getElementById('app')\n);\n"
  },
  {
    "path": "examples/media-element.js",
    "content": "import React from 'react';\nimport { render } from 'react-dom';\n\nimport {\n  RAudioContext,\n  RCycle,\n  RDelay,\n  RGain,\n  RMediaElementSource,\n  RPipeline\n} from '../index.js';\n\nexport default class MediaElementSourceExample extends React.Component {\n  constructor(props) {\n    super(props);\n    this.audio = new Audio('/assets/audio/clarinet.mp3');\n    this.audio.autoplay = true;\n    this.audio.loop = true;\n  }\n\n  render() {\n    return (\n      <RAudioContext debug={true}>\n        <article>\n          <h1>Media Element</h1>\n          <p>This example demonstrates plugging a HTML5 Audio element to the <em>r-audio</em> graph using <code>RMediaElementSource</code>. A reference to the audio element could also be obtained via React refs.</p>\n        </article>\n        <RPipeline>\n          <RMediaElementSource element={this.audio} />\n          <RCycle>\n            <RPipeline>\n              <RDelay delayTime={0.3} />\n              <RGain gain={0.8} />\n            </RPipeline>\n          </RCycle>\n          <RGain gain={2} />\n        </RPipeline>\n      </RAudioContext>\n    );\n  }\n}\n"
  },
  {
    "path": "examples/media-stream.js",
    "content": "import React from 'react';\nimport { render } from 'react-dom';\n\nimport {\n  RAudioContext,\n  RCycle,\n  RDelay,\n  RGain,\n  RMediaStreamSource,\n  RPanner,\n  RPipeline\n} from '../index.js';\n\nexport default class MediaStreamSourceExample extends React.Component {\n  constructor(props) {\n    super(props);\n\n    this.state = { stream: null };\n\n    navigator.mediaDevices.getUserMedia({ audio: true, video: false })\n      .then(stream => this.setState({ stream }));\n  }\n\n  render() {\n    return this.state.stream ? (\n      <RAudioContext debug={true}>\n        <article>\n          <h1>Media Stream</h1>\n          <p>This example demonstrates plugging a<code>MediaStream</code> object (from either a WebRTC peer or the native audio input device)\n          into the <em>r-audio</em> graph using <code>RMediaStreamSource</code>.</p>\n        </article>\n        <RPipeline>\n          <RMediaStreamSource stream={this.state.stream} />\n          <RCycle>\n            <RPipeline>\n              <RDelay delayTime={0.3} />\n              <RGain gain={0.2} />\n            </RPipeline>\n          </RCycle>\n          <RPanner positionY={0} positionX={0} panningModel=\"HRTF\"/>\n        </RPipeline>\n      </RAudioContext>\n    ) : null;\n  }\n}\n"
  },
  {
    "path": "examples/mutation.js",
    "content": "import React from 'react';\nimport { render } from 'react-dom';\n\nimport {\n  RAudioContext,\n  RBiquadFilter,\n  RGain,\n  ROscillator,\n  RPipeline,\n  RSplit,\n  RStereoPanner\n} from '../index.js';\n\nexport default class Mutation extends React.Component {\n  constructor() {\n    super();\n    this.nodeCache = [\n      <ROscillator start={1} key={1} frequency={440} type=\"triangle\" detune={0} />,\n      <RBiquadFilter key={2} frequency={600} type=\"lowpass\" detune={0} transitionDuration={0.8} />,\n      <RStereoPanner key={3} />\n    ];\n\n    this.state = {\n      nodes: this.nodeCache,\n      toggle: true,\n      freq: 440\n    };\n\n    this.change = () => {\n      const changed = this.nodeCache.slice();\n      changed.splice(1, 1, <RGain key={2} gain={0.5} />);\n      this.setState({ nodes: changed });\n    };\n  }\n\n  render() {\n    return (\n      <RAudioContext debug={true}>\n        <article>\n          <h1>Mutation</h1>\n          This example demonstrates how <em>r-audio</em> graphs can be mutated via React state.\n          <em>r-audio</em> takes care of reconfiguring the connections and instantiating new nodes as necessary.\n        </article>\n        <RPipeline>\n          <button onClick={this.change}>Mutate audio graph</button>\n          <ROscillator start={0} frequency={440} type=\"triangle\" detune={0} />\n          {this.state.nodes}\n          <RGain gain={0.5} transitionDuration={1} />\n        </RPipeline>\n      </RAudioContext>\n    );\n  }\n}\n"
  },
  {
    "path": "index.js",
    "content": "import RAudioContext from './src/base/audio-context.js';\nimport RPipeline from './src/graph/pipeline.js';\nimport RSplit from './src/graph/split.js';\nimport RCycle from './src/graph/cycle.js';\nimport RExtensible from './src/graph/extensible.js';\nimport RSplitChannels from './src/graph/split-channels.js';\n\nimport {\n  RAnalyser,\n  RAudioWorklet,\n  RBiquadFilter,\n  RBufferSource,\n  RChannelMerger,\n  RChannelSplitter,\n  RConvolver,\n  RConstantSource,\n  RDelay,\n  RDynamicsCompressor,\n  RGain,\n  RIIRFilter,\n  RMediaElementSource,\n  RMediaStreamSource,\n  ROscillator,\n  RPanner,\n  RStereoPanner,\n  RWaveShaper\n} from './src/audio-nodes/index.js';\n\nexport {\n  RAnalyser,\n  RAudioContext,\n  RAudioWorklet,\n  RBiquadFilter,\n  RBufferSource,\n  RChannelMerger,\n  RChannelSplitter,\n  RConvolver,\n  RConstantSource,\n  RCycle,\n  RDelay,\n  RDynamicsCompressor,\n  RGain,\n  RIIRFilter,\n  RMediaElementSource,\n  RMediaStreamSource,\n  ROscillator,\n  RPanner,\n  RPipeline,\n  RSplit,\n  RSplitChannels,\n  RStereoPanner,\n  RWaveShaper,\n  RExtensible\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"r-audio\",\n  \"version\": \"1.2.0\",\n  \"description\": \"A library of React components for building Web Audio graphs. \",\n  \"module\": \"dist/r-audio.min.js\",\n  \"main\": \"dist/r-audio.min.js\",\n  \"scripts\": {\n    \"dev\": \"NODE_ENV=development webpack-dev-server\",\n    \"build\": \"webpack\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"prepublishOnly\": \"npm run build\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/bbc/r-audio.git\"\n  },\n  \"keywords\": [\n    \"web-audio\",\n    \"react\"\n  ],\n  \"author\": \"jakubfiala\",\n  \"license\": \"Apache-2.0\",\n  \"bugs\": {\n    \"url\": \"https://github.com/bbc/r-audio/issues\"\n  },\n  \"homepage\": \"https://github.com/bbc/r-audio#readme\",\n  \"devDependencies\": {\n    \"babel-cli\": \"^6.26.0\",\n    \"babel-core\": \"^6.26.3\",\n    \"babel-loader\": \"^7.1.5\",\n    \"babel-preset-env\": \"^1.7.0\",\n    \"babel-preset-react\": \"^6.24.1\",\n    \"eslint\": \"^4.19.1\",\n    \"eslint-config-standard\": \"^11.0.0\",\n    \"eslint-loader\": \"^2.1.0\",\n    \"eslint-plugin-import\": \"^2.14.0\",\n    \"eslint-plugin-node\": \"^6.0.1\",\n    \"eslint-plugin-promise\": \"^3.8.0\",\n    \"eslint-plugin-react\": \"^7.11.1\",\n    \"eslint-plugin-standard\": \"^3.1.0\",\n    \"uglifyjs-webpack-plugin\": \"^1.3.0\",\n    \"webpack\": \"^4.20.2\",\n    \"webpack-cli\": \"^3.1.0\",\n    \"webpack-dev-server\": \"^3.1.14\"\n  },\n  \"peerDependencies\": {\n    \"prop-types\": \"^15.6.2\",\n    \"react\": \"^16.5.0\",\n    \"react-dom\": \"^16.5.0\"\n  },\n  \"dependencies\": {\n    \"prop-types\": \"^15.6.2\",\n    \"react\": \"^16.5.0\",\n    \"react-dom\": \"^16.5.0\"\n  }\n}\n"
  },
  {
    "path": "src/audio-nodes/analyser.js",
    "content": "import React from 'react';\nimport RConnectableNode from './../base/connectable-node.js';\nimport PropTypes from 'prop-types';\n\nexport default class RAnalyser extends RConnectableNode {\n  constructor(props) {\n    super(props);\n\n    this.params = {\n      fftSize: this.props.fftSize,\n      minDecibels: this.props.minDecibels,\n      maxDecibels: this.props.maxDecibels,\n      smoothingTimeConstant: this.props.smoothingTimeConstant\n    };\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n\n    if (!this.node) {\n      this.node = this.context.audio.createAnalyser();\n      this.context.nodes.set(this.props.identifier, this.node);\n    }\n\n    this.updateParams = this.updateParams.bind(this);\n    this.updateParams(this.props);\n  }\n\n  render() {\n    const analyserProxy = Object.freeze({\n      getFloatFrequencyData: array => {\n        return this.node.getFloatFrequencyData(array);\n      },\n      getByteFrequencyData: array => {\n        return this.node.getByteFrequencyData(array);\n      },\n      getFloatTimeDomainData: array => {\n        return this.node.getFloatTimeDomainData(array);\n      },\n      getByteTimeDomainData: array => {\n        return this.node.getByteTimeDomainData(array);\n      },\n      frequencyBinCount: this.node.frequencyBinCount\n    });\n\n    this.props.children(analyserProxy);\n\n    return super.render();\n  }\n}\n\nRAnalyser.propTypes = {\n  children: PropTypes.func.isRequired\n};\n"
  },
  {
    "path": "src/audio-nodes/audio-worklet.js",
    "content": "/* global AudioWorkletNode */\nimport React from 'react';\nimport RConnectableNode from './../base/connectable-node.js';\n\nexport default class RAudioWorklet extends RConnectableNode {\n  constructor(props) {\n    super(props);\n\n    this.params = Object.assign({}, this.props);\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n\n    if (!this.node) {\n      this.node = new AudioWorkletNode(this.context.audio, this.props.worklet);\n      this.context.nodes.set(this.props.identifier, this.node);\n    }\n\n    this.updateParams = this.updateParams.bind(this);\n    this.updateParams(this.props);\n  }\n}\n"
  },
  {
    "path": "src/audio-nodes/biquad-filter.js",
    "content": "import React from 'react';\nimport RConnectableNode from './../base/connectable-node.js';\nimport PropTypes from 'prop-types';\n\nexport default class RBiquadFilter extends RConnectableNode {\n  constructor(props) {\n    super(props);\n\n    this.params = {\n      frequency: props.frequency,\n      detune: props.detune,\n      Q: props.Q,\n      gain: props.gain,\n      type: props.type\n    };\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n\n    if (!this.node) {\n      this.node = this.context.audio.createBiquadFilter();\n      this.context.nodes.set(this.props.identifier, this.node);\n    }\n\n    this.updateParams = this.updateParams.bind(this);\n    this.updateParams(this.props);\n  }\n\n  render() {\n    if (typeof this.props.children === 'function') {\n      const filterProxy = Object.freeze({\n        getFrequencyResponse: (frequencyHz, magResponse, phaseResponse) => {\n          return this.node.getFrequencyResponse(frequencyHz, magResponse, phaseResponse);\n        }\n      });\n\n      this.props.children(filterProxy);\n    }\n\n    return super.render();\n  }\n}\n\nRBiquadFilter.propTypes = {\n  children: PropTypes.func\n};\n"
  },
  {
    "path": "src/audio-nodes/buffer-source.js",
    "content": "import React from 'react';\nimport RScheduledSource from './../base/scheduled-source.js';\n\nexport default class RBufferSource extends RScheduledSource {\n  constructor(props) {\n    super(props);\n\n    this.params = {\n      buffer: props.buffer || null,\n      detune: props.detune || 0,\n      loop: props.loop || false,\n      loopStart: props.loopStart || 0,\n      loopEnd: props.loopEnd || 0,\n      playbackRate: props.playbackRate || 1\n    };\n\n    this.onEnded = this.onEnded.bind(this);\n    this.instantiateNode = this.instantiateNode.bind(this);\n  }\n\n  instantiateNode() {\n    if (!this.node) {\n      this.node = this.context.audio.createBufferSource();\n      this.node.addEventListener('ended', this.onEnded);\n\n      this.context.nodes.set(this.props.identifier, this.node);\n    }\n\n    this.updateParams = this.updateParams.bind(this);\n    this.updateParams(this.props);\n  }\n\n  // we need to make a new AudioBufferSourceNode after playback ends\n  onEnded(e) {\n    super.onEnded(e);\n    this.instantiateNode();\n    this.connectToAllDestinations(this.props.destination, this.node);\n    if (this.props.onEnded) this.props.onEnded(e);\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n    this.instantiateNode();\n  }\n\n  componentDidMount() {\n    this.readyToPlay = !!this.props.buffer;\n    super.componentDidMount();\n  }\n\n  shouldStartWithPropsChange(prevProps, currentProps) {\n    return prevProps.buffer !== currentProps.buffer;\n  }\n\n  componentDidUpdate(prevProps, prevState) {\n    this.readyToPlay = !!this.props.buffer;\n    super.componentDidUpdate(prevProps, prevState);\n  }\n}\n"
  },
  {
    "path": "src/audio-nodes/channel-merger.js",
    "content": "import React from 'react';\nimport RConnectableNode from './../base/connectable-node.js';\n\nexport default class RChannelMerger extends RConnectableNode {\n  constructor(props) {\n    super(props);\n\n    this.params = { channelCount: 1 };\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n\n    if (!this.node) {\n      this.node = this.context.audio.createChannelMerger(this.props.channelCount);\n      this.context.nodes.set(this.props.identifier, this.node);\n    }\n\n    this.updateParams = this.updateParams.bind(this);\n    this.updateParams(this.props);\n  }\n}\n"
  },
  {
    "path": "src/audio-nodes/channel-splitter.js",
    "content": "import React from 'react';\nimport RConnectableNode from './../base/connectable-node.js';\n\nexport default class RChannelSplitter extends RConnectableNode {\n  constructor(props) {\n    super(props);\n\n    this.params = { channelCount: 1 };\n  }\n\n  // override of RAudioNode.getConnectionArguments\n  // because we need to have some default many-to-many behaviour\n  getConnectionArguments(destination, destinationIndex, toParam) {\n    const connectTarget = toParam ? destination[toParam] : destination;\n    // we use modulo for channel distribution\n    // in case we're connecting to more nodes than we have channels\n    const fromChannel = destinationIndex % this.props.channelCount;\n    // normally we expect to connect to the first channel of each destination\n    // but this can be overriden\n    const toChannel = !isNaN(this.props.connectToChannel) ? this.props.connectToChannel : 0;\n\n    return [ connectTarget ].concat(toParam ? [] : [ fromChannel, toChannel ]);\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n\n    if (!this.node) {\n      this.node = this.context.audio.createChannelSplitter(this.props.channelCount);\n      this.context.nodes.set(this.props.identifier, this.node);\n    }\n\n    this.updateParams = this.updateParams.bind(this);\n    this.updateParams(this.props);\n  }\n}\n"
  },
  {
    "path": "src/audio-nodes/constant-source.js",
    "content": "import React from 'react';\nimport RScheduledSource from './../base/scheduled-source.js';\n\nexport default class RConstantSource extends RScheduledSource {\n  constructor(props) {\n    super(props);\n\n    this.params = {\n      offset: props.offset\n    };\n\n    this.instantiateNode = this.instantiateNode.bind(this);\n    this.readyToPlay = true;\n    this.onEnded = this.onEnded.bind(this);\n  }\n\n  onEnded(e) {\n    super.onEnded(e);\n    if (this.props.onEnded) this.props.onEnded(e);\n  }\n\n  instantiateNode() {\n    if (!this.node || this.playbackScheduled === false) {\n      this.node = this.context.audio.createConstantSource();\n      this.node.addEventListener('ended', this.onEnded);\n\n      this.context.nodes.set(this.props.identifier, this.node);\n    }\n\n    this.updateParams = this.updateParams.bind(this);\n    this.updateParams(this.props);\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n    this.instantiateNode();\n  }\n}\n"
  },
  {
    "path": "src/audio-nodes/convolver.js",
    "content": "import React from 'react';\nimport RConnectableNode from './../base/connectable-node.js';\n\nexport default class RConvolver extends RConnectableNode {\n  constructor(props) {\n    super(props);\n\n    this.params = {\n      buffer: props.buffer || null,\n      normalize: props.normalize || true\n    };\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n\n    if (!this.node) {\n      this.node = this.context.audio.createConvolver();\n      this.context.nodes.set(this.props.identifier, this.node);\n    }\n\n    this.updateParams = this.updateParams.bind(this);\n    this.updateParams(this.props);\n  }\n}\n"
  },
  {
    "path": "src/audio-nodes/delay.js",
    "content": "import React from 'react';\nimport RConnectableNode from './../base/connectable-node.js';\n\nexport default class RDelay extends RConnectableNode {\n  constructor(props) {\n    super(props);\n\n    this.params = {\n      delayTime: props.delayTime\n    };\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n\n    if (!this.node) {\n      this.node = this.context.audio.createDelay();\n      this.context.nodes.set(this.props.identifier, this.node);\n    }\n\n    this.updateParams = this.updateParams.bind(this);\n    this.updateParams(this.props);\n  }\n}\n"
  },
  {
    "path": "src/audio-nodes/dynamics-compressor.js",
    "content": "import React from 'react';\nimport RConnectableNode from './../base/connectable-node.js';\n\nexport default class RDynamicsCompressor extends RConnectableNode {\n  constructor(props) {\n    super(props);\n\n    this.params = {\n      threshold: props.threshold || -24,\n      knee: props.knee || 30,\n      ratio: props.ratio || 12,\n      attack: props.attack || 0.003,\n      release: props.release || 0.25\n    };\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n\n    if (!this.node) {\n      this.node = this.context.audio.createDynamicsCompressor();\n      this.context.nodes.set(this.props.identifier, this.node);\n    }\n\n    this.updateParams = this.updateParams.bind(this);\n    this.updateParams(this.props);\n  }\n}\n"
  },
  {
    "path": "src/audio-nodes/gain.js",
    "content": "import React from 'react';\nimport RConnectableNode from './../base/connectable-node.js';\n\nexport default class RGain extends RConnectableNode {\n  constructor(props) {\n    super(props);\n\n    this.params = {\n      gain: this.props.gain\n    };\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n\n    if (!this.node) {\n      this.node = this.context.audio.createGain();\n      this.context.nodes.set(this.props.identifier, this.node);\n    }\n\n    this.updateParams = this.updateParams.bind(this);\n    this.updateParams(this.props);\n  }\n}\n"
  },
  {
    "path": "src/audio-nodes/iir-filter.js",
    "content": "import React from 'react';\nimport RConnectableNode from './../base/connectable-node.js';\nimport PropTypes from 'prop-types';\n\nexport default class RIIRFilter extends RConnectableNode {\n  constructor(props) {\n    super(props);\n\n    this.params = {};\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n\n    if (!this.node) {\n      this.node = this.context.audio.createIIRFilter({\n        feedback: this.props.feedback,\n        feedforward: this.props.feedforward\n      });\n\n      this.context.nodes.set(this.props.identifier, this.node);\n    }\n\n    this.updateParams = this.updateParams.bind(this);\n    this.updateParams(this.props);\n  }\n\n  render() {\n    if (typeof this.props.children === 'function') {\n      const filterProxy = Object.freeze({\n        getFrequencyResponse: (frequencyHz, magResponse, phaseResponse) => {\n          return this.node.getFrequencyResponse(frequencyHz, magResponse, phaseResponse);\n        }\n      });\n\n      this.props.children(filterProxy);\n    }\n\n    return super.render();\n  }\n}\n\nRIIRFilter.propTypes = {\n  children: PropTypes.func\n};\n"
  },
  {
    "path": "src/audio-nodes/index.js",
    "content": "import RAnalyser from './analyser.js';\nimport RAudioWorklet from './audio-worklet.js';\nimport RBiquadFilter from './biquad-filter.js';\nimport RBufferSource from './buffer-source.js';\nimport RChannelMerger from './channel-merger.js';\nimport RChannelSplitter from './channel-splitter.js';\nimport RConstantSource from './constant-source.js';\nimport RConvolver from './convolver.js';\nimport RDelay from './delay.js';\nimport RDynamicsCompressor from './dynamics-compressor.js';\nimport RGain from './gain.js';\nimport RIIRFilter from './iir-filter.js';\nimport RMediaElementSource from './media-element-source.js';\nimport RMediaStreamSource from './media-stream-source.js';\nimport ROscillator from './oscillator.js';\nimport RPanner from './panner.js';\nimport RStereoPanner from './stereo-panner.js';\nimport RWaveShaper from './wave-shaper.js';\n\nexport {\n  RAnalyser,\n  RAudioWorklet,\n  RBiquadFilter,\n  RBufferSource,\n  RChannelMerger,\n  RChannelSplitter,\n  RConstantSource,\n  RConvolver,\n  RDelay,\n  RDynamicsCompressor,\n  RGain,\n  RIIRFilter,\n  RMediaElementSource,\n  RMediaStreamSource,\n  ROscillator,\n  RStereoPanner,\n  RPanner,\n  RWaveShaper\n};\n"
  },
  {
    "path": "src/audio-nodes/media-element-source.js",
    "content": "import React from 'react';\nimport RAudioNode from './../base/audio-node.js';\n\nexport default class RMediaElementSource extends RAudioNode {\n  constructor(props) {\n    super(props);\n\n    this.params = {};\n\n    this.createNode = this.createNode.bind(this);\n  }\n\n  createNode() {\n    this.node = this.context.audio.createMediaElementSource(this.props.element);\n    this.context.nodes.set(this.props.identifier, this.node);\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n\n    if (!this.node) {\n      this.createNode();\n    }\n\n    this.updateParams = this.updateParams.bind(this);\n    this.updateParams(this.props);\n  }\n\n  componentWillReceiveProps(nextProps) {\n    if (nextProps.element !== this.props.element) this.createNode();\n  }\n}\n"
  },
  {
    "path": "src/audio-nodes/media-stream-source.js",
    "content": "import React from 'react';\nimport RAudioNode from './../base/audio-node.js';\n\nexport default class RMediaStreamSource extends RAudioNode {\n  constructor(props) {\n    super(props);\n\n    this.params = {\n      buffer: props.buffer || null\n    };\n\n    this.createNode = this.createNode.bind(this);\n  }\n\n  createNode() {\n    this.node = this.context.audio.createMediaStreamSource(this.props.stream);\n    this.context.nodes.set(this.props.identifier, this.node);\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n\n    if (!this.node) {\n      this.createNode();\n    }\n\n    this.updateParams = this.updateParams.bind(this);\n    this.updateParams(this.props);\n  }\n\n  componentWillReceiveProps(nextProps) {\n    if (nextProps.stream !== this.props.stream) this.createNode();\n  }\n}\n"
  },
  {
    "path": "src/audio-nodes/oscillator.js",
    "content": "import React from 'react';\nimport RScheduledSource from './../base/scheduled-source.js';\n\nexport default class ROscillator extends RScheduledSource {\n  constructor(props) {\n    super(props);\n\n    this.params = {\n      frequency: props.frequency,\n      detune: props.detune,\n      type: props.type,\n      periodicWave: props.periodicWave\n    };\n\n    this.instantiateNode = this.instantiateNode.bind(this);\n    this.readyToPlay = true;\n    this.onEnded = this.onEnded.bind(this);\n  }\n\n  onEnded(e) {\n    super.onEnded(e);\n    if (this.props.onEnded) this.props.onEnded(e);\n  }\n\n  instantiateNode() {\n    if (!this.node || this.playbackScheduled === false) {\n      this.node = this.context.audio.createOscillator();\n      this.node.addEventListener('ended', this.onEnded);\n\n      if (this.props.periodicWave) {\n        this.node.setPeriodicWave(this.props.periodicWave);\n      }\n\n      this.context.nodes.set(this.props.identifier, this.node);\n    }\n\n    this.updateParams = this.updateParams.bind(this);\n    this.updateParams(this.props);\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n    this.instantiateNode();\n  }\n\n  componentWillReceiveProps(nextProps) {\n    super.componentWillReceiveProps(nextProps);\n\n    if (this.props.periodicWave !== nextProps.periodicWave) {\n      this.node.setPeriodicWave(nextProps.periodicWave);\n    }\n  }\n}\n"
  },
  {
    "path": "src/audio-nodes/panner.js",
    "content": "import React from 'react';\nimport RConnectableNode from './../base/connectable-node.js';\n\nexport default class RPanner extends RConnectableNode {\n  constructor(props) {\n    super(props);\n\n    this.params = {\n      panningModel: this.props.panningModel,\n      distanceModel: this.props.distanceModel,\n      refDistance: this.props.refDistance,\n      maxDistance: this.props.maxDistance,\n      rolloffFactor: this.props.rolloffFactor,\n      coneInnerAngle: this.props.coneInnerAngle,\n      coneOuterAngle: this.props.coneOuterAngle,\n      coneOuterGain: this.props.coneOuterGain,\n      positionX: this.props.positionX,\n      positionY: this.props.positionY,\n      positionZ: this.props.positionZ,\n      orientationX: this.props.orientationX,\n      orientationY: this.props.orientationY,\n      orientationZ: this.props.orientationZ\n    };\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n\n    if (!this.node) {\n      this.node = this.context.audio.createPanner();\n      this.context.nodes.set(this.props.identifier, this.node);\n    }\n\n    this.updateParams = this.updateParams.bind(this);\n    this.updateParams(this.props);\n  }\n}\n"
  },
  {
    "path": "src/audio-nodes/stereo-panner.js",
    "content": "import React from 'react';\nimport RConnectableNode from './../base/connectable-node.js';\n\nexport default class RStereoPanner extends RConnectableNode {\n  constructor(props) {\n    super(props);\n\n    this.params = {\n      pan: props.pan\n    };\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n\n    const props = this.props;\n\n    if (!this.node) {\n      this.node = this.context.audio.createStereoPanner();\n      this.context.nodes.set(this.props.identifier, this.node);\n    }\n\n    this.updateParams = this.updateParams.bind(this);\n    this.updateParams(this.props);\n  }\n}\n"
  },
  {
    "path": "src/audio-nodes/wave-shaper.js",
    "content": "import React from 'react';\nimport RConnectableNode from './../base/connectable-node.js';\n\nexport default class RWaveShaper extends RConnectableNode {\n  constructor(props) {\n    super(props);\n\n    this.params = {\n      curve: props.curve || null,\n      oversample: props.oversample || 'none'\n    };\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n\n    if (!this.node) {\n      this.node = this.context.audio.createWaveShaper();\n      this.context.nodes.set(this.props.identifier, this.node);\n    }\n\n    this.updateParams = this.updateParams.bind(this);\n    this.updateParams(this.props);\n  }\n}\n"
  },
  {
    "path": "src/base/audio-context.js",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport RComponent from './component.js';\n\nwindow.AudioContext = window.AudioContext || window.webkitAudioContext || null;\nif (!window.AudioContext) {\n  throw new Error(\n    'Could not find AudioContext. This may be because your browser does not support Web Audio.');\n}\n\n/**\n * Contains and manages the Web Audio graph.\n * All immediate children connect directly to its Destination.\n *\n * @class      RAudioContext (name)\n */\nexport default class RAudioContext extends React.Component {\n  constructor(props) {\n    super(props);\n    // repository of all nodes in the graph\n    // keyed by Symbols\n    this.nodes = new Map();\n\n    this._context = new AudioContext(props.options);\n\n    if (this.props.onInit) this.props.onInit(this._context);\n\n    if (this.props.debug) {\n      window.RAudioNodeMap = this.nodes;\n    }\n  }\n\n  componentWillMount() {\n    this._context.resume();\n  }\n\n  getChildContext() {\n    return {\n      audio: this._context,\n      debug: this.props.debug,\n      nodes: this.nodes\n    };\n  }\n\n  componentWillUnmount() {\n    this._context.suspend();\n  }\n\n  render() {\n    const children = React.Children\n      .toArray(this.props.children)\n      .map(child => {\n        if (!RComponent.isPrototypeOf(child.type)) return child;\n\n        const audioContextProps = {\n          destination: () => this._context.destination,\n          identifier: Symbol(child.type.name)\n        };\n\n        return React.cloneElement(child, audioContextProps);\n      });\n\n    if (this.props.debug) {\n      return (\n        <div>\n          <strong>RAudioContext</strong>\n          <ul>\n            {children}\n          </ul>\n        </div>\n      );\n    }\n\n    return children || [];\n  }\n}\n\nRAudioContext.childContextTypes = {\n  audio: PropTypes.instanceOf(AudioContext),\n  nodes: PropTypes.instanceOf(Map),\n  debug: PropTypes.bool\n};\n"
  },
  {
    "path": "src/base/audio-node.js",
    "content": "import React from 'react';\nimport RComponent from './component.js';\n\n/**\n * Any RComponent that corresponds to an AudioNode is a RAudioNode\n *\n * @class      RAudioNode (name)\n */\nexport default class RAudioNode extends RComponent {\n  constructor(props) {\n    super(props);\n    // internal AudioNode instance\n    this.node = null;\n    // dictionary of AudioNode parameters (either AudioParams or object properties)\n    this.params = {};\n    this.connectToAllDestinations = this.connectToAllDestinations.bind(this);\n    this.setParam = this.setParam.bind(this);\n  }\n\n  // recursively builds up a list of nodes pointed to by IDs or lists of IDs\n  flattenPointers(destinations, flattened = []) {\n    for (let element of destinations) {\n      if (Array.isArray(element)) {\n        this.flattenPointers(element, flattened);\n      } else if (typeof element === 'symbol') {\n        flattened.push(this.context.nodes.get(element));\n      } else {\n        flattened.push(element);\n      }\n    }\n\n    return flattened;\n  }\n\n  /**\n   * Generates arguments for AudioNode.connect\n   * Useful because we can, for instance, override the channel assignment logic for ChannelSplitter etc.\n   *\n   * @param      {function} destination The AudioNode to connect to\n   * @param      {number} destinationIndex The index of the AudioNode among other destinations\n   * @param      {string|null} toParam The name of the AudioParam to connect to (or undefined)\n   * @param      {number} fromChannel The index of the chosen output channel of this node (default is 0)\n   * @param      {number} toChannel The index of the chosen input channel of the destination node (default is 0)\n   */\n  getConnectionArguments(destination, destinationIndex, toParam, fromChannel = 0, toChannel = 0) {\n    const connectTarget = toParam ? destination[toParam] : destination;\n\n    return [ connectTarget ].concat(toParam ? [] : [ fromChannel, toChannel ]);\n  }\n\n  /**\n   * Connects the given AudioNode to this RAudioNode's destinations.\n   * Abstracts away this operation as it's used in multiple lifecycle stages.\n   *\n   * @param      {function} destinationFunction The function that will return the destinations\n   * @param      {AudioNode}  webAudioNode  The web audio node\n   */\n  connectToAllDestinations(destinationFunction, webAudioNode) {\n    webAudioNode.disconnect();\n\n    if (destinationFunction && !this.props.disconnected) {\n      let destinations = destinationFunction();\n\n      if (!(destinations instanceof Array)) destinations = [ destinations ];\n\n      this.flattenPointers(destinations).forEach((destination, di) => {\n        if (destination) {\n          const connectArgs = this.getConnectionArguments(\n            destination,\n            di,\n            this.props.connectToParam,\n            this.props.connectFromChannel,\n            this.props.connectToChannel);\n\n          webAudioNode.connect(...connectArgs);\n        }\n      });\n    }\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n  }\n\n  componentWillReceiveProps(nextProps) {\n    this.updateParams(nextProps);\n  }\n\n  componentWillUpdate(nextProps, nextState) {\n    // update the node's record in the node registry\n    if (this.props.identifier !== nextProps.identifier) {\n      this.context.nodes.delete(this.props.identifier);\n      this.context.nodes.set(nextProps.identifier, this.node);\n    }\n  }\n\n  // we use DidUpdate to connect to new destinations,\n  // because WillUpdate might get called before the new destinations are ready\n  componentDidUpdate(prevProps, prevState) {\n    if (prevProps.destination !== this.props.destination) {\n      this.connectToAllDestinations(this.props.destination, this.node);\n    }\n  }\n\n  componentWillUnmount() {\n    this.node.disconnect();\n    this.context.nodes.delete(this.props.identifier);\n  }\n\n  resolveTransitionProps(props, propName) {\n    const transitionTime = typeof props.transitionTime === 'number'\n      ? props.transitionTime\n      : props.transitionTime ? props.transitionTime[propName] : null;\n\n    const transitionCurve = typeof props.transitionCurve === 'string'\n      ? props.transitionCurve\n      : props.transitionCurve ? props.transitionCurve[propName] : null;\n\n    return [ transitionTime, transitionCurve ];\n  }\n\n  // updates only Web Audio-related parameters\n  // (both AudioParams and regular properties)\n  updateParams(props) {\n    if (!this.params) return;\n\n    for (let p in this.params) {\n      if (!(p in props)) continue;\n\n      const [ transitionTime, transitionCurve ] = this.resolveTransitionProps(props, p);\n\n      if (this.node[p] instanceof AudioParam) {\n        this.setParam(this.node[p], props[p], transitionTime, transitionCurve);\n      } else if (this.node.parameters && this.node.parameters.has(p)) {\n        let param = this.node.parameters.get(p);\n        this.setParam(param, props[p], transitionTime, transitionCurve);\n      } else if (p in this.node) {\n        // some browsers (e.g. Chrome) will try to set channelCount and throw an error\n        // since we can't use Object.getOwnPropertyDescriptor on the AudioNodes\n        // we simply wrap the action in a try-catch\n        try {\n          if (this.node[p] !== props[p]) this.node[p] = props[p];\n        } catch(e) {\n          console.warn(`Tried setting ${p} on node`, this.node); // eslint-disable-line no-console\n        }\n      }\n    }\n  }\n\n  setParam(param, value, transitionTime, transitionCurve) {\n    if (transitionCurve) {\n      const fn = `${transitionCurve}RampToValueAtTime`;\n      // `exponentialRamp` doesn't seem to work on Firefox, so fall back to linear\n      try {\n        param[fn](value, transitionTime);\n      } catch (e) {\n        param['linearRampToValueAtTime'](value, transitionTime);\n      }\n    } else {\n      param.setValueAtTime(value, transitionTime || this.context.audio.currentTime);\n    }\n  }\n\n  componentDidMount() {\n    this.connectToAllDestinations(this.props.destination, this.node);\n  }\n\n  render() {\n    if (this.context.debug) {\n      return (\n        <li>\n          <div>\n            <strong>\n              {this.constructor.name} <em>{this.props.name || ''}</em>\n              <sup><mark>{this.props.disconnected && 'disconnected' || ''}</mark></sup>\n            </strong>\n            <div>{ this.props.connectToParam ? <span> connects to <em>{this.props.connectToParam}</em></span> : null }</div>\n          </div>\n          <ul>\n            {\n              Object.keys(this.params).map((p, pi) => {\n                if (!this.props[p] && this.props[p] !== 0) return null;\n\n                let param = this.props[p];\n                if (typeof this.props[p] === 'boolean') param = this.props[p].toString();\n\n                if (!(['number', 'string', 'boolean'].includes(typeof this.props[p]))) {\n                  param = param.constructor.name;\n                }\n\n                return <li key={pi}>{p}: <code>{param}</code></li>;\n              })\n            }\n          </ul>\n        </li>\n      );\n    }\n\n    return null;\n  }\n}\n"
  },
  {
    "path": "src/base/component.js",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\n\n/**\n * Anything that requires an AudioContext is a RComponent\n *\n * @class      RComponent (name)\n */\nexport default class RComponent extends React.Component {\n  componentWillMount() {\n    if (!this.context.audio) throw new ReferenceError('RComponent needs to be a child of a RAudioContext');\n  }\n\n  render() { return null; }\n}\n\nRComponent.contextTypes = {\n  audio: PropTypes.instanceOf(window.AudioContext || window.webkitAudioContext),\n  nodes: PropTypes.instanceOf(Map),\n  debug: PropTypes.bool\n};\n"
  },
  {
    "path": "src/base/connectable-node.js",
    "content": "import React from 'react';\nimport RAudioNode from './audio-node.js';\n\n/**\n * Any RAudioNode that can be connected to is a RConnectableNode\n *\n * @class      RConnectableNode (name)\n */\nexport default class RConnectableNode extends RAudioNode {\n  componentWillUnmount() {\n    super.componentWillUnmount();\n\n    if (this.props.parent) {\n      const parents = this.props.parent();\n\n      this.flattenPointers(parents).forEach((parentIdentifier, parentIndex) => {\n        const parent = this.context.nodes.get(parentIdentifier);\n        if (!parent) return;\n\n        try {\n          parent.disconnect(this.node);\n        } catch (e) {\n          console.warn(e); // eslint-disable-line no-console\n        }\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "src/base/scheduled-source.js",
    "content": "import React from 'react';\nimport RAudioNode from './audio-node.js';\n\n/**\n * Any RAudioNode that can be scheduled to start/end is a RScheduledSource\n *\n * @class      RScheduledSource (name)\n */\nexport default class RScheduledSource extends RAudioNode {\n  constructor(props) {\n    super(props);\n    this.readyToPlay = false;\n    this.playbackScheduled = false;\n\n    this.onEnded = this.onEnded.bind(this);\n    this.schedule = this.schedule.bind(this);\n  }\n\n  onEnded() {\n    this.playbackScheduled = false;\n    // Web Audio will remove the node from the graph after stopping, so reinstantiate it\n    this.instantiateNode();\n    this.connectToAllDestinations(this.props.destination, this.node);\n  }\n\n  schedule() {\n    const shouldScheduleStart =\n      typeof this.props.start === 'number' &&\n      this.readyToPlay &&\n      !this.playbackScheduled &&\n      (typeof this.props.stop !== 'number' || this.props.start < this.props.stop);\n\n    const shouldScheduleStop =\n      typeof this.props.stop === 'number';\n\n    if (shouldScheduleStart) {\n      this.node.start(this.props.start || 0, this.props.offset || 0, this.props.duration);\n      this.playbackScheduled = true;\n    }\n\n    if (shouldScheduleStop) {\n      this.node.stop(this.props.stop);\n    }\n  }\n  /**\n  Overriding this method enables sources to specify special conditions when playback should be rescheduled.\n  e.g. BufferSource should be rescheduled if a new buffer is provided\n  **/\n  shouldStartWithPropsChange() {\n    return false;\n  }\n\n  componentDidMount() {\n    super.componentDidMount();\n    this.schedule();\n  }\n\n  componentDidUpdate(prevProps, prevState) {\n    super.componentDidUpdate(prevProps, prevState);\n\n    if (prevProps.start !== this.props.start ||\n        prevProps.stop !== this.props.stop ||\n        this.shouldStartWithPropsChange(prevProps, this.props)) {\n      this.schedule();\n    }\n  }\n}\n"
  },
  {
    "path": "src/graph/cycle.js",
    "content": "import React from 'react';\nimport RAudioNode from './../base/audio-node.js';\nimport RComponent from './../base/component.js';\n\nimport { isConnectable } from './utils.js';\n\n/**\n * A RComponent which connects each child to itself as well as the destination\n *\n * @class      RCycle (name)\n */\nexport default class RCycle extends RComponent {\n  constructor(props) {\n    super(props);\n    this.inputs = [];\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n    this.context.nodes.set(this.props.identifier, this.inputs);\n  }\n\n  componentWillUpdate(nextProps, nextState) {\n    // update the node's record in the node registry\n    if (this.props.identifier !== nextProps.identifier) {\n      this.context.nodes.delete(this.props.identifier);\n      this.context.nodes.set(nextProps.identifier, this.inputs);\n    }\n  }\n\n  render() {\n    while (this.inputs.length > 0) this.inputs.pop();\n\n    const children = React.Children\n      .toArray(this.props.children)\n      .filter(c => c !== null && c !== [])\n      .map(c => ({ component: c, identifier: Symbol(c.type.name + Date.now()) }))\n      .map((childTuple, childIndex, childrenArray) => {\n        const type = childTuple.component.type;\n        if (RComponent.isPrototypeOf(childTuple.component.type) && isConnectable(childTuple.component)) {\n          this.inputs.push(childTuple.identifier);\n        }\n\n        const pipelineProps = {\n          destination: () => {\n            let destination = this.props.destination();\n\n            const ownNode = this.context.nodes.get(childTuple.identifier);\n            if (!(destination instanceof Array)) destination = [ destination ];\n\n            return destination.concat([ ownNode ]);\n          },\n          identifier: childTuple.identifier\n        };\n\n        return React.cloneElement(childTuple.component, pipelineProps);\n      });\n\n    if (this.inputs.length === 0) {\n      const destination = this.props.destination();\n      if (destination instanceof Array) this.inputs.push(...destination);\n      else this.inputs.push(destination);\n    }\n\n    if (this.context.debug) {\n      return (\n        <li>\n          <strong>RCycle</strong>\n          <ul>\n            {children}\n          </ul>\n        </li>\n      );\n    }\n\n    return children;\n  }\n}\n"
  },
  {
    "path": "src/graph/extensible.js",
    "content": "import React from 'react';\nimport RPipeline from './pipeline.js';\n\n/**\n * A subclass of RPipeline which can be extended to create custom r-audio nodes\n * To create a custom node, subclass RExtensible and override the `renderGraph` method,\n * returning the r-audio graph of your custom node\n *\n * @class      RExtensible (name)\n**/\nexport default class RExtensible extends RPipeline {\n  renderGraph() {\n    return null;\n  }\n\n  addKeys(child, childIndex) {\n    return React.cloneElement(child, { key: childIndex });\n  }\n\n  render() {\n    this.customChildren = [ this.renderGraph() ].map(this.addKeys);\n    return super.render();\n  }\n}\n"
  },
  {
    "path": "src/graph/pipeline.js",
    "content": "import React from 'react';\nimport RComponent from './../base/component.js';\n\nimport { isConnectable } from './utils.js';\n\n/**\n * A RComponent which connects its children in a series, creating inbound branches if necessary.\n *\n * @class      RPipeline (name)\n */\nexport default class RPipeline extends RComponent {\n  constructor(props) {\n    super(props);\n    this.resolveDestination = this.resolveDestination.bind(this);\n    this.resolvePointer = this.resolvePointer.bind(this);\n    this.resolveParent = this.resolveParent.bind(this);\n  }\n\n  /**\n   * Ensures whatever value we get from the `nodes` Map, we resolve it to where the actual AudioNodes are.\n   *\n   * @param      {AudioNode|Array}  pointer  The value found in the `nodes` Map\n   * @return     {AudioNode|Array<AudioNode>}  the actual destination(s)\n   */\n  resolvePointer(pointer) {\n    // we might find that the pointer actually leads us to a list of other pointers (Symbols)\n    // this happens, for instance, if the next child is a RSplit\n    let resolved = pointer;\n\n    if (pointer instanceof Array) {\n      // it could also happen that the pointer leads to an AudioNode reference (esp. if it's an AudioContextDestination)\n      resolved = pointer.map(identifier => this.context.nodes.get(identifier) || identifier);\n    }\n\n    return resolved;\n  }\n\n  /**\n   * Tries to provide a destination getter function for the given index in the children array.\n   * It optimizes for finding the nearest node in the graph which the given child can connect to.\n   *\n   * @param      {number}  currentIndex   The current child's index\n   * @param      {Array}  childrenArray  The array of all children in the pipeline\n   * @return     {Function}  a function which returns the closest possible destination node\n   */\n  resolveDestination(currentIndex, childrenArray) {\n    let destinationFunction = null;\n\n    if (currentIndex === childrenArray.length - 1) {\n      destinationFunction = () => this.props.destination();\n    } else if (!isConnectable(childrenArray[currentIndex + 1].component)) {\n      let childIndex = currentIndex + 1;\n\n      while (childrenArray[++childIndex]) {\n        if (isConnectable(childrenArray[childIndex].component)) break;\n      }\n\n      if (childIndex === currentIndex + 1 || !childrenArray[childIndex]) {\n        destinationFunction = () => this.props.destination();\n      } else {\n        destinationFunction = () => this.resolvePointer(this.context.nodes.get(childrenArray[childIndex].identifier));\n      }\n    } else {\n      destinationFunction = () => this.resolvePointer(this.context.nodes.get(childrenArray[currentIndex + 1].identifier));\n    }\n\n    return destinationFunction;\n  }\n\n  resolveParent(currentIndex, childrenArray) {\n    if (currentIndex === 0) {\n      return this.getParent || null;\n    } else {\n      const children = childrenArray.slice(0, currentIndex);\n      const parents = [];\n\n      let child = children.pop();\n\n      if (RComponent.isPrototypeOf(child.component.type)) {\n        // the first preceding RComponent is always a valid parent\n        parents.push(child.identifier);\n        // if it's a connectable type it's also the only parent\n        if (isConnectable(child.component)) return () => parents;\n        // if not, continue\n        child = children.pop();\n      }\n\n      // look for all preceding RComponents until we hit one which is connectable\n      while (child) {\n        if (isConnectable(child.component) ||\n          !RComponent.isPrototypeOf(child.component.type)) break;\n\n        parents.push(child.identifier);\n        child = children.pop();\n      }\n\n      return () => parents;\n    }\n  }\n\n  /**\n   * Returns an Object containing the given Component and its unique identifier\n   *\n   * @param      {Component}  component  The React component\n   * @return     {Object}  the identified child object\n   */\n  createIdentifiedChild(component) {\n    const identifiedChild = {\n      component,\n      identifier: Symbol(component.type.name + Date.now())\n    };\n\n    if (!this.foundFirstConnectableType && isConnectable(component)) {\n      identifiedChild.identifier = this.props.identifier;\n      this.foundFirstConnectableType = true;\n    }\n\n    return identifiedChild;\n  }\n\n  /**\n   * Returns a clone of the child Component with parent, destination and identifier props added to it\n   *\n   * @param      {Object}  identifiedChild  The identified child object\n   * @param      {Number}  childIndex       The child index\n   * @param      {Array}  childrenArray    The children array\n   * @return     {Component}  A clone of the child Component\n   */\n  createEmbeddableChild(identifiedChild, childIndex, childrenArray) {\n    if (!RComponent.isPrototypeOf(identifiedChild.component.type)) return identifiedChild.component;\n\n    const getDestination = this.resolveDestination(childIndex, childrenArray);\n    const getParent = this.resolveParent(childIndex, childrenArray);\n\n    const pipelineProps = {\n      destination: getDestination,\n      parent: getParent,\n      identifier: identifiedChild.identifier\n    };\n\n    if (childIndex === childrenArray.length - 1) {\n      Object.assign(pipelineProps, {\n        connectFromChannel: this.props.connectFromChannel || 0,\n        connectToChannel: this.props.connectToChannel || 0\n      });\n    }\n\n    return React.cloneElement(identifiedChild.component, pipelineProps);\n  }\n\n  render() {\n    this.foundFirstConnectableType = false;\n\n    const originalChildren = React.Children.toArray(this.props.children);\n    const children = (this.customChildren || originalChildren)\n      .filter(c => c !== null && c !== [])\n      // double mapping because the second functor needs to peek ahead on the children array\n      .map(this.createIdentifiedChild, this)\n      .map(this.createEmbeddableChild, this);\n\n    if (this.context.debug) {\n      return (\n        <li>\n          <strong>{this.constructor.name}</strong>\n          <ul>\n            {children}\n          </ul>\n        </li>\n      );\n    }\n\n    return children;\n  }\n}\n"
  },
  {
    "path": "src/graph/split-channels.js",
    "content": "import React from 'react';\n\nimport RComponent from './../base/component.js';\nimport RSplit from './split.js';\nimport RPipeline from './pipeline.js';\nimport RChannelSplitter from '../audio-nodes/channel-splitter.js';\nimport RChannelMerger from '../audio-nodes/channel-merger.js';\n\n// This is a helper RComponent which splits the input between its children\n// connected in parallel, one channel per branch.\n// It's better to use this instead of RChannelSplitter and RChannelMerger\n// as it takes care of the channel connections automatically\nexport default class RSplitChannels extends RComponent {\n  render() {\n    const children = React.Children\n      .toArray(this.props.children)\n      .slice(0, this.props.channelCount)\n      .map((element, ci) => {\n        const channelProps = {\n          connectFromChannel: 0,\n          connectToChannel: element.props.connectToChannel || ci\n        };\n        return React.cloneElement(element, channelProps);\n      });\n\n    return (\n      <RPipeline identifier={this.props.identifier} destination={this.props.destination}>\n        <RChannelSplitter channelCount={this.props.channelCount} />\n        <RSplit>\n          {children}\n        </RSplit>\n        <RChannelMerger channelCount={this.props.channelCount} />\n      </RPipeline>\n    );\n  }\n}\n"
  },
  {
    "path": "src/graph/split.js",
    "content": "import React from 'react';\nimport RAudioNode from './../base/audio-node.js';\nimport RComponent from './../base/component.js';\n\nimport {\n  isConnectable,\n  propertyFromChildOrParent\n} from './utils.js';\n\n/**\n * A RComponent which connects its children in parallel, creating inbound branches if necessary.\n *\n * @class      RSplit (name)\n */\nexport default class RSplit extends RComponent {\n  constructor(props) {\n    super(props);\n    this.inputs = [];\n  }\n\n  componentWillMount() {\n    super.componentWillMount();\n    this.context.nodes.set(this.props.identifier, this.inputs);\n  }\n\n  componentWillUpdate(nextProps, nextState) {\n    // update the node's record in the node registry\n    if (this.props.identifier !== nextProps.identifier) {\n      this.context.nodes.delete(this.props.identifier);\n      this.context.nodes.set(nextProps.identifier, this.inputs);\n    }\n  }\n\n  render() {\n    while (this.inputs.length) this.inputs.pop();\n\n    const children = React.Children\n      .toArray(this.props.children)\n      .filter(c => c !== null && c !== [])\n      .map(c => ({ component: c, identifier: Symbol(c.type.name + Date.now()) }))\n      .map((childTuple, childIndex, childrenArray) => {\n        if (!RComponent.isPrototypeOf(childTuple.component.type)) return childTuple.component;\n\n        const type = childTuple.component.type;\n        if (RComponent.isPrototypeOf(type) && isConnectable(childTuple.component)) {\n          this.inputs.push(childTuple.identifier);\n        }\n\n        // this rather strange and terse piece of code\n        // figures out the channel connections of the RSplit child\n        // where children can override the parent (RSplit) settings\n        // this is useful for RSplitChannels\n        const [ connectFromChannel, connectToChannel ] =\n          [ 'connectFromChannel', 'connectToChannel' ]\n            .map(propertyFromChildOrParent(childTuple.component, this));\n\n        const splitProps = {\n          destination: this.props.destination,\n          identifier: childTuple.identifier,\n          connectFromChannel,\n          connectToChannel\n        };\n\n        return React.cloneElement(childTuple.component, splitProps);\n      });\n\n    if (!this.inputs.length) {\n      const destination = this.props.destination();\n      if (destination instanceof Array) this.inputs.push(...destination);\n      else this.inputs.push(destination);\n    }\n\n    if (this.context.debug) {\n      return (\n        <li>\n          <strong>RSplit</strong>\n          <ul>\n            {children}\n          </ul>\n        </li>\n      );\n    }\n\n    return children;\n  }\n}\n"
  },
  {
    "path": "src/graph/utils.js",
    "content": "import RConnectableNode from './../base/connectable-node.js';\n\nconst connectableComponents = [\n  'RSplit',\n  'RCycle',\n  'RSplitChannels',\n  'RPipeline'\n];\n\nconst isConnectable = component => {\n  return RConnectableNode.isPrototypeOf(component.type) ||\n      connectableComponents.includes(component.type.name) ||\n      Object.getPrototypeOf(component.type).name === 'RExtensible';\n};\n\n// this rather strange function is used when we want to get a numeric value\n// from either a child or its parent's props\n// but the child takes precedence (for overriding)\nconst propertyFromChildOrParent = (child, parent) => property => {\n  return !isNaN(child.props[property])\n    ? child.props[property]\n    : parent.props[property];\n};\n\nexport {\n  isConnectable,\n  propertyFromChildOrParent\n};\n"
  },
  {
    "path": "webpack.config.js",
    "content": "const webpack = require('webpack');\nconst path = require('path');\nconst UglifyJsPlugin = require('uglifyjs-webpack-plugin');\n\nconst Config = {\n  output: {\n    path: path.resolve(__dirname, 'dist'),\n    filename: 'r-audio.min.js',\n  },\n  entry: './examples/index.js',\n  mode: process.env['NODE_ENV'] || 'production',\n  devtool: process.env['NODE_ENV'] === 'development' ? 'source-map' : false,\n  resolve: {\n    modules: [\n      'node_modules'\n    ]\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.js$|\\.jsx$/,\n        exclude: /node_modules/,\n        use: ['babel-loader?presets[]=react,env', 'eslint-loader?fix=true&emitWarning=true']\n      }\n    ]\n  },\n  devServer: {\n    contentBase: path.join(__dirname, 'examples'),\n    compress: true,\n    port: 8080\n  }\n};\n\nif (!(process.env['NODE_ENV'] === 'development')) {\n  Config.output.library = 'r-audio';\n  Config.output.libraryTarget = 'umd';\n  Config.entry = './index.js';\n  Config.optimization = { minimizer: [ new UglifyJsPlugin() ] };\n  Config.externals = ['react', 'react-dom', 'prop-types'];\n}\n\nmodule.exports = Config;\n"
  }
]