home *** CD-ROM | disk | FTP | other *** search
- /*
- * 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
- * 11/2/1994 ver 0.2
- */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <math.h>
- #include "ptmid.h"
- #include "samples.h"
-
- /**
- ** The midiperiod array allows quick conversion between MIDI note
- ** values and Protracker note values. All "out-of-bounds" values
- ** are given closest legal value.
- **/
- unsigned midiperiod[128] = {
- 0, 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
- };
-
- /*
- * 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);
- cch--;
- }
- PutblanksPfile(pfile, cch);
- }
-
- /*
- * WritePfile: Writes division information (sample, pitch, effect) to
- * given file in the format specified by wModfmt.
- *
- * date: 2/7/1994 - added multi-format support
- * 3/7/1994 - now aborts on write error
- */
- void WritePfile(FILE *pfile, unsigned bSam, unsigned wPit, unsigned wEff)
- {
- static int cNote = -1, irgchPos, Buffsiz;
- static char *pchBuff;
-
- if (-1 == cNote) {
- irgchPos = 0;
- if (1 == wModfmt)
- pchBuff = (char *) malloc(cNote = Buffsiz = 4 * wMaxchan * DIVSPERPAT);
- else
- pchBuff = (char *) malloc(cNote = Buffsiz = 3 * wMaxchan * DIVSPERPAT);
- }
- switch (wModfmt) {
- case 1:
- wPit = midiperiod[wPit];
- pchBuff[0 + irgchPos] = (bSam & 0xF0) | (wPit >> 8);
- pchBuff[1 + irgchPos] = wPit & 0xFF;
- pchBuff[2 + irgchPos] = ((bSam & 0x0F) << 4) | (wEff >> 8);
- pchBuff[3 + irgchPos] = wEff & 0xFF;
- cNote -= 4;
- irgchPos += 4;
- break;
- case 2:
- if (0 < wPit)
- wPit = wPit - 36;
- pchBuff[0 + irgchPos] = ((bSam & 0x30) >> 4) | (wPit << 2);
- pchBuff[1 + irgchPos] = ((bSam & 0x0F) << 4) | (wEff >> 8);
- pchBuff[2 + irgchPos] = wEff & 0xFF;
- cNote -= 3;
- if ((irgchPos += DIVSPERPAT * 3) >= Buffsiz)
- irgchPos -= Buffsiz - 3;
- break;
- }
- if (0 == cNote) {
- if (0 == fwrite(pchBuff, Buffsiz, 1, pfile)) {
- ERROR;
- exit(1);
- }
- irgchPos = 0;
- cNote = Buffsiz;
- }
- }
-
- /*
- * 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 (6144 <= qpm) { /** Else if qpm is very big **/
- rgbTempo[0] = 1; /** approximate it too **/
- rgbTempo[1] = 255;
- } else { /** Else look for closest fraction **/
- int j, k, kMax;
- double ratio, junk;
-
- ratio = qpm / 24.0;
- j = k = 791 / qpm + 1; /** I hope these constraints are Ok **/
- kMax = 6143 / qpm;
- while (k++ < kMax)
- if (fabs(modf(ratio * k, &junk) - 0.5) >
- fabs(modf(ratio * j, &junk) - 0.5))
- j = k;
- rgbTempo[0] = j;
- rgbTempo[1] = j * ratio + 0.5;
- }
- }
-
- /*
- * PutpatternsPtunePfile: Given an output file and a tune, will output the
- * tune as standard Protracker patterns. wMaxchan determines number of
- * channels per division. wPatmax determines maximum number of patterns
- * to write before terminating.
- *
- * date: 3/7/1994 - quiet samples now play sample 0 rather than sample 31
- */
- int PutpatternsPtunePfile(Tune *ptune, FILE *pfile)
- {
- int iT, iT2, wPat = 0, cDiv, ipw, ipwMax, fGoing;
- unsigned *pwLen, pwNote[3 * MAXCHANS], rgbTempo[2] = {6,125};
- unsigned wNewtempo = 120;
- unsigned long cDev = 0;
- EI *pei;
-
- pwLen = (unsigned *) calloc(MAXCHANS, sizeof(unsigned));
- pei = PeiNextPtune(ptune, &fGoing); /** Get first event **/
- ipw = wMaxchan;
- ipwMax = wMaxchan * 3;
- for (wPat = 0; fGoing; wPat++) { /** Loop until told to stop **/
- if (wPat == wPatmax) { /** If pattern limit reached, stop **/
- Error("Warning -- Pattern limit %d reached. Aborting!", wPatmax);
- break;
- }
- for (cDiv = 64; cDiv--; ) { /** For each division in a pattern **/
- memset(pwNote, 0, ipwMax * sizeof(unsigned)); /** Clear next notes **/
-
- for (iT = wMaxchan; iT--; ) /** With any currently playing notes **/
- if (pwLen[iT])
- if (0 == --pwLen[iT]) { /** Check if just stopped **/
- pwNote[iT2 = iT * 3] = 0; /** Yes.. store quiet command **/
- pwNote[iT2 + 2] = 0xC00;
- }
-
- if (fGoing) { /** If still going in the tune **/
- for (; NULL != pei; pei = pei->pei) /** For each event at this position **/
- if (-1 != pei->pitch) { /** If note-event **/
- if (-2 != pei->pitch) { /** 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 = wMaxchan; iT-- > ipw; )
- if (0 == pwLen[iT])
- break;
- if (0 > iT) {
- iT2 = (unsigned) pei->effect / 2;
- for (iT = wMaxchan; iT--; )
- if (iT2 <= pwLen[iT])
- break;
- if (0 > iT) {
- cDev++;
- continue;
- }
- }
- }
- if (--ipw < 0)
- ipw = wMaxchan - 1;
- iT = iT * 3 + 2;
- }
-
- pwNote[iT - 2] = pei->inst + 1; /*** Store note ***/
- if (fStats && 0 != pwNote[iT - 1])
- cDev++;
- pwNote[iT - 1] = pei->pitch;
- if (pei->vol != rgmsDecided[pei->inst].bDefvol)
- pwNote[iT] = 0xC00 + pei->vol;
- else
- pwNote[iT] = 0;
- pwLen[iT / 3] = (unsigned) pei->effect;
- }
-
- } else /** Else store new tempo **/
- wNewtempo = (unsigned) 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]);
- }
- }
- if (fStats)
- printf(" Number of dropped notes: %lu\n", cDev);
- free(pwLen);
- return wPat; /** Return number of patterns written **/
- }
-
- /*
- * SavePtunePfile: Given an output file and a tune, will output the tune to
- * the file using module format specified by wModfmt.
- *
- * date: 30/6/1994 - added call to LenOutPfileFn
- * 2/7/1994 - added MTM saving
- */
- void SavePtunePfile(Tune *ptune, FILE *pfile)
- {
- int iT, cPatterns, cSamps;
- unsigned rgwSampsiz[MAXSAMPS], wT;
- long Patternpos;
- SI *psi;
-
- if (2 == wModfmt)
- fwrite("MTM\20", 1, 4, pfile);
- PutPfileSz(pfile, szTitle, 20); /** Write title **/
- if (2 == wModfmt)
- PutblanksPfile(pfile, 6);
- for (cSamps = 0; cSamps < MAXSAMPS; cSamps++)
- if (NULL == rgmsDecided[cSamps].psi)
- break;
- if (2 == wModfmt) {
- putc(cSamps, pfile);
- putc(0, pfile);
- putc(DIVSPERPAT, pfile);
- putc(wMaxchan, pfile);
- fwrite("\0\17\0\17\0\17\0\17\0\17\0\17\0\17\0\17\0\17\0\17\0\17\0\17\0\17"
- "\0\17\0\17\0\17", 1, 32, pfile); /** Write pan positions **/
- }
- for (iT = 0; iT < cSamps; iT++) { /** Write sample info **/
- PutPfileSz(pfile, rgmsDecided[iT].szName, 22);
- psi = rgmsDecided[iT].psi;
- switch (wModfmt) {
- case 1:
- putc(0, pfile);
- putc(0, pfile);
- putc(psi->bFinetune & 0x0F, pfile);
- putc(rgmsDecided[iT].bDefvol, pfile);
- putc(psi->wLppos >> 8, pfile);
- putc(psi->wLppos & 0xFF, pfile);
- putc(psi->wLplen >> 8, pfile);
- putc(psi->wLplen & 0xFF, pfile);
- break;
- case 2:
- PutblanksPfile(pfile, 4);
- wT = psi->wLppos;
- putc((wT & 0x007F) << 1, pfile);
- putc((wT & 0x7F80) >> 7, pfile);
- putc((wT > 0x7FFF) ? 1 : 0, pfile);
- putc(0, pfile);
- wT += psi->wLplen;
- putc((wT & 0x007F) << 1, pfile);
- putc((wT & 0x7F80) >> 7, pfile);
- putc((wT > 0x7FFF) ? 1 : 0, pfile);
- putc(0, pfile);
- putc(psi->bFinetune, pfile);
- putc(rgmsDecided[iT].bDefvol, pfile);
- putc(0, pfile);
- break;
- }
- }
- if (1 == wModfmt)
- for (iT = cSamps; iT < MAXSAMPS; iT++) { /** Write any blank samples **/
- PutPfileSz(pfile, rgmsDecided[iT].szName, 22);
- PutblanksPfile(pfile, 8);
- }
- Patternpos = ftell(pfile); /** Store start of pattern # table **/
- if (1 == wModfmt) {
- PutblanksPfile(pfile, 130); /** Write it as blanks **/
- fwrite(szId, 4, 1, pfile); /** Write protracker id **/
- } else
- PutblanksPfile(pfile, 128);
-
- cPatterns = PutpatternsPtunePfile(ptune, pfile); /** Write patterns **/
-
- if (2 == wModfmt) {
- int wT2;
-
- iT = 1;
- for (wT = cPatterns; wT--; )
- for (wT2 = 0; wT2 < 32; wT2++)
- if (wT2 < wMaxchan) {
- putc(iT & 0xFF, pfile);
- putc(iT >> 8, pfile);
- iT++;
- } else {
- putc(0, pfile);
- putc(0, pfile);
- }
-
- fseek(pfile, 24, SEEK_SET);
- wT = cPatterns * wMaxchan;
- putc(wT & 0xFF, pfile);
- putc(wT >> 8, pfile);
- putc(cPatterns - 1, pfile);
- putc(cPatterns - 1, pfile);
- }
-
- fseek(pfile, Patternpos, SEEK_SET); /** Go back to pattern # table **/
- if (1 == wModfmt) {
- putc(cPatterns, pfile);
- putc(127, pfile);
- }
- for (iT = 0; iT < cPatterns; ) /** Write pattern numbers **/
- putc(iT++, pfile);
-
- if (!fQuiet)
- printf("Writing %d samples: ", cSamps);
- fseek(pfile, 0, SEEK_END);
- for (iT = 0; iT < cSamps; iT++) { /** Write samples **/
- psi = rgmsDecided[iT].psi;
- rgwSampsiz[iT] = (unsigned) (LenOutPfileFn(pfile, psi->fnSample,
- 1 == wModfmt) / 2);
- if (!fQuiet)
- printf(".");
- }
- if (!fQuiet)
- printf("\n");
-
- for (iT = cSamps; iT--; ) /** Write samples' lengths **/
- switch (wModfmt) {
- case 1:
- fseek(pfile, 42 + 30 * iT, SEEK_SET);
- putc(rgwSampsiz[iT] >> 8, pfile);
- putc(rgwSampsiz[iT] & 0xFF, pfile);
- break;
- case 2:
- fseek(pfile, 88 + 37 * iT, SEEK_SET);
- wT = rgwSampsiz[iT];
- putc((wT & 0x007F) << 1, pfile);
- putc((wT & 0x7F80) >> 7, pfile);
- putc((wT > 0x7FFF) ? 1 : 0, pfile);
- break;
- }
- }
-