/***************************************************************************
*       NAME:  DUMPBNK.C $Revision: 1.5 $
**      COPYRIGHT:
**      "Copyright (c) 1994,1995 by e-Tek Labs"
**
**       "This software is furnished under a license and may be used,
**       copied, or disclosed only in accordance with the terms of such
**       license and with the inclusion of the above copyright notice.
**       This software or any other copies thereof may not be provided or
**       otherwise made available to any other person. No title to and
**       ownership of the software is hereby transfered."
****************************************************************************/
#define PRINT_INSTRUMENT_LIST 1
#include <dos.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "dumpbnk.h"
#include "sbosdata.h"
#include "sbosdefs.h"

void print_instruments(void);
void add_instrument(int,int);
void add_extra_instruments(void);
void sort_instruments(void);
void process_instruments(void);
int do_picker(INSTRUMENT *, INSTRUMENT *);

/** Globals **/
    FILE *handle;
    int size,inst_lcv,j,insts_in_bank,insts_in_table=0;
    BNK_HEADER header;
    INST_BLOCK inst;
    DATA data;
    int bypass;
    int method = 2; /** printing method **/ 
        /** Methods:
         **     0: print filenames and data in list format
         **     1: print data in table format
         **     2: print instrument picking information during instrument sort
         **/
    int firsttime = 1;
    FILE *inc;
    FILE *hdr;
    FILE *tbl;
    char filename_inc[10] = "pick.inc\0";
    char filename_hdr[10] = "pick.h\0";
    char filename_tbl[10] = "picktbl.h\0";
    INSTRUMENT first_node;
    INSTRUMENT *start = &first_node;
    INSTRUMENT *end = &first_node;
    int cur_inst;
    int cur_bank;

    /**
     ** These are instruments to skip in the building of the patch map
	 ** ? 0: Use piano 1 instead of 0
     ** ? 9,14,47,88,98,112: in 1M patch set, these sound bad with FM envlps
     ** ? 46: harp - matches too close to piano, leave out and pick piano
     ** n 47: timpani - this is questionable... some want this to be a piano
     ** ? 48: marcato - Really loud and obnoxious in games like prince
     ** y 55: orchit - who can make an ADLIB sound like an orchestra anyway?
     ** ? 58: tuba - pick trumpet instead - better all around instrument
     ** y 77: shakazul - make the OPL2 do this one...
     ** ? 106: shamisen - picks this & plays banjo-close to piano in menu
     ** ? 118: syntom - temp - very close to cymride in inferno
     ** y 119-125 - matched up to blank in the patch map
     ** y 126-127 noise
     **/
#define NUM_SKIP_MELODICS 22
    unsigned char skip_melodics[NUM_SKIP_MELODICS] = {
    0,9,14,47,48,55,58,77,88,98,106,112,118,119,120,121,122,123,124,125,126,127
    };
    /**
     ** y 39: claps - too close to a marimba - fixes Ultima bass line
     ** y 56,58,69,71,72,73,74,75,82,83,84,85,86,87: matched up to blank in map
     ** y 88,89 non-existant patches 
     ** y 80,81: both triangles - wont play noise if it picks this ipo cymbal
     ** y 78,79: cuica1,2 - ??
     ** y 67,68: agogohi,lo - ??
     **/
#define NUM_SKIP_PERCS 22
    unsigned char skip_percs[NUM_SKIP_PERCS] = {
    39,56,58,67,68,69,71,72,73,74,75,78,79,80,81,82,83,84,85,86,87,88
    };
    /**
     ** Instruments to add...
     **/
