/*
 *	mml.c
 *
 *	dpmucc - ミュージック・データ・コンバーター
 *	Copyright (C) 2015 Naoyuki Sawa
 *
 *	* Thu Jan 22 00:43:59 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sat Jan 24 05:36:54 JST 2015 Naoyuki Sawa
 *	- mmlEnd()のループ処理が間違っていたのを修正しました。
 *	  元のmucc.exeのソース(\usr\PIECE\tools\mucc\mmlcv.c)は正しくて、自分で再実装した際のエンバグです。
 *	* Tue Feb 03 20:30:54 JST 2015 Naoyuki Sawa
 *	- コマンドラインオプション'-p'を追加しました。
 */
#include "app.h"
/****************************************************************************
 *	ローカル変数
 ****************************************************************************/
#define MAXPART		6		//最大パート数			ドライバで処理されるので変更不可。(\usr\PIECE\sysdev\music\musdef.hのMAXCHに相当)
#define MAXREPLEV	(8-1)		//[]の最大ネスト数		ドライバで処理されるので変更不可。(\usr\PIECE\sysdev\music\musdef.hの((MAXNESTD/3)-1)に相当)	-1する理由は、パートの呼び出しで常に1レベル消費しているから。
#define MAXMACLEV	1024		//MMLマクロの最大ネスト数	コンバーター(当プログラム)が処理するので調整可。
#define MAXLABEL	1024		//パート毎のラベルの最大定義数	調整可。
#define MAXMMLMAC	1024		//MMLマクロの最大定義数		調整可。
#define MAXDRUMMAC	(0xE0-0x80)	//ドラムマクロの最大定義数	ドライバで処理されるので変更不可。(\usr\PIECE\sysdev\music\seq.cのProcSeq()のSeqDrums()呼び出しの第二引数に相当)
/*--------------------------------------------------------------------------*/
static int		repLev;				//リピートのネストレベル
static int		TBL_RepStk[MAXREPLEV];		//リピートスタック		//']数値'を処理する時に、'['の所に出力したコマンド引数に繰り返し回数を格納する位置を記憶します。
/*--------------------------------------------------------------------------*/
static int		macLev;				//MMLマクロのネストレベル
static char*		TBL_MacStk[MAXMACLEV];		//MMLマクロスタック		//MMLマクロが終端に達した後に復元する、行バッファの走査位置(linePtr)を記憶します。
/*--------------------------------------------------------------------------*/
//MMLマクロ
typedef struct _ST_MmlMac {
	char*		name;		//マクロ名
	char*		mmlStr;		//MML文字列
} ST_MmlMac;
static int		nMmlMac;			//MMLマクロの登録数
static ST_MmlMac	TBL_MmlMac[MAXMMLMAC];		//MMLマクロテーブル
//MMLマクロを検索する。
static char* getMmlMac(const char* name) {
	int iMmlMac;
	for(iMmlMac = 0; iMmlMac < nMmlMac; iMmlMac++) {
		if(!strcmp(TBL_MmlMac[iMmlMac].name, name)) {
			return TBL_MmlMac[iMmlMac].mmlStr;	//見つかったら、MML文字列を返す。
		}
	}
	return NULL;	//見つからなければ、NULLを返す。
}
/*--------------------------------------------------------------------------*/
//ドラムマクロ
typedef struct _ST_DrumMac {
	char*		name;		//マクロ名
	int		addr;		//アドレス
} ST_DrumMac;
static int		nDrumMac;			//ドラムマクロの登録数
static ST_DrumMac	TBL_DrumMac[MAXDRUMMAC];	//ドラムマクロテーブル
//ドラムマクロを検索する。
static int getDrumMac(const char* name) {
	int iDrumMac;
	for(iDrumMac = 0; iDrumMac < nDrumMac; iDrumMac++) {
		if(!strcmp(TBL_DrumMac[iDrumMac].name, name)) {
			return iDrumMac;			//見つかったら、ドラムマクロ番号(0〜)を返す。
		}
	}
	return -1;	//見つからなければ、-1を返す。
}
/*--------------------------------------------------------------------------*/
//パート(A〜F)
typedef struct _ST_Part {
	int		nLabel;			//このパートに対してラベルを生成した数。一度でも指定されたパートならばdoPart()⇒setLabel()にて少なくとも一個以上のラベルが生成される。従って(nLabel=0)ならば不使用パートである。
	int		labelAddr[MAXLABEL];	//ラベルに対応するアドレス。
	int		loopLabel;		//-1=ループ無し,≧0=ループ戻り位置を示すラベル。
	//
	int		trackAttr;		//トラック属性		bit0=ドラムパート,bit1=無停止パート(Undoc'd)
	int		lastNote;		//最後に出力した音階
	int		lastLen;		//最後に出力した音長(カウント)	0=無効
	//
	int		oct1, oct2;		//オクターブ
	int		len1, len2;		//音長(○分音符)
	int		vol1, vol2;		//ボリューム
	int		det1, det2;		//デチューン
	int		trs1, trs2;		//トランスポーズ
	//
	int		legSw;			//レガート状態(0=Off,1=On)
	int		porSw;			//ポルタメント状態		'{ab}'の'{'を検出時に1にする。'}'を検出時に2にして、genNote()が処理してすぐに0に戻す。
	int		porNote1;		//ポルタメント開始音程
	int		porNote2;		//ポルタメント終了音程
} ST_Part;
static ST_Part		TBL_Part[MAXPART];	//各パートの状態追跡用ワークエリア
/*--------------------------------------------------------------------------*/
static char*		linePtr;		//行バッファの走査位置
static int		partMask;		//現在指定されているパートのビットマスク。(bit0=A,bit1=B,…,bit5=F)	//ドラムマクロ解析中は、現在指定されているパートのビットマスクを0にする。(doMacro()参照)
static int		tempo1, tempo2;		//テンポ
/****************************************************************************
 *	endSection
 ****************************************************************************/
