Mercurial > hg > dmlib
view minijss/jloadjss.c @ 1896:f80b2dc77c30
Work begins on IFF ILBM/PBM image writer. It is pretty broken, some things
will not work and some things are hardcoded. The ByteRun1 compression
implementation is somewhat inefficient. Interleaved files do not work yet.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Tue, 26 Jun 2018 03:13:38 +0300 |
parents | ca9fe688ab6b |
children | 40ccc09f09be |
line wrap: on
line source
/* * miniJSS - JSSMOD module loader * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2007-2015 Tecnic Software productions (TNSP) */ #include "jssmod.h" // If all pattern modes are required, enable them here #ifdef JM_SUP_PATMODE_ALL # define JM_SUP_PATMODE_1 1 # define JM_SUP_PATMODE_2 1 # define JM_SUP_PATMODE_3 1 # define JM_SUP_PATMODE_4 1 # define JM_SUP_PATMODE_5 1 #endif // Short helper macros for reading data #define JSGETBYTE(XV) \ if (!dmf_read_byte(inFile, XV)) \ return DMERR_OUT_OF_DATA static int jssDoGetConvertedNote(DMResource *inFile, JSSNote *pnote, Uint8 note) { Uint8 tmp; if (note == 127) pnote->note = jsetNoteOff; else if (note == 0) pnote->note = jsetNotSet; else pnote->note = note - 1; JSGETBYTE(&tmp); pnote->instrument = (tmp > 0) ? tmp - 1 : jsetNotSet; JSGETBYTE(&tmp); pnote->volume = (tmp > 0) ? tmp - 1 : jsetNotSet; JSGETBYTE(&tmp); pnote->effect = (tmp > 0) ? tmp - 1 : jsetNotSet; JSGETBYTE(&tmp); pnote->param = (tmp == 0 && pnote->effect == jsetNotSet) ? jsetNotSet : tmp; return DMERR_OK; } static inline int jssGetConvertedNote(DMResource *inFile, JSSNote *pnote) { Uint8 tmp; JSGETBYTE(&tmp); return jssDoGetConvertedNote(inFile, pnote, tmp); } #if defined(JM_SUP_PATMODE_2) || defined(JM_SUP_PATMODE_4) static int jssGetCompressedNote(DMResource *inFile, JSSNote *pnote) { Uint8 packb, tmp; JSGETBYTE(&packb); if (packb & 0x80) { if (packb & JM_COMP_NOTE) { JSGETBYTE(&tmp); if (tmp == 127) pnote->note = jsetNoteOff; else pnote->note = tmp; } if (packb & JM_COMP_INSTRUMENT) { JSGETBYTE(&tmp); pnote->instrument = tmp; } if (packb & JM_COMP_VOLUME) { JSGETBYTE(&tmp); pnote->volume = tmp; } if (packb & JM_COMP_EFFECT) { JSGETBYTE(&tmp); pnote->effect = tmp; pnote->param = 0; } if (packb & JM_COMP_PARAM) { JSGETBYTE(&tmp); pnote->param = tmp; } } else { int ret; if ((ret = jssDoGetConvertedNote(inFile, pnote, packb)) != DMERR_OK) return ret; } return DMERR_OK; } #endif #ifdef JM_SUP_PATMODE_2 static int jssGetPatternCompHoriz(DMResource *inFile, JSSPattern *pattern) { int row, channel; assert(buf != NULL); assert(pattern != NULL); for (row = 0; row < pattern->nrows; row++) for (channel = 0; channel < pattern->nchannels; channel++) { JSSNote *pnote = &pattern->data[(pattern->nchannels * row) + channel]; int res = jssGetCompressedNote(inFile, pnote); if (res != DMERR_OK) JSSERROR(res, res, "Error uncompressing note on row=%d, chn=%d\n", row, channel); } return DMERR_OK; } #endif #ifdef JM_SUP_PATMODE_4 static int jssGetPatternCompVert(DMResource *inFile, JSSPattern *pattern) { int row, channel; assert(buf != NULL); assert(pattern != NULL); for (channel = 0; channel < pattern->nchannels; channel++) for (row = 0; row < pattern->nrows; row++) { JSSNote *pnote = &pattern->data[(pattern->nchannels * row) + channel]; int res = jssGetCompressedNote(inFile, pnote); if (res != DMERR_OK) JSSERROR(res, res, "Error uncompressing note on row=%d, chn=%d\n", row, channel); } return DMERR_OK; } #endif #ifdef JM_SUP_PATMODE_1 static int jssGetPatternRawHoriz(DMResource *inFile, JSSPattern *pattern) { int row, channel; assert(buf != NULL); assert(pattern != NULL); for (row = 0; row < pattern->nrows; row++) for (channel = 0; channel < pattern->nchannels; channel++) { JSSNote *pnote = &pattern->data[(pattern->nchannels * row) + channel]; int res = jssGetConvertedNote(inFile, pnote); if (res != DMERR_OK) JSSERROR(res, res, "Error converting note on row=%d, chn=%d\n", row, channel); } return DMERR_OK; } #endif #ifdef JM_SUP_PATMODE_3 static int jssGetPatternRawVert(DMResource *inFile, JSSPattern *pattern) { int row, channel; assert(buf != NULL); assert(pattern != NULL); for (channel = 0; channel < pattern->nchannels; channel++) for (row = 0; row < pattern->nrows; row++) { JSSNote *pnote = &pattern->data[(pattern->nchannels * row) + channel]; int res = jssGetConvertedNote(inFile, pnote); if (res != DMERR_OK) JSSERROR(res, res, "Error converting note on row=%d, chn=%d\n", row, channel); } return DMERR_OK; } #endif #ifdef JM_SUP_PATMODE_5 #undef JSGETBYTE #define JSGETBYTE(XV) if (!dmf_read_byte(inFile, XV)) return DMERR_OUT_OF_DATA #define JSFOREACHNOTE1 \ for (channel = 0; channel < pattern->nchannels; channel++) \ for (row = 0; row < pattern->nrows; row++) { \ JSSNote *pnote = pattern->data + (pattern->nchannels * row) + channel; #define JSFOREACHNOTE2 } static int jssGetPatternRawVertElem(DMResource *inFile, JSSPattern *pattern) { int row, channel; Uint8 tmp; assert(buf != NULL); assert(pattern != NULL); JSFOREACHNOTE1 JSGETBYTE(&tmp); if (tmp == 0) pnote->note = jsetNotSet; else if (tmp == 127) pnote->note = jsetNoteOff; else pnote->note = tmp - 1; JSFOREACHNOTE2 JSFOREACHNOTE1 JSGETBYTE(&tmp); pnote->instrument = (tmp > 0) ? tmp - 1 : jsetNotSet; JSFOREACHNOTE2 JSFOREACHNOTE1 JSGETBYTE(&tmp); pnote->volume = (tmp > 0) ? tmp - 1 : jsetNotSet; JSFOREACHNOTE2 JSFOREACHNOTE1 JSGETBYTE(&tmp); pnote->effect = (tmp > 0) ? tmp - 1 : jsetNotSet; JSFOREACHNOTE2 JSFOREACHNOTE1 JSGETBYTE(&tmp); pnote->param = (tmp == 0 && pnote->effect == jsetNotSet) ? jsetNotSet : tmp; JSFOREACHNOTE2 return DMERR_OK; } #endif #ifdef JM_SUP_EXT_INSTR static int jssMODLoadEnvelope(DMResource *inFile, JSSEnvelope *env, const char *name, const int ninst) { JSSMODEnvelope jssEnv; (void) name; (void) ninst; // Read envelope data if (!dmf_read_byte(inFile, &jssEnv.flags) || !dmf_read_byte(inFile, &jssEnv.npoints) || !dmf_read_byte(inFile, &jssEnv.sustain) || !dmf_read_byte(inFile, &jssEnv.loopS) || !dmf_read_byte(inFile, &jssEnv.loopE)) { JSSERROR(DMERR_FREAD, DMERR_FREAD, "Failed to read %s-envelope data for instrument #%d.\n", name, ninst); } // Do some sanity checking if (jssEnv.npoints > jsetMaxEnvPoints || jssEnv.loopS > jssEnv.loopE || jssEnv.loopS > jssEnv.npoints || jssEnv.loopE > jssEnv.npoints) { JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Invalid values in %s-envelope for instrument #%d.\n", name, ninst); } // Copy data env->flags = jssEnv.flags; env->npoints = jssEnv.npoints; env->sustain = jssEnv.sustain; env->loopS = jssEnv.loopS; env->loopE = jssEnv.loopE; for (int i = 0; i < jssEnv.npoints; i++) { Uint16 frame, value; if (!dmf_read_le16(inFile, &frame) || !dmf_read_le16(inFile, &value)) { JSSERROR(DMERR_FREAD, DMERR_FREAD, "Failed to read %s-envelope values (%d) for instrument #%d.\n", name, i, ninst); } env->points[i].frame = frame; env->points[i].value = value; } return DMERR_OK; } #endif int jssLoadJSSMOD(DMResource *inFile, JSSModule **pmodule, BOOL probe) { JSSModule *module; JSSMODHeader jssH; int index, ret = DMERR_OK; *pmodule = NULL; // Check the JSSMOD header if (!dmf_read_str(inFile, &jssH.idMagic, sizeof(jssH.idMagic)) || !dmf_read_byte(inFile, &jssH.idVersion)) JSSERROR(DMERR_FREAD, DMERR_FREAD, "Failed to read JSSMOD header #1.\n"); if (memcmp(jssH.idMagic, "JM", 2) != 0) { if (probe) return DMERR_NOT_SUPPORTED; else { JSSERROR(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED, "Not a JSSMOD file, header signature mismatch!\n"); } } if (jssH.idVersion != JSSMOD_VERSION) { if (probe) return DMERR_VERSION; else { JSSERROR(DMERR_VERSION, DMERR_VERSION, "Unsupported version of JSSMOD 0x%2x, this version only supports 0x%2x!\n", jssH.idVersion, JSSMOD_VERSION); } } // Read rest of the header if (!dmf_read_le16(inFile, &jssH.defFlags) || !dmf_read_le16(inFile, &jssH.intVersion) || !dmf_read_le16(inFile, &jssH.norders) || !dmf_read_le16(inFile, &jssH.npatterns) || !dmf_read_le16(inFile, &jssH.nextInstruments) || !dmf_read_le16(inFile, &jssH.ninstruments) || !dmf_read_le16(inFile, &jssH.defRestartPos) || !dmf_read_byte(inFile, &jssH.nchannels) || !dmf_read_byte(inFile, &jssH.defSpeed) || !dmf_read_byte(inFile, &jssH.defTempo) || !dmf_read_byte(inFile, &jssH.patMode)) { JSSERROR(DMERR_FREAD, DMERR_FREAD, "Failed to read JSSMOD header #2.\n"); } if (probe) return DMERR_OK; // Check some of the things for sanity if (jssH.nchannels > jsetMaxChannels || jssH.nchannels == 0 || jssH.norders > jsetMaxOrders || jssH.npatterns > jsetMaxPatterns || jssH.nextInstruments > jsetMaxInstruments || jssH.ninstruments > jsetMaxInstruments) { JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Invalid values in JSSMOD header.\n"); } // Allocate the module if ((*pmodule = module = jssAllocateModule()) == NULL) { JSSERROR(DMERR_MALLOC, DMERR_MALLOC, "Could not allocate memory for module structure.\n"); } // Copy header information module->defFlags = jssH.defFlags; module->intVersion = jssH.intVersion; module->norders = jssH.norders; module->npatterns = jssH.npatterns; module->nchannels = jssH.nchannels; module->nextInstruments = jssH.nextInstruments; module->ninstruments = jssH.ninstruments; module->defRestartPos = jssH.defRestartPos; module->defSpeed = jssH.defSpeed; module->defTempo = jssH.defTempo; // Get the orders list for (index = 0; index < module->norders; index++) { Uint16 tmp; if (!dmf_read_le16(inFile, &tmp)) JSSERROR(DMERR_FREAD, DMERR_FREAD, "Failed to read orders list entry #%d.\n", index); if (tmp != 0xffff && tmp > jssH.npatterns) JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Invalid orders list entry #%d value %d > %d.\n", index, tmp, jssH.npatterns); module->orderList[index] = (tmp == 0xffff) ? jsetNotSet : tmp; } // Parse the patterns for (index = 0; index < module->npatterns; index++) { JSSPattern *pattern; JSSMODPattern jssP; // Read pattern header if (!dmf_read_le32(inFile, &jssP.size) || !dmf_read_le16(inFile, &jssP.nrows)) JSSERROR(DMERR_FREAD, DMERR_FREAD, "Failed to read JSSMOD pattern header #%d.\n", index); // Validate if (jssP.nrows == 0 || jssP.nrows > jsetMaxRows) JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Invalid number of rows in pattern #%d: %d.\n", index, jssP.nrows); // Allocate pattern pattern = module->patterns[index] = jssAllocatePattern(jssP.nrows, module->nchannels); if (pattern == NULL) { JSSERROR(DMERR_MALLOC, DMERR_MALLOC, "Could not allocate memory for pattern #%d.\n", index); } // Get pattern data switch (jssH.patMode) { #ifdef JM_SUP_PATMODE_1 case PATMODE_RAW_HORIZ: ret = jssGetPatternRawHoriz(inFile, pattern); break; #endif #ifdef JM_SUP_PATMODE_2 case PATMODE_COMP_HORIZ: ret = jssGetPatternCompHoriz(inFile, pattern); break; #endif #ifdef JM_SUP_PATMODE_3 case PATMODE_RAW_VERT: ret = jssGetPatternRawVert(inFile, pattern); break; #endif #ifdef JM_SUP_PATMODE_4 case PATMODE_COMP_VERT: ret = jssGetPatternCompVert(inFile, pattern); break; #endif #ifdef JM_SUP_PATMODE_5 case PATMODE_RAW_ELEM: ret = jssGetPatternRawVertElem(inFile, pattern); break; #endif default: JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Unsupported pattern mode %d. Check compilation options.", jssH.patMode); break; } if (ret != DMERR_OK) { JSSERROR(ret, ret, "Error in unpacking pattern #%d data.\n", index); } } #ifdef JM_SUP_EXT_INSTR // Read extended instruments for (index = 0; index < module->nextInstruments; index++) { JSSMODExtInstrument jssE; JSSExtInstrument *einst; int i; // Read header data if (!dmf_read_byte(inFile, &jssE.nsamples) || !dmf_read_byte(inFile, &jssE.vibratoType) || !dmf_read_le16(inFile, &jssE.vibratoSweep) || !dmf_read_le16(inFile, &jssE.vibratoDepth) || !dmf_read_le16(inFile, &jssE.vibratoRate) || !dmf_read_le16(inFile, &jssE.fadeOut)) { JSSERROR(DMERR_FREAD, DMERR_FREAD, "Failed to read ext.instrument #%d header.\n", index); } // Allocate instrument einst = module->extInstruments[index] = jssAllocateExtInstrument(); if (einst == NULL) { JSSERROR(DMERR_MALLOC, DMERR_MALLOC, "Could not allocate extended instrument structure #%d\n", index); } einst->nsamples = jssE.nsamples; einst->vibratoType = jssE.vibratoType; einst->vibratoSweep = jssE.vibratoSweep; einst->vibratoDepth = jssE.vibratoDepth; einst->vibratoRate = jssE.vibratoRate; einst->fadeOut = jssE.fadeOut; // Read and somewhat validate sNumForNotes for (i = 0; i < jsetNNotes; i++) { int snum; Uint32 tmp; if (!dmf_read_le32(inFile, &tmp)) { JSSERROR(DMERR_FREAD, DMERR_FREAD, "Failed to read ext.instrument #%d sNumForNotes[%d].\n", index, i); } einst->sNumForNotes[i] = snum = (tmp > 0) ? ((int) tmp - 1) : jsetNotSet; if (snum != jsetNotSet && snum > module->ninstruments) { JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Ext.instrument #%d has invalid sNumForNotes[%d] = %d > %d max.\n", index, i, snum, module->ninstruments); } } // Read and validate envelopes if ((ret = jssMODLoadEnvelope(inFile, &einst->volumeEnv, "volume", index)) != DMERR_OK || (ret = jssMODLoadEnvelope(inFile, &einst->panningEnv, "panning", index)) != DMERR_OK) return ret; } #ifdef JM_SUP_INSTR // Read sample instrument headers for (index = 0; index < module->ninstruments; index++) { JSSMODInstrument jssI; JSSInstrument *inst; // Read header data if (!dmf_read_le32(inFile, &jssI.size) || !dmf_read_le32(inFile, &jssI.loopS) || !dmf_read_le32(inFile, &jssI.loopE) || !dmf_read_le16(inFile, &jssI.flags) || !dmf_read_le16(inFile, &jssI.C4BaseSpeed) || !dmf_read_le16(inFile, (Uint16 *) &jssI.ERelNote) || !dmf_read_le16(inFile, (Uint16 *) &jssI.EFineTune) || !dmf_read_le16(inFile, (Uint16 *) &jssI.EPanning) || !dmf_read_byte(inFile, &jssI.volume) || !dmf_read_byte(inFile, &jssI.convFlags)) { JSSERROR(DMERR_FREAD, DMERR_FREAD, "Failed to read sample instrument #%d header.\n", index); } // Validate what we can if (jssI.loopS > jssI.size || jssI.loopE > jssI.size || jssI.loopS > jssI.loopE) { JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, "Invalid or corrupted sample instrument #%d.\n", index); } // Allocate instrument inst = module->instruments[index] = jssAllocateInstrument(); if (inst == NULL) { JSSERROR(DMERR_MALLOC, DMERR_MALLOC, "Could not allocate sample instrument structure #%d\n", index); } // Copy data inst->size = jssI.size; inst->loopS = jssI.loopS; inst->loopE = jssI.loopE; inst->flags = jssI.flags; inst->C4BaseSpeed = jssI.C4BaseSpeed; inst->ERelNote = jssI.ERelNote; inst->EFineTune = jssI.EFineTune; inst->EPanning = jssI.EPanning; inst->volume = jssI.volume; inst->convFlags = jssI.convFlags; } #ifdef JM_SUP_SAMPLES // Read sample data for (index = 0; index < module->ninstruments; index++) { JSSInstrument *inst = module->instruments[index]; if (inst != NULL && (inst->convFlags & jsampHasData) != 0) { int ret; size_t bsize = (inst->flags & jsf16bit) ? sizeof(Uint16) : sizeof(Uint8); bsize *= inst->size; // Allocate sample data memory if ((inst->data = dmMalloc(bsize)) == NULL) { JSSERROR(DMERR_MALLOC, DMERR_MALLOC, "Could not allocate %d bytes of sample data #%d\n", bsize, index); } // Read data if (!dmf_read_str(inFile, inst->data, bsize)) { JSSERROR(DMERR_FREAD, DMERR_FREAD, "Could not read %d bytes of sample data for #%d\n", bsize, index); } // Convert, if needed if (inst->flags & jsf16bit) ret = jssDecodeSample16(inst->data, inst->size, inst->convFlags); else ret = jssDecodeSample8(inst->data, inst->size, inst->convFlags); if (ret != DMERR_OK) { JSSERROR(ret, ret, "Failed to decode sample data for #%d\n", index); } } } #else # warning Not including JSSMOD sample loading! #endif // JM_SUP_SAMPLES #else # warning Not including JSSMOD instrument loading! #endif // JM_SUP_INSTR #else # warning Not including JSSMOD ext.instrument loading! #endif // JM_SUP_EXT_INSTR return DMERR_OK; }