@logue/smfplayer
Version:
smfplayer.js is JavaScript based Standard Midi Player for WebMidiLink based synthesizer.
1,518 lines (1,171 loc) • 138 kB
JavaScript
/*! @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