Mercurial > hg > dmlib
annotate jloadxm.c @ 183:a65f0c3deaa7
Some more player logic cleanups and variable renames.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Sat, 06 Oct 2012 14:58:29 +0300 |
parents | 82b825bb08b0 |
children | a89500f26dde |
rev | line source |
---|---|
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 for (i = 0; i < XM_MaxEnvPoints; i++) | |
223 { | |
183
a65f0c3deaa7
Some more player logic cleanups and variable renames.
Matti Hamalainen <ccr@tnsp.org>
parents:
98
diff
changeset
|
224 d->points[i].frame = s->points[i].frame; |
a65f0c3deaa7
Some more player logic cleanups and variable renames.
Matti Hamalainen <ccr@tnsp.org>
parents:
98
diff
changeset
|
225 d->points[i].value = s->points[i].value; |
0 | 226 } |
227 | |
228 // Convert other values | |
183
a65f0c3deaa7
Some more player logic cleanups and variable renames.
Matti Hamalainen <ccr@tnsp.org>
parents:
98
diff
changeset
|
229 d->npoints = s->npoints; |
a65f0c3deaa7
Some more player logic cleanups and variable renames.
Matti Hamalainen <ccr@tnsp.org>
parents:
98
diff
changeset
|
230 d->sustain = s->sustain; |
a65f0c3deaa7
Some more player logic cleanups and variable renames.
Matti Hamalainen <ccr@tnsp.org>
parents:
98
diff
changeset
|
231 d->loopS = s->loopS; |
a65f0c3deaa7
Some more player logic cleanups and variable renames.
Matti Hamalainen <ccr@tnsp.org>
parents:
98
diff
changeset
|
232 d->loopE = s->loopE; |
0 | 233 |
234 // Check if the envelope is used | |
235 if (s->flags & 0x01) | |
236 { | |
237 // Convert envelope flags | |
238 d->flags = jenvfUsed; | |
239 if (s->flags & 0x02) | |
240 d->flags |= jenvfSustain; | |
241 | |
242 if (s->flags & 0x04) | |
243 d->flags |= jenvfLooped; | |
244 | |
245 // Check other values | |
246 if (s->npoints > XM_MaxEnvPoints) | |
247 { | |
248 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
249 "Inst#%i/%s-env: nPoints > MAX, possibly broken file.\n", instr, e); | |
250 s->npoints = XM_MaxEnvPoints; | |
251 } | |
252 | |
253 if ((d->flags & jenvfSustain) && s->sustain > s->npoints) | |
254 { | |
255 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
256 "Inst#%i/%s-env: iSustain > nPoints (%i > %i), possibly broken file.\n", | |
257 instr, e, s->sustain, s->npoints); | |
258 s->sustain = s->npoints; | |
259 } | |
260 | |
261 if ((d->flags & jenvfLooped) && s->loopE > s->npoints) | |
262 { | |
263 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
264 "Inst#%i/%s-env: loopE > nPoints (%i > %i), possibly broken file.\n", | |
265 instr, e, s->loopE, s->npoints); | |
266 s->loopE = s->npoints; | |
267 } | |
268 | |
269 if ((d->flags & jenvfLooped) && s->loopS > s->loopE) | |
270 { | |
271 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
272 "Inst#%i/%s-env: loopS > loopE (%i > %i), possibly broken file.\n", | |
273 instr, e, s->loopS, s->loopE); | |
274 s->loopS = 0; | |
275 } | |
276 } | |
277 | |
278 return DMERR_OK; | |
279 } | |
280 | |
281 | |
282 /* Load XM-format extended instrument from file-stream into JSS module's given inst | |
283 */ | |
284 static int jssXMLoadExtInstrument(DMResource *inFile, int ninst, JSSModule *module) | |
285 { | |
286 XMInstrument1 xmI1; | |
287 off_t pos, remainder; | |
288 | |
289 // Get instrument header #1 | |
290 pos = dmftell(inFile); | |
291 dmf_read_le32(inFile, &xmI1.headSize); | |
292 dmf_read_str(inFile, (Uint8 *) &xmI1.instName, sizeof(xmI1.instName)); | |
293 xmI1.instType = dmfgetc(inFile); | |
294 dmf_read_le16(inFile, &xmI1.nsamples); | |
295 | |
296 // If there are samples, there is header #2 | |
297 if (xmI1.nsamples > 0) | |
298 { | |
299 int i, nsample, tmp; | |
300 int xmConvTable[XM_MaxInstruments + 1]; | |
301 JSSExtInstrument *pEInst; | |
302 JSSInstrument *pInst; | |
303 XMInstrument2 xmI2; | |
304 | |
305 // Allocate instrument | |
306 if ((pEInst = jssAllocateExtInstrument()) == NULL) | |
307 { | |
308 JSSERROR(DMERR_MALLOC, DMERR_MALLOC, | |
309 "Could not allocate extended instrument structure #%i\n", ninst); | |
310 } | |
311 | |
312 module->extInstruments[ninst] = pEInst; | |
313 | |
314 // Get instrument header #2 | |
315 dmf_read_le32(inFile, &xmI2.headSize); | |
316 dmf_read_str(inFile, (Uint8 *) &xmI2.sNumForNotes, sizeof(xmI2.sNumForNotes)); | |
317 | |
318 for (i = 0; i < XM_MaxEnvPoints; i++) | |
319 { | |
320 dmf_read_le16(inFile, &xmI2.volumeEnv.points[i].frame); | |
321 dmf_read_le16(inFile, &xmI2.volumeEnv.points[i].value); | |
322 } | |
323 | |
324 for (i = 0; i < XM_MaxEnvPoints; i++) | |
325 { | |
326 dmf_read_le16(inFile, &xmI2.panningEnv.points[i].frame); | |
327 dmf_read_le16(inFile, &xmI2.panningEnv.points[i].value); | |
328 } | |
329 | |
330 xmI2.volumeEnv.npoints = dmfgetc(inFile); | |
331 xmI2.panningEnv.npoints = dmfgetc(inFile); | |
332 | |
333 xmI2.volumeEnv.sustain = dmfgetc(inFile); | |
334 xmI2.volumeEnv.loopS = dmfgetc(inFile); | |
335 xmI2.volumeEnv.loopE = dmfgetc(inFile); | |
336 | |
337 xmI2.panningEnv.sustain = dmfgetc(inFile); | |
338 xmI2.panningEnv.loopS = dmfgetc(inFile); | |
339 xmI2.panningEnv.loopE = dmfgetc(inFile); | |
340 | |
341 xmI2.volumeEnv.flags = dmfgetc(inFile); | |
342 xmI2.panningEnv.flags = dmfgetc(inFile); | |
343 | |
344 xmI2.vibratoType = dmfgetc(inFile); | |
345 xmI2.vibratoSweep = dmfgetc(inFile); | |
346 xmI2.vibratoDepth = dmfgetc(inFile); | |
347 xmI2.vibratoRate = dmfgetc(inFile); | |
348 | |
349 dmf_read_le16(inFile, &xmI2.fadeOut); | |
350 dmf_read_le16(inFile, &xmI2.ARESERVED); | |
351 | |
352 // Skip the extra data after header #2 | |
353 remainder = xmI1.headSize - (dmftell(inFile) - pos); | |
354 if (remainder > 0) | |
355 { | |
98 | 356 JSSDEBUG("xmI1#1 Skipping: %li\n", remainder); |
0 | 357 dmfseek(inFile, remainder, SEEK_CUR); |
358 } | |
359 | |
360 // Check and convert all ext instrument information | |
361 #ifndef JSS_LIGHT | |
362 pEInst->desc = jssASCIItoStr(xmI1.instName, 0, sizeof(xmI1.instName)); | |
363 #endif | |
364 jssXMConvertEnvelope(&pEInst->volumeEnv, &xmI2.volumeEnv, "vol", ninst); | |
365 jssXMConvertEnvelope(&pEInst->panningEnv, &xmI2.panningEnv, "pan", ninst); | |
366 | |
367 switch (xmI2.vibratoType) | |
368 { | |
369 case 0: pEInst->vibratoType = jvibSine; break; | |
370 case 1: pEInst->vibratoType = jvibRamp; break; | |
371 case 2: pEInst->vibratoType = jvibSquare; break; | |
372 case 3: pEInst->vibratoType = jvibRandom; break; | |
373 default: | |
374 pEInst->vibratoType = jvibSine; | |
375 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
376 "Invalid extinstrument vibrato type %d for inst #%d\n", ninst); | |
377 break; | |
378 } | |
379 pEInst->vibratoSweep = xmI2.vibratoSweep; | |
380 pEInst->vibratoDepth = xmI2.vibratoDepth; | |
381 pEInst->vibratoRate = xmI2.vibratoRate; | |
382 pEInst->fadeOut = xmI2.fadeOut; | |
383 pEInst->nsamples = xmI1.nsamples; | |
384 | |
385 // Initialize the SNumForNotes conversion table | |
386 for (i = 0; i < XM_MaxInstruments; i++) | |
387 xmConvTable[i] = jsetNotSet; | |
388 | |
389 // Read sample headers | |
390 for (nsample = 0; nsample < xmI1.nsamples; nsample++) | |
391 { | |
392 XMSample xmS; | |
393 | |
394 // Read header data | |
395 dmf_read_le32(inFile, &xmS.size); | |
396 dmf_read_le32(inFile, &xmS.loopS); | |
397 dmf_read_le32(inFile, &xmS.loopL); | |
398 xmS.volume = dmfgetc(inFile); | |
399 xmS.fineTune = (signed char) dmfgetc(inFile); | |
400 xmS.type = dmfgetc(inFile); | |
401 xmS.panning = dmfgetc(inFile); | |
402 xmS.relNote = (signed char) dmfgetc(inFile); | |
403 xmS.ARESERVED = dmfgetc(inFile); | |
404 dmf_read_str(inFile, (Uint8 *) &xmS.sampleName, sizeof(xmS.sampleName)); | |
405 | |
406 if (xmS.size > 0) | |
407 { | |
408 // Allocate sample instrument | |
409 JSSDEBUG("Allocating sample #%i/%i [%i]\n", | |
410 ninst, nsample, module->ninstruments); | |
411 | |
412 xmConvTable[nsample] = module->ninstruments; | |
413 pInst = module->instruments[module->ninstruments] = jssAllocateInstrument(); | |
414 if (pInst == NULL) | |
415 { | |
416 JSSERROR(DMERR_MALLOC, DMERR_MALLOC, | |
417 "Could not allocate sample #%i/%i [%i]\n", | |
418 ninst, nsample, module->ninstruments); | |
419 } | |
420 module->ninstruments++; | |
421 } else | |
422 pInst = NULL; | |
423 | |
424 // Check and convert sample information | |
425 if (pInst != NULL) | |
426 { | |
427 // Copy values | |
428 if (xmS.volume > XM_MaxSampleVolume) | |
429 { | |
430 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
431 "Samp #%i/%i: volume > MAX\n", ninst, nsample); | |
432 xmS.volume = XM_MaxSampleVolume; | |
433 } | |
434 | |
435 pInst->volume = xmS.volume; | |
436 pInst->ERelNote = xmS.relNote; | |
437 pInst->EFineTune = xmS.fineTune; | |
438 pInst->EPanning = xmS.panning; | |
439 #ifndef JSS_LIGHT | |
440 pInst->desc = jssASCIItoStr(xmS.sampleName, 0, sizeof(xmS.sampleName)); | |
441 #endif | |
442 | |
443 // Convert flags | |
444 switch (xmS.type & 0x03) | |
445 { | |
446 case 0: pInst->flags = 0; break; | |
447 case 1: pInst->flags = jsfLooped; break; | |
448 case 2: pInst->flags = jsfLooped | jsfBiDi; break; | |
449 default: | |
450 pInst->flags = 0; | |
451 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
452 "Samp #%i/%i: Invalid sample type 0x%x\n", | |
453 ninst, nsample, xmS.type); | |
454 break; | |
455 } | |
456 | |
457 if (xmS.type & 0x10) | |
458 { | |
459 // 16-bit sample | |
460 JSFSET(pInst->flags, jsf16bit); | |
461 | |
462 pInst->size = xmS.size / sizeof(Uint16); | |
463 pInst->loopS = xmS.loopS / sizeof(Uint16); | |
464 pInst->loopE = ((xmS.loopS + xmS.loopL) / sizeof(Uint16)); | |
465 } | |
466 else | |
467 { | |
468 // 8-bit sample | |
469 pInst->size = xmS.size; | |
470 pInst->loopS = xmS.loopS; | |
471 pInst->loopE = (xmS.loopS + xmS.loopL); | |
472 } | |
473 | |
474 if (xmS.loopL == 0) | |
475 { | |
476 // Always unset loop, if loop length is zero | |
477 JSFUNSET(pInst->flags, jsfLooped); | |
478 } | |
479 | |
480 if (pInst->flags & jsfLooped) | |
481 { | |
482 if (pInst->loopS >= pInst->size) | |
483 { | |
484 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
485 "Samp #%i/%i: loopS >= size (%d >= %d)\n", | |
486 ninst, nsample, pInst->loopS, pInst->size); | |
487 JSFUNSET(pInst->flags, jsfLooped); | |
488 } | |
489 | |
490 if (pInst->loopE > pInst->size) | |
491 { | |
492 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
493 "Samp #%i/%i: loopE > size (%d > %d)\n", | |
494 ninst, nsample, pInst->loopE, pInst->size); | |
495 JSFUNSET(pInst->flags, jsfLooped); | |
496 } | |
497 } | |
498 | |
499 | |
500 // Allocate memory for sample data | |
501 if (pInst->flags & jsf16bit) | |
502 pInst->data = dmCalloc(pInst->size, sizeof(Uint16)); | |
503 else | |
504 pInst->data = dmCalloc(pInst->size, sizeof(Uint8)); | |
505 | |
506 if (pInst->data == NULL) | |
507 JSSERROR(DMERR_MALLOC, DMERR_MALLOC, | |
508 "Could not allocate sample data #%i/%i.\n", ninst, nsample); | |
509 } | |
510 } | |
511 | |
512 // Read sample data | |
513 for (nsample = 0; nsample < xmI1.nsamples; nsample++) | |
514 if (xmConvTable[nsample] != jsetNotSet) | |
515 { | |
516 pInst = module->instruments[xmConvTable[nsample]]; | |
517 if (pInst) | |
518 { | |
519 JSSDEBUG("desc....: '%s'\n" | |
520 "size....: %i\n" | |
521 "loopS...: %i\n" | |
522 "loopE...: %i\n" | |
523 "volume..: %i\n" | |
524 "flags...: %x\n", | |
525 pInst->desc, | |
526 pInst->size, pInst->loopS, pInst->loopE, | |
527 pInst->volume, pInst->flags); | |
528 | |
529 if (pInst->flags & jsf16bit) | |
530 { | |
531 // Read sampledata | |
532 if (dmfread(pInst->data, sizeof(Uint16), pInst->size, inFile) != (size_t) pInst->size) | |
533 JSSERROR(DMERR_FREAD, DMERR_FREAD, | |
534 "Error reading sampledata for instrument #%i/%i, %i words.\n", | |
535 ninst, nsample, pInst->size); | |
536 | |
537 // Convert data | |
538 jssDecodeSample16((Uint16 *) pInst->data, pInst->size, | |
539 #if (SDL_BYTEORDER == SDL_BIG_ENDIAN) | |
540 (jsampDelta | jsampSwapEndianess) | |
541 #else | |
542 (jsampDelta) | |
543 #endif | |
544 ); | |
545 } | |
546 else | |
547 { | |
548 // Read sampledata | |
549 if (dmfread(pInst->data, sizeof(Uint8), pInst->size, inFile) != (size_t) pInst->size) | |
550 JSSERROR(DMERR_FREAD, DMERR_FREAD, | |
551 "Error reading sampledata for instrument #%i/%i, %i bytes.\n", | |
552 ninst, nsample, pInst->size); | |
553 | |
554 // Convert data | |
555 jssDecodeSample8((Uint8 *) pInst->data, pInst->size, | |
556 (jsampDelta | jsampFlipSign)); | |
557 } | |
558 } | |
559 } | |
560 | |
561 // Apply new values to sNumForNotes values | |
562 for (i = 0; i < XM_MaxNotes; i++) | |
563 { | |
564 tmp = xmI2.sNumForNotes[i]; | |
565 if (tmp >= 0 && tmp < xmI1.nsamples) | |
566 pEInst->sNumForNotes[i] = xmConvTable[tmp]; | |
567 else | |
568 { | |
569 pEInst->sNumForNotes[i] = jsetNotSet; | |
570 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
571 "Ext.instrument #%d sNumForNotes[%d] out of range (%d).\n", | |
572 ninst, i, tmp); | |
573 } | |
574 } | |
575 } | |
576 else | |
577 { | |
578 // We may STILL need to skip extra data after 1st instr. header | |
579 remainder = xmI1.headSize - (dmftell(inFile) - pos); | |
580 if (remainder > 0) | |
581 { | |
98 | 582 JSSDEBUG("xmI1#2 Skipping: %li\n", remainder); |
0 | 583 dmfseek(inFile, remainder, SEEK_CUR); |
584 } | |
585 } | |
586 | |
587 return 0; | |
588 } | |
589 | |
590 | |
591 /* Load XM-format module from given file-stream | |
592 */ | |
593 int jssLoadXM(DMResource *inFile, JSSModule **ppModule) | |
594 { | |
595 JSSModule *module; | |
596 XMHeader xmH; | |
597 int result, index, tmp; | |
598 | |
599 assert(ppModule != NULL); | |
600 assert(inFile != NULL); | |
601 *ppModule = NULL; | |
602 | |
603 /* Get XM-header and check it | |
604 */ | |
605 dmf_read_str(inFile, (Uint8 *) &xmH.idMagic, sizeof(xmH.idMagic)); | |
606 dmf_read_str(inFile, (Uint8 *) &xmH.songName, sizeof(xmH.songName)); | |
607 xmH.unUsed1A = dmfgetc(inFile); | |
608 dmf_read_str(inFile, (Uint8 *) &xmH.trackerName, sizeof(xmH.trackerName)); | |
609 dmf_read_le16(inFile, &xmH.version); | |
610 dmf_read_le32(inFile, &xmH.headSize); | |
611 dmf_read_le16(inFile, &xmH.norders); | |
612 dmf_read_le16(inFile, &xmH.defRestartPos); | |
613 dmf_read_le16(inFile, &xmH.nchannels); | |
614 dmf_read_le16(inFile, &xmH.npatterns); | |
615 dmf_read_le16(inFile, &xmH.ninstruments); | |
616 dmf_read_le16(inFile, &xmH.flags); | |
617 dmf_read_le16(inFile, &xmH.defSpeed); | |
618 dmf_read_le16(inFile, &xmH.defTempo); | |
619 dmf_read_str(inFile, (Uint8 *)&xmH.orderList, sizeof(xmH.orderList)); | |
620 | |
621 | |
622 // Check the fields, none of these are considered fatal | |
623 if (strncmp(xmH.idMagic, "Extended Module: ", 17) != 0) | |
624 { | |
625 JSSERROR(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED, | |
626 "Not a FT2 Extended Module (XM), ident mismatch!\n"); | |
627 } | |
628 | |
629 if (xmH.version != 0x0104) | |
630 { | |
631 JSSWARNING(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED, | |
632 "Unsupported version of XM format 0x%04x instead of expected 0x0104.\n", | |
633 xmH.version); | |
634 } | |
635 | |
636 if (xmH.unUsed1A != 0x1a) | |
637 { | |
638 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
639 "Possibly modified or corrupted XM [%x]\n", xmH.unUsed1A); | |
640 } | |
641 | |
642 if (xmH.norders > XM_MaxOrders) | |
643 { | |
644 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
645 "Number of orders %i > %i, possibly broken module.\n", | |
646 xmH.norders, XM_MaxOrders); | |
647 xmH.norders = XM_MaxOrders; | |
648 } | |
649 | |
650 if (xmH.norders == 0) | |
651 { | |
652 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
653 "Number of orders was zero.\n"); | |
654 } | |
655 | |
656 if (xmH.npatterns > XM_MaxPatterns) | |
657 { | |
658 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
659 "Number of patterns %i > %i, possibly broken module.\n", | |
660 xmH.npatterns, XM_MaxPatterns); | |
661 xmH.npatterns = XM_MaxPatterns; | |
662 } | |
663 | |
664 if (xmH.npatterns == 0) | |
665 { | |
666 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
667 "Number of patterns was zero.\n"); | |
668 } | |
669 | |
670 if (xmH.nchannels <= 0 || xmH.nchannels > XM_MaxChannels) | |
671 { | |
672 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
673 "Number of channels was invalid, %i (should be 1 - %i).\n", | |
674 xmH.nchannels, XM_MaxChannels); | |
675 } | |
676 | |
677 if (xmH.ninstruments <= 0 || xmH.ninstruments > XM_MaxInstruments) | |
678 { | |
679 JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
680 "Number of instruments was invalid, %i (should be 1 - %i).\n", | |
681 xmH.ninstruments, XM_MaxInstruments); | |
682 } | |
683 | |
684 /* Okay, allocate a module structure | |
685 */ | |
686 module = jssAllocateModule(); | |
687 if (module == NULL) | |
688 { | |
689 JSSERROR(DMERR_MALLOC, DMERR_MALLOC, | |
690 "Could not allocate memory for module structure.\n"); | |
691 } | |
692 *ppModule = module; | |
693 | |
694 | |
695 // Convert and check the header data | |
696 module->moduleType = jmdtXM; | |
697 module->intVersion = xmH.version; | |
698 #ifndef JSS_LIGHT | |
699 module->moduleName = jssASCIItoStr(xmH.songName, 0, sizeof(xmH.songName)); | |
700 module->trackerName = jssASCIItoStr(xmH.trackerName, 0, sizeof(xmH.trackerName)); | |
701 #endif | |
702 module->defSpeed = xmH.defSpeed; | |
703 module->defTempo = xmH.defTempo; | |
704 module->nextInstruments = xmH.ninstruments; | |
705 module->ninstruments = 0; | |
706 module->npatterns = xmH.npatterns; | |
707 module->norders = xmH.norders; | |
708 module->nchannels = xmH.nchannels; | |
709 module->defFlags = jmdfStereo | jmdfFT2Replay; | |
710 module->defRestartPos = xmH.defRestartPos; | |
711 | |
712 if ((xmH.flags & 1) == 0) | |
713 module->defFlags |= jmdfAmigaPeriods; | |
714 | |
715 // Setup the default pannings | |
716 for (index = 0; index < jsetNChannels; index++) | |
717 module->defPanning[index] = jchPanMiddle; | |
718 | |
719 /* Read patterns | |
720 */ | |
721 for (index = 0; index < module->npatterns; index++) | |
722 { | |
88 | 723 off_t pos, remainder; |
0 | 724 XMPattern xmP; |
725 | |
726 // Get the pattern header | |
98 | 727 pos = dmftell(inFile); |
0 | 728 dmf_read_le32(inFile, &xmP.headSize); |
729 xmP.packing = dmfgetc(inFile); | |
730 dmf_read_le16(inFile, &xmP.nrows); | |
731 dmf_read_le16(inFile, &xmP.size); | |
732 | |
733 // Check the header | |
734 if (xmP.packing != 0) | |
735 JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
736 "Pattern #%i packing type unsupported (%i)\n", | |
737 index, xmP.packing); | |
738 | |
739 if (xmP.nrows == 0) | |
740 JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, | |
741 "Pattern #%i has %i rows, invalid data.\n", | |
742 index, xmP.nrows); | |
743 | |
744 if (xmP.size > 0) | |
745 { | |
746 // Allocate and unpack pattern | |
747 module->patterns[index] = jssAllocatePattern(xmP.nrows, module->nchannels); | |
748 if (module->patterns[index] == NULL) | |
749 JSSERROR(DMERR_MALLOC, DMERR_MALLOC, | |
750 "Could not allocate memory for pattern #%i\n", index); | |
751 | |
752 result = jssXMUnpackPattern(inFile, xmP.size, module->patterns[index]); | |
753 if (result != 0) | |
754 JSSERROR(result, result, "Error in unpacking pattern #%i data\n", index); | |
755 } | |
756 | |
757 // Skip extra data (if the file is damaged) | |
88 | 758 remainder = xmP.headSize - (dmftell(inFile) - pos); |
759 if (remainder > 0) | |
0 | 760 { |
98 | 761 JSSDEBUG("xmP Skipping: %li\n", remainder); |
88 | 762 dmfseek(inFile, remainder, SEEK_CUR); |
0 | 763 } |
764 } | |
765 | |
766 // Allocate the empty pattern | |
767 module->patterns[jsetMaxPatterns] = jssAllocatePattern(64, module->nchannels); | |
768 | |
769 /* Convert song orders list by replacing nonexisting patterns | |
770 * with pattern number jsetMaxPatterns. | |
771 */ | |
772 for (index = 0; index < module->norders; index++) | |
773 { | |
774 tmp = xmH.orderList[index]; | |
775 if (tmp >= module->npatterns || module->patterns[tmp] == NULL) | |
776 tmp = jsetMaxPatterns; | |
777 | |
778 module->orderList[index] = tmp; | |
779 } | |
780 | |
781 /* Read instruments | |
782 */ | |
783 for (index = 0; index < module->nextInstruments; index++) | |
784 { | |
785 result = jssXMLoadExtInstrument(inFile, index, module); | |
786 if (result != 0) | |
787 JSSERROR(result, result, "Errors while reading instrument #%i\n", index); | |
788 } | |
789 | |
790 return DMERR_OK; | |
791 } |