#define NUM_ADD_INSTS 4
#define ADD_INSTS_CHARS 7
#if (NUM_ADD_INSTS > 0)
    unsigned char add_insts[NUM_ADD_INSTS*ADD_INSTS_CHARS] = {
        /** inst #, CA, CR, MA, MS, MR, FB **/
        164,0x0a,0x0f,0x0e,0x0e,0x0f,0x03, /** bass drum in wolf 3d **/
        56, 0x09,0x0b,0x04,0x01,0x0f,0x07, /** not a bad trumpet... **/
        1,  0x0f,0x04,0x0f,0x05,0x03,0x02, /** piano in menu program**/
        38, 0x0e,0x02,0x0e,0x0f,0x05,0x00, /** bass in uw **/
        //0,   0xf4, 0xf3, 0x2,   /** piano in menu program "linus" and more **/
        //107, 0xc2, 0xa3, 0x3,   /** fixed room enter in keen-was perc **/
        };
#endif
        
/****************************************************************************

FUNCTION DEFINITION:
main - Reads an ADLIB bank file and formats the data into a set of header
       and include files for compilation in to the SBOS runtime code.

DESCRIPTION:
  Opens the bank file and performs some basic statistical analysis and sorting
  on the instruments and writes some of the data to a header file.  This header
  file will be compiled into the ADLIB emulation runtime code.

EXPECTS:
	filename as argv[0] for data output.

*/
void main(int argc,char *argv[])
{
    /** Get the bank file **/
    if(argc != 2)
        {
        printf("\nSpecify .bnk file to decode\n");
        exit(0);
        }

    handle = fopen(argv[1],"rb");
    if(handle == NULL)
        {
        printf("\nUnable to open file %s\n",argv[1]);
        exit(1);
        }

    printf("Bank file: %s\n",strupr(argv[1]));

    /** Read the header **/
    size = fread(&header,1,sizeof(BNK_HEADER),handle);
    if(size != sizeof(BNK_HEADER))
        {
        printf("\nCan't read all of header\n");
        exit(2);
        }

    if( (header.adlib[0] != 'A') ||
        (header.adlib[1] != 'D') ||
        (header.adlib[2] != 'L') ||
        (header.adlib[3] != 'I') ||
        (header.adlib[4] != 'B'))
        {
        printf("Not a valid adlib bank file\n");
        exit(4);
        }

    printf("No. instruments = %d\n\n",header.num_instruments);

    /** Read all the instrument names and print them out **/
    for(inst_lcv=0;inst_lcv<header.num_instruments;inst_lcv++)
        {
        size = fread(&inst,1,sizeof(INST_BLOCK),handle);
        if(size != sizeof(INST_BLOCK))
            {
            printf("Can't read instrument block\n");
            exit(3);
            }
        if(method == 0)
            {
            printf("Instrument Number: %d\n",inst.number);
            printf("Instrument Name: %s\n",inst.name);
            }
        }

    /** Read the blank instrument names **/
    for(inst_lcv=header.num_instruments; inst_lcv<header.num_inst_blocks; 
        inst_lcv++)
        fread(&inst,1,sizeof(INST_BLOCK),handle);

    /** Open both the include file and the header file **/
    inc = fopen(filename_inc,"wt");
    hdr = fopen(filename_hdr,"wt");
    tbl = fopen(filename_tbl,"wt");
    if((inc == NULL) || (hdr == NULL) || (tbl == NULL))
        {
        printf("Problems opening output files\n");
        return;
        }

    /**
     ** Header files are open ...
     ** insts_in_bank is the number to parse out of the table 
     ** insts_in_table is the number we want to put in the new pick table
     **/
    insts_in_bank = 128+88-35+1; /** mel+total percs-unwanted percs **/

    /**
     ** Finish initing the linked list
     ** start is a dummy element
     **/
    start->next = NULL;
    start->prev = NULL;
    end = start;

    /** Read the instrument data and print **/
    for(inst_lcv=0; inst_lcv<header.num_instruments; inst_lcv++)
        {
        if(inst_lcv > 127) 
            {
            cur_bank = TYPE_PERC;
            cur_inst = inst_lcv - PERC_OFFSET;
            }
        else 
            {
            cur_bank = TYPE_MELODIC;
            cur_inst = inst_lcv;
            }

        fread(&data,1,sizeof(DATA),handle);

        if (method == 0)
            {
            if(cur_bank == TYPE_PERC)
                printf("\n-- Percussion Instrument %d --\n",cur_inst);
            else
                printf("\n-- Melodic Instrument %d --\n",cur_inst);

            printf("Feedback: %d\n",data.feedback);
            printf("Connection: %d\n",data.con);
            printf("Patch Sustain: ");
            printf("-------- Modulator\n");
            printf("KSL: %d\n",data.mod_ksl);
            printf("MULTI: %d\n",data.mod_multi);
            printf("Attack: %d\n",data.mod_attack);
            printf("Decay: %d\n",data.mod_decay);
            printf("Sustain: %d\n",data.mod_sust);
            printf("Release: %d\n",data.mod_release);
            printf("Sustain bit: %d\n",data.mod_sust01);
            printf("Volume: %d\n",data.mod_vol);
            printf("KSR: %d\n",data.mod_ksr);
            printf("Wave: %d\n",data.mod_wave);
            printf("-------- Carrier\n");
            printf("KSL: %d\n",data.car_ksl);
            printf("MULTI: %d\n",data.car_multi);
            printf("Attack: %d\n",data.car_attack);
            printf("Decay: %d\n",data.car_decay);
            printf("Sustain: %d\n",data.car_sust);
            printf("Release: %d\n",data.car_release);
            printf("Sustain bit: %d\n",data.car_sust01);
            printf("Volume: %d\n",data.car_vol);
            printf("KSR: %d\n",data.car_ksr);
            printf("Wave: %d\n",data.car_wave);
            }
        else if(method == 1)
            {
            /**
             ** print data in a table format 
             **/
            if(firsttime)
                {
printf("            CARRIER                MODULATOR            CHANNEL\n");
printf("#     A  D  S  R  M  V  SB    A  D  S  R  M  V  SB    FB  CON  PS\n");
printf("---   --------------------    --------------------    -----------\n");
                firsttime = 0;
                }
            printf("%.3d   ",inst_lcv);
    printf("%X  %X  %X  %X  %X  %.2X  %X    %X  %X  %X  %X  %X  %.2X  %X",
                data.car_attack,
                data.car_decay,
                data.car_sust,
                data.car_release,
                data.car_multi,
                data.car_vol,
                data.car_sust01,
                data.mod_attack,
                data.mod_decay,
                data.mod_sust,
                data.mod_release,
                data.mod_multi,
                data.mod_vol,
                data.car_sust01);
            printf("     %X    %X", data.feedback, data.con);
            printf("\n");
            } // end else if method == 1
        /**
         ** do each one until told not to
         **/
        bypass = 0;

        /**
         ** If its a melodic and we want to skip it, set bypass
         **/
        if(cur_bank == TYPE_MELODIC)
            for(j = 0;j < NUM_SKIP_MELODICS; j++)
                {
                if(skip_melodics[j] == cur_inst)
                    /** don't put it in the table **/
                    bypass = 1;
                }
        else
        /**
         ** Else, if its a perc and we want to skip it, set bypass
         **/
            for(j = 0;j < NUM_SKIP_PERCS; j++)
                {
                if(skip_percs[j] == cur_inst)
                    /** don't put it in the table **/
                    bypass = 1;
                }
        /**
         ** For now, skip all percs in the second bank 
         **/
        if(inst_lcv > 181)
            bypass = 1;

        if(!bypass)
            add_instrument(cur_inst,cur_bank);
        } // end of main instrument loop

    add_extra_instruments();
    if(method == 2) process_instruments();
    sort_instruments();
    print_instruments();

    fclose(hdr);
    fclose(inc);
    fclose(tbl);

    fclose(handle);

    start = start->next;
    end = start;
    while(end != NULL)
        {
        end = start->next;
        free(start);
        start = end;
        }
}

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

