/**************************************/
/* cseq2midi - CSEQ Conversion Tool   */
/* Copyright (C) 2010-11, Ruben Nunez */
/* Modified by soneek for CSEQ		  */
/**************************************/
/* Changelog:                         */
/*   11-11-01                         */
/*     Revamped code.                 */
/*     Fixed up delta timing issue.   */
/*     Magic ID -> big endian.        */
/*     Enhanced readability.          */
/*     Slight optimizations.          */
/*   10-07-08                         */
/*     Original version.              */
/**************************************/
/* Notes:                             */
/*   Command list:                    */
/*     80 - Wait [var. length arg]    */
/*     81 - Program [1-3 args]        */
/*     88 - Split [track, offset]     */
/*     89 - Jump [offset]             */
/*     8A - Call [offset]             */
/*     B0 - Unknown                   */
/*     C0 - Pan [0.127]               */
/*     C1 - Volume [0.127]            */
/*     C2 - Master volume [0.127]     */
/*     C3 - Transpose [-128.+127]     */
/*     C4 - Bend [-128.+127]          */
/*     C5 - Bend range [0.127]        */
/*     C6 - Priority [0.?]            */
/*     C7 - Polyphony [?]             */
/*     C8 - Tie ???                   */
/*     C9 - Portamento control [?]    */
/*     CA - Mod depth [0.127]         */
/*     CB - Mod speed [0.127]         */
/*     CC - Mod type [?]              */
/*     CD - Mod range [?]             */
/*     CE - Portamento [?]            */
/*     CF - Portamento time [?]       */
/*     D0 - Attack [0.127]            */
/*     D1 - Decay [0.127]             */
/*     D2 - Sustain [0.127]           */
/*     D3 - Release [0.127]           */
/*     D4 - Loop start [marker?]      */
/*     D5 - Expression [0.127]        */
/*     D6 - Print???                  */
/*     D8 - ???                       */
/*     D9 - ???                       */
/*     DA - ???                       */
/*     DB - ???                       */
/*     E0 - Mod delay [?]             */
/*     E1 - Tempo [0.65535]           */
/*     E3 - Sweep?                    */
/*     FC - Loop end [marker?]        */
/*     FD - Return                    */
/*     FE - Track usage [16-bit]      */
/*     FF - Fine                      */
/**************************************/
#define DEBUG
/**************************************/
#define CHNK_HAVE_DATA (0x01)
#define CHNK_HAVE_LABL (0x02)
#define CHNK_NEEDED    (CHNK_HAVE_DATA)
/**************************************/
#define BOOL_EQUAL(x,y) (((x)&(y)) == (y))
/**************************************/
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <vector>
/**************************************/
using namespace std;
/**************************************/

typedef   signed char      s8 ;
typedef unsigned char      u8 ;
typedef   signed short     s16;
typedef unsigned short     u16;
typedef   signed int       s32;
typedef unsigned int       u32;

/**************************************/

typedef struct {
	u32 key; //! key
	u32 pos; //! end [tick]
} Note_t;

/**************************************/

static inline u32 Swap32(u32 x) {
	return (x>>24) | 
	       ((x<<8) & 0x00FF0000) |
	       ((x>>8) & 0x0000FF00) |
	       (x<<24);
}

static inline u16 Swap16(u16 x) {
	return (x>>8) | (x<<8);
}

/**************************************/

static inline u32 ReadLE(FILE *f, u32 b) {
	u32 v = 0;
	for(u32 i=0;i<b;i+=8) v |= fgetc(f) << i;
	return v;
}

static inline u32 ReadBE(FILE *f, s32 b) {
	u32 v = 0;
	for(s32 i=b-8;i>=0;i-=8) v |= fgetc(f) << i;
	return v;
}

/**************************************/

static inline u32 ReadVarLen(FILE *f) {
	u32 t = 0;
	while(1) {
		u32 c = fgetc(f);
		t = (t<<7) | (c&127);
		
		if((c&0x80) == 0) break;
	} return t;
}

/**************************************/

