Initial commit

bulk data dump
This commit is contained in:
Jason Bou-Samra
2024-03-11 19:26:51 +11:00
committed by GitHub
commit 3f0c6d4114
10 changed files with 2353 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
## build TinyMOD
## jbs - paragonsoft
all: tinymod
tinymod: tinymod.cpp
## g++ -o tinymod tinymod.cpp
g++ -o tinymod `pkg-config --libs alsa` tinymod.cpp -lm -L . -l:libportaudio.a
clean:
rm tinymod
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+612
View File
@@ -0,0 +1,612 @@
// ================================ Define ModPlayer Class (MOD Player) ===============================================
class ModPlayer // ModPlayer class
{
private:
Paula *P; // create instance of Paula
static sInt BasePTable[5 * 12 + 1]; // base period table (5 octaves x 12 semitones + extra 1)
static sInt PTable[16][60]; // period table (16 period tables x 60 semitones (5 x 12)
static sInt VibTable[3][15][64]; // vibrato table (vib waveform, vib amplitude, vib position)
// ** tick related
sInt Speed; // speed
sInt TickRate; // tick rate
sInt TRCounter; // tick rate counter
sInt CurTick; // current tick
sInt CurRow; // current row
sInt CurPos; // current song position
sInt Delay; // delay?
// ** sample related
sS8 *SData[32]; // sample data
sInt SampleCount; // sample count
sInt ChannelCount; // channel count
// ** pattern related
sU8 PatternList[128]; // pattern list
sInt PositionCount; // position count
sInt PatternCount; // pattern count
// ################################################ SAMPLE STRUCTURE ################################################
private:
struct Sample
{
char Name[22]; // sample name
sU16 Length; // sample length
sS8 Finetune; // sample finetune
sU8 Volume; // sample volume
sU16 LoopStart; // sample loop start position
sU16 LoopLen; // sample loop length
// ************ PREPARE ************
void Prepare () // take care on endianness issues
{
sSwapEndian (Length); // swap high & low bytes
sSwapEndian (LoopStart); // swap high & low bytes
sSwapEndian (LoopLen); // swap high & low bytes
Finetune &= 0x0f;
if (Finetune >= 8) Finetune -= 16; // sample finetune is between -8 & 7
}
} *Samples; // Sample structure end
// Sample *Samples;
// ################################################ PATTERN STRUCTURE ################################################
private:
struct Pattern // pattern structure
{
struct Event
{
sInt Sample; // sample
sInt Note; // note
sInt FX; // effect
sInt FXParm; // effect paramater
} Events[64][4];
// ************ PATTERN CONSTRUCTOR ************
Pattern ()
{
sZeroMem (this, sizeof (Pattern)); // pattern constructor
}
// ************ LOAD ************
void Load (sU8 *ptr) // Load start
{
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); // sample
e.FX = ptr[2] & 0x0f; // effect
e.FXParm = ptr[3]; // effect paramater
e.Note = 0; // note
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;
}
} // period
ptr += 4;
}
} // Load end
} Patterns[128]; // Pattern structure end
// Pattern Patterns[128]; // patterns
// ################################################ CHANNEL STRUCTURE ################################################
private:
struct Chan // Channel Structure start
{
sInt Note; // note
sInt Period; // period
sInt Sample; // sample
sInt FineTune; // fine tune
sInt Volume; // volume
sInt FXBuf[16]; // effects buffer
sInt FXBuf14[16]; // effects buffer (command 14 - extend)
sInt LoopStart; // loop start
sInt LoopCount; // loop count
sInt RetrigCount; // retrigger count
sInt VibWave; // vibrato waveform
sInt VibRetr; // vibrato retrigger
sInt VibPos; // vibrato position
sInt VibAmpl; // vibrato amplitude
sInt VibSpeed; // vibrato speed
sInt TremWave; // tremolo waveform
sInt TremRetr; // tremolo retrigger
sInt TremPos; // tremolo position
sInt TremAmpl; // tremolo amplitude
sInt TremSpeed; // tremolo speed
// ************ CONSTRUCTOR ************
Chan () { sZeroMem (this, sizeof (Chan)); } // channel constructor
// ************ GET PERIOD ************
sInt GetPeriod (sInt offs = 0, sInt fineoffs = 0) // Get Period
{
sInt ft = FineTune + fineoffs; // fintune offset
while (ft > 7)
{
offs++; // offset
ft -= 16;
}
while (ft < -8)
{
offs--;
ft += 16;
}
return Note ? (PTable[ft & 0x0f][sClamp (Note + offs - 1, 0, 59)]) : 0;
}
// ************ SET PERIOD ************
void SetPeriod (sInt offs = 0, sInt fineoffs = 0) // Set Period
{
if (Note)
Period = GetPeriod (offs, fineoffs);
}
} Chans[4]; // Channel Structure end
// ************ CALCULATE TICK RATE ************
private:
void CalcTickRate (sInt bpm) // calculate tick rate start
{
TickRate = (125 * OUTRATE) / (bpm * OUTFPS);
} // calculate tick rate end
// ************ TRIGGER NOTE ************
private:
void TrigNote (sInt ch, const Pattern::Event &e) // trigger note (channel, event)
{
Chan &c = Chans[ch]; // channel
Paula::Voice &v = P->V[ch]; // Paula::Voice &v = P->[ch]; P is instance of Paula class,
// v is voice, ch is channel - reference (alias)
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;
}
} // Trigger note end
// ************ RESET ************
private:
void Reset () // reset function
{
CalcTickRate (125); // default tick rate = 125
Speed = 6; // default speed = 6
TRCounter = 0; // tick rate counter = 0
CurTick = 0; // current tick = 0
CurRow = 0; // current row = 0
CurPos = 0; // current song position = 0
Delay = 0; // delay = 0
}
// ************ TICK ************
private:
void Tick () // start tick routine, cycle (50Hz, 20ms)
{
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; // pattern list
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 (4) / vibrato + volume slide (6)
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 (7)
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 volume (C)
c.Volume = sClamp (e.FXParm, 0, 64);
break;
case 14: // special (Exx)
// (E0X - turn filter on/off),
// (E1x - porta up, fine),
// (E2x - porta down, fine),
// (E3x - glissando control),
// (E4x - vibratio waveform),
// (E5x - set finetune),
// (E6x - pattern loop),
// (E7x - tremolo waveform),
// (E8x - not implemented),
// (E9x - retrigger note),
// (EAx - volume slide up, fine),
// (EBx - volume slide down, fine),
// (ECx - cut note),
// (EDx - delay note),
// (EEx - pattern delay),
// (EFx - not implemented)
if (fxpl)
c.FXBuf14[e.FXParm >> 4] = fxpl;
switch (e.FXParm >> 4)
{
case 0: // set filter (0x)
break;
case 1: // fineslide up (1x)
c.Period = sMax (113, c.Period - c.FXBuf14[1]);
break;
case 2: // slide down (2x)
c.Period = sMin (856, c.Period + c.FXBuf14[2]);
break;
case 3: // set glissando sucks! (0/1)
break;
case 4: // set vib waveform (1/2)
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 (1/2)
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 14 end
case 15: // set speed (F)
if (e.FXParm)
if (e.FXParm <= 32)
Speed = e.FXParm;
else
CalcTickRate (e.FXParm);
break;
}
}
else
{
switch (e.FX)
{
case 0: // arpeggio (or normal play)
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: // Tone Portamento + volume slide slide
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: // tone portamento (slide to note) slide speed
{
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 (speed + depth)
c.SetPeriod (0, VibTable[c.VibWave][c.VibAmpl - 1][c.VibPos]);
c.VibPos = (c.VibPos + c.VibSpeed) & 0x3f;
break;
case 7: // tremolo (rate + depth)
TremVol = VibTable[c.TremWave][c.TremAmpl - 1][c.TremPos];
c.TremPos = (c.TremPos + c.TremSpeed) & 0x3f;
break;
case 10: // volume slide
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: // position 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: // set filter (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: // set sample offset (re-trigger)
if (++c.RetrigCount == c.FXBuf14[9])
{
c.RetrigCount = 0;
TrigNote (ch, e);
}
break;
case 12: // set volume
if (CurTick == c.FXBuf14[12])
c.Volume = 0;
break;
case 13: // pattern break
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;
}; // end tick routine
// ************ MODPLAYER CONSTRUCTOR ************
public:
char Name[21]; // song name
ModPlayer (Paula *p, sU8 *moddata) : P (p) // ModPlayer constructor (paula object and MOD data)
{
for (sInt ft = 0; ft < 16; ft++) // calc ptable (period table) - finetune
{
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);
}
for (sInt ampl = 0; ampl < 15; ampl++) // calc vibtable - vibrato amplitude
{
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); // copy from source to destination
Name[20] = 0;
moddata += 20; // begining of sample data
SampleCount = 32; // 32 samples (default was 16)
ChannelCount = 4; // 4 channels
Samples = (Sample *)(moddata - sizeof (Sample));
moddata += 15 * sizeof (Sample); // number of positions
sU32 &tag = *(sU32 *)(moddata + 130 + 16 * sizeof (Sample)); // get tag info (treat result as unsigned 32 then dereference)
switch (tag) // magic number / signature / i.d. / tag string
{
case '.K.M': // Michael Kleps (M.K.)
case '4TLF': // Startrekker 4 channel (fairlight) (FLT4)
case '!K!M': // more than 100 patterns (M!K!)
SampleCount = 32; // 32 samples if M.K. FLT4, M!K!
break;
}
if (SampleCount > 16)
moddata += (SampleCount - 16) * sizeof (Sample); // moddata=moddata+(Sampl.......
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)); // zap memory?
for (sInt i = 1; i < SampleCount; i++)
{
SData[i] = (sS8 *)moddata;
moddata += 2 * Samples[i].Length;
}
Reset ();
} // ModPlayer constructor end
// ************ RENDER output************
sU32 Render (sF32 *buf, sU32 len) // Render paramaters (pointer to buffer and length of buffer)
{
while (len)
{
sInt todo = sMin<sInt> (len, TRCounter); // sMin function using template
if (todo)
{
P->Render (buf, todo); // paula buffer and todo
buf += 2 * todo;
len -= todo;
TRCounter -= todo; // tick rate counter
}
else
{
Tick ();
TRCounter = TickRate;
}
}
return 1;
} // Render end
// ************ CALLBACK FUNCTION ************
static sU32 __stdcall RenderProxy (void *parm, sF32 *buf, sU32 len) // (modplayer is parm)
{
return ((ModPlayer *)parm)->Render (buf, len); // typecast void pointer (parm) into modplayer object and access 'Render' member.
} // (returns buffer to the audio out)
};
// ************ PERIOD TABLE ************
sInt ModPlayer::BasePTable[61] = // scope resolution (belongs to ModPlayer class)
{
0, // finetune = 0
1712, 1616, 1525, 1440, 1357, 1281, 1209, 1141, 1077, 1017, 961, 907, // C-0 to B-0 (octave 0)
856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453, // C-1 to B-1 (octave 1)
428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226, // C-2 to B-2 (octave 2)
214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113, // C-3 to B-3 (octave 3)
107, 101, 95, 90, 85, 80, 76, 71, 67, 64, 60, 57, // C-4 to B-4 (octave 4)
};
sInt ModPlayer::PTable[16][60];
sInt ModPlayer::VibTable[3][15][64];
+178
View File
@@ -0,0 +1,178 @@
// ========================= Paula Class (Paula Emulator) =======================
class Paula
{
public:
static const sInt FIR_WIDTH = 512; // Finite Impulse Response (FIR) filter width
sF32 FIRMem[2 * FIR_WIDTH + 1]; // FIR memory (1025), one dimensional array
struct Voice // Start Voice Structure
{
private:
sInt Pos; // position ?
sInt PWMCnt, DivCnt; // Pulse Width Modulation, pwm division count?
sIntFlt Cur; // current ?
public:
sS8 *Sample; // audio channel data (sample) location
sInt SampleLen; // audio channel data (sample) length
sInt LoopLen; // loop length
sInt Period; // 124 .. 65535 (audio channel period (rate))
sInt Volume; // 0 .. 64 AUDxVOL
Voice ()
: Period (65535), Volume (0), Sample (0), Pos (0), PWMCnt (0), DivCnt (0), LoopLen (1)
{
Cur.F32 = 0;
} // voice constructor ( initailization list - zero everything)
public:
void Render (sF32 *buffer, sInt samples) // define render function
{
if (!Sample) // return if no samples... i think
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; // smp[pos] XOR 0x80 << 15 OR 4000 0000
Cur.F32 -= 3.0f;
if (++Pos == SampleLen)
Pos -= LoopLen;
DivCnt = Period;
}
if (PWMCnt < Volume)
buffer[i] += Cur.F32; // PWM counter
PWMCnt = (PWMCnt + 1) & 0x3f; // 0x3f = 63
DivCnt--;
}
} // end render function
public:
void Trigger (sS8 *smp, sInt sl, sInt ll, sInt offs = 0) // define trigger function (trigger voice data)
{
Sample = smp; // sample
SampleLen = sl; // sample length
LoopLen = ll; // looplength
Pos = sMin (offs, SampleLen - 1); // offset
} // end trigger function
// }; // end voice structure
// Voice V[4];
} V[4]; // create array of instance of voice structure
// --
// rendering in paula freq
static const sInt RBSIZE = 4096; // ring buffer (aka circular buffer) size
sF32 RingBuf[2 * RBSIZE];
sInt WritePos; // write position
sInt ReadPos; // read position
sF32 ReadFrac; // fraction?
public:
void CalcFrag (sF32 *out, sInt samples) // i believe this function transfers
// samples into ring buffer
{
sZeroMem (out, sizeof (sF32) * samples); // zero-out mem
sZeroMem (out + RBSIZE, sizeof (sF32) * samples);
for (sInt i = 0; i < 4; i++) // four voices(0 - 3)
{
if (i == 1 || i == 2)
V[i].Render (out + RBSIZE, samples);
else
V[i].Render (out, samples);
}
}
// =================================== Calc
public:
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;
}; // Calc end
// =================== rendering in output freq P->Render
public:
sF32 MasterVolume; // master volume
sF32 MasterSeparation; // master stereo separation
void Render (sF32 *outbuf, sInt samples) // iutput buffer
{
const sF32 step = sF32 (PAULARATE) / sF32 (OUTRATE);// ratio paula/output rate step (3740000/48000 = 77.92)
const sF32 pan = 0.5f + 0.5f * MasterSeparation; // audio panning (50% each left/right) (0.5 + 0.5 * 0.5 = 0.75)
const sF32 vm0 = MasterVolume * sFSqrt (pan); // master volume 0
const sF32 vm1 = MasterVolume * sFSqrt (1 - pan); // master volume 1
for (sInt s = 0; s < samples; s++)
{
sInt ReadEnd = ReadPos + FIR_WIDTH + 1;
if (WritePos < ReadPos)
ReadEnd -= RBSIZE;
if (ReadEnd > WritePos)
Calc (); // call calc() - render in paula rate
sF32 outl0 = 0, outl1 = 0; // out left
sF32 outr0 = 0, outr1 = 0; // out right
sInt offs
= (ReadPos - FIR_WIDTH - 1) & (RBSIZE - 1); // offset [this needs optimization. SSE would
// come to mind. (streaming SMID extensions)]
sF32 vl = RingBuf[offs];
sF32 vr = RingBuf[offs + RBSIZE];
for (sInt i = 1; i < 2 * FIR_WIDTH - 1; i++)
{
sF32 w = FIRMem[i]; // w = FIRMem[i]
outl0 += vl * w; // outl0 = outl0 + (vl * w)
outr0 += vr * w; // outr0 = outr0 + (vl * 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); // output left
sF32 outr = sLerp (outr0, outr1, ReadFrac); // output right
*outbuf++ = vm0 * outl + vm1 * outr;
*outbuf++ = vm1 * outl + vm0 * outr;
ReadFrac += step;
sInt rfi = sInt (ReadFrac);
ReadPos = (ReadPos + rfi) & (RBSIZE - 1);
ReadFrac -= rfi;
}
} // Render end
// --
public:
Paula () // paula constructor
{
// make Finite Impulse Response (FIR) table (for low pass filter?)
sF32 *FIRTable = FIRMem + FIR_WIDTH; // FIR table size
sF32 yscale = sF32 (OUTRATE) / sF32 (PAULARATE); // Y scale
sF32 xscale = sFPi * yscale; // X scale
for (sInt i = -FIR_WIDTH; i <= FIR_WIDTH; i++) // windowed-sinc FIR filter (product of sinc & window function)
FIRTable[i]
= yscale * sFSinc (sF32 (i) * xscale)
* sFHamming (sF32 (i)
/ sF32 (FIR_WIDTH
- 1)); // Firtable = (yscale) * (sinc(i) * xscale) * hamming(i) / (fir_width-1)
sZeroMem (RingBuf, sizeof (RingBuf));
ReadPos = 0;
ReadFrac = 0;
WritePos = FIR_WIDTH; // reset ring buffer
MasterVolume = 0.66f; // master volume 66%
MasterSeparation = 0.5f; // stereo seperation 50:50
// FltBuf = 0;
} // Paula Constructor end
};
+1228
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
+247
View File
@@ -0,0 +1,247 @@
/*
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:
2024-03-09:
* modifications by Jason Bou-Samra
* changes to main() routine to make executable from Linux command line interface
* modularised paula and modplayer classes into seperate files
* added sound output using port audio
* sprinkled comments throughout source code, and general source formatting
* created makefile
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.
*/
// TO COMPILE: g++ -o tinymod `pkg-config --libs alsa` tinymod.cpp -lm -L . -l:libportaudio.a
// =================== system dependent stuff starts here =====================
// Multicharacter literal
#pragma GCC diagnostic ignored "-Wmultichar"
#pragma intrinsic(memset, sqrt, sin, cos, atan, powf) // memory set, square root, SINe, COSine, Arc TAngent, power of
// ### defines
#define __stdcall
#define __cdecl
#define NUM_SECONDS (1000)
#define SAMPLE_RATE (96000)
#define FRAMES_PER_BUFFER (0x10000)
#define cls() printf("\033[H\033[J") // control chars to clear screen
// ### includes
#include <inttypes.h> // fixed size integer types (part of c standard library)
#include <math.h> // basic mathematical operations (part of c standard library) - mostly floating point
#include <stdio.h> // standard I/O library
#include <string.h> // string handling (part of c standard library)
#include <unistd.h> // (unix standard)
#include <fcntl.h> // (file control options) - used by open()
#include <sys/stat.h> // needed by stat function (get file status)
#include <cstdint> // defines set of integeral types
#include <cstdio> // defines set of C standard I/O types
#include "portaudio.h" // port audio
#include "types.h" // various type definitions
#include "paula.h" // Amiga "Paula" sound hardware emulator
#include "modplayer.h" // MOD play routine
// ## declerations
void error(PaError err1); // declare port audio error handling function
// ***********************************************
// ** handle Command Line Interface (CLI) stuff **
// ***********************************************
int __cdecl main (int argc, const char **argv) // main start
{
// freopen("warning.log", "w", stderr); // surpress console messages
const char* filename = argv[1]; // only one argument and command
if (argc != 2){ // if less than 2 arguments passed on command line, display usage
printf("Usage: tinymod [<mod name>|OPTION]\n\
tinymod --help for help\n");
return 1;
} // display usage, then exit
if(!strcmp(filename, "--about")) {
printf("TinyMOD\n"); return 0;
} // display about, then exit
if(!strcmp (filename, "--help")) {
printf("Usage: tinymod [<mod name>|OPTION]\n\n\
An Amiga MOD file player that tries to replicate the authentic sound\n\
charateristics of an Amiga via software emulation of the Paula chip.\n\n\
OPTIONS\n\
--about displays about message\n\
--help displays this help message\n"); return 0;
} // display help, then exit
// **********************
// ** load MOD file **
// **********************
static sU8 mod[4 * 1024 * 1024]; // 4MB unsigned character array to hold MOD file
FILE* fh = fopen(filename, "rb");
if (!fh) {
perror("fopen");
exit(EXIT_FAILURE);
}
struct stat sb;
if (stat(filename, &sb) == -1) {
perror("stat");
exit(EXIT_FAILURE);
}
fread(mod, sb.st_size, 1, fh);
fclose(fh);
// **********************
// ** port audio setup **
// **********************
PaStreamParameters outputParameters;
PaStream *stream;
PaError err;
float buffer[FRAMES_PER_BUFFER][2]; // stereo output buffer
int left_phase = 0;
int right_phase = 0;
int left_inc = 1;
int right_inc = 1; // higher pitch so we can distinguish left and right.
int i, j, k; // for indexing
int bufferCount;
static const int BUFFERLEN = 0x10000; // buffer length
sInt nwrite = 0x10000; // number of samples
sF32 mixbuffer[BUFFERLEN]; // sample buffer
err = Pa_Initialize(); // initialize
if( err != paNoError ) error(err);
outputParameters.device = Pa_GetDefaultOutputDevice(); // default output device
if (outputParameters.device == paNoDevice) {
fprintf(stderr,"Error: No default output device.\n");
error(err);
}
outputParameters.channelCount = 2; // stereo output
outputParameters.sampleFormat = paFloat32; // 32 bit floating point output
outputParameters.suggestedLatency = 0.050; // Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;
outputParameters.hostApiSpecificStreamInfo = NULL;
err = Pa_OpenStream(
&stream,
NULL, // no input
&outputParameters,
SAMPLE_RATE,
FRAMES_PER_BUFFER,
paClipOff, // we won't output out of range samples so don't bother clipping them
NULL, // no callback, use blocking API
NULL ); // no callback, so no callback userData
if( err != paNoError ) error(err);
err = Pa_StartStream( stream );
if( err != paNoError ) error(err);
// *********************
// ** play setup/loop **
// *********************
Paula P; // instantiate paula
ModPlayer player (&P, mod); // instantiate paramaterised constructior ModPlayer
cls(); // clear screen
printf("TinyMOD\n");
printf ("currently playing: %s\n", player.Name); // display details of MOD being played
printf("Playing for %d seconds.\n", NUM_SECONDS );
printf("^C to stop\n");
// printf("PortAudio: SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER);
bufferCount = ((NUM_SECONDS * SAMPLE_RATE) / FRAMES_PER_BUFFER); // determine buffer loads
for( i=0; i < bufferCount; i++ ) // countdown buffers
{
player.RenderProxy(&player, mixbuffer, nwrite/2); // MOD player
for( j=0; j < FRAMES_PER_BUFFER; j++ ) // copy samples to buffer
{
buffer[j][0] = mixbuffer[left_phase]; // left
buffer[j][1] = mixbuffer[right_phase]; // right
left_phase += left_inc;
right_phase += right_inc;
if( left_phase >= FRAMES_PER_BUFFER ) left_phase -= FRAMES_PER_BUFFER;
if( right_phase >= FRAMES_PER_BUFFER ) right_phase -= FRAMES_PER_BUFFER;
}
// left_phase = right_phase = 0;
err = Pa_WriteStream( stream, buffer, FRAMES_PER_BUFFER ); // transfer buffer to stream
if( err != paNoError ) error(err);
} // keep going
// *************************
// ** port audio shutdown **
// *************************
err = Pa_StopStream( stream );
if( err != paNoError ) error(err);
// ++left_inc;
// ++right_inc;
Pa_Sleep( 1000 ); // mainly for test purposes so we can hear sound
err = Pa_CloseStream( stream );
if( err != paNoError ) error(err);
Pa_Terminate();
printf("Bye bye!.\n");
return err; // that's all folks, all done
}
// **********************
// ** hidden message **
// **********************
char author[] = { "tinymod" };
const char* text = "09/03/2023";
// *******************************
// ** error handling routine **
// *******************************
void error(PaError err1)
{
fprintf( stderr, "An error occured while using the portaudio stream\n" );
fprintf( stderr, "Error number: %d\n", err1 );
fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err1 ) );
// Print more information about the error.
if( err1 == paUnanticipatedHostError )
{
const PaHostErrorInfo *hostErrorInfo = Pa_GetLastHostErrorInfo();
fprintf( stderr, "Host API error = #%ld, hostApiType = %d\n", hostErrorInfo->errorCode, hostErrorInfo->hostApiType );
fprintf( stderr, "Host API error = %s\n", hostErrorInfo->errorText );
}
Pa_Terminate();
exit(1);
}
+78
View File
@@ -0,0 +1,78 @@
typedef int sInt; // signed Integer
typedef unsigned int sUInt; // unsigned Integer
typedef sInt sBool; // signed Bool
typedef char sChar; // signed Character
typedef signed char sS8; // 8 bit signed int
typedef signed short sS16; // 16 bit signed int
typedef signed long sS32; // 32 bit signed int
typedef int64_t sS64; // 64 bit signed int (long long) (signed)
typedef unsigned char sU8; // 8 bit unsigned int
typedef unsigned short sU16; // 16 bit unsigned int
typedef unsigned long sU32; // 32 bit unsigned int
typedef uint64_t sU64; // 64 bit unsigned int (long long) (unsigned)
typedef float sF32; // 32 bit signed float
typedef double sF64; // 64 bit signed float
// #define _CRT_SECURE_NO_DEPRECATE // disable C Runtime Library deprecation warnings
inline void sZeroMem(void *dest, sInt size)
{ memset(dest, 0, size);} // declare & define function to zero memory
inline sF32 sFSqrt(sF32 x)
{ return sqrtf(x); } // 32 bit signed float square root
inline sF32 sFSin(sF32 x)
{ return sinf(x); } // 32 bit signed float sine
inline sF32 sFCos(sF32 x)
{ return cosf(x); } // 32 bit signed float cosine
inline sF32 sFAtan(sF32 x)
{ return atanf(x); } // 32 bit signed float arc tangent
inline sF32 sFPow(sF32 b, sF32 e)
{ return powf(b, e); } // 32 bit signed float base to the power exponent
inline void sSwapEndian(sU16 &v)
{ v = ((v & 0xff) << 8) | (v >> 8); } // endian swap
const sF32 sFPi = 4 * sFAtan(1); // signed floating Pi
// =================== system dependent stuff ends here ========================
template <typename T> inline T sMin(const T a, const T b)
{ return (a < b) ? a : b; } // min function, return smallest
template <typename T> inline T sMax(const T a, const T b)
{ return (a > b) ? a : b; } // max function, return largest
template <typename T> inline T sClamp(const T x, const T min, const T max)
{ return sMax(min, sMin(max, x)); } // variable clamp
template <typename T> T sSqr(T v)
{ return v * v; } // square root
template <typename T> T sLerp(T a, T b, sF32 f)
{ return a + f * (b - a); } // linear interpolation
template <typename T> T sAbs(T x)
{ return abs(x); } // absolute value
inline sF32 sFSinc(sF32 x)
{ return x ? sFSin(x) / x : 1; } // low pass filter: (sinc function) sin(x)/x or 1
inline sF32 sFHamming(sF32 x)
{ return (x > -1 && x < 1) ? sSqr(sFCos(x * sFPi / 2)) : 0; } // hamming window: cos(X * PI / 2)^2 or 0
union sIntFlt {
sU32 U32;
sF32 F32;
}; // union integer/float
const sInt PAULARATE = 3740000; // approx. pal timing amiga paula rate (3.546895MHz DAC base clock)
const sInt OUTRATE = 48000; // approx. pal timing output rate (48Khz)
const sInt OUTFPS = 50; // approx. pal timing frames per second (50Hz - PAL)