/**
 * @file mml.h
 * @author Shinichiro Nakamura
 * @copyright
 * ===============================================================
 * A tiny MML parser (Version 0.5.0)
 * ===============================================================
 * Copyright (c) 2014-2015 Shinichiro Nakamura
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */
// 2015/09/09 for X-BASIC' by AIG-Soft

#ifndef MML_H
#define MML_H

#include "mml_stack.h"

#define MML_CONFIG_USE_AVR_PROGMEM	(0)
#ifdef IOS
#define MML_CONFIG_OCTAVE_REVERSE	(1)	//  <>の仕様を逆転する；> = down < = up (X68互換のため
#else
#define MML_CONFIG_OCTAVE_REVERSE	(0)
#endif

// ユーザーイベントの最大文字列長
#define MML_COMMENT_NAME_SIZE	(256)	// iOSではコメントを有効にするためぐっと広げる
										// BASICの最大文字列長より長い場合は、読み出し側＝BASIC側で対処すること
// ジャンプ系の最大文字列長
#define MML_JUMP_NAME_SIZE		(16)	// 実際にはTOCODAが最長

// サポートするオクターブ
#define	MUSIC_OCTAVES		(9)	// 0～8

// チャンネル=同時発声数
#define	MML_CHANNELS		(8)
// BASICレベルの値(Cレベルでは-1)
#define	MML_MIN_CHANNEL		(1)
#define	MML_MAX_CHANNEL		(MML_MIN_CHANNEL+MML_CHANNELS-1)

// トラック数
#define	MML_TRACKS			(80)
// BASICレベルの値(Cレベルでは-1)
#define	MML_MIN_TRACK		(1)
#define	MML_MAX_TRACK		(MML_MIN_TRACK+MML_TRACKS-1)

// オクターブ範囲
#define MML_OCTAVE_MIN		(0)
#define MML_OCTAVE_MAX		(8)		// 音源の特性上これ以上は無意味だから;必要なら音色データ自体を差し替えてMML上ではオクターブを範囲内に収める

// テンポ範囲
#define	MML_MIN_TEMPO		(32)
#define	MML_MAX_TEMPO		(200)

// 音量範囲
#define	MML_MIN_VOLUME		(0)
#define	MML_MAX_VOLUME		(15)
// 詳細音量範囲
#define	MML_MIN_EX_VOLUME	(0)
#define	MML_MAX_EX_VOLUME	(127)

// パンポット範囲
enum {
	MML_PANPOT_LEFT=1,
	MML_PANPOT_RIGHT,
	MML_PANPOT_CENTER,
};
#define	MML_MIN_PANPOT		(MML_PANPOT_LEFT)
#define	MML_MAX_PANPOT		(MML_PANPOT_CENTER)
// 詳細パンポット範囲
#define	MML_MIN_EX_PANPOT		(0)
#define MML_CENTER_EX_PANPOT	(64)
#define	MML_MAX_EX_PANPOT		(127)

// 音色範囲
#define	MML_TONES			(100)	// 音色数
// BASICレベルの値(Cレベルでは-1)
#define	MML_MIN_TONE		(1)
#define	MML_MAX_TONE		(MML_MIN_TONE+MML_TONES-1)

// |:n 〜 :|ループの回数
#define MML_MIN_LOOP		(1)		// 最小値
#define MML_MAX_LOOP		(256)	// 最大値

// 発声割合
#define	MML_MIN_RATIO		(1)
#define	MML_MAX_RATIO		(8)
// 詳細音長範囲
#define	MML_MIN_EX_LENGTH	(1)
#define	MML_MAX_EX_LENGTH	(192)

// デフォルト値
#define MML_OPTION_DEFAULT_OCTAVE		(4)			// O4
#define MML_OPTION_DEFAULT_LENGTH		(4)			// L4
#define MML_OPTION_DEFAULT_TEMPO		(120)		// T120
#define	MML_OPTION_DEFAULT_VOLUME		(64)		// @V64 音量
#define	MML_OPTION_DEFAULT_SOUND		(0)			// @n-1 音色番号(0〜):この音色は必ず標準で設定しておくこと
#define MML_OPTION_DEFAULT_PANPOT		(64)		// @P   パンポット
#define MML_OPTION_DEFAULT_RATIO		(8)			// Q8
#define MML_OPTION_DEFAULT_EX_LENGTH	(48)		// @L48

