Last modified on 19 November 2024, at 00:41

MYM

MYM is a file format for ripped PSG tunes, created by Marq/Lieves!Tuore & Fit (marq@iki.fi)

Players

2 MYM players exist for the Amstrad CPC: MYMPlay by Andy Cadley and another one by Morpheus.

Both can be downloaded with their sources at http://www.kameli.net/lt/prod.htm

Here you'll find a pile of songs suitable for the MYM player: http://ftp.kameli.net/pub/mym/


File format

I have not been able to find a formal specification of this file format.

The best we have is the commented source code of the original converters 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 */