//前の区間の最後に'終了'を置く。
static void endSection() {
	if(getAddr() > 20) {		//前の区間が有れば…(前の区間とは、タイトル文字列,又は,ラベル区間の事です。)	//'20'の意味は、mmlSetup()を参照せよ。
		addByte(SEQ_END);	//前の区間の最後に、タイトル文字列の終端ヌル文字,又は,'終了'コマンドを置く。
	}
}
/****************************************************************************
 *	doTitle
 ****************************************************************************/
//タイトル行を処理する。
static void doTitle() {
	int addr;
	       if(!strncmp(linePtr, "Title2", 6)) {				//←┐
		linePtr += 6;	//  "Title2"を飛ばす。				//　├この判定順序必須。
		addr = 18;	//タイトルポインタのアドレス(mmlSetup()参照)	//　│逆にするとTitle2もTitleに一致してしまうから。
	} else if(!strncmp(linePtr, "Title" , 5)) {				//←┘
		linePtr += 5;	//  "Title" を飛ばす。
		addr = 16;	//タイトルポインタのアドレス(mmlSetup()参照)
	} else {
		die("%s(%d): タイトル指定が不正です。", srcName, srcLine);
	}
	if(getWord(addr)) {	//既に指定されていたら…
		die("%s(%d): タイトル指定が重複しています。", srcName, srcLine);
	}
	endSection();		//前の区間の最後に'終了'を置く。
	putWord(addr, getAddr());			//タイトルポインタを格納する。
	linePtr = strtrim(linePtr, 0, NULL);		//タイトル本文の前後の空白を削除する。
	while(*linePtr) { addByte(*linePtr++); }	//タイトル文字列を出力する。終端のnulは出力しなくて良い。この後の各処理で「前の区間の最後に'終了'を置く」処理として出力されるから。
}
/****************************************************************************
 *	getMacroName
 ****************************************************************************/
//マクロ名を取得する。
static char* getMacroName() {
	char name[1024];	//充分なサイズとする事。以下の処理でバッファ溢れはチェックしていません。
	//アルファベットが続く間を、マクロ名と見なして取得する。	//数値はマクロ名として使用できない。ドラムパートの中で'!bg4'のようにマクロ名の後に音長を指定する場合が有るから。
	int i = 0;
	while(isalpha(*linePtr)) { name[i++] = *linePtr++; }
	if(!i) { die("%s(%d): マクロ名が不正です。", srcName, srcLine); }	//0文字のマクロ名は不可
	name[i] = '\0';
	//文字列を複製して返す。	//呼び出し側が、戻り値のポインタをそのままテーブルに格納するので、複製必須です。
	return strdup(name);
}
/****************************************************************************
 *	convLen
 ****************************************************************************/
//○分音符を、4分音符当り96のカウント形式に変換する。
static int convLen(int len) {
	if((len < 1) || (len > 96)) {
		die("%s(%d): 音長が範囲外です。(%d)", srcName, srcLine, len);
	}
	return 96 / len;	//lenが1〜96であることを確認済みなので、戻り値は96〜1の範囲になる。
}
/****************************************************************************
 *	getLen
 ****************************************************************************/
static int getLen(int len) {
	int f = 0;
	if((linePtr[0] == '%') && isdigit(linePtr[1])) {	//'%数値'で○分音符でなくカウントの直接指定になる。mucc.exeのUndoc'dな動作だと思うが、demucc.exeも対応しているので対応必要。
		linePtr++;	//'%'の次へ進める。
		f = 1;
	}
	//数値有りならば、音長を取得する。引数のlenは使用しない。
	if(isdigit(*linePtr)) {
		len = 0;
		do {
			len = (len * 10) + (*linePtr - '0');
		} while(isdigit(*++linePtr));
		if(!f) { len = convLen(len); }	//○分音符⇒カウント変換する。
	//数値無しならば、引数のlenを使用する。
	} else {
		/** no job **/
	}
	//付点の処理。
	if(*linePtr == '.') {
		int x = len;
		do {
			len += (x >>= 1);
		} while(*++linePtr == '.');
	}
	return len;
}
/****************************************************************************
 *	genNote
 ****************************************************************************/