// チェンネル指定をビット指定でする時の定数
// X-BASIC'のBCH?とは1bitずれているので注意
#define MUSIC_bCH0	BIT0
#define MUSIC_bCH1	BIT1
#define MUSIC_bCH2	BIT2
#define MUSIC_bCH3	BIT3
#define MUSIC_bCH4	BIT4
#define MUSIC_bCH5	BIT5
#define MUSIC_bCH6	BIT6
#define MUSIC_bCH7	BIT7
#define MUSIC_all_CH	(MUSIC_bCH0|MUSIC_bCH1|MUSIC_bCH2|MUSIC_bCH3|MUSIC_bCH4|MUSIC_bCH5|MUSIC_bCH6|MUSIC_bCH7)


#define MML_OPTION_INITIALIZER(P, OCTAVE, LENGTH, BTICKS) \
	do { \
		(P)->octave = (OCTAVE); \
		(P)->length = (LENGTH); \
		(P)->bticks = (BTICKS); \
	} while (0)

#define MML_OPTION_INITIALIZER_DEFAULT(P) \
	do { \
		(P)->octave = (MML_OPTION_DEFAULT_OCTAVE); \
		(P)->length = (MML_OPTION_DEFAULT_LENGTH); \
		(P)->bticks = (MML_OPTION_DEFAULT_BTICKS); \
	} while (0)

typedef enum {
	MML_TYPE_NOTE,			// 発声準備
	MML_TYPE_NOTEW,			// 和音準備
	MML_TYPE_NOTE_GO,		// 実発声
	MML_TYPE_NOTEW_GO,		// 実和音
	MML_TYPE_NOTE_FINISH,	// 発声終了
	MML_TYPE_REST,			// 休符準備
	MML_TYPE_REST_GO,		// 実休符
	MML_TYPE_TEMPO,
	MML_TYPE_VOLUME,
	MML_TYPE_OCTAVE,
	MML_TYPE_SOUND,
	MML_TYPE_PANPOT,
	MML_TYPE_COMMENT,
} MML_TYPE;

typedef struct {
	int 	number;
} MML_ARGS_NOTE;

typedef struct {
	int 	value;	// ダミー
} MML_ARGS_REST;

typedef struct {
	int 	value;
} MML_ARGS_TEMPO;

typedef struct {
	int 	value;
} MML_ARGS_VOLUME;

typedef struct {
	int 	value;
} MML_ARGS_OCTAVE;

typedef struct {
	int 	value;
} MML_ARGS_SOUND;

typedef struct {
	int 	value;
} MML_ARGS_PANPOT;

typedef struct {
	char 	*name;
} MML_ARGS_COMMENT;

typedef struct {
	int value;
} MML_ARGS_RATIO;

typedef struct {
	char 	*obj;
	int		stat;
} MML_ARGS_FETCH;


typedef struct {
	MML_TYPE type;
	int	subValue;	// 補足的データを入れる
	union {
		MML_ARGS_NOTE 		note;
		MML_ARGS_REST 		rest;
		MML_ARGS_TEMPO 		tempo;
		MML_ARGS_VOLUME 	volume;
		MML_ARGS_OCTAVE 	octave;
		MML_ARGS_SOUND		sound;
		MML_ARGS_PANPOT		panpot;
		MML_ARGS_COMMENT 	comment;
		MML_ARGS_RATIO		ratio;
		MML_ARGS_FETCH		fetch;
	} args;
} MML_INFO;

typedef void (*MML_CALLBACK)(MML_INFO *p, void *extobj);
/*
 テンポは1分間に4分音符を何拍演奏できるか			テンポはticksで調整する
 標準120							最遅32		最速200
 					ticks		*3.75		*0.6
 1		4倍=2		1920		7200		1152
 2		2倍=1		 960		3600		576
 4		1倍=0.5s	 	 480		1800		288		<-bticks
 8		1/2=0.25	 240		900			144
 16		1/4=0.125	 120		450			72
 32		1/8			  60		225			36
 64		1/16		  30		112.5		18
 T120 L4 1音 60s/120=0.5秒		内部は整数処理なので誤差が出ることがある
 故にticks=0.5/480=1/960秒固定
 この周期で割り込み、ダウンカウンターで音長を作る。カウンタは小数点以下を切り捨てるので若干短くなる可能性があるが、
 全体のタイミングは固定なので音はずれない。
  */
#define calcTempo2Tick(t)	(480*120/(t))
#define MML_1TICK_TIME		(1.0/960.0)

//------------------------------------

typedef enum {
	MML_STATE_INIT,
	MML_STATE_WAIT_INIT,
	MML_STATE_WAIT_SETUP,
	MML_STATE_FETCH_READY,
} MML_STATE;

