// マルチバイト文字セット
#pragma comment(lib, "winmm")
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Windows.h>
// 関数プロトタイプ宣言
int Note(const char* p);
int Rest(const char* p);
int GetValue(const char* p, int* value);
void WriteBE(int len, DWORD val);
void WriteData(DWORD t, BYTE cmd, BYTE d1, BYTE d2);
// 外部変数
FILE* pfo;
int datalen; // データ長
int timebase = 480;
int deflen = 4; // デフォルト長
int octave = 4;
int tempo = 120;
int rest = 0; // 休符tick
//==============================================================================
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "usage: mml2smf mmlfile\n");
return 1;
}
// 入力ファイル
char path_in[_MAX_PATH];
strcpy_s(path_in, argv[1]);
FILE* pfi;
if (fopen_s(&pfi, path_in, "rt") != 0) {
fprintf(stderr, "error: %s\n", path_in);
return 1;
}
// 出力ファイル
char drv[_MAX_DRIVE];
char dir[_MAX_DIR];
char fnm[_MAX_FNAME];
char ext[_MAX_EXT];
char path_out[_MAX_PATH];
_splitpath_s(path_in, drv, dir, fnm, ext);
_makepath_s(path_out, drv, dir, fnm, "mid");
if (fopen_s(&pfo, path_out, "wb") != 0) {
fprintf(stderr, "error: %s\n", path_out);
return 1;
}
// ヘッダチャンク
fwrite("MThd", 1, 4, pfo);
WriteBE(4, 6); // データ長
WriteBE(2, 0); // フォーマットタイプ
WriteBE(2, 1); // トラック数
WriteBE(2, timebase); // タイムベース
// トラックチャンク
fwrite("MTrk", 1, 4, pfo);
WriteBE(4, 0); // データ長(仮)
datalen = 0;
// 読み込みループ
char line[256];
int row = 0;
while (fgets(line, _countof(line), pfi) != NULL) {
row++;
int len = strlen(line);
if (0 < len && line[len - 1] == '\n') line[len - 1] = '\0'; // 改行文字を削除
char* p = strchr(line, '\'');
if (p != NULL) *p = '\0'; // コメントアウトを削除
for (int c = 0; line[c]; ) {
switch (line[c++]) {
case '\t': case ' ':
// スルー
break;
case '<':
octave--;
break;
case '>':
octave++;
break;
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
c += Note(line + --c);
break;
case 'l':
c += GetValue(line + c, &deflen);
break;
case 'o':
c += GetValue(line + c, &octave);
break;
case 'r':
c += Rest(line + c);
break;
case 't':
c += GetValue(line + c, &tempo);
WriteData(0, 0xff, 0x51, 3);
WriteBE(3, 60000000 / tempo); // 4分音符のマイクロ秒数
break;
default:
fprintf(stderr, "%s\nerror: row=%d col=%d\n", line, row, c);
return 1;
}
}
}
// トラック終了
WriteData(0, 0xff, 0x2f, 0x00);
fseek(pfo, -4 - datalen, SEEK_CUR);
WriteBE(4, datalen); // データ長
fclose(pfo);
fclose(pfi);
// 出力ファイルの再生
MCI_OPEN_PARMS mop;
mop.lpstrElementName = path_out;
MCIERROR e;
e = mciSendCommand(NULL, MCI_OPEN, MCI_OPEN_ELEMENT, (DWORD_PTR)&mop);
if (e != 0) {
char buf[256];
mciGetErrorString(e, buf, _countof(buf));
fprintf(stderr, "%s\n", buf);
return 1;
}
mciSendCommand(mop.wDeviceID, MCI_PLAY, 0, NULL);
printf("Enterキーで終了");
getchar();
mciSendCommand(mop.wDeviceID, MCI_STOP, 0, NULL);
mciSendCommand(mop.wDeviceID, MCI_CLOSE, 0, NULL);
return 0;
}
// 音符
int Note(const char* p)
{
const int scale[] = {9, 11, 0, 2, 4, 5, 7};
int c = 0;
// 音程
c++;
// 臨時記号 accidental
int acci = 0;
switch (p[c]) {
case '+': case '#':
acci++;
c++;
break;
case '-':
acci--;
c++;
break;
}
int note = (octave + 1) * 12 + scale[*p - 'a'] + acci;
// 音符の長さ
int value;
c += GetValue(p + c, &value);
if (value == 0) value = deflen;
// 付点
int dot = 0;
for ( ; p[c] == '.'; c++) dot++;
int tick = timebase * 4 / value;
tick += tick * ((1<<dot) - 1) / (1<<dot);
WriteData(rest, 0x90, note, 0x70);
WriteData(tick, 0x80, note, 0x00);
rest = 0;
printf("%c a=%d v=%d d=%d c=%d t=%d n=%d\n", *p, acci, value, dot, c, tick, note);
return c;
}
// 休符
int Rest(const char* p)
{
// 休符の長さ
int value;
int c = GetValue(p, &value);
if (value == 0) value = deflen;
// 付点
int dot = 0;
for ( ; p[c] == '.'; c++) dot++;
int tick = timebase * 4 / value;
tick += tick * ((1<<dot) - 1) / (1<<dot);
rest += tick;
return c;
}
// 値の取得
int GetValue(const char* p, int* value)
{
int c = 0;
*value = 0;
for ( ; isdigit(p[c]); c++) {
*value = *value * 10 + (p[c] - '0');
}
return c;
}
void WriteBE(int len, DWORD val)
{
BYTE* p = (BYTE*)&val + len;
for (int n = 0; n < len; n++) {
fwrite(--p, 1, 1, pfo);
}
datalen += len;
}
void WriteData(DWORD t, BYTE cmd, BYTE d1, BYTE d2)
{
// 可変長tick
DWORD v = ((t&0xfe00000)<<3)|((t&0x1fc000)<<2)|((t&0x3f80)<<1)|(t&0x7f)|0x80808000;
BYTE* p = (BYTE*)&v + 4;
for (int n = 0; n < 4; n++) {
if (*--p != 0x80) {
fwrite(p, 1, 1, pfo);
datalen++;
}
}
fwrite(&cmd, 1, 1, pfo);
fwrite(&d1, 1, 1, pfo);
fwrite(&d2, 1, 1, pfo);
datalen += 3;
}