実行環境 Microsoft Windows 8.1 (64bit)

手順1
  1. 適当なディレクトリに下のmml2smf.jsとmml.txtを用意する
  2. コマンドプロンプトを起動し先のディレクトリに移動する
  3. 以下のコマンドを入力する
cscript mml2smf.js mml.txt

手順2
  1. 適当なディレクトリに下のmml2smf.jsとmml.txtを用意する
  2. mml2smf.jsのショートカットをデスクトップ等に作成する
  3. 上のショートカットにmml.txtをDrag&Dropする

mml2smf.js
// OpenTextFile
var ForReading = 1;
 
// SaveOptionsEnum
var adSaveCreateOverWrite = 2;
 
var scale = {A:9, B:11, C:0, D:2, E:4, F:5, G:7};	// 音階
 
// 外部変数
var deflen = 4;
var octave = 4;
var rest = 0;
var datalen;	// データ長
var st;
 
var timebase = 480;
 
WScript.Quit(main());
 
//==============================================================================
function main()
{
	// 引数処理
	var args = WScript.Arguments;
	if (args.length != 1) {
		WScript.Echo("usage: cscript mml2smf.js mmlfile");
		return 1;
	}
	var path_in = args(0);
 
	// パス名処理
	var fso = WScript.CreateObject("Scripting.FileSystemObject");
	path_in = fso.GetAbsolutePathName(path_in);
	var dir = fso.GetParentFolderName(path_in);
	var fnm = fso.GetBaseName(path_in);
	var path_out = dir + "\\" + fnm + ".mid";
 
	// 入力ファイルのオープン
	var fi = fso.OpenTextFile(path_in, ForReading);	// ASCII
 
	// ストリームのオープン
	st = WScript.CreateObject("ADODB.Stream");
	st.Charset = "iso-8859-1";
	st.Open();
 
	// ヘッダチャンク
	WriteBE(4, 0x4d546864);	// MThd
	WriteBE(4, 6);		// データ長
	WriteBE(2, 0);		// フォーマットタイプ
	WriteBE(2, 1);		// トラック数
	WriteBE(2, timebase);	// タイムベース
 
	// トラックチャンク
	WriteBE(4, 0x4d54726b);	// MTrk
	WriteBE(4, 0);		// データ長(仮)
	datalen = 0;
 
	while (! fi.AtEndOfStream) {
		var line = fi.ReadLine();
		line.match(/[^']*/);
		line = RegExp.lastMatch;
 
		var token = line.match(/[<>A-GLORT][#+\-\d\.]*/gi);
		if (token == null) continue;
 
		for (var n = 0; n < token.length; n++) {
			var cmd = token[n].substr(0, 1).toUpperCase();
			var param = token[n].substr(1);
			switch (cmd) {
			case "<":
				octave--;
				break;
			case ">":
				octave++;
				break;
			case "A": case "B": case "C": case "D": case "E": case "F": case "G":
				Note(cmd, param);
				break;
			case "L":
				deflen = parseInt(param);
				break;
			case "O":
				octave = parseInt(param);
				break;
			case "R":
				Rest(param);
				break;
			case "T":
				var tempo = parseInt(param);
				WriteData(0, 0xff, 0x51, 3);
				WriteBE(3, 60000000 / tempo);	// 4分音符のマイクロ秒数
				break;
			}
		}
	}
	fi.Close();
 
	// トラック終了
	WriteData(rest, 0xff, 0x2f, 0x00);
	st.Position -= datalen + 4;
	WriteBE(4, datalen);	// データ長
 
	// ストリームをファイルに保存
	st.SaveToFile(path_out, adSaveCreateOverWrite);
	st.Close();
 
	// 再生
	var WshShell = WScript.CreateObject("WScript.Shell");
	WshShell.Run(path_out);
 
	return 0;
}
 
function WriteBE(count, value)
{
	for (var n = count - 1; 0 <= n; n--) {
		st.WriteText(String.fromCharCode( (value >> (8 * n)) & 0xff ));
	}
	datalen += count;
}
 
function WriteData(t, cmd, d1, d2)
{
	// 可変長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) {
			WriteBE(1, b);
		}
	}
	WriteBE(1, cmd);
	WriteBE(1, d1);
	WriteBE(1, d2);
}
 
function Note(cmd, param)
{
	param.match(/([#+]*)(\-*)(\d*)(\.*)/);
 
	// 臨時記号 accidental
	var acci = RegExp.$1.length - RegExp.$2.length;
 
	var note = (octave + 1) * 12 + scale[cmd] + acci;
 
	var value = (RegExp.$3 == "") ? deflen : parseInt(RegExp.$3);
	var dot = RegExp.$4.length;
 
	var tick = timebase * 4 / value;
	tick += tick * ((1<<dot) - 1) / (1<<dot);
	var gate = tick * 7 / 8;
 
	WriteData(rest, 0x90, note, 0x70);
	WriteData(gate, 0x80, note, 0x00);
	rest = tick - gate;
}
 
function Rest(param)
{
	param.match(/(\d*)(\.*)/);
	var value = (RegExp.$1 == "") ? deflen : parseInt(RegExp.$1);
	var dot = RegExp.$2.length;
	var tick = timebase * 4 / value;
	tick += tick * ((1<<dot) - 1) / (1<<dot);
	rest += tick;
}
 

mml.txt
' アニーローリー
' スコットランド民謡
 
t150
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.
 
最終更新:2014年03月23日 12:08