static inline void DebugMsg(const char *str, ...) {
#ifdef DEBUG
	static FILE *dstF = fopen("cseq2midi.log.txt", "wt");
	
	fseek(dstF, 0, SEEK_END);
	va_list myList;
	va_start(myList, str);
	vfprintf(dstF, str, myList);
	va_end(myList);
#endif
}

/**************************************/

//! compare function for sorting note order
static int NoteSortCmp(const void *a, const void *b) {
	return ((Note_t*)a)->pos - ((Note_t*)b)->pos;
}

/**************************************/

typedef struct {
	u8             gIndx; //! self-index
	u8             gStat; //! on/off
	s8             gTrns; //! transpose
	u8             gRPNR; //! RPNs ready
	u32            gWait; //! waiting left
	u32            gDPos; //! data position [offset]
	u32            gGPos; //! global position [tick]
	u32            gRPos; //! return position [offset]
	vector<Note_t> gNote; //! notes
	vector<u8>     gData; //! midi data
	
	//! reset track
	void Reset(u32 idx) {
		gIndx = idx;
		gStat = 0;
		gTrns = 0;
		gRPNR = 0;
		gDPos = 0;
		gGPos = 0;
		gRPos = 0;
		gNote.clear();
		gData.clear();
	}
	
	//! start track
	void Start(u32 adr) {
		//! init struct data
		gStat = 1;
		gTrns = 0;
		gDPos = adr;
		gGPos = 0;
		gRPos = 0;
		gData.clear();
		gNote.clear();
		
		//! debug stuff
		DebugMsg("  Trk %02u started from 0x%X...\n", gIndx, adr);
	}
	
	//! write midi-style delta
	void PushDelta(u32 t) {
		s32 c = 0;
		u32 n = t;
		
		//! count bytes required
		while(n > 127) {
			c++;
			n >>= 7;
		}
		
		//! write individual data
		for(int i=c;i>=0;i--) {
			//! calculate value
			u32 v = (t>>(7*i)) & 127;
			if(i) v |= 0x80;
			
			//! push data to seq
			gData.push_back(v);
		}
	}
	
	//! process delta
	void ProcDelta(void) {
		PushDelta(gWait);
		gWait = 0;
	}
	
	//! write event to seq
	void Event(u32 ev, u32 argc, u8 *argv) {
		//! process delta
		ProcDelta();
		
		//! push back command + arguments
		gData.push_back(ev|gIndx);
		while(argc--) gData.push_back(*argv++);
	}
	
	//! note-on
	void mNoteOn(u32 key, u32 vel, u32 time) {
		//! write data
		Event(0x90, 2, (u8[]) {key, vel});
		
		//! push note into stack
		gNote.push_back((Note_t) {key, gGPos + time});
	}
	
	//! set volume
	void mVol(u32 vol) {
		//! write data
		Event(0xB0, 2, (u8[]) {0x07, vol});
	}
	
	//! set panning
	void mPan(u32 pan) {
		//! write data
		Event(0xB0, 2, (u8[]) {0x0A, pan});
	}
	
	//! set expression
	void mExp(u32 exp) {
		//! write data
		Event(0xB0, 2, (u8[]) {0x0B, exp});
	}
	
	//! set program
	void mPrg(u32 prg) {
		//! write data
		Event(0xC0, 1, (u8[]) {prg});
	}
	
	//! set bend amount
	void mBnd(u32 bnd) {
		//! scale 
		u32 n = bnd*16384/256;
		
		//! write data
		Event(0xE0, 2, (u8[]) {n&127, n>>7});
	}
	
	//! set bend range
	void mBndRng(u32 rng) {
		//! RPNs ready?
		if(!gRPNR) {
			//! no, write them
			gRPNR = 1;
			Event(0xB0, 2, (u8[]) {0x64, 0}); //! low
			Event(0xB0, 2, (u8[]) {0x65, 0}); //! high
		}
		
		//! write data
		Event(0xB0, 2, (u8[]) {0x06, rng});
	}
	
	//! set tempo
	void mTmp(u32 tmp) {
		//! calculate ms per quarter note
		u32 n = 60000000 / tmp;
		
		//! write data
		Event(0xFF, 5, (u8[]) {0x51, 3, n>>16, n>>8, n>>0});
	}
	
	//! kill track
	void mEnd(void) {
		//! send kill command
		Event(0xFF, 2, (u8[]) {0x2F,0});
		
		//! turn off
		gStat = 0;
	}
	
	//! wait n ticks
	void Wait(u32 timeLeft) {
		//! sort notes
		if(u32 len = gNote.size()) qsort(&gNote[0], len, sizeof(Note_t), NoteSortCmp);
		
		//! final position after this
		u32 pPos = gGPos + timeLeft;
		
		//! process notes from front
		for(u32 i=0;i<gNote.size();i++) {
			//! fetch pointer to note
			Note_t &note = gNote[i];
			
			//! doesn't end this tick?
			if(note.pos > pPos) continue;
			
			//! take time until note ends
			u32 dif = note.pos - gGPos;
			
			//! push a delta that long
			PushDelta(dif);
			
			//! push note-off
			gData.push_back(0x90|gIndx);
			gData.push_back(note.key  );
			gData.push_back(0         );
			
			//! destroy note
			gNote.erase(gNote.begin() + i);
			i--;
			
			//! set new position
			gGPos    += dif;
			timeLeft -= dif;
		}
		
		//! set new position
		gGPos += timeLeft;
		gWait += timeLeft;
	}
} Track_t;

