/*
 * ptmidzap.c: Resolution module for ptmid. Takes a structure representing
 * a tune and tries to make it fit into 4 channels.
 *
 * 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 "ptmid.h"

MS rgmsDecided[MAXPTSAMPS];
int cmsDecided = 0, wVolfract = 1;

/**
 ** The midiperiod1 array allows quick conversion between MIDI note
 ** values and Protracker note values. The midiperiod2 array is
 ** similar, but uses an extended range of values. All "out-of-bounds"
 ** values are given closest legal value.
 **/
unsigned midiperiod1[128] = {
    856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856,
    856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856,
    856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856,
    856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856,
    856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453,
    428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226,
    214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113,
    113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
    113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
    113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
    113, 113, 113, 113, 113, 113, 113, 113
    };
unsigned midiperiod2[128] = {
    1712,1712,1712,1712,1712,1712,1712,1712,1712,1712,1712,1712,
    1712,1712,1712,1712,1712,1712,1712,1712,1712,1712,1712,1712,
    1712,1712,1712,1712,1712,1712,1712,1712,1712,1712,1712,1712,
    1712,1616,1525,1440,1357,1281,1209,1141,1077,1017,961, 907,
    856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453,
    428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226,
    214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113,
    107, 101, 95,  90,  85,  80,  76,  71,  67,  64,  60,  57,
    57,  57,  57,  57,  57,  57,  57,  57,  57,  57,  57,  57,
    57,  57,  57,  57,  57,  57,  57,  57,  57,  57,  57,  57,
    57,  57,  57,  57,  57,  57,  57,  57
    };

/**
 ** The midivolume array does the same thing as the midiperiod array
 ** except it is for velocity-volume conversion.
 **/
unsigned midivolume[128] = {
     0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
    16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
    32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
    48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
  };

/*
 * Init: Initializes stuff that should be initialized.
 */
static void Init()
{
    int ims = MAXPTSAMPS;

    while (ims--) { /** Put default values in sample array **/
        sprintf(rgmsDecided[ims].szName, "%02d-NO NAME", ims);
        rgmsDecided[ims].cMix = 0;
        rgmsDecided[ims].bDefvol = 0;
    }
}

/*
 * FitSzBFn: Given a filename, will fit it into the given sample name,
 * and prepend given number, ensuring that it doesn't overflow the
 * 22 character array.
 */
void FitSzBFn(Sz szName, int bPos, Sz fnSample)
{
    int iT = 18;

    sprintf(szName, "%02d-", bPos);                /** First the number **/
    szName += 3;
    while (iT-- && (*(szName++) = *(fnSample++))); /** Then the name **/
}

/*
 * AnalyzePtune: Given a tune, searches through and allocates samples
 * to sample array rgmsDecided (includes determining mixes).
 * Eventually (?) this will become much more extensive, and include
 * compacting by changing tempo, filtering of quiet notes, and identification
 * of common chords and replacing these with a separate sample.
 */
void AnalyzePtune(Tune *ptune)
{
    unsigned long Lastnoise = 0;
    EI *pei;
    SI *psi, **ppsi;
    NI *pni;
    int ini;

    while (NULL != ptune) { /** While not at end of tune **/
        for (pei = ptune->pei; NULL != pei; pei = pei->peiNext) /** With each event **/
            if (0 != pei->cni) {
                if (ptune->count + pei->effect > Lastnoise) /** Find the note which **/
                    Lastnoise = ptune->count + pei->effect; /** will carry the longest **/

                for (ini = pei->cni; ini--; ) { /** For each note in event **/
                    pni = pei->pni;
                    if (pni[ini].inst < 0) /** If a percussion instrument **/
                        if ((psi = rgpsiDrum[-1 - pni[ini].inst]) == NULL)
                            pni[ini].inst = -1;
                        else if (fExtend) /** convert pitch to protracker period **/
                            pni[ini].pitch = midiperiod2[psi->pitch];
                        else
                            pni[ini].pitch = midiperiod1[psi->pitch];
                    else { /** Else if a non-percussion instrument **/
                        unsigned wMin;

                        if ((ppsi = rgppsiIns[pni[ini].inst]) == NULL)
                            ppsi = rgppsiIns[128];
                        psi = *(ppsi++);
                        wMin = abs(psi->pitch - pni[ini].pitch);
                        for (; NULL != *ppsi; ppsi++) /** Find closest matching sample **/
                            if (abs((*ppsi)->pitch - pni[ini].pitch) < wMin) {
                                psi = *ppsi;
                                wMin = abs(psi->pitch - pni[ini].pitch);
                            }
                        wMin = 60 + pni[ini].pitch - psi->pitch;
                        if (fExtend) /** And use it's base note when converting to **/
                            pni[ini].pitch = midiperiod2[wMin]; /** protracker periods **/
                        else
                            pni[ini].pitch = midiperiod1[wMin];
                    }

                    if (NULL != psi) /** If sample info **/
                        if (-1 != psi->sample) /** and sample has been used before **/
                            pni[ini].inst = psi->sample; /** use that sample for instrument **/
                        else if (MAXPTSAMPS == cmsDecided) /** Else if no samples left **/
                            pni[ini].inst = -1; /** make note invalid **/

                        else { /** Else we've got a new sample **/
                            FitSzBFn(rgmsDecided[cmsDecided].szName, cmsDecided,
                                psi->fnSample); /** Fix up it's name **/
                            rgmsDecided[cmsDecided].bDefvol = 64;
                            rgmsDecided[cmsDecided].cMix = 1;
                            if ((ppsi = (SI **) malloc(sizeof(SI *))) == NULL) {
                                fprintf(stderr, "ptmid: Cannot allocate any more memory\n");
                                exit(1);
                            } /** Give it a spot in memory **/
                            *ppsi = psi;
                            rgmsDecided[cmsDecided].ppsiMix = ppsi; /** Put it in array **/
                            rgmsDecided[cmsDecided].pwMixnote = NULL;
                            psi->sample = pni[ini].inst = cmsDecided++;
                        }
                }
            }
        ptune = ptune->ptune;
    }
}

/*
 * UnitifyPtune: Given a tune, will convert its volume into Protracker
 * standard units, as well as convert lengths into division multiples.
 */
void UnitifyPtune(Tune *ptune)
{
    EI *pei;
    NI *pni;
    int cni;

    while (NULL != ptune) { /** While not end of tune **/
        pei = ptune->pei;
        ptune->count /= wQuant; /** Divide intervals by quantize amount **/
        ptune = ptune->ptune;
        while (NULL != pei) { /** Go through each event **/
            if (pei->cni) {
                pei->effect /= wQuant; /** Divide durations by quantize amount **/
                /**
                 ** When implemented, samples that are chords will have volumes
                 ** smaller than other samples (ie. 3 notes in one sample has
                 ** 1/3 volume for each note), so much divide all other volumes
                 ** to even things out.
                 **/
                for (cni = pei->cni, pni = pei->pni; cni--; pni++)
                    pni->vol = midivolume[pni->vol] / wVolfract;
            }
            pei = pei->peiNext;
        }
    }
}

/*
 * ResolvePtune: Given a tune, goes through and finds out useful stuff
 * about it for use in creating a MOD-file. Also trims off chords that
 * are too big, notes that are too quiet, etc.
 */
void ResolvePtune(Tune *ptune)
{
    Init();
    AnalyzePtune(ptune);
    UnitifyPtune(ptune);
}