Mercurial > codedump
comparison win95kggui/dep/ft2play/pmplay.c @ 126:8e4ee43d3b81
remove submodules
author | Paper <mrpapersonic@gmail.com> |
---|---|
date | Sun, 01 Oct 2023 03:48:43 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
125:5cc85ef3a675 | 126:8e4ee43d3b81 |
---|---|
1 /* | |
2 ** - loaders and replayer handlers - | |
3 */ | |
4 | |
5 #define DEFAULT_AMP 4 | |
6 #define DEFAULT_MASTER_VOL 256 | |
7 | |
8 #include <stdio.h> | |
9 #include <stdlib.h> | |
10 #include <string.h> | |
11 #include <stdint.h> | |
12 #include <stdbool.h> | |
13 #include <math.h> | |
14 #include <assert.h> | |
15 #include "pmplay.h" | |
16 #include "pmp_mix.h" | |
17 #include "snd_masm.h" | |
18 #include "tables.h" | |
19 | |
20 #define INSTR_HEADER_SIZE 263 | |
21 | |
22 #define SWAP16(value) \ | |
23 ( \ | |
24 (((uint16_t)((value) & 0x00FF)) << 8) | \ | |
25 (((uint16_t)((value) & 0xFF00)) >> 8) \ | |
26 ) | |
27 | |
28 #ifdef _MSC_VER | |
29 #pragma pack(push) | |
30 #pragma pack(1) | |
31 #endif | |
32 typedef struct songHeaderTyp_t | |
33 { | |
34 char sig[17], name[21], progName[20]; | |
35 uint16_t ver; | |
36 int32_t headerSize; | |
37 uint16_t len, repS, antChn, antPtn, antInstrs, flags, defTempo, defSpeed; | |
38 uint8_t songTab[256]; | |
39 } | |
40 #ifdef __GNUC__ | |
41 __attribute__ ((packed)) | |
42 #endif | |
43 songHeaderTyp; | |
44 | |
45 typedef struct modSampleTyp | |
46 { | |
47 char name[22]; | |
48 uint16_t len; | |
49 uint8_t fine, vol; | |
50 uint16_t repS, repL; | |
51 } | |
52 #ifdef __GNUC__ | |
53 __attribute__ ((packed)) | |
54 #endif | |
55 modSampleTyp; | |
56 | |
57 typedef struct songMOD31HeaderTyp | |
58 { | |
59 char name[20]; | |
60 modSampleTyp sample[31]; | |
61 uint8_t len, repS, songTab[128]; | |
62 char Sig[4]; | |
63 } | |
64 #ifdef __GNUC__ | |
65 __attribute__ ((packed)) | |
66 #endif | |
67 songMOD31HeaderTyp; | |
68 | |
69 typedef struct songMOD15HeaderTyp | |
70 { | |
71 char name[20]; | |
72 modSampleTyp sample[15]; | |
73 uint8_t len, repS, songTab[128]; | |
74 } | |
75 #ifdef __GNUC__ | |
76 __attribute__ ((packed)) | |
77 #endif | |
78 songMOD15HeaderTyp; | |
79 | |
80 typedef struct sampleHeaderTyp_t | |
81 { | |
82 int32_t len, repS, repL; | |
83 uint8_t vol; | |
84 int8_t fine; | |
85 uint8_t typ, pan; | |
86 int8_t relTon; | |
87 uint8_t skrap; | |
88 char name[22]; | |
89 } | |
90 #ifdef __GNUC__ | |
91 __attribute__ ((packed)) | |
92 #endif | |
93 sampleHeaderTyp; | |
94 | |
95 typedef struct instrHeaderTyp_t | |
96 { | |
97 int32_t instrSize; | |
98 char name[22]; | |
99 uint8_t typ; | |
100 uint16_t antSamp; | |
101 int32_t sampleSize; | |
102 uint8_t ta[96]; | |
103 int16_t envVP[12][2], envPP[12][2]; | |
104 uint8_t envVPAnt, envPPAnt, envVSust, envVRepS, envVRepE, envPSust, envPRepS; | |
105 uint8_t envPRepE, envVTyp, envPTyp, vibTyp, vibSweep, vibDepth, vibRate; | |
106 uint16_t fadeOut; | |
107 uint8_t midiOn, midiChannel; | |
108 int16_t midiProgram, midiBend; | |
109 int8_t mute; | |
110 uint8_t reserved[15]; | |
111 sampleHeaderTyp samp[32]; | |
112 } | |
113 #ifdef __GNUC__ | |
114 __attribute__ ((packed)) | |
115 #endif | |
116 instrHeaderTyp; | |
117 | |
118 typedef struct patternHeaderTyp_t | |
119 { | |
120 int32_t patternHeaderSize; | |
121 uint8_t typ; | |
122 uint16_t pattLen, dataLen; | |
123 } | |
124 #ifdef __GNUC__ | |
125 __attribute__ ((packed)) | |
126 #endif | |
127 patternHeaderTyp; | |
128 #ifdef _MSC_VER | |
129 #pragma pack(pop) | |
130 #endif | |
131 | |
132 static int32_t soundBufferSize; | |
133 | |
134 // globalized | |
135 volatile bool interpolationFlag, volumeRampingFlag, moduleLoaded, musicPaused, WAVDump_Flag; | |
136 bool linearFrqTab; | |
137 volatile const uint16_t *note2Period; | |
138 uint16_t pattLens[256]; | |
139 int16_t PMPTmpActiveChannel, boostLevel = DEFAULT_AMP; | |
140 int32_t masterVol = DEFAULT_MASTER_VOL, PMPLeft = 0; | |
141 int32_t realReplayRate, quickVolSizeVal, speedVal; | |
142 uint32_t frequenceDivFactor, frequenceMulFactor, CDA_Amp = 8*DEFAULT_AMP; | |
143 tonTyp *patt[256]; | |
144 instrTyp *instr[1+128]; | |
145 songTyp song; | |
146 stmTyp stm[32]; | |
147 // ------------------ | |
148 | |
149 // 8bb: added these for loader | |
150 typedef struct | |
151 { | |
152 uint8_t *_ptr, *_base; | |
153 bool _eof; | |
154 size_t _cnt, _bufsiz; | |
155 } MEMFILE; | |
156 | |
157 static MEMFILE *mopen(const uint8_t *src, uint32_t length); | |
158 static void mclose(MEMFILE **buf); | |
159 static size_t mread(void *buffer, size_t size, size_t count, MEMFILE *buf); | |
160 static bool meof(MEMFILE *buf); | |
161 static void mseek(MEMFILE *buf, int32_t offset, int32_t whence); | |
162 static void mrewind(MEMFILE *buf); | |
163 // -------------------------- | |
164 | |
165 static void resetMusic(void); | |
166 static void freeAllPatterns(void); | |
167 static void setFrqTab(bool linear); | |
168 | |
169 static CIType *getVoice(int32_t ch) // 8bb: added this | |
170 { | |
171 if (ch < 0 || ch > 31) | |
172 return NULL; | |
173 | |
174 return &CI[chnReloc[ch]]; | |
175 } | |
176 | |
177 /*************************************************************************** | |
178 * ROUTINES FOR SAMPLE HANDLING ETC. * | |
179 ***************************************************************************/ | |
180 | |
181 // 8bb: modifies wrapped sample after loop/end (for branchless mixer interpolation) | |
182 static void fixSample(sampleTyp *s) | |
183 { | |
184 if (s->pek == NULL) | |
185 return; // empty sample | |
186 | |
187 const bool sample16Bit = !!(s->typ & SAMPLE_16BIT); | |
188 uint8_t loopType = s->typ & 3; | |
189 int16_t *ptr16 = (int16_t *)s->pek; | |
190 int32_t len = s->len; | |
191 int32_t loopStart = s->repS; | |
192 int32_t loopEnd = s->repS + s->repL; | |
193 | |
194 if (sample16Bit) | |
195 { | |
196 len >>= 1; | |
197 loopStart >>= 1; | |
198 loopEnd >>= 1; | |
199 } | |
200 | |
201 if (len < 1) | |
202 return; | |
203 | |
204 /* 8bb: | |
205 ** This is the exact bit test order of which FT2 handles | |
206 ** the sample tap fix. | |
207 ** | |
208 ** This order is important for rare cases where both the | |
209 ** "forward" and "pingpong" loop bits are set at once. | |
210 ** | |
211 ** This means that if both flags are set, the mixer will | |
212 ** play the sample with pingpong looping, but the sample fix | |
213 ** is handled as if it was a forward loop. This results in | |
214 ** the wrong interpolation tap sample being written after the | |
215 ** loop end point. | |
216 */ | |
217 | |
218 if (loopType & LOOP_FORWARD) | |
219 { | |
220 if (sample16Bit) | |
221 ptr16[loopEnd] = ptr16[loopStart]; | |
222 else | |
223 s->pek[loopEnd] = s->pek[loopStart]; | |
224 | |
225 return; | |
226 } | |
227 else if (loopType & LOOP_PINGPONG) | |
228 { | |
229 if (sample16Bit) | |
230 ptr16[loopEnd] = ptr16[loopEnd-1]; | |
231 else | |
232 s->pek[loopEnd] = s->pek[loopEnd-1]; | |
233 } | |
234 else // no loop | |
235 { | |
236 if (sample16Bit) | |
237 ptr16[len] = 0; | |
238 else | |
239 s->pek[len] = 0; | |
240 } | |
241 } | |
242 | |
243 static void checkSampleRepeat(int32_t nr, int32_t nr2) | |
244 { | |
245 instrTyp *i = instr[nr]; | |
246 if (i == NULL) | |
247 return; | |
248 | |
249 sampleTyp *s = &i->samp[nr2]; | |
250 | |
251 if (s->repS < 0) s->repS = 0; | |
252 if (s->repL < 0) s->repL = 0; | |
253 if (s->repS > s->len) s->repS = s->len; | |
254 if (s->repS+s->repL > s->len) s->repL = s->len - s->repS; | |
255 } | |
256 | |
257 static void upDateInstrs(void) | |
258 { | |
259 for (int32_t i = 0; i <= 128; i++) | |
260 { | |
261 instrTyp *ins = instr[i]; | |
262 if (ins == NULL) | |
263 continue; | |
264 | |
265 sampleTyp *s = ins->samp; | |
266 for (int32_t j = 0; j < 16; j++, s++) | |
267 { | |
268 checkSampleRepeat(i, j); | |
269 fixSample(s); | |
270 | |
271 if (s->pek == NULL) | |
272 { | |
273 s->len = 0; | |
274 s->repS = 0; | |
275 s->repL = 0; | |
276 } | |
277 } | |
278 } | |
279 } | |
280 | |
281 static bool patternEmpty(uint16_t nr) | |
282 { | |
283 if (patt[nr] == NULL) | |
284 return true; | |
285 | |
286 const uint8_t *scanPtr = (const uint8_t *)patt[nr]; | |
287 const int32_t scanLen = pattLens[nr] * song.antChn * sizeof (tonTyp); | |
288 | |
289 for (int32_t i = 0; i < scanLen; i++) | |
290 { | |
291 if (scanPtr[i] != 0) | |
292 return false; | |
293 } | |
294 | |
295 return true; | |
296 } | |
297 | |
298 static bool allocateInstr(uint16_t i) | |
299 { | |
300 if (instr[i] != NULL) | |
301 return true; | |
302 | |
303 instrTyp *p = (instrTyp *)calloc(1, sizeof (instrTyp)); | |
304 if (p == NULL) | |
305 return false; | |
306 | |
307 sampleTyp *s = p->samp; | |
308 for (int32_t j = 0; j < 16; j++, s++) | |
309 { | |
310 s->pan = 128; | |
311 s->vol = 64; | |
312 } | |
313 | |
314 instr[i] = p; | |
315 return true; | |
316 } | |
317 | |
318 static void freeInstr(uint16_t nr) | |
319 { | |
320 if (nr > 128) | |
321 return; | |
322 | |
323 instrTyp *ins = instr[nr]; | |
324 if (ins == NULL) | |
325 return; | |
326 | |
327 sampleTyp *s = ins->samp; | |
328 for (uint8_t i = 0; i < 16; i++, s++) | |
329 { | |
330 if (s->pek != NULL) | |
331 free(s->pek); | |
332 } | |
333 | |
334 free(ins); | |
335 instr[nr] = NULL; | |
336 } | |
337 | |
338 static void freeAllInstr(void) | |
339 { | |
340 for (uint16_t i = 0; i <= 128; i++) | |
341 freeInstr(i); | |
342 } | |
343 | |
344 static void freeAllPatterns(void) // 8bb: added this one, since it's handy | |
345 { | |
346 for (int32_t i = 0; i < 256; i++) | |
347 { | |
348 if (patt[i] != NULL) | |
349 { | |
350 free(patt[i]); | |
351 patt[i] = NULL; | |
352 } | |
353 | |
354 pattLens[i] = 64; | |
355 } | |
356 } | |
357 | |
358 static void delta2Samp(int8_t *p, uint32_t len, bool sample16Bit) | |
359 { | |
360 if (sample16Bit) | |
361 { | |
362 len >>= 1; | |
363 | |
364 int16_t *p16 = (int16_t *)p; | |
365 | |
366 int16_t olds16 = 0; | |
367 for (uint32_t i = 0; i < len; i++) | |
368 { | |
369 const int16_t news16 = p16[i] + olds16; | |
370 p16[i] = news16; | |
371 olds16 = news16; | |
372 } | |
373 } | |
374 else | |
375 { | |
376 int8_t *p8 = (int8_t *)p; | |
377 | |
378 int8_t olds8 = 0; | |
379 for (uint32_t i = 0; i < len; i++) | |
380 { | |
381 const int8_t news8 = p8[i] + olds8; | |
382 p8[i] = news8; | |
383 olds8 = news8; | |
384 } | |
385 } | |
386 } | |
387 | |
388 static void unpackPatt(uint8_t *dst, uint16_t inn, uint16_t len, uint8_t antChn) | |
389 { | |
390 if (dst == NULL) | |
391 return; | |
392 | |
393 const uint8_t *src = dst + inn; | |
394 const int32_t srcEnd = len * (sizeof (tonTyp) * antChn); | |
395 | |
396 int32_t srcIdx = 0; | |
397 for (int32_t i = 0; i < len; i++) | |
398 { | |
399 for (int32_t j = 0; j < antChn; j++) | |
400 { | |
401 if (srcIdx >= srcEnd) | |
402 return; // error! | |
403 | |
404 const uint8_t note = *src++; | |
405 if (note & 0x80) | |
406 { | |
407 *dst++ = (note & 0x01) ? *src++ : 0; | |
408 *dst++ = (note & 0x02) ? *src++ : 0; | |
409 *dst++ = (note & 0x04) ? *src++ : 0; | |
410 *dst++ = (note & 0x08) ? *src++ : 0; | |
411 *dst++ = (note & 0x10) ? *src++ : 0; | |
412 } | |
413 else | |
414 { | |
415 *dst++ = note; | |
416 *dst++ = *src++; | |
417 *dst++ = *src++; | |
418 *dst++ = *src++; | |
419 *dst++ = *src++; | |
420 } | |
421 | |
422 // 8bb: added this. If note >97, remove it (prevents out-of-range read in note->sample LUT) | |
423 if (*(dst-5) > 97) | |
424 *(dst-5) = 0; | |
425 | |
426 srcIdx += sizeof (tonTyp); | |
427 } | |
428 } | |
429 } | |
430 | |
431 void freeMusic(void) | |
432 { | |
433 stopMusic(); | |
434 freeAllInstr(); | |
435 freeAllPatterns(); | |
436 | |
437 song.tempo = 6; | |
438 song.speed = 125; | |
439 song.timer = 1; | |
440 | |
441 setFrqTab(true); | |
442 resetMusic(); | |
443 } | |
444 | |
445 void stopVoices(void) | |
446 { | |
447 lockMixer(); | |
448 | |
449 stmTyp *ch = stm; | |
450 for (uint8_t i = 0; i < 32; i++, ch++) | |
451 { | |
452 ch->tonTyp = 0; | |
453 ch->relTonNr = 0; | |
454 ch->instrNr = 0; | |
455 ch->instrSeg = instr[0]; // 8bb: placeholder instrument | |
456 ch->status = IS_Vol; | |
457 | |
458 ch->realVol = 0; | |
459 ch->outVol = 0; | |
460 ch->oldVol = 0; | |
461 ch->finalVol = 0; | |
462 ch->oldPan = 128; | |
463 ch->outPan = 128; | |
464 ch->finalPan = 128; | |
465 ch->vibDepth = 0; | |
466 } | |
467 | |
468 unlockMixer(); | |
469 } | |
470 | |
471 static void resetMusic(void) | |
472 { | |
473 song.timer = 1; | |
474 stopVoices(); | |
475 setPos(0, 0); | |
476 } | |
477 | |
478 void setPos(int32_t pos, int32_t row) // -1 = don't change | |
479 { | |
480 if (pos != -1) | |
481 { | |
482 song.songPos = (int16_t)pos; | |
483 if (song.len > 0 && song.songPos >= song.len) | |
484 song.songPos = song.len - 1; | |
485 | |
486 song.pattNr = song.songTab[song.songPos]; | |
487 song.pattLen = pattLens[song.pattNr]; | |
488 } | |
489 | |
490 if (row != -1) | |
491 { | |
492 song.pattPos = (int16_t)row; | |
493 if (song.pattPos >= song.pattLen) | |
494 song.pattPos = song.pattLen - 1; | |
495 } | |
496 | |
497 song.timer = 1; | |
498 } | |
499 | |
500 /*************************************************************************** | |
501 * MODULE LOADING ROUTINES * | |
502 ***************************************************************************/ | |
503 | |
504 static bool loadInstrHeader(MEMFILE *f, uint16_t i) | |
505 { | |
506 instrHeaderTyp ih; | |
507 | |
508 memset(&ih, 0, INSTR_HEADER_SIZE); | |
509 mread(&ih.instrSize, 4, 1, f); | |
510 if (ih.instrSize > INSTR_HEADER_SIZE) ih.instrSize = INSTR_HEADER_SIZE; | |
511 | |
512 if (ih.instrSize < 4) // 8bb: added protection | |
513 return false; | |
514 | |
515 mread(ih.name, ih.instrSize-4, 1, f); | |
516 | |
517 if (ih.antSamp > 16) | |
518 return false; | |
519 | |
520 if (ih.antSamp > 0) | |
521 { | |
522 if (!allocateInstr(i)) | |
523 return false; | |
524 | |
525 instrTyp *ins = instr[i]; | |
526 | |
527 memcpy(ins->name, ih.name, 22); | |
528 ins->name[22] = '\0'; | |
529 | |
530 // 8bb: copy instrument header elements to our instrument struct | |
531 memcpy(ins->ta, ih.ta, 96); | |
532 memcpy(ins->envVP, ih.envVP, 12*2*sizeof(int16_t)); | |
533 memcpy(ins->envPP, ih.envPP, 12*2*sizeof(int16_t)); | |
534 ins->envVPAnt = ih.envVPAnt; | |
535 ins->envPPAnt = ih.envPPAnt; | |
536 ins->envVSust = ih.envVSust; | |
537 ins->envVRepS = ih.envVRepS; | |
538 ins->envVRepE = ih.envVRepE; | |
539 ins->envPSust = ih.envPSust; | |
540 ins->envPRepS = ih.envPRepS; | |
541 ins->envPRepE = ih.envPRepE; | |
542 ins->envVTyp = ih.envVTyp; | |
543 ins->envPTyp = ih.envPTyp; | |
544 ins->vibTyp = ih.vibTyp; | |
545 ins->vibSweep = ih.vibSweep; | |
546 ins->vibDepth = ih.vibDepth; | |
547 ins->vibRate = ih.vibRate; | |
548 ins->fadeOut = ih.fadeOut; | |
549 ins->mute = (ih.mute == 1) ? true : false; // 8bb: correct logic! | |
550 ins->antSamp = ih.antSamp; | |
551 | |
552 if (mread(ih.samp, ih.antSamp * sizeof (sampleHeaderTyp), 1, f) != 1) | |
553 return false; | |
554 | |
555 sampleTyp *s = instr[i]->samp; | |
556 sampleHeaderTyp *src = ih.samp; | |
557 for (int32_t j = 0; j < ih.antSamp; j++, s++, src++) | |
558 { | |
559 memcpy(s->name, src->name, 22); | |
560 s->name[22] = '\0'; | |
561 | |
562 s->len = src->len; | |
563 s->repS = src->repS; | |
564 s->repL = src->repL; | |
565 s->vol = src->vol; | |
566 s->fine = src->fine; | |
567 s->typ = src->typ; | |
568 s->pan = src->pan; | |
569 s->relTon = src->relTon; | |
570 } | |
571 } | |
572 | |
573 return true; | |
574 } | |
575 | |
576 static bool loadInstrSample(MEMFILE *f, uint16_t i) | |
577 { | |
578 if (instr[i] == NULL) | |
579 return true; // empty instrument | |
580 | |
581 sampleTyp *s = instr[i]->samp; | |
582 for (uint16_t j = 0; j < instr[i]->antSamp; j++, s++) | |
583 { | |
584 if (s->len > 0) | |
585 { | |
586 bool sample16Bit = !!(s->typ & SAMPLE_16BIT); | |
587 | |
588 s->pek = (int8_t *)malloc(s->len+2); // 8bb: +2 for fixed interpolation tap sample | |
589 if (s->pek == NULL) | |
590 return false; | |
591 | |
592 mread(s->pek, 1, s->len, f); | |
593 delta2Samp(s->pek, s->len, sample16Bit); | |
594 } | |
595 | |
596 checkSampleRepeat(i, j); | |
597 } | |
598 | |
599 return true; | |
600 } | |
601 | |
602 static bool loadPatterns(MEMFILE *f, uint16_t antPtn) | |
603 { | |
604 uint8_t tmpLen; | |
605 patternHeaderTyp ph; | |
606 | |
607 for (uint16_t i = 0; i < antPtn; i++) | |
608 { | |
609 mread(&ph.patternHeaderSize, 4, 1, f); | |
610 mread(&ph.typ, 1, 1, f); | |
611 | |
612 ph.pattLen = 0; | |
613 if (song.ver == 0x0102) | |
614 { | |
615 mread(&tmpLen, 1, 1, f); | |
616 mread(&ph.dataLen, 2, 1, f); | |
617 ph.pattLen = (uint16_t)tmpLen + 1; // 8bb: +1 in v1.02 | |
618 | |
619 if (ph.patternHeaderSize > 8) | |
620 mseek(f, ph.patternHeaderSize - 8, SEEK_CUR); | |
621 } | |
622 else | |
623 { | |
624 mread(&ph.pattLen, 2, 1, f); | |
625 mread(&ph.dataLen, 2, 1, f); | |
626 | |
627 if (ph.patternHeaderSize > 9) | |
628 mseek(f, ph.patternHeaderSize - 9, SEEK_CUR); | |
629 } | |
630 | |
631 if (meof(f)) | |
632 { | |
633 mclose(&f); | |
634 return false; | |
635 } | |
636 | |
637 pattLens[i] = ph.pattLen; | |
638 if (ph.dataLen) | |
639 { | |
640 const uint16_t a = ph.pattLen * song.antChn * sizeof (tonTyp); | |
641 | |
642 patt[i] = (tonTyp *)malloc(a); | |
643 if (patt[i] == NULL) | |
644 return false; | |
645 | |
646 uint8_t *pattPtr = (uint8_t *)patt[i]; | |
647 | |
648 memset(pattPtr, 0, a); | |
649 mread(&pattPtr[a - ph.dataLen], 1, ph.dataLen, f); | |
650 unpackPatt(pattPtr, a - ph.dataLen, ph.pattLen, song.antChn); | |
651 } | |
652 | |
653 if (patternEmpty(i)) | |
654 { | |
655 if (patt[i] != NULL) | |
656 { | |
657 free(patt[i]); | |
658 patt[i] = NULL; | |
659 } | |
660 | |
661 pattLens[i] = 64; | |
662 } | |
663 } | |
664 | |
665 return true; | |
666 } | |
667 | |
668 static bool loadMusicMOD(MEMFILE *f) | |
669 { | |
670 uint8_t ha[sizeof (songMOD31HeaderTyp)]; | |
671 songMOD31HeaderTyp *h_MOD31 = (songMOD31HeaderTyp *)ha; | |
672 songMOD15HeaderTyp *h_MOD15 = (songMOD15HeaderTyp *)ha; | |
673 | |
674 mread(ha, sizeof (ha), 1, f); | |
675 if (meof(f)) | |
676 goto loadError2; | |
677 | |
678 memcpy(song.name, h_MOD31->name, 20); | |
679 song.name[20] = '\0'; | |
680 | |
681 uint8_t j = 0; | |
682 for (uint8_t i = 1; i <= 16; i++) | |
683 { | |
684 if (memcmp(h_MOD31->Sig, MODSig[i-1], 4) == 0) | |
685 j = i + i; | |
686 } | |
687 | |
688 if (memcmp(h_MOD31->Sig, "M!K!", 4) == 0 || memcmp(h_MOD31->Sig, "FLT4", 4) == 0) | |
689 j = 4; | |
690 | |
691 if (memcmp(h_MOD31->Sig, "OCTA", 4) == 0) | |
692 j = 8; | |
693 | |
694 uint8_t typ; | |
695 if (j > 0) | |
696 { | |
697 typ = 1; | |
698 song.antChn = j; | |
699 } | |
700 else | |
701 { | |
702 typ = 2; | |
703 song.antChn = 4; | |
704 } | |
705 | |
706 int16_t ai; | |
707 if (typ == 1) | |
708 { | |
709 mseek(f, sizeof (songMOD31HeaderTyp), SEEK_SET); | |
710 song.len = h_MOD31->len; | |
711 song.repS = h_MOD31->repS; | |
712 memcpy(song.songTab, h_MOD31->songTab, 128); | |
713 ai = 31; | |
714 } | |
715 else | |
716 { | |
717 mseek(f, sizeof (songMOD15HeaderTyp), SEEK_SET); | |
718 song.len = h_MOD15->len; | |
719 song.repS = h_MOD15->repS; | |
720 memcpy(song.songTab, h_MOD15->songTab, 128); | |
721 ai = 15; | |
722 } | |
723 | |
724 song.antInstrs = ai; // 8bb: added this | |
725 | |
726 if (meof(f)) | |
727 goto loadError2; | |
728 | |
729 int32_t b = 0; | |
730 for (int32_t a = 0; a < 128; a++) | |
731 { | |
732 if (song.songTab[a] > b) | |
733 b = song.songTab[a]; | |
734 } | |
735 | |
736 uint8_t pattBuf[32 * 4 * 64]; // 8bb: max pattern size (32 channels, 64 rows) | |
737 for (uint16_t a = 0; a <= b; a++) | |
738 { | |
739 patt[a] = (tonTyp *)calloc(song.antChn * 64, sizeof (tonTyp)); | |
740 if (patt[a] == NULL) | |
741 goto loadError; | |
742 | |
743 pattLens[a] = 64; | |
744 | |
745 mread(pattBuf, 1, song.antChn * 4 * 64, f); | |
746 if (meof(f)) | |
747 goto loadError; | |
748 | |
749 // convert pattern | |
750 uint8_t *bytes = pattBuf; | |
751 tonTyp *ton = patt[a]; | |
752 for (int32_t i = 0; i < 64 * song.antChn; i++, bytes += 4, ton++) | |
753 { | |
754 const uint16_t period = ((bytes[0] & 0x0F) << 8) | bytes[1]; | |
755 for (uint8_t k = 0; k < 96; k++) | |
756 { | |
757 if (period >= amigaPeriod[k]) | |
758 { | |
759 ton->ton = k+1; | |
760 break; | |
761 } | |
762 } | |
763 | |
764 ton->instr = (bytes[0] & 0xF0) | (bytes[2] >> 4); | |
765 ton->effTyp = bytes[2] & 0x0F; | |
766 ton->eff = bytes[3]; | |
767 | |
768 switch (ton->effTyp) | |
769 { | |
770 case 0xC: | |
771 { | |
772 if (ton->eff > 64) | |
773 ton->eff = 64; | |
774 } | |
775 break; | |
776 | |
777 case 0x1: | |
778 case 0x2: | |
779 { | |
780 if (ton->eff == 0) | |
781 ton->effTyp = 0; | |
782 } | |
783 break; | |
784 | |
785 case 0x5: | |
786 { | |
787 if (ton->eff == 0) | |
788 ton->effTyp = 3; | |
789 } | |
790 break; | |
791 | |
792 case 0x6: | |
793 { | |
794 if (ton->eff == 0) | |
795 ton->effTyp = 4; | |
796 } | |
797 break; | |
798 | |
799 case 0xA: | |
800 { | |
801 if (ton->eff == 0) | |
802 ton->effTyp = 0; | |
803 } | |
804 break; | |
805 | |
806 case 0xE: | |
807 { | |
808 const uint8_t effTyp = ton->effTyp >> 4; | |
809 const uint8_t eff = ton->effTyp & 15; | |
810 | |
811 if (eff == 0 && (effTyp == 0x1 || effTyp == 0x2 || effTyp == 0xA || effTyp == 0xB)) | |
812 { | |
813 ton->eff = 0; | |
814 ton->effTyp = 0; | |
815 } | |
816 } | |
817 break; | |
818 | |
819 default: break; | |
820 } | |
821 } | |
822 | |
823 if (patternEmpty(a)) | |
824 { | |
825 free(patt[a]); | |
826 patt[a] = NULL; | |
827 pattLens[a] = 64; | |
828 } | |
829 } | |
830 | |
831 for (uint16_t a = 1; a <= ai; a++) | |
832 { | |
833 modSampleTyp *modSmp = &h_MOD31->sample[a-1]; | |
834 | |
835 uint32_t len = 2 * SWAP16(modSmp->len); | |
836 if (len == 0) | |
837 continue; | |
838 | |
839 if (!allocateInstr(a)) | |
840 goto loadError; | |
841 | |
842 sampleTyp *xmSmp = &instr[a]->samp[0]; | |
843 | |
844 memcpy(xmSmp->name, modSmp->name, 22); | |
845 xmSmp->name[22] = '\0'; | |
846 | |
847 uint32_t repS = 2 * SWAP16(modSmp->repS); | |
848 uint32_t repL = 2 * SWAP16(modSmp->repL); | |
849 | |
850 if (repL <= 2) | |
851 { | |
852 repS = 0; | |
853 repL = 0; | |
854 } | |
855 | |
856 if (repS+repL > len) | |
857 { | |
858 if (repS >= len) | |
859 { | |
860 repS = 0; | |
861 repL = 0; | |
862 } | |
863 else | |
864 { | |
865 repL = len-repS; | |
866 } | |
867 } | |
868 | |
869 xmSmp->typ = (repL > 2) ? 1 : 0; | |
870 xmSmp->len = len; | |
871 xmSmp->vol = (modSmp->vol <= 64) ? modSmp->vol : 64; | |
872 xmSmp->fine = 8 * ((2 * ((modSmp->fine & 15) ^ 8)) - 16); | |
873 xmSmp->repL = repL; | |
874 xmSmp->repS = repS; | |
875 | |
876 xmSmp->pek = (int8_t *)malloc(len + 2); | |
877 if (xmSmp->pek == NULL) | |
878 goto loadError; | |
879 | |
880 mread(xmSmp->pek, 1, len, f); | |
881 } | |
882 | |
883 mclose(&f); | |
884 | |
885 if (song.repS > song.len) | |
886 song.repS = 0; | |
887 | |
888 resetMusic(); | |
889 upDateInstrs(); | |
890 | |
891 moduleLoaded = true; | |
892 return true; | |
893 loadError: | |
894 freeAllInstr(); | |
895 freeAllPatterns(); | |
896 loadError2: | |
897 mclose(&f); | |
898 return false; | |
899 } | |
900 | |
901 bool loadMusicFromData(const uint8_t *data, uint32_t dataLength) // .XM/.MOD/.FT | |
902 { | |
903 uint16_t i; | |
904 songHeaderTyp h; | |
905 | |
906 freeMusic(); | |
907 setFrqTab(false); | |
908 | |
909 moduleLoaded = false; | |
910 | |
911 MEMFILE *f = mopen(data, dataLength); | |
912 if (f == NULL) | |
913 return false; | |
914 | |
915 // 8bb: instr 0 is a placeholder for empty instruments | |
916 allocateInstr(0); | |
917 instr[0]->samp[0].vol = 0; | |
918 | |
919 mread(&h, sizeof (h), 1, f); | |
920 if (meof(f)) | |
921 goto loadError2; | |
922 | |
923 if (memcmp(h.sig, "Extended Module: ", 17) != 0) | |
924 { | |
925 mrewind(f); | |
926 return loadMusicMOD(f); | |
927 } | |
928 | |
929 if (h.ver < 0x0102 || h.ver > 0x104 || h.antChn < 2 || h.antChn > 32 || (h.antChn & 1) != 0 || | |
930 h.antPtn > 256 || h.antInstrs > 128) | |
931 { | |
932 goto loadError2; | |
933 } | |
934 | |
935 mseek(f, 60+h.headerSize, SEEK_SET); | |
936 if (meof(f)) | |
937 goto loadError2; | |
938 | |
939 memcpy(song.name, h.name, 20); | |
940 song.name[20] = '\0'; | |
941 | |
942 song.len = h.len; | |
943 song.repS = h.repS; | |
944 song.antChn = (uint8_t)h.antChn; | |
945 bool linearFrequencies = !!(h.flags & LINEAR_FREQUENCIES); | |
946 setFrqTab(linearFrequencies); | |
947 memcpy(song.songTab, h.songTab, 256); | |
948 | |
949 song.antInstrs = h.antInstrs; // 8bb: added this | |
950 if (h.defSpeed == 0) h.defSpeed = 125; // 8bb: (BPM) FT2 doesn't do this, but we do it for safety | |
951 song.speed = h.defSpeed; | |
952 song.tempo = h.defTempo; | |
953 song.ver = h.ver; | |
954 | |
955 // 8bb: bugfixes... | |
956 if (song.speed < 1) song.speed = 1; | |
957 if (song.tempo < 1) song.tempo = 1; | |
958 // ---------------- | |
959 | |
960 if (song.ver < 0x0104) // old FT2 XM format | |
961 { | |
962 for (i = 1; i <= h.antInstrs; i++) | |
963 { | |
964 if (!loadInstrHeader(f, i)) | |
965 goto loadError; | |
966 } | |
967 | |
968 if (!loadPatterns(f, h.antPtn)) | |
969 goto loadError; | |
970 | |
971 for (i = 1; i <= h.antInstrs; i++) | |
972 { | |
973 if (!loadInstrSample(f, i)) | |
974 goto loadError; | |
975 } | |
976 } | |
977 else // latest FT2 XM format | |
978 { | |
979 if (!loadPatterns(f, h.antPtn)) | |
980 goto loadError; | |
981 | |
982 for (i = 1; i <= h.antInstrs; i++) | |
983 { | |
984 if (!loadInstrHeader(f, i)) | |
985 goto loadError; | |
986 | |
987 if (!loadInstrSample(f, i)) | |
988 goto loadError; | |
989 } | |
990 } | |
991 | |
992 mclose(&f); | |
993 | |
994 if (song.repS > song.len) | |
995 song.repS = 0; | |
996 | |
997 resetMusic(); | |
998 upDateInstrs(); | |
999 | |
1000 moduleLoaded = true; | |
1001 return true; | |
1002 | |
1003 loadError: | |
1004 freeAllInstr(); | |
1005 freeAllPatterns(); | |
1006 loadError2: | |
1007 mclose(&f); | |
1008 return false; | |
1009 } | |
1010 | |
1011 bool loadMusic(const char *fileName) // .XM/.MOD/.FT | |
1012 { | |
1013 FILE *f = fopen(fileName, "rb"); | |
1014 if (f == NULL) | |
1015 return false; | |
1016 | |
1017 fseek(f, 0, SEEK_END); | |
1018 const uint32_t fileSize = (uint32_t)ftell(f); | |
1019 rewind(f); | |
1020 | |
1021 uint8_t *fileBuffer = (uint8_t *)malloc(fileSize); | |
1022 if (fileBuffer == NULL) | |
1023 { | |
1024 fclose(f); | |
1025 return false; | |
1026 } | |
1027 | |
1028 if (fread(fileBuffer, 1, fileSize, f) != fileSize) | |
1029 { | |
1030 free(fileBuffer); | |
1031 fclose(f); | |
1032 return false; | |
1033 } | |
1034 | |
1035 fclose(f); | |
1036 | |
1037 if (!loadMusicFromData((const uint8_t *)fileBuffer, fileSize)) | |
1038 { | |
1039 free(fileBuffer); | |
1040 return false; | |
1041 } | |
1042 | |
1043 free(fileBuffer); | |
1044 return true; | |
1045 } | |
1046 | |
1047 /*************************************************************************** | |
1048 * PROCESS HANDLING * | |
1049 ***************************************************************************/ | |
1050 | |
1051 bool startMusic(void) | |
1052 { | |
1053 if (!moduleLoaded || song.speed == 0) | |
1054 return false; | |
1055 | |
1056 mix_ClearChannels(); | |
1057 stopVoices(); | |
1058 song.globVol = 64; | |
1059 | |
1060 speedVal = ((realReplayRate * 5) / 2) / song.speed; | |
1061 quickVolSizeVal = realReplayRate / 200; | |
1062 | |
1063 if (!mix_Init(soundBufferSize)) | |
1064 return false; | |
1065 | |
1066 if (openMixer(realReplayRate, soundBufferSize)) | |
1067 { | |
1068 musicPaused = false; | |
1069 return true; | |
1070 } | |
1071 | |
1072 return false; | |
1073 } | |
1074 | |
1075 void stopMusic(void) | |
1076 { | |
1077 pauseMusic(); | |
1078 | |
1079 closeMixer(); | |
1080 mix_Free(); | |
1081 song.globVol = 64; | |
1082 | |
1083 resumeMusic(); | |
1084 } | |
1085 | |
1086 void startPlaying(void) | |
1087 { | |
1088 stopMusic(); | |
1089 song.pattDelTime = song.pattDelTime2 = 0; // 8bb: added these | |
1090 setPos(0, 0); | |
1091 startMusic(); | |
1092 } | |
1093 | |
1094 void stopPlaying(void) | |
1095 { | |
1096 stopMusic(); | |
1097 stopVoices(); | |
1098 } | |
1099 | |
1100 void pauseMusic(void) | |
1101 { | |
1102 musicPaused = true; | |
1103 } | |
1104 | |
1105 void resumeMusic(void) | |
1106 { | |
1107 musicPaused = false; | |
1108 } | |
1109 | |
1110 // 8bb: added these three, handy | |
1111 void toggleMusic(void) | |
1112 { | |
1113 musicPaused ^= 1; | |
1114 } | |
1115 | |
1116 void setInterpolation(bool on) | |
1117 { | |
1118 interpolationFlag = on; | |
1119 mix_ClearChannels(); | |
1120 } | |
1121 | |
1122 void setVolumeRamping(bool on) | |
1123 { | |
1124 volumeRampingFlag = on; | |
1125 mix_ClearChannels(); | |
1126 } | |
1127 | |
1128 /*************************************************************************** | |
1129 * CONFIGURATION ROUTINES * | |
1130 ***************************************************************************/ | |
1131 | |
1132 void setMasterVol(int32_t v) // 0..256 | |
1133 { | |
1134 masterVol = CLAMP(v, 0, 256); | |
1135 | |
1136 stmTyp *ch = stm; | |
1137 for (int32_t i = 0; i < 32; i++, ch++) | |
1138 ch->status |= IS_Vol; | |
1139 } | |
1140 | |
1141 void setAmp(int32_t level) // 1..32 | |
1142 { | |
1143 boostLevel = (int16_t)CLAMP(level, 1, 32); | |
1144 CDA_Amp = boostLevel * 8; | |
1145 } | |
1146 | |
1147 int32_t getMasterVol(void) // 8bb: added this | |
1148 { | |
1149 return masterVol; | |
1150 } | |
1151 | |
1152 int32_t getAmp(void) // 8bb: added this | |
1153 { | |
1154 return boostLevel; | |
1155 } | |
1156 | |
1157 uint8_t getNumActiveVoices(void) // 8bb: added this | |
1158 { | |
1159 uint8_t activeVoices = 0; | |
1160 for (int32_t i = 0; i < song.antChn; i++) | |
1161 { | |
1162 CIType *v = getVoice(i); | |
1163 if (!(v->SType & SType_Off) && v->SVol > 0) | |
1164 activeVoices++; | |
1165 } | |
1166 | |
1167 return activeVoices; | |
1168 } | |
1169 | |
1170 static void setFrqTab(bool linear) | |
1171 { | |
1172 linearFrqTab = linear; | |
1173 note2Period = linear ? linearPeriods : amigaPeriods; | |
1174 } | |
1175 | |
1176 void updateReplayRate(void) | |
1177 { | |
1178 lockMixer(); | |
1179 | |
1180 // 8bb: bit-exact to FT2 | |
1181 frequenceDivFactor = (uint32_t)round(65536.0*1712.0/realReplayRate*8363.0); | |
1182 frequenceMulFactor = (uint32_t)round(256.0*65536.0/realReplayRate*8363.0); | |
1183 | |
1184 unlockMixer(); | |
1185 } | |
1186 | |
1187 /*************************************************************************** | |
1188 * INITIALIZATION ROUTINES * | |
1189 ***************************************************************************/ | |
1190 | |
1191 bool initMusic(int32_t audioFrequency, int32_t audioBufferSize, bool interpolation, bool volumeRamping) | |
1192 { | |
1193 closeMixer(); | |
1194 freeMusic(); | |
1195 memset(stm, 0, sizeof (stm)); | |
1196 | |
1197 realReplayRate = CLAMP(audioFrequency, 8000, 96000); | |
1198 updateReplayRate(); | |
1199 | |
1200 soundBufferSize = audioBufferSize; | |
1201 interpolationFlag = interpolation; | |
1202 volumeRampingFlag = volumeRamping; | |
1203 | |
1204 song.tempo = 6; | |
1205 song.speed = 125; | |
1206 setFrqTab(true); | |
1207 resetMusic(); | |
1208 | |
1209 return true; | |
1210 } | |
1211 | |
1212 /*************************************************************************** | |
1213 * WAV DUMPING ROUTINES * | |
1214 ***************************************************************************/ | |
1215 | |
1216 static void WAV_WriteHeader(FILE *f, int32_t frq) | |
1217 { | |
1218 uint16_t w; | |
1219 uint32_t l; | |
1220 | |
1221 // 12 bytes | |
1222 | |
1223 const uint32_t RIFF = 0x46464952; | |
1224 fwrite(&RIFF, 4, 1, f); | |
1225 fseek(f, 4, SEEK_CUR); | |
1226 const uint32_t WAVE = 0x45564157; | |
1227 fwrite(&WAVE, 4, 1, f); | |
1228 | |
1229 // 24 bytes | |
1230 | |
1231 const uint32_t fmt = 0x20746D66; | |
1232 fwrite(&fmt, 4, 1, f); | |
1233 l = 16; fwrite(&l, 4, 1, f); | |
1234 w = 1; fwrite(&w, 2, 1, f); | |
1235 w = 2; fwrite(&w, 2, 1, f); | |
1236 l = frq; fwrite(&l, 4, 1, f); | |
1237 l = frq*2*2; fwrite(&l, 4, 1, f); | |
1238 w = 2*2; fwrite(&w, 2, 1, f); | |
1239 w = 8*2; fwrite(&w, 2, 1, f); | |
1240 | |
1241 // 8 bytes | |
1242 | |
1243 const uint32_t DATA = 0x61746164; | |
1244 fwrite(&DATA, 4, 1, f); | |
1245 fseek(f, 4, SEEK_CUR); | |
1246 } | |
1247 | |
1248 static void WAV_WriteEnd(FILE *f, uint32_t size) | |
1249 { | |
1250 fseek(f, 4, SEEK_SET); | |
1251 uint32_t l = size+4+24+8; | |
1252 fwrite(&l, 4, 1, f); | |
1253 fseek(f, 12+24+4, SEEK_SET); | |
1254 fwrite(&size, 4, 1, f); | |
1255 } | |
1256 | |
1257 void WAVDump_Abort(void) // 8bb: added this | |
1258 { | |
1259 WAVDump_Flag = false; | |
1260 } | |
1261 | |
1262 bool WAVDump_Record(const char *filenameOut) | |
1263 { | |
1264 FILE *fil = fopen(filenameOut, "wb"); | |
1265 if (fil == NULL) | |
1266 { | |
1267 WAVDump_Flag = false; | |
1268 return false; | |
1269 } | |
1270 | |
1271 const int32_t WDFrequency = realReplayRate; | |
1272 const int32_t WDAmp = boostLevel; | |
1273 | |
1274 const uint32_t maxSamplesPerTick = (WDFrequency*5 / 2) / 1; // 8bb: added this (min. BPM = 1, through hex editing) | |
1275 int16_t *pBlock = (int16_t *)malloc(maxSamplesPerTick * (2 * sizeof (int16_t))); | |
1276 if (pBlock == NULL) | |
1277 { | |
1278 fclose(fil); | |
1279 WAVDump_Flag = false; | |
1280 return false; | |
1281 } | |
1282 | |
1283 WAV_WriteHeader(fil, WDFrequency); | |
1284 | |
1285 stopMusic(); | |
1286 mix_Init(maxSamplesPerTick); | |
1287 | |
1288 uint16_t WDStartPos = 0; | |
1289 uint16_t WDStopPos = song.len-1; | |
1290 | |
1291 dump_Init(WDFrequency, WDAmp, WDStartPos); | |
1292 | |
1293 uint32_t totSize = 0; | |
1294 | |
1295 WAVDump_Flag = true; | |
1296 while (!dump_EndOfTune(WDStopPos)) | |
1297 { | |
1298 if (!WAVDump_Flag) // extra check so that external threads can force-abort render | |
1299 break; | |
1300 | |
1301 const uint32_t size = dump_GetFrame(pBlock); | |
1302 fwrite(pBlock, 1, size, fil); | |
1303 totSize += size; | |
1304 } | |
1305 WAVDump_Flag = false; | |
1306 | |
1307 mix_Free(); | |
1308 | |
1309 WAV_WriteEnd(fil, totSize); | |
1310 dump_Close(); | |
1311 | |
1312 stopMusic(); | |
1313 fclose(fil); | |
1314 | |
1315 free(pBlock); | |
1316 | |
1317 WAVDump_Flag = false; | |
1318 return true; | |
1319 } | |
1320 | |
1321 /*************************************************************************** | |
1322 * MEMORY READ ROUTINES (8bb: added these) * | |
1323 ***************************************************************************/ | |
1324 | |
1325 static MEMFILE *mopen(const uint8_t *src, uint32_t length) | |
1326 { | |
1327 if (src == NULL || length == 0) | |
1328 return NULL; | |
1329 | |
1330 MEMFILE *b = (MEMFILE *)malloc(sizeof (MEMFILE)); | |
1331 if (b == NULL) | |
1332 return NULL; | |
1333 | |
1334 b->_base = (uint8_t *)src; | |
1335 b->_ptr = (uint8_t *)src; | |
1336 b->_cnt = length; | |
1337 b->_bufsiz = length; | |
1338 b->_eof = false; | |
1339 | |
1340 return b; | |
1341 } | |
1342 | |
1343 static void mclose(MEMFILE **buf) | |
1344 { | |
1345 if (*buf != NULL) | |
1346 { | |
1347 free(*buf); | |
1348 *buf = NULL; | |
1349 } | |
1350 } | |
1351 | |
1352 static size_t mread(void *buffer, size_t size, size_t count, MEMFILE *buf) | |
1353 { | |
1354 if (buf == NULL || buf->_ptr == NULL) | |
1355 return 0; | |
1356 | |
1357 size_t wrcnt = size * count; | |
1358 if (size == 0 || buf->_eof) | |
1359 return 0; | |
1360 | |
1361 int32_t pcnt = (buf->_cnt > wrcnt) ? (int32_t)wrcnt : (int32_t)buf->_cnt; | |
1362 memcpy(buffer, buf->_ptr, pcnt); | |
1363 | |
1364 buf->_cnt -= pcnt; | |
1365 buf->_ptr += pcnt; | |
1366 | |
1367 if (buf->_cnt <= 0) | |
1368 { | |
1369 buf->_ptr = buf->_base + buf->_bufsiz; | |
1370 buf->_cnt = 0; | |
1371 buf->_eof = true; | |
1372 } | |
1373 | |
1374 return pcnt / size; | |
1375 } | |
1376 | |
1377 static bool meof(MEMFILE *buf) | |
1378 { | |
1379 if (buf == NULL) | |
1380 return true; | |
1381 | |
1382 return buf->_eof; | |
1383 } | |
1384 | |
1385 static void mseek(MEMFILE *buf, int32_t offset, int32_t whence) | |
1386 { | |
1387 if (buf == NULL) | |
1388 return; | |
1389 | |
1390 if (buf->_base) | |
1391 { | |
1392 switch (whence) | |
1393 { | |
1394 case SEEK_SET: buf->_ptr = buf->_base + offset; break; | |
1395 case SEEK_CUR: buf->_ptr += offset; break; | |
1396 case SEEK_END: buf->_ptr = buf->_base + buf->_bufsiz + offset; break; | |
1397 default: break; | |
1398 } | |
1399 | |
1400 buf->_eof = false; | |
1401 if (buf->_ptr >= buf->_base+buf->_bufsiz) | |
1402 { | |
1403 buf->_ptr = buf->_base + buf->_bufsiz; | |
1404 buf->_eof = true; | |
1405 } | |
1406 | |
1407 buf->_cnt = (buf->_base + buf->_bufsiz) - buf->_ptr; | |
1408 } | |
1409 } | |
1410 | |
1411 static void mrewind(MEMFILE *buf) | |
1412 { | |
1413 mseek(buf, 0, SEEK_SET); | |
1414 } |