/* MikMod sound library (c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file AUTHORS for complete list. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /*============================================================================== DMP Advanced Module Format loader ==============================================================================*/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_UNISTD_H #include #endif #include #ifdef HAVE_MEMORY_H #include #endif #include #include "mikmod_internals.h" #ifdef SUNOS extern int fprintf(FILE *, const char *, ...); #endif /*========== Module structure */ typedef struct AMFHEADER { UBYTE id[3]; /* AMF file marker */ UBYTE version; /* upper major, lower nibble minor version number */ CHAR songname[32]; /* ASCIIZ songname */ UBYTE numsamples; /* number of samples saved */ UBYTE numorders; UWORD numtracks; /* number of tracks saved */ UBYTE numchannels; /* number of channels used */ SBYTE panpos[32]; /* voice pan positions */ UBYTE songbpm; UBYTE songspd; } AMFHEADER; typedef struct AMFSAMPLE { UBYTE type; CHAR samplename[32]; CHAR filename[13]; ULONG offset; ULONG length; UWORD c2spd; UBYTE volume; ULONG reppos; ULONG repend; } AMFSAMPLE; typedef struct AMFNOTE { UBYTE note,instr,volume,fxcnt; UBYTE effect[3]; SBYTE parameter[3]; } AMFNOTE; /*========== Loader variables */ static AMFHEADER *mh = NULL; #define AMFTEXTLEN 22 static CHAR AMF_Version[AMFTEXTLEN+1] = "DSMI Module Format 0.0"; static AMFNOTE *track = NULL; /*========== Loader code */ static int AMF_Test(void) { UBYTE id[3],ver; if(!_mm_read_UBYTES(id,3,modreader)) return 0; if(memcmp(id,"AMF",3)) return 0; ver=_mm_read_UBYTE(modreader); if((ver>=10)&&(ver<=14)) return 1; return 0; } static int AMF_Init(void) { if(!(mh=(AMFHEADER*)MikMod_malloc(sizeof(AMFHEADER)))) return 0; if(!(track=(AMFNOTE*)MikMod_calloc(64,sizeof(AMFNOTE)))) return 0; return 1; } static void AMF_Cleanup(void) { MikMod_free(mh); MikMod_free(track); mh=NULL; track=NULL; } static int AMF_UnpackTrack(MREADER* r) { ULONG tracksize; UBYTE row,cmd; SBYTE arg; /* empty track */ memset(track,0,64*sizeof(AMFNOTE)); /* read packed track */ if (r) { tracksize=_mm_read_I_UWORD(r); tracksize+=((ULONG)_mm_read_UBYTE(r))<<16; if (tracksize) while(tracksize--) { row=_mm_read_UBYTE(r); cmd=_mm_read_UBYTE(r); arg=_mm_read_SBYTE(r); /* unexpected end of track */ if(!tracksize) { if((row==0xff)&&(cmd==0xff)&&(arg==-1)) break; /* the last triplet should be FF FF FF, but this is not always the case... maybe a bug in m2amf ? else return 0; */ } /* invalid row (probably unexpected end of row) */ if (row>=64) return 0; if (cmd<0x7f) { /* note, vol */ track[row].note=cmd; track[row].volume=(UBYTE)arg+1; } else if (cmd==0x7f) { /* duplicate row */ if ((arg<0)&&(row+arg>=0)) { memcpy(track+row,track+(row+arg),sizeof(AMFNOTE)); } } else if (cmd==0x80) { /* instr */ track[row].instr=arg+1; } else if (cmd==0x83) { /* volume without note */ track[row].volume=(UBYTE)arg+1; } else if (cmd==0xff) { /* apparently, some M2AMF version fail to estimate the size of the compressed patterns correctly, and end up with blanks, i.e. dead triplets. Those are marked with cmd == 0xff. Let's ignore them. */ } else if(track[row].fxcnt<3) { /* effect, param */ if(cmd>0x97) return 0; track[row].effect[track[row].fxcnt]=cmd&0x7f; track[row].parameter[track[row].fxcnt]=arg; track[row].fxcnt++; } else return 0; } } return 1; } static UBYTE* AMF_ConvertTrack(void) { int row,fx4memory=0; /* convert track */ UniReset(); for (row=0;row<64;row++) { if (track[row].instr) UniInstrument(track[row].instr-1); if (track[row].note>OCTAVE) UniNote(track[row].note-OCTAVE); /* AMF effects */ while(track[row].fxcnt--) { SBYTE inf=track[row].parameter[track[row].fxcnt]; switch(track[row].effect[track[row].fxcnt]) { case 1: /* Set speed */ UniEffect(UNI_S3MEFFECTA,inf); break; case 2: /* Volume slide */ if(inf) { UniWriteByte(UNI_S3MEFFECTD); if (inf>=0) UniWriteByte((inf&0xf)<<4); else UniWriteByte((-inf)&0xf); } break; /* effect 3, set channel volume, done in UnpackTrack */ case 4: /* Porta up/down */ if(inf) { if(inf>0) { UniEffect(UNI_S3MEFFECTE,inf); fx4memory=UNI_S3MEFFECTE; } else { UniEffect(UNI_S3MEFFECTF,-inf); fx4memory=UNI_S3MEFFECTF; } } else if(fx4memory) UniEffect(fx4memory,0); break; /* effect 5, "Porta abs", not supported */ case 6: /* Porta to note */ UniEffect(UNI_ITEFFECTG,inf); break; case 7: /* Tremor */ UniEffect(UNI_S3MEFFECTI,inf); break; case 8: /* Arpeggio */ UniPTEffect(0x0,inf); break; case 9: /* Vibrato */ UniPTEffect(0x4,inf); break; case 0xa: /* Porta + Volume slide */ UniPTEffect(0x3,0); if(inf) { UniWriteByte(UNI_S3MEFFECTD); if (inf>=0) UniWriteByte((inf&0xf)<<4); else UniWriteByte((-inf)&0xf); } break; case 0xb: /* Vibrato + Volume slide */ UniPTEffect(0x4,0); if(inf) { UniWriteByte(UNI_S3MEFFECTD); if (inf>=0) UniWriteByte((inf&0xf)<<4); else UniWriteByte((-inf)&0xf); } break; case 0xc: /* Pattern break (in hex) */ UniPTEffect(0xd,inf); break; case 0xd: /* Pattern jump */ UniPTEffect(0xb,inf); break; /* effect 0xe, "Sync", not supported */ case 0xf: /* Retrig */ UniEffect(UNI_S3MEFFECTQ,inf&0xf); break; case 0x10: /* Sample offset */ UniPTEffect(0x9,inf); break; case 0x11: /* Fine volume slide */ if(inf) { UniWriteByte(UNI_S3MEFFECTD); if (inf>=0) UniWriteByte((inf&0xf)<<4|0xf); else UniWriteByte(0xf0|((-inf)&0xf)); } break; case 0x12: /* Fine portamento */ if(inf) { if(inf>0) { UniEffect(UNI_S3MEFFECTE,0xf0|(inf&0xf)); fx4memory=UNI_S3MEFFECTE; } else { UniEffect(UNI_S3MEFFECTF,0xf0|((-inf)&0xf)); fx4memory=UNI_S3MEFFECTF; } } else if(fx4memory) UniEffect(fx4memory,0); break; case 0x13: /* Delay note */ UniPTEffect(0xe,0xd0|(inf&0xf)); break; case 0x14: /* Note cut */ UniPTEffect(0xc,0); track[row].volume=0; break; case 0x15: /* Set tempo */ UniEffect(UNI_S3MEFFECTT,inf); break; case 0x16: /* Extra fine portamento */ if(inf) { if(inf>0) { UniEffect(UNI_S3MEFFECTE,0xe0|((inf>>2)&0xf)); fx4memory=UNI_S3MEFFECTE; } else { UniEffect(UNI_S3MEFFECTF,0xe0|(((-inf)>>2)&0xf)); fx4memory=UNI_S3MEFFECTF; } } else if(fx4memory) UniEffect(fx4memory,0); break; case 0x17: /* Panning */ if (inf>64) UniEffect(UNI_ITEFFECTS0,0x91); /* surround */ else UniPTEffect(0x8,(inf==64)?255:(inf+64)<<1); of.flags |= UF_PANNING; break; } } if (track[row].volume) UniVolEffect(VOL_VOLUME,track[row].volume-1); UniNewline(); } return UniDup(); } static int AMF_Load(int curious) { int u,defaultpanning; unsigned int t,realtrackcnt,realsmpcnt; AMFSAMPLE s; SAMPLE *q; UWORD *track_remap; ULONG samplepos, fileend; int channel_remap[16]; (void)curious; /* try to read module header */ _mm_read_UBYTES(mh->id,3,modreader); mh->version =_mm_read_UBYTE(modreader); _mm_read_string(mh->songname,32,modreader); mh->numsamples =_mm_read_UBYTE(modreader); mh->numorders =_mm_read_UBYTE(modreader); mh->numtracks =_mm_read_I_UWORD(modreader); mh->numchannels =_mm_read_UBYTE(modreader); if((!mh->numchannels)||(mh->numchannels>(mh->version>=12?32:16))) { _mm_errno=MMERR_NOT_A_MODULE; return 0; } if(mh->version>=11) { memset(mh->panpos,0,32); _mm_read_SBYTES(mh->panpos,(mh->version>=13)?32:16,modreader); } else _mm_read_UBYTES(channel_remap,16,modreader); if (mh->version>=13) { mh->songbpm=_mm_read_UBYTE(modreader); if(mh->songbpm<32) { _mm_errno=MMERR_NOT_A_MODULE; return 0; } mh->songspd=_mm_read_UBYTE(modreader); if(mh->songspd>32) { _mm_errno=MMERR_NOT_A_MODULE; return 0; } } else { mh->songbpm=125; mh->songspd=6; } if(_mm_eof(modreader)) { _mm_errno = MMERR_LOADING_HEADER; return 0; } /* set module variables */ of.initspeed = mh->songspd; of.inittempo = mh->songbpm; AMF_Version[AMFTEXTLEN-3]='0'+(mh->version/10); AMF_Version[AMFTEXTLEN-1]='0'+(mh->version%10); of.modtype = MikMod_strdup(AMF_Version); of.numchn = mh->numchannels; of.numtrk = mh->numorders*mh->numchannels; if (mh->numtracks>of.numtrk) of.numtrk=mh->numtracks; of.numtrk++; /* add room for extra, empty track */ of.songname = DupStr(mh->songname,32,1); of.numpos = mh->numorders; of.numpat = mh->numorders; of.reppos = 0; of.flags |= UF_S3MSLIDES; /* XXX whenever possible, we should try to determine the original format. Here we assume it was S3M-style wrt bpmlimit... */ of.bpmlimit = 32; /* * Play with the panning table. Although the AMF format embeds a * panning table, if the module was a MOD or an S3M with default * panning and didn't use any panning commands, don't flag * UF_PANNING, to use our preferred panning table for this case. */ defaultpanning = 1; for (t = 0; t < 32; t++) { if (mh->panpos[t] > 64) { of.panning[t] = PAN_SURROUND; defaultpanning = 0; } else if (mh->panpos[t] == 64) of.panning[t] = PAN_RIGHT; else of.panning[t] = (mh->panpos[t] + 64) << 1; } if (defaultpanning) { for (t = 0; t < of.numchn; t++) if (of.panning[t] == (((t + 1) & 2) ? PAN_RIGHT : PAN_LEFT)) { defaultpanning = 0; /* not MOD canonical panning */ break; } } if (defaultpanning) of.flags |= UF_PANNING; of.numins=of.numsmp=mh->numsamples; if(!AllocPositions(of.numpos)) return 0; for(t=0;tversion>=14) /* track size */ of.pattrows[t]=_mm_read_I_UWORD(modreader); if (mh->version>=10) _mm_read_I_UWORDS(of.patterns+(t*of.numchn),of.numchn,modreader); else for(u=0;uversion>=10) {/* was 11 */ s.reppos =_mm_read_I_ULONG(modreader); s.repend =_mm_read_I_ULONG(modreader); } else { s.reppos =_mm_read_I_UWORD(modreader); s.repend =s.length; } if(_mm_eof(modreader)) { _mm_errno = MMERR_LOADING_SAMPLEINFO; return 0; } q->samplename = DupStr(s.samplename,32,1); q->speed = s.c2spd; q->volume = s.volume; if (s.type) { q->seekpos = s.offset; q->length = s.length; q->loopstart = s.reppos; q->loopend = s.repend; if((s.repend-s.reppos)>2) q->flags |= SF_LOOP; } q++; } /* read track table */ if(!(track_remap=(UWORD*)MikMod_calloc(mh->numtracks+1,sizeof(UWORD)))) return 0; _mm_read_I_UWORDS(track_remap+1,mh->numtracks,modreader); if(_mm_eof(modreader)) { MikMod_free(track_remap); _mm_errno=MMERR_LOADING_TRACK; return 0; } for(realtrackcnt=t=0;t<=mh->numtracks;t++) if (realtrackcnt (int)mh->numtracks) { MikMod_free(track_remap); _mm_errno=MMERR_NOT_A_MODULE; return 0; } for(t=0;tnumtracks)? track_remap[of.patterns[t]]-1:(int)realtrackcnt; MikMod_free(track_remap); /* unpack tracks */ for(t=0;tseekpos!=t) { if(++u==of.numsmp) goto fail; q++; } q->seekpos=samplepos; samplepos+=q->length; } if(samplepos>fileend) goto fail; return 1; fail: _mm_errno = MMERR_LOADING_SAMPLEINFO; return 0; } static CHAR *AMF_LoadTitle(void) { CHAR s[32]; _mm_fseek(modreader,4,SEEK_SET); if(!_mm_read_UBYTES(s,32,modreader)) return NULL; return(DupStr(s,32,1)); } /*========== Loader information */ MIKMODAPI MLOADER load_amf={ NULL, "AMF", "AMF (DSMI Advanced Module Format)", AMF_Init, AMF_Test, AMF_Load, AMF_Cleanup, AMF_LoadTitle }; /* ex:set ts=4: */