static void genNote(ST_Part* pPart, int note) {		//note≧0:音程,又は,ドラムマクロ番号、note=-1:休符
	int len;
	//音階,又は,音長,又は,'}'の後の音長指定から、音長カウントを計算して取得する。
	//音階,又は,音長,又は,'}'の後に音長指定が無ければ、getLen()が判断して、デフォルト音長から計算した音長カウントを返す。
	len = getLen(pPart->len1 + pPart->len2);
	//その後に'&'が指定されていたら…
	if(*linePtr == '&') {
		linePtr++;			//'&'の次へ進める。
		if(!pPart->legSw) {		//既にレガートOnでなければ…
			pPart->legSw = 1;	//レガートOnにする。
			addByte(SEQ_LEG);	//レガートOnコマンドを生成する。
		}
	//その後に'&'が指定されていなければ…
	} else {
		if( pPart->legSw) {		//既にレガートOffでなければ…
			pPart->legSw = 0;	//レガートOffにする。
			addByte(SEQ_LOF);	//レガートOffコマンドを生成する。	レガート区間の最後の発声の直前にOffコマンドを生成することになるが、それで良い。レガート区間の最後の発声の直前にOffコマンドが生成されることを前提として、ドライバが動作する仕様である。
		}
	}
	//ポルタメント区間の終了位置'}'ならば…
	if(pPart->porSw == 2) {
		int x = pPart->porNote2 - pPart->porNote1;			//┐
		int y = (int)(log((double)abs(x) / (double)len) / log(2.0));	//├この式は「\usr\PIECE\tools\mucc\mmlcv.c」から引用した。式の意味はまだ理解できていない。
		y <<=  4;							//│
		y += 128;							//┘
		addByte(SEQ_PPA);	//ポルタメントパラメータ生成		//┐
		addByte(x);							//├ポルタメントコマンドのパラメータの形式は「\usr\PIECE\tools\mucc\mmlcv.c」から引用した。パラメータの意味はまだ理解できていない。
		addByte(y);							//┘
		addByte(SEQ_PON);	//ポルタメントOn生成
		//この下の処理で、発声コマンドを生成し、ポルタメントOff生成,及び,ポルタメント状態をクリアする。
	}
	//ポルタメント区間中('{'から、'}'の直前まで)ならば…
	if(pPart->porSw == 1) {
		pPart->porNote2 = pPart->porNote1;		//直前のポルタメント開始音程を、新しいポルタメント終了音程に格納する。
		                  pPart->porNote1 = note;	//　　　　　今回発声する音程を、新しいポルタメント開始音程に格納する。
	//ポルタメント区間中でないか,又は,ポルタメント区間の終了位置'}'ならば…
	} else {
		if(pPart->lastLen != len) {			//音長が変化したら…
			addLen((pPart->lastLen = len));		//新しい音長を格納し、音長コマンドを生成する。
		}
		if((unsigned)note < (unsigned)(0xE0 - 0x80)) {	//音程,又は,ドラムマクロ番号ならば…
			addByte(0x80 + note);			//発声コマンドを生成する。
			pPart->lastNote = note;			//最後に発生したノート番号を記憶しておく。('x',及び,音長のみで発声するMMLのために)
		} else {					//休符ならば…
			addByte(SEQ_RRR);			//休符コマンドを生成する。
		}
	}
	//ポルタメント区間の終了位置'}'ならば…
	if(pPart->porSw == 2) {
		addByte(SEQ_POF);	//ポルタメントOff生成
		pPart->porSw = 0;	//ポルタメント状態をクリア
	}
}
/****************************************************************************
 *	getVal
 ****************************************************************************/
//数値を取得する。数値が無ければ0を返す。
static int getVal() {
	int ans, sgn;
	       if(linePtr[0] == '+') {
		if(!isdigit(linePtr[1])) { return 0; }	//'+'の後に数値が無ければ'+'も消費せずに0を返す。多分すぐにMMLコマンドエラーが検出されるだろう。
		sgn =  1;
		linePtr++;	//'+'の次へ進める。
	} else if(linePtr[0] == '-') {
		if(!isdigit(linePtr[1])) { return 0; }	//'-'の後に数値が無ければ'-'も消費せずに0を返す。多分すぐにMMLコマンドエラーが検出されるだろう。
		sgn = -1;
		linePtr++;	//'-'の次へ進める。
	} else {
		if(!isdigit(linePtr[0])) { return 0; }	//数値が無ければ0を返す。
		sgn =  1;
	}
	ans = 0;
	do {
		ans = (ans * 10) + (*linePtr++ - '0');
	} while(isdigit(*linePtr));
	return ans * sgn;
}
/****************************************************************************
 *	genRepeat
 ****************************************************************************/
static void genRepeat() {
	addByte(SEQ_RPT);
	if((unsigned)repLev >= (unsigned)MAXREPLEV) { DIE(); }		//エラー:リピートのネストレベルが多過ぎる。まず発生しないと思うので、丁寧なエラーメッセージは不要だろう。
	TBL_RepStk[repLev++] = getAddr();	//']数値'を処理する時に、'['の所に出力したコマンド引数に繰り返し回数を格納する位置を記憶します。
	addByte(0);				//プレースホルダとして、繰り返し回数0を格納しておく。
}
/****************************************************************************
 *	genNext
 ****************************************************************************/
static void genNext(int cnt) {
	//実際には正しくてもエラーと見なしてしまう可能性が有る。(mucc.exeでも同様)
	//<例>
	//│AB [cde
	//│A  ]1
	//│B  ]1
	//この例のような書き方を許容するには、状態を追跡する処理が増えすぎるので、対応しないことにした。
	//mucc.exeも同じ問題が有るので、当プログラムも対応しなくて構わないだろう。
	if(!repLev) { die("%s(%d): '['〜']'が対応していません。", srcName, srcLine); }
	putByte(TBL_RepStk[--repLev], cnt);
	addByte(SEQ_NXT);
}
/****************************************************************************
 *	extMacro
 ****************************************************************************/
