Mercurial > hg > dmlib
view jloadxm.c @ 510:43ea59887c69
Start work on making C64 formats encoding possible by changing DMDecodeOps
to DMEncDecOps and adding fields and op enums for custom encode functions, renaming,
etc. Split generic op sanity checking into a separate function in
preparation for its use in generic encoding function.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 19 Nov 2012 15:06:01 +0200 |
parents | a89500f26dde |
children | d4cee32e7050 |
line wrap: on
line source
/* * 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 jssXMConvertNote(int val) { if (val < 1 || val > 97) return jsetNotSet; else if (val == 97) return jsetNoteOff; else return val - 1; } 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); 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 (%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 for (i = 0; i < XM_MaxEnvPoints; i++) { d->points[i].frame = s->points[i].frame; d->points[i].value = s->points[i].value; } // Convert other values d->npoints = s->npoints; d->sustain = s->sustain; d->loopS = s->loopS; d->loopE = s->loopE; // 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("xmI1#1 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("xmI1#2 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, remainder; XMPattern xmP; // Get the pattern header pos = dmftell(inFile); dmf_read_le32(inFile, &xmP.headSize); xmP.packing = dmfgetc(inFile); dmf_read_le16(inFile, &xmP.nrows); dmf_read_le16(inFile, &xmP.size); // 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) 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 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; }