Mercurial > hg > dmlib
diff jloadxm.c @ 0:32250b436bca
Initial re-import.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Fri, 28 Sep 2012 01:54:23 +0300 |
parents | |
children | aa9fbdbcea70 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jloadxm.c Fri Sep 28 01:54:23 2012 +0300 @@ -0,0 +1,791 @@ +/* + * miniJSS - Fast Tracker ][ (XM) module loader + * Programmed and designed by Matti 'ccr' Hamalainen + * (C) Copyright 2006-2007 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" +#include <string.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) + + +/* 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 0x0104 + 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; +} xm_envpoint_t; + + +typedef struct +{ + Uint8 flags, npoints, sustain, loopS, loopE; + xm_envpoint_t points[XM_MaxEnvPoints]; +} xm_envelope_t; + + +typedef struct +{ + Uint32 headSize; // Header size + Uint8 sNumForNotes[XM_MaxNotes]; // Sample numbers for notes + xm_envelope_t volumeEnv, panningEnv; + Uint8 vibratoType, vibratoSweep, vibratoDepth, vibratoRate; + + Uint16 fadeOut, ARESERVED; +} XMInstrument2; + + +typedef struct +{ + Uint32 size, loopS, loopL; + Uint8 volume; + int fineTune; + Uint8 type, panning; + int 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 { \ + size--; \ + if (size < 0) \ + JSSERROR(DMERR_OUT_OF_DATA, DMERR_OUT_OF_DATA, \ + "Unexpected end of packed pattern data.\n"); \ + XV = dmfgetc(inFile); \ +} while (0) + + +static int jssXMUnpackPattern(DMResource *inFile, int size, JSSPattern *pattern) +{ + int packb, row, channel, tmp; + JSSNote *pnote; + assert(pattern != NULL); + + pnote = pattern->data; + + for (row = 0; (row < pattern->nrows) && (size > 0); row++) + for (channel = 0; (channel < pattern->nchannels) && (size > 0); channel++) + { + JSGETBYTE(packb); + if (packb & 0x80) + { + if (packb & 0x01) + { + // PACK 0x01: Read note + JSGETBYTE(tmp); + if (tmp < 1 || tmp > 97) + pnote->note = jsetNotSet; + else if (tmp == 97) + pnote->note = jsetNoteOff; + else + pnote->note = tmp - 1; + } + + 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 + tmp = (packb & 0x7f); + + if (tmp < 1 || tmp > 97) + pnote->note = jsetNotSet; + else if (tmp == 97) + pnote->note = jsetNoteOff; + else + pnote->note = tmp - 1; + + // 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 (%i bytes), possibly broken file.\n", size); + } + + return DMERR_OK; +} + + +/* Convert XM envelope structure to JSS envelope structure + */ +static int jssXMConvertEnvelope(JSSEnvelope * d, xm_envelope_t * s, char * e, int instr) +{ + int i; + (void) e; (void) instr; + + // Convert envelope points + d->points[0].frame = s->points[0].frame; + d->points[0].value = s->points[0].value; + for (i = 0; i < XM_MaxEnvPoints; i++) + { + d->points[i + 1].frame = s->points[i].frame + 1; + d->points[i + 1].value = s->points[i].value; + } + + // Convert other values + d->npoints = s->npoints + 1; + d->sustain = s->sustain + 1; + d->loopS = s->loopS + 1; + d->loopE = s->loopE + 1; + + // Check if the envelope is used + if (s->flags & 0x01) + { + // Convert envelope flags + d->flags = jenvfUsed; + if (s->flags & 0x02) + d->flags |= jenvfSustain; + + if (s->flags & 0x04) + d->flags |= jenvfLooped; + + // Check other values + if (s->npoints > XM_MaxEnvPoints) + { + JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Inst#%i/%s-env: nPoints > MAX, possibly broken file.\n", instr, e); + s->npoints = XM_MaxEnvPoints; + } + + if ((d->flags & jenvfSustain) && s->sustain > s->npoints) + { + JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Inst#%i/%s-env: iSustain > nPoints (%i > %i), possibly broken file.\n", + instr, e, s->sustain, s->npoints); + s->sustain = s->npoints; + } + + if ((d->flags & jenvfLooped) && s->loopE > s->npoints) + { + JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Inst#%i/%s-env: loopE > nPoints (%i > %i), possibly broken file.\n", + instr, e, s->loopE, s->npoints); + s->loopE = s->npoints; + } + + if ((d->flags & jenvfLooped) && s->loopS > s->loopE) + { + JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Inst#%i/%s-env: loopS > loopE (%i > %i), possibly broken file.\n", + instr, e, s->loopS, s->loopE); + s->loopS = 0; + } + } + + return DMERR_OK; +} + + +/* Load XM-format extended instrument from file-stream into JSS module's given inst + */ +static int jssXMLoadExtInstrument(DMResource *inFile, int ninst, JSSModule *module) +{ + XMInstrument1 xmI1; + off_t pos, remainder; + + // Get instrument header #1 + pos = dmftell(inFile); + dmf_read_le32(inFile, &xmI1.headSize); + dmf_read_str(inFile, (Uint8 *) &xmI1.instName, sizeof(xmI1.instName)); + xmI1.instType = dmfgetc(inFile); + dmf_read_le16(inFile, &xmI1.nsamples); + + // If there are samples, there is header #2 + if (xmI1.nsamples > 0) + { + int i, nsample, tmp; + int xmConvTable[XM_MaxInstruments + 1]; + JSSExtInstrument *pEInst; + JSSInstrument *pInst; + XMInstrument2 xmI2; + + // Allocate instrument + if ((pEInst = jssAllocateExtInstrument()) == NULL) + { + JSSERROR(DMERR_MALLOC, DMERR_MALLOC, + "Could not allocate extended instrument structure #%i\n", ninst); + } + + module->extInstruments[ninst] = pEInst; + + // Get instrument header #2 + dmf_read_le32(inFile, &xmI2.headSize); + dmf_read_str(inFile, (Uint8 *) &xmI2.sNumForNotes, sizeof(xmI2.sNumForNotes)); + + for (i = 0; i < XM_MaxEnvPoints; i++) + { + dmf_read_le16(inFile, &xmI2.volumeEnv.points[i].frame); + dmf_read_le16(inFile, &xmI2.volumeEnv.points[i].value); + } + + for (i = 0; i < XM_MaxEnvPoints; i++) + { + dmf_read_le16(inFile, &xmI2.panningEnv.points[i].frame); + dmf_read_le16(inFile, &xmI2.panningEnv.points[i].value); + } + + xmI2.volumeEnv.npoints = dmfgetc(inFile); + xmI2.panningEnv.npoints = dmfgetc(inFile); + + xmI2.volumeEnv.sustain = dmfgetc(inFile); + xmI2.volumeEnv.loopS = dmfgetc(inFile); + xmI2.volumeEnv.loopE = dmfgetc(inFile); + + xmI2.panningEnv.sustain = dmfgetc(inFile); + xmI2.panningEnv.loopS = dmfgetc(inFile); + xmI2.panningEnv.loopE = dmfgetc(inFile); + + xmI2.volumeEnv.flags = dmfgetc(inFile); + xmI2.panningEnv.flags = dmfgetc(inFile); + + xmI2.vibratoType = dmfgetc(inFile); + xmI2.vibratoSweep = dmfgetc(inFile); + xmI2.vibratoDepth = dmfgetc(inFile); + xmI2.vibratoRate = dmfgetc(inFile); + + dmf_read_le16(inFile, &xmI2.fadeOut); + dmf_read_le16(inFile, &xmI2.ARESERVED); + + // Skip the extra data after header #2 + remainder = xmI1.headSize - (dmftell(inFile) - pos); + if (remainder > 0) + { + JSSDEBUG("Skipping: %li\n", remainder); + dmfseek(inFile, remainder, SEEK_CUR); + } + + // Check and convert all ext instrument information +#ifndef JSS_LIGHT + pEInst->desc = jssASCIItoStr(xmI1.instName, 0, sizeof(xmI1.instName)); +#endif + jssXMConvertEnvelope(&pEInst->volumeEnv, &xmI2.volumeEnv, "vol", ninst); + jssXMConvertEnvelope(&pEInst->panningEnv, &xmI2.panningEnv, "pan", ninst); + + switch (xmI2.vibratoType) + { + case 0: pEInst->vibratoType = jvibSine; break; + case 1: pEInst->vibratoType = jvibRamp; break; + case 2: pEInst->vibratoType = jvibSquare; break; + case 3: pEInst->vibratoType = jvibRandom; break; + default: + pEInst->vibratoType = jvibSine; + JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Invalid extinstrument vibrato type %d for inst #%d\n", ninst); + break; + } + pEInst->vibratoSweep = xmI2.vibratoSweep; + pEInst->vibratoDepth = xmI2.vibratoDepth; + pEInst->vibratoRate = xmI2.vibratoRate; + pEInst->fadeOut = xmI2.fadeOut; + pEInst->nsamples = xmI1.nsamples; + + // Initialize the SNumForNotes conversion table + for (i = 0; i < XM_MaxInstruments; i++) + xmConvTable[i] = jsetNotSet; + + // Read sample headers + for (nsample = 0; nsample < xmI1.nsamples; nsample++) + { + XMSample xmS; + + // Read header data + dmf_read_le32(inFile, &xmS.size); + dmf_read_le32(inFile, &xmS.loopS); + dmf_read_le32(inFile, &xmS.loopL); + xmS.volume = dmfgetc(inFile); + xmS.fineTune = (signed char) dmfgetc(inFile); + xmS.type = dmfgetc(inFile); + xmS.panning = dmfgetc(inFile); + xmS.relNote = (signed char) dmfgetc(inFile); + xmS.ARESERVED = dmfgetc(inFile); + dmf_read_str(inFile, (Uint8 *) &xmS.sampleName, sizeof(xmS.sampleName)); + + if (xmS.size > 0) + { + // Allocate sample instrument + JSSDEBUG("Allocating sample #%i/%i [%i]\n", + ninst, nsample, module->ninstruments); + + xmConvTable[nsample] = module->ninstruments; + pInst = module->instruments[module->ninstruments] = jssAllocateInstrument(); + if (pInst == NULL) + { + JSSERROR(DMERR_MALLOC, DMERR_MALLOC, + "Could not allocate sample #%i/%i [%i]\n", + ninst, nsample, module->ninstruments); + } + module->ninstruments++; + } else + pInst = NULL; + + // Check and convert sample information + if (pInst != NULL) + { + // Copy values + if (xmS.volume > XM_MaxSampleVolume) + { + JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Samp #%i/%i: volume > MAX\n", ninst, nsample); + xmS.volume = XM_MaxSampleVolume; + } + + pInst->volume = xmS.volume; + pInst->ERelNote = xmS.relNote; + pInst->EFineTune = xmS.fineTune; + pInst->EPanning = xmS.panning; +#ifndef JSS_LIGHT + pInst->desc = jssASCIItoStr(xmS.sampleName, 0, sizeof(xmS.sampleName)); +#endif + + // Convert flags + switch (xmS.type & 0x03) + { + case 0: pInst->flags = 0; break; + case 1: pInst->flags = jsfLooped; break; + case 2: pInst->flags = jsfLooped | jsfBiDi; break; + default: + pInst->flags = 0; + JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Samp #%i/%i: Invalid sample type 0x%x\n", + ninst, nsample, xmS.type); + break; + } + + if (xmS.type & 0x10) + { + // 16-bit sample + JSFSET(pInst->flags, jsf16bit); + + pInst->size = xmS.size / sizeof(Uint16); + pInst->loopS = xmS.loopS / sizeof(Uint16); + pInst->loopE = ((xmS.loopS + xmS.loopL) / sizeof(Uint16)); + } + else + { + // 8-bit sample + pInst->size = xmS.size; + pInst->loopS = xmS.loopS; + pInst->loopE = (xmS.loopS + xmS.loopL); + } + + if (xmS.loopL == 0) + { + // Always unset loop, if loop length is zero + JSFUNSET(pInst->flags, jsfLooped); + } + + if (pInst->flags & jsfLooped) + { + if (pInst->loopS >= pInst->size) + { + JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Samp #%i/%i: loopS >= size (%d >= %d)\n", + ninst, nsample, pInst->loopS, pInst->size); + JSFUNSET(pInst->flags, jsfLooped); + } + + if (pInst->loopE > pInst->size) + { + JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Samp #%i/%i: loopE > size (%d > %d)\n", + ninst, nsample, pInst->loopE, pInst->size); + JSFUNSET(pInst->flags, jsfLooped); + } + } + + + // Allocate memory for sample data + if (pInst->flags & jsf16bit) + pInst->data = dmCalloc(pInst->size, sizeof(Uint16)); + else + pInst->data = dmCalloc(pInst->size, sizeof(Uint8)); + + if (pInst->data == NULL) + JSSERROR(DMERR_MALLOC, DMERR_MALLOC, + "Could not allocate sample data #%i/%i.\n", ninst, nsample); + } + } + + // Read sample data + for (nsample = 0; nsample < xmI1.nsamples; nsample++) + if (xmConvTable[nsample] != jsetNotSet) + { + pInst = module->instruments[xmConvTable[nsample]]; + if (pInst) + { + JSSDEBUG("desc....: '%s'\n" + "size....: %i\n" + "loopS...: %i\n" + "loopE...: %i\n" + "volume..: %i\n" + "flags...: %x\n", + pInst->desc, + pInst->size, pInst->loopS, pInst->loopE, + pInst->volume, pInst->flags); + + if (pInst->flags & jsf16bit) + { + // Read sampledata + if (dmfread(pInst->data, sizeof(Uint16), pInst->size, inFile) != (size_t) pInst->size) + JSSERROR(DMERR_FREAD, DMERR_FREAD, + "Error reading sampledata for instrument #%i/%i, %i words.\n", + ninst, nsample, pInst->size); + + // Convert data + jssDecodeSample16((Uint16 *) pInst->data, pInst->size, +#if (SDL_BYTEORDER == SDL_BIG_ENDIAN) + (jsampDelta | jsampSwapEndianess) +#else + (jsampDelta) +#endif + ); + } + else + { + // Read sampledata + if (dmfread(pInst->data, sizeof(Uint8), pInst->size, inFile) != (size_t) pInst->size) + JSSERROR(DMERR_FREAD, DMERR_FREAD, + "Error reading sampledata for instrument #%i/%i, %i bytes.\n", + ninst, nsample, pInst->size); + + // Convert data + jssDecodeSample8((Uint8 *) pInst->data, pInst->size, + (jsampDelta | jsampFlipSign)); + } + } + } + + // Apply new values to sNumForNotes values + for (i = 0; i < XM_MaxNotes; i++) + { + tmp = xmI2.sNumForNotes[i]; + if (tmp >= 0 && tmp < xmI1.nsamples) + pEInst->sNumForNotes[i] = xmConvTable[tmp]; + else + { + pEInst->sNumForNotes[i] = jsetNotSet; + JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Ext.instrument #%d sNumForNotes[%d] out of range (%d).\n", + ninst, i, tmp); + } + } + } + else + { + // We may STILL need to skip extra data after 1st instr. header + remainder = xmI1.headSize - (dmftell(inFile) - pos); + if (remainder > 0) + { + JSSDEBUG("PPM! Skipping: %li\n", remainder); + dmfseek(inFile, remainder, SEEK_CUR); + } + } + + return 0; +} + + +/* Load XM-format module from given file-stream + */ +int jssLoadXM(DMResource *inFile, JSSModule **ppModule) +{ + JSSModule *module; + XMHeader xmH; + int result, index, tmp; + + assert(ppModule != NULL); + assert(inFile != NULL); + *ppModule = NULL; + + /* Get XM-header and check it + */ + dmf_read_str(inFile, (Uint8 *) &xmH.idMagic, sizeof(xmH.idMagic)); + dmf_read_str(inFile, (Uint8 *) &xmH.songName, sizeof(xmH.songName)); + xmH.unUsed1A = dmfgetc(inFile); + dmf_read_str(inFile, (Uint8 *) &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); + dmf_read_str(inFile, (Uint8 *)&xmH.orderList, sizeof(xmH.orderList)); + + + // Check the fields, none of these are considered fatal + if (strncmp(xmH.idMagic, "Extended Module: ", 17) != 0) + { + JSSERROR(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED, + "Not a FT2 Extended Module (XM), ident mismatch!\n"); + } + + if (xmH.version != 0x0104) + { + JSSWARNING(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED, + "Unsupported version of XM format 0x%04x instead of expected 0x0104.\n", + xmH.version); + } + + if (xmH.unUsed1A != 0x1a) + { + JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Possibly modified or corrupted XM [%x]\n", xmH.unUsed1A); + } + + if (xmH.norders > XM_MaxOrders) + { + JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Number of orders %i > %i, possibly broken module.\n", + xmH.norders, XM_MaxOrders); + xmH.norders = XM_MaxOrders; + } + + if (xmH.norders == 0) + { + JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Number of orders was zero.\n"); + } + + if (xmH.npatterns > XM_MaxPatterns) + { + JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Number of patterns %i > %i, possibly broken module.\n", + xmH.npatterns, XM_MaxPatterns); + xmH.npatterns = XM_MaxPatterns; + } + + if (xmH.npatterns == 0) + { + JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Number of patterns was zero.\n"); + } + + if (xmH.nchannels <= 0 || xmH.nchannels > XM_MaxChannels) + { + JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Number of channels was invalid, %i (should be 1 - %i).\n", + xmH.nchannels, XM_MaxChannels); + } + + if (xmH.ninstruments <= 0 || xmH.ninstruments > XM_MaxInstruments) + { + JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Number of instruments was invalid, %i (should be 1 - %i).\n", + xmH.ninstruments, XM_MaxInstruments); + } + + /* Okay, allocate a module structure + */ + module = jssAllocateModule(); + if (module == NULL) + { + JSSERROR(DMERR_MALLOC, DMERR_MALLOC, + "Could not allocate memory for module structure.\n"); + } + *ppModule = module; + + + // Convert and check the 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 < jsetNChannels; index++) + module->defPanning[index] = jchPanMiddle; + + /* Read patterns + */ + for (index = 0; index < module->npatterns; index++) + { + off_t pos; + XMPattern xmP; + + // Get the pattern header + dmf_read_le32(inFile, &xmP.headSize); + xmP.packing = dmfgetc(inFile); + dmf_read_le16(inFile, &xmP.nrows); + dmf_read_le16(inFile, &xmP.size); + pos = dmftell(inFile); + + // Check the header + if (xmP.packing != 0) + JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Pattern #%i packing type unsupported (%i)\n", + index, xmP.packing); + + if (xmP.nrows == 0) + JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Pattern #%i has %i 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 #%i\n", index); + + result = jssXMUnpackPattern(inFile, xmP.size, module->patterns[index]); + if (result != 0) + JSSERROR(result, result, "Error in unpacking pattern #%i data\n", index); + } + + // Skip extra data (if the file is damaged) + if (xmP.size > dmftell(inFile) - pos) + { + dmfseek(inFile, dmftell(inFile) - pos, SEEK_CUR); + } + } + + // Allocate the empty pattern + module->patterns[jsetMaxPatterns] = jssAllocatePattern(64, module->nchannels); + + /* Convert song orders list by replacing nonexisting patterns + * with pattern number jsetMaxPatterns. + */ + for (index = 0; index < module->norders; index++) + { + tmp = xmH.orderList[index]; + if (tmp >= module->npatterns || module->patterns[tmp] == NULL) + tmp = jsetMaxPatterns; + + module->orderList[index] = tmp; + } + + /* Read instruments + */ + for (index = 0; index < module->nextInstruments; index++) + { + result = jssXMLoadExtInstrument(inFile, index, module); + if (result != 0) + JSSERROR(result, result, "Errors while reading instrument #%i\n", index); + } + + return DMERR_OK; +}