FUNCTION DEFINITION:
add_instruments - Adds instrument data to linked list for processing later

DESCRIPTION:
  Creates a node for a doubly linked list of instruments and adds the current
  instrument.  Will perform the picking algorithm on the instrument with the
  existing data in the list to see if there is an instrument which is to 
  close for the picker to distinguish.  If there is an instrument which 
  is too close, the new instrument will be omitted.

EXPECTS:
  inst_num should be GM number for melodics and 128 + GM number for percs
  data structure should be setup with valid instrument data for processing

SEE ALSO:
  asm_fm_picker in calc.asm for picking algorigm implementation

*/
void add_instrument(int inst_num, int bank_num)
{
    INSTRUMENT * new;
    INSTRUMENT * temp;
    int weight,lowest_weight,closest_inst,closest_bank;
    int skip_instrument;

    new = malloc(sizeof(INSTRUMENT));
    if(new == NULL)
        {
        printf("Trouble allocating space for new instrument!!\n");
        exit(1);
        }

    /** Fill in the data **/
    if(bank_num == TYPE_PERC) // It's a percussion
        new->type = TYPE_PERC;
    else
        new->type = TYPE_MELODIC;

    new->num = inst_num;
    new->ca = data.car_attack;
    new->cr = data.car_release;
    new->ma = data.mod_attack;
    new->ms = data.mod_sust;
    new->mr = data.mod_release;
    new->fb = data.feedback;

    temp = start->next;
    skip_instrument = FALSE;
    if(method == 2)
        {
        if(new->type == TYPE_PERC)
            printf("- Percussion #%d  ",new->num);
        else
            printf("- Melodic #%d     ",new->num);
        }

    /**
     ** Test instrument for exact matches or matches that are close enough.
     ** For reliablilty, the weighting factors here should exactly match
     ** the picker in calc.asm (asm_fm_picker())
     **/
    lowest_weight = 100;
    while(temp != NULL)
        {
        weight = do_picker(new, temp);

        if(weight < lowest_weight)
            {
            lowest_weight = weight;
            closest_inst = temp->num;
            closest_bank = temp->type;
            }

        if(weight <= MIN_WEIGHT_SPREAD)
            {
            skip_instrument = TRUE;
            if(method == 2)
                {
                if(temp->type == TYPE_PERC)
                    printf("SKIPPED (%d from P%d)",
                    weight, temp->num);
                else
                    printf("SKIPPED: (%d from M%d)",
                    weight, temp->num);
                }
            }
        temp = temp->next;
        }

    if(!(skip_instrument))
        {
        if(closest_bank == TYPE_PERC)
            {
            if(method==2) 
                printf("Closest: (%d from P%d)",lowest_weight,closest_inst);
            }
        else
            {
            if(method==2) 
                printf("Closest: (%d from M%d)",lowest_weight,closest_inst);
            }
        }
    if(method==2)printf("\n");
    
    if(skip_instrument)
        free(new);
    else
        {
        /** Add the new node to the chain **/
        insts_in_table++;
        new->next = NULL;
        new->prev = end;
        end->next = new;
        end = new;
        }
}

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

