開発環境 メモ帳
実行環境 Internet Explorer 11


mml2smf.html
<!doctype html>
<head>
<title>mml2smf</title>
<script>
var scale = {A:9, B:11, C:0, D:2, E:4, F:5, G:7};	// 音階
 
var timebase = 960;
var st;			// stream
var stpos;		// stream position
var track;
var seq;		// sequence
var atick;		// absolute tick
var deflen;		// default length
var octave;
var velocity;
var ch;			// channel
var mml;
var pos;		// position
var defpc;		// default program change
 
function download()
{
	var element = document.getElementById("mml");
	mml = element.value.toUpperCase();
 
	element = document.getElementById("defpc");
	defpc = parseInt(element.value);
	if (isNaN(defpc) || defpc < 1 || 128 < defpc) {
		alert("error: 楽器No.");
		return;
	}
 
	st = [];
	stpos = 0;
	track = [];
	ch = 0;
 
	trackStart();
	for (pos = 0; pos < mml.length; ) {
		var cmd = mml.substr(pos++, 1);
		console.log(cmd);
		switch (cmd) {
		case ",":
			trackEnd();
			trackStart();
			break;
		case "<":
			octave--;
			break;
		case ">":
			octave++;
			break;
		case "@":	// program change
			var value = getValue();
			if (value < 1) break;
			seqData(0, [0xc0 | ch, value - 1]);
			break;
		case "A": case "B": case "C": case "D": case "E": case "F": case "G":
			note(cmd);
			break;
		case "L":
			deflen = getValue();
			if (deflen < 1) deflen = 4;
			break;
		case "N":	// note number, mml:0-96 C4=48
			noteNo();
			break;
		case "O":
			octave = getValue();
			break;
		case "R":
			rest();
			break;
		case "T":
			var tempo = getValue();
			var usec = 60000000 / tempo;	// 4分音符のマイクロ秒数
			seqData(0, [0xff, 0x51, 3, (usec>>16)&0xff, (usec>>8)&0xff, usec&0xff]);
			break;
		case "V":	// volume, mml:0-15 midi:0-127
			var volume = getValue();
			seqData(0, [0xb0 | ch, 0x07, volume<<3]);
			break;
		}
	}
	trackEnd();
 
	// タイ処理
	for (var tr in track) {
		seq = track[tr];
		for (var s in seq) {
			var ds = seq[s];
			for (var n = s - 1; 0 <= n; n--) {
				var dn = seq[n];
				if (dn == null) continue;
				if (dn.tick != ds.tick) break;
				if (dn[1] != ds[1]) continue;			// note number
				if (dn[0] & 0xf != ds[0] & 0xf) continue;	// channel
				if (dn[0] & 0x80 && ds[0] & 0x90) {		// note off/on
					delete seq[n];
					delete seq[s];
				}
			}
		}
	}
 
	// ヘッダチャンク
	writeBE(4, 0x4d546864);		// MThd
	writeBE(4, 6);			// データ長
	writeBE(2, 1);			// フォーマットタイプ
	writeBE(2, track.length);	// トラック数
	writeBE(2, timebase);		// タイムベース
 
	for (var tr in track) {
		seq = track[tr];
 
		// トラックチャンク
		writeBE(4, 0x4d54726b);	// MTrk
		writeBE(4, 0);		// データ長(仮)
		var datalen = 0;
		var prevtick = 0;
		for (var s in seq) {
			var data = seq[s];
			if (data == null) continue;
			var t = data.tick - prevtick;	// delta time
			prevtick = data.tick;
			// 可変長tick
			var v = ((t&0xfe00000)<<3)|((t&0x1fc000)<<2)|((t&0x3f80)<<1)|(t&0x7f)|0x80808000;
			for (var n = 3; 0 <= n; n--) {
				var b = (v >> (8 * n)) & 0xff;
				if (b != 0x80) {
					st[stpos++] = b;
					datalen++;
				}
			}
			for (var d = 0; d < data.length; d++) {
				st[stpos++] = data[d];
				datalen++;
			}
		}
		var p = stpos;
		stpos = p - datalen - 4;
		writeBE(4, datalen);	// データ長
		stpos = p;
	}
 
	var ar = new Uint8Array(st);
	var blob = new Blob([ar]);
	navigator.msSaveOrOpenBlob(blob, "sample.mid");
}
 
