/* UNITRK.C All routines dealing with the manipulation of UNITRK(tm) streams */ #include #include #include "munitrk.h" #define BUFPAGE 128 // smallest unibuffer size #define TRESHOLD 16 /* unibuffer is increased by BUFPAGE bytes when unipc reaches unimax-TRESHOLD */ /* Ok.. I'll try to explain the new internal module format.. so here it goes: The UNITRK(tm) Format: ====================== A UNITRK stream is an array of bytes representing a single track of a pattern. It's made up of 'repeat/length' bytes, opcodes and operands (sort of a assembly language): rrrlllll [REP/LEN][OPCODE][OPERAND][OPCODE][OPERAND] [REP/LEN][OPCODE][OPERAND].. ^ ^ ^ |-------ROWS 0 - 0+REP of a track---------| |-------ROWS xx - xx+REP of a track... The rep/len byte contains the number of bytes in the current row, _including_ the length byte itself (So the LENGTH byte of row 0 in the previous example would have a value of 5). This makes it easy to search through a stream for a particular row. A track is concluded by a 0-value length byte. The upper 3 bits of the rep/len byte contain the number of times -1 this row is repeated for this track. (so a value of 7 means this row is repeated 8 times) Opcodes can range from 1 to 255 but currently only opcodes 1 to 19 are being used. Each opcode can have a different number of operands. You can find the number of operands to a particular opcode by using the opcode as an index into the 'unioperands' table. */ UWORD unioperands[256]={ 0, // not used 1, // UNI_NOTE 1, // UNI_INSTRUMENT 1, // UNI_PTEFFECT0 1, // UNI_PTEFFECT1 1, // UNI_PTEFFECT2 1, // UNI_PTEFFECT3 1, // UNI_PTEFFECT4 1, // UNI_PTEFFECT5 1, // UNI_PTEFFECT6 1, // UNI_PTEFFECT7 1, // UNI_PTEFFECT8 1, // UNI_PTEFFECT9 1, // UNI_PTEFFECTA 1, // UNI_PTEFFECTB 1, // UNI_PTEFFECTC 1, // UNI_PTEFFECTD 1, // UNI_PTEFFECTE 1, // UNI_PTEFFECTF 1, // UNI_S3MEFFECTA 1, // UNI_S3MEFFECTD 1, // UNI_S3MEFFECTE 1, // UNI_S3MEFFECTF 1, // UNI_S3MEFFECTI 1, // UNI_S3MEFFECTQ 1, // UNI_S3MEFFECTT }; UBYTE *unibuf; // pointer to the temporary unitrk buffer UWORD unimax; // maximum number of bytes to be written to this buffer UWORD unipc; // index in the buffer where next opcode will be written UWORD unitt; // holds index of the rep/len byte of a row UWORD lastp; // holds index to the previous row (needed for compressing) UBYTE *rowstart; // startadress of a row UBYTE *rowend; // endaddress of a row (exclusive) UBYTE *rowpc; // current unimod(tm) programcounter void UniSetRow(UBYTE *t) { rowstart=t; rowpc=rowstart; rowend=rowstart+(*(rowpc++)&0x1f); } UBYTE UniGetByte(void) { return (rowpc end of track.. l=(c>>5)+1; // extract repeat value if(l>row) break; // reached wanted row? -> return pointer row-=l; // havn't reached row yet.. update row t+=c&0x1f; // point t to the next row } return t; } void UniReset(void) /* Resets index-pointers to create a new track. */ { unitt=0; // reset index to rep/len byte unipc=1; // first opcode will be written to index 1 lastp=0; // no previous row yet unibuf[0]=0; // clear rep/len byte } void UniWrite(UBYTE data) /* Appends one byte of data to the current row of a track. */ { // write byte to current position and update unibuf[unipc++]=data; // Check if we've reached the end of the buffer if(unipc>(unimax-TRESHOLD)){ UBYTE *newbuf; /* We've reached the end of the buffer, so expand the buffer by BUFPAGE bytes */ newbuf=realloc(unibuf,unimax+BUFPAGE); // Check if realloc succeeded if(newbuf!=NULL){ unibuf=newbuf; unimax+=BUFPAGE; } else{ /* realloc failed, so decrease unipc so we won't write beyond the end of the buffer.. I don't report the out-of-memory here; the UniDup() will fail anyway so that's where the loader sees that something went wrong */ unipc--; } } } void UniInstrument(UBYTE ins) /* Appends UNI_INSTRUMENT opcode to the unitrk stream. */ { UniWrite(UNI_INSTRUMENT); UniWrite(ins); } void UniNote(UBYTE note) /* Appends UNI_NOTE opcode to the unitrk stream. */ { UniWrite(UNI_NOTE); UniWrite(note); } void UniPTEffect(UBYTE eff,UBYTE dat) /* Appends UNI_PTEFFECTX opcode to the unitrk stream. */ { if(eff!=0 || dat!=0){ // don't write empty effect UniWrite(UNI_PTEFFECT0+eff); UniWrite(dat); } } BOOL MyCmp(UBYTE *a,UBYTE *b,UWORD l) { UWORD t; for(t=0;t>5)+1; // repeat of previous row l=(unibuf[lastp]&0x1f); // length of previous row len=unipc-unitt; // length of current row /* Now, check if the previous and the current row are identical.. when they are, just increase the repeat field of the previous row */ if(n<8 && len==l && MyCmp(&unibuf[lastp+1],&unibuf[unitt+1],len-1)){ unibuf[lastp]+=0x20; unipc=unitt+1; } else{ // current and previous row aren't equal.. so just update the pointers unibuf[unitt]=len; lastp=unitt; unitt=unipc; unipc++; } } UBYTE *UniDup(void) /* Terminates the current unitrk stream and returns a pointer to a copy of the stream. */ { UBYTE *d; unibuf[unitt]=0; if((d=malloc(unipc))==NULL){ myerr=ERROR_ALLOC_STRUCT; return NULL; } memcpy(d,unibuf,unipc); return d; } UWORD TrkLen(UBYTE *t) /* Determines the length (in rows) of a unitrk stream 't' */ { UWORD len=0; UBYTE c; while(c=*t&0x1f){ len+=c; t+=c; } len++; return len; } BOOL UniInit(void) { unimax=BUFPAGE; if(!(unibuf=malloc(unimax))){ myerr=ERROR_ALLOC_STRUCT; return 0; } return 1; } void UniCleanup(void) { if(unibuf!=NULL) free(unibuf); unibuf=NULL; }