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