MCUME/MCUME_esp32/espboot/main/LibFC14/FC.cpp

1166 wiersze
38 KiB
C++

// Future Composer audio decoder -- Copyright (C) Michael Schwendt
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "FC.h"
#include <cstring>
#ifdef FC_HAVE_NOTHROW
#include <new>
#endif
//#define DEBUG 1
//#define DEBUG2 1
//#define DEBUG3 1
#if defined(DEBUG) || defined(DEBUG2) || defined(DEBUG3)
#include <iomanip>
#include <iostream>
using namespace std;
#include "Dump.h"
#endif
FC::FC() {
input = 0;
inputLen = 0;
_admin.initialized = false;
// Set up some dummy voices to decouple the decoder from the mixer.
for (int v=0; v<channels; v++) {
_CHdata[v].ch = &_dummyVoices[v];
}
off();
}
FC::~FC() {
delete[] input;
inputLen = 0;
}
void FC::off() {
_admin.isEnabled = false;
_admin.dmaFlags = 0;
for (int c=0; c<channels; c++) {
_CHdata[c].ch->off();
}
// (AMIGA) Power-LED on = low-pass filter on.
// May be simulated by external audio post-processing.
}
void FC::setMixer(PaulaMixer* mixer) {
// Create needed number of voices and replace the dummies.
mixer->init(channels);
for (int v=0; v<channels; v++) {
_CHdata[v].ch = mixer->getVoice(v);
_CHdata[v].ch->off();
killChannel(_CHdata[v]);
}
}
bool FC::isOurData(void *data, unsigned long int length) {
if ( length<5 ) {
return false;
}
ubyte *d = static_cast<ubyte*>(data);
// Check for "SMOD" ID (Future Composer 1.0 to 1.3).
isSMOD = (d[0]==0x53 && d[1]==0x4D && d[2]==0x4F && d[3]==0x44 &&
d[4]==0x00);
// Check for "FC14" ID (Future Composer 1.4).
isFC14 = (d[0]==0x46 && d[1]==0x43 && d[2]==0x31 && d[3]==0x34 &&
d[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.
return (isSMOD || isFC14);
}
bool FC::init(void *data, udword length, int startStep, int endStep) {
if ( !isOurData(data,length) ) {
formatName = UNKNOWN_FORMAT_NAME;
return false;
}
if (isSMOD)
formatName = SMOD_FORMAT_NAME;
else if (isFC14)
formatName = FC14_FORMAT_NAME;
udword copyLen = length+sizeof(silenceData);
if (copyLen > inputLen) {
delete[] input;
inputLen = 0;
#ifdef FC_HAVE_NOTHROW
if ( (input = new(std::nothrow) ubyte[copyLen]) == 0 ) {
#else
if ( (input = new ubyte[copyLen]) == 0 ) {
#endif
return false;
}
}
memcpy(input,data,copyLen);
inputLen = copyLen;
// 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*)input,inputLen);
fcBuf.setBuffer((ubyte*)input,inputLen);
// (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.
_admin.offsets.silence = inputLen-sizeof(silenceData);
for (ubyte i=0; i<sizeof(silenceData); i++) {
fcBuf[_admin.offsets.silence+i] = silenceData[i];
}
// (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) {
_admin.offsets.trackTable = SMOD_SONGTAB_OFFSET;
}
else { // if (isFC14)
_admin.offsets.trackTable = FC14_SONGTAB_OFFSET;
}
#if defined(DEBUG)
cout << "Track table (sequencer): " << endl;
dumpLines(fcBuf,_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.
_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.
_admin.usedPatterns = readEndian(fcBuf[12],fcBuf[13],
fcBuf[14],fcBuf[15])/PATTERN_LENGTH;
#if defined(DEBUG)
cout << "Patterns: " << hex << _admin.usedPatterns << endl;
dumpBlocks(fcBuf,_admin.offsets.patterns,
_admin.usedPatterns*PATTERN_LENGTH,PATTERN_LENGTH);
#endif
// At +16 is offset to first sound modulation sequence.
_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.
_admin.usedSndModSeqs = readEndian(fcBuf[20],fcBuf[21],
fcBuf[22],fcBuf[23])/64;
#if defined(DEBUG)
cout << "Sound modulation sequences: "
<< hex << _admin.usedSndModSeqs << endl;
dumpBlocks(fcBuf,_admin.offsets.sndModSeqs,
_admin.usedSndModSeqs*64,64);
#endif
// At +24 is offset to first volume modulation sequence.
_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.
_admin.usedVolModSeqs = readEndian(fcBuf[28],fcBuf[29],
fcBuf[30],fcBuf[31])/64;
#if defined(DEBUG)
cout << "Volume modulation sequences: "
<< hex << _admin.usedVolModSeqs << endl;
dumpBlocks(fcBuf,_admin.offsets.volModSeqs,
_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++) {
_sounds[sam].start = sampleOffset+fcBuf.tellBegin();
// Sample length in words.
uword sampleLength = readEndian(fcBuf[sampleHeader],
fcBuf[sampleHeader+1]);
_sounds[sam].len = sampleLength;
_sounds[sam].repOffs = readEndian(fcBuf[sampleHeader+2],
fcBuf[sampleHeader+3]);
_sounds[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 (_sounds[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 &&
_sounds[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) << (long)_sounds[sam].start-(long)fcBuf.tellBegin() << " "
<< dec << setw(4) << (int)_sounds[sam].len*2L << " "
<< dec << setw(6) << (int)_sounds[sam].repOffs << " "
<< dec << setw(6) << (int)_sounds[sam].repLen*2L << " "
<< endl;
dumpLines(fcBuf,(long)_sounds[sam].start-(long)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;
_sounds[sam].start = wavePtr+SMOD_waveInfo[infoIndex++];
_sounds[sam].len = SMOD_waveInfo[infoIndex++];
_sounds[sam].repOffs = SMOD_waveInfo[infoIndex++];
_sounds[sam].repLen = SMOD_waveInfo[infoIndex++];
#if defined(DEBUG)
cout
<< dec << setw(6) << (long)_sounds[sam].start-(long)wavePtr << " "
<< dec << setw(4) << (int)_sounds[sam].len*2L << " "
<< dec << setw(6) << (int)_sounds[sam].repOffs << " "
<< dec << setw(6) << (int)_sounds[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;
_sounds[sam].start = waveOffset+fcBuf.tellBegin();
ubyte waveLength = fcBuf[waveHeader++];
waveOffset += waveLength;
waveOffset += waveLength;
_sounds[sam].len = waveLength;
_sounds[sam].repOffs = 0;
_sounds[sam].repLen = waveLength;
#if defined(DEBUG)
cout
<< dec << setw(6) << (long)_sounds[sam].start-(long)fcBuf.tellBegin() << " "
<< dec << setw(4) << (int)_sounds[sam].len*2L << " "
<< dec << setw(6) << (int)_sounds[sam].repOffs << " "
<< dec << setw(6) << (int)_sounds[sam].repLen*2L << " "
<< endl;
#endif
}
}
_admin.initialized = true;
restart(startStep,endStep);
// (NOTE) The lowest octave in the period table is unreachable
// due to a hardcoded range-check (see bottom).
return true;
}
void FC::restart(int startStep, int endStep) {
if (!_admin.initialized) {
return;
}
// This one later on gets incremented prior to first comparison
// (4+1=5 => get speed at first step).
_admin.RScount = 4;
// (NOTE) Some FC implementations instead count from 0 to 4.
// 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
off();
for (ubyte c=0; c<channels; c++) {
_CHdata[c].dmaMask = (1<<c);
_CHdata[c].trackPos =
_CHdata[c].pattPos = 0;
_CHdata[c].volSlideSpeed =
_CHdata[c].volSlideTime =
_CHdata[c].volSlideDelayFlag =
_CHdata[c].volSustainTime = 0;
_CHdata[c].envelopeCount =
_CHdata[c].envelopeSpeed = 1;
_CHdata[c].vibSpeed =
_CHdata[c].vibDelay =
_CHdata[c].vibAmpl =
_CHdata[c].vibCurOffs =
_CHdata[c].vibFlag = 0;
_CHdata[c].pitchBendSpeed =
_CHdata[c].pitchBendTime =
_CHdata[c].pitchBendDelayFlag = 0;
_CHdata[c].transpose =
_CHdata[c].seqTranspose = 0;
_CHdata[c].portaInfo =
_CHdata[c].portaOffs =
_CHdata[c].portDelayFlag = 0;
_CHdata[c].volSeq = _admin.offsets.silence;
_CHdata[c].sndSeq = _admin.offsets.silence+1;
_CHdata[c].volSeqPos =
_CHdata[c].sndSeqPos = 0;
_CHdata[c].sndModSustainTime = 0;
_CHdata[c].noteValue = 0;
_CHdata[c].period = 0;
_CHdata[c].volume = 0;
killChannel(_CHdata[c]);
// Track table start and end.
_CHdata[c].trackStart = _admin.offsets.trackTable+c*3;
_CHdata[c].trackEnd = _CHdata[c].trackStart+trackTabLen;
_CHdata[c].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 = _CHdata[c].trackStart+_CHdata[c].trackPos;
uword pattern = fcBuf[trackTabPos++]; // PT
_CHdata[c].pattStart = _admin.offsets.patterns+(pattern<<6);
_CHdata[c].transpose = fcBuf[trackTabPos++]; // TR
_CHdata[c].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.
//
_admin.speed = fcBuf[_CHdata[0].trackStart+_CHdata[0].trackPos+12];
if (_admin.speed == 0) {
_admin.speed = 3; // documented default
}
_admin.count = _admin.speed;
_admin.isEnabled = true;
songEnd = false;
}
// --------------------------------------------------------------------------
void FC::run()
{
if (!_admin.isEnabled) // on/off flag
return;
if (--_admin.count == 0) {
_admin.count = _admin.speed; // reload
// Prepare next note for each voice.
nextNote(_CHdata[0]);
nextNote(_CHdata[1]);
nextNote(_CHdata[2]);
nextNote(_CHdata[3]);
#if defined(DEBUG2)
cout << endl << flush;
#endif
}
// Procedure calls in next loop will decide which
// audio channel to turn on.
_admin.dmaFlags = 0;
for (ubyte c=0; c<channels; c++) {
// Start or update instrument.
processModulation(_CHdata[c]);
_CHdata[c].ch->paula.period = _CHdata[c].period;
_CHdata[c].ch->paula.volume = _CHdata[c].volume;
if (_CHdata[c].repeatDelay != 0) {
if (--_CHdata[c].repeatDelay == 1) {
_CHdata[c].repeatDelay = 0;
_CHdata[c].ch->paula.start = _CHdata[c].pSampleStart +
_CHdata[c].repeatOffset;
_CHdata[c].ch->paula.length = _CHdata[c].repeatLength;
_CHdata[c].ch->takeNextBuf();
}
}
}
// Finally decide which audio channels to start.
// This could be moved into previous loop.
for (ubyte c=0; c<channels; c++) {
// Enable channel? Else, do not touch it.
if ((_admin.dmaFlags&(1<<c)) != 0) {
_CHdata[c].ch->on();
}
}
}
// --------------------------------------------------------------------------
void FC::killChannel(CHdata& CHXdata) {
// The interface to a cheap Paula simulator/mixer.
CHXdata.ch->off();
CHXdata.ch->paula.start = fcBuf.tellBegin()+_admin.offsets.silence+1;
// (NOTE) Some implementations set this to 0x0100.
CHXdata.ch->paula.length = 1;
CHXdata.ch->takeNextBuf();
}
void FC::nextNote(CHdata& CHXdata)
{
// Get offset to (or address of) current pattern position.
udword pattOffs = CHXdata.pattStart+CHXdata.pattPos;
// Check for pattern end or whether pattern BREAK
// command is set.
if (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.
CHXdata.pattPos = 0;
// Advance one step in track table.
CHXdata.trackPos += TRACKTAB_ENTRY_LENGTH; // 0x000d
udword trackOffs = CHXdata.trackStart+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) >= CHXdata.trackEnd) // pattern table end?
{
CHXdata.trackPos = 0; // restart by default
trackOffs = CHXdata.trackStart;
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 (++_admin.RScount == 5)
{
_admin.RScount = 1;
ubyte newSpeed = fcBuf[trackOffs+12]; // RS (replay speed)
if (newSpeed != 0) // 0 would be underflow
{
_admin.count = _admin.speed = newSpeed;
}
}
uword pattern = fcBuf[trackOffs++]; // PT
CHXdata.transpose = fcBufS[trackOffs++];
CHXdata.soundTranspose = fcBufS[trackOffs++];
CHXdata.pattStart = _admin.offsets.patterns+(pattern<<6);
// Get new pattern pointer (pattPos is 0 already, see above).
pattOffs = CHXdata.pattStart;
}
#if defined(DEBUG2)
if (CHXdata.dmaMask==1
&& CHXdata.pattPos==0)
{
cout << endl;
cout << "Step = " << hex << setw(4) << setfill('0') << CHXdata.trackPos/ TRACKTAB_ENTRY_LENGTH;
cout << " | " << hex << setw(5) << setfill('0') << (int)CHXdata.trackStart << ", " << (int)(CHXdata.trackStart+CHXdata.trackPos) << ", " << (int)CHXdata.trackEnd << endl;
udword tmp = CHXdata.trackStart+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 (CHXdata.dmaMask != 8)
cout << " | ";
#endif
// Process pattern entry.
ubyte note = fcBuf[pattOffs++];
ubyte info1 = fcBuf[pattOffs]; // info byte #1
if (note != 0)
{
CHXdata.portaOffs = 0; // reset portamento offset
CHXdata.portaInfo = 0; // stop port., erase old parameter
// (BUG-FIX) Disallow signed underflow here.
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.
CHXdata.ch->off();
// Later enable channel.
_admin.dmaFlags |= 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)+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 > (_admin.usedVolModSeqs-1))
{
seqOffs = _admin.offsets.silence;
}
else
{
seqOffs = _admin.offsets.volModSeqs+(sound<<6);
}
CHXdata.envelopeSpeed = CHXdata.envelopeCount = fcBuf[seqOffs++];
// Get sound modulation sequence number.
sound = fcBuf[seqOffs++];
CHXdata.vibSpeed = fcBuf[seqOffs++];
CHXdata.vibFlag = 0x40; // vibrato UP at start
CHXdata.vibAmpl = CHXdata.vibCurOffs = fcBuf[seqOffs++];
CHXdata.vibDelay = fcBuf[seqOffs++];
CHXdata.volSeq = seqOffs;
CHXdata.volSeqPos = 0;
CHXdata.volSustainTime = 0;
if (sound > (_admin.usedSndModSeqs-1))
{
// (NOTE) Silent sound modulation sequence is different
// from silent instrument definition sequence.
seqOffs = _admin.offsets.silence+1;
}
else
{
seqOffs = _admin.offsets.sndModSeqs+(sound<<6);
}
CHXdata.sndSeq = seqOffs;
CHXdata.sndSeqPos = 0;
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?
{
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.
CHXdata.portaInfo = fcBuf[pattOffs+2]&0x3f;
}
// Advance to next pattern entry.
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''.
inline void FC::setWave(CHdata& CHXdata, ubyte num)
{
CHXdata.pSampleStart = _sounds[num].start;
CHXdata.ch->paula.start = _sounds[num].start;
CHXdata.ch->paula.length = _sounds[num].len;
CHXdata.ch->takeNextBuf();
CHXdata.repeatOffset = _sounds[num].repOffs;
CHXdata.repeatLength = _sounds[num].repLen;
CHXdata.repeatDelay = 3;
}
inline void FC::readSeqTranspose(CHdata& CHXdata)
{
CHXdata.seqTranspose = fcBufS[CHXdata.sndSeq+CHXdata.sndSeqPos];
++CHXdata.sndSeqPos;
}
void FC::processModulation(CHdata& CHXdata)
{
if (CHXdata.sndModSustainTime != 0)
{
--CHXdata.sndModSustainTime;
processPerVol(CHXdata);
return;
}
readModCommand(CHXdata);
}
void FC::readModCommand(CHdata& CHXdata)
{
udword seqOffs = CHXdata.sndSeq+CHXdata.sndSeqPos;
// (NOTE) After each command (except LOOP, END, SUSTAIN,
// and NEWVIB) follows a transpose value.
if (fcBuf[seqOffs] == SNDMOD_LOOP)
{
CHXdata.sndSeqPos = fcBuf[seqOffs+1]&0x3f;
// Calc new sequence address.
seqOffs = CHXdata.sndSeq+CHXdata.sndSeqPos;
}
if (fcBuf[seqOffs] == SNDMOD_END)
{
processPerVol(CHXdata);
return;
}
else if (fcBuf[seqOffs] == SNDMOD_SETWAVE)
{
// Disable channel right now.
CHXdata.ch->off();
// Enable channel later.
_admin.dmaFlags |= CHXdata.dmaMask;
// Restart envelope.
CHXdata.volSeqPos = 0;
CHXdata.envelopeCount = 1;
setWave(CHXdata,fcBuf[seqOffs+1]);
CHXdata.sndSeqPos += 2;
readSeqTranspose(CHXdata);
processPerVol(CHXdata);
return;
}
else if (fcBuf[seqOffs] == SNDMOD_CHANGEWAVE)
{
setWave(CHXdata,fcBuf[seqOffs+1]);
CHXdata.sndSeqPos += 2;
readSeqTranspose(CHXdata);
processPerVol(CHXdata);
return;
}
else if (fcBuf[seqOffs] == SNDMOD_SETPACKWAVE)
{
// Disable channel right now.
CHXdata.ch->off();
// Enable channel later.
_admin.dmaFlags |= CHXdata.dmaMask;
uword i = fcBuf[seqOffs+1]; // sample/pack nr.
if (i < 10) // sample or waveform?
{
udword sndOffs = _sounds[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]);
CHXdata.pSampleStart = smpStart+fcBuf.tellBegin();
CHXdata.ch->paula.start = CHXdata.pSampleStart;
CHXdata.ch->paula.length = readEndian(fcBuf[sndOffs+4],
fcBuf[sndOffs+5]);
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.
CHXdata.repeatOffset = readEndian(fcBuf[sndOffs+6],
fcBuf[sndOffs+7]);
CHXdata.repeatLength = readEndian(fcBuf[sndOffs+8],
fcBuf[sndOffs+9]);
if (CHXdata.repeatLength == 1)
{
// Erase first word behind sample to avoid beeping
// one-shot mode upon true emulation of Paula.
fcBuf[smpStart+CHXdata.repeatOffset] = 0;
fcBuf[smpStart+CHXdata.repeatOffset+1] = 0;
}
// Restart envelope.
CHXdata.volSeqPos = 0;
CHXdata.envelopeCount = 1;
//
CHXdata.repeatDelay = 3;
}
}
CHXdata.sndSeqPos += 3;
readSeqTranspose(CHXdata);
processPerVol(CHXdata);
return;
}
else if (fcBuf[seqOffs] == SNDMOD_NEWSEQ)
{
uword seq = fcBuf[seqOffs+1];
CHXdata.sndSeq = _admin.offsets.sndModSeqs+(seq<<6);
CHXdata.sndSeqPos = 0;
// Recursive call (ought to be protected via a counter).
readModCommand(CHXdata);
return;
}
else if (fcBuf[seqOffs] == SNDMOD_SUSTAIN)
{
CHXdata.sndModSustainTime = fcBuf[seqOffs+1];
CHXdata.sndSeqPos += 2;
// Decrease sustain counter and decide whether to continue
// to envelope modulation.
// Recursive call (ought to be protected via a counter).
processModulation(CHXdata);
return;
}
else if (fcBuf[seqOffs] == SNDMOD_NEWVIB)
{
CHXdata.vibSpeed = fcBuf[seqOffs+1];
CHXdata.vibAmpl = fcBuf[seqOffs+2];
CHXdata.sndSeqPos += 3;
processPerVol(CHXdata);
return;
}
else if (fcBuf[seqOffs] == SNDMOD_PITCHBEND)
{
CHXdata.pitchBendSpeed = fcBufS[seqOffs+1];
CHXdata.pitchBendTime = fcBuf[seqOffs+2];
CHXdata.sndSeqPos += 3;
readSeqTranspose(CHXdata);
processPerVol(CHXdata);
return;
}
else // Not a command, but a transpose value.
{
readSeqTranspose(CHXdata);
processPerVol(CHXdata);
}
}
// --------------------------------------------------------------------------
// (NOTE) This part of the code is not protected against a deadlock
// caused by damaged music module data.
void FC::volSlide(CHdata& CHXdata)
{
// Following flag divides the volume sliding speed by two.
CHXdata.volSlideDelayFlag ^= 0xff; // = NOT
if (CHXdata.volSlideDelayFlag != 0)
{
--CHXdata.volSlideTime;
CHXdata.volume += CHXdata.volSlideSpeed;
if (CHXdata.volume < 0)
{
CHXdata.volume = CHXdata.volSlideTime = 0;
}
// (NOTE) Most FC players do not check whether Paula's
// maximum volume level is exceeded.
if (CHXdata.volume > 64)
{
CHXdata.volume = 64;
CHXdata.volSlideTime = 0;
}
}
}
void FC::processPerVol(CHdata& CHXdata)
{
bool repeatVolSeq; // JUMP/GOTO - WHILE conversion
do
{
repeatVolSeq = false;
// Sustain current volume level? NE => yes, EQ => no.
if (CHXdata.volSustainTime != 0)
{
--CHXdata.volSustainTime;
}
// Slide volume? NE => yes, EQ => no.
else if (CHXdata.volSlideTime != 0)
{
volSlide(CHXdata);
}
// Time to set next volume level? NE => no, EQ => yes.
else if (--CHXdata.envelopeCount == 0)
{
CHXdata.envelopeCount = CHXdata.envelopeSpeed;
bool readNextVal; // JUMP/GOTO - WHILE conversion
do
{
readNextVal = false;
udword seqOffs = CHXdata.volSeq+CHXdata.volSeqPos;
ubyte command = fcBuf[seqOffs];
switch (command)
{
case ENVELOPE_SUSTAIN:
{
CHXdata.volSustainTime = fcBuf[seqOffs+1];
CHXdata.volSeqPos += 2;
// This shall loop to beginning of proc.
repeatVolSeq = true;
break;
}
case ENVELOPE_SLIDE:
{
CHXdata.volSlideSpeed = fcBuf[seqOffs+1];
CHXdata.volSlideTime = fcBuf[seqOffs+2];
CHXdata.volSeqPos += 3;
volSlide(CHXdata);
break;
}
case ENVELOPE_LOOP:
{
// Range check should be done here.
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.
CHXdata.volume = fcBuf[seqOffs];
if (++CHXdata.volSeqPos > 0x3f) {
CHXdata.volSeqPos = 0x3f;
}
// Full range check for volume 0-64.
if (CHXdata.volume > 64) {
CHXdata.volume = 64;
}
else if (CHXdata.volume < 0) {
CHXdata.volume = 0;
}
break;
}
}
}
while (readNextVal);
}
}
while (repeatVolSeq);
// Now determine note and period value to play.
sdword tmp0, tmp1;
tmp0 = CHXdata.seqTranspose;
if (tmp0 >= 0)
{
tmp0 += CHXdata.noteValue;
tmp0 += 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 = periods[tmp0];
// Vibrato.
//
// Vibrato offset changes between [0,1,...,2*vibAmpl]
// Offset minus vibAmpl is value to apply.
if (CHXdata.vibDelay == 0)
{
uword noteTableOffset = tmp1; // tmp1 is note*2;
sword vibDelta = 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 = CHXdata.vibCurOffs;
if ((CHXdata.vibFlag&(1<<5))==0)
{
tmp1 -= CHXdata.vibSpeed;
// Lowest value reached?
if (tmp1 < 0)
{
tmp1 = 0;
CHXdata.vibFlag |= (1<<5); // switch to vibrato up
}
}
else
{
tmp1 += CHXdata.vibSpeed;
// Amplitude reached?
if (tmp1 > vibDelta)
{
tmp1 = vibDelta;
CHXdata.vibFlag &= ~(1<<5); // switch to vibrato down
}
}
CHXdata.vibCurOffs = tmp1;
// noteTableOffset is note*2;
tmp1 -= 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
{
--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.
CHXdata.portDelayFlag ^= 0xff; // = NOT
if (isSMOD || CHXdata.portDelayFlag!=0)
{
sbyte param = CHXdata.portaInfo;
if (param != 0)
{
if (param > 0x1f) // > 0x20 = portamento down
{
param &= 0x1f;
param = (-param);
}
CHXdata.portaOffs -= param;
}
}
// Pitchbend.
// Following flag divides the pitch bending speed by two.
CHXdata.pitchBendDelayFlag ^= 0xff; // not
if (CHXdata.pitchBendDelayFlag != 0)
{
if (CHXdata.pitchBendTime != 0)
{
--CHXdata.pitchBendTime;
sbyte speed = CHXdata.pitchBendSpeed;
if (speed != 0)
{
CHXdata.portaOffs -= speed;
}
}
}
tmp0 += 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;
}
CHXdata.period = tmp0;
}