├── .babelrc
├── .eslintignore
├── .eslintrc
├── .flowconfig
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── TODO.md
├── demo
├── demo.js
├── index.css
├── index.js
├── polysynth.js
└── visualization.js
├── dist
├── bundle.js
├── index.css
├── index.html
├── reverb
│ └── room.wav
└── samples
│ ├── cowbell.wav
│ ├── hihat.wav
│ ├── kick.wav
│ └── snare.wav
├── interfaces
├── audio-contour.js
├── note-parser.js
├── tunajs.js
└── uuid.js
├── package.json
├── public
├── index.css
├── index.html
├── reverb
│ └── room.wav
└── samples
│ ├── cowbell.wav
│ ├── hihat.wav
│ ├── kick.wav
│ └── snare.wav
├── src
├── components
│ ├── analyser.js
│ ├── bitcrusher.js
│ ├── bus.js
│ ├── chorus.js
│ ├── compressor.js
│ ├── delay.js
│ ├── filter.js
│ ├── gain.js
│ ├── lfo.js
│ ├── monosynth.js
│ ├── moog-filter.js
│ ├── overdrive.js
│ ├── ping-pong.js
│ ├── reverb.js
│ ├── sampler.js
│ ├── sequencer.js
│ ├── song.js
│ └── synth.js
├── index.js
└── utils
│ ├── buffer-loader.js
│ └── scheduler.js
├── webpack.config.dev.js
├── webpack.config.js
└── webpack.config.umd.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react", "stage-0"],
3 | "plugins": ["transform-flow-strip-types"]
4 | }
5 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /demo/**
2 | /dist/**
3 | /interfaces/**
4 | /lib/**
5 | /node_modules/**
6 | /public/**
7 | /umd/**
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | "extends": "formidable/configurations/es6-react"
2 | "parser": "babel-eslint"
3 | "env":
4 | "browser": true
5 | "rules":
6 | "no-magic-numbers": 0
7 | "react/sort-comp": 0
8 | "comma-dangle": [2, "always-multiline"]
9 | "jsx-quotes": [2, "prefer-double"]
10 | "quotes": [2, "single",{"allowTemplateLiterals": true}]
11 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [libs]
2 | interfaces
3 |
4 | [ignore]
5 | .*/dist/.*
6 | .*/lib/.*
7 | .*/umd/.*
8 | .*/demo/.*
9 | .*/node_modules/flow-bin/.*
10 |
11 | [include]
12 | src
13 |
14 | [options]
15 | esproposal.class_static_fields=enable
16 | esproposal.class_instance_fields=enable
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | lib
4 | umd
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | dist
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | This project adheres to [Semantic Versioning](http://semver.org/).
4 | Every release, along with the migration instructions, is documented on the Github [Releases](https://github.com/FormidableLabs/react-music/releases) page.
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
preact-music
2 |
3 |
4 | Make music with Preact!
5 |
6 |
7 | ***
8 |
9 | 
10 |
11 |
12 |
13 | - [Install](#install)
14 | - [Get Started](#get-started)
15 | - [Basic Concepts](#basic-concepts)
16 | - [Instruments](#instruments)
17 | - [Effects](#effects)
18 | - [Effect Busses](#effect-busses)
19 | - [LFO](#lfo)
20 | - [API](#api)
21 | - [Top Level](#top-level)
22 | - [Intruments](#intruments)
23 | - [Effects](#effects-1)
24 | - [Special](#special)
25 | - [Known Issues & Roadmap](#known-issues--roadmap)
26 | - [License](#license)
27 |
28 |
29 |
30 |
31 | ## Install
32 |
33 | `npm install preact-music`
34 |
35 | ## Get Started
36 |
37 | The easiest way to get started is to clone this repo and run `npm start`. The demo song will be running at [http://localhost:3000](http://localhost:3000). You can open up the `/demo/index.js` file and edit your song there, using the API below as reference.
38 |
39 | That said, you can import the primitives yourself and run your own build setup if you want.
40 |
41 | ## Basic Concepts
42 |
43 | #### Song
44 |
45 | The first thing you want to do is create a `Song` component. This is the controller for your entire beat. It takes a `tempo` prop where you specify a BPM, and an `autoplay` prop that configures whether the song should play right away, or wait to press the play button. Set up it like so:
46 |
47 | ```js
48 |
49 |
50 |
51 | ```
52 |
53 | #### Sequencer
54 |
55 |
56 | Your `Sequencer`'s are what you use to define a looping section. They take two props. The first `resolution` is the resolution of steps in your sequence array. This defaults to `16`, which is a sixteenth note. The second is `bars` which is how many bars the sequencer sequences before it loops. You can have multiple sequencers in your song, and the main Song loop is based upon the sequencer with the largest number of bars. Here is an example:
57 |
58 | ```js
59 |
60 |
61 |
62 |
63 |
64 | ```
65 |
66 | Once you have a `Song` and a `Sequencer` component, you can add instruments to your `Sequencer`. Lets take a look at how these work:
67 |
68 | ## Instruments
69 |
70 | #### Sampler
71 |
72 | The sampler component is used to play audio samples. To use it, you must at very least provide two props, `sample` and `steps`.`sample` is a path to an audio file, and `steps` is an array of indexes that map to the steps available based upon the `resolution` and `bars` props of your sequencer. So if you wanted a 4/4 kick line, you would do this:
73 |
74 | ```js
75 |
76 |
77 |
81 |
82 |
83 | ```
84 |
85 | You can also provide an array for a step, where the second value is a tuning, from -12 to 12.
86 |
87 | #### Synth
88 |
89 | The `Synth` component is used to create an oscillator and play it on steps, just like the `Sampler` does. To use it, you must provide two props, `type` and `steps`. Valid types are `sine`, `square`, `triangle` and `sawtooth`. The `Synth` component also takes an `envelope` prop, where you can specify your ASDR settings. The shape of the `step` prop is a bit different for the `Synth` component, as you must specify an array in the format of `[ step, duration, note || [notes] ]`. The `duration` portion specifies duration in steps. The `note` portion is a string of a musical note and octave like "a4" or "c#1", and for chords, can be an array of the same notes. This would look like:
90 |
91 | ```js
92 |
93 |
94 |
101 |
102 |
103 | ```
104 |
105 | #### Monosynth
106 |
107 | The `Monosynth` component is a `Synth` component, but it only plays one note at a time. It also has a `glide` prop that specifies portamento length. So if two notes overlap, the monosynth glides up to the next value on that duration. Check out how:
108 |
109 | ```js
110 |
111 |
112 |
120 |
121 |
122 | ```
123 |
124 | ## Effects
125 |
126 | There are a ton of new effects added in 1.0.0. You can compose effect chains by wrapping effects around your instruments. Here is an example of how you would do that:
127 |
128 | ```js
129 |
130 |
131 |
132 |
133 |
139 |
140 |
141 |
142 |
143 | ```
144 |
145 | ### Effect Busses
146 |
147 | If you want to define an effects bus, which is a set of effects that multiple instruments can send their output to, this is achieved with the `Bus` component.
148 |
149 | First you want to create a `Bus` component, and give it an identifier:
150 |
151 | ```js
152 |
153 |
154 |
155 | ```
156 |
157 | Next, wrap your bus with the effect chain you want to make available, similarly to the way you would wrap effects around an instrument. You generally want to do this with effects that have wet/dry control, and set the `dryLevel` to 0:
158 |
159 | ```js
160 |
161 |
162 |
163 |
164 |
165 | ```
166 |
167 | Finally, to hook an instrument up to your bus, or several busses, add their id's to the `busses` prop on an instrument:
168 |
169 | ```js
170 |
171 |
172 |
173 |
174 |
179 |
180 | ```
181 |
182 | ## LFO
183 |
184 | You know whats bananas? LFO. Thats what. You can use an oscillator to modify properties of your instruments and effects. This is done with the `LFO` component. Any node that you want to apply LFO to just needs it added as a child. Then you define a `connect` prop that returns a function that lets you select a parent AudioNode property to oscillate. See the following example.
185 |
186 | ```js
187 |
188 |
195 | c.gain}
199 | />
200 |
201 |
202 | ```
203 |
204 | ## API
205 |
206 | ### Top Level
207 |
208 | ---
209 |
210 | #### \
211 |
212 | **autoplay** (_boolean_) : Whether the song should start playing automatically
213 |
214 | **tempo** (_number_) : Your song tempo
215 |
216 | --
217 |
218 | #### \
219 |
220 | **bars** (_number_) : Number of bars in your sequence
221 |
222 | **resolution** (_number_) : Step resolution for your sequence
223 |
224 | ### Intruments
225 |
226 | ---
227 |
228 | #### \
229 |
230 | **busses** (_array_) : An array of `Bus` id strings to send output to
231 |
232 | **envelope** (_object_) : An object specifying envelope settings
233 |
234 | ```js
235 | envelope={{
236 | attack: 0.1,
237 | sustain: 0.3,
238 | decay: 20,
239 | release: 0.5
240 | }}
241 | ```
242 |
243 | **gain** (_number_) : A number specifying instrument gain
244 |
245 | **glide** (_number_) : Portamento length for overlapping notes
246 |
247 | **steps** (_array_) : Array of step arrays for the notes to be played at
248 |
249 | ```js
250 | steps={[
251 | [0, 2, "a2"]
252 | ]}
253 | ```
254 |
255 | **transpose** (_number_) : Positive or negative number for transposition of notes
256 |
257 | **type** (_string_) : Oscillator type. Accepts `square`, `triangle`, `sawtooth` & `sine`
258 |
259 | --
260 |
261 | #### \
262 |
263 | **busses** (_array_) : An array of `Bus` id strings to send output to
264 |
265 | **detune** (_number_) : A number (in cents) specifying instrument detune
266 |
267 | **gain** (_number_) : A number specifying instrument gain
268 |
269 | **sample** (_number_) : Step resolution for your sequence
270 |
271 | **steps** (_array_) : Array of step indexes for the sample to be played at. Accepts arrays for steps in order to provide a second argument for index based detune (in between -12 & 12).
272 |
273 | --
274 |
275 | #### \
276 |
277 | **busses** (_array_) : An array of `Bus` id strings to send output to
278 |
279 | **envelope** (_object_) : An object specifying envelope settings
280 |
281 | ```js
282 | envelope={{
283 | attack: 0.1,
284 | sustain: 0.3,
285 | decay: 20,
286 | release: 0.5
287 | }}
288 | ```
289 |
290 | **gain** (_number_) : A number specifying instrument gain
291 |
292 | **steps** (_array_) : Array of step arrays for the notes to be played at. Accepts in array in the `[ step, duration, note || [notes] ]` format.
293 |
294 | ```js
295 | // single note
296 | steps={[
297 | [0, 2, "a2"]
298 | ]}
299 |
300 | // chord
301 | steps={[
302 | [0, 2, ["c2", "e2", "g2"]]
303 | ]}
304 | ```
305 |
306 | **transpose** (_number_) : Positive or negative number for transposition of notes
307 |
308 | **type** (_string_) : Oscillator type. Accepts `square`, `triangle`, `sawtooth` & `sine`
309 |
310 |
311 | ### Effects
312 |
313 | ---
314 |
315 | #### \
316 |
317 | **bits** (_number_)
318 |
319 | **bufferSize** (_number_)
320 |
321 | **normfreq** (_number_)
322 |
323 |
324 | --
325 |
326 | #### \
327 |
328 | **bypass** (_number_)
329 |
330 | **delay** (_number_)
331 |
332 | **feedback** (_number_)
333 |
334 | **rate** (_number_)
335 |
336 |
337 | --
338 |
339 | #### \
340 |
341 | **attack** (_number_)
342 |
343 | **knee** (_number_)
344 |
345 | **ratio** (_number_)
346 |
347 | **release** (_number_)
348 |
349 | **threshold** (_number_)
350 |
351 |
352 | --
353 |
354 | #### \
355 |
356 | **bypass** (_number_)
357 |
358 | **cutoff** (_number_)
359 |
360 | **delayTime** (_number_)
361 |
362 | **dryLevel** (_number_)
363 |
364 | **feedback** (_number_)
365 |
366 | **wetLevel** (_number_)
367 |
368 |
369 | --
370 |
371 | #### \
372 |
373 | **Q** (_number_)
374 |
375 | **frequency** (_number_)
376 |
377 | **gain** (_number_)
378 |
379 | **type** (_string_)
380 |
381 |
382 | --
383 |
384 | #### \
385 |
386 | **amount** (_number_)
387 |
388 |
389 | --
390 |
391 | #### \
392 |
393 | **bufferSize** (_number_)
394 |
395 | **cutoff** (_number_)
396 |
397 | **resonance** (_number_)
398 |
399 |
400 | --
401 |
402 | #### \
403 |
404 | **algorithmIndex** (_number_)
405 |
406 | **bypass** (_number_)
407 |
408 | **curveAmount** (_number_)
409 |
410 | **drive** (_number_)
411 |
412 | **outputGain** (_number_)
413 |
414 | --
415 |
416 | #### \
417 |
418 | **delayTimeLeft** (_number_)
419 |
420 | **delayTimeRight** (_number_)
421 |
422 | **feedback** (_number_)
423 |
424 | **wetLevel** (_number_)
425 |
426 |
427 | --
428 |
429 | #### \
430 |
431 | **bypass** (_number_)
432 |
433 | **dryLevel** (_number_)
434 |
435 | **highCut** (_number_)
436 |
437 | **impulse** (_string_)
438 |
439 | **level** (_number_)
440 |
441 | **lowCut** (_number_)
442 |
443 | **wetLevel** (_number_)
444 |
445 |
446 | ### Special
447 |
448 | ---
449 |
450 | #### \
451 |
452 | **fftSize** (_number_) : FFT Size value
453 |
454 | **onAudioProcess** (_function_) : Callback function with audio processing data
455 |
456 | **smoothingTimeConstant** (_number_) : Smoothing time constant
457 |
458 | --
459 |
460 | #### \
461 |
462 | **gain** (_number_) : A number specifying Bus gain
463 |
464 | **id** (_string_) : Bus ID
465 |
466 | --
467 |
468 | #### \
469 |
470 | **connect** (_function_) : LFO property selection function
471 |
472 | **frequency** (_number_) : LFO frequency
473 |
474 | **gain** (_number_) : A number specifying LFO gain
475 |
476 | **type** (_string_) : Oscillator type. Accepts `square`, `triangle`, `sawtooth` & `sine`
477 |
478 |
479 | ## Known Issues & Roadmap
480 |
481 | - Currently only the 4/4 time signature is supported
482 | - Hot reloading doesn't work
483 | - `Synth` presets need to be added
484 | - Record/Ouput audio file
485 | - Optional working mixing board alongside viz
486 | - Sampler sample maps
487 |
488 |
489 | ## License
490 |
491 | [MIT License](http://opensource.org/licenses/MIT)
492 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | ## TODO
2 |
3 | - Add Tests
4 | - Add Hot Reload
5 | - Update Docs
--------------------------------------------------------------------------------
/demo/demo.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import {
4 | Analyser,
5 | Song,
6 | Sequencer,
7 | Sampler,
8 | Synth,
9 | } from '../src';
10 |
11 | import Polysynth from './polysynth';
12 | import Visualization from './visualization';
13 |
14 | import './index.css';
15 |
16 | export default class Demo extends Component {
17 | constructor(props) {
18 | super(props);
19 |
20 | this.state = {
21 | playing: true,
22 | };
23 |
24 | this.handleAudioProcess = this.handleAudioProcess.bind(this);
25 | this.handlePlayToggle = this.handlePlayToggle.bind(this);
26 | }
27 | handleAudioProcess(analyser) {
28 | this.visualization.audioProcess(analyser);
29 | }
30 | handlePlayToggle() {
31 | this.setState({
32 | playing: !this.state.playing,
33 | });
34 | }
35 | render() {
36 | return (
37 |
38 |
42 |
43 |
47 |
51 |
55 |
56 |
60 |
77 |
78 |
82 |
92 |
93 |
94 |
95 |
96 |
{ this.visualization = c; }} />
97 |
98 |
103 | {this.state.playing ? 'Stop' : 'Play'}
104 |
105 |
106 | );
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/demo/index.css:
--------------------------------------------------------------------------------
1 | .react-music-canvas {
2 | display: block;
3 | margin: 30px auto 0;
4 | border: 1px solid #aaa;
5 | padding: 10px;
6 | }
7 |
8 | .react-music-button {
9 | display: block;
10 | text-decoration: none;
11 | background-color: #f7f7f7;
12 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f7f7f7), to(#e7e7e7));
13 | background-image: -webkit-linear-gradient(top, #f7f7f7, #e7e7e7);
14 | background-image: -moz-linear-gradient(top, #f7f7f7, #e7e7e7);
15 | background-image: -ms-linear-gradient(top, #f7f7f7, #e7e7e7);
16 | background-image: -o-linear-gradient(top, #f7f7f7, #e7e7e7);
17 | color: #a7a7a7;
18 | margin: 30px auto;
19 | width: 108px;
20 | height: 108px;
21 | position: relative;
22 | text-align: center;
23 | line-height: 108px;
24 | border-radius: 50%;
25 | box-shadow: 0px 3px 8px #aaa, inset 0px 2px 3px #fff;
26 | border: solid 1px transparent;
27 | outline: none;
28 | font-size: 16px;
29 | text-transform: uppercase;
30 | }
31 |
32 | .react-music-button:before {
33 | content: "";
34 | display: block;
35 | background: #fff;
36 | border-top: 2px solid #ddd;
37 | position: absolute;
38 | top: -9px;
39 | left: -9px;
40 | bottom: -9px;
41 | right: -9px;
42 | z-index: -1;
43 | border-radius: 50%;
44 | box-shadow: inset 0px 8px 48px #ddd;
45 | }
46 |
47 | .react-music-button:active {
48 | box-shadow: none;
49 | border: solid 1px #a7a7a7;
50 | }
--------------------------------------------------------------------------------
/demo/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Demo from './demo';
4 |
5 | ReactDOM.render(
6 | ,
7 | document.getElementById('root')
8 | );
9 |
--------------------------------------------------------------------------------
/demo/polysynth.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 |
3 | import {
4 | Delay,
5 | MoogFilter,
6 | Reverb,
7 | Synth,
8 | } from '../src';
9 |
10 | const Polysynth = (props) => (
11 |
12 |
13 |
18 |
19 |
25 |
26 |
27 |
28 | );
29 |
30 | Polysynth.propTypes = {
31 | steps: PropTypes.array,
32 | };
33 |
34 | export default Polysynth;
35 |
--------------------------------------------------------------------------------
/demo/visualization.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | export default class Visualization extends Component {
4 | constructor(props) {
5 | super(props);
6 | this.audioProcess = this.audioProcess.bind(this);
7 | }
8 | componentDidMount() {
9 | this.ctx = this.canvas.getContext('2d');
10 | }
11 | componentDidReceiveProps() {
12 |
13 | }
14 | audioProcess(analyser) {
15 | if (this.ctx) {
16 | const gradient = this.ctx.createLinearGradient(0, 0, 0, 512);
17 | gradient.addColorStop(1, '#000000');
18 | gradient.addColorStop(0.75, '#2ecc71');
19 | gradient.addColorStop(0.25, '#f1c40f');
20 | gradient.addColorStop(0, '#e74c3c');
21 |
22 | const array = new Uint8Array(analyser.frequencyBinCount);
23 | analyser.getByteFrequencyData(array);
24 | this.ctx.clearRect(0, 0, 800, 512);
25 | this.ctx.fillStyle = gradient;
26 |
27 | for (let i = 0; i < (array.length); i++) {
28 | const value = array[i];
29 | this.ctx.fillRect(i * 12, 512, 10, value * -2);
30 | }
31 | }
32 | }
33 | render() {
34 | return (
35 | { this.canvas = c; }}
40 | />
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/dist/bundle.js:
--------------------------------------------------------------------------------
1 | !function(e){function t(o){if(n[o])return n[o].exports;var r=n[o]={exports:{},id:o,loaded:!1};return e[o].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var n={};return t.m=e,t.c=n,t.p="/dist/",t(0)}([function(e,t,n){e.exports=n(8)},function(e,t,n){(function(t){!function(t,o){e.exports=o(n(35),n(33))}(this,function(e,n){function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t,o){var r=t._preactCompatRendered;r&&r.parentNode!==t&&(r=null);var i=n.render(e,t,r);return t._preactCompatRendered=i,"function"==typeof o&&o(),i&&i._component||i.base}function i(e,t,o,i){var a=r(n.h(W,{context:e.context},t),o);return i&&i(a),a}function a(e){var t=e._preactCompatRendered;return!(!t||t.parentNode!==e)&&(n.render(n.h(F),e,t),!0)}function s(e){return f.bind(null,e)}function u(e,t){for(var n=t||0;n2?r-2:0),a=2;ar&&(r=i),o=e%t,r<-100||r>20?(a=Math.round(Math.log(o)/Math.log(10)),s=Math.pow(10,a),(o/s).toFixed(a-r)*s):parseFloat(o.toFixed(-r))}function s(e){return 0===e?1:Math.abs(e)/e}function u(e){return(Math.exp(e)-Math.exp(-e))/(Math.exp(e)+Math.exp(-e))}function c(e,t){return void 0===e?t:e}var l,p,f=function(e,t){e.value=t},h=Object.create(null,{activate:{writable:!0,value:function(e){e?(this.input.disconnect(),this.input.connect(this.activateNode),this.activateCallback&&this.activateCallback(e)):(this.input.disconnect(),this.input.connect(this.output))}},bypass:{get:function(){return this._bypass},set:function(e){this._lastBypassValue!==e&&(this._bypass=e,this.activate(!e),this._lastBypassValue=e)}},connect:{value:function(e){this.output.connect(e)}},disconnect:{value:function(e){this.output.disconnect(e)}},connectInOrder:{value:function(e){for(var t=e.length-1;t--;){if(!e[t].connect)return console.error("AudioNode.connectInOrder: TypeError: Not an AudioNode.",e[t]);e[t+1].input?e[t].connect(e[t+1].input):e[t].connect(e[t+1])}}},getDefaults:{value:function(){var e={};for(var t in this.defaults)e[t]=this.defaults[t].value;return e}},automate:{value:function(e,t,n,o){var r,i=o?~~(o/1e3):l.currentTime,a=n?~~(n/1e3):0,s=this.defaults[e],u=this[e];u?s.automatable?(n?(r="linearRampToValueAtTime",u.cancelScheduledValues(i),u.setValueAtTime(u.value,i)):r="setValueAtTime",u[r](t,a+i)):u=t:console.error("Invalid Property for "+this.name)}}}),d="float",y="boolean",v="string",b="int";"undefined"!=typeof e&&e.exports?e.exports=o:t.define("Tuna",n),o.prototype.Bitcrusher=function(e){e||(e=this.getDefaults()),this.bufferSize=e.bufferSize||this.defaults.bufferSize.value,this.input=l.createGain(),this.activateNode=l.createGain(),this.processor=l.createScriptProcessor(this.bufferSize,1,1),this.output=l.createGain(),this.activateNode.connect(this.processor),this.processor.connect(this.output);var t,n,o,r,i,a=0,s=0;this.processor.onaudioprocess=function(e){for(t=e.inputBuffer.getChannelData(0),n=e.outputBuffer.getChannelData(0),o=Math.pow(.5,this.bits),i=t.length,r=0;r=1&&(a-=1,s=o*Math.floor(t[r]/o+.5)),n[r]=s},this.bits=e.bits||this.defaults.bits.value,this.normfreq=c(e.normfreq,this.defaults.normfreq.value),this.bypass=e.bypass||!1},o.prototype.Bitcrusher.prototype=Object.create(h,{name:{value:"Bitcrusher"},defaults:{writable:!0,value:{bits:{value:4,min:1,max:16,automatable:!1,type:b},bufferSize:{value:4096,min:256,max:16384,automatable:!1,type:b},bypass:{value:!1,automatable:!1,type:y},normfreq:{value:.1,min:1e-4,max:1,automatable:!1,type:d}}},bits:{enumerable:!0,get:function(){return this.processor.bits},set:function(e){this.processor.bits=e}},normfreq:{enumerable:!0,get:function(){return this.processor.normfreq},set:function(e){this.processor.normfreq=e}}}),o.prototype.Cabinet=function(e){e||(e=this.getDefaults()),this.input=l.createGain(),this.activateNode=l.createGain(),this.convolver=this.newConvolver(e.impulsePath||"../impulses/impulse_guitar.wav"),this.makeupNode=l.createGain(),this.output=l.createGain(),this.activateNode.connect(this.convolver.input),this.convolver.output.connect(this.makeupNode),this.makeupNode.connect(this.output),this.makeupGain=c(e.makeupGain,this.defaults.makeupGain),this.bypass=e.bypass||!1},o.prototype.Cabinet.prototype=Object.create(h,{name:{value:"Cabinet"},defaults:{writable:!0,value:{makeupGain:{value:1,min:0,max:20,automatable:!0,type:d},bypass:{value:!1,automatable:!1,type:y}}},makeupGain:{enumerable:!0,get:function(){return this.makeupNode.gain},set:function(e){this.makeupNode.gain.value=e}},newConvolver:{value:function(e){return new p.Convolver({impulse:e,dryLevel:0,wetLevel:1})}}}),o.prototype.Chorus=function(e){e||(e=this.getDefaults()),this.input=l.createGain(),this.attenuator=this.activateNode=l.createGain(),this.splitter=l.createChannelSplitter(2),this.delayL=l.createDelay(),this.delayR=l.createDelay(),this.feedbackGainNodeLR=l.createGain(),this.feedbackGainNodeRL=l.createGain(),this.merger=l.createChannelMerger(2),this.output=l.createGain(),this.lfoL=new p.LFO({target:this.delayL.delayTime,callback:f}),this.lfoR=new p.LFO({target:this.delayR.delayTime,callback:f}),this.input.connect(this.attenuator),this.attenuator.connect(this.output),this.attenuator.connect(this.splitter),this.splitter.connect(this.delayL,0),this.splitter.connect(this.delayR,1),this.delayL.connect(this.feedbackGainNodeLR),this.delayR.connect(this.feedbackGainNodeRL),this.feedbackGainNodeLR.connect(this.delayR),this.feedbackGainNodeRL.connect(this.delayL),this.delayL.connect(this.merger,0,0),this.delayR.connect(this.merger,0,1),this.merger.connect(this.output),this.feedback=c(e.feedback,this.defaults.feedback.value),this.rate=c(e.rate,this.defaults.rate.value),this.delay=c(e.delay,this.defaults.delay.value),this.depth=c(e.depth,this.defaults.depth.value),this.lfoR.phase=Math.PI/2,this.attenuator.gain.value=.6934,this.lfoL.activate(!0),this.lfoR.activate(!0),this.bypass=e.bypass||!1},o.prototype.Chorus.prototype=Object.create(h,{name:{value:"Chorus"},defaults:{writable:!0,value:{feedback:{value:.4,min:0,max:.95,automatable:!1,type:d},delay:{value:.0045,min:0,max:1,automatable:!1,type:d},depth:{value:.7,min:0,max:1,automatable:!1,type:d},rate:{value:1.5,min:0,max:8,automatable:!1,type:d},bypass:{value:!1,automatable:!1,type:y}}},delay:{enumerable:!0,get:function(){return this._delay},set:function(e){this._delay=2e-4*(2*Math.pow(10,e)),this.lfoL.offset=this._delay,this.lfoR.offset=this._delay,this._depth=this._depth}},depth:{enumerable:!0,get:function(){return this._depth},set:function(e){this._depth=e,this.lfoL.oscillation=this._depth*this._delay,this.lfoR.oscillation=this._depth*this._delay}},feedback:{enumerable:!0,get:function(){return this._feedback},set:function(e){this._feedback=e,this.feedbackGainNodeLR.gain.value=this._feedback,this.feedbackGainNodeRL.gain.value=this._feedback}},rate:{enumerable:!0,get:function(){return this._rate},set:function(e){this._rate=e,this.lfoL.frequency=this._rate,this.lfoR.frequency=this._rate}}}),o.prototype.Compressor=function(e){e||(e=this.getDefaults()),this.input=l.createGain(),this.compNode=this.activateNode=l.createDynamicsCompressor(),this.makeupNode=l.createGain(),this.output=l.createGain(),this.compNode.connect(this.makeupNode),this.makeupNode.connect(this.output),this.automakeup=c(e.automakeup,this.defaults.automakeup.value),this.makeupGain=c(e.makeupGain,this.defaults.makeupGain.value),this.threshold=c(e.threshold,this.defaults.threshold.value),this.release=c(e.release,this.defaults.release.value),this.attack=c(e.attack,this.defaults.attack.value),this.ratio=e.ratio||this.defaults.ratio.value,this.knee=c(e.knee,this.defaults.knee.value),this.bypass=e.bypass||!1},o.prototype.Compressor.prototype=Object.create(h,{name:{value:"Compressor"},defaults:{writable:!0,value:{threshold:{value:-20,min:-60,max:0,automatable:!0,type:d},release:{value:250,min:10,max:2e3,automatable:!0,type:d},makeupGain:{value:1,min:1,max:100,automatable:!0,type:d},attack:{value:1,min:0,max:1e3,automatable:!0,type:d},ratio:{value:4,min:1,max:50,automatable:!0,type:d},knee:{value:5,min:0,max:40,automatable:!0,type:d},automakeup:{value:!1,automatable:!1,type:y},bypass:{value:!1,automatable:!1,type:y}}},computeMakeup:{value:function(){var e=4,t=this.compNode;return-(t.threshold.value-t.threshold.value/t.ratio.value)/e}},automakeup:{enumerable:!0,get:function(){return this._automakeup},set:function(e){this._automakeup=e,this._automakeup&&(this.makeupGain=this.computeMakeup())}},threshold:{enumerable:!0,get:function(){return this.compNode.threshold},set:function(e){this.compNode.threshold.value=e,this._automakeup&&(this.makeupGain=this.computeMakeup())}},ratio:{enumerable:!0,get:function(){return this.compNode.ratio},set:function(e){this.compNode.ratio.value=e,this._automakeup&&(this.makeupGain=this.computeMakeup())}},knee:{enumerable:!0,get:function(){return this.compNode.knee},set:function(e){this.compNode.knee.value=e,this._automakeup&&(this.makeupGain=this.computeMakeup())}},attack:{enumerable:!0,get:function(){return this.compNode.attack},set:function(e){this.compNode.attack.value=e/1e3}},release:{enumerable:!0,get:function(){return this.compNode.release},set:function(e){this.compNode.release.value=e/1e3}},makeupGain:{enumerable:!0,get:function(){return this.makeupNode.gain},set:function(e){this.makeupNode.gain.value=i(e)}}}),o.prototype.Convolver=function(e){e||(e=this.getDefaults()),this.input=l.createGain(),this.activateNode=l.createGain(),this.convolver=l.createConvolver(),this.dry=l.createGain(),this.filterLow=l.createBiquadFilter(),this.filterHigh=l.createBiquadFilter(),this.wet=l.createGain(),this.output=l.createGain(),this.activateNode.connect(this.filterLow),this.activateNode.connect(this.dry),this.filterLow.connect(this.filterHigh),this.filterHigh.connect(this.convolver),this.convolver.connect(this.wet),this.wet.connect(this.output),this.dry.connect(this.output),this.dryLevel=c(e.dryLevel,this.defaults.dryLevel.value),this.wetLevel=c(e.wetLevel,this.defaults.wetLevel.value),this.highCut=e.highCut||this.defaults.highCut.value,this.buffer=e.impulse||"../impulses/ir_rev_short.wav",this.lowCut=e.lowCut||this.defaults.lowCut.value,this.level=c(e.level,this.defaults.level.value),this.filterHigh.type="lowpass",this.filterLow.type="highpass",this.bypass=e.bypass||!1},o.prototype.Convolver.prototype=Object.create(h,{name:{value:"Convolver"},defaults:{writable:!0,value:{highCut:{value:22050,min:20,max:22050,automatable:!0,type:d},lowCut:{value:20,min:20,max:22050,automatable:!0,type:d},dryLevel:{value:1,min:0,max:1,automatable:!0,type:d},wetLevel:{value:1,min:0,max:1,automatable:!0,type:d},level:{value:1,min:0,max:1,automatable:!0,type:d}}},lowCut:{get:function(){return this.filterLow.frequency},set:function(e){this.filterLow.frequency.value=e}},highCut:{get:function(){return this.filterHigh.frequency},set:function(e){this.filterHigh.frequency.value=e}},level:{get:function(){return this.output.gain},set:function(e){this.output.gain.value=e}},dryLevel:{get:function(){return this.dry.gain},set:function(e){this.dry.gain.value=e}},wetLevel:{get:function(){return this.wet.gain},set:function(e){this.wet.gain.value=e}},buffer:{enumerable:!1,get:function(){return this.convolver.buffer},set:function(e){var t=this.convolver,n=new XMLHttpRequest;return e?(n.open("GET",e,!0),n.responseType="arraybuffer",n.onreadystatechange=function(){4===n.readyState&&(n.status<300&&n.status>199||302===n.status)&&l.decodeAudioData(n.response,function(e){t.buffer=e},function(e){e&&console.log("Tuna.Convolver.setBuffer: Error decoding data"+e)})},void n.send(null)):void console.log("Tuna.Convolver.setBuffer: Missing impulse path!")}}}),o.prototype.Delay=function(e){e||(e=this.getDefaults()),this.input=l.createGain(),this.activateNode=l.createGain(),this.dry=l.createGain(),this.wet=l.createGain(),this.filter=l.createBiquadFilter(),this.delay=l.createDelay(),this.feedbackNode=l.createGain(),this.output=l.createGain(),this.activateNode.connect(this.delay),this.activateNode.connect(this.dry),this.delay.connect(this.filter),this.filter.connect(this.feedbackNode),this.feedbackNode.connect(this.delay),this.feedbackNode.connect(this.wet),this.wet.connect(this.output),this.dry.connect(this.output),this.delayTime=e.delayTime||this.defaults.delayTime.value,this.feedback=c(e.feedback,this.defaults.feedback.value),this.wetLevel=c(e.wetLevel,this.defaults.wetLevel.value),this.dryLevel=c(e.dryLevel,this.defaults.dryLevel.value),this.cutoff=e.cutoff||this.defaults.cutoff.value,this.filter.type="lowpass",this.bypass=e.bypass||!1},o.prototype.Delay.prototype=Object.create(h,{name:{value:"Delay"},defaults:{writable:!0,value:{delayTime:{value:100,min:20,max:1e3,automatable:!1,type:d},feedback:{value:.45,min:0,max:.9,automatable:!0,type:d},cutoff:{value:2e4,min:20,max:2e4,automatable:!0,type:d},wetLevel:{value:.5,min:0,max:1,automatable:!0,type:d},dryLevel:{value:1,min:0,max:1,automatable:!0,type:d}}},delayTime:{enumerable:!0,get:function(){return this.delay.delayTime},set:function(e){this.delay.delayTime.value=e/1e3}},wetLevel:{enumerable:!0,get:function(){return this.wet.gain},set:function(e){this.wet.gain.value=e}},dryLevel:{enumerable:!0,get:function(){return this.dry.gain},set:function(e){this.dry.gain.value=e}},feedback:{enumerable:!0,get:function(){return this.feedbackNode.gain},set:function(e){this.feedbackNode.gain.value=e}},cutoff:{enumerable:!0,get:function(){return this.filter.frequency},set:function(e){this.filter.frequency.value=e}}}),o.prototype.Filter=function(e){e||(e=this.getDefaults()),this.input=l.createGain(),this.activateNode=l.createGain(),this.filter=l.createBiquadFilter(),this.output=l.createGain(),this.activateNode.connect(this.filter),this.filter.connect(this.output),this.frequency=e.frequency||this.defaults.frequency.value,this.Q=e.resonance||this.defaults.Q.value,this.filterType=c(e.filterType,this.defaults.filterType.value),this.gain=c(e.gain,this.defaults.gain.value),this.bypass=e.bypass||!1},o.prototype.Filter.prototype=Object.create(h,{name:{value:"Filter"},defaults:{writable:!0,value:{frequency:{value:800,min:20,max:22050,automatable:!0,type:d},Q:{value:1,min:.001,max:100,automatable:!0,type:d},gain:{value:0,min:-40,max:40,automatable:!0,type:d},bypass:{value:!1,automatable:!1,type:y},filterType:{value:"lowpass",automatable:!1,type:v}}},filterType:{enumerable:!0,get:function(){return this.filter.type},set:function(e){this.filter.type=e}},Q:{enumerable:!0,get:function(){return this.filter.Q},set:function(e){this.filter.Q.value=e}},gain:{enumerable:!0,get:function(){return this.filter.gain},set:function(e){this.filter.gain.value=e}},frequency:{enumerable:!0,get:function(){return this.filter.frequency},set:function(e){this.filter.frequency.value=e}}}),o.prototype.MoogFilter=function(e){e||(e=this.getDefaults()),this.bufferSize=e.bufferSize||this.defaults.bufferSize.value,this.input=l.createGain(),this.activateNode=l.createGain(),this.processor=l.createScriptProcessor(this.bufferSize,1,1),this.output=l.createGain(),this.activateNode.connect(this.processor),this.processor.connect(this.output);var t,n,o,r,i,a,s,u;t=n=o=r=i=a=s=u=0;var p,f,h,d,y,v,b;this.processor.onaudioprocess=function(e){for(p=e.inputBuffer.getChannelData(0),f=e.outputBuffer.getChannelData(0),h=1.16*this.cutoff,b=.35013*(h*h)*(h*h),d=this.resonance*(1-.15*h*h),v=p.length,y=0;y=0?5.8:1.2,n[o]=u(i)},function(e,t,n){var o,r,i,a=1-e;for(o=0;o.99?.99:1-e;for(o=0;ou?i=u+(a-u)/(1+Math.pow((a-u)/(1-u),2)):a>1&&(i=a),n[o]=s(r)*i*(1/((u+1)/2))},function(e,t,n){var o,r;for(o=0;o=-.08905&&r<.320018?n[o]=-6.153*(r*r)+3.9375*r:n[o]=.630035},function(e,t,n){var o,r,i=2+Math.round(14*e),a=Math.round(Math.pow(2,i-1));for(o=0;o1?1:e<0?0:e,this._sensitivity),this.setFilterFreq()}},baseFrequency:{enumerable:!0,get:function(){return this._baseFrequency},set:function(e){this._baseFrequency=50*Math.pow(10,2*e),this._excursionFrequency=Math.min(l.sampleRate/2,this.baseFrequency*Math.pow(2,this._excursionOctaves)),this.setFilterFreq()}},excursionOctaves:{enumerable:!0,get:function(){return this._excursionOctaves},set:function(e){this._excursionOctaves=e,this._excursionFrequency=Math.min(l.sampleRate/2,this.baseFrequency*Math.pow(2,this._excursionOctaves)),this.setFilterFreq()}},sensitivity:{enumerable:!0,get:function(){return this._sensitivity},set:function(e){this._sensitivity=Math.pow(10,e)}},resonance:{enumerable:!0,get:function(){return this._resonance},set:function(e){this._resonance=e,this.filterPeaking.Q=this._resonance}},init:{value:function(){this.output.gain.value=1,this.filterPeaking.type="peaking",this.filterBp.type="bandpass",this.filterPeaking.frequency.value=100,this.filterPeaking.gain.value=20,this.filterPeaking.Q.value=5,this.filterBp.frequency.value=100,this.filterBp.Q.value=1}}}),o.prototype.EnvelopeFollower=function(e){e||(e=this.getDefaults()),this.input=l.createGain(),this.jsNode=this.output=l.createScriptProcessor(this.buffersize,1,1),this.input.connect(this.output),this.attackTime=c(e.attackTime,this.defaults.attackTime.value),this.releaseTime=c(e.releaseTime,this.defaults.releaseTime.value),this._envelope=0,this.target=e.target||{},this.callback=e.callback||function(){}},o.prototype.EnvelopeFollower.prototype=Object.create(h,{name:{value:"EnvelopeFollower"},defaults:{value:{attackTime:{value:.003,min:0,max:.5,automatable:!1,type:d},releaseTime:{value:.5,min:0,max:.5,automatable:!1,type:d}}},buffersize:{value:256},envelope:{value:0},sampleRate:{value:44100},attackTime:{enumerable:!0,get:function(){return this._attackTime},set:function(e){this._attackTime=e,this._attackC=Math.exp(-1/this._attackTime*this.sampleRate/this.buffersize)}},releaseTime:{enumerable:!0,get:function(){return this._releaseTime},set:function(e){this._releaseTime=e,this._releaseC=Math.exp(-1/this._releaseTime*this.sampleRate/this.buffersize)}},callback:{get:function(){return this._callback},set:function(e){"function"==typeof e?this._callback=e:console.error("tuna.js: "+this.name+": Callback must be a function!")}},target:{get:function(){return this._target},set:function(e){this._target=e}},activate:{value:function(e){this.activated=e,e?(this.jsNode.connect(l.destination),this.jsNode.onaudioprocess=this.returnCompute(this)):(this.jsNode.disconnect(),this.jsNode.onaudioprocess=null)}},returnCompute:{value:function(e){return function(t){e.compute(t)}}},compute:{value:function(e){var t,n,o,r,i=e.inputBuffer.getChannelData(0).length,a=e.inputBuffer.numberOfChannels;if(n=o=r=0,a>1)for(r=0;r2*Math.PI&&(t._phase=0),e(t._target,t._offset+t._oscillation*Math.sin(t._phase))}}}}),o.toString=o.prototype.toString=function(){return"Please visit https://github.com/Theodeus/tuna/wiki for instructions on how to use Tuna.js"}}(this)},function(e,t,n){function o(e,t,n){var o=t&&n||0,r=0;for(t=t||[],e.toLowerCase().replace(/[0-9a-f]{2}/g,function(e){r<16&&(t[o+r++]=c[e])});r<16;)t[o+r++]=0;return t}function r(e,t){var n=t||0,o=u;return o[e[n++]]+o[e[n++]]+o[e[n++]]+o[e[n++]]+"-"+o[e[n++]]+o[e[n++]]+"-"+o[e[n++]]+o[e[n++]]+"-"+o[e[n++]]+o[e[n++]]+"-"+o[e[n++]]+o[e[n++]]+o[e[n++]]+o[e[n++]]+o[e[n++]]+o[e[n++]]}function i(e,t,n){var o=t&&n||0,i=t||[];e=e||{};var a=void 0!==e.clockseq?e.clockseq:h,s=void 0!==e.msecs?e.msecs:(new Date).getTime(),u=void 0!==e.nsecs?e.nsecs:y+1,c=s-d+(u-y)/1e4;if(c<0&&void 0===e.clockseq&&(a=a+1&16383),(c<0||s>d)&&void 0===e.nsecs&&(u=0),u>=1e4)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");d=s,y=u,h=a,s+=122192928e5;var l=(1e4*(268435455&s)+u)%4294967296;i[o++]=l>>>24&255,i[o++]=l>>>16&255,i[o++]=l>>>8&255,i[o++]=255&l;var p=s/4294967296*1e4&268435455;i[o++]=p>>>8&255,i[o++]=255&p,i[o++]=p>>>24&15|16,i[o++]=p>>>16&255,i[o++]=a>>>8|128,i[o++]=255&a;for(var v=e.node||f,b=0;b<6;b++)i[o+b]=v[b];return t?t:r(i)}function a(e,t,n){var o=t&&n||0;"string"==typeof e&&(t="binary"==e?new Array(16):null,e=null),e=e||{};var i=e.random||(e.rng||s)();if(i[6]=15&i[6]|64,i[8]=63&i[8]|128,t)for(var a=0;a<16;a++)t[o+a]=i[a];return t||r(i)}for(var s=n(38),u=[],c={},l=0;l<256;l++)u[l]=(l+256).toString(16).substr(1),c[u[l]]=l;var p=s(),f=[1|p[0],p[1],p[2],p[3],p[4],p[5]],h=16383&(p[6]<<8|p[7]),d=0,y=0,v=a;v.v1=i,v.v4=a,v.parse=o,v.unparse=r,e.exports=v},function(e,t,n){"use strict";function o(e){return e?"linearRampToValueAtTime":"exponentialRampToValueAtTime"}function r(e,t,n,r){t.gain[o(e)](n,r)}function i(e,t){var n=e.createGain(),o=i.params(t,n),s="linear"===o.ramp,u=e.createGain();u.connect(n);var c=e.createGain();c.connect(u);var l=a(e);return l.connect(c),n.start=function(t){t=Math.max(t||0,e.currentTime),n.onstart&&n.onstart(t),l.start(t),c.gain.setValueAtTime(0,t),c.gain.setValueAtTime(.01,t+1e-6),r(s,c,o.l1,t+o.t1),r(s,c,o.l2,t+o.t1+o.t2),r(s,c,o.l3,t+o.t1+o.t2+o.t3),isFinite(o.duration)&&n.stop(t+o.duration)},n.stop=function(t){t=Math.max(t||0,e.currentTime),u.gain.cancelScheduledValues(t),u.gain.setValueAtTime(n.gain.value,t);var i=t+o.t4;if(r(s,u,1e-4,i),n.onended){var c=a(e,0);c.connect(e.destination),c.onended=n.onended,c.start(e.currentTime),c.stop(i)}return i},n}var a=n(39),s=function(e){return"number"==typeof e},u=["duration","t1","t2","t3","t4","l1","l2","l3"],c={duration:1/0,l1:1,l2:.2,l3:.8,t1:.01,t2:.1,t3:0,t4:.2};i.params=function(e,t){return t=t||{},e=e||{},u.forEach(function(n){t[n]=s(e[n])?e[n]:c[n]}),s(e.attack)&&(t.t1=e.attack),s(e.decay)&&(t.t2=e.decay),s(e.sustain)&&(t.l3=e.sustain),s(e.release)&&(t.t4=e.release),t.ramp="exponential"===e.ramp?e.ramp:"linear",t},e.exports=i},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(t,"__esModule",{value:!0}),t.Synth=t.Song=t.Sampler=t.Sequencer=t.Reverb=t.PingPong=t.Overdrive=t.Monosynth=t.MoogFilter=t.LFO=t.Gain=t.Filter=t.Delay=t.Compressor=t.Chorus=t.Bitcrusher=t.Bus=t.Analyser=void 0;var r=n(11),i=o(r),a=n(12),s=o(a),u=n(13),c=o(u),l=n(14),p=o(l),f=n(15),h=o(f),d=n(16),y=o(d),v=n(17),b=o(v),m=n(18),g=o(m),_=n(19),P=o(_),x=n(20),T=o(x),w=n(21),k=o(w),C=n(22),O=o(C),N=n(23),j=o(N),M=n(24),L=o(M),S=n(26),R=o(S),E=n(25),q=o(E),G=n(27),A=o(G),D=n(28),F=o(D);t.Analyser=i["default"],t.Bus=c["default"],t.Bitcrusher=s["default"],t.Chorus=p["default"],t.Compressor=h["default"],t.Delay=y["default"],t.Filter=b["default"],t.Gain=g["default"],t.LFO=P["default"],t.MoogFilter=k["default"],t.Monosynth=T["default"],t.Overdrive=O["default"],t.PingPong=j["default"],t.Reverb=L["default"],t.Sequencer=R["default"],t.Sampler=q["default"],t.Song=A["default"],t.Synth=F["default"]},function(e,t){"use strict";function n(e,t){return Array(t+1).join(e)}function o(e){return"number"==typeof e}function r(e){return"string"==typeof e}function i(e){return"undefined"!=typeof e}function a(e,t){return Math.pow(2,(e-69)/12)*(t||440)}function s(){return d}function u(e,t,n){if("string"!=typeof e)return null;var o=d.exec(e);if(!o||!t&&o[4])return null;var r={letter:o[1].toUpperCase(),acc:o[2].replace(/x/g,"##")};r.pc=r.letter+r.acc,r.step=(r.letter.charCodeAt(0)+3)%7,r.alt="b"===r.acc[0]?-r.acc.length:r.acc.length;var i=y[r.step]+r.alt;return r.chroma=i<0?12+i:i%12,o[3]&&(r.oct=+o[3],r.midi=i+12*(r.oct+1),r.freq=a(r.midi,n)),t&&(r.tonicOf=o[4]),r}function c(e){return o(e)?e<0?n("b",-e):n("#",e):""}function l(e){return o(e)?""+e:""}function p(e,t,n){return null===e||"undefined"==typeof e?null:e.step?p(e.step,e.alt,e.oct):e<0||e>6?null:v.charAt(e)+c(t)+l(n)}function f(e){if((o(e)||r(e))&&e>=0&&e<128)return+e;var t=u(e);return t&&i(t.midi)?t.midi:null}function h(e,t){var n=f(e);return null===n?null:a(n,t)}var d=/^([a-gA-G])(#{1,}|b{1,}|x{1,}|)(-?\d*)\s*(.*)\s*$/,y=[0,2,4,5,7,9,11],v="CDEFGAB",b={parse:u,build:p,regex:s,midi:f,freq:h},m=["letter","acc","pc","step","alt","chroma","oct"];m.forEach(function(e){b[e]=function(t){var n=u(t);return n&&i(n[e])?n[e]:null}}),e.exports=b},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var s=function(){function e(e,t){for(var n=0;na}t.context.scheduler.insert(e+a,t.playStep,{time:e,step:n,glide:s})})},i=0;i2){var h=typeof o;if(3===f&&"object"!==h&&"function"!==h)c(o)||(r=[String(o)]);else{r=[];for(var d=2;d2?i(arguments,2):e.children)}function h(e,t,n){var o=t.split("."),i=o[0];return function(t){var a,u,c,l=t&&t.currentTarget||this,p=e.state,f=p;if(u=s(n)?r(t,n):l.nodeName?(l.nodeName+l.type).match(/^input(check|rad)/i)?l.checked:l.value:t,o.length>1){for(c=0;c1)for(var o=1;o>",k={array:r("array"),bool:r("boolean"),func:r("function"),number:r("number"),object:r("object"),string:r("string"),any:i(),arrayOf:a,element:s(),instanceOf:u,node:f(),objectOf:l,oneOf:c,oneOfType:p,shape:h};t.exports=k})},function(e,t,n){function o(e,t){for(var n=0;n=0&&g.splice(t,1)}function s(e){var t=document.createElement("style");return t.type="text/css",i(e,t),t}function u(e){var t=document.createElement("link");return t.rel="stylesheet",i(e,t),t}function c(e,t){var n,o,r;if(t.singleton){var i=m++;n=b||(b=s(t)),o=l.bind(null,n,i,!1),r=l.bind(null,n,i,!0)}else e.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=u(t),o=f.bind(null,n),r=function(){a(n),n.href&&URL.revokeObjectURL(n.href)}):(n=s(t),o=p.bind(null,n),r=function(){a(n)});return o(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;o(e=t)}else r()}}function l(e,t,n,o){var r=n?"":o.css;if(e.styleSheet)e.styleSheet.cssText=_(t,r);else{var i=document.createTextNode(r),a=e.childNodes;a[t]&&e.removeChild(a[t]),a.length?e.insertBefore(i,a[t]):e.appendChild(i)}}function p(e,t){var n=t.css,o=t.media;if(o&&e.setAttribute("media",o),e.styleSheet)e.styleSheet.cssText=n;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(n))}}function f(e,t){var n=t.css,o=t.sourceMap;o&&(n+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(o))))+" */");var r=new Blob([n],{type:"text/css"}),i=e.href;e.href=URL.createObjectURL(r),i&&URL.revokeObjectURL(i)}var h={},d=function(e){var t;return function(){return"undefined"==typeof t&&(t=e.apply(this,arguments)),t}},y=d(function(){return/msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase())}),v=d(function(){return document.head||document.getElementsByTagName("head")[0]}),b=null,m=0,g=[];e.exports=function(e,t){t=t||{},"undefined"==typeof t.singleton&&(t.singleton=y()),"undefined"==typeof t.insertAt&&(t.insertAt="bottom");var n=r(e);return o(n,t),function(e){for(var i=[],a=0;a>>((3&t)<<3)&255;return r}}e.exports=n}).call(t,function(){return this}())},function(e,t){"use strict";e.exports=function(e,t){t=t||0===t?t:1;var n=e.createBuffer(1,2,e.sampleRate),o=n.getChannelData(0);o[0]=o[1]=t;var r=e.createBufferSource();return r.buffer=n,r.loop=!0,r}}]);
--------------------------------------------------------------------------------
/dist/index.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/developit/preact-music/a40f28916e38650a1e6fc9ab514d019373e46048/dist/index.css
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | React Music
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/dist/reverb/room.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/developit/preact-music/a40f28916e38650a1e6fc9ab514d019373e46048/dist/reverb/room.wav
--------------------------------------------------------------------------------
/dist/samples/cowbell.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/developit/preact-music/a40f28916e38650a1e6fc9ab514d019373e46048/dist/samples/cowbell.wav
--------------------------------------------------------------------------------
/dist/samples/hihat.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/developit/preact-music/a40f28916e38650a1e6fc9ab514d019373e46048/dist/samples/hihat.wav
--------------------------------------------------------------------------------
/dist/samples/kick.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/developit/preact-music/a40f28916e38650a1e6fc9ab514d019373e46048/dist/samples/kick.wav
--------------------------------------------------------------------------------
/dist/samples/snare.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/developit/preact-music/a40f28916e38650a1e6fc9ab514d019373e46048/dist/samples/snare.wav
--------------------------------------------------------------------------------
/interfaces/audio-contour.js:
--------------------------------------------------------------------------------
1 | type Contour = {
2 | connect(node: Object): void,
3 | start(): void,
4 | stop(time: number): void,
5 | onended: Function,
6 | onstart: Function,
7 | };
8 |
9 | declare module 'audio-contour' {
10 | declare function exports(
11 | context: Object,
12 | options: Object
13 | ): Contour
14 | }
15 |
--------------------------------------------------------------------------------
/interfaces/note-parser.js:
--------------------------------------------------------------------------------
1 | type Note = {
2 | letter: string,
3 | acc: string,
4 | pc: string,
5 | step: number,
6 | alt: number,
7 | chroma: number,
8 | oct: ?number,
9 | midi: ?number,
10 | freq: ?number,
11 | };
12 |
13 | declare module 'note-parser' {
14 | declare function regex(): RegExp;
15 | declare function parse(note: string, isTonic: boolean, tuning: number): Note;
16 | declare function build(obj: Object): string;
17 | declare function midi(note: string | number): number;
18 | declare function freq(note: string, tuning: ?string): number;
19 | }
20 |
--------------------------------------------------------------------------------
/interfaces/tunajs.js:
--------------------------------------------------------------------------------
1 | declare type TunaInstance = {
2 | Bitcrusher: (options: Object) => Object;
3 | Chorus: (options: Object) => Object;
4 | Convolver: (options: Object) => Object;
5 | Delay: (options: Object) => Object;
6 | MoogFilter: (options: Object) => Object;
7 | Overdrive: (options: Object) => Object;
8 | Phaser: (options: Object) => Object;
9 | PingPongDelay: (options: Object) => Object;
10 | };
11 |
12 | declare class Tuna {
13 | constructor(context: Object): TunaInstance;
14 | }
15 |
16 | declare module 'tunajs' {
17 | declare var exports: typeof Tuna;
18 | }
19 |
--------------------------------------------------------------------------------
/interfaces/uuid.js:
--------------------------------------------------------------------------------
1 | type UUIDGenerator = () => string;
2 |
3 | declare module 'uuid' {
4 | declare function exports(
5 | v1: UUIDGenerator
6 | ): Contour
7 | }
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "preact-music",
3 | "version": "1.0.1",
4 | "description": "Make beats with preact",
5 | "main": "lib",
6 | "files": [
7 | "lib",
8 | "umd"
9 | ],
10 | "scripts": {
11 | "start": "webpack-dev-server --inline --port 3000 --config webpack.config.dev.js --content-base public/",
12 | "build": "babel src -d lib --copy-files",
13 | "clean": "rimraf dist",
14 | "clean-umd": "rimraf umd",
15 | "dist": "npm run clean && webpack -p && cp -a public/. dist/",
16 | "lint": "eslint src demo --fix",
17 | "flow-check": "flow check",
18 | "flow": "flow",
19 | "umd": "npm run clean-umd && webpack --config webpack.config.umd.js"
20 | },
21 | "author": "Ken Wheeler",
22 | "license": "MIT",
23 | "repository": "https://github.com/developit/preact-music",
24 | "dependencies": {
25 | "audio-contour": "0.0.1",
26 | "note-parser": "^2.0.0",
27 | "tunajs": "^0.4.5",
28 | "uuid": "^2.0.2"
29 | },
30 | "peerDependencies": {
31 | "preact": "*"
32 | },
33 | "devDependencies": {
34 | "babel-cli": "^6.10.1",
35 | "babel-core": "^6.10.4",
36 | "babel-eslint": "^6.1.2",
37 | "babel-loader": "^6.2.4",
38 | "babel-plugin-transform-flow-strip-types": "^6.14.0",
39 | "babel-preset-es2015": "^6.9.0",
40 | "babel-preset-react": "^6.11.1",
41 | "babel-preset-stage-0": "^6.5.0",
42 | "css-loader": "^0.23.1",
43 | "eslint": "^3.3.1",
44 | "eslint-config-formidable": "^1.0.1",
45 | "eslint-plugin-filenames": "^1.1.0",
46 | "eslint-plugin-import": "^1.14.0",
47 | "eslint-plugin-jsx-a11y": "^2.1.0",
48 | "eslint-plugin-react": "^6.1.2",
49 | "flow-bin": "^0.31.1",
50 | "json-loader": "^0.5.4",
51 | "postcss-loader": "^0.10.1",
52 | "preact": "^6.0.0",
53 | "preact-compat": "^2.3.0",
54 | "rimraf": "^2.5.4",
55 | "style-loader": "^0.13.1",
56 | "webpack": "^1.13.1",
57 | "webpack-dev-server": "^1.15.0"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/public/index.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/developit/preact-music/a40f28916e38650a1e6fc9ab514d019373e46048/public/index.css
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | React Music
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/public/reverb/room.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/developit/preact-music/a40f28916e38650a1e6fc9ab514d019373e46048/public/reverb/room.wav
--------------------------------------------------------------------------------
/public/samples/cowbell.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/developit/preact-music/a40f28916e38650a1e6fc9ab514d019373e46048/public/samples/cowbell.wav
--------------------------------------------------------------------------------
/public/samples/hihat.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/developit/preact-music/a40f28916e38650a1e6fc9ab514d019373e46048/public/samples/hihat.wav
--------------------------------------------------------------------------------
/public/samples/kick.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/developit/preact-music/a40f28916e38650a1e6fc9ab514d019373e46048/public/samples/kick.wav
--------------------------------------------------------------------------------
/public/samples/snare.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/developit/preact-music/a40f28916e38650a1e6fc9ab514d019373e46048/public/samples/snare.wav
--------------------------------------------------------------------------------
/src/components/analyser.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | /* eslint-disable no-restricted-syntax */
3 | import React, { PropTypes, Component } from 'react';
4 |
5 | type Props = {
6 | children?: any;
7 | fftSize?: number;
8 | onAudioProcess?: Function;
9 | smoothingTimeConstant?: number;
10 | };
11 |
12 | type Context = {
13 | audioContext: Object;
14 | connectNode: Object;
15 | };
16 |
17 | export default class Analyser extends Component {
18 | applyProps: Function;
19 | connectNode: Object;
20 | context: Context;
21 | props: Props;
22 | visualization: Object;
23 | static propTypes = {
24 | children: PropTypes.node,
25 | fftSize: PropTypes.number,
26 | onAudioProcess: PropTypes.func,
27 | smoothingTimeConstant: PropTypes.number,
28 | };
29 | static defaultProps = {
30 | fftSize: 128,
31 | onAudioProcess: () => {},
32 | smoothingTimeConstant: 0.3,
33 | };
34 | static contextTypes = {
35 | audioContext: PropTypes.object,
36 | connectNode: PropTypes.object,
37 | };
38 | static childContextTypes = {
39 | audioContext: PropTypes.object,
40 | connectNode: PropTypes.object,
41 | };
42 | constructor(props: Props, context: Context) {
43 | super(props);
44 |
45 | this.visualization = context.audioContext.createScriptProcessor(2048, 1, 1);
46 | this.visualization.connect(context.audioContext.destination);
47 |
48 | this.connectNode = context.audioContext.createAnalyser();
49 | this.connectNode.connect(context.connectNode);
50 | this.applyProps = this.applyProps.bind(this);
51 |
52 | this.visualization.onaudioprocess = () => {
53 | if (props.onAudioProcess) {
54 | props.onAudioProcess(this.connectNode);
55 | }
56 | };
57 | }
58 | getChildContext(): Object {
59 | return {
60 | ...this.context,
61 | connectNode: this.connectNode,
62 | };
63 | }
64 | componentDidMount() {
65 | this.applyProps(this.props);
66 | }
67 | componentWillReceiveProps(nextProps: Props) {
68 | this.applyProps(nextProps);
69 | }
70 | componentWillUnmount() {
71 | this.connectNode.disconnect();
72 | }
73 | applyProps(props: Props) {
74 | for (const prop in props) {
75 | if (this.connectNode[prop]) {
76 | this.connectNode[prop] = props[prop];
77 | }
78 | }
79 | }
80 | render(): React.Element {
81 | return {this.props.children} ;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/components/bitcrusher.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | /* eslint-disable no-restricted-syntax */
3 | import React, { PropTypes, Component } from 'react';
4 | import Tuna from 'tunajs';
5 |
6 | type Props = {
7 | bits?: number;
8 | bufferSize?: number;
9 | children?: any;
10 | normfreq?: number;
11 | };
12 |
13 | type Context = {
14 | audioContext: Object;
15 | connectNode: Object;
16 | };
17 |
18 | export default class Bitcrusher extends Component {
19 | connectNode: Object;
20 | context: Context;
21 | props: Props;
22 | static propTypes = {
23 | bits: PropTypes.number,
24 | bufferSize: PropTypes.number,
25 | children: PropTypes.node,
26 | normfreq: PropTypes.number,
27 | };
28 | static defaultProps = {
29 | bits: 8,
30 | bufferSize: 256,
31 | normfreq: 0.1,
32 | };
33 | static contextTypes = {
34 | audioContext: PropTypes.object,
35 | connectNode: PropTypes.object,
36 | };
37 | static childContextTypes = {
38 | audioContext: PropTypes.object,
39 | connectNode: PropTypes.object,
40 | };
41 | constructor(props: Props, context: Context) {
42 | super(props);
43 |
44 | const tuna = new Tuna(context.audioContext);
45 |
46 | this.connectNode = new tuna.Bitcrusher({
47 | bits: props.bits,
48 | normfreq: props.normfreq,
49 | bufferSize: props.bufferSize,
50 | });
51 |
52 | this.connectNode.connect(context.connectNode);
53 | }
54 | getChildContext(): Object {
55 | return {
56 | ...this.context,
57 | connectNode: this.connectNode,
58 | };
59 | }
60 | componentWillReceiveProps(nextProps: Props) {
61 | for (const prop in nextProps) {
62 | if (this.connectNode[prop]) {
63 | this.connectNode[prop] = nextProps[prop];
64 | }
65 | }
66 | }
67 | componentWillUnmount() {
68 | this.connectNode.disconnect();
69 | }
70 | render(): React.Element {
71 | return {this.props.children} ;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/components/bus.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React, { PropTypes, Component } from 'react';
3 |
4 | type Props = {
5 | children?: any;
6 | gain?: number;
7 | id: string;
8 | };
9 |
10 | type Context = {
11 | audioContext: Object;
12 | connectNode: Object;
13 | getMaster: Function;
14 | };
15 |
16 | export default class Bus extends Component {
17 | connectNode: Object;
18 | context: Context;
19 | props: Props;
20 | static propTypes = {
21 | children: PropTypes.node,
22 | gain: PropTypes.number,
23 | id: PropTypes.string.isRequired,
24 | };
25 | static defaultProps = {
26 | gain: 0.5,
27 | };
28 | static contextTypes = {
29 | audioContext: PropTypes.object,
30 | connectNode: PropTypes.object,
31 | getMaster: PropTypes.func,
32 | };
33 | static childContextTypes = {
34 | audioContext: PropTypes.object,
35 | connectNode: PropTypes.object,
36 | getMaster: PropTypes.func,
37 | };
38 | constructor(props: Props, context: Context) {
39 | super(props);
40 |
41 | this.connectNode = context.audioContext.createGain();
42 | this.connectNode.gain.value = props.gain;
43 | this.connectNode.connect(context.connectNode);
44 | }
45 | getChildContext(): Object {
46 | return {
47 | ...this.context,
48 | connectNode: this.connectNode,
49 | };
50 | }
51 | componentDidMount() {
52 | const master = this.context.getMaster();
53 | master.busses[this.props.id] = this.connectNode;
54 | }
55 | componentWillReceiveProps(nextProps: Props) {
56 | const master = this.context.getMaster();
57 | delete master.busses[this.props.id];
58 |
59 | this.connectNode.gain.value = nextProps.gain;
60 | master.busses[nextProps.id] = this.connectNode;
61 | }
62 | componentWillUnmount() {
63 | this.connectNode.disconnect();
64 | delete this.context.getMaster().busses[this.props.id];
65 | }
66 | render(): React.Element {
67 | return {this.props.children} ;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/components/chorus.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | /* eslint-disable no-restricted-syntax */
3 | import React, { PropTypes, Component } from 'react';
4 | import Tuna from 'tunajs';
5 |
6 | type Props = {
7 | bypass?: number;
8 | children?: any;
9 | delay?: number;
10 | feedback?: number;
11 | rate?: number;
12 | };
13 |
14 | type Context = {
15 | audioContext: Object;
16 | connectNode: Object;
17 | };
18 |
19 | export default class Chorus extends Component {
20 | connectNode: Object;
21 | context: Context;
22 | props: Props;
23 | static propTypes = {
24 | bypass: PropTypes.number,
25 | children: PropTypes.node,
26 | delay: PropTypes.number,
27 | feedback: PropTypes.number,
28 | rate: PropTypes.number,
29 | };
30 | static defaultProps = {
31 | bypass: 0,
32 | delay: 0.0045,
33 | feedback: 0.2,
34 | rate: 1.5,
35 | };
36 | static contextTypes = {
37 | audioContext: PropTypes.object,
38 | connectNode: PropTypes.object,
39 | };
40 | static childContextTypes = {
41 | audioContext: PropTypes.object,
42 | connectNode: PropTypes.object,
43 | };
44 | constructor(props: Props, context: Context) {
45 | super(props);
46 |
47 | const tuna = new Tuna(context.audioContext);
48 |
49 | this.connectNode = new tuna.Chorus({
50 | feedback: props.feedback,
51 | rate: props.rate,
52 | delay: props.delay,
53 | bypass: props.bypass,
54 | });
55 |
56 | this.connectNode.connect(context.connectNode);
57 | }
58 | getChildContext(): Object {
59 | return {
60 | ...this.context,
61 | connectNode: this.connectNode,
62 | };
63 | }
64 | componentWillReceiveProps(nextProps: Props) {
65 | for (const prop in nextProps) {
66 | if (this.connectNode[prop]) {
67 | this.connectNode[prop] = nextProps[prop];
68 | }
69 | }
70 | }
71 | componentWillUnmount() {
72 | this.connectNode.disconnect();
73 | }
74 | render(): React.Element {
75 | return {this.props.children} ;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/components/compressor.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | /* eslint-disable no-restricted-syntax */
3 | import React, { PropTypes, Component } from 'react';
4 |
5 | type Props = {
6 | attack?: number;
7 | children?: any;
8 | knee?: number;
9 | ratio?: number;
10 | release?: number;
11 | threshold?: number;
12 | };
13 |
14 | type Context = {
15 | audioContext: Object;
16 | connectNode: Object;
17 | };
18 |
19 | export default class Compressor extends Component {
20 | applyProps: Function;
21 | connectNode: Object;
22 | context: Context;
23 | props: Props;
24 | static propTypes = {
25 | attack: PropTypes.number,
26 | children: PropTypes.node,
27 | knee: PropTypes.number,
28 | ratio: PropTypes.number,
29 | release: PropTypes.number,
30 | threshold: PropTypes.number,
31 | };
32 | static defaultProps = {
33 | attack: 0.003,
34 | knee: 32,
35 | ratio: 12,
36 | release: 0.25,
37 | threshold: -24,
38 | };
39 | static contextTypes = {
40 | audioContext: PropTypes.object,
41 | connectNode: PropTypes.object,
42 | };
43 | static childContextTypes = {
44 | audioContext: PropTypes.object,
45 | connectNode: PropTypes.object,
46 | };
47 | constructor(props: Props, context: Context) {
48 | super(props);
49 |
50 | this.connectNode = context.audioContext.createDynamicsCompressor();
51 | this.connectNode.connect(context.connectNode);
52 |
53 | this.applyProps = this.applyProps.bind(this);
54 | }
55 | getChildContext(): Object {
56 | return {
57 | ...this.context,
58 | connectNode: this.connectNode,
59 | };
60 | }
61 | componentDidMount() {
62 | this.applyProps(this.props);
63 | }
64 | componentWillReceiveProps(nextProps: Props) {
65 | this.applyProps(nextProps);
66 | }
67 | componentWillUnmount() {
68 | this.connectNode.disconnect();
69 | }
70 | applyProps(props: Props) {
71 | for (const prop in props) {
72 | if (this.connectNode[prop]) {
73 | this.connectNode[prop].value = props[prop];
74 | }
75 | }
76 | }
77 | render(): React.Element {
78 | return {this.props.children} ;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/components/delay.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | /* eslint-disable no-restricted-syntax */
3 | import React, { PropTypes, Component } from 'react';
4 | import Tuna from 'tunajs';
5 |
6 | type Props = {
7 | bypass?: number;
8 | children?: any;
9 | cutoff?: number;
10 | delayTime?: number;
11 | dryLevel?: number;
12 | feedback?: number;
13 | wetLevel?: number;
14 | };
15 |
16 | type Context = {
17 | audioContext: Object;
18 | connectNode: Object;
19 | };
20 |
21 | export default class Delay extends Component {
22 | connectNode: Object;
23 | context: Context;
24 | props: Props;
25 | static propTypes = {
26 | bypass: PropTypes.number,
27 | children: PropTypes.node,
28 | cutoff: PropTypes.number,
29 | delayTime: PropTypes.number,
30 | dryLevel: PropTypes.number,
31 | feedback: PropTypes.number,
32 | wetLevel: PropTypes.number,
33 | };
34 | static defaultProps = {
35 | bypass: 0,
36 | cutoff: 2000,
37 | delayTime: 150,
38 | dryLevel: 1,
39 | feedback: 0.45,
40 | wetLevel: 0.25,
41 | };
42 | static contextTypes = {
43 | audioContext: PropTypes.object,
44 | connectNode: PropTypes.object,
45 | };
46 | static childContextTypes = {
47 | audioContext: PropTypes.object,
48 | connectNode: PropTypes.object,
49 | };
50 | constructor(props: Props, context: Context) {
51 | super(props);
52 |
53 | const tuna = new Tuna(context.audioContext);
54 |
55 | this.connectNode = new tuna.Delay({
56 | feedback: props.feedback,
57 | delayTime: props.delayTime,
58 | wetLevel: props.wetLevel,
59 | dryLevel: props.dryLevel,
60 | cutoff: props.cutoff,
61 | bypass: props.bypass,
62 | });
63 |
64 | this.connectNode.connect(context.connectNode);
65 | }
66 | getChildContext(): Object {
67 | return {
68 | ...this.context,
69 | connectNode: this.connectNode,
70 | };
71 | }
72 | componentWillReceiveProps(nextProps: Props) {
73 | for (const prop in nextProps) {
74 | if (this.connectNode[prop]) {
75 | this.connectNode[prop] = nextProps[prop];
76 | }
77 | }
78 | }
79 | componentWillUnmount() {
80 | this.connectNode.disconnect();
81 | }
82 | render(): React.Element {
83 | return {this.props.children} ;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/components/filter.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | /* eslint-disable no-restricted-syntax */
3 | import React, { PropTypes, Component } from 'react';
4 |
5 | type Props = {
6 | children?: any;
7 | frequency?: number;
8 | gain?: number;
9 | type?: string;
10 | };
11 |
12 | type Context = {
13 | audioContext: Object;
14 | connectNode: Object;
15 | };
16 |
17 | export default class Filter extends Component {
18 | applyProps: Function;
19 | connectNode: Object;
20 | context: Context;
21 | props: Props;
22 | static propTypes = {
23 | children: PropTypes.node,
24 | frequency: PropTypes.number,
25 | gain: PropTypes.number,
26 | type: PropTypes.string,
27 | };
28 | static defaultProps = {
29 | frequency: 2000,
30 | gain: 0,
31 | type: 'lowpass',
32 | };
33 | static contextTypes = {
34 | audioContext: PropTypes.object,
35 | connectNode: PropTypes.object,
36 | };
37 | static childContextTypes = {
38 | audioContext: PropTypes.object,
39 | connectNode: PropTypes.object,
40 | };
41 | constructor(props: Props, context: Context) {
42 | super(props);
43 |
44 | this.connectNode = context.audioContext.createBiquadFilter();
45 | this.connectNode.connect(context.connectNode);
46 |
47 | this.applyProps = this.applyProps.bind(this);
48 | }
49 | getChildContext(): Object {
50 | return {
51 | ...this.context,
52 | connectNode: this.connectNode,
53 | };
54 | }
55 | componentDidMount() {
56 | this.applyProps(this.props);
57 | }
58 | componentWillReceiveProps(nextProps: Props) {
59 | this.applyProps(nextProps);
60 | }
61 | componentWillUnmount() {
62 | this.connectNode.disconnect();
63 | }
64 | applyProps(props: Props) {
65 | for (const prop in props) {
66 | if (this.connectNode[prop]) {
67 | if (typeof this.connectNode[prop] === 'object') {
68 | this.connectNode[prop].value = props[prop];
69 | } else {
70 | this.connectNode[prop] = props[prop];
71 | }
72 | }
73 | }
74 | }
75 | render(): React.Element {
76 | return {this.props.children} ;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/components/gain.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React, { PropTypes, Component } from 'react';
3 |
4 | type Props = {
5 | amount?: number;
6 | children?: any;
7 | };
8 |
9 | type Context = {
10 | audioContext: Object;
11 | connectNode: Object;
12 | };
13 |
14 | export default class Gain extends Component {
15 | connectNode: Object;
16 | context: Context;
17 | props: Props;
18 | static propTypes = {
19 | amount: PropTypes.number,
20 | children: PropTypes.node,
21 | };
22 | static defaultProps = {
23 | amount: 1.0,
24 | };
25 | static contextTypes = {
26 | audioContext: PropTypes.object,
27 | connectNode: PropTypes.object,
28 | };
29 | static childContextTypes = {
30 | audioContext: PropTypes.object,
31 | connectNode: PropTypes.object,
32 | };
33 | constructor(props: Props, context: Context) {
34 | super(props);
35 |
36 | this.connectNode = context.audioContext.createGain();
37 | this.connectNode.gain.value = props.amount;
38 | this.connectNode.connect(context.connectNode);
39 | }
40 | getChildContext(): Object {
41 | return {
42 | ...this.context,
43 | connectNode: this.connectNode,
44 | };
45 | }
46 | componentWillReceiveProps(nextProps: Props) {
47 | this.connectNode.gain.value = nextProps.amount;
48 | }
49 | componentWillUnmount() {
50 | this.connectNode.disconnect();
51 | }
52 | render(): React.Element {
53 | return {this.props.children} ;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/lfo.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React, { PropTypes, Component } from 'react';
3 |
4 | type Props = {
5 | children?: any;
6 | connect: Function;
7 | frequency?: number;
8 | gain?: number;
9 | type?: string;
10 | };
11 |
12 | type Context = {
13 | audioContext: Object;
14 | connectNode: Object;
15 | };
16 |
17 | export default class LFO extends Component {
18 | connectNode: Object;
19 | context: Context;
20 | osc: Object;
21 | props: Props;
22 | static displayName = 'Synth';
23 | static propTypes = {
24 | children: PropTypes.node,
25 | connect: PropTypes.func,
26 | frequency: PropTypes.number,
27 | gain: PropTypes.number,
28 | type: PropTypes.string,
29 | };
30 | static defaultProps = {
31 | connect: (node) => node.gain,
32 | frequency: 1,
33 | gain: 0.5,
34 | type: 'sine',
35 | };
36 | static contextTypes = {
37 | audioContext: PropTypes.object,
38 | connectNode: PropTypes.object,
39 | };
40 | componentDidMount() {
41 | const volumeGain = this.context.audioContext.createGain();
42 | volumeGain.gain.value = this.props.gain;
43 | this.osc = this.context.audioContext.createOscillator();
44 | this.osc.frequency.value = this.props.frequency;
45 | this.osc.type = this.props.type;
46 | this.osc.connect(volumeGain);
47 | volumeGain.connect(this.props.connect(this.context.connectNode));
48 |
49 | this.osc.start(this.context.audioContext.currentTime);
50 | }
51 | componentWillUnmount() {
52 | this.osc.stop();
53 | this.connectNode.disconnect();
54 | }
55 | render(): React.Element {
56 | return {this.props.children} ;
57 | }
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/src/components/monosynth.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React, { PropTypes, Component } from 'react';
3 | import parser from 'note-parser';
4 | import contour from 'audio-contour';
5 | import uuid from 'uuid';
6 |
7 | type Envelope = {
8 | attack?: number;
9 | decay?: number;
10 | sustain?: number;
11 | release?: number;
12 | };
13 |
14 | type Props = {
15 | busses: Array;
16 | children: any;
17 | envelope: Envelope;
18 | gain?: number;
19 | glide?: number;
20 | steps: Array;
21 | transpose?: number;
22 | type: string;
23 | };
24 |
25 | type Context = {
26 | audioContext: Object;
27 | bars: number;
28 | barInterval: number;
29 | connectNode: Object;
30 | getMaster: Function;
31 | resolution: number;
32 | scheduler: Object;
33 | tempo: number;
34 | };
35 |
36 | export default class Monosynth extends Component {
37 | amplitudeGain: Object;
38 | connectNode: Object;
39 | context: Context;
40 | id: String;
41 | getSteps: Function;
42 | osc: Object;
43 | playStep: Function;
44 | props: Props;
45 | static displayName = 'Synth';
46 | static propTypes = {
47 | busses: PropTypes.array,
48 | children: PropTypes.node,
49 | envelope: PropTypes.shape({
50 | attack: PropTypes.number,
51 | decay: PropTypes.number,
52 | sustain: PropTypes.number,
53 | release: PropTypes.number,
54 | }),
55 | gain: PropTypes.number,
56 | glide: PropTypes.number,
57 | steps: PropTypes.array.isRequired,
58 | transpose: PropTypes.number,
59 | type: PropTypes.string.isRequired,
60 | };
61 | static defaultProps = {
62 | envelope: {
63 | attack: 0.01,
64 | decay: 0.2,
65 | sustain: 0.2,
66 | release: 0.2,
67 | },
68 | gain: 0.5,
69 | glide: 0.1,
70 | transpose: 0,
71 | };
72 | static contextTypes = {
73 | audioContext: PropTypes.object,
74 | bars: PropTypes.number,
75 | barInterval: PropTypes.number,
76 | connectNode: PropTypes.object,
77 | getMaster: PropTypes.func,
78 | resolution: PropTypes.number,
79 | scheduler: PropTypes.object,
80 | tempo: PropTypes.number,
81 | };
82 | static childContextTypes = {
83 | audioContext: PropTypes.object,
84 | bars: PropTypes.number,
85 | barInterval: PropTypes.number,
86 | connectNode: PropTypes.object,
87 | getMaster: PropTypes.func,
88 | resolution: PropTypes.number,
89 | scheduler: PropTypes.object,
90 | tempo: PropTypes.number,
91 | };
92 | constructor(props: Props, context: Context) {
93 | super(props);
94 |
95 | this.getSteps = this.getSteps.bind(this);
96 | this.playStep = this.playStep.bind(this);
97 |
98 | this.connectNode = context.audioContext.createGain();
99 | this.connectNode.gain.value = props.gain;
100 | this.connectNode.connect(context.connectNode);
101 | }
102 | getChildContext(): Object {
103 | return {
104 | ...this.context,
105 | connectNode: this.connectNode,
106 | };
107 | }
108 | componentDidMount() {
109 | this.id = uuid.v1();
110 | const master = this.context.getMaster();
111 | master.instruments[this.id] = this.getSteps;
112 |
113 | this.amplitudeGain = this.context.audioContext.createGain();
114 | this.amplitudeGain.gain.value = 0;
115 | this.amplitudeGain.connect(this.connectNode);
116 |
117 | this.osc = this.context.audioContext.createOscillator();
118 | this.osc.type = this.props.type;
119 | this.osc.connect(this.amplitudeGain);
120 |
121 | if (this.props.busses) {
122 | this.props.busses.forEach((bus) => {
123 | if (master.busses[bus]) {
124 | this.osc.connect(master.busses[bus]);
125 | }
126 | });
127 | }
128 |
129 | this.osc.start(this.context.audioContext.currentTime);
130 | }
131 | componentWillUnmount() {
132 | const master = this.context.getMaster();
133 | delete master.instruments[this.id];
134 | this.osc.stop();
135 | this.connectNode.disconnect();
136 | }
137 | getSteps(playbackTime: number) {
138 | const totalBars = this.context.getMaster().getMaxBars();
139 | const loopCount = totalBars / this.context.bars;
140 | for (let i = 0; i < loopCount; i++) {
141 | const barOffset = ((this.context.barInterval * this.context.bars) * i) / 1000;
142 | const stepInterval = this.context.barInterval / this.context.resolution;
143 | this.props.steps.forEach((step, index) => {
144 | const time = barOffset + ((step[0] * stepInterval) / 1000);
145 | let glide = false;
146 |
147 | if (index !== 0) {
148 | const lastTime = barOffset + ((this.props.steps[index - 1][0] * stepInterval) / 1000);
149 | const lastDuration = (this.props.steps[index - 1][1] * stepInterval) / 1000;
150 | glide = lastTime + lastDuration > time;
151 | }
152 |
153 | this.context.scheduler.insert(playbackTime + time, this.playStep, {
154 | time: playbackTime,
155 | step,
156 | glide,
157 | });
158 | });
159 | }
160 | }
161 | createOscillator() {
162 | const [ time, note, duration, glide ] = arguments;
163 | const transposed = note.slice(0, -1) +
164 | (parseInt(note[note.length - 1], 0) + parseInt(this.props.transpose, 0));
165 |
166 | const env = contour(this.context.audioContext, {
167 | attack: this.props.envelope.attack,
168 | decay: this.props.envelope.decay,
169 | sustain: this.props.envelope.sustain,
170 | release: this.props.envelope.release,
171 | });
172 |
173 | env.connect(this.amplitudeGain.gain);
174 | this.osc.frequency.setTargetAtTime(
175 | parser.freq(transposed), time, glide ? this.props.glide : 0.001
176 | );
177 |
178 | env.start(time);
179 | env.stop(this.context.audioContext.currentTime + duration);
180 | }
181 | playStep(e: Object) {
182 | const { step, glide, time } = e.args;
183 | const note = step[2];
184 | const stepInterval = this.context.barInterval / this.context.resolution;
185 | const duration = (step[1] * stepInterval) / 1000;
186 | this.createOscillator(time, note, duration, glide);
187 | }
188 | render(): React.Element {
189 | return {this.props.children} ;
190 | }
191 | }
192 |
193 |
--------------------------------------------------------------------------------
/src/components/moog-filter.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | /* eslint-disable no-restricted-syntax */
3 | import React, { PropTypes, Component } from 'react';
4 | import Tuna from 'tunajs';
5 |
6 | type Props = {
7 | bufferSize?: number;
8 | children?: any;
9 | cutoff?: number;
10 | resonance?: number;
11 | };
12 |
13 | type Context = {
14 | audioContext: Object;
15 | connectNode: Object;
16 | };
17 |
18 | export default class MoogFilter extends Component {
19 | connectNode: Object;
20 | context: Context;
21 | props: Props;
22 | static propTypes = {
23 | bufferSize: PropTypes.number,
24 | children: PropTypes.node,
25 | cutoff: PropTypes.number,
26 | resonance: PropTypes.number,
27 | };
28 | static defaultProps = {
29 | bufferSize: 256,
30 | cutoff: 0.065,
31 | resonance: 3.5,
32 | };
33 | static contextTypes = {
34 | audioContext: PropTypes.object,
35 | connectNode: PropTypes.object,
36 | };
37 | static childContextTypes = {
38 | audioContext: PropTypes.object,
39 | connectNode: PropTypes.object,
40 | };
41 | constructor(props: Props, context: Context) {
42 | super(props);
43 |
44 | const tuna = new Tuna(context.audioContext);
45 |
46 | this.connectNode = new tuna.MoogFilter({
47 | cutoff: props.cutoff,
48 | resonance: props.resonance,
49 | bufferSize: props.bufferSize,
50 | });
51 |
52 | this.connectNode.connect(context.connectNode);
53 | }
54 | getChildContext(): Object {
55 | return {
56 | ...this.context,
57 | connectNode: this.connectNode,
58 | };
59 | }
60 | componentWillReceiveProps(nextProps: Props) {
61 | for (const prop in nextProps) {
62 | if (this.connectNode[prop]) {
63 | this.connectNode[prop] = nextProps[prop];
64 | }
65 | }
66 | }
67 | componentWillUnmount() {
68 | this.connectNode.disconnect();
69 | }
70 | render(): React.Element {
71 | return {this.props.children} ;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/components/overdrive.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | /* eslint-disable no-restricted-syntax */
3 | import React, { PropTypes, Component } from 'react';
4 | import Tuna from 'tunajs';
5 |
6 | type Props = {
7 | algorithmIndex?: number;
8 | bypass?: number;
9 | children?: any;
10 | curveAmount?: number;
11 | drive?: number;
12 | outputGain?: number;
13 | };
14 |
15 | type Context = {
16 | audioContext: Object;
17 | connectNode: Object;
18 | };
19 |
20 | export default class Overdrive extends Component {
21 | connectNode: Object;
22 | context: Context;
23 | props: Props;
24 | static propTypes = {
25 | algorithmIndex: PropTypes.number,
26 | bypass: PropTypes.number,
27 | children: PropTypes.node,
28 | curveAmount: PropTypes.number,
29 | drive: PropTypes.number,
30 | outputGain: PropTypes.number,
31 | };
32 | static defaultProps = {
33 | algorithmIndex: 0,
34 | bypass: 0,
35 | curveAmount: 1,
36 | drive: 0.7,
37 | outputGain: 0.5,
38 | };
39 | static contextTypes = {
40 | audioContext: PropTypes.object,
41 | connectNode: PropTypes.object,
42 | };
43 | static childContextTypes = {
44 | audioContext: PropTypes.object,
45 | connectNode: PropTypes.object,
46 | };
47 | constructor(props: Props, context: Context) {
48 | super(props);
49 |
50 | const tuna = new Tuna(context.audioContext);
51 |
52 | this.connectNode = new tuna.Overdrive({
53 | outputGain: props.outputGain,
54 | drive: props.drive,
55 | curveAmount: props.curveAmount,
56 | algorithmIndex: props.algorithmIndex,
57 | bypass: props.bypass,
58 | });
59 |
60 | this.connectNode.connect(context.connectNode);
61 | }
62 | getChildContext(): Object {
63 | return {
64 | ...this.context,
65 | connectNode: this.connectNode,
66 | };
67 | }
68 | componentWillReceiveProps(nextProps: Props) {
69 | for (const prop in nextProps) {
70 | if (this.connectNode[prop]) {
71 | this.connectNode[prop] = nextProps[prop];
72 | }
73 | }
74 | }
75 | componentWillUnmount() {
76 | this.connectNode.disconnect();
77 | }
78 | render(): React.Element {
79 | return {this.props.children} ;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/components/ping-pong.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | /* eslint-disable no-restricted-syntax */
3 | import React, { PropTypes, Component } from 'react';
4 | import Tuna from 'tunajs';
5 |
6 | type Props = {
7 | children?: any;
8 | delayTimeLeft?: number;
9 | delayTimeRight?: number;
10 | feedback?: number;
11 | wetLevel?: number;
12 | };
13 |
14 | type Context = {
15 | audioContext: Object;
16 | connectNode: Object;
17 | };
18 |
19 | export default class PingPong extends Component {
20 | connectNode: Object;
21 | context: Context;
22 | props: Props;
23 | static propTypes = {
24 | children: PropTypes.node,
25 | delayTimeLeft: PropTypes.number,
26 | delayTimeRight: PropTypes.number,
27 | feedback: PropTypes.number,
28 | wetLevel: PropTypes.number,
29 | };
30 | static defaultProps = {
31 | delayTimeLeft: 150,
32 | delayTimeRight: 200,
33 | feedback: 0.3,
34 | wetLevel: 0.5,
35 | };
36 | static contextTypes = {
37 | audioContext: PropTypes.object,
38 | connectNode: PropTypes.object,
39 | };
40 | static childContextTypes = {
41 | audioContext: PropTypes.object,
42 | connectNode: PropTypes.object,
43 | };
44 | constructor(props: Props, context: Context) {
45 | super(props);
46 |
47 | const tuna = new Tuna(context.audioContext);
48 |
49 | this.connectNode = new tuna.PingPongDelay({
50 | wetLevel: props.wetLevel,
51 | feedback: props.feedback,
52 | delayTimeLeft: props.delayTimeLeft,
53 | delayTimeRight: props.delayTimeRight,
54 | });
55 |
56 | this.connectNode.connect(context.connectNode);
57 | }
58 | getChildContext(): Object {
59 | return {
60 | ...this.context,
61 | connectNode: this.connectNode,
62 | };
63 | }
64 | componentWillReceiveProps(nextProps: Props) {
65 | for (const prop in nextProps) {
66 | if (this.connectNode[prop]) {
67 | this.connectNode[prop] = nextProps[prop];
68 | }
69 | }
70 | }
71 | componentWillUnmount() {
72 | this.connectNode.disconnect();
73 | }
74 | render(): React.Element {
75 | return {this.props.children} ;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/components/reverb.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | /* eslint-disable no-restricted-syntax */
3 | import React, { PropTypes, Component } from 'react';
4 | import Tuna from 'tunajs';
5 |
6 | type Props = {
7 | bypass?: number;
8 | children?: any;
9 | dryLevel?: number;
10 | highCut?: number;
11 | impulse?: string;
12 | level?: number;
13 | lowCut?: number;
14 | wetLevel?: number;
15 | };
16 |
17 | type Context = {
18 | audioContext: Object;
19 | connectNode: Object;
20 | };
21 |
22 | export default class Reverb extends Component {
23 | connectNode: Object;
24 | context: Context;
25 | props: Props;
26 | static propTypes = {
27 | bypass: PropTypes.number,
28 | children: PropTypes.node,
29 | dryLevel: PropTypes.number,
30 | highCut: PropTypes.number,
31 | impulse: PropTypes.string,
32 | level: PropTypes.number,
33 | lowCut: PropTypes.number,
34 | wetLevel: PropTypes.number,
35 | };
36 | static defaultProps = {
37 | bypass: 0,
38 | dryLevel: 0.5,
39 | highCut: 22050,
40 | impulse: 'reverb/room.wav',
41 | level: 1,
42 | lowCut: 20,
43 | wetLevel: 1,
44 | };
45 | static contextTypes = {
46 | audioContext: PropTypes.object,
47 | connectNode: PropTypes.object,
48 | };
49 | static childContextTypes = {
50 | audioContext: PropTypes.object,
51 | connectNode: PropTypes.object,
52 | };
53 | constructor(props: Props, context: Context) {
54 | super(props);
55 |
56 | const tuna = new Tuna(context.audioContext);
57 |
58 | this.connectNode = new tuna.Convolver({
59 | highCut: props.highCut,
60 | lowCut: props.lowCut,
61 | dryLevel: props.dryLevel,
62 | wetLevel: props.wetLevel,
63 | level: props.level,
64 | impulse: props.impulse,
65 | bypass: props.bypass,
66 | });
67 |
68 | this.connectNode.connect(context.connectNode);
69 | }
70 | getChildContext(): Object {
71 | return {
72 | ...this.context,
73 | connectNode: this.connectNode,
74 | };
75 | }
76 | componentWillReceiveProps(nextProps: Props) {
77 | for (const prop in nextProps) {
78 | if (this.connectNode[prop]) {
79 | this.connectNode[prop] = nextProps[prop];
80 | }
81 | }
82 | }
83 | componentWillUnmount() {
84 | this.connectNode.disconnect();
85 | }
86 | render(): React.Element {
87 | return {this.props.children} ;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/components/sampler.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React, { PropTypes, Component } from 'react';
3 | import uuid from 'uuid';
4 |
5 | import { BufferLoader } from '../utils/buffer-loader';
6 |
7 | type Props = {
8 | busses: Array;
9 | children?: any;
10 | detune?: number;
11 | gain?: number;
12 | sample: string;
13 | steps: Array;
14 | };
15 |
16 | type Context = {
17 | audioContext: Object;
18 | bars: number;
19 | barInterval: number;
20 | bufferLoaded: Function;
21 | connectNode: Object;
22 | getMaster: Function;
23 | resolution: number;
24 | scheduler: Object;
25 | tempo: number;
26 | };
27 |
28 | export default class Sampler extends Component {
29 | buffer: Object;
30 | bufferLoaded: Function;
31 | connectNode: Object;
32 | context: Context;
33 | id: String;
34 | getSteps: Function;
35 | playStep: Function;
36 | props: Props;
37 | static displayName = 'Sampler';
38 | static propTypes = {
39 | busses: PropTypes.array,
40 | children: PropTypes.node,
41 | detune: PropTypes.number,
42 | gain: PropTypes.number,
43 | sample: PropTypes.string.isRequired,
44 | steps: PropTypes.array.isRequired,
45 | };
46 | static defaultProps = {
47 | detune: 0,
48 | gain: 0.5,
49 | };
50 | static contextTypes = {
51 | audioContext: PropTypes.object,
52 | bars: PropTypes.number,
53 | barInterval: PropTypes.number,
54 | bufferLoaded: PropTypes.func,
55 | connectNode: PropTypes.object,
56 | getMaster: PropTypes.func,
57 | resolution: PropTypes.number,
58 | scheduler: PropTypes.object,
59 | tempo: PropTypes.number,
60 | };
61 | static childContextTypes = {
62 | audioContext: PropTypes.object,
63 | bars: PropTypes.number,
64 | barInterval: PropTypes.number,
65 | bufferLoaded: PropTypes.func,
66 | connectNode: PropTypes.object,
67 | getMaster: PropTypes.func,
68 | resolution: PropTypes.number,
69 | scheduler: PropTypes.object,
70 | tempo: PropTypes.number,
71 | };
72 | constructor(props: Props, context: Context) {
73 | super(props);
74 |
75 | this.bufferLoaded = this.bufferLoaded.bind(this);
76 | this.getSteps = this.getSteps.bind(this);
77 | this.playStep = this.playStep.bind(this);
78 |
79 | this.connectNode = context.audioContext.createGain();
80 | this.connectNode.gain.value = props.gain;
81 | this.connectNode.connect(context.connectNode);
82 | }
83 | getChildContext(): Object {
84 | return {
85 | ...this.context,
86 | connectNode: this.connectNode,
87 | };
88 | }
89 | componentDidMount() {
90 | this.id = uuid.v1();
91 |
92 | const master = this.context.getMaster();
93 | master.instruments[this.id] = this.getSteps;
94 | master.buffers[this.id] = 1;
95 |
96 | const bufferLoader = new BufferLoader(
97 | this.context.audioContext,
98 | [this.props.sample],
99 | this.bufferLoaded
100 | );
101 |
102 | bufferLoader.load();
103 | }
104 | componentWillReceiveProps(nextProps: Props) {
105 | this.connectNode.gain.value = nextProps.gain;
106 | if (this.props.sample !== nextProps.sample) {
107 | const master = this.context.getMaster();
108 | delete master.buffers[this.id];
109 |
110 | this.id = uuid.v1();
111 | master.buffers[this.id] = 1;
112 |
113 | const bufferLoader = new BufferLoader(
114 | this.context.audioContext,
115 | [nextProps.sample ],
116 | this.bufferLoaded
117 | );
118 |
119 | bufferLoader.load();
120 | }
121 | }
122 | componentWillUnmount() {
123 | const master = this.context.getMaster();
124 |
125 | delete master.buffers[this.id];
126 | delete master.instruments[this.id];
127 | this.connectNode.disconnect();
128 | }
129 | getSteps(playbackTime: number) {
130 | const totalBars = this.context.getMaster().getMaxBars();
131 | const loopCount = totalBars / this.context.bars;
132 | for (let i = 0; i < loopCount; i++) {
133 | const barOffset = ((this.context.barInterval * this.context.bars) * i) / 1000;
134 | const stepInterval = this.context.barInterval / this.context.resolution;
135 |
136 | this.props.steps.forEach((step) => {
137 | const stepValue = Array.isArray(step) ? step[0] : step;
138 | const time = barOffset + ((stepValue * stepInterval) / 1000);
139 |
140 | this.context.scheduler.insert(playbackTime + time, this.playStep, {
141 | time: playbackTime,
142 | step,
143 | });
144 | });
145 | }
146 | }
147 | playStep(e: Object) {
148 | const source = this.context.audioContext.createBufferSource();
149 | source.buffer = this.buffer;
150 | if (source.detune) {
151 | if (Array.isArray(e.args.step)) {
152 | source.detune.value = (this.props.detune + e.args.step[1]) * 100;
153 | } else {
154 | source.detune.value = this.props.detune;
155 | }
156 | }
157 | source.connect(this.connectNode);
158 |
159 | if (this.props.busses) {
160 | const master = this.context.getMaster();
161 | this.props.busses.forEach((bus) => {
162 | if (master.busses[bus]) {
163 | source.connect(master.busses[bus]);
164 | }
165 | });
166 | }
167 |
168 | source.start(e.args.playbackTime);
169 | this.context.scheduler.nextTick(e.args.playbackTime + this.buffer.duration, () => {
170 | source.disconnect();
171 | });
172 | }
173 | bufferLoaded(buffers: Array) {
174 | this.buffer = buffers[0];
175 | const master = this.context.getMaster();
176 | delete master.buffers[this.id];
177 | this.context.bufferLoaded();
178 | }
179 | render(): React.Element {
180 | return {this.props.children} ;
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/src/components/sequencer.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React, { PropTypes, Component } from 'react';
3 |
4 | import uuid from 'uuid';
5 |
6 | type Props = {
7 | bars?: number;
8 | children?: any;
9 | resolution?: number;
10 | };
11 |
12 | type Context = {
13 | getMaster: Function;
14 | };
15 |
16 | export default class Sequencer extends Component {
17 | id: String;
18 | context: Context;
19 | props: Props;
20 | static propTypes = {
21 | bars: PropTypes.number,
22 | children: PropTypes.node,
23 | resolution: PropTypes.number,
24 | };
25 | static defaultProps = {
26 | bars: 1,
27 | resolution: 16,
28 | };
29 | static contextTypes = {
30 | getMaster: PropTypes.func,
31 | };
32 | static childContextTypes = {
33 | bars: PropTypes.number,
34 | getMaster: PropTypes.func,
35 | resolution: PropTypes.number,
36 | };
37 | getChildContext(): Object {
38 | return {
39 | ...this.context,
40 | bars: this.props.bars,
41 | resolution: this.props.resolution,
42 | };
43 | }
44 | componentDidMount() {
45 | this.id = uuid.v1();
46 | const master = this.context.getMaster();
47 | master.bars[this.id] = this.props.bars;
48 | }
49 | componentWillReceiveProps(nextProps: Props) {
50 | const master = this.context.getMaster();
51 | master.bars[this.id] = nextProps.bars;
52 | }
53 | componentWillUnmount() {
54 | delete this.context.getMaster().bars[this.id];
55 | }
56 | render(): React.Element {
57 | return {this.props.children} ;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/components/song.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | /* eslint-disable no-loop-func, react/no-did-mount-set-state */
3 | import React, { Component, PropTypes } from 'react';
4 | import Scheduler from '../utils/scheduler';
5 |
6 | type Props = {
7 | children?: any;
8 | playing?: boolean;
9 | tempo: number;
10 | };
11 |
12 | export default class Song extends Component {
13 | audioContext: Object;
14 | barInterval: number;
15 | bars: Object;
16 | bufferLoaded: Function;
17 | buffers: Object;
18 | busses: Object;
19 | getMaster: Function;
20 | getMaxBars: Function;
21 | instruments: Object;
22 | loop: Function;
23 | props: Props;
24 | scheduler: Object;
25 | state: Object;
26 | static propTypes = {
27 | children: PropTypes.node,
28 | playing: PropTypes.bool,
29 | tempo: PropTypes.number,
30 | };
31 | static defaultProps = {
32 | playing: false,
33 | tempo: 90,
34 | };
35 | static childContextTypes = {
36 | audioContext: PropTypes.object,
37 | barInterval: PropTypes.number,
38 | bufferLoaded: PropTypes.func,
39 | connectNode: PropTypes.object,
40 | getMaster: PropTypes.func,
41 | scheduler: PropTypes.object,
42 | tempo: PropTypes.number,
43 | };
44 | constructor(props: Props) {
45 | super(props);
46 |
47 | this.state = {
48 | buffersLoaded: false,
49 | };
50 |
51 | this.barInterval = (60000 / props.tempo) * 4;
52 | this.bars = {};
53 | this.buffers = {};
54 | this.instruments = {};
55 | this.busses = {};
56 |
57 | this.loop = this.loop.bind(this);
58 | this.bufferLoaded = this.bufferLoaded.bind(this);
59 | this.getMaster = this.getMaster.bind(this);
60 | this.getMaxBars = this.getMaxBars.bind(this);
61 |
62 | window.AudioContext = window.AudioContext || window.webkitAudioContext;
63 | this.audioContext = new AudioContext();
64 |
65 | this.scheduler = new Scheduler({
66 | context: this.audioContext,
67 | });
68 | }
69 | getChildContext(): Object {
70 | return {
71 | tempo: this.props.tempo,
72 | audioContext: this.audioContext,
73 | barInterval: this.barInterval,
74 | bufferLoaded: this.bufferLoaded,
75 | connectNode: this.audioContext.destination,
76 | getMaster: this.getMaster,
77 | scheduler: this.scheduler,
78 | };
79 | }
80 | componentDidMount() {
81 | if (Object.keys(this.buffers).length === 0) {
82 | this.setState({
83 | buffersLoaded: true,
84 | });
85 | }
86 | }
87 | componentWillReceiveProps(nextProps: Props) {
88 | this.barInterval = (60000 / nextProps.tempo) * 4;
89 | }
90 | componentDidUpdate(prevProps: Object, prevState: Object) {
91 | if (prevState.buffersLoaded !== this.state.buffersLoaded ||
92 | prevProps.playing !== this.props.playing) {
93 | if (this.state.buffersLoaded === true && this.props.playing === true) {
94 | setTimeout(() => {
95 | this.scheduler.start(this.loop);
96 | }, 0);
97 | } else {
98 | this.scheduler.stop(true);
99 | }
100 | }
101 | }
102 | componentWillUnmount() {
103 | this.context.close();
104 | }
105 | getMaster(): Object {
106 | return this;
107 | }
108 | getMaxBars(): number {
109 | return Math.max(...Object.keys(this.bars).map((b) => this.bars[b]));
110 | }
111 | bufferLoaded() {
112 | if (Object.keys(this.buffers).length === 0) {
113 | this.setState({
114 | buffersLoaded: true,
115 | });
116 | }
117 | }
118 | loop(e: Object) {
119 | const maxBars = Object.keys(this.bars).length ? this.getMaxBars() : 1;
120 | Object.keys(this.instruments).forEach((id) => {
121 | const callback = this.instruments[id];
122 | callback(e.playbackTime);
123 | });
124 | this.scheduler.insert(e.playbackTime + ((this.barInterval * maxBars) / 1000), this.loop);
125 | }
126 | render(): React.Element {
127 | return {this.props.children} ;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/components/synth.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | /* eslint-disable max-statements */
3 | import React, { PropTypes, Component } from 'react';
4 | import parser from 'note-parser';
5 | import contour from 'audio-contour';
6 | import uuid from 'uuid';
7 |
8 | type Envelope = {
9 | attack?: number;
10 | decay?: number;
11 | sustain?: number;
12 | release?: number;
13 | };
14 |
15 | type Props = {
16 | busses: Array;
17 | children: any;
18 | envelope: Envelope;
19 | gain?: number;
20 | steps: Array;
21 | transpose?: number;
22 | type: string;
23 | };
24 |
25 | type Context = {
26 | audioContext: Object;
27 | bars: number;
28 | barInterval: number;
29 | connectNode: Object;
30 | getMaster: Function;
31 | resolution: number;
32 | scheduler: Object;
33 | tempo: number;
34 | };
35 |
36 | export default class Synth extends Component {
37 | connectNode: Object;
38 | context: Context;
39 | getSteps: Function;
40 | id: string;
41 | playStep: Function;
42 | props: Props;
43 | static displayName = 'Synth';
44 | static propTypes = {
45 | busses: PropTypes.array,
46 | children: PropTypes.node,
47 | envelope: PropTypes.shape({
48 | attack: PropTypes.number,
49 | decay: PropTypes.number,
50 | sustain: PropTypes.number,
51 | release: PropTypes.number,
52 | }),
53 | gain: PropTypes.number,
54 | steps: PropTypes.array.isRequired,
55 | transpose: PropTypes.number,
56 | type: PropTypes.string.isRequired,
57 | };
58 | static defaultProps = {
59 | envelope: {
60 | attack: 0.01,
61 | decay: 0.2,
62 | sustain: 0.2,
63 | release: 0.2,
64 | },
65 | gain: 0.5,
66 | transpose: 0,
67 | };
68 | static contextTypes = {
69 | audioContext: PropTypes.object,
70 | bars: PropTypes.number,
71 | barInterval: PropTypes.number,
72 | connectNode: PropTypes.object,
73 | getMaster: PropTypes.func,
74 | resolution: PropTypes.number,
75 | scheduler: PropTypes.object,
76 | tempo: PropTypes.number,
77 | };
78 | static childContextTypes = {
79 | audioContext: PropTypes.object,
80 | bars: PropTypes.number,
81 | barInterval: PropTypes.number,
82 | connectNode: PropTypes.object,
83 | getMaster: PropTypes.func,
84 | resolution: PropTypes.number,
85 | scheduler: PropTypes.object,
86 | tempo: PropTypes.number,
87 | };
88 | constructor(props: Props, context: Context) {
89 | super(props);
90 |
91 | this.getSteps = this.getSteps.bind(this);
92 | this.playStep = this.playStep.bind(this);
93 |
94 | this.connectNode = context.audioContext.createGain();
95 | this.connectNode.gain.value = props.gain;
96 | this.connectNode.connect(context.connectNode);
97 | }
98 | getChildContext(): Object {
99 | return {
100 | ...this.context,
101 | connectNode: this.connectNode,
102 | };
103 | }
104 | componentDidMount() {
105 | this.id = uuid.v1();
106 | const master = this.context.getMaster();
107 | master.instruments[this.id] = this.getSteps;
108 | }
109 | componentWillReceiveProps(nextProps: Props) {
110 | this.connectNode.gain.value = nextProps.gain;
111 | }
112 | componentWillUnmount() {
113 | const master = this.context.getMaster();
114 | delete master.instruments[this.id];
115 | this.connectNode.disconnect();
116 | }
117 | getSteps(playbackTime: number) {
118 | const totalBars = this.context.getMaster().getMaxBars();
119 | const loopCount = totalBars / this.context.bars;
120 | for (let i = 0; i < loopCount; i++) {
121 | const barOffset = ((this.context.barInterval * this.context.bars) * i) / 1000;
122 | const stepInterval = this.context.barInterval / this.context.resolution;
123 | this.props.steps.forEach((step) => {
124 | const time = barOffset + ((step[0] * stepInterval) / 1000);
125 |
126 | this.context.scheduler.insert(playbackTime + time, this.playStep, {
127 | time: playbackTime,
128 | step,
129 | });
130 | });
131 | }
132 | }
133 | createOscillator(time: number, note: string, duration: number) {
134 | const amplitudeGain = this.context.audioContext.createGain();
135 | amplitudeGain.gain.value = 0;
136 | amplitudeGain.connect(this.connectNode);
137 |
138 | const env = contour(this.context.audioContext, {
139 | attack: this.props.envelope.attack,
140 | decay: this.props.envelope.decay,
141 | sustain: this.props.envelope.sustain,
142 | release: this.props.envelope.release,
143 | });
144 |
145 | env.connect(amplitudeGain.gain);
146 |
147 | const osc = this.context.audioContext.createOscillator();
148 | const transposed = note.slice(0, -1) +
149 | (parseInt(note[note.length - 1], 0) + parseInt(this.props.transpose, 0));
150 |
151 | osc.frequency.value = parser.freq(transposed);
152 | osc.type = this.props.type;
153 | osc.connect(amplitudeGain);
154 |
155 | if (this.props.busses) {
156 | const master = this.context.getMaster();
157 | this.props.busses.forEach((bus) => {
158 | if (master.busses[bus]) {
159 | osc.connect(master.busses[bus]);
160 | }
161 | });
162 | }
163 |
164 | osc.start(time);
165 | env.start(time);
166 |
167 | const finish = env.stop(this.context.audioContext.currentTime + duration);
168 | osc.stop(finish);
169 | }
170 | playStep(e: Object) {
171 | const { step, time } = e.args;
172 | const notes = step[2];
173 | const stepInterval = this.context.barInterval / this.context.resolution;
174 | const duration = (step[1] * stepInterval) / 1000;
175 |
176 | if (Array.isArray(notes)) {
177 | notes.forEach((n) => {
178 | this.createOscillator(time, n, duration);
179 | });
180 | } else {
181 | this.createOscillator(time, notes, duration);
182 | }
183 | }
184 | render(): React.Element {
185 | return {this.props.children} ;
186 | }
187 | }
188 |
189 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Analyser from './components/analyser.js';
2 | import Bitcrusher from './components/bitcrusher.js';
3 | import Bus from './components/bus.js';
4 | import Chorus from './components/chorus.js';
5 | import Compressor from './components/compressor.js';
6 | import Delay from './components/delay.js';
7 | import Filter from './components/filter.js';
8 | import Gain from './components/gain.js';
9 | import LFO from './components/lfo.js';
10 | import Monosynth from './components/monosynth.js';
11 | import MoogFilter from './components/moog-filter.js';
12 | import Overdrive from './components/overdrive.js';
13 | import PingPong from './components/ping-pong.js';
14 | import Reverb from './components/reverb.js';
15 | import Sequencer from './components/sequencer.js';
16 | import Sampler from './components/sampler.js';
17 | import Song from './components/song.js';
18 | import Synth from './components/synth.js';
19 |
20 | export {
21 | Analyser,
22 | Bus,
23 | Bitcrusher,
24 | Chorus,
25 | Compressor,
26 | Delay,
27 | Filter,
28 | Gain,
29 | LFO,
30 | MoogFilter,
31 | Monosynth,
32 | Overdrive,
33 | PingPong,
34 | Reverb,
35 | Sequencer,
36 | Sampler,
37 | Song,
38 | Synth,
39 | };
40 |
--------------------------------------------------------------------------------
/src/utils/buffer-loader.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | /* eslint-disable no-console */
3 | export const BufferLoader = function BufferLoader(
4 | context: Object, urlList: Array, callback: Function
5 | ) {
6 | this.context = context;
7 | this.urlList = urlList;
8 | this.onload = callback;
9 | this.bufferList = [];
10 | this.loadCount = 0;
11 | };
12 |
13 | BufferLoader.prototype.loadBuffer = function loadBuffer(url, index) {
14 | const request = new XMLHttpRequest();
15 | request.open('GET', url, true);
16 | request.responseType = 'arraybuffer';
17 |
18 | const self = this;
19 |
20 | request.onload = function onload() {
21 | self.context.decodeAudioData(
22 | request.response,
23 | (buffer) => {
24 | if (!buffer) {
25 | console.error(`error decoding file data: ${url}`);
26 | return;
27 | }
28 | self.bufferList[index] = buffer;
29 | if (++self.loadCount === self.urlList.length) {
30 | self.onload(self.bufferList);
31 | }
32 | },
33 | (error) => {
34 | console.error('decodeAudioData error', error);
35 | }
36 | );
37 | };
38 |
39 | request.onerror = function onError() {
40 | console.error('BufferLoader: XHR error');
41 | };
42 |
43 | request.send();
44 | };
45 |
46 | BufferLoader.prototype.load = function load() {
47 | for (let i = 0; i < this.urlList.length; ++i) {
48 | this.loadBuffer(this.urlList[i], i);
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/src/utils/scheduler.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | export default class Scheduler {
3 | context: Object;
4 | interval: number;
5 | aheadTime: number;
6 | playbackTime: number;
7 | timerID: number;
8 | scheduleID: number;
9 | schedules: Array;
10 | constructor(opts: Object) {
11 | this.context = opts.context;
12 | this.interval = 0.025;
13 | this.aheadTime = 0.0;
14 | this.playbackTime = this.context.currentTime;
15 |
16 | this.timerID = 0;
17 | this.scheduleID = 0;
18 | this.schedules = [];
19 | }
20 |
21 | start(callback: Function, args: Array): Object {
22 | const loop = () => {
23 | const t0 = this.context.currentTime;
24 | const t1 = t0 + this.aheadTime;
25 |
26 | this.process(t0, t1);
27 | };
28 |
29 | if (this.timerID === 0) {
30 | this.timerID = setInterval(loop, this.interval * 1000);
31 |
32 | if (callback) {
33 | this.insert(this.context.currentTime, callback, args);
34 | loop();
35 | }
36 | } else {
37 | this.insert(this.context.currentTime, callback, args);
38 | }
39 |
40 | return this;
41 | }
42 |
43 | stop(reset: boolean): Object {
44 | if (this.timerID !== 0) {
45 | clearInterval(this.timerID);
46 | this.timerID = 0;
47 | }
48 |
49 | if (reset) {
50 | this.schedules.splice(0);
51 | }
52 |
53 | return this;
54 | }
55 |
56 | insert(time: number, callback: Function, args: Array): number {
57 | const id = ++this.scheduleID;
58 | const event = { id, time, callback, args };
59 |
60 | if (this.schedules.length === 0 || this.schedules[this.schedules.length - 1].time <= time) {
61 | this.schedules.push(event);
62 | } else {
63 | for (let i = 0, imax = this.schedules.length; i < imax; i++) {
64 | if (time < this.schedules[i].time) {
65 | this.schedules.splice(i, 0, event);
66 | break;
67 | }
68 | }
69 | }
70 |
71 | return id;
72 | }
73 |
74 | nextTick(time: number, callback: Function, args: Array): number {
75 | return this.insert(time + this.aheadTime, callback, args);
76 | }
77 |
78 | remove(scheduleID: number): number {
79 | if (typeof scheduleID === 'number') {
80 | for (let i = 0, imax = this.schedules.length; i < imax; i++) {
81 | if (scheduleID === this.schedules[i].id) {
82 | this.schedules.splice(i, 1);
83 | break;
84 | }
85 | }
86 | }
87 |
88 | return scheduleID;
89 | }
90 |
91 | removeAll() {
92 | this.schedules.splice(0);
93 | }
94 |
95 | process(t0: number, t1: number) {
96 | this.playbackTime = t0;
97 |
98 | while (this.schedules.length && this.schedules[0].time < t1) {
99 | const event = this.schedules.shift();
100 | const playbackTime = event.time;
101 | const args = event.args;
102 |
103 | this.playbackTime = playbackTime;
104 |
105 | event.callback({ playbackTime, args });
106 | }
107 |
108 | this.playbackTime = t0;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | module.exports = {
5 | entry: [
6 | './demo/index',
7 | ],
8 | output: {
9 | path: __dirname,
10 | filename: 'bundle.js',
11 | publicPath: '/',
12 | },
13 | resolve: {
14 | alias: {
15 | 'react': 'preact-compat',
16 | 'react-dom': 'preact-compat'
17 | }
18 | },
19 | plugins: [
20 | new webpack.NoErrorsPlugin(),
21 | ],
22 | module: {
23 | loaders: [{
24 | test: /\.js$/,
25 | loaders: ['babel'],
26 | include: [
27 | path.join(__dirname, 'src'),
28 | path.join(__dirname, 'demo'),
29 | ],
30 | }, {
31 | test: /\.css$/,
32 | include: [
33 | path.join(__dirname, 'src'),
34 | path.join(__dirname, 'demo'),
35 | ],
36 | loader: 'style!css!postcss',
37 | }],
38 | },
39 | };
40 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | module.exports = {
5 | entry: [
6 | './demo/index',
7 | ],
8 | output: {
9 | path: path.join(__dirname, 'dist'),
10 | filename: 'bundle.js',
11 | publicPath: '/dist/',
12 | },
13 | resolve: {
14 | alias: {
15 | 'react': 'preact-compat',
16 | 'react-dom': 'preact-compat'
17 | }
18 | },
19 | plugins: [
20 | new webpack.optimize.OccurenceOrderPlugin(),
21 | new webpack.DefinePlugin({
22 | 'process.env': {
23 | NODE_ENV: JSON.stringify('production'),
24 | },
25 | }),
26 | ],
27 | module: {
28 | loaders: [{
29 | test: /\.js$/,
30 | exclude: /node_modules/,
31 | loader: 'babel',
32 | }, {
33 | test: /\.css$/,
34 | loader: 'style!css!postcss',
35 | }],
36 | },
37 | };
38 |
--------------------------------------------------------------------------------
/webpack.config.umd.js:
--------------------------------------------------------------------------------
1 | /* globals __dirname */
2 | const webpack = require('webpack');
3 | const path = require('path');
4 |
5 | module.exports = {
6 | entry: path.join(__dirname, 'src/index.js'),
7 | externals: [
8 | {
9 | 'preact-compat': true,
10 | 'preact': true,
11 | },
12 | ],
13 | resolve: {
14 | alias: {
15 | 'react': 'preact-compat',
16 | 'react-dom': 'preact-compat'
17 | }
18 | },
19 | output: {
20 | library: 'ReactMusic',
21 | libraryTarget: 'umd',
22 | filename: 'react-music.min.js',
23 | path: path.join(__dirname, 'umd'),
24 | },
25 | module: {
26 | loaders: [
27 | { test: /\.js$/, exclude: /node_modules/, loader: 'babel' },
28 | ],
29 | },
30 | plugins: [
31 | new webpack.optimize.DedupePlugin(),
32 | new webpack.optimize.UglifyJsPlugin({
33 | compress: {
34 | warnings: false,
35 | },
36 | }),
37 | new webpack.DefinePlugin({
38 | 'process.env.NODE_ENV': JSON.stringify('production'),
39 | }),
40 | new webpack.SourceMapDevToolPlugin('[file].map'),
41 | ],
42 | };
43 |
--------------------------------------------------------------------------------