static void extMacro(ST_Part* pPart) {
	char *name, *mmlStr;
	int iDrumMac;
	//マクロ名を取得する。
	name = getMacroName();
	//MMLマクロか？
	if((mmlStr = getMmlMac(name))) {
		if((unsigned)macLev >= (unsigned)MAXMACLEV) { DIE(); }		//エラー:MMLマクロのネストレベルが多過ぎる。まず発生しないと思うので、丁寧なエラーメッセージは不要だろう。
		TBL_MacStk[macLev++] = linePtr;		//MMLマクロが終端に達した後に復元する、行バッファの走査位置(linePtr)を記憶します。
		linePtr = mmlStr;			//走査位置を、MMLマクロの文字列に切り替えます。
	//ドラムマクロか？
	} else if((iDrumMac = getDrumMac(name)) >= 0) {
		genNote(pPart, iDrumMac);		//ドラムマクロの発声コマンドを生成する。ドラムパートの中では通常の発声コマンド(0x80〜0xDF)がドラムマクロの呼び出しと解釈される。
	} else {
		die("%s(%d): マクロ名'%s'が定義されていません。", srcName, srcLine, name);
	}
}
/****************************************************************************
 *	setLabel
 ****************************************************************************/
//現在指定されている個々のパートに対して、現在のアドレスに対応したラベルを生成する。
static void setLabel() {
	static int lastLabelAddr;	//最後にラベルを出力したアドレス。(アドレス0〜19)はmmlSetup()が出力済みなので、ここでgetAddr()が0には成り得ず、labelAddrの初期値0に偶然一致する心配は無い。
	static int lastPartMask;	//最後のラベルの区間の対象パート。(これが違っていたらアドレスが同じでもラベルを生成しなければならない。'A l4'のようなコマンド生成の無い行の先頭ラベルと、次の行の先頭ラベルが同じ位置を指すことを避けるため。)
	int iPart;
	if((lastLabelAddr != getAddr()) ||	//前のラベルを出力した直後でなければ…(例えば'A Lcde'のように先頭に'L'が有った場合に、先頭と'L'の間に空区間が生じてしまう無駄を省くための判定です。)
	   (lastPartMask  != partMask )) {	//又は、最後のラベル区間の対象パートと異なるパートを対称とするラベルならば…
		endSection();			//前の区間の最後に'終了'を置く。
		lastLabelAddr = getAddr();	//上の処理で'終了'を置くと、getAddr()の戻り値が変化するので、getAddr()を再取得する必要が有る。
		lastPartMask  = partMask;
		for(iPart = 0; iPart < MAXPART; iPart++) {	//doPart()で指定された個々のパートに対して、ラベルを生成する。
			if(partMask & (1 << iPart)) {
				ST_Part* pPart = &TBL_Part[iPart];
				if((unsigned)pPart->nLabel >= (unsigned)MAXLABEL) { DIE(); }	//エラー:ラベルが多過ぎる。まず発生しないと思うので、丁寧なエラーメッセージは不要だろう。
				pPart->labelAddr[pPart->nLabel++] = lastLabelAddr;		//ラベルに対応するアドレスを格納し、このパートに対してラベルを生成した数を増やす。
			}
		}
	}
}
/****************************************************************************
 *	genCtrl
 ****************************************************************************/
//指定された引数の個数のコマンドを生成する、汎用処理。
static void genCtrl(int c, int cmd, int nArg/*≧1*/) {
	addByte(cmd);
	do {
		addByte(getVal());	//引数を取得し出力する。
		nArg--;
	} while(*linePtr++ == ',');	//引数の後に','が有れば継続する。
	linePtr--;
	if(nArg) { die("%s(%d): MMLコマンド'%c'の引数が不正です。", srcName, srcLine, c); }	//指定された引数の個数と、取得した引数の個数が一致したことを確認する。
}
/****************************************************************************
 *	doBody
 ****************************************************************************/
