react-orchestra
Version:
A toolbox to build interactive and smart instruments on the web and mobile.
1,530 lines (1,288 loc) • 1.8 MB
JavaScript
/*!
* InstrumentJS v1.0.1
* MIT Licensed
*/
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["InstrumentJS"] = factory();
else
root["InstrumentJS"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
module.exports = __webpack_require__(1);
/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
exports.__esModule = true;
exports.InstrumentHelpers = exports.INSTRUMENT_NAMES = exports.Note = exports.Instrument = exports.AudioContext = undefined;
var _AudioContext = __webpack_require__(2);
var _AudioContext2 = _interopRequireDefault(_AudioContext);
var _Instruments = __webpack_require__(3);
var _Instruments2 = _interopRequireDefault(_Instruments);
var _Note = __webpack_require__(36);
var _Note2 = _interopRequireDefault(_Note);
var _INSTRUMENTS = __webpack_require__(59);
var _INSTRUMENTS2 = _interopRequireDefault(_INSTRUMENTS);
var _helpers = __webpack_require__(40);
var _helpers2 = _interopRequireDefault(_helpers);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
exports.AudioContext = _AudioContext2.default;
exports.Instrument = _Instruments2.default;
exports.Note = _Note2.default;
exports.INSTRUMENT_NAMES = _INSTRUMENTS2.default;
exports.InstrumentHelpers = _helpers2.default;
// import Melody from './Melody';
// import Musician from './Musician';
/***/ },
/* 2 */
/***/ function(module, exports) {
'use strict';
exports.__esModule = true;
var getAudioContext = function getAudioContext() {
var AudioContext = window.AudioContext // Default
|| window.webkitAudioContext // Safari and old versions of Chrome
|| false;
if (!AudioContext) {
alert('Sorry but the WebAudio API is not supported on this browser. Please consider using Chrome or Safari for the best experience ');
return null;
}
return new AudioContext();
};
var audioContext = getAudioContext();
exports.default = audioContext;
/***/ },
/* 3 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
exports.__esModule = true;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _soundfontPlayer = __webpack_require__(4);
var _soundfontPlayer2 = _interopRequireDefault(_soundfontPlayer);
var _bluebird = __webpack_require__(19);
var _bluebird2 = _interopRequireDefault(_bluebird);
var _lodash = __webpack_require__(23);
var _lodash2 = _interopRequireDefault(_lodash);
var _MidiIO = __webpack_require__(25);
var _MidiIO2 = _interopRequireDefault(_MidiIO);
var _immutabilityHelper = __webpack_require__(34);
var _immutabilityHelper2 = _interopRequireDefault(_immutabilityHelper);
var _Note = __webpack_require__(36);
var _Note2 = _interopRequireDefault(_Note);
var _AudioContext = __webpack_require__(2);
var _AudioContext2 = _interopRequireDefault(_AudioContext);
var _NOTES = __webpack_require__(37);
var _NOTES2 = _interopRequireDefault(_NOTES);
var _SCALES = __webpack_require__(38);
var _SCALES2 = _interopRequireDefault(_SCALES);
var _CHORDS = __webpack_require__(39);
var _CHORDS2 = _interopRequireDefault(_CHORDS);
var _helpers = __webpack_require__(40);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var delay = function delay(ms) {
return new _bluebird2.default(function (resolve) {
return setTimeout(resolve, ms);
});
};
// const NOTES = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'];
window.delay = function (ms) {
return new _bluebird2.default(function (resolve) {
return setTimeout(resolve, ms);
});
};
/** Instrument Class. Use it to play music from the browser */
var Instrument = function () {
/**
* Create an instrument. It will import audio context and prepare its internal state.
*/
function Instrument() {
_classCallCheck(this, Instrument);
this.instruments = {};
this.ac = _AudioContext2.default;
this.playingNotes = {};
this.isPlayingMelody = false;
}
/**
* Loads instrument from the web and stores it as a resolvable promise
* @param instrumentName. List of all instrument names in src/constants/INSTRUMENTS
* @returns Promise<musicPlayer>
*/
Instrument.prototype.get = function get(instrumentName) {
var _this = this;
return new _bluebird2.default(function (resolve, reject) {
if (_lodash2.default.has(_this.instruments, instrumentName)) {
if (_this.instruments[instrumentName].promise.done) {
return resolve(_this.instruments[instrumentName].instrument);
}
return _this.instruments[instrumentName].promise.then(function (instrument) {
return resolve(instrument);
});
}
_this.instruments[instrumentName] = {};
_this.instruments[instrumentName].isLoading = true;
_this.instruments[instrumentName].instrument = {};
_this.instruments[instrumentName].promise = _soundfontPlayer2.default.instrument(_AudioContext2.default, instrumentName, {
nameToUrl: function nameToUrl(name, sf, format) {
format = format === 'ogg' ? format : 'mp3';
sf = sf === 'FluidR3_GM' ? sf : 'MusyngKite';
var url = 'https://d2ovwkm0xvqw0n.cloudfront.net/'; // `https://raw.githubusercontent.com/RakanNimer/midi-js-soundfonts/master/`;// 'https://s3-eu-west-1.amazonaws.com/ut-music-player/midi-js-soundfonts/'
var fullPath = '' + url + sf + '/' + name + '-' + format + '.js';
return fullPath;
}
}).then(function (instrument) {
_this.instruments[instrumentName].instrument = instrument;
_this.instruments[instrumentName].promise.done = true;
_this.instruments[instrumentName].isLoading = false;
resolve(instrument);
return instrument;
}).catch(function (err) {
console.log('ERRROR ', err);
reject('Couldn\'t load instrument ' + instrumentName + '. Error : ' + JSON.stringify(err));
});
_this.instruments[instrumentName].promise.done = false;
return _this.instruments[instrumentName].promise;
});
};
/**
* Start playing note, if note.payload.durationInMS is -1 it will keep playing infinitely until it is explicitly stopped.
* { payload: { id, instrumentName, startTimeInMS, instrumentName, name, gain, duration }}
* @param note
* @returns Promise<notePlayer>
*/
Instrument.prototype.startPlayingNote = function startPlayingNote(note) {
var _this2 = this;
var noteID = note.payload.id; // `${note.payload.noteName}_${note.payload.instrumentName}`;
if (!(noteID in this.playingNotes)) {
this.playingNotes[noteID] = [];
}
return delay(note.payload.startTimeInMS).then(function () {
_this2.playingNotes[noteID].push(_this2.get(note.payload.instrumentName).then(function (instrument) {
return instrument.start(note.payload.name, null, {
loop: true,
gain: note.payload.gain,
duration: note.payload.duration === -1 ? 9999999 : note.payload.duration / 1000
});
}));
var currentNotePromise = _this2.playingNotes[noteID][_this2.playingNotes[noteID].length - 1];
if (note.payload.durationInMS !== -1) {
return delay(note.payload.durationInMS).then(function () {
return currentNotePromise;
}).then(function (notePlayer) {
return notePlayer.stop();
});
}
return currentNotePromise;
});
};
/**
* Stop playing note
* { payload: { id, instrumentName, startTimeInMS, instrumentName, name, gain, duration }}
* @param note
* @returns Promise<notePlayer>
*/
Instrument.prototype.stopPlayingNote = function stopPlayingNote(note) {
if (!note.payload) {
return _bluebird2.default.resolve(false);
}
var noteID = note.payload.id; // note.payload.id ? note.payload.id : `${note.payload.noteName}_${note.payload.instrumentName}`;
if (typeof this.playingNotes[noteID] === 'undefined') {
return _bluebird2.default.resolve(true);
}
// 'Cloning' promise before deleting note.
var playingNotes = this.playingNotes[noteID];
var playingNote = playingNotes[playingNotes.length - 1].then();
this.playingNotes[noteID].pop();
if (this.playingNotes[noteID].length === 0) {
delete this.playingNotes[noteID];
}
return delay(note.payload.fadeDurationInMS).then(function () {
return playingNote;
}).then(function (notePlayer) {
notePlayer.stop();
}).catch(function (error) {
alert('Failed stopping audio. Unsupported Browser ?' + JSON.stringify(error));
});
};
/**
* Start playing chord by note names
* @param noteNames
* @param note settings
* @example
* const instrument = new Instrument();
* instrument.startPlayingChordByNoteNames(['A3','C3','E3'], {instrumentName: 'acoustic_grand_piano', startTimeInMS: 0, durationInMS: 1000, endTimeInMS: 1000})
* @returns Promise<notePlayer>
*/
Instrument.prototype.startPlayingChordByNoteNames = function startPlayingChordByNoteNames(noteNames, settings) {
var _this3 = this;
return _bluebird2.default.map(noteNames, function (noteName) {
var note = new _Note2.default(_extends({}, settings, { name: noteName }));
return _this3.startPlayingNote(note);
});
};
/**
* Stop playing chord by note names
* @param noteNames
* @param note settings
* @example
* const instrument = new Instrument();
* instrument.stopPlayingChordByNoteNames(['A3','C3','E3'], {instrumentName: 'acoustic_grand_piano', startTimeInMS: 0, durationInMS: 1000, endTimeInMS: 1000})
* @returns Promise<notePlayer>
*/
Instrument.prototype.stopPlayingChordByNoteNames = function stopPlayingChordByNoteNames(noteNames, settings) {
var _this4 = this;
return _bluebird2.default.map(noteNames, function (noteName) {
var note = new _Note2.default(_extends({}, settings, { name: noteName }));
return _this4.stopPlayingNote(note);
});
};
/**
* startPlayingChordByChordName
* @param firstNoteName
* @param chordName
* @param note settings
* @example
* const instrument = new Instrument();
* instrument.startPlayingChordByChordName('A3', 'maj', {instrumentName: 'acoustic_grand_piano', startTimeInMS: 0, durationInMS: 1000, endTimeInMS: 1000})
* @returns Promise<notePlayer>
*/
Instrument.prototype.startPlayingChordByChordName = function startPlayingChordByChordName(firstNoteName, chordName) {
var noteSettings = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var noteNames = (0, _helpers.getNoteNamesFromChordName)(firstNoteName, chordName);
// console.log({ firstNoteName, noteNames });
return this.startPlayingChordByNoteNames(noteNames, noteSettings);
};
/**
* stopPlayingChordByChordName
* @param firstNoteName
* @param chordName
* @param note settings
* @example
* const instrument = new Instrument();
* instrument.stopPlayingChordByChordName('A3', 'maj', {instrumentName: 'acoustic_grand_piano', startTimeInMS: 0, durationInMS: 1000, endTimeInMS: 1000})
* @returns Promise<notePlayer>
*/
Instrument.prototype.stopPlayingChordByChordName = function stopPlayingChordByChordName(firstNoteName, chordName) {
var noteSettings = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var noteNames = (0, _helpers.getNoteNamesFromChordName)(firstNoteName, chordName);
// console.log({ firstNoteName, noteNames });
return this.stopPlayingChordByNoteNames(noteNames, noteSettings);
};
/**
* play array of notes
* @param notes
* @param meta
* @param note settings
* @returns Promise<notePlayer>
*/
Instrument.prototype.play = function play(notes, meta) {
var _this5 = this;
this.isPlayingMelody = true;
var melodyStartTime = notes[0].payload.startTimeInMS;
return _bluebird2.default.map(notes, function (note, i) {
var currentStartTime = note.payload.startTimeInMS - melodyStartTime;
return window.delay(currentStartTime).then(function () {
if (_this5.isPlayingMelody === false) {
throw new function () {
return { code: 'STOPPED_PLAYING' };
}();
}
return _this5.startPlayingNote(note);
}).then(function () {
return window.delay(note.payload.durationInMS);
}).then(function () {
return _this5.stopPlayingNote(note);
}).catch(function (error) {
if (error.code && error.code === 'STOPPED_PLAYING') {
return null;
}throw error;
});
});
};
/**
* stop playing array of notes
* @param notes
* @param meta
* @param note settings
* @returns Promise<notePlayer>
*/
Instrument.prototype.stop = function stop(notes, meta) {
var _this6 = this;
this.isPlayingMelody = false;
return _bluebird2.default.map(notes, function (note, i) {
return _this6.stopPlayingNote(note.payload.instrumentName, note.payload.noteName);
});
};
return Instrument;
}();
exports.default = Instrument;
/***/ },
/* 4 */
/***/ function(module, exports, __webpack_require__) {
'use strict'
var load = __webpack_require__(5)
var player = __webpack_require__(8)
/**
* Load a soundfont instrument. It returns a promise that resolves to a
* instrument object.
*
* The instrument object returned by the promise has the following properties:
*
* - name: the instrument name
* - play: A function to play notes from the buffer with the signature
* `play(note, time, duration, options)`
*
*
* The valid options are:
*
* - `format`: the soundfont format. 'mp3' by default. Can be 'ogg'
* - `soundfont`: the soundfont name. 'MusyngKite' by default. Can be 'FluidR3_GM'
* - `nameToUrl` <Function>: a function to convert from instrument names to URL
* - `destination`: by default Soundfont uses the `audioContext.destination` but you can override it.
* - `gain`: the gain of the player (1 by default)
* - `notes`: an array of the notes to decode. It can be an array of strings
* with note names or an array of numbers with midi note numbers. This is a
* performance option: since decoding mp3 is a cpu intensive process, you can limit
* limit the number of notes you want and reduce the time to load the instrument.
*
* @param {AudioContext} ac - the audio context
* @param {String} name - the instrument name. For example: 'acoustic_grand_piano'
* @param {Object} options - (Optional) the same options as Soundfont.loadBuffers
* @return {Promise}
*
* @example
* var Soundfont = require('sounfont-player')
* Soundfont.instrument('marimba').then(function (marimba) {
* marimba.play('C4')
* })
*/
function instrument (ac, name, options) {
if (arguments.length === 1) return function (n, o) { return instrument(ac, n, o) }
var opts = options || {}
var isUrl = opts.isSoundfontURL || isSoundfontURL
var toUrl = opts.nameToUrl || nameToUrl
var url = isUrl(name) ? name : toUrl(name, opts.soundfont, opts.format)
return load(ac, url, { only: opts.only || opts.notes }).then(function (buffers) {
var p = player(ac, buffers, opts).connect(ac.destination)
p.url = url
p.name = name
return p
})
}
function isSoundfontURL (name) {
return /\.js(\?.*)?$/i.test(name)
}
/**
* Given an instrument name returns a URL to to the Benjamin Gleitzman's
* package of [pre-rendered sound fonts](https://github.com/gleitz/midi-js-soundfonts)
*
* @param {String} name - instrument name
* @param {String} soundfont - (Optional) the soundfont name. One of 'FluidR3_GM'
* or 'MusyngKite' ('MusyngKite' by default)
* @param {String} format - (Optional) Can be 'mp3' or 'ogg' (mp3 by default)
* @returns {String} the Soundfont file url
* @example
* var Soundfont = require('soundfont-player')
* Soundfont.nameToUrl('marimba', 'mp3')
*/
function nameToUrl (name, sf, format) {
format = format === 'ogg' ? format : 'mp3'
sf = sf === 'FluidR3_GM' ? sf : 'MusyngKite'
return 'https://gleitz.github.io/midi-js-soundfonts/' + sf + '/' + name + '-' + format + '.js'
}
// In the 1.0.0 release it will be:
// var Soundfont = {}
var Soundfont = __webpack_require__(17)
Soundfont.instrument = instrument
Soundfont.nameToUrl = nameToUrl
if (typeof module === 'object' && module.exports) module.exports = Soundfont
if (typeof window !== 'undefined') window.Soundfont = Soundfont
/***/ },
/* 5 */
/***/ function(module, exports, __webpack_require__) {
'use strict'
var base64 = __webpack_require__(6)
var fetch = __webpack_require__(7)
// Given a regex, return a function that test if against a string
function fromRegex (r) {
return function (o) { return typeof o === 'string' && r.test(o) }
}
// Try to apply a prefix to a name
function prefix (pre, name) {
return typeof pre === 'string' ? pre + name
: typeof pre === 'function' ? pre(name)
: name
}
/**
* Load one or more audio files
*
*
* Possible option keys:
*
* - __from__ {Function|String}: a function or string to convert from file names to urls.
* If is a string it will be prefixed to the name:
* `load(ac, 'snare.mp3', { from: 'http://audio.net/samples/' })`
* If it's a function it receives the file name and should return the url as string.
* - __only__ {Array} - when loading objects, if provided, only the given keys
* will be included in the decoded object:
* `load(ac, 'piano.json', { only: ['C2', 'D2'] })`
*
* @param {AudioContext} ac - the audio context
* @param {Object} source - the object to be loaded
* @param {Object} options - (Optional) the load options for that object
* @param {Object} defaultValue - (Optional) the default value to return as
* in a promise if not valid loader found
*/
function load (ac, source, options, defVal) {
var loader =
// Basic audio loading
isArrayBuffer(source) ? loadArrayBuffer
: isAudioFileName(source) ? loadAudioFile
: isPromise(source) ? loadPromise
// Compound objects
: isArray(source) ? loadArrayData
: isObject(source) ? loadObjectData
: isJsonFileName(source) ? loadJsonFile
// Base64 encoded audio
: isBase64Audio(source) ? loadBase64Audio
: isJsFileName(source) ? loadMidiJSFile
: null
var opts = options || {}
return loader ? loader(ac, source, opts)
: defVal ? Promise.resolve(defVal)
: Promise.reject('Source not valid (' + source + ')')
}
load.fetch = fetch
// BASIC AUDIO LOADING
// ===================
// Load (decode) an array buffer
function isArrayBuffer (o) { return o instanceof ArrayBuffer }
function loadArrayBuffer (ac, array, options) {
return new Promise(function (done, reject) {
ac.decodeAudioData(array,
function (buffer) { done(buffer) },
function () { reject("Can't decode audio data (" + array.slice(0, 30) + '...)') }
)
})
}
// Load an audio filename
var isAudioFileName = fromRegex(/\.(mp3|wav|ogg)(\?.*)?$/i)
function loadAudioFile (ac, name, options) {
var url = prefix(options.from, name)
return load(ac, load.fetch(url, 'arraybuffer'), options)
}
// Load the result of a promise
function isPromise (o) { return o && typeof o.then === 'function' }
function loadPromise (ac, promise, options) {
return promise.then(function (value) {
return load(ac, value, options)
})
}
// COMPOUND OBJECTS
// ================
// Try to load all the items of an array
var isArray = Array.isArray
function loadArrayData (ac, array, options) {
return Promise.all(array.map(function (data) {
return load(ac, data, options, data)
}))
}
// Try to load all the values of a key/value object
function isObject (o) { return o && typeof o === 'object' }
function loadObjectData (ac, obj, options) {
var dest = {}
var promises = Object.keys(obj).map(function (key) {
if (options.only && options.only.indexOf(key) === -1) return null
var value = obj[key]
return load(ac, value, options, value).then(function (audio) {
dest[key] = audio
})
})
return Promise.all(promises).then(function () { return dest })
}
// Load the content of a JSON file
var isJsonFileName = fromRegex(/\.json(\?.*)?$/i)
function loadJsonFile (ac, name, options) {
var url = prefix(options.from, name)
return load(ac, load.fetch(url, 'text').then(JSON.parse), options)
}
// BASE64 ENCODED FORMATS
// ======================
// Load strings with Base64 encoded audio
var isBase64Audio = fromRegex(/^data:audio/)
function loadBase64Audio (ac, source, options) {
var i = source.indexOf(',')
return load(ac, base64.decode(source.slice(i + 1)).buffer, options)
}
// Load .js files with MidiJS soundfont prerendered audio
var isJsFileName = fromRegex(/\.js(\?.*)?$/i)
function loadMidiJSFile (ac, name, options) {
var url = prefix(options.from, name)
return load(ac, load.fetch(url, 'text').then(midiJsToJson), options)
}
// convert a MIDI.js javascript soundfont file to json
function midiJsToJson (data) {
var begin = data.indexOf('MIDI.Soundfont.')
if (begin < 0) throw Error('Invalid MIDI.js Soundfont format')
begin = data.indexOf('=', begin) + 2
var end = data.lastIndexOf(',')
return JSON.parse(data.slice(begin, end) + '}')
}
if (typeof module === 'object' && module.exports) module.exports = load
if (typeof window !== 'undefined') window.loadAudio = load
/***/ },
/* 6 */
/***/ function(module, exports) {
'use strict'
// DECODE UTILITIES
function b64ToUint6 (nChr) {
return nChr > 64 && nChr < 91 ? nChr - 65
: nChr > 96 && nChr < 123 ? nChr - 71
: nChr > 47 && nChr < 58 ? nChr + 4
: nChr === 43 ? 62
: nChr === 47 ? 63
: 0
}
// Decode Base64 to Uint8Array
// ---------------------------
function decode (sBase64, nBlocksSize) {
var sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, '')
var nInLen = sB64Enc.length
var nOutLen = nBlocksSize
? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize
: nInLen * 3 + 1 >> 2
var taBytes = new Uint8Array(nOutLen)
for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
nMod4 = nInIdx & 3
nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4
if (nMod4 === 3 || nInLen - nInIdx === 1) {
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255
}
nUint24 = 0
}
}
return taBytes
}
module.exports = { decode: decode }
/***/ },
/* 7 */
/***/ function(module, exports) {
/* global XMLHttpRequest */
'use strict'
/**
* Given a url and a return type, returns a promise to the content of the url
* Basically it wraps a XMLHttpRequest into a Promise
*
* @param {String} url
* @param {String} type - can be 'text' or 'arraybuffer'
* @return {Promise}
*/
module.exports = function (url, type) {
return new Promise(function (done, reject) {
var req = new XMLHttpRequest()
if (type) req.responseType = type
req.open('GET', url)
req.onload = function () {
req.status === 200 ? done(req.response) : reject(Error(req.statusText))
}
req.onerror = function () { reject(Error('Network Error')) }
req.send()
})
}
/***/ },
/* 8 */
/***/ function(module, exports, __webpack_require__) {
'use strict'
var player = __webpack_require__(9)
var events = __webpack_require__(11)
var notes = __webpack_require__(12)
var scheduler = __webpack_require__(14)
var midi = __webpack_require__(15)
function SamplePlayer (ac, source, options) {
return midi(scheduler(notes(events(player(ac, source, options)))))
}
if (typeof module === 'object' && module.exports) module.exports = SamplePlayer
if (typeof window !== 'undefined') window.SamplePlayer = SamplePlayer
/***/ },
/* 9 */
/***/ function(module, exports, __webpack_require__) {
/* global AudioBuffer */
'use strict'
var ADSR = __webpack_require__(10)
var EMPTY = {}
var DEFAULTS = {
gain: 1,
attack: 0.01,
decay: 0.1,
sustain: 0.9,
release: 0.3,
loop: false,
cents: 0,
loopStart: 0,
loopEnd: 0
}
/**
* Create a sample player.
*
* @param {AudioContext} ac - the audio context
* @param {ArrayBuffer|Object<String,ArrayBuffer>} source
* @param {Onject} options - (Optional) an options object
* @return {player} the player
* @example
* var SamplePlayer = require('sample-player')
* var ac = new AudioContext()
* var snare = SamplePlayer(ac, <AudioBuffer>)
* snare.play()
*/
function SamplePlayer (ac, source, options) {
var connected = false
var nextId = 0
var tracked = {}
var out = ac.createGain()
out.gain.value = 1
var opts = Object.assign({}, DEFAULTS, options)
/**
* @namespace
*/
var player = { context: ac, out: out, opts: opts }
if (source instanceof AudioBuffer) player.buffer = source
else player.buffers = source
/**
* Start a sample buffer.
*
* The returned object has a function `stop(when)` to stop the sound.
*
* @param {String} name - the name of the buffer. If the source of the
* SamplePlayer is one sample buffer, this parameter is not required
* @param {Float} when - (Optional) when to start (current time if by default)
* @param {Object} options - additional sample playing options
* @return {AudioNode} an audio node with a `stop` function
* @example
* var sample = player(ac, <AudioBuffer>).connect(ac.destination)
* sample.start()
* sample.start(5, { gain: 0.7 }) // name not required since is only one AudioBuffer
* @example
* var drums = player(ac, { snare: <AudioBuffer>, kick: <AudioBuffer>, ... }).connect(ac.destination)
* drums.start('snare')
* drums.start('snare', 0, { gain: 0.3 })
*/
player.start = function (name, when, options) {
// if only one buffer, reorder arguments
if (player.buffer && name !== null) return player.start(null, name, when)
var buffer = name ? player.buffers[name] : player.buffer
if (!buffer) {
console.warn('Buffer ' + name + ' not found.')
return
} else if (!connected) {
console.warn('SamplePlayer not connected to any node.')
return
}
var opts = options || EMPTY
when = Math.max(ac.currentTime, when || 0)
player.emit('start', when, name, opts)
var node = createNode(name, buffer, opts)
node.id = track(name, node)
node.env.start(when)
node.source.start(when)
player.emit('started', when, node.id, node)
if (opts.duration) node.stop(when + opts.duration)
return node
}
// NOTE: start will be override so we can't copy the function reference
// this is obviously not a good design, so this code will be gone soon.
/**
* An alias for `player.start`
* @see player.start
* @since 0.3.0
*/
player.play = function (name, when, options) {
return player.start(name, when, options)
}
/**
* Stop some or all samples
*
* @param {Float} when - (Optional) an absolute time in seconds (or currentTime
* if not specified)
* @param {Array} nodes - (Optional) an array of nodes or nodes ids to stop
* @return {Array} an array of ids of the stoped samples
*
* @example
* var longSound = player(ac, <AudioBuffer>).connect(ac.destination)
* longSound.start(ac.currentTime)
* longSound.start(ac.currentTime + 1)
* longSound.start(ac.currentTime + 2)
* longSound.stop(ac.currentTime + 3) // stop the three sounds
*/
player.stop = function (when, ids) {
var node
ids = ids || Object.keys(tracked)
return ids.map(function (id) {
node = tracked[id]
if (!node) return null
node.stop(when)
return node.id
})
}
/**
* Connect the player to a destination node
*
* @param {AudioNode} destination - the destination node
* @return {AudioPlayer} the player
* @chainable
* @example
* var sample = player(ac, <AudioBuffer>).connect(ac.destination)
*/
player.connect = function (dest) {
connected = true
out.connect(dest)
return player
}
player.emit = function (event, when, obj, opts) {
if (player.onevent) player.onevent(event, when, obj, opts)
var fn = player['on' + event]
if (fn) fn(when, obj, opts)
}
return player
// =============== PRIVATE FUNCTIONS ============== //
function track (name, node) {
node.id = nextId++
tracked[node.id] = node
node.source.onended = function () {
var now = ac.currentTime
node.source.disconnect()
node.env.disconnect()
node.disconnect()
player.emit('ended', now, node.id, node)
}
return node.id
}
function createNode (name, buffer, options) {
var node = ac.createGain()
node.gain.value = 0 // the envelope will control the gain
node.connect(out)
node.env = envelope(ac, options, opts)
node.env.connect(node.gain)
node.source = ac.createBufferSource()
node.source.buffer = buffer
node.source.connect(node)
node.source.loop = options.loop || opts.loop
node.source.playbackRate.value = centsToRate(options.cents || opts.cents)
node.source.loopStart = options.loopStart || opts.loopStart
node.source.loopEnd = options.loopEnd || opts.loopEnd
node.stop = function (when) {
var time = when || ac.currentTime
player.emit('stop', time, name)
var stopAt = node.env.stop(time)
node.source.stop(stopAt)
}
return node
}
}
function isNum (x) { return typeof x === 'number' }
var PARAMS = ['attack', 'decay', 'sustain', 'release']
function envelope (ac, options, opts) {
var env = ADSR(ac)
var adsr = options.adsr || opts.adsr
PARAMS.forEach(function (name, i) {
if (adsr) env[name] = adsr[i]
else env[name] = options[name] || opts[name]
})
env.value.value = isNum(options.gain) ? options.gain
: isNum(opts.gain) ? opts.gain : 1
return env
}
/*
* Get playback rate for a given pitch change (in cents)
* Basic [math](http://www.birdsoft.demon.co.uk/music/samplert.htm):
* f2 = f1 * 2^( C / 1200 )
*/
function centsToRate (cents) { return cents ? Math.pow(2, cents / 1200) : 1 }
module.exports = SamplePlayer
/***/ },
/* 10 */
/***/ function(module, exports) {
module.exports = ADSR
function ADSR(audioContext){
var node = audioContext.createGain()
var voltage = node._voltage = getVoltage(audioContext)
var value = scale(voltage)
var startValue = scale(voltage)
var endValue = scale(voltage)
node._startAmount = scale(startValue)
node._endAmount = scale(endValue)
node._multiplier = scale(value)
node._multiplier.connect(node)
node._startAmount.connect(node)
node._endAmount.connect(node)
node.value = value.gain
node.startValue = startValue.gain
node.endValue = endValue.gain
node.startValue.value = 0
node.endValue.value = 0
Object.defineProperties(node, props)
return node
}
var props = {
attack: { value: 0, writable: true },
decay: { value: 0, writable: true },
sustain: { value: 1, writable: true },
release: {value: 0, writable: true },
getReleaseDuration: {
value: function(){
return this.release
}
},
start: {
value: function(at){
var target = this._multiplier.gain
var startAmount = this._startAmount.gain
var endAmount = this._endAmount.gain
this._voltage.start(at)
this._decayFrom = this._decayFrom = at+this.attack
this._startedAt = at
var sustain = this.sustain
target.cancelScheduledValues(at)
startAmount.cancelScheduledValues(at)
endAmount.cancelScheduledValues(at)
endAmount.setValueAtTime(0, at)
if (this.attack){
target.setValueAtTime(0, at)
target.linearRampToValueAtTime(1, at + this.attack)
startAmount.setValueAtTime(1, at)
startAmount.linearRampToValueAtTime(0, at + this.attack)
} else {
target.setValueAtTime(1, at)
startAmount.setValueAtTime(0, at)
}
if (this.decay){
target.setTargetAtTime(sustain, this._decayFrom, getTimeConstant(this.decay))
}
}
},
stop: {
value: function(at, isTarget){
if (isTarget){
at = at - this.release
}
var endTime = at + this.release
if (this.release){
var target = this._multiplier.gain
var startAmount = this._startAmount.gain
var endAmount = this._endAmount.gain
target.cancelScheduledValues(at)
startAmount.cancelScheduledValues(at)
endAmount.cancelScheduledValues(at)
var expFalloff = getTimeConstant(this.release)
// truncate attack (required as linearRamp is removed by cancelScheduledValues)
if (this.attack && at < this._decayFrom){
var valueAtTime = getValue(0, 1, this._startedAt, this._decayFrom, at)
target.linearRampToValueAtTime(valueAtTime, at)
startAmount.linearRampToValueAtTime(1-valueAtTime, at)
startAmount.setTargetAtTime(0, at, expFalloff)
}
endAmount.setTargetAtTime(1, at, expFalloff)
target.setTargetAtTime(0, at, expFalloff)
}
this._voltage.stop(endTime)
return endTime
}
},
onended: {
get: function(){
return this._voltage.onended
},
set: function(value){
this._voltage.onended = value
}
}
}
var flat = new Float32Array([1,1])
function getVoltage(context){
var voltage = context.createBufferSource()
var buffer = context.createBuffer(1, 2, context.sampleRate)
buffer.getChannelData(0).set(flat)
voltage.buffer = buffer
voltage.loop = true
return voltage
}
function scale(node){
var gain = node.context.createGain()
node.connect(gain)
return gain
}
function getTimeConstant(time){
return Math.log(time+1)/Math.log(100)
}
function getValue(start, end, fromTime, toTime, at){
var difference = end - start
var time = toTime - fromTime
var truncateTime = at - fromTime
var phase = truncateTime / time
var value = start + phase * difference
if (value <= start) {
value = start
}
if (value >= end) {
value = end
}
return value
}
/***/ },
/* 11 */
/***/ function(module, exports) {
module.exports = function (player) {
/**
* Adds a listener of an event
* @chainable
* @param {String} event - the event name
* @param {Function} callback - the event handler
* @return {SamplePlayer} the player
* @example
* player.on('start', function(time, note) {
* console.log(time, note)
* })
*/
player.on = function (event, cb) {
if (arguments.length === 1 && typeof event === 'function') return player.on('event', event)
var prop = 'on' + event
var old = player[prop]
player[prop] = old ? chain(old, cb) : cb
return player
}
return player
}
function chain (fn1, fn2) {
return function (a, b, c, d) { fn1(a, b, c, d); fn2(a, b, c, d) }
}
/***/ },
/* 12 */
/***/ function(module, exports, __webpack_require__) {
'use strict'
var note = __webpack_require__(13)
var isMidi = function (n) { return n !== null && n !== [] && n >= 0 && n < 129 }
var toMidi = function (n) { return isMidi(n) ? +n : note.midi(n) }
// Adds note name to midi conversion
module.exports = function (player) {
if (player.buffers) {
var map = player.opts.map
var toKey = typeof map === 'function' ? map : toMidi
var mapper = function (name) {
return name ? toKey(name) || name : null
}
player.buffers = mapBuffers(player.buffers, mapper)
var start = player.start
player.start = function (name, when, options) {
var key = mapper(name)
var dec = key % 1
if (dec) {
key = Math.floor(key)
options = Object.assign(options || {}, { cents: Math.floor(dec * 100) })
}
return start(key, when, options)
}
}
return player
}
function mapBuffers (buffers, toKey) {
return Object.keys(buffers).reduce(function (mapped, name) {
mapped[toKey(name)] = buffers[name]
return mapped
}, {})
}
/***/ },
/* 13 */
/***/ function(module, exports) {
'use strict'
var REGEX = /^([a-gA-G])(#{1,}|b{1,}|x{1,}|)(-?\d*)\s*(.*)\s*$/
/**
* A regex for matching note strings in scientific notation.
*
* @name regex
* @function
* @return {RegExp} the regexp used to parse the note name
*
* The note string should have the form `letter[accidentals][octave][element]`
* where:
*
* - letter: (Required) is a letter from A to G either upper or lower case
* - accidentals: (Optional) can be one or more `b` (flats), `#` (sharps) or `x` (double sharps).
* They can NOT be mixed.
* - octave: (Optional) a positive or negative integer
* - element: (Optional) additionally anything after the duration is considered to
* be the element name (for example: 'C2 dorian')
*
* The executed regex contains (by array index):
*
* - 0: the complete string
* - 1: the note letter
* - 2: the optional accidentals
* - 3: the optional octave
* - 4: the rest of the string (trimmed)
*
* @example
* var parser = require('note-parser')
* parser.regex.exec('c#4')
* // => ['c#4', 'c', '#', '4', '']
* parser.regex.exec('c#4 major')
* // => ['c#4major', 'c', '#', '4', 'major']
* parser.regex().exec('CMaj7')
* // => ['CMaj7', 'C', '', '', 'Maj7']
*/
function regex () { return REGEX }
var SEMITONES = [0, 2, 4, 5, 7, 9, 11]
/**
* Parse a note name in scientific notation an return it's components,
* and some numeric properties including midi number and frequency.
*
* @name parse
* @function
* @param {String} note - the note string to be parsed
* @param {Boolean} isTonic - true if the note is the tonic of something.
* If true, en extra tonicOf property is returned. It's false by default.
* @param {Float} tunning - The frequency of A4 note to calculate frequencies.
* By default it 440.
* @return {Object} the parsed note name or null if not a valid note
*
* The parsed note name object will ALWAYS contains:
* - letter: the uppercase letter of the note
* - acc: the accidentals of the note (only sharps or flats)
* - pc: the pitch class (letter + acc)
* - step: s a numeric representation of the letter. It's an integer from 0 to 6
* where 0 = C, 1 = D ... 6 = B
* - alt: a numeric representation of the accidentals. 0 means no alteration,
* positive numbers are for sharps and negative for flats
* - chroma: a numeric representation of the pitch class. It's like midi for
* pitch classes. 0 = C, 1 = C#, 2 = D ... It can have negative values: -1 = Cb.
* Can detect pitch class enhramonics.
*
* If the note has octave, the parser object will contain:
* - oct: the octave number (as integer)
* - midi: the midi number
* - freq: the frequency (using tuning parameter as base)
*
* If the parameter `isTonic` is set to true, the parsed object will contain:
* - tonicOf: the rest of the string that follows note name (left and right trimmed)
*
* @example
* var parse = require('note-parser').parse
* parse('Cb4')
* // => { letter: 'C', acc: 'b', pc: 'Cb', step: 0, alt: -1, chroma: -1,
* oct: 4, midi: 59, freq: 246.94165062806206 }
* // if no octave, no midi, no freq
* parse('fx')
* // => { letter: 'F', acc: '##', pc: 'F##', step: 3, alt: 2, chroma: 7 })
*/
function parse (str, isTonic, tuning) {
if (typeof str !== 'string') return null
var m = REGEX.exec(str)
if (!m || !isTonic && m[4]) return null
var p = { letter: m[1].toUpperCase(), acc: m[2].replace(/x/g, '##') }
p.pc = p.letter + p.acc
p.step = (p.letter.charCodeAt(0) + 3) % 7
p.alt = p.acc[0] === 'b' ? -p.acc.length : p.acc.length
p.chroma = SEMITONES[p.step] + p.alt
if (m[3]) {
p.oct = +m[3]
p.midi = p.chroma + 12 * (p.oct + 1)
p.freq = midiToFreq(p.midi, tuning)
}
if (isTonic) p.tonicOf = m[4]
return p
}
/**
* Given a midi number, return its frequency
* @param {Integer} midi - midi note number
* @param {Float} tuning - (Optional) the A4 tuning (440Hz by default)
* @return {Float} frequency in hertzs
*/
function midiToFreq (midi, tuning) {
return Math.pow(2, (midi - 69) / 12) * (tuning || 440)
}
var parser = { parse: parse, regex: regex, midiToFreq: midiToFreq }
var FNS = ['letter', 'acc', 'pc', 'step', 'alt', 'chroma', 'oct', 'midi', 'freq']
FNS.forEach(function (name) {
parser[name] = function (src) {
var p = parse(src)
return p && (typeof p[name] !== 'undefined') ? p[name] : null
}
})
module.exports = parser
// extra API docs
/**
* Get midi of a note
*
* @name midi
* @function
* @param {String} note - the note name
* @return {Integer} the midi number of the note or null if not a valid note
* or the note does NOT contains octave
* @example
* var parser = require('note-parser')
* parser.midi('A4') // => 69
* parser.midi('A') // => null
*/
/**
* Get freq of a note in hertzs (in a well tempered 440Hz A4)
*
* @name freq
* @function
* @param {String} note - the note name
* @return {Float} the freq of the number if hertzs or null if not valid note
* or the note does NOT contains octave
* @example
* var parser = require('note-parser')
* parser.freq('A4') // => 440
* parser.freq('A') // => null
*/
/***/ },
/* 14 */
/***/ function(module, exports) {
'use strict'
var isArr = Array.isArray
var isObj = function (o) { return o && typeof o === 'object' }
var OPTS = {}
module.exports = function (player) {
/**
* Schedule a list of events to be played at specific time.
*
* It supports three formats of events for the events list:
*
* - An array with [time, note]
* - An array with [time, object]
* - An object with { time: ?, [name|note|midi|key]: ? }
*
* @param {Float} time - an absolute time to start (or AudioContext's
* currentTime if provided number is 0)
* @param {Array} events - the events list.
* @return {Array} an array of ids
*
* @example
* // Event format: [time, note]
* var piano = player(ac, ...).connect(ac.destination)
* piano.schedule(0, [ [0, 'C2'], [0.5, 'C3'], [1, 'C4'] ])
*
* @example
* // Event format: an object { time: ?, name: ? }
* var drums = player(ac, ...).connect(ac.destination)
* drums.schedule(0, [
* { name: 'kick', time: 0 },
* { name: 'snare', time: 0.5 },
* { name: 'kick', time: 1 },
* { name: 'snare', time: 1.5 }
* ])
*/
player.schedule = function (time, events) {
var now = player.context.currentTime
var when = time < now ? now : time
player.emit('schedule', when, events)
var t, o, note, opts
return events.map(function (event) {
if (!event) return null
else if (isArr(event)) {
t = event[0]; o = event[1]
} else {
t = event.time; o = event
}
if (isObj(o)) {
note = o.name || o.key || o.note || o.midi || null
opts = o
} else {
note = o
opts = OPTS
}
return player.start(note, when + (t || 0), opts)
})
}
return player
}
/***/ },
/* 15 */
/***/ function(module, exports, __webpack_require__) {
var midimessage = __webpack_require__(16)
module.exports = function (player) {
/**
* Connect a player to a midi input
*
* The options accepts:
*
* - channel: the channel to listen to. Listen to all channels by default.
*
* @param {MIDIInput} input
* @param {Object} options - (Optional)
* @return {SamplePlayer} the player
* @example
* var piano = player(...)
* window.navigator.requestMIDIAccess().then(function (midiAccess) {
* midiAccess.inputs.forEach(function (midiInput) {
* piano.listenToMidi(midiInput)
* })
* })
*/
player.listenToMidi = function (input, options) {
var started = {}
var opts = options || {}
var gain = opts.gain || function (vel) { return vel / 127 }
input.onmidimessage = function (msg) {
var mm = msg.messageType ? msg : midimessage(msg)
if (mm.messageType === 'noteon' && mm.velocity === 0) {
mm.messageType = 'noteoff'
}
if (opts.channel && mm.channel !== opts.channel) return
switch (mm.messageType) {
case 'noteon':
started[mm.key] = player.play(mm.key, 0, { gain: gain(mm.velocity) })
break
case 'noteoff':
if (started[mm.key]) {
started[mm.key].stop()
delete started[mm.key]
}
break
}
}
return player
}
return player
}
/***/ },
/* 16 */
/***/ function(module, exports, __webpack_require__) {
var require;var require;(function(e){if(true){module.exports=e()}else if(typeof define==="function"&&define.amd){define([],e)}else{var t;if(typeof window!=="undefined"){t=window}else if(typeof global!=="undefined"){t=global}else if(typeof self!=="undefined"){t=self}else{t=this}t.midimessage=e()}})(function(){var e,t,s;return function o(e,t,s){function a(n,i){if(!t[n]){if(!e[n]){var l=typeof require=="function"&&require;if(!i&&l)return require(n,!0);if(r)return r(n,!0);var h=new Error("Cannot find module '"+n+"'");throw h.code="MODULE_NOT_FOUND",h}var c=t[n]={exports:{}};e[n][0].call(c.exports,function(t){var s=e[n][1][t];return a(s?s:t)},c,c.exports,o,e,t,s)}return t[n].exports}var r=typeof require=="function"&&require;for(var n=0;n<s.length;n++)a(s[n]);return a}({1:[function(e,t,s){"use strict";Object.defineProperty(s,"__esModule",{value:true});s["default"]=function(e){function t(e){this._event=e;this._data=e.data;this.receivedTime=e.receivedTime;if(this._data&&this._data.length<2){console.warn("Illegal MIDI message of length",this._data.length);return}this._messageCode=e.data[0]&240;this.channel=e.data[0]&15;switch(this._messageCode){case 128:this.messageType="noteoff";this.key=e.data[1]&127;this.velocity=e.data[2]&127;break;case 144:this.messageType="noteon";this.key=e.data[1]&127;this.velocity=e.data[2]&127;break;case 160:this.messageType="keypressure";this.key=e.data[1]&127;this.pressure=e.data[2]&127;break;case 176:this.messageType="controlchange";this.controllerNumber=e.data[1]&127;this.controllerValue=e.data[2]&127;if(this.controllerNumber===120&&this.controllerValue===0){this.channelModeMessage="allsoundoff"}else if(this.controllerNumber===121){this.channelModeMessage="resetallcontrollers"}else if(this.controllerNumber===122){if(this.controllerValue===0){this.channelModeMessage="localcontroloff"}else{this.channelModeMessage="localcontrolon"}}else if(this.controllerNumber===123&&this.controllerValue===0){this.channelModeMessage="allnotesoff"}else if(this.controllerNumber===124&&this.controllerValue===0){this.channelModeMessage="omnimodeoff"}else if(this.controllerNumber===125&&this.controllerValue===0){this.channelModeMessage="omnimodeon"}else if(this.controllerNumber===126){this.channelModeMessage="monomodeon"}else if(this.controllerNumber===127){this.channelModeMessage="polymodeon"}break;case 192:this.messageType="programchange";this.program=e.data[1];break;case 208:this.messageType="channelpressure";this.pressure=e.data[1]&127;break;case 224:this.messageType="pitchbendchange";var t=e.data[2]&127;var s=e.data[1]&127;this.pitchBend=(t<<8)+s;break}}return new t(e)};t.exports=s["de