FUNCTION DEFINITION:
print_instruments - Write header information to the two header files and
  the include file used for the fm picking algorithm.

DESCRIPTION:
  Writes picker includes and defines to an include and header file.  Creates
  the pick table in a third header file.

EXPECTS:
  start points to the start of the list of instruments to be printed out

*/
void print_instruments(void)
{
    unsigned char byte1, byte2, byte3, byte4;
    INSTRUMENT * temp; 

    /**
     ** set temp pointer to beginning of list 
     ** Remember that start is a dummy element - first instrument is
     ** start->next.
     **/
    temp = start->next;

    /** Print the header **/
    fprintf(tbl,"/** IWSBOS Patch picking table **/\n\n");
    fprintf(tbl,"unsigned char inst_table[%d] = {\n",
        insts_in_table*NUM_CHARS);

    /** Traverse the list one by one and print the elements to hdr **/
    while(temp != NULL) /** Go through the list until we reach null **/
        {
        if(temp->type == TYPE_PERC)
            byte1 = (temp->num | 0x80);
        else
            byte1 = temp->num;

        byte2 = (temp->ca << 4) | temp->cr;
        byte3 = (temp->ma << 4) | temp->mr;
        byte4 = (temp->ms << 4) | temp->fb;

        /** Now, print out the formatted bytes to the header file **/
        fprintf(tbl,"\t%u, 0x%X, 0x%X, 0x%X,\n", byte1, byte2, byte3, byte4);

        temp = temp->next;
        }

    /** Send equates to include files **/
    fprintf(inc,"NUM_INSTRUMENTS equ %d\n",insts_in_table);
    fprintf(hdr,"#define NUM_INSTRUMENTS %d\n",insts_in_table);
    fprintf(inc,"NUM_CHARS equ %d\n",NUM_CHARS);
    fprintf(hdr,"#define NUM_CHARS %d\n",NUM_CHARS);
    fprintf(inc,"PERC_OFFSET equ %d\n",PERC_OFFSET);
    fprintf(hdr,"#define PERC_OFFSET %d\n",PERC_OFFSET);

    fprintf(inc,"WEIGHT_CA equ %d\n",WEIGHT_CA);
    fprintf(inc,"WEIGHT_CR equ %d\n",WEIGHT_CR);
    fprintf(inc,"WEIGHT_MS equ %d\n",WEIGHT_MS);
    fprintf(inc,"WEIGHT_FB equ %d\n",WEIGHT_FB);

    /**
     ** When the picker table is built, MIN_WEIGHT_SPREAD is used to guarantee
     ** that there are no entries that are too close.  If it is 4, there are
     ** no two instruments in the table that are 4 or closer apart.  Therefore,
     ** when I search the table, I can select an instrument prematurely if
     ** it is within 2 of the FM instrument.  So, I set MIN_WEIGHT to half
     ** (rounded down) of MIN_WEIGHT_SPREAD.
     **/
    fprintf(inc,"MIN_WEIGHT equ %d\n",MIN_WEIGHT_SPREAD/2);

    fprintf(tbl,"\t};\n");
}

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

