A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 693 lines 18 kB view raw
1/* MikMod sound library 2 (c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file 3 AUTHORS for complete list. 4 5 This library is free software; you can redistribute it and/or modify 6 it under the terms of the GNU Library General Public License as 7 published by the Free Software Foundation; either version 2 of 8 the License, or (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU Library General Public License for more details. 14 15 You should have received a copy of the GNU Library General Public 16 License along with this library; if not, write to the Free Software 17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 18 02111-1307, USA. 19*/ 20 21/*============================================================================== 22 23 DMP Advanced Module Format loader 24 25==============================================================================*/ 26 27#ifdef HAVE_CONFIG_H 28#include "config.h" 29#endif 30 31#ifdef HAVE_UNISTD_H 32#include <unistd.h> 33#endif 34 35#include <stdio.h> 36#ifdef HAVE_MEMORY_H 37#include <memory.h> 38#endif 39#include <string.h> 40 41#include "mikmod_internals.h" 42 43#ifdef SUNOS 44extern int fprintf(FILE *, const char *, ...); 45#endif 46 47/*========== Module structure */ 48 49typedef struct AMFHEADER { 50 UBYTE id[3]; /* AMF file marker */ 51 UBYTE version; /* upper major, lower nibble minor version number */ 52 CHAR songname[32]; /* ASCIIZ songname */ 53 UBYTE numsamples; /* number of samples saved */ 54 UBYTE numorders; 55 UWORD numtracks; /* number of tracks saved */ 56 UBYTE numchannels; /* number of channels used */ 57 SBYTE panpos[32]; /* voice pan positions */ 58 UBYTE songbpm; 59 UBYTE songspd; 60} AMFHEADER; 61 62typedef struct AMFSAMPLE { 63 UBYTE type; 64 CHAR samplename[32]; 65 CHAR filename[13]; 66 ULONG offset; 67 ULONG length; 68 UWORD c2spd; 69 UBYTE volume; 70 ULONG reppos; 71 ULONG repend; 72} AMFSAMPLE; 73 74typedef struct AMFNOTE { 75 UBYTE note,instr,volume,fxcnt; 76 UBYTE effect[3]; 77 SBYTE parameter[3]; 78} AMFNOTE; 79 80/*========== Loader variables */ 81 82static AMFHEADER *mh = NULL; 83#define AMFTEXTLEN 22 84static CHAR AMF_Version[AMFTEXTLEN+1] = "DSMI Module Format 0.0"; 85static AMFNOTE *track = NULL; 86 87/*========== Loader code */ 88 89static int AMF_Test(void) 90{ 91 UBYTE id[3],ver; 92 93 if(!_mm_read_UBYTES(id,3,modreader)) return 0; 94 if(memcmp(id,"AMF",3)) return 0; 95 96 ver=_mm_read_UBYTE(modreader); 97 if((ver>=8)&&(ver<=14)) return 1; 98 return 0; 99} 100 101static int AMF_Init(void) 102{ 103 if(!(mh=(AMFHEADER*)MikMod_malloc(sizeof(AMFHEADER)))) return 0; 104 if(!(track=(AMFNOTE*)MikMod_calloc(64,sizeof(AMFNOTE)))) return 0; 105 106 return 1; 107} 108 109static void AMF_Cleanup(void) 110{ 111 MikMod_free(mh); 112 MikMod_free(track); 113 mh=NULL; 114 track=NULL; 115} 116 117/* Some older version 1.0 AMFs contain an anomaly where the sample length is 118 * a DWORD, the loop start is a WORD, and the loop end is missing. Later AMF 119 * 1.0 modules and up contain all three as DWORDs, and earlier versions have 120 * all three as WORDs. This function tries to detect this edge case in the 121 * instruments table. This should only be called on 1.0 AMFs. 122 */ 123static int AMF_ScanV10Instruments(MREADER *r, unsigned int numins) 124{ 125 SLONG resetpos; 126 int res = 0; 127 char str[32]; 128 ULONG idx, len, start, end; 129 UBYTE type, vol; 130 UWORD c2spd; 131 unsigned int i; 132 133 resetpos = _mm_ftell(r); 134 if(resetpos < 0) return 0; 135 136 for(i = 0; i < numins; i++) { 137 type = _mm_read_UBYTE(r); /* type: should be 0 or 1 */ 138 _mm_read_string(str, 32, r); /* name */ 139 _mm_read_string(str, 13, r); /* filename */ 140 idx = _mm_read_I_ULONG(r); /* index (should be <= numins) */ 141 len = _mm_read_I_ULONG(r); 142 c2spd = _mm_read_I_UWORD(r); /* should be > 0 */ 143 vol = _mm_read_UBYTE(r); /* should be [0,0x40] */ 144 start = _mm_read_I_ULONG(r); /* should be <= len */ 145 end = _mm_read_I_ULONG(r); /* should be <= len */ 146 147 if((type != 0 && type != 1) || (idx > numins) || (c2spd == 0) || 148 (vol > 0x40) || (start > len) || (end > len)) { 149 res = 1; 150 break; 151 } 152 153 } 154 _mm_fseek(r, resetpos, SEEK_SET); 155 return res; 156} 157 158static int AMF_UnpackTrack(MREADER *r) 159{ 160 ULONG tracksize; 161 UBYTE row,cmd; 162 SBYTE arg; 163 164 /* empty track */ 165 memset(track,0,64*sizeof(AMFNOTE)); 166 167 /* read packed track */ 168 if (r) { 169 tracksize=_mm_read_I_UWORD(r); 170 171 /* The original code in DSMI library read the byte, 172 but it is not used, so we won't either */ 173// tracksize+=((ULONG)_mm_read_UBYTE(r))<<16; 174 (void)_mm_read_UBYTE(r); 175 176 if (tracksize) 177 while(tracksize--) { 178 row=_mm_read_UBYTE(r); 179 cmd=_mm_read_UBYTE(r); 180 arg=_mm_read_SBYTE(r); 181 /* unexpected end of track */ 182 if(!tracksize) { 183 if((row==0xff)&&(cmd==0xff)&&(arg==-1)) 184 break; 185 /* the last triplet should be FF FF FF, but this is not 186 always the case... maybe a bug in m2amf ? 187 else 188 return 0; 189 */ 190 191 } 192 /* invalid row (probably unexpected end of row) */ 193 if (row>=64) { 194 _mm_fseek(modreader, tracksize * 3, SEEK_CUR); 195 return 1; 196 } 197 if (cmd<0x7f) { 198 /* note, vol */ 199 /* Note that 0xff values mean this note was not originally 200 accomanied by a volume event. The +1 here causes it to 201 overflow to 0, which will then (correctly) be ignored later. */ 202 track[row].note=cmd; 203 track[row].volume=(UBYTE)arg+1; 204 } else 205 if (cmd==0x7f) { 206 /* AMF.TXT claims this should duplicate the previous row, but 207 this is a lie. This note value is used to communicate to 208 DSMI that the current playing note should be updated when 209 an instrument is used with no note. This can be ignored. */ 210 } else 211 if (cmd==0x80) { 212 /* instr */ 213 track[row].instr=arg+1; 214 } else 215 if (cmd==0x83) { 216 /* volume without note */ 217 track[row].volume=(UBYTE)arg+1; 218 } else 219 if (cmd==0xff) { 220 /* apparently, some M2AMF version fail to estimate the 221 size of the compressed patterns correctly, and end 222 up with blanks, i.e. dead triplets. Those are marked 223 with cmd == 0xff. Let's ignore them. */ 224 } else 225 if(track[row].fxcnt<3) { 226 /* effect, param */ 227 if(cmd>0x97) { 228 /* Instead of failing, we just ignore unknown effects. 229 This will load the "escape from dulce base" module */ 230 continue; 231 } 232 track[row].effect[track[row].fxcnt]=cmd&0x7f; 233 track[row].parameter[track[row].fxcnt]=arg; 234 track[row].fxcnt++; 235 } else 236 return 0; 237 } 238 } 239 return 1; 240} 241 242static UBYTE *AMF_ConvertTrack(void) 243{ 244 int row,fx4memory=0; 245 246 /* convert track */ 247 UniReset(); 248 for (row=0;row<64;row++) { 249 if (track[row].instr) UniInstrument(track[row].instr-1); 250 if (track[row].note>OCTAVE) UniNote(track[row].note-OCTAVE); 251 252 /* AMF effects */ 253 while(track[row].fxcnt--) { 254 SBYTE inf=track[row].parameter[track[row].fxcnt]; 255 256 switch(track[row].effect[track[row].fxcnt]) { 257 case 1: /* Set speed */ 258 UniEffect(UNI_S3MEFFECTA,inf); 259 break; 260 case 2: /* Volume slide */ 261 if(inf) { 262 UniWriteByte(UNI_S3MEFFECTD); 263 if (inf>=0) 264 UniWriteByte((inf&0xf)<<4); 265 else 266 UniWriteByte((-inf)&0xf); 267 } 268 break; 269 /* effect 3, set channel volume, done in UnpackTrack */ 270 case 4: /* Porta up/down */ 271 if(inf) { 272 if(inf>0) { 273 UniEffect(UNI_S3MEFFECTE,inf); 274 fx4memory=UNI_S3MEFFECTE; 275 } else { 276 UniEffect(UNI_S3MEFFECTF,-inf); 277 fx4memory=UNI_S3MEFFECTF; 278 } 279 } else if(fx4memory) 280 UniEffect(fx4memory,0); 281 break; 282 /* effect 5, "Porta abs", not supported */ 283 case 6: /* Porta to note */ 284 UniEffect(UNI_ITEFFECTG,inf); 285 break; 286 case 7: /* Tremor */ 287 UniEffect(UNI_S3MEFFECTI,inf); 288 break; 289 case 8: /* Arpeggio */ 290 UniPTEffect(0x0,inf); 291 break; 292 case 9: /* Vibrato */ 293 UniPTEffect(0x4,inf); 294 break; 295 case 0xa: /* Porta + Volume slide */ 296 UniPTEffect(0x3,0); 297 if(inf) { 298 UniWriteByte(UNI_S3MEFFECTD); 299 if (inf>=0) 300 UniWriteByte((inf&0xf)<<4); 301 else 302 UniWriteByte((-inf)&0xf); 303 } 304 break; 305 case 0xb: /* Vibrato + Volume slide */ 306 UniPTEffect(0x4,0); 307 if(inf) { 308 UniWriteByte(UNI_S3MEFFECTD); 309 if (inf>=0) 310 UniWriteByte((inf&0xf)<<4); 311 else 312 UniWriteByte((-inf)&0xf); 313 } 314 break; 315 case 0xc: /* Pattern break (in hex) */ 316 UniPTEffect(0xd,inf); 317 break; 318 case 0xd: /* Pattern jump */ 319 UniPTEffect(0xb,inf); 320 break; 321 /* effect 0xe, "Sync", not supported */ 322 case 0xf: /* Retrig */ 323 UniEffect(UNI_S3MEFFECTQ,inf&0xf); 324 break; 325 case 0x10: /* Sample offset */ 326 UniPTEffect(0x9,inf); 327 break; 328 case 0x11: /* Fine volume slide */ 329 if(inf) { 330 UniWriteByte(UNI_S3MEFFECTD); 331 if (inf>=0) 332 UniWriteByte((inf&0xf)<<4|0xf); 333 else 334 UniWriteByte(0xf0|((-inf)&0xf)); 335 } 336 break; 337 case 0x12: /* Fine portamento */ 338 if(inf) { 339 if(inf>0) { 340 UniEffect(UNI_S3MEFFECTE,0xf0|(inf&0xf)); 341 fx4memory=UNI_S3MEFFECTE; 342 } else { 343 UniEffect(UNI_S3MEFFECTF,0xf0|((-inf)&0xf)); 344 fx4memory=UNI_S3MEFFECTF; 345 } 346 } else if(fx4memory) 347 UniEffect(fx4memory,0); 348 break; 349 case 0x13: /* Delay note */ 350 UniPTEffect(0xe,0xd0|(inf&0xf)); 351 break; 352 case 0x14: /* Note cut */ 353 UniPTEffect(0xc,0); 354 track[row].volume=0; 355 break; 356 case 0x15: /* Set tempo */ 357 UniEffect(UNI_S3MEFFECTT,inf); 358 break; 359 case 0x16: /* Extra fine portamento */ 360 if(inf) { 361 if(inf>0) { 362 UniEffect(UNI_S3MEFFECTE,0xe0|((inf>>2)&0xf)); 363 fx4memory=UNI_S3MEFFECTE; 364 } else { 365 UniEffect(UNI_S3MEFFECTF,0xe0|(((-inf)>>2)&0xf)); 366 fx4memory=UNI_S3MEFFECTF; 367 } 368 } else if(fx4memory) 369 UniEffect(fx4memory,0); 370 break; 371 case 0x17: /* Panning */ 372 /* S3M pan, except offset by -64. */ 373 if (inf>64) 374 UniEffect(UNI_ITEFFECTS0,0x91); /* surround */ 375 else 376 UniPTEffect(0x8,(inf==64)?255:(inf+64)<<1); 377 of.flags |= UF_PANNING; 378 break; 379 } 380 381 } 382 if (track[row].volume) UniVolEffect(VOL_VOLUME,track[row].volume-1); 383 UniNewline(); 384 } 385 return UniDup(); 386} 387 388static int AMF_Load(int curious) 389{ 390 int u,defaultpanning; 391 unsigned int t,realtrackcnt,realsmpcnt; 392 AMFSAMPLE s; 393 SAMPLE *q; 394 UWORD *track_remap; 395 ULONG samplepos, fileend; 396 UBYTE channel_remap[16]; 397 int no_loopend; 398 (void)curious; 399 /* try to read module header */ 400 _mm_read_UBYTES(mh->id,3,modreader); 401 mh->version =_mm_read_UBYTE(modreader); 402 403 /* For version 8, the song name is only 20 characters long and then come 404 // some data, which I do not know what is. The original code by Otto Chrons 405 // load the song name as 20 characters long and then it is overwritten again 406 // it another function, where it loads 32 characters, no matter which version 407 // it is. So we do the same here */ 408 _mm_read_string(mh->songname,32,modreader); 409 410 mh->numsamples =_mm_read_UBYTE(modreader); 411 mh->numorders =_mm_read_UBYTE(modreader); 412 mh->numtracks =_mm_read_I_UWORD(modreader); 413 414 if(mh->version>=9) 415 mh->numchannels=_mm_read_UBYTE(modreader); 416 else 417 mh->numchannels=4; 418 419 if((!mh->numchannels)||(mh->numchannels>(mh->version>=12?32:16))) { 420 _mm_errno=MMERR_NOT_A_MODULE; 421 return 0; 422 } 423 424 if(mh->version>=11) { 425 memset(mh->panpos,0,32); 426 _mm_read_SBYTES(mh->panpos,(mh->version>=13)?32:16,modreader); 427 } else if(mh->version>=9) 428 _mm_read_UBYTES(channel_remap,16,modreader); 429 430 if (mh->version>=13) { 431 mh->songbpm=_mm_read_UBYTE(modreader); 432 if(mh->songbpm<32) { 433 _mm_errno=MMERR_NOT_A_MODULE; 434 return 0; 435 } 436 mh->songspd=_mm_read_UBYTE(modreader); 437 if(mh->songspd>32) { 438 _mm_errno=MMERR_NOT_A_MODULE; 439 return 0; 440 } 441 } else { 442 mh->songbpm=125; 443 mh->songspd=6; 444 } 445 446 if(_mm_eof(modreader)) { 447 _mm_errno = MMERR_LOADING_HEADER; 448 return 0; 449 } 450 451 /* set module variables */ 452 of.initspeed = mh->songspd; 453 of.inittempo = mh->songbpm; 454 AMF_Version[AMFTEXTLEN-3]='0'+(mh->version/10); 455 AMF_Version[AMFTEXTLEN-1]='0'+(mh->version%10); 456 of.modtype = MikMod_strdup(AMF_Version); 457 of.numchn = mh->numchannels; 458 of.numtrk = mh->numorders*mh->numchannels; 459 if (mh->numtracks>of.numtrk) 460 of.numtrk=mh->numtracks; 461 of.numtrk++; /* add room for extra, empty track */ 462 of.songname = DupStr(mh->songname,32,1); 463 of.numpos = mh->numorders; 464 of.numpat = mh->numorders; 465 of.reppos = 0; 466 of.flags |= UF_S3MSLIDES; 467 /* XXX whenever possible, we should try to determine the original format. 468 Here we assume it was S3M-style wrt bpmlimit... */ 469 of.bpmlimit = 32; 470 471 /* 472 * Play with the panning table. Although the AMF format embeds a 473 * panning table, if the module was a MOD or an S3M with default 474 * panning and didn't use any panning commands, don't flag 475 * UF_PANNING, to use our preferred panning table for this case. 476 */ 477 defaultpanning = 1; 478 479 if(mh->version>=11) { 480 for (t = 0; t < 32; t++) { 481 if (mh->panpos[t] > 64) { 482 of.panning[t] = PAN_SURROUND; 483 defaultpanning = 0; 484 } else 485 if (mh->panpos[t] == 64) 486 of.panning[t] = PAN_RIGHT; 487 else 488 of.panning[t] = (mh->panpos[t] + 64) << 1; 489 } 490 } 491 else 492 defaultpanning = 0; 493 494 if (defaultpanning) { 495 for (t = 0; t < of.numchn; t++) 496 if (of.panning[t] == (((t + 1) & 2) ? PAN_RIGHT : PAN_LEFT)) { 497 defaultpanning = 0; /* not MOD canonical panning */ 498 break; 499 } 500 } 501 if (defaultpanning) 502 of.flags |= UF_PANNING; 503 504 of.numins=of.numsmp=mh->numsamples; 505 506 if(!AllocPositions(of.numpos)) return 0; 507 for(t=0;t<of.numpos;t++) 508 of.positions[t]=t; 509 510 if(!AllocTracks()) return 0; 511 if(!AllocPatterns()) return 0; 512 513 /* read AMF order table */ 514 for (t=0;t<of.numpat;t++) { 515 if (mh->version>=14) 516 /* track size */ 517 of.pattrows[t]=_mm_read_I_UWORD(modreader); 518 if ((mh->version==9) || (mh->version==10)) { 519 /* Only version 9 and 10 uses channel remap */ 520 for (u = 0; u < of.numchn; u++) 521 of.patterns[t * of.numchn + channel_remap[u]] = _mm_read_I_UWORD(modreader); 522 } 523 else 524 _mm_read_I_UWORDS(of.patterns + (t * of.numchn), of.numchn, modreader); 525 } 526 if(_mm_eof(modreader)) { 527 _mm_errno = MMERR_LOADING_HEADER; 528 return 0; 529 } 530 531 /* read sample information */ 532 if(!AllocSamples()) return 0; 533 534 no_loopend = 0; 535 if(mh->version == 10) { 536 no_loopend = AMF_ScanV10Instruments(modreader, of.numins); 537 } 538 539 q=of.samples; 540 for(t=0;t<of.numins;t++) { 541 /* try to read sample info */ 542 s.type=_mm_read_UBYTE(modreader); 543 _mm_read_string(s.samplename,32,modreader); 544 _mm_read_string(s.filename,13,modreader); 545 s.offset =_mm_read_I_ULONG(modreader); 546 547 if(mh->version>=10) 548 s.length =_mm_read_I_ULONG(modreader); 549 else 550 s.length = _mm_read_I_UWORD(modreader); 551 552 s.c2spd =_mm_read_I_UWORD(modreader); 553 if(s.c2spd==8368) s.c2spd=8363; 554 s.volume =_mm_read_UBYTE(modreader); 555 /* "the tribal zone.amf" and "the way its gonna b.amf" by Maelcum 556 * are the only version 10 files I can find, and they have 32 bit 557 * reppos and repend, not 16. */ 558 if(mh->version>=10 && no_loopend==0) {/* was 11 */ 559 s.reppos =_mm_read_I_ULONG(modreader); 560 s.repend =_mm_read_I_ULONG(modreader); 561 } else if(mh->version==10) { 562 /* Early AMF 1.0 modules have the upper two bytes of 563 * the loop start and the entire loop end truncated. 564 * libxmp cites "sweetdrm.amf" and "facing_n.amf", but 565 * these are currently missing. M2AMF 1.3 (from DMP 2.32) 566 * has been confirmed to output these, however. */ 567 s.reppos =_mm_read_I_UWORD(modreader); 568 s.repend =s.length; 569 /* There's not really a correct way to handle the loop 570 * end, but this makes unlooped samples work at least. */ 571 if(s.reppos==0) 572 s.repend=0; 573 } else { 574 s.reppos =_mm_read_I_UWORD(modreader); 575 s.repend =_mm_read_I_UWORD(modreader); 576 if (s.repend==0xffff) 577 s.repend=0; 578 } 579 580 if(_mm_eof(modreader)) { 581 _mm_errno = MMERR_LOADING_SAMPLEINFO; 582 return 0; 583 } 584 585 q->samplename = DupStr(s.samplename,32,1); 586 q->speed = s.c2spd; 587 q->volume = s.volume; 588 if (s.type) { 589 q->seekpos = s.offset; 590 q->length = s.length; 591 q->loopstart = s.reppos; 592 q->loopend = s.repend; 593 if((s.repend-s.reppos)>2) q->flags |= SF_LOOP; 594 } 595 q++; 596 } 597 598 /* read track table */ 599 if(!(track_remap=(UWORD*)MikMod_calloc(mh->numtracks+1,sizeof(UWORD)))) 600 return 0; 601 _mm_read_I_UWORDS(track_remap+1,mh->numtracks,modreader); 602 if(_mm_eof(modreader)) { 603 MikMod_free(track_remap); 604 _mm_errno=MMERR_LOADING_TRACK; 605 return 0; 606 } 607 608 for(realtrackcnt=t=0;t<=mh->numtracks;t++) 609 if (realtrackcnt<track_remap[t]) 610 realtrackcnt=track_remap[t]; 611 if (realtrackcnt > (int)mh->numtracks) { 612 MikMod_free(track_remap); 613 _mm_errno=MMERR_NOT_A_MODULE; 614 return 0; 615 } 616 for(t=0;t<of.numpat*of.numchn;t++) 617 of.patterns[t]=(of.patterns[t]<=mh->numtracks)? 618 track_remap[of.patterns[t]]-1:(int)realtrackcnt; 619 620 MikMod_free(track_remap); 621 622 /* unpack tracks */ 623 for(t=0;t<realtrackcnt;t++) { 624 if(_mm_eof(modreader)) { 625 _mm_errno = MMERR_LOADING_TRACK; 626 return 0; 627 } 628 if (!AMF_UnpackTrack(modreader)) { 629 _mm_errno = MMERR_LOADING_TRACK; 630 return 0; 631 } 632 if(!(of.tracks[t]=AMF_ConvertTrack())) 633 return 0; 634 } 635 /* add an extra void track */ 636 UniReset(); 637 for(t=0;t<64;t++) UniNewline(); 638 of.tracks[realtrackcnt++]=UniDup(); 639 for(t=realtrackcnt;t<of.numtrk;t++) of.tracks[t]=NULL; 640 641 /* compute sample offsets */ 642 if(_mm_eof(modreader)) goto fail; 643 samplepos=_mm_ftell(modreader); 644 _mm_fseek(modreader,0,SEEK_END); 645 fileend=_mm_ftell(modreader); 646 _mm_fseek(modreader,samplepos,SEEK_SET); 647 for(realsmpcnt=t=0;t<of.numsmp;t++) 648 if(realsmpcnt<of.samples[t].seekpos) 649 realsmpcnt=of.samples[t].seekpos; 650 for(t=1;t<=realsmpcnt;t++) { 651 q=of.samples; 652 u=0; 653 while(q->seekpos!=t) { 654 if(++u==of.numsmp) 655 goto fail; 656 q++; 657 } 658 q->seekpos=samplepos; 659 samplepos+=q->length; 660 } 661 if(samplepos>fileend) 662 goto fail; 663 664 return 1; 665fail: 666 _mm_errno = MMERR_LOADING_SAMPLEINFO; 667 return 0; 668} 669 670static CHAR *AMF_LoadTitle(void) 671{ 672 CHAR s[32]; 673 674 _mm_fseek(modreader,4,SEEK_SET); 675 if(!_mm_read_UBYTES(s,32,modreader)) return NULL; 676 677 return(DupStr(s,32,1)); 678} 679 680/*========== Loader information */ 681 682MIKMODAPI MLOADER load_amf={ 683 NULL, 684 "AMF", 685 "AMF (DSMI Advanced Module Format)", 686 AMF_Init, 687 AMF_Test, 688 AMF_Load, 689 AMF_Cleanup, 690 AMF_LoadTitle 691}; 692 693/* ex:set ts=4: */