UNPKG

@logue/smfplayer

Version:

smfplayer.js is JavaScript based Standard Midi Player for WebMidiLink based synthesizer.

1,518 lines (1,171 loc) 138 kB
/*! @logue/smfplayer v0.3.6 | imaya / GREE Inc. / Logue | license: MIT | build: 2021-11-04T11:03:12.998Z */ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define("SMF", [], factory); else if(typeof exports === 'object') exports["SMF"] = factory(); else root["SMF"] = factory(); })((typeof self !== 'undefined' ? self : this), function() { return /******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ /***/ "./src/3mle.js": /*!*********************!*\ !*** ./src/3mle.js ***! \*********************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": () => (/* binding */ ThreeMacroLanguageEditor) /* harmony export */ }); /* harmony import */ var _PSGConverter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./PSGConverter */ "./src/PSGConverter.js"); /* harmony import */ var _midi_event__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./midi_event */ "./src/midi_event.js"); /* harmony import */ var _mms__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./mms */ "./src/mms.js"); function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } /** * @classdesc Three Macro Language Editor (3MLE) mml file Parser * * @author Logue <logue@hotmail.co.jp> * @copyright 2019 Masashi Yoshikawa <https://logue.dev/> All rights reserved. * @license MIT */ var ThreeMacroLanguageEditor = /*#__PURE__*/function (_MakiMabiSequence) { _inherits(ThreeMacroLanguageEditor, _MakiMabiSequence); var _super = _createSuper(ThreeMacroLanguageEditor); /** * @param {ByteArray} input * @param {Object=} optParams */ function ThreeMacroLanguageEditor(input) { var optParams = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; _classCallCheck(this, ThreeMacroLanguageEditor); return _super.call(this, input, optParams); } /** */ _createClass(ThreeMacroLanguageEditor, [{ key: "parse", value: function parse() { this.parseHeader(); this.parseTracks(); this.toPlainTrack(); } /** */ }, { key: "parseHeader", value: function parseHeader() { var header = this.input.Settings; /** @type {TextEncoder} */ this.encoder = new TextEncoder(header.Encoding || 'shift_jis'); /** @param {string} */ this.title = header.Title; /** @param {string} */ this.author = header.Source; /** @param {number} */ this.timeDivision = header.TimeBase | 0 || 32; // 曲名と著者情報を付加 /** @type {array} */ var headerTrack = []; // GM Reset headerTrack.push(new _midi_event__WEBPACK_IMPORTED_MODULE_1__.SystemExclusiveEvent('SystemExclusive', 0, 0, [0x7e, 0x7f, 0x09, 0x01])); headerTrack.push(new _midi_event__WEBPACK_IMPORTED_MODULE_1__.MetaEvent('SequenceTrackName', 0, 0, [this.title])); headerTrack.push(new _midi_event__WEBPACK_IMPORTED_MODULE_1__.MetaEvent('CopyrightNotice', 0, 0, [this.author])); headerTrack.push(new _midi_event__WEBPACK_IMPORTED_MODULE_1__.MetaEvent('TextEvent', 0, 0, [header.Memo])); headerTrack.push(new _midi_event__WEBPACK_IMPORTED_MODULE_1__.MetaEvent('TimeSignature', 0, 0, [header.TimeSignatureNN | 0 || 4, header.TimeSignatureDD | 0 || 4, 0, 0])); headerTrack.push(new _midi_event__WEBPACK_IMPORTED_MODULE_1__.MetaEvent('EndOfTrack', 0, 0)); this.tracks.push(headerTrack); // 3MLE EXTENSION、Settingsを取り除く delete this.input['3MLE EXTENSION']; delete this.input.Settings; } /** * MML parse */ }, { key: "parseTracks", value: function parseTracks() { var input = this.input; /** @type {array} 終了時間比較用 */ var endTimes = []; /** @type {array} 各ブロックのMML */ var mmls = []; /** @type {array} 各ブロックの演奏情報 */ var settings = []; for (var block in this.input) { if (!Object.prototype.hasOwnProperty.call(this.input, block)) { continue; } if (block.match(/^Channel(\d+)$/i)) { // MMLは[Channel[n]]ブロックのキー // ひどいファイル形式だ・・・。 mmls[(RegExp.$1 | 0) - 1] = Object.keys(input[block]).join('').replace(/\/\*([^*]|\*[^/])*\*\//g, ''); } if (block.match(/^ChannelProperty(\d+)$/i)) { // 各パートの楽器情報などは[ChannelProperty[n]]に格納されている settings[(RegExp.$1 | 0) - 1] = { name: input[block].Name, instrument: input[block].Patch | 0, panpot: input[block].Pan | 0 }; } } /** @type {array} 整形済みデータ */ var data = []; // データを整形 for (var no in mmls) { if (!Object.prototype.hasOwnProperty.call(mmls, no)) { continue; } if (settings[no] !== void 0) { data[no] = { mml: mmls[no], name: settings[no].name || '', instrument: settings[no].instrument || 0, panpot: settings[no].panpot || 64 }; } else { data[no] = { mml: mmls[no], name: '', instrument: 0, panpot: 64 }; } } // console.log(data); for (var part in data) { if (!Object.prototype.hasOwnProperty.call(data, part)) { continue; } /** @type {number} */ var ch = part | 0; /** @type {array} MIDIイベント */ var track = []; if (data[part].mml === '') { // 空っぽのMMLトラックの場合処理しない return; } // 楽器名 track.push(new _midi_event__WEBPACK_IMPORTED_MODULE_1__.MetaEvent('InsturumentName', 0, 48, [data[part].name])); // プログラムチェンジ track.push(new _midi_event__WEBPACK_IMPORTED_MODULE_1__.ChannelEvent('ProgramChange', 0, 96, ch, data[part].instrument)); // パン track.push(new _midi_event__WEBPACK_IMPORTED_MODULE_1__.ChannelEvent('ControlChange', 0, 154, ch, 10, data[part].panpot)); /** @param {PSGConverter} */ var mml2Midi = new _PSGConverter__WEBPACK_IMPORTED_MODULE_0__["default"]({ timeDivision: this.timeDivision, channel: ch, timeOffset: 386, mml: data[part].mml }); // トラックにマージ track = track.concat(mml2Midi.events); // 演奏時間を更新 endTimes.push(mml2Midi.endTime); // トラック終了 track.concat(new _midi_event__WEBPACK_IMPORTED_MODULE_1__.MetaEvent('EndOfTrack', 0, Math.max(endTimes))); this.tracks.push(track); } this.numberOfTracks = this.tracks.length; } }]); return ThreeMacroLanguageEditor; }(_mms__WEBPACK_IMPORTED_MODULE_2__["default"]); /***/ }), /***/ "./src/PSGConverter.js": /*!*****************************!*\ !*** ./src/PSGConverter.js ***! \*****************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": () => (/* binding */ PSGConverter) /* harmony export */ }); /* harmony import */ var _midi_event__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./midi_event */ "./src/midi_event.js"); function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } /** * @class PSGConverter * @classdesc Mabinogi MML and Maple Story 2 MML to MIDI Converter. * @version 3.0.2 * * @author Logue <logue@hotmail.co.jp> * @copyright 2019 Masashi Yoshikawa <https://logue.dev/> All rights reserved. * @license MIT */ var PSGConverter = /*#__PURE__*/function () { /** * Constructor * @param {array} optParams */ function PSGConverter() { var optParams = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _classCallCheck(this, PSGConverter); /** @type {number} 分解能 */ this.timeDivision = optParams.timeDivision | 0 || 96; /** @type {number} チャンネル(0~15) */ this.channel = optParams.channel | 0; /** @type {number} 演奏開始までのオフセット時間 */ this.timeOffset = optParams.timeOffset | 0; /** @type {string} MMLのチャンネルごとのマッチパターン */ this.PATTERN = /[a-glnortv<>][+#-]?\d*\.?&?/g; /** @type {Array<string, number>} ノートのマッチングテーブル */ this.NOTE_TABLE = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 }; /** @type {number} 1拍(Tick連動) */ this.MINIM = this.timeDivision * 2; /** @type {number} 1小節 */ this.SEMIBREVE = this.timeDivision * 4; /** @type {number} ベロシティの倍率 */ this.VELOCITY_MAGNIFICATION = 7; // 127÷15≒8.4 /** @type {array} MMLデータ */ this.mml = optParams.mml; /** @type {array} イベント */ this.events = []; /** @type {array} WML送信用イベント */ this.plainEvents = []; /** @type {number} 終了時間 */ this.endTime = 0; /** @type {number} ノートオフの逆オフセット(tick指定) */ this.noteOffNegativeOffset = 2; /** @type {bool} テンポ命令を無視する */ this.ignoreTempo = optParams.igonreTempo | false; /** @type {number} 最大オクターブ */ this.maxOctave = optParams.maxOctave | 8; /** @type {number} 最小オクターブ */ this.minOctave = optParams.minOctave | 0; /** @type {number} オクターブモード(0:処理しない。1:外れる音階はその前後のオクターブをループする。2:常に同じ音を鳴らす */ this.octaveMode = optParams.octaveMode | 0; /** @type {number} 最低音階(octaveModeが0の場合は無視されます。デフォルトはピアノの音階。GM音源で再生するとき用) */ this.minNote = optParams.minNote | 12; /** @type {number} 最高音階(octaveModeが0の場合は無視されます。デフォルトはピアノの音階。GM音源で再生するとき用) */ this.maxNote = optParams.minNote | 98; // 変換実行 this.parse(); } /** * Parse MML */ _createClass(PSGConverter, [{ key: "parse", value: function parse() { /** @type {Array} MMLストリーム */ var mmls = []; try { // 小文字に変換した後正規表現で命令単位で分割する。 mmls = this.mml.toLowerCase().match(this.PATTERN); } catch (e) { console.warn('Could not parse MML.', this.mml); return; } if (!mmls) { // 空欄の場合処理しない return; } /** @type {number} タイムスタンプ */ var time = this.timeOffset; /** @type {number} 現在の音の長さ */ var currentSoundLength = this.timeDivision; /** @type {number} 現在の音階 */ var currentNote = 0; /** @type {number} ベロシティ(0~15) */ var currentVelocity = 8; /** @type {number} オクターブ(0~8) */ var currentOctave = 4; /** @type {bool} タイ記号 */ var tieEnabled = false; /** @type {array} MIDIイベント */ var events = []; // MMLを命令単位でパース var _iterator = _createForOfIteratorHelper(mmls), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var message = _step.value; /** @type {number} すすめるtick数 */ var tick = currentSoundLength | 0; /** @type {string} コマンド */ var command = ''; /** @type {number} 値 */ var value = 0; // 音長(L)、オクターブ(O<>)、テンポ(T)、ベロシティ(V)をパース if (message.match(/([lotv<>])([1-9]\d*|0?)(\.?)(&?)/)) { command = RegExp.$1.toLowerCase(); value = RegExp.$2 | 0; if (tieEnabled && RegExp.$4 !== '&') { // タイ記号 tieEnabled = false; events.push(new _midi_event__WEBPACK_IMPORTED_MODULE_0__.ChannelEvent('NoteOff', 0, time - this.noteOffNegativeOffset, this.channel, currentNote)); } switch (command) { case 'l': // 音長設定 Ln[.] (n=1~192) if (value >= 1 && value <= this.MINIM) { currentSoundLength = Math.floor(this.SEMIBREVE / value); if (RegExp.$3 === '.') { // 付点の場合音長を1.5倍する currentSoundLength = Math.floor(currentSoundLength * 1.5); } } break; case 'o': // オクターブ設定 On (n=1~8) if (value >= this.minOctave && value <= this.maxOctave) { currentOctave = value; } break; case 't': // テンポ設定 Tn (n=32~255) events.push(new _midi_event__WEBPACK_IMPORTED_MODULE_0__.MetaEvent('SetTempo', 0, time, [Math.floor(60000000 / value)])); break; case 'v': // ベロシティ調整 if (value >= 0 && value <= 15) { currentVelocity = value; } break; // 簡易オクターブ設定 {<>} case '<': currentOctave = currentOctave <= this.minOctave ? this.minOctave : currentOctave - 1; break; case '>': currentOctave = currentOctave >= this.maxOctave ? this.maxOctave : currentOctave + 1; break; } } else if (message.match(/([a-gn])([+#-]?)(\d*)(\.?)(&?)/)) { // ノート命令(CDEFGAB)、絶対音階指定(N)をパース /** @type {number} 音階 */ var note = 0; command = RegExp.$1.toLowerCase(); value = RegExp.$3 | 0; if (command === 'n') { // Nn:絶対音階指定 Lで指定した長さに設定 note = value; } else { // [A-G]:音名表記 // 音符の長さ指定: n分音符→128分音符×tick数 if (value >= 1 && value <= this.MINIM) { tick = Math.floor(this.SEMIBREVE / value); // L1 -> 384tick .. L64 -> 6tick } if (RegExp.$4 === '.') { tick = Math.floor(tick * 1.5); // 付点つき -> 1.5倍 } if (this.octaveMode !== 2) { // 音名→音階番号変換(C1 -> 12, C4 -> 48, ..) note = 12 * currentOctave + this.NOTE_TABLE[command]; // 調音記号の処理 if (RegExp.$2 === '+' || RegExp.$2 === '#') { note++; } else if (RegExp.$2 === '-') { note--; } } } // オクターブ調整(楽器の音域エミュレーション。通常は0。GM互換モード時のみ使用) switch (this.octaveMode) { case 1: // オクターブループモード while (note < this.minNote) { note = note + 12; } while (note > this.maxNote) { note = note - 12; } note += 12; break; case 2: // ワンショットモード(音階の強制指定) note = this.maxNote; break; default: // 通常モード(非GMモードでは常にこれ) note += 12; break; } if (!tieEnabled) { // 前回タイ記号が無いときのみノートオン events.push(new _midi_event__WEBPACK_IMPORTED_MODULE_0__.ChannelEvent('NoteOn', 0, time, this.channel, note, currentVelocity * this.VELOCITY_MAGNIFICATION // ※127÷15≒8.4なので8とする。 )); } else if (note !== currentNote) { // c&dなど無効なタイの処理 events.push(new _midi_event__WEBPACK_IMPORTED_MODULE_0__.ChannelEvent('NoteOff', 0, time - this.noteOffNegativeOffset, this.channel, currentNote)); tieEnabled = false; } // タイムカウンタを音符の長さだけ進める time += tick; // ノートオフ命令の追加 if (RegExp.$5 === '&') { // タイ記号の処理 tieEnabled = true; currentNote = note; // 直前の音階を保存 } else { tieEnabled = false; // 発音と消音が同じ時間の場合、そこのノートが再生されないため、消音時にtimeを-1する。 events.push(new _midi_event__WEBPACK_IMPORTED_MODULE_0__.ChannelEvent('NoteOff', 0, time - this.noteOffNegativeOffset, this.channel, note)); } } else if (message.match(/R(\d*)(\.?)/i)) { // 休符設定 R[n][.] (n=1~64) value = RegExp.$1 | 0; if (value >= 1 && value <= this.MINIM) { // L1 -> 128tick .. L64 -> 2tick tick = Math.floor(this.SEMIBREVE / value); } if (RegExp.$2 === '.') { // 付点つき -> 1.5倍 tick = Math.floor(tick * 1.5); } time += tick; // タイムカウンタを休符の長さだけ進める } else { console.warn('unknown signeture.', message); } } // イベントを代入 } catch (err) { _iterator.e(err); } finally { _iterator.f(); } this.events = events; // 演奏完了時間を代入 this.endTime = time; } }]); return PSGConverter; }(); /***/ }), /***/ "./src/meta.js": /*!*********************!*\ !*** ./src/meta.js ***! \*********************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) /* harmony export */ }); // This file is auto-generated by the build system. var Meta = { version: '0.3.5', date: '2021-09-19T04:13:21.442Z' }; /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Meta); /***/ }), /***/ "./src/midi_event.js": /*!***************************!*\ !*** ./src/midi_event.js ***! \***************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "ChannelEvent": () => (/* binding */ ChannelEvent), /* harmony export */ "SystemExclusiveEvent": () => (/* binding */ SystemExclusiveEvent), /* harmony export */ "MetaEvent": () => (/* binding */ MetaEvent) /* harmony export */ }); function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** * Midi Event abstract Structure */ var Event = /** * @param {string} subtype event subtype name. * @param {number} deltaTime delta time. * @param {number} time time. */ function Event(subtype, deltaTime, time) { _classCallCheck(this, Event); /** @type {string} */ this.subtype = subtype; /** @type {number} */ this.deltaTime = deltaTime; /** @type {number} */ this.time = time; }; /** * Midi Channel Event Structure * @extends {Event} */ var ChannelEvent = /*#__PURE__*/function (_Event) { _inherits(ChannelEvent, _Event); var _super = _createSuper(ChannelEvent); /** * @param {string} subtype * @param {number} deltaTime delta time. * @param {number} time time. * @param {number} channel * @param {number=} optParameter1 * @param {number=} optParameter2 */ function ChannelEvent(subtype, deltaTime, time, channel, optParameter1, optParameter2) { var _this; _classCallCheck(this, ChannelEvent); _this = _super.call(this, subtype, deltaTime, time); /** @type {number} */ _this.channel = channel; /** @type {(number|undefined)} */ _this.parameter1 = optParameter1; /** @type {(number|undefined)} */ _this.parameter2 = optParameter2; return _this; } return ChannelEvent; }(Event); /** * System Exclusive Event Structure * @extends {Event} */ var SystemExclusiveEvent = /*#__PURE__*/function (_Event2) { _inherits(SystemExclusiveEvent, _Event2); var _super2 = _createSuper(SystemExclusiveEvent); /** * @param {string} subtype * @param {number} deltaTime delta time. * @param {number} time time. * @param {ByteArray} data */ function SystemExclusiveEvent(subtype, deltaTime, time, data) { var _this2; _classCallCheck(this, SystemExclusiveEvent); _this2 = _super2.call(this, subtype, deltaTime, time); /** @type {ByteArray} */ _this2.data = data; return _this2; } return SystemExclusiveEvent; }(Event); /** * Midi Meta Event Structure * @extends {Event} */ var MetaEvent = /*#__PURE__*/function (_Event3) { _inherits(MetaEvent, _Event3); var _super3 = _createSuper(MetaEvent); /** * @param {string} subtype * @param {number} deltaTime delta time. * @param {number} time time. * @param {Array.<*>} data meta data. */ function MetaEvent(subtype, deltaTime, time, data) { var _this3; _classCallCheck(this, MetaEvent); _this3 = _super3.call(this, subtype, deltaTime, time); /** @type {Array.<*>} */ _this3.data = data; return _this3; } return MetaEvent; }(Event); /***/ }), /***/ "./src/mld.js": /*!********************!*\ !*** ./src/mld.js ***! \********************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": () => (/* binding */ Mld) /* harmony export */ }); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } /** * Mld Parser Class */ var Mld = /*#__PURE__*/function () { /** * @param {ByteArray} input * @param {Object=} optParams */ function Mld(input) { var optParams = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; _classCallCheck(this, Mld); /** @type {ByteArray} */ this.input = input; /** @type {number} */ this.ip = optParams.index || 0; /** @type {number} */ this.timeDivision = optParams.timeDivision || 48; /** @type {Object} */ this.header = {}; /** @type {Object} */ this.dataInformation = {}; /** @type {Array.<Array.<Object>>} */ this.tracks = []; } /** */ _createClass(Mld, [{ key: "parse", value: function parse() { this.parseHeader(); this.parseDataInformation(); this.parseTracks(); } /** */ }, { key: "parseHeader", value: function parseHeader() { /** @type {ByteArray} */ var input = this.input; /** @type {number} */ var ip = this.ip; /** @type {Object} */ var header = this.header = {}; /** @type {string} */ var signature = String.fromCharCode(input[ip++], input[ip++], input[ip++], input[ip++]); if (signature !== 'melo') { throw new Error('invalid MFi signature:' + signature); } header.fileLength = (input[ip++] << 24 | input[ip++] << 16 | input[ip++] << 8 | input[ip++]) >>> 0; header.trackOffset = (input[ip++] << 16 | input[ip++]) + ip; header.dataMajorType = input[ip++]; header.dataMinorType = input[ip++]; header.numberOfTracks = input[ip++]; this.ip = ip; } /** */ }, { key: "parseDataInformation", value: function parseDataInformation() { /** @type {ByteArray} */ var input = this.input; /** @type {number} */ var ip = this.ip; /** @type {Object} */ var dataInformation = this.dataInformation = { copy: null, date: null, exst: null, note: null, prot: null, sorc: null, titl: null, trac: null, vers: null }; /** @type {string} */ var type; /** @type {number} */ var size; while (ip < this.header.trackOffset) { type = String.fromCharCode(input[ip++], input[ip++], input[ip++], input[ip++]); size = input[ip++] << 8 | input[ip++]; switch (type) { case 'titl': /* FALLTHROUGH */ case 'copy': /* FALLTHROUGH */ case 'vers': /* FALLTHROUGH */ case 'date': /* FALLTHROUGH */ case 'prot': dataInformation[type] = String.fromCharCode.apply(null, input.subarray(ip, ip += size)); break; case 'sorc': dataInformation[type] = input[ip++]; break; case 'note': dataInformation[type] = input[ip++] << 8 | input[ip++]; break; case 'exst': /* FALLTHROUGH */ break; default: dataInformation[type] = input.subarray(ip, ip += size); break; } } this.ip = ip; } /** */ }, { key: "parseTracks", value: function parseTracks() { /** @type {ByteArray} */ var input = this.input; /** @type {number} */ var ip = this.ip; /** @type {string} */ var signature; /** @type {number} */ var size; /** @type {number} */ var limit; /** @type {number} */ var status; /** @type {number} */ var extendStatus; /** @type {Object} */ var message; /** @type {Array.<Array.<Object>>} */ var tracks = this.tracks = []; /** @type {Array.<Object>} */ var track; /** @type {number} */ var i; /** @type {number} */ var il; /** * @return {Array.<Object>} */ var parseEditInstrument = function parseEditInstrument() { /** @type {number} */ var length = input[ip++] << 8 | input[ip++]; /** @type {number} */ var limit = ip + length; /** @type {Array.<Object>} */ var result = []; /** @type {Object} */ var info; // const if (input[ip++] !== 1) { throw new Error('invalid EditInstrument const value:' + input[ip - 1]); } while (ip < limit) { info = {}; info.part = input[ip++] >> 4 & 0x3; info.modulator = { ML: input[ip] >> 5, VIV: input[ip] >> 4 & 0x1, EG: input[ip] >> 3 & 0x1, SUS: input[ip] >> 2 & 0x1, RR: (input[ip++] & 0x3) << 2 | input[ip] >> 6, DR: input[ip] >> 4 & 0xf, AR: (input[ip++] & 0x3) << 2 | input[ip] >> 6, SL: input[ip] >> 4 & 0xf, TL: (input[ip++] & 0x3) << 4 | input[ip] >> 4, WF: input[ip] >> 3 & 0x1, FB: input[ip++] & 0x7 }; info.carrier = { ML: input[ip] >> 5, VIV: input[ip] >> 4 & 0x1, EG: input[ip] >> 3 & 0x1, SUS: input[ip] >> 2 & 0x1, RR: (input[ip++] & 0x3) << 2 | input[ip] >> 6, DR: input[ip] >> 4 & 0xf, AR: (input[ip++] & 0x3) << 2 | input[ip] >> 6, SL: input[ip] >> 4 & 0xf, TL: (input[ip++] & 0x3) << 4 | input[ip] >> 4, WF: input[ip] >> 3 & 0x1, FB: input[ip++] & 0x7 }; info.octaveSelect = input[ip++] & 0x3; result.push(info); } return result; }; /** * @return {{part: number, switch: number}} */ var parseVibrato = function parseVibrato() { // const if (input[ip++] !== 1) { throw new Error('invalid Vibrato const value:' + input[ip - 1]); } return { part: input[ip++] >> 5 & 0x3, "switch": input[ip++] >> 6 }; }; /** * @return {{data: ByteArray}} */ var parseDeviceSpecific = function parseDeviceSpecific() { /** @type {number} */ var length = input[ip++] << 8 | input[ip++]; /** @type {number} */ var limit = ip + length; // const if (input[ip++] !== 0x11) { throw new Error('invalid DeviceSpecific const value:' + input[ip - 1]); } return { data: input.subarray(ip, ip += limit - ip) }; }; for (i = 0, il = this.header.numberOfTracks; i < il; ++i) { signature = String.fromCharCode(input[ip++], input[ip++], input[ip++], input[ip++]); if (signature !== 'trac') { throw new Error('invalid track signature:' + signature); } size = input[ip++] << 24 | input[ip++] << 16 | input[ip++] << 8 | input[ip++]; limit = ip + size; track = tracks[i] = []; while (ip < limit) { message = { key: null, length: null, octaveShift: null, subType: null, type: null, value: {}, velocity: null, voice: null }; // delta time message.deltaTime = input[ip++]; // status status = input[ip++]; if (status !== 0xff) { message.type = 'note'; message.subType = 'Note'; message.voice = status >> 6; message.key = status & 0x3f; // note length // noteLength = message.length = input[ip++]; // extend status if (this.dataInformation.note === 1) { extendStatus = input[ip++]; message.velocity = extendStatus >> 2; message.octaveShift = extendStatus & 0x3; } } else { message.type = 'meta'; // status status = input[ip++]; switch (status >> 4) { // system message case 0xb: switch (status & 0xf) { case 0x0: message.subType = 'MasterVolume'; message.value = input[ip++]; break; case 0xa: message.subType = 'DrumScale'; message.value = { channel: input[ip] >> 3 & 0x7, drum: input[ip++] & 0x1 }; break; default: throw new Error('unknown message type:' + status.toString(16)); } break; // tempo message case 0xc: message.subType = 'SetTempo'; message.value = { timeBase: (status & 0x7) === 7 ? NaN : Math.pow(2, status & 0x7) * ((status & 0x8) === 0 ? 6 : 15), tempo: input[ip++] }; break; // control message case 0xd: switch (status & 0xf) { case 0x0: message.subType = 'Point'; message.value = input[ip++]; break; case 0xd: message.subType = 'Loop'; message.value = { id: input[ip] >> 6, count: input[ip] >> 2 & 0xf, point: input[ip++] & 0x3 }; break; case 0xe: message.subType = 'Nop'; message.value = input[ip++]; break; case 0xf: message.subType = 'EndOfTrack'; message.value = input[ip++]; break; default: throw new Error('unkwnon message type:' + status.toString(16)); } break; // instrument case 0xe: switch (status & 0xf) { case 0x0: message.subType = 'InstrumentLowPart'; message.value = { part: input[ip] >> 6, instrument: input[ip++] & 0x3f }; break; case 0x1: message.subType = 'InstrumentHighPart'; message.value = { part: input[ip] >> 6, instrument: input[ip++] & 0x1 }; break; case 0x2: message.subType = 'Volume'; message.value = { part: input[ip] >> 6, volume: input[ip++] & 0x3f }; break; case 0x3: message.subType = 'Valance'; message.value = { part: input[ip] >> 6, valance: input[ip++] & 0x3f }; break; case 0x4: message.subType = 'PitchBend'; message.value = { part: input[ip] >> 6, value: input[ip++] & 0x3f }; break; case 0x5: message.subType = 'ChannelAssign'; message.value = { part: input[ip] >> 6, channel: input[ip++] & 0x3f }; break; case 0x6: message.subType = 'VolumeChange'; message.value = { part: input[ip] >> 6, volume: (input[ip++] & 0x3f) << 26 >> 26 }; break; case 0x7: message.subType = 'PitchBendRange'; message.value = { part: input[ip] >> 6, value: input[ip++] & 0x3f }; break; /* case 0x8: // TODO: 未遭遇 message.subType = 'MasterFineTuning'; message.value = { 'part': input[ip] >> 6, 'value': (input[ip++] & 0x3f) }; break; */ // TODO: あってるか自信ない case 0x9: message.subType = 'MasterCoarseTuning'; message.value = { part: input[ip] >> 6, value: input[ip++] & 0x3f }; break; case 0xa: message.subType = 'Modulation'; message.value = { part: input[ip] >> 6, depth: input[ip++] & 0x3f }; break; default: throw new Error('unkwnon message type:' + status.toString(16)); } break; // extended information case 0xf: switch (status & 0xf) { case 0x0: message.subType = 'EditInstrument'; message.value = parseEditInstrument(); break; case 0x1: message.subType = 'Vibrato'; message.value = parseVibrato(); break; case 0xf: message.subType = 'DeviceSpecific'; message.value = parseDeviceSpecific(); break; default: throw new Error('unkwnon message type:' + status.toString(16)); } break; default: throw new Error('unkwnon message type:' + status.toString(16)); } } track.push(message); } ip = limit; } this.ip = ip; } /** * @return {Object} */ }, { key: "convertToMidiTracks", value: function convertToMidiTracks() { /** @type {Object} */ var result = { timeDivision: this.timeDivision, trac: [], tracks: [], plainTracks: [] }; /** @type {Array.<Array.<Object>>} */ var tracks = result.tracks; /** @type {Array.<Array.<Array.<number>>>} */ var plainTracks = result.plainTracks; /** @type {Array.<Array.<Object>>} */ var mfiTracks = this.tracks; /** @type {Array.<Object>} */ var mfiTrack; /** @type {Object} */ var mfiEvent; /** @type {Object} */ var prevEvent; /** @type {Array.<Object>} */ var tmpTrack; /** @type {number} */ var time; /** @type {number} */ var pos; /** @type {number} */ var key; /** @type {number} */ var tmp; /** @type {string} */ var str; /** @type {number} */ var i; /** @type {number} */ var il; /** @type {number} */ var j; /** @type {number} */ var jl; /** @type {Array.<number>} */ var channelTime = []; /** @type {number} */ var channel; for (i = 0; i < 16; ++i) { plainTracks[i] = []; channelTime[i] = 0; } // 変換しにくい形式を平坦化する for (i = 0, il = mfiTracks.length; i < il; ++i) { mfiTrack = mfiTracks[i]; tmpTrack = []; // note の処理 for (time = pos = j = 0, jl = mfiTrack.length; j < jl; ++j) { mfiEvent = mfiTrack[j]; time += mfiEvent.deltaTime; mfiEvent.id = pos; mfiEvent.time = time; switch (mfiEvent.subType) { case 'Nop': break; case 'Note': tmpTrack[pos++] = mfiEvent; // TODO: value: ... 形式になおす tmpTrack[pos] = { id: pos, type: 'internal', subType: 'NoteOff', time: time + mfiEvent.length, key: mfiEvent.key, voice: mfiEvent.voice, velocity: mfiEvent.velocity, octaveShift: mfiEvent.octaveShift }; pos++; break; case 'InstrumentHighPart': prevEvent = mfiEvent; mfiEvent = mfiTrack[++j]; if (mfiEvent.subType !== 'InstrumentLowPart') { throw new Error('broken instrument'); } // TODO: value: ... 形式になおす tmpTrack[pos] = { id: pos, type: 'internal', subType: 'ProgramChange', time: time, part: mfiEvent.value.part, instrument: prevEvent.value.instrument << 6 | mfiEvent.value.instrument }; pos++; break; default: tmpTrack[pos++] = mfiEvent; break; } } tmpTrack.sort(function (a, b) { return a.time > b.time ? 1 : a.time < b.time ? -1 : a.id > b.id ? 1 : a.id < b.id ? -1 : 0; }); // MIDI トラックに作成 tracks[i] = []; for (time = j = 0, jl = tmpTrack.length; j < jl; ++j) { mfiEvent = tmpTrack[j]; time = mfiEvent.time; switch (mfiEvent.subType) { case 'Note': // NoteOn: 9n kk vv key = this.applyOctaveShift(mfiEvent.key + 45, mfiEvent.octaveShift); channel = i * 4 + mfiEvent.voice; // TODO: リズムトラックの時は Key が -10 されているような気がする if (channel === 9) { key -= 10; } plainTracks[channel].push(this.deltaTimeToByteArray(time - channelTime[channel]).concat(0x90 | channel, key, mfiEvent.velocity * 2)); break; case 'NoteOff': // NoteOff: 8n kk vv key = this.applyOctaveShift(mfiEvent.key + 45, mfiEvent.octaveShift); channel = i * 4 + mfiEvent.voice; // TODO: リズムトラックの時は Key が -10 されているような気がする if (channel === 9) { key -= 10; } plainTracks[channel].push(this.deltaTimeToByteArray(time - channelTime[channel]).concat(0x80 | channel, key, mfiEvent.velocity * 2)); break; case 'ProgramChange': // Program Change: Cn pp channel = i * 4 + mfiEvent.part; plainTracks[channel].push(this.deltaTimeToByteArray(time - channelTime[channel]).concat(0xc0 | channel, mfiEvent.instrument)); break; case 'SetTempo': // SetTempo: FF 51 03 tt tt tt tmp = 2880000000 / (mfiEvent.value.tempo * mfiEvent.value.timeBase); channel = 0; // SetTempo は必ず先頭のトラックに配置する plainTracks[channel].push(this.deltaTimeToByteArray(time - channelTime[channel]).concat(0xff, 0x51, 0x03, tmp >> 16 & 0xff, tmp >> 8 & 0xff, tmp & 0xff)); break; case 'Loop': // Marker: FF 06 ll ss ss ss ... tmp = mfiEvent.value.count; str = 'LOOP_' + (mfiEvent.value.point === 0 ? 'START' : 'END') + '=ID:' + mfiEvent.value.id + ',COUNT:' + (tmp === 0 ? -1 : tmp); channel = 0; plainTracks[channel].push(this.deltaTimeToByteArray(time - channelTime[channel]).concat([0xff, 0x06, str.length], str.split('').map(function (a) { return a.charCodeAt(0); }))); break; case 'MasterVolume': // Master Volume: F0 7F ee 04 01 dl dm F7 tmp = mfiEvent.value; channel = 0; plainTracks[channel].push(this.deltaTimeToByteArray(time - channelTime[channel]).concat(0xf0, 0x07, // length 0x7f, 0x7f, 0x04, 0x01, tmp, tmp, 0xf7)); break; case 'Modulation': // CC#1 Modulation: Bn 01 dd channel = i * 4 + mfiEvent.value.part; plainTracks[channel].push(this.deltaTimeToByteArray(time - channelTime[channel]).concat(0xb0 | channel, 0x01, mfiEvent.value.depth * 2)); break; case 'Volume': // CC#7 Volume: Bn 07 dd channel = i * 4 + mfiEvent.value.part; plainTracks[channel].pu