0
|
1 /*
|
|
2 * miniJSS - Fast Tracker ][ (XM) module loader
|
|
3 * Programmed and designed by Matti 'ccr' Hamalainen
|
|
4 * (C) Copyright 2006-2007 Tecnic Software productions (TNSP)
|
|
5 *
|
|
6 * TO DO:
|
|
7 * - Add support for 1.02/1.03 XM-format versions.
|
|
8 * (Not very useful, but if it's not too hard, then do it)
|
|
9 */
|
|
10 #include "jssmod.h"
|
|
11 #include <string.h>
|
|
12
|
|
13
|
|
14 /* XM value limit definitions
|
|
15 */
|
|
16 #define XM_MaxChannels (32)
|
|
17 #define XM_MaxPatterns (256)
|
|
18 #define XM_MaxOrders (255)
|
|
19 #define XM_MaxInstruments (128)
|
|
20 #define XM_MaxInstSamples (16)
|
|
21 #define XM_MaxEnvPoints (12)
|
|
22 #define XM_MaxNotes (96)
|
|
23 #define XM_MaxSampleVolume (64)
|
|
24
|
|
25
|
|
26 /* XM format structures
|
|
27 */
|
|
28 typedef struct
|
|
29 {
|
|
30 char idMagic[17]; // XM header ID "Extended Module: "
|
|
31 char songName[20]; // Module song name
|
|
32 Uint8 unUsed1A; // ALWAYS 0x1a
|
|
33 char trackerName[20]; // ID-string of tracker software
|
|
34 Uint16 version; // XM-version 0x0104
|
|
35 Uint32 headSize; // Module header size, FROM THIS POINT!
|
|
36 Uint16 norders, // Number of orders
|
|
37 defRestartPos, // Default song restart position
|
|
38 nchannels, // Number of channels
|
|
39 npatterns, // Number of patterns
|
|
40 ninstruments, // Number of instruments
|
|
41 flags, /* Module flags:
|
|
42 bit0: 0 = Amiga frequency table
|
|
43 1 = Linear frequency table
|
|
44 */
|
|
45 defSpeed, // Default speed
|
|
46 defTempo; // Default tempo
|
|
47 Uint8 orderList[256]; // Order list
|
|
48 } XMHeader;
|
|
49
|
|
50
|
|
51 typedef struct
|
|
52 {
|
|
53 Uint32 headSize; // Instrument header size (see docs!)
|
|
54 char instName[22]; // Name/description
|
|
55 Uint8 instType; // Type
|
|
56 Uint16 nsamples; // Number of samples
|
|
57 } XMInstrument1;
|
|
58
|
|
59
|
|
60 typedef struct
|
|
61 {
|
|
62 Uint16 frame, value;
|
|
63 } xm_envpoint_t;
|
|
64
|
|
65
|
|
66 typedef struct
|
|
67 {
|
|
68 Uint8 flags, npoints, sustain, loopS, loopE;
|
|
69 xm_envpoint_t points[XM_MaxEnvPoints];
|
|
70 } xm_envelope_t;
|
|
71
|
|
72
|
|
73 typedef struct
|
|
74 {
|
|
75 Uint32 headSize; // Header size
|
|
76 Uint8 sNumForNotes[XM_MaxNotes]; // Sample numbers for notes
|
|
77 xm_envelope_t volumeEnv, panningEnv;
|
|
78 Uint8 vibratoType, vibratoSweep, vibratoDepth, vibratoRate;
|
|
79
|
|
80 Uint16 fadeOut, ARESERVED;
|
|
81 } XMInstrument2;
|
|
82
|
|
83
|
|
84 typedef struct
|
|
85 {
|
|
86 Uint32 size, loopS, loopL;
|
|
87 Uint8 volume;
|
|
88 int fineTune;
|
|
89 Uint8 type, panning;
|
|
90 int relNote;
|
|
91 Uint8 ARESERVED;
|
|
92 char sampleName[22];
|
|
93 } XMSample;
|
|
94
|
|
95
|
|
96 typedef struct
|
|
97 {
|
|
98 Uint32 headSize;
|
|
99 Uint8 packing;
|
|
100 Uint16 nrows, size;
|
|
101 } XMPattern;
|
|
102
|
|
103
|
|
104
|
|
105 /* Unpack XM-format pattern from file-stream into JSS-pattern structure
|
|
106 */
|
|
107 #define JSGETBYTE(XV) do { \
|
|
108 size--; \
|
|
109 if (size < 0) \
|
|
110 JSSERROR(DMERR_OUT_OF_DATA, DMERR_OUT_OF_DATA, \
|
|
111 "Unexpected end of packed pattern data.\n"); \
|
|
112 XV = dmfgetc(inFile); \
|
|
113 } while (0)
|
|
114
|
|
115
|
|
116 static int jssXMUnpackPattern(DMResource *inFile, int size, JSSPattern *pattern)
|
|
117 {
|
|
118 int packb, row, channel, tmp;
|
|
119 JSSNote *pnote;
|
|
120 assert(pattern != NULL);
|
|
121
|
|
122 pnote = pattern->data;
|
|
123
|
|
124 for (row = 0; (row < pattern->nrows) && (size > 0); row++)
|
|
125 for (channel = 0; (channel < pattern->nchannels) && (size > 0); channel++)
|
|
126 {
|
|
127 JSGETBYTE(packb);
|
|
128 if (packb & 0x80)
|
|
129 {
|
|
130 if (packb & 0x01)
|
|
131 {
|
|
132 // PACK 0x01: Read note
|
|
133 JSGETBYTE(tmp);
|
|
134 if (tmp < 1 || tmp > 97)
|
|
135 pnote->note = jsetNotSet;
|
|
136 else if (tmp == 97)
|
|
137 pnote->note = jsetNoteOff;
|
|
138 else
|
|
139 pnote->note = tmp - 1;
|
|
140 }
|
|
141
|
|
142 if (packb & 0x02)
|
|
143 {
|
|
144 // PACK 0x02: Read instrument
|
|
145 JSGETBYTE(tmp);
|
|
146 pnote->instrument = (tmp > 0) ? tmp - 1 : jsetNotSet;
|
|
147 }
|
|
148
|
|
149 if (packb & 0x04)
|
|
150 {
|
|
151 // PACK 0x04: Read volume
|
|
152 JSGETBYTE(tmp);
|
|
153 pnote->volume = (tmp >= 0x10) ? tmp - 0x10 : jsetNotSet;
|
|
154 }
|
|
155
|
|
156 if (packb & 0x08)
|
|
157 {
|
|
158 // PACK 0x08: Read effect
|
|
159 JSGETBYTE(pnote->effect);
|
|
160 pnote->param = 0;
|
|
161 }
|
|
162
|
|
163 if (packb & 0x10)
|
|
164 {
|
|
165 // PACK 0x10: Read effect parameter
|
|
166 JSGETBYTE(pnote->param);
|
|
167 if (pnote->effect == jsetNotSet && pnote->param != 0)
|
|
168 pnote->effect = 0;
|
|
169 }
|
|
170 }
|
|
171 else
|
|
172 {
|
|
173 // All data available
|
|
174 tmp = (packb & 0x7f);
|
|
175
|
|
176 if (tmp < 1 || tmp > 97)
|
|
177 pnote->note = jsetNotSet;
|
|
178 else if (tmp == 97)
|
|
179 pnote->note = jsetNoteOff;
|
|
180 else
|
|
181 pnote->note = tmp - 1;
|
|
182
|
|
183 // Get instrument
|
|
184 JSGETBYTE(tmp);
|
|
185 pnote->instrument = (tmp > 0) ? tmp - 1 : jsetNotSet;
|
|
186
|
|
187 // Get volume
|
|
188 JSGETBYTE(tmp);
|
|
189 pnote->volume = (tmp >= 0x10) ? tmp - 0x10 : jsetNotSet;
|
|
190
|
|
191 // Get effect
|
|
192 JSGETBYTE(pnote->effect);
|
|
193
|
|
194 // Get parameter
|
|
195 JSGETBYTE(pnote->param);
|
|
196 if (pnote->effect == 0 && pnote->param == 0)
|
|
197 pnote->effect = pnote->param = jsetNotSet;
|
|
198 }
|
|
199 pnote++;
|
|
200 }
|
|
201
|
|
202 // Check the state
|
|
203 if (size > 0)
|
|
204 {
|
|
205 // Some data left unparsed
|
|
206 JSSWARNING(DMERR_EXTRA_DATA, DMERR_EXTRA_DATA,
|
|
207 "Unparsed data after pattern (%i bytes), possibly broken file.\n", size);
|
|
208 }
|
|
209
|
|
210 return DMERR_OK;
|
|
211 }
|
|
212
|
|
213
|
|
214 /* Convert XM envelope structure to JSS envelope structure
|
|
215 */
|
|
216 static int jssXMConvertEnvelope(JSSEnvelope * d, xm_envelope_t * s, char * e, int instr)
|
|
217 {
|
|
218 int i;
|
|
219 (void) e; (void) instr;
|
|
220
|
|
221 // Convert envelope points
|
|
222 d->points[0].frame = s->points[0].frame;
|
|
223 d->points[0].value = s->points[0].value;
|
|
224 for (i = 0; i < XM_MaxEnvPoints; i++)
|
|
225 {
|
|
226 d->points[i + 1].frame = s->points[i].frame + 1;
|
|
227 d->points[i + 1].value = s->points[i].value;
|
|
228 }
|
|
229
|
|
230 // Convert other values
|
|
231 d->npoints = s->npoints + 1;
|
|
232 d->sustain = s->sustain + 1;
|
|
233 d->loopS = s->loopS + 1;
|
|
234 d->loopE = s->loopE + 1;
|
|
235
|
|
236 // Check if the envelope is used
|
|
237 if (s->flags & 0x01)
|
|
238 {
|
|
239 // Convert envelope flags
|
|
240 d->flags = jenvfUsed;
|
|
241 if (s->flags & 0x02)
|
|
242 d->flags |= jenvfSustain;
|
|
243
|
|
244 if (s->flags & 0x04)
|
|
245 d->flags |= jenvfLooped;
|
|
246
|
|
247 // Check other values
|
|
248 if (s->npoints > XM_MaxEnvPoints)
|
|
249 {
|
|
250 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
251 "Inst#%i/%s-env: nPoints > MAX, possibly broken file.\n", instr, e);
|
|
252 s->npoints = XM_MaxEnvPoints;
|
|
253 }
|
|
254
|
|
255 if ((d->flags & jenvfSustain) && s->sustain > s->npoints)
|
|
256 {
|
|
257 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
258 "Inst#%i/%s-env: iSustain > nPoints (%i > %i), possibly broken file.\n",
|
|
259 instr, e, s->sustain, s->npoints);
|
|
260 s->sustain = s->npoints;
|
|
261 }
|
|
262
|
|
263 if ((d->flags & jenvfLooped) && s->loopE > s->npoints)
|
|
264 {
|
|
265 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
266 "Inst#%i/%s-env: loopE > nPoints (%i > %i), possibly broken file.\n",
|
|
267 instr, e, s->loopE, s->npoints);
|
|
268 s->loopE = s->npoints;
|
|
269 }
|
|
270
|
|
271 if ((d->flags & jenvfLooped) && s->loopS > s->loopE)
|
|
272 {
|
|
273 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
274 "Inst#%i/%s-env: loopS > loopE (%i > %i), possibly broken file.\n",
|
|
275 instr, e, s->loopS, s->loopE);
|
|
276 s->loopS = 0;
|
|
277 }
|
|
278 }
|
|
279
|
|
280 return DMERR_OK;
|
|
281 }
|
|
282
|
|
283
|
|
284 /* Load XM-format extended instrument from file-stream into JSS module's given inst
|
|
285 */
|
|
286 static int jssXMLoadExtInstrument(DMResource *inFile, int ninst, JSSModule *module)
|
|
287 {
|
|
288 XMInstrument1 xmI1;
|
|
289 off_t pos, remainder;
|
|
290
|
|
291 // Get instrument header #1
|
|
292 pos = dmftell(inFile);
|
|
293 dmf_read_le32(inFile, &xmI1.headSize);
|
|
294 dmf_read_str(inFile, (Uint8 *) &xmI1.instName, sizeof(xmI1.instName));
|
|
295 xmI1.instType = dmfgetc(inFile);
|
|
296 dmf_read_le16(inFile, &xmI1.nsamples);
|
|
297
|
|
298 // If there are samples, there is header #2
|
|
299 if (xmI1.nsamples > 0)
|
|
300 {
|
|
301 int i, nsample, tmp;
|
|
302 int xmConvTable[XM_MaxInstruments + 1];
|
|
303 JSSExtInstrument *pEInst;
|
|
304 JSSInstrument *pInst;
|
|
305 XMInstrument2 xmI2;
|
|
306
|
|
307 // Allocate instrument
|
|
308 if ((pEInst = jssAllocateExtInstrument()) == NULL)
|
|
309 {
|
|
310 JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
|
|
311 "Could not allocate extended instrument structure #%i\n", ninst);
|
|
312 }
|
|
313
|
|
314 module->extInstruments[ninst] = pEInst;
|
|
315
|
|
316 // Get instrument header #2
|
|
317 dmf_read_le32(inFile, &xmI2.headSize);
|
|
318 dmf_read_str(inFile, (Uint8 *) &xmI2.sNumForNotes, sizeof(xmI2.sNumForNotes));
|
|
319
|
|
320 for (i = 0; i < XM_MaxEnvPoints; i++)
|
|
321 {
|
|
322 dmf_read_le16(inFile, &xmI2.volumeEnv.points[i].frame);
|
|
323 dmf_read_le16(inFile, &xmI2.volumeEnv.points[i].value);
|
|
324 }
|
|
325
|
|
326 for (i = 0; i < XM_MaxEnvPoints; i++)
|
|
327 {
|
|
328 dmf_read_le16(inFile, &xmI2.panningEnv.points[i].frame);
|
|
329 dmf_read_le16(inFile, &xmI2.panningEnv.points[i].value);
|
|
330 }
|
|
331
|
|
332 xmI2.volumeEnv.npoints = dmfgetc(inFile);
|
|
333 xmI2.panningEnv.npoints = dmfgetc(inFile);
|
|
334
|
|
335 xmI2.volumeEnv.sustain = dmfgetc(inFile);
|
|
336 xmI2.volumeEnv.loopS = dmfgetc(inFile);
|
|
337 xmI2.volumeEnv.loopE = dmfgetc(inFile);
|
|
338
|
|
339 xmI2.panningEnv.sustain = dmfgetc(inFile);
|
|
340 xmI2.panningEnv.loopS = dmfgetc(inFile);
|
|
341 xmI2.panningEnv.loopE = dmfgetc(inFile);
|
|
342
|
|
343 xmI2.volumeEnv.flags = dmfgetc(inFile);
|
|
344 xmI2.panningEnv.flags = dmfgetc(inFile);
|
|
345
|
|
346 xmI2.vibratoType = dmfgetc(inFile);
|
|
347 xmI2.vibratoSweep = dmfgetc(inFile);
|
|
348 xmI2.vibratoDepth = dmfgetc(inFile);
|
|
349 xmI2.vibratoRate = dmfgetc(inFile);
|
|
350
|
|
351 dmf_read_le16(inFile, &xmI2.fadeOut);
|
|
352 dmf_read_le16(inFile, &xmI2.ARESERVED);
|
|
353
|
|
354 // Skip the extra data after header #2
|
|
355 remainder = xmI1.headSize - (dmftell(inFile) - pos);
|
|
356 if (remainder > 0)
|
|
357 {
|
|
358 JSSDEBUG("Skipping: %li\n", remainder);
|
|
359 dmfseek(inFile, remainder, SEEK_CUR);
|
|
360 }
|
|
361
|
|
362 // Check and convert all ext instrument information
|
|
363 #ifndef JSS_LIGHT
|
|
364 pEInst->desc = jssASCIItoStr(xmI1.instName, 0, sizeof(xmI1.instName));
|
|
365 #endif
|
|
366 jssXMConvertEnvelope(&pEInst->volumeEnv, &xmI2.volumeEnv, "vol", ninst);
|
|
367 jssXMConvertEnvelope(&pEInst->panningEnv, &xmI2.panningEnv, "pan", ninst);
|
|
368
|
|
369 switch (xmI2.vibratoType)
|
|
370 {
|
|
371 case 0: pEInst->vibratoType = jvibSine; break;
|
|
372 case 1: pEInst->vibratoType = jvibRamp; break;
|
|
373 case 2: pEInst->vibratoType = jvibSquare; break;
|
|
374 case 3: pEInst->vibratoType = jvibRandom; break;
|
|
375 default:
|
|
376 pEInst->vibratoType = jvibSine;
|
|
377 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
378 "Invalid extinstrument vibrato type %d for inst #%d\n", ninst);
|
|
379 break;
|
|
380 }
|
|
381 pEInst->vibratoSweep = xmI2.vibratoSweep;
|
|
382 pEInst->vibratoDepth = xmI2.vibratoDepth;
|
|
383 pEInst->vibratoRate = xmI2.vibratoRate;
|
|
384 pEInst->fadeOut = xmI2.fadeOut;
|
|
385 pEInst->nsamples = xmI1.nsamples;
|
|
386
|
|
387 // Initialize the SNumForNotes conversion table
|
|
388 for (i = 0; i < XM_MaxInstruments; i++)
|
|
389 xmConvTable[i] = jsetNotSet;
|
|
390
|
|
391 // Read sample headers
|
|
392 for (nsample = 0; nsample < xmI1.nsamples; nsample++)
|
|
393 {
|
|
394 XMSample xmS;
|
|
395
|
|
396 // Read header data
|
|
397 dmf_read_le32(inFile, &xmS.size);
|
|
398 dmf_read_le32(inFile, &xmS.loopS);
|
|
399 dmf_read_le32(inFile, &xmS.loopL);
|
|
400 xmS.volume = dmfgetc(inFile);
|
|
401 xmS.fineTune = (signed char) dmfgetc(inFile);
|
|
402 xmS.type = dmfgetc(inFile);
|
|
403 xmS.panning = dmfgetc(inFile);
|
|
404 xmS.relNote = (signed char) dmfgetc(inFile);
|
|
405 xmS.ARESERVED = dmfgetc(inFile);
|
|
406 dmf_read_str(inFile, (Uint8 *) &xmS.sampleName, sizeof(xmS.sampleName));
|
|
407
|
|
408 if (xmS.size > 0)
|
|
409 {
|
|
410 // Allocate sample instrument
|
|
411 JSSDEBUG("Allocating sample #%i/%i [%i]\n",
|
|
412 ninst, nsample, module->ninstruments);
|
|
413
|
|
414 xmConvTable[nsample] = module->ninstruments;
|
|
415 pInst = module->instruments[module->ninstruments] = jssAllocateInstrument();
|
|
416 if (pInst == NULL)
|
|
417 {
|
|
418 JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
|
|
419 "Could not allocate sample #%i/%i [%i]\n",
|
|
420 ninst, nsample, module->ninstruments);
|
|
421 }
|
|
422 module->ninstruments++;
|
|
423 } else
|
|
424 pInst = NULL;
|
|
425
|
|
426 // Check and convert sample information
|
|
427 if (pInst != NULL)
|
|
428 {
|
|
429 // Copy values
|
|
430 if (xmS.volume > XM_MaxSampleVolume)
|
|
431 {
|
|
432 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
433 "Samp #%i/%i: volume > MAX\n", ninst, nsample);
|
|
434 xmS.volume = XM_MaxSampleVolume;
|
|
435 }
|
|
436
|
|
437 pInst->volume = xmS.volume;
|
|
438 pInst->ERelNote = xmS.relNote;
|
|
439 pInst->EFineTune = xmS.fineTune;
|
|
440 pInst->EPanning = xmS.panning;
|
|
441 #ifndef JSS_LIGHT
|
|
442 pInst->desc = jssASCIItoStr(xmS.sampleName, 0, sizeof(xmS.sampleName));
|
|
443 #endif
|
|
444
|
|
445 // Convert flags
|
|
446 switch (xmS.type & 0x03)
|
|
447 {
|
|
448 case 0: pInst->flags = 0; break;
|
|
449 case 1: pInst->flags = jsfLooped; break;
|
|
450 case 2: pInst->flags = jsfLooped | jsfBiDi; break;
|
|
451 default:
|
|
452 pInst->flags = 0;
|
|
453 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
454 "Samp #%i/%i: Invalid sample type 0x%x\n",
|
|
455 ninst, nsample, xmS.type);
|
|
456 break;
|
|
457 }
|
|
458
|
|
459 if (xmS.type & 0x10)
|
|
460 {
|
|
461 // 16-bit sample
|
|
462 JSFSET(pInst->flags, jsf16bit);
|
|
463
|
|
464 pInst->size = xmS.size / sizeof(Uint16);
|
|
465 pInst->loopS = xmS.loopS / sizeof(Uint16);
|
|
466 pInst->loopE = ((xmS.loopS + xmS.loopL) / sizeof(Uint16));
|
|
467 }
|
|
468 else
|
|
469 {
|
|
470 // 8-bit sample
|
|
471 pInst->size = xmS.size;
|
|
472 pInst->loopS = xmS.loopS;
|
|
473 pInst->loopE = (xmS.loopS + xmS.loopL);
|
|
474 }
|
|
475
|
|
476 if (xmS.loopL == 0)
|
|
477 {
|
|
478 // Always unset loop, if loop length is zero
|
|
479 JSFUNSET(pInst->flags, jsfLooped);
|
|
480 }
|
|
481
|
|
482 if (pInst->flags & jsfLooped)
|
|
483 {
|
|
484 if (pInst->loopS >= pInst->size)
|
|
485 {
|
|
486 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
487 "Samp #%i/%i: loopS >= size (%d >= %d)\n",
|
|
488 ninst, nsample, pInst->loopS, pInst->size);
|
|
489 JSFUNSET(pInst->flags, jsfLooped);
|
|
490 }
|
|
491
|
|
492 if (pInst->loopE > pInst->size)
|
|
493 {
|
|
494 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
495 "Samp #%i/%i: loopE > size (%d > %d)\n",
|
|
496 ninst, nsample, pInst->loopE, pInst->size);
|
|
497 JSFUNSET(pInst->flags, jsfLooped);
|
|
498 }
|
|
499 }
|
|
500
|
|
501
|
|
502 // Allocate memory for sample data
|
|
503 if (pInst->flags & jsf16bit)
|
|
504 pInst->data = dmCalloc(pInst->size, sizeof(Uint16));
|
|
505 else
|
|
506 pInst->data = dmCalloc(pInst->size, sizeof(Uint8));
|
|
507
|
|
508 if (pInst->data == NULL)
|
|
509 JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
|
|
510 "Could not allocate sample data #%i/%i.\n", ninst, nsample);
|
|
511 }
|
|
512 }
|
|
513
|
|
514 // Read sample data
|
|
515 for (nsample = 0; nsample < xmI1.nsamples; nsample++)
|
|
516 if (xmConvTable[nsample] != jsetNotSet)
|
|
517 {
|
|
518 pInst = module->instruments[xmConvTable[nsample]];
|
|
519 if (pInst)
|
|
520 {
|
|
521 JSSDEBUG("desc....: '%s'\n"
|
|
522 "size....: %i\n"
|
|
523 "loopS...: %i\n"
|
|
524 "loopE...: %i\n"
|
|
525 "volume..: %i\n"
|
|
526 "flags...: %x\n",
|
|
527 pInst->desc,
|
|
528 pInst->size, pInst->loopS, pInst->loopE,
|
|
529 pInst->volume, pInst->flags);
|
|
530
|
|
531 if (pInst->flags & jsf16bit)
|
|
532 {
|
|
533 // Read sampledata
|
|
534 if (dmfread(pInst->data, sizeof(Uint16), pInst->size, inFile) != (size_t) pInst->size)
|
|
535 JSSERROR(DMERR_FREAD, DMERR_FREAD,
|
|
536 "Error reading sampledata for instrument #%i/%i, %i words.\n",
|
|
537 ninst, nsample, pInst->size);
|
|
538
|
|
539 // Convert data
|
|
540 jssDecodeSample16((Uint16 *) pInst->data, pInst->size,
|
|
541 #if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
|
|
542 (jsampDelta | jsampSwapEndianess)
|
|
543 #else
|
|
544 (jsampDelta)
|
|
545 #endif
|
|
546 );
|
|
547 }
|
|
548 else
|
|
549 {
|
|
550 // Read sampledata
|
|
551 if (dmfread(pInst->data, sizeof(Uint8), pInst->size, inFile) != (size_t) pInst->size)
|
|
552 JSSERROR(DMERR_FREAD, DMERR_FREAD,
|
|
553 "Error reading sampledata for instrument #%i/%i, %i bytes.\n",
|
|
554 ninst, nsample, pInst->size);
|
|
555
|
|
556 // Convert data
|
|
557 jssDecodeSample8((Uint8 *) pInst->data, pInst->size,
|
|
558 (jsampDelta | jsampFlipSign));
|
|
559 }
|
|
560 }
|
|
561 }
|
|
562
|
|
563 // Apply new values to sNumForNotes values
|
|
564 for (i = 0; i < XM_MaxNotes; i++)
|
|
565 {
|
|
566 tmp = xmI2.sNumForNotes[i];
|
|
567 if (tmp >= 0 && tmp < xmI1.nsamples)
|
|
568 pEInst->sNumForNotes[i] = xmConvTable[tmp];
|
|
569 else
|
|
570 {
|
|
571 pEInst->sNumForNotes[i] = jsetNotSet;
|
|
572 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
573 "Ext.instrument #%d sNumForNotes[%d] out of range (%d).\n",
|
|
574 ninst, i, tmp);
|
|
575 }
|
|
576 }
|
|
577 }
|
|
578 else
|
|
579 {
|
|
580 // We may STILL need to skip extra data after 1st instr. header
|
|
581 remainder = xmI1.headSize - (dmftell(inFile) - pos);
|
|
582 if (remainder > 0)
|
|
583 {
|
88
|
584 JSSDEBUG("Skipping: %li\n", remainder);
|
0
|
585 dmfseek(inFile, remainder, SEEK_CUR);
|
|
586 }
|
|
587 }
|
|
588
|
|
589 return 0;
|
|
590 }
|
|
591
|
|
592
|
|
593 /* Load XM-format module from given file-stream
|
|
594 */
|
|
595 int jssLoadXM(DMResource *inFile, JSSModule **ppModule)
|
|
596 {
|
|
597 JSSModule *module;
|
|
598 XMHeader xmH;
|
|
599 int result, index, tmp;
|
|
600
|
|
601 assert(ppModule != NULL);
|
|
602 assert(inFile != NULL);
|
|
603 *ppModule = NULL;
|
|
604
|
|
605 /* Get XM-header and check it
|
|
606 */
|
|
607 dmf_read_str(inFile, (Uint8 *) &xmH.idMagic, sizeof(xmH.idMagic));
|
|
608 dmf_read_str(inFile, (Uint8 *) &xmH.songName, sizeof(xmH.songName));
|
|
609 xmH.unUsed1A = dmfgetc(inFile);
|
|
610 dmf_read_str(inFile, (Uint8 *) &xmH.trackerName, sizeof(xmH.trackerName));
|
|
611 dmf_read_le16(inFile, &xmH.version);
|
|
612 dmf_read_le32(inFile, &xmH.headSize);
|
|
613 dmf_read_le16(inFile, &xmH.norders);
|
|
614 dmf_read_le16(inFile, &xmH.defRestartPos);
|
|
615 dmf_read_le16(inFile, &xmH.nchannels);
|
|
616 dmf_read_le16(inFile, &xmH.npatterns);
|
|
617 dmf_read_le16(inFile, &xmH.ninstruments);
|
|
618 dmf_read_le16(inFile, &xmH.flags);
|
|
619 dmf_read_le16(inFile, &xmH.defSpeed);
|
|
620 dmf_read_le16(inFile, &xmH.defTempo);
|
|
621 dmf_read_str(inFile, (Uint8 *)&xmH.orderList, sizeof(xmH.orderList));
|
|
622
|
|
623
|
|
624 // Check the fields, none of these are considered fatal
|
|
625 if (strncmp(xmH.idMagic, "Extended Module: ", 17) != 0)
|
|
626 {
|
|
627 JSSERROR(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED,
|
|
628 "Not a FT2 Extended Module (XM), ident mismatch!\n");
|
|
629 }
|
|
630
|
|
631 if (xmH.version != 0x0104)
|
|
632 {
|
|
633 JSSWARNING(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED,
|
|
634 "Unsupported version of XM format 0x%04x instead of expected 0x0104.\n",
|
|
635 xmH.version);
|
|
636 }
|
|
637
|
|
638 if (xmH.unUsed1A != 0x1a)
|
|
639 {
|
|
640 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
641 "Possibly modified or corrupted XM [%x]\n", xmH.unUsed1A);
|
|
642 }
|
|
643
|
|
644 if (xmH.norders > XM_MaxOrders)
|
|
645 {
|
|
646 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
647 "Number of orders %i > %i, possibly broken module.\n",
|
|
648 xmH.norders, XM_MaxOrders);
|
|
649 xmH.norders = XM_MaxOrders;
|
|
650 }
|
|
651
|
|
652 if (xmH.norders == 0)
|
|
653 {
|
|
654 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
655 "Number of orders was zero.\n");
|
|
656 }
|
|
657
|
|
658 if (xmH.npatterns > XM_MaxPatterns)
|
|
659 {
|
|
660 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
661 "Number of patterns %i > %i, possibly broken module.\n",
|
|
662 xmH.npatterns, XM_MaxPatterns);
|
|
663 xmH.npatterns = XM_MaxPatterns;
|
|
664 }
|
|
665
|
|
666 if (xmH.npatterns == 0)
|
|
667 {
|
|
668 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
669 "Number of patterns was zero.\n");
|
|
670 }
|
|
671
|
|
672 if (xmH.nchannels <= 0 || xmH.nchannels > XM_MaxChannels)
|
|
673 {
|
|
674 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
675 "Number of channels was invalid, %i (should be 1 - %i).\n",
|
|
676 xmH.nchannels, XM_MaxChannels);
|
|
677 }
|
|
678
|
|
679 if (xmH.ninstruments <= 0 || xmH.ninstruments > XM_MaxInstruments)
|
|
680 {
|
|
681 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
682 "Number of instruments was invalid, %i (should be 1 - %i).\n",
|
|
683 xmH.ninstruments, XM_MaxInstruments);
|
|
684 }
|
|
685
|
|
686 /* Okay, allocate a module structure
|
|
687 */
|
|
688 module = jssAllocateModule();
|
|
689 if (module == NULL)
|
|
690 {
|
|
691 JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
|
|
692 "Could not allocate memory for module structure.\n");
|
|
693 }
|
|
694 *ppModule = module;
|
|
695
|
|
696
|
|
697 // Convert and check the header data
|
|
698 module->moduleType = jmdtXM;
|
|
699 module->intVersion = xmH.version;
|
|
700 #ifndef JSS_LIGHT
|
|
701 module->moduleName = jssASCIItoStr(xmH.songName, 0, sizeof(xmH.songName));
|
|
702 module->trackerName = jssASCIItoStr(xmH.trackerName, 0, sizeof(xmH.trackerName));
|
|
703 #endif
|
|
704 module->defSpeed = xmH.defSpeed;
|
|
705 module->defTempo = xmH.defTempo;
|
|
706 module->nextInstruments = xmH.ninstruments;
|
|
707 module->ninstruments = 0;
|
|
708 module->npatterns = xmH.npatterns;
|
|
709 module->norders = xmH.norders;
|
|
710 module->nchannels = xmH.nchannels;
|
|
711 module->defFlags = jmdfStereo | jmdfFT2Replay;
|
|
712 module->defRestartPos = xmH.defRestartPos;
|
|
713
|
|
714 if ((xmH.flags & 1) == 0)
|
|
715 module->defFlags |= jmdfAmigaPeriods;
|
|
716
|
|
717 // Setup the default pannings
|
|
718 for (index = 0; index < jsetNChannels; index++)
|
|
719 module->defPanning[index] = jchPanMiddle;
|
|
720
|
|
721 /* Read patterns
|
|
722 */
|
|
723 for (index = 0; index < module->npatterns; index++)
|
|
724 {
|
88
|
725 off_t pos, remainder;
|
0
|
726 XMPattern xmP;
|
|
727
|
|
728 // Get the pattern header
|
|
729 dmf_read_le32(inFile, &xmP.headSize);
|
|
730 xmP.packing = dmfgetc(inFile);
|
|
731 dmf_read_le16(inFile, &xmP.nrows);
|
|
732 dmf_read_le16(inFile, &xmP.size);
|
|
733 pos = dmftell(inFile);
|
|
734
|
|
735 // Check the header
|
|
736 if (xmP.packing != 0)
|
|
737 JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
738 "Pattern #%i packing type unsupported (%i)\n",
|
|
739 index, xmP.packing);
|
|
740
|
|
741 if (xmP.nrows == 0)
|
|
742 JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
|
|
743 "Pattern #%i has %i rows, invalid data.\n",
|
|
744 index, xmP.nrows);
|
|
745
|
|
746 if (xmP.size > 0)
|
|
747 {
|
|
748 // Allocate and unpack pattern
|
|
749 module->patterns[index] = jssAllocatePattern(xmP.nrows, module->nchannels);
|
|
750 if (module->patterns[index] == NULL)
|
|
751 JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
|
|
752 "Could not allocate memory for pattern #%i\n", index);
|
|
753
|
|
754 result = jssXMUnpackPattern(inFile, xmP.size, module->patterns[index]);
|
|
755 if (result != 0)
|
|
756 JSSERROR(result, result, "Error in unpacking pattern #%i data\n", index);
|
|
757 }
|
|
758
|
|
759 // Skip extra data (if the file is damaged)
|
88
|
760 remainder = xmP.headSize - (dmftell(inFile) - pos);
|
|
761 if (remainder > 0)
|
0
|
762 {
|
88
|
763 JSSDEBUG("Skipping: %li\n", remainder);
|
|
764 dmfseek(inFile, remainder, SEEK_CUR);
|
0
|
765 }
|
|
766 }
|
|
767
|
|
768 // Allocate the empty pattern
|
|
769 module->patterns[jsetMaxPatterns] = jssAllocatePattern(64, module->nchannels);
|
|
770
|
|
771 /* Convert song orders list by replacing nonexisting patterns
|
|
772 * with pattern number jsetMaxPatterns.
|
|
773 */
|
|
774 for (index = 0; index < module->norders; index++)
|
|
775 {
|
|
776 tmp = xmH.orderList[index];
|
|
777 if (tmp >= module->npatterns || module->patterns[tmp] == NULL)
|
|
778 tmp = jsetMaxPatterns;
|
|
779
|
|
780 module->orderList[index] = tmp;
|
|
781 }
|
|
782
|
|
783 /* Read instruments
|
|
784 */
|
|
785 for (index = 0; index < module->nextInstruments; index++)
|
|
786 {
|
|
787 result = jssXMLoadExtInstrument(inFile, index, module);
|
|
788 if (result != 0)
|
|
789 JSSERROR(result, result, "Errors while reading instrument #%i\n", index);
|
|
790 }
|
|
791
|
|
792 return DMERR_OK;
|
|
793 }
|