// test.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "dsio.h" const sInt PAULARATE=3740000; // approx. pal timing const sInt OUTRATE=48000; // approx. pal timing const sInt OUTFPS=50; // approx. pal timing //------------------------------------------------------------------------------ const sF32 sFPi=4*atanf(1); union sIntFlt { sU32 U32; sF32 F32; }; template T sSqr(T v) { return v*v; } template T sLerp(T a, T b, sF32 f) { return a+f*(b-a); } template T sAbs(T x) { return abs(x); } inline sF32 sFSqrt(sF32 x) { return sqrtf(x); } inline sF32 sFSin(sF32 x) { return sinf(x); } inline sF32 sFCos(sF32 x) { return cosf(x); } inline sF32 sFSinc(sF32 x) { return x?sFSin(x)/x:1; } inline sF32 sFHamming(sF32 x) { return (x>-1 && x<1)?sSqr(sFCos(x*sFPi/2)):0;} inline sF32 sFPow(sF32 b, sF32 e) { return powf(b,e); } inline void sSetMem(void *dest, sU8 v, sInt size) { memset(dest,v,size); } inline void sCopyMem(void *dest, const void *src, sInt size) { memcpy(dest,src,size); } inline void sZeroMem(void *dest, sInt size) { sSetMem(dest,0,size); } //------------------------------------------------------------------------------ class Paula { public: static const sInt FIR_WIDTH=512; sF32 FIRMem[2*FIR_WIDTH+1]; struct Voice { private: sInt Pos; sInt PWMCnt, DivCnt; sIntFlt Cur; public: sS8* Sample; sInt SampleLen; sInt LoopLen; sInt Period; // 124 .. 65535 sInt Volume; // 0 .. 64 Voice() : Period(65535), Volume(0), Sample(0), Pos(0), PWMCnt(0), DivCnt(0), LoopLen(1) { Cur.F32=0; } void Render(sF32 *buffer, sInt samples) { if (!Sample || !Volume) return; sU8 *smp=(sU8*)Sample; for (sInt i=0; iWritePos) Calc(); sF32 outl0=0, outl1=0; sF32 outr0=0, outr1=0; // this needs optimization. SSE would come to mind. sInt offs=(ReadPos-FIR_WIDTH-1)&(RBSIZE-1); sF32 vl=RingBuf[offs]; sF32 vr=RingBuf[offs+RBSIZE]; for (sInt i=1; i<2*FIR_WIDTH-1; i++) { sF32 w=FIRMem[i]; outl0+=vl*w; outr0+=vr*w; offs=(offs+1)&(RBSIZE-1); vl=RingBuf[offs]; vr=RingBuf[offs+RBSIZE]; outl1+=vl*w; outr1+=vr*w; } sF32 outl=sLerp(outl0,outl1,ReadFrac); sF32 outr=sLerp(outr0,outr1,ReadFrac); *outbuf++=vm0*outl+vm1*outr; *outbuf++=vm1*outl+vm0*outr; ReadFrac+=step; sInt rfi=sInt(ReadFrac); ReadPos=(ReadPos+rfi)&(RBSIZE-1); ReadFrac-=rfi; } } Paula() { // make FIR table sF32 *FIRTable=FIRMem+FIR_WIDTH; sF32 yscale=sF32(OUTRATE)/sF32(PAULARATE); sF32 xscale=sFPi*yscale; for (sInt i=-FIR_WIDTH; i<=FIR_WIDTH; i++) FIRTable[i]=yscale*sFSinc(sF32(i)*xscale)*sFHamming(sF32(i)/sF32(FIR_WIDTH-1)); sZeroMem(RingBuf,sizeof(RingBuf)); ReadPos=0; ReadFrac=0; WritePos=FIR_WIDTH; MasterVolume=0.66f; MasterSeparation=0.5f; //FltBuf=0; } }; //------------------------------------------------------------------------------ class ModPlayer { Paula *P; static inline void SwapEndian(sU16 &v) { v=((v&0xff)<<8)|(v>>8); } static sInt BasePTable[5*12+1]; static sInt PTable[16][60]; static sInt VibTable[3][15][64]; struct Sample { char Name[22]; sU16 Length; sS8 Finetune; sU8 Volume; sU16 LoopStart; sU16 LoopLen; void Prepare() { SwapEndian(Length); SwapEndian(LoopStart); SwapEndian(LoopLen); Finetune&=0x0f; if (Finetune>=8) Finetune-=16; } }; struct Pattern { struct Event { sInt Sample; sInt Note; sInt FX; sInt FXParm; } Events[64][4]; Pattern() { sZeroMem(this,sizeof(Pattern)); } void Load(sU8 *ptr) { for (sInt row=0; row<64; row++) for (sInt ch=0; ch<4; ch++) { Event &e=Events[row][ch]; e.Sample = (ptr[0]&0xf0)|(ptr[2]>>4); e.FX = ptr[2]&0x0f; e.FXParm = ptr[3]; e.Note=0; sInt period = (sInt(ptr[0]&0x0f)<<8)|ptr[1]; sInt bestd = sAbs(period-BasePTable[0]); if (period) for (sInt i=1; i<=60; i++) { sInt d=sAbs(period-BasePTable[i]); if (d7) { offs++; ft-=16; } while (ft<-8) { offs--; ft+=16; } return Note?(PTable[ft&0x0f][sClamp(Note+offs-1,0,59)]):0; } void SetPeriod(sInt offs=0, sInt fineoffs=0) { if (Note) Period=GetPeriod(offs,fineoffs); } } Chans[4]; sInt Speed; sInt TickRate; sInt TRCounter; sInt CurTick; sInt CurRow; sInt CurPos; sInt Delay; void CalcTickRate(sInt bpm) { TickRate=(125*OUTRATE)/(bpm*OUTFPS); } void TrigNote(sInt ch, const Pattern::Event &e) { Chan &c=Chans[ch]; Paula::Voice &v=P->V[ch]; const Sample &s=Samples[c.Sample]; sInt offset=0; if (e.FX==9) offset=c.FXBuf[9]<<8; if (e.FX!=3 && e.FX!=5) { c.SetPeriod(); if (s.LoopLen>1) v.Trigger(SData[c.Sample],2*(s.LoopStart+s.LoopLen),2*s.LoopLen,offset); else v.Trigger(SData[c.Sample],v.SampleLen=2*s.Length,1,offset); if (!c.VibRetr) c.VibPos=0; if (!c.TremRetr) c.TremPos=0; } } void Reset() { CalcTickRate(125); Speed=6; TRCounter=0; CurTick=0; CurRow=0; CurPos=0; Delay=0; } void Tick() { const Pattern &p=Patterns[PatternList[CurPos]]; const Pattern::Event *re=p.Events[CurRow]; for (sInt ch=0; ch<4; ch++) { const Pattern::Event &e=re[ch]; Paula::Voice &v=P->V[ch]; Chan &c=Chans[ch]; const sInt fxpl=e.FXParm&0x0f; sInt TremVol=0; if (!CurTick) { if (e.Sample) { c.Sample=e.Sample; c.FineTune=Samples[c.Sample].Finetune; c.Volume=Samples[c.Sample].Volume; } if (e.FXParm) c.FXBuf[e.FX]=e.FXParm; if (e.Note && (e.FX!=14 || ((e.FXParm>>4)!=13))) { c.Note=e.Note; TrigNote(ch,e); } switch (e.FX) { case 4: // vibrato if (c.FXBuf[4]&0x0f) c.SetPeriod(0,VibTable[c.VibWave][(c.FXBuf[4]&0x0f)-1][c.VibPos]); break; case 7: // tremolo if (c.FXBuf[7]&0x0f) TremVol=VibTable[c.TremWave][(c.FXBuf[7]&0x0f)-1][c.TremPos]; break; case 12: // set vol c.Volume=sClamp(e.FXParm,0,64); break; case 14: // special if (fxpl) c.FXBuf14[e.FXParm>>4]=fxpl; switch (e.FXParm>>4) { case 0: // set filter break; case 1: // fineslide up c.Period=sMax(113,c.Period-c.FXBuf14[1]); break; case 2: // slide down c.Period=sMin(856,c.Period+c.FXBuf14[2]); break; case 3: // set glissando sucks! break; case 4: // set vib waveform c.VibWave=fxpl&3; if (c.VibWave==3) c.VibWave=0; c.VibRetr=fxpl&4; break; case 5: // set finetune c.FineTune=fxpl; if (c.FineTune>=8) c.FineTune-=16; break; case 7: // set tremolo c.TremWave=fxpl&3; if (c.TremWave==3) c.TremWave=0; c.TremRetr=fxpl&4; break; case 9: // retrigger if (c.FXBuf14[9] && !e.Note) TrigNote(ch,e); c.RetrigCount=0; break; case 10: // fine volslide up c.Volume=sMin(c.Volume+c.FXBuf14[10],64); break; case 11: // fine volslide down; c.VibRetr=sMax(c.Volume-c.FXBuf14[11],0); break; case 14: // delay pattern Delay=c.FXBuf14[14]; break; case 15: // invert loop (WTF) break; } break; case 15: // set speed if (e.FXParm) if (e.FXParm<=32) Speed=e.FXParm; else CalcTickRate(e.FXParm); break; } } else { switch (e.FX) { case 0: // arpeggio if (e.FXParm) { sInt no=0; switch (CurTick%3) { case 1: no=e.FXParm>>4; break; case 2: no=e.FXParm&0x0f; break; } c.SetPeriod(no); } break; case 1: // slide up c.Period=sMax(113,c.Period-c.FXBuf[1]); break; case 2: // slide down c.Period=sMin(856,c.Period+c.FXBuf[2]); break; case 5: // slide plus volslide if (c.FXBuf[5]&0xf0) c.Volume=sMin(c.Volume+(c.FXBuf[5]>>4),0x40); else c.Volume=sMax(c.Volume-(c.FXBuf[5]&0x0f),0); // no break! case 3: // slide to note { sInt np=c.GetPeriod(); if (c.Period>np) c.Period=sMax(c.Period-c.FXBuf[3],np); else if (c.Period>4),0x40); else c.Volume=sMax(c.Volume-(c.FXBuf[6]&0x0f),0); // no break! case 4: // vibrato ??? if (c.FXBuf[4]&0x0f) c.SetPeriod(0,VibTable[c.VibWave][(c.FXBuf[4]&0x0f)-1][c.VibPos]); c.VibPos=(c.VibPos+(c.FXBuf[4]>>4))&0x3f; break; case 7: // tremolo ??? if (c.FXBuf[7]&0x0f) TremVol=VibTable[c.TremWave][(c.FXBuf[7]&0x0f)-1][c.TremPos]; c.TremPos=(c.TremPos+(c.FXBuf[7]>>4))&0x3f; break; case 10: // volslide if (c.FXBuf[10]&0xf0) c.Volume=sMin(c.Volume+(c.FXBuf[10]>>4),0x40); else c.Volume=sMax(c.Volume-(c.FXBuf[10]&0x0f),0); break; case 11: // pos jump if (CurTick==Speed-1) { CurRow=-1; CurPos=e.FXParm; } break; case 13: // pattern break if (CurTick==Speed-1) { CurPos++; CurRow=(10*(e.FXParm>>4)+(e.FXParm&0x0f))-1; } break; case 14: // special switch (e.FXParm>>4) { case 6: // loop pattern if (!fxpl) // loop start c.LoopStart=CurRow; else if (c.LoopCount=Speed*(Delay+1)) { CurTick=0; CurRow++; Delay=0; } if (CurRow>=64) { CurRow=0; CurPos++; } if (CurPos>=PositionCount) CurPos=0; }; public: char Name[21]; ModPlayer(Paula *p, sU8 *moddata) : P(p) { // calc ptable for (sInt ft=0; ft<16; ft++) { sInt rft= -((ft>=8)?ft-16:ft); sF32 fac=sFPow(2.0f,sF32(rft)/(12.0f*16.0f)); for (sInt i=0; i<60; i++) PTable[ft][i]=sInt(sF32(BasePTable[i])*fac+0.5f); } // calc vibtable for (sInt ampl=0; ampl<15; ampl++) { sF32 scale=ampl+1.5f; sF32 shift=0; for (sInt x=0; x<64; x++) { VibTable[0][ampl][x]=sInt(scale*sFSin(x*sFPi/32.0f)+shift); VibTable[1][ampl][x]=sInt(scale*((63-x)/31.5f-1.0f)+shift); VibTable[2][ampl][x]=sInt(scale*((x<32)?1:-1)+shift); } } // "load" the mod memcpy(Name,moddata,20); Name[20]=0; moddata+=20; SampleCount=16; ChannelCount=4; Samples=(Sample*)(moddata-sizeof(Sample)); moddata+=15*sizeof(Sample); sU32 &tag=*(sU32*)(moddata+130+16*sizeof(Sample)); switch (tag) { case '.K.M': case '4LTF': case '!K!M': SampleCount=32; break; } if (SampleCount>16) moddata+=(SampleCount-16)*sizeof(Sample); for (sInt i=1; i15) moddata+=4; // skip tag PatternCount=0; for (sInt i=0; i<128; i++) PatternCount=sClamp(PatternCount,PatternList[i]+1,128); for (sInt i=0; i(len,TRCounter); if (todo) { P->Render(buf,todo); buf+=2*todo; len-=todo; TRCounter-=todo; } else { Tick(); TRCounter=TickRate; } } return 1; } static sU32 __stdcall RenderProxy(void *parm, sF32 *buf, sU32 len) { return ((ModPlayer*)parm)->Render(buf,len); } }; sInt ModPlayer::BasePTable[61]= { 0, 1712,1616,1525,1440,1357,1281,1209,1141,1077,1017, 961, 907, 856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453, 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226, 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113, 107, 101, 95, 90, 85, 80, 76, 71, 67, 64, 60, 57, }; sInt ModPlayer::PTable[16][60]; sInt ModPlayer::VibTable[3][15][64]; //------------------------------------------------------------------------------ int main(int argc, char* argv[]) { FILE *f; fopen_s(&f,"c:\\mod\\dynasong.mod","rb"); fseek(f,0,SEEK_END); sInt size=ftell(f); fseek(f,0,SEEK_SET); sU8 *mod = new sU8[size]; fread(mod,size,1,f); fclose(f); Paula P; ModPlayer player(&P,mod); dsInit(player.RenderProxy,&player,GetForegroundWindow()); MessageBox(0,player.Name,"TinyMOD",MB_OK); dsClose(); delete[] mod; return 0; }