MYM is a file format for ripped PSG tunes, created by Marq/Lieves!Tuore & Fit (marq@iki.fi)
Players
2 players exist for the Amstrad CPC: one by Andy Cadley and another one by Morpheus.
They can be downloaded at http://www.kameli.net/lt/prod.htm
File format
I have not been able to find a formal description of this file format.
The best we have is the source code of the original converter from YM to MYM files and vice versa.
ym2mym.c
/* ym2mym.c Converts _unpacked_ YM tunes to a format better suitable for MSX1. Supports YM2 and YM3 types well plus YM5 somehow. 30.1.2000 Marq/Lieves!Tuore & Fit (marq@iki.fi) 3.2.2000 - Added a rude YM5 loader. Skips most of the header. Output format: Rows in the tune, 16 bits (lobyte first) For each register, 0 - fragment contains only unchanged data 1 - fragment contains packed data In a packed fragment, 0 - register value is the same as before 11 - raw register data follows. Only regbits[i] bits, not full 8 10 - offset + number of bytes from preceding data. As many bits as are required to hold fragment offset & counter data (OFFNUM). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #define REGS 14 #define FRAG 128 /* Nuber of rows to compress at a time */ #define OFFNUM 14 /* Bits needed to store off+num of FRAG */ void writebits(unsigned data,int bits,FILE *f); int main(int argc,char *argv[]) { unsigned char *data[REGS]; /* The unpacked YM data */ unsigned current[REGS]; char id[5]={0,0,0,0,0},ym_new=0, LeOnArD[8]; /* Check string */ FILE *f; long n,i,row,change,pack,biggest=0,hits,oldrow,remain,offi, regbits[REGS]={8,4,8,4, 8,4,5,8, 5,5,5,8, 8,8}, /* Bits per PSG reg */ /* AND values to mask out extra bits from register data */ regand[REGS]={255,15,255,15, 255,15,31,255, 31,31,31,255, 255,255}; unsigned long length, /* Song length */ ldata; /* Needed in the loader */ if(argc!=3) { printf("Usage: ym2mym source.ym destination.mym\n"); printf("Raw YM files only. Uncompress with LHA.\n"); return(EXIT_FAILURE); } if((f=fopen(argv[1],"rb"))==NULL) { printf("File open error.\n"); return(EXIT_FAILURE); } fseek(f,0,SEEK_END); length=ftell(f)-4; fseek(f,0,SEEK_SET); fread(id,1,4,f); if(strcmp(id,"YM2!")) /* YM2 is ok */ if(strcmp(id,"YM3!")) /* YM3 is ok */ if(strcmp(id,"YM3b")) /* YM3b is ok */ { if(!strcmp(id,"YM5!")) /* YM5 is ok but needs a */ ym_new=1; /* different loader */ else { printf("Unknown file format.\n"); exit(EXIT_FAILURE); } } else { fread(id,1,4,f); /* Skip restart for YM3b */ length-=4; } if(ym_new) /* New YM5 format loader */ { fread(LeOnArD,1,8,f); /* Skip checkstring */ for(n=length=0;n<4;n++) /* Number of VBL's */ { length<<=8; length+=fgetc(f); } length*=REGS; fread(&ldata,1,3,f); /* Skip first 3 bytes of info */ if(!(fgetc(f)&1)) { printf("Only interleaved data supported.\n"); return(EXIT_FAILURE); } if(fgetc(f) || fgetc(f)) /* Number of digidrums */ { printf("Digidrums not supported.\n"); return(EXIT_FAILURE); } fread(&ldata,1,4,f); /* Skip external freq */ fread(&ldata,1,2,f); /* Skip VBL freq */ fread(&ldata,1,4,f); /* Skip loop position */ fread(&ldata,1,2,f); /* Skip additional data */ while(fgetc(f)) /* Skip song name */ ; while(fgetc(f)) /* Skip author name */ ; while(fgetc(f)) /* Skip comments */ ; } /* Old YM2/YM3 format loader */ for(n=0;n<REGS;n++) /* Allocate memory & read data */ { /* Allocate extra fragment to make packing easier */ if((data[n]=malloc(length/REGS+FRAG))==NULL) { printf("Out of memory.\n"); return(EXIT_FAILURE); } memset(data[n],0,length/REGS+FRAG); fread(data[n],1,length/REGS,f); } if(ym_new) /* Let's mask the extra YM5 data out */ { for(n=0;n<REGS;n++) for(row=0;row<length/REGS;row++) data[n][row]&=regand[n]; } fclose(f); if((f=fopen(argv[2],"wb"))==NULL) { printf("Cannot open destination file.\n"); return(EXIT_FAILURE); } for(n=0;n<REGS;n++) /* Set current values to impossible */ current[n]=0xffff; fputc(length/REGS&0xff,f); /* Write tune length */ fputc(length/REGS>>8,f); for(n=0;n<length/REGS;n+=FRAG) /* Go through fragments... */ { for(i=0;i<REGS;i++) /* ... for each register */ { for(row=change=0;row<FRAG;row++) if(data[i][n+row]!=current[i]) change=1; if(!change) /* No changes in the whole fragment */ { writebits(0,1,f); continue; /* Skip the next pass */ } else writebits(1,1,f); for(row=0;row<FRAG;row++) { if(data[i][n+row]!=current[i]) { change=1; current[i]=data[i][n+row]; biggest=0; if(n) /* Skip first fragment */ { offi=0; remain=FRAG-row; /* Go through the preceding data and try to find similar data */ for(oldrow=0;oldrow<FRAG;oldrow++) { hits=0; for(pack=0;pack<remain;pack++) { if(data[i][n+row+pack]==data[i][n-FRAG+row+oldrow+pack] && oldrow+pack<FRAG) hits++; else break; } if(hits>biggest) /* Bigger sequence found */ { biggest=hits; offi=oldrow; } } } if(biggest>1) /* Could we pack data? */ { row+=biggest-1; current[i]=data[i][n+row]; writebits(2,2,f); writebits((offi<<OFFNUM/2)+(biggest-1),OFFNUM,f); } else /* Nope, write raw bits */ { writebits(3,2,f); writebits(data[i][n+row],regbits[i],f); } } else /* Same as former value, write 0 */ writebits(0,1,f); } } } writebits(0,0,f); /* Pad to byte size */ fclose(f); return(EXIT_SUCCESS); } /* Writes bits to a file. If bits is 0, pads to byte size. */ void writebits(unsigned data,int bits,FILE *f) { static unsigned char byte=0; static int off=0; int n; if(!bits && off) { off=byte=0; fputc(byte,f); return; } /* Go through the bits and write a whole byte if needed */ for(n=0;n<bits;n++) { if(data&(1<<bits-1-n)) byte|=0x80>>off; if(++off==8) { fputc(byte,f); off=byte=0; } } } /* EOS */
mym2ym.c
/* mym2ym.c Converts MYM files back to upacked YM3. 31.1.2000 Marq/Lieves!Tuore & Fit (marq@iki.fi) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #define REGS 14 #define FRAG 128 /* Nuber of rows to compress at a time */ #define OFFNUM 14 /* Bits needed to store off+num of FRAG */ unsigned readbits(int bits,FILE *f); int main(int argc,char *argv[]) { unsigned char *data[REGS], /* The unpacked YM data */ c; unsigned current[REGS]; FILE *f; long n,i,row,index,compoff,compnum, bytes=0, regbits[REGS]={8,4,8,4, 8,4,5,8, 5,5,5,8, 8,8}; /* Bits per PSG reg */ unsigned long rows; if(argc!=3) { printf("Usage: mym2ym source.mym destination.ym\n"); printf("Raw YM files only. Compress with LHA.\n"); return(EXIT_FAILURE); } if((f=fopen(argv[1],"rb"))==NULL) { printf("File open error.\n"); return(EXIT_FAILURE); } rows=fgetc(f); /* Read the number of rows */ rows+=fgetc(f)<<8u; for(n=0;n<REGS;n++) /* Allocate memory for rows */ { if((data[n]=malloc(rows+FRAG))==NULL) { printf("Out of memory.\n"); exit(EXIT_FAILURE); } } for(n=0;n<rows;n+=FRAG) /* Go through rows... */ { for(i=0;i<REGS;i++) /* ... and registers */ { index=0; if(!readbits(1,f)) /* Totally unchanged fragment */ { for(row=0;row<FRAG;row++) data[i][n+row]=current[i]; continue; } while(index<FRAG) /* Packed fragment */ { if(!readbits(1,f)) /* Unchanged register */ { data[i][n+index]=current[i]; index++; } else { if(readbits(1,f)) /* Raw data */ { c=readbits(regbits[i],f); current[i]=data[i][n+index]=c; index++; } else /* Reference to previous data */ { compoff=readbits(OFFNUM/2,f)+index; compnum=readbits(OFFNUM/2,f)+1; for(row=0;row<compnum;row++) { c=data[i][n-FRAG+compoff+row]; data[i][n+index]=current[i]=c; index++; } } } } } } fclose(f); if((f=fopen(argv[2],"wb"))==NULL) { printf("Cannot open destination file.\n"); return(EXIT_FAILURE); } /* Write uncompressed data to YM3 format */ fwrite("YM2!",1,4,f); for(n=0;n<REGS;n++) fwrite(data[n],1,rows,f); fclose(f); return(EXIT_SUCCESS); } /* Reads bits from a while */ unsigned readbits(int bits,FILE *f) { static unsigned char byte; static int off=7; unsigned n,data=0; /* Go through the bits and read a whole byte if needed */ for(n=0;n<bits;n++) { data<<=1; if(++off==8) { byte=fgetc(f); off=0; } if(byte&(0x80>>off)) data++; } return(data); } /* EOS */