/*
 * ptmidsav.c: MOD-format saver module for ptmid. Takes a structure
 * representing a tune and creates a file which contains that tune.
 *
 * Author: Andrew Scott  (c)opyright 1994
 *
 * Date: 17/11/1993 ver 0.0
 *       8/1/1994   ver 0.1
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ptmid.h"

/*
 * PutblanksPfile: Outputs as many null characters to a given file as
 * is desired (max. 131). Inelegant solution.
 */
void PutblanksPfile(FILE *pfile, int cch)
{
    if (cch)
        fwrite("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
            "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
            "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
            "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
            "\0\0", 1, cch, pfile);
}

/*
 * PutPfileSz: Outputs a string to a given file and pads it to the correct
 * length by outputting null characters if necessary.
 */
void PutPfileSz(FILE *pfile, Sz szT, int cch)
{
    while (*szT && cch) {
        putc(*(szT), pfile); szT++;
        cch--;
    }
    PutblanksPfile(pfile, cch);
}

/*
 * WritePfile: Writes division information (sample, period, effect) to
 * given file in the standard Protracker format.
 */
void WritePfile(FILE *pfile, unsigned bSam, unsigned wPer, unsigned wEff)
{
    putc((bSam & 0xF0) | (wPer >> 8), pfile);
    putc(wPer & 0xFF, pfile);
    putc((bSam & 0x0F) << 4 | (wEff >> 8), pfile);
    putc(wEff & 0xFF, pfile);
}

/*
 * PeiNextPtune: Given a pointer to a tune, will start using it if not
 * already using one. Will return next event in quanta, else NULL.
 * If it gets to the end of the tune, will set flag to false and will
 * wait to be given a new tune.
 */
EI *PeiNextPtune(Tune *ptuneMain, int *pf)
{
    static Tune *ptune = NULL;
    static unsigned long quant;
    EI *pei;

    if (NULL == ptune) { /** If first time called **/
        if (NULL == ptuneMain) /** If no tune given, quit **/
            return NULL;
        *pf = 1;
        quant = 0;
        ptune = ptuneMain; /** Initialize tune pointer to start of tune **/
    } else /** Else **/
        quant++; /** Advance along tune 1 quantize fraction **/
    if (quant < ptune->count) /** If haven't reached next event **/
        return NULL; /** return nothing-happening **/

    pei = ptune->pei; /** Otherwise note next event **/
    if ((ptune = ptune->ptune) == NULL) /** If no more events **/
        *pf = 0; /** register end of tune **/
    return pei; /** Return that event **/
}

/*
 * Convqpm: Converts a given qpm number into a double tempo thingy.
 */
void Convqpm(unsigned qpm, int rgbTempo[2], int ticks)
{
    if (792 / ticks <= qpm && 6144 / ticks - 1 >= qpm) {
        rgbTempo[0] = ticks; /** If can use current ticks/div, do so **/
        rgbTempo[1] = qpm * ticks / 24;
    } else if (33 > qpm) /** Else if qpm is very small **/
        if (26 > qpm) { /** approximate it **/
            rgbTempo[0] = 31;
            rgbTempo[1] = 33;
        } else {
            rgbTempo[0] = 31;
            rgbTempo[1] = qpm * 31 / 24;
        }
    else if (6120 <= qpm) { /** Else if qpm is very big **/
        rgbTempo[0] = 1; /** approximate it too **/
        rgbTempo[1] = 255;
    } else { /** Else look for closest fraction **/
        unsigned iT;

        iT = (qpm + 254) / 255; /**### try best fit fraction later ###**/
        rgbTempo[0] = 24 / iT;
        rgbTempo[1] = qpm / iT;
    }
}

/*
 * PutpatternsPtunePfile: Given an output file and a tune, will output the
 * tune as standard Protracker patterns. wPtchan determines number of
 * channels per division. wPatmax determines maximum number of patterns
 * to write before terminating.
 */
