Mercurial > hg > dmlib
view minijss/jloadxm.c @ 2337:8f4cfe59b2bb
Comment.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 23 Sep 2019 11:11:33 +0300 |
parents | b47109fce375 |
children | e0f9200b94ad |
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(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; int row, channel; assert(pattern != NULL); pnote = pattern->data; for (row = 0; row < pattern->nrows && size > 0; row++) for (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; int row, channel; assert(pattern != NULL); pnote = pattern->data; for (row = 0; row < pattern->nrows && size > 0; row++) for (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) { int index, ret; XMPattern xmP; for (index = 0; index < module->npatterns; index++) { off_t remainder, pos = dmftell(inFile); Uint32 headSize; // 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 { // 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: ret = jssXMUnpackPattern104(inFile, xmP.size, module->patterns[index]); break; case 0x0102: ret = jssXMUnpackPattern102(inFile, xmP.size, module->patterns[index]); break; } if (ret != 0) JSSERROR(ret, ret, "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[module->npatterns] = jssAllocatePattern(64, module->nchannels); /* Convert song orders list by replacing nonexisting * pattern numbers with pattern number jsetMaxPatterns. */ for (index = 0; index < module->norders; index++) { int tmp = xmH->orderList[index]; if (tmp >= module->npatterns || module->patterns[tmp] == NULL) tmp = module->npatterns; 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) { int i; (void) name; (void) ninstr; // Convert envelope points for (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 (dmfread(inst->data, sizeof(Uint8), bsize, inFile) != 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; }