aboutsummaryrefslogtreecommitdiff
path: root/sound/mods
diff options
context:
space:
mode:
Diffstat (limited to 'sound/mods')
-rw-r--r--sound/mods/paula.cpp45
-rw-r--r--sound/mods/paula.h74
-rw-r--r--sound/mods/soundfx.cpp11
-rw-r--r--sound/mods/tfmx.cpp996
-rw-r--r--sound/mods/tfmx.h296
5 files changed, 1390 insertions, 32 deletions
diff --git a/sound/mods/paula.cpp b/sound/mods/paula.cpp
index 545390ff93..c9866e51ab 100644
--- a/sound/mods/paula.cpp
+++ b/sound/mods/paula.cpp
@@ -27,19 +27,20 @@
namespace Audio {
-Paula::Paula(bool stereo, int rate, int interruptFreq) :
- _stereo(stereo), _rate(rate), _intFreq(interruptFreq) {
+Paula::Paula(bool stereo, int rate, uint interruptFreq) :
+ _stereo(stereo), _rate(rate), _periodScale((kPalSystemClock / 2.0) / rate), _intFreq(interruptFreq) {
clearVoices();
- _voice[0].panning = 63;
- _voice[1].panning = 191;
- _voice[2].panning = 191;
- _voice[3].panning = 63;
+ _voice[0].panning = 191;
+ _voice[1].panning = 63;
+ _voice[2].panning = 63;
+ _voice[3].panning = 191;
- if (_intFreq <= 0)
+ if (_intFreq == 0)
_intFreq = _rate;
- _curInt = _intFreq;
+ _curInt = 0;
+ _timerBase = 1;
_playing = false;
_end = true;
}
@@ -55,8 +56,10 @@ void Paula::clearVoice(byte voice) {
_voice[voice].length = 0;
_voice[voice].lengthRepeat = 0;
_voice[voice].period = 0;
+ _voice[voice].periodRepeat = 0;
_voice[voice].volume = 0;
_voice[voice].offset = 0;
+ _voice[voice].dmaCount = 0;
}
int Paula::readBuffer(int16 *buffer, const int numSamples) {
@@ -95,18 +98,17 @@ int Paula::readBufferIntern(int16 *buffer, const int numSamples) {
// Handle 'interrupts'. This gives subclasses the chance to adjust the channel data
// (e.g. insert new samples, do pitch bending, whatever).
- if (_curInt == _intFreq) {
+ if (_curInt == 0) {
+ _curInt = _intFreq;
interrupt();
- _curInt = 0;
}
// Compute how many samples to generate: at most the requested number of samples,
// of course, but we may stop earlier when an 'interrupt' is expected.
- const int nSamples = MIN(samples, _intFreq - _curInt);
+ const uint nSamples = MIN((uint)samples, _curInt);
// Loop over the four channels of the emulated Paula chip
for (int voice = 0; voice < NUM_VOICES; voice++) {
-
// No data, or paused -> skip channel
if (!_voice[voice].data || (_voice[voice].period <= 0))
continue;
@@ -115,8 +117,7 @@ int Paula::readBufferIntern(int16 *buffer, const int numSamples) {
// the requested output sampling rate (typicall 44.1 kHz or 22.05 kHz)
// as well as the "period" of the channel we are processing right now,
// to compute the correct output 'rate'.
- const double frequency = (7093789.2 / 2.0) / _voice[voice].period;
- frac_t rate = doubleToFrac(frequency / _rate);
+ frac_t rate = doubleToFrac(_periodScale / _voice[voice].period);
// Cap the volume
_voice[voice].volume = MIN((byte) 0x40, _voice[voice].volume);
@@ -126,6 +127,7 @@ int Paula::readBufferIntern(int16 *buffer, const int numSamples) {
frac_t offset = _voice[voice].offset;
frac_t sLen = intToFrac(_voice[voice].length);
const int8 *data = _voice[voice].data;
+ int dmaCount = _voice[voice].dmaCount;
int16 *p = buffer;
int end = 0;
int neededSamples = nSamples;
@@ -141,21 +143,29 @@ int Paula::readBufferIntern(int16 *buffer, const int numSamples) {
// If we have not yet generated enough samples, and looping is active: loop!
if (neededSamples > 0 && _voice[voice].lengthRepeat > 2) {
-
// At this point we know that we have used up all samples in the buffer, so reset it.
_voice[voice].data = data = _voice[voice].dataRepeat;
_voice[voice].length = _voice[voice].lengthRepeat;
sLen = intToFrac(_voice[voice].length);
+ // TODO: the value in offset shouldnt be dropped but scaled to new rate
+
+ if (_voice[voice].period != _voice[voice].periodRepeat) {
+ _voice[voice].period = _voice[voice].periodRepeat;
+ rate = doubleToFrac(_periodScale / _voice[voice].period);
+ }
// If the "rate" exceeds the sample rate, we would have to perform constant
// wrap arounds. So, apply the first step of the euclidean algorithm to
// achieve the same more efficiently: Take rate modulo sLen
+ // TODO: This messes up dmaCount
if (sLen < rate)
rate %= sLen;
// Repeat as long as necessary.
while (neededSamples > 0) {
- offset = 0;
+ // TODO offset -= sLen, but only if same rate otherwise need to scale first
+ offset &= FRAC_LO_MASK;
+ dmaCount++;
// Compute the number of samples to generate (see above) and mix 'em.
end = MIN(neededSamples, (int)((sLen - offset + rate - 1) / rate));
@@ -166,10 +176,11 @@ int Paula::readBufferIntern(int16 *buffer, const int numSamples) {
// Write back the cached data
_voice[voice].offset = offset;
+ _voice[voice].dmaCount = dmaCount;
}
buffer += _stereo ? nSamples * 2 : nSamples;
- _curInt += nSamples;
+ _curInt -= nSamples;
samples -= nSamples;
}
return numSamples;
diff --git a/sound/mods/paula.h b/sound/mods/paula.h
index e3c6002451..fbc984f121 100644
--- a/sound/mods/paula.h
+++ b/sound/mods/paula.h
@@ -40,12 +40,27 @@ namespace Audio {
class Paula : public AudioStream {
public:
static const int NUM_VOICES = 4;
+ enum {
+ kPalSystemClock = 7093790,
+ kNtscSystemClock = 7159090,
+ kPalCiaClock = kPalSystemClock / 10,
+ kNtscCiaClock = kNtscSystemClock / 10
+ };
- Paula(bool stereo = false, int rate = 44100, int interruptFreq = 0);
+ Paula(bool stereo = false, int rate = 44100, uint interruptFreq = 0);
~Paula();
bool playing() const { return _playing; }
- void setInterruptFreq(int freq) { _curInt = _intFreq = freq; }
+ void setTimerBaseValue( uint32 ticksPerSecond ) { _timerBase = ticksPerSecond; }
+ uint32 getTimerBaseValue() { return _timerBase; }
+ void setSingleInterrupt(uint sampleDelay) { assert(sampleDelay < _intFreq); _curInt = sampleDelay; }
+ void setSingleInterruptUnscaled(uint timerDelay) {
+ setSingleInterrupt((uint)(((double)timerDelay * getRate()) / _timerBase));
+ }
+ void setInterruptFreq(uint sampleDelay) { _intFreq = sampleDelay; _curInt = 0; }
+ void setInterruptFreqUnscaled(uint timerDelay) {
+ setInterruptFreq((uint)(((double)timerDelay * getRate()) / _timerBase));
+ }
void clearVoice(byte voice);
void clearVoices() { for (int i = 0; i < NUM_VOICES; ++i) clearVoice(i); }
void startPlay(void) { _playing = true; }
@@ -65,9 +80,11 @@ protected:
uint32 length;
uint32 lengthRepeat;
int16 period;
+ int16 periodRepeat;
byte volume;
frac_t offset;
byte panning; // For stereo mixing: 0 = far left, 255 = far right
+ int dmaCount;
};
bool _end;
@@ -90,9 +107,24 @@ protected:
_voice[channel].panning = panning;
}
+ void disableChannel(byte channel) {
+ assert(channel < NUM_VOICES);
+ _voice[channel].data = 0;
+ }
+
+ void enableChannel(byte channel) {
+ assert(channel < NUM_VOICES);
+ Channel &ch = _voice[channel];
+ ch.data = ch.dataRepeat;
+ ch.length = ch.lengthRepeat;
+ // actually first 2 bytes are dropped?
+ ch.offset = intToFrac(0);
+ ch.period = ch.periodRepeat;
+ }
+
void setChannelPeriod(byte channel, int16 period) {
assert(channel < NUM_VOICES);
- _voice[channel].period = period;
+ _voice[channel].periodRepeat = period;
}
void setChannelVolume(byte channel, byte volume) {
@@ -100,6 +132,17 @@ protected:
_voice[channel].volume = volume;
}
+ void setChannelSampleStart(byte channel, const int8 *data) {
+ assert(channel < NUM_VOICES);
+ _voice[channel].dataRepeat = data;
+ }
+
+ void setChannelSampleLen(byte channel, uint32 length) {
+ assert(channel < NUM_VOICES);
+ assert(length < 32768/2);
+ _voice[channel].lengthRepeat = 2 * length;
+ }
+
void setChannelData(uint8 channel, const int8 *data, const int8 *dataRepeat, uint32 length, uint32 lengthRepeat, int32 offset = 0) {
assert(channel < NUM_VOICES);
@@ -110,11 +153,14 @@ protected:
assert(lengthRepeat < 32768);
Channel &ch = _voice[channel];
- ch.data = data;
+
+ ch.dataRepeat = data;
+ ch.lengthRepeat = length;
+ enableChannel(channel);
+ ch.offset = intToFrac(offset);
+
ch.dataRepeat = dataRepeat;
- ch.length = length;
ch.lengthRepeat = lengthRepeat;
- ch.offset = intToFrac(offset);
}
void setChannelOffset(byte channel, frac_t offset) {
@@ -128,13 +174,25 @@ protected:
return _voice[channel].offset;
}
+ int getChannelDmaCount(byte channel) {
+ assert(channel < NUM_VOICES);
+ return _voice[channel].dmaCount;
+ }
+
+ void setChannelDmaCount(byte channel, int dmaVal = 0) {
+ assert(channel < NUM_VOICES);
+ _voice[channel].dmaCount = dmaVal;
+ }
+
private:
Channel _voice[NUM_VOICES];
const bool _stereo;
const int _rate;
- int _intFreq;
- int _curInt;
+ const double _periodScale;
+ uint _intFreq;
+ uint _curInt;
+ uint32 _timerBase;
bool _playing;
template<bool stereo>
diff --git a/sound/mods/soundfx.cpp b/sound/mods/soundfx.cpp
index 101d8a077d..3af8ca19c6 100644
--- a/sound/mods/soundfx.cpp
+++ b/sound/mods/soundfx.cpp
@@ -46,8 +46,7 @@ public:
enum {
NUM_CHANNELS = 4,
- NUM_INSTRUMENTS = 15,
- CIA_FREQ = 715909
+ NUM_INSTRUMENTS = 15
};
SoundFx(int rate, bool stereo);
@@ -75,12 +74,12 @@ protected:
uint16 _curPos;
uint8 _ordersTable[128];
uint8 *_patternData;
- int _eventsFreq;
uint16 _effects[NUM_CHANNELS];
};
SoundFx::SoundFx(int rate, bool stereo)
: Paula(stereo, rate) {
+ setTimerBaseValue(kPalCiaClock);
_ticks = 0;
_delay = 0;
memset(_instruments, 0, sizeof(_instruments));
@@ -89,7 +88,6 @@ SoundFx::SoundFx(int rate, bool stereo)
_curPos = 0;
memset(_ordersTable, 0, sizeof(_ordersTable));
_patternData = 0;
- _eventsFreq = 0;
memset(_effects, 0, sizeof(_effects));
}
@@ -167,8 +165,7 @@ void SoundFx::play() {
_curPos = 0;
_curOrder = 0;
_ticks = 0;
- _eventsFreq = CIA_FREQ / _delay;
- setInterruptFreq(getRate() / _eventsFreq);
+ setInterruptFreqUnscaled(_delay);
startPaula();
}
@@ -252,7 +249,7 @@ void SoundFx::handleTick() {
}
void SoundFx::disablePaulaChannel(uint8 channel) {
- setChannelPeriod(channel, 0);
+ disableChannel(channel);
}
void SoundFx::setupPaulaChannel(uint8 channel, const int8 *data, uint16 len, uint16 repeatPos, uint16 repeatLen) {
diff --git a/sound/mods/tfmx.cpp b/sound/mods/tfmx.cpp
new file mode 100644
index 0000000000..c3d7288990
--- /dev/null
+++ b/sound/mods/tfmx.cpp
@@ -0,0 +1,996 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/scummsys.h"
+#include "common/endian.h"
+#include "common/stream.h"
+#include "common/util.h"
+#include "common/debug.h"
+
+#include "sound/mods/tfmx.h"
+#ifdef _MSC_VER
+#include "tfmx/tfmxdebug.h"
+#endif
+namespace Audio {
+
+const uint16 Tfmx::noteIntervalls[64] = {
+ 1710, 1614, 1524, 1438, 1357, 1281, 1209, 1141, 1077, 1017, 960, 908,
+ 856, 810, 764, 720, 680, 642, 606, 571, 539, 509, 480, 454,
+ 428, 404, 381, 360, 340, 320, 303, 286, 270, 254, 240, 227,
+ 214, 202, 191, 180, 170, 160, 151, 143, 135, 127, 120, 113,
+ 214, 202, 191, 180, 170, 160, 151, 143, 135, 127, 120, 113,
+ 214, 202, 191, 180 };
+
+Tfmx::Tfmx(int rate, bool stereo)
+: Paula(stereo, rate), _resource(), _playerCtx() {
+ _playerCtx.stopWithLastPattern = false;
+
+ for (int i = 0; i < kNumVoices; ++i)
+ _channelCtx[i].paulaChannel = (byte)i;
+
+ _playerCtx.song = -1;
+ _playerCtx.volume = 0x40;
+ _playerCtx.patternSkip = 6;
+ stopPatternChannels();
+ stopMacroChannels();
+
+ setTimerBaseValue(kPalCiaClock);
+ setInterruptFreqUnscaled(kPalDefaultCiaVal);
+}
+
+Tfmx::~Tfmx() {
+}
+
+void Tfmx::interrupt() {
+ assert(!_end);
+ ++_playerCtx.tickCount;
+ for (int i = 0; i < kNumVoices; ++i) {
+ ChannelContext &channel = _channelCtx[i];
+ if (channel.dmaIntCount) {
+ // wait for DMA Interupts to happen
+ int doneDma = getChannelDmaCount(channel.paulaChannel);
+ if (doneDma >= channel.dmaIntCount) {
+ channel.dmaIntCount = 0;
+ channel.macroRun = true;
+ }
+ }
+
+ if (channel.sfxLockTime >= 0)
+ --channel.sfxLockTime;
+ else {
+ channel.sfxLocked = false;
+ channel.customMacroPrio = 0;
+ }
+
+ // externally queued macros
+ if (channel.customMacro) {
+ const byte *const noteCmd = (const byte *)&channel.customMacro;
+ channel.sfxLocked = false;
+ noteCommand(noteCmd[0], noteCmd[1], (noteCmd[2] & 0xF0) | (uint8)i, noteCmd[3]);
+ channel.customMacro = 0;
+ channel.sfxLocked = (channel.customMacroPrio != 0);
+ }
+
+ // apply timebased effects on Parameters
+ if (channel.macroSfxRun > 0)
+ effects(channel);
+
+ // see if we have to run the macro-program
+ if (channel.macroRun) {
+ if (!channel.macroWait) {
+ macroRun(channel);
+ //assert( !channel.deferWait ); // we can remove this variable as it should be never true after macroRun?
+ }
+ else
+ --channel.macroWait;
+ }
+
+ Paula::setChannelPeriod(channel.paulaChannel, channel.period);
+ if (channel.macroSfxRun >= 0)
+ channel.macroSfxRun = 1;
+
+ // TODO: handling pending DMAOff?
+ }
+
+ // Patterns are only processed each _playerCtx.timerCount + 1 tick
+ if (_playerCtx.song >= 0 && !_playerCtx.patternCount--) {
+ _playerCtx.patternCount = _playerCtx.patternSkip;
+ advancePatterns();
+ }
+}
+
+void Tfmx::effects(ChannelContext &channel) {
+ // addBegin
+ if (channel.addBeginLength) {
+ channel.sampleStart += channel.addBeginDelta;
+ Paula::setChannelSampleStart(channel.paulaChannel, _resource.getSamplePtr(channel.sampleStart));
+ if (!(--channel.addBeginCount)) {
+ channel.addBeginCount = channel.addBeginLength;
+ channel.addBeginDelta = -channel.addBeginDelta;
+ }
+ }
+
+ // vibrato
+ if (channel.vibLength) {
+ channel.vibValue += channel.vibDelta;
+ if (--channel.vibCount == 0) {
+ channel.vibCount = channel.vibLength;
+ channel.vibDelta = -channel.vibDelta;
+ }
+ if (!channel.portaDelta) {
+ // 16x16 bit multiplication, casts needed for the right results
+ channel.period = (uint16)(((uint32)channel.refPeriod * (uint16)((1 << 11) + channel.vibValue)) >> 11);
+ }
+ }
+
+ // portamento
+ if (channel.portaDelta && --channel.portaCount == 0) {
+ channel.portaCount = channel.portaSkip;
+
+ bool resetPorta = true;
+ const uint16 period = channel.refPeriod;
+ uint16 portaVal = channel.portaValue;
+
+ if (period > portaVal) {
+ portaVal = ((uint32)portaVal * (uint16)((1 << 8) + channel.portaDelta)) >> 8;
+ resetPorta = (period <= portaVal);
+
+ } else if (period < portaVal) {
+ portaVal = ((uint32)portaVal * (uint16)((1 << 8) - channel.portaDelta)) >> 8;
+ resetPorta = (period >= portaVal);
+ }
+
+ if (resetPorta) {
+ channel.portaDelta = 0;
+ channel.portaValue = period & 0x7FF;
+ } else
+ channel.period = channel.portaValue = portaVal & 0x7FF;
+ }
+
+ // envelope
+ if (channel.envSkip && !channel.envCount--) {
+ channel.envCount = channel.envSkip;
+
+ const int8 endVol = channel.envEndVolume;
+ int8 volume = channel.volume;
+ bool resetEnv = true;
+
+ if (endVol > volume) {
+ volume += channel.envDelta;
+ resetEnv = endVol <= volume;
+ } else {
+ volume -= channel.envDelta;
+ resetEnv = volume <= 0 || endVol >= volume;
+ }
+
+ if (resetEnv) {
+ channel.envSkip = 0;
+ volume = endVol;
+ }
+ channel.volume = volume;
+ }
+
+ // Fade
+ if (_playerCtx.fadeDelta && !(--_playerCtx.fadeCount)) {
+ _playerCtx.fadeCount = _playerCtx.fadeSkip;
+
+ _playerCtx.volume += _playerCtx.fadeDelta;
+ if (_playerCtx.volume == _playerCtx.fadeEndVolume)
+ _playerCtx.fadeDelta = 0;
+ }
+
+ // Volume
+ const uint8 finVol = _playerCtx.volume * channel.volume >> 6;
+ Paula::setChannelVolume(channel.paulaChannel, finVol);
+}
+
+static void warnMacroUnimplemented(const byte *macroPtr, int level) {
+ if (level > 0)
+ return;
+ if (level == 0)
+ debug("Warning - Macro not supported:");
+ else
+ debug("Warning - Macro not completely supported:");
+#ifdef _MSC_VER
+ displayMacroStep(macroPtr);
+#endif
+}
+
+void Tfmx::macroRun(ChannelContext &channel) {
+ bool deferWait = false;
+ for (;;) {
+ const byte *const macroPtr = (byte *)(_resource.getMacroPtr(channel.macroOffset) + channel.macroStep);
+ ++channel.macroStep;
+
+ switch (macroPtr[0]) {
+ case 0x00: // Reset + DMA Off. Parameters: deferWait, addset, vol
+ clearEffects(channel);
+ // FT
+ case 0x13: // DMA Off. Parameters: deferWait, addset, vol
+ // TODO: implement PArameters
+ Paula::disableChannel(channel.paulaChannel);
+ channel.deferWait = deferWait = (macroPtr[1] != 0);
+ if (deferWait) {
+ // if set, then we expect a DMA On in the same tick.
+ channel.period = 4;
+ //Paula::setChannelPeriod(channel.paulaChannel, channel.period);
+ Paula::setChannelSampleLen(channel.paulaChannel, 1);
+ // in this state we then need to allow some commands that normally
+ // would halt the macroprogamm to continue instead.
+ // those commands are: Wait, WaitDMA, AddPrevNote, AddNote, SetNote, <unknown Cmd>
+ // DMA On is affected aswell
+ // TODO remember time disabled, remember pending dmaoff?.
+ } else {
+ //TODO ?
+ }
+
+ if (macroPtr[2])
+ channel.volume = macroPtr[3];
+ else if (macroPtr[3])
+ channel.volume = channel.relVol * 3 + macroPtr[3];
+ else
+ continue;
+ Paula::setChannelVolume(channel.paulaChannel, channel.volume);
+ continue;
+
+ case 0x01: // DMA On
+ // TODO: Parameter macroPtr[1] - en-/disable effects
+ if (macroPtr[1])
+ debug("Tfmx: DMA On %i", (int8)macroPtr[1]);
+ channel.dmaIntCount = 0;
+ if (deferWait) {
+ // TODO
+ // there is actually a small delay in the player, but I think that
+ // only allows to clear DMA-State on real Hardware
+ }
+ Paula::setChannelPeriod(channel.paulaChannel, channel.period);
+ Paula::enableChannel(channel.paulaChannel);
+ channel.deferWait = deferWait = false;
+ continue;
+
+ case 0x02: // SetBeginn. Parameters: SampleOffset(L)
+ channel.addBeginLength = 0;
+ channel.sampleStart = READ_BE_UINT32(macroPtr) & 0xFFFFFF;
+ Paula::setChannelSampleStart(channel.paulaChannel, _resource.getSamplePtr(channel.sampleStart));
+ continue;
+
+ case 0x03: // SetLength. Parameters: SampleLength(W)
+ channel.sampleLen = READ_BE_UINT16(&macroPtr[2]);
+ Paula::setChannelSampleLen(channel.paulaChannel, channel.sampleLen);
+ continue;
+
+ case 0x04: // Wait. Parameters: Ticks to wait(W).
+ // TODO: some unkown Parameter? (macroPtr[1] & 1)
+ channel.macroWait = READ_BE_UINT16(&macroPtr[2]);
+ return;
+
+ case 0x10: // Loop Key Up. Parameters: Loopcount, MacroStep(W)
+ if (channel.keyUp)
+ continue;
+ // FT
+ case 0x05: // Loop. Parameters: Loopcount, MacroStep(W)
+ if (channel.macroLoopCount != 0) {
+ if (channel.macroLoopCount == 0xFF)
+ channel.macroLoopCount = macroPtr[1];
+ channel.macroStep = READ_BE_UINT16(&macroPtr[2]);
+ }
+ --channel.macroLoopCount;
+ continue;
+
+ case 0x06: // Jump. Parameters: MacroIndex, MacroStep(W)
+ channel.macroIndex = macroPtr[1] & (kMaxMacroOffsets - 1);
+ channel.macroOffset = _macroOffset[macroPtr[1] & (kMaxMacroOffsets - 1)];
+ channel.macroStep = READ_BE_UINT16(&macroPtr[2]);
+ channel.macroLoopCount = 0xFF;
+ continue;
+
+ case 0x07: // Stop Macro
+ channel.macroRun = false;
+ --channel.macroStep;
+ return;
+
+ case 0x08: // AddNote. Parameters: Note, Finetune(W)
+ setNoteMacro(channel, channel.note + macroPtr[1], READ_BE_UINT16(&macroPtr[2]));
+ break;
+
+ case 0x09: // SetNote. Parameters: Note, Finetune(W)
+ setNoteMacro(channel, macroPtr[1], READ_BE_UINT16(&macroPtr[2]));
+ break;
+
+ case 0x0A: // Clear Effects
+ clearEffects(channel);
+ continue;
+
+ case 0x0B: // Portamento. Parameters: count, speed
+ channel.portaSkip = macroPtr[1];
+ channel.portaCount = 1;
+ // if porta is already running, then keep using old value
+ if (!channel.portaDelta)
+ channel.portaValue = channel.refPeriod;
+ channel.portaDelta = READ_BE_UINT16(&macroPtr[2]);
+ continue;
+
+ case 0x0C: // Vibrato. Parameters: Speed, intensity
+ channel.vibLength = macroPtr[1];
+ channel.vibCount = macroPtr[1] / 2;
+ channel.vibDelta = macroPtr[3];
+ if (!channel.portaDelta) {
+ channel.period = channel.refPeriod;
+ //Paula::setChannelPeriod(channel.paulaChannel, channel.period);
+ channel.vibValue = 0;
+ }
+ continue;
+
+ case 0x0D: // Add Volume. Parameters: note, addNoteFlag, volume
+ if (macroPtr[2] == 0xFE)
+ setNoteMacro(channel, channel.note + macroPtr[1], 0);
+ channel.volume = channel.relVol * 3 + macroPtr[3];
+ continue;
+
+ case 0x0E: // Set Volume. Parameters: note, addNoteFlag, volume
+ if (macroPtr[2] == 0xFE)
+ setNoteMacro(channel, channel.note + macroPtr[1], 0);
+ channel.volume = macroPtr[3];
+ continue;
+
+ case 0x0F: // Envelope. Parameters: speed, count, endvol
+ channel.envDelta = macroPtr[1];
+ channel.envCount = channel.envSkip = macroPtr[2];
+ channel.envEndVolume = macroPtr[3];
+ continue;
+
+ case 0x11: // AddBegin. Parameters: times, Offset(W)
+ channel.addBeginLength = channel.addBeginCount = macroPtr[1];
+ channel.addBeginDelta = (int16)READ_BE_UINT16(&macroPtr[2]);
+ channel.sampleStart += channel.addBeginDelta;
+ Paula::setChannelSampleStart(channel.paulaChannel, _resource.getSamplePtr(channel.sampleStart));
+ warnMacroUnimplemented(macroPtr, 1);
+ continue;
+
+ case 0x12: // AddLen. Parameters: added Length(W)
+ channel.sampleLen += (int16)READ_BE_UINT16(&macroPtr[2]);
+ Paula::setChannelSampleLen(channel.paulaChannel, channel.sampleLen);
+ continue;
+
+ case 0x14: // Wait key up. Parameters: wait cycles
+ if (channel.keyUp || channel.macroLoopCount == 0) {
+ channel.macroLoopCount = 0xFF;
+ continue;
+ } else if (channel.macroLoopCount == 0xFF)
+ channel.macroLoopCount = macroPtr[3];
+ --channel.macroLoopCount;
+ --channel.macroStep;
+ return;
+
+ case 0x15: // Subroutine. Parameters: MacroIndex, Macrostep(W)
+ channel.macroReturnOffset = channel.macroOffset;
+ channel.macroReturnStep = channel.macroStep;
+
+ channel.macroOffset = _macroOffset[macroPtr[1] & (kMaxMacroOffsets - 1)];
+ channel.macroStep = READ_BE_UINT16(&macroPtr[2]);
+ // TODO: MI does some weird stuff there. Figure out which varioables need to be set
+ continue;
+
+ case 0x16: // Return from Sub.
+ channel.macroOffset = channel.macroReturnOffset;
+ channel.macroStep = channel.macroReturnStep;
+ continue;
+
+ case 0x17: // set Period. Parameters: Period(W)
+ channel.refPeriod = READ_BE_UINT16(&macroPtr[2]);
+ if (!channel.portaDelta) {
+ channel.period = channel.refPeriod;
+ //Paula::setChannelPeriod(channel.paulaChannel, channel.period);
+ }
+ continue;
+
+ case 0x18: { // Sampleloop. Parameters: Offset from Samplestart(W)
+ // TODO: MI loads 24 bit, but thats useless?
+ const uint16 temp = /* ((int8)macroPtr[1] << 16) | */ READ_BE_UINT16(&macroPtr[2]);
+ if (macroPtr[1] || (temp & 1))
+ warning("Tfmx: Problematic value for sampleloop: %i", (macroPtr[1] << 16) | temp);
+ channel.sampleStart += temp & 0xFFFE;
+ channel.sampleLen -= (temp / 2) /* & 0x7FFF */;
+ Paula::setChannelSampleStart(channel.paulaChannel, _resource.getSamplePtr(channel.sampleStart));
+ Paula::setChannelSampleLen(channel.paulaChannel, channel.sampleLen);
+ continue;
+ }
+ case 0x19: // set one-shot Sample
+ channel.addBeginLength = 0;
+ channel.sampleStart = 0;
+ channel.sampleLen = 1;
+ Paula::setChannelSampleStart(channel.paulaChannel, _resource.getSamplePtr(0));
+ Paula::setChannelSampleLen(channel.paulaChannel, 1);
+ continue;
+
+ case 0x1A: // Wait on DMA. Parameters: Cycles-1(W) to wait
+ channel.dmaIntCount = READ_BE_UINT16(&macroPtr[2]) + 1;
+ channel.macroRun = false;
+ Paula::setChannelDmaCount(channel.paulaChannel);
+ break;
+
+ case 0x1B: // Random play. Parameters: macro/speed/mode
+ warnMacroUnimplemented(macroPtr, 0);
+ continue;
+
+ case 0x1C: // Branch on Note. Parameters: note/macrostep(W)
+ if (channel.note > macroPtr[1])
+ channel.macroStep = READ_BE_UINT16(&macroPtr[2]);
+ continue;
+
+ case 0x1D: // Branch on Volume. Parameters: volume/macrostep(W)
+ if (channel.volume > macroPtr[1])
+ channel.macroStep = READ_BE_UINT16(&macroPtr[2]);
+ continue;
+
+ case 0x1E: // Addvol+note. Parameters: note/CONST./volume
+ warnMacroUnimplemented(macroPtr, 0);
+ continue;
+
+ case 0x1F: // AddPrevNote. Parameters: Note, Finetune(W)
+ setNoteMacro(channel, channel.prevNote + macroPtr[1], READ_BE_UINT16(&macroPtr[2]));
+ break;
+
+ case 0x20: // Signal. Parameters: signalnumber/value
+ if (_playerCtx.signal)
+ _playerCtx.signal[macroPtr[1]] = READ_BE_UINT16(&macroPtr[2]);
+ continue;
+
+ case 0x21: // Play macro. Parameters: macro/chan/detune
+ noteCommand(channel.note, (channel.relVol << 4) | macroPtr[1], macroPtr[2], macroPtr[3]);
+ continue;
+ #if defined(TFMX_NOT_IMPLEMENTED)
+ // used by Gem`X according to the docs
+ case 0x22: // SID setbeg. Parameters: sample-startadress
+ return true;
+ case 0x23: // SID setlen. Parameters: buflen/sourcelen
+ return true;
+ case 0x24: // SID op3 ofs. Parameters: offset
+ return true;
+ case 0x25: // SID op3 frq. Parameters: speed/amplitude
+ return true;
+ case 0x26: // SID op2 ofs. Parameters: offset
+ return true;
+ case 0x27: // SID op2 frq. Parameters: speed/amplitude
+ return true;
+ case 0x28: // ID op1. Parameters: speed/amplitude/TC
+ return true;
+ case 0x29: // SID stop. Parameters: flag (1=clear all)
+ return true;
+ // 30-34 used by Carribean Disaster
+ #endif
+ default:
+ warnMacroUnimplemented(macroPtr, 0);
+ }
+ if (!deferWait)
+ return;
+ }
+}
+
+void Tfmx::advancePatterns() {
+startPatterns:
+ int runningPatterns = 0;
+
+ for (int i = 0; i < kNumChannels; ++i) {
+ const uint8 pattCmd = _patternCtx[i].command;
+ if (pattCmd < 0x90) { // execute Patternstep
+ ++runningPatterns;
+ if (_patternCtx[i].wait == 0) {
+ // issue all Steps for this tick
+ const bool pendingTrackstep = patternRun(_patternCtx[i]);
+
+ if (pendingTrackstep) {
+ // we load the next Trackstep Command and then process all Channels again
+ trackRun(true);
+ goto startPatterns;
+ }
+
+ } else
+ --_patternCtx[i].wait;
+
+ } else if (pattCmd == 0xFE) { // Stop voice in pattern.expose
+ _patternCtx[i].command = 0xFF;
+ ChannelContext &channel = _channelCtx[_patternCtx[i].expose & (kNumVoices - 1)];
+ if (!channel.sfxLocked) {
+ clearMacroProgramm(channel);
+ Paula::disableChannel(channel.paulaChannel);
+ }
+ } // else this pattern-Channel is stopped
+ }
+ if (_playerCtx.stopWithLastPattern && !runningPatterns) {
+ stopPaula();
+ }
+}
+
+static void warnPatternUnimplemented(const byte *patternPtr, int level) {
+ if (level > 0)
+ return;
+ if (level == 0)
+ debug("Warning - Pattern not supported:");
+ else
+ debug("Warning - Pattern not completely supported:");
+#ifdef _MSC_VER
+ displayPatternstep(patternPtr);
+#endif
+}
+
+bool Tfmx::patternRun(PatternContext &pattern) {
+ for (;;) {
+ const byte *const patternPtr = (byte *)(_resource.getPatternPtr(pattern.offset) + pattern.step);
+ ++pattern.step;
+ const byte pattCmd = patternPtr[0];
+
+ if (pattCmd < 0xF0) { // Playnote
+ bool doWait = false;
+ byte noteCmd = pattCmd + pattern.expose;
+ byte param3 = patternPtr[3];
+ if (pattCmd < 0xC0) { // Note
+ if (pattCmd >= 0x80) { // Wait
+ pattern.wait = param3;
+ param3 = 0;
+ doWait = true;
+ }
+ noteCmd &= 0x3F;
+ } // else Portamento
+ noteCommand(noteCmd, patternPtr[1], patternPtr[2], param3);
+ if (doWait)
+ return false;
+
+ } else { // Patterncommand
+ switch (pattCmd & 0xF) {
+ case 0: // End Pattern + Next Trackstep
+ pattern.command = 0xFF;
+ --pattern.step;
+ return true;
+
+ case 1: // Loop Pattern. Parameters: Loopcount, PatternStep(W)
+ if (pattern.loopCount != 0) {
+ if (pattern.loopCount == 0xFF)
+ pattern.loopCount = patternPtr[1];
+ pattern.step = READ_BE_UINT16(&patternPtr[2]);
+ }
+ --pattern.loopCount;
+ continue;
+
+ case 2: // Jump. Parameters: PatternIndex, PatternStep(W)
+ pattern.offset = _patternOffset[patternPtr[1]];
+ pattern.step = READ_BE_UINT16(&patternPtr[2]);
+ continue;
+
+ case 3: // Wait. Paramters: ticks to wait
+ pattern.wait = patternPtr[1];
+ return false;
+
+ case 14: // Stop custompattern
+ // TODO ?
+ warnPatternUnimplemented(patternPtr, 1);
+ // FT
+ case 4: // Stop this pattern
+ pattern.command = 0xFF;
+ --pattern.step;
+ // TODO: try figuring out if this was the last Channel?
+ return false;
+
+ case 5: // Key Up Signal
+ if (!_channelCtx[patternPtr[2] & (kNumVoices - 1)].sfxLocked)
+ _channelCtx[patternPtr[2] & (kNumVoices - 1)].keyUp = true;
+ continue;
+
+ case 6: // Vibrato
+ case 7: // Envelope
+ noteCommand(pattCmd, patternPtr[1], patternPtr[2], patternPtr[3]);
+ continue;
+
+ case 8: // Subroutine
+ warnPatternUnimplemented(patternPtr, 0);
+ continue;
+
+ case 9: // Return from Subroutine
+ warnPatternUnimplemented(patternPtr, 0);
+ continue;
+
+ case 10: // fade master volume
+ _playerCtx.fadeCount = _playerCtx.fadeSkip = patternPtr[1];
+ _playerCtx.fadeEndVolume = (int8)patternPtr[3];
+ if (_playerCtx.fadeSkip) {
+ const int diff = _playerCtx.fadeEndVolume - _playerCtx.volume;
+ _playerCtx.fadeDelta = (diff != 0) ? ((diff > 0) ? 1 : -1) : 0;
+ } else {
+ _playerCtx.volume = _playerCtx.fadeEndVolume;
+ _playerCtx.fadeDelta = 0;
+ }
+ ++_trackCtx.posInd;
+ continue;
+
+ case 11: { // play pattern. Parameters: patternCmd, channel, expose
+ PatternContext &target = _patternCtx[patternPtr[2] & (kNumChannels - 1)];
+
+ target.command = patternPtr[1];
+ target.offset = _patternOffset[patternPtr[1] & (kMaxPatternOffsets - 1)];
+ target.expose = patternPtr[3];
+ target.step = 0;
+ target.wait = 0;
+ target.loopCount = 0xFF;
+ }
+ continue;
+
+ case 12: // Lock
+ _channelCtx[patternPtr[2] & (kNumVoices - 1)].sfxLocked = (patternPtr[1] != 0);
+ _channelCtx[patternPtr[2] & (kNumVoices - 1)].sfxLockTime = patternPtr[3];
+ continue;
+
+ case 13: // Cue
+ if (_playerCtx.signal)
+ _playerCtx.signal[patternPtr[1]] = READ_BE_UINT16(&patternPtr[2]);
+ continue;
+
+ case 15: // NOP
+ continue;
+ }
+ }
+ }
+}
+
+bool Tfmx::trackRun(const bool incStep) {
+ assert(_playerCtx.song >= 0);
+ if (incStep) {
+ // TODO Optionally disable looping
+ if (_trackCtx.posInd == _trackCtx.stopInd)
+ _trackCtx.posInd = _trackCtx.startInd;
+ else
+ ++_trackCtx.posInd;
+ }
+ for (;;) {
+ const uint16 *const trackData = _resource.getTrackPtr(_trackCtx.posInd);
+
+ if (trackData[0] != FROM_BE_16(0xEFFE)) {
+ // 8 commands for Patterns
+ for (int i = 0; i < 8; ++i) {
+ const uint patCmd = READ_BE_UINT16(&trackData[i]);
+ // First byte is pattern number
+ const uint patNum = (patCmd >> 8);
+ // if highest bit is set then keep previous pattern
+ if (patNum < 0x80) {
+ _patternCtx[i].step = 0;
+ _patternCtx[i].wait = 0;
+ _patternCtx[i].loopCount = 0xFF;
+ _patternCtx[i].offset = _patternOffset[patNum];
+ }
+ _patternCtx[i].command = (uint8)patNum;
+ _patternCtx[i].expose = patCmd & 0xFF;
+ }
+ return true;
+
+ } else {
+ // 16 byte Trackstep Command
+ switch (READ_BE_UINT16(&trackData[1])) {
+ case 0: // Stop Player. No Parameters
+ stopPaula();
+ return false;
+
+ case 1: // Branch/Loop section of tracksteps. Parameters: branch target, loopcount
+ if (_trackCtx.loopCount != 0) {
+ if (_trackCtx.loopCount < 0)
+ _trackCtx.loopCount = READ_BE_UINT16(&trackData[3]);
+ _trackCtx.posInd = READ_BE_UINT16(&trackData[2]);
+ continue;
+ }
+ --_trackCtx.loopCount;
+ break;
+
+ case 2: { // Set Tempo. Parameters: tempo, divisor
+ _playerCtx.patternCount = _playerCtx.patternSkip = READ_BE_UINT16(&trackData[2]); // tempo
+ const uint16 temp = READ_BE_UINT16(&trackData[3]); // divisor
+
+ if (!(temp & 0x8000) && (temp & 0x1FF))
+ setInterruptFreqUnscaled(temp & 0x1FF);
+ break;
+ }
+ case 4: // Fade
+ _playerCtx.fadeCount = _playerCtx.fadeSkip = (uint8)READ_BE_UINT16(&trackData[2]);
+ _playerCtx.fadeEndVolume = (int8)READ_BE_UINT16(&trackData[3]);
+
+ if (_playerCtx.fadeSkip) {
+ const int diff = _playerCtx.fadeEndVolume - _playerCtx.volume;
+ _playerCtx.fadeDelta = (diff != 0) ? ((diff > 0) ? 1 : -1) : 0;
+ } else {
+ _playerCtx.volume = _playerCtx.fadeEndVolume;
+ _playerCtx.fadeDelta = 0;
+ }
+ break;
+
+ case 3: // Unknown, stops player aswell
+ default:
+ debug("Unknown Command: %02X", READ_BE_UINT16(&trackData[1]));
+ // MI-Player handles this by stopping the player, we just continue
+ }
+ }
+
+ if (_trackCtx.posInd == _trackCtx.stopInd) {
+ warning("Tfmx: Reached invalid Song-Position");
+ return false;
+ }
+ ++_trackCtx.posInd;
+ }
+}
+
+void Tfmx::noteCommand(const uint8 note, const uint8 param1, const uint8 param2, const uint8 param3) {
+ ChannelContext &channel = _channelCtx[param2 & (kNumVoices - 1)];
+
+ if (note == 0xFC) { // Lock
+ channel.sfxLocked = (param1 != 0);
+ channel.sfxLockTime = param3; // only 1 byte read!
+ return;
+ }
+ if (channel.sfxLocked)
+ return;
+
+ if (note < 0xC0) { // Play Note
+ channel.prevNote = channel.note;
+ channel.note = note;
+ channel.macroIndex = param1 & (kMaxMacroOffsets - 1);
+ channel.macroOffset = _macroOffset[param1 & (kMaxMacroOffsets - 1)];
+ channel.relVol = (param2 >> 4) & 0xF;
+ channel.fineTune = (int8)param3;
+
+ initMacroProgramm(channel);
+ channel.keyUp = false; // key down = playing a Note
+
+ } else if (note < 0xF0) { // Portamento
+ channel.portaSkip = param1;
+ channel.portaCount = 1;
+ if (!channel.portaDelta)
+ channel.portaValue = channel.refPeriod;
+ channel.portaDelta = param3;
+
+ channel.note = note & 0x3F;
+ channel.refPeriod = noteIntervalls[channel.note];
+ } else switch (note & 0xF) { // Command
+ case 5: // Key Up Signal
+ channel.keyUp = true;
+ break;
+ case 6: // Vibratio
+ channel.vibLength = param1 & 0xFE;
+ channel.vibCount = param1 / 2;
+ channel.vibValue = 0;
+ break;
+ case 7: // Envelope
+ channel.envDelta = param1;
+ channel.envSkip = channel.envCount = (param2 >> 4) + 1;
+ channel.envEndVolume = param3;
+ break;
+ }
+}
+
+bool Tfmx::load(Common::SeekableReadStream &musicData, Common::SeekableReadStream &sampleData) {
+ bool res;
+
+ assert(0 == _resource._mdatData);
+ assert(0 == _resource._sampleData);
+
+ // TODO: Sanity checks if we have a valid TFMX-Module
+ // TODO: check for Stream-Errors (other than using asserts)
+
+ // 0x0000: 10 Bytes Header "TFMX-SONG "
+ // 0x000A: int16 ?
+ // 0x000C: int32 ?
+ musicData.read(_resource.header, 10);
+ _resource.headerFlags = musicData.readUint16BE();
+ _resource.headerUnknown = musicData.readUint32BE();
+
+ // This might affect timing
+ // bool isPalModule = (_resource.headerFlags & 2) != 0;
+
+ // 0x0010: 6*40 Textfield
+ musicData.read(_resource.textField, 6 * 40);
+
+ /* 0x0100: Songstart x 32*/
+ for (int i = 0; i < kNumSubsongs; ++i)
+ _subsong[i].songstart = musicData.readUint16BE();
+
+ /* 0x0140: Songend x 32*/
+ for (int i = 0; i < kNumSubsongs; ++i)
+ _subsong[i].songend = musicData.readUint16BE();
+
+ /* 0x0180: Tempo x 32*/
+ for (int i = 0; i < kNumSubsongs; ++i)
+ _subsong[i].tempo = musicData.readUint16BE();
+
+ /* 0x01c0: unused ? */
+ musicData.skip(16);
+
+ /* 0x01d0: trackstep, pattern data p, macro data p */
+ uint32 offTrackstep = musicData.readUint32BE();
+ uint32 offPatternP = musicData.readUint32BE();
+ uint32 offMacroP = musicData.readUint32BE();
+ _resource._sfxTableOffset = 0x200;
+ bool getSfxIndex = false;
+
+ // This is how MI`s TFMX-Player tests for unpacked Modules.
+ if (offTrackstep == 0) {
+ offTrackstep = 0x600 + 0x200;
+ offPatternP = 0x200 + 0x200;
+ offMacroP = 0x400 + 0x200;
+ getSfxIndex = true;
+ _resource._sfxTableOffset = 0x5FC;
+ }
+
+ _resource._trackstepOffset = offTrackstep;
+
+ // Read in pattern starting offsets
+ musicData.seek(offPatternP);
+ for (int i = 0; i < kMaxPatternOffsets; ++i)
+ _patternOffset[i] = musicData.readUint32BE();
+
+ res = musicData.err();
+ assert(!res);
+
+ if (getSfxIndex)
+ _resource._sfxTableOffset = _patternOffset[127];
+
+ // Read in macro starting offsets
+ musicData.seek(offMacroP);
+ for (int i = 0; i < kMaxMacroOffsets; ++i)
+ _macroOffset[i] = musicData.readUint32BE();
+
+ res = musicData.err();
+ assert(!res);
+
+ // Read in whole mdat-file
+ int32 size = musicData.size();
+ assert(size != -1);
+ // TODO: special routine if size = -1?
+
+ _resource._mdatData = new byte[size];
+ assert(_resource._mdatData);
+ _resource._mdatLen = size;
+ musicData.seek(0);
+ musicData.read(_resource._mdatData, size);
+
+ res = musicData.err();
+ assert(!res);
+ musicData.readByte();
+ res = musicData.eos();
+ assert(res);
+
+
+ // TODO: It would be possible to analyze the pointers and be able to
+ // seperate the file in trackstep, patterns and macro blocks
+ // Modules could do weird stuff like having those blocks mixed though.
+ // TODO: Analyze pointers if they are correct offsets,
+ // so that accesses can be unchecked later
+
+ // Read in whole sample-file
+ size = sampleData.size();
+ assert(size >= 4);
+ assert(size != -1);
+ // TODO: special routine if size = -1?
+
+ _resource._sampleData = new byte[size];
+ assert(_resource._sampleData);
+ _resource._sampleLen = size;
+ sampleData.seek(0);
+ sampleData.read(_resource._sampleData, size);
+ for (int i = 0; i < 4; ++i)
+ _resource._sampleData[i] = 0;
+
+ res = sampleData.err();
+ assert(!res);
+ sampleData.readByte();
+ res = sampleData.eos();
+ assert(res);
+
+ return true;
+}
+
+
+void Tfmx::doMacro(int note, int macro, int relVol, int finetune, int channelNo) {
+ assert(0 <= macro && macro < kMaxMacroOffsets);
+ assert(0 <= note && note < 0xC0);
+ Common::StackLock lock(_mutex);
+
+ channelNo &= (kNumVoices - 1);
+ ChannelContext &channel = _channelCtx[channelNo];
+ unlockMacroChannel(channel);
+
+ noteCommand((uint8)note, (uint8)macro, (uint8)(relVol << 4) | channelNo, (uint8)finetune);
+ startPaula();
+}
+
+void Tfmx::stopSong(bool stopAudio) {
+ Common::StackLock lock(_mutex);
+ _playerCtx.song = -1;
+ if (stopAudio) {
+ stopMacroChannels();
+ stopPaula();
+ }
+}
+
+void Tfmx::doSong(int songPos, bool stopAudio) {
+ assert(0 <= songPos && songPos < kNumSubsongs);
+ Common::StackLock lock(_mutex);
+
+ stopPatternChannels();
+ if (stopAudio) {
+ stopMacroChannels();
+ stopPaula();
+ }
+
+ _playerCtx.song = (int8)songPos;
+
+ _trackCtx.loopCount = -1;
+ _trackCtx.startInd = _trackCtx.posInd = _subsong[songPos].songstart;
+ _trackCtx.stopInd = _subsong[songPos].songend;
+
+ const bool palFlag = (_resource.headerFlags & 2) != 0;
+ const uint16 tempo = _subsong[songPos].tempo;
+ uint16 ciaIntervall;
+ if (tempo >= 0x10) {
+ ciaIntervall = (uint16)(kCiaBaseInterval / tempo);
+ _playerCtx.patternSkip = 0;
+ } else {
+ ciaIntervall = palFlag ? (uint16)kPalDefaultCiaVal : (uint16)kNtscDefaultCiaVal;
+ _playerCtx.patternSkip = tempo;
+ }
+ setInterruptFreqUnscaled(ciaIntervall);
+
+ _playerCtx.patternCount = 0;
+ if (trackRun())
+ startPaula();
+}
+
+int Tfmx::doSfx(uint16 sfxIndex, bool unlockChannel) {
+ assert(0 <= sfxIndex && sfxIndex < 128);
+ Common::StackLock lock(_mutex);
+
+ const byte *sfxEntry = _resource.getSfxPtr(sfxIndex);
+ if (sfxEntry[0] == 0xFB) {
+ // custompattern
+ const uint8 patCmd = sfxEntry[2];
+ const int8 patExp = (int8)sfxEntry[3];
+ } else {
+ // custommacro
+ const byte channelNo = ((_playerCtx.song >= 0) ? sfxEntry[2] : sfxEntry[4]) & (kNumVoices - 1);
+ const byte priority = sfxEntry[5] & 0x7F;
+
+ ChannelContext &channel = _channelCtx[channelNo];
+ if (unlockChannel)
+ unlockMacroChannel(channel);
+
+ const int16 sfxLocktime = channel.sfxLockTime;
+ if (priority >= channel.customMacroPrio || sfxLocktime < 0) {
+ if (sfxIndex != channel.customMacroIndex || sfxLocktime < 0 || (sfxEntry[5] < 0x80)) {
+ channel.customMacro = READ_UINT32(sfxEntry); // intentionally not "endian-correct"
+ channel.customMacroPrio = priority;
+ channel.customMacroIndex = (uint8)sfxIndex;
+ debug(3, "Tfmx: running Macro %08X on channel %i - priority: %02X", TO_BE_32(channel.customMacro), channelNo, priority);
+ return channelNo;
+ }
+ }
+ }
+ return -1;
+}
+
+}
diff --git a/sound/mods/tfmx.h b/sound/mods/tfmx.h
new file mode 100644
index 0000000000..fccdf0aa01
--- /dev/null
+++ b/sound/mods/tfmx.h
@@ -0,0 +1,296 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef SOUND_MODS_TFMX_H
+#define SOUND_MODS_TFMX_H
+
+#include "sound/mods/paula.h"
+
+namespace {
+#ifndef NDEBUG
+inline void boundaryCheck(const void *bufStart, size_t bufLen, const void *address, size_t accessLen = 1) {
+ assert(bufStart <= address && (const byte *)address + accessLen <= (const byte *)bufStart + bufLen);
+}
+#else
+inline void boundaryCheck(const void *, size_t, void *, size_t = 1) {}
+#endif
+}
+
+namespace Audio {
+
+class Tfmx : public Paula {
+public:
+ Tfmx(int rate, bool stereo);
+ virtual ~Tfmx();
+
+ void interrupt();
+ void stopSong(bool stopAudio = true);
+ void doSong(int songPos, bool stopAudio = false);
+ int doSfx(uint16 sfxIndex, bool unlockChannel = false);
+ void doMacro(int note, int macro, int relVol = 0, int finetune = 0, int channelNo = 0);
+ bool load(Common::SeekableReadStream &musicData, Common::SeekableReadStream &sampleData);
+ int getTicks() const {return _playerCtx.tickCount;}
+ int getSongIndex() const {return _playerCtx.song;}
+ void setSignalPtr(uint16 *ptr) {_playerCtx.signal = ptr;}
+ void stopMacroEffect(int channel) {
+ assert(0 <= channel && channel < kNumVoices);
+ Common::StackLock lock(_mutex);
+ unlockMacroChannel(_channelCtx[channel]);
+ clearMacroProgramm(_channelCtx[channel]);
+ Paula::disableChannel(_channelCtx[channel].paulaChannel);
+ }
+
+// Note: everythings public so the debug-Routines work.
+// private:
+ enum {kPalDefaultCiaVal = 11822, kNtscDefaultCiaVal = 14320, kCiaBaseInterval = 0x1B51F8};
+ enum {kNumVoices = 4, kNumChannels = 8, kNumSubsongs = 32, kMaxPatternOffsets = 128, kMaxMacroOffsets = 128};
+
+ static const uint16 noteIntervalls[64];
+
+ struct Resource {
+ uint32 _trackstepOffset; //!< Offset in mdat
+ uint32 _sfxTableOffset;
+
+ byte *_mdatData; //!< Currently the whole mdat-File
+ byte *_sampleData; //!< Currently the whole sample-File
+
+ uint32 _mdatLen;
+ uint32 _sampleLen;
+
+ byte header[10];
+ uint16 headerFlags;
+ uint32 headerUnknown;
+ char textField[6 * 40];
+
+ const byte *getSfxPtr(uint16 index = 0) {
+ byte *sfxPtr = (byte *)(_mdatData + _sfxTableOffset + index * 8);
+
+ boundaryCheck(_mdatData, _mdatLen, sfxPtr, 8);
+ return sfxPtr;
+ }
+
+ const uint16 *getTrackPtr(uint16 trackstep = 0) {
+ uint16 *trackData = (uint16 *)(_mdatData + _trackstepOffset + 16 * trackstep);
+
+ boundaryCheck(_mdatData, _mdatLen, trackData, 16);
+ return trackData;
+ }
+
+ const uint32 *getPatternPtr(uint32 offset) {
+ uint32 *pattData = (uint32 *)(_mdatData + offset);
+
+ boundaryCheck(_mdatData, _mdatLen, pattData, 4);
+ return pattData;
+ }
+
+ const uint32 *getMacroPtr(uint32 offset) {
+ uint32 *macroData = (uint32 *)(_mdatData + offset);
+
+ boundaryCheck(_mdatData, _mdatLen, macroData, 4);
+ return macroData;
+ }
+
+ const int8 *getSamplePtr(const uint32 offset) {
+ int8 *sampleData = (int8 *)(_sampleData + offset);
+
+ boundaryCheck(_sampleData, _sampleLen, sampleData, 2);
+ return sampleData;
+ }
+ Resource() : _mdatData(), _mdatLen(), _sampleData(), _sampleLen() {}
+
+ ~Resource() {
+ delete[] _mdatData;
+ delete[] _sampleData;
+ }
+ } _resource;
+
+ uint32 _patternOffset[kMaxPatternOffsets]; //!< Offset in mdat
+ uint32 _macroOffset[kMaxMacroOffsets]; //!< Offset in mdat
+
+ struct Subsong {
+ uint16 songstart; //!< Index in Trackstep-Table
+ uint16 songend; //!< Last index in Trackstep-Table
+ uint16 tempo;
+ } _subsong[kNumSubsongs];
+
+ struct ChannelContext {
+ byte paulaChannel;
+
+ byte macroIndex;
+ uint16 macroWait;
+ uint32 macroOffset;
+ uint32 macroReturnOffset;
+ uint16 macroStep;
+ uint16 macroReturnStep;
+ uint8 macroLoopCount;
+ bool macroRun;
+ int8 macroSfxRun;
+
+ uint32 customMacro;
+ uint8 customMacroIndex;
+ uint8 customMacroPrio;
+
+ bool sfxLocked;
+ int16 sfxLockTime;
+ bool keyUp;
+
+ bool deferWait;
+ uint16 dmaIntCount;
+
+ uint32 sampleStart;
+ uint16 sampleLen;
+ uint16 refPeriod;
+ uint16 period;
+
+ int8 volume;
+ uint8 relVol;
+ uint8 note;
+ uint8 prevNote;
+ int16 fineTune; // always a signextended byte
+
+ uint8 portaSkip;
+ uint8 portaCount;
+ uint16 portaDelta;
+ uint16 portaValue;
+
+ uint8 envSkip;
+ uint8 envCount;
+ uint8 envDelta;
+ int8 envEndVolume;
+
+ uint8 vibLength;
+ uint8 vibCount;
+ int16 vibValue;
+ int8 vibDelta;
+
+ uint8 addBeginLength;
+ uint8 addBeginCount;
+ int32 addBeginDelta;
+ } _channelCtx[kNumVoices];
+
+ struct PatternContext {
+ uint32 offset; // patternStart, Offset from mdat
+ uint32 savedOffset; // for subroutine calls
+ uint16 step; // distance from patternStart
+ uint16 savedStep;
+
+ uint8 command;
+ int8 expose;
+ uint8 loopCount;
+ uint8 wait; //!< how many ticks to wait before next Command
+ } _patternCtx[kNumChannels];
+
+ struct TrackStepContext {
+ uint16 startInd;
+ uint16 stopInd;
+ uint16 posInd;
+ int16 loopCount;
+ } _trackCtx;
+
+ struct PlayerContext {
+ int8 song; //!< >= 0 if Song is running (means process Patterns)
+
+ uint16 patternCount;
+ uint16 patternSkip; //!< skip that amount of CIA-Interrupts
+
+ int8 volume; //!< Master Volume
+
+ uint8 fadeSkip;
+ uint8 fadeCount;
+ int8 fadeEndVolume;
+ int8 fadeDelta;
+
+ int tickCount;
+
+ uint16 *signal;
+
+ bool stopWithLastPattern; //!< hack to automatically stop the whole player if no Pattern is running
+ } _playerCtx;
+private:
+ static void initMacroProgramm(ChannelContext &channel) {
+ channel.macroStep = 0;
+ channel.macroWait = 0;
+ channel.macroRun = true;
+ channel.macroSfxRun = 0;
+ channel.macroLoopCount = 0xFF;
+ channel.dmaIntCount = 0;
+ }
+
+ static void clearEffects(ChannelContext &channel) {
+ channel.addBeginLength = 0;
+ channel.envSkip = 0;
+ channel.vibLength = 0;
+ channel.portaDelta = 0;
+ }
+
+ static void clearMacroProgramm(ChannelContext &channel) {
+ channel.macroRun = false;
+ channel.macroSfxRun = 0;
+ channel.dmaIntCount = 0;
+ }
+
+ static void unlockMacroChannel(ChannelContext &channel) {
+ channel.customMacro = 0;
+ channel.customMacroPrio = false;
+ channel.sfxLocked = false;
+ channel.sfxLockTime = -1;
+ }
+
+ void stopPatternChannels() {
+ for (int i = 0; i < kNumChannels; ++i) {
+ _patternCtx[i].command = 0xFF;
+ _patternCtx[i].expose = 0;
+ }
+ }
+
+ void stopMacroChannels() {
+ for (int i = 0; i < kNumVoices; ++i) {
+ clearEffects(_channelCtx[i]);
+ unlockMacroChannel(_channelCtx[i]);
+ clearMacroProgramm(_channelCtx[i]);
+ _channelCtx[i].note = 0;
+ _channelCtx[i].volume = 0;
+ }
+ }
+
+ static void setNoteMacro(ChannelContext &channel, uint note, int fineTune) {
+ const uint16 noteInt = noteIntervalls[note & 0x3F];
+ const uint16 finetune = (uint16)(fineTune + channel.fineTune + (1 << 8));
+ channel.refPeriod = ((uint32)noteInt * finetune >> 8);
+ if (!channel.portaDelta)
+ channel.period = channel.refPeriod;
+ }
+
+ void effects(ChannelContext &channel);
+ void macroRun(ChannelContext &channel);
+ void advancePatterns();
+ bool patternRun(PatternContext &pattern);
+ bool trackRun(bool incStep = false);
+ void noteCommand(uint8 note, uint8 param1, uint8 param2, uint8 param3);
+};
+
+}
+
+#endif