/* ===========================
      zmd to mid converter
		version 1.5
	programmed by Y.Yagi
	    thanks to A.Miwa
		      T.Ito
		  Y.Tanigawa
		& S.Fukagawa
	latest update:01/11/28
   =========================== */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include "config.h"

#ifdef UNIXY_OS
	#define inpEXT ".zmd"
	#define outEXT ".mid"
#else
	#define inpEXT ".ZMD"
	#define outEXT ".MID"
#endif

/* ryk ==> */
#ifdef ZTOM_STATIC_LIB	/* Static lib */
	#include "setjmp.h"
	#include "etc.h"
	int ZTOMMain(int , char **);
	extern jmp_buf g_ztommark;
#endif
/* <== ryk */

#ifndef UNIXY_OS					/* for setmode() */
	#include <fcntl.h>
	#include <io.h>
	#ifdef X68000
		#include <unistd.h>
	#endif
#endif

#include "MThdINFO.h"
#include "structs.h"
#include "makeMThd.h"
#include "makeMTr0.h"
#include "makeMTrk.h"
#include "options.h"
#include "instload.h"
#include "etc.h"
#include "sync.h"
#include "tempo.h"
#include "readfile.h"
#include "debugcmd.h"



int fmtchk(UBYTE *buf);
void help(int exitcode);

DWORD wavedatanum[WAVMAX],wavelooppoint[WAVMAX];
WORD *wavetable[WAVMAX];
BYTE wavelooptype[WAVMAX];


