開発環境 |
メモ帳 |
実行環境 |
Microsoft Windows 8.1 (64bit) |
mml2smf4.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 deflen = 4; // default length
var octave = 4;
var st; // stream
var sharp = "";
var flat = "";
var seq = new Array(); // sequence
var ch = 0; // channel
var atick = 0; // absolute tick
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
while (! fi.AtEndOfStream) {
var line = fi.ReadLine();
ParseLine:
for (var c = 0; c < line.length; ) {
var cmd = line.substr(c++, 1).toUpperCase();
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;
atick = 0;
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].toUpperCase();
} else {
sharp = p[2].toUpperCase();
}
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 "[": // 並列音名
c += ParallelNote(param);
break;
}
}
}
fi.Close();
// トラック終了
SeqData(0, new Array(0xff, 0x2f, 0)); // 最終チャネルの絶対tickに注意
// ソート
seq.sort(SeqComp);
// タイ処理
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, 0); // フォーマットタイプ
WriteBE(2, 1); // トラック数
WriteBE(2, timebase); // タイムベース
// トラックチャンク
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++;
}
}
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));
}
}
function SeqData(tick, data)
{
data.tick = atick + tick;
seq.push(data);
}
function SeqComp(a, b)
{
return a.tick - b.tick;
}
// 休符処理
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, 0x70));
SeqData(gate, new Array(0x80 | ch, num, 0x00));
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); // 音名配列
for (var i = 0; i < note.length; i++) {
var n = note[i].match(/(.)(\/?)([#+]*)(\-*)(\d?)/);
var name = n[1].toUpperCase();
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
SeqData( 0, new Array(0x90 | ch, num, 0x70));
SeqData(gate, new Array(0x80 | ch, num, 0x00));
}
atick += tick;
return p[0].length;
}
ivi.txt
' ivi.txt
t60
*1 ' 右手
[egc5][dgb][egc5]r
*2 ' 左手
c<g>cr
最終更新:2014年03月30日 17:12