typedef enum { // エラーコード
	// <0はエラー
	MML_RESULT_ILLEGAL_DATA			=-100,	// 内容がおかしい	; 以下のエラーコードが全て<0になるような初期値にすること
	MML_RESULT_ILLEGAL_COMMAND,				// 異常なコマンド
	MML_RESULT_IN_FETCH,					// fetch中なのに別のMMLオブジェクトを設定しようとした
	//
	MML_RESULT_ILLEGAL_NOTE_VALUE,
	MML_RESULT_ILLEGAL_OCTAVE_VALUE,
	MML_RESULT_ILLEGAL_LENGTH_VALUE,	
	MML_RESULT_ILLEGAL_TEMPO_VALUE,
	MML_RESULT_ILLEGAL_VOLUME_VALUE,
	MML_RESULT_ILLEGAL_SOUND_VALUE,
	MML_RESULT_ILLEGAL_PANPOT_VALUE,
	MML_RESULT_ILLEGAL_RATIO_VALUE,		// -90
	MML_RESULT_ILLEGAL_COMMENT_LENGTH,
	MML_RESULT_ILLEGAL_NOTE_TEXT,
	MML_RESULT_ILLEGAL_SEQUENCE_FOUND,	// 異常なMMLが存在する
	MML_RESULT_ILLEGAL_TOKEN_LENGTH,
	MML_RESULT_ILLEGAL_USER_EVENT_TOKEN,
	MML_RESULT_ILLEGAL_USER_EVENT_LENGTH,
	MML_RESULT_ILLEGAL_REPEAT_PAIR,		// |:がないのに|nがあった,|:〜:|の対が正しくない(閉じてない)
	MML_RESULT_NOT_READY_FOR_SETUP,
	MML_RESULT_NOT_READY_FOR_FETCH,
	MML_RESULT_STACK_FULL_REPEAT_START,	// |:スタックがいっぱい＝ネストが深すぎる	-80
	MML_RESULT_STACK_EMPTY_REPEAT_START,// |:スタックが空なのに読み出そうとした=対でない:|がある
	MML_RESULT_STACK_FULL_REPEAT_N,		// |nスタックがいっぱい
	MML_RESULT_STACK_EMPTY_REPEAT_N,	// |nスタックが空なのに読み出そうとした
	MML_RESULT_NO_SEGNO,				// SEGNOがない
	MML_RESULT_ILLEGAL_SEGNO,			// SEGNOが多重にある
	MML_RESULT_NO_DO,					// DOがない
	MML_RESULT_ILLEGAL_DO,				// DOが多重にある
	MML_RESULT_NO_CODA,					// CODAがない
	MML_RESULT_ILLEGAL_CODA,			// CODAが多重にある
	MML_RESULT_ILLEGAL_RENPU,			// 連符により1音が短くなりすぎる、連符なのに個別の音長さ指定がある	-70
	MML_RESULT_ILLEGAL_RENPU_PAIR,		// {}が対になってない,{がないのに}があった,{がネストしている
	MML_RESULT_ILLEGAL_REPEATN_VALUE,	// |nのn記述がない
	MML_RESULT_WAON_TOO_MUCH,			// 和音数が多すぎる
	MML_RESULT_ILLEGAL_WAON,			// 和音の中にノート以外がある,和音が閉じていない
	MML_RESULT_COMMENT_TOO_LONG,		// コメントが長過ぎる
	MML_RESULT_ILLEGAL_TIE,
	//
	MML_RESULT_NOMEMORY=-10,			// メモリが足りない
	MML_RESULT_TOO_LARGE,				// MMLが大きすぎる(オブジェクト、コメント)
	MML_RESULT_NO_MML,					// MMLの実体がない(テキスト、オブジェクト)
	// >=0は正常系
	MML_RESULT_OK	=0,
	MML_RESULT_EOT	,
} MML_RESULT;

//------------------------------------

typedef enum {
	SCALE_C=0,	// C		0
	SCALE_CS,	// C#		1
	SCALE_D,	// D		2
	SCALE_DS,	// D#		3
	SCALE_E,	// E		4
	SCALE_F,	// F		5
	SCALE_FS,	// F#		6
	SCALE_G,	// G		7
	SCALE_GS,	// G#		8
	SCALE_A,	// A		9
	SCALE_AS,	// A#		10
	SCALE_B,	// B		11
	//
	MUSIC_SCALES	// 1オクターブ内の音
} SCALE_NOTE;

