[Scummvm-git-logs] scummvm master -> 5fe07a8fb1afbf19fce6f94e8c9eaf609cb299db

athrxx noreply at scummvm.org
Wed Feb 28 20:33:08 UTC 2024


This automated email contains information about 5 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
c774054a4a SCUMM: (V3/Mac) - restructure sound code
95027d64d3 SCUMM: (V3/Mac) - more preparations to extend sound player
aee2bf53bb SCUMM: (Loom/Mac) - add new sound player
c52bfe4f27 SCUMM: (Loom/Mac) - add music quality slider widget
5fe07a8fb1 SCUMM: (Loom/Mac) - work around original tempo glitch


Commit: c774054a4a2cc26d6caaa6b959a8f00ee8ae0bad
    https://github.com/scummvm/scummvm/commit/c774054a4a2cc26d6caaa6b959a8f00ee8ae0bad
Author: athrxx (athrxx at scummvm.org)
Date: 2024-02-28T21:01:00+01:00

Commit Message:
SCUMM: (V3/Mac) - restructure sound code

(preliminary reorganization to make support for other new players easier,
e. g. Loom or MI1)

Changed paths:
  A engines/scumm/players/player_mac_intern.h
  A engines/scumm/players/player_mac_new.cpp
  A engines/scumm/players/player_mac_new.h
  R engines/scumm/players/player_mac_indy3.h
    engines/scumm/module.mk
    engines/scumm/players/player_mac_indy3.cpp
    engines/scumm/scumm.cpp


diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index c10262493eb..abea3f04492 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -54,6 +54,7 @@ MODULE_OBJS := \
 	players/player_he.o \
 	players/player_mac.o \
 	players/player_mac_indy3.o \
+	players/player_mac_new.o \
 	players/player_mod.o \
 	players/player_nes.o \
 	players/player_pce.o \
diff --git a/engines/scumm/players/player_mac_indy3.cpp b/engines/scumm/players/player_mac_indy3.cpp
index f7b0a62b8b5..cca30683ad6 100644
--- a/engines/scumm/players/player_mac_indy3.cpp
+++ b/engines/scumm/players/player_mac_indy3.cpp
@@ -20,7 +20,8 @@
  */
 
 
-#include "scumm/players/player_mac_indy3.h"
+#include "scumm/players/player_mac_new.h"
+#include "scumm/players/player_mac_intern.h"
 #include "scumm/resource.h"
 #include "scumm/scumm.h"
 
@@ -31,142 +32,14 @@
 namespace Scumm {
 
 #define ASC_DEVICE_RATE		0x56EE8BA3
-#define VBL_UPDATE_RATE		0x003C25BD
 #define PCM_BUFFER_SIZE		1024
-#define PCM_BUFFER_RESERVE	64
-#define RATECNV_BIT_PRECSN	24
 
 extern const uint8 *g_pv2ModTbl;
 extern const uint32 g_pv2ModTblSize;
 
-class I3MPlayer;
-class AudioStream_I3M : public Audio::AudioStream {
+class LegacyMusicDriver : public MacSoundDriver {
 public:
-	AudioStream_I3M(I3MPlayer *drv, uint32 scummVMOutputrate, bool stereo, bool interpolate, bool internal16Bit);
-	~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 byteSize, 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), interpolate(false) {}
-		int8 *start;
-		int8 *pos;
-		const int8 *end;
-		uint32 volume;
-		int32 lastL;
-		int32 lastR;
-		uint32 size;
-		uint32 rateConvInt;
-		uint32 rateConvFrac;
-		int32 rateConvAcc;
-		bool interpolate;
-	} _buffers[2];
-
-	const uint32 _outputRate;
-	const uint8 _frameSize;
-	const bool _interp;
-	const int _smpInternalSize;
-	const int _volDown;
-
-	const bool _isStereo;
-};
-
-class I3MSoundDriver {
-public:
-	I3MSoundDriver(Common::Mutex &mutex, uint32 deviceRate, bool canInterpolate, bool internal16Bit) : _mutex(mutex), _caps(deviceRate, canInterpolate),
-		_smpSize(internal16Bit ? 2 : 1), _smpMin(internal16Bit ? -32768 : -128), _smpMax(internal16Bit ? 32767 : 127), _status(0) {}
-	virtual ~I3MSoundDriver() {}
-	virtual void feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) = 0;
-
-	struct Caps {
-		Caps(uint32 rate, bool interp) :deviceRate(rate), allowInterPolation(interp) {}
-		const uint32 deviceRate;
-		const bool allowInterPolation;
-	};
-	const Caps &getCaps() const { return _caps; }
-
-	enum StatusFlag : uint8 {
-		kStatusPlaying =		1	<<		0,
-		kStatusOverflow =		1	<<		1,
-		kStatusStartup =		1	<<		2,
-		kStatusDone =			1	<<		3
-	};
-	uint8 getStatus() const { return _status; }
-	void clearFlags(uint8 flags) { _status &= ~flags; }
-
-protected:
-	void setFlags(uint8 flags) { _status |= flags; }
-
-	Common::Mutex &_mutex;
-	const int _smpSize;
-	const int16 _smpMin;
-	const int16 _smpMax;
-	const Caps _caps;
-	uint8 _status;
-};
-
-class I3MLowLevelPCMDriver final : public I3MSoundDriver {
-public:
-	struct PCMSound {
-		PCMSound() : len(0), rate(0), loopst(0), loopend(0), baseFreq(0), stereo(false) {}
-		Common::SharedPtr<const byte> data;
-		uint32 len;
-		uint32 rate;
-		uint32 loopst;
-		uint32 loopend;
-		byte baseFreq;
-		bool stereo;
-	};
-public:
-	I3MLowLevelPCMDriver(Common::Mutex &mutex, uint32 deviceRate, bool enableInterpolation, bool internal16Bit);
-	void feed(int8 *dst, uint32 byteSize, 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 bool _interp;
-	int8 _lastSmp[2];
-	uint32 _len;
-	uint16 _rmH;
-	uint16 _rmL;
-	uint32 _loopSt;
-	uint32 _loopEnd;
-	byte _baseFreq;
-	uint32 _rcPos;
-	uint32 _smpWtAcc;
-	uint16 _frameSize;
-};
-
-class I3MMusicDriver : public I3MSoundDriver {
-public:
-	I3MMusicDriver(uint16 numChannels, Common::Mutex &mutex, bool canInterpolate, bool internal16Bit) : I3MSoundDriver(mutex, ASC_DEVICE_RATE, canInterpolate, internal16Bit), _numChan(numChannels) {}
+	LegacyMusicDriver(uint16 numChannels, Common::Mutex &mutex, bool canInterpolate, bool internal16Bit) : MacSoundDriver(mutex, ASC_DEVICE_RATE, canInterpolate, internal16Bit), _numChan(numChannels) {}
 	virtual void start() = 0;
 	virtual void stop() = 0;
 
@@ -200,10 +73,10 @@ protected:
 	const uint16 _numChan;
 };
 
-class I3MFourToneSynthDriver final : public I3MMusicDriver {
+class FourToneSynthDriver final : public LegacyMusicDriver {
 public:
-	I3MFourToneSynthDriver(Common::Mutex &mutex, bool internal16bit);
-	~I3MFourToneSynthDriver() override;
+	FourToneSynthDriver(Common::Mutex &mutex, bool internal16bit);
+	~FourToneSynthDriver() override;
 
 	void feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) override;
 	void start() override;
@@ -228,10 +101,10 @@ private:
 	Channel *_chan;
 };
 
-class I3MSquareWaveSynthDriver final : public I3MMusicDriver {
+class SquareWaveSynthDriver final : public LegacyMusicDriver {
 public:
-	I3MSquareWaveSynthDriver(Common::Mutex &mutex, bool internal16Bit);
-	~I3MSquareWaveSynthDriver() override {};
+	SquareWaveSynthDriver(Common::Mutex &mutex, bool internal16Bit);
+	~SquareWaveSynthDriver() override {};
 
 	void feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) override;
 	void start() override;
@@ -250,7 +123,7 @@ private:
 		uint16 duration;
 	};
 
-	Common::Array<I3MSquareWaveSynthDriver::Triplet> _tripletsQueue;
+	Common::Array<SquareWaveSynthDriver::Triplet> _tripletsQueue;
 	Triplet _lastPara;
 	uint16 _pos;
 	uint32 _count;
@@ -259,590 +132,18 @@ private:
 	uint32 _phase;
 };
 