FUNCTION DEFINITION:
add_extra_instruments - Gives the ability to add extra instruments to the
  picking table beyond those listed in the bank file.

DESCRIPTION:
  Fills in the data structure and calls add_instrument to add the instrument
  to the end of the picking list.

MODIFIES:
  data structure

*/
void add_extra_instruments(void)
{
    int new_bank, new_inst;

#if (NUM_ADD_INSTS > 0)
    /**
     ** Now add the extra instruments to the table 
     **/
    for(inst_lcv=0;inst_lcv<NUM_ADD_INSTS;inst_lcv++)
        {
        data.car_attack  = add_insts[1+(ADD_INSTS_CHARS*inst_lcv)];
        data.car_release = add_insts[2+(ADD_INSTS_CHARS*inst_lcv)];
        data.mod_attack  = add_insts[3+(ADD_INSTS_CHARS*inst_lcv)];
        data.mod_sust    = add_insts[4+(ADD_INSTS_CHARS*inst_lcv)];
        data.mod_release = add_insts[5+(ADD_INSTS_CHARS*inst_lcv)];
        data.feedback    = add_insts[6+(ADD_INSTS_CHARS*inst_lcv)];

        if(add_insts[0+(ADD_INSTS_CHARS*inst_lcv)] & 0x80)
            {
            new_bank = TYPE_PERC;
            new_inst = add_insts[0+(ADD_INSTS_CHARS*inst_lcv)] - 128;
            }
        else
            {
            new_bank = TYPE_MELODIC;
            new_inst = add_insts[0+(ADD_INSTS_CHARS*inst_lcv)];
            }

        add_instrument(new_inst,new_bank);
        }
#endif
}

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

FUNCTION DEFINITION:
sort_instruments - Sorts the instruments in the instrument list in order
  of fastest envelopes first.

DESCRIPTION:
  It was found that there were more instruments played that had faster
  envelopes so this sorts the instruments with faster envelopes first so
  the picking algorithm may find a match earlier.

EXPECTS:
  start must equal the address of the first node