extern const char *scale_string[];

//------------------------------------
// コンパイラ

#ifdef DEBUG	//!!test!!debug!!
#define MML_OBJECT_SIZE_STEP	(32)	// MMLオブジェクト領域のサイズ増分>初期設定分
#define MML_COMMENT_SIZE_STEP	(16)	// コメント領域のサイズ増分
#define MML_MAX_OBJECT			(128)	// オブジェクト最大サイズ（コメントを除く）
#else	// 本番
#define MML_OBJECT_SIZE_STEP	(256)	// MMLオブジェクト領域のサイズ増分>初期設定分
#define MML_COMMENT_SIZE_STEP	(64)	// コメント領域のサイズ増分
#define MML_MAX_OBJECT		(64*1024)	// オブジェクト最大サイズ（コメントを除く）
#endif

typedef enum { // 連符処理状態
	RENPU_NONE,		// 連符処理してない
	RENPU_START,	// {開始
	RENPU_REWRITE	// 連符音長書き込み中
} RENPU_STATE;

enum {	// Nopでの識別子
	MML_NOP_NONE=0,
	MML_NOP_LOOP_START,
	MML_NOP_SEGNO,
	MML_NOP_CODA,
	MML_NOP_DO,
};

typedef struct {
	int octave;
	int length;
	int bticks;
} MML_OPTION;

typedef struct {
	mml_pointer_stack_t start;
	mml_int_stack_t 	end;
} MML_REPEAT_STACK;

// コンパイルはトラックごと
// コンパイル結果がコメントを含め64KB(word範囲)を超えないこと
typedef struct {
	MML_OPTION 	option;
	
	char	*top;			// MMLソース先頭
	char 	*tp;			// MMLソース
	
	char	*objTop;		// コンパイラ結果先頭
	int		obj;			// コンパイル結果内オフセット
	int		objTrueTop;		// 初期化を除く実質の先頭内オフセット
	int		objSize;		// オブジェクト領域サイズ
	
	char	*comTop;		// コメント領域先頭
	int		comp;			// コメント領域内オフセット
	int		comSize;		// コメント領域サイズ
	int		realComSize;	// 実際のコメント総サイズ<=comSize

	// タイ処理
	int		prevNote;		// 直前のNOTE出力アドレス(=0;直前はNOTEでない)
	
	// 連符処理用
	RENPU_STATE	frenpu;		// 連符処理中の印;RENPU_*
	char	*renpuTop;		// 連符開始位置
	char	*renpuEnd;		// 連符終了位置（音長の後ろ）
	int		renpuCount;		// 連符内の音符数
	int		renpuTicks;		// 連符内のticks

	// ループ処理用
	mml_int_stack_t startAdrs;
	mml_int_stack_t cnt;
	mml_int_stack_t repeatNAdrs;

	// ジャンプ処理用
	// 各位置はオブジェクト内オフセット位置なので注意
	int		segno;			// [SEGNO]位置
	int		coda;			// [CODA]位置
	int		storeToCoda;	// [TOCODA]の書き込み位置
	int		doloop;			// [DO]の位置
	int		jumpCnt;		// [!]=次の[!]まで強制ジャンプ=次の[!]までコンパイルを飛ばす
	int		prevRepeat;		// 1つ前の|nの位置
	
	// 和音処理用
	int		fwaon;			// 和音モード
	int		waonCnt;		// 和音数
} MML;

#define MML_MAX_WAON		(4)	// 和音の最大発声数

typedef enum {
	MML_COM_NOP		=' ',	// No Operation オブジェクトレベルのコメントのようなもの
	MML_COM_SOUND	='S',
	MML_COM_PANPOT	='P',
	MML_COM_VOLUME	='V',
	MML_COM_OCTAVE	='O',
	MML_COM_TEMPO	='T',
	MML_COM_FINE	='F',
	MML_COM_RATIO	='Q',
	MML_COM_WAON	='W',
	MML_COM_WAON_GO	='w',	// 和音実実行
	MML_COM_END		='E',	// 終端
	//
	MML_COM_JUMP	='J',	// フラグジャンプ	val=フラグ	wval=アドレス
	MML_COM_NEJUMP	='j',	// loop!=nでジャンプ	val=n	wval=アドレス
	MML_COM_NOTE	='N',	// val=note , wval=ticks	タイ無し	準備
	MML_COM_NOTET 	='n',	// val=note , wval=ticks	タイあり	準備
	MML_COM_NOTE_GO	='g',	// NOTE/NOTEWの実実行
	MML_COM_REST	='R',	// val=0	, wval=ticks
	MML_COM_REST_GO	='r',
	MML_COM_COMMENT	='C',	// val=0	, wval=コメント領域アドレス
	//
	MML_COM_LOOP	='|',	// :| 	val=ループ回数	wval=アドレス		loop1,0
} MML_COMMAND;

