Mercurial > hg > dmlib
view jloadxm.c @ 266:4ad2b9739c4a
Fix a stupid bug in the clipping.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Wed, 10 Oct 2012 13:30:07 +0300 |
parents | a65f0c3deaa7 |
children | a89500f26dde |
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 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 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; }