/* ryk ==> */
#ifdef ZTOM_STATIC_LIB	/* Static lib */
int ZTOMMain(int argc,char *argv[])
#else
int main(int argc,char *argv[])
#endif
/* <== ryk */
{
#ifdef _DEBUG
    while(!IsDebuggerPresent()){
        Sleep(1);
    }
#endif
	FILE *fp;
	MThdINFO minfo;
	FILEINFO finfo;
	SYNCDAT *sync;		/* [SYNCMAX]; */
	TEMPODAT *tempo;	/* [TEMPOMAX]; */
	DEBUGDAT *debug;	/* [DEBUGMAX]; */
#ifdef _0
	#ifdef DOS68_TIMESTAMP
		DWORD		timep;
	#elif defined LSIC86
		time_t		timep[2];
	#else
		struct utimbuf		timep;
	#endif
#endif
	DWORD syncs=0,tempos=0,debugs=0,syncmax=100,tempomax=100,debugmax=60;
	/*DWORD i,midc[TRKMAX+1],zmdc,trks,trkst[TRKMAX],trkch[TRKMAX],MDDgate;del by Kobarin*/
    /*DWORD i; => int i; に修正 by Kobarin*/
    int i;
	DWORD midc[TRKMAX+1],zmdc,trks,trkst[TRKMAX],trkch[TRKMAX],MDDgate;
	DWORD loopst[TRKMAX],looped[TRKMAX];
	WORD envdata[ENVMAX];
	UBYTE *zbuf,MThd[14],*MTrk[TRKMAX];
	char *instname[TRKMAX],copyright[CRlen];
/* ryk ==> */
#ifdef ZTOM_STATIC_LIB	/* Static lib */
	int				result;
	extern jmp_buf	g_ztommark;
#endif
/* <== ryk */

	initMemMaster();

/* ryk ==> */
#ifdef ZTOM_STATIC_LIB	/* Static lib */
	result = setjmp(g_ztommark);
	if(result != 0) {
		return(result);
	}
#endif
/* <== ryk */

#ifdef XCLIB
	allmem();
#endif
	setvbuf(stderr,NULL,_IONBF,0);		/* 出力のバッファリングをしない */
	ZTOMFormatMessage(stderr,"ZMD(V2) to MID converter version 1.63, by Y.Yagi.\n" );	/* ryk */
	finfo = option(argc,argv);
	/* finfo = supplement(finfo); */

	if (!(finfo.inpfile[0])) {
#ifdef UNIXY_OS
		strcpy(finfo.inpfile,"-");
#else
	#ifndef ENGLISH
		fatal(1,"Error: 入力ファイルが指定されていません。\n");
	#else
		fatal(1,"Error: source file is not specified.\n");
	#endif
#endif
	}

#ifdef _0
	if (!(zbuf = ZMDread(finfo.inpfile,&timep))) {		/* ZMDファイル読み込み */
#endif
	if (!(zbuf = readFile(&finfo,inpEXT,outEXT))) {
		terminateProgram(1);
/* ryk ==> */
#ifdef ZTOM_STATIC_LIB	/* Static lib */
		longjmp(g_ztommark, 1);
#endif
/* <== ryk */
	}

	if ( (i = fmtchk(zbuf)) < 0 ) {
#ifndef ENGLISH
		fatal(1,"Error: %sは ZMD形式のフォーマットではありません。\n",finfo.inpfile);
#else
		fatal(1,"Error: %s is not ZMD format file.\n",finfo.inpfile);
#endif
	} else {
		BYTE verH = (i>>4)	/*, verL = (i&15)*/ ;
		/* printf( "[%s]はZmuSiC ver %d.%dx で作成されています。\n",finfo.inpfile,verH,verL ); */
		if (verH > 2) {
			if (verH == 3) {
#ifndef ENGLISH
				fatal(1,"Error: Z-MUSIC V3 のデータは変換出来ません。\n\
ZMR.x(じんべさん作)やz2m3.x(拙作)をお使い下さい。\n\
ちなみに、前者がお勧めです:-)\n");
#else
				fatal(1,"Error: can't convert Z-MUSIC V3 format file.\n");
#endif
			} else if (verH == 0xF) {
#ifndef ENGLISH
				ZTOMFormatMessage(stderr,"Warning: 改造したZ-MUSICのデータは恐らく変換に失敗するでしょう。\n");
#else
				ZTOMFormatMessage(stderr,"Warning: it will fail to convert this special ZMD, maybe.\n");
#endif
			}
		}
	}

	/* printf( "1トラックごとに%dKB確保します。\n",finfo.trkbuf ); */
#ifndef ENGLISH
	if (finfo.timer) {
		ZTOMFormatMessage(stderr, "TIMER-Aモードです。\n" );
	}
	/* if (!finfo.looptime) {
		fprintf(sttderr, "無限ループに拡張イベントを用います。\n");
	} */
	if (finfo.opmtrk) {
		ZTOMFormatMessage(stderr, "FMトラックも変換します。\n" );
	}
	if (finfo.init >= 0) {
		ZTOMFormatMessage(stderr, "ドライバの送る初期化コードを埋め込みます。\n" );
	}
	if (finfo.doloop) {
		ZTOMFormatMessage(stderr, "[do]〜[loop]の展開回数を自動調整します。\n");
	}
#else
	if (finfo.timer) {
		ZTOMFormatMessage(stderr, "TIMER-A mode.\n" );
	}
	/* if (!finfo.looptime) {
		fprintf(stderr, "special meta event will be used for [do]-[loop].\n" );
	} */
	if (finfo.opmtrk) {
		ZTOMFormatMessage(stderr, "convert FM tracks, too.\n" );
	}
	if (finfo.init >= 0) {
		ZTOMFormatMessage(stderr, "set initial reset code.\n" );
	}
	if (finfo.doloop) {
		ZTOMFormatMessage(stderr, "set loop times in [do]-[loop] automatically.\n");
	}
#endif

	for (i = TRKMAX-1; i >= 0; i--) {		/* 楽器名 初期化 */
		instname[i] = (char *)emalloc(256, "instname[i]");
		instname[i][0] = '\0';
	}
	//for (i = WAVMAX; i >= 0; i--) {//削除 by Kobarin
    for(i = WAVMAX-1; i >= 0; i--){  //修正 by Kobarin(WAVMAX => WAVMAX-1)
		wavetable[i] = (WORD)NULL;			/* 波形メモリ データ 初期化 */
		wavedatanum[i] = 0;					/* 波形メモリ データ数 初期化 */
		wavelooptype[i] = 0;				/* 波形メモリ ループタイプ 初期化 */
		wavelooppoint[i] = 0;				/* 波形メモリ ループ地点 初期化 */
	}
	sync = (SYNCDAT *)emalloc(sizeof(SYNCDAT)*syncmax, "sync");
	tempo = (TEMPODAT *)emalloc(sizeof(TEMPODAT)*tempomax, "tempo");
	debug = (DEBUGDAT *)emalloc(sizeof(DEBUGDAT)*debugmax, "debug");
	copyright[0] = 0;						/* 著作権表示 初期化 */
	envdata[0]  = -1;						/* 拍/拍子 初期化  */
	minfo.Z = 192;							/* 全音符の音長 初期化 */
	MDDgate = finfo.MDDdelta;				/* MDDgate..最初はMDDdelta */
	envread(finfo,instname,envdata,copyright);	/* 環境ファイル読み込み */


	MTrk[0] = (UBYTE *)emalloc(finfo.trkbuf*1024, "MTrk[0]");		/* 共通コマンドを変換 */
	minfo = makecommon(zbuf,&MTrk[0],&midc[0],&zmdc,finfo.trkbuf,
				minfo,envdata,copyright,&MDDgate,	/* MDDdelta→MDDgate変換 */
				finfo.init);
	if (!((zmdc++)&1)) {
		zmdc++;
	}

	/* trks = (zbuf[zmdc]<<8) + zbuf[zmdc+1]; */	/* 総トラック数 */
	trks = getWordAlign(&zbuf[zmdc]);
/*
	printf( "Trks :%d\n",trks);
*/
	zmdc += 2;

	for (i = 1; i <= trks; i++) {			/* 各トラックのスタートアドレス初期化 */
		/* trkst[i] = *(int *)(zbuf + zmdc); */
		trkst[i] = getDword(zbuf + zmdc);
		zmdc += 4;

		trkst[i] += zmdc;					/* 各トラックのスタートアドレス */
		trkch[i]  = zbuf[zmdc + 1];			/* 絶対チャンネル番号 */
/* printf("[%d:%X]",i,trkst[i]); */
		zmdc += 2;
		/* printf( "Trk %d:%X,%X\n",i,trkst[i],trkch[i]); */
	}
	trkst[trks + 1] = 0x2000000;			/* これだけ大きければいいだろう */

	usech_init();
	for (i = 1; i <= trks; i++) {		/* 変換メイン */
		zmdc = trkst[i];
		MTrk[i] = (UBYTE *)emalloc(finfo.trkbuf*1024, "MTrk[i]");

		MTrk[i] = makeMTrk(zbuf,&MTrk[i][0],&midc[i],&zmdc,finfo.trkbuf,
				i-1,trkch[i],trkst[i+1],finfo.init,finfo.looptime,
				finfo.R,MDDgate,finfo.fadespeed,finfo.fadedelay,
				finfo.ABspc,finfo.run_status,&instname[i-1][0],
				&syncs,&sync,syncmax,&tempos,&tempo,tempomax,
				&debugs,&debug,debugmax,
				&loopst[i],&looped[i],finfo.opmtrk);
	}

	if (syncs) {			/* 必要なら同期処理 */
		/* printf("\n同期の処理をしています。"); */
		ZTOMFormatMessage(stderr,"\nSetting Synchronous.");
		/* fflush(stdout); */
		do_sync(&MTrk[0],syncs,&sync[0],&sync,&midc[0]);
	}

	if (finfo.doloop) {
		int MAXTOTAL = 0,orglooptime = finfo.looptime,orgdelay = finfo.fadedelay;


		for (i = 1; i <= trks; i++) {		/* [do]〜[loop]チェック */
			if ((looped[i] - loopst[i]) * finfo.looptime + loopst[i] > MAXTOTAL) {
				MAXTOTAL = (looped[i]-loopst[i]) * finfo.looptime + loopst[i];
			}
		}

		/* printf("MT=%d\n",MAXTOTAL); */

		if (MAXTOTAL) {
#ifndef ENGLISH
			ZTOMFormatMessage(stderr,"[do]〜[loop]の展開回数を調整します。\n");
#else
			ZTOMFormatMessage(stderr,"calculating loop times for [do]-[loop]...\n");
#endif
			efree(tempo, "tempo");
			efree(sync, "sync");
			sync  = (SYNCDAT *)emalloc(sizeof(SYNCDAT)*syncmax, "sync");
			tempo = (TEMPODAT *)emalloc(sizeof(TEMPODAT)*tempomax, "tempo");
			syncs = tempos = 0;
			syncmax = tempomax = 100;

			usech_init();
			for (i = 1; i <= trks; i++) {
				int j,sd_do = 0,sd_loop = 0;

				for (j = 0; j < tempos; j++) {
					DWORD reltime;
					int k;
					for (k = 0; k < syncs; k++) {	/* 以下、同一trkで同期受信してないか探す */
						if (sync[k].trk == tempo[j].trk &&
							 sync[k].midc >= 0 &&
							 sync[k].trk == i-1 &&
							(sync[i].total == tempo[j].total &&
							 sync[i].count >  tempo[j].count)) {

							reltime = (sync[k].offset < 0)?	/* 安全装置 */
								0 : sync[k].offset - sync[k].total;
							if (loopst[k] <= sync[k].total) {
								sd_do = tempo[j].total + reltime;
							} else if (looped[k] <= sync[k].total) {
								sd_loop = tempo[j].total + reltime;
							}
						}
					}
					/* abstimesort[j] = tempo[j].total + reltime; */
					/* テンポ設定の絶対時間 */
				}
				loopst[i] += sd_do;
				looped[i] += sd_loop;

				/* printf("t%d: M=%d ed=%d st=%d ",i,MAXTOTAL,looped[i],loopst[i]); */
				/*    if ( (MAXTOTAL - loopst[i]) / (looped[i] - loopst[i]) > 1) { */
				if (looped[i] - loopst[i]) {
					finfo.looptime  = (MAXTOTAL - loopst[i] + orgdelay) /
								(looped[i] - loopst[i]);
					finfo.fadedelay = (MAXTOTAL - loopst[i] + orgdelay) %
								(looped[i] - loopst[i]);
				} else {
					finfo.looptime  = orglooptime;
					finfo.fadedelay = orgdelay;
				}
				/* printf("loop=%d\n",finfo.looptime); */
				zmdc = trkst[i];

				efree(MTrk[i], "MTrk[i]");
				MTrk[i] = (UBYTE *)emalloc(finfo.trkbuf*1024, "MTrk[i]");
				MTrk[i] = makeMTrk(zbuf,&MTrk[i][0],&midc[i],&zmdc,finfo.trkbuf,
					i-1,trkch[i],trkst[i+1],finfo.init,finfo.looptime,
					finfo.R,MDDgate,finfo.fadespeed,finfo.fadedelay,
					finfo.ABspc,finfo.run_status,&instname[i-1][0],
					&syncs,&sync,syncmax,&tempos,&tempo,tempomax,
					&debugs,&debug,debugmax,
					&loopst[i],&looped[i],finfo.opmtrk);
			}

			if (syncs) {			/* 必要なら同期処理 */
				ZTOMFormatMessage(stderr,"\nSetting Synchronous.");
				do_sync(&MTrk[0],syncs,&sync[0],&sync,&midc[0]);
			}
		}
	}

	if (tempos) {			/* テンポ処理 */
		/* int j = *(int *)(MTrk[0] + 4); */
		DWORD j = getDwordAlign(MTrk[0] + 4);
		MTrk[0] = (UBYTE *)erealloc( MTrk[0], j + 8 + 10 * tempos, "MTrk[0]");
		/* printf("\nテンポの処理をしています。"); */
		/* fflush(stdout); */
		do_tempo(&MTrk[0],tempos,&tempo[0],syncs,&sync[0],finfo.timer);
	}

	if (debugs) {			/* [!][@]コマンド処理 */
		int i;
		for (i =  0; i <= trks; i++) {
			DWORD j = getDwordAlign(MTrk[i] + 4);
			MTrk[i] = (UBYTE *)erealloc( MTrk[i], j + 100 * (debugs / 2 + 1), "MTrk[i]");
		}
		do_debug(&MTrk[0],trks,debugs,debug,syncs,sync);
	}

	makeMThd(&MThd[0],trks+1,minfo.Z/4);	/* MIDヘッダ作成 */
	/* printf( "\x0d\x1b[0KWriting Track = " ); */
#ifdef AAA
#ifndef UNIXY_OS
	fprintf( stderr,"\nWriting Track = " );
#endif
#endif

	if (finfo.outfile[0] == '-') {				/* use stdout */
#ifndef UNIXY_OS								/* set stdin to binary mode (MS-DOS only) */
//	#if defined (X68000) || defined (BCB) //↓に修正 by Kobarin
	#if defined (X68000) || defined (_WIN32) 
		#ifndef XCLIB
			setmode(fileno(stdout),O_BINARY);	/* for Win32/16, DOS, OS/2, X680x0(libc) */
		#else
			fmode(stdout, _IOBINARY);			/* for X68000(XC) */
		#endif
	#endif
#endif	/* UNIXY_OS */
		fp = stdout;
	} else {
		fp = fopen( finfo.outfile,"wb" );
		if (!fp) {
#ifndef ENGLISH
			fatal(1,"\nError: ファイルのオープンに失敗しました。\n");
#else
			fatal(1,"\nError: fail in opening file.\n");
#endif
		}
	}
	i = fwrite(MThd,sizeof(UBYTE),14,fp);
	if (i < 14) {
#ifndef ENGLISH
		fatal(1,"\nError: ファイルの書き込みに失敗しました。\n");
#else
		fatal(1,"\nError: fail in wrinting file.\n");
#endif
	}
	for (i = 0; i <= trks; i++) {			/* MIDデータ書き込み */

		/* int j, k = *(int *)(MTrk[i] + 4); */
		int j, k = getDwordAlign(MTrk[i] + 4);

#ifdef AAA
#ifndef UNIXY_OS
		fprintf( stderr,"%.2ld\x1b[2D",i );
#endif
#endif
		/* fflush(stdout); */
		j = fwrite(&MTrk[i][0],sizeof(UBYTE),k+8,fp);
		if (j < k + 8) {
#ifndef ENGLISH
			fatal(1,"\nError: ファイルの書き込みに失敗しました。\n");
#else
			fatal(1,"\nError: fail in wrinting file.\n");
#endif
		}
	}

#ifdef _0
		if ( fflush(fp) ) {
	#ifndef ENGLISH
			fatal(1,"\nError: ファイルのフラッシュに失敗しました。\n");
	#else
			fatal(1,"\nError: fail in flushing file.\n");
	#endif
		}

	#ifndef DOS68_TIMESTAMP
		utime(finfo.outfile,&timep);		/* タイムスタンプ変更 */
	#else
		FILEDATE(fileno(fp),timep);
	#endif
#endif

	fclose(fp);

#ifdef AAA
#ifdef UNIXY_OS
	fprintf(stderr,"Completed.\n");
#else
	fprintf(stderr,"\x0d\x1b[0KCompleted.\n");
#endif
#endif
	ZTOMFormatMessage(stderr,"Completed.\n");

	efree(debug, "debug");
	efree(tempo, "tempo");
	efree(sync, "sync");
	for (i = trks; i >= 0; i--) efree(MTrk[i], "MTrk[i]");
	efree(zbuf, "zbuf");
#ifdef XCLIB
	rstmem();
#endif

	efreeAll();
	
	return 0;
}


/* ================================
     ファイルフォーマットを調べる	返り値：ZmuSiCのVersion 若しくは -1
   ================================ */

int fmtchk(UBYTE *buf)
{
	int rc = -1;

	if (!strncmp(buf,"\x10ZmuSiC",7) || !strncmp(buf,"\x1AZmuSiC",7)) {	/* ヘッダの調査 */
		rc = buf[7];
	}
	return rc;
}



/* ================
     使用法の表示
   ================ */

void help(int exitcode)
{
	const char *helpmes[] = {	/* HKMNQVWYZ */

#ifndef ENGLISH
	#ifndef UNIXY_OS
		"使用法：ztom [-switches] [source[.ZMD] [-o destination[.MID]]] [-e errfile]",
	#else
		"使用法：ZtoM [-switches] [source[.zmd] [-o destination[.mid]]]",
	#endif
		"switch：-Lnum(def= L2  ) [do]〜[loop] 展開回数指定",
		"\t-Fnum(def= F10 )     〃       F.O.スピード指定",
		"\t-Dnum(def= D0  )     〃       F.O.ディレイ指定",
		"\t-Bnum(def= B1  ) オートベンドなどの変更間隔(clock)",
		"\t-Xnum(def= X0  ) 共通コマンド部のエクスクルーシブ送信ウエイト",
		"\t-U   (def= off ) テンポ値の計算に四捨五入を用いる(def=切り捨て)",
		"\t-A   (def= off ) TIMER-A 使用の ZMDファイルのコンバート",
		/* "\t-S   (def= off ) ランニングステータスを用いない", */
		/* "\t-O   (def= off ) 出力されるSMFの最適化を行う", */
		/* "\t-Pnum(def= P0  ) 16ch以上使用の場合のch指定法", */
		/* "  0: meta event FF21 1:SMF format1を2つ作成 2:SMF format2", */
		"\t-J   (def= off ) [do]〜[loop]の展開回数を最長トラックで判断する",
		/* "\t-M   (def= off ) OPMトラックをスキップしない", */
		"\t-R   (def= off ) @Rに応じてキーオフ処理を省略する",
		"\t-I   (def= off ) ドライバが送る初期化命令も埋め込む",
		"\t-    (def= off ) 標準入力を用いる",
		"\t-c   (def= off ) 標準出力を用いる",
		"\t-e filename      エラー出力をファイルにリダイレクトする",
		"\t-g filename      signatureファイルを指定する",
		"",
		"最新版は http://www2s.biglobe.ne.jp/~yyagi/ にて入手可能です.",
		"ピッチベンドが正しく変換されない場合、-i スイッチを用いて下さい.",
#ifdef X68000
		"ZMD作成時には zmusic -C file のように、大文字-Cを用いて下さい.",
		"V3データの変換には、ZMR.x(じんべさん作,お勧め)やz2m3.x(拙作)をどうぞ.",
#endif
		NULL
#else
	#ifndef UNIXY_OS
		"usage：ztom [-switches] [source[.ZMD] [-o destination[.MID]]]",
	#else
		"usage：ZtoM [-switches] [source[.zmd] [-o destination[.mid]]]",
	#endif
		"switch：-L#  (def= L2  ) [do]-[loop] expansion times",
		"\t-F#  (def= F10 ) [do]-[loop] F.O.speed",
		"\t-D#  (def= D0  ) [do]-[loop] F.O.delay",
		"\t-B#  (def= B1  ) steps for autobend/portament etc.(clock)",
		"\t-X#  (def= X0  ) deltatime for exclusive in common part",
		"\t-U   (def= off ) use rounding up to calculate tempo (def= cut off)",
		"\t-A   (def= off ) TIMER-A ZMD convert",
		/* "\t-S   (def= off ) no use of running status", */
		/* "\t-O   (def= off ) optimize SMF", */
		/* "\t-Pnum(def= P0  ) 16ch以上使用の場合のch指定法", */
		/* "  0: meta event FF21 1:SMF format1を2つ作成 2:SMF format2", */
		"\t-J   (def= off ) set loop times for [do]-[loop] automatically",
		/* "\t-M   (def= off ) don't skip OPM/ADPCM part", */
		"\t-R   (def= off ) omit NOTEOFF event with MML @R",
		"\t-I   (def= off ) set initial code",
		"\t-    (def= off ) use standard input",
		"\t-c   (def= off ) use standard output",
		"\t-e filename      redirect stderr to specified file",
		"\t-g filename      specify signature-file",
		"",
		"You can get the latest version at http://www2s.biglobe.ne.jp/~yyagi/",
		"If converted pitchbend is bad, use -i switch and try again.",
		NULL
#endif
	};
	char **str = (char**)helpmes;

	do {
		puts(*str++);
	} while (*str != NULL);

	terminateProgram(exitcode);
/* ryk ==> */
#ifdef ZTOM_STATIC_LIB	/* Static lib */
	longjmp(g_ztommark, exitcode);
#endif
/* <== ryk */
}