MODIFIES:
  the instrument chain

*/
/****************************************************************************
    sort_instruments()

****************************************************************************/
void sort_instruments(void)
{
    INSTRUMENT * temp; 
    INSTRUMENT * tempnext; 
    INSTRUMENT * marker; 
    marker = start->next;
    while(marker != NULL)
        {
        temp = start->next;
        while(temp->next != NULL)
            {
            if((temp->ca < temp->next->ca) ||
              ((temp->ca == temp->next->ca) && (temp->cr < temp->next->cr))) 
                {
                tempnext = temp->next;
                temp->next = tempnext->next;
                tempnext->prev = temp->prev;
                temp->prev->next = tempnext;
                temp->prev = tempnext;
                if(tempnext->next != NULL)
                    tempnext->next->prev = temp;
                tempnext->next = temp;
                }
            else
                temp = temp->next;
            }
        marker = marker->next;
        }
}

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

FUNCTION DEFINITION:
do_picker -  Performs the picking algorithm on the two instruments passed in
  and returns the weight difference

RETURNS:
  int - weight between the two instruments

*/
int do_picker(INSTRUMENT * one, INSTRUMENT * other)
{
    int weight;

    weight  = abs(one->ca - other->ca) << WEIGHT_CA;
    weight += abs(one->cr - other->cr) << WEIGHT_CR;
    weight += abs(one->ma - other->ma);
    weight += abs(one->ms - other->ms) << WEIGHT_MS;
    weight += abs(one->mr - other->mr);
    weight += abs(one->fb - other->fb) << WEIGHT_FB;
    /**
     ** I used to compare sustain bits (ability for a patch to sustain) here
     ** as the real picking algorighm does in the driver, but I don't have 
     ** access to the information anymore.  Actually, it makes no difference
     ** anyway.
     **/
    return weight;
}

#pragma warn -O2 // don't optimize this or it gets really mean...
/****************************************************************************

FUNCTION DEFINITION:
process_instruments - Prints out a list of each of the instruments in the
  instrument list with the top five closest instruments from the table.

SEE ALSO:
	do_picker

*/
#define BEST_SIZE 5
void process_instruments(void)
{
    INSTRUMENT * stat; 
    INSTRUMENT * move; 
    unsigned int weight;
    int bcnt;
    unsigned char best_w[BEST_SIZE+1];
    unsigned char best_i[BEST_SIZE+1];
    
#ifdef PRINT_INSTRUMENT_LIST
    stat = start->next;
    while(stat != NULL)
        {
        printf("%d\n",stat->num);
        stat = stat->next;
        }
#endif

    stat = start->next;
    while(stat != NULL)
        {
        move = start->next;

        for(bcnt = 0; bcnt<BEST_SIZE; bcnt++)
            best_w[bcnt] = -1;

        while(move != NULL)
            {
            if(stat->num != move->num)
                {
                weight = do_picker(stat,move);

                for(bcnt=BEST_SIZE-1;bcnt>=0;bcnt--)
                    {
                    if(weight < best_w[bcnt])
                        {
                        best_w[bcnt+1] = best_w[bcnt];
                        best_w[bcnt] = weight;
                        best_i[bcnt+1] = best_i[bcnt];
                        if(move->type == TYPE_PERC)
                            best_i[bcnt] = move->num | 0x80;
                        else
                            best_i[bcnt] = move->num;
                        }
                    }
                }
            move = move->next;
            }
        if(stat->type == TYPE_PERC)
            printf("P");
        else
            printf("M");
        printf("%d: ",stat->num);
        for(bcnt = 0; bcnt<BEST_SIZE; bcnt++)
            {
            if(best_i[bcnt] & 0x80)
                printf("(P%d %d) ",best_i[bcnt] ^ 0x80, best_w[bcnt]);
            else
                printf("(M%d %d) ",best_i[bcnt], best_w[bcnt]);
            best_w[bcnt] = -1;
            }
        printf("\n");
        stat = stat->next;
        }
}
#pragma warn .O2
