[Scummvm-git-logs] scummvm master -> bb75c87df4ad08d4510146f8bdaa6959c071b971
athrxx
noreply at scummvm.org
Sun Jan 28 16:00:29 UTC 2024
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
938b703ba5 SCUMM: move VAR_SOUNDCARD reset to extra function
008c6c93c4 SCUMM: minor cleanup
bb75c87df4 SCUMM: (INDY3/MAC) - add dedicated sound player
Commit: 938b703ba5ef5945309263713aa2fd6f830af57b
https://github.com/scummvm/scummvm/commit/938b703ba5ef5945309263713aa2fd6f830af57b
Author: athrxx (athrxx at scummvm.org)
Date: 2024-01-28T16:57:59+01:00
Commit Message:
SCUMM: move VAR_SOUNDCARD reset to extra function
(also update it after loading a savegame)
Changed paths:
engines/scumm/saveload.cpp
engines/scumm/scumm.h
engines/scumm/vars.cpp
diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp
index 2b14338b7e6..32592600c7a 100644
--- a/engines/scumm/saveload.cpp
+++ b/engines/scumm/saveload.cpp
@@ -2053,6 +2053,9 @@ void ScummEngine::saveLoadWithSerializer(Common::Serializer &s) {
_musicEngine->saveLoadWithSerializer(s);
}
+ // At least from now on, VAR_SOUNDCARD will have a reliable value.
+ if (s.isLoading() && (_game.heversion < 70 && _game.version <= 6))
+ setSoundCardVarToCurrentConfig();
//
// Save/load the charset renderer state
diff --git a/engines/scumm/scumm.h b/engines/scumm/scumm.h
index 826ebec9047..afde6939270 100644
--- a/engines/scumm/scumm.h
+++ b/engines/scumm/scumm.h
@@ -602,6 +602,7 @@ protected:
virtual void setupScummVars();
virtual void resetScummVars();
void setVideoModeVarToCurrentConfig();
+ void setSoundCardVarToCurrentConfig();
void setupCharsetRenderer(const Common::Path &macFontFile);
void setupCostumeRenderer();
diff --git a/engines/scumm/vars.cpp b/engines/scumm/vars.cpp
index 14d548a7207..1a2b530eabd 100644
--- a/engines/scumm/vars.cpp
+++ b/engines/scumm/vars.cpp
@@ -773,36 +773,7 @@ void ScummEngine_v100he::resetScummVars() {
void ScummEngine::resetScummVars() {
if (_game.heversion < 70 && _game.version <= 6) {
- // VAR_SOUNDCARD modes
- // 0 PC Speaker
- // 1 Tandy
- // 2 CMS
- // 3 AdLib
- // 4 Roland
- switch (_sound->_musicType) {
- case MDT_NONE:
- case MDT_PCSPK:
- VAR(VAR_SOUNDCARD) = 0;
- break;
- case MDT_PCJR:
- VAR(VAR_SOUNDCARD) = 1;
- break;
- case MDT_CMS:
- VAR(VAR_SOUNDCARD) = 2;
- break;
- case MDT_ADLIB:
- VAR(VAR_SOUNDCARD) = 3;
- break;
- default:
- if ((_game.id == GID_MONKEY_EGA || _game.id == GID_MONKEY_VGA || (_game.id == GID_LOOM && _game.version == 3))
- && (_game.platform == Common::kPlatformDOS)) {
- VAR(VAR_SOUNDCARD) = 4;
- } else {
- VAR(VAR_SOUNDCARD) = 3;
- }
- break;
- }
-
+ setSoundCardVarToCurrentConfig();
setVideoModeVarToCurrentConfig();
if (_game.platform == Common::kPlatformMacintosh && (_game.features & GF_OLD_BUNDLE)) {
@@ -874,4 +845,39 @@ void ScummEngine::setVideoModeVarToCurrentConfig() {
VAR(VAR_VIDEOMODE) = 19;
}
+void ScummEngine::setSoundCardVarToCurrentConfig() {
+ if (VAR_SOUNDCARD == 0xFF)
+ return;
+
+ // VAR_SOUNDCARD modes
+ // 0 PC Speaker
+ // 1 Tandy
+ // 2 CMS
+ // 3 AdLib
+ // 4 Roland
+ switch (_sound->_musicType) {
+ case MDT_NONE:
+ case MDT_PCSPK:
+ VAR(VAR_SOUNDCARD) = 0;
+ break;
+ case MDT_PCJR:
+ VAR(VAR_SOUNDCARD) = 1;
+ break;
+ case MDT_CMS:
+ VAR(VAR_SOUNDCARD) = 2;
+ break;
+ case MDT_ADLIB:
+ VAR(VAR_SOUNDCARD) = 3;
+ break;
+ default:
+ if ((_game.id == GID_MONKEY_EGA || _game.id == GID_MONKEY_VGA || (_game.id == GID_LOOM && _game.version == 3))
+ && (_game.platform == Common::kPlatformDOS)) {
+ VAR(VAR_SOUNDCARD) = 4;
+ } else {
+ VAR(VAR_SOUNDCARD) = 3;
+ }
+ break;
+ }
+}
+
} // End of namespace Scumm
Commit: 008c6c93c453e7a63426958fa29e245cdb143667
https://github.com/scummvm/scummvm/commit/008c6c93c453e7a63426958fa29e245cdb143667
Author: athrxx (athrxx at scummvm.org)
Date: 2024-01-28T16:57:59+01:00
Commit Message:
SCUMM: minor cleanup
Changed paths:
engines/scumm/actor.cpp
engines/scumm/actor.h
diff --git a/engines/scumm/actor.cpp b/engines/scumm/actor.cpp
index 8a6c44563f7..865423a66f6 100644
--- a/engines/scumm/actor.cpp
+++ b/engines/scumm/actor.cpp
@@ -544,8 +544,8 @@ int Actor::calcMovementFactor(const Common::Point& next) {
_walkdata.deltaYFactor = deltaYFactor;
if (_vm->_game.version >= 7) {
- _walkdata.nextDir = ((int)(atan2((double)deltaXFactor, (double)-deltaYFactor) * 180 / M_PI) + 360) % 360;
- startWalkAnim((_moving & MF_IN_LEG) ? 2 : 1, _walkdata.nextDir);
+ _walkdata.facing = ((int)(atan2((double)deltaXFactor, (double)-deltaYFactor) * 180 / M_PI) + 360) % 360;
+ startWalkAnim((_moving & MF_IN_LEG) ? 2 : 1, _walkdata.facing);
_moving |= MF_IN_LEG;
} else {
_targetFacing = (ABS(diffY) * 3 > ABS(diffX)) ? (deltaYFactor > 0 ? 180 : 0) : (deltaXFactor > 0 ? 90 : 270);
@@ -602,11 +602,9 @@ int Actor::actorWalkStep() {
if (_vm->_game.version < 7) {
int nextFacing = updateActorDirection(true);
- if (!(_moving & MF_IN_LEG) || _facing != nextFacing) {
- if (_walkFrame != _frame || _facing != nextFacing)
- startWalkAnim(1, nextFacing);
- _moving |= MF_IN_LEG;
- }
+ if ((_walkFrame != _frame && !(_moving & MF_IN_LEG)) || _facing != nextFacing)
+ startWalkAnim(1, nextFacing);
+ _moving |= MF_IN_LEG;
}
if (_walkbox != _walkdata.curbox && _vm->checkXYInBoxBounds(_walkdata.curbox, _pos.x, _pos.y))
@@ -875,7 +873,7 @@ void Actor::startWalkActor(int destX, int destY, int dir) {
void Actor::startWalkAnim(int cmd, int angle) {
if (_vm->_game.version >= 7)
- angle = remapDirection(normalizeAngle(_vm->_costumeLoader->hasManyDirections(_costume), angle == -1 ? _walkdata.nextDir : angle), false);
+ angle = remapDirection(normalizeAngle(_vm->_costumeLoader->hasManyDirections(_costume), angle == -1 ? _walkdata.facing : angle), false);
else if (angle == -1)
angle = _facing;
@@ -3873,7 +3871,7 @@ void Actor::saveLoadWithSerializer(Common::Serializer &s) {
s.syncAsSint32LE(_walkdata.deltaYFactor, VER(8));
s.syncAsUint16LE(_walkdata.xfrac, VER(8));
s.syncAsUint16LE(_walkdata.yfrac, VER(8));
- s.syncAsSint16LE(_walkdata.nextDir, VER(111));
+ s.syncAsSint16LE(_walkdata.facing, VER(111));
s.syncAsUint16LE(_walkdata.point3.x, VER(42));
s.syncAsUint16LE(_walkdata.point3.y, VER(42));
diff --git a/engines/scumm/actor.h b/engines/scumm/actor.h
index 6a35204d6fa..aad01e97061 100644
--- a/engines/scumm/actor.h
+++ b/engines/scumm/actor.h
@@ -151,7 +151,7 @@ protected:
int32 deltaXFactor, deltaYFactor;
uint16 xfrac, yfrac;
uint16 xAdd, yAdd;
- int16 nextDir;
+ int16 facing;
void reset() {
dest.x = dest.y = 0;
@@ -167,7 +167,7 @@ protected:
yfrac = 0;
xAdd = 0;
yAdd = 0;
- nextDir = 0;
+ facing = 0;
}
};
Commit: bb75c87df4ad08d4510146f8bdaa6959c071b971
https://github.com/scummvm/scummvm/commit/bb75c87df4ad08d4510146f8bdaa6959c071b971
Author: athrxx (athrxx at scummvm.org)
Date: 2024-01-28T16:59:26+01:00
Commit Message:
SCUMM: (INDY3/MAC) - add dedicated sound player
(no support for low quality music yet)
Changed paths:
A engines/scumm/players/player_mac_indy3.cpp
A engines/scumm/players/player_mac_indy3.h
engines/scumm/detection_tables.h
engines/scumm/module.mk
engines/scumm/music.h
engines/scumm/players/player_towns.h
engines/scumm/players/player_v2base.cpp
engines/scumm/scumm.cpp
engines/scumm/scumm.h
engines/scumm/sound.cpp
engines/scumm/vars.cpp
diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h
index 0fb64401beb..9fb2080b474 100644
--- a/engines/scumm/detection_tables.h
+++ b/engines/scumm/detection_tables.h
@@ -170,7 +170,7 @@ static const GameSettings gameVariantsTable[] = {
{"indyzak", "FM-TOWNS", 0, GID_ZAK, 3, 0, MDT_TOWNS, GF_OLD256 | GF_AUDIOTRACKS, Common::kPlatformFMTowns, GUIO5(GUIO_NOSPEECH, GUIO_NOMIDI, GUIO_MIDITOWNS, GUIO_TRIM_FMTOWNS_TO_200_PIXELS, GUIO_ORIGINALGUI)},
{"indy3", "EGA", "ega", GID_INDY3, 3, 0, MDT_PCSPK | MDT_PCJR | MDT_CMS | MDT_ADLIB, 0, UNK, GUIO5(GUIO_NOSPEECH, GUIO_NOMIDI, GUIO_RENDERCGA, GUIO_ENHANCEMENTS, GUIO_ORIGINALGUI)},
- {"indy3", "Mac", "ega", GID_INDY3, 3, 0, MDT_PCSPK | MDT_PCJR, 0, Common::kPlatformMacintosh, GUIO6(GUIO_NOSPEECH, GUIO_NOMIDI, GUIO_RENDERMACINTOSHBW, GUIO_ENHANCEMENTS, GUIO_ORIGINALGUI, GUIO_NOASPECT)},
+ {"indy3", "Mac", "ega", GID_INDY3, 3, 0, MDT_MACINTOSH, 0, Common::kPlatformMacintosh, GUIO6(GUIO_NOSPEECH, GUIO_NOMIDI, GUIO_RENDERMACINTOSHBW, GUIO_ENHANCEMENTS, GUIO_ORIGINALGUI, GUIO_NOASPECT)},
{"indy3", "No AdLib", "ega", GID_INDY3, 3, 0, MDT_PCSPK | MDT_PCJR, 0, UNK, GUIO5(GUIO_NOSPEECH, GUIO_NOMIDI, GUIO_RENDERCGA, GUIO_ENHANCEMENTS, GUIO_ORIGINALGUI)},
{"indy3", "VGA", "vga", GID_INDY3, 3, 0, MDT_PCSPK | MDT_PCJR | MDT_CMS | MDT_ADLIB, GF_OLD256 | GF_FEW_LOCALS, Common::kPlatformDOS, GUIO4(GUIO_NOSPEECH, GUIO_NOMIDI, GUIO_ENHANCEMENTS, GUIO_ORIGINALGUI)},
{"indy3", "Steam", "steam", GID_INDY3, 3, 0, MDT_PCSPK | MDT_PCJR | MDT_CMS | MDT_ADLIB, GF_OLD256 | GF_FEW_LOCALS, UNK, GUIO4(GUIO_NOSPEECH, GUIO_NOMIDI, GUIO_ENHANCEMENTS, GUIO_ORIGINALGUI)},
diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index 46990c22c0e..c10262493eb 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -53,6 +53,7 @@ MODULE_OBJS := \
players/player_apple2.o \
players/player_he.o \
players/player_mac.o \
+ players/player_mac_indy3.o \
players/player_mod.o \
players/player_nes.o \
players/player_pce.o \
diff --git a/engines/scumm/music.h b/engines/scumm/music.h
index c5ae40dcc47..81dd5d9743e 100644
--- a/engines/scumm/music.h
+++ b/engines/scumm/music.h
@@ -42,11 +42,22 @@ public:
~MusicEngine() override {}
/**
- * Set the output volume.
+ * Set the output volume for music.
+ * Also used, if the inheriting class doesn't
+ * distinguish between music and sfx.
* @param vol the new output volume
*/
virtual void setMusicVolume(int vol) = 0;
+ /**
+ * Set the output volume for sound effects.
+ * No need to implement this in the inheriting
+ * class if it doesn't distinguish between
+ * music and sfx.
+ * @param vol the new output volume
+ */
+ virtual void setSfxVolume(int vol) {}
+
/**
* Start playing the sound with the given id.
* @param sound the sound to start
@@ -86,6 +97,12 @@ public:
*/
virtual int getMusicTimer() { return 0; }
+ /**
+ * Set sound quality if applicable (used for Macintosh sound)
+ * @param qual quality setting (range and meaning are specific to the respective player)
+ */
+ virtual void setQuality(int qual) {}
+
/**
* Save or load the music state.
*/
diff --git a/engines/scumm/players/player_mac_indy3.cpp b/engines/scumm/players/player_mac_indy3.cpp
new file mode 100644
index 00000000000..70e79a27f1b
--- /dev/null
+++ b/engines/scumm/players/player_mac_indy3.cpp
@@ -0,0 +1,1606 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include "scumm/players/player_mac_indy3.h"
+#include "scumm/resource.h"
+#include "scumm/scumm.h"
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "common/endian.h"
+
+namespace Scumm {
+
+#define ASC_DEVICE_RATE 0x56EE8BA3
+#define PCM_BUFFER_SIZE 1024
+#define RATECNV_BIT_PRECSN 24
+
+extern const uint8 *_pv2ModTbl;
+extern const uint32 _pv2ModTblSize;
+
+class I3MPlayer;
+class AudioStream_I3M : public Audio::AudioStream {
+public:
+ AudioStream_I3M(I3MPlayer *drv, uint32 scummVMOutputrate, bool stereo, bool interpolate);
+ ~AudioStream_I3M() override;
+
+ void initBuffers(uint32 feedBufferSize);
+ void initDrivers();
+ typedef Common::Functor0Mem<void, I3MPlayer> CallbackProc;
+ void setVblCallback(const CallbackProc *proc);
+ void clearBuffer();
+
+ void setMasterVolume(Audio::Mixer::SoundType type, uint16 vol);
+
+ // AudioStream interface
+ int readBuffer(int16 *buffer, const int numSamples) override;
+ bool isStereo() const override { return _isStereo; }
+ int getRate() const override { return _outputRate; }
+ bool endOfData() const override { return false; }
+
+private:
+ void generateData(int8 *dst, uint32 len, Audio::Mixer::SoundType, bool expectStereo) const;
+ void runVblTask();
+
+ I3MPlayer *_drv;
+
+ uint32 _vblSmpQty;
+ uint32 _vblSmpQtyRem;
+ uint32 _vblCountDown;
+ uint32 _vblCountDownRem;
+ const CallbackProc *_vblCbProc;
+
+ struct SmpBuffer {
+ SmpBuffer() : start(0), pos(0), end(0), volume(0x10000), lastL(0), lastR(0), size(0), rateConvInt(0), rateConvFrac(0), rateConvAcc(-1) {}
+ int8 *start;
+ int8 *pos;
+ const int8 *end;
+ uint32 volume;
+ int32 lastL;
+ int32 lastR;
+ uint32 size;
+ uint32 rateConvInt;
+ uint32 rateConvFrac;
+ int32 rateConvAcc;
+ } _buffers[2];
+
+ const uint32 _outputRate;
+ const uint8 _frameSize;
+ bool _interp;
+
+ const bool _isStereo;
+};
+
+class I3MSoundDriver {
+public:
+ I3MSoundDriver(Common::Mutex &mutex, uint32 deviceRate, bool isStereo) : _mutex(mutex), _sig(false), _deviceRate(deviceRate), _stereo(isStereo) {}
+ virtual ~I3MSoundDriver() {}
+ virtual void feed(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) = 0;
+ uint32 getDeviceRate() const { return _deviceRate; }
+ bool checkSignal() const { return _sig; }
+ void resetSignal(bool state) { _sig = state; }
+protected:
+ Common::Mutex &_mutex;
+ const bool _stereo;
+ const uint32 _deviceRate;
+ bool _sig;
+};
+
+class I3MLowLevelPCMDriver final : public I3MSoundDriver {
+public:
+ struct PCMSound {
+ PCMSound() : len(0), rate(0), loopst(0), loopend(0), baseFreq(0) {}
+ Common::SharedPtr<const byte> data;
+ uint32 len;
+ uint32 rate;
+ uint32 loopst;
+ uint32 loopend;
+ byte baseFreq;
+ };
+public:
+ I3MLowLevelPCMDriver(Common::Mutex &mutex, uint32 deviceRate, bool enableInterpolation, bool isStereo);
+ void feed(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) override;
+ void play(PCMSound *snd);
+ void stop();
+private:
+ uint32 calcRate(uint32 outRate, uint32 factor, uint32 dataRate);
+
+ Common::SharedPtr<const int8> _res;
+ const int8 *_data;
+ const uint16 _frameSize;
+ const bool _interp;
+ int8 _lastSmp[2];
+ uint32 _len;
+ uint16 _rmH;
+ uint16 _rmL;
+ uint32 _loopSt;
+ uint32 _loopEnd;
+ byte _baseFreq;
+ uint32 _rcPos;
+ uint32 _smpWtAcc;
+};
+
+class I3MMusicDriver : public I3MSoundDriver {
+public:
+ I3MMusicDriver(Common::Mutex &mutex, bool isStereo) : I3MSoundDriver(mutex, ASC_DEVICE_RATE, isStereo) {}
+ virtual void start() = 0;
+ virtual void stop() = 0;
+ virtual void setDuration(uint16 duration) = 0;
+ virtual void setRate(uint8 chan, uint16 rate) = 0;
+};
+
+class I3MFourToneSynthDriver final : public I3MMusicDriver {
+public:
+ I3MFourToneSynthDriver(Common::Mutex &mutex, bool isStereo);
+ ~I3MFourToneSynthDriver() override;
+
+ void feed(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) override;
+ void start();
+ void stop();
+
+ void setDuration(uint16 duration);
+ void setWaveForm(uint8 chan, const uint8 *data, uint32 dataSize);
+ void setRate(uint8 chan, uint16 rate);
+
+private:
+ uint32 _pos;
+ uint16 _duration;
+
+ struct Channel {
+ Channel() : rate(0), phase(0), waveform(nullptr) {}
+ uint32 rate;
+ uint32 phase;
+ const int8 *waveform;
+ };
+
+ Channel *_chan;
+ const uint16 _numChan;
+};
+
+class I3MLQSynthDriver final : public I3MMusicDriver {
+public:
+ I3MLQSynthDriver(Common::Mutex &mutex, bool isStereo) : I3MMusicDriver(mutex, isStereo) {}
+ ~I3MLQSynthDriver() override {}
+
+ void feed(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) override {}
+ void start() override {}
+ void stop() override {}
+
+ void setDuration(uint16 duration) override {}
+ void setRate(uint8 chan, uint16 rate) override {}
+};
+
+class I3MPlayer {
+private:
+ I3MPlayer(ScummEngine *vm, Audio::Mixer *mixer);
+public:
+ ~I3MPlayer();
+ static Common::SharedPtr<I3MPlayer> open(ScummEngine *scumm, Audio::Mixer *mixer);
+ bool startDevices(uint32 outputRate, uint32 pcmDeviceRate, uint32 feedBufferSize, bool enableInterpolation);
+
+ void setMusicVolume(int vol);
+ void setSfxVolume(int vol);
+ void startSound(int id);
+ void stopSound(int id);
+ void stopAllSounds();
+ int getMusicTimer();
+ int getSoundStatus(int id) const;
+ void setQuality(int qual);
+
+ void generateData(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) const;
+ uint32 getDriverDeviceRate(uint8 drvID) const;
+ void nextTick();
+
+private:
+ void startSong(int id);
+ void startSoundEffect(int id);
+ void stopSong();
+ void stopSoundEffect();
+ void stopActiveSound();
+ void updateSong();
+ void updateSoundEffect();
+
+ void checkRestartSoundEffects();
+ void endOfTrack();
+
+ bool isSong(int id) const;
+ bool isHiQuality() const;
+
+ int _lastSound;
+ int _lastSong;
+ int _lastSoundEffectPrio;
+ int _soundEffectNumLoops;
+ int _songTimer;
+ uint _activeChanCount;
+ byte _songTimerInternal;
+ byte *_soundUsage;
+
+ bool _songPlaying;
+ bool _soundEffectPlaying;
+ int _qmode;
+ bool _qualHi;
+ bool _mixerThread;
+
+ I3MLowLevelPCMDriver::PCMSound _pcmSnd;
+
+ AudioStream_I3M *_macstr;
+ Audio::SoundHandle _soundHandle;
+ AudioStream_I3M::CallbackProc _nextTickProc;
+
+ ScummEngine *_vm;
+ Audio::Mixer *_mixer;
+ static Common::WeakPtr<I3MPlayer> *_inst;
+
+ const byte *_musicIDTable;
+ int _musicIDTableLen;
+ const int _idRangeMax;
+
+ I3MMusicDriver *_mdrv;
+ I3MLowLevelPCMDriver *_sdrv;
+ Common::Array<I3MSoundDriver*> _drivers;
+
+private:
+ class MusicChannel {
+ public:
+ MusicChannel(I3MPlayer *pl);
+ ~MusicChannel();
+ void clear();
+
+ void start(Common::SharedPtr<const byte> &songRes, uint16 offset, bool hq);
+ void nextTick();
+ void parseNextEvents();
+ void noteOn(uint16 duration, uint8 note);
+ uint16 checkPeriod() const;
+
+ uint16 _frameLen;
+ uint16 _curPos;
+ uint16 _freqCur;
+ uint16 _freqIncr;
+ uint16 _freqEff;
+ uint16 _envPhase;
+ uint16 _envRate;
+ uint16 _tempo;
+ uint16 _envCutoff;
+ int16 _transpose;
+ uint16 _envLen;
+ uint16 _envShape;
+ uint16 _envStep;
+ uint16 _envStepLen;
+ uint16 _modType;
+ uint16 _modState;
+ uint16 _modStep;
+ uint16 _modSensitivity;
+ uint16 _modRange;
+ uint16 _localVars[5];
+ Common::SharedPtr<const byte> _resource;
+ bool _hq;
+
+ private:
+ typedef bool (I3MPlayer::MusicChannel::*CtrlProc)(const byte *&);
+
+ bool ctrl_setShape(const byte *&pos);
+ bool ctrl_modPara(const byte *&pos);
+ bool ctrl_init(const byte *&pos);
+ bool ctrl_returnFromSubroutine(const byte *&pos);
+ bool ctrl_jumpToSubroutine(const byte *&pos);
+ bool ctrl_initOther(const byte *&pos);
+ bool ctrl_decrJumpIf(const byte *&pos);
+ bool ctrl_writeVar(const byte *&pos);
+
+ const CtrlProc *_ctrlProc;
+
+ void limitedClear();
+ uint16 &getMemberRef(int pos);
+
+ uint16 **_vars;
+ int _numVars;
+ uint16 &_savedOffset;
+
+ uint16 _resSize;
+
+ I3MPlayer *_player;
+ static MusicChannel *_ctrlChan;
+
+ static const uint32 _envShapes[98];
+ const uint8 *&_modShapes;
+ const uint32 &_modShapesTableSize;
+
+ bool ctrlProc(int procId, const byte *&arg);
+ void setFrameLen(uint8 len);
+ };
+
+ MusicChannel **_musicChannels;
+ const int _numMusicChannels;
+
+ static const uint8 _fourToneSynthWaveForm[256];
+
+public:
+ MusicChannel *getMusicChannel(uint8 id) const;
+};
+
+/*template <typename T> Common::SharedPtr<T> &&makeSharedBuffer(T *buff) {
+
+ return Common::SharedPtr<T>::SharedPtr//<T, Common::ArrayDeleter<T> >(buff);
+}*/
+
+AudioStream_I3M::AudioStream_I3M(I3MPlayer *drv, uint32 scummVMOutputrate, bool stereo, bool interpolate) : Audio::AudioStream(), _drv(drv), _vblSmpQty(0), _vblSmpQtyRem(0), _frameSize(stereo ? 2 : 1),
+ _vblCountDown(0), _vblCountDownRem(0), _outputRate(scummVMOutputrate), _vblCbProc(nullptr), _isStereo(stereo), _interp(interpolate) {
+ assert(_drv);
+ _vblSmpQty = _outputRate / 60;
+ _vblSmpQtyRem = _outputRate % 60;
+ _vblCountDown = _vblSmpQty;
+ _vblCountDownRem = 0;
+}
+
+AudioStream_I3M::~AudioStream_I3M() {
+ for (int i = 0; i < 2; ++i)
+ delete[] _buffers[i].start;
+}
+
+void AudioStream_I3M::initBuffers(uint32 feedBufferSize) {
+ for (int i = 0; i < 2; ++i)
+ delete[] _buffers[i].start;
+
+ for (int i = 0; i < 2; ++i) {
+ _buffers[i].size = feedBufferSize;
+ _buffers[i].start = new int8[_buffers[i].size];
+ _buffers[i].end = _buffers[i].start + _buffers[i].size;
+ }
+ clearBuffer();
+}
+
+void AudioStream_I3M::initDrivers() {
+ for (int i = 0; i < 2; ++i) {
+ uint32 dr = _drv ? _drv->getDriverDeviceRate(i) : 0;
+ if (!dr)
+ error("AudioStream_I3M::initDrivers(): Failed to query device rate for device %d", i);
+ uint64 irt = (uint64)dr * (1 << RATECNV_BIT_PRECSN) / _outputRate;
+ _buffers[i].rateConvInt = irt >> (RATECNV_BIT_PRECSN + 16);
+ _buffers[i].rateConvFrac = (irt >> 16) & ((1 << RATECNV_BIT_PRECSN) - 1);
+ _buffers[i].rateConvAcc = 0;
+ }
+}
+
+void AudioStream_I3M::setVblCallback(const CallbackProc *proc) {
+ _vblCbProc = proc;
+}
+
+void AudioStream_I3M::clearBuffer() {
+ for (int i = 0; i < 2; ++i) {
+ memset(_buffers[i].start, 0, _buffers[i].size);
+ _buffers[i].pos = _buffers[i].start;
+ }
+}
+
+void AudioStream_I3M::setMasterVolume(Audio::Mixer::SoundType type, uint16 vol) {
+ if (type == Audio::Mixer::kMusicSoundType || type == Audio::Mixer::kPlainSoundType)
+ _buffers[0].volume = vol * vol;
+ if (type == Audio::Mixer::kSFXSoundType || type == Audio::Mixer::kPlainSoundType)
+ _buffers[1].volume = vol * vol;
+}
+
+int AudioStream_I3M::readBuffer(int16 *buffer, const int numSamples) {
+ static const Audio::Mixer::SoundType stype[2] = {
+ Audio::Mixer::kMusicSoundType,
+ Audio::Mixer::kSFXSoundType
+ };
+
+ static const char errFnNames[2][8] = {"Buffers", "Drivers"};
+ int errNo = (!_buffers[0].size || !_buffers[1].size) ? 0 : ((_buffers[0].rateConvAcc == -1 || _buffers[1].rateConvAcc == -1) ? 1 : -1);
+ if (errNo != -1)
+ error("AudioStream_I3M::readBuffer(): init%s() must be called before playback", errFnNames[errNo]);
+
+ for (int i = _isStereo ? numSamples >> 1 : numSamples; i; --i) {
+ if (!--_vblCountDown) {
+ _vblCountDownRem += _vblSmpQtyRem;
+ _vblCountDown = _vblSmpQty + _vblCountDownRem / _vblSmpQty;
+ _vblCountDownRem %= _vblSmpQty;
+ runVblTask();
+ }
+
+ int32 smpL = 0;
+ int32 smpR = 0;
+ for (int ii = 0; ii < 2; ++ii) {
+ int diff = _buffers[ii].pos[0] - _buffers[ii].lastL;
+ if (diff && _buffers[ii].rateConvAcc && _interp)
+ diff = (diff * _buffers[ii].rateConvAcc) >> RATECNV_BIT_PRECSN;
+ smpL += (int32)((_buffers[ii].lastL + diff) * _buffers[ii].volume);
+ }
+ if (_isStereo) {
+ for (int ii = 0; ii < 2; ++ii) {
+ int diff = _buffers[ii].pos[1] - _buffers[ii].lastR;
+ if (diff && _buffers[ii].rateConvAcc && _interp)
+ diff = (diff * _buffers[ii].rateConvAcc) >> RATECNV_BIT_PRECSN;
+ smpR += (int32)((_buffers[ii].lastR + diff) * _buffers[ii].volume);
+ }
+ }
+
+ for (int ii = 0; ii < 2; ++ii) {
+ uint32 incr = (_buffers[ii].rateConvInt * _frameSize);
+ _buffers[ii].rateConvAcc += _buffers[ii].rateConvFrac;
+ if (_buffers[ii].rateConvAcc & ~((1 << RATECNV_BIT_PRECSN) - 1)) {
+ incr += _frameSize;
+ _buffers[ii].rateConvAcc &= ((1 << RATECNV_BIT_PRECSN) - 1);
+ }
+
+ if (incr) {
+ _buffers[ii].pos += incr;
+ if (_buffers[ii].pos == _buffers[ii].end) {
+ _buffers[ii].pos -= _buffers[ii].size;
+ generateData(_buffers[ii].pos, _buffers[ii].size, stype[ii], _isStereo);
+ }
+
+ const int8 *lpos = _buffers[ii].pos;
+ if (lpos >= _buffers[ii].start + _frameSize)
+ lpos -= _frameSize;
+ _buffers[ii].lastL = lpos[0];
+ if (_isStereo)
+ _buffers[ii].lastR = lpos[1];
+ }
+ }
+
+ *buffer++ = CLIP<int16>(smpL >> 8, -32768, 32767);
+ if (_isStereo)
+ *buffer++ = CLIP<int16>(smpR >> 8, -32768, 32767);
+ }
+ return numSamples;
+}
+
+void AudioStream_I3M::generateData(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) const {
+ if (_drv)
+ _drv->generateData(dst, len, type, expectStereo);
+}
+
+void AudioStream_I3M::runVblTask() {
+ if (_vblCbProc && _vblCbProc->isValid())
+ (*_vblCbProc)();
+}
+
+I3MLowLevelPCMDriver::I3MLowLevelPCMDriver(Common::Mutex &mutex, uint32 deviceRate, bool enableInterpolation, bool isStereo) :
+ I3MSoundDriver(mutex, deviceRate, isStereo), _interp(enableInterpolation), _frameSize(isStereo ? 2 : 1), _len(0), _rmH(0), _rmL(0), _smpWtAcc(0), _loopSt(0), _loopEnd(0), _baseFreq(0), _rcPos(0), _data(nullptr) {
+ _lastSmp[0] = _lastSmp[1] = 0;
+}
+
+void I3MLowLevelPCMDriver::feed(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) {
+ if (dst == nullptr || type != Audio::Mixer::kSFXSoundType)
+ return;
+
+ memset(dst, 0, len);
+
+ if (_data == nullptr)
+ return;
+
+ if (expectStereo != _stereo)
+ error("I3MLowLevelPCMDriver::feed(): stereo/mono mismatch between sound data and mixer stream");
+
+ int32 diff = 0;
+ bool interp = (_interp && _rmL);
+
+ for (const int8 *end = dst + len; dst < end; ) {
+ if (interp) {
+ for (int i = 0; i < _frameSize; ++ i) {
+ int8 in = _data[_rcPos + i];
+ if (in != _lastSmp[i]) {
+ diff = in - _lastSmp[i];
+ diff = (diff * (_smpWtAcc >> 1)) >> 15;
+ in = (_lastSmp[i] + diff) & 0xff;
+ }
+ *dst++ = in;
+ }
+ } else {
+ *dst++ = _data[_rcPos];
+ }
+
+ uint32 lpos = _rcPos;
+ _rcPos += (_rmH * _frameSize);
+ _smpWtAcc += _rmL;
+ if (_smpWtAcc > 0xffff) {
+ _smpWtAcc &= 0xffff;
+ _rcPos += _frameSize;
+ }
+
+ if (interp && _rcPos >= lpos + _frameSize) {
+ for (int i = 0; i < _frameSize; ++i)
+ _lastSmp[i] = _data[_rcPos - _frameSize + i];
+ }
+
+ if (_rcPos >= _len) {
+ if (_loopSt && _loopEnd) {
+ _rcPos = _loopSt + (_rcPos - _len);
+ _len = _loopEnd;
+ _lastSmp[0] = _data[_rcPos];
+ if ((_len - _rcPos) > 1)
+ _lastSmp[1] = _data[_rcPos + 1];
+ _smpWtAcc = 0;
+ } else {
+ _data = nullptr;
+ _res.reset();
+ end = dst;
+ }
+ _sig = true;
+ }
+ }
+}
+
+void I3MLowLevelPCMDriver::play(PCMSound *snd) {
+ if (!snd || !snd->data)
+ return;
+
+ Common::StackLock lock(_mutex);
+
+ _res = snd->data.reinterpretCast<const int8>();
+ _data = _res.get();
+ _len = snd->len;
+ uint32 rmul = calcRate(_deviceRate, 0x10000, snd->rate);
+
+ if (rmul >= 0x7FFD && rmul <= 0x8003)
+ rmul = 0x8000;
+ else if (ABS((int16)(rmul & 0xffff)) <= 7)
+ rmul = (rmul + 7) & ~0xffff;
+
+ if (rmul > (uint32)-64)
+ rmul = (uint32)-64;
+
+ assert(rmul);
+
+ _rmL = rmul & 0xffff;
+ _rmH = rmul >> 16;
+
+ if (snd->loopend - snd->loopst < 2 || snd->loopend < snd->loopst) {
+ _loopSt = 0;
+ _loopEnd = 0;
+ } else {
+ _loopSt = snd->loopst - (snd->loopst % _frameSize);
+ _loopEnd = snd->loopend - (snd->loopend % _frameSize);
+ }
+
+ _baseFreq = snd->baseFreq;
+ _rcPos = 0;
+ _smpWtAcc = 0;
+ _lastSmp[0] = _data[0];
+ if (_len >= _frameSize)
+ _lastSmp[1] = _data[1];
+ _sig = false;
+}
+
+void I3MLowLevelPCMDriver::stop() {
+ Common::StackLock lock(_mutex);
+ _data = nullptr;
+ _res.reset();
+ _sig = true;
+}
+
+uint32 I3MLowLevelPCMDriver::calcRate(uint32 outRate, uint32 factor, uint32 dataRate) {
+ uint32 result = outRate;
+ uint64 t = 0;
+ uint32 c = 0;
+
+ if (!factor || !dataRate)
+ return (uint32)-1;
+
+ if (factor > 0x10000 && dataRate > 0x10000) {
+ bool altpth = true;
+
+ if (!(dataRate & 0xffff)) {
+ SWAP(factor, dataRate);
+ if (!(dataRate & 0xffff)) {
+ dataRate = (dataRate >> 16) * (factor >> 16);
+ factor = 0;
+ altpth = false;
+ }
+ } else if (factor & 0xffff) {
+ t = (dataRate & 0xffff) * (factor >> 16) + (dataRate >> 16) * (factor & 0xffff);
+ c = (factor & 0xffff) * (dataRate & 0xffff);
+ dataRate = (factor >> 16) * (dataRate >> 16) + (t >> 16);
+ t = c + ((t & 0xffff) << 16);
+ factor = t & (uint32)-1;
+ dataRate += (t >> 32);
+ altpth = false;
+ }
+
+ if (altpth) {
+ c = dataRate;
+ dataRate = (factor >> 16) * (dataRate >> 16);
+ factor = (factor >> 16) * (c & 0xffff);
+ dataRate += (factor >> 16);
+ factor <<= 16;
+ }
+
+ } else if (factor < 0x10000 && dataRate < 0x10000) {
+ factor = factor * dataRate;
+ dataRate = 0;
+ } else if (factor == 0x10000 || dataRate == 0x10000) {
+ if (dataRate == 0x10000)
+ SWAP(dataRate, factor);
+ factor = dataRate << 16;
+ dataRate = (factor | (dataRate >> 16)) ^ factor;
+ } else {
+ if (factor > 0x10000 && dataRate <= 0x10000)
+ SWAP(dataRate, factor);
+
+ c = (dataRate >> 16) * (factor & 0xffff);
+ factor = (factor & 0xffff) * (dataRate & 0xffff);
+ uint32 x = ((factor >> 16) + (c & 0xffff)) & ~0xffff;
+ factor += (c << 16);
+ result = (c + x) >> 16;
+ dataRate = result;
+ }
+
+ t = factor + (outRate >> 1);
+ factor = t & (uint32)-1;
+ dataRate += (t >> 32);
+
+ if (dataRate >= outRate)
+ return (uint32)-1;
+
+ dataRate ^= factor;
+
+ if (outRate < 0x10000) {
+ factor <<= 16;
+ dataRate = (dataRate >> 16) | (dataRate << 16);
+ outRate = (outRate >> 16) | (outRate << 16);
+ }
+
+ int32 sh = -1;
+
+ if (outRate < 0x1000000) {
+ outRate <<= 8;
+ sh = -9;
+ }
+
+ for (t = (int32)outRate; !(t >> 32); t = (int32)outRate) {
+ --sh;
+ outRate += outRate;
+ }
+
+ sh = ~sh;
+ factor <<= sh;
+ dataRate = ((dataRate >> (32 - sh)) | (dataRate << sh)) ^ factor;
+
+ if (outRate & 0xffff) {
+ bool altpth = false;
+
+ if (dataRate / (outRate >> 16) > 0xffff) {
+ dataRate = ((dataRate - outRate) << 16) | (factor >> 16);
+ factor &= ~0xffff;
+ altpth = true;
+ } else {
+ c = dataRate % (outRate >> 16);
+ dataRate /= (outRate >> 16);
+ t = ((c << 16) | (factor >> 16)) - (dataRate * (outRate & 0xffff));
+ factor = (factor << 16) | dataRate;
+ dataRate = t & (uint32)-1;
+ altpth = (int64)t < 0;
+ }
+
+ if (altpth) {
+ for (t = dataRate; !(t >> 32); ) {
+ --factor;
+ t += outRate;
+ }
+ dataRate = t & (uint32)-1;
+ }
+
+ if (dataRate / (outRate >> 16) > 0xffff) {
+ dataRate = ((dataRate - outRate) << 16) | (factor >> 16);
+ factor <<= 16;
+ altpth = true;
+ } else {
+ c = dataRate % (outRate >> 16);
+ dataRate /= (outRate >> 16);
+ t = ((c << 16) | (factor >> 16)) - (dataRate * (outRate & 0xffff));
+ factor = (factor << 16) | dataRate;
+ dataRate = t & (uint32)-1;;
+ altpth = (int64)t < 0;
+ }
+
+ if (altpth) {
+ t = dataRate;
+ do {
+ factor = (factor & ~0xffff) | (((factor & 0xffff) - 1) & 0xffff);
+ t += outRate;
+ } while (!(t >> 32));
+ dataRate = t & (uint32)-1;
+ }
+
+ result = factor;
+ } else {
+ outRate >>= 16;
+ if (outRate == 0x8000) {
+ t = factor << 1;
+ t = (t >> 32) + (dataRate << 1);
+ } else {
+ c = dataRate % outRate;
+ t = ((dataRate / outRate) << 16) | (((c << 16) | (factor >> 16)) / outRate);
+ }
+ result = t & (uint32)-1;
+ }
+
+ return result;
+}
+
+I3MFourToneSynthDriver::I3MFourToneSynthDriver(Common::Mutex &mutex, bool isStereo) :
+ I3MMusicDriver(mutex, isStereo), _duration(0), _pos(0), _chan(nullptr), _numChan(4) {
+ _chan = new Channel[_numChan];
+}
+
+I3MFourToneSynthDriver::~I3MFourToneSynthDriver() {
+ Common::StackLock lock(_mutex);
+ for (int i = 0; i < _numChan; ++i)
+ setWaveForm(i, 0, 0);
+ delete[] _chan;
+}
+
+void I3MFourToneSynthDriver::feed(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) {
+ if (dst == nullptr || type != Audio::Mixer::kMusicSoundType)
+ return;
+
+ if (expectStereo != _stereo)
+ error("I3MFourToneSynthDriver::feed(): stereo/mono mismatch between sound data and mixer stream");
+
+ const int8 *end = dst + len;
+
+ while (_duration && dst < end) {
+ if (_pos == 0)
+ --_duration;
+
+ int16 smp = 0;
+ for (int i = 0; i < _numChan; ++i) {
+ _chan[i].phase += _chan[i].rate;
+ smp += _chan[i].waveform[(_chan[i].phase >> 16) & 0xff];
+ }
+
+ smp = CLIP<int8>(smp >> 2, -128, 127);
+ *dst++ = smp;
+ if (_stereo)
+ *dst++ = smp;
+
+ if (++_pos == 370) {
+ _pos = 0;
+ if (!_duration)
+ _sig = true;
+ }
+ }
+
+ if (end > dst)
+ memset(dst, 0, end - dst);
+}
+
+void I3MFourToneSynthDriver::start() {
+ Common::StackLock lock(_mutex);
+ stop();
+ setDuration(50);
+}
+
+void I3MFourToneSynthDriver::stop() {
+ Common::StackLock lock(_mutex);
+ for (int i = 0; i < _numChan; ++i) {
+ _chan[i].phase = 0;
+ _chan[i].rate = 0;
+ }
+ setDuration(0);
+}
+
+void I3MFourToneSynthDriver::setDuration(uint16 duration) {
+ Common::StackLock lock(_mutex);
+ _duration = duration;
+ _pos = 0;
+ _sig = false;
+}
+
+void I3MFourToneSynthDriver::setWaveForm(uint8 chan, const uint8 *data, uint32 dataSize) {
+ assert(chan < _numChan);
+ Common::StackLock lock(_mutex);
+
+ delete[] _chan[chan].waveform;
+ if (data == nullptr || dataSize == 0)
+ return;
+ dataSize = MIN<uint32>(256, dataSize);
+ int8 *wf = new int8[256];
+ memset(wf, 0, 256);
+ for (uint32 i = 0; i < dataSize; ++i)
+ wf[i] = data[i] ^ 0x80;
+ _chan[chan].waveform = wf;
+}
+
+void I3MFourToneSynthDriver::setRate(uint8 chan, uint16 rate) {
+ assert(chan < _numChan);
+ Common::StackLock lock(_mutex);
+
+ _chan[chan].rate = rate ? (0x5060000 / (rate >> ((rate < 1600) ? 8 : 6))) : 0;
+}
+
+Common::WeakPtr<I3MPlayer> *I3MPlayer::_inst = nullptr;
+
+I3MPlayer::I3MPlayer(ScummEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer), _musicChannels(nullptr), _lastSound(0), _lastSong(-1), _lastSoundEffectPrio(0), _soundEffectNumLoops(-1),
+ _musicIDTable(nullptr), _macstr(nullptr), _musicIDTableLen(0), _soundUsage(0), _idRangeMax(86), _mdrv(nullptr), _sdrv(nullptr), _nextTickProc(this, &I3MPlayer::nextTick),
+ _songPlaying(false), _soundEffectPlaying(false), _songTimer(0), _songTimerInternal(0), _qmode(0), _qualHi(false), _mixerThread(false), _activeChanCount(0), _numMusicChannels(4) {
+ assert(_vm);
+ assert(_mixer);
+
+ if (_vm->_game.id == GID_INDY3) {
+ static const byte table[] = { 0x1D, 0x23, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x36, 0x3B, 0x42, 0x43, 0x45, 0x46, 0x53 };
+ _musicIDTable = table;
+ _musicIDTableLen = ARRAYSIZE(table);
+ }
+
+ _soundUsage = new uint8[_idRangeMax]();
+
+ _musicChannels = new MusicChannel*[_numMusicChannels];
+ assert(_musicChannels);
+ for (int i = 0; i < _numMusicChannels; ++i)
+ _musicChannels[i] = new MusicChannel(this);
+ memset(&_pcmSnd, 0, sizeof(_pcmSnd));
+}
+
+I3MPlayer::~I3MPlayer() {
+ _mixer->stopHandle(_soundHandle);
+ delete _macstr;
+ delete[] _soundUsage;
+
+ for (Common::Array<I3MSoundDriver*>::const_iterator i = _drivers.begin(); i != _drivers.end(); ++i)
+ delete *i;
+ _drivers.clear();
+
+ if (_musicChannels) {
+ for (int i = 0; i < _numMusicChannels; ++i)
+ delete _musicChannels[i];
+ delete[] _musicChannels;
+ }
+
+ delete _inst;
+ _inst = nullptr;
+}
+
+Common::SharedPtr<I3MPlayer> I3MPlayer::open(ScummEngine *vm, Audio::Mixer *mixer) {
+ Common::SharedPtr<I3MPlayer> scp = nullptr;
+
+ if (_inst == nullptr) {
+ scp = Common::SharedPtr<I3MPlayer>(new I3MPlayer(vm, mixer));
+ _inst = new Common::WeakPtr<I3MPlayer>(scp);
+ // We can start this with the ScummVM mixer output rate instead of the ASC rate. The Mac sample rate converter can handle it (at
+ // least for up to 48 KHz, I haven't tried higher settings) and we don't have to do another rate conversion in the ScummVM mixer.
+ if ((_inst == nullptr) || (mixer == nullptr) || !(scp->startDevices(mixer->getOutputRate(), mixer->getOutputRate() << 16/*ASC_DEVICE_RATE*/, PCM_BUFFER_SIZE, true)))
+ error("I3MPlayer::open(): Failed to start player");
+ }
+
+ return _inst->lock();
+}
+
+bool I3MPlayer::startDevices(uint32 outputRate, uint32 pcmDeviceRate, uint32 feedBufferSize, bool enableInterpolation) {
+ _macstr = new AudioStream_I3M(this, outputRate, false, enableInterpolation);
+ if (!_macstr || !_mixer)
+ return false;
+
+ _sdrv = new I3MLowLevelPCMDriver(_mixer->mutex(), pcmDeviceRate, enableInterpolation, false);
+ I3MFourToneSynthDriver *mdrv = new I3MFourToneSynthDriver(_mixer->mutex(), false);
+ if (!mdrv || !_sdrv)
+ return false;
+
+ for (int i = 0; i < 4; ++i)
+ mdrv->setWaveForm(i, _fourToneSynthWaveForm, sizeof(_fourToneSynthWaveForm));
+
+ _mdrv = mdrv;
+
+ _drivers.push_back(_mdrv);
+ _drivers.push_back(_sdrv);
+
+ _macstr->initDrivers();
+ _macstr->initBuffers(feedBufferSize);
+ _macstr->setVblCallback(&_nextTickProc);
+
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, _macstr, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+
+ return true;
+}
+
+void I3MPlayer::setMusicVolume(int vol) {
+ Common::StackLock lock(_mixer->mutex());
+ if (_macstr)
+ _macstr->setMasterVolume(Audio::Mixer::kMusicSoundType, vol);
+}
+
+void I3MPlayer::setSfxVolume(int vol) {
+ Common::StackLock lock(_mixer->mutex());
+ if (_macstr)
+ _macstr->setMasterVolume(Audio::Mixer::kSFXSoundType, vol);
+}
+
+void I3MPlayer::startSound(int id) {
+ if (id < 0 || id >= _idRangeMax)
+ return;
+
+ if (isSong(id))
+ startSong(id);
+ else
+ startSoundEffect(id);
+}
+
+void I3MPlayer::stopSound(int id) {
+ if (id < 0 || id >= _idRangeMax) {
+ warning("I3MPlayer::stopSound(): sound id '%d' out of range (0 - 85)", id);
+ return;
+ }
+
+ Common::StackLock lock(_mixer->mutex());
+ _soundUsage[id] = 0;
+
+ if (id == _lastSound)
+ stopActiveSound();
+}
+
+void I3MPlayer::stopAllSounds() {
+ Common::StackLock lock(_mixer->mutex());
+ memset(_soundUsage, 0, _idRangeMax);
+ stopActiveSound();
+}
+
+int I3MPlayer::getMusicTimer() {
+ Common::StackLock lock(_mixer->mutex());
+ return _songTimer;
+}
+
+int I3MPlayer::getSoundStatus(int id) const {
+ if (id < 0 || id >= _idRangeMax) {
+ warning("I3MPlayer::getSoundStatus(): sound id '%d' out of range (0 - 85)", id);
+ return 0;
+ }
+ Common::StackLock lock(_mixer->mutex());
+ return _soundUsage[id];
+}
+
+void I3MPlayer::setQuality(int qual) {
+ assert(qual >= Player_Mac_Indy3::kQualAuto && qual <= Player_Mac_Indy3::kQualLo);
+ while (_qualHi == isHiQuality()) {
+ if (_qmode == qual)
+ return;
+ _qmode = qual;
+ }
+
+ Common::StackLock lock(_mixer->mutex());
+ Common::Array<I3MSoundDriver*>::iterator dr = Common::find(_drivers.begin(), _drivers.end(), _mdrv);
+ delete _mdrv;
+ _qmode = qual;
+
+ if (isHiQuality()) {
+ I3MFourToneSynthDriver *mdrv = new I3MFourToneSynthDriver(_mixer->mutex(), false);
+ assert(mdrv);
+ for (int i = 0; i < 4; ++i)
+ mdrv->setWaveForm(i, _fourToneSynthWaveForm, sizeof(_fourToneSynthWaveForm));
+ _mdrv = mdrv;
+ _qualHi = true;
+ } else {
+ _mdrv = new I3MLQSynthDriver(_mixer->mutex(), false);
+ _qualHi = false;
+ assert(_mdrv);
+ }
+
+ if (dr != _drivers.end())
+ *dr = _mdrv;
+ else if (_drivers.empty())
+ _drivers.push_back(_mdrv);
+ else
+ error("I3MPlayer::setQuality(): Invalid usage");
+
+ assert(_macstr);
+ _macstr->initDrivers();
+}
+
+void I3MPlayer::generateData(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) const {
+ for (Common::Array<I3MSoundDriver*>::const_iterator i = _drivers.begin(); i != _drivers.end(); ++i)
+ (*i)->feed(dst, len, type, expectStereo);
+}
+
+uint32 I3MPlayer::getDriverDeviceRate(uint8 drvID) const {
+ return (drvID < _drivers.size()) ? _drivers[drvID]->getDeviceRate() : 0;
+}
+
+void I3MPlayer::nextTick() {
+ if (_songTimerInternal++ == 29) {
+ _songTimerInternal = 0;
+ ++_songTimer;
+ }
+
+ _mixerThread = true;
+
+ if (!_songPlaying && _sdrv->checkSignal())
+ updateSoundEffect();
+ else if (_songPlaying)
+ updateSong();
+
+ _mixerThread = false;
+}
+
+void I3MPlayer::startSong(int id) {
+ if (_mdrv == nullptr || id < 0 || id >= _idRangeMax) {
+ warning("I3MPlayer::startSong(): sound id '%d' out of range (0 - 85)", id);
+ return;
+ }
+
+ Common::StackLock lock(_mixer->mutex());
+
+ stopActiveSound();
+
+ uint32 sz = _vm->getResourceSize(rtSound, id);
+ const byte *ptr = _vm->getResourceAddress(rtSound, id);
+ assert(ptr);
+ byte *buff = new byte[sz];
+ memcpy(buff, ptr, sz);
+ Common::SharedPtr<const byte> sres(buff, Common::ArrayDeleter<const byte>());
+
+ _songTimer = 0;
+ ++_soundUsage[id];
+ if (_lastSong != -1)
+ --_soundUsage[_lastSong];
+ _lastSong = _lastSound = id;
+
+ //byte unused1 = ptr[4];
+ //byte unused2 = ptr[5];
+
+ if (isHiQuality()) {
+ _qualHi = true;
+ ptr += 14;
+ } else {
+ _qualHi = false;
+ ptr += 6;
+ }
+
+ _mdrv->start();
+
+ _activeChanCount = 0;
+ for (int i = 0; i < 3; ++i) {
+ uint16 offs = READ_LE_UINT16(ptr);
+ ptr += 2;
+ if (offs)
+ ++_activeChanCount;
+ _musicChannels[i]->start(sres, offs, _qualHi);
+ }
+ _songPlaying = true;
+}
+
+void I3MPlayer::startSoundEffect(int id) {
+ if (_sdrv == nullptr || id < 0 || id >= _idRangeMax) {
+ warning("I3MPlayer::startSoundEffect(): sound id '%d' out of range (0 - 85)", id);
+ return;
+ }
+
+ Common::StackLock lock(_mixer->mutex());
+
+ const uint8 *ptr = _vm->getResourceAddress(rtSound, id);
+ assert(ptr);
+
+ if (READ_LE_UINT16(ptr) < 28) {
+ warning("I3MPlayer::startSoundEffect(%d): invalid resource", id);
+ return;
+ }
+
+ if (_songPlaying)
+ return;
+
+ uint16 prio = READ_BE_UINT16(ptr + 4);
+
+ if (_lastSound) {
+ if (prio < _lastSoundEffectPrio)
+ return;
+ const uint8 *ptr2 = _vm->getResourceAddress(rtSound, _lastSound);
+ assert(ptr2);
+ if (READ_BE_UINT16(ptr2 + 6) == 0)
+ _soundUsage[_lastSound] = 0;
+ }
+
+ stopActiveSound();
+ _soundEffectPlaying = true;
+
+ // Two-byte prio always gets through.
+ _lastSoundEffectPrio = prio & 0xff;
+ _soundEffectNumLoops = (int8)ptr[27];
+
+ int offs = (READ_BE_UINT16(ptr + 14) >= READ_BE_UINT16(ptr + 12)) ? 2 : 0;
+ uint16 numSamples = READ_BE_UINT16(ptr + 12 + offs);
+ uint16 spos = READ_BE_UINT16(ptr + 8 + offs);
+ if (spos <= 20)
+ return;
+
+ byte *buff = new byte[numSamples - 22];
+ memcpy(buff, ptr + spos + 22, numSamples - 22);
+
+ _pcmSnd.rate = 0x4E200000 / (READ_BE_UINT16(ptr + 20 + offs) >> 7);
+ _pcmSnd.data = Common::SharedPtr<const byte> (buff, Common::ArrayDeleter<const byte>());
+ _pcmSnd.len = numSamples - 23;
+ _pcmSnd.loopst = numSamples - 2;
+ _pcmSnd.loopend = numSamples - 1;
+ _pcmSnd.baseFreq = 60;
+
+ _sdrv->play(&_pcmSnd);
+
+ _lastSound = id;
+ _soundUsage[id]++;
+}
+
+void I3MPlayer::stopSong() {
+ Common::StackLock lock(_mixer->mutex());
+ _mdrv->stop();
+ _songPlaying = false;
+ --_soundUsage[_lastSong];
+ _lastSound = _lastSong = 0;
+}
+
+void I3MPlayer::stopSoundEffect() {
+ Common::StackLock lock(_mixer->mutex());
+ _sdrv->stop();
+ _soundEffectPlaying = false;
+ _lastSoundEffectPrio = 0;
+ _lastSound = 0;
+}
+
+void I3MPlayer::stopActiveSound() {
+ if (_soundEffectPlaying)
+ stopSoundEffect();
+ else if (_songPlaying)
+ stopSong();
+}
+
+void I3MPlayer::updateSong() {
+ if (_lastSong) {
+ for (int i = (_qualHi ? 4 : 1); i; --i) {
+ for (int ii = 0; ii < _numMusicChannels && _songPlaying; ++ii)
+ _musicChannels[ii]->nextTick();
+ }
+ }
+
+ for (int i = _numMusicChannels - 1; i >= 0; --i)
+ _mdrv->setRate(i, _lastSong ? _musicChannels[i]->checkPeriod() : 0);
+ if (_songPlaying)
+ _mdrv->setDuration(10);
+}
+
+uint16 savedOffset = 0;
+I3MPlayer::MusicChannel *I3MPlayer::MusicChannel::_ctrlChan = nullptr;
+
+I3MPlayer::MusicChannel::MusicChannel(I3MPlayer *pl) : _player(pl), _vars(nullptr), _numVars(0), _ctrlProc(nullptr),
+ _resSize(0), _savedOffset(savedOffset), _modShapes(_pv2ModTbl), _modShapesTableSize(_pv2ModTblSize) {
+ static const CtrlProc ctrl[8] {
+ &I3MPlayer::MusicChannel::ctrl_setShape,
+ &I3MPlayer::MusicChannel::ctrl_modPara,
+ &I3MPlayer::MusicChannel::ctrl_init,
+ &I3MPlayer::MusicChannel::ctrl_returnFromSubroutine,
+ &I3MPlayer::MusicChannel::ctrl_jumpToSubroutine,
+ &I3MPlayer::MusicChannel::ctrl_initOther,
+ &I3MPlayer::MusicChannel::ctrl_decrJumpIf,
+ &I3MPlayer::MusicChannel::ctrl_writeVar
+ };
+
+ const uint16 *mVars[] = {
+ /* 0 */ &_frameLen, &_curPos, &_freqCur, &_freqIncr, &_freqEff,
+ /* 5 */ &_envPhase, &_envRate, &_tempo, &_envCutoff, (uint16*)&_transpose,
+ /* 10 */ &_envLen, &_envShape, &_envStep, &_envStepLen, &_modType,
+ /* 15 */ &_modState, &_modStep, &_modSensitivity, &_modRange, &_localVars[0],
+ /* 20 */ &_localVars[1], &_localVars[2], &_localVars[3], &_localVars[4]
+ };
+
+ _ctrlProc = ctrl;
+ _vars = new uint16*[ARRAYSIZE(mVars)];
+ memcpy(_vars, mVars, sizeof(mVars));
+ _numVars = ARRAYSIZE(mVars);
+ _savedOffset = 0;
+ _ctrlChan = nullptr;
+
+ clear();
+}
+
+I3MPlayer::MusicChannel::~MusicChannel() {
+ clear();
+ delete[] _vars;
+ _vars = nullptr;
+ _numVars = 0;
+}
+
+void I3MPlayer::MusicChannel::clear() {
+ for (int i = 0; i < _numVars; ++i)
+ getMemberRef(i) = 0;
+ _resource.reset();
+ _resSize = 0;
+ _hq = false;
+}
+
+void I3MPlayer::MusicChannel::start(Common::SharedPtr<const byte> &songRes, uint16 offset, bool hq) {
+ clear();
+ _resource = songRes;
+ _resSize = READ_LE_UINT16(_resource.get());
+ _curPos = offset;
+ _frameLen = 1;
+ _hq = hq;
+}
+
+void I3MPlayer::MusicChannel::nextTick() {
+ if (!_frameLen)
+ return;
+
+ _ctrlChan = this;
+ _envPhase += _envRate;
+ _freqCur += _freqIncr;
+
+ uint16 v = _modState + _modStep;
+ int frqAdjust = 0;
+
+ if (v != 0) {
+ if (v >= _modRange)
+ v -= _modRange;
+ _modState = v;
+ uint16 ix = (_modType + (v >> 4)) >> 4;
+ assert(ix < _modShapesTableSize);
+ frqAdjust = (((_modShapes[ix] << 7) * _modSensitivity) >> 16);
+ }
+
+ _freqEff = _freqCur + frqAdjust;
+
+ if (_envLen && !--_envLen) {
+ _envStep = 4;
+ _envStepLen = 1;
+ }
+
+ if (!--_frameLen)
+ parseNextEvents();
+
+ if (!_envStepLen || --_envStepLen)
+ return;
+
+ int ix = _envShape + _envStep++;
+ assert(ix < ARRAYSIZE(_envShapes));
+ const uint32 *in = &_envShapes[ix];
+
+ for (; (*in & 0xffff) == 0xffff; ++in) {
+ _envPhase = *in >> 16;
+ if (_envPhase == 0)
+ _envRate = 0;
+ ++_envStep;
+ }
+
+ _envStepLen = *in & 0xffff;
+ _envRate = *in >> 16;
+}
+
+void I3MPlayer::MusicChannel::parseNextEvents() {
+ if (_resSize && _curPos >= _resSize) {
+ warning("I3MPlayer::MusicChannel::parseNext(): playback error");
+ _frameLen = 0;
+ _curPos = 0;
+ _player->stopSong();
+ }
+
+ if (_curPos == 0)
+ return;
+
+ const byte *in = _resource.get() + _curPos;
+
+ for (bool loop = true, loop2 = false; loop; ) {
+ uint8 cmd = *in++;
+
+ if (in - _resource.get() >= _resSize)
+ break;
+
+ if (cmd >= 0xf8 && !loop2) {
+ if (!ctrlProc(cmd - 0xf8, in))
+ loop = false;
+
+ if (in - _resource.get() >= _resSize)
+ break;
+
+ } else {
+ loop2 = true;
+ MusicChannel *ch = _player->getMusicChannel(cmd >> 5);
+ setFrameLen(cmd);
+ cmd = *in++;
+
+ if (in - _resource.get() >= _resSize)
+ break;
+
+ if (ch != nullptr && ((cmd & 0x7f) != 0x7f))
+ ch->noteOn(_ctrlChan->_frameLen, cmd & 0x7f);
+
+ if (cmd & 0x80)
+ loop = false;
+ }
+ }
+
+ int cp = in - _resource.get();
+ if ((cp >= _resSize && _frameLen) || cp & ~0xffff) {
+ warning("I3MPlayer::MusicChannel::parseNext(): playback error");
+ _frameLen = 0;
+ _player->stopSong();
+ }
+
+ _curPos = _frameLen ? cp : 0;
+ if (!_frameLen)
+ _player->endOfTrack();
+}
+
+void I3MPlayer::MusicChannel::noteOn(uint16 duration, uint8 note) {
+ static const uint16 noteFreqTable[2][12] = {
+ { 0xFFC0, 0xF140, 0xE3C0, 0xD700, 0xCB40, 0xBF80, 0xB4C0, 0xAA80, 0xA100, 0x9800, 0x8F80, 0x8740 },
+ { 0x8E84, 0x8684, 0x7EF7, 0x77D7, 0x714F, 0x6AC4, 0x64C6, 0x5F1E, 0x59C7, 0x54BD, 0x4FFC, 0x4B7E }
+ };
+ _envStep = 0;
+ _envStepLen = 1;
+ _frameLen = duration;
+ _envLen = _frameLen - _envCutoff;
+ int n = note + _transpose;
+ while (n < 0)
+ n += 12;
+
+ _freqEff = _freqCur = noteFreqTable[_hq ? 0 : 1][n % 12] >> ( n / 12);
+}
+
+uint16 I3MPlayer::MusicChannel::checkPeriod() const {
+ return (_frameLen && _envPhase) ? _freqEff : 0;
+}
+
+bool I3MPlayer::MusicChannel::ctrl_setShape(const byte *&pos) {
+ static const uint16 offsets[15] = { 0, 6, 12, 18, 24, 30, 36, 44, 52, 60, 68, 82, 76, 82, 90 };
+ uint8 i = (*pos++) >> 1;
+ assert(i < ARRAYSIZE(offsets));
+ _envShape = offsets[i];
+ return true;
+}
+
+bool I3MPlayer::MusicChannel::ctrl_modPara(const byte *&pos) {
+ static const uint16 table[10] = { 0x0000, 0x1000, 0x1000, 0x1000, 0x2000, 0x0020, 0x3020, 0x2000, 0x2020, 0x1000 };
+ int ix = (*pos++);
+ if ((ix & 1) || (ix >> 1 >= ARRAYSIZE(table)))
+ error("I3MPlayer::MusicChannel::ctrl_modPara(): data error");
+ ix >>= 1;
+ _modType = table[ix];
+ _modRange = table[ix + 1];
+ return true;
+}
+
+bool I3MPlayer::MusicChannel::ctrl_init(const byte *&pos) {
+ limitedClear();
+ return true;
+}
+
+bool I3MPlayer::MusicChannel::ctrl_returnFromSubroutine(const byte *&pos) {
+ pos = _resource.get() + _savedOffset;
+ if (pos >= _resource.get() + _resSize)
+ error("I3MPlayer::MusicChannel::ctrl_returnFromSubroutine(): invalid address");
+ return true;
+}
+
+bool I3MPlayer::MusicChannel::ctrl_jumpToSubroutine(const byte *&pos) {
+ uint16 offs = READ_LE_UINT16(pos);
+ _savedOffset = pos + 2 - _resource.get();
+ if (offs >= _resSize)
+ error("I3MPlayer::MusicChannel::ctrl_jumpToSubroutine(): invalid address");
+ pos = _resource.get() + offs;
+ return true;
+}
+
+bool I3MPlayer::MusicChannel::ctrl_initOther(const byte *&pos) {
+ uint16 val = READ_LE_UINT16(pos);
+ pos += 2;
+ if (val % 50)
+ error("I3MPlayer::MusicChannel::ctrl_initOther(): data error");
+ _ctrlChan = _player->getMusicChannel(val / 50);
+ assert(_ctrlChan);
+ _ctrlChan->limitedClear();
+ return true;
+}
+
+bool I3MPlayer::MusicChannel::ctrl_decrJumpIf(const byte *&pos) {
+ uint16 &var = getMemberRef(*pos++ >> 1);
+ int16 offs = READ_LE_INT16(pos);
+ pos += 2;
+ if (var == 0) {
+ pos += offs;
+ if (pos < _resource.get() || pos >= _resource.get() + _resSize)
+ error("I3MPlayer::MusicChannel::ctrl_jumpToSubroutine(): invalid address");
+ } else {
+ --var;
+ }
+ return true;
+}
+
+bool I3MPlayer::MusicChannel::ctrl_writeVar(const byte *&pos) {
+ byte ix = *pos++;
+ uint16 val = READ_LE_UINT16(pos);
+ pos += 2;
+ (getMemberRef(ix >> 1)) = val;
+ return (bool)ix;
+}
+
+bool I3MPlayer::MusicChannel::ctrlProc(int procId, const byte *&arg) {
+ return (_ctrlChan && _ctrlProc && procId >= 0 && procId <= 7) ? (_ctrlChan->*_ctrlProc[procId])(arg) : false;
+}
+
+void I3MPlayer::MusicChannel::setFrameLen(uint8 len) {
+ static const uint8 durationTicks[22] = {
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x04, 0x05, 0x06, 0x08, 0x09,
+ 0x0C, 0x10, 0x12, 0x18, 0x20, 0x26, 0x30, 0x40, 0x48, 0x60, 0x00
+ };
+
+ assert(_ctrlChan);
+ len &= 0x1f;
+ if (len >= ARRAYSIZE(durationTicks))
+ error("I3MPlayer::MusicChannel::setFrameLen(): Out of range (val %d, range 0 - %d)", len, ARRAYSIZE(durationTicks) - 1);
+ _ctrlChan->_frameLen = MAX<uint16>(_ctrlChan->_tempo, 1) * durationTicks[len];
+}
+
+void I3MPlayer::MusicChannel::limitedClear() {
+ for (int i = 1; i < 7; ++i)
+ getMemberRef(i) = 0;
+ for (int i = 8; i < 10; ++i)
+ getMemberRef(i) = 0;
+ for (int i = 11; i < 15; ++i)
+ getMemberRef(i) = 0;
+ for (int i = 15; i < 19; ++i)
+ getMemberRef(i) = 0;
+}
+
+uint16 &I3MPlayer::MusicChannel::getMemberRef(int pos) {
+ assert(_vars);
+ if (pos < 0 || pos >= _numVars)
+ error("I3MPlayer::MusicChannel::getMemberRef(): attempting invalid access (var: %d, valid range: %d - %d)", pos, 0, _numVars - 1);
+ return *_vars[pos];
+}
+
+const uint32 I3MPlayer::MusicChannel::_envShapes[98] = {
+ 0x0003ffff, 0x00000000, 0x00000000, 0x00000000, 0x0000ffff, 0x00000000,
+ 0x0003ffff, 0x00000020, 0x0000ffff, 0x00000000, 0x0000ffff, 0x00000000,
+ 0x0003ffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x0003ffff, 0x00000002, 0x0000ffff, 0x00000000, 0x0000ffff, 0x00000000,
+ 0x0003ffff, 0x00000006, 0x0000ffff, 0x00000000, 0x0000ffff, 0x00000000,
+ 0x0003ffff, 0x00000010, 0x0000ffff, 0x00000000, 0x0000ffff, 0x00000000,
+ 0xea60ffff, 0xfc180014, 0x00000000, 0x00000000, 0x9c40ffff, 0xec780005, 0x0000ffff, 0x00000000,
+ 0xc350ffff, 0x00000008, 0x7530ffff, 0x00000000, 0x6d60ffff, 0xec780005, 0x0000ffff, 0x00000000,
+ 0xea60ffff, 0xf8300010, 0x00000000, 0x00000000, 0x6d60ffff, 0xe8900005, 0x0000ffff, 0x00000000,
+ 0xd6d8ffff, 0x00000008, 0x88b8ffff, 0x00000000, 0x9c40ffff, 0xf63c000a, 0x0000ffff, 0x00000000,
+ 0xea60ffff, 0x00000004, 0xf63c0008, 0x00000000, 0x9c40ffff, 0xe8900005, 0x0000ffff, 0x00000000,
+ 0x0000ffff, 0x00960154, 0xff6a0154, 0x0000ffff, 0x0000ffff, 0x00000000,
+ 0x4e20ffff, 0x0fa00007, 0x03e8000f, 0x00000000, 0x88b8ffff, 0xf830000f, 0x0000ffff, 0x00000000,
+ 0x88b8ffff, 0x01f40014, 0x00000000, 0x00000000, 0xafc8ffff, 0xfe0c003c, 0x0000ffff, 0x00000000
+};
+
+void I3MPlayer::updateSoundEffect() {
+ _sdrv->resetSignal(false);
+ bool chkRestart = false;
+
+ if (!_soundEffectPlaying || !_lastSound) {
+ chkRestart = true;
+ } else {
+ if (_soundEffectNumLoops > 0)
+ --_soundEffectNumLoops;
+ if (_soundEffectNumLoops)
+ _sdrv->play(&_pcmSnd);
+ else
+ --_soundUsage[_lastSound];
+ chkRestart = (_soundEffectNumLoops == 0);
+ }
+
+ if (chkRestart) {
+ _lastSound = 0;
+ _lastSoundEffectPrio = 0;
+ checkRestartSoundEffects();
+ }
+}
+
+void I3MPlayer::checkRestartSoundEffects() {
+ for (int i = 1; i < _idRangeMax; ++i) {
+ if (!_soundUsage[i] || isSong(i))
+ continue;
+
+ const uint8 *ptr = _vm->getResourceAddress(rtSound, i);
+ assert(ptr);
+ if (READ_BE_UINT16(ptr + 6) == 0)
+ continue;
+
+ _soundUsage[i] = 1;
+ startSoundEffect(i);
+ }
+}
+
+const uint8 I3MPlayer::_fourToneSynthWaveForm[256] = {
+ 0x80, 0x7a, 0x74, 0x6e, 0x69, 0x63, 0x5d, 0x57, 0x52, 0x4c, 0x47, 0x42, 0x3e, 0x3b, 0x38, 0x35,
+ 0x34, 0x33, 0x34, 0x35, 0x37, 0x3a, 0x3e, 0x43, 0x49, 0x4e, 0x54, 0x5b, 0x61, 0x67, 0x6c, 0x71,
+ 0x75, 0x78, 0x7a, 0x7c, 0x7c, 0x7b, 0x79, 0x76, 0x73, 0x6f, 0x6b, 0x66, 0x62, 0x5e, 0x5b, 0x58,
+ 0x56, 0x56, 0x57, 0x59, 0x5c, 0x61, 0x67, 0x6e, 0x77, 0x80, 0x8a, 0x95, 0xa0, 0xac, 0xb7, 0xc2,
+ 0xcc, 0xd6, 0xdf, 0xe7, 0xee, 0xf4, 0xf8, 0xfb, 0xfe, 0xff, 0xff, 0xfe, 0xfd, 0xfb, 0xf9, 0xf6,
+ 0xf3, 0xf0, 0xec, 0xe9, 0xe6, 0xe3, 0xe0, 0xdd, 0xda, 0xd7, 0xd4, 0xd1, 0xce, 0xca, 0xc6, 0xc2,
+ 0xbd, 0xb8, 0xb3, 0xad, 0xa7, 0xa1, 0x9a, 0x93, 0x8d, 0x86, 0x7f, 0x79, 0x73, 0x6d, 0x68, 0x63,
+ 0x5f, 0x5c, 0x5a, 0x58, 0x57, 0x57, 0x58, 0x5a, 0x5c, 0x5f, 0x63, 0x67, 0x6b, 0x70, 0x75, 0x7b,
+ 0x80, 0x85, 0x8b, 0x90, 0x95, 0x99, 0x9d, 0xa1, 0xa4, 0xa6, 0xa8, 0xa9, 0xa9, 0xa8, 0xa6, 0xa4,
+ 0xa1, 0x9d, 0x98, 0x93, 0x8d, 0x87, 0x81, 0x7a, 0x73, 0x6d, 0x66, 0x5f, 0x59, 0x53, 0x4d, 0x48,
+ 0x43, 0x3e, 0x3a, 0x36, 0x32, 0x2f, 0x2c, 0x29, 0x26, 0x23, 0x20, 0x1d, 0x1a, 0x17, 0x14, 0x10,
+ 0x0d, 0x0a, 0x07, 0x05, 0x03, 0x02, 0x01, 0x01, 0x02, 0x05, 0x08, 0x0c, 0x12, 0x19, 0x21, 0x2a,
+ 0x34, 0x3e, 0x49, 0x54, 0x60, 0x6b, 0x76, 0x80, 0x89, 0x92, 0x99, 0x9f, 0xa4, 0xa7, 0xa9, 0xaa,
+ 0xaa, 0xa8, 0xa5, 0xa2, 0x9e, 0x9a, 0x95, 0x91, 0x8d, 0x8a, 0x87, 0x85, 0x84, 0x84, 0x86, 0x88,
+ 0x8b, 0x8f, 0x94, 0x99, 0x9f, 0xa5, 0xac, 0xb2, 0xb7, 0xbd, 0xc2, 0xc6, 0xc9, 0xcb, 0xcc, 0xcd,
+ 0xcc, 0xcb, 0xc8, 0xc5, 0xc2, 0xbe, 0xb9, 0xb4, 0xae, 0xa9, 0xa3, 0x9d, 0x97, 0x92, 0x8c, 0x86
+};
+
+void I3MPlayer::endOfTrack() {
+ if (!_activeChanCount || !--_activeChanCount)
+ stopSong();
+}
+
+bool I3MPlayer::isSong(int id) const {
+ return (Common::find(_musicIDTable, &_musicIDTable[_musicIDTableLen], id) != &_musicIDTable[_musicIDTableLen]);
+}
+
+bool I3MPlayer::isHiQuality() const {
+ return _mixerThread ? _qualHi : (_qmode == Player_Mac_Indy3::kQualAuto && (_vm->VAR_SOUNDCARD == 0xff || _vm->VAR(_vm->VAR_SOUNDCARD) == 11)) || (_qmode == Player_Mac_Indy3::kQualHi);
+}
+
+I3MPlayer::MusicChannel *I3MPlayer::getMusicChannel(uint8 id) const {
+ return (id < _numMusicChannels) ? _musicChannels[id] : 0;
+}
+
+Player_Mac_Indy3::Player_Mac_Indy3(ScummEngine *vm, Audio::Mixer *mixer) : _player(nullptr) {
+ _player = I3MPlayer::open(vm, mixer);
+}
+
+Player_Mac_Indy3::~Player_Mac_Indy3() {
+ _player = nullptr;
+}
+
+void Player_Mac_Indy3::setMusicVolume(int vol) {
+ if (_player != nullptr)
+ _player->setMusicVolume(vol);
+}
+
+void Player_Mac_Indy3::setSfxVolume(int vol) {
+ if (_player != nullptr)
+ _player->setSfxVolume(vol);
+}
+
+void Player_Mac_Indy3::startSound(int id) {
+ if (_player != nullptr)
+ _player->startSound(id);
+}
+
+void Player_Mac_Indy3::stopSound(int id) {
+ if (_player != nullptr)
+ _player->stopSound(id);
+}
+
+void Player_Mac_Indy3::stopAllSounds() {
+ if (_player != nullptr)
+ _player->stopAllSounds();
+}
+
+int Player_Mac_Indy3::getMusicTimer() {
+ return (_player != nullptr) ? _player->getMusicTimer() : 0;
+}
+
+int Player_Mac_Indy3::getSoundStatus(int id) const {
+ return (_player != nullptr) ? _player->getSoundStatus(id) : 0;
+}
+
+void Player_Mac_Indy3::setQuality(int qual) {
+ if (_player != nullptr)
+ _player->setQuality(qual);
+}
+
+#undef ASC_DEVICE_RATE
+#undef PCM_BUFFER_SIZE
+#undef RATE_CNV_BIT_RES
+
+} // End of namespace Scumm
diff --git a/engines/scumm/players/player_mac_indy3.h b/engines/scumm/players/player_mac_indy3.h
new file mode 100644
index 00000000000..a5e79a1d1be
--- /dev/null
+++ b/engines/scumm/players/player_mac_indy3.h
@@ -0,0 +1,61 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef SCUMM_PLAYERS_PLAYER_MAC_INDY3_H
+#define SCUMM_PLAYERS_PLAYER_MAC_INDY3_H
+
+#include "scumm/music.h"
+
+namespace Audio {
+class Mixer;
+}
+
+namespace Scumm {
+
+class ScummEngine;
+class I3MPlayer;
+
+class Player_Mac_Indy3 : public MusicEngine {
+public:
+ enum Quality {
+ kQualAuto = 0,
+ kQualHi,
+ kQualLo
+ };
+
+ Player_Mac_Indy3(ScummEngine *vm, Audio::Mixer *mixer);
+ ~Player_Mac_Indy3() override;
+ void setMusicVolume(int vol) override;
+ void setSfxVolume(int vol) override;
+ void startSound(int id) override;
+ void stopSound(int id) override;
+ void stopAllSounds() override;
+ int getMusicTimer() override;
+ int getSoundStatus(int id) const override;
+ void setQuality(int qual) override;
+
+private:
+ Common::SharedPtr<I3MPlayer> _player;
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/players/player_towns.h b/engines/scumm/players/player_towns.h
index 128a926d996..93148f89b6f 100644
--- a/engines/scumm/players/player_towns.h
+++ b/engines/scumm/players/player_towns.h
@@ -36,7 +36,7 @@ public:
virtual bool init() = 0;
- void setSfxVolume(int vol);
+ void setSfxVolume(int vol) override;
int getSoundStatus(int sound) const override;
diff --git a/engines/scumm/players/player_v2base.cpp b/engines/scumm/players/player_v2base.cpp
index 57f2d09d45b..8f89952c348 100644
--- a/engines/scumm/players/player_v2base.cpp
+++ b/engines/scumm/players/player_v2base.cpp
@@ -648,5 +648,7 @@ void Player_V2Base::nextTick() {
}
}
+extern const uint8 *_pv2ModTbl = reinterpret_cast<const uint8*>(freqmod_table);
+extern const uint32 _pv2ModTblSize = ARRAYSIZE(freqmod_table);
} // End of namespace Scumm
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 35246f0d02a..69b5eb14b42 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -62,6 +62,7 @@
#include "scumm/players/player_sid.h"
#include "scumm/players/player_pce.h"
#include "scumm/players/player_apple2.h"
+#include "scumm/players/player_mac_indy3.h"
#include "scumm/players/player_v1.h"
#include "scumm/players/player_v2.h"
#include "scumm/players/player_v2cms.h"
@@ -2167,6 +2168,9 @@ void ScummEngine::setupMusic(int midi, const Common::Path &macInstrumentFile) {
#endif
} else if (_game.platform == Common::kPlatformAmiga && _game.version <= 4) {
_musicEngine = new Player_V4A(this, _mixer);
+ } else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_INDY3) {
+ _musicEngine = new Player_Mac_Indy3(this, _mixer);
+ _sound->_musicType = MDT_MACINTOSH;
} else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_LOOM) {
_musicEngine = new Player_V3M(this, _mixer, ConfMan.getBool("mac_v3_low_quality_music"));
((Player_V3M *)_musicEngine)->init(macInstrumentFile);
@@ -2336,10 +2340,7 @@ void ScummEngine::syncSoundSettings() {
if (_musicEngine) {
_musicEngine->setMusicVolume(soundVolumeMusic);
- }
-
- if (_townsPlayer) {
- _townsPlayer->setSfxVolume(soundVolumeSfx);
+ _musicEngine->setSfxVolume(soundVolumeSfx);
}
if (ConfMan.getBool("speech_mute"))
diff --git a/engines/scumm/scumm.h b/engines/scumm/scumm.h
index afde6939270..45866e07ae4 100644
--- a/engines/scumm/scumm.h
+++ b/engines/scumm/scumm.h
@@ -975,6 +975,7 @@ protected:
const byte *_scriptOrgPointer = nullptr;
const byte * const *_lastCodePtr = nullptr;
byte _opcode = 0;
+ bool _debug =false;
byte _currentScript = 0xFF; // Let debug() work on init stage
int _scummStackPos = 0;
int _vmStack[256];
diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp
index 8953cf93865..6526a742a0f 100644
--- a/engines/scumm/sound.cpp
+++ b/engines/scumm/sound.cpp
@@ -529,68 +529,7 @@ void Sound::triggerSound(int soundID) {
warning("Scumm::Sound::triggerSound: encountered audio resource with chunk type 'SOUN' and sound type %d", type);
}
}
- else if ((_vm->_game.platform == Common::kPlatformMacintosh) && (_vm->_game.id == GID_INDY3) && ptr[4] != 0x7F) {
- // Sound format as used in Indy3 EGA Mac.
- // It seems to be closely related to the Amiga format, see player_v3a.cpp
- //
- // We assume that if byte 5 is 0x7F, it's music because that's
- // where the priority of the track is stored, and it's always
- // that value. See player_v2.cpp
- //
- // The following is known:
- // offset 0, 16 LE: total size
- // offset 2-7: ?
- // offset 8, 16BE: offset to sound data (usually 0x1C = 28 -> header size 28?)
- // offset 10-11: ? another offset, maybe related to looping?
- // offset 12, 16BE: size of sound data
- // offset 14-15: ? often the same as 12-13: maybe loop size/end?
- // offset 16-19: ? all 0?
- // offset 20, 16BE: rate divisor
- // offset 22-23: ? often identical to the rate divisior? (but not in sound 8, which loops)
- // offset 24, byte (?): volume
- // offset 25: ? same as volume -- maybe left vs. right channel?
- // offset 26: if == 0: stop current identical sound (see ptr[26] comment below)
- // offset 27: ? loopcount? 0xff == -1 for infinite?
-
- size = READ_BE_UINT16(ptr + 12);
- assert(size);
-
- rate = 3579545 / READ_BE_UINT16(ptr + 20);
- sound = (byte *)malloc(size);
- int vol = ptr[24] * 4;
- int loopStart = 0, loopEnd = 0;
- int loopcount = ptr[27];
-
- memcpy(sound, ptr + READ_BE_UINT16(ptr + 8), size);
- Audio::SeekableAudioStream *plainStream = Audio::makeRawStream(sound, size, rate, 0);
-
- if (loopcount > 1) {
- loopStart = READ_BE_UINT16(ptr + 10) - READ_BE_UINT16(ptr + 8);
- loopEnd = READ_BE_UINT16(ptr + 14);
-
- // TODO: Currently we will only ever play till "loopEnd", even when we only have
- // a finite repetition count.
- stream = new Audio::SubLoopingAudioStream(plainStream, loopcount == 255 ? 0 : loopcount, Audio::Timestamp(0, loopStart, rate), Audio::Timestamp(0, loopEnd, rate));
- } else {
- stream = plainStream;
- }
-
- // When unset, we assume that this byte is meant to interrupt any other
- // instance of the current sound (as done by the Indy3 Amiga driver,
- // which was checked against disassembly). A good test for the expected
- // behavior is to ring the boxing bell in room 73; in the original
- // interpreter it rings 3 times in a row, and if we don't do this the
- // second bell sound is never heard. Another example is the thunder
- // sound effect when Indy is outside the windows of Castle Brunwald
- // (room 13): it's meant to have a couple of "false starts".
- // TODO: do an actual disasm of Indy3 Macintosh (anyone? ;)
- if (!ptr[26])
- _mixer->stopID(soundID);
-
- _mixer->playStream(Audio::Mixer::kSFXSoundType, nullptr, stream, soundID, vol, 0);
- }
else {
-
if (_vm->_game.id == GID_MONKEY_VGA || _vm->_game.id == GID_MONKEY_EGA) {
// Works around the fact that in some places in MonkeyEGA/VGA,
// the music is never explicitly stopped.
@@ -602,9 +541,8 @@ void Sound::triggerSound(int soundID) {
}
}
- if (_vm->_musicEngine) {
+ if (_vm->_musicEngine)
_vm->_musicEngine->startSound(soundID);
- }
if (_vm->_townsPlayer)
_currentCDSound = _vm->_townsPlayer->getCurrentCdaSound();
@@ -2468,7 +2406,7 @@ int ScummEngine::readSoundResourceSmallHeader(ResId idx) {
}
}
- if ((_sound->_musicType == MDT_PCSPK || _sound->_musicType == MDT_PCJR) && wa_offs != 0) {
+ if ((_sound->_musicType == MDT_PCSPK || _sound->_musicType == MDT_PCJR || _sound->_musicType == MDT_MACINTOSH) && wa_offs != 0) {
if (_game.features & GF_OLD_BUNDLE) {
_fileHandle->seek(wa_offs, SEEK_SET);
_fileHandle->read(_res->createResource(rtSound, idx, wa_size), wa_size);
diff --git a/engines/scumm/vars.cpp b/engines/scumm/vars.cpp
index 1a2b530eabd..ec21546739c 100644
--- a/engines/scumm/vars.cpp
+++ b/engines/scumm/vars.cpp
@@ -21,6 +21,7 @@
#include "common/config-manager.h"
+#include "scumm/music.h"
#include "scumm/scumm.h"
#include "scumm/scumm_v0.h"
#include "scumm/scumm_v8.h"
@@ -856,6 +857,9 @@ void ScummEngine::setSoundCardVarToCurrentConfig() {
// 3 AdLib
// 4 Roland
switch (_sound->_musicType) {
+ case MDT_MACINTOSH:
+ VAR(VAR_SOUNDCARD) = (_game.id == GID_INDY3) ? 11 : 3;
+ break;
case MDT_NONE:
case MDT_PCSPK:
VAR(VAR_SOUNDCARD) = 0;
@@ -871,7 +875,7 @@ void ScummEngine::setSoundCardVarToCurrentConfig() {
break;
default:
if ((_game.id == GID_MONKEY_EGA || _game.id == GID_MONKEY_VGA || (_game.id == GID_LOOM && _game.version == 3))
- && (_game.platform == Common::kPlatformDOS)) {
+ && (_game.platform == Common::kPlatformDOS)) {
VAR(VAR_SOUNDCARD) = 4;
} else {
VAR(VAR_SOUNDCARD) = 3;
More information about the Scummvm-git-logs
mailing list