UNPKG

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
/*! * 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