function trackStart()
{
	seq = [];
	atick = 0;
	deflen = 4;
	octave = 4;
	velocity = 100;
 
	seqData(0, [0xc0 | ch, defpc - 1]);
}
 
function trackEnd()
{
	seqData(0, [0xff, 0x2f, 0]);
	track.push(seq);
}
 
function seqData(tick, data)
{
	data.tick = atick + tick;
	seq.push(data);
}
 
function writeBE(count, value)
{
	for (var n = count - 1; 0 <= n; n--) {
		st[stpos++] = (value>>(8*n)) & 0xff;
	}
}
 
// 音符処理
function note(cmd)
{
	var acci = getAccidental();
	var len = getValue();
	var dot = getDot();
	var tie = getTie();
 
	var noteno = (octave + 1) * 12 + scale[cmd] + acci;	// note number
	var tick = calcTick(len, dot);
	var gate = tie ? tick : tick * 7 / 8;
	seqData(   0, [0x90 | ch, noteno, velocity]);
	seqData(gate, [0x80 | ch, noteno, 0]);
	atick += tick;
}
 
// 音番号処理
function noteNo()
{
	var value = getValue();
	var tie = getTie();
 
	var noteno = 12 + value;	// note number
	var tick = calcTick(0, 0);
	var gate = tie ? tick : tick * 7 / 8;
	seqData(   0, [0x90 | ch, noteno, velocity]);
	seqData(gate, [0x80 | ch, noteno, 0]);
	atick += tick;
}
 
// 休符処理
function rest()
{
	var len = getValue();
	var dot = getDot();
 
	var tick = calcTick(len, dot);
	atick += tick;
}
 
// tick計算
function calcTick(len, dot)
{
	if (len == 0) len = deflen;
	var tick = timebase * 4 / len;
	var n = 1 << dot;
	tick += tick * (n - 1) / n;
	return tick;
}
 
// 臨時記号の取得
function getAccidental()
{
	var acci = 0;
	for ( ; pos < mml.length; pos++) {
		switch (mml.substr(pos, 1)) {
		case "+": case "#":
			acci++;
			break;
		case "-":
			acci--;
			break;
		default:
			return acci;
		}
	}
	return acci;
}
 
// 値の取得
function getValue()
{
	var value = 0;
	for ( ; pos < mml.length; pos++) {
		var num = parseInt(mml.substr(pos, 1));
		if (isNaN(num)) break;
		value = value * 10 + num;
	}
	return value;
}
 
// 付点の取得
function getDot()
{
	var dot = 0;
	for ( ; pos < mml.length; pos++) {
		if (mml.substr(pos, 1) != ".") break;
		dot++;
	}
	return dot;
}
 
// タイの取得
function getTie()
{
	if (mml.substr(pos, 1) == "&") {
		pos++;
		return true;
	}
	return false;
}
</script>
</head>
 
<body>
<center>
<h1>mml2smf</h1>
<textarea id="mml" cols="80" rows="25">
t150 v15
e8.d16 c.c8>c.<b8 ba2a g.e8ed8c8 d2.
e8.d16 c.c8>c.<b8 ba2a g.e8d.c8 c2r
g >c.c8d.d8 e2.<g >c.c8d.d8 e2.
o5 e8.d16 c.<b8a>c8<a8 ge2e8.d16 c8>c<e8d.c8 c2.
</textarea><br>
<br>
General MIDI 楽器No.(1-128) <input id="defpc" type="number" value="1"><br>
<br>
<button onclick="download()">download</button>
</center>
</body>
 
最終更新:2014年07月18日 22:19