diff --git a/Config.h b/Config.h new file mode 100644 index 0000000..713370a --- /dev/null +++ b/Config.h @@ -0,0 +1,5 @@ +#define WORDS_LITTLEENDIAN +#define OPTIMIZE_ENDIAN_ACCESS +#define SIZEOF_CHAR 1 +#define SIZEOF_SHORT_INT 2 + diff --git a/FC.cpp b/FC.cpp new file mode 100644 index 0000000..4712f23 --- /dev/null +++ b/FC.cpp @@ -0,0 +1,1242 @@ +// -------------------------------------------------------------------------- +// AMIGA Future Composer v1.0 - v1.4 music player +// +// Implemented in 1997 by Michael Schwendt (sidplay@geocities.com). +// +// Based mainly on hand-written notes from 1990 which were made upon fixing +// a variety of bugs in the original player source code which was released +// with Future Composer. +// +// http://www.geocities.com/SiliconValley/Lakes/5147/mod/ +// +// No effort has been put into making this implementation elegant or +// efficient in either way. +// -------------------------------------------------------------------------- + +#if defined(DEBUG) || defined(DEBUG2) || defined(DEBUG3) +#include +#include +#include "Dump.h" +#endif + +#include "FC.h" +#include "FC_Defs.h" + +bool FC_songEnd; // whether song end has been reached + +static smartPtr < ubyte > fcBuf; // for safe unsigned access +static smartPtr < sbyte > fcBufS; // for safe signed access + +static bool isSMOD; // whether file is in Future Composer 1.0 - 1.3 format +static bool isFC14; // whether file is in Future Composer 1.4 format + +static _FC_CHdata FC_CHdata[4]; + +// This array will be moved behind the input file. So don't forget +// to allocate additional sizeof(..) bytes. +static const ubyte FC_silent_data[8] = { + // Used as ``silent'' volume and modulation sequence. + // seqSpeed, sndSeq, vibSpeed, vibAmp, vibDelay, initial Volume, ... + // + // Silent volume sequence starts here. + 0x01, + // Silent modulation sequence starts here. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, SEQ_END +}; + +// Index is AND 0x7f. +static const uword FC_periods[(5 + 6) * 12 + 4] = { + 0x06b0, 0x0650, 0x05f4, 0x05a0, 0x054c, 0x0500, 0x04b8, 0x0474, + 0x0434, 0x03f8, 0x03c0, 0x038a, + // +0x0c (*2 = byte-offset) + 0x0358, 0x0328, 0x02fa, 0x02d0, 0x02a6, 0x0280, 0x025c, 0x023a, + 0x021a, 0x01fc, 0x01e0, 0x01c5, + // +0x18 (*2 = byte-offset) + 0x01ac, 0x0194, 0x017d, 0x0168, 0x0153, 0x0140, 0x012e, 0x011d, + 0x010d, 0x00fe, 0x00f0, 0x00e2, + // +0x24 (*2 = byte-offset) + 0x00d6, 0x00ca, 0x00be, 0x00b4, 0x00aa, 0x00a0, 0x0097, 0x008f, + 0x0087, 0x007f, 0x0078, 0x0071, + // +0x30 (*2 = byte-offset) + 0x0071, 0x0071, 0x0071, 0x0071, 0x0071, 0x0071, 0x0071, 0x0071, + 0x0071, 0x0071, 0x0071, 0x0071, + // +0x3c (*2 = byte-offset) + 0x0d60, 0x0ca0, 0x0be8, 0x0b40, 0x0a98, 0x0a00, 0x0970, 0x08e8, + 0x0868, 0x07f0, 0x0780, 0x0714, + // +0x48 (*2 = byte-offset) + // + // (NOTE) 0x49 = PATTERN BREAK, so this extra low octave is quite + // useless for direct access. Transpose values would be required. + // However, the FC player has hardcoded 0x0d60 as the lowest + // sample period and does a range-check prior to writing a period + // to the AMIGA custom chip. In short: This octave is useless! + // + 0x1ac0, 0x1940, 0x17d0, 0x1680, 0x1530, 0x1400, 0x12e0, 0x11d0, + 0x10d0, 0x0fe0, 0x0f00, 0x0e28, + // + // +0x54 (*2 = byte-offset) + // + // End of Future Composer 1.0 - 1.3 period table. + // + 0x06b0, 0x0650, 0x05f4, 0x05a0, 0x054c, 0x0500, 0x04b8, 0x0474, + 0x0434, 0x03f8, 0x03c0, 0x038a, + // +0x60 (*2 = byte-offset) + 0x0358, 0x0328, 0x02fa, 0x02d0, 0x02a6, 0x0280, 0x025c, 0x023a, + 0x021a, 0x01fc, 0x01e0, 0x01c5, + // +0x6c (*2 = byte-offset) + 0x01ac, 0x0194, 0x017d, 0x0168, 0x0153, 0x0140, 0x012e, 0x011d, + 0x010d, 0x00fe, 0x00f0, 0x00e2, + // +0x78 (*2 = byte-offset) + 0x00d6, 0x00ca, 0x00be, 0x00b4, 0x00aa, 0x00a0, 0x0097, 0x008f, + // +0x80 (*2 = byte-offset), everything from here on is unreachable. + 0x0087, 0x007f, 0x0078, 0x0071 + // +0x84 (*2 = byte-offset) +}; + +static const uword SMOD_waveInfo[47 * 4] = { + 0x0000, 0x0010, 0x0000, 0x0010, + 0x0020, 0x0010, 0x0000, 0x0010, + 0x0040, 0x0010, 0x0000, 0x0010, + 0x0060, 0x0010, 0x0000, 0x0010, + 0x0080, 0x0010, 0x0000, 0x0010, + 0x00a0, 0x0010, 0x0000, 0x0010, + 0x00c0, 0x0010, 0x0000, 0x0010, + 0x00e0, 0x0010, 0x0000, 0x0010, + 0x0100, 0x0010, 0x0000, 0x0010, + 0x0120, 0x0010, 0x0000, 0x0010, + 0x0140, 0x0010, 0x0000, 0x0010, + 0x0160, 0x0010, 0x0000, 0x0010, + 0x0180, 0x0010, 0x0000, 0x0010, + 0x01a0, 0x0010, 0x0000, 0x0010, + 0x01c0, 0x0010, 0x0000, 0x0010, + 0x01e0, 0x0010, 0x0000, 0x0010, + 0x0200, 0x0010, 0x0000, 0x0010, + 0x0220, 0x0010, 0x0000, 0x0010, + 0x0240, 0x0010, 0x0000, 0x0010, + 0x0260, 0x0010, 0x0000, 0x0010, + 0x0280, 0x0010, 0x0000, 0x0010, + 0x02a0, 0x0010, 0x0000, 0x0010, + 0x02c0, 0x0010, 0x0000, 0x0010, + 0x02e0, 0x0010, 0x0000, 0x0010, + 0x0300, 0x0010, 0x0000, 0x0010, + 0x0320, 0x0010, 0x0000, 0x0010, + 0x0340, 0x0010, 0x0000, 0x0010, + 0x0360, 0x0010, 0x0000, 0x0010, + 0x0380, 0x0010, 0x0000, 0x0010, + 0x03a0, 0x0010, 0x0000, 0x0010, + 0x03c0, 0x0010, 0x0000, 0x0010, + 0x03e0, 0x0010, 0x0000, 0x0010, + 0x0400, 0x0008, 0x0000, 0x0008, + 0x0410, 0x0008, 0x0000, 0x0008, + 0x0420, 0x0008, 0x0000, 0x0008, + 0x0430, 0x0008, 0x0000, 0x0008, + 0x0440, 0x0008, 0x0000, 0x0008, + 0x0450, 0x0008, 0x0000, 0x0008, + 0x0460, 0x0008, 0x0000, 0x0008, + 0x0470, 0x0008, 0x0000, 0x0008, + 0x0480, 0x0010, 0x0000, 0x0010, + 0x04a0, 0x0008, 0x0000, 0x0008, + 0x04b0, 0x0010, 0x0000, 0x0010, + 0x04d0, 0x0010, 0x0000, 0x0010, + 0x04f0, 0x0008, 0x0000, 0x0008, + 0x0500, 0x0008, 0x0000, 0x0008, + 0x0510, 0x0018, 0x0000, 0x0018 +}; + +static const ubyte SMOD_waveforms[] = { + 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, + 0x3f, 0x37, 0x2f, 0x27, 0x1f, 0x17, 0x0f, 0x07, 0xff, 0x07, 0x0f, 0x17, 0x1f, 0x27, 0x2f, 0x37, + 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, + 0xc0, 0x37, 0x2f, 0x27, 0x1f, 0x17, 0x0f, 0x07, 0xff, 0x07, 0x0f, 0x17, 0x1f, 0x27, 0x2f, 0x37, + 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, + 0xc0, 0xb8, 0x2f, 0x27, 0x1f, 0x17, 0x0f, 0x07, 0xff, 0x07, 0x0f, 0x17, 0x1f, 0x27, 0x2f, 0x37, + 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, + 0xc0, 0xb8, 0xb0, 0x27, 0x1f, 0x17, 0x0f, 0x07, 0xff, 0x07, 0x0f, 0x17, 0x1f, 0x27, 0x2f, 0x37, + 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, + 0xc0, 0xb8, 0xb0, 0xa8, 0x1f, 0x17, 0x0f, 0x07, 0xff, 0x07, 0x0f, 0x17, 0x1f, 0x27, 0x2f, 0x37, + 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, + 0xc0, 0xb8, 0xb0, 0xa8, 0xa0, 0x17, 0x0f, 0x07, 0xff, 0x07, 0x0f, 0x17, 0x1f, 0x27, 0x2f, 0x37, + 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, + 0xc0, 0xb8, 0xb0, 0xa8, 0xa0, 0x98, 0x0f, 0x07, 0xff, 0x07, 0x0f, 0x17, 0x1f, 0x27, 0x2f, 0x37, + 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, + 0xc0, 0xb8, 0xb0, 0xa8, 0xa0, 0x98, 0x90, 0x07, 0xff, 0x07, 0x0f, 0x17, 0x1f, 0x27, 0x2f, 0x37, + 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, + 0xc0, 0xb8, 0xb0, 0xa8, 0xa0, 0x98, 0x90, 0x88, 0xff, 0x07, 0x0f, 0x17, 0x1f, 0x27, 0x2f, 0x37, + 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, + 0xc0, 0xb8, 0xb0, 0xa8, 0xa0, 0x98, 0x90, 0x88, 0x80, 0x07, 0x0f, 0x17, 0x1f, 0x27, 0x2f, 0x37, + 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, + 0xc0, 0xb8, 0xb0, 0xa8, 0xa0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x0f, 0x17, 0x1f, 0x27, 0x2f, 0x37, + 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, + 0xc0, 0xb8, 0xb0, 0xa8, 0xa0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x17, 0x1f, 0x27, 0x2f, 0x37, + 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, + 0xc0, 0xb8, 0xb0, 0xa8, 0xa0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x98, 0x1f, 0x27, 0x2f, 0x37, + 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, + 0xc0, 0xb8, 0xb0, 0xa8, 0xa0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x98, 0xa0, 0x27, 0x2f, 0x37, + 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, + 0xc0, 0xb8, 0xb0, 0xa8, 0xa0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x98, 0xa0, 0xa8, 0x2f, 0x37, + 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, + 0xc0, 0xb8, 0xb0, 0xa8, 0xa0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x98, 0xa0, 0xa8, 0xb0, 0x37, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7f, 0x7f, 0x7f, 0x7f, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7f, 0x7f, 0x7f, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x80, 0x80, 0x90, 0x98, 0xa0, 0xa8, 0xb0, 0xb8, 0xc0, 0xc8, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, + 0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0x48, 0x50, 0x58, 0x60, 0x68, 0x70, 0x7f, + 0x80, 0x80, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, + 0x45, 0x45, 0x79, 0x7d, 0x7a, 0x77, 0x70, 0x66, 0x61, 0x58, 0x53, 0x4d, 0x2c, 0x20, 0x18, 0x12, + 0x04, 0xdb, 0xd3, 0xcd, 0xc6, 0xbc, 0xb5, 0xae, 0xa8, 0xa3, 0x9d, 0x99, 0x93, 0x8e, 0x8b, 0x8a, + 0x45, 0x45, 0x79, 0x7d, 0x7a, 0x77, 0x70, 0x66, 0x5b, 0x4b, 0x43, 0x37, 0x2c, 0x20, 0x18, 0x12, + 0x04, 0xf8, 0xe8, 0xdb, 0xcf, 0xc6, 0xbe, 0xb0, 0xa8, 0xa4, 0x9e, 0x9a, 0x95, 0x94, 0x8d, 0x83, + 0x00, 0x00, 0x40, 0x60, 0x7f, 0x60, 0x40, 0x20, 0x00, 0xe0, 0xc0, 0xa0, 0x80, 0xa0, 0xc0, 0xe0, + 0x00, 0x00, 0x40, 0x60, 0x7f, 0x60, 0x40, 0x20, 0x00, 0xe0, 0xc0, 0xa0, 0x80, 0xa0, 0xc0, 0xe0, + 0x80, 0x80, 0x90, 0x98, 0xa0, 0xa8, 0xb0, 0xb8, 0xc0, 0xc8, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, + 0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0x48, 0x50, 0x58, 0x60, 0x68, 0x70, 0x7f, + 0x80, 0x80, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70 +}; + +// -------------------------------------------------------------------------- + +void FC_off() +{ + // (AMIGA) Power-LED on = low-pass filter on. + + FC_admin.isEnabled = false; + + for (int c = 0; c < 4; c++) { + FC_CHdata[c].ch->off(); + FC_CHdata[c].ch->paula.period = 0; + FC_CHdata[c].ch->paula.volume = 0; + FC_CHdata[c].ch->updatePerVol(); + } +} + +bool FC_init(void *data, udword dataLen, int startStep, int endStep) +{ + // Set up smart pointers for signed and unsigned input buffer access. + // Ought to be read-only (const), but this implementation appends + // a few values to the end of the buffer (see further below). + fcBufS.setBuffer((sbyte *) data, dataLen); + fcBuf.setBuffer((ubyte *) data, dataLen); + + // Check for "SMOD" ID (Future Composer 1.0 to 1.3). + isSMOD = (fcBuf[0] == 0x53 && fcBuf[1] == 0x4D && fcBuf[2] == 0x4F && fcBuf[3] == 0x44 && fcBuf[4] == 0x00); + + // Check for "FC14" ID (Future Composer 1.4). + isFC14 = (fcBuf[0] == 0x46 && fcBuf[1] == 0x43 && fcBuf[2] == 0x31 && fcBuf[3] == 0x34 && fcBuf[4] == 0x00); + + // (NOTE) A very few hacked "SMOD" modules exist which contain an ID + // string "FC13". Although this could be supported easily, it should + // NOT. Format detection must be able to rely on the ID field. As no + // version of Future Composer has ever created a "FC13" ID, such hacked + // modules are likely to be incompatible in other parts due to incorrect + // conversion, e.g. effect parameters. It is like creating non-standard + // module files whose only purpose is to confuse accurate music players. + + if (!isSMOD && !isFC14) { + return false; + } + // (NOTE) This next bit is just for convenience. + // + // It is the only place where the module buffer is written to. + // + // Copy ``silent'' modulation sequence to end of FC module buffer so it + // is in the address space of the FC module and thus allows using the + // same smart pointers as throughout the rest of the code. Assume file + // loader has provided that extra space at the end. + FC_admin.offsets.FC_silent = dataLen - sizeof(FC_silent_data); + for (unsigned int i = 0; i < sizeof(FC_silent_data); i++) { + fcBuf[FC_admin.offsets.FC_silent + i] = FC_silent_data[i]; + } + + // (AMIGA) Power-LED off => low-pass filter off. + + FC_admin.isEnabled = false; + FC_admin.dmaFlags = 0; + + // This one later on gets incremented prior to first comparison + // (4+1=5 => get speed at first step). + FC_admin.RScount = 4; + // (NOTE) Some FC implementations instead count from 0 to 4. + + // (NOTE) Instead of addresses (pointers) use 32-bit offsets everywhere. + // This is just for added safety and convenience. One could range-check + // each pointer/offset where appropriate to avoid segmentation faults + // caused by damaged input data. + + if (isSMOD) { + FC_admin.offsets.trackTable = SMOD_SONGTAB_OFFSET; + } else // if (isFC14) + { + FC_admin.offsets.trackTable = FC14_SONGTAB_OFFSET; + } + +#if defined(DEBUG) + cout << "Track table (sequencer): " << endl; + dumpLines(fcBuf, FC_admin.offsets.trackTable, + readEndian(fcBuf[4], fcBuf[5], fcBuf[6], fcBuf[7]), TRACKTAB_ENTRY_LENGTH); + cout << endl; +#endif + + // At +8 is offset to pattern data. + FC_admin.offsets.patterns = readEndian(fcBuf[8], fcBuf[9], fcBuf[10], fcBuf[11]); + // At +12 is length of patterns. + // Divide by pattern length to get number of used patterns. + // The editor is limited to 128 patterns. + FC_admin.usedPatterns = readEndian(fcBuf[12], fcBuf[13], fcBuf[14], fcBuf[15]) / PATTERN_LENGTH; +#if defined(DEBUG) + cout << "Patterns: " << hex << FC_admin.usedPatterns << endl; + dumpBlocks(fcBuf, FC_admin.offsets.patterns, FC_admin.usedPatterns * PATTERN_LENGTH, PATTERN_LENGTH); +#endif + + // At +16 is offset to first sound modulation sequence. + FC_admin.offsets.sndModSeqs = readEndian(fcBuf[16], fcBuf[17], fcBuf[18], fcBuf[19]); + // At +20 is total length of sound modulation sequences. + // Divide by sequence length to get number of used sequences. + // Each sequence is 64 bytes long. + FC_admin.usedSndModSeqs = readEndian(fcBuf[20], fcBuf[21], fcBuf[22], fcBuf[23]) / 64; +#if defined(DEBUG) + cout << "Sound modulation sequences: " << hex << FC_admin.usedSndModSeqs << endl; + dumpBlocks(fcBuf, FC_admin.offsets.sndModSeqs, FC_admin.usedSndModSeqs * 64, 64); +#endif + + // At +24 is offset to first volume modulation sequence. + FC_admin.offsets.volModSeqs = readEndian(fcBuf[24], fcBuf[25], fcBuf[26], fcBuf[27]); + // At +28 is total length of volume modulation sequences. + // Divide by sequence length to get number of used sequences. + // Each sequence is 64 bytes long. + FC_admin.usedVolModSeqs = readEndian(fcBuf[28], fcBuf[29], fcBuf[30], fcBuf[31]) / 64; +#if defined(DEBUG) + cout << "Volume modulation sequences: " << hex << FC_admin.usedVolModSeqs << endl; + dumpBlocks(fcBuf, FC_admin.offsets.volModSeqs, FC_admin.usedVolModSeqs * 64, 64); +#endif + +#if defined(DEBUG) + cout << "Samples:" << endl; +#endif + // At +32 is module offset to start of samples. + udword sampleOffset = readEndian(fcBuf[32], fcBuf[33], + fcBuf[34], fcBuf[35]); + udword sampleHeader = FC14_SMPHEADERS_OFFSET; + // Max. 10 samples ($0-$9) or 10 sample-packs of 10 samples each. + // Maximum sample length = 50000. + // Previously: 32KB. + // Total: 100000 (gee, old times). + // + // Sample length in words, repeat offset in bytes (but even), + // Repeat length (in words, min=1). But in editor: all in bytes! + // + // One-shot sample (*recommended* method): + // repeat offset = length*2 (to zero word at end of sample) and + // replength = 1 + for (int sam = 0; sam < 10; sam++) { + FC_SOUNDinfo.snd[sam].start = sampleOffset + fcBuf.tellBegin(); + // Sample length in words. + uword sampleLength = readEndian(fcBuf[sampleHeader], + fcBuf[sampleHeader + 1]); + FC_SOUNDinfo.snd[sam].len = sampleLength; + FC_SOUNDinfo.snd[sam].repOffs = readEndian(fcBuf[sampleHeader + 2], fcBuf[sampleHeader + 3]); + FC_SOUNDinfo.snd[sam].repLen = readEndian(fcBuf[sampleHeader + 4], fcBuf[sampleHeader + 5]); + + // Safety treatment of "one-shot" samples. + // + // We erase a word (two sample bytes) in the right place to + // ensure that one-shot samples do not beep at their end + // when looping on that part of the sample. + // + // It might not be strictly necessary to do this as it is + // documented that FC is supposed to do this. But better be + // safe than sorry. + // + // It is done because a cheap mixer is implemented which can + // be used in the same way AMIGA custom chip Paula is used to + // play quick-and-dirty one-shot samples. + // + // (NOTE) There is a difference in how one-shot samples are treated + // in Future Composer 1.4 in comparison with older versions. + + if (isSMOD) { + // Check whether this is a one-shot sample. + if (FC_SOUNDinfo.snd[sam].repLen == 1) { + fcBuf[sampleOffset] = fcBuf[sampleOffset + 1] = 0; + } + } + // Skip to start of next sample data. + sampleOffset += sampleLength; + sampleOffset += sampleLength; + + if (isFC14) { + udword pos = sampleOffset; + // Make sure we do not erase the sample-pack ID "SSMP" + // and check whether this is a one-shot sample. + if (fcBuf[pos] != 0x53 && fcBuf[pos + 1] != 0x53 && + fcBuf[pos + 2] != 0x4D && fcBuf[pos + 3] != 0x50 && FC_SOUNDinfo.snd[sam].repLen == 1) { + fcBuf[pos] = fcBuf[pos + 1] = 0; + } + // FC 1.4 keeps silent word behind sample. + // Now skip that one to the next sample. + // + // (BUG-FIX) Add +2 to sample address is incorrect + // for unused (i.e. empty) samples. + // + if (sampleLength != 0) + sampleOffset += 2; + } + + sampleHeader += 6; // skip unused rest of header + +#if defined(DEBUG) + cout << dec << setw(6) << (int) FC_SOUNDinfo.snd[sam].start - (udword) fcBuf.tellBegin() << " " + << dec << setw(4) << (int) FC_SOUNDinfo.snd[sam].len * 2L << " " + << dec << setw(6) << (int) FC_SOUNDinfo.snd[sam].repOffs << " " + << dec << setw(6) << (int) FC_SOUNDinfo.snd[sam].repLen * 2L << " " << endl; + dumpLines(fcBuf, (udword) FC_SOUNDinfo.snd[sam].start - (udword) fcBuf.tellBegin(), 16, 16); +#endif + } + +#if defined(DEBUG) + cout << "Waveforms:" << endl; +#endif + // 80 waveforms ($0a-$59), max $100 bytes length each. + if (isSMOD) { + // Old FC has built-in waveforms. + const ubyte *wavePtr = SMOD_waveforms; + int infoIndex = 0; + for (int wave = 0; wave < 47; wave++) { + int sam = 10 + wave; + FC_SOUNDinfo.snd[sam].start = wavePtr + SMOD_waveInfo[infoIndex++]; + FC_SOUNDinfo.snd[sam].len = SMOD_waveInfo[infoIndex++]; + FC_SOUNDinfo.snd[sam].repOffs = SMOD_waveInfo[infoIndex++]; + FC_SOUNDinfo.snd[sam].repLen = SMOD_waveInfo[infoIndex++]; +#if defined(DEBUG) + cout << dec << setw(6) << (int) FC_SOUNDinfo.snd[sam].start - (udword) wavePtr << " " + << dec << setw(4) << (int) FC_SOUNDinfo.snd[sam].len * 2L << " " + << dec << setw(6) << (int) FC_SOUNDinfo.snd[sam].repOffs << " " + << dec << setw(6) << (int) FC_SOUNDinfo.snd[sam].repLen * 2L << " " << endl; +#endif + } + } else //if (isFC14) + { + // At +36 is module offset to start of waveforms. + udword waveOffset = readEndian(fcBuf[36], fcBuf[37], + fcBuf[38], fcBuf[39]); + // Module offset to array of waveform lengths. + udword waveHeader = FC14_WAVEHEADERS_OFFSET; + for (int wave = 0; wave < 80; wave++) { + int sam = 10 + wave; + FC_SOUNDinfo.snd[sam].start = waveOffset + fcBuf.tellBegin(); + ubyte waveLength = fcBuf[waveHeader++]; + waveOffset += waveLength; + waveOffset += waveLength; + FC_SOUNDinfo.snd[sam].len = waveLength; + FC_SOUNDinfo.snd[sam].repOffs = 0; + FC_SOUNDinfo.snd[sam].repLen = waveLength; +#if defined(DEBUG) + cout << dec << setw(6) << (int) FC_SOUNDinfo.snd[sam].start - (udword) fcBuf.tellBegin() << " " + << dec << setw(4) << (int) FC_SOUNDinfo.snd[sam].len * 2L << " " + << dec << setw(6) << (int) FC_SOUNDinfo.snd[sam].repOffs << " " + << dec << setw(6) << (int) FC_SOUNDinfo.snd[sam].repLen * 2L << " " << endl; +#endif + } + } + + // At +4 is length of track table. + udword trackTabLen = readEndian(fcBuf[4], fcBuf[5], fcBuf[6], fcBuf[7]); +#if defined(DEBUG) + cout << "trackTabLen = " << hex << setw(8) << setfill('0') << trackTabLen << endl; +#endif + for (int chan = 0; chan < 4; chan++) { + FC_CHdata[chan].dmaMask = (1 << chan); + FC_CHdata[chan].trackPos = FC_CHdata[chan].pattPos = 0; + FC_CHdata[chan].volSlideSpeed = + FC_CHdata[chan].volSlideTime = + FC_CHdata[chan].volSlideDelayFlag = FC_CHdata[chan].volSustainTime = 0; + FC_CHdata[chan].envelopeCount = FC_CHdata[chan].envelopeSpeed = 1; + FC_CHdata[chan].vibSpeed = + FC_CHdata[chan].vibDelay = + FC_CHdata[chan].vibAmpl = FC_CHdata[chan].vibCurOffs = FC_CHdata[chan].vibFlag = 0; + FC_CHdata[chan].pitchBendSpeed = FC_CHdata[chan].pitchBendTime = FC_CHdata[chan].pitchBendDelayFlag = 0; + FC_CHdata[chan].transpose = FC_CHdata[chan].seqTranspose = 0; + FC_CHdata[chan].portaInfo = FC_CHdata[chan].portaOffs = FC_CHdata[chan].portDelayFlag = 0; + FC_CHdata[chan].volSeq = FC_admin.offsets.FC_silent; + FC_CHdata[chan].sndSeq = FC_admin.offsets.FC_silent + 1; + FC_CHdata[chan].volSeqPos = FC_CHdata[chan].sndSeqPos = 0; + FC_CHdata[chan].sndModSustainTime = 0; + FC_CHdata[chan].noteValue = 0; + FC_CHdata[chan].volume = 0; + + // The interface to a cheap Paula simulator/mixer. + extern channel logChannel[32]; + FC_CHdata[chan].ch = &logChannel[chan]; + FC_CHdata[chan].ch->off(); + FC_CHdata[chan].ch->paula.start = fcBuf.tellBegin(); // 0 + // (NOTE) Some implementations set this to 0x0100. + FC_CHdata[chan].ch->paula.length = 1; + FC_CHdata[chan].ch->takeNextBuf(); + FC_CHdata[chan].ch->paula.period = 0; + FC_CHdata[chan].ch->paula.volume = 0; + FC_CHdata[chan].ch->updatePerVol(); + + // Track table start and end. + FC_CHdata[chan].trackStart = FC_admin.offsets.trackTable + chan * 3; + FC_CHdata[chan].trackEnd = FC_CHdata[chan].trackStart + trackTabLen; + FC_CHdata[chan].trackPos = TRACKTAB_ENTRY_LENGTH * startStep; + + // 4* + // PT = PATTERN + // TR = TRANSPOSE + // ST = SOUND TRANSPOSE + // + // 1* + // RS = REPLAY SPEED + + // Read PT/TR/ST from track table. + udword trackTabPos = FC_CHdata[chan].trackStart + FC_CHdata[chan].trackPos; + uword pattern = fcBuf[trackTabPos++]; // PT + FC_CHdata[chan].pattStart = FC_admin.offsets.patterns + (pattern << 6); + FC_CHdata[chan].transpose = fcBuf[trackTabPos++]; // TR + FC_CHdata[chan].soundTranspose = fcBufS[trackTabPos]; // ST + } + + // Get and set song speed. + // + // (BUG-FIX) Some FC players here read the speed from the first step. + // This is the wrong speed if a sub-song is selected by skipping steps. + // + // (NOTE) If it is skipped to a step where no replay step is specified, + // the default speed is taken. This can be wrong. The only solution + // would be to fast-forward the song, i.e. read the speed from all + // steps up to the starting step. + // + ubyte songSpeed = fcBuf[FC_CHdata[0].trackStart + FC_CHdata[0].trackPos + 12]; + if (songSpeed == 0) + songSpeed = 3; // documented default + + FC_admin.count = FC_admin.speed = songSpeed; + FC_admin.isEnabled = true; + FC_songEnd = false; + + // + + mixerPlayRout = &FC_play; // this would be called upon interrupt + + MIXER_voices = 4; + MIXER_samples = 128; + MIXER_speed = FC_admin.speed; // set default playing speed + + // (NOTE) The lowest octave in the period table is unreachable + // due to a hardcoded range-check (see bottom). + + if (isSMOD) { + MIXER_minPeriod = 0x0071; + MIXER_maxPeriod = 0x0d60; + } else //if (isFC14) + { + MIXER_minPeriod = 0x0071; + MIXER_maxPeriod = 0x0d60; + } + + for (int c = 0; c < 4; c++) { + FC_CHdata[c].ch->sampleFrequency = 11200; + FC_CHdata[c].ch->bitsPerSample = 8; + FC_CHdata[c].ch->sign = true; + FC_CHdata[c].ch->looping = true; + } + + if (isSMOD) + mixerFormatName = "Future Composer 1.0 - 1.3 (AMIGA)"; + else if (isFC14) + mixerFormatName = "Future Composer 1.4 (AMIGA)"; + else + mixerFormatName = "unknown format"; + + return isFC14 || isSMOD; +} + +// -------------------------------------------------------------------------- + +void FC_play() +{ + if (!FC_admin.isEnabled) // on/off flag + return; + + if (--FC_admin.count == 0) { + FC_admin.count = FC_admin.speed; // reload + + void FC_nextNote(_FC_CHdata &); + // Prepare next note for each voice. + FC_nextNote(FC_CHdata[0]); + FC_nextNote(FC_CHdata[1]); + FC_nextNote(FC_CHdata[2]); + FC_nextNote(FC_CHdata[3]); +#if defined(DEBUG2) + cout << endl << flush; +#endif + } + // Procedure calls in next loop will decide which + // audio channel to turn on. + FC_admin.dmaFlags = 0; + + for (int c = 0; c < 4; c++) { + void FC_processModulation(_FC_CHdata &); + // Start or update instrument. + FC_processModulation(FC_CHdata[c]); + + FC_CHdata[c].ch->paula.period = FC_CHdata[c].period; + FC_CHdata[c].ch->paula.volume = FC_CHdata[c].volume; + FC_CHdata[c].ch->updatePerVol(); + + if (FC_CHdata[c].repeatDelay != 0) { + if (--FC_CHdata[c].repeatDelay == 1) { + FC_CHdata[c].repeatDelay = 0; + FC_CHdata[c].ch->paula.start = FC_CHdata[c].pSampleStart + FC_CHdata[c].repeatOffset; + FC_CHdata[c].ch->paula.length = FC_CHdata[c].repeatLength; + FC_CHdata[c].ch->takeNextBuf(); + } + } + } + + // Finally decide which audio channels to start. + // This could be moved into previous loop. + for (int c = 0; c < 4; c++) { + // Enable channel? Else, do not touch it. + if ((FC_admin.dmaFlags & (1 << c)) != 0) { + FC_CHdata[c].ch->on(); + } + } +} + +// -------------------------------------------------------------------------- + +void FC_nextNote(_FC_CHdata & FC_CHXdata) +{ + // Get offset to (or address of) current pattern position. + udword pattOffs = FC_CHXdata.pattStart + FC_CHXdata.pattPos; + + // Check for pattern end or whether pattern BREAK + // command is set. + if (FC_CHXdata.pattPos == PATTERN_LENGTH || (isFC14 && fcBuf[pattOffs] == PATTERN_BREAK)) { + // End pattern. + +#if defined(DEBUG3) + if (fcBuf[pattOffs] == PATTERN_BREAK) + cout << "--- PATTERN BREAK ---" << endl; +#endif + + // (NOTE) In order for pattern break to work correctly, the + // pattern break value 0x49 must be at the same position in + // each of the four patterns which are currently activated + // for the four voices. + // + // Alternatively, one could advance all voices to the next + // track step here in a 4-voice loop to make sure voices + // stay in sync. + + FC_CHXdata.pattPos = 0; + + // Advance one step in track table. + FC_CHXdata.trackPos += TRACKTAB_ENTRY_LENGTH; // 0x000d + udword trackOffs = FC_CHXdata.trackStart + FC_CHXdata.trackPos; + + // (BUG-FIX) Some FC players here apply a normal + // compare-if-equal which is not accurate enough and + // can cause the player to step beyond the song end. + // + // (BUG-FIX) Some FC14 modules have a pattern table length + // which is not a multiple of 13. Hence we check whether + // the currently activated table line would fit. + + if ((trackOffs + 12) >= FC_CHXdata.trackEnd) // pattern table end? + { + FC_CHXdata.trackPos = 0; // restart by default + trackOffs = FC_CHXdata.trackStart; + + FC_songEnd = true; + + // (NOTE) Some songs better stop here or reset all + // channels to cut off any pending sounds. + } + // Step Voice 1 Voice 2 Voice 3 Voice 4 Speed + // SP PT TR ST PT TR ST PT TR ST PT TR ST RS + // + // SP = STEP + // PT = PATTERN + // TR = TRANSPOSE + // ST = SOUND TRANSPOSE + // RS = REPLAY SPEED + + // Decide whether to read new song speed. + if (++FC_admin.RScount == 5) { + FC_admin.RScount = 1; + ubyte newSpeed = fcBuf[trackOffs + 12]; // RS (replay speed) + if (newSpeed != 0) // 0 would be underflow + { + FC_admin.count = FC_admin.speed = newSpeed; + } + } + + uword pattern = fcBuf[trackOffs++]; // PT + FC_CHXdata.transpose = fcBufS[trackOffs++]; + FC_CHXdata.soundTranspose = fcBufS[trackOffs++]; + + FC_CHXdata.pattStart = FC_admin.offsets.patterns + (pattern << 6); + // Get new pattern pointer (pattPos is 0 already, see above). + pattOffs = FC_CHXdata.pattStart; + } +#if defined(DEBUG2) + if (FC_CHXdata.dmaMask == 1 && FC_CHXdata.pattPos == 0) { + cout << endl; + cout << "Step = " << hex << setw(4) << setfill('0') << FC_CHXdata.trackPos / TRACKTAB_ENTRY_LENGTH; + cout << " | " << hex << setw(5) << setfill('0') << (int) FC_CHXdata. + trackStart << ", " << (int) (FC_CHXdata.trackStart + + FC_CHXdata.trackPos) << ", " << (int) FC_CHXdata.trackEnd << endl; + udword tmp = FC_CHXdata.trackStart + FC_CHXdata.trackPos; + for (int t = 0; t < 13; ++t) + cout << hex << setw(2) << setfill('0') << (int) fcBuf[tmp++] << ' '; + cout << endl; + cout << endl; + } + + cout << hex << setw(2) << setfill('0') << (int) fcBuf[pattOffs] << ' ' << setw(2) << (int) fcBuf[pattOffs + 1]; + if (FC_CHXdata.dmaMask != 8) + cout << " | "; +#endif + + // Process pattern entry. + + ubyte note = fcBuf[pattOffs++]; + ubyte info1 = fcBuf[pattOffs]; // info byte #1 + + if (note != 0) { + FC_CHXdata.portaOffs = 0; // reset portamento offset + FC_CHXdata.portaInfo = 0; // stop port., erase old parameter + + // (BUG-FIX) Disallow signed underflow here. + FC_CHXdata.noteValue = note & 0x7f; + + // (NOTE) Since first note is 0x01, first period at array + // offset 0 cannot be accessed directly (i.e. without adding + // transpose values from track table or modulation sequence). + + // Disable channel right now. + FC_CHXdata.ch->off(); + // Later enable channel. + FC_admin.dmaFlags |= FC_CHXdata.dmaMask; + + // Pattern offset stills points to info byte #1. + // Get instrument/volModSeq number from info byte #1 + // and add sound transpose value from track table. + uword sound = (fcBuf[pattOffs] & 0x3f) + FC_CHXdata.soundTranspose; + // + // (FC14 BUG-FIX) Better mask here to take care of overflow. + // + sound &= 0x3f; + + // (NOTE) Some FC players here put pattern info byte #1 + // into an unused byte variable at structure offset 9. + + udword seqOffs; // the modulation sequence for this sound + + if (sound > (FC_admin.usedVolModSeqs - 1)) { + seqOffs = FC_admin.offsets.FC_silent; + } else { + seqOffs = FC_admin.offsets.volModSeqs + (sound << 6); + } + FC_CHXdata.envelopeSpeed = FC_CHXdata.envelopeCount = fcBuf[seqOffs++]; + // Get sound modulation sequence number. + sound = fcBuf[seqOffs++]; + FC_CHXdata.vibSpeed = fcBuf[seqOffs++]; + FC_CHXdata.vibFlag = 0x40; // vibrato UP at start + FC_CHXdata.vibAmpl = FC_CHXdata.vibCurOffs = fcBuf[seqOffs++]; + FC_CHXdata.vibDelay = fcBuf[seqOffs++]; + FC_CHXdata.volSeq = seqOffs; + FC_CHXdata.volSeqPos = 0; + FC_CHXdata.volSustainTime = 0; + + if (sound > (FC_admin.usedSndModSeqs - 1)) { + // (NOTE) Silent sound modulation sequence is different + // from silent instrument definition sequence. + seqOffs = FC_admin.offsets.FC_silent + 1; + } else { + seqOffs = FC_admin.offsets.sndModSeqs + (sound << 6); + } + FC_CHXdata.sndSeq = seqOffs; + FC_CHXdata.sndSeqPos = 0; + FC_CHXdata.sndModSustainTime = 0; + } + // Portamento: bit 7 set = ON, bit 6 set = OFF, bits 5-0 = speed + // New note resets and clears portamento working values. + + if ((info1 & 0x40) != 0) // portamento OFF? + { + FC_CHXdata.portaInfo = 0; // stop port., erase old parameter + } + + if ((info1 & 0x80) != 0) // portamento ON? + { + // + // (FC14 BUG-FIX) Kill portamento ON/OFF bits. + // + // Get portamento speed from info byte #2. + // Info byte #2 is info byte #1 in next line of pattern, + // Therefore the +2 offset. + FC_CHXdata.portaInfo = fcBuf[pattOffs + 2] & 0x3f; + } + // Advance to next pattern entry. + FC_CHXdata.pattPos += 2; +} + +// -------------------------------------------------------------------------- +// The order of func/proc calls might be confusing, but is necessary +// to simulate JMP instructions in the original player code without +// making use of ``goto''. + +void FC_processModulation(_FC_CHdata &); +void FC_readModCommand(_FC_CHdata &); + +void FC_processPerVol(_FC_CHdata &); + +inline void FC_setWave(_FC_CHdata & FC_CHXdata, ubyte num) +{ + FC_CHXdata.pSampleStart = FC_SOUNDinfo.snd[num].start; + FC_CHXdata.ch->paula.start = FC_SOUNDinfo.snd[num].start; + FC_CHXdata.ch->paula.length = FC_SOUNDinfo.snd[num].len; + FC_CHXdata.ch->takeNextBuf(); + FC_CHXdata.repeatOffset = FC_SOUNDinfo.snd[num].repOffs; + FC_CHXdata.repeatLength = FC_SOUNDinfo.snd[num].repLen; + FC_CHXdata.repeatDelay = 3; +} + +inline void FC_readSeqTranspose(_FC_CHdata & FC_CHXdata) +{ + FC_CHXdata.seqTranspose = fcBufS[FC_CHXdata.sndSeq + FC_CHXdata.sndSeqPos]; + ++FC_CHXdata.sndSeqPos; +} + +void FC_processModulation(_FC_CHdata & FC_CHXdata) +{ + if (FC_CHXdata.sndModSustainTime != 0) { + --FC_CHXdata.sndModSustainTime; + FC_processPerVol(FC_CHXdata); + return; + } + FC_readModCommand(FC_CHXdata); +} + +void FC_readModCommand(_FC_CHdata & FC_CHXdata) +{ + udword seqOffs = FC_CHXdata.sndSeq + FC_CHXdata.sndSeqPos; + + // (NOTE) After each command (except LOOP, END, SUSTAIN, + // and NEWVIB) follows a transpose value. + + if (fcBuf[seqOffs] == SNDMOD_LOOP) { + FC_CHXdata.sndSeqPos = fcBuf[seqOffs + 1] & 0x3f; + // Calc new sequence address. + seqOffs = FC_CHXdata.sndSeq + FC_CHXdata.sndSeqPos; + } + + if (fcBuf[seqOffs] == SNDMOD_END) { + FC_processPerVol(FC_CHXdata); + return; + } + + else if (fcBuf[seqOffs] == SNDMOD_SETWAVE) { + // Disable channel right now. + FC_CHXdata.ch->off(); + // Enable channel later. + FC_admin.dmaFlags |= FC_CHXdata.dmaMask; + // Restart envelope. + FC_CHXdata.volSeqPos = 0; + FC_CHXdata.envelopeCount = 1; + + FC_setWave(FC_CHXdata, fcBuf[seqOffs + 1]); + FC_CHXdata.sndSeqPos += 2; + + FC_readSeqTranspose(FC_CHXdata); + + FC_processPerVol(FC_CHXdata); + return; + } + + else if (fcBuf[seqOffs] == SNDMOD_CHANGEWAVE) { + FC_setWave(FC_CHXdata, fcBuf[seqOffs + 1]); + FC_CHXdata.sndSeqPos += 2; + + FC_readSeqTranspose(FC_CHXdata); + + FC_processPerVol(FC_CHXdata); + return; + } + + else if (fcBuf[seqOffs] == SNDMOD_SETPACKWAVE) { + // Disable channel right now. + FC_CHXdata.ch->off(); + // Enable channel later. + FC_admin.dmaFlags |= FC_CHXdata.dmaMask; + + uword i = fcBuf[seqOffs + 1]; // sample/pack nr. + if (i < 10) // sample or waveform? + { + udword sndOffs = FC_SOUNDinfo.snd[i].start - fcBuf.tellBegin(); + // "SSMP"? sample-pack? + if (fcBuf[sndOffs] == 0x53 && fcBuf[sndOffs + 1] == 0x53 && + fcBuf[sndOffs + 2] == 0x4D && fcBuf[sndOffs + 3] == 0x50) { + sndOffs += 4; + // Skip header and 10*2 info blocks of size 16. + udword smpStart = sndOffs + 320; + i = fcBuf[seqOffs + 2]; // sample nr. + i <<= 4; // *16 (block size) + sndOffs += i; + smpStart += readEndian(fcBuf[sndOffs], fcBuf[sndOffs + 1], + fcBuf[sndOffs + 2], fcBuf[sndOffs + 3]); + FC_CHXdata.pSampleStart = smpStart + fcBuf.tellBegin(); + FC_CHXdata.ch->paula.start = FC_CHXdata.pSampleStart; + FC_CHXdata.ch->paula.length = readEndian(fcBuf[sndOffs + 4], fcBuf[sndOffs + 5]); + FC_CHXdata.ch->takeNextBuf(); + + // (FC14 BUG-FIX): Players set period here by accident. + // m68k code move.l 4(a2),4(a3), but 6(a3) is period. + + FC_CHXdata.repeatOffset = readEndian(fcBuf[sndOffs + 6], fcBuf[sndOffs + 7]); + FC_CHXdata.repeatLength = readEndian(fcBuf[sndOffs + 8], fcBuf[sndOffs + 9]); + if (FC_CHXdata.repeatLength == 1) { + // Erase first word behind sample to avoid beeping + // one-shot mode upon true emulation of Paula. + fcBuf[smpStart + FC_CHXdata.repeatOffset] = 0; + fcBuf[smpStart + FC_CHXdata.repeatOffset + 1] = 0; + } + // Restart envelope. + FC_CHXdata.volSeqPos = 0; + FC_CHXdata.envelopeCount = 1; + // + FC_CHXdata.repeatDelay = 3; + } + } + FC_CHXdata.sndSeqPos += 3; + + FC_readSeqTranspose(FC_CHXdata); + + FC_processPerVol(FC_CHXdata); + return; + } + + else if (fcBuf[seqOffs] == SNDMOD_NEWSEQ) { + uword seq = fcBuf[seqOffs + 1]; + FC_CHXdata.sndSeq = FC_admin.offsets.sndModSeqs + (seq << 6); + FC_CHXdata.sndSeqPos = 0; + // Recursive call (ought to be protected via a counter). + FC_readModCommand(FC_CHXdata); + return; + } + + else if (fcBuf[seqOffs] == SNDMOD_SUSTAIN) { + FC_CHXdata.sndModSustainTime = fcBuf[seqOffs + 1]; + FC_CHXdata.sndSeqPos += 2; + + // Decrease sustain counter and decide whether to continue + // to envelope modulation. + // Recursive call (ought to be protected via a counter). + FC_processModulation(FC_CHXdata); + return; + } + + else if (fcBuf[seqOffs] == SNDMOD_NEWVIB) { + FC_CHXdata.vibSpeed = fcBuf[seqOffs + 1]; + FC_CHXdata.vibAmpl = fcBuf[seqOffs + 2]; + FC_CHXdata.sndSeqPos += 3; + + FC_processPerVol(FC_CHXdata); + return; + } + + else if (fcBuf[seqOffs] == SNDMOD_PITCHBEND) { + FC_CHXdata.pitchBendSpeed = fcBufS[seqOffs + 1]; + FC_CHXdata.pitchBendTime = fcBuf[seqOffs + 2]; + FC_CHXdata.sndSeqPos += 3; + + FC_readSeqTranspose(FC_CHXdata); + + FC_processPerVol(FC_CHXdata); + return; + } + + else // Not a command, but a transpose value. + { + FC_readSeqTranspose(FC_CHXdata); + + FC_processPerVol(FC_CHXdata); + } +} + +// -------------------------------------------------------------------------- + +void FC_processPerVol(_FC_CHdata &); +void FC_volSlide(_FC_CHdata &); + +// (NOTE) This part of the code is not protected against a deadlock +// caused by damaged music module data. + +void FC_volSlide(_FC_CHdata & FC_CHXdata) +{ + // Following flag divides the volume sliding speed by two. + FC_CHXdata.volSlideDelayFlag ^= 0xff; // = NOT + if (FC_CHXdata.volSlideDelayFlag != 0) { + --FC_CHXdata.volSlideTime; + FC_CHXdata.volume += FC_CHXdata.volSlideSpeed; + if (FC_CHXdata.volume < 0) { + FC_CHXdata.volume = FC_CHXdata.volSlideTime = 0; + } + // (NOTE) Most FC players do not check whether Paula's + // maximum volume level is exceeded. + + if (FC_CHXdata.volume > 64) { + FC_CHXdata.volume = 64; + FC_CHXdata.volSlideTime = 0; + } + } +} + +void FC_processPerVol(_FC_CHdata & FC_CHXdata) +{ + bool repeatVolSeq; // JUMP/GOTO - WHILE conversion + do { + repeatVolSeq = false; + + // Sustain current volume level? NE => yes, EQ => no. + if (FC_CHXdata.volSustainTime != 0) { + --FC_CHXdata.volSustainTime; + } + // Slide volume? NE => yes, EQ => no. + else if (FC_CHXdata.volSlideTime != 0) { + FC_volSlide(FC_CHXdata); + } + // Time to set next volume level? NE => no, EQ => yes. + else if (--FC_CHXdata.envelopeCount == 0) { + FC_CHXdata.envelopeCount = FC_CHXdata.envelopeSpeed; + + bool readNextVal; // JUMP/GOTO - WHILE conversion + do { + readNextVal = false; + + udword seqOffs = FC_CHXdata.volSeq + FC_CHXdata.volSeqPos; + ubyte command = fcBuf[seqOffs]; + + switch (command) { + case ENVELOPE_SUSTAIN: + { + FC_CHXdata.volSustainTime = fcBuf[seqOffs + 1]; + FC_CHXdata.volSeqPos += 2; + // This shall loop to beginning of proc. + repeatVolSeq = true; + break; + } + case ENVELOPE_SLIDE: + { + FC_CHXdata.volSlideSpeed = fcBuf[seqOffs + 1]; + FC_CHXdata.volSlideTime = fcBuf[seqOffs + 2]; + FC_CHXdata.volSeqPos += 3; + FC_volSlide(FC_CHXdata); + break; + } + case ENVELOPE_LOOP: + { + // Range check should be done here. + FC_CHXdata.volSeqPos = (fcBuf[seqOffs + 1] - 5) & 0x3f; + // (FC14 BUG) Some FC players here do not read a + // parameter at the new sequence position. They + // leave the pos value in d0, which then passes + // as parameter through all the command comparisons + // (this switch statement) in FC_effa() up to + // FC_effno(). + readNextVal = true; + break; + } + case ENVELOPE_END: + { + break; + } + default: + { + // Read volume value and advance. + FC_CHXdata.volume = fcBuf[seqOffs]; + ++FC_CHXdata.volSeqPos; + break; + } + } + } + while (readNextVal); + } + } + while (repeatVolSeq); + + // Now determine note and period value to play. + + sdword tmp0, tmp1; + + tmp0 = FC_CHXdata.seqTranspose; + if (tmp0 >= 0) { + tmp0 += FC_CHXdata.noteValue; + tmp0 += FC_CHXdata.transpose; + // (NOTE) Permit underflow at this point. Some modules + // need it because--for some unknown reason--they work + // with huge values such as transpose = 0x8c. + } + // else: lock note (i.e. transpose value from sequence is note to play) + +#if defined(DEBUG2) + if ((tmp0 & 0x7f) > 0x53) { + cout << "X "; +#if defined(DEBUG3) + cout << "=== NOTE > 0x53 ===" << endl; +#endif + } +#endif + + tmp0 &= 0x7f; + tmp1 = tmp0 << 1; // *2 (later used to find octave) + tmp0 = FC_periods[tmp0]; + + // Vibrato. + // + // Vibrato offset changes between [0,1,...,2*vibAmpl] + // Offset minus vibAmpl is value to apply. + + if (FC_CHXdata.vibDelay == 0) { + uword noteTableOffset = tmp1; // tmp1 is note*2; + + sword vibDelta = FC_CHXdata.vibAmpl; + vibDelta <<= 1; // pos/neg amplitude delta + + // vibFlag bit 5: 0 => vibrato down, 1 => vibrato up + // + // (NOTE) In the original player code the vibrato half speed delay + // flag (D6) in bit 0 is toggled but never checked, because the + // vibrato flag byte will never get negative. + + tmp1 = FC_CHXdata.vibCurOffs; + + if ((FC_CHXdata.vibFlag & (1 << 5)) == 0) { + tmp1 -= FC_CHXdata.vibSpeed; + // Lowest value reached? + if (tmp1 < 0) { + tmp1 = 0; + FC_CHXdata.vibFlag |= (1 << 5); // switch to vibrato up + } + } else { + tmp1 += FC_CHXdata.vibSpeed; + // Amplitude reached? + if (tmp1 > vibDelta) { + tmp1 = vibDelta; + FC_CHXdata.vibFlag &= ~(1 << 5); // switch to vibrato down + } + } + + FC_CHXdata.vibCurOffs = tmp1; + + // noteTableOffset is note*2; + + tmp1 -= FC_CHXdata.vibAmpl; + + // Octave 5 at period table byte-offset 96 contains the highest + // period only. 96+160 = 256. This next bit ensures that vibrato + // does not exceed the five octaves in the period table. + // Octave 6 (but lowest!) is FC14 only. + noteTableOffset += 160; // + $a0 + while (noteTableOffset < 256) { + tmp1 <<= 1; // double vibrato value for each octave + noteTableOffset += 2 * 12; // advance octave index + }; + tmp0 += tmp1; // apply vibrato to period + + // (NOTE) Questionable code here in the original player sources. + // Although bit 0 of D6 is toggled, the code (see above) that + // checks it is unreachable. + } else { + --FC_CHXdata.vibDelay; + + // (NOTE) Questionable code here in existing FC players. Although + // bit 0 of D6 is toggled, the code that checks it is unreachable. + // That bad code has not been converted. + } + + // Portamento. + + // (NOTE) As of FC 1.4 portamento plays at half speed compared to + // old versions. + + // Following flag divides the portamento speed by two + // for FC14 modules. + FC_CHXdata.portDelayFlag ^= 0xff; // = NOT + if (isSMOD || FC_CHXdata.portDelayFlag != 0) { + sbyte param = FC_CHXdata.portaInfo; + if (param != 0) { + if (param > 0x1f) // > 0x20 = portamento down + { + param &= 0x1f; + param = (-param); + } + FC_CHXdata.portaOffs -= param; + } + } + // Pitchbend. + + // Following flag divides the pitch bending speed by two. + FC_CHXdata.pitchBendDelayFlag ^= 0xff; // not + if (FC_CHXdata.pitchBendDelayFlag != 0) { + if (FC_CHXdata.pitchBendTime != 0) { + --FC_CHXdata.pitchBendTime; + sbyte speed = FC_CHXdata.pitchBendSpeed; + if (speed != 0) { + FC_CHXdata.portaOffs -= speed; + } + } + } + + tmp0 += FC_CHXdata.portaOffs; + if (tmp0 <= 0x0070) { + tmp0 = 0x0071; + } + // (NOTE) This should be 0x1ac0, but the extra low octave has + // been added in FC 1.4 and is a non-working hack due to this + // range-check (see header file). + if (tmp0 > 0x0d60) { + tmp0 = 0x0d60; + } + FC_CHXdata.period = tmp0; +} diff --git a/FC.h b/FC.h new file mode 100644 index 0000000..f99d57c --- /dev/null +++ b/FC.h @@ -0,0 +1,14 @@ +#ifndef FC_H +#define FC_H + +#include "MyTypes.h" +#include "MyEndian.h" +#include "LamePaula.h" + +extern bool FC_init(void*,udword,int,int); +extern void FC_play(); +extern void FC_off(); + +extern bool FC_songEnd; + +#endif // FC_H diff --git a/FC_Defs.h b/FC_Defs.h new file mode 100644 index 0000000..3efe69a --- /dev/null +++ b/FC_Defs.h @@ -0,0 +1,124 @@ +#ifndef FC_DEFS_H +#define FC_DEFS_H + +#include "FC.h" +#include "MyTypes.h" +#include "MyEndian.h" +#include "LamePaula.h" +#include "SmartPtr.h" + +static const uword SMOD_SONGTAB_OFFSET = 0x0064; // 100 + +static const uword FC14_SMPHEADERS_OFFSET = 0x0028; // 40 +static const uword FC14_WAVEHEADERS_OFFSET = 0x0064; // 100 +static const uword FC14_SONGTAB_OFFSET = 0x00b4; // 180 + +static const uword TRACKTAB_ENTRY_LENGTH = 0x000d; // 3*4+1 +static const uword PATTERN_LENGTH = 0x0040; // 32*2 +static const ubyte PATTERN_BREAK = 0x49; + +static const ubyte SEQ_END = 0xE1; + +static const ubyte SNDMOD_LOOP = 0xE0; +static const ubyte SNDMOD_END = SEQ_END; +static const ubyte SNDMOD_SETWAVE = 0xE2; +static const ubyte SNDMOD_CHANGEWAVE = 0xE4; +static const ubyte SNDMOD_NEWVIB = 0xE3; +static const ubyte SNDMOD_SUSTAIN = 0xE8; +static const ubyte SNDMOD_NEWSEQ = 0xE7; +static const ubyte SNDMOD_SETPACKWAVE = 0xE9; +static const ubyte SNDMOD_PITCHBEND = 0xEA; + +static const ubyte ENVELOPE_LOOP = 0xE0; +static const ubyte ENVELOPE_END = SEQ_END; +static const ubyte ENVELOPE_SUSTAIN = 0xE8; +static const ubyte ENVELOPE_SLIDE = 0xEA; + +struct _FC_admin +{ + uword dmaFlags; // which audio channels to turn on (AMIGA related) + ubyte count; // speed count + ubyte speed; // speed + ubyte RScount; + bool isEnabled; // player on => true, else false + + struct _moduleOffsets + { + udword trackTable; + udword patterns; + udword sndModSeqs; + udword volModSeqs; + udword FC_silent; + } offsets; + + int usedPatterns; + int usedSndModSeqs; + int usedVolModSeqs; +} +FC_admin; + +struct FC_SOUNDinfo_internal +{ + const ubyte* start; + uword len, repOffs, repLen; + // rest was place-holder (6 bytes) +}; + +struct _FC_SOUNDinfo +{ + // 10 samples/sample-packs + // 80 waveforms + FC_SOUNDinfo_internal snd[10+80]; +} +FC_SOUNDinfo; + +struct _FC_CHdata +{ + channel* ch; // paula and mixer interface + + uword dmaMask; + + udword trackStart; // track/step pattern table + udword trackEnd; + uword trackPos; + + udword pattStart; + uword pattPos; + + sbyte transpose; // TR + sbyte soundTranspose; // ST + sbyte seqTranspose; // from sndModSeq + + ubyte noteValue; + + sbyte pitchBendSpeed; + ubyte pitchBendTime, pitchBendDelayFlag; + + ubyte portaInfo, portDelayFlag; + sword portaOffs; + + udword volSeq; + uword volSeqPos; + + ubyte volSlideSpeed, volSlideTime, volSustainTime; + + ubyte envelopeSpeed, envelopeCount; + + udword sndSeq; + uword sndSeqPos; + + ubyte sndModSustainTime; + + ubyte vibFlag, vibDelay, vibSpeed, + vibAmpl, vibCurOffs, volSlideDelayFlag; + + sbyte volume; + uword period; + + const ubyte* pSampleStart; + uword repeatOffset; + uword repeatLength; + uword repeatDelay; +}; + +#endif // FC_DEFS_H diff --git a/LamePaula.cpp b/LamePaula.cpp new file mode 100644 index 0000000..d184b6d --- /dev/null +++ b/LamePaula.cpp @@ -0,0 +1,469 @@ +// History: This once was a simple mixer (it still is ;) that was +// used in a private MOD/TFMX player and was configured at run-time +// to mix up to 32 individual audio channels. In this particular +// version 16-bit and mono are moved back in. Most of that old code +// has not been touched though because it has done its job well even +// if it looks quite ugly. So, please bear with me if you find things +// in here that are not used by the FC player. + +#include "LamePaula.h" + +uword MIXER_patterns; +uword MIXER_samples; +uword MIXER_voices; +ubyte MIXER_speed; +ubyte MIXER_bpm; +ubyte MIXER_count; +uword MIXER_minPeriod; +uword MIXER_maxPeriod; + +struct channel logChannel[32]; + +void (*mixerPlayRout)(); +const char* mixerFormatName = 0; + +void* mixerFill8bitMono( void*, udword ); +void* mixerFill8bitStereo( void*, udword ); +void* mixerFill16bitMono( void*, udword ); +void* mixerFill16bitStereo( void*, udword ); + +void* (*mixerFillRout)( void*, udword ) = &mixerFill8bitMono; + +void mixerSetReplayingSpeed(); + +static const udword AMIGA_CLOCK_PAL = 3546895; +static const udword AMIGA_CLOCK_NTSC = 3579546; +static const udword AMIGA_CLOCK = AMIGA_CLOCK_PAL; + +static sbyte mix8[256]; +static sword mix16[256]; + +static ubyte zero8bit; // ``zero''-sample +static uword zero16bit; // either signed or unsigned + +static udword pcmFreq; +static ubyte bufferScale; + +static udword samplesAdd; +static udword samplesPnt; +static uword samples, samplesOrg; + +static udword toFill = 0; + +static ubyte emptySample; + + +void channel::takeNextBuf() +{ + if (!isOn) + { + // If channel is off, take sample START parameters. + start = paula.start; + length = paula.length; + length <<= 1; + if (length == 0) // Paula would play $FFFF words (!) + length = 1; + end = start+length; + } + + repeatStart = paula.start; + repeatLength = paula.length; + repeatLength <<= 1; + if (repeatLength == 0) // Paula would play $FFFF words (!) + repeatLength = 1; + repeatEnd = repeatStart+repeatLength; +} + +void channel::on() +{ + takeNextBuf(); + + isOn = true; +} + +void channel::updatePerVol() +{ + if (paula.period != curPeriod) + { + period = paula.period; // !!! + curPeriod = paula.period; + if (curPeriod != 0) + { + stepSpeed = (AMIGA_CLOCK/pcmFreq) / curPeriod; + stepSpeedPnt = (((AMIGA_CLOCK/pcmFreq)%curPeriod)*65536) / curPeriod; + } + else + { + stepSpeed = stepSpeedPnt = 0; + } + } + + volume = paula.volume; + if (volume > 64) + { + volume = 64; + } +} + +void mixerInit(udword freq, int bits, int channels, uword zero) +{ + pcmFreq = freq; + bufferScale = 0; + + if (bits == 8) + { + zero8bit = zero; + if (channels == 1) + { + mixerFillRout = &mixerFill8bitMono; + } + else // if (channels == 2) + { + mixerFillRout = &mixerFill8bitStereo; + ++bufferScale; + } + } + else // if (bits == 16) + { + zero16bit = zero; + ++bufferScale; + if (channels == 1) + { + mixerFillRout = &mixerFill16bitMono; + } + else // if (channels == 2) + { + mixerFillRout = &mixerFill16bitStereo; + ++bufferScale; + } + } + + uword ui; + long si; + uword voicesPerChannel = MIXER_voices/channels; + + // Input samples: 80,81,82,...,FE,FF,00,01,02,...,7E,7F + // Array: 00/x, 01/x, 02/x,...,7E/x,7F/x,80/x,81/x,82/x,...,FE/x/,FF/x + ui = 0; + si = 0; + while (si++ < 128) + mix8[ui++] = (sbyte)(si/voicesPerChannel); + + si = -128; + while (si++ < 0) + mix8[ui++] = (sbyte)(si/voicesPerChannel); + + // Input samples: 80,81,82,...,FE,FF,00,01,02,...,7E,7F + // Array: 0/x, 100/x, 200/x, ..., FF00/x + ui = 0; + si = 0; + while (si < 128*256) + { + mix16[ui++] = (sword)(si/voicesPerChannel); + si += 256; + } + si = -128*256; + while (si < 0) + { + mix16[ui++] = (sword)(si/voicesPerChannel); + si += 256; + } + + for ( int i = 0; i < 32; i++ ) + { + logChannel[i].start = &emptySample; + logChannel[i].end = &emptySample+1; + logChannel[i].repeatStart = &emptySample; + logChannel[i].repeatEnd = &emptySample+1; + logChannel[i].length = 1; + logChannel[i].curPeriod = 0; + logChannel[i].stepSpeed = 0; + logChannel[i].stepSpeedPnt = 0; + logChannel[i].stepSpeedAddPnt = 0; + logChannel[i].volume = 0; + logChannel[i].off(); + } + + mixerSetReplayingSpeed(); +} + +void mixerFillBuffer( void* buffer, udword bufferLen ) +{ + // Both, 16-bit and stereo samples take more memory. + // Hence fewer samples fit into the buffer. + bufferLen >>= bufferScale; + + while ( bufferLen > 0 ) + { + if ( toFill > bufferLen ) + { + buffer = (*mixerFillRout)(buffer, bufferLen); + toFill -= bufferLen; + bufferLen = 0; + } + else if ( toFill > 0 ) + { + buffer = (*mixerFillRout)(buffer, toFill); + bufferLen -= toFill; + toFill = 0; + } + + if ( toFill == 0 ) + { + (*mixerPlayRout)(); + + register udword temp = ( samplesAdd += samplesPnt ); + samplesAdd = temp & 0xFFFF; + toFill = samples + ( temp > 65535 ); + + for ( int i = 0; i < MIXER_voices; i++ ) + { + if ( logChannel[i].period != logChannel[i].curPeriod ) + { + logChannel[i].curPeriod = logChannel[i].period; + if (logChannel[i].curPeriod != 0) + { + logChannel[i].stepSpeed = (AMIGA_CLOCK/pcmFreq) / logChannel[i].period; + logChannel[i].stepSpeedPnt = (((AMIGA_CLOCK/pcmFreq) % logChannel[i].period ) * 65536 ) / logChannel[i].period; + } + else + { + logChannel[i].stepSpeed = logChannel[i].stepSpeedPnt = 0; + } + } + } + } + + } // while bufferLen +} + +void mixerSetReplayingSpeed() +{ + samples = ( samplesOrg = pcmFreq / 50 ); + samplesPnt = (( pcmFreq % 50 ) * 65536 ) / 50; + samplesAdd = 0; +} + +void mixerSetBpm( uword bpm ) +{ + uword callsPerSecond = (bpm * 2) / 5; + samples = ( samplesOrg = pcmFreq / callsPerSecond ); + samplesPnt = (( pcmFreq % callsPerSecond ) * 65536 ) / callsPerSecond; + samplesAdd = 0; +} + +void* mixerFill8bitMono( void* buffer, udword numberOfSamples ) +{ + ubyte* buffer8bit = (ubyte*)buffer; + for ( int i = 0; i < MIXER_voices; i++ ) + { + buffer8bit = (ubyte*)buffer; + + for ( udword n = numberOfSamples; n > 0; n-- ) + { + if ( i == 0 ) + { + *buffer8bit = zero8bit; + } + logChannel[i].stepSpeedAddPnt += logChannel[i].stepSpeedPnt; + logChannel[i].start += ( logChannel[i].stepSpeed + ( logChannel[i].stepSpeedAddPnt > 65535 ) ); + logChannel[i].stepSpeedAddPnt &= 65535; + if ( logChannel[i].start < logChannel[i].end ) + { + *buffer8bit += ( logChannel[i].volume * mix8[*logChannel[i].start] ) >> 6; + } + else + { + if ( logChannel[i].looping ) + { + logChannel[i].start = logChannel[i].repeatStart; + logChannel[i].end = logChannel[i].repeatEnd; + if ( logChannel[i].start < logChannel[i].end ) + { + *buffer8bit += ( logChannel[i].volume * mix8[*logChannel[i].start] ) >> 6; + } + } + } + buffer8bit++; + } + } + return(buffer8bit); +} + +void* mixerFill8bitStereo( void* buffer, udword numberOfSamples ) +{ + ubyte* buffer8bit = (ubyte*)buffer; + for ( int i = 1; i < MIXER_voices; i+=2 ) + { + buffer8bit = ((ubyte*)buffer)+1; + + for ( udword n = numberOfSamples; n > 0; n-- ) + { + if ( i == 1 ) + { + *buffer8bit = zero8bit; + } + logChannel[i].stepSpeedAddPnt += logChannel[i].stepSpeedPnt; + logChannel[i].start += ( logChannel[i].stepSpeed + ( logChannel[i].stepSpeedAddPnt > 65535 ) ); + logChannel[i].stepSpeedAddPnt &= 65535; + if ( logChannel[i].start < logChannel[i].end ) + { + *buffer8bit += ( logChannel[i].volume * mix8[*logChannel[i].start] ) >> 6; + } + else + { + if ( logChannel[i].looping ) + { + logChannel[i].start = logChannel[i].repeatStart; + logChannel[i].end = logChannel[i].repeatEnd; + if ( logChannel[i].start < logChannel[i].end ) + { + *buffer8bit += ( logChannel[i].volume * mix8[*logChannel[i].start] ) >> 6; + } + } + } + buffer8bit += 2; + } + } + for ( int i = 0; i < MIXER_voices; i+=2 ) + { + buffer8bit = (ubyte*)buffer; + + for ( udword n = numberOfSamples; n > 0; n-- ) + { + if ( i == 0 ) + { + *buffer8bit = zero8bit; + } + logChannel[i].stepSpeedAddPnt += logChannel[i].stepSpeedPnt; + logChannel[i].start += ( logChannel[i].stepSpeed + ( logChannel[i].stepSpeedAddPnt > 65535 ) ); + logChannel[i].stepSpeedAddPnt &= 65535; + if ( logChannel[i].start < logChannel[i].end ) + { + *buffer8bit += ( logChannel[i].volume * mix8[*logChannel[i].start] ) >> 6; + } + else + { + if ( logChannel[i].looping ) + { + logChannel[i].start = logChannel[i].repeatStart; + logChannel[i].end = logChannel[i].repeatEnd; + if ( logChannel[i].start < logChannel[i].end ) + { + *buffer8bit += ( logChannel[i].volume * mix8[*logChannel[i].start] ) >> 6; + } + } + } + buffer8bit += 2; + } + } + return(buffer8bit); +} + +void* mixerFill16bitMono( void* buffer, udword numberOfSamples ) +{ + sword* buffer16bit = (sword*)buffer; + for ( int i = 0; i < MIXER_voices; i++ ) + { + buffer16bit = (sword*)buffer; + + for ( udword n = numberOfSamples; n > 0; n-- ) + { + if ( i == 0 ) + { + *buffer16bit = zero16bit; + } + logChannel[i].stepSpeedAddPnt += logChannel[i].stepSpeedPnt; + logChannel[i].start += ( logChannel[i].stepSpeed + ( logChannel[i].stepSpeedAddPnt > 65535 ) ); + logChannel[i].stepSpeedAddPnt &= 65535; + if ( logChannel[i].start < logChannel[i].end ) + { + *buffer16bit += ( logChannel[i].volume * mix16[*logChannel[i].start] ) >> 6; + } + else + { + if ( logChannel[i].looping ) + { + logChannel[i].start = logChannel[i].repeatStart; + logChannel[i].end = logChannel[i].repeatEnd; + if ( logChannel[i].start < logChannel[i].end ) + { + *buffer16bit += ( logChannel[i].volume * mix16[*logChannel[i].start] ) >> 6; + } + } + } + buffer16bit++; + } + } + return(buffer16bit); +} + +void* mixerFill16bitStereo( void* buffer, udword numberOfSamples ) +{ + sword* buffer16bit = (sword*)buffer; + for ( int i = 1; i < MIXER_voices; i+=2 ) + { + buffer16bit = ((sword*)buffer)+1; + + for ( udword n = numberOfSamples; n > 0; n-- ) + { + if ( i == 1 ) + { + *buffer16bit = zero16bit; + } + logChannel[i].stepSpeedAddPnt += logChannel[i].stepSpeedPnt; + logChannel[i].start += ( logChannel[i].stepSpeed + ( logChannel[i].stepSpeedAddPnt > 65535 ) ); + logChannel[i].stepSpeedAddPnt &= 65535; + if ( logChannel[i].start < logChannel[i].end ) + { + *buffer16bit += ( logChannel[i].volume * mix16[*logChannel[i].start] ) >> 6; + } + else + { + if ( logChannel[i].looping ) + { + logChannel[i].start = logChannel[i].repeatStart; + logChannel[i].end = logChannel[i].repeatEnd; + if ( logChannel[i].start < logChannel[i].end ) + { + *buffer16bit += ( logChannel[i].volume * mix16[*logChannel[i].start] ) >> 6; + } + } + } + buffer16bit += 2; + } + } + for ( int i = 0; i < MIXER_voices; i+=2 ) + { + buffer16bit = (sword*)buffer; + + for ( udword n = numberOfSamples; n > 0; n-- ) + { + if ( i == 0 ) + { + *buffer16bit = zero16bit; + } + logChannel[i].stepSpeedAddPnt += logChannel[i].stepSpeedPnt; + logChannel[i].start += ( logChannel[i].stepSpeed + ( logChannel[i].stepSpeedAddPnt > 65535 ) ); + logChannel[i].stepSpeedAddPnt &= 65535; + if ( logChannel[i].start < logChannel[i].end ) + { + *buffer16bit += ( logChannel[i].volume * mix16[*logChannel[i].start] ) >> 6; + } + else + { + if ( logChannel[i].looping ) + { + logChannel[i].start = logChannel[i].repeatStart; + logChannel[i].end = logChannel[i].repeatEnd; + if ( logChannel[i].start < logChannel[i].end ) + { + *buffer16bit += ( logChannel[i].volume * mix16[*logChannel[i].start] ) >> 6; + } + } + } + buffer16bit += 2; + } + } + return(buffer16bit); +} diff --git a/LamePaula.h b/LamePaula.h new file mode 100644 index 0000000..48eb545 --- /dev/null +++ b/LamePaula.h @@ -0,0 +1,75 @@ +#ifndef LAMEPAULA_H +#define LAMEPAULA_H + +#include "MyTypes.h" + +extern uword MIXER_patterns; +extern uword MIXER_samples; +extern uword MIXER_voices; +extern ubyte MIXER_speed; +extern ubyte MIXER_bpm; +extern ubyte MIXER_count; +extern uword MIXER_minPeriod; +extern uword MIXER_maxPeriod; + +extern const char* mixerFormatName; +extern void (*mixerPlayRout)(); + +extern void mixerSetBpm( uword ); + +struct _paula +{ + // Paula + const ubyte* start; // start address + uword length; // length in 16-bit words + uword period; + uword volume; // 0-64 +}; + +class channel +{ + public: + _paula paula; + + bool isOn; + + const ubyte* start; + const ubyte* end; + udword length; + + const ubyte* repeatStart; + const ubyte* repeatEnd; + udword repeatLength; + + ubyte bitsPerSample; + uword volume; + uword period; + udword sampleFrequency; + bool sign; + bool looping; // whether to loop sample buffer continously (PAULA emu) + ubyte panning; + // + uword curPeriod; + udword stepSpeed; + udword stepSpeedPnt; + udword stepSpeedAddPnt; + + void off() + { + isOn = false; + } + + channel() + { + off(); + } + + void on(); + void takeNextBuf(); // take parameters from paula.* (or just to repeat.*) + void updatePerVol(); // period, volume +}; + +void mixerFillBuffer( void* buffer, udword bufferLen ); +void mixerInit(udword freq, int bits, int channels, uword zero); +void mixerSetReplayingSpeed(); +#endif // LAMEPAULA_H diff --git a/Main.cpp b/Main.cpp new file mode 100644 index 0000000..e69de29 diff --git a/Main_psp.cpp b/Main_psp.cpp new file mode 100644 index 0000000..0f4523c --- /dev/null +++ b/Main_psp.cpp @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +#include "FC.h" +#include "MyTypes.h" +#include "LamePaula.h" + +#define printf pspDebugScreenPrintf + +PSP_MODULE_INFO ("FCPLAY", 0, 1, 1); + +/* Exit callback */ +int +exitCallback (int arg1, int arg2, void *common) +{ + sceKernelExitGame (); + return 0; +} + +/* Callback thread */ +int +callbackThread (SceSize args, void *argp) +{ + int cbid; + + cbid = sceKernelCreateCallback ("Exit Callback", exitCallback, NULL); + sceKernelRegisterExitCallback (cbid); + sceKernelSleepThreadCB (); + + return 0; +} + +/* Sets up the callback thread and returns its thread id */ +int +setupCallbacks (void) +{ + int thid = 0; + + thid = sceKernelCreateThread ("update_thread", callbackThread, 0x11, + 0xFA0, THREAD_ATTR_USER, 0); + if (thid >= 0) { + sceKernelStartThread (thid, 0, 0); + } + return thid; +} + + +int done = 0; +int channel; + +int +audio_thread (SceSize args, void *argp) +{ + fprintf (stderr, "audio_thread: start\n"); + ubyte* sample_buffer; + sample_buffer = (ubyte *) malloc (1024 * 4); + while (!done) { + mixerFillBuffer(sample_buffer, 1024 * 4); + sceAudioOutputPannedBlocking(channel, PSP_AUDIO_VOLUME_MAX, PSP_AUDIO_VOLUME_MAX, sample_buffer); + } + free(sample_buffer); + return 0; +} + + +char infile[] = "host0:/songs/dextrous-synthtronic.fc4"; + + +int main(int argc, char *argv[]) +{ + char *file_buffer; + int file_len; + SceCtrlData pad; + pspDebugScreenInit (); + setupCallbacks (); + sceCtrlSetSamplingCycle (0); + sceCtrlSetSamplingMode (PSP_CTRL_MODE_ANALOG); + + printf ("--------------------------------------------------\n"); + printf ("FC Play by Optixx 2006\n"); + printf ("--------------------------------------------------\n"); + + + SceUID fd; + SceIoStat stat; + sceIoGetstat(infile,&stat); + file_len = stat.st_size; + printf("Open File '%s' (%i bytes) \n",infile,file_len); + file_buffer = (char*)malloc(file_len); + fprintf(stderr,"Song buffer=%p\n",file_buffer); + if ((fd = sceIoOpen (infile, PSP_O_RDONLY, 0777))) { + file_len = + sceIoRead (fd, file_buffer,file_len); + sceIoClose (fd); + } else { + fprintf(stderr,"Cannot open '%s'\n",infile); + return -1; + } + printf("Song '%s' loaded\n",infile); + + + channel = sceAudioChReserve(PSP_AUDIO_NEXT_CHANNEL,1024,PSP_AUDIO_FORMAT_STEREO); + printf("Got audio channel %i \n",channel); + if (!FC_init(file_buffer, file_len, 0, 0)) { + fprintf( stderr, "File format not recognized.\n"); + return -1; + } + printf("FC init done\n"); + + printf("Mixer init\n"); + mixerInit(44100, 16, 2, 0); + mixerSetReplayingSpeed(); + + SceUID thid; + thid = sceKernelCreateThread ("audio_thread", audio_thread, 0x18, + 0x1000, PSP_THREAD_ATTR_USER, NULL); + if (thid < 0) { + fprintf (stderr, "Error, could not create thread\n"); + sceKernelSleepThread (); + } + sceKernelStartThread (thid, 0, NULL); + printf("Enter Main loop\n"); + while (!done) { + sceCtrlReadBufferPositive (&pad, 1); + if (pad.Buttons & PSP_CTRL_CROSS) { + done=1; + } + sceKernelDelayThread (1000); + } + fprintf (stderr, "done..\n"); + return 0; +} diff --git a/Main_x86.cpp b/Main_x86.cpp new file mode 100644 index 0000000..8ff50b6 --- /dev/null +++ b/Main_x86.cpp @@ -0,0 +1,189 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "FC.h" +#include "MyTypes.h" + + +#include + +using namespace std; + +const udword extraFileBufLen = 8 + 1; // see FC.cpp + +bool exitFlag; + +void (*oldSigHupHandler) (int); +void (*oldSigIntHandler) (int); +void (*oldSigQuitHandler) (int); +void (*oldSigTermHandler) (int); +void mysighandler(int signum) +{ + switch (signum) { + case SIGHUP: + { + exitFlag = true; + break; + } + case SIGINT: + { + exitFlag = true; + break; + } + case SIGQUIT: + { + exitFlag = true; + break; + } + case SIGTERM: + { + exitFlag = true; + break; + } + default: + break; + } +} + +int main(int argc, char *argv[]) +{ + int inFileArgN = 0; + int startStep = 0; + int endStep = 0; + int i; + int err; + snd_pcm_t *playback_handle; + snd_pcm_hw_params_t *hw_params; + + for (int a = 1; a < argc; a++) { + if (argv[a][0] == '-') { + if (strncasecmp(&argv[a][0], "--start=", 8) == 0) { + startStep = atoi(argv[a] + 8); + } else if (strncasecmp(&argv[a][0], "--end=", 6) == 0) { + endStep = atoi(argv[a] + 6); + } + } else { + if (inFileArgN == 0) { + inFileArgN = a; + } else { + cerr << argv[0] << ": Missings argument(s)." << endl; + } + } + } + + if (inFileArgN == 0) { + cerr << "Missing file name argument." << endl; + exit(-1); + } + + ubyte *buffer = 0; + streampos fileLen = 0; + ifstream myIn(argv[inFileArgN], ios::in | ios::binary | ios::ate); + myIn.seekg(0, ios::end); + fileLen = myIn.tellg(); + buffer = (ubyte *) malloc((int) fileLen + extraFileBufLen); + myIn.seekg(0, ios::beg); + cout << "Loading" << flush; + streampos restFileLen = fileLen; + while (restFileLen > INT_MAX) { + myIn.read((char *) buffer + (fileLen - restFileLen), INT_MAX); + restFileLen -= INT_MAX; + cout << "." << flush; + } + if (restFileLen > 0) { + myIn.read((char *) buffer + (fileLen - restFileLen), restFileLen); + cout << "." << flush; + } + cout << endl << flush; + + if (myIn.bad()) { + cerr << argv[0] << ": ERROR: Cannot read file: " << argv[inFileArgN] << endl; + return (-1); + } + myIn.close(); + udword pcmFreq = 44100; + ubyte *sampleBuffer = new ubyte[pcmFreq]; + + if ((err = snd_pcm_open(&playback_handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + fprintf(stderr, "cannot open audio device %s (%s)\n", argv[1], snd_strerror(err)); + exit(1); + } + + if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) { + fprintf(stderr, "cannot allocate hardware parameter structure (%s)\n", snd_strerror(err)); + exit(1); + } + + if ((err = snd_pcm_hw_params_any(playback_handle, hw_params)) < 0) { + fprintf(stderr, "cannot initialize hardware parameter structure (%s)\n", snd_strerror(err)); + exit(1); + } + + if ((err = snd_pcm_hw_params_set_access(playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "cannot set access type (%s)\n", snd_strerror(err)); + exit(1); + } + + if ((err = snd_pcm_hw_params_set_format(playback_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "cannot set sample format (%s)\n", snd_strerror(err)); + exit(1); + } + + if ((err = snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, (unsigned int *) &pcmFreq, 0)) < 0) { + fprintf(stderr, "cannot set sample rate (%s)\n", snd_strerror(err)); + exit(1); + } + + if ((err = snd_pcm_hw_params_set_channels(playback_handle, hw_params, 2)) < 0) { + fprintf(stderr, "cannot set channel count (%s)\n", snd_strerror(err)); + exit(1); + } + + if ((err = snd_pcm_hw_params(playback_handle, hw_params)) < 0) { + fprintf(stderr, "cannot set parameters (%s)\n", snd_strerror(err)); + exit(1); + } + + snd_pcm_hw_params_free(hw_params); + + if ((err = snd_pcm_prepare(playback_handle)) < 0) { + fprintf(stderr, "cannot prepare audio interface for use (%s)\n", snd_strerror(err)); + exit(1); + } + + exitFlag = false; + if ((signal(SIGHUP, &mysighandler) == SIG_ERR) + || (signal(SIGINT, &mysighandler) == SIG_ERR) + || (signal(SIGQUIT, &mysighandler) == SIG_ERR) || (signal(SIGTERM, &mysighandler) == SIG_ERR)) { + cerr << argv[0] << ": ERROR: Cannot set signal handlers." << endl; + exit(-1); + } + + if (FC_init(buffer, fileLen, startStep, endStep)) { + cout << "Module format: " << mixerFormatName << endl; + extern void mixerInit(udword freq, int bits, int channels, uword zero); + mixerInit(pcmFreq, 16, 2, 0); + extern void mixerSetReplayingSpeed(); + mixerSetReplayingSpeed(); + while (true) { + extern void mixerFillBuffer(void *, udword); + mixerFillBuffer(sampleBuffer, 1024); + if ((err = snd_pcm_writei(playback_handle, sampleBuffer, 256)) != 256) { + fprintf(stderr, "write to audio interface failed (%s)\n", snd_strerror(err)); + exit(1); + } + if (exitFlag) + break; + } + } else { + cout << "File format not recognized." << endl; + } + snd_pcm_close(playback_handle); + delete[]sampleBuffer; + return (0); +} diff --git a/Main_x86_ b/Main_x86_ new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e69de29 diff --git a/Makefile.psp b/Makefile.psp new file mode 100644 index 0000000..09af9be --- /dev/null +++ b/Makefile.psp @@ -0,0 +1,21 @@ +PSPSDK = $(shell psp-config --pspsdk-path) +PSPDIR = $(shell psp-config --psp-prefix) + +INCDIR := audio/psp $(PSPDIR)/include +CFLAGS = -G0 -Wall -O2 -Wno-deprecated -fno-strict-aliasing +LIBS = -lstdc++ -lm -lpspaudiolib -lpspaudio + +OBJS = FC.o LamePaula.o Main_psp.o + + +TARGET = fcplay +EXTRA_TARGETS = EBOOT.PBP +PSP_EBOOT_TITLE = fcplay +EXTRA_CLEAN = clean_kxploit + +include $(PSPSDK)/lib/build.mak + +clean_kxploit: + rm -rf $(TARGET) + rm -rf "$(TARGET)%" + diff --git a/Makefile.x86 b/Makefile.x86 new file mode 100644 index 0000000..c8fbc97 --- /dev/null +++ b/Makefile.x86 @@ -0,0 +1,10 @@ + +all: + g++ -c Main_x86.cpp + g++ -c FC.cpp + g++ -c LamePaula.cpp + g++ -o fcplay Main_x86.o FC.o LamePaula.o -lasound + +clean: + rm -rf *.o fcplay + diff --git a/MyEndian.h b/MyEndian.h new file mode 100644 index 0000000..7d852d5 --- /dev/null +++ b/MyEndian.h @@ -0,0 +1,168 @@ +#ifndef MYENDIAN_H +#define MYENDIAN_H + + +#include "Config.h" +#include "MyTypes.h" + +// This should never be true. +#if defined(LO) || defined(HI) || defined(LOLO) || defined(LOHI) || defined(HILO) || defined(HIHI) + #error Redefinition of these values can cause trouble. +#endif + +// For optional direct memory access. +// First value in memory/array = index 0. +// Second value in memory/array = index 1. + +// For a pair of bytes/words/longwords. +#undef LO +#undef HI + +// For two pairs of bytes/words/longwords. +#undef LOLO +#undef LOHI +#undef HILO +#undef HIHI + +#if defined(WORDS_BIGENDIAN) +// byte-order: HI..3210..LO + #define LO 1 + #define HI 0 + #define LOLO 3 + #define LOHI 2 + #define HILO 1 + #define HIHI 0 +#else +// byte-order: LO..0123..HI +#define WORDS_LITTLEENDIAN + #define LO 0 + #define HI 1 + #define LOLO 0 + #define LOHI 1 + #define HILO 2 + #define HIHI 3 +#endif + +union cpuLword +{ + uword w[2]; // single 16-bit low and high word + udword l; // complete 32-bit longword +}; + +union cpuWord +{ + ubyte b[2]; // single 8-bit low and high byte + uword w; // complete 16-bit word +}; + +union cpuLBword +{ + ubyte b[4]; // single 8-bit bytes + udword l; // complete 32-bit longword +}; + +// Convert high-byte and low-byte to 16-bit word. +// Used to read 16-bit words in little-endian order. +inline uword readEndian(ubyte hi, ubyte lo) +{ + return(( (uword)hi << 8 ) + (uword)lo ); +} + +// Convert high bytes and low bytes of MSW and LSW to 32-bit word. +// Used to read 32-bit words in little-endian order. +inline udword readEndian(ubyte hihi, ubyte hilo, ubyte hi, ubyte lo) +{ + return(( (udword)hihi << 24 ) + ( (udword)hilo << 16 ) + + ( (udword)hi << 8 ) + (udword)lo ); +} + +// Read a little-endian 16-bit word from two bytes in memory. +inline uword readLEword(const ubyte ptr[2]) +{ +#if defined(WORDS_LITTLEENDIAN) && defined(OPTIMIZE_ENDIAN_ACCESS) + return *((uword*)ptr); +#else + return readEndian(ptr[1],ptr[0]); +#endif +} + +// Write a big-endian 16-bit word to two bytes in memory. +inline void writeLEword(ubyte ptr[2], uword someWord) +{ +#if defined(WORDS_LITTLEENDIAN) && defined(OPTIMIZE_ENDIAN_ACCESS) + *((uword*)ptr) = someWord; +#else + ptr[0] = (someWord & 0xFF); + ptr[1] = (someWord >> 8); +#endif +} + + + +// Read a big-endian 16-bit word from two bytes in memory. +inline uword readBEword(const ubyte ptr[2]) +{ +#if defined(WORDS_BIGENDIAN) && defined(OPTIMIZE_ENDIAN_ACCESS) + return *((uword*)ptr); +#else + return ( (((uword)ptr[0])<<8) + ((uword)ptr[1]) ); +#endif +} + +// Read a big-endian 32-bit word from four bytes in memory. +inline udword readBEdword(const ubyte ptr[4]) +{ +#if defined(WORDS_BIGENDIAN) && defined(OPTIMIZE_ENDIAN_ACCESS) + return *((udword*)ptr); +#else + return ( (((udword)ptr[0])<<24) + (((udword)ptr[1])<<16) + + (((udword)ptr[2])<<8) + ((udword)ptr[3]) ); +#endif +} + +// Write a big-endian 16-bit word to two bytes in memory. +inline void writeBEword(ubyte ptr[2], uword someWord) +{ +#if defined(WORDS_BIGENDIAN) && defined(OPTIMIZE_ENDIAN_ACCESS) + *((uword*)ptr) = someWord; +#else + ptr[0] = someWord >> 8; + ptr[1] = someWord & 0xFF; +#endif +} + +// Write a big-endian 32-bit word to four bytes in memory. +inline void writeBEdword(ubyte ptr[4], udword someDword) +{ +#if defined(WORDS_BIGENDIAN) && defined(OPTIMIZE_ENDIAN_ACCESS) + *((udword*)ptr) = someDword; +#else + ptr[0] = someDword >> 24; + ptr[1] = (someDword>>16) & 0xFF; + ptr[2] = (someDword>>8) & 0xFF; + ptr[3] = someDword & 0xFF; +#endif +} + + + +// Convert 16-bit little-endian word to big-endian order or vice versa. +inline uword convertEndianess( uword intelword ) +{ + uword hi = intelword >> 8; + uword lo = intelword & 255; + return(( lo << 8 ) + hi ); +} + +// Convert 32-bit little-endian word to big-endian order or vice versa. +inline udword convertEndianess( udword inteldword ) +{ + udword hihi = inteldword >> 24; + udword hilo = ( inteldword >> 16 ) & 0xFF; + udword hi = ( inteldword >> 8 ) & 0xFF; + udword lo = inteldword & 0xFF; + return(( lo << 24 ) + ( hi << 16 ) + ( hilo << 8 ) + hihi ); +} + + +#endif // MYENDIAN_H diff --git a/MyTypes.h b/MyTypes.h new file mode 100644 index 0000000..ab3f39f --- /dev/null +++ b/MyTypes.h @@ -0,0 +1,23 @@ +#ifndef MYTYPES_H +#define MYTYPES_H + +#include "Config.h" + +typedef signed char sbyte; +typedef unsigned char ubyte; + +typedef signed short int sword; +typedef unsigned short int uword; + +typedef signed long int sdword; +typedef unsigned long int udword; + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long int ulong; + + +typedef void (*ptr2func)(); + + +#endif diff --git a/SmartPtr.h b/SmartPtr.h new file mode 100644 index 0000000..3e12d88 --- /dev/null +++ b/SmartPtr.h @@ -0,0 +1,224 @@ +// Simple smart pointer class. + +#ifndef SMARTPTR_H +#define SMARTPTR_H + +typedef unsigned long int ulint; + +template +class smartPtrBase +{ + public: // ---------------------------------------------------------------- + + // --- constructors --- + + smartPtrBase(T* buffer, ulint bufferLen, bool bufOwner = false) : dummy(0) + { + doFree = bufOwner; + if ( bufferLen >= 1 ) + { + pBufCurrent = ( bufBegin = buffer ); + bufEnd = bufBegin + bufferLen; + bufLen = bufferLen; + status = true; + } + else + { + pBufCurrent = ( bufBegin = ( bufEnd = 0 )); + bufLen = 0; + status = false; + } + } + + // --- destructor --- + + virtual ~smartPtrBase() + { + if ( doFree && (bufBegin != 0) ) + { + delete[] bufBegin; + } + } + + // --- public member functions --- + + virtual T* tellBegin() { return bufBegin; } + virtual ulint tellLength() { return bufLen; } + virtual ulint tellPos() { return (ulint)(pBufCurrent-bufBegin); } + + virtual bool checkIndex(ulint index) + { + return ((pBufCurrent+index)= 1 ) + { + pBufCurrent = bufBegin; + return (status = true); + } + else + { + return (status = false); + } + } + + virtual bool good() + { + return (pBufCurrent= bufBegin) + { + pBufCurrent -= offset; + } + else + { + status = false; + } + } + + T operator*() + { + if ( good() ) + { + return *pBufCurrent; + } + else + { + status = false; + return dummy; + } + } + + T& operator [](ulint index) + { + if (checkIndex(index)) + { + return pBufCurrent[index]; + } + else + { + status = false; + return dummy; + } + } + + virtual operator bool() { return status; } + + protected: // ------------------------------------------------------------- + + T* bufBegin; + T* bufEnd; + T* pBufCurrent; + ulint bufLen; + bool status; + bool doFree; + T dummy; +}; + + +template +class smartPtr : public smartPtrBase +{ + public: // ---------------------------------------------------------------- + + // --- constructors --- + + smartPtr(T* buffer, ulint bufferLen, bool bufOwner = false) + : smartPtrBase(buffer, bufferLen, bufOwner) + { + } + + smartPtr() + : smartPtrBase(0,0) + { + } + + void setBuffer(T* buffer, ulint bufferLen) + { + if ( bufferLen >= 1 ) + { + this->pBufCurrent = ( this->bufBegin = buffer ); + this->bufEnd = this->bufBegin + bufferLen; + this->bufLen = bufferLen; + this->status = true; + } + else + { + this->pBufCurrent = this->bufBegin = this->bufEnd = 0; + this->bufLen = 0; + this->status = false; + } + } +}; + +#endif // SMARTPTR_H diff --git a/shot b/shot new file mode 100644 index 0000000..c95a683 Binary files /dev/null and b/shot differ diff --git a/shot.png b/shot.png new file mode 100644 index 0000000..15c4a30 Binary files /dev/null and b/shot.png differ diff --git a/songs/astaroth_3.fc13 b/songs/astaroth_3.fc13 new file mode 100644 index 0000000..ca1ad9c Binary files /dev/null and b/songs/astaroth_3.fc13 differ diff --git a/songs/cytax-1.fc4 b/songs/cytax-1.fc4 new file mode 100644 index 0000000..eae9353 Binary files /dev/null and b/songs/cytax-1.fc4 differ diff --git a/songs/dextrous-synthtronic.fc4 b/songs/dextrous-synthtronic.fc4 new file mode 100644 index 0000000..fb3a8fb Binary files /dev/null and b/songs/dextrous-synthtronic.fc4 differ