-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, bool stereo, bool internal16Bit);
-
-	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 saveLoadWithSerializer(Common::Serializer &ser);
-	void restoreAfterLoad();
-
-	void generateData(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) const;
-	void nextTick();
-
-	const I3MSoundDriver::Caps &getDriverCaps(uint8 drvID) const;
-
-private:
-	void startSong(int id);
-	void startSoundEffect(int id);
-	void stopSong();
-	void stopSoundEffect();
-	void stopActiveSound();
-	void finishSong();
-	void updateSong();
-	void updateSoundEffect();
-
-	void checkRestartSoundEffects();
-	void endOfTrack();
-
-	bool isSong(int id) const;
-	bool isHiQuality() const;
-
-	int _curSound;
-	int _curSong;
-	int _lastSoundEffectPrio;
-	int _soundEffectNumLoops;
-	int _songTimer;
-	bool _songUnfinished;
-	uint _activeChanCount;
-	byte _songTimerInternal;
-	byte *_soundUsage;
-
-	bool _soundEffectPlaying;
-	int _qmode;
-	bool _16bit;
-	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 _envSust;
-		int16 _transpose;
-		uint16 _envAtt;
-		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;
-	const int _numMusicTracks;
-
-	static const uint8 _fourToneSynthWaveForm[256];
-
-public:
-	MusicChannel *getMusicChannel(uint8 id) const;
-};
-
-AudioStream_I3M::AudioStream_I3M(I3MPlayer *drv, uint32 scummVMOutputrate, bool stereo, bool interpolate, bool internal16Bit) : Audio::AudioStream(), _drv(drv),
-	_vblSmpQty(0), _vblSmpQtyRem(0), _frameSize((stereo ? 2 : 1) * (internal16Bit ? 2 : 1)), _vblCountDown(0), _vblCountDownRem(0), _outputRate(scummVMOutputrate),
-		_vblCbProc(nullptr), _isStereo(stereo), _interp(interpolate), _smpInternalSize(internal16Bit ? 2 : 1), _volDown(internal16Bit ? 2 : 0) {
-	assert(_drv);
-	_vblSmpQty = (_outputRate << 16) / VBL_UPDATE_RATE;
-	_vblSmpQtyRem = (_outputRate << 16) % VBL_UPDATE_RATE;
-	_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 / _frameSize;
-		_buffers[i].start = new int8[_buffers[i].size + PCM_BUFFER_RESERVE];
-		_buffers[i].end = &_buffers[i].start[_buffers[i].size];
-	}
-	clearBuffer();
-}
-
-void AudioStream_I3M::initDrivers() {
-	for (int i = 0; i < 2; ++i) {
-		if (!_drv)
-			error("AudioStream_I3M::initDrivers(): Failed to query device rate for device %d", i);
-		uint64 irt = (uint64)_drv->getDriverCaps(i).deviceRate * (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;
-		_buffers[i].interpolate = _interp && _drv->getDriverCaps(i).allowInterPolation;
-	}
-}
-
-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 + PCM_BUFFER_RESERVE);
-		_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) {
-			_vblCountDown = _vblSmpQty;
-			_vblCountDownRem += _vblSmpQtyRem;
-			while (_vblCountDownRem >= (_vblSmpQty << 16)) {
-				_vblCountDownRem -= (_vblSmpQty << 16);
-				++_vblCountDown;
-			}
-			runVblTask();
-		}
-
-		int32 smpL = 0;
-		int32 smpR = 0;
-		for (int ii = 0; ii < 2; ++ii) {
-			int smpN = _smpInternalSize == 2 ? *reinterpret_cast<int16*>(_buffers[ii].pos) : _buffers[ii].pos[0];
-			int diff = smpN - _buffers[ii].lastL;
-			if (diff && _buffers[ii].rateConvAcc && _buffers[ii].interpolate)
-				diff = (diff * _buffers[ii].rateConvAcc) >> RATECNV_BIT_PRECSN;
-			smpL += (int32)((_buffers[ii].lastL + diff) * (_buffers[ii].volume >> _volDown));
-		}
-		if (_isStereo) {
-			for (int ii = 0; ii < 2; ++ii) {
-				int smpN = _smpInternalSize == 2 ? *reinterpret_cast<int16*>(&_buffers[ii].pos[2]) : _buffers[ii].pos[1];
-				int diff = smpN - _buffers[ii].lastR;
-				if (diff && _buffers[ii].rateConvAcc && _buffers[ii].interpolate)
-					diff = (diff * _buffers[ii].rateConvAcc) >> RATECNV_BIT_PRECSN;
-				smpR += (int32)((_buffers[ii].lastR + diff) * (_buffers[ii].volume >> _volDown));
-			}
-		}
-
-		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;
-				const int8 *lpos = _buffers[ii].pos;
-				if (lpos >= _buffers[ii].start + _frameSize)
-					lpos -= _frameSize;
-
-				if (_smpInternalSize == 2) {
-					_buffers[ii].lastL = *reinterpret_cast<const int16*>(lpos);
-					if (_isStereo)
-						_buffers[ii].lastR = *reinterpret_cast<const int16*>(&lpos[2]);
-				} else {
-					_buffers[ii].lastL = lpos[0];
-					if (_isStereo)
-						_buffers[ii].lastR = lpos[1];
-				}
-
-				if (_buffers[ii].pos >= _buffers[ii].end) {
-					int refreshSize = MIN<int>(_vblCountDown * _frameSize, _buffers[ii].size);
-					_buffers[ii].pos -= refreshSize;
-					assert(_buffers[ii].pos + refreshSize < _buffers[ii].end + PCM_BUFFER_RESERVE);
-					generateData(_buffers[ii].pos, refreshSize, stype[ii], _isStereo);
-				}
-			}
-		}
-
-		*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 byteSize, Audio::Mixer::SoundType type, bool expectStereo) const {
-	if (_drv)
-		_drv->generateData(dst, byteSize, type, expectStereo);
-}
-
-void AudioStream_I3M::runVblTask() {
-	if (_vblCbProc && _vblCbProc->isValid())
-		(*_vblCbProc)();
-}
-
-I3MLowLevelPCMDriver::I3MLowLevelPCMDriver(Common::Mutex &mutex, uint32 deviceRate, bool enableInterpolation, bool internal16Bit) :
-	I3MSoundDriver(mutex, deviceRate, true, internal16Bit), _interp(enableInterpolation), _frameSize(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 byteSize, Audio::Mixer::SoundType type, bool expectStereo) {
-	if (dst == nullptr || type != Audio::Mixer::kSFXSoundType)
-		return;
-
-	memset(dst, 0, byteSize);
-
-	if (_data == nullptr)
-		return;
-
-	int32 diff = 0;
-	uint16 destFrameSize = expectStereo ? 2 : 1;
-	bool interp = (_interp && _rmL);
-
-	for (const int8 *end = &dst[byteSize]; dst < end; ) {
-		int8 in = 0;
-		for (int i = 0; i < destFrameSize; ++i) {
-			if (i < _frameSize) {
-				in = _data[_rcPos + i];
-				if (interp && in != _lastSmp[i]) {
-					diff = in - _lastSmp[i];
-					diff = (diff * (_smpWtAcc >> 1)) >> 15;
-					in = (_lastSmp[i] + diff) & 0xff;
-				}
-			}
-			if (_smpSize == 2)
-				*reinterpret_cast<int16*>(dst) = in << 2;
-			else
-				*dst = in;
-			dst += _smpSize;
-		}
-		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;
-			}
-			setFlags(kStatusDone);
-		}
-	}
-}
-
-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(_caps.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;
-
-	_frameSize = snd->stereo ? 2 : 1;
-
-	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];
-	clearFlags(kStatusDone);
-}
-
-void I3MLowLevelPCMDriver::stop() {
-	Common::StackLock lock(_mutex);
-	_data = nullptr;
-	_res.reset();
-	setFlags(kStatusDone);
-}
-
-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;
-	if (sh) {
-		factor <<= sh;
-		dataRate = ((dataRate >> (32 - sh)) | (dataRate << sh));
-	}
-
-	dataRate ^= 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 & 0xffff) * (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 internal16Bit) : I3MMusicDriver(4, mutex, false, internal16Bit), _duration(0), _pos(0), _chan(nullptr) {
+FourToneSynthDriver::FourToneSynthDriver(Common::Mutex &mutex, bool internal16Bit) : LegacyMusicDriver(4, mutex, false, internal16Bit), _duration(0), _pos(0), _chan(nullptr) {
 	_chan = new Channel[_numChan];
 }
 
-I3MFourToneSynthDriver::~I3MFourToneSynthDriver() {
+FourToneSynthDriver::~FourToneSynthDriver() {
 	Common::StackLock lock(_mutex);
 	for (int i = 0; i < _numChan; ++i)
 		setWaveForm(i, 0, 0);
 	delete[] _chan;
 }
 
-void I3MFourToneSynthDriver::feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) {
+void FourToneSynthDriver::feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) {
 	if (dst == nullptr || type != Audio::Mixer::kMusicSoundType)
 		return;
 
@@ -872,13 +173,13 @@ void I3MFourToneSynthDriver::feed(int8 *dst, uint32 byteSize, Audio::Mixer::Soun
 		memset(dst, 0, end - dst);
 }
 
-void I3MFourToneSynthDriver::start() {
+void FourToneSynthDriver::start() {
 	Common::StackLock lock(_mutex);
 	stop();
 	setDuration(50);
 }
 
-void I3MFourToneSynthDriver::stop() {
+void FourToneSynthDriver::stop() {
 	Common::StackLock lock(_mutex);
 	for (int i = 0; i < _numChan; ++i) {
 		_chan[i].phase = 0;
@@ -887,7 +188,7 @@ void I3MFourToneSynthDriver::stop() {
 	setDuration(0);
 }
 
-void I3MFourToneSynthDriver::send(int dataType, ...)  {
+void FourToneSynthDriver::send(int dataType, ...)  {
 	Common::StackLock lock(_mutex);
 	va_list arg;
 	va_start(arg, dataType);
@@ -913,7 +214,7 @@ void I3MFourToneSynthDriver::send(int dataType, ...)  {
 	va_end(arg);
 }
 
-void I3MFourToneSynthDriver::setWaveForm(uint8 chan, const uint8 *data, uint32 dataSize) {
+void FourToneSynthDriver::setWaveForm(uint8 chan, const uint8 *data, uint32 dataSize) {
 	assert(chan < _numChan);
 	delete[] _chan[chan].waveform;
 	_chan[chan].waveform = nullptr;
@@ -927,23 +228,23 @@ void I3MFourToneSynthDriver::setWaveForm(uint8 chan, const uint8 *data, uint32 d
 	_chan[chan].waveform = wf;
 }
 
-void I3MFourToneSynthDriver::setDuration(uint16 duration) {
+void FourToneSynthDriver::setDuration(uint16 duration) {
 	_duration = duration;
 	_pos = 0;
 	clearFlags(kStatusDone);
 }
 
-void I3MFourToneSynthDriver::setRate(uint8 chan, uint16 rate) {
+void FourToneSynthDriver::setRate(uint8 chan, uint16 rate) {
 	assert(chan < _numChan);
 	_chan[chan].rate = rate ? (0x5060000 / (rate >> ((rate < 1600) ? 4 : 6))) : 0;
 }
 
-I3MSquareWaveSynthDriver::I3MSquareWaveSynthDriver(Common::Mutex &mutex, bool internal16Bit) :
-	I3MMusicDriver(1, mutex, false, internal16Bit), _count(0xffff), _duration(0), _amplitude(0), _phase(0), _pos(0) {
+SquareWaveSynthDriver::SquareWaveSynthDriver(Common::Mutex &mutex, bool internal16Bit) :
+	LegacyMusicDriver(1, mutex, false, internal16Bit), _count(0xffff), _duration(0), _amplitude(0), _phase(0), _pos(0) {
 
 }
 
-void I3MSquareWaveSynthDriver::feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) {
+void SquareWaveSynthDriver::feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) {
 	if (dst == nullptr || type != Audio::Mixer::kMusicSoundType)
 		return;
 
@@ -975,13 +276,13 @@ void I3MSquareWaveSynthDriver::feed(int8 *dst, uint32 byteSize, Audio::Mixer::So
 		memset(dst, 0, end - dst);
 }
 
-void I3MSquareWaveSynthDriver::start() {
+void SquareWaveSynthDriver::start() {
 	Common::StackLock lock(_mutex);
 	stop();
 	setFlags(kStatusPlaying | kStatusStartup);
 }
 
-void I3MSquareWaveSynthDriver::stop() {
+void SquareWaveSynthDriver::stop() {
 	Common::StackLock lock(_mutex);
 	_lastPara = Triplet();
 	_count = 0xffff;
@@ -991,7 +292,7 @@ void I3MSquareWaveSynthDriver::stop() {
 	setFlags(kStatusDone);
 }
 
-void I3MSquareWaveSynthDriver::send(int dataType, ...)  {
+void SquareWaveSynthDriver::send(int dataType, ...)  {
 	Common::StackLock lock(_mutex);
 	va_list arg;
 	va_start(arg, dataType);
@@ -1008,7 +309,7 @@ void I3MSquareWaveSynthDriver::send(int dataType, ...)  {
 	va_end(arg);
 }
 
-void I3MSquareWaveSynthDriver::addTriplet(uint16 frequency, uint16 amplitude) {
+void SquareWaveSynthDriver::addTriplet(uint16 frequency, uint16 amplitude) {
 	if ((_status & kStatusStartup) && frequency < 3)
 		return;
 
@@ -1032,11 +333,11 @@ void I3MSquareWaveSynthDriver::addTriplet(uint16 frequency, uint16 amplitude) {
 		setFlags(kStatusOverflow);
 }
 
-Common::WeakPtr<I3MPlayer> *I3MPlayer::_inst = nullptr;
+Common::WeakPtr<Indy3MacSnd> *Indy3MacSnd::_inst = nullptr;
 
-I3MPlayer::I3MPlayer(ScummEngine *vm, Audio::Mixer *mixer) :
+Indy3MacSnd::Indy3MacSnd(ScummEngine *vm, Audio::Mixer *mixer) : VblTaskClientDriver(),
 	_vm(vm), _mixer(mixer), _musicChannels(nullptr), _curSound(0), _curSong(0), _lastSoundEffectPrio(0), _idRangeMax(86), _soundEffectNumLoops(-1),
-	_musicIDTable(nullptr), _macstr(nullptr), _musicIDTableLen(0), _soundUsage(0), _mdrv(nullptr), _sdrv(nullptr), _nextTickProc(this, &I3MPlayer::nextTick),
+	_musicIDTable(nullptr), _macstr(nullptr), _musicIDTableLen(0), _soundUsage(0), _mdrv(nullptr), _sdrv(nullptr), _nextTickProc(this, &VblTaskClientDriver::callback),
 	_soundEffectPlaying(false), _songTimer(0), _songTimerInternal(0), _qmode(0), _16bit(false), _qualHi(false),	_mixerThread(false), _activeChanCount(0),
 	_songUnfinished(false), _numMusicChannels(8), _numMusicTracks(4) {
 	assert(_vm);
@@ -1056,12 +357,12 @@ I3MPlayer::I3MPlayer(ScummEngine *vm, Audio::Mixer *mixer) :
 		_musicChannels[i] = new MusicChannel(this);
 }
 
-I3MPlayer::~I3MPlayer() {
+Indy3MacSnd::~Indy3MacSnd() {
 	_mixer->stopHandle(_soundHandle);
 	delete _macstr;
 	delete[] _soundUsage;
 
-	for (Common::Array<I3MSoundDriver*>::const_iterator i = _drivers.begin(); i != _drivers.end(); ++i)
+	for (Common::Array<MacSoundDriver*>::const_iterator i = _drivers.begin(); i != _drivers.end(); ++i)
 		delete *i;
 	_drivers.clear();
 
@@ -1075,33 +376,33 @@ I3MPlayer::~I3MPlayer() {
 	_inst = nullptr;
 }
 
-Common::SharedPtr<I3MPlayer> I3MPlayer::open(ScummEngine *vm, Audio::Mixer *mixer) {
-	Common::SharedPtr<I3MPlayer> scp = nullptr;
+Common::SharedPtr<Indy3MacSnd> Indy3MacSnd::open(ScummEngine *vm, Audio::Mixer *mixer) {
+	Common::SharedPtr<Indy3MacSnd> scp = nullptr;
 
 	if (_inst == nullptr) {
-		scp = Common::SharedPtr<I3MPlayer>(new I3MPlayer(vm, mixer));
-		_inst = new Common::WeakPtr<I3MPlayer>(scp);
+		scp = Common::SharedPtr<Indy3MacSnd>(new Indy3MacSnd(vm, mixer));
+		_inst = new Common::WeakPtr<Indy3MacSnd>(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, false, true)))
-			error("I3MPlayer::open(): Failed to start player");
+			error("Indy3MacSnd::open(): Failed to start player");
 	}
 
 	return _inst->lock();
 }
 
-bool I3MPlayer::startDevices(uint32 outputRate, uint32 pcmDeviceRate, uint32 feedBufferSize, bool enableInterpolation, bool stereo, bool internal16Bit) {
-	_macstr = new AudioStream_I3M(this, outputRate, stereo, enableInterpolation, internal16Bit);
+bool Indy3MacSnd::startDevices(uint32 outputRate, uint32 pcmDeviceRate, uint32 feedBufferSize, bool enableInterpolation, bool stereo, bool internal16Bit) {
+	_macstr = new MacPlayerAudioStream(this, outputRate, stereo, enableInterpolation, internal16Bit);
 	if (!_macstr || !_mixer)
 		return false;
 
-	_sdrv = new I3MLowLevelPCMDriver(_mixer->mutex(), pcmDeviceRate, enableInterpolation, internal16Bit);
-	I3MFourToneSynthDriver *mdrv = new I3MFourToneSynthDriver(_mixer->mutex(), internal16Bit);
+	_sdrv = new MacLowLevelPCMDriver(_mixer->mutex(), pcmDeviceRate, enableInterpolation, internal16Bit);
+	FourToneSynthDriver *mdrv = new FourToneSynthDriver(_mixer->mutex(), internal16Bit);
 	if (!mdrv || !_sdrv)
 		return false;
 
 	for (int i = 0; i < mdrv->numChannels(); ++i)
-		mdrv->send(I3MMusicDriver::kChanWaveform, i, _fourToneSynthWaveForm, sizeof(_fourToneSynthWaveForm));
+		mdrv->send(LegacyMusicDriver::kChanWaveform, i, _fourToneSynthWaveForm, sizeof(_fourToneSynthWaveForm));
 	_qualHi = true;
 	_16bit = internal16Bit;
 	_mdrv = mdrv;
@@ -1118,19 +419,19 @@ bool I3MPlayer::startDevices(uint32 outputRate, uint32 pcmDeviceRate, uint32 fee
 	return true;
 }
 
-void I3MPlayer::setMusicVolume(int vol) {
+void Indy3MacSnd::setMusicVolume(int vol) {
 	Common::StackLock lock(_mixer->mutex());
 	if (_macstr)
 		_macstr->setMasterVolume(Audio::Mixer::kMusicSoundType, vol);
 }
 
-void I3MPlayer::setSfxVolume(int vol) {
+void Indy3MacSnd::setSfxVolume(int vol) {
 	Common::StackLock lock(_mixer->mutex());
 	if (_macstr)
 		_macstr->setMasterVolume(Audio::Mixer::kSFXSoundType, vol);
 }
 
-void I3MPlayer::startSound(int id) {
+void Indy3MacSnd::startSound(int id) {
 	if (id < 0 || id >= _idRangeMax)
 		return;
 
@@ -1140,9 +441,9 @@ void I3MPlayer::startSound(int id) {
 		startSoundEffect(id);
 }
 
-void I3MPlayer::stopSound(int id) {
+void Indy3MacSnd::stopSound(int id) {
 	if (id < 0 || id >= _idRangeMax) {
-		warning("I3MPlayer::stopSound(): sound id '%d' out of range (0 - 85)", id);
+		warning("Indy3MacSnd::stopSound(): sound id '%d' out of range (0 - 85)", id);
 		return;
 	}
 
@@ -1153,28 +454,28 @@ void I3MPlayer::stopSound(int id) {
 		stopActiveSound();
 }
 
-void I3MPlayer::stopAllSounds() {
+void Indy3MacSnd::stopAllSounds() {
 	Common::StackLock lock(_mixer->mutex());
 	memset(_soundUsage, 0, _idRangeMax);
 	stopActiveSound();
 }
 
-int I3MPlayer::getMusicTimer() {
+int Indy3MacSnd::getMusicTimer() {
 	Common::StackLock lock(_mixer->mutex());
 	return _songTimer;
 }
 
-int I3MPlayer::getSoundStatus(int id) const {
+int Indy3MacSnd::getSoundStatus(int id) const {
 	if (id < 0 || id >= _idRangeMax) {
-		warning("I3MPlayer::getSoundStatus(): sound id '%d' out of range (0 - 85)", id);
+		warning("Indy3MacSnd::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);
+void Indy3MacSnd::setQuality(int qual) {
+	assert(qual >= MacSound::kQualityAuto && qual <= MacSound::kQualityLow);
 	while (_qualHi == isHiQuality()) {
 		if (_qmode == qual)
 			return;
@@ -1182,20 +483,20 @@ void I3MPlayer::setQuality(int qual) {
 	}
 
 	Common::StackLock lock(_mixer->mutex());
-	Common::Array<I3MSoundDriver*>::iterator dr = Common::find(_drivers.begin(), _drivers.end(), _mdrv);
+	Common::Array<MacSoundDriver*>::iterator dr = Common::find(_drivers.begin(), _drivers.end(), _mdrv);
 	delete _mdrv;
 	_mdrv = nullptr;
 	_qmode = qual;
 
 	if (isHiQuality()) {
-		I3MFourToneSynthDriver *mdrv = new I3MFourToneSynthDriver(_mixer->mutex(), _16bit);
+		FourToneSynthDriver *mdrv = new FourToneSynthDriver(_mixer->mutex(), _16bit);
 		assert(mdrv);
 		for (int i = 0; i < mdrv->numChannels(); ++i)
-			mdrv->send(I3MMusicDriver::kChanWaveform, i, _fourToneSynthWaveForm, sizeof(_fourToneSynthWaveForm));
+			mdrv->send(LegacyMusicDriver::kChanWaveform, i, _fourToneSynthWaveForm, sizeof(_fourToneSynthWaveForm));
 		_mdrv = mdrv;
 		_qualHi = true;
 	} else {
-		_mdrv = new I3MSquareWaveSynthDriver(_mixer->mutex(), _16bit);
+		_mdrv = new SquareWaveSynthDriver(_mixer->mutex(), _16bit);
 		_qualHi = false;
 		assert(_mdrv);
 	}
@@ -1205,17 +506,17 @@ void I3MPlayer::setQuality(int qual) {
 	else if (_drivers.empty())
 		_drivers.push_back(_mdrv);
 	else
-		error("I3MPlayer::setQuality(): Invalid usage");
+		error("Indy3MacSnd::setQuality(): Invalid usage");
 
 	assert(_macstr);
 	_macstr->initDrivers();
 }
 
-void I3MPlayer::saveLoadWithSerializer(Common::Serializer &ser) {
+void Indy3MacSnd::saveLoadWithSerializer(Common::Serializer &ser) {
 	ser.syncBytes(_soundUsage, _idRangeMax, VER(113));
 }
 
-void I3MPlayer::restoreAfterLoad() {
+void Indy3MacSnd::restoreAfterLoad() {
 	stopActiveSound();
 	for (int i = 0; i < _idRangeMax; ++i) {
 		if (_soundUsage[i] && isSong(i)) {
@@ -1225,12 +526,7 @@ void I3MPlayer::restoreAfterLoad() {
 	}
 }
 
-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);
-}
-
-void I3MPlayer::nextTick() {
+void Indy3MacSnd::nextTick() {
 	if (_songTimerInternal++ == 29) {
 		_songTimerInternal = 0;
 		++_songTimer;
@@ -1238,25 +534,30 @@ void I3MPlayer::nextTick() {
 
 	_mixerThread = true;
 
-	if (!_curSong && (_sdrv->getStatus() & I3MSoundDriver::kStatusDone))
+	if (!_curSong && (_sdrv->getStatus() & MacSoundDriver::kStatusDone))
 		updateSoundEffect();
 	else if (_curSong)
 		updateSong();
-	else if (_songUnfinished && (_mdrv->getStatus() & I3MSoundDriver::kStatusDone))
+	else if (_songUnfinished && (_mdrv->getStatus() & MacSoundDriver::kStatusDone))
 		stopSong();
 
 	_mixerThread = false;
 }
 
-const I3MSoundDriver::Caps &I3MPlayer::getDriverCaps(uint8 drvID) const {
+void Indy3MacSnd::generateData(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) const {
+	for (Common::Array<MacSoundDriver*>::const_iterator i = _drivers.begin(); i != _drivers.end(); ++i)
+		(*i)->feed(dst, len, type, expectStereo);
+}
+
+const MacSoundDriver::Caps &Indy3MacSnd::getDriverCaps(uint8 drvID) const {
 	if (drvID >= _drivers.size())
-		error(" I3MPlayer::getDriverCaps(): Invalid driver id %d", drvID);
+		error(" Indy3MacSnd::getDriverCaps(): Invalid driver id %d", drvID);
 	return _drivers[drvID]->getCaps();
 }
 
-void I3MPlayer::startSong(int id) {
+void Indy3MacSnd::startSong(int id) {
 	if (_mdrv == nullptr || id < 0 || id >= _idRangeMax) {
-		warning("I3MPlayer::startSong(): sound id '%d' out of range (0 - 85)", id);
+		warning("Indy3MacSnd::startSong(): sound id '%d' out of range (0 - 85)", id);
 		return;
 	}
 
@@ -1302,9 +603,9 @@ void I3MPlayer::startSong(int id) {
 	}
 }
 
-void I3MPlayer::startSoundEffect(int id) {
+void Indy3MacSnd::startSoundEffect(int id) {
 	if (_sdrv == nullptr || id < 0 || id >= _idRangeMax) {
-		warning("I3MPlayer::startSoundEffect(): sound id '%d' out of range (0 - 85)", id);
+		warning("Indy3MacSnd::startSoundEffect(): sound id '%d' out of range (0 - 85)", id);
 		return;
 	}
 
@@ -1314,7 +615,7 @@ void I3MPlayer::startSoundEffect(int id) {
 	assert(ptr);
 
 	if (READ_LE_UINT16(ptr) < 28) {
-		warning("I3MPlayer::startSoundEffect(%d): invalid resource", id);
+		warning("Indy3MacSnd::startSoundEffect(%d): invalid resource", id);
 		return;
 	}
 
@@ -1361,14 +662,14 @@ void I3MPlayer::startSoundEffect(int id) {
 	_soundUsage[id]++;
 }
 
-void I3MPlayer::stopSong() {
+void Indy3MacSnd::stopSong() {
 	Common::StackLock lock(_mixer->mutex());
 	_mdrv->stop();
 	finishSong();
 	_curSound = 0;
 }
 
-void I3MPlayer::stopSoundEffect() {
+void Indy3MacSnd::stopSoundEffect() {
 	Common::StackLock lock(_mixer->mutex());
 	_sdrv->stop();
 	_soundEffectPlaying = false;
@@ -1376,7 +677,7 @@ void I3MPlayer::stopSoundEffect() {
 	_curSound = 0;
 }
 
-void I3MPlayer::stopActiveSound() {
+void Indy3MacSnd::stopActiveSound() {
 	if (_soundEffectPlaying)
 		stopSoundEffect();
 	else if (_curSong || _songUnfinished)
@@ -1384,17 +685,17 @@ void I3MPlayer::stopActiveSound() {
 	_songUnfinished = false;
 }
 
-void I3MPlayer::finishSong() {
+void Indy3MacSnd::finishSong() {
 	if (_soundUsage[_curSong])
 		--_soundUsage[_curSong];
 	_curSong = 0;
-	_songUnfinished = !(_mdrv->getStatus() & I3MSoundDriver::kStatusDone);
+	_songUnfinished = !(_mdrv->getStatus() & MacSoundDriver::kStatusDone);
 }
 
-void I3MPlayer::updateSong() {
-	if (_curSong && (_qualHi || (_mdrv->getStatus() & I3MSoundDriver::kStatusDone))) {
-		_mdrv->clearFlags(I3MSoundDriver::kStatusOverflow);
-		while (_curSong && !(_mdrv->getStatus() & I3MSoundDriver::kStatusOverflow)) {
+void Indy3MacSnd::updateSong() {
+	if (_curSong && (_qualHi || (_mdrv->getStatus() & MacSoundDriver::kStatusDone))) {
+		_mdrv->clearFlags(MacSoundDriver::kStatusOverflow);
+		while (_curSong && !(_mdrv->getStatus() & MacSoundDriver::kStatusOverflow)) {
 			for (int i = 4; i; --i) {
 				for (int ii = 0; ii < _numMusicTracks && _curSong; ++ii)
 					_musicChannels[ii]->nextTick();
@@ -1402,23 +703,23 @@ void I3MPlayer::updateSong() {
 
 			if (_qualHi) {
 				for (int i = 0; i < _mdrv->numChannels(); ++i)
-					_mdrv->send(I3MMusicDriver::kChanRate, i, _curSong ? _musicChannels[i]->checkPeriod() : 0);
+					_mdrv->send(LegacyMusicDriver::kChanRate, i, _curSong ? _musicChannels[i]->checkPeriod() : 0);
 				if (_curSong)
-					_mdrv->send(I3MMusicDriver::kDuration, 10);
+					_mdrv->send(LegacyMusicDriver::kDuration, 10);
 			} else {
 				MusicChannel *ch = nullptr;
 				for (int i = 0; i < _numMusicTracks && ch == nullptr && _curSong; ++i) {
 					if (_musicChannels[i]->checkPeriod())
 						ch = _musicChannels[i];
 				}
-				_mdrv->send(I3MMusicDriver::kSquareWaveTriplet, ch ? ch->checkPeriod() : 0, 0xff);
+				_mdrv->send(LegacyMusicDriver::kSquareWaveTriplet, ch ? ch->checkPeriod() : 0, 0xff);
 			}
 		}
 	}
 }
 
-void I3MPlayer::updateSoundEffect() {
-	_sdrv->clearFlags(I3MSoundDriver::kStatusDone);
+void Indy3MacSnd::updateSoundEffect() {
+	_sdrv->clearFlags(MacSoundDriver::kStatusDone);
 	bool chkRestart = false;
 
 	if (!_soundEffectPlaying || !_curSound) {
@@ -1440,7 +741,7 @@ void I3MPlayer::updateSoundEffect() {
 	}
 }
 
-void I3MPlayer::checkRestartSoundEffects() {
+void Indy3MacSnd::checkRestartSoundEffects() {
 	for (int i = 1; i < _idRangeMax; ++i) {
 		if (!_soundUsage[i] || isSong(i))
 			continue;
@@ -1455,7 +756,7 @@ void I3MPlayer::checkRestartSoundEffects() {
 	}
 }
 
-const uint8 I3MPlayer::_fourToneSynthWaveForm[256] = {
+const uint8 Indy3MacSnd::_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,
@@ -1474,37 +775,37 @@ const uint8 I3MPlayer::_fourToneSynthWaveForm[256] = {
 	0xcc, 0xcb, 0xc8, 0xc5, 0xc2, 0xbe, 0xb9, 0xb4, 0xae, 0xa9, 0xa3, 0x9d, 0x97, 0x92, 0x8c, 0x86
 };
 
-void I3MPlayer::endOfTrack() {
+void Indy3MacSnd::endOfTrack() {
 	if (!_activeChanCount || !--_activeChanCount)
 		finishSong();
 }
 
-bool I3MPlayer::isSong(int id) const {
+bool Indy3MacSnd::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);
+bool Indy3MacSnd::isHiQuality() const {
+	return _mixerThread ? _qualHi : (_qmode == MacSound::kQualityAuto && (_vm->VAR_SOUNDCARD == 0xff || _vm->VAR(_vm->VAR_SOUNDCARD) == 11)) || (_qmode == MacSound::kQualityHigh);
 }
 
-I3MPlayer::MusicChannel *I3MPlayer::getMusicChannel(uint8 id) const {
+Indy3MacSnd::MusicChannel *Indy3MacSnd::getMusicChannel(uint8 id) const {
 	return (id < _numMusicChannels) ? _musicChannels[id] : 0;
 }
 
 uint16 savedOffset = 0;
-I3MPlayer::MusicChannel *I3MPlayer::MusicChannel::_ctrlChan = nullptr;
+Indy3MacSnd::MusicChannel *Indy3MacSnd::MusicChannel::_ctrlChan = nullptr;
 
-I3MPlayer::MusicChannel::MusicChannel(I3MPlayer *pl) : _player(pl), _vars(nullptr), _numVars(0), _ctrlProc(nullptr),
+Indy3MacSnd::MusicChannel::MusicChannel(Indy3MacSnd *pl) : _player(pl), _vars(nullptr), _numVars(0), _ctrlProc(nullptr),
 _resSize(0), _savedOffset(savedOffset), _modShapes(g_pv2ModTbl), _modShapesTableSize(g_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
+		&Indy3MacSnd::MusicChannel::ctrl_setShape,
+		&Indy3MacSnd::MusicChannel::ctrl_modPara,
+		&Indy3MacSnd::MusicChannel::ctrl_init,
+		&Indy3MacSnd::MusicChannel::ctrl_returnFromSubroutine,
+		&Indy3MacSnd::MusicChannel::ctrl_jumpToSubroutine,
+		&Indy3MacSnd::MusicChannel::ctrl_initOther,
+		&Indy3MacSnd::MusicChannel::ctrl_decrJumpIf,
+		&Indy3MacSnd::MusicChannel::ctrl_writeVar
 	};
 
 	const uint16 *mVars[] = {
@@ -1525,14 +826,14 @@ _resSize(0), _savedOffset(savedOffset), _modShapes(g_pv2ModTbl), _modShapesTable
 	clear();
 }
 
-I3MPlayer::MusicChannel::~MusicChannel() {
+Indy3MacSnd::MusicChannel::~MusicChannel() {
 	clear();
 	delete[] _vars;
 	_vars = nullptr;
 	_numVars = 0;
 }
 
-void I3MPlayer::MusicChannel::clear() {
+void Indy3MacSnd::MusicChannel::clear() {
 	for (int i = 0; i < _numVars; ++i)
 		getMemberRef(i) = 0;
 	_resource.reset();
@@ -1540,7 +841,7 @@ void I3MPlayer::MusicChannel::clear() {
 	_hq = false;
 }
 
-void I3MPlayer::MusicChannel::start(Common::SharedPtr<const byte> &songRes, uint16 offset, bool hq) {
+void Indy3MacSnd::MusicChannel::start(Common::SharedPtr<const byte> &songRes, uint16 offset, bool hq) {
 	clear();
 	_resource = songRes;
 	_resSize = READ_LE_UINT16(_resource.get());
@@ -1549,7 +850,7 @@ void I3MPlayer::MusicChannel::start(Common::SharedPtr<const byte> &songRes, uint
 	_hq = hq;
 }
 
-void I3MPlayer::MusicChannel::nextTick() {
+void Indy3MacSnd::MusicChannel::nextTick() {
 	if (!_frameLen)
 		return;
 
@@ -1597,9 +898,9 @@ void I3MPlayer::MusicChannel::nextTick() {
 	_envRate = *in >> 16;
 }
 
-void I3MPlayer::MusicChannel::parseNextEvents() {
+void Indy3MacSnd::MusicChannel::parseNextEvents() {
 	if (_resSize && _curPos >= _resSize) {
-		warning("I3MPlayer::MusicChannel::parseNext(): playback error");
+		warning("Indy3MacSnd::MusicChannel::parseNext(): playback error");
 		_frameLen = 0;
 		_curPos = 0;
 		_player->stopSong();
@@ -1642,7 +943,7 @@ void I3MPlayer::MusicChannel::parseNextEvents() {
 
 	int cp = in - _resource.get();
 	if ((cp >= _resSize && _frameLen) || cp & ~0xffff) {
-		warning("I3MPlayer::MusicChannel::parseNext(): playback error");
+		warning("Indy3MacSnd::MusicChannel::parseNext(): playback error");
 		_frameLen = 0;
 		_player->stopSong();
 	}
@@ -1652,7 +953,7 @@ void I3MPlayer::MusicChannel::parseNextEvents() {
 		_player->endOfTrack();
 }
 
-void I3MPlayer::MusicChannel::noteOn(uint16 duration, uint8 note) {
+void Indy3MacSnd::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 }
@@ -1668,11 +969,11 @@ void I3MPlayer::MusicChannel::noteOn(uint16 duration, uint8 note) {
 	_freqEff = _freqCur = noteFreqTable[_hq ? 0 : 1][n % 12] >> (n / 12);
 }
 
-uint16 I3MPlayer::MusicChannel::checkPeriod() const {
+uint16 Indy3MacSnd::MusicChannel::checkPeriod() const {
 	return (_frameLen && _envPhase) ? _freqEff : 0;
 }
 
-bool I3MPlayer::MusicChannel::ctrl_setShape(const byte *&pos) {
+bool Indy3MacSnd::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));
@@ -1680,64 +981,64 @@ bool I3MPlayer::MusicChannel::ctrl_setShape(const byte *&pos) {
 	return true;
 }
 
-bool I3MPlayer::MusicChannel::ctrl_modPara(const byte *&pos) {
+bool Indy3MacSnd::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) + 1 >= ARRAYSIZE(table)))
-		error("I3MPlayer::MusicChannel::ctrl_modPara(): data error");
+		error("Indy3MacSnd::MusicChannel::ctrl_modPara(): data error");
 	ix >>= 1;
 	_modType = table[ix];
 	_modRange = table[ix + 1];
 	return true;
 }
 
-bool I3MPlayer::MusicChannel::ctrl_init(const byte *&pos) {
+bool Indy3MacSnd::MusicChannel::ctrl_init(const byte *&pos) {
 	limitedClear();
 	return true;
 }
 
-bool I3MPlayer::MusicChannel::ctrl_returnFromSubroutine(const byte *&pos) {
+bool Indy3MacSnd::MusicChannel::ctrl_returnFromSubroutine(const byte *&pos) {
 	pos = _resource.get() + _savedOffset;
 	if (pos >= _resource.get() + _resSize)
-		error("I3MPlayer::MusicChannel::ctrl_returnFromSubroutine(): invalid address");
+		error("Indy3MacSnd::MusicChannel::ctrl_returnFromSubroutine(): invalid address");
 	return true;
 }
 
-bool I3MPlayer::MusicChannel::ctrl_jumpToSubroutine(const byte *&pos) {
+bool Indy3MacSnd::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");
+		error("Indy3MacSnd::MusicChannel::ctrl_jumpToSubroutine(): invalid address");
 	pos = _resource.get() + offs;
 	return true;
 }
 
-bool I3MPlayer::MusicChannel::ctrl_initOther(const byte *&pos) {
+bool Indy3MacSnd::MusicChannel::ctrl_initOther(const byte *&pos) {
 	uint16 val = READ_LE_UINT16(pos);
 	pos += 2;
 	if (val % 50)
-		error("I3MPlayer::MusicChannel::ctrl_initOther(): data error");
+		error("Indy3MacSnd::MusicChannel::ctrl_initOther(): data error");
 	_ctrlChan = _player->getMusicChannel(val / 50);
 	assert(_ctrlChan);
 	_ctrlChan->limitedClear();
 	return true;
 }
 
-bool I3MPlayer::MusicChannel::ctrl_decrJumpIf(const byte *&pos) {
+bool Indy3MacSnd::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");
+			error("Indy3MacSnd::MusicChannel::ctrl_jumpToSubroutine(): invalid address");
 	} else {
 		--var;
 	}
 	return true;
 }
 
-bool I3MPlayer::MusicChannel::ctrl_writeVar(const byte *&pos) {
+bool Indy3MacSnd::MusicChannel::ctrl_writeVar(const byte *&pos) {
 	byte ix = *pos++;
 	uint16 val = READ_LE_UINT16(pos);
 	pos += 2;
@@ -1745,11 +1046,11 @@ bool I3MPlayer::MusicChannel::ctrl_writeVar(const byte *&pos) {
 	return (bool)ix;
 }
 
-bool I3MPlayer::MusicChannel::ctrlProc(int procId, const byte *&arg) {
+bool Indy3MacSnd::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) {
+void Indy3MacSnd::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
@@ -1758,11 +1059,11 @@ void I3MPlayer::MusicChannel::setFrameLen(uint8 len) {
 	assert(_ctrlChan);
 	len &= 0x1f;
 	if (len >= ARRAYSIZE(durationTicks))
-		error("I3MPlayer::MusicChannel::setFrameLen(): Out of range (val %d, range 0 - %d)", len, ARRAYSIZE(durationTicks) - 1);
+		error("Indy3MacSnd::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() {
+void Indy3MacSnd::MusicChannel::limitedClear() {
 	for (int i = 1; i < 7; ++i)
 		getMemberRef(i) = 0;
 	for (int i = 8; i < 10; ++i)
@@ -1773,14 +1074,14 @@ void I3MPlayer::MusicChannel::limitedClear() {
 		getMemberRef(i) = 0;
 }
 
-uint16 &I3MPlayer::MusicChannel::getMemberRef(int pos) {
+uint16 &Indy3MacSnd::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);
+		error("Indy3MacSnd::MusicChannel::getMemberRef(): attempting invalid access (var: %d, valid range: %d - %d)", pos, 0, _numVars - 1);
 	return *_vars[pos];
 }
 
-const uint32 I3MPlayer::MusicChannel::_envShapes[98] = {
+const uint32 Indy3MacSnd::MusicChannel::_envShapes[98] = {
 	0x0003ffff, 0x00000000, 0x00000000, 0x00000000, 0x0000ffff, 0x00000000,
 	0x0003ffff, 0x00000020, 0x0000ffff, 0x00000000, 0x0000ffff, 0x00000000,
 	0x0003ffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
@@ -1797,66 +1098,7 @@ const uint32 I3MPlayer::MusicChannel::_envShapes[98] = {
 	0x88b8ffff, 0x01f40014, 0x00000000, 0x00000000, 0xafc8ffff, 0xfe0c003c, 0x0000ffff, 0x00000000
 };
 
-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);
-}
-
-void Player_Mac_Indy3::saveLoadWithSerializer(Common::Serializer &ser) {
-	if (_player != nullptr)
-		_player->saveLoadWithSerializer(ser);
-}
-
-void Player_Mac_Indy3::restoreAfterLoad() {
-	if (_player != nullptr)
-		_player->restoreAfterLoad();
-}
-
 #undef ASC_DEVICE_RATE
-#undef VBL_UPDATE_RATE
 #undef PCM_BUFFER_SIZE
-#undef PCM_BUFFER_RESERVE
-#undef RATECNV_BIT_PRECSN
 
 } // End of namespace Scumm
diff --git a/engines/scumm/players/player_mac_intern.h b/engines/scumm/players/player_mac_intern.h
new file mode 100644
index 00000000000..8169e445e6a
--- /dev/null
+++ b/engines/scumm/players/player_mac_intern.h
@@ -0,0 +1,318 @@
+/* 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_INTERN_H
+#define SCUMM_PLAYERS_PLAYER_MAC_INTERN_H
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+namespace Scumm {
+
+class MacSoundDriver {
+public:
+	MacSoundDriver(Common::Mutex &mutex, uint32 deviceRate, bool canInterpolate, bool internal16Bit) : _mutex(mutex), _caps(deviceRate, canInterpolate),
+		_smpSize(internal16Bit ? 2 : 1), _smpMin(internal16Bit ? -32768 : -128), _smpMax(internal16Bit ? 32767 : 127), _status(0) {}
+	virtual ~MacSoundDriver() {}
+	virtual void feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) = 0;
+
+	struct Caps {
+		Caps(uint32 rate, bool interp) :deviceRate(rate), allowInterPolation(interp) {}
+		const uint32 deviceRate;
+		const bool allowInterPolation;
+	};
+	const Caps &getCaps() const { return _caps; }
+
+	enum StatusFlag : uint8 {
+		kStatusPlaying =		1	<<		0,
+		kStatusOverflow =		1	<<		1,
+		kStatusStartup =		1	<<		2,
+		kStatusDone =			1	<<		3
+	};
+	uint8 getStatus() const { return _status; }
+	void clearFlags(uint8 flags) { _status &= ~flags; }
+
+protected:
+	void setFlags(uint8 flags) { _status |= flags; }
+
+	Common::Mutex &_mutex;
+	const int _smpSize;
+	const int16 _smpMin;
+	const int16 _smpMax;
+	const Caps _caps;
+	uint8 _status;
+};
+
+class MacLowLevelPCMDriver final : public MacSoundDriver {
+public:
+	struct PCMSound {
+		PCMSound() : len(0), rate(0), loopst(0), loopend(0), baseFreq(0), stereo(false) {}
+		Common::SharedPtr<const byte> data;
+		uint32 len;
+		uint32 rate;
+		uint32 loopst;
+		uint32 loopend;
+		byte baseFreq;
+		bool stereo;
+	};
+public:
+	MacLowLevelPCMDriver(Common::Mutex &mutex, uint32 deviceRate, bool enableInterpolation, bool internal16Bit);
+	void feed(int8 *dst, uint32 byteSize, 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 bool _interp;
+	int8 _lastSmp[2];
+	uint32 _len;
+	uint16 _rmH;
+	uint16 _rmL;
+	uint32 _loopSt;
+	uint32 _loopEnd;
+	byte _baseFreq;
+	uint32 _rcPos;
+	uint32 _smpWtAcc;
+	uint16 _frameSize;
+};
+
+class VblTaskClientDriver {
+public:
+	virtual void callback() = 0;
+	virtual void generateData(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) const = 0;
+	virtual const MacSoundDriver::Caps &getDriverCaps(uint8 drvID) const = 0;
+};
+
+class MacPlayerAudioStream : public Audio::AudioStream {
+public:
+	MacPlayerAudioStream(VblTaskClientDriver *drv, uint32 scummVMOutputrate, bool stereo, bool interpolate, bool internal16Bit);
+	~MacPlayerAudioStream() override;
+
+	void initBuffers(uint32 feedBufferSize);
+	void initDrivers();
+	typedef Common::Functor0Mem<void, VblTaskClientDriver> 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 byteSize, Audio::Mixer::SoundType, bool expectStereo) const;
+	void runVblTask();
+
+	VblTaskClientDriver *_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), interpolate(false) {}
+		int8 *start;
+		int8 *pos;
+		const int8 *end;
+		uint32 volume;
+		int32 lastL;
+		int32 lastR;
+		uint32 size;
+		uint32 rateConvInt;
+		uint32 rateConvFrac;
+		int32 rateConvAcc;
+		bool interpolate;
+	} _buffers[2];
+
+	const uint32 _outputRate;
+	const uint8 _frameSize;
+	const bool _interp;
+	const int _smpInternalSize;
+	const int _volDown;
+
+	const bool _isStereo;
+};
+
+class LegacyMusicDriver;
+class Indy3MacSnd final : public VblTaskClientDriver {
+private:
+	Indy3MacSnd(ScummEngine *vm, Audio::Mixer *mixer);
+public:
+	~Indy3MacSnd();
+	static Common::SharedPtr<Indy3MacSnd> open(ScummEngine *scumm, Audio::Mixer *mixer);
+	bool startDevices(uint32 outputRate, uint32 pcmDeviceRate, uint32 feedBufferSize, bool enableInterpolation, bool stereo, bool internal16Bit);
+
+	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 saveLoadWithSerializer(Common::Serializer &ser);
+	void restoreAfterLoad();
+
+	void nextTick();
+	void callback() override { nextTick(); }
+	void generateData(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) const override;
+	const MacSoundDriver::Caps &getDriverCaps(uint8 drvID) const override;
+
+private:
+	void startSong(int id);
+	void startSoundEffect(int id);
+	void stopSong();
+	void stopSoundEffect();
+	void stopActiveSound();
+	void finishSong();
+	void updateSong();
+	void updateSoundEffect();
+
+	void checkRestartSoundEffects();
+	void endOfTrack();
+
+	bool isSong(int id) const;
+	bool isHiQuality() const;
+
+	int _curSound;
+	int _curSong;
+	int _lastSoundEffectPrio;
+	int _soundEffectNumLoops;
+	int _songTimer;
+	bool _songUnfinished;
+	uint _activeChanCount;
+	byte _songTimerInternal;
+	byte *_soundUsage;
+
+	bool _soundEffectPlaying;
+	int _qmode;
+	bool _16bit;
+	bool _qualHi;
+	bool _mixerThread;
+
+	MacLowLevelPCMDriver::PCMSound _pcmSnd;
+
+	MacPlayerAudioStream *_macstr;
+	Audio::SoundHandle _soundHandle;
+	MacPlayerAudioStream::CallbackProc _nextTickProc;
+
+	ScummEngine *_vm;
+	Audio::Mixer *_mixer;
+	static Common::WeakPtr<Indy3MacSnd> *_inst;
+
+	const byte *_musicIDTable;
+	int _musicIDTableLen;
+	const int _idRangeMax;
+
+	LegacyMusicDriver *_mdrv;
+	MacLowLevelPCMDriver *_sdrv;
+	Common::Array<MacSoundDriver*> _drivers;
+
+private:
+	class MusicChannel {
+	public:
+		MusicChannel(Indy3MacSnd *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 _envSust;
+		int16 _transpose;
+		uint16 _envAtt;
+		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 (Indy3MacSnd::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;
+
+		Indy3MacSnd *_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;
+	const int _numMusicTracks;
+
+	static const uint8 _fourToneSynthWaveForm[256];
+
+public:
+	MusicChannel *getMusicChannel(uint8 id) const;
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/players/player_mac_new.cpp b/engines/scumm/players/player_mac_new.cpp
new file mode 100644
index 00000000000..0c414e50500
--- /dev/null
+++ b/engines/scumm/players/player_mac_new.cpp
@@ -0,0 +1,551 @@
+/* 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_new.h"
+#include "scumm/players/player_mac_intern.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 VBL_UPDATE_RATE		0x003C25BD
+#define PCM_BUFFER_RESERVE	64
+#define RATECNV_BIT_PRECSN	24
+
+MacPlayerAudioStream::MacPlayerAudioStream(VblTaskClientDriver *drv, uint32 scummVMOutputrate, bool stereo, bool interpolate, bool internal16Bit) : Audio::AudioStream(), _drv(drv),
+	_vblSmpQty(0), _vblSmpQtyRem(0), _frameSize((stereo ? 2 : 1) * (internal16Bit ? 2 : 1)), _vblCountDown(0), _vblCountDownRem(0), _outputRate(scummVMOutputrate),
+		_vblCbProc(nullptr), _isStereo(stereo), _interp(interpolate), _smpInternalSize(internal16Bit ? 2 : 1), _volDown(internal16Bit ? 2 : 0) {
+	assert(_drv);
+	_vblSmpQty = (_outputRate << 16) / VBL_UPDATE_RATE;
+	_vblSmpQtyRem = (_outputRate << 16) % VBL_UPDATE_RATE;
+	_vblCountDown = _vblSmpQty;
+	_vblCountDownRem = 0;
+}
+
+MacPlayerAudioStream::~MacPlayerAudioStream() {
+	for (int i = 0; i < 2; ++i)
+		delete[] _buffers[i].start;
+}
+
+void MacPlayerAudioStream::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 / _frameSize;
+		_buffers[i].start = new int8[_buffers[i].size + PCM_BUFFER_RESERVE];
+		_buffers[i].end = &_buffers[i].start[_buffers[i].size];
+	}
+	clearBuffer();
+}
+
+void MacPlayerAudioStream::initDrivers() {
+	for (int i = 0; i < 2; ++i) {
+		if (!_drv)
+			error("MacPlayerAudioStream::initDrivers(): Failed to query device rate for device %d", i);
+		uint64 irt = (uint64)_drv->getDriverCaps(i).deviceRate * (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;
+		_buffers[i].interpolate = _interp && _drv->getDriverCaps(i).allowInterPolation;
+	}
+}
+
+void MacPlayerAudioStream::setVblCallback(const CallbackProc *proc) {
+	_vblCbProc = proc;
+}
+
+void MacPlayerAudioStream::clearBuffer() {
+	for (int i = 0; i < 2; ++i) {
+		memset(_buffers[i].start, 0, _buffers[i].size + PCM_BUFFER_RESERVE);
+		_buffers[i].pos = _buffers[i].start;
+	}
+}
+
+void MacPlayerAudioStream::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 MacPlayerAudioStream::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("MacPlayerAudioStream::readBuffer(): init%s() must be called before playback", errFnNames[errNo]);
+
+	for (int i = _isStereo ? numSamples >> 1 : numSamples; i; --i) {
+		if (!--_vblCountDown) {
+			_vblCountDown = _vblSmpQty;
+			_vblCountDownRem += _vblSmpQtyRem;
+			while (_vblCountDownRem >= (_vblSmpQty << 16)) {
+				_vblCountDownRem -= (_vblSmpQty << 16);
+				++_vblCountDown;
+			}
+			runVblTask();
+		}
+
+		int32 smpL = 0;
+		int32 smpR = 0;
+		for (int ii = 0; ii < 2; ++ii) {
+			int smpN = _smpInternalSize == 2 ? *reinterpret_cast<int16*>(_buffers[ii].pos) : _buffers[ii].pos[0];
+			int diff = smpN - _buffers[ii].lastL;
+			if (diff && _buffers[ii].rateConvAcc && _buffers[ii].interpolate)
+				diff = (diff * _buffers[ii].rateConvAcc) >> RATECNV_BIT_PRECSN;
+			smpL += (int32)((_buffers[ii].lastL + diff) * (_buffers[ii].volume >> _volDown));
+		}
+		if (_isStereo) {
+			for (int ii = 0; ii < 2; ++ii) {
+				int smpN = _smpInternalSize == 2 ? *reinterpret_cast<int16*>(&_buffers[ii].pos[2]) : _buffers[ii].pos[1];
+				int diff = smpN - _buffers[ii].lastR;
+				if (diff && _buffers[ii].rateConvAcc && _buffers[ii].interpolate)
+					diff = (diff * _buffers[ii].rateConvAcc) >> RATECNV_BIT_PRECSN;
+				smpR += (int32)((_buffers[ii].lastR + diff) * (_buffers[ii].volume >> _volDown));
+			}
+		}
+
+		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;
+				const int8 *lpos = _buffers[ii].pos;
+				if (lpos >= _buffers[ii].start + _frameSize)
+					lpos -= _frameSize;
+
+				if (_smpInternalSize == 2) {
+					_buffers[ii].lastL = *reinterpret_cast<const int16*>(lpos);
+					if (_isStereo)
+						_buffers[ii].lastR = *reinterpret_cast<const int16*>(&lpos[2]);
+				} else {
+					_buffers[ii].lastL = lpos[0];
+					if (_isStereo)
+						_buffers[ii].lastR = lpos[1];
+				}
+
+				if (_buffers[ii].pos >= _buffers[ii].end) {
+					int refreshSize = MIN<int>(_vblCountDown * _frameSize, _buffers[ii].size);
+					_buffers[ii].pos -= refreshSize;
+					assert(_buffers[ii].pos + refreshSize < _buffers[ii].end + PCM_BUFFER_RESERVE);
+					generateData(_buffers[ii].pos, refreshSize, stype[ii], _isStereo);
+				}
+			}
+		}
+
+		*buffer++ = CLIP<int16>(smpL >> 8, -32768, 32767);
+		if (_isStereo)
+			*buffer++ = CLIP<int16>(smpR >> 8, -32768, 32767);
+	}
+	return numSamples;
+}
+
+void MacPlayerAudioStream::generateData(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) const {
+	if (_drv)
+		_drv->generateData(dst, byteSize, type, expectStereo);
+}
+
+void MacPlayerAudioStream::runVblTask() {
+	if (_vblCbProc && _vblCbProc->isValid())
+		(*_vblCbProc)();
+}
+
+MacLowLevelPCMDriver::MacLowLevelPCMDriver(Common::Mutex &mutex, uint32 deviceRate, bool enableInterpolation, bool internal16Bit) :
+	MacSoundDriver(mutex, deviceRate, true, internal16Bit), _interp(enableInterpolation), _frameSize(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 MacLowLevelPCMDriver::feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) {
+	if (dst == nullptr || type != Audio::Mixer::kSFXSoundType)
+		return;
+
+	memset(dst, 0, byteSize);
+
+	if (_data == nullptr)
+		return;
+
+	int32 diff = 0;
+	uint16 destFrameSize = expectStereo ? 2 : 1;
+	bool interp = (_interp && _rmL);
+
+	for (const int8 *end = &dst[byteSize]; dst < end; ) {
+		int8 in = 0;
+		for (int i = 0; i < destFrameSize; ++i) {
+			if (i < _frameSize) {
+				in = _data[_rcPos + i];
+				if (interp && in != _lastSmp[i]) {
+					diff = in - _lastSmp[i];
+					diff = (diff * (_smpWtAcc >> 1)) >> 15;
+					in = (_lastSmp[i] + diff) & 0xff;
+				}
+			}
+			if (_smpSize == 2)
+				*reinterpret_cast<int16*>(dst) = in << 2;
+			else
+				*dst = in;
+			dst += _smpSize;
+		}
+		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;
+			}
+			setFlags(kStatusDone);
+		}
+	}
+}
+
+void MacLowLevelPCMDriver::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(_caps.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;
+
+	_frameSize = snd->stereo ? 2 : 1;
+
+	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];
+	clearFlags(kStatusDone);
+}
+
+void MacLowLevelPCMDriver::stop() {
+	Common::StackLock lock(_mutex);
+	_data = nullptr;
+	_res.reset();
+	setFlags(kStatusDone);
+}
+
+uint32 MacLowLevelPCMDriver::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;
+	if (sh) {
+		factor <<= sh;
+		dataRate = ((dataRate >> (32 - sh)) | (dataRate << sh));
+	}
+
+	dataRate ^= 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 & 0xffff) * (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;
+}
+
+class Indy3MacSnd;
+
+template<typename T> class Player_Mac_New : public MusicEngine {
+public:
+	Player_Mac_New(ScummEngine *vm, Audio::Mixer *mixer);
+	~Player_Mac_New() 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;
+	void saveLoadWithSerializer(Common::Serializer &ser) override;
+	void restoreAfterLoad() override;
+
+private:
+	Common::SharedPtr<T> _player;
+};
+
+template <typename T> Player_Mac_New<T>::Player_Mac_New(ScummEngine *vm, Audio::Mixer *mixer) : _player(nullptr) {
+	_player = T::open(vm, mixer);
+}
+
+template <typename T> Player_Mac_New<T>::~Player_Mac_New() {
+	_player = nullptr;
+}
+
+template <typename T> void Player_Mac_New<T>::setMusicVolume(int vol) {
+	if (_player != nullptr)
+		_player->setMusicVolume(vol);
+}
+
+template <typename T> void Player_Mac_New<T>::setSfxVolume(int vol) {
+	if (_player != nullptr)
+		_player->setSfxVolume(vol);
+}
+
+template <typename T> void Player_Mac_New<T>::startSound(int id) {
+	if (_player != nullptr)
+		_player->startSound(id);
+}
+
+template <typename T> void Player_Mac_New<T>::stopSound(int id) {
+	if (_player != nullptr)
+		_player->stopSound(id);
+}
+
+template <typename T> void Player_Mac_New<T>::stopAllSounds() {
+	if (_player != nullptr)
+		_player->stopAllSounds();
+}
+
+template <typename T> int Player_Mac_New<T>::getMusicTimer() {
+	return (_player != nullptr) ? _player->getMusicTimer() : 0;
+}
+
+template <typename T> int Player_Mac_New<T>::getSoundStatus(int id) const {
+	return (_player != nullptr) ? _player->getSoundStatus(id) : 0;
+}
+
+template <typename T> void Player_Mac_New<T>::setQuality(int qual) {
+	if (_player != nullptr)
+		_player->setQuality(qual);
+}
+
+template <typename T> void Player_Mac_New<T>::saveLoadWithSerializer(Common::Serializer &ser) {
+	if (_player != nullptr)
+		_player->saveLoadWithSerializer(ser);
+}
+
+template <typename T> void Player_Mac_New<T>::restoreAfterLoad() {
+	if (_player != nullptr)
+		_player->restoreAfterLoad();
+}
+
+namespace MacSound {
+MusicEngine *createPlayer(ScummEngine *vm) {
+	assert(vm);
+	assert(vm->_mixer);
+	if (vm->_game.id == GID_INDY3)
+		return new Player_Mac_New<Indy3MacSnd>(vm, vm->_mixer);
+
+	return nullptr;
+}
+} // end of namespace MacSound
+
+#undef ASC_DEVICE_RATE
+#undef VBL_UPDATE_RATE
+#undef PCM_BUFFER_SIZE
+#undef PCM_BUFFER_RESERVE
+#undef RATECNV_BIT_PRECSN
+
+} // End of namespace Scumm
diff --git a/engines/scumm/players/player_mac_indy3.h b/engines/scumm/players/player_mac_new.h
similarity index 60%
rename from engines/scumm/players/player_mac_indy3.h
rename to engines/scumm/players/player_mac_new.h
index 864ab50fa77..b7c32c19b5d 100644
--- a/engines/scumm/players/player_mac_indy3.h
+++ b/engines/scumm/players/player_mac_new.h
@@ -24,39 +24,19 @@
 
 #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;
-	void saveLoadWithSerializer(Common::Serializer &ser) override;
-	void restoreAfterLoad() override;
 
-private:
-	Common::SharedPtr<I3MPlayer> _player;
+namespace MacSound {
+enum {
+	kQualityAuto = 0,
+	kQualityHigh,
+	kQualityLow,
+	kQualityMedium
 };
+MusicEngine *createPlayer(ScummEngine *vm);
+} // end of namespace MacSound
 
 } // End of namespace Scumm
 
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 52927303543..c0340ba282b 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -62,7 +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_mac_new.h"
 #include "scumm/players/player_v1.h"
 #include "scumm/players/player_v2.h"
 #include "scumm/players/player_v2cms.h"
@@ -2169,9 +2169,9 @@ void ScummEngine::setupMusic(int midi, const Common::Path &macInstrumentFile) {
 	} 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);
+		_musicEngine = MacSound::createPlayer(this);
 		if (ConfMan.hasKey("mac_v3_low_quality_music") && ConfMan.getBool("mac_v3_low_quality_music"))
-			_musicEngine->setQuality(Player_Mac_Indy3::kQualLo);
+			_musicEngine->setQuality(MacSound::kQualityLow);
 		_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"));


Commit: 95027d64d391af38b18fa807eb075fcd8622418a
    https://github.com/scummvm/scummvm/commit/95027d64d391af38b18fa807eb075fcd8622418a
Author: athrxx (athrxx at scummvm.org)
Date: 2024-02-28T21:01:35+01:00

Commit Message:
SCUMM: (V3/Mac) - more preparations to extend sound player

Changed paths:
    engines/scumm/players/player_mac_indy3.cpp
    engines/scumm/players/player_mac_intern.h
    engines/scumm/players/player_mac_new.cpp


diff --git a/engines/scumm/players/player_mac_indy3.cpp b/engines/scumm/players/player_mac_indy3.cpp
index cca30683ad6..2e6e6126337 100644
--- a/engines/scumm/players/player_mac_indy3.cpp
+++ b/engines/scumm/players/player_mac_indy3.cpp
@@ -39,7 +39,7 @@ extern const uint32 g_pv2ModTblSize;
 
 class LegacyMusicDriver : public MacSoundDriver {
 public:
-	LegacyMusicDriver(uint16 numChannels, Common::Mutex &mutex, bool canInterpolate, bool internal16Bit) : MacSoundDriver(mutex, ASC_DEVICE_RATE, canInterpolate, internal16Bit), _numChan(numChannels) {}
+	LegacyMusicDriver(uint16 numChannels, Common::Mutex &mutex, bool canInterpolate, bool internal16Bit) : MacSoundDriver(mutex, ASC_DEVICE_RATE, internal16Bit ? numChannels : 1, canInterpolate, internal16Bit), _numChan(numChannels) {}
 	virtual void start() = 0;
 	virtual void stop() = 0;
 
@@ -57,17 +57,17 @@ protected:
 	void putSample(int8 *&dst, int16 smp, bool expectStereo) {
 		if (_smpSize == 2) {
 			smp = CLIP<int16>(smp, _smpMin, _smpMax);
-			*reinterpret_cast<int16*>(dst) = smp;
+			*reinterpret_cast<int16*>(dst) += smp;
 			dst += _smpSize;
 			if (expectStereo) {
-				*reinterpret_cast<int16*>(dst) = smp;
+				*reinterpret_cast<int16*>(dst) += smp;
 				dst += _smpSize;
 			}
 		} else {
-			smp = CLIP<int8>(smp >> 2, _smpMin, _smpMax);
-			*dst++ = smp;
+			smp = CLIP<int16>(smp / _numChan, _smpMin, _smpMax);
+			*dst++ += smp;
 			if (expectStereo)
-				*dst++ = smp;
+				*dst++ += smp;
 		}
 	}
 	const uint16 _numChan;
@@ -168,9 +168,6 @@ void FourToneSynthDriver::feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundTy
 				setFlags(kStatusDone);
 		}
 	}
-
-	if (end > dst)
-		memset(dst, 0, end - dst);
 }
 
 void FourToneSynthDriver::start() {
@@ -209,7 +206,7 @@ void FourToneSynthDriver::send(int dataType, ...)  {
 		break;
 	}
 
-	_status |= kStatusOverflow;
+	_status.flags |= kStatusOverflow;
 
 	va_end(arg);
 }
@@ -241,7 +238,6 @@ void FourToneSynthDriver::setRate(uint8 chan, uint16 rate) {
 
 SquareWaveSynthDriver::SquareWaveSynthDriver(Common::Mutex &mutex, bool internal16Bit) :
 	LegacyMusicDriver(1, mutex, false, internal16Bit), _count(0xffff), _duration(0), _amplitude(0), _phase(0), _pos(0) {
-
 }
 
 void SquareWaveSynthDriver::feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) {
@@ -271,9 +267,6 @@ void SquareWaveSynthDriver::feed(int8 *dst, uint32 byteSize, Audio::Mixer::Sound
 		if (++_pos == 370)
 			_pos = 0;
 	}
-
-	if (dst < end)
-		memset(dst, 0, end - dst);
 }
 
 void SquareWaveSynthDriver::start() {
@@ -310,7 +303,7 @@ void SquareWaveSynthDriver::send(int dataType, ...)  {
 }
 
 void SquareWaveSynthDriver::addTriplet(uint16 frequency, uint16 amplitude) {
-	if ((_status & kStatusStartup) && frequency < 3)
+	if ((_status.flags & kStatusStartup) && frequency < 3)
 		return;
 
 	clearFlags(kStatusStartup);
@@ -320,7 +313,7 @@ void SquareWaveSynthDriver::addTriplet(uint16 frequency, uint16 amplitude) {
 	if (_lastPara.amplitude == 0xffff)
 		_lastPara.amplitude = amplitude;
 
-	if ((_status & kStatusPlaying) && _tripletsQueue.size() < 176) {
+	if ((_status.flags & kStatusPlaying) && _tripletsQueue.size() < 176) {
 		if (frequency >> 3 != _lastPara.count >> 3 || amplitude != _lastPara.amplitude) {
 			_tripletsQueue.push_back(_lastPara.fromScumm());
 			_lastPara = Triplet(frequency, amplitude, 0);
@@ -329,7 +322,7 @@ void SquareWaveSynthDriver::addTriplet(uint16 frequency, uint16 amplitude) {
 		_lastPara.duration++;
 	}
 
-	if (!(_status & kStatusPlaying) || _tripletsQueue.size() >= 176)
+	if (!(_status.flags & kStatusPlaying) || _tripletsQueue.size() >= 176)
 		setFlags(kStatusOverflow);
 }
 
@@ -339,7 +332,7 @@ Indy3MacSnd::Indy3MacSnd(ScummEngine *vm, Audio::Mixer *mixer) : VblTaskClientDr
 	_vm(vm), _mixer(mixer), _musicChannels(nullptr), _curSound(0), _curSong(0), _lastSoundEffectPrio(0), _idRangeMax(86), _soundEffectNumLoops(-1),
 	_musicIDTable(nullptr), _macstr(nullptr), _musicIDTableLen(0), _soundUsage(0), _mdrv(nullptr), _sdrv(nullptr), _nextTickProc(this, &VblTaskClientDriver::callback),
 	_soundEffectPlaying(false), _songTimer(0), _songTimerInternal(0), _qmode(0), _16bit(false), _qualHi(false),	_mixerThread(false), _activeChanCount(0),
-	_songUnfinished(false), _numMusicChannels(8), _numMusicTracks(4) {
+	_songUnfinished(false), _numMusicChannels(8), _numMusicTracks(4), _handle(0) {
 	assert(_vm);
 	assert(_mixer);
 
@@ -362,6 +355,8 @@ Indy3MacSnd::~Indy3MacSnd() {
 	delete _macstr;
 	delete[] _soundUsage;
 
+	_sdrv->disposeChannel(_handle);
+
 	for (Common::Array<MacSoundDriver*>::const_iterator i = _drivers.begin(); i != _drivers.end(); ++i)
 		delete *i;
 	_drivers.clear();
@@ -396,7 +391,7 @@ bool Indy3MacSnd::startDevices(uint32 outputRate, uint32 pcmDeviceRate, uint32 f
 	if (!_macstr || !_mixer)
 		return false;
 
-	_sdrv = new MacLowLevelPCMDriver(_mixer->mutex(), pcmDeviceRate, enableInterpolation, internal16Bit);
+	_sdrv = new MacLowLevelPCMDriver(_mixer->mutex(), pcmDeviceRate, internal16Bit);
 	FourToneSynthDriver *mdrv = new FourToneSynthDriver(_mixer->mutex(), internal16Bit);
 	if (!mdrv || !_sdrv)
 		return false;
@@ -407,6 +402,8 @@ bool Indy3MacSnd::startDevices(uint32 outputRate, uint32 pcmDeviceRate, uint32 f
 	_16bit = internal16Bit;
 	_mdrv = mdrv;
 
+	_handle = _sdrv->createChannel(Audio::Mixer::kSFXSoundType, MacLowLevelPCMDriver::kSampledSynth, enableInterpolation);
+
 	_drivers.push_back(_mdrv);
 	_drivers.push_back(_sdrv);
 
@@ -534,25 +531,27 @@ void Indy3MacSnd::nextTick() {
 
 	_mixerThread = true;
 
-	if (!_curSong && (_sdrv->getStatus() & MacSoundDriver::kStatusDone))
+	if (!_curSong && (_sdrv->getChannelStatus(_handle) & MacSoundDriver::kStatusDone))
 		updateSoundEffect();
 	else if (_curSong)
 		updateSong();
-	else if (_songUnfinished && (_mdrv->getStatus() & MacSoundDriver::kStatusDone))
+	else if (_songUnfinished && (_mdrv->getStatus().flags & MacSoundDriver::kStatusDone))
 		stopSong();
 
 	_mixerThread = false;
 }
 
 void Indy3MacSnd::generateData(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) const {
+	assert(dst);
+	memset(dst, 0, len);
 	for (Common::Array<MacSoundDriver*>::const_iterator i = _drivers.begin(); i != _drivers.end(); ++i)
 		(*i)->feed(dst, len, type, expectStereo);
 }
 
-const MacSoundDriver::Caps &Indy3MacSnd::getDriverCaps(uint8 drvID) const {
+const MacSoundDriver::Status &Indy3MacSnd::getDriverStatus(uint8 drvID) const {
 	if (drvID >= _drivers.size())
 		error(" Indy3MacSnd::getDriverCaps(): Invalid driver id %d", drvID);
-	return _drivers[drvID]->getCaps();
+	return _drivers[drvID]->getStatus();
 }
 
 void Indy3MacSnd::startSong(int id) {
@@ -656,7 +655,7 @@ void Indy3MacSnd::startSoundEffect(int id) {
 	_pcmSnd.loopend = numSamples - 1;
 	_pcmSnd.baseFreq = 60;
 
-	_sdrv->play(&_pcmSnd);
+	_sdrv->playSamples(_handle, &_pcmSnd);
 
 	_curSound = id;
 	_soundUsage[id]++;
@@ -671,7 +670,7 @@ void Indy3MacSnd::stopSong() {
 
 void Indy3MacSnd::stopSoundEffect() {
 	Common::StackLock lock(_mixer->mutex());
-	_sdrv->stop();
+	_sdrv->stop(_handle);
 	_soundEffectPlaying = false;
 	_lastSoundEffectPrio = 0;
 	_curSound = 0;
@@ -689,13 +688,13 @@ void Indy3MacSnd::finishSong() {
 	if (_soundUsage[_curSong])
 		--_soundUsage[_curSong];
 	_curSong = 0;
-	_songUnfinished = !(_mdrv->getStatus() & MacSoundDriver::kStatusDone);
+	_songUnfinished = !(_mdrv->getStatus().flags & MacSoundDriver::kStatusDone);
 }
 
 void Indy3MacSnd::updateSong() {
-	if (_curSong && (_qualHi || (_mdrv->getStatus() & MacSoundDriver::kStatusDone))) {
+	if (_curSong && (_qualHi || (_mdrv->getStatus().flags & MacSoundDriver::kStatusDone))) {
 		_mdrv->clearFlags(MacSoundDriver::kStatusOverflow);
-		while (_curSong && !(_mdrv->getStatus() & MacSoundDriver::kStatusOverflow)) {
+		while (_curSong && !(_mdrv->getStatus().flags & MacSoundDriver::kStatusOverflow)) {
 			for (int i = 4; i; --i) {
 				for (int ii = 0; ii < _numMusicTracks && _curSong; ++ii)
 					_musicChannels[ii]->nextTick();
@@ -728,7 +727,7 @@ void Indy3MacSnd::updateSoundEffect() {
 		if (_soundEffectNumLoops > 0)
 			--_soundEffectNumLoops;
 		if (_soundEffectNumLoops)
-			_sdrv->play(&_pcmSnd);
+			_sdrv->playSamples(_handle, &_pcmSnd);
 		else
 			--_soundUsage[_curSound];
 		chkRestart = (_soundEffectNumLoops == 0);
diff --git a/engines/scumm/players/player_mac_intern.h b/engines/scumm/players/player_mac_intern.h
index 8169e445e6a..324008acc03 100644
--- a/engines/scumm/players/player_mac_intern.h
+++ b/engines/scumm/players/player_mac_intern.h
@@ -29,17 +29,19 @@ namespace Scumm {
 
 class MacSoundDriver {
 public:
-	MacSoundDriver(Common::Mutex &mutex, uint32 deviceRate, bool canInterpolate, bool internal16Bit) : _mutex(mutex), _caps(deviceRate, canInterpolate),
-		_smpSize(internal16Bit ? 2 : 1), _smpMin(internal16Bit ? -32768 : -128), _smpMax(internal16Bit ? 32767 : 127), _status(0) {}
+	MacSoundDriver(Common::Mutex &mutex, uint32 deviceRate, int activeChannels, bool canInterpolate, bool internal16Bit) : _mutex(mutex), _status(deviceRate, canInterpolate, activeChannels),
+		_smpSize(internal16Bit ? 2 : 1), _smpMin(internal16Bit ? -32768 : -128), _smpMax(internal16Bit ? 32767 : 127) {}
 	virtual ~MacSoundDriver() {}
 	virtual void feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) = 0;
 
-	struct Caps {
-		Caps(uint32 rate, bool interp) :deviceRate(rate), allowInterPolation(interp) {}
-		const uint32 deviceRate;
-		const bool allowInterPolation;
+	struct Status {
+		Status(uint32 rate, bool interp, int actChan) :deviceRate(rate), allowInterPolation(interp), numExternalMixChannels(actChan), flags(0) {}
+		uint32 deviceRate;
+		int numExternalMixChannels;
+		bool allowInterPolation;
+		uint8 flags;
 	};
-	const Caps &getCaps() const { return _caps; }
+	const Status &getStatus() const { return _status; }
 
 	enum StatusFlag : uint8 {
 		kStatusPlaying =		1	<<		0,
@@ -47,18 +49,17 @@ public:
 		kStatusStartup =		1	<<		2,
 		kStatusDone =			1	<<		3
 	};
-	uint8 getStatus() const { return _status; }
-	void clearFlags(uint8 flags) { _status &= ~flags; }
+
+	void clearFlags(uint8 flags) { _status.flags &= ~flags; }
 
 protected:
-	void setFlags(uint8 flags) { _status |= flags; }
+	void setFlags(uint8 flags) { _status.flags |= flags; }
 
 	Common::Mutex &_mutex;
 	const int _smpSize;
 	const int16 _smpMin;
 	const int16 _smpMax;
-	const Caps _caps;
-	uint8 _status;
+	Status _status;
 };
 
 class MacLowLevelPCMDriver final : public MacSoundDriver {
@@ -73,34 +74,71 @@ public:
 		byte baseFreq;
 		bool stereo;
 	};
+
+	enum SynthType {
+		kSquareWaveSynth = 1,
+		kWaveTableSynth = 3,
+		kSampledSynth = 5
+	};
+
+	typedef int ChanHandle;
 public:
-	MacLowLevelPCMDriver(Common::Mutex &mutex, uint32 deviceRate, bool enableInterpolation, bool internal16Bit);
+	MacLowLevelPCMDriver(Common::Mutex &mutex, uint32 deviceRate, bool internal16Bit);
+	~MacLowLevelPCMDriver() override;
+
 	void feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) override;
-	void play(PCMSound *snd);
-	void stop();
+
+	ChanHandle createChannel(Audio::Mixer::SoundType sndType, SynthType synthType, bool interpolate);
+	void disposeChannel(ChanHandle handle);
+
+	void playSamples(ChanHandle handle, PCMSound *snd);
+	void stop(ChanHandle handle);
+
+	uint8 getChannelStatus(ChanHandle handle) const { Channel *ch = findChannel(handle); return ch ? ch->_flags : 0; }
 private:
 	uint32 calcRate(uint32 outRate, uint32 factor, uint32 dataRate);
+	void updateStatus();
 
-	Common::SharedPtr<const int8> _res;
-	const int8 *_data;
-	const bool _interp;
-	int8 _lastSmp[2];
-	uint32 _len;
-	uint16 _rmH;
-	uint16 _rmL;
-	uint32 _loopSt;
-	uint32 _loopEnd;
-	byte _baseFreq;
-	uint32 _rcPos;
-	uint32 _smpWtAcc;
-	uint16 _frameSize;
+private:
+	class Channel {
+	public:
+		Channel(Audio::Mixer::SoundType sndtp, int synth, bool interp);
+		ChanHandle getHandle() const;
+		void stop();
+
+		const Audio::Mixer::SoundType _sndType;
+		const int _synth;
+		const bool _interpolate;
+
+		Common::SharedPtr<const int8> _res;
+		const int8 *_data;
+		int8 _lastSmp[2];
+		uint32 _len;
+		uint16 _rmH;
+		uint16 _rmL;
+		uint32 _loopSt;
+		uint32 _loopEnd;
+		byte _baseFreq;
+		uint32 _rcPos;
+		uint32 _smpWtAcc;
+		uint16 _frameSize;
+		uint8 _flags;
+	};
+
+	void setChanFlags(Channel *ch, uint8 flags) { if (ch) ch->_flags |= flags; }
+	void clearChanFlags(Channel *ch, uint8 flags) { if (ch) ch->_flags &= ~flags; }
+	Channel *findChannel(ChanHandle h) const;
+	Common::Array<Channel*> _channels;
+	int _numInternalMixChannels;
+	int32 *_mixBuffer = 0;
+	uint32 _mixBufferSize;
 };
 
 class VblTaskClientDriver {
 public:
 	virtual void callback() = 0;
 	virtual void generateData(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) const = 0;
-	virtual const MacSoundDriver::Caps &getDriverCaps(uint8 drvID) const = 0;
+	virtual const MacSoundDriver::Status &getDriverStatus(uint8 drvID) const = 0;
 };
 
 class MacPlayerAudioStream : public Audio::AudioStream {
@@ -135,7 +173,7 @@ private:
 	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), interpolate(false) {}
+		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;
@@ -146,14 +184,12 @@ private:
 		uint32 rateConvInt;
 		uint32 rateConvFrac;
 		int32 rateConvAcc;
-		bool interpolate;
 	} _buffers[2];
 
 	const uint32 _outputRate;
 	const uint8 _frameSize;
 	const bool _interp;
 	const int _smpInternalSize;
-	const int _volDown;
 
 	const bool _isStereo;
 };
@@ -181,7 +217,7 @@ public:
 	void nextTick();
 	void callback() override { nextTick(); }
 	void generateData(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) const override;
-	const MacSoundDriver::Caps &getDriverCaps(uint8 drvID) const override;
+	const MacSoundDriver::Status &getDriverStatus(uint8 drvID) const override;
 
 private:
 	void startSong(int id);
@@ -216,6 +252,7 @@ private:
 	bool _mixerThread;
 
 	MacLowLevelPCMDriver::PCMSound _pcmSnd;
+	MacLowLevelPCMDriver::ChanHandle _handle;
 
 	MacPlayerAudioStream *_macstr;
 	Audio::SoundHandle _soundHandle;
diff --git a/engines/scumm/players/player_mac_new.cpp b/engines/scumm/players/player_mac_new.cpp
index 0c414e50500..4ed4af4f1a3 100644
--- a/engines/scumm/players/player_mac_new.cpp
+++ b/engines/scumm/players/player_mac_new.cpp
@@ -38,7 +38,7 @@ namespace Scumm {
 
 MacPlayerAudioStream::MacPlayerAudioStream(VblTaskClientDriver *drv, uint32 scummVMOutputrate, bool stereo, bool interpolate, bool internal16Bit) : Audio::AudioStream(), _drv(drv),
 	_vblSmpQty(0), _vblSmpQtyRem(0), _frameSize((stereo ? 2 : 1) * (internal16Bit ? 2 : 1)), _vblCountDown(0), _vblCountDownRem(0), _outputRate(scummVMOutputrate),
-		_vblCbProc(nullptr), _isStereo(stereo), _interp(interpolate), _smpInternalSize(internal16Bit ? 2 : 1), _volDown(internal16Bit ? 2 : 0) {
+		_vblCbProc(nullptr), _isStereo(stereo), _interp(interpolate), _smpInternalSize(internal16Bit ? 2 : 1) {
 	assert(_drv);
 	_vblSmpQty = (_outputRate << 16) / VBL_UPDATE_RATE;
 	_vblSmpQtyRem = (_outputRate << 16) % VBL_UPDATE_RATE;
@@ -67,11 +67,10 @@ void MacPlayerAudioStream::initDrivers() {
 	for (int i = 0; i < 2; ++i) {
 		if (!_drv)
 			error("MacPlayerAudioStream::initDrivers(): Failed to query device rate for device %d", i);
-		uint64 irt = (uint64)_drv->getDriverCaps(i).deviceRate * (1 << RATECNV_BIT_PRECSN) / _outputRate;
+		uint64 irt = (uint64)_drv->getDriverStatus(i).deviceRate * (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;
-		_buffers[i].interpolate = _interp && _drv->getDriverCaps(i).allowInterPolation;
 	}
 }
 
@@ -118,19 +117,23 @@ int MacPlayerAudioStream::readBuffer(int16 *buffer, const int numSamples) {
 		int32 smpL = 0;
 		int32 smpR = 0;
 		for (int ii = 0; ii < 2; ++ii) {
+			int numch = _drv->getDriverStatus(ii).numExternalMixChannels;
+			bool interp = _interp && _drv->getDriverStatus(ii).allowInterPolation;
+			if (!numch)
+				continue;
+
 			int smpN = _smpInternalSize == 2 ? *reinterpret_cast<int16*>(_buffers[ii].pos) : _buffers[ii].pos[0];
 			int diff = smpN - _buffers[ii].lastL;
-			if (diff && _buffers[ii].rateConvAcc && _buffers[ii].interpolate)
+			if (diff && _buffers[ii].rateConvAcc && interp)
 				diff = (diff * _buffers[ii].rateConvAcc) >> RATECNV_BIT_PRECSN;
-			smpL += (int32)((_buffers[ii].lastL + diff) * (_buffers[ii].volume >> _volDown));
-		}
-		if (_isStereo) {
-			for (int ii = 0; ii < 2; ++ii) {
-				int smpN = _smpInternalSize == 2 ? *reinterpret_cast<int16*>(&_buffers[ii].pos[2]) : _buffers[ii].pos[1];
-				int diff = smpN - _buffers[ii].lastR;
-				if (diff && _buffers[ii].rateConvAcc && _buffers[ii].interpolate)
+			smpL += (int32)((_buffers[ii].lastL + diff) * (_buffers[ii].volume / numch));
+
+			if (_isStereo) {
+				smpN = _smpInternalSize == 2 ? *reinterpret_cast<int16*>(&_buffers[ii].pos[2]) : _buffers[ii].pos[1];
+				diff = smpN - _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 >> _volDown));
+				smpR += (int32)((_buffers[ii].lastR + diff) * (_buffers[ii].volume / numch));
 			}
 		}
 
@@ -167,9 +170,9 @@ int MacPlayerAudioStream::readBuffer(int16 *buffer, const int numSamples) {
 			}
 		}
 
-		*buffer++ = CLIP<int16>(smpL >> 8, -32768, 32767);
+		*buffer++ = CLIP<int32>(smpL >> 8, -32768, 32767);
 		if (_isStereo)
-			*buffer++ = CLIP<int16>(smpR >> 8, -32768, 32767);
+			*buffer++ = CLIP<int32>(smpR >> 8, -32768, 32767);
 	}
 	return numSamples;
 }
@@ -184,83 +187,153 @@ void MacPlayerAudioStream::runVblTask() {
 		(*_vblCbProc)();
 }
 
-MacLowLevelPCMDriver::MacLowLevelPCMDriver(Common::Mutex &mutex, uint32 deviceRate, bool enableInterpolation, bool internal16Bit) :
-	MacSoundDriver(mutex, deviceRate, true, internal16Bit), _interp(enableInterpolation), _frameSize(1), _len(0), _rmH(0), _rmL(0), _smpWtAcc(0), _loopSt(0),
-		_loopEnd(0), _baseFreq(0), _rcPos(0), _data(nullptr) {
-			_lastSmp[0] = _lastSmp[1] = 0;
+MacLowLevelPCMDriver::MacLowLevelPCMDriver(Common::Mutex &mutex, uint32 deviceRate, bool internal16Bit) :
+	MacSoundDriver(mutex, deviceRate, 0, true, internal16Bit), _numInternalMixChannels(1), _mixBufferSize(0), _mixBuffer(nullptr) {
+}
+
+MacLowLevelPCMDriver::~MacLowLevelPCMDriver() {
+	for (Common::Array<Channel*>::const_iterator i = _channels.begin(); i != _channels.end(); ++i) {
+		(*i)->stop();
+		delete *i;
+	}
+	delete[] _mixBuffer;
 }
 
 void MacLowLevelPCMDriver::feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) {
-	if (dst == nullptr || type != Audio::Mixer::kSFXSoundType)
+	if (dst == nullptr)
 		return;
 
-	memset(dst, 0, byteSize);
-
-	if (_data == nullptr)
-		return;
+	uint32 mixBufferReqSize = byteSize / _smpSize;
+	assert(!(byteSize & (_smpSize - 1)));
 
-	int32 diff = 0;
+	if (mixBufferReqSize > _mixBufferSize) {
+		delete[] _mixBuffer;
+		_mixBufferSize = mixBufferReqSize;
+		_mixBuffer = new int32[mixBufferReqSize];
+	}
+	memset(_mixBuffer, 0, sizeof(int32) * mixBufferReqSize);
 	uint16 destFrameSize = expectStereo ? 2 : 1;
-	bool interp = (_interp && _rmL);
-
-	for (const int8 *end = &dst[byteSize]; dst < end; ) {
-		int8 in = 0;
-		for (int i = 0; i < destFrameSize; ++i) {
-			if (i < _frameSize) {
-				in = _data[_rcPos + i];
-				if (interp && in != _lastSmp[i]) {
-					diff = in - _lastSmp[i];
-					diff = (diff * (_smpWtAcc >> 1)) >> 15;
-					in = (_lastSmp[i] + diff) & 0xff;
+	bool bufferChanged = false;
+
+	for (Common::Array<Channel*>::const_iterator itr = _channels.begin(); itr != _channels.end(); ++itr) {
+		Channel *ch = *itr;
+		if (ch->_data == nullptr || ch->_sndType != type)
+			continue;
+
+		bufferChanged = true;
+		int32 diff = 0;
+		bool interp = (ch->_interpolate && ch->_rmL);
+		int32 *tmp = _mixBuffer;
+
+		for (const int32 *end = &tmp[mixBufferReqSize]; tmp < end; ) {
+			int8 in = 0;
+			for (int i = 0; i < destFrameSize; ++i) {
+				if (i < ch->_frameSize) {
+					in = ch->_data[ch->_rcPos + i];
+					if (interp && in != ch->_lastSmp[i]) {
+						diff = in - ch->_lastSmp[i];
+						diff = (diff * (ch->_smpWtAcc >> 1)) >> 15;
+						in = (ch->_lastSmp[i] + diff) & 0xff;
+					}
 				}
+				*tmp++ += in;
 			}
-			if (_smpSize == 2)
-				*reinterpret_cast<int16*>(dst) = in << 2;
-			else
-				*dst = in;
-			dst += _smpSize;
-		}
-		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];
-		}
+			uint32 lpos = ch->_rcPos;
+			ch->_rcPos += (ch->_rmH * ch->_frameSize);
+			ch->_smpWtAcc += ch->_rmL;
+			if (ch->_smpWtAcc > 0xffff) {
+				ch->_smpWtAcc &= 0xffff;
+				ch->_rcPos += ch->_frameSize;
+			}
+
+			if (interp && ch->_rcPos >= lpos + ch->_frameSize) {
+				for (int i = 0; i < ch->_frameSize; ++i)
+					ch->_lastSmp[i] = ch->_data[ch->_rcPos - ch->_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;
+			if (ch->_rcPos >= ch->_len) {
+				if (ch->_loopSt && ch->_loopEnd) {
+					lpos = ch->_rcPos;
+					ch->_rcPos = ch->_loopSt + (ch->_rcPos - ch->_len);
+					ch->_len = ch->_loopEnd;
+					for (int i = 0; i < ch->_frameSize; ++i)
+						ch->_lastSmp[i] = ch->_data[(ch->_rcPos >= ch->_loopSt + ch->_frameSize - i ? ch->_rcPos : ch->_len) - ch->_frameSize + i];
+					/*ch->_lastSmp[0] = ch->_data[ch->_rcPos];
+					if ((ch->_len - ch->_rcPos) > 1)
+						ch->_lastSmp[1] = ch->_data[ch->_rcPos + 1];
+					ch->_smpWtAcc = 0;*/
+				} else {
+					ch->_data = nullptr;
+					ch->_res.reset();
+					end = tmp;
+				}
+				setChanFlags(ch, kStatusDone);
 			}
-			setFlags(kStatusDone);
 		}
 	}
+
+	if (!bufferChanged)
+		return;
+
+	const int32 *src = _mixBuffer;
+	for (const int8 *end = &dst[byteSize]; dst < end; ++src) {
+		if (_smpSize == 2)
+			*reinterpret_cast<int16*>(dst) += CLIP<int32>(*src, _smpMin, _smpMax);
+		else
+			*dst += CLIP<int32>(*src / _numInternalMixChannels, _smpMin, _smpMax);
+		dst += _smpSize;
+	}
 }
 
-void MacLowLevelPCMDriver::play(PCMSound *snd) {
+MacLowLevelPCMDriver::ChanHandle MacLowLevelPCMDriver::createChannel(Audio::Mixer::SoundType sndType, SynthType synthType, bool interpolate) {
+	Common::StackLock lock(_mutex);
+	Channel *ch = new Channel(sndType, synthType, synthType == kSampledSynth && interpolate);
+	assert(ch);
+	_channels.push_back(ch);
+	updateStatus();
+	return ch->getHandle();
+}
+
+void MacLowLevelPCMDriver::disposeChannel(ChanHandle handle) {
+	Common::StackLock lock(_mutex);
+	Channel *ch = findChannel(handle);
+	if (!ch) {
+		warning("MacLowLevelPCMDriver::disposeChannel(): Channel not found");
+		return;
+	}
+
+	ch->stop();
+
+	for (Common::Array<Channel*>::iterator i = _channels.begin(); i != _channels.end(); ++i) {
+		if (*i == ch) {
+			delete *i;
+			_channels.erase(i--);
+		}
+	}
+	updateStatus();
+}
+
+void MacLowLevelPCMDriver::playSamples(ChanHandle handle, PCMSound *snd) {
 	if (!snd || !snd->data)
 		return;
 
 	Common::StackLock lock(_mutex);
+	Channel *ch = findChannel(handle);
+	if (!ch) {
+		warning("MacLowLevelPCMDriver::playSamples(): Channel not found");
+		return;
+	}
+
+	if (ch->_synth != kSampledSynth) {
+		warning("MacLowLevelPCMDriver::playSamples(): Wrong channel type");
+		return;
+	}
 
-	_res = snd->data.reinterpretCast<const int8>();
-	_data = _res.get();
-	_len = snd->len;
-	uint32 rmul = calcRate(_caps.deviceRate, 0x10000, snd->rate);
+	ch->_res = snd->data.reinterpretCast<const int8>();
+	ch->_data = ch->_res.get();
+	ch->_len = snd->len;
+	uint32 rmul = calcRate(_status.deviceRate, 0x10000, snd->rate);
 
 	if (rmul >= 0x7FFD && rmul <= 0x8003)
 		rmul = 0x8000;
@@ -272,33 +345,39 @@ void MacLowLevelPCMDriver::play(PCMSound *snd) {
 
 	assert(rmul);
 
-	_rmL = rmul & 0xffff;
-	_rmH = rmul >> 16;
+	ch->_rmL = rmul & 0xffff;
+	ch->_rmH = rmul >> 16;
 
-	_frameSize = snd->stereo ? 2 : 1;
+	ch->_frameSize = snd->stereo ? 2 : 1;
 
 	if (snd->loopend - snd->loopst < 2 || snd->loopend < snd->loopst) {
-		_loopSt = 0;
-		_loopEnd = 0;
+		ch->_loopSt = 0;
+		ch->_loopEnd = 0;
 	} else {
-		_loopSt = snd->loopst - (snd->loopst % _frameSize);
-		_loopEnd = snd->loopend - (snd->loopend % _frameSize);
+		ch->_loopSt = snd->loopst - (snd->loopst % ch->_frameSize);
+		ch->_loopEnd = snd->loopend - (snd->loopend % ch->_frameSize);
 	}
 
-	_baseFreq = snd->baseFreq;
-	_rcPos = 0;
-	_smpWtAcc = 0;
-	_lastSmp[0] = _data[0];
-	if (_len >= _frameSize)
-		_lastSmp[1] = _data[1];
+	ch->_baseFreq = snd->baseFreq;
+	ch->_rcPos = 0;
+	ch->_smpWtAcc = 0;
+	ch->_lastSmp[0] = ch->_data[0];
+	if (ch->_len >= ch->_frameSize)
+		ch->_lastSmp[1] = ch->_data[1];
 	clearFlags(kStatusDone);
+	clearChanFlags(ch, kStatusDone);
+
 }
 
-void MacLowLevelPCMDriver::stop() {
+void MacLowLevelPCMDriver::stop(ChanHandle handle) {
 	Common::StackLock lock(_mutex);
-	_data = nullptr;
-	_res.reset();
-	setFlags(kStatusDone);
+	Channel *ch = findChannel(handle);
+	if (!ch) {
+		warning("MacLowLevelPCMDriver::stop(): Channel not found");
+		return;
+	}
+	ch->stop();
+	setChanFlags(ch, kStatusDone);
 }
 
 uint32 MacLowLevelPCMDriver::calcRate(uint32 outRate, uint32 factor, uint32 dataRate) {
@@ -454,12 +533,46 @@ uint32 MacLowLevelPCMDriver::calcRate(uint32 outRate, uint32 factor, uint32 data
 	return result;
 }
 
+void MacLowLevelPCMDriver::updateStatus() {
+	_numInternalMixChannels = _smpSize > 1 ? 1 : _channels.size();
+	_status.numExternalMixChannels = _smpSize > 1 ? _channels.size() : 1;
+	_status.allowInterPolation = true;
+	for (Common::Array<Channel*>::const_iterator ch = _channels.begin(); ch != _channels.end(); ++ch) {
+		if (!(*ch)->_interpolate)
+			_status.allowInterPolation = false;
+	}
+}
+
+MacLowLevelPCMDriver::Channel *MacLowLevelPCMDriver::findChannel(ChanHandle handle) const {
+	for (Common::Array<Channel*>::const_iterator ch = _channels.begin(); ch != _channels.end(); ++ch) {
+		if ((*ch)->getHandle() == handle)
+			return *ch;
+	}
+	return nullptr;
+}
+
+MacLowLevelPCMDriver::Channel::Channel(Audio::Mixer::SoundType sndtp, int synth, bool interp) : _sndType(sndtp), _synth(synth),
+	_interpolate(interp), _frameSize(1), _len(0), _rmH(0), _rmL(0), _smpWtAcc(0), _loopSt(0), _loopEnd(0), _baseFreq(0), _rcPos(0),
+	_flags(0), _data(nullptr) {
+	_lastSmp[0] = _lastSmp[1] = 0;
+}
+
+MacLowLevelPCMDriver::ChanHandle MacLowLevelPCMDriver::Channel::getHandle() const {
+	const void *ptr = this;
+	return *reinterpret_cast<const int*>(&ptr);
+}
+
+void MacLowLevelPCMDriver::Channel::stop() {
+	_data = nullptr;
+	_res.reset();
+}
+
 class Indy3MacSnd;
 
-template<typename T> class Player_Mac_New : public MusicEngine {
+template<typename T> class MusicEngineImpl : public MusicEngine {
 public:
-	Player_Mac_New(ScummEngine *vm, Audio::Mixer *mixer);
-	~Player_Mac_New() override;
+	MusicEngineImpl(ScummEngine *vm, Audio::Mixer *mixer);
+	~MusicEngineImpl() override;
 	void setMusicVolume(int vol) override;
 	void setSfxVolume(int vol) override;
 	void startSound(int id) override;
@@ -470,63 +583,62 @@ public:
 	void setQuality(int qual) override;
 	void saveLoadWithSerializer(Common::Serializer &ser) override;
 	void restoreAfterLoad() override;
-
 private:
 	Common::SharedPtr<T> _player;
 };
 
-template <typename T> Player_Mac_New<T>::Player_Mac_New(ScummEngine *vm, Audio::Mixer *mixer) : _player(nullptr) {
+template <typename T> MusicEngineImpl<T>::MusicEngineImpl(ScummEngine *vm, Audio::Mixer *mixer) : _player(nullptr) {
 	_player = T::open(vm, mixer);
 }
 
-template <typename T> Player_Mac_New<T>::~Player_Mac_New() {
+template <typename T> MusicEngineImpl<T>::~MusicEngineImpl() {
 	_player = nullptr;
 }
 
-template <typename T> void Player_Mac_New<T>::setMusicVolume(int vol) {
+template <typename T> void MusicEngineImpl<T>::setMusicVolume(int vol) {
 	if (_player != nullptr)
 		_player->setMusicVolume(vol);
 }
 
-template <typename T> void Player_Mac_New<T>::setSfxVolume(int vol) {
+template <typename T> void MusicEngineImpl<T>::setSfxVolume(int vol) {
 	if (_player != nullptr)
 		_player->setSfxVolume(vol);
 }
 
-template <typename T> void Player_Mac_New<T>::startSound(int id) {
+template <typename T> void MusicEngineImpl<T>::startSound(int id) {
 	if (_player != nullptr)
 		_player->startSound(id);
 }
 
-template <typename T> void Player_Mac_New<T>::stopSound(int id) {
+template <typename T> void MusicEngineImpl<T>::stopSound(int id) {
 	if (_player != nullptr)
 		_player->stopSound(id);
 }
 
-template <typename T> void Player_Mac_New<T>::stopAllSounds() {
+template <typename T> void MusicEngineImpl<T>::stopAllSounds() {
 	if (_player != nullptr)
 		_player->stopAllSounds();
 }
 
-template <typename T> int Player_Mac_New<T>::getMusicTimer() {
+template <typename T> int MusicEngineImpl<T>::getMusicTimer() {
 	return (_player != nullptr) ? _player->getMusicTimer() : 0;
 }
 
-template <typename T> int Player_Mac_New<T>::getSoundStatus(int id) const {
+template <typename T> int MusicEngineImpl<T>::getSoundStatus(int id) const {
 	return (_player != nullptr) ? _player->getSoundStatus(id) : 0;
 }
 
-template <typename T> void Player_Mac_New<T>::setQuality(int qual) {
+template <typename T> void MusicEngineImpl<T>::setQuality(int qual) {
 	if (_player != nullptr)
 		_player->setQuality(qual);
 }
 
-template <typename T> void Player_Mac_New<T>::saveLoadWithSerializer(Common::Serializer &ser) {
+template <typename T> void MusicEngineImpl<T>::saveLoadWithSerializer(Common::Serializer &ser) {
 	if (_player != nullptr)
 		_player->saveLoadWithSerializer(ser);
 }
 
-template <typename T> void Player_Mac_New<T>::restoreAfterLoad() {
+template <typename T> void MusicEngineImpl<T>::restoreAfterLoad() {
 	if (_player != nullptr)
 		_player->restoreAfterLoad();
 }
@@ -536,7 +648,7 @@ MusicEngine *createPlayer(ScummEngine *vm) {
 	assert(vm);
 	assert(vm->_mixer);
 	if (vm->_game.id == GID_INDY3)
-		return new Player_Mac_New<Indy3MacSnd>(vm, vm->_mixer);
+		return new MusicEngineImpl<Indy3MacSnd>(vm, vm->_mixer);
 
 	return nullptr;
 }
@@ -544,7 +656,6 @@ MusicEngine *createPlayer(ScummEngine *vm) {
 
 #undef ASC_DEVICE_RATE
 #undef VBL_UPDATE_RATE
-#undef PCM_BUFFER_SIZE
 #undef PCM_BUFFER_RESERVE
 #undef RATECNV_BIT_PRECSN
 


Commit: aee2bf53bbe450c0aca8b57221459e1312d556df
    https://github.com/scummvm/scummvm/commit/aee2bf53bbe450c0aca8b57221459e1312d556df
Author: athrxx (athrxx at scummvm.org)
Date: 2024-02-28T21:01:41+01:00

Commit Message:
SCUMM: (Loom/Mac) - add new sound player

This  should provide more accurate and feature complete
sound than the existing player (which is still there, just
disabled). I haven't implemented a proper quality selection
method yet.

Changed paths:
  A engines/scumm/players/player_mac_loom.cpp
    engines/scumm/macgui/macgui_loom.cpp
    engines/scumm/module.mk
    engines/scumm/players/player_mac.cpp
    engines/scumm/players/player_mac_indy3.cpp
    engines/scumm/players/player_mac_intern.h
    engines/scumm/players/player_mac_new.cpp
    engines/scumm/players/player_mac_new.h
    engines/scumm/saveload.cpp
    engines/scumm/scumm.cpp


diff --git a/engines/scumm/macgui/macgui_loom.cpp b/engines/scumm/macgui/macgui_loom.cpp
index 08cba46e696..693a601e08b 100644
--- a/engines/scumm/macgui/macgui_loom.cpp
+++ b/engines/scumm/macgui/macgui_loom.cpp
@@ -636,7 +636,6 @@ bool MacLoomGui::runOptionsDialog() {
 	int scrolling = _vm->_snapScroll == 0;
 	int fullAnimation = _vm->VAR(_vm->VAR_MACHINE_SPEED) == 1 ? 0 : 1;
 	int textSpeed = _vm->_defaultTextSpeed;
-	int musicQuality = (ConfMan.hasKey("mac_v3_low_quality_music") && ConfMan.getBool("mac_v3_low_quality_music")) ? 0 : (_vm->VAR(_vm->VAR_SOUNDCARD) == 10 ? 0 : 2);
 
 	MacDialogWindow *window = createDialog(1000);
 
@@ -652,7 +651,7 @@ bool MacLoomGui::runOptionsDialog() {
 	window->setWidgetValue(11, textSpeed);
 
 	window->addPictureSlider(8, 9, true, 5, 69, 0, 2, 6, 4);
-	window->setWidgetValue(12, musicQuality);
+	window->setWidgetValue(12, 2/* TODO: save var*/);
 
 	// Machine rating
 	window->addSubstitution(Common::String::format("%d", _vm->VAR(53)));
@@ -721,11 +720,12 @@ bool MacLoomGui::runOptionsDialog() {
 		// selection 0 activates the low quality channel in
 		// the sequence files and mutes everything else)
 		//
-		// This is currently incomplete. Let's just set the proper
-		// value for VAR_SOUNDCARD...
-		_vm->VAR(_vm->VAR_SOUNDCARD) = window->getWidgetValue(12) == 0 ? 10 : 11;
-		((Player_V3M *)_vm->_musicEngine)->overrideQuality(_vm->VAR(_vm->VAR_SOUNDCARD) == 10);
-		ConfMan.setBool("mac_v3_low_quality_music", _vm->VAR(_vm->VAR_SOUNDCARD) == 10);
+
+		//_vm->VAR(_vm->VAR_SOUNDCARD) = window->getWidgetValue(12) == 0 ? 10 : 11;
+		//((Player_V3M *)_vm->_musicEngine)->overrideQuality(_vm->VAR(_vm->VAR_SOUNDCARD) == 10);
+		int musicQuality = (ConfMan.hasKey("mac_v3_low_quality_music") && ConfMan.getBool("mac_v3_low_quality_music")) ? 0 : (_vm->VAR(_vm->VAR_SOUNDCARD) == 10 ? 0 : 2);
+		_vm->_musicEngine->setQuality(musicQuality * 3 + 1 + window->getWidgetValue(12));
+		//ConfMan.setBool("mac_v3_low_quality_music", _vm->VAR(_vm->VAR_SOUNDCARD) == 10);
 
 		debug(6, "MacLoomGui::runOptionsDialog(): music quality: %d - unimplemented!", window->getWidgetValue(12));
 
diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index abea3f04492..4ce393f1ac1 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -54,6 +54,7 @@ MODULE_OBJS := \
 	players/player_he.o \
 	players/player_mac.o \
 	players/player_mac_indy3.o \
+	players/player_mac_loom.o \
 	players/player_mac_new.o \
 	players/player_mod.o \
 	players/player_nes.o \
diff --git a/engines/scumm/players/player_mac.cpp b/engines/scumm/players/player_mac.cpp
index 50f6288df25..bb0aa54e363 100644
--- a/engines/scumm/players/player_mac.cpp
+++ b/engines/scumm/players/player_mac.cpp
@@ -119,8 +119,20 @@ void Player_Mac::saveLoadWithSerializer(Common::Serializer &s) {
 		uint32 mixerSampleRate = _sampleRate;
 		int i;
 
-		s.syncAsUint32LE(_sampleRate, VER(94));
-		s.syncAsSint16LE(_soundPlaying, VER(94));
+		if (s.getVersion() > VER(113)) {
+			if (s.isLoading())
+				warning ("Player_Mac::saveLoadWithSerializer(): Incompatible savegame version. Sound may glitch");
+			byte tmp[200];
+			s.syncBytes(tmp, 200);
+			_soundPlaying = 0;
+			for (i = 1; !_soundPlaying && i < 200; ++i) {
+				if (tmp[i])
+					_soundPlaying = i;
+			}
+		}
+
+		s.syncAsUint32LE(_sampleRate, VER(94), VER(113));
+		s.syncAsSint16LE(_soundPlaying, VER(94), VER(113));
 
 		if (s.isLoading() && _soundPlaying != -1) {
 			const byte *ptr = _vm->getResourceAddress(rtSound, _soundPlaying);
@@ -128,9 +140,9 @@ void Player_Mac::saveLoadWithSerializer(Common::Serializer &s) {
 			loadMusic(ptr);
 		}
 
-		s.syncArray(_channel, _numberOfChannels, syncWithSerializer);
+		s.syncArray(_channel, _numberOfChannels, syncWithSerializer, VER(94), VER(113));
 		for (i = 0; i < _numberOfChannels; i++) {
-			if (s.getVersion() >= 94 && s.getVersion() <= 103) {
+			if (s.getVersion() >= VER(94) && s.getVersion() <= VER(103)) {
 				// It was always the intention to save the instrument entries
 				// here. Unfortunately there was a regression in late 2017 that
 				// caused the channel data to be saved a second time, instead
@@ -139,7 +151,7 @@ void Player_Mac::saveLoadWithSerializer(Common::Serializer &s) {
 
 				_channel[i]._instrument._pos = 0;
 				_channel[i]._instrument._subPos = 0;
-			} else {
+			} else if (s.getVersion() < VER(114)) {
 				syncWithSerializer(s, _channel[i]._instrument);
 			}
 		}
diff --git a/engines/scumm/players/player_mac_indy3.cpp b/engines/scumm/players/player_mac_indy3.cpp
index 2e6e6126337..f90407982fb 100644
--- a/engines/scumm/players/player_mac_indy3.cpp
+++ b/engines/scumm/players/player_mac_indy3.cpp
@@ -39,7 +39,9 @@ extern const uint32 g_pv2ModTblSize;
 
 class LegacyMusicDriver : public MacSoundDriver {
 public:
-	LegacyMusicDriver(uint16 numChannels, Common::Mutex &mutex, bool canInterpolate, bool internal16Bit) : MacSoundDriver(mutex, ASC_DEVICE_RATE, internal16Bit ? numChannels : 1, canInterpolate, internal16Bit), _numChan(numChannels) {}
+	LegacyMusicDriver(uint16 numChannels, Common::Mutex &mutex, bool canInterpolate, bool internal16Bit) :
+		MacSoundDriver(mutex, ASC_DEVICE_RATE, internal16Bit ? numChannels : 1, canInterpolate, internal16Bit),
+			_numChan(numChannels), _sndType(Audio::Mixer::kMusicSoundType) {}
 	virtual void start() = 0;
 	virtual void stop() = 0;
 
@@ -71,6 +73,7 @@ protected:
 		}
 	}
 	const uint16 _numChan;
+	const Audio::Mixer::SoundType _sndType;
 };
 
 class FourToneSynthDriver final : public LegacyMusicDriver {
@@ -144,7 +147,7 @@ FourToneSynthDriver::~FourToneSynthDriver() {
 }
 
 void FourToneSynthDriver::feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) {
-	if (dst == nullptr || type != Audio::Mixer::kMusicSoundType)
+	if (dst == nullptr || type != _sndType)
 		return;
 
 	const int8 *end = &dst[byteSize];
@@ -206,7 +209,7 @@ void FourToneSynthDriver::send(int dataType, ...)  {
 		break;
 	}
 
-	_status.flags |= kStatusOverflow;
+	setFlags(kStatusOverflow);
 
 	va_end(arg);
 }
@@ -241,7 +244,7 @@ SquareWaveSynthDriver::SquareWaveSynthDriver(Common::Mutex &mutex, bool internal
 }
 
 void SquareWaveSynthDriver::feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) {
-	if (dst == nullptr || type != Audio::Mixer::kMusicSoundType)
+	if (dst == nullptr || type != _sndType)
 		return;
 
 	Common::Array<Triplet>::iterator t = _tripletsQueue.begin();
@@ -303,7 +306,7 @@ void SquareWaveSynthDriver::send(int dataType, ...)  {
 }
 
 void SquareWaveSynthDriver::addTriplet(uint16 frequency, uint16 amplitude) {
-	if ((_status.flags & kStatusStartup) && frequency < 3)
+	if ((getStatus().flags & kStatusStartup) && frequency < 3)
 		return;
 
 	clearFlags(kStatusStartup);
@@ -313,7 +316,7 @@ void SquareWaveSynthDriver::addTriplet(uint16 frequency, uint16 amplitude) {
 	if (_lastPara.amplitude == 0xffff)
 		_lastPara.amplitude = amplitude;
 
-	if ((_status.flags & kStatusPlaying) && _tripletsQueue.size() < 176) {
+	if ((getStatus().flags & kStatusPlaying) && _tripletsQueue.size() < 176) {
 		if (frequency >> 3 != _lastPara.count >> 3 || amplitude != _lastPara.amplitude) {
 			_tripletsQueue.push_back(_lastPara.fromScumm());
 			_lastPara = Triplet(frequency, amplitude, 0);
@@ -322,7 +325,7 @@ void SquareWaveSynthDriver::addTriplet(uint16 frequency, uint16 amplitude) {
 		_lastPara.duration++;
 	}
 
-	if (!(_status.flags & kStatusPlaying) || _tripletsQueue.size() >= 176)
+	if (!(getStatus().flags & kStatusPlaying) || _tripletsQueue.size() >= 176)
 		setFlags(kStatusOverflow);
 }
 
@@ -330,9 +333,9 @@ Common::WeakPtr<Indy3MacSnd> *Indy3MacSnd::_inst = nullptr;
 
 Indy3MacSnd::Indy3MacSnd(ScummEngine *vm, Audio::Mixer *mixer) : VblTaskClientDriver(),
 	_vm(vm), _mixer(mixer), _musicChannels(nullptr), _curSound(0), _curSong(0), _lastSoundEffectPrio(0), _idRangeMax(86), _soundEffectNumLoops(-1),
-	_musicIDTable(nullptr), _macstr(nullptr), _musicIDTableLen(0), _soundUsage(0), _mdrv(nullptr), _sdrv(nullptr), _nextTickProc(this, &VblTaskClientDriver::callback),
+	_musicIDTable(nullptr), _macstr(nullptr), _musicIDTableLen(0), _soundUsage(0), _mdrv(nullptr), _sdrv(nullptr), _nextTickProc(this, &VblTaskClientDriver::vblCallback),
 	_soundEffectPlaying(false), _songTimer(0), _songTimerInternal(0), _qmode(0), _16bit(false), _qualHi(false),	_mixerThread(false), _activeChanCount(0),
-	_songUnfinished(false), _numMusicChannels(8), _numMusicTracks(4), _handle(0) {
+	_songUnfinished(false), _numMusicChannels(8), _numMusicTracks(4), _sfxChan(0) {
 	assert(_vm);
 	assert(_mixer);
 
@@ -355,7 +358,7 @@ Indy3MacSnd::~Indy3MacSnd() {
 	delete _macstr;
 	delete[] _soundUsage;
 
-	_sdrv->disposeChannel(_handle);
+	_sdrv->disposeChannel(_sfxChan);
 
 	for (Common::Array<MacSoundDriver*>::const_iterator i = _drivers.begin(); i != _drivers.end(); ++i)
 		delete *i;
@@ -397,12 +400,12 @@ bool Indy3MacSnd::startDevices(uint32 outputRate, uint32 pcmDeviceRate, uint32 f
 		return false;
 
 	for (int i = 0; i < mdrv->numChannels(); ++i)
-		mdrv->send(LegacyMusicDriver::kChanWaveform, i, _fourToneSynthWaveForm, sizeof(_fourToneSynthWaveForm));
+		mdrv->send(LegacyMusicDriver::kChanWaveform, i, _fourToneSynthWaveForm, _fourToneSynthWaveForm);
 	_qualHi = true;
 	_16bit = internal16Bit;
 	_mdrv = mdrv;
 
-	_handle = _sdrv->createChannel(Audio::Mixer::kSFXSoundType, MacLowLevelPCMDriver::kSampledSynth, enableInterpolation);
+	_sfxChan = _sdrv->createChannel(Audio::Mixer::kSFXSoundType, MacLowLevelPCMDriver::kSampledSynth, 0, nullptr);
 
 	_drivers.push_back(_mdrv);
 	_drivers.push_back(_sdrv);
@@ -472,7 +475,12 @@ int Indy3MacSnd::getSoundStatus(int id) const {
 }
 
 void Indy3MacSnd::setQuality(int qual) {
-	assert(qual >= MacSound::kQualityAuto && qual <= MacSound::kQualityLow);
+	if (qual != MacSound::kQualityAuto && qual != MacSound::kQualityLowest && qual != MacSound::kQualityHighest) {
+		warning ("Indy3MacSnd::setQuality(): Indy 3 supports only the following quality settings:"
+			" kQualityAuto, kQualityLowest and kQualityHighest. Setting is now changed to kQualityAuto");
+		qual = MacSound::kQualityAuto;
+	}
+
 	while (_qualHi == isHiQuality()) {
 		if (_qmode == qual)
 			return;
@@ -489,7 +497,7 @@ void Indy3MacSnd::setQuality(int qual) {
 		FourToneSynthDriver *mdrv = new FourToneSynthDriver(_mixer->mutex(), _16bit);
 		assert(mdrv);
 		for (int i = 0; i < mdrv->numChannels(); ++i)
-			mdrv->send(LegacyMusicDriver::kChanWaveform, i, _fourToneSynthWaveForm, sizeof(_fourToneSynthWaveForm));
+			mdrv->send(LegacyMusicDriver::kChanWaveform, i, _fourToneSynthWaveForm, _fourToneSynthWaveForm);
 		_mdrv = mdrv;
 		_qualHi = true;
 	} else {
@@ -523,7 +531,7 @@ void Indy3MacSnd::restoreAfterLoad() {
 	}
 }
 
-void Indy3MacSnd::nextTick() {
+void Indy3MacSnd::vblCallback() {
 	if (_songTimerInternal++ == 29) {
 		_songTimerInternal = 0;
 		++_songTimer;
@@ -531,7 +539,7 @@ void Indy3MacSnd::nextTick() {
 
 	_mixerThread = true;
 
-	if (!_curSong && (_sdrv->getChannelStatus(_handle) & MacSoundDriver::kStatusDone))
+	if (!_curSong && (_sdrv->getChannelStatus(_sfxChan) & MacSoundDriver::kStatusDone))
 		updateSoundEffect();
 	else if (_curSong)
 		updateSong();
@@ -548,10 +556,10 @@ void Indy3MacSnd::generateData(int8 *dst, uint32 len, Audio::Mixer::SoundType ty
 		(*i)->feed(dst, len, type, expectStereo);
 }
 
-const MacSoundDriver::Status &Indy3MacSnd::getDriverStatus(uint8 drvID) const {
+const MacSoundDriver::Status &Indy3MacSnd::getDriverStatus(uint8 drvID, Audio::Mixer::SoundType type) const {
 	if (drvID >= _drivers.size())
-		error(" Indy3MacSnd::getDriverCaps(): Invalid driver id %d", drvID);
-	return _drivers[drvID]->getStatus();
+		error("Indy3MacSnd::getDriverCaps(): Invalid driver id %d", drvID);
+	return _drivers[drvID]->getStatus(type);
 }
 
 void Indy3MacSnd::startSong(int id) {
@@ -646,7 +654,8 @@ void Indy3MacSnd::startSoundEffect(int id) {
 		return;
 
 	byte *buff = new byte[numSamples - 22];
-	memcpy(buff, ptr + spos + 22, numSamples - 22);
+	for (uint16 i = 0; i < numSamples - 22; ++i)
+		buff[i] = ptr[spos + 22 + i] ^ 0x80;
 
 	_pcmSnd.rate = 0x4E200000 / (READ_BE_UINT16(ptr + 20 + offs) >> 7);
 	_pcmSnd.data = Common::SharedPtr<const byte> (buff, Common::ArrayDeleter<const byte>());
@@ -655,7 +664,13 @@ void Indy3MacSnd::startSoundEffect(int id) {
 	_pcmSnd.loopend = numSamples - 1;
 	_pcmSnd.baseFreq = 60;
 
-	_sdrv->playSamples(_handle, &_pcmSnd);
+	_sdrv->playSamples(_sfxChan, MacLowLevelPCMDriver::kImmediate, &_pcmSnd);
+
+	if (READ_BE_UINT16(ptr + 6) || _soundEffectNumLoops == -1) {
+		_sdrv->playSamples(_sfxChan, MacLowLevelPCMDriver::kEnqueue, &_pcmSnd);
+		_sdrv->playSamples(_sfxChan, MacLowLevelPCMDriver::kEnqueue, &_pcmSnd);
+		_sdrv->playSamples(_sfxChan, MacLowLevelPCMDriver::kEnqueue, &_pcmSnd);
+	}
 
 	_curSound = id;
 	_soundUsage[id]++;
@@ -670,7 +685,8 @@ void Indy3MacSnd::stopSong() {
 
 void Indy3MacSnd::stopSoundEffect() {
 	Common::StackLock lock(_mixer->mutex());
-	_sdrv->stop(_handle);
+	_sdrv->quiet(_sfxChan, MacLowLevelPCMDriver::kImmediate);
+	_sdrv->flush(_sfxChan, MacLowLevelPCMDriver::kImmediate);
 	_soundEffectPlaying = false;
 	_lastSoundEffectPrio = 0;
 	_curSound = 0;
@@ -718,7 +734,7 @@ void Indy3MacSnd::updateSong() {
 }
 
 void Indy3MacSnd::updateSoundEffect() {
-	_sdrv->clearFlags(MacSoundDriver::kStatusDone);
+	_sdrv->clearChannelFlags(_sfxChan, MacSoundDriver::kStatusDone);
 	bool chkRestart = false;
 
 	if (!_soundEffectPlaying || !_curSound) {
@@ -727,7 +743,7 @@ void Indy3MacSnd::updateSoundEffect() {
 		if (_soundEffectNumLoops > 0)
 			--_soundEffectNumLoops;
 		if (_soundEffectNumLoops)
-			_sdrv->playSamples(_handle, &_pcmSnd);
+			_sdrv->playSamples(_sfxChan, MacLowLevelPCMDriver::kImmediate, &_pcmSnd);
 		else
 			--_soundUsage[_curSound];
 		chkRestart = (_soundEffectNumLoops == 0);
@@ -755,25 +771,6 @@ void Indy3MacSnd::checkRestartSoundEffects() {
 	}
 }
 
-const uint8 Indy3MacSnd::_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 Indy3MacSnd::endOfTrack() {
 	if (!_activeChanCount || !--_activeChanCount)
 		finishSong();
@@ -784,7 +781,7 @@ bool Indy3MacSnd::isSong(int id) const {
 }
 
 bool Indy3MacSnd::isHiQuality() const {
-	return _mixerThread ? _qualHi : (_qmode == MacSound::kQualityAuto && (_vm->VAR_SOUNDCARD == 0xff || _vm->VAR(_vm->VAR_SOUNDCARD) == 11)) || (_qmode == MacSound::kQualityHigh);
+	return _mixerThread ? _qualHi : (_qmode == MacSound::kQualityAuto && (_vm->VAR_SOUNDCARD == 0xff || _vm->VAR(_vm->VAR_SOUNDCARD) == 11)) || (_qmode == MacSound::kQualityHighest);
 }
 
 Indy3MacSnd::MusicChannel *Indy3MacSnd::getMusicChannel(uint8 id) const {
diff --git a/engines/scumm/players/player_mac_intern.h b/engines/scumm/players/player_mac_intern.h
index 324008acc03..ee5d04b26a8 100644
--- a/engines/scumm/players/player_mac_intern.h
+++ b/engines/scumm/players/player_mac_intern.h
@@ -24,24 +24,33 @@
 
 #include "audio/audiostream.h"
 #include "audio/mixer.h"
+#include "common/array.h"
+#include "common/func.h"
 
 namespace Scumm {
 
 class MacSoundDriver {
 public:
-	MacSoundDriver(Common::Mutex &mutex, uint32 deviceRate, int activeChannels, bool canInterpolate, bool internal16Bit) : _mutex(mutex), _status(deviceRate, canInterpolate, activeChannels),
-		_smpSize(internal16Bit ? 2 : 1), _smpMin(internal16Bit ? -32768 : -128), _smpMax(internal16Bit ? 32767 : 127) {}
+	MacSoundDriver(Common::Mutex &mutex, uint32 deviceRate, int activeChannels, bool canInterpolate, bool internal16Bit) : _mutex(mutex),
+		_smpSize(internal16Bit ? 2 : 1), _smpMin(internal16Bit ? -32768 : -128), _smpMax(internal16Bit ? 32767 : 127) {
+		for (int i = 0; i < 4; ++i) {
+			_status[i].deviceRate = deviceRate;
+			_status[i].numExternalMixChannels = activeChannels;
+			_status[i].allowInterPolation = canInterpolate;
+			_status[i].flags = 0;
+		}
+	}
 	virtual ~MacSoundDriver() {}
 	virtual void feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) = 0;
 
 	struct Status {
-		Status(uint32 rate, bool interp, int actChan) :deviceRate(rate), allowInterPolation(interp), numExternalMixChannels(actChan), flags(0) {}
+		Status() : deviceRate(0), numExternalMixChannels(0), allowInterPolation(false), flags(0) {}
 		uint32 deviceRate;
 		int numExternalMixChannels;
 		bool allowInterPolation;
 		uint8 flags;
 	};
-	const Status &getStatus() const { return _status; }
+	const Status &getStatus(Audio::Mixer::SoundType sndType = Audio::Mixer::kPlainSoundType) const { return _status[sndType]; }
 
 	enum StatusFlag : uint8 {
 		kStatusPlaying =		1	<<		0,
@@ -50,85 +59,94 @@ public:
 		kStatusDone =			1	<<		3
 	};
 
-	void clearFlags(uint8 flags) { _status.flags &= ~flags; }
+	void clearFlags(uint8 flags, Audio::Mixer::SoundType sndType = Audio::Mixer::kPlainSoundType) { _status[sndType].flags &= ~flags; }
 
 protected:
-	void setFlags(uint8 flags) { _status.flags |= flags; }
+	void setFlags(uint8 flags, Audio::Mixer::SoundType sndType = Audio::Mixer::kPlainSoundType) { _status[sndType].flags |= flags; }
 
 	Common::Mutex &_mutex;
 	const int _smpSize;
 	const int16 _smpMin;
 	const int16 _smpMax;
-	Status _status;
+	Status _status[4];
 };
 
+class MacSndChannel;
 class MacLowLevelPCMDriver final : public MacSoundDriver {
 public:
 	struct PCMSound {
-		PCMSound() : len(0), rate(0), loopst(0), loopend(0), baseFreq(0), stereo(false) {}
+		PCMSound() : len(0), rate(0), loopst(0), loopend(0), baseFreq(0), stereo(false), enc(0) {}
+		PCMSound(Common::SharedPtr<const byte> a, uint32 b, uint32 c, uint32 d, uint32 e, byte f, byte g, bool h) : data(a), len(b), rate(c), loopst(d), loopend(e), baseFreq(f), enc(g), stereo(h) {}
 		Common::SharedPtr<const byte> data;
 		uint32 len;
 		uint32 rate;
 		uint32 loopst;
 		uint32 loopend;
 		byte baseFreq;
+		byte enc;
 		bool stereo;
 	};
 
-	enum SynthType {
+	enum SynthType : byte {
 		kSquareWaveSynth = 1,
 		kWaveTableSynth = 3,
-		kSampledSynth = 5
+		kSampledSynth = 5,
+		kIgnoreSynth = 0xff
+	};
+
+	enum ChanAttrib : byte {
+		kInitChanLeft = 2,
+		kInitChanRight = 3,
+		kWaveInitChannel0 = 4,
+		kWaveInitChannel1 = 5,
+		kWaveInitChannel2 = 6,
+		kWaveInitChannel3 = 7,
+		kNoInterp = 4,
+		kInitNoDrop = 8,
+		kInitMono = 0x80,
+		kInitStereo = 0xC0
+	};
+
+	enum ExecMode : byte {
+		kImmediate,
+		kEnqueue,
 	};
 
 	typedef int ChanHandle;
+
+	class CallbackClient {
+	public:
+		virtual ~CallbackClient() {}
+		virtual void sndChannelCallback(uint16 arg1, const void *arg2) = 0;
+	};
+	typedef Common::Functor2Mem<uint16, const void*, void, CallbackClient> ChanCallback;
+
 public:
 	MacLowLevelPCMDriver(Common::Mutex &mutex, uint32 deviceRate, bool internal16Bit);
 	~MacLowLevelPCMDriver() override;
 
 	void feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) override;
 
-	ChanHandle createChannel(Audio::Mixer::SoundType sndType, SynthType synthType, bool interpolate);
+	ChanHandle createChannel(Audio::Mixer::SoundType sndType, SynthType synthType, byte attributes, ChanCallback *callback);
 	void disposeChannel(ChanHandle handle);
 
-	void playSamples(ChanHandle handle, PCMSound *snd);
-	void stop(ChanHandle handle);
+	void playSamples(ChanHandle handle, ExecMode mode, const PCMSound *snd);
+	void playNote(ChanHandle handle, ExecMode mode, uint8 note, uint16 duration);
+	void quiet(ChanHandle handle, ExecMode mode);
+	void flush(ChanHandle handle, ExecMode mode);
+	void loadWaveTable(ChanHandle handle, ExecMode mode, const byte *data, uint16 dataSize);
+	void loadInstrument(ChanHandle handle, ExecMode mode, const PCMSound *snd);
+	void setTimbre(ChanHandle handle, ExecMode mode, uint16 timbre);
+	void callback(ChanHandle handle, ExecMode mode, uint16 arg1, const void *arg2);
 
-	uint8 getChannelStatus(ChanHandle handle) const { Channel *ch = findChannel(handle); return ch ? ch->_flags : 0; }
-private:
-	uint32 calcRate(uint32 outRate, uint32 factor, uint32 dataRate);
-	void updateStatus();
+	uint8 getChannelStatus(ChanHandle handle) const;
+	void clearChannelFlags(ChanHandle handle, uint8 flags);
 
 private:
-	class Channel {
-	public:
-		Channel(Audio::Mixer::SoundType sndtp, int synth, bool interp);
-		ChanHandle getHandle() const;
-		void stop();
-
-		const Audio::Mixer::SoundType _sndType;
-		const int _synth;
-		const bool _interpolate;
-
-		Common::SharedPtr<const int8> _res;
-		const int8 *_data;
-		int8 _lastSmp[2];
-		uint32 _len;
-		uint16 _rmH;
-		uint16 _rmL;
-		uint32 _loopSt;
-		uint32 _loopEnd;
-		byte _baseFreq;
-		uint32 _rcPos;
-		uint32 _smpWtAcc;
-		uint16 _frameSize;
-		uint8 _flags;
-	};
-
-	void setChanFlags(Channel *ch, uint8 flags) { if (ch) ch->_flags |= flags; }
-	void clearChanFlags(Channel *ch, uint8 flags) { if (ch) ch->_flags &= ~flags; }
-	Channel *findChannel(ChanHandle h) const;
-	Common::Array<Channel*> _channels;
+	void updateStatus(Audio::Mixer::SoundType sndType);	
+	MacSndChannel *findAndCheckChannel(ChanHandle h, const char *caller, byte reqSynthType) const;
+	MacSndChannel *findChannel(ChanHandle h) const;
+	Common::Array<MacSndChannel*> _channels;
 	int _numInternalMixChannels;
 	int32 *_mixBuffer = 0;
 	uint32 _mixBufferSize;
@@ -136,9 +154,10 @@ private:
 
 class VblTaskClientDriver {
 public:
-	virtual void callback() = 0;
+	virtual ~VblTaskClientDriver() {}
+	virtual void vblCallback() = 0;
 	virtual void generateData(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) const = 0;
-	virtual const MacSoundDriver::Status &getDriverStatus(uint8 drvID) const = 0;
+	virtual const MacSoundDriver::Status &getDriverStatus(uint8 drvID, Audio::Mixer::SoundType sndType) const = 0;
 };
 
 class MacPlayerAudioStream : public Audio::AudioStream {
@@ -214,10 +233,9 @@ public:
 	void saveLoadWithSerializer(Common::Serializer &ser);
 	void restoreAfterLoad();
 
-	void nextTick();
-	void callback() override { nextTick(); }
+	void vblCallback() override;
 	void generateData(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) const override;
-	const MacSoundDriver::Status &getDriverStatus(uint8 drvID) const override;
+	const MacSoundDriver::Status &getDriverStatus(uint8 drvID, Audio::Mixer::SoundType sndType) const override;
 
 private:
 	void startSong(int id);
@@ -252,7 +270,7 @@ private:
 	bool _mixerThread;
 
 	MacLowLevelPCMDriver::PCMSound _pcmSnd;
-	MacLowLevelPCMDriver::ChanHandle _handle;
+	MacLowLevelPCMDriver::ChanHandle _sfxChan;
 
 	MacPlayerAudioStream *_macstr;
 	Audio::SoundHandle _soundHandle;
@@ -344,12 +362,115 @@ private:
 	const int _numMusicChannels;
 	const int _numMusicTracks;
 
-	static const uint8 _fourToneSynthWaveForm[256];
-
 public:
 	MusicChannel *getMusicChannel(uint8 id) const;
 };
 
+class LoomMacSnd final : public VblTaskClientDriver, public MacLowLevelPCMDriver::CallbackClient {
+private:
+	LoomMacSnd(ScummEngine *vm, Audio::Mixer *mixer);
+public:
+	~LoomMacSnd();
+	static Common::SharedPtr<LoomMacSnd> open(ScummEngine *scumm, Audio::Mixer *mixer);
+	bool startDevice(uint32 outputRate, uint32 pcmDeviceRate, uint32 feedBufferSize, bool enableInterpolation, bool stereo, bool internal16Bit);
+
+	void setMusicVolume(int vol);
+	void setSfxVolume(int vol);
+	void startSound(int id, int jumpToTick = 0);
+	void stopSound(int id);
+	void stopAllSounds();
+	int getMusicTimer();
+	int getSoundStatus(int id) const;
+	void setQuality(int qual);
+	void saveLoadWithSerializer(Common::Serializer &ser);
+	void restoreAfterLoad();
+
+	void vblCallback() override;
+	void generateData(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) const override;
+	const MacSoundDriver::Status &getDriverStatus(uint8, Audio::Mixer::SoundType sndType) const override;
+
+	void sndChannelCallback(uint16 arg1, const void *arg2) override;
+
+private:
+	void sendSoundCommands(const byte *data, int timeStamp);
+	void stopActiveSound();
+	void setupChannels();
+	void disposeAllChannels();
+
+	void detectQuality();
+	bool isSoundCardType10() const;
+
+	struct Instrument {
+	public:
+		Instrument(uint16 id, Common::SeekableReadStream *&in, Common::String &&name);
+		~Instrument() { _res.reset(); }
+		const MacLowLevelPCMDriver::PCMSound *data() const { return &_snd; }
+		uint16 id() const { return _id; }
+	private:
+		uint16 _id;
+		Common::SharedPtr<const byte> _res;
+		Common::String _name;
+		MacLowLevelPCMDriver::PCMSound _snd;
+	};
+
+	bool loadInstruments();
+	const Common::SharedPtr<Instrument> *findInstrument(uint16 id) const;
+	Common::Array<Common::SharedPtr<Instrument> > _instruments;
+
+	int _curSound;
+	int _songTimer;
+	byte _songTimerInternal;
+	byte *_soundUsage;
+	byte *_chanConfigTable;
+	const int _idRangeMax;
+	bool _mixerThread;
+
+	MacLowLevelPCMDriver *_sdrv;
+
+	int _machineRating;
+	int _selectedQuality;
+	int _effectiveChanConfig;
+	int _defaultChanConfig;
+	bool _16bit;
+
+	MacLowLevelPCMDriver::ChanHandle _sndChannel;
+	MacLowLevelPCMDriver::ChanHandle _musChannels[4];
+
+	MacPlayerAudioStream *_macstr;
+	Audio::SoundHandle _soundHandle;
+	MacPlayerAudioStream::CallbackProc _songTimerUpdt;
+	MacLowLevelPCMDriver::ChanCallback _chanCbProc;
+
+	ScummEngine *_vm;
+	Audio::Mixer *_mixer;
+	static Common::WeakPtr<LoomMacSnd> *_inst;
+
+	byte _curChanConfig;
+	byte _chanUse;
+	byte _curSynthType;
+	Audio::Mixer::SoundType _curSndType;
+	Audio::Mixer::SoundType _lastSndType;
+	byte _chanPlaying;
+
+	struct SoundConfig {
+		SoundConfig(LoomMacSnd *pl) : player(pl), sndRes6(0), switchable(0), sndRes10(0), chanSetup(0), timbre(0) {
+			assert(player);
+			memset(instruments, 0, sizeof(instruments));
+		}
+		void load(const byte *data);
+		byte sndRes6;
+		byte switchable;
+		byte sndRes10;
+		uint16 chanSetup;
+		uint16 timbre;
+		const Common::SharedPtr<Instrument> *instruments[5];
+		LoomMacSnd *player;
+	} _soundConfig;
+};
+
+extern const uint8 _fourToneSynthWaveForm[256];
+extern const uint32 _fourToneSynthWaveFormSize;
+
 } // End of namespace Scumm
 
 #endif
diff --git a/engines/scumm/players/player_mac_loom.cpp b/engines/scumm/players/player_mac_loom.cpp
new file mode 100644
index 00000000000..08485e04f60
--- /dev/null
+++ b/engines/scumm/players/player_mac_loom.cpp
@@ -0,0 +1,573 @@
+/* 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_new.h"
+#include "scumm/players/player_mac_intern.h"
+#include "scumm/resource.h"
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "common/punycode.h"
+#include "common/macresman.h"
+
+namespace Scumm {
+
+#define ASC_DEVICE_RATE		0x56EE8BA3
+#define PCM_BUFFER_SIZE		1024
+
+Common::WeakPtr<LoomMacSnd> *LoomMacSnd::_inst = nullptr;
+
+LoomMacSnd::LoomMacSnd(ScummEngine *vm, Audio::Mixer *mixer) : VblTaskClientDriver(),
+	_vm(vm), _mixer(mixer), _curSound(0), _macstr(nullptr), _soundUsage(0), _sdrv(nullptr), _songTimerUpdt(this, &VblTaskClientDriver::vblCallback), _soundConfig(this),
+	_songTimer(0), _songTimerInternal(0), _machineRating(0), _selectedQuality(2), _effectiveChanConfig(0), _16bit(false), _idRangeMax(200), _sndChannel(0), _chanUse(0),
+	_defaultChanConfig(0), _chanConfigTable(nullptr), _chanPlaying(0), _curChanConfig(0), _curSynthType(0), _curSndType(Audio::Mixer::kPlainSoundType),
+	_mixerThread(false), _lastSndType(Audio::Mixer::kPlainSoundType), _chanCbProc(this, &MacLowLevelPCMDriver::CallbackClient::sndChannelCallback) {
+	assert(_vm);
+	assert(_mixer);
+
+	static const byte cfgtable[] = { 0, 0, 0, 1, 4, 5, 1, 5, 6, 6, 8, 9 };
+	_chanConfigTable = new byte[sizeof(cfgtable)]();
+	memcpy(_chanConfigTable, cfgtable, sizeof(cfgtable));
+	_soundUsage = new byte[_idRangeMax]();
+	memset(_musChannels, 0, sizeof(_musChannels));
+}
+
+LoomMacSnd::~LoomMacSnd() {
+	_mixer->stopHandle(_soundHandle);
+	delete _macstr;
+	delete[] _soundUsage;
+	delete[] _chanConfigTable;
+
+	disposeAllChannels();
+	delete _sdrv;
+
+	delete _inst;
+	_inst = nullptr;
+}
+
+Common::SharedPtr<LoomMacSnd> LoomMacSnd::open(ScummEngine *vm, Audio::Mixer *mixer) {
+	Common::SharedPtr<LoomMacSnd> scp = nullptr;
+
+	if (_inst == nullptr) {
+		scp = Common::SharedPtr<LoomMacSnd>(new LoomMacSnd(vm, mixer));
+		_inst = new Common::WeakPtr<LoomMacSnd>(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->startDevice(mixer->getOutputRate(), mixer->getOutputRate() << 16/*ASC_DEVICE_RATE*/, PCM_BUFFER_SIZE, true, false, true)))
+			error("LoomMacSnd::open(): Failed to start player");
+	}
+
+	return _inst->lock();
+}
+
+bool LoomMacSnd::startDevice(uint32 outputRate, uint32 pcmDeviceRate, uint32 feedBufferSize, bool enableInterpolation, bool stereo, bool internal16Bit) {
+	_macstr = new MacPlayerAudioStream(this, outputRate, stereo, enableInterpolation, internal16Bit);
+	if (!_macstr || !_mixer)
+		return false;
+
+	if (!loadInstruments())
+		return false;
+
+	_sdrv = new MacLowLevelPCMDriver(_mixer->mutex(), pcmDeviceRate, internal16Bit);
+	if (!_sdrv)
+		return false;
+
+	_effectiveChanConfig = 9;
+	_16bit = internal16Bit;
+
+	_macstr->initDrivers();
+	_macstr->initBuffers(feedBufferSize);
+	_macstr->setVblCallback(&_songTimerUpdt);
+
+	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, _macstr, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+
+	return true;
+}
+
+void LoomMacSnd::setMusicVolume(int vol) {
+	Common::StackLock lock(_mixer->mutex());
+	if (_macstr)
+		_macstr->setMasterVolume(Audio::Mixer::kMusicSoundType, vol);
+}
+
+void LoomMacSnd::setSfxVolume(int vol) {
+	Common::StackLock lock(_mixer->mutex());
+	if (_macstr)
+		_macstr->setMasterVolume(Audio::Mixer::kSFXSoundType, vol);
+}
+
+void LoomMacSnd::startSound(int id, int jumpToTick) {
+	if (_sdrv == nullptr || id < 0 || id >= _idRangeMax) {
+		warning("LoomMacSnd::startSound(): sound id '%d' out of range (0 - %d)", id, _idRangeMax - 1);
+		return;
+	}
+
+	Common::StackLock lock(_mixer->mutex());
+
+	const byte *ptr = _vm->getResourceAddress(rtSound, id);
+	assert(ptr);
+
+	if (READ_BE_UINT16(ptr + 4) != 'so' || READ_BE_UINT32(ptr + 10)) {
+		warning("LoomMacSnd::startSound(): Sound resource '%d' cannot be played", id);
+		return;
+	}
+
+	stopActiveSound();
+	if (_chanUse <= 1)
+		disposeAllChannels();
+
+	if (!_defaultChanConfig)
+		detectQuality();
+
+	_soundConfig.load(ptr);
+	//if (_sndDisableFlags && _soundConfig.switchable)
+	//	return;
+
+	if (_soundConfig.chanSetup) {
+		_effectiveChanConfig = _soundConfig.chanSetup;
+		_curSndType = Audio::Mixer::kSFXSoundType;
+	} else {
+		_effectiveChanConfig = _defaultChanConfig;
+		_curSndType = Audio::Mixer::kMusicSoundType;
+	}
+
+	if (_lastSndType != _curSndType)
+		_curChanConfig = 0;
+
+	_curSound = id;
+	_soundUsage[id] = 1;
+
+	setupChannels();
+	sendSoundCommands(ptr, jumpToTick);
+
+	if (!jumpToTick) {
+		_songTimer = 0;
+		_songTimerInternal = 0;
+	}
+}
+
+void LoomMacSnd::stopSound(int id) {
+	if (id < 0 || id >= _idRangeMax) {
+		warning("LoomMacSnd::stopSound(): sound id '%d' out of range (0 - %d)", id, _idRangeMax - 1);
+		return;
+	}
+
+	Common::StackLock lock(_mixer->mutex());
+	_soundUsage[id] = 0;
+
+	if (id == _curSound)
+		stopActiveSound();
+}
+
+void LoomMacSnd::stopAllSounds() {
+	Common::StackLock lock(_mixer->mutex());
+	memset(_soundUsage, 0, _idRangeMax);
+	stopActiveSound();
+}
+
+int LoomMacSnd::getMusicTimer() {
+	Common::StackLock lock(_mixer->mutex());
+	return _songTimer;
+}
+
+int LoomMacSnd::getSoundStatus(int id) const {
+	if (id < 0 || id >= _idRangeMax) {
+		warning("LoomMacSnd::getSoundStatus(): sound id '%d' out of range (0 - %d)", id, _idRangeMax - 1);
+		return 0;
+	}
+	Common::StackLock lock(_mixer->mutex());
+	return _soundUsage[id];
+}
+
+void LoomMacSnd::setQuality(int qual) {
+	assert(qual >= MacSound::kQualityAuto && qual <= MacSound::kQualityHighest);
+	Common::StackLock lock(_mixer->mutex());
+
+	if (qual > MacSound::kQualityAuto) {
+		qual--;
+		_machineRating = (qual / 3) + 1;
+		_selectedQuality = qual % 3;
+		qual = _chanConfigTable[_machineRating * 3 + _selectedQuality];
+		if (qual && qual == _defaultChanConfig)
+			return;
+	}
+
+	int csnd = _curSound;
+	int32 timeStamp = csnd ? _songTimer * 1000 + ((_songTimerInternal * 1000) / 30) : 0;
+	stopActiveSound();
+	
+	detectQuality();
+	if (csnd)
+		startSound(csnd, timeStamp);
+}
+
+void LoomMacSnd::saveLoadWithSerializer(Common::Serializer &ser) {
+	if (ser.isLoading() && ser.getVersion() < VER(114)) {
+		memset(_soundUsage, 0, _idRangeMax);
+		// Skip over old driver savedata, since it is not needed here.
+		ser.skip(4);
+		// We need only this
+		ser.syncAsSint16LE(_curSound);
+		_curSound = CLIP<int>(_curSound, 0, _idRangeMax - 1);
+		if (_curSound > 0)
+			_soundUsage[_curSound] = 1;
+		// Skip the rest
+		ser.skip(ser.getVersion() >= VER(94) && ser.getVersion() <= VER(103) ? 120 : 100);
+	} else {
+		ser.syncBytes(_soundUsage, _idRangeMax, VER(114));
+	}
+}
+
+void LoomMacSnd::restoreAfterLoad() {
+	for (int i = 1; i < _idRangeMax; ++i) {
+		if (_soundUsage[i]) {
+			_soundUsage[i] = 0;
+			startSound(i);
+		}
+	}
+}
+
+void LoomMacSnd::vblCallback() {
+	if (_songTimerInternal++ == 29) {
+		_songTimerInternal = 0;
+		++_songTimer;
+	}
+}
+
+void LoomMacSnd::generateData(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) const {
+	assert(dst);
+	memset(dst, 0, len);
+	_sdrv->feed(dst, len, type, expectStereo);
+}
+
+const MacSoundDriver::Status &LoomMacSnd::getDriverStatus(uint8, Audio::Mixer::SoundType sndType) const {
+	return _sdrv->getStatus(sndType);
+}
+
+void LoomMacSnd::sndChannelCallback(uint16 arg1, const void*) {
+	// We do this a little smarter than the original player which would stop the track immediately when
+	// the first channel invoked its end-of-track callback. This would cut of the playback early, in an
+	// unpleasant way. Instead, we stop the playback not before all channels have finished.
+	_chanPlaying &= ~arg1;
+	if (_chanPlaying)
+		return;
+	stopActiveSound();
+}
+
+void LoomMacSnd::sendSoundCommands(const byte *data, int timeStamp) {
+	if (!_defaultChanConfig || !_curSound)
+		return;
+
+	if (_chanUse == 1 && _sndChannel) {
+		const byte *s = data + READ_BE_UINT16(data + 30) + 6;
+		uint16 len = READ_BE_UINT16(s - 2);
+		while (len--) {
+			uint16 p1 = READ_BE_UINT16(s);
+			s += 2;
+			uint32 p2 = 0x8f00 | *s++;
+			if (timeStamp > 0) {
+				int ts = timeStamp;
+				timeStamp = MAX<int>(0, timeStamp - p1);
+				p1 -= ts;
+			}
+			if (!timeStamp)
+				_sdrv->playNote(_sndChannel, MacLowLevelPCMDriver::kEnqueue, p2 & 0x7f, p1);
+		}
+		_sdrv->quiet(_sndChannel, MacLowLevelPCMDriver::kEnqueue);
+		_sdrv->callback(_sndChannel, MacLowLevelPCMDriver::kEnqueue, 1, nullptr);
+		_chanPlaying |= 1;
+
+	} else if (_chanUse == 4) {
+		const byte *src[4];
+		uint16 len[4];
+		int tmstmp[4];
+
+		for (int i = 0; i < 4; ++i) {
+			src[i] = nullptr;
+			len[i] = 0;
+			tmstmp[i] = timeStamp;
+			if (!_musChannels[i])
+				continue;
+			src[i] = data + READ_BE_UINT16(data + 32 + 2 * i) + 6;
+			len[i] = READ_BE_UINT16(src[i] - 2);
+		}
+
+		for (bool loop = true; loop; ) {
+			loop = false;
+			for (int i = 0; i < 4; ++i) {
+				if (!src[i] || !len[i])
+					continue;
+				uint16 p1 = READ_BE_UINT16(src[i]);
+				src[i] += 2;
+				byte note = *src[i]++;
+				uint32 p2 = (/*_curSynthType == 4 && */note == 0) ? 1 : (0x8f00 | note);
+				if (tmstmp[i] > 0) {
+					int ts = tmstmp[i];
+					tmstmp[i] = MAX<int>(0, tmstmp[i] - p1);
+					p1 -= ts;
+				}
+				loop |= static_cast<bool>(--len[i]);
+				if (!tmstmp[i])
+					_sdrv->playNote(_musChannels[i], MacLowLevelPCMDriver::kEnqueue, p2 & 0x7f, p1);
+			}
+		}
+
+		_chanPlaying = 0;
+		for (int i = 0; i < 4; ++i) {
+			if (src[i]) {
+				_sdrv->quiet(_musChannels[i], MacLowLevelPCMDriver::kEnqueue);
+				_sdrv->callback(_musChannels[i], MacLowLevelPCMDriver::kEnqueue, 1 << i, nullptr);
+				_chanPlaying |= (1 << i);
+			}
+		}
+	}
+}
+
+void LoomMacSnd::stopActiveSound() {
+	if (_sndChannel) {
+		_sdrv->quiet(_sndChannel, MacLowLevelPCMDriver::kImmediate);
+		_sdrv->flush(_sndChannel, MacLowLevelPCMDriver::kImmediate);
+	}
+
+	for (int i = 0; i < ARRAYSIZE(_musChannels); ++i) {
+		if (_musChannels[i]) {
+			_sdrv->quiet(_musChannels[i], MacLowLevelPCMDriver::kImmediate);
+			_sdrv->flush(_musChannels[i], MacLowLevelPCMDriver::kImmediate);
+		}
+	}
+
+	if (_curSound) {
+		_soundUsage[_curSound] = 0;
+		_curSound = 0;
+	}
+	_chanPlaying = 0;
+}
+
+void LoomMacSnd::setupChannels() {
+	static const byte synthType[] =	{ 0x00,	0x01, 0x02, 0x04, 0x04, 0x04, 0x02, 0x04, 0x04, 0x04 };
+	static const byte numChan[] =	{ 0x00,	0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x04, 0x04, 0x04 };
+	static const byte attrib[] =	{ 0x00, 0x00, 0x04, 0xAC, 0xA4, 0xA0, 0x04, 0xAC, 0xA4, 0xA0 };
+
+	if (!_defaultChanConfig)
+		return;
+
+	if (_curChanConfig != _effectiveChanConfig) {
+		disposeAllChannels();
+		_curChanConfig = _effectiveChanConfig;
+		_curSynthType = synthType[_curChanConfig];
+		_chanUse = numChan[_curChanConfig];
+		_lastSndType = _curSndType;
+
+		switch (_curSynthType) {
+		case 1:
+			if (_chanUse == 1 && !_sndChannel) {
+				_sndChannel = _sdrv->createChannel(_curSndType, MacLowLevelPCMDriver::kSquareWaveSynth, attrib[_curChanConfig], &_chanCbProc);
+			}
+			break;
+		case 2:
+			if (_chanUse == 1 && !_sndChannel) {
+				_sndChannel = _sdrv->createChannel(_curSndType, MacLowLevelPCMDriver::kWaveTableSynth, attrib[_curChanConfig], &_chanCbProc);
+			} else if (_chanUse == 4) {
+				for (int i = 0; i < ARRAYSIZE(_musChannels); ++i) {
+					if ((_soundConfig.instruments[i + 1] && _soundConfig.instruments[i + 1]->get()->id() == 0x2D1C) || _musChannels[i])
+						continue;
+					_musChannels[i] = _sdrv->createChannel(_curSndType, MacLowLevelPCMDriver::kWaveTableSynth, attrib[_curChanConfig] + i, &_chanCbProc);
+				}
+			}
+			break;
+		case 4:
+			if (_chanUse == 1 && !_sndChannel) {
+				_sndChannel = _sdrv->createChannel(_curSndType, MacLowLevelPCMDriver::kSampledSynth, attrib[_curChanConfig], &_chanCbProc);
+			} else if (_chanUse == 4) {
+				for (int i = 0; i < ARRAYSIZE(_musChannels); ++i) {
+					if ((_soundConfig.instruments[i + 1] && _soundConfig.instruments[i + 1]->get()->id() == 0x2D1C) || _musChannels[i])
+						continue;
+					_musChannels[i] = _sdrv->createChannel(_curSndType, MacLowLevelPCMDriver::kSampledSynth, attrib[_curChanConfig], &_chanCbProc);
+				}
+			}
+			break;
+		default:
+			break;
+		}
+	}
+
+	switch (_curSynthType) {
+	case 1:
+		if (_sndChannel)
+			_sdrv->setTimbre(_sndChannel, MacLowLevelPCMDriver::kImmediate, _soundConfig.timbre);
+		break;
+	case 2:
+		if (_chanUse == 1) {
+			if (_sndChannel)
+				_sdrv->loadWaveTable(_sndChannel, MacLowLevelPCMDriver::kImmediate, _fourToneSynthWaveForm, _fourToneSynthWaveFormSize);
+		} else {
+			for (int i = 0; i < ARRAYSIZE(_musChannels); ++i) {
+				if (_musChannels[i])
+					_sdrv->loadWaveTable(_musChannels[i], MacLowLevelPCMDriver::kImmediate, _fourToneSynthWaveForm, _fourToneSynthWaveFormSize);
+			}
+		}
+		break;
+	case 4:
+		if (_chanUse == 1) {
+			if (_sndChannel && _soundConfig.instruments[0])
+				_sdrv->loadInstrument(_sndChannel, MacLowLevelPCMDriver::kImmediate, _soundConfig.instruments[0]->get()->data());
+		} else {
+			for (int i = 0; i < ARRAYSIZE(_musChannels); ++i) {
+				if (_musChannels[i] && _soundConfig.instruments[i + 1])
+					_sdrv->loadInstrument(_musChannels[i], MacLowLevelPCMDriver::kImmediate, _soundConfig.instruments[i + 1]->get()->data());
+			}
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+void LoomMacSnd::disposeAllChannels() {
+	if (_sndChannel)
+		_sdrv->disposeChannel(_sndChannel);
+	_sndChannel = 0;
+
+	for (int i = 0; i < ARRAYSIZE(_musChannels); ++i) {
+		if (_musChannels[i])
+			_sdrv->disposeChannel(_musChannels[i]);
+		_musChannels[i] = 0;
+	}
+
+	_curChanConfig = 0;
+}
+
+void LoomMacSnd::detectQuality() {
+	if (_machineRating == 0) {
+		if (isSoundCardType10())
+			_machineRating = 1;
+		/*else if (0)
+			_machineQuality = 2;*/
+		else
+			_machineRating = 3;
+	}
+
+	_defaultChanConfig = _effectiveChanConfig = _chanConfigTable[_machineRating * 3 + _selectedQuality];
+	_curChanConfig = 0;
+	disposeAllChannels();
+	setupChannels();
+	_chanConfigTable[_machineRating * 3 + _selectedQuality] = _defaultChanConfig;
+}
+
+bool LoomMacSnd::isSoundCardType10() const {
+	return _mixerThread ? (_machineRating == 1) : (_vm->VAR_SOUNDCARD != 0xff && _vm->VAR(_vm->VAR_SOUNDCARD) == 10);
+}
+
+LoomMacSnd::Instrument::Instrument(uint16 id, Common::SeekableReadStream *&in, Common::String &&name) : _id(id), _name(name) {
+	in->seek(2);
+	uint16 numTypes = in->readUint16BE();
+	in->seek(numTypes * 6 + 4);
+	in->seek(in->readUint16BE() * 8 + numTypes * 6 + 10);
+
+	_snd.len = in->readUint32BE();
+	_snd.rate = in->readUint32BE();
+	_snd.loopst = in->readUint32BE();
+	_snd.loopend = in->readUint32BE();
+	_snd.enc = in->readByte();
+	_snd.baseFreq = in->readByte();
+
+	byte *buff = new byte[_snd.len];
+	in->read(buff, _snd.len);
+	_snd.data = Common::SharedPtr<const byte> (buff, Common::ArrayDeleter<const byte>());
+}
+
+bool LoomMacSnd::loadInstruments() {
+	static const char *tryNames[] = {
+		"Loom",
+		"Loom\xaa",
+		"Loom PPC",
+		"Loom\xaa PPC"
+	};
+
+	const Common::CodePage tryCodePages[] = {
+		Common::kMacRoman,
+		Common::kISO8859_1
+	};
+
+	Common::MacResManager resMan;
+	Common::Path resFile;
+	for (int i = 0; resFile.empty() && i < ARRAYSIZE(tryNames); ++i) {
+		for (int ii = 0; resFile.empty() && ii < ARRAYSIZE(tryCodePages); ++ii) {
+			Common::U32String fn(tryNames[i], tryCodePages[ii]);
+			resFile = Common::Path(fn.encode(Common::kUtf8));
+			if (!resMan.exists(resFile) || !resMan.open(resFile) || !resMan.hasResFork()) {
+				resMan.close();
+				resFile = Common::Path(Common::punycode_encodefilename(fn));
+				if (!resMan.exists(resFile) || !resMan.open(resFile) || !resMan.hasResFork()) {
+					resMan.close();
+					resFile.clear();
+				}
+			}
+		}
+	}
+
+	if (resFile.empty()) {
+		warning("LoomMacSnd::loadInstruments(): Loom resource fork not found");
+		return false;
+	}
+
+	Common::MacResIDArray ids = resMan.getResIDArray(MKTAG('s', 'n', 'd', ' '));
+	for (uint i = 0; i < ids.size(); ++i) {
+		Common::SeekableReadStream *str = resMan.getResource(MKTAG('s', 'n', 'd', ' '), ids[i]);
+		if (!str || str->readUint16BE() != 1) {
+			static const char *const errStr[2] = { "Failed to load", "Invalid sound resource format for" };
+			warning("LoomMacSnd::loadInstruments(): %s instrument with id 0x%04x", errStr[str ? 1 : 0], ids[i]);
+			delete str;
+			return false;
+		}
+		_instruments.push_back(Common::SharedPtr<Instrument>(new Instrument(ids[i], str, resMan.getResName(MKTAG('s', 'n', 'd', ' '), ids[i]))));
+		delete str;
+	}
+
+	return true;
+}
+
+const Common::SharedPtr<LoomMacSnd::Instrument> *LoomMacSnd::findInstrument(uint16 id) const {
+	Common::Array<Common::SharedPtr<Instrument> >::const_iterator replacement = _instruments.end();
+	for (Common::Array<Common::SharedPtr<Instrument> >::const_iterator i = _instruments.begin(); i != _instruments.end(); ++i) {
+		if ((*i)->id() == id)
+			return i;
+		else if ((*i)->id() == 0x2D1C)
+			replacement = i;
+	}
+	return (replacement != _instruments.end()) ? replacement : nullptr;
+}
+
+void LoomMacSnd::SoundConfig::load(const byte *data) {
+	sndRes6 = READ_BE_UINT16(data + 6) & 0xff;
+	switchable = READ_BE_UINT16(data + 8) >> 8;
+	sndRes10 = READ_BE_UINT16(data + 10) >> 8;
+	chanSetup = READ_BE_UINT16(data + 16);
+	timbre = READ_BE_UINT16(data + 18);
+	for (int i = 0; i < 5; ++i)
+		instruments[i] = player->findInstrument(READ_BE_UINT16(data + 20 + 2 * i));
+}
+
+#undef ASC_DEVICE_RATE
+#undef PCM_BUFFER_SIZE
+
+} // End of namespace Scumm
diff --git a/engines/scumm/players/player_mac_new.cpp b/engines/scumm/players/player_mac_new.cpp
index 4ed4af4f1a3..cbaa4082e3d 100644
--- a/engines/scumm/players/player_mac_new.cpp
+++ b/engines/scumm/players/player_mac_new.cpp
@@ -36,6 +36,110 @@ namespace Scumm {
 #define PCM_BUFFER_RESERVE	64
 #define RATECNV_BIT_PRECSN	24
 
+
+class MacSndChannel {
+public:
+	MacSndChannel(MacLowLevelPCMDriver *drv, Audio::Mixer::SoundType sndtp, int synth, bool interp, bool enableL, bool enableR, MacLowLevelPCMDriver::ChanCallback *callback);
+	MacLowLevelPCMDriver::ChanHandle getHandle() const;
+
+	void playSamples(const MacLowLevelPCMDriver::PCMSound *snd);
+	void playNote(uint8 note, uint16 duration);
+	void quiet();
+	void flush();
+	void loadWaveTable(const byte *data, uint16 dataSize);
+	void loadInstrument(const MacLowLevelPCMDriver::PCMSound *snd);
+	void setTimbre(uint16 timbre);
+	void callback(uint16 p1, const void *p2);
+
+	struct SoundCommand {
+		SoundCommand() : cmd(0), arg1(0), arg2(0), ptr(nullptr) {}
+		SoundCommand(uint8 c, uint16 p1, uint32 p2) : cmd(c), arg1(p1), arg2(p2), ptr(nullptr) {}
+		SoundCommand(uint8 c, uint16 p1, void *p2, byte ptrType) : cmd(c), arg1(p1), arg2(ptrType), ptr(p2) {}
+		uint8 cmd;
+		uint16 arg1;
+		uint32 arg2;
+		void *ptr;
+	};
+
+	void enqueueSndCmd(uint8 c, uint16 p1, uint32 p2, byte mode);
+	void enqueueSndCmd(uint8 c, uint16 p1, const void *p2, byte ptrType, byte mode);
+	bool idle() const;
+
+	void setFlags(uint8 flags) { _flags |= flags; }
+	void clearFlags(uint8 flags) { _flags &= ~flags; }
+
+	void feed(int32 *dst, uint32 dstSize, byte dstFrameSize);
+
+	const Audio::Mixer::SoundType _sndType;
+	const int _synth;
+	const bool _interpolate;
+	uint8 _flags;
+
+private:
+	void setupSound(const MacLowLevelPCMDriver::PCMSound *snd);
+	void setupRateConv(uint32 drate, uint32 mod, uint32 srate, bool ppr);
+	void startSound(uint32 tmr);
+	void processSndCmdQueue();
+	uint32 calcRate(uint32 outRate, uint32 factor, uint32 dataRate);
+	uint32 calcNoteRateAdj(int diff);
+	void makeSquareWave(int8 *dstBuff, uint16 dstSize, byte timbre);
+
+	Common::Array<SoundCommand> _sndCmdQueue;
+	MacLowLevelPCMDriver *_drv;
+	MacLowLevelPCMDriver::ChanCallback *_callback;
+	Common::SharedPtr<const int8> _res;
+	const int8 *_data;
+
+	int8 _lastSmp[2];
+	bool _enable[2];
+	uint32 _len;
+	uint16 _rmH;
+	uint16 _rmL;
+	uint32 _loopSt2;
+	uint32 _loopSt;
+	uint32 _loopEnd;
+	uint32 _loopLen;
+	byte _baseFreq;
+	uint32 _rcPos;
+	uint32 _smpWtAcc;
+	uint16 _frameSize;
+	byte _timbre;
+	uint32 _srate;
+	uint32 _phase;
+	uint32 _tmrPos;
+	uint32 _tmrInc;
+	uint32 _tmrRate;
+	uint32 _duration;
+};
+
+uint32 fixDiv2Frac(uint32 fxdvnd, uint32 fxdvs, byte prcbits) {
+	uint32 dv = fxdvnd << (prcbits - 16);
+	uint32 res = 0;
+	for (uint32 ck = fxdvs; ck; ck = ck >> 16) {
+		res = (res << 16) | (dv / fxdvs);
+		dv = (dv % fxdvs);
+		fxdvs >>= 16;
+	}
+	return res;
+}
+
+uint32 fracMul(uint32 frac, uint32 fx) {
+	uint32 a = (frac >> 30) * (fx >> 16);
+	uint32 b = (frac >> 30) * (fx & 0xffff);
+	uint32 c = ((frac >> 14) & 0xffff) * (fx >> 16);
+	uint32 d = ((frac >> 14) & 0xffff) * (fx & 0xffff);
+	uint32 e = ((frac << 2) & 0xffff) * (fx >> 16);
+	return (a << 16) + b + c + (d >> 16) + (e >> 16);
+}
+
+uint32 fixMul(uint32 fx1, uint32 fx2) {
+	uint32 a = (fx1 >> 16) * (fx2 >> 16);
+	uint32 b = (fx1 >> 16) * (fx2 & 0xffff);
+	uint32 c = (fx1 & 0xffff) * (fx2 >> 16);
+	uint32 d = (fx1 & 0xffff) * (fx2 & 0xffff);
+	return (a << 16) + b + c + (d >> 16);
+}
+
 MacPlayerAudioStream::MacPlayerAudioStream(VblTaskClientDriver *drv, uint32 scummVMOutputrate, bool stereo, bool interpolate, bool internal16Bit) : Audio::AudioStream(), _drv(drv),
 	_vblSmpQty(0), _vblSmpQtyRem(0), _frameSize((stereo ? 2 : 1) * (internal16Bit ? 2 : 1)), _vblCountDown(0), _vblCountDownRem(0), _outputRate(scummVMOutputrate),
 		_vblCbProc(nullptr), _isStereo(stereo), _interp(interpolate), _smpInternalSize(internal16Bit ? 2 : 1) {
@@ -67,7 +171,7 @@ void MacPlayerAudioStream::initDrivers() {
 	for (int i = 0; i < 2; ++i) {
 		if (!_drv)
 			error("MacPlayerAudioStream::initDrivers(): Failed to query device rate for device %d", i);
-		uint64 irt = (uint64)_drv->getDriverStatus(i).deviceRate * (1 << RATECNV_BIT_PRECSN) / _outputRate;
+		uint64 irt = (uint64)_drv->getDriverStatus(i, Audio::Mixer::kPlainSoundType).deviceRate * (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;
@@ -117,8 +221,8 @@ int MacPlayerAudioStream::readBuffer(int16 *buffer, const int numSamples) {
 		int32 smpL = 0;
 		int32 smpR = 0;
 		for (int ii = 0; ii < 2; ++ii) {
-			int numch = _drv->getDriverStatus(ii).numExternalMixChannels;
-			bool interp = _interp && _drv->getDriverStatus(ii).allowInterPolation;
+			int numch = _drv->getDriverStatus(ii, stype[ii]).numExternalMixChannels;
+			bool interp = _interp && _drv->getDriverStatus(ii, stype[ii]).allowInterPolation;
 			if (!numch)
 				continue;
 
@@ -192,10 +296,8 @@ MacLowLevelPCMDriver::MacLowLevelPCMDriver(Common::Mutex &mutex, uint32 deviceRa
 }
 
 MacLowLevelPCMDriver::~MacLowLevelPCMDriver() {
-	for (Common::Array<Channel*>::const_iterator i = _channels.begin(); i != _channels.end(); ++i) {
-		(*i)->stop();
+	for (Common::Array<MacSndChannel*>::const_iterator i = _channels.begin(); i != _channels.end(); ++i)
 		delete *i;
-	}
 	delete[] _mixBuffer;
 }
 
@@ -212,65 +314,15 @@ void MacLowLevelPCMDriver::feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundT
 		_mixBuffer = new int32[mixBufferReqSize];
 	}
 	memset(_mixBuffer, 0, sizeof(int32) * mixBufferReqSize);
-	uint16 destFrameSize = expectStereo ? 2 : 1;
 	bool bufferChanged = false;
 
-	for (Common::Array<Channel*>::const_iterator itr = _channels.begin(); itr != _channels.end(); ++itr) {
-		Channel *ch = *itr;
-		if (ch->_data == nullptr || ch->_sndType != type)
+	for (Common::Array<MacSndChannel*>::const_iterator itr = _channels.begin(); itr != _channels.end(); ++itr) {
+		MacSndChannel *ch = *itr;
+		if (ch->_sndType != type || ch->idle())
 			continue;
 
 		bufferChanged = true;
-		int32 diff = 0;
-		bool interp = (ch->_interpolate && ch->_rmL);
-		int32 *tmp = _mixBuffer;
-
-		for (const int32 *end = &tmp[mixBufferReqSize]; tmp < end; ) {
-			int8 in = 0;
-			for (int i = 0; i < destFrameSize; ++i) {
-				if (i < ch->_frameSize) {
-					in = ch->_data[ch->_rcPos + i];
-					if (interp && in != ch->_lastSmp[i]) {
-						diff = in - ch->_lastSmp[i];
-						diff = (diff * (ch->_smpWtAcc >> 1)) >> 15;
-						in = (ch->_lastSmp[i] + diff) & 0xff;
-					}
-				}
-				*tmp++ += in;
-			}
-
-			uint32 lpos = ch->_rcPos;
-			ch->_rcPos += (ch->_rmH * ch->_frameSize);
-			ch->_smpWtAcc += ch->_rmL;
-			if (ch->_smpWtAcc > 0xffff) {
-				ch->_smpWtAcc &= 0xffff;
-				ch->_rcPos += ch->_frameSize;
-			}
-
-			if (interp && ch->_rcPos >= lpos + ch->_frameSize) {
-				for (int i = 0; i < ch->_frameSize; ++i)
-					ch->_lastSmp[i] = ch->_data[ch->_rcPos - ch->_frameSize + i];
-			}
-
-			if (ch->_rcPos >= ch->_len) {
-				if (ch->_loopSt && ch->_loopEnd) {
-					lpos = ch->_rcPos;
-					ch->_rcPos = ch->_loopSt + (ch->_rcPos - ch->_len);
-					ch->_len = ch->_loopEnd;
-					for (int i = 0; i < ch->_frameSize; ++i)
-						ch->_lastSmp[i] = ch->_data[(ch->_rcPos >= ch->_loopSt + ch->_frameSize - i ? ch->_rcPos : ch->_len) - ch->_frameSize + i];
-					/*ch->_lastSmp[0] = ch->_data[ch->_rcPos];
-					if ((ch->_len - ch->_rcPos) > 1)
-						ch->_lastSmp[1] = ch->_data[ch->_rcPos + 1];
-					ch->_smpWtAcc = 0;*/
-				} else {
-					ch->_data = nullptr;
-					ch->_res.reset();
-					end = tmp;
-				}
-				setChanFlags(ch, kStatusDone);
-			}
-		}
+		ch->feed(_mixBuffer, mixBufferReqSize, expectStereo ? 2 : 1);
 	}
 
 	if (!bufferChanged)
@@ -286,101 +338,481 @@ void MacLowLevelPCMDriver::feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundT
 	}
 }
 
-MacLowLevelPCMDriver::ChanHandle MacLowLevelPCMDriver::createChannel(Audio::Mixer::SoundType sndType, SynthType synthType, bool interpolate) {
+MacLowLevelPCMDriver::ChanHandle MacLowLevelPCMDriver::createChannel(Audio::Mixer::SoundType sndType, SynthType synthType, byte attributes, ChanCallback *callback) {
 	Common::StackLock lock(_mutex);
-	Channel *ch = new Channel(sndType, synthType, synthType == kSampledSynth && interpolate);
+	MacSndChannel *ch = new MacSndChannel(this, sndType, synthType, synthType == kSampledSynth && !(attributes & kNoInterp), !(synthType == kSampledSynth && ((attributes & 3) == kInitChanRight)), !(synthType == kSampledSynth && ((attributes & 3) == kInitChanLeft)), callback);
 	assert(ch);
+
 	_channels.push_back(ch);
-	updateStatus();
+	updateStatus(sndType);
 	return ch->getHandle();
 }
 
 void MacLowLevelPCMDriver::disposeChannel(ChanHandle handle) {
 	Common::StackLock lock(_mutex);
-	Channel *ch = findChannel(handle);
-	if (!ch) {
-		warning("MacLowLevelPCMDriver::disposeChannel(): Channel not found");
+	MacSndChannel *ch = findAndCheckChannel(handle, __FUNCTION__, kIgnoreSynth);
+	if (!ch)
 		return;
-	}
 
-	ch->stop();
+	Audio::Mixer::SoundType sndType = ch->_sndType;
 
-	for (Common::Array<Channel*>::iterator i = _channels.begin(); i != _channels.end(); ++i) {
+	for (Common::Array<MacSndChannel*>::iterator i = _channels.begin(); i != _channels.end(); ++i) {
 		if (*i == ch) {
 			delete *i;
 			_channels.erase(i--);
 		}
 	}
-	updateStatus();
+	updateStatus(sndType);
 }
 
-void MacLowLevelPCMDriver::playSamples(ChanHandle handle, PCMSound *snd) {
+void MacLowLevelPCMDriver::playSamples(ChanHandle handle, ExecMode mode, const PCMSound *snd) {
 	if (!snd || !snd->data)
 		return;
 
 	Common::StackLock lock(_mutex);
-	Channel *ch = findChannel(handle);
+	MacSndChannel *ch = findAndCheckChannel(handle, __FUNCTION__, kSampledSynth);
+	if (!ch)
+		return;
+
+	ch->enqueueSndCmd(81, 0, snd, 1, mode);
+}
+
+void MacLowLevelPCMDriver::playNote(ChanHandle handle, ExecMode mode, uint8 note, uint16 duration) {
+	Common::StackLock lock(_mutex);
+	MacSndChannel *ch = findAndCheckChannel(handle, __FUNCTION__, kIgnoreSynth);
+	if (!ch)
+		return;
+
+	ch->enqueueSndCmd(40, note, duration, mode);
+}
+
+void MacLowLevelPCMDriver::quiet(ChanHandle handle, ExecMode mode) {
+	Common::StackLock lock(_mutex);
+	MacSndChannel *ch = findAndCheckChannel(handle, __FUNCTION__, kIgnoreSynth);
+	if (!ch)
+		return;
+
+	ch->enqueueSndCmd(3, 0, 0, mode);
+}
+
+void MacLowLevelPCMDriver::flush(ChanHandle handle, ExecMode mode) {
+	Common::StackLock lock(_mutex);
+	MacSndChannel *ch = findAndCheckChannel(handle, __FUNCTION__, kIgnoreSynth);
+	if (!ch)
+		return;
+	ch->enqueueSndCmd(4, 0, 0, mode);
+}
+
+void MacLowLevelPCMDriver::loadWaveTable(ChanHandle handle, ExecMode mode, const byte *data, uint16 dataSize) {
+	if (!data || !dataSize)
+		return;
+
+	Common::StackLock lock(_mutex);
+	MacSndChannel *ch = findAndCheckChannel(handle, __FUNCTION__, kWaveTableSynth);
+	if (!ch)
+		return;
+
+	ch->enqueueSndCmd(60, dataSize, data, 2, mode);
+}
+
+void MacLowLevelPCMDriver::loadInstrument(ChanHandle handle, ExecMode mode, const PCMSound *snd) {
+	if (!snd || !snd->data)
+		return;
+
+	Common::StackLock lock(_mutex);
+	MacSndChannel *ch = findAndCheckChannel(handle, __FUNCTION__, kSampledSynth);
+	if (!ch)
+		return;
+
+	ch->enqueueSndCmd(80, 0, snd, 1, mode);
+}
+
+void MacLowLevelPCMDriver::setTimbre(ChanHandle handle, ExecMode mode, uint16 timbre) {
+	Common::StackLock lock(_mutex);
+	MacSndChannel *ch = findAndCheckChannel(handle, __FUNCTION__, kSquareWaveSynth);
+	if (!ch)
+		return;
+
+	ch->enqueueSndCmd(44, timbre, 0, mode);
+}
+
+void MacLowLevelPCMDriver::callback(ChanHandle handle, ExecMode mode, uint16 arg1, const void *arg2) {
+	Common::StackLock lock(_mutex);
+	MacSndChannel *ch = findAndCheckChannel(handle, __FUNCTION__, kIgnoreSynth);
+	if (!ch)
+		return;
+
+	ch->enqueueSndCmd(13, arg1, arg2, 0, mode);
+}
+
+uint8 MacLowLevelPCMDriver::getChannelStatus(ChanHandle handle) const {
+	MacSndChannel *ch = findChannel(handle); return ch ? ch->_flags : 0;
+}
+
+void MacLowLevelPCMDriver::clearChannelFlags(ChanHandle handle, uint8 flags) {
+	MacSndChannel *ch = findChannel(handle); if (ch) ch->clearFlags(flags);
+}
+
+void MacLowLevelPCMDriver::updateStatus(Audio::Mixer::SoundType sndType) {
+	_numInternalMixChannels = _smpSize > 1 ? 1 : _channels.size();
+	_status[sndType].numExternalMixChannels = _smpSize > 1 ? _channels.size() : 1;
+	_status[sndType].allowInterPolation = true;
+	for (Common::Array<MacSndChannel*>::const_iterator ch = _channels.begin(); ch != _channels.end(); ++ch) {
+		if (!(*ch)->_interpolate)
+			_status[sndType].allowInterPolation = false;
+	}
+}
+
+MacSndChannel *MacLowLevelPCMDriver::findAndCheckChannel(ChanHandle h, const char *callingProcName, byte reqSynthType) const {
+	MacSndChannel *ch = findChannel(h);
 	if (!ch) {
-		warning("MacLowLevelPCMDriver::playSamples(): Channel not found");
+		warning("%s(): Channel not found", callingProcName);
+		return nullptr;
+	}
+
+	if (reqSynthType != kIgnoreSynth && reqSynthType != ch->_synth) {
+		warning("%s(): Wrong channel type", callingProcName);
+		return nullptr;
+	}
+
+	return ch;
+}
+
+MacSndChannel *MacLowLevelPCMDriver::findChannel(ChanHandle handle) const {
+	for (Common::Array<MacSndChannel*>::const_iterator ch = _channels.begin(); ch != _channels.end(); ++ch) {
+		if ((*ch)->getHandle() == handle)
+			return *ch;
+	}
+	return nullptr;
+}
+
+MacSndChannel::MacSndChannel(MacLowLevelPCMDriver *drv, Audio::Mixer::SoundType sndtp, int synth, bool interp, bool enableL, bool enableR, MacLowLevelPCMDriver::ChanCallback *callback) : _drv(drv), _sndType(sndtp),
+	_synth(synth), _interpolate(interp), _frameSize(1), _len(0), _rmH(0), _rmL(0), _smpWtAcc(0), _loopSt2(0), _loopEnd(0), _loopLen(0), _loopSt(0), _baseFreq(0), _rcPos(0),
+	_flags(0), _tmrPos(0), _tmrInc(0), _timbre(0), _data(nullptr), _srate(0), _phase(0), _duration(0), _tmrRate(0), _callback(callback) {
+	_lastSmp[0] = _lastSmp[1] = 0;
+	_enable[0] = enableL;
+	_enable[1] = enableR;
+	_tmrRate = fixDiv2Frac(0x10000, fixDiv2Frac(_drv->getStatus().deviceRate, 0x7D00000, 16), 30);
+
+	if (synth != MacLowLevelPCMDriver::kSampledSynth) {
+		_len = 256;
+		_frameSize = 1;
+		_loopSt2 = 0;
+		_loopEnd = _len;
+		_baseFreq = 69;
+		setupRateConv(_drv->getStatus().deviceRate, 0x10000, ASC_DEVICE_RATE, false);
+		if (_synth == MacLowLevelPCMDriver::kSquareWaveSynth)
+			setTimbre(0x50);
+	}
+}
+
+MacLowLevelPCMDriver::ChanHandle MacSndChannel::getHandle() const {
+	const void *ptr = this;
+	return *reinterpret_cast<const int*>(&ptr);
+}
+
+void MacSndChannel::playSamples(const MacLowLevelPCMDriver::PCMSound *snd) {
+	setupRateConv(_drv->getStatus().deviceRate, calcNoteRateAdj(60 - snd->baseFreq), snd->rate, true);
+	setupSound(snd);
+	startSound(0);
+}
+
+void MacSndChannel::playNote(uint8 note, uint16 duration) {
+	note &= 0x7f;
+	if (!note) {
+		quiet();
 		return;
 	}
 
-	if (ch->_synth != kSampledSynth) {
-		warning("MacLowLevelPCMDriver::playSamples(): Wrong channel type");
+	uint32 adj = calcNoteRateAdj(note - _baseFreq);
+	if (_synth == MacLowLevelPCMDriver::kSampledSynth)
+		setupRateConv(_drv->getStatus().deviceRate, adj, _srate, true);
+	else
+		_srate = fixMul(0x50FBA, adj);
+
+	startSound(duration);
+}
+
+void MacSndChannel::quiet() {
+	_data = nullptr;
+	_tmrInc = 0;
+}
+
+void MacSndChannel::flush() {
+	_sndCmdQueue.clear();
+}
+
+void MacSndChannel::loadWaveTable(const byte *data, uint16 dataSize) {
+	assert(dataSize == _len);
+	int8 *buff = new int8[dataSize]();
+	const int8 *s = reinterpret_cast<const int8*>(data);
+	for (uint32 i = 0; i < dataSize; ++i)
+		buff[i] = *s++ ^ 0x80;
+	_res = Common::SharedPtr<const int8>(buff, Common::ArrayDeleter<const int8>());
+}
+
+void MacSndChannel::loadInstrument(const MacLowLevelPCMDriver::PCMSound *snd) {
+	setupSound(snd);
+	setupRateConv(_drv->getStatus().deviceRate, 0x10000, snd->rate, false);
+}
+
+void MacSndChannel::setTimbre(uint16 timbre) {
+	if (_timbre == timbre)
+		return;
+
+	int8 *buff = new int8[256]();
+	makeSquareWave(buff, 256, timbre);
+	_res = Common::SharedPtr<const int8>(buff);
+	_timbre = timbre;
+}
+
+void MacSndChannel::callback(uint16 p1, const void *p2) {
+	if (_callback && _callback->isValid())
+		(*_callback)(p1, p2);
+}
+
+void MacSndChannel::enqueueSndCmd(uint8 c, uint16 p1, uint32 p2, byte mode) {
+	if (mode == MacLowLevelPCMDriver::kImmediate) {
+		switch (c) {
+		case 3:
+			quiet();
+			break;
+		case 4:
+			flush();
+			break;
+		case 44:
+			setTimbre(p1);
+			break;
+		default:
+			_sndCmdQueue.insert(_sndCmdQueue.begin(), SoundCommand(c, p1, p2));
+			break;
+		}
+
+	} else {
+		_sndCmdQueue.push_back(SoundCommand(c, p1, p2));
+	}
+}
+
+void MacSndChannel::enqueueSndCmd(uint8 c, uint16 p1, const void *p2, byte ptrType, byte mode) {
+	if (mode == MacLowLevelPCMDriver::kImmediate && (c == 60 || c == 80)) {
+		if (c == 60)
+			loadWaveTable(reinterpret_cast<const byte*>(p2), p1);
+		else if (c == 80)
+			loadInstrument(reinterpret_cast<const MacLowLevelPCMDriver::PCMSound*>(p2));
+		else if (c == 13)
+			callback(p1, p2);
 		return;
 	}
 
-	ch->_res = snd->data.reinterpretCast<const int8>();
-	ch->_data = ch->_res.get();
-	ch->_len = snd->len;
-	uint32 rmul = calcRate(_status.deviceRate, 0x10000, snd->rate);
+	void *ptr = nullptr;
+	if (ptrType == 1) {
+		const MacLowLevelPCMDriver::PCMSound *s = reinterpret_cast<const MacLowLevelPCMDriver::PCMSound*>(p2);
+		MacLowLevelPCMDriver::PCMSound *p = new MacLowLevelPCMDriver::PCMSound(s->data, s->len, s->rate, s->loopst, s->loopend, s->baseFreq, s->enc, s->stereo);
+		ptr = p;
+	} else if (ptrType == 2) {
+		byte *d = new byte[p1];
+		memcpy(d, p2, p1);
+		ptr = d;
+	}
 
-	if (rmul >= 0x7FFD && rmul <= 0x8003)
-		rmul = 0x8000;
-	else if (ABS((int16)(rmul & 0xffff)) <= 7)
-		rmul = (rmul + 7) & ~0xffff;
+	if (mode == MacLowLevelPCMDriver::kImmediate)
+		_sndCmdQueue.insert(_sndCmdQueue.begin(), SoundCommand(c, p1, ptr, ptrType));
+	else
+		_sndCmdQueue.push_back(SoundCommand(c, p1, ptr, ptrType));
+}
 
-	if (rmul > (uint32)-64)
-		rmul = (uint32)-64;
+bool MacSndChannel::idle() const {
+	return _sndCmdQueue.empty() && !(_flags & MacLowLevelPCMDriver::kStatusPlaying);
+}
 
-	assert(rmul);
+void MacSndChannel::feed(int32 *dst, uint32 dstSize, byte dstFrameSize) {
+	const bool interp = _interpolate && _rmL;
+	int32 diff = 0;
 
-	ch->_rmL = rmul & 0xffff;
-	ch->_rmH = rmul >> 16;
+	for (const int32 *end = &dst[dstSize]; dst < end; ) {
+		processSndCmdQueue();
 
-	ch->_frameSize = snd->stereo ? 2 : 1;
+		int8 in = 0;
+		for (int i = 0; i < dstFrameSize; ++i) {
+			if (_data != nullptr && i < _frameSize) {
+				if (_synth != MacLowLevelPCMDriver::kSampledSynth) {
+					in = _data[(_phase >> 16) & 0xff];
+				} else {
+					in = _data[_rcPos + i];
+					if (interp && in != _lastSmp[i]) {
+						diff = in - _lastSmp[i];
+						diff = (diff * (_smpWtAcc >> 1)) >> 15;
+						in = (_lastSmp[i] + diff) & 0xff;
+					}
+				}
+			}
+			if (_enable[i])
+				*dst += in;
+			dst++;
+		}
+
+		uint32 cpos = _rcPos;
+		_rcPos += (_rmH * _frameSize);
+		_phase += (_rmH * _srate);
+		_smpWtAcc += _rmL;
+		if (_smpWtAcc > 0xffff) {
+			_smpWtAcc &= 0xffff;
+			_rcPos += _frameSize;
+			_phase += _srate;
+		}
+
+		_tmrPos += _tmrInc;
+		while (_tmrPos > 0x3fffffff) {
+			_tmrPos -= 0x40000000;
+			if (_duration && !--_duration)
+				_data = nullptr;
+		}
+
+		if (_synth != MacLowLevelPCMDriver::kSampledSynth || cpos == _rcPos || _data == nullptr)
+			continue;
+
+		if (interp) {
+			for (int i = 0; i < _frameSize; ++i)
+				_lastSmp[i] = _data[_loopSt + ((_rcPos - _frameSize + i - _loopSt) % _loopLen)];
+		}
+
+		if (_rcPos >= _loopSt + _loopLen) {
+			if (_loopEnd) {
+				_loopSt = _loopSt2;
+				_loopLen = _loopEnd - _loopSt2;
+				if (interp) {
+					for (int i = 0; i < _frameSize; ++i)
+						_lastSmp[i] = _data[_loopSt + ((_rcPos - _frameSize + i - _loopSt) % _loopLen)];
+				}
+				_rcPos = _loopSt + ((_rcPos - _loopSt) % _loopLen);
+			} else {
+				_data = nullptr;
+			}
+		}
+	}
+}
+
+void MacSndChannel::setupSound(const MacLowLevelPCMDriver::PCMSound *snd) {
+	assert(_synth == MacLowLevelPCMDriver::kSampledSynth);
+
+	_len = snd->len;
+	const byte *in = snd->data.get();
+	assert(in);
+	int8 *buff = new int8[_len];
+	for (uint32 i = 0; i < _len; ++i)
+		buff[i] = *in++ ^ 0x80;
+	_res = Common::SharedPtr<const int8>(buff, Common::ArrayDeleter<const int8>());
+	_frameSize = snd->stereo ? 2 : 1;
+	_loopSt = 0;
 
 	if (snd->loopend - snd->loopst < 2 || snd->loopend < snd->loopst) {
-		ch->_loopSt = 0;
-		ch->_loopEnd = 0;
+		_loopSt2 = 0;
+		_loopEnd = 0;
 	} else {
-		ch->_loopSt = snd->loopst - (snd->loopst % ch->_frameSize);
-		ch->_loopEnd = snd->loopend - (snd->loopend % ch->_frameSize);
+		_loopSt2 = snd->loopst - (snd->loopst % _frameSize);
+		_loopEnd = snd->loopend - (snd->loopend % _frameSize);
+		assert(_loopEnd <= _len);
 	}
 
-	ch->_baseFreq = snd->baseFreq;
-	ch->_rcPos = 0;
-	ch->_smpWtAcc = 0;
-	ch->_lastSmp[0] = ch->_data[0];
-	if (ch->_len >= ch->_frameSize)
-		ch->_lastSmp[1] = ch->_data[1];
-	clearFlags(kStatusDone);
-	clearChanFlags(ch, kStatusDone);
+	_baseFreq = snd->baseFreq;
+	_srate = snd->rate;
+}
+
+void MacSndChannel::setupRateConv(uint32 drate, uint32 mod, uint32 srate, bool ppr) {
+	uint32 rmul = calcRate(drate, mod, srate);
+
+	if (ppr) {
+		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;
 }
 
-void MacLowLevelPCMDriver::stop(ChanHandle handle) {
-	Common::StackLock lock(_mutex);
-	Channel *ch = findChannel(handle);
-	if (!ch) {
-		warning("MacLowLevelPCMDriver::stop(): Channel not found");
+void MacSndChannel::startSound(uint32 duration) {
+	_duration = duration;
+	if (duration) {
+		_tmrInc = _tmrRate;
+		_tmrPos = 0;
+	}
+
+	_data = _res.get();
+	_lastSmp[0] = _data[0];
+	if (_len >= _frameSize)
+		_lastSmp[1] = _data[1];
+	_rcPos = 0;
+	_loopSt = 0;
+	_loopLen = _loopEnd ? _loopEnd : _len;
+	_smpWtAcc = 0;
+	_phase = 0;
+}
+
+void MacSndChannel::processSndCmdQueue() {
+	if (_data)
+		return;
+
+	if (_sndCmdQueue.empty()) {
+		setFlags(MacLowLevelPCMDriver::kStatusDone);
+		clearFlags(MacLowLevelPCMDriver::kStatusPlaying);
 		return;
 	}
-	ch->stop();
-	setChanFlags(ch, kStatusDone);
+
+	_drv->clearFlags(MacLowLevelPCMDriver::kStatusDone);
+	clearFlags(MacLowLevelPCMDriver::kStatusDone);
+	setFlags(MacLowLevelPCMDriver::kStatusPlaying);
+
+	SoundCommand &c = _sndCmdQueue.front();
+	MacLowLevelPCMDriver::PCMSound *p = (c.ptr && c.arg2 == 1) ? reinterpret_cast<MacLowLevelPCMDriver::PCMSound*>(c.ptr) : nullptr;
+	const byte *b = (c.ptr && c.arg2 == 2) ? reinterpret_cast<byte*>(c.ptr) : nullptr;
+
+	switch (c.cmd) {
+	case 3:
+		quiet();
+		break;
+	case 4:
+		flush();
+		break;
+	case 13:
+		callback(c.arg1, c.ptr);
+		break;
+	case 40:
+		playNote(c.arg1, c.arg2);
+		break;
+	case 44:
+		setTimbre(c.arg1);
+		break;
+	case 60:
+		loadWaveTable(b, c.arg1);
+		break;
+	case 80:
+		loadInstrument(p);
+		break;
+	case 81:
+		playSamples(p);
+		break;
+	default:
+		break;
+	}
+
+	if (p) {
+		p->data.reset();
+		delete p;
+	} else if (b) {
+		delete[] b;
+	}
+
+	if (!_sndCmdQueue.empty())
+		_sndCmdQueue.erase(_sndCmdQueue.begin());
 }
 
-uint32 MacLowLevelPCMDriver::calcRate(uint32 outRate, uint32 factor, uint32 dataRate) {
+uint32 MacSndChannel::calcRate(uint32 outRate, uint32 factor, uint32 dataRate) {
 	uint32 result = outRate;
 	uint64 t = 0;
 	uint32 c = 0;
@@ -533,41 +965,68 @@ uint32 MacLowLevelPCMDriver::calcRate(uint32 outRate, uint32 factor, uint32 data
 	return result;
 }
 
-void MacLowLevelPCMDriver::updateStatus() {
-	_numInternalMixChannels = _smpSize > 1 ? 1 : _channels.size();
-	_status.numExternalMixChannels = _smpSize > 1 ? _channels.size() : 1;
-	_status.allowInterPolation = true;
-	for (Common::Array<Channel*>::const_iterator ch = _channels.begin(); ch != _channels.end(); ++ch) {
-		if (!(*ch)->_interpolate)
-			_status.allowInterPolation = false;
-	}
-}
+uint32 MacSndChannel::calcNoteRateAdj(int diff) {
+	static const uint32 adjFrac[23] = {
+		0x21e71f26, 0x23eb3588, 0x260dfc14, 0x285145f3, 0x2ab70212, 0x2d413ccd,
+		0x2ff221af, 0x32cbfd4a, 0x35d13f33, 0x39047c0f, 0x3c686fce, 0x40000000,
+		0x43ce3e4b, 0x47d66b0f, 0x4c1bf829, 0x50a28be6, 0x556e0424, 0x5a82799a,
+		0x5fe4435e, 0x6597fa95, 0x6ba27e65, 0x7208f81d, 0x78d0df9c
+	};
 
-MacLowLevelPCMDriver::Channel *MacLowLevelPCMDriver::findChannel(ChanHandle handle) const {
-	for (Common::Array<Channel*>::const_iterator ch = _channels.begin(); ch != _channels.end(); ++ch) {
-		if ((*ch)->getHandle() == handle)
-			return *ch;
-	}
-	return nullptr;
+	diff = CLIP<int>(diff, -127, 127);
+	return fracMul(adjFrac[11 + (diff % 12)], (diff >= 0) ? 1 << ((diff / 12) + 16) : 0x10000 / (1 << (-diff / 12)));
 }
 
-MacLowLevelPCMDriver::Channel::Channel(Audio::Mixer::SoundType sndtp, int synth, bool interp) : _sndType(sndtp), _synth(synth),
-	_interpolate(interp), _frameSize(1), _len(0), _rmH(0), _rmL(0), _smpWtAcc(0), _loopSt(0), _loopEnd(0), _baseFreq(0), _rcPos(0),
-	_flags(0), _data(nullptr) {
-	_lastSmp[0] = _lastSmp[1] = 0;
+void MacSndChannel::makeSquareWave(int8 *dstBuff, uint16 dstSize, byte timbre) {
+	static const byte ampTbl[128] = {
+		0x80, 0x82, 0x83, 0x85, 0x86, 0x88, 0x89, 0x8b, 0x8d, 0x8e, 0x90, 0x91, 0x93, 0x94, 0x96, 0x97,
+		0x99, 0x9a, 0x9c, 0x9d, 0x9f, 0xa1, 0xa2, 0xa4,	0xa5, 0xa6, 0xa8, 0xa9, 0xab, 0xac, 0xae, 0xaf,
+		0xb1, 0xb2, 0xb4, 0xb5, 0xb6, 0xb8, 0xb9, 0xbb,	0xbc, 0xbd, 0xbf, 0xc0, 0xc1, 0xc3, 0xc4, 0xc5,
+		0xc7, 0xc8, 0xc9, 0xca, 0xcc, 0xcd, 0xce, 0xcf,	0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd8, 0xd9,
+		0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1,	0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
+		0xe9, 0xea, 0xeb, 0xec, 0xed, 0xed, 0xee, 0xef,	0xf0, 0xf0, 0xf1, 0xf2, 0xf2, 0xf3, 0xf4, 0xf4,
+		0xf5, 0xf5, 0xf6, 0xf7, 0xf7, 0xf8, 0xf8, 0xf9,	0xf9, 0xf9, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb, 0xfc,
+		0xfc, 0xfc, 0xfc, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd,	0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff
+	};
+	assert(dstSize == 256);
+	timbre = MAX<byte>(8, 255 - ampTbl[127 - (timbre >> 1)]);
+
+	byte p = 0;
+	int8 *d1 = dstBuff;
+	int8 *d2 = &d1[128];
+	for (int i = 0; i < 65; ++i) {
+		*d1++ = *d2-- = ((ampTbl[p] ^ 0x80) >> 2);
+		p = MIN<int>(127, timbre + p);
+	}
+	d1 = dstBuff;
+	d2 = &d1[128];
+	for (int i = 0; i < 128; ++i)
+		*d2++ = *d1++ ^ 0xff;
 }
 
-MacLowLevelPCMDriver::ChanHandle MacLowLevelPCMDriver::Channel::getHandle() const {
-	const void *ptr = this;
-	return *reinterpret_cast<const int*>(&ptr);
-}
+const uint8 _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 MacLowLevelPCMDriver::Channel::stop() {
-	_data = nullptr;
-	_res.reset();
-}
+const uint32 _fourToneSynthWaveFormSize = sizeof(_fourToneSynthWaveForm);
 
 class Indy3MacSnd;
+class LoomMacSnd;
 
 template<typename T> class MusicEngineImpl : public MusicEngine {
 public:
@@ -644,14 +1103,26 @@ template <typename T> void MusicEngineImpl<T>::restoreAfterLoad() {
 }
 
 namespace MacSound {
+
 MusicEngine *createPlayer(ScummEngine *vm) {
 	assert(vm);
 	assert(vm->_mixer);
-	if (vm->_game.id == GID_INDY3)
-		return new MusicEngineImpl<Indy3MacSnd>(vm, vm->_mixer);
+	MusicEngine *res = nullptr;
+
+	switch (vm->_game.id) {
+	case GID_INDY3:
+		res = new MusicEngineImpl<Indy3MacSnd>(vm, vm->_mixer);
+		break;
+	case GID_LOOM:
+		res = new MusicEngineImpl<LoomMacSnd>(vm, vm->_mixer);
+		break;
+	default:
+		break;
+	}
 
-	return nullptr;
+	return res;
 }
+
 } // end of namespace MacSound
 
 #undef ASC_DEVICE_RATE
diff --git a/engines/scumm/players/player_mac_new.h b/engines/scumm/players/player_mac_new.h
index b7c32c19b5d..3015880dbc8 100644
--- a/engines/scumm/players/player_mac_new.h
+++ b/engines/scumm/players/player_mac_new.h
@@ -31,10 +31,10 @@ class ScummEngine;
 namespace MacSound {
 enum {
 	kQualityAuto = 0,
-	kQualityHigh,
-	kQualityLow,
-	kQualityMedium
+	kQualityLowest = 1,
+	kQualityHighest = 9
 };
+
 MusicEngine *createPlayer(ScummEngine *vm);
 } // end of namespace MacSound
 
diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp
index 8c3a870f5ec..3bdcbed12b1 100644
--- a/engines/scumm/saveload.cpp
+++ b/engines/scumm/saveload.cpp
@@ -69,7 +69,7 @@ struct SaveInfoSection {
 
 #define SaveInfoSectionSize (4+4+4 + 4+4 + 4+2)
 
-#define CURRENT_VER 113
+#define CURRENT_VER 114
 #define INFOSECTION_VERSION 2
 
 #pragma mark -
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index c0340ba282b..fd0cbb9bab5 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -2168,14 +2168,19 @@ 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 = MacSound::createPlayer(this);
-		if (ConfMan.hasKey("mac_v3_low_quality_music") && ConfMan.getBool("mac_v3_low_quality_music"))
-			_musicEngine->setQuality(MacSound::kQualityLow);
-		_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);
+	} else if (_game.platform == Common::kPlatformMacintosh && (_game.id == GID_INDY3 || _game.id == GID_LOOM)) {
+#if 0
+		if (_game.id == GID_LOOM) {
+			_musicEngine = new Player_V3M(this, _mixer, ConfMan.getBool("mac_v3_low_quality_music"));
+			((Player_V3M *)_musicEngine)->init(macInstrumentFile);
+		} else
+#endif
+		{
+			_musicEngine = MacSound::createPlayer(this);
+			if (ConfMan.hasKey("mac_v3_low_quality_music") && ConfMan.getBool("mac_v3_low_quality_music"))
+				_musicEngine->setQuality(MacSound::kQualityLowest);
+			_sound->_musicType = MDT_MACINTOSH;
+		}
 	} else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_MONKEY) {
 		_musicEngine = new Player_V5M(this, _mixer);
 		((Player_V5M *)_musicEngine)->init(macInstrumentFile);


Commit: c52bfe4f270e4fd9d23ed277e9d1d78136f17e7f
    https://github.com/scummvm/scummvm/commit/c52bfe4f270e4fd9d23ed277e9d1d78136f17e7f
Author: athrxx (athrxx at scummvm.org)
Date: 2024-02-28T21:31:50+01:00

Commit Message:
SCUMM: (Loom/Mac) - add music quality slider widget

Changed paths:
    engines/scumm/dialogs.cpp
    engines/scumm/dialogs.h
    engines/scumm/macgui/macgui_loom.cpp
    engines/scumm/metaengine.cpp
    engines/scumm/scumm.cpp
    engines/scumm/vars.cpp


diff --git a/engines/scumm/dialogs.cpp b/engines/scumm/dialogs.cpp
index f6c4fd5d812..52e3c80f1a3 100644
--- a/engines/scumm/dialogs.cpp
+++ b/engines/scumm/dialogs.cpp
@@ -1354,7 +1354,6 @@ void LoomEgaGameOptionsWidget::defineLayout(GUI::ThemeEval &layouts, const Commo
 }
 
 void LoomEgaGameOptionsWidget::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
-
 	switch (cmd) {
 	case kOvertureTicksChanged:
 		updateOvertureTicksValue();
@@ -1371,6 +1370,86 @@ void LoomEgaGameOptionsWidget::updateOvertureTicksValue() {
 	_overtureTicksValue->setLabel(Common::String::format("%d:%02d.%d", ticks / 600, (ticks % 600) / 10, ticks % 10));
 }
 
+// Mac Loom options
+LoomMacGameOptionsWidget::LoomMacGameOptionsWidget(GuiObject *boss, const Common::String &name, const Common::String &domain) :
+	ScummOptionsContainerWidget(boss, name, "LoomMacGameOptionsWidget", domain), _sndQualitySlider(nullptr), _sndQualityValue(nullptr), _enableOriginalGUICheckbox(nullptr), _quality(0) {
+	GUI::StaticTextWidget *text = new GUI::StaticTextWidget(widgetsBoss(), "LoomMacGameOptionsWidget.SndQualityLabel", _("Music Quality:"));
+	text->setAlign(Graphics::TextAlign::kTextAlignEnd);
+
+	_sndQualitySlider = new GUI::SliderWidget(widgetsBoss(), "LoomMacGameOptionsWidget.SndQuality", _("Select music quality. The original would determine the basic setup by hardware detection and speed tests, but it could also be changed in the game menu to some degree."), kQualitySliderUpdate);
+	_sndQualitySlider->setMinValue(0);
+	_sndQualitySlider->setMaxValue(9);
+	_sndQualityValue = new GUI::StaticTextWidget(widgetsBoss(), "LoomMacGameOptionsWidget.SndQualityValue", Common::U32String());
+	_sndQualityValue->setFlags(GUI::WIDGET_CLEARBG);
+	updateQualitySlider();
+
+	createEnhancementsWidget(widgetsBoss(), "LoomMacGameOptionsWidget");
+	_enableOriginalGUICheckbox = createOriginalGUICheckbox(widgetsBoss(), "LoomMacGameOptionsWidget.EnableOriginalGUI");
+}
+
+void LoomMacGameOptionsWidget::load() {
+	ScummOptionsContainerWidget::load();
+
+	_quality = 0;
+
+	if (ConfMan.hasKey("mac_snd_quality", _domain))
+		_quality = ConfMan.getInt("mac_snd_quality", _domain);
+
+	// Migrate old bool setting...
+	if (_quality == 0 && ConfMan.hasKey("mac_v3_low_quality_music", _domain)) {
+		if (ConfMan.getBool("mac_v3_low_quality_music"))
+			_quality = 1;
+	}
+	ConfMan.removeKey("mac_v3_low_quality_music", _domain);
+
+	_sndQualitySlider->setValue(_quality);
+	updateQualitySlider();
+	_enableOriginalGUICheckbox->setState(ConfMan.getBool("original_gui", _domain));
+}
+
+bool LoomMacGameOptionsWidget::save() {
+	bool res = ScummOptionsContainerWidget::save();
+	ConfMan.setInt("mac_snd_quality", _quality, _domain);
+	ConfMan.setBool("original_gui", _enableOriginalGUICheckbox->getState(), _domain);
+	return res;
+}
+
+void LoomMacGameOptionsWidget::defineLayout(GUI::ThemeEval &layouts, const Common::String &layoutName, const Common::String &overlayedLayout) const {
+	layouts.addDialog(layoutName, overlayedLayout)
+		.addLayout(GUI::ThemeLayout::kLayoutVertical, 5)
+		.addPadding(0, 0, 0, 0)
+		.addLayout(GUI::ThemeLayout::kLayoutVertical, 4)
+		.addPadding(0, 0, 10, 0)
+		.addWidget("EnableOriginalGUI", "Checkbox");
+	addEnhancementsLayout(layouts)
+		.closeLayout()
+		.addLayout(GUI::ThemeLayout::kLayoutHorizontal, 12)
+		.addPadding(0, 0, 10, 0)
+		.addWidget("SndQualityLabel", "OptionsLabel")
+		.addWidget("SndQuality", "Slider")
+		.addWidget("SndQualityValue", "ShortOptionsLabel")
+		.closeLayout()
+		.closeLayout()
+		.closeDialog();
+}
+
+void LoomMacGameOptionsWidget::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
+	switch (cmd) {
+	case kQualitySliderUpdate:
+		updateQualitySlider();
+		break;
+	default:
+		GUI::OptionsContainerWidget::handleCommand(sender, cmd, data);
+		break;
+	}
+}
+
+void LoomMacGameOptionsWidget::updateQualitySlider() {
+	_quality = _sndQualitySlider->getValue();
+	Common::U32String label(_quality == 0 ? "auto" : Common::String::format("%4d", _quality));
+	_sndQualityValue->setLabel(label);
+}
+
 // VGA Loom Playback Adjustment settings
 
 LoomVgaGameOptionsWidget::LoomVgaGameOptionsWidget(GuiObject *boss, const Common::String &name, const Common::String &domain) :
diff --git a/engines/scumm/dialogs.h b/engines/scumm/dialogs.h
index ea15e8d2449..eeeeb373d84 100644
--- a/engines/scumm/dialogs.h
+++ b/engines/scumm/dialogs.h
@@ -298,6 +298,30 @@ private:
 	void updateOvertureTicksValue();
 };
 
+/**
+* Options widget for Mac Loom.
+*/
+class LoomMacGameOptionsWidget : public ScummOptionsContainerWidget {
+public:
+	LoomMacGameOptionsWidget(GuiObject *boss, const Common::String &name, const Common::String &domain);
+	~LoomMacGameOptionsWidget() override {};
+
+	void load() override;
+	bool save() override;
+private:
+	enum {
+		kQualitySliderUpdate = 'QUAL'
+	};
+	void defineLayout(GUI::ThemeEval &layouts, const Common::String &layoutName, const Common::String &overlayedLayout) const override;
+	void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) override;
+	void updateQualitySlider();
+
+	GUI::CheckboxWidget *_enableOriginalGUICheckbox;
+	GUI::SliderWidget *_sndQualitySlider;
+	GUI::StaticTextWidget *_sndQualityValue;
+	int _quality;
+};
+
 /**
  * Options widget for VGA Loom (DOS CD).
  */
diff --git a/engines/scumm/macgui/macgui_loom.cpp b/engines/scumm/macgui/macgui_loom.cpp
index 693a601e08b..93fce2cb5f9 100644
--- a/engines/scumm/macgui/macgui_loom.cpp
+++ b/engines/scumm/macgui/macgui_loom.cpp
@@ -636,6 +636,9 @@ bool MacLoomGui::runOptionsDialog() {
 	int scrolling = _vm->_snapScroll == 0;
 	int fullAnimation = _vm->VAR(_vm->VAR_MACHINE_SPEED) == 1 ? 0 : 1;
 	int textSpeed = _vm->_defaultTextSpeed;
+	int musicQuality = ConfMan.hasKey("mac_snd_quality") ? ConfMan.getInt("mac_snd_quality") : 0;
+	int musicQualityOption = (musicQuality == 0) ? 1 : (musicQuality - 1) % 3;
+	musicQuality = (musicQuality == 0) ? (_vm->VAR(_vm->VAR_SOUNDCARD) == 10 ? 0 : 2) : (musicQuality - 1) / 3;
 
 	MacDialogWindow *window = createDialog(1000);
 
@@ -651,7 +654,7 @@ bool MacLoomGui::runOptionsDialog() {
 	window->setWidgetValue(11, textSpeed);
 
 	window->addPictureSlider(8, 9, true, 5, 69, 0, 2, 6, 4);
-	window->setWidgetValue(12, 2/* TODO: save var*/);
+	window->setWidgetValue(12, musicQualityOption);
 
 	// Machine rating
 	window->addSubstitution(Common::String::format("%d", _vm->VAR(53)));
@@ -721,11 +724,10 @@ bool MacLoomGui::runOptionsDialog() {
 		// the sequence files and mutes everything else)
 		//
 
-		//_vm->VAR(_vm->VAR_SOUNDCARD) = window->getWidgetValue(12) == 0 ? 10 : 11;
 		//((Player_V3M *)_vm->_musicEngine)->overrideQuality(_vm->VAR(_vm->VAR_SOUNDCARD) == 10);
-		int musicQuality = (ConfMan.hasKey("mac_v3_low_quality_music") && ConfMan.getBool("mac_v3_low_quality_music")) ? 0 : (_vm->VAR(_vm->VAR_SOUNDCARD) == 10 ? 0 : 2);
-		_vm->_musicEngine->setQuality(musicQuality * 3 + 1 + window->getWidgetValue(12));
-		//ConfMan.setBool("mac_v3_low_quality_music", _vm->VAR(_vm->VAR_SOUNDCARD) == 10);
+		musicQuality = musicQuality * 3 + 1 + window->getWidgetValue(12);
+		_vm->_musicEngine->setQuality(musicQuality);
+		ConfMan.setInt("mac_snd_quality", musicQuality);
 
 		debug(6, "MacLoomGui::runOptionsDialog(): music quality: %d - unimplemented!", window->getWidgetValue(12));
 
diff --git a/engines/scumm/metaengine.cpp b/engines/scumm/metaengine.cpp
index 647fa986223..51badcb06aa 100644
--- a/engines/scumm/metaengine.cpp
+++ b/engines/scumm/metaengine.cpp
@@ -567,7 +567,7 @@ SaveStateDescriptor ScummMetaEngine::querySaveMetaInfos(const char *target, int
 
 GUI::OptionsContainerWidget *ScummMetaEngine::buildLoomOptionsWidget(GUI::GuiObject *boss, const Common::String &name, const Common::String &target) const {
 	Common::Platform platform = Common::parsePlatform(ConfMan.get("platform", target));
-	if (platform != Common::kPlatformUnknown && platform != Common::kPlatformDOS)
+	if (platform != Common::kPlatformUnknown && platform != Common::kPlatformDOS && platform != Common::kPlatformMacintosh)
 		return nullptr;
 
 	Common::String extra = ConfMan.get("extra", target);
@@ -580,10 +580,11 @@ GUI::OptionsContainerWidget *ScummMetaEngine::buildLoomOptionsWidget(GUI::GuiObj
 
 	if (extra == "Steam")
 		return MetaEngine::buildEngineOptionsWidget(boss, name, target);
+	else if (platform == Common::kPlatformMacintosh)
+		return new Scumm::LoomMacGameOptionsWidget(boss, name, target);
 
 	// These EGA Loom settings are only relevant for the EGA
 	// version, since that is the only one that has an overture.
-
 	return new Scumm::LoomEgaGameOptionsWidget(boss, name, target);
 }
 
@@ -653,7 +654,7 @@ static const ExtraGuiOption fmtownsTrimTo200 = {
 
 static const ExtraGuiOption macV3LowQualityMusic = {
 	_s("Play simplified music"),
-	_s("This music was presumably intended for low-end Macs, and uses only one channel."),
+	_s("This music was intended for low-end Macs, and uses only one channel."),
 	"mac_v3_low_quality_music",
 	false,
 	0,
@@ -773,7 +774,7 @@ const ExtraGuiOptions ScummMetaEngine::getExtraGuiOptions(const Common::String &
 	// The low quality music in Loom was probably intended for low-end
 	// Macs. It plays only one channel, instead of three.
 
-	if (target.empty() || ((gameid == "loom" || gameid == "indy3") && platform == Common::kPlatformMacintosh && extra != "Steam")) {
+	if (target.empty() || (gameid == "indy3" && platform == Common::kPlatformMacintosh && extra != "Steam")) {
 		options.push_back(macV3LowQualityMusic);
 	}
 
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index fd0cbb9bab5..3562f8f312c 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -2179,6 +2179,8 @@ void ScummEngine::setupMusic(int midi, const Common::Path &macInstrumentFile) {
 			_musicEngine = MacSound::createPlayer(this);
 			if (ConfMan.hasKey("mac_v3_low_quality_music") && ConfMan.getBool("mac_v3_low_quality_music"))
 				_musicEngine->setQuality(MacSound::kQualityLowest);
+			else if (ConfMan.hasKey("mac_snd_quality"))
+				_musicEngine->setQuality(ConfMan.getInt("mac_snd_quality"));
 			_sound->_musicType = MDT_MACINTOSH;
 		}
 	} else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_MONKEY) {
diff --git a/engines/scumm/vars.cpp b/engines/scumm/vars.cpp
index 65bba51185c..ae7c807bf7e 100644
--- a/engines/scumm/vars.cpp
+++ b/engines/scumm/vars.cpp
@@ -858,8 +858,10 @@ void ScummEngine::setSoundCardVarToCurrentConfig() {
 	// 4 Roland
 	switch (_sound->_musicType) {
 	case MDT_MACINTOSH:
-		if (_game.id == GID_INDY3 || _game.id == GID_LOOM)
+		if (_game.id == GID_INDY3)
 			VAR(VAR_SOUNDCARD) = (ConfMan.hasKey("mac_v3_low_quality_music") && ConfMan.getBool("mac_v3_low_quality_music")) ? 10 : 11;
+		else if (_game.id == GID_LOOM)
+			VAR(VAR_SOUNDCARD) = (ConfMan.hasKey("mac_snd_quality") && ConfMan.getInt("mac_snd_quality") > 0 && ConfMan.getInt("mac_snd_quality") < 4) ? 10 : 11;
 		else
 			VAR(VAR_SOUNDCARD) = 3;
 		break;


Commit: 5fe07a8fb1afbf19fce6f94e8c9eaf609cb299db
    https://github.com/scummvm/scummvm/commit/5fe07a8fb1afbf19fce6f94e8c9eaf609cb299db
Author: athrxx (athrxx at scummvm.org)
Date: 2024-02-28T21:31:59+01:00

Commit Message:
SCUMM: (Loom/Mac) - work around original tempo glitch

Changed paths:
    engines/scumm/players/player_mac_intern.h
    engines/scumm/players/player_mac_loom.cpp
    engines/scumm/players/player_mac_new.cpp


diff --git a/engines/scumm/players/player_mac_intern.h b/engines/scumm/players/player_mac_intern.h
index ee5d04b26a8..3b48d57d23c 100644
--- a/engines/scumm/players/player_mac_intern.h
+++ b/engines/scumm/players/player_mac_intern.h
@@ -134,6 +134,7 @@ public:
 	void playNote(ChanHandle handle, ExecMode mode, uint8 note, uint16 duration);
 	void quiet(ChanHandle handle, ExecMode mode);
 	void flush(ChanHandle handle, ExecMode mode);
+	void wait(ChanHandle handle, ExecMode mode, uint16 duration);
 	void loadWaveTable(ChanHandle handle, ExecMode mode, const byte *data, uint16 dataSize);
 	void loadInstrument(ChanHandle handle, ExecMode mode, const PCMSound *snd);
 	void setTimbre(ChanHandle handle, ExecMode mode, uint16 timbre);
diff --git a/engines/scumm/players/player_mac_loom.cpp b/engines/scumm/players/player_mac_loom.cpp
index 08485e04f60..0b79ca53dab 100644
--- a/engines/scumm/players/player_mac_loom.cpp
+++ b/engines/scumm/players/player_mac_loom.cpp
@@ -283,14 +283,19 @@ void LoomMacSnd::sendSoundCommands(const byte *data, int timeStamp) {
 		while (len--) {
 			uint16 p1 = READ_BE_UINT16(s);
 			s += 2;
-			uint32 p2 = 0x8f00 | *s++;
+			uint8 note = *s++;
+
 			if (timeStamp > 0) {
 				int ts = timeStamp;
 				timeStamp = MAX<int>(0, timeStamp - p1);
 				p1 -= ts;
 			}
-			if (!timeStamp)
-				_sdrv->playNote(_sndChannel, MacLowLevelPCMDriver::kEnqueue, p2 & 0x7f, p1);
+			if (timeStamp)
+				continue;
+
+			_sdrv->playNote(_sndChannel, MacLowLevelPCMDriver::kEnqueue, note & 0x7f, p1);
+			if (note == 0) // Workaround for tempo glitch in original driver
+				_sdrv->wait(_sndChannel, MacLowLevelPCMDriver::kEnqueue, p1);
 		}
 		_sdrv->quiet(_sndChannel, MacLowLevelPCMDriver::kEnqueue);
 		_sdrv->callback(_sndChannel, MacLowLevelPCMDriver::kEnqueue, 1, nullptr);
@@ -319,15 +324,21 @@ void LoomMacSnd::sendSoundCommands(const byte *data, int timeStamp) {
 				uint16 p1 = READ_BE_UINT16(src[i]);
 				src[i] += 2;
 				byte note = *src[i]++;
-				uint32 p2 = (/*_curSynthType == 4 && */note == 0) ? 1 : (0x8f00 | note);
 				if (tmstmp[i] > 0) {
 					int ts = tmstmp[i];
 					tmstmp[i] = MAX<int>(0, tmstmp[i] - p1);
 					p1 -= ts;
 				}
 				loop |= static_cast<bool>(--len[i]);
-				if (!tmstmp[i])
-					_sdrv->playNote(_musChannels[i], MacLowLevelPCMDriver::kEnqueue, p2 & 0x7f, p1);
+
+				if (tmstmp[i])
+					continue;
+
+				_sdrv->playNote(_musChannels[i], MacLowLevelPCMDriver::kEnqueue, (_curSynthType == 4 && note == 0) ? 1 : note & 0x7f, p1);
+				// Workaround for tempo glitch in original driver. For the sampled synth in 4 channel mode, there is
+				// some sort of fix in the original (see above), but that really does not work well for the other cases.
+				if (note == 0 && _curSynthType != 4)
+					_sdrv->wait(_musChannels[i], MacLowLevelPCMDriver::kEnqueue, p1);
 			}
 		}
 
@@ -461,7 +472,7 @@ void LoomMacSnd::detectQuality() {
 		if (isSoundCardType10())
 			_machineRating = 1;
 		/*else if (0)
-			_machineQuality = 2;*/
+			_machineRating = 2;*/
 		else
 			_machineRating = 3;
 	}
diff --git a/engines/scumm/players/player_mac_new.cpp b/engines/scumm/players/player_mac_new.cpp
index cbaa4082e3d..938084a1b72 100644
--- a/engines/scumm/players/player_mac_new.cpp
+++ b/engines/scumm/players/player_mac_new.cpp
@@ -46,6 +46,7 @@ public:
 	void playNote(uint8 note, uint16 duration);
 	void quiet();
 	void flush();
+	void wait(uint32 duration);
 	void loadWaveTable(const byte *data, uint16 dataSize);
 	void loadInstrument(const MacLowLevelPCMDriver::PCMSound *snd);
 	void setTimbre(uint16 timbre);
@@ -403,6 +404,14 @@ void MacLowLevelPCMDriver::flush(ChanHandle handle, ExecMode mode) {
 	ch->enqueueSndCmd(4, 0, 0, mode);
 }
 
+void MacLowLevelPCMDriver::wait(ChanHandle handle, ExecMode mode, uint16 duration) {
+	Common::StackLock lock(_mutex);
+	MacSndChannel *ch = findAndCheckChannel(handle, __FUNCTION__, kIgnoreSynth);
+	if (!ch)
+		return;
+	ch->enqueueSndCmd(10, duration, 0, mode);
+}
+
 void MacLowLevelPCMDriver::loadWaveTable(ChanHandle handle, ExecMode mode, const byte *data, uint16 dataSize) {
 	if (!data || !dataSize)
 		return;
@@ -538,6 +547,14 @@ void MacSndChannel::quiet() {
 	_tmrInc = 0;
 }
 
+void MacSndChannel::wait(uint32 duration) {
+	_duration = duration;
+	if (duration) {
+		_tmrInc = _tmrRate;
+		_tmrPos = 0;
+	}
+}
+
 void MacSndChannel::flush() {
 	_sndCmdQueue.clear();
 }
@@ -580,6 +597,9 @@ void MacSndChannel::enqueueSndCmd(uint8 c, uint16 p1, uint32 p2, byte mode) {
 		case 4:
 			flush();
 			break;
+		case 10:
+			wait(p1);
+			break;
 		case 44:
 			setTimbre(p1);
 			break;
@@ -664,8 +684,8 @@ void MacSndChannel::feed(int32 *dst, uint32 dstSize, byte dstFrameSize) {
 		_tmrPos += _tmrInc;
 		while (_tmrPos > 0x3fffffff) {
 			_tmrPos -= 0x40000000;
-			if (_duration && !--_duration)
-				_data = nullptr;
+			if (_duration)
+				--_duration;
 		}
 
 		if (_synth != MacLowLevelPCMDriver::kSampledSynth || cpos == _rcPos || _data == nullptr)
@@ -738,10 +758,8 @@ void MacSndChannel::setupRateConv(uint32 drate, uint32 mod, uint32 srate, bool p
 
 void MacSndChannel::startSound(uint32 duration) {
 	_duration = duration;
-	if (duration) {
-		_tmrInc = _tmrRate;
-		_tmrPos = 0;
-	}
+	_tmrInc = duration ? _tmrRate : 0;
+	_tmrPos = 0;
 
 	_data = _res.get();
 	_lastSmp[0] = _data[0];
@@ -755,7 +773,7 @@ void MacSndChannel::startSound(uint32 duration) {
 }
 
 void MacSndChannel::processSndCmdQueue() {
-	if (_data)
+	if ((_data && _tmrInc == 0) || _duration)
 		return;
 
 	if (_sndCmdQueue.empty()) {
@@ -779,6 +797,9 @@ void MacSndChannel::processSndCmdQueue() {
 	case 4:
 		flush();
 		break;
+	case 10:
+		wait(c.arg1);
+		break;
 	case 13:
 		callback(c.arg1, c.ptr);
 		break;




More information about the Scummvm-git-logs mailing list