#define MML_HEADER	"MML1"	// 4バイト識別子
#define SZ_MML_HEADER	(sizeof(MML_HEADER)-1)	// EOSを除く

//------------------------------------
// インタープリター

typedef enum {
	FWAIT_NONE=0,	// ウエイト無し
	FWAIT_NOTE,		// ウエイトはNOTE
	FWAIT_NOTER,	// ウエイトはNOTE；ただしRATEで消音済み
	FWAIT_REST,		// ウエイトは休符
	FWAIT_NOTE_GO,	// 次はNOTE実実行
	FWAIT_NOTEW_GO,	// 次はNOTEW実実行
	FWAIT_REST_GO,	// 次はREST実実行
} FWAIT_TYPE;		// 直前のwaitの種類

typedef enum {
	FSYNC_NONE=0,	// 同期待ちなし
	FSYNC_NOW,		// ↑ 同期待ち中
	FSYNC_WAIT,		// ↑ 準備が済みまで処理を進め、そこで同期待ちに入る
} FSYNC_TYPE;

typedef struct {
	MML_STATE 	state;
	// C-callback
	MML_CALLBACK	callback;
	void 		*extobj;
	//
	char 		*mmlObjTop;		// MMLオブジェクト先頭
	char 		*mmlObj;		// MMLオブジェクト
	//
	int 		adrs;			// MMLオブジェクトアドレス
	char		*p;				// フェッチ位置
	char 		*commentTop;	// コメント領域先頭アドレス
	//
	int			ftie;			// 前の発音をタイにする
	int			ticks;			// 実発声/休符時間
	int 		ratio;			// 発声割合
	// タイマー関係
#define	MAX_TIMER	2	// 最大同時設定可能本数 ; 同一開始時から終了時刻のみ変更
	// しかし現在プログラム中では２本の固定記述がされているので、これだけ変えても無駄。
	// NOTE : [0]=length分 , [1]=ratio分
	// REST : [0]=length分
	volatile u_int64_t *tickUp;	// カウントアップタイマーのアドレス；本体は外部にあるので参照のみ出来るようにする

	u_int64_t	timerStart;
	u_int64_t	timerEnd[MAX_TIMER];
	int			usingTimers;	// 1 〜 MAX_TIMERS
	//
	volatile
	FSYNC_TYPE	fsync;		// 初回発生同期待ち
	
	FWAIT_TYPE	fwait;		// 直前のwaitの種類
	//
	int			fjump;		// ジャンプ実行済みかどうか
	// 和音処理用
	int			fwaon;		// 和音モード
	int			waonCnt;	// 和音数
	// 発声/休符数
	unsigned int count;
} MML_INTERPRETER;

//------------------------------------
// 関数プロトタイプ宣言

#ifdef __cplusplus
extern "C" {
#endif

// インタープリター
MML_RESULT mml_init( MML_INTERPRETER *handle, MML_CALLBACK callback, void *extobj);
MML_RESULT mml_setup(MML_INTERPRETER *handle, MML_OPTION *option, char *mmlObject);
MML_RESULT mml_fetch_start(MML_INTERPRETER *handle);
MML_RESULT mml_fetch_once(MML_INTERPRETER *handle,id obj,SEL selector);
MML_RESULT mml_init_work(MML_INTERPRETER *handle);
// コンパイラ
MML_RESULT mml_tempo(MML *handle,int tempo);
MML_RESULT mml_compile(MML *handle,char *mmlSource);
MML_RESULT mml_compile_init(MML *handle);
char *mml_compile_object(MML *handle,int *size);
void mml_compile_free(MML *handle);
MML_RESULT mml_compile_dump(MML *handle);
int numberToOctaveAndScale(int number,int *scale);
//
void setTimer1(MML_INTERPRETER *handle,int tim);
void setTimer2(MML_INTERPRETER *handle,int tim1,int tim2);
int checkTimer(MML_INTERPRETER *handle);
void clearWaitEnd(MML_INTERPRETER *handle);
BOOL isWaitEnd(MML_INTERPRETER *handle);
	
#ifdef __cplusplus
}
#endif

#endif

