/* 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(int 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() {return _playerCtx.tickCount;} int getSongIndex() {return _playerCtx.song;} uint16 getSignal(int index) {return (index < ARRAYSIZE(_playerCtx.signal)) ? _playerCtx.signal[index] : 0xFFFF;} 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(uint8 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; 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 addBeginTime; uint8 addBeginReset; int16 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[4]; bool stopWithLastPattern; //!< hack to automatically stop the whole player if no Pattern is running } _playerCtx; static void initMacroProgramm(ChannelContext &channel) { channel.macroStep = 0; channel.macroWait = 0; channel.macroRun = true; channel.macroLoopCount = 0xFF; channel.dmaIntCount = 0; } static void clearEffects(ChannelContext &channel) { channel.envSkip = 0; channel.vibLength = 0; channel.portaDelta = 0; } static void clearMacroProgramm(ChannelContext &channel) { channel.macroRun = false; 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; } } 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; //Paula::setChannelPeriod(channel.paulaChannel, channel.period); } } void effects(ChannelContext &channel); FORCEINLINE bool macroStep(ChannelContext &channel); void advancePatterns(); FORCEINLINE bool patternStep(PatternContext &pattern, bool &pendingTrackstep); bool trackStep(); void noteCommand(uint8 note, uint8 param1, uint8 param2, uint8 param3); }; } #endif