/**************************************/

typedef struct {
	u32 id;     //! chunk ID
	u32 magic;  //! magic [0xFEFF0100]
	u32 size;   //! chunk length
	u32 typen;  //! nothing
	u32 cplace;  //! nothing
	u32 cSize;  //! chunk header size
	u32 cBlock; //! chunk blocks
	u32 reserved[4];
} cseqHead_t;

/**************************************/

typedef struct {
	u32 id;     //! chunk ID
	u32 size;   //! chunk length
	u32 offset; //! seq data offset
	
	u32 fOff;   //! first track offset
} DATAHead_t;

/**************************************/

static struct {
	//! state
	u32 gStat;
	
	//! cseq chunk header
	cseqHead_t gcseqHead;
	
	//! DATA chunk header
	DATAHead_t gDATAHead;
	
	//! cseq tracks
	Track_t gTrack[16];
	
	//! reset all
	void Reset(void) {
		//! clear state
		gStat = 0;
		
		//! clear chunk headers
		memset(&gcseqHead, 0, sizeof(gcseqHead));
		memset(&gDATAHead, 0, sizeof(gDATAHead));
		
		//! reset tracks
		for(int i=0;i<16;i++) gTrack[i].Reset(i);
	}
} gData;

/**************************************/

void cseqDo(FILE *midi, FILE *cseq) {
	u32 mdOff = gData.gDATAHead.fOff;
	
	//! debug
	DebugMsg("  Begin decoding...\n");
	
	//! start track 0
	gData.gTrack[0].Start(mdOff);
	
	//! process while there's tracks
	//! this setup is needed 'just in case'
	//! as tracks can spawn from *any* track
	bool gTrkCnt = true;
	while(gTrkCnt) {
		//! flip process flag
		gTrkCnt = false;
		
		//! process each track
		for(u32 i=0;i<16;i++) {
			//! verify track is active
			Track_t *trk = &gData.gTrack[i];
			if(!trk->gStat) continue;
			
			//! continue the main loop as we have a track
			gTrkCnt = true;
			
			//! seek to current track position
			fseek(cseq, trk->gDPos, SEEK_SET);
			
			//! loop until end of track
			bool loop = true;
			while(loop) {
				//! note on [implicit command]
				u32 cmd = fgetc(cseq);
				if(cmd < 0x80) {
					//! read data
					u32 key = cmd;
					u32 vel = fgetc(cseq);
					u32 len = ReadVarLen(cseq);
					
					//! push note-on
					trk->mNoteOn(key, vel, len);
					
					//! continue loop
					continue;
				}
				
				//! switch command
				switch(cmd) {
					//! rest
					case 0x80: {
						//! read time
						u32 len = ReadVarLen(cseq);
						trk->Wait(len);
					} break;
					
					//! program:bank
					case 0x81: {
						//! fetch tone
						u32 c = fgetc(cseq);
						trk->mPrg(c&127);
						
						//! read/skip bank select command if needed
						if(c&0x80) c = fgetc(cseq);
						if(c&0x80) c = fgetc(cseq);
					} break;
					
					//! split
					case 0x88: {
						//! fetch info
						u32 trk = ReadBE(cseq,  8);
						u32 adr = ReadBE(cseq, 24) + mdOff;
						
						//! start new track
						gData.gTrack[trk].Start(adr);
					} break;
					
					//! jump
					case 0x89: {
						//! fetch address
						u32 adr = ReadBE(cseq, 24) + mdOff;
						
						//! debug stuff
						DebugMsg("  Trk %02u: Jump to 0x%X\n", i, adr);
						
						//! kill track, stop read loop
						trk->mEnd();
						loop = false;
					} break;
					
					//! call
					case 0x8A: {
						//! fetch targe address
						u32 adr = mdOff + ReadBE(cseq, 24);
						
						//! set return address
						trk->gRPos = ftell(cseq);
						
						//! debug stuff
						DebugMsg("  Trk %02u: Call to 0x%X\n", i, adr);
						
						//! jump to + set new address
						fseek(cseq, trk->gDPos = adr, SEEK_SET);
					} break;
					
					//! unknown - 1 byte?
					case 0xB0: {
						//! skip argument
						fseek(cseq, 1, SEEK_CUR);
					} break;
					
					//! pan
					case 0xC0: {
						//! set pan
						trk->mPan(fgetc(cseq));
					} break;
					
					//! volume
					case 0xC1: {
						//! set volume
						trk->mVol(fgetc(cseq));
					} break;
					
					//! master vol
					case 0xC2: {
						//! just read argument
						//! unsure on how to handle this, tbh
						//! maybe vol*mvol/127 on every call?
						fgetc(cseq);
					} break;
					
					//! transpose
					case 0xC3: {
						//! step amount
						//trk->mTranspose(fgetc(cseq));
						fgetc(cseq);
					} break;
					
					//! bend
					case 0xC4: {
						//! bend
						trk->mBnd(fgetc(cseq));
					} break;
					
					//! bend range
					case 0xC5: {
						//! bend range
						trk->mBndRng(fgetc(cseq));
					} break;
					
					//! priority
					case 0xC6: {
						//! just read argument
						//! AFAIK, has no meaning in midi
						fgetc(cseq);
					} break;
					
					//! polyphony
					case 0xC7: {
						//! not bothering with this
						fgetc(cseq);
					} break;
					
					//! tie ???
					case 0xC8: {
						fgetc(cseq);
					} break;
					
					//! portamento cnt
					case 0xC9: {
						//! not bothering with this
						fgetc(cseq);
					} break;
					
					//! mod-depth
					case 0xCA: {
						//! not bothering with this
						fgetc(cseq);
					} break;
					
					//! mod-speed
					case 0xCB: {
						//! not bothering with this
						fgetc(cseq);
					} break;
					
					//! mod-type
					case 0xCC: {
						//! not bothering with this
						fgetc(cseq);
					} break;
					
					//! mod-range
					case 0xCD: {
						//! not bothering with this
						fgetc(cseq);
					} break;
					
					//! portamento
					case 0xCE: {
						//! not bothering with this
						fgetc(cseq);
					} break;
					
					//! portamento-time
					case 0xCF: {
						//! not bothering with this
						fgetc(cseq);
					} break;
					
					case 0xD0: /* attack  */
					case 0xD1: /* decay   */
					case 0xD2: /* sustain */
					case 0xD3: /* release */ {
						//! not bothering with this
						fgetc(cseq);
					} break;
					
					//! loop start
					case 0xD4: {
						//! just a marker command AFAIK
						//! no arguments
					} break;
					
					//! expression
					case 0xD5: {
						//! set expression
						trk->mExp(fgetc(cseq));
					} break;
					
					//! print?
					case 0xD6: {
						//! yeah, no idea =P
						fgetc(cseq);
					} break;
					
					//! unknown commands - 1 argument
					case 0xD8:
					case 0xD9:
					case 0xDA:
					case 0xDB: {
						//! skip arg
						fgetc(cseq);
					} break;
					
					//! mod-delay
					case 0xE0: {
						//! not bothering with this
						ReadBE(cseq, 16);
					} break;
					
					//! tempo
					case 0xE1: {
						//! set tempo
						trk->mTmp(ReadBE(cseq, 16));
					} break;
					
					//! sweep?
					case 0xE3: {
						//! not bothering with this
						ReadBE(cseq, 16);
					} break;
					
					//! loop-end
					case 0xFC: {
						//! mark AFAIK
						//! no args
					} break;
					
					//! return
					case 0xFD: {
						//! has return adr?
						if(trk->gRPos) {
							//! seek back
							fseek(cseq, trk->gDPos = trk->gRPos, SEEK_SET);
							
							//! clear old return adr
							trk->gRPos = 0;
						}
					} break;
					
					//! track usage
					case 0xFE: {
						//! has no meaning in midi AFAIK
						//! one bit per track used
						ReadBE(cseq, 16);
					} break;
					
					//! end of track
					case 0xFF: {
						//! kil trck, stop read loop
						trk->mEnd();
						loop = false;
					} break;
					
					//! O_O
					default: {
						DebugMsg("  WARNING: Unknown command %02X\n", cmd);
					} break;
				}
			}
			
			//! done \o/
			printf("  Track %02u OK\n", i);
			DebugMsg("  Trk %02u OK\n", i);
		}
	}
	
	//! write midi. yay.
	u32 trkMax = 0;
	for(int i=0;i<16;i++) if(gData.gTrack[i].gData.size()) trkMax++;
	
	//! MThd header
	//! 96-tick per quarter-note resolution
	struct {
		u32 id;
		u32 size;
		u16 format;
		u16 tracks;
		u16 time;
	} midiHeader = {
		Swap32(0x4D546864),
		Swap32(6),
		Swap16(1),
		Swap16(trkMax),
		Swap16(96),
	}; fwrite(&midiHeader, 14, 1, midi);
	
	//! process each track
	for(int i=0;i<16;i++) {
		//! have data?
		u32 len = gData.gTrack[i].gData.size();
		if(!len) continue;
		
		//! yop, create MTrk struct
		struct {
			u32 id;
			u32 len;
		} trkHead = {
			Swap32(0x4D54726B),
			Swap32(len),
		}; fwrite(&trkHead, 8, 1, midi);
		
		//! dump all data
		for(u32 j=0;j<len;j++) fputc(gData.gTrack[i].gData[j], midi);
	} fclose(midi);
}
/**************************************/