//MML本体を解析する。
static void doBody() {
	ST_Part *pPart, dummyPart;
	int c;
	//パート定義のMML解析中ならば、現在指定されているパートのうち、最小のパート番号の状態追跡用ワークエリアを使用する。	//!!!MMLパートとドラムパートが混在して指定された場合の動作は未定義です。!!!
	if(partMask) {
		pPart = &TBL_Part[ffs(partMask) - 1];	//(ffs()の戻り値=1〜MAXPART)⇒(ffs()の戻り値-1=0〜(MAXPART-1))
	//ドラムマクロ解析中ならば、ダミーの状態追跡用ワークエリアを使用する。
	} else {
		memset(&dummyPart, 0, sizeof dummyPart);
	  //×	dummyPart.trackAttr = (1<<0);	//ドラムパート	//これをセットしてはいけない。無駄であるばかりでなく害である。具体的にはドラムマクロの解析中に以下のwhile()ループの中で'音階'処理の部分の特例処理に引っかかり'?o4c'のような指定の音階が破棄され'?o0c'になってしまう。ドラムマクロの解析中は音階を破棄してはいけない。
		pPart = &dummyPart;
	}
L_REDO:	//MMLマクロ呼び出し前の走査位置へ戻してMML本体の解析を継続する場合は、ここから繰り返す。当関数の先頭から繰り返しても支障は無いが、上の処理を行っても結果は同じなので無駄である。
	while((c = *linePtr++)) {
		if(isspace(c)) { continue; }	//空白文字は処理せずに飛ばす。
		switch(c) {
		case 'a':
		case 'b':
		case 'c':
		case 'd':
		case 'e':
		case 'f':
		case 'g':
			{//音階
				//                                        a  b  c  d  e  f  g
				static const unsigned char TBL_Note[] = { 9,11, 0, 2, 4, 5, 7 };
				int note = TBL_Note[c - 'a'] + (pPart->oct1 + pPart->oct2) * 12;
				for(;;) {
					if(*linePtr == '+') { note++; linePtr++; continue; }
					if(*linePtr == '-') { note--; linePtr++; continue; }
					break;
				}
				if(pPart->trackAttr & (1<<0)) { note = pPart->lastNote; }	//ドラムパートの中で通常の発声コマンドを使用した場合、取得した音階は破棄して、最後に発声したドラムマクロ番号を発声する。mucc.exeのUndoc'dな動作だと思うが、\usr\PIECE\data\music\c350.mmlがこの動作に依存しているので対応せざるを得ない。
				genNote(pPart, note);
				break;
			}
		case 'r':
			{//休符
				genNote(pPart, -1);
				break;
			}
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
		case '.':
			{//音長のみの指定で前回と同じノートを発声する。						//mucc.exeのUndoc'dな動作	'音長'
				linePtr--;	//('0'〜'9',又は,'.')の位置へ戻す。
				genNote(pPart, pPart->lastNote);
				break;
			}
		case 'x':
			{//直前の音										//C:\usr\PIECE\docs\muslib\mml.htmの「音程の符号」に説明有り。
				genNote(pPart, pPart->lastNote);
				break;
			}
		case 'l':
			{//音長
				if((*linePtr == '+') || (*linePtr == '-')) {
					pPart->len2 = convLen(getVal());	//相対デフォルト音長指定	//mucc.exeのUndoc'dな動作	'l±数値'
				} else {
					pPart->len1 = convLen(getVal());	//デフォルト音長指定
				}
				break;
			}
		case 'o':
			{//オクターブ
				if((*linePtr == '+') || (*linePtr == '-')) {
					pPart->oct2 = getVal();			//相対オクターブ設定
				} else {
					pPart->oct1 = getVal();			//オクターブ設定
				}
				break;
			}
		case 't':
		case 'T':
			{//テンポ
				if((*linePtr == '+') || (*linePtr == '-')) {
					tempo2 = getVal();
				} else {
					tempo1 = getVal();
				}
				addByte(SEQ_TMP);
				addByte(tempo1 + tempo2);
				break;
			}
		case 'v':
			{//音量調整
				if((*linePtr == '+') || (*linePtr == '-')) {
					addByte(SEQ_EXR);
					addByte(getVal());
				} else {
					addByte(SEQ_EXP);
					addByte(getVal());
				}
				break;
			}
		case 'V':
			{//音量設定
				if((*linePtr == '+') || (*linePtr == '-')) {
					pPart->vol2 = getVal();							//mucc.exeのUndoc'dな動作	'V±数値'
				} else {
					pPart->vol1 = getVal();
				}
				addByte(SEQ_VOL);
				addByte(pPart->vol1 + pPart->vol2);
				break;
			}
		case 'D':
			{//デチューン
				if(*linePtr =='D') {
					linePtr++;	//二個目の'D'の次へ進める。
					pPart->det2 = getVal();							//mucc.exeのUndoc'dな動作	'DD[±]数値'
				} else {
					pPart->det1 = getVal();
				}
				addByte(SEQ_DTN);
				addByte(pPart->det1 + pPart->det2);
				break;
			}
		case '[':
			{//局所ループ設定(開始)
				pPart->lastLen = 0;	//リピート区間の最初に確実に音長コマンドを出力するために、最後に出力した音長を無効化する。
				genRepeat();		//リピート開始コマンドを生成する。
				break;
			}
		case ']':
			{//局所ループ設定(終端)
				genNext(getVal());	//リピート終端コマンドを生成する。
				pPart->lastLen = 0;	//リピート区間の直後に確実に音長コマンドを出力するために、最後に出力した音長を無効化する。	リピート区間を':'で抜けた場合のために必須です。
				break;
			}
		case ':':
			{//局所ループ設定(脱出)
				addByte(SEQ_BRK);	//リピート脱出コマンドを生成する。
				break;
			}
		case '<':
			{//オクターブダウン
				pPart->oct1--;
				break;
			}
		case '>':
			{//オクターブアップ
				pPart->oct1++;
				break;
			}
		case '_':
			{//トランスポーズ設定
				if(*linePtr == '_') {
					linePtr++;	//二個目の'_'の次へ進める。
					pPart->trs2 = getVal();							//C:\usr\PIECE\docs\muslib\mml.htmの「トランスポーズ設定」に説明有り。
				} else {
					pPart->trs1 = getVal();
				}
				addByte(SEQ_TRS);
				addByte(pPart->trs1 + pPart->trs2);
				break;
			}
		case '!':
			{//マクロ
				extMacro(pPart);	//MMLマクロもドラムマクロもパート定義行の中での書き方は同じ'!マクロ名'です。extMacro()の中でどちらの種類か判断して処理分けしています。
				break;
			}
		case 'L':
			{//全体ループ設定
				int iPart;
				setLabel();		//各パートに、現在位置のラベルを作成する。
				for(iPart = 0; iPart < MAXPART; iPart++) {
					if(partMask & (1 << iPart)) {
						ST_Part* pPart = &TBL_Part[iPart];
						if(pPart->loopLabel != -1) {
							die("%s(%d): ループ指定が重複しています。", srcName, srcLine);
						}
						pPart->loopLabel = pPart->nLabel - 1;	//(ラベルを生成した数-1)が最後に生成したラベル番号である。
					}
				}
				break;
			}
		case '=':
			{//トラック属性
				addByte(SEQ_TAT);
				addByte((pPart->trackAttr = getVal()));
				break;
			}
		case '@':
			{//音色設定
				genCtrl(c, SEQ_INO, 1);
				break;
			}
		case 'E':
			{//エンベロープ
				genCtrl(c, SEQ_ENV, 4);
				break;
			}
		case 'Y':
			{//ビブラート
				genCtrl(c, SEQ_VIB, 4);
				break;
			}
		case 'Z':
			{//ポルタメントパラメータ
				// - ポルタメントパラメータは、genNote()が自動計算するのに任せるのが適切であり、MMLで明示的に指定するものではないと思う。
				//   MMLで明示的に指定しても、その後でgenNote()が自動生成するポルタメントパラメータの方が有効になるので、無意味だと思う。
				// - 本当はこのUndoc'dなコマンドを削除したいのだが、\usr\PIECE\data\music\free_001.mml,free_002.mml,free_003.mmlが使用しているので削除できなかった。
				//   free_001.mml,free_002.mml,free_003.mmlが、どういう目的でこのUndoc'dなコマンドを使用しているのか、まだ理解できていない。
				//   もしかすると、昔はgenNote()が自動計算せずにMMLで明示的に指定する必要があったのかも知れない。古い仕様の名残かも知れない。
				genCtrl(c, SEQ_PPA, 2);								//mucc.exeのUndoc'dな動作	'Z数値,数値'
				break;
			}
		case '?':
			{//ドラムマクロ内での発声コマンド。ドラムパートの中では通常の発声コマンド(0x80〜0xDF)がドラムマクロの呼び出しと解釈される。ドラムマクロの中で通常の発声コマンドを使うとドラムマクロ呼び出しの無限ループになってしまうので、そうならないよう'?'を使う必要が有る。尚、ドラムパートの中以外でも'?'を使用しても動作上は問題ないが無意味である。
				addByte(SEQ_NOT);
				break;
			}
		case '{':
			{//ポルタメント(開始)
				pPart->porSw = 1;
				break;
			}
		case '}':
			{//ポルタメント(終端)
				pPart->porSw = 2;
				genNote(pPart, pPart->porNote1);	//ポルタメント関連のコマンド,及び,ポルタメントが影響する発声コマンドは、全てこの中で生成される。
				break;
			}
		case 'q':
		case 'Q':
			{//音の切り方
				//「Ｐ/ＥＣＥ　ＭＭＬ文法リファレンス」【音長】音の切り方 ２(file:///C:/usr/PIECE/docs/muslib/mml.htm#mml14)には、
				//＞q《0〜100》数値の時間分、後ろから音をカットします。
				//と書いてありますが、値の範囲が間違っています。
				//mucc.exeのソース(\usr\PIECE\tools\mucc\mmlcv.c)のana_body()の'q'の処理を見ると、
				//＞q《0〜24》数値の時間分、後ろから音をカットします。
				//と考えるのが正解です。実際にmucc.exeを使って試してみて、0〜24の挙動である思いました。
				//自作ツールdpmucc.exe(clip/tool/dpmucc/mml.c)も、0〜24の範囲で指定するようにしました。
				int gate = getVal();
				if(c == 'q') {
					gate = ((24 - gate) * 99) / 24;		//音の切り方2
				} else {
					gate = (      gate  * 99) /  8;		//音の切り方1
				}
				if(gate <  1) { gate =  1; }
				if(gate > 99) { gate = 99; }
				addByte(SEQ_GAT);
				addByte(gate);
				break;
			}
		case '(':
		case ')':
			{//音量相対変化
				int expr = 1;
				if(isdigit(*linePtr)) { expr = getVal(); }
				if(c == '(') { expr = -expr; }
				addByte(SEQ_EXR);
				addByte(expr);
				break;
			}
		default:
			die("%s(%d): MMLコマンド'%c'が不正です。", srcName, srcLine, c);
		}
	}
	//MML本体の解析中にMMLマクロ呼び出しが行われていたら、MMLマクロ呼び出し前の走査位置へ戻してMML本体の解析を継続する。
	if(macLev) {
		linePtr = TBL_MacStk[--macLev];
		goto L_REDO;
	}
}
/****************************************************************************
 *	doMacro
 ****************************************************************************/
