UNPKG

react-orchestra

Version:

A toolbox to build interactive and smart instruments on the web and mobile.

326 lines (263 loc) 11.5 kB
'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 = require('soundfont-player'); var _soundfontPlayer2 = _interopRequireDefault(_soundfontPlayer); var _bluebird = require('bluebird'); var _bluebird2 = _interopRequireDefault(_bluebird); var _lodash = require('lodash'); var _lodash2 = _interopRequireDefault(_lodash); var _MidiIO = require('../MidiIO'); var _MidiIO2 = _interopRequireDefault(_MidiIO); var _immutabilityHelper = require('immutability-helper'); var _immutabilityHelper2 = _interopRequireDefault(_immutabilityHelper); var _Note = require('./Note'); var _Note2 = _interopRequireDefault(_Note); var _AudioContext = require('./AudioContext'); var _AudioContext2 = _interopRequireDefault(_AudioContext); var _NOTES = require('./constants/NOTES'); var _NOTES2 = _interopRequireDefault(_NOTES); var _SCALES = require('./constants/SCALES'); var _SCALES2 = _interopRequireDefault(_SCALES); var _CHORDS = require('./constants/CHORDS'); var _CHORDS2 = _interopRequireDefault(_CHORDS); var _helpers = require('./helpers/'); 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; module.exports = exports['default'];