Mercurial > hg > dmlib
view minijss/jloadxm.c @ 2576:812b16ee49db
I had been living under apparent false impression that "realfft.c"
on which the FFT implementation in DMLIB was basically copied from
was released in public domain at some point, but it could very well
be that it never was. Correct license is (or seems to be) GNU GPL.
Thus I removing the code from DMLIB, and profusely apologize to the
author, Philip Van Baren.
It was never my intention to distribute code based on his original
work under a more liberal license than originally intended.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Fri, 11 Mar 2022 16:32:50 +0200 |
parents | 0c0576544d41 |
children | 9807ae37ad69 |
line wrap: on
line source
/* * miniJSS - Fast Tracker ][ (XM) module loader * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2006-2015 Tecnic Software productions (TNSP) * * TO DO: * - Add support for 1.02/1.03 XM-format versions. * (Not very useful, but if it's not too hard, then do it) */ #include "jssmod.h" /* XM value limit definitions */ #define XM_MaxChannels (32) #define XM_MaxPatterns (256) #define XM_MaxOrders (255) #define XM_MaxInstruments (128) #define XM_MaxInstSamples (16) #define XM_MaxEnvPoints (12) #define XM_MaxNotes (96) #define XM_MaxSampleVolume (64) #define XM_HeaderSize 276 /* XM format structures */ typedef struct { char idMagic[17]; // XM header ID "Extended Module: " char songName[20]; // Module song name Uint8 unUsed1A; // ALWAYS 0x1a char trackerName[20]; // ID-string of tracker software Uint16 version; // XM-version tag Uint32 headSize; // Module header size, FROM THIS POINT! Uint16 norders, // Number of orders defRestartPos, // Default song restart position nchannels, // Number of channels npatterns, // Number of patterns ninstruments, // Number of instruments flags, // Module flags: // bit0: 0 = Amiga frequency table // 1 = Linear frequency table // defSpeed, // Default speed defTempo; // Default tempo Uint8 orderList[256]; // Order list } XMHeader; typedef struct { Uint32 headSize; // Instrument header size (see docs!) char instName[22]; // Name/description Uint8 instType; // Type Uint16 nsamples; // Number of samples } XMInstrument1; typedef struct { Uint16 frame, value; } XMEnvPoint; typedef struct { Uint8 flags, npoints, sustain, loopS, loopE; XMEnvPoint points[XM_MaxEnvPoints]; } XMEnvelope; typedef struct { Uint32 headSize; // Header size Uint8 sNumForNotes[XM_MaxNotes]; // Sample numbers for notes XMEnvelope volumeEnv, panningEnv; Uint8 vibratoType, vibratoSweep, vibratoDepth, vibratoRate; Uint16 fadeOut, ARESERVED; } XMInstrument2; typedef struct { Uint32 size, loopS, loopL; Uint8 volume; Sint8 fineTune; Uint8 type, panning; Sint8 relNote; Uint8 ARESERVED; char sampleName[22]; } XMSample; typedef struct { Uint32 headSize; Uint8 packing; Uint16 nrows, size; } XMPattern; /* Unpack XM-format pattern from file-stream into JSS-pattern structure */ #define JSGETBYTE(XV) \ do { \ if (size <= 0) \ JSSERROR(DMERR_OUT_OF_DATA, DMERR_OUT_OF_DATA, \ "Unexpected end of packed pattern data.\n"); \ size--; \ XV = dmfgetc(inFile); \ } while (0) /* Convert XM note value to internal JSS note */ static int jssXMConvertNote(const int val) { if (val < 1 || val > 97) return jsetNotSet; else if (val == 97) return jsetNoteOff; else return val - 1; } /* Unpack a XM pattern structure from resource to given JSS pattern */ static int jssXMUnpackPattern104(DMResource *inFile, int size, JSSPattern *pattern) { JSSNote *pnote = pattern->data; for (int row = 0; row < pattern->nrows && size > 0; row++) for (int channel = 0; channel < pattern->nchannels && size > 0; channel++) { int packb, tmp; JSGETBYTE(packb); if (packb & 0x80) { if (packb & 0x01) { // PACK 0x01: Read note JSGETBYTE(tmp); pnote->note = jssXMConvertNote(tmp); } if (packb & 0x02) { // PACK 0x02: Read instrument JSGETBYTE(tmp); pnote->instrument = (tmp > 0) ? tmp - 1 : jsetNotSet; } if (packb & 0x04) { // PACK 0x04: Read volume JSGETBYTE(tmp); pnote->volume = (tmp >= 0x10) ? tmp - 0x10 : jsetNotSet; } if (packb & 0x08) { // PACK 0x08: Read effect JSGETBYTE(pnote->effect); pnote->param = 0; } if (packb & 0x10) { // PACK 0x10: Read effect parameter JSGETBYTE(pnote->param); if (pnote->effect == jsetNotSet && pnote->param != 0) pnote->effect = 0; } } else { // All data available pnote->note = jssXMConvertNote(packb & 0x7f); // Get instrument JSGETBYTE(tmp); pnote->instrument = (tmp > 0) ? tmp - 1 : jsetNotSet; // Get volume JSGETBYTE(tmp); pnote->volume = (tmp >= 0x10) ? tmp - 0x10 : jsetNotSet; // Get effect JSGETBYTE(pnote->effect); // Get parameter JSGETBYTE(pnote->param); if (pnote->effect == 0 && pnote->param == 0) pnote->effect = pnote->param = jsetNotSet; } pnote++; } // Check the state if (size > 0) { // Some data left unparsed JSSWARNING(DMERR_EXTRA_DATA, DMERR_EXTRA_DATA, "Unparsed data after pattern (%d bytes), possibly broken file.\n", size); } return DMERR_OK; } static int jssXMUnpackPattern102(DMResource *inFile, int size, JSSPattern *pattern) { JSSNote *pnote = pattern->data; for (int row = 0; row < pattern->nrows && size > 0; row++) for (int channel = 0; channel < pattern->nchannels && size > 0; channel++) { int packb, tmp; JSGETBYTE(packb); if (packb & 0x80) { if (packb & 0x01) { // PACK 0x01: Read note JSGETBYTE(tmp); pnote->note = jssXMConvertNote(tmp); } if (packb & 0x02) { // PACK 0x02: Read instrument JSGETBYTE(tmp); pnote->instrument = (tmp > 0) ? tmp - 1 : jsetNotSet; } if (packb & 0x04) { // PACK 0x04: Read volume JSGETBYTE(tmp); pnote->volume = (tmp >= 0x10) ? tmp - 0x10 : jsetNotSet; } if (packb & 0x08) { // PACK 0x08: Read effect JSGETBYTE(pnote->effect); pnote->param = 0; } if (packb & 0x10) { // PACK 0x10: Read effect parameter JSGETBYTE(pnote->param); if (pnote->effect == jsetNotSet && pnote->param != 0) pnote->effect = 0; } } else { // All data available pnote->note = jssXMConvertNote(packb & 0x7f); // Get instrument JSGETBYTE(tmp); pnote->instrument = (tmp > 0) ? tmp - 1 : jsetNotSet; // Get volume JSGETBYTE(tmp); pnote->volume = (tmp >= 0x10) ? tmp - 0x10 : jsetNotSet; if (tmp & 0xc0) { JSGETBYTE(pnote->effect); JSGETBYTE(pnote->param); } else { JSGETBYTE(pnote->param); pnote->effect = tmp & 0xf; } if (pnote->effect == 0 && pnote->param == 0) pnote->effect = pnote->param = jsetNotSet; } pnote++; } // Check the state if (size > 0) { // Some data left unparsed JSSWARNING(DMERR_EXTRA_DATA, DMERR_EXTRA_DATA, "Unparsed data after pattern (%d bytes), possibly broken file.\n", size); } return DMERR_OK; } static int jssXMLoadPatterns(DMResource *inFile, JSSModule *module, XMHeader *xmH) { for (int index = 0; index < module->npatterns; index++) { XMPattern xmP; off_t remainder, pos = dmftell(inFile); Uint32 headSize = 0; BOOL ret = FALSE; int res; // Get the pattern header size and packing if (!dmf_read_le32(inFile, &xmP.headSize) || !dmf_read_byte(inFile, &xmP.packing)) { JSSERROR(DMERR_FREAD, DMERR_FREAD, "Could not read pattern header #%d.\n", index); } // Different format versions have slightly different headers if (module->intVersion == 0x0102) { Uint8 tmp; // 0x0102 has one byte number of rows ret = dmf_read_byte(inFile, &tmp); xmP.nrows = ((Uint16) tmp) + 1; headSize = 4 + 1 + 1 + 2; } else if (module->intVersion == 0x0104) { // 0x0104 has 16-bit word for nrows ret = dmf_read_le16(inFile, &xmP.nrows); headSize = 4 + 1 + 2 + 2; } // Check header size against known values if (headSize != xmP.headSize) { JSSERROR(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED, "Invalid pattern #%d header size %d, expected %d bytes.\n", index, xmP.headSize, headSize); } // Read rest of the header if (!ret || !dmf_read_le16(inFile, &xmP.size)) { JSSERROR(DMERR_FREAD, DMERR_FREAD, "Could not read pattern header data #%d.\n", index); } // Sanity-check rest of the header data if (xmP.packing != 0) { JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Pattern #%d packing type unsupported (%d)\n", index, xmP.packing); } if (xmP.nrows == 0) { JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Pattern #%d has %d rows, invalid data.\n", index, xmP.nrows); } if (xmP.size > 0) { // Allocate and unpack pattern module->patterns[index] = jssAllocatePattern(xmP.nrows, module->nchannels); if (module->patterns[index] == NULL) JSSERROR(DMERR_MALLOC, DMERR_MALLOC, "Could not allocate memory for pattern #%d\n", index); switch (module->intVersion) { case 0x0104: res = jssXMUnpackPattern104(inFile, xmP.size, module->patterns[index]); break; case 0x0102: res = jssXMUnpackPattern102(inFile, xmP.size, module->patterns[index]); break; } if (res != DMERR_OK) JSSERROR(res, res, "Error in unpacking pattern #%d data\n", index); } // Skip extra data if there is any .. shouldn't usually happen tho. remainder = xmP.headSize - (dmftell(inFile) - pos); if (remainder > 0) { JSSDEBUG("xmP Skipping: %li\n", remainder); dmfseek(inFile, remainder, SEEK_CUR); } } // Allocate the empty pattern module->patterns[jsetMaxPatterns] = jssAllocatePattern(64, module->nchannels); /* Convert song orders list by replacing nonexisting * pattern numbers with pattern number jsetMaxPatterns. */ for (int index = 0; index < module->norders; index++) { int tmp = xmH->orderList[index]; if (tmp >= module->npatterns || module->patterns[tmp] == NULL) tmp = jsetMaxPatterns; module->orderList[index] = tmp; } return DMERR_OK; } /* Convert XM envelope structure to JSS envelope structure */ static int jssXMConvertEnvelope( JSSEnvelope *dst, XMEnvelope *src, const char *name, const int ninstr) { (void) name; (void) ninstr; // Convert envelope points for (int i = 0; i < XM_MaxEnvPoints; i++) { dst->points[i].frame = src->points[i].frame; dst->points[i].value = src->points[i].value; } // Convert other values dst->npoints = src->npoints; dst->sustain = src->sustain; dst->loopS = src->loopS; dst->loopE = src->loopE; // Check if the envelope is used if (src->flags & 0x01) { // Convert envelope flags dst->flags = jenvfUsed; if (src->flags & 0x02) dst->flags |= jenvfSustain; if (src->flags & 0x04) dst->flags |= jenvfLooped; if (src->flags & 0xf0) { JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Inst#%d/%s-env: Uses unsupported flags values, 0x%02x.\n", ninstr, name, src->flags); } // Check other values if (src->npoints > XM_MaxEnvPoints) { JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Inst#%d/%s-env: npoints > MAX, possibly broken file.\n", ninstr, name); dst->npoints = XM_MaxEnvPoints; } if ((dst->flags & jenvfSustain) && src->sustain > src->npoints) { JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Inst#%d/%s-env: sustain > npoints (%d > %d), possibly broken file.\n", ninstr, name, src->sustain, src->npoints); dst->sustain = src->npoints; } if ((dst->flags & jenvfLooped) && src->loopE > src->npoints) { JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Inst#%d/%s-env: loopE > npoints (%d > %d), possibly broken file.\n", ninstr, name, src->loopE, src->npoints); dst->loopE = src->npoints; } if ((dst->flags & jenvfLooped) && src->loopS > src->loopE) { JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Inst#%d/%s-env: loopS > loopE (%d > %d), possibly broken file.\n", ninstr, name, src->loopS, src->loopE); dst->loopS = src->loopE; } } return DMERR_OK; } static int jssXMLoadSampleInstrument( DMResource *inFile, JSSModule *module, JSSExtInstrument *einst, int neinst, int nsample) { XMSample xmS; JSSInstrument *inst; (void) neinst; // Read header data if (!dmf_read_le32(inFile, &xmS.size) || !dmf_read_le32(inFile, &xmS.loopS) || !dmf_read_le32(inFile, &xmS.loopL) || !dmf_read_byte(inFile, &xmS.volume) || !dmf_read_byte(inFile, (Uint8 *) &xmS.fineTune) || !dmf_read_byte(inFile, &xmS.type) || !dmf_read_byte(inFile, &xmS.panning) || !dmf_read_byte(inFile, (Uint8 *) &xmS.relNote) || !dmf_read_byte(inFile, &xmS.ARESERVED) || !dmf_read_str(inFile, &xmS.sampleName, sizeof(xmS.sampleName))) { JSSERROR(DMERR_FREAD, DMERR_FREAD, "Error reading instrument sample header #%d/%d [%d]\n", neinst, nsample, module->ninstruments); } if (xmS.size <= 0) return DMERR_OK; if (xmS.size > 16 * 1024 * 1024) { JSSERROR(DMERR_FREAD, DMERR_FREAD, "Sample instrument #%d/%d [%d] too large, %d bytes.\n", neinst, nsample, module->ninstruments, xmS.size); } // Allocate sample instrument JSSDEBUG("Allocating sample #%d/%d [%d]\n", neinst, nsample, module->ninstruments); einst->instConvTable[nsample] = module->ninstruments; inst = module->instruments[module->ninstruments] = jssAllocateInstrument(); if (inst == NULL) { JSSERROR(DMERR_MALLOC, DMERR_MALLOC, "Could not allocate sample #%d/%d [%d]\n", neinst, nsample, module->ninstruments); } module->ninstruments++; // Copy values if (xmS.volume > XM_MaxSampleVolume) { JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Samp #%d/%d: volume > MAX\n", neinst, nsample); xmS.volume = XM_MaxSampleVolume; } inst->volume = xmS.volume; inst->ERelNote = xmS.relNote; inst->EFineTune = xmS.fineTune; inst->EPanning = xmS.panning; #ifndef JSS_LIGHT inst->desc = jssASCIItoStr(xmS.sampleName, 0, sizeof(xmS.sampleName)); #endif // Convert flags switch (xmS.type & 0x03) { case 0: inst->flags = 0; break; case 1: inst->flags = jsfLooped; break; default: // Basically 2 is the value for bidi-loop, and // 3 is undefined, but some module writers might've // assumed that this is a bit-field, e.g. 1 | 2 = 3 inst->flags = jsfLooped | jsfBiDi; break; break; } if (xmS.type & 0x10) { // 16-bit sample JSFSET(inst->flags, jsf16bit); inst->size = xmS.size / sizeof(Uint16); inst->loopS = xmS.loopS / sizeof(Uint16); inst->loopE = (xmS.loopS + xmS.loopL) / sizeof(Uint16); if ((xmS.size & 1) || (xmS.loopS & 1) || (xmS.loopL & 1)) { JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Samp #%d/%d: size=%d, loopS=%d or loopL=%d not divisible by 2 for 16-bit sample.\n", neinst, nsample, xmS.size, xmS.loopS, xmS.loopL); } } else { // 8-bit sample inst->size = xmS.size; inst->loopS = xmS.loopS; inst->loopE = xmS.loopS + xmS.loopL; } if (xmS.loopL == 0) { // Always unset loop, if loop length is zero JSFUNSET(inst->flags, jsfLooped); } if (inst->flags & jsfLooped) { if (inst->loopS >= inst->size) { JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Samp #%d/%d: loopS >= size (%d >= %d)\n", neinst, nsample, inst->loopS, inst->size); JSFUNSET(inst->flags, jsfLooped); } if (inst->loopE > inst->size) { JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Samp #%d/%d: loopE > size (%d > %d)\n", neinst, nsample, inst->loopE, inst->size); JSFUNSET(inst->flags, jsfLooped); } } return DMERR_OK; } static int jssXMLoadSampleData(DMResource *inFile, JSSInstrument *inst, int ninst, int nsample) { int ret; size_t bsize; (void) ninst; (void) nsample; JSSDEBUG( "desc....: '%s'\n" "size....: %d\n" "loopS...: %d\n" "loopE...: %d\n" "volume..: %d\n" "flags...: %x\n", inst->desc, inst->size, inst->loopS, inst->loopE, inst->volume, inst->flags); // Allocate memory for sample data bsize = (inst->flags & jsf16bit) ? sizeof(Uint16) : sizeof(Uint8); bsize *= inst->size; if ((inst->data = dmMalloc(bsize)) == NULL) { JSSERROR(DMERR_MALLOC, DMERR_MALLOC, "Could not allocate %d bytes of sample data for instrument/sample #%d/%d.\n", bsize, ninst, nsample); } // Read sampledata if (!dmf_read_str(inFile, inst->data, bsize)) { JSSERROR(DMERR_FREAD, DMERR_FREAD, "Error reading sample data for instrument #%d/%d, %d bytes.\n", ninst, nsample, bsize); } // Convert the sample data if (inst->flags & jsf16bit) { ret = jssDecodeSample16( (Uint16 *) inst->data, inst->size, #if (SDL_BYTEORDER == SDL_BIG_ENDIAN) (jsampDelta | jsampSwapEndianess) #else (jsampDelta) #endif ); } else { ret = jssDecodeSample8( (Uint8 *) inst->data, inst->size, (jsampDelta | jsampFlipSign)); } return ret; } static int jssXMLoadInstrumentSamples( DMResource *inFile, JSSModule *module, JSSExtInstrument *einst, int neinst) { int nsample, ret; for (nsample = 0; nsample < einst->nsamples; nsample++) if (einst->instConvTable[nsample] != jsetNotSet) { JSSInstrument *inst = module->instruments[einst->instConvTable[nsample]]; if ((ret = jssXMLoadSampleData(inFile, inst, neinst, nsample)) != DMERR_OK) return ret; } return DMERR_OK; } static BOOL jssXMLoadEnvelopePoints(DMResource *inFile, XMEnvelope *env) { int i; for (i = 0; i < XM_MaxEnvPoints; i++) { if (!dmf_read_le16(inFile, &(env->points[i].frame)) || !dmf_read_le16(inFile, &(env->points[i].value))) return FALSE; } return TRUE; } static BOOL jssXMLoadEnvelopeData(DMResource *inFile, XMEnvelope *env) { return dmf_read_byte(inFile, &(env->sustain)) && dmf_read_byte(inFile, &(env->loopS)) && dmf_read_byte(inFile, &(env->loopE)); } /* Load XM-format extended instrument from file-stream into JSS module's given inst */ static int jssXMLoadExtInstrument(DMResource *inFile, int neinst, JSSModule *module) { XMInstrument1 xmI1; off_t remainder, pos = dmftell(inFile); JSSExtInstrument *einst; XMInstrument2 xmI2; int i, nsample, ret; // Get instrument header #1 if (!dmf_read_le32(inFile, &xmI1.headSize) || !dmf_read_str(inFile, &xmI1.instName, sizeof(xmI1.instName)) || !dmf_read_byte(inFile, &xmI1.instType) || !dmf_read_le16(inFile, &xmI1.nsamples)) { JSSERROR(DMERR_FREAD, DMERR_FREAD, "Failed to read primary extended instrument header #%d.\n", neinst); } // If there are samples, there is header #2 if (xmI1.nsamples == 0) { // We may STILL need to skip extra data after 1st instr. header remainder = xmI1.headSize - (dmftell(inFile) - pos); if (remainder > 0) { JSSDEBUG("xmI1#2 Skipping: %li\n", remainder); dmfseek(inFile, remainder, SEEK_CUR); } return DMERR_OK; } if (xmI1.nsamples > XM_MaxInstSamples) { JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Extended instrument #%d has invalid number of samples %d.\n", neinst, xmI1.nsamples); } // Allocate instrument JSSDEBUG("Allocating extended instrument #%d [%d]\n", neinst, module->nextInstruments); if ((einst = jssAllocateExtInstrument()) == NULL) { JSSERROR(DMERR_MALLOC, DMERR_MALLOC, "Could not allocate extended instrument #%d.\n", neinst); } module->extInstruments[neinst] = einst; // Get instrument header #2 if (!dmf_read_le32(inFile, &xmI2.headSize) || !dmf_read_str(inFile, &xmI2.sNumForNotes, sizeof(xmI2.sNumForNotes))) { JSSERROR(DMERR_FREAD, DMERR_FREAD, "Could not read secondary instrument header part #1 for #%d.\n", neinst); } if (!jssXMLoadEnvelopePoints(inFile, &xmI2.volumeEnv) || !jssXMLoadEnvelopePoints(inFile, &xmI2.panningEnv)) { JSSERROR(DMERR_FREAD, DMERR_FREAD, "Could not read envelope point data for instrument #%d.\n", neinst); } if (!dmf_read_byte(inFile, &xmI2.volumeEnv.npoints) || !dmf_read_byte(inFile, &xmI2.panningEnv.npoints) || !jssXMLoadEnvelopeData(inFile, &xmI2.volumeEnv) || !jssXMLoadEnvelopeData(inFile, &xmI2.panningEnv) || !dmf_read_byte(inFile, &xmI2.volumeEnv.flags) || !dmf_read_byte(inFile, &xmI2.panningEnv.flags) || !dmf_read_byte(inFile, &xmI2.vibratoType) || !dmf_read_byte(inFile, &xmI2.vibratoSweep) || !dmf_read_byte(inFile, &xmI2.vibratoDepth) || !dmf_read_byte(inFile, &xmI2.vibratoRate) || !dmf_read_le16(inFile, &xmI2.fadeOut) || !dmf_read_le16(inFile, &xmI2.ARESERVED)) { JSSERROR(DMERR_FREAD, DMERR_FREAD, "Could not read secondary instrument header part #2 for #%d.\n", neinst); } // Skip the extra data after header #2 remainder = xmI1.headSize - (dmftell(inFile) - pos); if (remainder > 0) { JSSDEBUG("xmI1#1 Skipping: %li\n", remainder); dmfseek(inFile, remainder, SEEK_CUR); } // Check and convert all ext instrument information #ifndef JSS_LIGHT einst->desc = jssASCIItoStr(xmI1.instName, 0, sizeof(xmI1.instName)); #endif jssXMConvertEnvelope(&einst->volumeEnv, &xmI2.volumeEnv, "vol", neinst); jssXMConvertEnvelope(&einst->panningEnv, &xmI2.panningEnv, "pan", neinst); switch (xmI2.vibratoType) { case 0: einst->vibratoType = jvibSine; break; case 1: einst->vibratoType = jvibRamp; break; case 2: einst->vibratoType = jvibSquare; break; case 3: einst->vibratoType = jvibRandom; break; default: einst->vibratoType = jvibSine; JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Invalid extinstrument vibrato type %d for inst #%d\n", neinst); break; } einst->vibratoSweep = xmI2.vibratoSweep; einst->vibratoDepth = xmI2.vibratoDepth; einst->vibratoRate = xmI2.vibratoRate; einst->fadeOut = xmI2.fadeOut; einst->nsamples = xmI1.nsamples; // Initialize the SNumForNotes conversion table if ((einst->instConvTable = dmCalloc(XM_MaxInstruments + 1, sizeof(int))) == NULL) { JSSERROR(DMERR_MALLOC, DMERR_MALLOC, "Could not allocate memory for instConvTable of instrument #%d.\n", neinst); } for (i = 0; i < XM_MaxInstruments; i++) einst->instConvTable[i] = jsetNotSet; // Read sample headers for (nsample = 0; nsample < einst->nsamples; nsample++) { if ((ret = jssXMLoadSampleInstrument(inFile, module, einst, neinst, nsample)) != DMERR_OK) return ret; } // Apply new values to sNumForNotes values for (i = 0; i < XM_MaxNotes; i++) { int tmp = xmI2.sNumForNotes[i]; if (tmp >= 0 && tmp < xmI1.nsamples) einst->sNumForNotes[i] = einst->instConvTable[tmp]; else { einst->sNumForNotes[i] = jsetNotSet; JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Ext.instrument #%d sNumForNotes[%d] out of range (%d).\n", neinst, i, tmp); } } // Read sample data if needed if (module->intVersion == 0x0104) { if ((ret = jssXMLoadInstrumentSamples(inFile, module, einst, neinst)) != DMERR_OK) return ret; } return DMERR_OK; } static int jssXMLoadInstruments(DMResource *inFile, JSSModule *module) { int index; for (index = 0; index < module->nextInstruments; index++) { int result = jssXMLoadExtInstrument(inFile, index, module); if (result != 0) JSSERROR(result, result, "Errors while reading instrument #%d\n", index); } return DMERR_OK; } /* Load XM-format module from given file-stream */ int jssLoadXM(DMResource *inFile, JSSModule **pmodule, BOOL probe) { JSSModule *module; XMHeader xmH; int index, ret = DMERR_OK; *pmodule = NULL; // Try to read the XM header if (!dmf_read_str(inFile, &xmH.idMagic, sizeof(xmH.idMagic)) || !dmf_read_str(inFile, &xmH.songName, sizeof(xmH.songName)) || !dmf_read_byte(inFile, &xmH.unUsed1A) || !dmf_read_str(inFile, &xmH.trackerName, sizeof(xmH.trackerName)) || !dmf_read_le16(inFile, &xmH.version) || !dmf_read_le32(inFile, &xmH.headSize) || !dmf_read_le16(inFile, &xmH.norders) || !dmf_read_le16(inFile, &xmH.defRestartPos) || !dmf_read_le16(inFile, &xmH.nchannels) || !dmf_read_le16(inFile, &xmH.npatterns) || !dmf_read_le16(inFile, &xmH.ninstruments) || !dmf_read_le16(inFile, &xmH.flags) || !dmf_read_le16(inFile, &xmH.defSpeed) || !dmf_read_le16(inFile, &xmH.defTempo)) return DMERR_FREAD; // Check the fields, none of these are considered fatal if (strncmp(xmH.idMagic, "Extended Module: ", 17) != 0) { if (probe) return DMERR_NOT_SUPPORTED; else JSSERROR(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED, "Not a FT2 Extended Module (XM), header signature mismatch!\n"); } if (xmH.unUsed1A != 0x1a) { if (!probe) JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Possibly modified or corrupted XM [%x]\n", xmH.unUsed1A); } if (xmH.norders > XM_MaxOrders) { if (!probe) JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Number of orders %d > %d, possibly broken module.\n", xmH.norders, XM_MaxOrders); } if (xmH.norders == 0) { if (!probe) JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Number of orders was zero.\n"); } if (xmH.npatterns > XM_MaxPatterns) { if (!probe) JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Number of patterns %d > %d, possibly broken module.\n", xmH.npatterns, XM_MaxPatterns); } if (xmH.npatterns == 0) { if (!probe) JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Number of patterns was zero.\n"); } if (xmH.nchannels <= 0 || xmH.nchannels > XM_MaxChannels) { if (!probe) JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Number of channels was invalid, %d (should be 1 - %d).\n", xmH.nchannels, XM_MaxChannels); } if (xmH.ninstruments <= 0 || xmH.ninstruments > XM_MaxInstruments) { if (!probe) JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Number of instruments was invalid, %d (should be 1 - %d).\n", xmH.ninstruments, XM_MaxInstruments); } if (xmH.headSize < XM_HeaderSize) { if (probe) return DMERR_NOT_SUPPORTED; else JSSERROR(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED, "XM header size less than %d bytes (%d bytes).\n", XM_HeaderSize, xmH.headSize); } switch (xmH.version) { case 0x0104: case 0x0102: break; default: if (!probe) JSSWARNING(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED, "Unsupported version of XM format 0x%04x.\n", xmH.version); } if (!dmf_read_str(inFile, &xmH.orderList, sizeof(xmH.orderList))) { JSSERROR(DMERR_FREAD, DMERR_FREAD, "Error reading pattern order list.\n"); } if (xmH.headSize > 276) { JSSWARNING(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED, "XM header size > %d bytes, skipping the rest.\n", XM_HeaderSize, xmH.headSize); dmfseek(inFile, xmH.headSize - XM_HeaderSize, SEEK_CUR); } if (probe) return DMERR_OK; // Okay, allocate a module structure if ((*pmodule = module = jssAllocateModule()) == NULL) { JSSERROR(DMERR_MALLOC, DMERR_MALLOC, "Could not allocate memory for module structure.\n"); } // Convert the module header data module->moduleType = jmdtXM; module->intVersion = xmH.version; #ifndef JSS_LIGHT module->moduleName = jssASCIItoStr(xmH.songName, 0, sizeof(xmH.songName)); module->trackerName = jssASCIItoStr(xmH.trackerName, 0, sizeof(xmH.trackerName)); #endif module->defSpeed = xmH.defSpeed; module->defTempo = xmH.defTempo; module->nextInstruments = xmH.ninstruments; module->ninstruments = 0; module->npatterns = xmH.npatterns; module->norders = xmH.norders; module->nchannels = xmH.nchannels; module->defFlags = jmdfStereo | jmdfFT2Replay; module->defRestartPos = xmH.defRestartPos; if ((xmH.flags & 1) == 0) module->defFlags |= jmdfAmigaPeriods; // Setup the default pannings for (index = 0; index < module->nchannels; index++) module->defPanning[index] = jchPanMiddle; // Load rest of the module switch (xmH.version) { case 0x0104: if ((ret = jssXMLoadPatterns(inFile, module, &xmH)) != DMERR_OK) goto out; if ((ret = jssXMLoadInstruments(inFile, module)) != DMERR_OK) goto out; break; case 0x0102: if ((ret = jssXMLoadInstruments(inFile, module)) != DMERR_OK) goto out; if ((ret = jssXMLoadPatterns(inFile, module, &xmH)) != DMERR_OK) goto out; // Read sample data if needed for (index = 0; index < module->nextInstruments; index++) { JSSExtInstrument *einst = module->extInstruments[index]; if (einst != NULL && (ret = jssXMLoadInstrumentSamples(inFile, module, einst, index)) != DMERR_OK) goto out; } break; } out: return ret; }