//マクロ行を処理する。
static void doMacro() {
	int drum = 0;
	char* name;
	//mmlParse()で飛ばした'!'の直後に二個目の'!'が有れば、'!!〜'でありドラムマクロである。
	if(*linePtr == '!') {
		linePtr++;	//二個目の'!'を飛ばす。
		drum = 1;
	}
	//マクロ名を取得する。
	name = getMacroName();
	//このマクロ名が、MMLマクロとしてもドラムマクロとしても登録されていないことを確認する。
	// - マクロ定義の書式はMMLマクロ(!name)とドラムマクロ(!!name)で別だが、マクロ呼び出しの書式は同じ(!name)なので、全体を通してマクロ名がユニークでなければならない。
	if((getMmlMac(name) != NULL) || (getDrumMac(name) >= 0)) {
		die("%s(%d): マクロ名'%s'が重複しています。", srcName, srcLine, name);
	}
	//MMLマクロならば…
	if(!drum) {
		if((unsigned)nMmlMac >= (unsigned)MAXMMLMAC) { DIE(); }		//エラー:MMLマクロが多過ぎる。まず発生しないと思うので、丁寧なエラーメッセージは不要だろう。
		//MMLマクロを登録する。
		TBL_MmlMac[nMmlMac].name	= name;			//getMacroName()が複製済みなのでここでは複製不要。
		TBL_MmlMac[nMmlMac].mmlStr	= strdup(linePtr);	//複製必須。
		nMmlMac++;
		//MMLマクロのMML本体は定義時に解析しない。呼び出された所で都度解析する。
	//ドラムマクロならば…
	} else {
		if((unsigned)nDrumMac >= (unsigned)MAXDRUMMAC) { DIE(); }	//エラー:ドラムマクロが多過ぎる。まず発生しないと思うので、丁寧なエラーメッセージは不要だろう。
		endSection();		//前の区間の最後に'終了'を置く。	//必要!!忘れないで!!
		//ドラムマクロを登録する。
		TBL_DrumMac[nDrumMac].name	= name;			//getMacroName()が複製済みなのでここでは複製不要。
		TBL_DrumMac[nDrumMac].addr	= getAddr();
		nDrumMac++;
		//ドラムマクロを解析する。
		partMask = 0;		//ドラムマクロ解析中は、現在指定されているパートのビットマスクを0にする。
		doBody();		//MML本体を解析する。
	}
}
/****************************************************************************
 *	doPart
 ****************************************************************************/
