Add files via upload
This commit is contained in:
@@ -0,0 +1,706 @@
|
||||
// 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<typename T> T sSqr(T v) { return v*v; }
|
||||
template<typename T> T sLerp(T a, T b, sF32 f) { return a+f*(b-a); }
|
||||
template<typename T> 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; i<samples; i++)
|
||||
{
|
||||
if (!DivCnt)
|
||||
{
|
||||
// todo: use a fake d/a table for this
|
||||
Cur.U32=((smp[Pos]^0x80)<<15)|0x40000000;
|
||||
Cur.F32-=3.0f;
|
||||
if (++Pos==SampleLen) Pos-=LoopLen;
|
||||
DivCnt=Period;
|
||||
}
|
||||
if (PWMCnt<Volume) buffer[i]+=Cur.F32;
|
||||
PWMCnt=(PWMCnt+1)&0x3f;
|
||||
DivCnt--;
|
||||
}
|
||||
}
|
||||
|
||||
void Trigger(sS8 *smp,sInt sl, sInt ll, sInt offs=0)
|
||||
{
|
||||
Sample=smp;
|
||||
SampleLen=sl;
|
||||
LoopLen=ll;
|
||||
Pos=sMin(offs,SampleLen-1);
|
||||
}
|
||||
};
|
||||
|
||||
Voice V[4];
|
||||
|
||||
// rendering in paula freq
|
||||
static const sInt RBSIZE = 4096;
|
||||
sF32 RingBuf[2*RBSIZE];
|
||||
sInt WritePos;
|
||||
sInt ReadPos;
|
||||
sF32 ReadFrac;
|
||||
//sF32 FltFreq;
|
||||
//sF32 FltBuf;
|
||||
|
||||
void CalcFrag(sF32 *out, sInt samples)
|
||||
{
|
||||
sZeroMem(out,sizeof(sF32)*samples);
|
||||
sZeroMem(out+RBSIZE,sizeof(sF32)*samples);
|
||||
for (sInt i=0; i<4; i++)
|
||||
{
|
||||
if (i==1 || i==2)
|
||||
V[i].Render(out+RBSIZE,samples);
|
||||
else
|
||||
V[i].Render(out,samples);
|
||||
}
|
||||
}
|
||||
|
||||
void Calc() // todo: stereo
|
||||
{
|
||||
sInt RealReadPos=ReadPos-FIR_WIDTH-1;
|
||||
sInt samples=(RealReadPos-WritePos)&(RBSIZE-1);
|
||||
|
||||
sInt todo=sMin(samples,RBSIZE-WritePos);
|
||||
CalcFrag(RingBuf+WritePos,todo);
|
||||
if (todo<samples)
|
||||
{
|
||||
WritePos=0;
|
||||
todo=samples-todo;
|
||||
CalcFrag(RingBuf,todo);
|
||||
}
|
||||
WritePos+=todo;
|
||||
};
|
||||
|
||||
sF32 MasterVolume;
|
||||
sF32 MasterSeparation;
|
||||
|
||||
// rendering in output freq
|
||||
void Render(sF32 *outbuf, sInt samples)
|
||||
{
|
||||
const sF32 step=sF32(PAULARATE)/sF32(OUTRATE);
|
||||
const sF32 pan=0.5f+0.5f*MasterSeparation;
|
||||
const sF32 vm0=MasterVolume*sFSqrt(pan);
|
||||
const sF32 vm1=MasterVolume*sFSqrt(1-pan);
|
||||
|
||||
for (sInt s=0; s<samples; s++)
|
||||
{
|
||||
sInt ReadEnd=ReadPos+FIR_WIDTH+1;
|
||||
if (WritePos<ReadPos) ReadEnd-=RBSIZE;
|
||||
if (ReadEnd>WritePos) 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 (d<bestd)
|
||||
{
|
||||
bestd=d;
|
||||
e.Note=i;
|
||||
}
|
||||
}
|
||||
|
||||
ptr+=4;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Sample *Samples;
|
||||
sS8 *SData[32];
|
||||
sInt SampleCount;
|
||||
sInt ChannelCount;
|
||||
|
||||
sU8 PatternList[128];
|
||||
sInt PositionCount;
|
||||
sInt PatternCount;
|
||||
|
||||
Pattern Patterns[128];
|
||||
|
||||
struct Chan
|
||||
{
|
||||
sInt Note;
|
||||
sInt Period;
|
||||
sInt Sample;
|
||||
sInt FineTune;
|
||||
sInt Volume;
|
||||
sInt FXBuf[16];
|
||||
sInt FXBuf14[16];
|
||||
sInt LoopStart;
|
||||
sInt LoopCount;
|
||||
sInt RetrigCount;
|
||||
sInt VibWave;
|
||||
sInt VibRetr;
|
||||
sInt VibPos;
|
||||
sInt TremWave;
|
||||
sInt TremRetr;
|
||||
sInt TremPos;
|
||||
|
||||
Chan() { sZeroMem(this,sizeof(Chan)); }
|
||||
|
||||
sInt GetPeriod(sInt offs=0, sInt fineoffs=0)
|
||||
{
|
||||
sInt ft=FineTune+fineoffs;
|
||||
while (ft>7) { 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<np)
|
||||
c.Period=sMin(c.Period+c.FXBuf[3],np);
|
||||
}
|
||||
break;
|
||||
case 6: // vibrato plus volslide
|
||||
if (c.FXBuf[6]&0xf0)
|
||||
c.Volume=sMin(c.Volume+(c.FXBuf[6]>>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<fxpl)
|
||||
{
|
||||
CurRow=c.LoopStart-1;
|
||||
c.LoopCount++;
|
||||
}
|
||||
else
|
||||
c.LoopCount=0;
|
||||
break;
|
||||
case 9: // retrigger
|
||||
if (++c.RetrigCount == c.FXBuf14[9])
|
||||
{
|
||||
c.RetrigCount=0;
|
||||
TrigNote(ch,e);
|
||||
}
|
||||
break;
|
||||
case 12: // cut
|
||||
if (CurTick==c.FXBuf14[12])
|
||||
c.Volume=0;
|
||||
break;
|
||||
case 13: // delay
|
||||
if (CurTick==c.FXBuf14[13])
|
||||
TrigNote(ch,e);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
v.Volume=sClamp(c.Volume+TremVol,0,64);
|
||||
v.Period=c.Period;
|
||||
}
|
||||
|
||||
CurTick++;
|
||||
if (CurTick>=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; i<SampleCount; i++) Samples[i].Prepare();
|
||||
|
||||
PositionCount=*moddata; moddata+=2; // + skip unused byte
|
||||
memcpy(PatternList,moddata,128); moddata+=128;
|
||||
if (SampleCount>15) 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<PatternCount; i++)
|
||||
{
|
||||
Patterns[i].Load(moddata);
|
||||
moddata+=1024;
|
||||
}
|
||||
|
||||
sZeroMem(SData,sizeof(SData));
|
||||
for (sInt i=1; i<SampleCount; i++)
|
||||
{
|
||||
SData[i]=(sS8*)moddata;
|
||||
moddata+=2*Samples[i].Length;
|
||||
}
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
sU32 Render(sF32 *buf, sU32 len)
|
||||
{
|
||||
while (len)
|
||||
{
|
||||
sInt todo=sMin<sInt>(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;
|
||||
}
|
||||
@@ -0,0 +1,788 @@
|
||||
/*
|
||||
|
||||
TinyMOD
|
||||
Written by Tammo "kb" Hinrichs in 2007
|
||||
This source code is hereby placed into the public domain. Use, distribute,
|
||||
modify, misappropriate and generally abuse it as you wish. Giving credits
|
||||
would be nice of course.
|
||||
|
||||
This player includes an Amiga Paula chip "emulation" that faithfully recreates
|
||||
how it sounds when a sample is resampled using a master clock of 3.5 MHz. Yes,
|
||||
rendering at this rate and downsampling to the usual 48KHz takes quite a bit
|
||||
of CPU. Feel free to replace this part with some conventional mixing routines
|
||||
if authenticity isn't your goal and you really need Protracker MOD support
|
||||
for any other reason...
|
||||
|
||||
The code should be pretty portable, all OS/platform dependent stuff is
|
||||
at the top. Code for testing is at the bottom.
|
||||
|
||||
You'll need some kind of sound output that calls back the player providing
|
||||
it a stereo interleaved single float buffer to write into (0dB=1.0).
|
||||
|
||||
Changelog:
|
||||
|
||||
2007-12-07:
|
||||
* fixed 40x and 4x0 vibrato effects (jogeir - tiny tunes)
|
||||
* fixed pattern loop (olof gustafsson - pinball illusions)
|
||||
* fixed fine volslide down (olof gustafsson - pinball illusions)
|
||||
* included some external header files
|
||||
* cleanups
|
||||
|
||||
2007-12-06: first "release". Note to self: Don't post stuff on pouet.net when drunk.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// system dependent stuff starts here
|
||||
|
||||
#define _CRT_SECURE_NO_DEPRECATE
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#pragma intrinsic (memset, sqrt, sin, cos, atan, pow)
|
||||
|
||||
typedef int sInt;
|
||||
typedef unsigned int sUInt;
|
||||
|
||||
typedef sInt sBool;
|
||||
typedef char sChar;
|
||||
|
||||
typedef signed char sS8;
|
||||
typedef signed short sS16;
|
||||
typedef signed long sS32;
|
||||
typedef signed __int64 sS64;
|
||||
|
||||
typedef unsigned char sU8;
|
||||
typedef unsigned short sU16;
|
||||
typedef unsigned long sU32;
|
||||
typedef unsigned __int64 sU64;
|
||||
|
||||
typedef float sF32;
|
||||
typedef double sF64;
|
||||
|
||||
inline void sZeroMem(void *dest, sInt size) { memset(dest,0,size); }
|
||||
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 sFAtan(sF32 x) { return atanf(x); }
|
||||
inline sF32 sFPow(sF32 b, sF32 e) { return powf(b,e); }
|
||||
|
||||
inline void sSwapEndian(sU16 &v) { v=((v&0xff)<<8)|(v>>8); }
|
||||
|
||||
|
||||
// system dependent stuff ends here
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const sF32 sFPi=4*sFAtan(1);
|
||||
|
||||
template<typename T> inline T sMin(const T a, const T b) { return (a<b)?a:b; }
|
||||
template<typename T> inline T sMax(const T a, const T b) { return (a>b)?a:b; }
|
||||
template<typename T> inline T sClamp(const T x, const T min, const T max) { return sMax(min,sMin(max,x)); }
|
||||
template<typename T> T sSqr(T v) { return v*v; }
|
||||
template<typename T> T sLerp(T a, T b, sF32 f) { return a+f*(b-a); }
|
||||
template<typename T> T sAbs(T x) { return abs(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;}
|
||||
|
||||
union sIntFlt { sU32 U32; sF32 F32; };
|
||||
|
||||
const sInt PAULARATE=3740000; // approx. pal timing
|
||||
const sInt OUTRATE=48000; // approx. pal timing
|
||||
const sInt OUTFPS=50; // approx. pal timing
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
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) return;
|
||||
|
||||
sU8 *smp=(sU8*)Sample;
|
||||
for (sInt i=0; i<samples; i++)
|
||||
{
|
||||
if (!DivCnt)
|
||||
{
|
||||
// todo: use a fake d/a table for this
|
||||
Cur.U32=((smp[Pos]^0x80)<<15)|0x40000000;
|
||||
Cur.F32-=3.0f;
|
||||
if (++Pos==SampleLen) Pos-=LoopLen;
|
||||
DivCnt=Period;
|
||||
}
|
||||
if (PWMCnt<Volume) buffer[i]+=Cur.F32;
|
||||
PWMCnt=(PWMCnt+1)&0x3f;
|
||||
DivCnt--;
|
||||
}
|
||||
}
|
||||
|
||||
void Trigger(sS8 *smp,sInt sl, sInt ll, sInt offs=0)
|
||||
{
|
||||
Sample=smp;
|
||||
SampleLen=sl;
|
||||
LoopLen=ll;
|
||||
Pos=sMin(offs,SampleLen-1);
|
||||
}
|
||||
};
|
||||
|
||||
Voice V[4];
|
||||
|
||||
// rendering in paula freq
|
||||
static const sInt RBSIZE = 4096;
|
||||
sF32 RingBuf[2*RBSIZE];
|
||||
sInt WritePos;
|
||||
sInt ReadPos;
|
||||
sF32 ReadFrac;
|
||||
|
||||
void CalcFrag(sF32 *out, sInt samples)
|
||||
{
|
||||
sZeroMem(out,sizeof(sF32)*samples);
|
||||
sZeroMem(out+RBSIZE,sizeof(sF32)*samples);
|
||||
for (sInt i=0; i<4; i++)
|
||||
{
|
||||
if (i==1 || i==2)
|
||||
V[i].Render(out+RBSIZE,samples);
|
||||
else
|
||||
V[i].Render(out,samples);
|
||||
}
|
||||
}
|
||||
|
||||
void Calc()
|
||||
{
|
||||
sInt RealReadPos=ReadPos-FIR_WIDTH-1;
|
||||
sInt samples=(RealReadPos-WritePos)&(RBSIZE-1);
|
||||
|
||||
sInt todo=sMin(samples,RBSIZE-WritePos);
|
||||
CalcFrag(RingBuf+WritePos,todo);
|
||||
if (todo<samples)
|
||||
{
|
||||
WritePos=0;
|
||||
todo=samples-todo;
|
||||
CalcFrag(RingBuf,todo);
|
||||
}
|
||||
WritePos+=todo;
|
||||
};
|
||||
|
||||
sF32 MasterVolume;
|
||||
sF32 MasterSeparation;
|
||||
|
||||
// rendering in output freq
|
||||
void Render(sF32 *outbuf, sInt samples)
|
||||
{
|
||||
const sF32 step=sF32(PAULARATE)/sF32(OUTRATE);
|
||||
const sF32 pan=0.5f+0.5f*MasterSeparation;
|
||||
const sF32 vm0=MasterVolume*sFSqrt(pan);
|
||||
const sF32 vm1=MasterVolume*sFSqrt(1-pan);
|
||||
|
||||
for (sInt s=0; s<samples; s++)
|
||||
{
|
||||
sInt ReadEnd=ReadPos+FIR_WIDTH+1;
|
||||
if (WritePos<ReadPos) ReadEnd-=RBSIZE;
|
||||
if (ReadEnd>WritePos) 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 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()
|
||||
{
|
||||
sSwapEndian(Length);
|
||||
sSwapEndian(LoopStart);
|
||||
sSwapEndian(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 (d<bestd)
|
||||
{
|
||||
bestd=d;
|
||||
e.Note=i;
|
||||
}
|
||||
}
|
||||
|
||||
ptr+=4;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Sample *Samples;
|
||||
sS8 *SData[32];
|
||||
sInt SampleCount;
|
||||
sInt ChannelCount;
|
||||
|
||||
sU8 PatternList[128];
|
||||
sInt PositionCount;
|
||||
sInt PatternCount;
|
||||
|
||||
Pattern Patterns[128];
|
||||
|
||||
struct Chan
|
||||
{
|
||||
sInt Note;
|
||||
sInt Period;
|
||||
sInt Sample;
|
||||
sInt FineTune;
|
||||
sInt Volume;
|
||||
sInt FXBuf[16];
|
||||
sInt FXBuf14[16];
|
||||
sInt LoopStart;
|
||||
sInt LoopCount;
|
||||
sInt RetrigCount;
|
||||
sInt VibWave;
|
||||
sInt VibRetr;
|
||||
sInt VibPos;
|
||||
sInt VibAmpl;
|
||||
sInt VibSpeed;
|
||||
sInt TremWave;
|
||||
sInt TremRetr;
|
||||
sInt TremPos;
|
||||
sInt TremAmpl;
|
||||
sInt TremSpeed;
|
||||
|
||||
Chan() { sZeroMem(this,sizeof(Chan)); }
|
||||
|
||||
sInt GetPeriod(sInt offs=0, sInt fineoffs=0)
|
||||
{
|
||||
sInt ft=FineTune+fineoffs;
|
||||
while (ft>7) { 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
|
||||
case 6:
|
||||
if (c.FXBuf[4]&0x0f) c.VibAmpl=c.FXBuf[4]&0x0f;
|
||||
if (c.FXBuf[4]&0xf0) c.VibSpeed=c.FXBuf[4]>>4;
|
||||
c.SetPeriod(0,VibTable[c.VibWave][(c.VibAmpl)-1][c.VibPos]);
|
||||
break;
|
||||
case 7: // tremolo
|
||||
if (c.FXBuf[7]&0x0f) c.TremAmpl=c.FXBuf[7]&0x0f;
|
||||
if (c.FXBuf[7]&0xf0) c.TremSpeed=c.FXBuf[7]>>4;
|
||||
TremVol=VibTable[c.TremWave][(c.TremAmpl)-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.Volume=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<np)
|
||||
c.Period=sMin(c.Period+c.FXBuf[3],np);
|
||||
}
|
||||
break;
|
||||
case 6: // vibrato plus volslide
|
||||
if (c.FXBuf[6]&0xf0)
|
||||
c.Volume=sMin(c.Volume+(c.FXBuf[6]>>4),0x40);
|
||||
else
|
||||
c.Volume=sMax(c.Volume-(c.FXBuf[6]&0x0f),0);
|
||||
// no break!
|
||||
case 4: // vibrato ???
|
||||
c.SetPeriod(0,VibTable[c.VibWave][c.VibAmpl-1][c.VibPos]);
|
||||
c.VibPos=(c.VibPos+c.VibSpeed)&0x3f;
|
||||
break;
|
||||
case 7: // tremolo ???
|
||||
TremVol=VibTable[c.TremWave][c.TremAmpl-1][c.TremPos];
|
||||
c.TremPos=(c.TremPos+c.TremSpeed)&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 (CurTick==Speed-1)
|
||||
{
|
||||
if (c.LoopCount<fxpl)
|
||||
{
|
||||
CurRow=c.LoopStart-1;
|
||||
c.LoopCount++;
|
||||
}
|
||||
else
|
||||
c.LoopCount=0;
|
||||
}
|
||||
break;
|
||||
case 9: // retrigger
|
||||
if (++c.RetrigCount == c.FXBuf14[9])
|
||||
{
|
||||
c.RetrigCount=0;
|
||||
TrigNote(ch,e);
|
||||
}
|
||||
break;
|
||||
case 12: // cut
|
||||
if (CurTick==c.FXBuf14[12])
|
||||
c.Volume=0;
|
||||
break;
|
||||
case 13: // delay
|
||||
if (CurTick==c.FXBuf14[13])
|
||||
TrigNote(ch,e);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
v.Volume=sClamp(c.Volume+TremVol,0,64);
|
||||
v.Period=c.Period;
|
||||
}
|
||||
|
||||
CurTick++;
|
||||
if (CurTick>=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 '4TLF': case '!K!M':
|
||||
SampleCount=32;
|
||||
break;
|
||||
}
|
||||
if (SampleCount>16)
|
||||
moddata+=(SampleCount-16)*sizeof(Sample);
|
||||
for (sInt i=1; i<SampleCount; i++) Samples[i].Prepare();
|
||||
|
||||
PositionCount=*moddata; moddata+=2; // + skip unused byte
|
||||
memcpy(PatternList,moddata,128); moddata+=128;
|
||||
if (SampleCount>15) 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<PatternCount; i++)
|
||||
{
|
||||
Patterns[i].Load(moddata);
|
||||
moddata+=1024;
|
||||
}
|
||||
|
||||
sZeroMem(SData,sizeof(SData));
|
||||
for (sInt i=1; i<SampleCount; i++)
|
||||
{
|
||||
SData[i]=(sS8*)moddata;
|
||||
moddata+=2*Samples[i].Length;
|
||||
}
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
sU32 Render(sF32 *buf, sU32 len)
|
||||
{
|
||||
while (len)
|
||||
{
|
||||
sInt todo=sMin<sInt>(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];
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// ok, let's test it:
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
|
||||
#include <windows.h>
|
||||
#include "dsio.h" // good luck.
|
||||
|
||||
static sU8 mod[4*1024*1024];
|
||||
|
||||
int __cdecl main(int argc, const char **argv)
|
||||
{
|
||||
if (argc<2)
|
||||
{
|
||||
MessageBox(0,"Usage: tinymod <mod name>","TinyMOD",MB_OK);
|
||||
return 1;
|
||||
}
|
||||
HANDLE fh=CreateFile(argv[1],GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,0,0);
|
||||
if (fh==INVALID_HANDLE_VALUE)
|
||||
{
|
||||
MessageBox(0,"couldn't open file!","TinyMOD",MB_OK);
|
||||
return 1;
|
||||
}
|
||||
sInt size=GetFileSize(fh,0);
|
||||
DWORD read;
|
||||
ReadFile(fh,mod,size,&read,0);
|
||||
CloseHandle(fh);
|
||||
|
||||
Paula P;
|
||||
ModPlayer player(&P,mod);
|
||||
dsInit(player.RenderProxy,&player,GetForegroundWindow());
|
||||
MessageBox(0,player.Name,"TinyMOD",MB_OK);
|
||||
dsClose();
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user