開発環境 Microsoft Visual C++ 2013
実行環境 Microsoft Windows 8.1 (64bit)
プロジェクトの種類 Win32 コンソール アプリケーション
プロジェクト名 mml2smf
アプリケーションの種類 コンソール アプリケーション
追加のオプション 空のプロジェクト

mml2smf.cpp
// マルチバイト文字セット
 
#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;
}
 

アニーローリー.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月14日 00:22