commit 3f0c6d4114b0724f904a9b6ddbd88f37e17ee579 Author: Jason Bou-Samra <154414066+bou-samra@users.noreply.github.com> Date: Mon Mar 11 19:26:51 2024 +1100 Initial commit bulk data dump diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5d46159 --- /dev/null +++ b/Makefile @@ -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 diff --git a/cream_of_the_earth.mod b/cream_of_the_earth.mod new file mode 100644 index 0000000..da53924 Binary files /dev/null and b/cream_of_the_earth.mod differ diff --git a/klisje.mod b/klisje.mod new file mode 100644 index 0000000..92c17e6 Binary files /dev/null and b/klisje.mod differ diff --git a/libportaudio.a b/libportaudio.a new file mode 100644 index 0000000..c264ae6 Binary files /dev/null and b/libportaudio.a differ diff --git a/modplayer.h b/modplayer.h new file mode 100644 index 0000000..d50ec9e --- /dev/null +++ b/modplayer.h @@ -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 (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]; diff --git a/paula.h b/paula.h new file mode 100644 index 0000000..b526e07 --- /dev/null +++ b/paula.h @@ -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 +}; diff --git a/portaudio.h b/portaudio.h new file mode 100644 index 0000000..5d84731 --- /dev/null +++ b/portaudio.h @@ -0,0 +1,1228 @@ +#ifndef PORTAUDIO_H +#define PORTAUDIO_H +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * PortAudio API Header File + * Latest version available at: http://www.portaudio.com/ + * + * Copyright (c) 1999-2002 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** @file + @ingroup public_header + @brief The portable PortAudio API. +*/ + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/** Retrieve the release number of the currently running PortAudio build. + For example, for version "19.5.1" this will return 0x00130501. + + @see paMakeVersionNumber +*/ +int Pa_GetVersion( void ); + +/** Retrieve a textual description of the current PortAudio build, + e.g. "PortAudio V19.5.0-devel, revision 1952M". + The format of the text may change in the future. Do not try to parse the + returned string. + + @deprecated As of 19.5.0, use Pa_GetVersionInfo()->versionText instead. +*/ +const char* Pa_GetVersionText( void ); + +/** + Generate a packed integer version number in the same format used + by Pa_GetVersion(). Use this to compare a specified version number with + the currently running version. For example: + + @code + if( Pa_GetVersion() < paMakeVersionNumber(19,5,1) ) {} + @endcode + + @see Pa_GetVersion, Pa_GetVersionInfo + @version Available as of 19.5.0. +*/ +#define paMakeVersionNumber(major, minor, subminor) \ + (((major)&0xFF)<<16 | ((minor)&0xFF)<<8 | ((subminor)&0xFF)) + + +/** + A structure containing PortAudio API version information. + @see Pa_GetVersionInfo, paMakeVersionNumber + @version Available as of 19.5.0. +*/ +typedef struct PaVersionInfo { + int versionMajor; + int versionMinor; + int versionSubMinor; + /** + This is currently the Git revision hash but may change in the future. + The versionControlRevision is updated by running a script before compiling the library. + If the update does not occur, this value may refer to an earlier revision. + */ + const char *versionControlRevision; + /** Version as a string, for example "PortAudio V19.5.0-devel, revision 1952M" */ + const char *versionText; +} PaVersionInfo; + +/** Retrieve version information for the currently running PortAudio build. + @return A pointer to an immutable PaVersionInfo structure. + + @note This function can be called at any time. It does not require PortAudio + to be initialized. The structure pointed to is statically allocated. Do not + attempt to free it or modify it. + + @see PaVersionInfo, paMakeVersionNumber + @version Available as of 19.5.0. +*/ +const PaVersionInfo* Pa_GetVersionInfo( void ); + + +/** Error codes returned by PortAudio functions. + Note that with the exception of paNoError, all PaErrorCodes are negative. +*/ + +typedef int PaError; +typedef enum PaErrorCode +{ + paNoError = 0, + + paNotInitialized = -10000, + paUnanticipatedHostError, + paInvalidChannelCount, + paInvalidSampleRate, + paInvalidDevice, + paInvalidFlag, + paSampleFormatNotSupported, + paBadIODeviceCombination, + paInsufficientMemory, + paBufferTooBig, + paBufferTooSmall, + paNullCallback, + paBadStreamPtr, + paTimedOut, + paInternalError, + paDeviceUnavailable, + paIncompatibleHostApiSpecificStreamInfo, + paStreamIsStopped, + paStreamIsNotStopped, + paInputOverflowed, + paOutputUnderflowed, + paHostApiNotFound, + paInvalidHostApi, + paCanNotReadFromACallbackStream, + paCanNotWriteToACallbackStream, + paCanNotReadFromAnOutputOnlyStream, + paCanNotWriteToAnInputOnlyStream, + paIncompatibleStreamHostApi, + paBadBufferPtr +} PaErrorCode; + + +/** Translate the supplied PortAudio error code into a human readable + message. +*/ +const char *Pa_GetErrorText( PaError errorCode ); + + +/** Library initialization function - call this before using PortAudio. + This function initializes internal data structures and prepares underlying + host APIs for use. With the exception of Pa_GetVersion(), Pa_GetVersionText(), + and Pa_GetErrorText(), this function MUST be called before using any other + PortAudio API functions. + + If Pa_Initialize() is called multiple times, each successful + call must be matched with a corresponding call to Pa_Terminate(). + Pairs of calls to Pa_Initialize()/Pa_Terminate() may overlap, and are not + required to be fully nested. + + Note that if Pa_Initialize() returns an error code, Pa_Terminate() should + NOT be called. + + @return paNoError if successful, otherwise an error code indicating the cause + of failure. + + @see Pa_Terminate +*/ +PaError Pa_Initialize( void ); + + +/** Library termination function - call this when finished using PortAudio. + This function deallocates all resources allocated by PortAudio since it was + initialized by a call to Pa_Initialize(). In cases where Pa_Initialise() has + been called multiple times, each call must be matched with a corresponding call + to Pa_Terminate(). The final matching call to Pa_Terminate() will automatically + close any PortAudio streams that are still open. + + Pa_Terminate() MUST be called before exiting a program which uses PortAudio. + Failure to do so may result in serious resource leaks, such as audio devices + not being available until the next reboot. + + @return paNoError if successful, otherwise an error code indicating the cause + of failure. + + @see Pa_Initialize +*/ +PaError Pa_Terminate( void ); + + + +/** The type used to refer to audio devices. Values of this type usually + range from 0 to (Pa_GetDeviceCount()-1), and may also take on the PaNoDevice + and paUseHostApiSpecificDeviceSpecification values. + + @see Pa_GetDeviceCount, paNoDevice, paUseHostApiSpecificDeviceSpecification +*/ +typedef int PaDeviceIndex; + + +/** A special PaDeviceIndex value indicating that no device is available, + or should be used. + + @see PaDeviceIndex +*/ +#define paNoDevice ((PaDeviceIndex)-1) + + +/** A special PaDeviceIndex value indicating that the device(s) to be used + are specified in the host api specific stream info structure. + + @see PaDeviceIndex +*/ +#define paUseHostApiSpecificDeviceSpecification ((PaDeviceIndex)-2) + + +/* Host API enumeration mechanism */ + +/** The type used to enumerate to host APIs at runtime. Values of this type + range from 0 to (Pa_GetHostApiCount()-1). + + @see Pa_GetHostApiCount +*/ +typedef int PaHostApiIndex; + + +/** Retrieve the number of available host APIs. Even if a host API is + available it may have no devices available. + + @return A non-negative value indicating the number of available host APIs + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + @see PaHostApiIndex +*/ +PaHostApiIndex Pa_GetHostApiCount( void ); + + +/** Retrieve the index of the default host API. The default host API will be + the lowest common denominator host API on the current platform and is + unlikely to provide the best performance. + + @return A non-negative value ranging from 0 to (Pa_GetHostApiCount()-1) + indicating the default host API index or, a PaErrorCode (which are always + negative) if PortAudio is not initialized or an error is encountered. +*/ +PaHostApiIndex Pa_GetDefaultHostApi( void ); + + +/** Unchanging unique identifiers for each supported host API. This type + is used in the PaHostApiInfo structure. The values are guaranteed to be + unique and to never change, thus allowing code to be written that + conditionally uses host API specific extensions. + + New type ids will be allocated when support for a host API reaches + "public alpha" status, prior to that developers should use the + paInDevelopment type id. + + @see PaHostApiInfo +*/ +typedef enum PaHostApiTypeId +{ + paInDevelopment=0, /* use while developing support for a new host API */ + paDirectSound=1, + paMME=2, + paASIO=3, + paSoundManager=4, + paCoreAudio=5, + paOSS=7, + paALSA=8, + paAL=9, + paBeOS=10, + paWDMKS=11, + paJACK=12, + paWASAPI=13, + paAudioScienceHPI=14 +} PaHostApiTypeId; + + +/** A structure containing information about a particular host API. */ + +typedef struct PaHostApiInfo +{ + /** this is struct version 1 */ + int structVersion; + /** The well known unique identifier of this host API @see PaHostApiTypeId */ + PaHostApiTypeId type; + /** A textual description of the host API for display on user interfaces. */ + const char *name; + + /** The number of devices belonging to this host API. This field may be + used in conjunction with Pa_HostApiDeviceIndexToDeviceIndex() to enumerate + all devices for this host API. + @see Pa_HostApiDeviceIndexToDeviceIndex + */ + int deviceCount; + + /** The default input device for this host API. The value will be a + device index ranging from 0 to (Pa_GetDeviceCount()-1), or paNoDevice + if no default input device is available. + */ + PaDeviceIndex defaultInputDevice; + + /** The default output device for this host API. The value will be a + device index ranging from 0 to (Pa_GetDeviceCount()-1), or paNoDevice + if no default output device is available. + */ + PaDeviceIndex defaultOutputDevice; + +} PaHostApiInfo; + + +/** Retrieve a pointer to a structure containing information about a specific + host Api. + + @param hostApi A valid host API index ranging from 0 to (Pa_GetHostApiCount()-1) + + @return A pointer to an immutable PaHostApiInfo structure describing + a specific host API. If the hostApi parameter is out of range or an error + is encountered, the function returns NULL. + + The returned structure is owned by the PortAudio implementation and must not + be manipulated or freed. The pointer is only guaranteed to be valid between + calls to Pa_Initialize() and Pa_Terminate(). +*/ +const PaHostApiInfo * Pa_GetHostApiInfo( PaHostApiIndex hostApi ); + + +/** Convert a static host API unique identifier, into a runtime + host API index. + + @param type A unique host API identifier belonging to the PaHostApiTypeId + enumeration. + + @return A valid PaHostApiIndex ranging from 0 to (Pa_GetHostApiCount()-1) or, + a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + The paHostApiNotFound error code indicates that the host API specified by the + type parameter is not available. + + @see PaHostApiTypeId +*/ +PaHostApiIndex Pa_HostApiTypeIdToHostApiIndex( PaHostApiTypeId type ); + + +/** Convert a host-API-specific device index to standard PortAudio device index. + This function may be used in conjunction with the deviceCount field of + PaHostApiInfo to enumerate all devices for the specified host API. + + @param hostApi A valid host API index ranging from 0 to (Pa_GetHostApiCount()-1) + + @param hostApiDeviceIndex A valid per-host device index in the range + 0 to (Pa_GetHostApiInfo(hostApi)->deviceCount-1) + + @return A non-negative PaDeviceIndex ranging from 0 to (Pa_GetDeviceCount()-1) + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + A paInvalidHostApi error code indicates that the host API index specified by + the hostApi parameter is out of range. + + A paInvalidDevice error code indicates that the hostApiDeviceIndex parameter + is out of range. + + @see PaHostApiInfo +*/ +PaDeviceIndex Pa_HostApiDeviceIndexToDeviceIndex( PaHostApiIndex hostApi, + int hostApiDeviceIndex ); + + + +/** Structure used to return information about a host error condition. +*/ +typedef struct PaHostErrorInfo{ + PaHostApiTypeId hostApiType; /**< the host API which returned the error code */ + long errorCode; /**< the error code returned */ + const char *errorText; /**< a textual description of the error if available, otherwise a zero-length string */ +}PaHostErrorInfo; + + +/** Return information about the last host error encountered. The error + information returned by Pa_GetLastHostErrorInfo() will never be modified + asynchronously by errors occurring in other PortAudio owned threads + (such as the thread that manages the stream callback.) + + This function is provided as a last resort, primarily to enhance debugging + by providing clients with access to all available error information. + + @return A pointer to an immutable structure constraining information about + the host error. The values in this structure will only be valid if a + PortAudio function has previously returned the paUnanticipatedHostError + error code. +*/ +const PaHostErrorInfo* Pa_GetLastHostErrorInfo( void ); + + + +/* Device enumeration and capabilities */ + +/** Retrieve the number of available devices. The number of available devices + may be zero. + + @return A non-negative value indicating the number of available devices or, + a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. +*/ +PaDeviceIndex Pa_GetDeviceCount( void ); + + +/** Retrieve the index of the default input device. The result can be + used in the inputDevice parameter to Pa_OpenStream(). + + @return The default input device index for the default host API, or paNoDevice + if no default input device is available or an error was encountered. +*/ +PaDeviceIndex Pa_GetDefaultInputDevice( void ); + + +/** Retrieve the index of the default output device. The result can be + used in the outputDevice parameter to Pa_OpenStream(). + + @return The default output device index for the default host API, or paNoDevice + if no default output device is available or an error was encountered. + + @note + On the PC, the user can specify a default device by + setting an environment variable. For example, to use device #1. +
+ set PA_RECOMMENDED_OUTPUT_DEVICE=1
+
+ The user should first determine the available device ids by using + the supplied application "pa_devs". +*/ +PaDeviceIndex Pa_GetDefaultOutputDevice( void ); + + +/** The type used to represent monotonic time in seconds. PaTime is + used for the fields of the PaStreamCallbackTimeInfo argument to the + PaStreamCallback and as the result of Pa_GetStreamTime(). + + PaTime values have unspecified origin. + + @see PaStreamCallback, PaStreamCallbackTimeInfo, Pa_GetStreamTime +*/ +typedef double PaTime; + + +/** A type used to specify one or more sample formats. Each value indicates + a possible format for sound data passed to and from the stream callback, + Pa_ReadStream and Pa_WriteStream. + + The standard formats paFloat32, paInt16, paInt32, paInt24, paInt8 + and aUInt8 are usually implemented by all implementations. + + The floating point representation (paFloat32) uses +1.0 and -1.0 as the + maximum and minimum respectively. + + paUInt8 is an unsigned 8 bit format where 128 is considered "ground" + + The paNonInterleaved flag indicates that audio data is passed as an array + of pointers to separate buffers, one buffer for each channel. Usually, + when this flag is not used, audio data is passed as a single buffer with + all channels interleaved. + + @see Pa_OpenStream, Pa_OpenDefaultStream, PaDeviceInfo + @see paFloat32, paInt16, paInt32, paInt24, paInt8 + @see paUInt8, paCustomFormat, paNonInterleaved +*/ +typedef unsigned long PaSampleFormat; + + +#define paFloat32 ((PaSampleFormat) 0x00000001) /**< @see PaSampleFormat */ +#define paInt32 ((PaSampleFormat) 0x00000002) /**< @see PaSampleFormat */ +#define paInt24 ((PaSampleFormat) 0x00000004) /**< Packed 24 bit format. @see PaSampleFormat */ +#define paInt16 ((PaSampleFormat) 0x00000008) /**< @see PaSampleFormat */ +#define paInt8 ((PaSampleFormat) 0x00000010) /**< @see PaSampleFormat */ +#define paUInt8 ((PaSampleFormat) 0x00000020) /**< @see PaSampleFormat */ +#define paCustomFormat ((PaSampleFormat) 0x00010000) /**< @see PaSampleFormat */ + +#define paNonInterleaved ((PaSampleFormat) 0x80000000) /**< @see PaSampleFormat */ + +/** A structure providing information and capabilities of PortAudio devices. + Devices may support input, output or both input and output. +*/ +typedef struct PaDeviceInfo +{ + int structVersion; /* this is struct version 2 */ + const char *name; + PaHostApiIndex hostApi; /**< note this is a host API index, not a type id*/ + + int maxInputChannels; + int maxOutputChannels; + + /** Default latency values for interactive performance. */ + PaTime defaultLowInputLatency; + PaTime defaultLowOutputLatency; + /** Default latency values for robust non-interactive applications (eg. playing sound files). */ + PaTime defaultHighInputLatency; + PaTime defaultHighOutputLatency; + + double defaultSampleRate; +} PaDeviceInfo; + + +/** Retrieve a pointer to a PaDeviceInfo structure containing information + about the specified device. + @return A pointer to an immutable PaDeviceInfo structure. If the device + parameter is out of range the function returns NULL. + + @param device A valid device index in the range 0 to (Pa_GetDeviceCount()-1) + + @note PortAudio manages the memory referenced by the returned pointer, + the client must not manipulate or free the memory. The pointer is only + guaranteed to be valid between calls to Pa_Initialize() and Pa_Terminate(). + + @see PaDeviceInfo, PaDeviceIndex +*/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceIndex device ); + + +/** Parameters for one direction (input or output) of a stream. +*/ +typedef struct PaStreamParameters +{ + /** A valid device index in the range 0 to (Pa_GetDeviceCount()-1) + specifying the device to be used or the special constant + paUseHostApiSpecificDeviceSpecification which indicates that the actual + device(s) to use are specified in hostApiSpecificStreamInfo. + This field must not be set to paNoDevice. + */ + PaDeviceIndex device; + + /** The number of channels of sound to be delivered to the + stream callback or accessed by Pa_ReadStream() or Pa_WriteStream(). + It can range from 1 to the value of maxInputChannels in the + PaDeviceInfo record for the device specified by the device parameter. + */ + int channelCount; + + /** The sample format of the buffer provided to the stream callback, + a_ReadStream() or Pa_WriteStream(). It may be any of the formats described + by the PaSampleFormat enumeration. + */ + PaSampleFormat sampleFormat; + + /** The desired latency in seconds. Where practical, implementations should + configure their latency based on these parameters, otherwise they may + choose the closest viable latency instead. Unless the suggested latency + is greater than the absolute upper limit for the device implementations + should round the suggestedLatency up to the next practical value - ie to + provide an equal or higher latency than suggestedLatency wherever possible. + Actual latency values for an open stream may be retrieved using the + inputLatency and outputLatency fields of the PaStreamInfo structure + returned by Pa_GetStreamInfo(). + @see default*Latency in PaDeviceInfo, *Latency in PaStreamInfo + */ + PaTime suggestedLatency; + + /** An optional pointer to a host api specific data structure + containing additional information for device setup and/or stream processing. + hostApiSpecificStreamInfo is never required for correct operation, + if not used it should be set to NULL. + */ + void *hostApiSpecificStreamInfo; + +} PaStreamParameters; + + +/** Return code for Pa_IsFormatSupported indicating success. */ +#define paFormatIsSupported (0) + +/** Determine whether it would be possible to open a stream with the specified + parameters. + + @param inputParameters A structure that describes the input parameters used to + open a stream. The suggestedLatency field is ignored. See PaStreamParameters + for a description of these parameters. inputParameters must be NULL for + output-only streams. + + @param outputParameters A structure that describes the output parameters used + to open a stream. The suggestedLatency field is ignored. See PaStreamParameters + for a description of these parameters. outputParameters must be NULL for + input-only streams. + + @param sampleRate The required sampleRate. For full-duplex streams it is the + sample rate for both input and output + + @return Returns 0 if the format is supported, and an error code indicating why + the format is not supported otherwise. The constant paFormatIsSupported is + provided to compare with the return value for success. + + @see paFormatIsSupported, PaStreamParameters +*/ +PaError Pa_IsFormatSupported( const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); + + + +/* Streaming types and functions */ + + +/** + A single PaStream can provide multiple channels of real-time + streaming audio input and output to a client application. A stream + provides access to audio hardware represented by one or more + PaDevices. Depending on the underlying Host API, it may be possible + to open multiple streams using the same device, however this behavior + is implementation defined. Portable applications should assume that + a PaDevice may be simultaneously used by at most one PaStream. + + Pointers to PaStream objects are passed between PortAudio functions that + operate on streams. + + @see Pa_OpenStream, Pa_OpenDefaultStream, Pa_OpenDefaultStream, Pa_CloseStream, + Pa_StartStream, Pa_StopStream, Pa_AbortStream, Pa_IsStreamActive, + Pa_GetStreamTime, Pa_GetStreamCpuLoad + +*/ +typedef void PaStream; + + +/** Can be passed as the framesPerBuffer parameter to Pa_OpenStream() + or Pa_OpenDefaultStream() to indicate that the stream callback will + accept buffers of any size. +*/ +#define paFramesPerBufferUnspecified (0) + + +/** Flags used to control the behavior of a stream. They are passed as + parameters to Pa_OpenStream or Pa_OpenDefaultStream. Multiple flags may be + ORed together. + + @see Pa_OpenStream, Pa_OpenDefaultStream + @see paNoFlag, paClipOff, paDitherOff, paNeverDropInput, + paPrimeOutputBuffersUsingStreamCallback, paPlatformSpecificFlags +*/ +typedef unsigned long PaStreamFlags; + +/** @see PaStreamFlags */ +#define paNoFlag ((PaStreamFlags) 0) + +/** Disable default clipping of out of range samples. + @see PaStreamFlags +*/ +#define paClipOff ((PaStreamFlags) 0x00000001) + +/** Disable default dithering. + @see PaStreamFlags +*/ +#define paDitherOff ((PaStreamFlags) 0x00000002) + +/** Flag requests that where possible a full duplex stream will not discard + overflowed input samples without calling the stream callback. This flag is + only valid for full duplex callback streams and only when used in combination + with the paFramesPerBufferUnspecified (0) framesPerBuffer parameter. Using + this flag incorrectly results in a paInvalidFlag error being returned from + Pa_OpenStream and Pa_OpenDefaultStream. + + @see PaStreamFlags, paFramesPerBufferUnspecified +*/ +#define paNeverDropInput ((PaStreamFlags) 0x00000004) + +/** Call the stream callback to fill initial output buffers, rather than the + default behavior of priming the buffers with zeros (silence). This flag has + no effect for input-only and blocking read/write streams. + + @see PaStreamFlags +*/ +#define paPrimeOutputBuffersUsingStreamCallback ((PaStreamFlags) 0x00000008) + +/** A mask specifying the platform specific bits. + @see PaStreamFlags +*/ +#define paPlatformSpecificFlags ((PaStreamFlags)0xFFFF0000) + +/** + Timing information for the buffers passed to the stream callback. + + Time values are expressed in seconds and are synchronised with the time base used by Pa_GetStreamTime() for the associated stream. + + @see PaStreamCallback, Pa_GetStreamTime +*/ +typedef struct PaStreamCallbackTimeInfo{ + PaTime inputBufferAdcTime; /**< The time when the first sample of the input buffer was captured at the ADC input */ + PaTime currentTime; /**< The time when the stream callback was invoked */ + PaTime outputBufferDacTime; /**< The time when the first sample of the output buffer will output the DAC */ +} PaStreamCallbackTimeInfo; + + +/** + Flag bit constants for the statusFlags to PaStreamCallback. + + @see paInputUnderflow, paInputOverflow, paOutputUnderflow, paOutputOverflow, + paPrimingOutput +*/ +typedef unsigned long PaStreamCallbackFlags; + +/** In a stream opened with paFramesPerBufferUnspecified, indicates that + input data is all silence (zeros) because no real data is available. In a + stream opened without paFramesPerBufferUnspecified, it indicates that one or + more zero samples have been inserted into the input buffer to compensate + for an input underflow. + @see PaStreamCallbackFlags +*/ +#define paInputUnderflow ((PaStreamCallbackFlags) 0x00000001) + +/** In a stream opened with paFramesPerBufferUnspecified, indicates that data + prior to the first sample of the input buffer was discarded due to an + overflow, possibly because the stream callback is using too much CPU time. + Otherwise indicates that data prior to one or more samples in the + input buffer was discarded. + @see PaStreamCallbackFlags +*/ +#define paInputOverflow ((PaStreamCallbackFlags) 0x00000002) + +/** Indicates that output data (or a gap) was inserted, possibly because the + stream callback is using too much CPU time. + @see PaStreamCallbackFlags +*/ +#define paOutputUnderflow ((PaStreamCallbackFlags) 0x00000004) + +/** Indicates that output data will be discarded because no room is available. + @see PaStreamCallbackFlags +*/ +#define paOutputOverflow ((PaStreamCallbackFlags) 0x00000008) + +/** Some of all of the output data will be used to prime the stream, input + data may be zero. + @see PaStreamCallbackFlags +*/ +#define paPrimingOutput ((PaStreamCallbackFlags) 0x00000010) + +/** + Allowable return values for the PaStreamCallback. + @see PaStreamCallback +*/ +typedef enum PaStreamCallbackResult +{ + paContinue=0, /**< Signal that the stream should continue invoking the callback and processing audio. */ + paComplete=1, /**< Signal that the stream should stop invoking the callback and finish once all output samples have played. */ + paAbort=2 /**< Signal that the stream should stop invoking the callback and finish as soon as possible. */ +} PaStreamCallbackResult; + + +/** + Functions of type PaStreamCallback are implemented by PortAudio clients. + They consume, process or generate audio in response to requests from an + active PortAudio stream. + + When a stream is running, PortAudio calls the stream callback periodically. + The callback function is responsible for processing buffers of audio samples + passed via the input and output parameters. + + The PortAudio stream callback runs at very high or real-time priority. + It is required to consistently meet its time deadlines. Do not allocate + memory, access the file system, call library functions or call other functions + from the stream callback that may block or take an unpredictable amount of + time to complete. + + In order for a stream to maintain glitch-free operation the callback + must consume and return audio data faster than it is recorded and/or + played. PortAudio anticipates that each callback invocation may execute for + a duration approaching the duration of frameCount audio frames at the stream + sample rate. It is reasonable to expect to be able to utilise 70% or more of + the available CPU time in the PortAudio callback. However, due to buffer size + adaption and other factors, not all host APIs are able to guarantee audio + stability under heavy CPU load with arbitrary fixed callback buffer sizes. + When high callback CPU utilisation is required the most robust behavior + can be achieved by using paFramesPerBufferUnspecified as the + Pa_OpenStream() framesPerBuffer parameter. + + @param input and @param output are either arrays of interleaved samples or; + if non-interleaved samples were requested using the paNonInterleaved sample + format flag, an array of buffer pointers, one non-interleaved buffer for + each channel. + + The format, packing and number of channels used by the buffers are + determined by parameters to Pa_OpenStream(). + + @param frameCount The number of sample frames to be processed by + the stream callback. + + @param timeInfo Timestamps indicating the ADC capture time of the first sample + in the input buffer, the DAC output time of the first sample in the output buffer + and the time the callback was invoked. + See PaStreamCallbackTimeInfo and Pa_GetStreamTime() + + @param statusFlags Flags indicating whether input and/or output buffers + have been inserted or will be dropped to overcome underflow or overflow + conditions. + + @param userData The value of a user supplied pointer passed to + Pa_OpenStream() intended for storing synthesis data etc. + + @return + The stream callback should return one of the values in the + ::PaStreamCallbackResult enumeration. To ensure that the callback continues + to be called, it should return paContinue (0). Either paComplete or paAbort + can be returned to finish stream processing, after either of these values is + returned the callback will not be called again. If paAbort is returned the + stream will finish as soon as possible. If paComplete is returned, the stream + will continue until all buffers generated by the callback have been played. + This may be useful in applications such as soundfile players where a specific + duration of output is required. However, it is not necessary to utilize this + mechanism as Pa_StopStream(), Pa_AbortStream() or Pa_CloseStream() can also + be used to stop the stream. The callback must always fill the entire output + buffer irrespective of its return value. + + @see Pa_OpenStream, Pa_OpenDefaultStream + + @note With the exception of Pa_GetStreamCpuLoad() it is not permissible to call + PortAudio API functions from within the stream callback. +*/ +typedef int PaStreamCallback( + const void *input, void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); + + +/** Opens a stream for either input, output or both. + + @param stream The address of a PaStream pointer which will receive + a pointer to the newly opened stream. + + @param inputParameters A structure that describes the input parameters used by + the opened stream. See PaStreamParameters for a description of these parameters. + inputParameters must be NULL for output-only streams. + + @param outputParameters A structure that describes the output parameters used by + the opened stream. See PaStreamParameters for a description of these parameters. + outputParameters must be NULL for input-only streams. + + @param sampleRate The desired sampleRate. For full-duplex streams it is the + sample rate for both input and output. Note that the actual sampleRate + may differ very slightly from the desired rate because of hardware limitations. + The exact rate can be queried using Pa_GetStreamInfo(). If nothing close + to the desired sampleRate is available then the open will fail and return an error. + + @param framesPerBuffer The number of frames passed to the stream callback + function, or the preferred block granularity for a blocking read/write stream. + The special value paFramesPerBufferUnspecified (0) may be used to request that + the stream callback will receive an optimal (and possibly varying) number of + frames based on host requirements and the requested latency settings. + Note: With some host APIs, the use of non-zero framesPerBuffer for a callback + stream may introduce an additional layer of buffering which could introduce + additional latency. PortAudio guarantees that the additional latency + will be kept to the theoretical minimum however, it is strongly recommended + that a non-zero framesPerBuffer value only be used when your algorithm + requires a fixed number of frames per stream callback. + + @param streamFlags Flags which modify the behavior of the streaming process. + This parameter may contain a combination of flags ORed together. Some flags may + only be relevant to certain buffer formats. + + @param streamCallback A pointer to a client supplied function that is responsible + for processing and filling input and output buffers. If this parameter is NULL + the stream will be opened in 'blocking read/write' mode. In blocking mode, + the client can receive sample data using Pa_ReadStream and write sample data + using Pa_WriteStream, the number of samples that may be read or written + without blocking is returned by Pa_GetStreamReadAvailable and + Pa_GetStreamWriteAvailable respectively. + + @param userData A client supplied pointer which is passed to the stream callback + function. It could for example, contain a pointer to instance data necessary + for processing the audio buffers. This parameter is ignored if streamCallback + is NULL. + + @return + Upon success Pa_OpenStream() returns paNoError and places a pointer to a + valid PaStream in the stream argument. The stream is inactive (stopped). + If a call to Pa_OpenStream() fails, a non-zero error code is returned (see + PaError for possible error codes) and the value of stream is invalid. + + @see PaStreamParameters, PaStreamCallback, Pa_ReadStream, Pa_WriteStream, + Pa_GetStreamReadAvailable, Pa_GetStreamWriteAvailable +*/ +PaError Pa_OpenStream( PaStream** stream, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); + + +/** A simplified version of Pa_OpenStream() that opens the default input + and/or output devices. + + @param stream The address of a PaStream pointer which will receive + a pointer to the newly opened stream. + + @param numInputChannels The number of channels of sound that will be supplied + to the stream callback or returned by Pa_ReadStream. It can range from 1 to + the value of maxInputChannels in the PaDeviceInfo record for the default input + device. If 0 the stream is opened as an output-only stream. + + @param numOutputChannels The number of channels of sound to be delivered to the + stream callback or passed to Pa_WriteStream. It can range from 1 to the value + of maxOutputChannels in the PaDeviceInfo record for the default output device. + If 0 the stream is opened as an output-only stream. + + @param sampleFormat The sample format of both the input and output buffers + provided to the callback or passed to and from Pa_ReadStream and Pa_WriteStream. + sampleFormat may be any of the formats described by the PaSampleFormat + enumeration. + + @param sampleRate Same as Pa_OpenStream parameter of the same name. + @param framesPerBuffer Same as Pa_OpenStream parameter of the same name. + @param streamCallback Same as Pa_OpenStream parameter of the same name. + @param userData Same as Pa_OpenStream parameter of the same name. + + @return As for Pa_OpenStream + + @see Pa_OpenStream, PaStreamCallback +*/ +PaError Pa_OpenDefaultStream( PaStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamCallback *streamCallback, + void *userData ); + + +/** Closes an audio stream. If the audio stream is active it + discards any pending buffers as if Pa_AbortStream() had been called. +*/ +PaError Pa_CloseStream( PaStream *stream ); + + +/** Functions of type PaStreamFinishedCallback are implemented by PortAudio + clients. They can be registered with a stream using the Pa_SetStreamFinishedCallback + function. Once registered they are called when the stream becomes inactive + (ie once a call to Pa_StopStream() will not block). + A stream will become inactive after the stream callback returns non-zero, + or when Pa_StopStream or Pa_AbortStream is called. For a stream providing audio + output, if the stream callback returns paComplete, or Pa_StopStream() is called, + the stream finished callback will not be called until all generated sample data + has been played. + + @param userData The userData parameter supplied to Pa_OpenStream() + + @see Pa_SetStreamFinishedCallback +*/ +typedef void PaStreamFinishedCallback( void *userData ); + + +/** Register a stream finished callback function which will be called when the + stream becomes inactive. See the description of PaStreamFinishedCallback for + further details about when the callback will be called. + + @param stream a pointer to a PaStream that is in the stopped state - if the + stream is not stopped, the stream's finished callback will remain unchanged + and an error code will be returned. + + @param streamFinishedCallback a pointer to a function with the same signature + as PaStreamFinishedCallback, that will be called when the stream becomes + inactive. Passing NULL for this parameter will un-register a previously + registered stream finished callback function. + + @return on success returns paNoError, otherwise an error code indicating the cause + of the error. + + @see PaStreamFinishedCallback +*/ +PaError Pa_SetStreamFinishedCallback( PaStream *stream, PaStreamFinishedCallback* streamFinishedCallback ); + + +/** Commences audio processing. +*/ +PaError Pa_StartStream( PaStream *stream ); + + +/** Terminates audio processing. It waits until all pending + audio buffers have been played before it returns. +*/ +PaError Pa_StopStream( PaStream *stream ); + + +/** Terminates audio processing immediately without waiting for pending + buffers to complete. +*/ +PaError Pa_AbortStream( PaStream *stream ); + + +/** Determine whether the stream is stopped. + A stream is considered to be stopped prior to a successful call to + Pa_StartStream and after a successful call to Pa_StopStream or Pa_AbortStream. + If a stream callback returns a value other than paContinue the stream is NOT + considered to be stopped. + + @return Returns one (1) when the stream is stopped, zero (0) when + the stream is running or, a PaErrorCode (which are always negative) if + PortAudio is not initialized or an error is encountered. + + @see Pa_StopStream, Pa_AbortStream, Pa_IsStreamActive +*/ +PaError Pa_IsStreamStopped( PaStream *stream ); + + +/** Determine whether the stream is active. + A stream is active after a successful call to Pa_StartStream(), until it + becomes inactive either as a result of a call to Pa_StopStream() or + Pa_AbortStream(), or as a result of a return value other than paContinue from + the stream callback. In the latter case, the stream is considered inactive + after the last buffer has finished playing. + + @return Returns one (1) when the stream is active (ie playing or recording + audio), zero (0) when not playing or, a PaErrorCode (which are always negative) + if PortAudio is not initialized or an error is encountered. + + @see Pa_StopStream, Pa_AbortStream, Pa_IsStreamStopped +*/ +PaError Pa_IsStreamActive( PaStream *stream ); + + + +/** A structure containing unchanging information about an open stream. + @see Pa_GetStreamInfo +*/ + +typedef struct PaStreamInfo +{ + /** this is struct version 1 */ + int structVersion; + + /** The input latency of the stream in seconds. This value provides the most + accurate estimate of input latency available to the implementation. It may + differ significantly from the suggestedLatency value passed to Pa_OpenStream(). + The value of this field will be zero (0.) for output-only streams. + @see PaTime + */ + PaTime inputLatency; + + /** The output latency of the stream in seconds. This value provides the most + accurate estimate of output latency available to the implementation. It may + differ significantly from the suggestedLatency value passed to Pa_OpenStream(). + The value of this field will be zero (0.) for input-only streams. + @see PaTime + */ + PaTime outputLatency; + + /** The sample rate of the stream in Hertz (samples per second). In cases + where the hardware sample rate is inaccurate and PortAudio is aware of it, + the value of this field may be different from the sampleRate parameter + passed to Pa_OpenStream(). If information about the actual hardware sample + rate is not available, this field will have the same value as the sampleRate + parameter passed to Pa_OpenStream(). + */ + double sampleRate; + +} PaStreamInfo; + + +/** Retrieve a pointer to a PaStreamInfo structure containing information + about the specified stream. + @return A pointer to an immutable PaStreamInfo structure. If the stream + parameter is invalid, or an error is encountered, the function returns NULL. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @note PortAudio manages the memory referenced by the returned pointer, + the client must not manipulate or free the memory. The pointer is only + guaranteed to be valid until the specified stream is closed. + + @see PaStreamInfo +*/ +const PaStreamInfo* Pa_GetStreamInfo( PaStream *stream ); + + +/** Returns the current time in seconds for a stream according to the same clock used + to generate callback PaStreamCallbackTimeInfo timestamps. The time values are + monotonically increasing and have unspecified origin. + + Pa_GetStreamTime returns valid time values for the entire life of the stream, + from when the stream is opened until it is closed. Starting and stopping the stream + does not affect the passage of time returned by Pa_GetStreamTime. + + This time may be used for synchronizing other events to the audio stream, for + example synchronizing audio to MIDI. + + @return The stream's current time in seconds, or 0 if an error occurred. + + @see PaTime, PaStreamCallback, PaStreamCallbackTimeInfo +*/ +PaTime Pa_GetStreamTime( PaStream *stream ); + + +/** Retrieve CPU usage information for the specified stream. + The "CPU Load" is a fraction of total CPU time consumed by a callback stream's + audio processing routines including, but not limited to the client supplied + stream callback. This function does not work with blocking read/write streams. + + This function may be called from the stream callback function or the + application. + + @return + A floating point value, typically between 0.0 and 1.0, where 1.0 indicates + that the stream callback is consuming the maximum number of CPU cycles possible + to maintain real-time operation. A value of 0.5 would imply that PortAudio and + the stream callback was consuming roughly 50% of the available CPU time. The + return value may exceed 1.0. A value of 0.0 will always be returned for a + blocking read/write stream, or if an error occurs. +*/ +double Pa_GetStreamCpuLoad( PaStream* stream ); + + +/** Read samples from an input stream. The function doesn't return until + the entire buffer has been filled - this may involve waiting for the operating + system to supply the data. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @param buffer A pointer to a buffer of sample frames. The buffer contains + samples in the format specified by the inputParameters->sampleFormat field + used to open the stream, and the number of channels specified by + inputParameters->numChannels. If non-interleaved samples were requested using + the paNonInterleaved sample format flag, buffer is a pointer to the first element + of an array of buffer pointers, one non-interleaved buffer for each channel. + + @param frames The number of frames to be read into buffer. This parameter + is not constrained to a specific range, however high performance applications + will want to match this parameter to the framesPerBuffer parameter used + when opening the stream. + + @return On success PaNoError will be returned, or PaInputOverflowed if input + data was discarded by PortAudio after the previous call and before this call. +*/ +PaError Pa_ReadStream( PaStream* stream, + void *buffer, + unsigned long frames ); + + +/** Write samples to an output stream. This function doesn't return until the + entire buffer has been written - this may involve waiting for the operating + system to consume the data. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @param buffer A pointer to a buffer of sample frames. The buffer contains + samples in the format specified by the outputParameters->sampleFormat field + used to open the stream, and the number of channels specified by + outputParameters->numChannels. If non-interleaved samples were requested using + the paNonInterleaved sample format flag, buffer is a pointer to the first element + of an array of buffer pointers, one non-interleaved buffer for each channel. + + @param frames The number of frames to be written from buffer. This parameter + is not constrained to a specific range, however high performance applications + will want to match this parameter to the framesPerBuffer parameter used + when opening the stream. + + @return On success PaNoError will be returned, or paOutputUnderflowed if + additional output data was inserted after the previous call and before this + call. +*/ +PaError Pa_WriteStream( PaStream* stream, + const void *buffer, + unsigned long frames ); + + +/** Retrieve the number of frames that can be read from the stream without + waiting. + + @return Returns a non-negative value representing the maximum number of frames + that can be read from the stream without blocking or busy waiting or, a + PaErrorCode (which are always negative) if PortAudio is not initialized or an + error is encountered. +*/ +signed long Pa_GetStreamReadAvailable( PaStream* stream ); + + +/** Retrieve the number of frames that can be written to the stream without + waiting. + + @return Returns a non-negative value representing the maximum number of frames + that can be written to the stream without blocking or busy waiting or, a + PaErrorCode (which are always negative) if PortAudio is not initialized or an + error is encountered. +*/ +signed long Pa_GetStreamWriteAvailable( PaStream* stream ); + + +/* Miscellaneous utilities */ + + +/** Retrieve the size of a given sample format in bytes. + + @return The size in bytes of a single sample in the specified format, + or paSampleFormatNotSupported if the format is not supported. +*/ +PaError Pa_GetSampleSize( PaSampleFormat format ); + + +/** Put the caller to sleep for at least 'msec' milliseconds. This function is + provided only as a convenience for authors of portable code (such as the tests + and examples in the PortAudio distribution.) + + The function may sleep longer than requested so don't rely on this for accurate + musical timing. +*/ +void Pa_Sleep( long msec ); + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PORTAUDIO_H */ diff --git a/tinymod b/tinymod new file mode 100644 index 0000000..bdedd50 Binary files /dev/null and b/tinymod differ diff --git a/tinymod.cpp b/tinymod.cpp new file mode 100644 index 0000000..cb69373 --- /dev/null +++ b/tinymod.cpp @@ -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 // fixed size integer types (part of c standard library) +#include // basic mathematical operations (part of c standard library) - mostly floating point +#include // standard I/O library +#include // string handling (part of c standard library) +#include // (unix standard) +#include // (file control options) - used by open() +#include // needed by stat function (get file status) +#include // defines set of integeral types +#include // 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 [|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 [|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); +} diff --git a/types.h b/types.h new file mode 100644 index 0000000..96b20ab --- /dev/null +++ b/types.h @@ -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 inline T sMin(const T a, const T b) +{ return (a < b) ? a : b; } // min function, return smallest + +template inline T sMax(const T a, const T b) +{ return (a > b) ? a : b; } // max function, return largest + +template inline T sClamp(const T x, const T min, const T max) +{ return sMax(min, sMin(max, x)); } // variable clamp + +template T sSqr(T v) +{ return v * v; } // square root + +template T sLerp(T a, T b, sF32 f) +{ return a + f * (b - a); } // linear interpolation + +template 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)