Ask pouet.net: .mod player with c/c++ source code?
category: code [glöplog]
I'm involved in making a program which needs to able to play .mod files on a windows machine and perhaps also mac, and the more popular libraries dont seem to cut it since I need functionality to manipulate the patterndata while playing.
So I guess the way to go is to use some player with full source code available, preferrably in c,c++ , or atleast not in assembler. I've started having a look at libmikmod and I also found this one http://www.fasterlight.com/hugg/projects/javamod.html
Does anyone have any other suggestions?
One problem is that the mastermind behind the program is very demanding when it comes to the player, and it has always seemed to me that whenever a mod player or library is mentioned there are always people complaining that it is shit and cant play all tunes properly.
In lack of suggestions, trashing will also do, so I know what not to use and why.
So I guess the way to go is to use some player with full source code available, preferrably in c,c++ , or atleast not in assembler. I've started having a look at libmikmod and I also found this one http://www.fasterlight.com/hugg/projects/javamod.html
Does anyone have any other suggestions?
One problem is that the mastermind behind the program is very demanding when it comes to the player, and it has always seemed to me that whenever a mod player or library is mentioned there are always people complaining that it is shit and cant play all tunes properly.
In lack of suggestions, trashing will also do, so I know what not to use and why.
No .mod-player plays _all_ tunes properly, so you just have to pick one who does a good job most of the time. XMPlay (and therefore; BASS) plays most modules pretty darn accurately, but I don't think that comes with full source unfortunately.
hollow: if you can bend to xm instead of mod, minifmod comes with full source. not that it's a great quality player.
bass/xmplay is probably the gold standard these days, but it's only a lib - no full source as you said.
bass/xmplay is probably the gold standard these days, but it's only a lib - no full source as you said.
You could give XMP a try. It's a command line player, but it should be possible to use the player as a lib. Also, it looks like an active project. Don't know of the playback quality...
like gloom said XMPlay plays it nearly all perfect. Just contact Ian Luck, perhaps you can convince him to release or give you an includeable playrout.
hollowman: I've written a MOD/XM player for the GBA, but it works on PC as well as a pure audio-renderer (no sound-streaming code for win32 yet). Full C-source code is available at http://pimpmobile.kjip.no/, or https://svn.kjip.no/svn/pimpmobile/ for SVN access (the SVN is way more up-to-date than the ). Unfortunately the SVN-server is a bit unstable at the moment. Now, it's far from perfect, but at least it's something. It worked well for some of the SFC-demos ;)
BASS
Since we're at giving away protracker replays:
http://pagesperso-orange.fr/franck.charlet/DS_PtReplay.zip
This a .mod replay for the NDS, it handles up to 16 channels (all hardware) and is the smallest and most accurate replay in town.
http://pagesperso-orange.fr/franck.charlet/DS_PtReplay.zip
This a .mod replay for the NDS, it handles up to 16 channels (all hardware) and is the smallest and most accurate replay in town.
Hey, that's one neat player!
Thanks for the replies! I dont think xm is an option, or is there a safe and reliable way to convert from .mod to .xm? XMP looks interesting and much more active than mikmod.
Why is that? Do some trackers have effects that other trackers and players dont, or do they just sound a bit different?
Sounds like we have a winner=) Or are .mod files often in a non protracker format?
Quote:
No .mod-player plays _all_ tunes properly
Why is that? Do some trackers have effects that other trackers and players dont, or do they just sound a bit different?
Quote:
This is the smallest and most accurate protracker replay in town
Sounds like we have a winner=) Or are .mod files often in a non protracker format?
Quote:
Why is that? Do some trackers have effects that other trackers and players dont, or do they just sound a bit different?
There's different ways to handle .mods fx but the canonical one is the pt 1.x/2.x way.
Quote:
Sounds like we have a winner=) Or are .mod files often in a non protracker format?
Originally .mod have 4 channels (original protracker) but there are some more (mainly on PC) rare trackers which had >4 channels .mods as native format (like protracker studio 16 or something). Modplug correctly handles the .mods with more than 4 channels.
Quote:
Why is that? Do some trackers have effects that other trackers and players dont, or do they just sound a bit different?
Tons of undocumented bugs in the original player routines. Ft2 (and hence .xm) is famous for it. Also, lack of documentation in general.
Try looking here: http://sourceforge.net/projects/modplug/
there must be numerous ways to code a routine for gliding/vibrating/sliding a sample. if the formulas used in pt 1.x/2.x aren't revealed, then tough luck.
"is there a safe and reliable way to convert from .mod to .xm?"
just load the .mod in fast tracker 2 and save it as .xm.
if you are tinkering with a random .mod by mc coolbeat/megahawks inc in 1989, then there's no need to hassle the hoff, as the mods from "that period" tend to lack all the fancy effects.
and if the playback routines of modplug are still the same as few years ago, then forget it - that is if the .mod you're planning to use has lots of effects.
"is there a safe and reliable way to convert from .mod to .xm?"
just load the .mod in fast tracker 2 and save it as .xm.
if you are tinkering with a random .mod by mc coolbeat/megahawks inc in 1989, then there's no need to hassle the hoff, as the mods from "that period" tend to lack all the fancy effects.
and if the playback routines of modplug are still the same as few years ago, then forget it - that is if the .mod you're planning to use has lots of effects.
the reason why some mods arent played correctly is of course because there are different trackers and hence versions of the mod format. so to speak if your mod is made in protracker then get the source of the protracker format. (but you allready know that).
I've always found Modplug to be awful as a replayer goes, although the last time i touched that heap of junk program was many moons ago. On tunes like cream.mod it completely missed out the left or right(or one was very very quiet) channel and couldn't handle many of the codes correctly. And any replayer that butchers the god-like cream.mod deserves an arse-kicking!
modplug beats mikmod at least, which isn't saying much. However the source is decently hackable.
The best player (in terms of compatibility) has and will always be in my mind CAPAMOD.
The best player (in terms of compatibility) has and will always be in my mind CAPAMOD.
i found uFmod to be working nicely. It's however not really portable I guess as it's done in assembler. :) But at least there's a linux port (only used the win32 version so I can't say anything about how the linux version works).
IIRC: Many effects in old tracker formats however are based on/implemented on some kind of feedback on previous channel data. If you are going to manipulate the pattern data real time you will need a very advanced replayer to pull it off without disabling all effects with memory. If you only want to edit the module between playing you will need a player that is smart enough to replay enough patterns in the background before you hit play on your position to get all the effects correct.
I guess many real time replayers ignores this and just works on whatever channel data are available, at least thats how lazy me would do it. Then again, I might be all wrong and the effects aren't implemented with feedback.
I guess many real time replayers ignores this and just works on whatever channel data are available, at least thats how lazy me would do it. Then again, I might be all wrong and the effects aren't implemented with feedback.
Code:
// 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;
}
(just so you know)
and if all fails, play with BASS and read out the patterns yourself (should be a trivial file read), and synchronise with BASS' GetPosition function (or whatever it's called).
yeah but how would you modify them in realtime then, with BASS ?
haha, even with antialiasing! Lovely :)
kb: heyhey, once again a very cool player source. The Paula implementation does seem to lack the ability to queue up samples that play back-to-back though.