// マルチバイト文字セット
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Windows.h>
#include <map>
using namespace std;
#define AC(a) a, _countof(a)
struct Data {
BYTE cmd;
BYTE d1;
BYTE d2;
};
typedef multimap<DWORD, Data> DataList;
// 関数プロトタイプ宣言
int GetNoteLen(const char* p);
int NoteName(const char* p, int tick_a, int tick_n);
int GetValue(const char* p, int* value);
void Output(LPCTSTR path_in);
void WriteBE(int len, DWORD val);
void WriteData(DWORD t, BYTE cmd, BYTE d1, BYTE d2);
// 外部変数
int timebase = 480;
int q = 7; // 音の長さの割合 q/8
char flat[16];
FILE* pfo;
int datalen; // データ長
DataList list;
//==============================================================================
int main(int argc, char* argv[])
{
if (argc != 2) {
fprintf(stderr, "usage: code2smf codefile\n");
return 1;
}
char path_in[_MAX_PATH];
strcpy_s(path_in, argv[1]);
// ヘッダ
GetPrivateProfileString("head", "b", NULL, AC(flat), path_in);
// 入力ファイル
FILE* pfi;
if (fopen_s(&pfi, path_in, "rt") != 0) {
fprintf(stderr, "error: %s\n", path_in);
return 1;
}
// 読み込みループ
char line[256];
BOOL track = FALSE;
int row = 0;
while (fgets(AC(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'; // コメントアウトを削除
if (line[0] == '\0') continue;
if (track == FALSE) {
if (_stricmp(line, "[track1]") == 0) track = TRUE;
continue;
}
int bar, numer, denom; // 小節、分子、分母
char notelen[16]; // 音長
char notename[16]; // 音名
int field = sscanf_s(line, "%d %d/%d %s %s", &bar, &numer, &denom, AC(notelen), AC(notename));
if (field != 5) {
fprintf(stderr, "error: %s\n", line);
break;
}
// 音長
int tick_n = GetNoteLen(notelen);
if (tick_n <= 0) {
fprintf(stderr, "error: %s\n", line);
break;
}
// 音名処理
int tick_a = (4 * timebase) * (bar - 1) + (4 * timebase) * numer / denom;
if (NoteName(notename, tick_a, tick_n) != 0) {
fprintf(stderr, "error: %s\n", line);
break;
}
printf("[%s] %d %d %d/%d t=%d\n", line, field, bar, numer, denom, tick_n);
}
fclose(pfi);
Output(path_in);
return 0;
}
// 音長の取得
int GetNoteLen(const char* p)
{
int tick = 0;
for (int c = 0; p[c]; ) {
// 音長
int value;
c += GetValue(p + c, &value);
if (value == 0) return 0;
// 付点
int dot = 0;
for ( ; p[c] == '.'; c++) dot++;
int t = timebase * 4 / value;
t += t * ((1 << dot) - 1) / (1 << dot);
tick += t;
// タイ
if (p[c] == '&') c++;
}
return tick;
}
// 音名処理
int NoteName(const char* p, int tick_a, int tick_n)
{
const int scale[] = { 9, 11, 0, 2, 4, 5, 7 }; // 音階
if (p[0] == 'r') return 0; // 休符
int ch = 0;
for (int c = 0; p[c]; ch++) {
// 音名
char name = p[c++];
int n = name - 'a';
if (n < 0 || 7 < n) {
return -1;
}
// オクターブ
int octave;
c += GetValue(p + c, &octave);
if (octave < 1 || 7 < octave) {
return -1;
}
// 臨時記号 accidental
int acci = 0;
switch (p[c]) {
case '+': case '#':
acci++; c++; break;
case '-':
acci--; c++; break;
}
if (acci == 0) {
if (strchr(flat, name) != NULL) {
acci = -1;
}
}
int notenum = (octave + 1) * 12 + scale[n] + acci;
Data data;
data.cmd = 0x90 | ch; // note on
data.d1 = notenum;
data.d2 = 0x70;
list.insert(make_pair(tick_a, data));
data.cmd = 0x80 | ch; // note off
data.d1 = notenum;
data.d2 = 0x00;
list.insert(make_pair(tick_a + tick_n * q / 8, data));
}
return 0;
}
// 値の取得
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;
}
//------------------------------------------------------------------------------
// SMFファイルへの出力
void Output(LPCTSTR path_in)
{
// 出力ファイル
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;
}
// ヘッダチャンク
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;
DWORD tick = 0;
for (DataList::iterator it = list.begin(); it != list.end(); it++) {
DWORD delta = it->first - tick;
Data data = it->second;
WriteData(delta, data.cmd, data.d1, data.d2);
tick = it->first;
// printf("%u %x %u %x\n", it->first, data.cmd, data.d1, data.d2);
}
// トラック終了
WriteData(0, 0xff, 0x2f, 0x00);
fseek(pfo, -4 - datalen, SEEK_CUR);
WriteBE(4, datalen); // データ長
fclose(pfo);
}
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;
}