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.
added on the 2007-12-05 09:44:55 by hollowman hollowman
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.
added on the 2007-12-05 09:50:13 by gloom gloom
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.
added on the 2007-12-05 09:57:55 by smash smash
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...
added on the 2007-12-05 09:59:16 by Spin Spin
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.
added on the 2007-12-05 09:59:21 by seppjo seppjo
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 ;)
added on the 2007-12-05 10:04:21 by kusma kusma
added on the 2007-12-05 10:48:09 by shock__ shock__
Since we're at giving away protracker replays:


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.

added on the 2007-12-05 11:19:06 by hitchhikr hitchhikr
Hey, that's one neat player!
added on the 2007-12-05 11:37:27 by kusma kusma
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.


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?


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?
added on the 2007-12-05 11:42:19 by hollowman hollowman

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.


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.
added on the 2007-12-05 11:50:07 by hitchhikr hitchhikr
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.
added on the 2007-12-05 13:52:22 by tomaes tomaes
added on the 2007-12-05 16:33:41 by xernobyl xernobyl
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.
added on the 2007-12-05 17:09:28 by tempest tempest
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!
added on the 2007-12-05 18:39:24 by Intrinsic Intrinsic
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.

added on the 2007-12-05 19:51:11 by _-_-__ _-_-__
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).
added on the 2007-12-06 00:12:31 by StingRay StingRay
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.
added on the 2007-12-06 00:16:47 by Hatikvah Hatikvah
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; }

added on the 2007-12-06 04:02:58 by kb_ kb_
(just so you know)
added on the 2007-12-06 04:05:33 by kb_ kb_
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).
added on the 2007-12-06 08:45:04 by skrebbel skrebbel
yeah but how would you modify them in realtime then, with BASS ?
added on the 2007-12-06 09:24:40 by _-_-__ _-_-__
haha, even with antialiasing! Lovely :)
added on the 2007-12-06 10:36:46 by iq iq
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.
added on the 2007-12-06 11:26:26 by kusma kusma