//パート定義行を解析する。空行でも呼び出されます。
static void doPart() {
	int iPart, fPart = 0;
	//行先頭から大文字のアルファベットが続く限り、パート指定である。
	while(isupper(*linePtr)) {
		//A〜Fがパート0〜5に対応する。G〜Zは全てパート6に対応する。
		//\usr\PIECE\data\music\c350.mmlがJを使っているのでG以降をエラーに出来ない。
		iPart = *linePtr++ - 'A';
		if((unsigned)iPart > (unsigned)(MAXPART - 1)) { iPart = MAXPART - 1; }
		//現在指定されているパートのビットマスクを作成する。
		fPart |= (1 << iPart);
	}
	//パートが一つ以上指定され,かつ,前回の組合せと変化したら、新しい区間を開始する。
	//<例>
	//│AB cde
	//│CD fg	;←ここを処理する時
	//<例>
	//│AB cde
	//│A  fg	;←ここを処理する時
	if(fPart && (fPart != partMask)) {
		partMask = fPart;	//新しい区間の、パートの組合せを格納する。	//setLabel()がpartMaskを参照するので、setLabel()を呼び出す前に行う必要が有る。
		setLabel();		//各パートに、現在位置のラベルを作成する。
	//パートが一つも指定されていないか,又は,前回の組合せと同じならば、前回の区間を継続する。
	//<例1>
	//│AB cde
	//│   fg	;←ここを処理する時
	//<例2>
	//│AB cde
	//│AB fg	;←ここを処理する時
	//<例3>
	//│AB cde
	//│BA fg	;←ここを処理する時
	} else {
		/** no job **/
	}
	//パート定義行のMML本体を解析する。
	doBody();		//MML本体を解析する。
}
/****************************************************************************
 *	mmlSetup
 ****************************************************************************/
void mmlSetup() {
	int iPart;
	//+ 0, 1
	//ファイルの先頭(オフセット=0)に'終了'を置いておく。理由は以下の通り:
	//・pmdデータのパートテーブルで、　(オフセット=0)のパート　が自然に'終了'　を指すようにするため。
	//・pmdデータのタイトルポインタで、(オフセット=0)のタイトルが自然に空文字列を指すようにするため。
	addByte(SEQ_END);
	//+ 1, 1
	addByte(MAXPART);
	//+ 2,12
	for(iPart = 0; iPart < MAXPART; iPart++) {
		addWord(0);	//パートテーブルのプレースホルダ。mmlEnd()で実際のオフセットを格納する。
		TBL_Part[iPart].loopLabel = -1;	//ループ無しとしておく。
	}
	//+14, 2
	addWord(0);		//ドラムマクロテーブルのプレースホルダ。mmlEnd()で実際のオフセットを格納する。
	//+16, 2
	addWord(0);		//Title のプレースホルダ。doTitle()で実際のオフセットを格納する。
	//+18, 2
	addWord(0);		//Title2のプレースホルダ。doTitle()で実際のオフセットを格納する。
	//=20
}
/****************************************************************************
 *	mmlParse
 ****************************************************************************/