void cseqProc(const char *filename, FILE *cseq) {
	u32 tPos;
	cseqHead_t &rcnk = gData.gcseqHead;
	DATAHead_t &dcnk = gData.gDATAHead;
	
	//! reset data
	gData.Reset();
	DebugMsg("  State reset successfully\n");
	
	//! write out debug message - position in code
	DebugMsg("  Attempting to read cseq chunk...\n");
	
	/* read cseq chunk */ {
		//! save position + read header
		tPos        = ftell(cseq);
		rcnk.id     = ReadBE(cseq, 32);
		rcnk.magic  = ReadBE(cseq, 32);
		rcnk.typen  = ReadLE(cseq, 32);
		rcnk.size   = ReadLE(cseq, 32);
		rcnk.cBlock = 4;
		rcnk.cSize  = 64;
		
		
		//! validate
		if(rcnk.id != 0x43534551 || rcnk.magic != 0xFFFE4000) {
			//! failed
			printf("Invalid CSEQ file (bad CSEQ chunk)\n");
			DebugMsg(
				"  Bad cseq chunk\n"
				"    Chunk ID          = 0x%08X\n"
				"    Chunk Magic       = 0x%08X\n"
				"    Chunk size        = %u bytes\n"
				"    Chunk header size = %u bytes\n"
				"    Chunk block count = %u blocks\n",
				rcnk.id,
				rcnk.magic,
				rcnk.size,
				rcnk.cSize,
				rcnk.cBlock
			);
			return;
		}
		
		//! skip header
		fseek(cseq, tPos + rcnk.cSize, SEEK_SET);
	}
	
	//! print off debug message
	DebugMsg(
		"  CSEQ chunk OK\n"
		"    Chunk ID          = 0x%08X\n"
		"    Chunk Magic       = 0x%08X\n"
		"    Chunk size        = %u bytes\n"
		"    Chunk header size = %u bytes\n"
		"    Chunk block count = %u blocks\n",
		rcnk.id,
		rcnk.magic,
		rcnk.size,
		rcnk.cSize,
		rcnk.cBlock
	);
	
	//! process other chunks
	u32 ckLen = 0; //! just to shut GCC up
	for(u32 i=0;i<rcnk.cBlock;i++) {
		//! save position
		
		tPos = ftell(cseq);
		//! read ID
		u32 id = ReadLE(cseq, 32);
		
		//! data chunk?
		if(!memcmp(&id, "DATA", 4)) {
			//! flag up data chunk
			gData.gStat |= CHNK_HAVE_DATA;
			
			//! DATA chunk
			dcnk.id     = id;
			dcnk.size   = ckLen = ReadLE(cseq, 32);
			dcnk.offset = 10;
			dcnk.fOff   = tPos + dcnk.offset;
			
			//! debug stuff
			DebugMsg(
				"  Have DATA chunk\n"
				"    Chunk ID     = 0x%08X\n"
				"    Chunk size   = %u bytes\n"
				"    Chunk offset = %u bytes (relative)\n"
				"    Seq. offset  = %u bytes (absolute)\n",
				dcnk.id,
				dcnk.size,
				dcnk.offset,
				dcnk.fOff
			);
		}
		
		//! label chunk?
		else if(!memcmp(&id, "LABL", 4)) {
			//! flag up label chunk
			gData.gStat |= CHNK_HAVE_LABL;
			
			//! LABL chunk
			//! ???
			
			//! debug stuff
			DebugMsg("  Have LABL chunk\n");
		}
		
		//! skip chunk
		fseek(cseq, tPos + ckLen, SEEK_SET);
	}
	
	//! can be decoded?
	if(!BOOL_EQUAL(gData.gStat, CHNK_NEEDED)) {
		//! fail - not enough data to decode
		printf("Not enough data to decode with\n");
		DebugMsg("  Insufficient data (exit code 0x%02X, needed 0x%02X)\n", gData.gStat, CHNK_NEEDED);
		return;
	}
	
	//! create target MIDI filename
	char *newFN = new char[strlen(filename)+1]; strcpy(newFN, filename);
	if(char *p = strrchr(newFN, '.')) strcpy(p, ".mid");
	else strcat(newFN, ".mid");
	DebugMsg("  Writing to %s\n", newFN);
	
	//! create target MIDI file
	FILE *midi = fopen(newFN, "wb"); delete newFN;
	if(!midi) {
		//! failed to open target
		printf("  Cannot open output Midi file\n");
		DebugMsg("  Can't open target\n");
		delete newFN;
		return;
	}
	
	//! start processing
	cseqDo(midi, cseq);
}

/**************************************/

int main(int argc, char *argv[]) {
	//! need at least two args
	if(argc < 2) {
		//! print msg
		printf(
			"cseq2midi\n"
			"Usage: cseq2midi file1.cseq [file2.cseq [file3.cseq [...]]]\n"
		);
		
		//! failed
		return 1;
	}
	
	//! read every arg
	for(int i=1;i<argc;i++) {
		//! print to console + debug
		printf("%s:\n", argv[i]);
		DebugMsg("%s:\n", argv[i]);
		
		//! open file
		FILE *cseq = fopen(argv[i], "rb");
		if(!cseq) {
			//! can't open - skip
			printf("  Couldn't open file\n");
			DebugMsg("  Failed\n");
			continue;
		}
		
		//! process file
		cseqProc(argv[i], cseq);
		
		//! close file
		fclose(cseq);
	}
	
	return 0;
}

/**************************************/
/* EOF                                */
/**************************************/
