/* Name: LOAD_STM.C Description: ScreamTracker 2 (STM) module Loader - Version 1.oOo Release 2 A Coding Nightmare by Rao and Air Richter of HaRDCoDE You can now play all of those wonderful old C.C. Catch STM's! Portability: All systems - all compilers (hopefully) */ package audio.jmikmod.MikMod.Loaders; import java.io.IOException; import audio.jmikmod.MikMod.clLOADER; import audio.jmikmod.MikMod.clMainBase; class STMNOTE{ short note,insvol,volcmd,cmdinf; } /* Raw STM sampleinfo struct: */ class STMSAMPLE{ byte filename[]; /* Can't have long comments - just filename comments :) */ byte unused; /* 0x00 */ short instdisk; /* Instrument disk */ short reserved; /* ISA in memory when in ST 2 */ int length; /* Sample length */ int loopbeg; /* Loop start point */ int loopend; /* Loop end point */ short volume; /* Volume */ byte reserved2; /* More reserved crap */ int c2spd; /* Good old c2spd */ byte reserved3[]; /* Yet more main_class.MLoader->of PSi's reserved crap */ int isa; /* Internal Segment Address -> */ /* contrary to the tech specs, this is NOT actually */ /* written to the stm file. */ public STMSAMPLE() { filename = new byte[12]; reserved3 = new byte[4]; } } /* Raw STM header struct: */ class STMHEADER{ byte songname[]; byte trackername[]; /* !SCREAM! for ST 2.xx */ byte unused; /* 0x1A */ byte filetype; /* 1=song, 2=module (only 2 is supported, of course) :) */ byte ver_major; /* Like 2 */ byte ver_minor; /* "ditto" */ short inittempo; /* initspeed= stm inittempo>>4 */ short numpat; /* number of patterns */ short globalvol; /* <- WoW! a RiGHT TRiANGLE =8*) */ byte reserved[]; /* More of PSi's internal crap */ STMSAMPLE sample[]; /* STM sample data */ short patorder[]; /* Docs say 64 - actually 128 */ public STMHEADER() { songname = new byte[20]; trackername = new byte[8]; reserved = new byte[13]; sample = new STMSAMPLE[31]; for(int i=0;i<31;i++) sample[i] = new STMSAMPLE(); patorder = new short[128]; } } public class STM_Loader extends clLOADER { public final String STM_Version="Screamtracker 2"; protected STMNOTE [] stmbuf; /* pointer to a complete S3M pattern */ protected STMHEADER mh; public STM_Loader(clMainBase theMain) { super(theMain); stmbuf = null; mh = null; type = new String("STM"); version = new String("Portable STM Loader - v1.2"); } public boolean Test() { byte str[] = new byte[9], filetype[] = new byte[1], should_be[] = new byte[10]; (new String("!SCREAM!")).getBytes(0,8, should_be, 0); should_be[8] = (byte)0; int a; m_.mmIO._mm_fseek(m_.MLoader.modfp,21,m_.mmIO.SEEK_SET); //fread(str,1,9,m_.MLoader.modfp); m_.MLoader.modfp.read(str,0,9); //fread(&filetype,1,1,m_.MLoader.modfp); m_.MLoader.modfp.read(filetype,0,1); for(a=0;a<8;a++) if (str[a] != should_be[a]) break; //if(!memcmp(str,"!SCREAM!",8) || (filetype[0]!=2)) /* STM Module = filetype 2 */ if ((a != 8) || (filetype[0] != 2)) /* STM Module = filetype 2 */ return false; return true; } public boolean Init() { int i, j; stmbuf=null; //if(!(mh=(STMHEADER *)m_.MLoader.MyCalloc(1,sizeof(STMHEADER)))) return 0; mh = new STMHEADER(); mh.unused = mh.filetype = mh.ver_major = mh.ver_minor = (byte)0; mh.inittempo = mh.numpat = mh.globalvol = (short)0; for(i=0;i<20;i++) mh.songname[i] = 0; for(i=0;i<8;i++) mh.trackername[i] = 0; for(i=0;i<13;i++) mh.reserved[i] = 0; for(i=0;i<128;i++) mh.patorder[i] = 0; for(i=0;i<31;i++) { mh.sample[i].unused = mh.sample[i].reserved2 = (byte)0; mh.sample[i].instdisk = mh.sample[i].reserved = mh.sample[i].volume = (short)0; mh.sample[i].length = mh.sample[i].loopbeg = mh.sample[i].loopend = mh.sample[i].c2spd = mh.sample[i].isa = 0; for(j=0;j<12;j++) mh.sample[i].filename[j] = 0; for(j=0;j<4;j++) mh.sample[i].reserved3[j] = 0; } return true; } public void Cleanup() { if(mh!=null) mh = null; if(stmbuf!=null) stmbuf = null; } public void STM_ConvertNote(STMNOTE n) { short note,ins,vol,cmd,inf; /* extract the various information from the 4 bytes that make up a single note */ note=n.note; ins=(short)(n.insvol>>3); vol=(short)((n.insvol&7)+(n.volcmd>>1)); cmd=(short)(n.volcmd&15); inf=n.cmdinf; if(ins!=0 && ins<32){ m_.MUniTrk.UniInstrument((short)(ins-1)); } /* special values of [char0] are handled here . */ /* we have no idea if these strange values will ever be encountered */ /* but it appears as though stms sound correct. */ if(note==254 || note==252) m_.MUniTrk.UniPTEffect((short)0xc,(short)0); /* <- note off command (???) */ else /* if note < 251, then all three bytes are stored in the file */ if(note<251) m_.MUniTrk.UniNote((short)((((note>>4)+2)*12)+(note&0xf))); /* <- normal note and up the octave by two */ if(vol<65){ m_.MUniTrk.UniPTEffect((short)0xc,vol); } if(cmd!=255){ switch(cmd){ case 1: /* Axx set speed to xx and add 0x1c to fix StoOoPiD STM 2.x */ m_.MUniTrk.UniPTEffect((short)0xf,(short)(inf>>4)); break; case 2: /* Bxx position jump */ m_.MUniTrk.UniPTEffect((short)0xb,inf); break; case 3: /* Cxx patternbreak to row xx */ m_.MUniTrk.UniPTEffect((short)0xd,inf); break; case 4: /* Dxy volumeslide */ m_.MUniTrk.UniWrite(m_.MUniTrk.UNI_S3MEFFECTD); m_.MUniTrk.UniWrite(inf); break; case 5: /* Exy toneslide down */ m_.MUniTrk.UniWrite(m_.MUniTrk.UNI_S3MEFFECTE); m_.MUniTrk.UniWrite(inf); break; case 6: /* Fxy toneslide up */ m_.MUniTrk.UniWrite(m_.MUniTrk.UNI_S3MEFFECTF); m_.MUniTrk.UniWrite(inf); break; case 7: /* Gxx Tone portamento,speed xx */ m_.MUniTrk.UniPTEffect((short)0x3,inf); break; case 8: /* Hxy vibrato */ m_.MUniTrk.UniPTEffect((short)0x4,inf); break; case 9: /* Ixy tremor, ontime x, offtime y */ m_.MUniTrk.UniWrite(m_.MUniTrk.UNI_S3MEFFECTI); m_.MUniTrk.UniWrite(inf); break; case 0xa: /* Jxy arpeggio */ m_.MUniTrk.UniPTEffect((short)0x0,inf); break; case 0xb: /* Kxy Dual command H00 & Dxy */ m_.MUniTrk.UniPTEffect((short)0x4,(short)0); m_.MUniTrk.UniWrite(m_.MUniTrk.UNI_S3MEFFECTD); m_.MUniTrk.UniWrite(inf); break; case 0xc: /* Lxy Dual command G00 & Dxy */ m_.MUniTrk.UniPTEffect((short)0x3,(short)0); m_.MUniTrk.UniWrite(m_.MUniTrk.UNI_S3MEFFECTD); m_.MUniTrk.UniWrite(inf); break; /* Support all these above, since ST2 can LOAD these values */ /* but can actually only play up to J - and J is only */ /* half-way implemented in ST2 */ case 0x18: /* Xxx amiga command 8xx - What the hell, support panning. :) */ m_.MUniTrk.UniPTEffect((short)0x8,inf); break; } } } public short [] STM_ConvertTrack(STMNOTE [] n, int offset) { int t; m_.MUniTrk.UniReset(); int n_ptr = offset; for(t=0;t<64;t++) { STM_ConvertNote(n[n_ptr]); m_.MUniTrk.UniNewline(); n_ptr+=m_.MLoader.of.numchn; } return m_.MUniTrk.UniDup(); } public boolean STM_LoadPatterns() { try { int t,s,tracks=0; if(!m_.MLoader.AllocPatterns()) return false; if(!m_.MLoader.AllocTracks()) return false; /* Allocate temporary buffer for loading and converting the patterns */ //if(!(stmbuf=(STMNOTE *)m_.MLoader.MyCalloc(64U*m_.MLoader.of.numchn,sizeof(STMNOTE)))) return 0; stmbuf = new STMNOTE[64*m_.MLoader.of.numchn]; for(t=0;t<64*m_.MLoader.of.numchn;t++) { stmbuf[t].note = stmbuf[t].insvol = stmbuf[t].volcmd = stmbuf[t].cmdinf; } for(t=0;t<m_.MLoader.of.numpat;t++){ for(s=0;s<(int)(64*m_.MLoader.of.numchn);s++){ stmbuf[s].note=m_.mmIO._mm_read_UBYTE(m_.MLoader.modfp); stmbuf[s].insvol=m_.mmIO._mm_read_UBYTE(m_.MLoader.modfp); stmbuf[s].volcmd=m_.mmIO._mm_read_UBYTE(m_.MLoader.modfp); stmbuf[s].cmdinf=m_.mmIO._mm_read_UBYTE(m_.MLoader.modfp); } //if(feof(m_.MLoader.modfp)){ if (m_.MLoader.modfp.getFilePointer() >= m_.MLoader.modfp.length()) { m_.mmIO.myerr=m_.ERROR_LOADING_PATTERN; return false; } for(s=0;s<m_.MLoader.of.numchn;s++){ if((m_.MLoader.of.tracks[tracks++]=STM_ConvertTrack(stmbuf,s))==null) return false; } } return true; } catch (IOException ioe1) { return false; } } public boolean Load() { try { int t; long MikMod_ISA; /* We MUST generate our own ISA - NOT stored in the stm */ //INSTRUMENT *d; //SAMPLE *q; int inst_num; /* try to read stm header */ m_.mmIO._mm_read_str(mh.songname,20,m_.MLoader.modfp); m_.mmIO._mm_read_str(mh.trackername,8,m_.MLoader.modfp); mh.unused =(byte)m_.mmIO._mm_read_UBYTE(m_.MLoader.modfp); mh.filetype =(byte)m_.mmIO._mm_read_UBYTE(m_.MLoader.modfp); mh.ver_major =(byte)m_.mmIO._mm_read_UBYTE(m_.MLoader.modfp); mh.ver_minor =(byte)m_.mmIO._mm_read_UBYTE(m_.MLoader.modfp); mh.inittempo =m_.mmIO._mm_read_UBYTE(m_.MLoader.modfp); mh.numpat =m_.mmIO._mm_read_UBYTE(m_.MLoader.modfp); mh.globalvol =m_.mmIO._mm_read_UBYTE(m_.MLoader.modfp); m_.mmIO._mm_read_SBYTES(mh.reserved,13,m_.MLoader.modfp); for(t=0;t<31;t++){ STMSAMPLE s=mh.sample[t]; /* STM sample data */ m_.mmIO._mm_read_str(s.filename,12,m_.MLoader.modfp); s.unused =(byte)m_.mmIO._mm_read_UBYTE(m_.MLoader.modfp); s.instdisk =m_.mmIO._mm_read_UBYTE(m_.MLoader.modfp); s.reserved =(short)m_.mmIO._mm_read_I_UWORD(m_.MLoader.modfp); s.length =m_.mmIO._mm_read_I_UWORD(m_.MLoader.modfp); s.loopbeg =m_.mmIO._mm_read_I_UWORD(m_.MLoader.modfp); s.loopend =m_.mmIO._mm_read_I_UWORD(m_.MLoader.modfp); s.volume =m_.mmIO._mm_read_UBYTE(m_.MLoader.modfp); s.reserved2=(byte)m_.mmIO._mm_read_UBYTE(m_.MLoader.modfp); s.c2spd =m_.mmIO._mm_read_I_UWORD(m_.MLoader.modfp); m_.mmIO._mm_read_SBYTES(s.reserved3,4,m_.MLoader.modfp); s.isa =m_.mmIO._mm_read_I_UWORD(m_.MLoader.modfp); } m_.mmIO._mm_read_UBYTES2(mh.patorder,128,m_.MLoader.modfp); //if(feof(m_.MLoader.modfp)){ if (m_.MLoader.modfp.getFilePointer() >= m_.MLoader.modfp.length()) { m_.mmIO.myerr=m_.ERROR_LOADING_HEADER; return false; } /* set module variables */ m_.MLoader.of.modtype=new String(STM_Version); m_.MLoader.of.songname=m_.MLoader.DupStr(mh.songname,20); /* make a cstr m_.MLoader.of songname */ m_.MLoader.of.numpat=mh.numpat; m_.MLoader.of.initspeed=6; /* Always this */ /* STM 2.x tempo has always been fucked... The default m_.MLoader.of 96 */ /* is actually 124, so we add 1ch to the initial value m_.MLoader.of 60h */ /* MikMak: No it's not.. STM tempo is UNI speed << 4 */ m_.MLoader.of.inittempo=125; /* mh.inittempo+0x1c; */ m_.MLoader.of.initspeed=(short)(mh.inittempo>>4); m_.MLoader.of.numchn=4; /* get number m_.MLoader.of channels */ t=0; while(mh.patorder[t]!=99){ /* 99 terminates the patorder list */ m_.MLoader.of.positions[t]=mh.patorder[t]; t++; } m_.MLoader.of.numpos=(short)(--t); m_.MLoader.of.numtrk=(short)(m_.MLoader.of.numpat*m_.MLoader.of.numchn); /* Finally, init the sampleinfo structures */ m_.MLoader.of.numins=31; /* always this */ if(!m_.MLoader.AllocInstruments()) return false; if(!STM_LoadPatterns()) return false; //d=m_.MLoader.of.instruments; inst_num = 0; //MikMod_ISA=ftell(m_.MLoader.modfp); MikMod_ISA=m_.MLoader.modfp.getFilePointer(); MikMod_ISA=(MikMod_ISA+15)&0xfffffff0; for(t=0;t<m_.MLoader.of.numins;t++){ m_.MLoader.of.instruments[inst_num].numsmp=1; if(!m_.MLoader.AllocSamples((m_.MLoader.of.instruments[inst_num]))) return false; //q=m_.MLoader.of.instruments[inst_num].samples; /* load sample info */ m_.MLoader.of.instruments[inst_num].insname=m_.MLoader.DupStr(mh.sample[t].filename,12); m_.MLoader.of.instruments[inst_num].samples[0].c2spd=mh.sample[t].c2spd; m_.MLoader.of.instruments[inst_num].samples[0].volume=mh.sample[t].volume; m_.MLoader.of.instruments[inst_num].samples[0].length=mh.sample[t].length; if ((mh.sample[t].volume == 0) || m_.MLoader.of.instruments[inst_num].samples[0].length==1 ) m_.MLoader.of.instruments[inst_num].samples[0].length = 0; /* if vol = 0 or length = 1, then no sample */ m_.MLoader.of.instruments[inst_num].samples[0].loopstart=mh.sample[t].loopbeg; m_.MLoader.of.instruments[inst_num].samples[0].loopend=mh.sample[t].loopend; m_.MLoader.of.instruments[inst_num].samples[0].seekpos=(int)MikMod_ISA; MikMod_ISA+=m_.MLoader.of.instruments[inst_num].samples[0].length; MikMod_ISA=(MikMod_ISA+15)&0xfffffff0; /* Once again, contrary to the STM specs, all the sample data is */ /* actually SIGNED! Sheesh */ m_.MLoader.of.instruments[inst_num].samples[0].flags = (m_.MDriver.SF_SIGNED); if(mh.sample[t].loopend>0 && mh.sample[t].loopend!=0xffff) m_.MLoader.of.instruments[inst_num].samples[0].flags |= (m_.MDriver.SF_LOOP); /* fix replen if repend>length */ if(m_.MLoader.of.instruments[inst_num].samples[0].loopend > m_.MLoader.of.instruments[inst_num].samples[0].length) m_.MLoader.of.instruments[inst_num].samples[0].loopend = m_.MLoader.of.instruments[inst_num].samples[0].length; //d++; inst_num++; } return true; } catch (IOException ioe1) { return false; } } }