int PutpatternsPtunePfile(Tune *ptune, FILE *pfile)
{
    int iT, iT2, wPat = 0, cDiv, ipw, ipwMax, fGoing, ini;
    unsigned pwLen[MAXPTCHAN] = {0,0,0,0,0,0,0,0}, pwNote[3 * MAXPTCHAN];
    unsigned rgbTempo[2] = {6,125};
    long wNewtempo = 120;
    EI *pei;
    NI *pni;

    pei = PeiNextPtune(ptune, &fGoing); /** Get first event **/
    ipw = wPtchan;
    ipwMax = wPtchan * 3;
    for (wPat = 0; fGoing; wPat++) { /** Loop until told to stop **/
        if (wPat == wPatmax) { /** If pattern limit reached, stop **/
            fprintf(stderr, "ptmid: Warning -- Pattern limit %d reached. "
                "Aborting!\n", wPatmax);
            break;
        }
        for (cDiv = 64; cDiv--; ) { /** For each division in a pattern **/
            memset(pwNote, 0, ipwMax * sizeof(unsigned)); /** Clear next notes **/

            for (iT = wPtchan; iT--; ) /** With any currently playing notes **/
                if (pwLen[iT])
                    if (0 == --pwLen[iT]) { /** Check if just stopped **/
                        pwNote[iT2 = iT * 3] = MAXPTSAMPS; /** Yes.. store quiet command **/
                        pwNote[iT2 + 1] = 0;
                        pwNote[iT2 + 2] = 0xC00;
                    }

            if (fGoing) { /** If still going in the tune **/
                for (; NULL != pei; pei = pei->peiNext) /** For each event at this position **/
                    if (pei->cni) { /** If note-event **/
                        pni = pei->pni;
                        for (ini = pei->cni; ini--; ) /** For each note **/
                            if (-1 != pni[ini].inst) { /** which is valid **/

                                iT = ipwMax - 1; /*** Find channel to stick note ***/
                                for (; 0 < iT && 0xC00 != pwNote[iT]; iT -= 3);
                                if (0 > iT) {
                                    for (iT = ipw; iT--; )
                                        if (0 == pwLen[iT])
                                            break;
                                    if (0 > iT) {
                                        for (iT = wPtchan; iT-- > ipw; )
                                            if (0 == pwLen[iT])
                                                break;
                                        if (0 > iT) {
                                            iT2 = pei->effect / 2;
                                            for (iT = ipw; iT--; )
                                                if (iT2 <= pwLen[iT])
                                                    break;
                                            if (0 > iT) {
                                                for (iT = wPtchan; iT-- > ipw; )
                                                    if (iT2 <= pwLen[iT])
                                                        break;
                                                if (0 > iT)
                                                    continue;
                                            }
                                        }
                                    }
                                    if (--ipw < 0)
                                        ipw = wPtchan - 1;
                                    iT = iT * 3 + 2;
                                }

                                pwNote[iT - 2] = pni[ini].inst + 1; /*** Store note ***/
                                pwNote[iT - 1] = pni[ini].pitch;
                                if (pni[ini].vol != rgmsDecided[pni[ini].inst].bDefvol)
                                    pwNote[iT] = 0xC00 + pni[ini].vol;
                                else
                                    pwNote[iT] = 0;
                                pwLen[iT / 3] = pei->effect;
                            }

                    } else /** Else store new tempo **/
                        wNewtempo = pei->effect;
                pei = PeiNextPtune(NULL, &fGoing);
            }

            if (0 != wNewtempo) { /** If need to set a tempo this division **/
                int rgbNew[2];

                Convqpm(wNewtempo, rgbNew, rgbTempo[0]); /** Find protracker equivalent **/
                if (rgbNew[0] != rgbTempo[0] || rgbNew[1] != rgbTempo[1]) {
                    for (iT = ipwMax - 1; iT > 0; iT -= 3) /** Find a channel for it **/
                        if (0 == pwNote[iT])
                            break;
                    if (iT < 0) { /** If no channel.. damn. Have to replace something **/
                        unsigned bMin = -1, bTest;

                        for (iT2 = ipwMax - 1; iT2 > 0; iT2 -= 3) {
                            bTest = abs((pwNote[iT2] & 0xFF) - rgmsDecided[pwNote[iT2 - 2] - 1].bDefvol);
                            if (bTest < bMin) {
                                bMin = bTest;
                                iT = iT2; /** Find best thing to replace **/
                            }
                        }
                    }

                    if (rgbNew[0] != rgbTempo[0])
                        if (rgbNew[1] != rgbTempo[1]) { /** If need two places **/
                            pwNote[iT] = 0xFFF;
                            for (iT2 = ipwMax - 1; iT2 > 0; iT2 -= 3) /** Find a channel **/
                                if (0 == pwNote[iT2])
                                    break;
                            if (iT2 < 0) { /** No channels.. use different new tempo **/
                                iT2 = rgbNew[1] * rgbTempo[0] / rgbNew[0];
                                if (255 < iT2)
                                    if (280 < iT2)
                                        iT2 = 0;
                                    else
                                        iT2 = 255;
                                else if (33 > iT2)
                                    if (30 > iT2)
                                        iT2 = 33;
                                    else
                                        iT2 = 0;
                                if (0 != iT2) { /** If we can allow for ~10% bpm variance **/
                                    pwNote[iT] = 0xF00 + iT2; /** then use it **/
                                    rgbTempo[1] = iT2;
                                } else { /** Else try to find a ticks value **/
                                    iT2 = rgbNew[0] * rgbTempo[1] / rgbNew[1];
                                    if (0 == iT2)
                                        iT2 = 1;
                                    else if (32 < iT2)
                                        iT2 = 32;
                                    pwNote[iT] = 0xF00 + iT2; /** and use it **/
                                    rgbTempo[0] = iT2;
                                }
                            } else {
                                pwNote[iT] = 0xF00 + rgbNew[0]; /** Store 2 value tempo **/
                                rgbTempo[0] = rgbNew[0];
                                pwNote[iT2] = 0xF00 + rgbNew[1];
                                rgbTempo[1] = rgbNew[1];
                            }
                        } else {
                            pwNote[iT] = 0xF00 + rgbNew[0]; /** Store just ticks/div **/
                            rgbTempo[0] = rgbNew[0];
                        }
                    else {
                        pwNote[iT] = 0xF00 + rgbNew[1]; /** Store just bpm **/
                        rgbTempo[1] = rgbNew[1];
                    }
                }
                wNewtempo = 0;
            }

            for (iT = ipwMax - 1; iT > 0; iT -= 3) /** Write division to file **/
                WritePfile(pfile, pwNote[iT - 2], pwNote[iT - 1], pwNote[iT]);
        }
    }
    return wPat; /** Return number of patterns written **/
}

