開発環境 メモ帳
実行環境 Microsoft Windows 8.1 (64bit)

mml2smf5.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 timebase = 480;
var st;			// stream
var sharp = "";
var flat = "";
var track = new Array();
var seq;		// sequence
var atick;		// absolute tick
var deflen;		// default length
var octave;
var velocity;
var ch = 0;		// channel
 
WScript.Quit(main());
 
//==============================================================================
function main()
{
	// 引数処理
	var args = WScript.Arguments;
	if (args.length != 1) {
		WScript.Echo("usage: cscript mml2smf2.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
 
	TrackStart();
	while (! fi.AtEndOfStream) {
		var line = fi.ReadLine().toUpperCase();
 
		ParseLine:
		for (var c = 0; c < line.length; ) {
			var cmd = line.substr(c++, 1);
			var param = line.substr(c);
			switch (cmd) {
			case "\t": case " ": case "|":
				break;
			case "'":
				break ParseLine;
			case "*":	// channel
				var p = param.match(/^(\d*)/);
				c += p[0].length;
				ch = parseInt(p[1]) - 1;
				TrackEnd();
				TrackStart();
				break;
			case "<":
				octave--;
				break;
			case ">":
				octave++;
				break;
			case "@":	// program change
				var p = param.match(/^(\d*)/);
				c += p[0].length;
				var value = parseInt(p[1]);
				SeqData(0, new Array(0xc0 | ch, value - 1));
				break;
			case "A": case "B": case "C": case "D": case "E": case "F": case "G":
				// 音名
				c += Note(cmd, param);
				break;
			case "K":	// 調号 key signature
				var p = param.match(/^([#+\-])([A-G]+)/i);
				c += p[0].length;
				if (p[1] == "-") {
					flat = p[2];
				} else {
					sharp = p[2];
				}
				break;
			case "L":	// 音価
				var p = param.match(/^(\d*)/);
				c += p[0].length;
				deflen = parseInt(p[1]);
				break;
			case "O":
				var p = param.match(/^(\d*)/);
				c += p[0].length;
				octave = parseInt(p[1]);
				break;
			case "R":
				c += Rest(param);
				break;
			case "T":
				var p = param.match(/^(\d*)/);
				c += p[0].length;
				var tempo = parseInt(p[1]);
				var usec = 60000000 / tempo;	// 4分音符のマイクロ秒数
				SeqData(0, new Array(0xff, 0x51, 3,
					(usec>>16) & 0xff, (usec>>8) & 0xff, usec & 0xff));
				break;
			case "V":	// volume 0-127
				var p = param.match(/^(\d*)/);
				c += p[0].length;
				var value = parseInt(p[1]);
				SeqData(0, new Array(0xb0 | ch, 0x07, value));
				break;
			case "[":	// 並列音名
				c += ParallelNote(param);
				break;
			}
		}
	}
	TrackEnd();
	fi.Close();
 
	// タイ処理
	for (var t in track) {
		seq = track[t];
		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];
				}
			}
		}
	}
 
	//-----------------------------------------------------------------------------
	// ストリームのオープン
	st = WScript.CreateObject("ADODB.Stream");
	st.Charset = "iso-8859-1";
	st.Open();
 
	// ヘッダチャンク
	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.WriteText(String.fromCharCode(b));
					datalen++;
				}
			}
			for (var d = 0; d < data.length; d++) {
				st.WriteText(String.fromCharCode(data[d]));
				datalen++;
			}
		}
		var pos = st.Position;
		st.Position = pos - datalen - 4;
		WriteBE(4, datalen);	// データ長
		st.Position = pos;
	}
 
	// ストリームをファイルに保存
	st.SaveToFile(path_out, adSaveCreateOverWrite);
	st.Close();
 
	// 再生
	var WshShell = WScript.CreateObject("WScript.Shell");
	WshShell.Run(path_out);
 
	return 0;
}
 
function TrackStart()
{
	seq = new Array();
	atick = 0;
	deflen = 4;
	octave = 4;
	velocity = 100;
}
 
function TrackEnd()
{
	SeqData(0, new Array(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.WriteText(String.fromCharCode((value>>(8*n)) & 0xff));
	}
}
 
// 休符処理
function Rest(param)
{
	var p = param.match(/^(\d*)(\.*)/);
	var value = (p[1] == "") ? deflen : parseInt(p[1]);
	var dot = p[2].length;
	var tick = timebase * 4 / value;
	tick += tick * ((1<<dot) - 1) / (1<<dot);
	atick += tick;
	return p[0].length;
}
 
// 音符処理
function Note(cmd, param)
{
	var p = param.match(/^(\/?)([#+]*)(\-*)(\d*)(\.*)(&?)/);
	var value = (p[4] == "") ? deflen : parseInt(p[4]);
	var dot = p[5].length;
	var tick = timebase * 4 / value;
	tick += tick * ((1<<dot) - 1) / (1<<dot);
	var gate = (p[6]) ? tick : tick * 7 / 8;
 
	var acci;	// 臨時記号 accidental
	if (p[1]) {
		acci = 0;
	} else {
		acci = p[2].length - p[3].length;
		if (acci == 0) {
			if (sharp.indexOf(cmd) != -1) acci = 1;
			if (flat.indexOf(cmd) != -1) acci = -1;
		}
	}
	var num = (octave + 1) * 12 + scale[cmd] + acci;	// note number
	SeqData(   0, new Array(0x90 | ch, num, velocity));
	SeqData(gate, new Array(0x80 | ch, num, 0));
 
	atick += tick;
	return p[0].length;
}
 
// 並列音符処理
function ParallelNote(param)
{
	var p = param.match(/^([^]]*)](\d*)(\.*)(&?)/);
	var value = (p[2] == "") ? deflen : parseInt(p[2]);
	var dot = p[3].length;
	var tick = timebase * 4 / value;
	tick += tick * ((1<<dot) - 1) / (1<<dot);
	var gate = (p[4]) ? tick : tick * 7 / 8;
 
	var note = p[1].match(/[A-G][^A-G]*/gi);	// 音名配列
	var anum = new Array();				// 音番号配列
	for (var i = 0; i < note.length; i++) {
		var n = note[i].match(/(.)(\/?)([#+]*)(\-*)(\d?)/);
		var name = n[1];
		var acci;	// 臨時記号 accidental
		if (n[2]) {
			acci = 0;
		} else {
			acci = n[3].length - n[4].length;
			if (acci == 0) {
				if (sharp.indexOf(name) != -1) acci = 1;
				if (flat.indexOf(name) != -1) acci = -1;
			}
		}
		var o = (n[5] == "") ? octave : parseInt(n[5]);
		var num = (o + 1) * 12 + scale[name] + acci;	// note number
		anum.push(num);
		SeqData(   0, new Array(0x90 | ch, num, velocity));
	}
	for (var n in anum) {
		SeqData(gate, new Array(0x80 | ch, anum[n], 0));
	}
 
	atick += tick;
	return p[0].length;
}
 

ivi.txt
' ivi.txt
t60
 
*1 @1	' 右手
[egc5][dgb][egc5]r
 
*1 @1	' 左手
c<g>cr
 
最終更新:2014年04月01日 21:42