void mmlParse() {
	char buf[1024], *p;
	//一行読み込む。
	while((linePtr = fgets(buf, sizeof buf, fp))) {
		//行番号を進める。
		srcLine++;
		//';'以降をコメントとして切り捨てる。
		if((p = strchr(linePtr, ';'))) { *p = '\0'; }
		//先頭文字が'#'ならば、タイトル定義行である。
		if(*linePtr == '#') {
			linePtr++;	//'#'を飛ばす。
			doTitle();
		//先頭文字が'#'以外ならば…
		} else {
			//先頭文字が'!'ならば、MMLマクロ定義行,又は,ドラムマクロ定義行である。
			if(*linePtr == '!') {
				linePtr++;	//'!'を飛ばす。
				doMacro();	//マクロ名部分を解析する。
			//先頭文字がそれ以外ならば、パート定義行である。(パート指定有り,又は,パート指定無し)
			} else {
				doPart();	//パート定義行を解析する。空行でも呼び出して安全です。
			}
		}
	}
}
/****************************************************************************
 *	mmlEnd
 ****************************************************************************/
void mmlEnd() {
	int iPart, iLabel, iDrumMac, addr;
	endSection();		//前の区間の最後に'終了'を置く。
	//各パートについて…
	for(iPart = 0; iPart < MAXPART; iPart++) {
		ST_Part* pPart = &TBL_Part[iPart];
		if(pPart->nLabel) {	//不使用パートでなければ…
//{{2015/01/24修正:mmlEnd()のループ処理が間違っていたのを修正しました。元のmucc.exeのソース(\usr\PIECE\tools\mucc\mmlcv.c)は正しくて、自分で再実装した際のエンバグです。
//			putWord(2 + (iPart * 2), getAddr());	//パートテーブルのアドレスを格納する。		//'2'の意味は、mmlSetup()を参照せよ。
//			for(iLabel = 0; iLabel < pPart->nLabel; iLabel++) {
//				addByte(SEQ_CAL);
//				addWord(pPart->labelAddr[iLabel]);
//				addByte(1);	//繰り返し回数1固定		コール命令のコマンド仕様上は繰り返し回数を指定出来るが、mucc.exeはコール命令の繰り返し回数を1固定でしか生成しない。
//			}
//			if(pPart->loopLabel != -1) {	//ループ有りならば…
//				addByte(SEQ_JMP);	//ループ位置へジャンプする。
//				addWord(pPart->labelAddr[pPart->loopLabel]);
//			} else {			//ループ無しならば…
//				addByte(SEQ_END);	//終了する。
//			}
//↓2015/01/24修正:mmlEnd()のループ処理が間違っていたのを修正しました。元のmucc.exeのソース(\usr\PIECE\tools\mucc\mmlcv.c)は正しくて、自分で再実装した際のエンバグです。
// * Sat Jan 24 05:33:00 JST 2015 Naoyuki Sawa
// - ループ処理は、'ループ区間のラベル位置'へ戻るのではなく、'ループ区間を呼び出すSEQ_CALの位置'へ戻らなければいけない。
//   'ループ区間のラベル位置'へ戻ってしまうと、その区間のSEQ_ENDを曲全体の終了と見なしてしまい、正しいループにならない。
			int loopAddr = 0;		//ループ無しとしておく。
			putWord(2 + (iPart * 2), getAddr());	//パートテーブルのアドレスを格納する。		//'2'の意味は、mmlSetup()を参照せよ。
			for(iLabel = 0; iLabel < pPart->nLabel; iLabel++) {
				if(iLabel == pPart->loopLabel) {	//ループラベルを呼び出す位置ならば…
					loopAddr = getAddr();		//ループラベルを呼び出す位置のアドレスを記憶する。
				}
				addByte(SEQ_CAL);
				addWord(pPart->labelAddr[iLabel]);
				addByte(1);	//繰り返し回数1固定		コール命令のコマンド仕様上は繰り返し回数を指定出来るが、mucc.exeはコール命令の繰り返し回数を1固定でしか生成しない。
			}
			if(loopAddr) {			//ループ有りならば…
				addByte(SEQ_JMP);	//ループラベルを呼び出す位置へジャンプする。
				addWord(loopAddr);
			} else {			//ループ無しならば…
				addByte(SEQ_END);	//終了する。
			}
//}}2015/01/24修正:mmlEnd()のループ処理が間違っていたのを修正しました。元のmucc.exeのソース(\usr\PIECE\tools\mucc\mmlcv.c)は正しくて、自分で再実装した際のエンバグです。
		}
	}
	//ドラムマクロが一つ以上定義されていたら、ドラムマクロテーブルを出力する。
	if(nDrumMac) {
		putWord(14, getAddr());				//ドラムマクロテーブルのアドレスを格納する。	//'14'の意味は、mmlSetup()を参照せよ。
		for(iDrumMac = 0; iDrumMac < nDrumMac; iDrumMac++) {
			addWord(TBL_DrumMac[iDrumMac].addr);
		}
	}
	//コードをファイルに出力する。
	for(addr = 0; addr < getAddr(); addr++) {
		fputc(getByte(addr), fp);
	}
}