unsigned char lo(int n) { return((unsigned char)n&255); }
unsigned char hi(int n) { return((unsigned char)(n>>8)&255); }
int putw(int n,FILE *f)
{
    unsigned char c;
    int r;
    c=hi(n);   putc(c,f);
    c=lo(n); r=putc(c,f);
    return(r);
}

/*
 * SavePtunePfile: Given an output file and a tune, will output the tune to
 * the file using standard Protracker MODule format.
 */
void SavePtunePfile(Tune *ptune, FILE *pfile)
{
    int iT, cPatterns, ch, cSamps = 0;
    long Patternpos, Samplepos, cch, slen;
    SI **ppsi;
    FILE *pfileSamp;
    char *pch;

    /*--------------------------------------------------------------------*/
    /* MODULE'S TITLE                                                     */

    PutPfileSz(pfile, szTitle, 20); /** Write title **/

    /*--------------------------------------------------------------------*/
    /* ALL SAMPLES' INFORMATION                                           */

    for (iT = 0; iT < MAXPTSAMPS; iT++) { /** Write sample info **/

        /* SAMPLE'S NAME */
        PutPfileSz(pfile, rgmsDecided[iT].szName, 22);

        if (0 == rgmsDecided[iT].cMix)
            PutblanksPfile(pfile, 8);
        else {
            cSamps++;
            putw(0, pfile);                         /* WORD SAMPLE LENGTH */
            putc(0, pfile);                         /* NIB. FINE TUNE     */
            putc(rgmsDecided[iT].bDefvol, pfile);   /* VOLUME 0-64        */
            ppsi = rgmsDecided[iT].ppsiMix;         /* START OF SAMPLE    */
            putw((*ppsi)->wLppos, pfile);           /* REPEAT IN WORDS    */
            putw((*ppsi)->wLplen,pfile);            /* LENGTH OF SAMPLE   */
        }
    }

    /*--------------------------------------------------------------------*/
    /* ALL PATTERNS' INFORMATION                                          */

    Patternpos = ftell(pfile);  /** Store start of pattern # table     **/

    PutblanksPfile(pfile, 130); /** Main pattern table first as blanks **/
    fwrite(szId, 4, 1, pfile);  /** Write ProTracker ID                **/

    cPatterns = PutpatternsPtunePfile(ptune, pfile); /** Write patterns **/

    Samplepos = ftell(pfile); /** Store start of samples data **/
    fseek(pfile, Patternpos, SEEK_SET); /** Go back to pattern # table **/

    putc(cPatterns, pfile);     /* Number of Song positions */
    putc(127, pfile);           /* Historically 127         */

    for (iT = 0; iT < cPatterns; iT++)  /* Write pattern numbers to main */
        putc(iT, pfile);                /* pattern table.                */

    pch = (char *) malloc(2049);        /* Allocate buffer */

    if (!fQuiet) printf("Writing %d samples: ", cSamps);

    for (iT = 0; iT < MAXPTSAMPS; iT++) /** Write samples **/
        if (0 < rgmsDecided[iT].cMix) {
            fseek(pfile, Samplepos, SEEK_SET);
            ppsi = rgmsDecided[iT].ppsiMix;
            pfileSamp = fopen((*ppsi)->fnSample, "rb");
            if(!pfileSamp)
            {
                if(!fQuiet) printf("Could not open sample '%s'.\n",(*ppsi)->fnSample);
                cch=0;
            }
            else
            {
                /* Sample is opened, let's copy it into MOD file */

                if (NULL == pch)                            /* No buffer? */
                {
                    if(!fQuiet) printf("-");
                    while ((ch = getc(pfileSamp)) != EOF)   /* Write it */
                        putc(ch, pfile);
                }
                else                                        /* Buffer? */
                {
                    if(!fQuiet) printf(".");
                    while (fwrite(pch, 1, fread(pch, 1, 2048, pfileSamp), pfile) == 2048)
                    {   if(!fQuiet) printf(":"); } /* Write it */
                }
                cch = ftell(pfileSamp) / 2;
                if(!fQuiet) printf("\nSample length: %d\n",cch*2);
                fclose(pfileSamp);
            }
            Samplepos = ftell(pfile);
            fseek(pfile, 42 + 30 * iT, SEEK_SET);
            putw(cch,pfile);
        }

    if (NULL != pch) free(pch);         /* Free buffer */
}