[Scummvm-git-logs] scummvm master -> 2ebed575239f0af86a70e13048262a62b2a1caac

neuromancer noreply at scummvm.org
Sat Sep 5 09:43:52 UTC 2026


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

Summary:
2ebed57523 EEM: use halestorm common driver and removed duplicated code


Commit: 2ebed575239f0af86a70e13048262a62b2a1caac
    https://github.com/scummvm/scummvm/commit/2ebed575239f0af86a70e13048262a62b2a1caac
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-05T11:43:39+02:00

Commit Message:
EEM: use halestorm common driver and removed duplicated code

Changed paths:
    engines/eem/audio.cpp
    engines/eem/eem.cpp
    engines/eem/music.cpp
    engines/eem/music.h
    engines/eem/resource.cpp
    engines/eem/resource.h


diff --git a/engines/eem/audio.cpp b/engines/eem/audio.cpp
index 5db6134304e..59fb5369aa2 100644
--- a/engines/eem/audio.cpp
+++ b/engines/eem/audio.cpp
@@ -27,7 +27,6 @@
 #include "common/debug.h"
 #include "common/endian.h"
 #include "common/events.h"
-#include "common/memstream.h"
 #include "common/substream.h"
 #include "common/system.h"
 #include "common/textconsole.h"
@@ -88,93 +87,6 @@ uint16 macSndResourceIdForPath(const Common::Path &path) {
 	return 0;
 }
 
-Audio::SeekableAudioStream *makeMacCsndStream(Common::SeekableReadStream *stream,
-											  DisposeAfterUse::Flag disposeAfterUse) {
-	if (!stream)
-		return nullptr;
-
-	if (stream->size() < 4) {
-		if (disposeAfterUse == DisposeAfterUse::YES)
-			delete stream;
-		warning("makeMacCsndStream: resource too short");
-		return nullptr;
-	}
-
-	const uint32 decodedSize = stream->readUint32BE();
-	const uint32 packedSize = (uint32)(stream->size() - stream->pos());
-	Common::Array<byte> packed;
-	packed.resize(packedSize);
-	if (stream->read(packed.data(), packedSize) != packedSize) {
-		if (disposeAfterUse == DisposeAfterUse::YES)
-			delete stream;
-		warning("makeMacCsndStream: short packed read (%u bytes)", packedSize);
-		return nullptr;
-	}
-	if (disposeAfterUse == DisposeAfterUse::YES)
-		delete stream;
-
-	byte *decoded = (byte *)malloc(decodedSize);
-	if (!decoded) {
-		warning("makeMacCsndStream: oom (%u bytes)", decodedSize);
-		return nullptr;
-	}
-
-	uint32 src = 0;
-	uint32 dst = 0;
-	bool ok = true;
-	while (ok && dst < decodedSize && src < packedSize) {
-		byte flags = packed[src++];
-		for (uint bit = 0; bit < 8 && dst < decodedSize; bit++, flags >>= 1) {
-			if (flags & 1) {
-				if (src >= packedSize) {
-					ok = false;
-					break;
-				}
-				decoded[dst++] = packed[src++];
-			} else {
-				if (src + 1 >= packedSize) {
-					ok = false;
-					break;
-				}
-				const uint16 token = ((uint16)packed[src] << 8) | packed[src + 1];
-				src += 2;
-				int32 copyPos = (int32)dst + (int32)(token & 0x0fff) - 0x1000;
-				uint count = ((token >> 12) & 0x0f) + 3;
-				while (count-- && dst < decodedSize) {
-					if (copyPos < 0 || (uint32)copyPos >= dst) {
-						ok = false;
-						break;
-					}
-					decoded[dst++] = decoded[copyPos++];
-				}
-			}
-		}
-	}
-
-	if (!ok || dst != decodedSize) {
-		warning("makeMacCsndStream: decoded %u of %u bytes", dst, decodedSize);
-		free(decoded);
-		return nullptr;
-	}
-
-	if (decodedSize != 0) {
-		byte acc = decoded[0];
-		for (uint32 i = 1; i < decodedSize; i++) {
-			acc = (byte)(acc + decoded[i]);
-			decoded[i] = acc;
-		}
-	}
-
-	Common::MemoryReadStream *sndStream =
-		new Common::MemoryReadStream(decoded, decodedSize,
-									 DisposeAfterUse::YES);
-	Audio::SeekableAudioStream *audioStream =
-		Audio::makeMacSndStream(sndStream, DisposeAfterUse::YES);
-	if (!audioStream)
-		delete sndStream;
-	return audioStream;
-}
-
 AudioPlayer::~AudioPlayer() {
 	stopAll();
 }
@@ -546,12 +458,16 @@ bool AudioPlayer::playMacMysterySound(uint num) {
 
 	stopSpool();
 
-	Audio::SeekableAudioStream *audioStream = compressed
-		? makeMacCsndStream(stream, DisposeAfterUse::YES)
-		: Audio::makeMacSndStream(stream, DisposeAfterUse::YES);
+	if (compressed) {
+		Common::SeekableReadStream *decoded = decompressMacSound(*stream);
+		delete stream;
+		stream = decoded;
+	}
+
+	Audio::SeekableAudioStream *audioStream = stream
+		? Audio::makeMacSndStream(stream, DisposeAfterUse::YES) : nullptr;
 	if (!audioStream) {
-		if (!compressed)
-			delete stream;
+		delete stream;
 		warning("AudioPlayer: Mac mystery sound resource %u is not playable",
 				resourceId);
 		return false;
diff --git a/engines/eem/eem.cpp b/engines/eem/eem.cpp
index 480ebbc889b..84b6edc5853 100644
--- a/engines/eem/eem.cpp
+++ b/engines/eem/eem.cpp
@@ -555,10 +555,10 @@ Common::Error EEMEngine::run() {
 		warning("FONT.FNT failed to load; text will not render");
 	}
 
-	// _InitMIDI @ 20a2:013a. The demo ships no music. The Mac release stores
-	// SMF MIDI resources in EEM Sound&Music instead of loose DOS XMIDI files.
+	// _InitMIDI @ 20a2:013a. The demo ships no music. Mac releases use
+	// Halestorm song and instrument resources instead of loose DOS XMIDI files.
 	if (!isDemo())
-		_music = new MusicPlayer(isFloppy(), isMacintosh());
+		_music = new MusicPlayer(isFloppy(), isMacintosh(), isLondon());
 
 	// _InitDrivers @ 1ff1:0368 (SBDIG.ADV / PASDIG.ADV).
 	_audio = new AudioPlayer(this);
diff --git a/engines/eem/music.cpp b/engines/eem/music.cpp
index 61bff51bc2d..3db172c6ece 100644
--- a/engines/eem/music.cpp
+++ b/engines/eem/music.cpp
@@ -21,12 +21,15 @@
 
 #include "audio/midiparser.h"
 #include "audio/miles.h"
+#include "audio/mixer.h"
 
 #include "common/config-manager.h"
 #include "common/debug.h"
 #include "common/endian.h"
 #include "common/file.h"
+#include "common/memstream.h"
 #include "common/stream.h"
+#include "common/system.h"
 #include "common/textconsole.h"
 
 #include "eem/detection.h"
@@ -36,10 +39,7 @@
 namespace EEM {
 
 const int kMidiDriverFlags = MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32;
-const int kMacMidiDriverFlags = MDT_MIDI | MDT_PREFER_GM;
-const uint16 kInvalidMacMidiResource = 0xffff;
 const uint16 kInvalidMacSongResource = 0xffff;
-const byte kNoMacInstrument = 0xff;
 
 Common::String musicNameFromPath(const Common::Path &path) {
 	Common::String name = path.baseName();
@@ -50,7 +50,89 @@ Common::String musicNameFromPath(const Common::Path &path) {
 	return name;
 }
 
-Common::SeekableReadStream *openMacMidiResource(uint16 resourceId) {
+static bool copyMacMidiVLQ(Common::SeekableReadStream &stream, int64 end,
+						  Common::WriteStream &output, uint32 &value) {
+	value = 0;
+	for (uint i = 0; i < 4 && stream.pos() < end; ++i) {
+		const byte b = stream.readByte();
+		output.writeByte(b);
+		value = (value << 7) | (b & 0x7f);
+		if (!(b & 0x80))
+			return true;
+	}
+	return false;
+}
+
+static Common::SeekableReadStream *expandMacMidiRunningStatus(Common::SeekableReadStream &stream) {
+	if (stream.size() < 14 || stream.readUint32BE() != MKTAG('M', 'T', 'h', 'd'))
+		return nullptr;
+	stream.seek(0);
+
+	// Halestorm shares running status between tracks. Expand it here so each
+	// EEM MIDI event carries its own command and channel, including note-offs.
+	Common::MemoryWriteStreamDynamic output(DisposeAfterUse::YES);
+	while (stream.size() - stream.pos() >= 8) {
+		const uint32 tag = stream.readUint32BE();
+		const uint32 size = stream.readUint32BE();
+		if (size > stream.size() - stream.pos())
+			return nullptr;
+		const int64 end = stream.pos() + size;
+		output.writeUint32BE(tag);
+		const uint32 sizeOffset = output.pos();
+		output.writeUint32BE(size);
+
+		if (tag != MKTAG('M', 'T', 'r', 'k')) {
+			if (output.writeStream(&stream, size) != size)
+				return nullptr;
+			continue;
+		}
+
+		byte runningStatus = 0;
+		while (stream.pos() < end) {
+			uint32 delta;
+			if (!copyMacMidiVLQ(stream, end, output, delta) || stream.pos() == end)
+				return nullptr;
+
+			byte status = stream.readByte();
+			if (status < 0x80) {
+				if (!runningStatus)
+					return nullptr;
+				stream.seek(-1, SEEK_CUR);
+				status = runningStatus;
+			}
+			output.writeByte(status);
+
+			uint32 dataSize;
+			if (status < 0xf0) {
+				runningStatus = status;
+				dataSize = ((status & 0xf0) == 0xc0 || (status & 0xf0) == 0xd0) ? 1 : 2;
+			} else if (status == 0xff) {
+				if (stream.pos() == end)
+					return nullptr;
+				output.writeByte(stream.readByte());
+				if (!copyMacMidiVLQ(stream, end, output, dataSize))
+					return nullptr;
+			} else if (status == 0xf0 || status == 0xf7) {
+				runningStatus = 0;
+				if (!copyMacMidiVLQ(stream, end, output, dataSize))
+					return nullptr;
+			} else {
+				return nullptr;
+			}
+
+			if (dataSize > end - stream.pos() || output.writeStream(&stream, dataSize) != dataSize)
+				return nullptr;
+		}
+		WRITE_BE_UINT32(output.getData() + sizeOffset, output.size() - sizeOffset - 4);
+	}
+	if (stream.pos() != stream.size() || stream.err())
+		return nullptr;
+
+	Common::MemoryReadStream expanded(output.getData(), output.size());
+	return expanded.readStream(expanded.size());
+}
+
+Common::SeekableReadStream *MusicPlayer::getResource(uint16 id, uint32 type) {
 	static const char *const kMacMusicForks[] = {
 		"EEM Sound&Music",
 		"rsrc/EEM Sound&Music",
@@ -63,14 +145,34 @@ Common::SeekableReadStream *openMacMidiResource(uint16 resourceId) {
 		MKTAG('M', 'i', 'd', 'i'),
 	};
 
-	for (uint i = 0; i < ARRAYSIZE(kMacMusicForks); i++) {
-		for (uint j = 0; j < ARRAYSIZE(kMacMidiTypes); j++) {
-			Common::SeekableReadStream *stream =
-				openMacResource(Common::Path(kMacMusicForks[i]),
-								kMacMidiTypes[j], resourceId);
-			if (stream)
+	const uint firstFork = _isLondon ? 2 : 0;
+	for (uint i = firstFork; i < firstFork + 2; i++) {
+		const Common::Path path(kMacMusicForks[i]);
+		if (type == MKTAG('M', 'I', 'D', 'I') || type == MKTAG('M', 'i', 'd', 'i')) {
+			for (uint j = 0; j < ARRAYSIZE(kMacMidiTypes); j++) {
+				Common::SeekableReadStream *stream = openMacResource(path, kMacMidiTypes[j], id);
+				if (stream) {
+					Common::SeekableReadStream *expanded = expandMacMidiRunningStatus(*stream);
+					delete stream;
+					if (!expanded)
+						warning("MusicPlayer: invalid Mac MIDI resource %u", id);
+					return expanded;
+				}
+			}
+			continue;
+		} else if (type == MKTAG('s', 'n', 'd', ' ')) {
+			// London stores most instrument samples as delta-compressed csnd.
+			Common::SeekableReadStream *packed = openMacResource(path, MKTAG('c', 's', 'n', 'd'), id);
+			if (packed) {
+				Common::SeekableReadStream *stream = decompressMacSound(*packed);
+				delete packed;
 				return stream;
+			}
 		}
+
+		Common::SeekableReadStream *stream = openMacResource(path, type, id);
+		if (stream)
+			return stream;
 	}
 
 	return nullptr;
@@ -116,45 +218,38 @@ uint16 macSongResourceIdForMus(uint num) {
 	return kInvalidMacSongResource;
 }
 
-MusicPlayer::MusicPlayer(bool isFloppy, bool isMacintosh) :
-	_isFloppy(isFloppy), _isMacintosh(isMacintosh) {
-	clearMacInstrumentMap();
+MusicPlayer::MusicPlayer(bool isFloppy, bool isMacintosh, bool isLondon) :
+	_isFloppy(isFloppy), _isMacintosh(isMacintosh), _isLondon(isLondon) {
+	if (_isMacintosh)
+		return;
 
 	// _InitMIDI @ 20a2:013a — `_AIL_register_driver` against
 	// ADLIB.ADV / SBFM.ADV / MT32MPU.ADV. We honour the launcher's
 	// "Music driver" setting and prefer MT-32 when unset.
-	const MidiDriver::DeviceHandle dev =
-		MidiDriver::detectDevice(_isMacintosh ? kMacMidiDriverFlags
-											  : kMidiDriverFlags);
+	const MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(kMidiDriverFlags);
 	MusicType musicType = MidiDriver::getMusicType(dev);
-	if (!_isMacintosh && musicType == MT_GM &&
-		ConfMan.getBool("native_mt32"))
+	if (musicType == MT_GM && ConfMan.getBool("native_mt32"))
 		musicType = MT_MT32;
 
-	if (_isMacintosh) {
+	switch (musicType) {
+	case MT_ADLIB:
+		// _MIDIPlayFile @ 20a2:024c opens SAMPLE.AD (string at 29be:14d6)
+		// and installs every patch the sequence requests via
+		// `_AIL_install_timbre`.
+		_milesAudioMode = true;
+		_driver = Audio::MidiDriver_Miles_AdLib_create(
+			Common::Path("SAMPLE.AD"), Common::Path());
+		break;
+	case MT_MT32:
+		// MT32MPU.ADV in the original. No Miles MT-32 bank ships with
+		// EEM, so use the standard MT-32 driver.
+		_milesAudioMode = true;
+		_driver = Audio::MidiDriver_Miles_MT32_create(Common::Path());
+		break;
+	default:
 		_milesAudioMode = false;
-		createDriver(kMacMidiDriverFlags);
-	} else {
-		switch (musicType) {
-		case MT_ADLIB:
-			// _MIDIPlayFile @ 20a2:024c opens SAMPLE.AD (string at 29be:14d6)
-			// and installs every patch the sequence requests via
-			// `_AIL_install_timbre`.
-			_milesAudioMode = true;
-			_driver = Audio::MidiDriver_Miles_AdLib_create(
-				Common::Path("SAMPLE.AD"), Common::Path());
-			break;
-		case MT_MT32:
-			// MT32MPU.ADV in the original. No Miles MT-32 bank ships with
-			// EEM, so use the standard MT-32 driver.
-			_milesAudioMode = true;
-			_driver = Audio::MidiDriver_Miles_MT32_create(Common::Path());
-			break;
-		default:
-			_milesAudioMode = false;
-			createDriver(kMidiDriverFlags);
-			break;
-		}
+		createDriver(kMidiDriverFlags);
+		break;
 	}
 
 	if (_driver) {
@@ -165,9 +260,7 @@ MusicPlayer::MusicPlayer(bool isFloppy, bool isMacintosh) :
 			_driver = nullptr;
 		} else {
 			// Miles AdLib handles its own reset.
-			if (_isMacintosh) {
-				_driver->sendGMReset();
-			} else if (musicType != MT_ADLIB) {
+			if (musicType != MT_ADLIB) {
 				if (musicType == MT_MT32 || _nativeMT32)
 					_driver->sendMT32Reset();
 				else
@@ -180,16 +273,38 @@ MusicPlayer::MusicPlayer(bool isFloppy, bool isMacintosh) :
 	}
 }
 
-void MusicPlayer::send(uint32 b) {
-	if (_isMacintosh && (b & 0xF0) == 0xC0) {
-		const byte channel = (byte)(b & 0x0F);
-		const byte rawProgram = (byte)((b >> 8) & 0x7F);
-		const byte inst = _macChannelInstrument[channel] != kNoMacInstrument
-			? _macChannelInstrument[channel] : rawProgram;
-		const byte gmProgram = mapMacInstrumentToGM(inst, channel);
-		b = (b & 0xFFFF00FF) | ((uint32)gmProgram << 8);
+MusicPlayer::~MusicPlayer() {
+	stop();
+}
+
+void MusicPlayer::stop() {
+	if (_isMacintosh) {
+		// Halestorm may already report the song as finished and ignore abort.
+		// Releasing EEM's driver also stops any remaining sample voices.
+		delete _macDriver;
+		_macDriver = nullptr;
+	} else {
+		Audio::MidiPlayer::stop();
+	}
+}
+
+bool MusicPlayer::isPlaying() const {
+	if (_isMacintosh)
+		return _macDriver && _macDriver->doCommand(Audio::HalestormDriver::kSongIsPlaying);
+	return Audio::MidiPlayer::isPlaying();
+}
+
+void MusicPlayer::setVolume(int volume) {
+	if (_isMacintosh) {
+		_masterVolume = CLIP<int>(volume, 0, Audio::Mixer::kMaxMixerVolume);
+		if (_macDriver)
+			_macDriver->setMusicVolume(_masterVolume);
+	} else {
+		Audio::MidiPlayer::setVolume(volume);
 	}
+}
 
+void MusicPlayer::send(uint32 b) {
 	// Miles drivers (both AdLib and MT-32) implement their own per-
 	// source-channel mixing and timbre installation, so forward the raw
 	// event.
@@ -200,18 +315,15 @@ void MusicPlayer::send(uint32 b) {
 	Audio::MidiPlayer::send(b);
 }
 
-void MusicPlayer::startLoadedMusic(const Common::String &name, bool loop,
-								   bool smf) {
-	_parser = smf ? MidiParser::createParser_SMF()
-				  : MidiParser::createParser_XMIDI(nullptr, nullptr, 0);
+void MusicPlayer::startLoadedMusic(const Common::String &name, bool loop) {
+	_parser = MidiParser::createParser_XMIDI(nullptr, nullptr, 0);
 	_parser->setMidiDriver(this);
 	_parser->setTimerRate(_driver->getBaseTempo());
 	_parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
 	_parser->property(MidiParser::mpSendSustainOffOnNotesOff, 1);
 
 	if (!_parser->loadMusic(_xmiData.data(), _xmiData.size())) {
-		warning("MusicPlayer: %s parser rejected %s",
-				smf ? "SMF" : "XMIDI", name.c_str());
+		warning("MusicPlayer: XMIDI parser rejected %s", name.c_str());
 		delete _parser;
 		_parser = nullptr;
 		_xmiData.clear();
@@ -225,142 +337,39 @@ void MusicPlayer::startLoadedMusic(const Common::String &name, bool loop,
 	syncVolume();
 	_isPlaying = true;
 	debugC(1, kDebugSound,
-		   "MusicPlayer: playing %s (%u bytes, loop=%d, miles=%d, smf=%d)",
-		   name.c_str(), _xmiData.size(), loop, _milesAudioMode, smf);
-}
-
-void MusicPlayer::clearMacInstrumentMap() {
-	memset(_macChannelInstrument, kNoMacInstrument,
-		   sizeof(_macChannelInstrument));
-}
-
-byte MusicPlayer::mapMacInstrumentToGM(byte inst, byte channel) const {
-	if (channel == 9)
-		return 0;
-
-	switch (inst) {
-	case 2:
-	case 3:
-		return 0;   // Piano
-	case 11:
-		return 16;  // Organ
-	case 60:
-		return 24;  // Guitar
-	case 64:
-	case 65:
-		return 33;  // Bass
-	case 73:
-	case 74:
-	case 75:
-		return 73;  // Flute
-	case 83:
-	case 84:
-		return 71;  // Clarinet
-	default:
-		return inst < 128 ? inst : 0;
-	}
-}
-
-bool MusicPlayer::loadMacSong(uint16 resourceId, uint16 &midiId) {
-	midiId = kInvalidMacMidiResource;
-	clearMacInstrumentMap();
-
-	static const char *const kMacMusicForks[] = {
-		"EEM Sound&Music",
-		"rsrc/EEM Sound&Music",
-		"EEM London CD",
-		"rsrc/EEM London CD",
-	};
-
-	Common::SeekableReadStream *stream = nullptr;
-	for (uint i = 0; i < ARRAYSIZE(kMacMusicForks) && !stream; i++) {
-		stream = openMacResource(Common::Path(kMacMusicForks[i]),
-								 MKTAG('S', 'O', 'N', 'G'), resourceId);
-	}
-	if (!stream) {
-		warning("MusicPlayer: Mac SONG resource %u missing", resourceId);
-		return false;
-	}
-
-	const uint32 size = (uint32)stream->size();
-	if (size < 18) {
-		delete stream;
-		warning("MusicPlayer: Mac SONG resource %u too short", resourceId);
-		return false;
-	}
-
-	Common::Array<byte> song;
-	song.resize(size);
-	if (stream->read(song.data(), size) != size) {
-		delete stream;
-		warning("MusicPlayer: short read on Mac SONG resource %u", resourceId);
-		return false;
-	}
-	delete stream;
-
-	midiId = READ_BE_UINT16(song.data());
-	const uint16 instCount = READ_BE_UINT16(song.data() + 16);
-	uint32 pos = 18;
-	for (uint i = 0; i < instCount && pos + 4 <= size; i++, pos += 4) {
-		const uint16 channel = READ_BE_UINT16(song.data() + pos);
-		const uint16 inst = READ_BE_UINT16(song.data() + pos + 2);
-		if (channel < ARRAYSIZE(_macChannelInstrument) && inst < 128)
-			_macChannelInstrument[channel] = (byte)inst;
-	}
-
-	return true;
+		   "MusicPlayer: playing %s (%u bytes, loop=%d, miles=%d)",
+		   name.c_str(), _xmiData.size(), loop, _milesAudioMode);
 }
 
 void MusicPlayer::playMacSongResource(uint16 resourceId, bool loop) {
+	stop();
 	if (resourceId == kInvalidMacSongResource)
 		return;
 
-	uint16 midiId = kInvalidMacMidiResource;
-	if (!loadMacSong(resourceId, midiId))
-		return;
-
-	playMacMidiResource(midiId, loop);
-}
-
-void MusicPlayer::playMacMidiResource(uint16 resourceId, bool loop) {
-	if (resourceId == kInvalidMacMidiResource)
-		return;
-
-	Common::SeekableReadStream *stream = openMacMidiResource(resourceId);
-	if (!stream) {
-		warning("MusicPlayer: Mac Midi resource %u missing", resourceId);
+	_macDriver = new Audio::HalestormDriver(this, g_system->getMixer());
+	if (!_macDriver->init(true, Audio::HalestormDriver::kSimple, 0, false)) {
+		warning("MusicPlayer: Halestorm initialization failed");
+		stop();
 		return;
 	}
 
-	const uint32 size = (uint32)stream->size();
-	if (size == 0) {
-		delete stream;
-		warning("MusicPlayer: Mac Midi resource %u is empty", resourceId);
-		return;
-	}
-	_xmiData.resize(size);
-	if (stream->read(_xmiData.data(), size) != size) {
-		delete stream;
-		_xmiData.clear();
-		warning("MusicPlayer: short read on Mac Midi resource %u", resourceId);
+	syncVolume();
+	const int command = loop ? Audio::HalestormDriver::kSongPlayLoop : Audio::HalestormDriver::kSongPlayOnce;
+	const int result = _macDriver->doCommand(command, resourceId);
+	if (result) {
+		warning("MusicPlayer: failed to play Mac SONG resource %u (%d)", resourceId, result);
+		stop();
 		return;
 	}
-	delete stream;
 
-	startLoadedMusic(Common::String::format("Mac Midi %u", resourceId), loop,
-					 /* smf= */ true);
+	debugC(1, kDebugSound, "MusicPlayer: playing Mac SONG %u (loop=%d)", resourceId, loop);
 }
 
 void MusicPlayer::playFile(const Common::Path &xmiPath, bool loop) {
-	if (!_driver)
-		return;
-
-	Common::StackLock lock(_mutex);
-	stop();
-
 	if (_isMacintosh) {
 		const uint16 resourceId = macSongResourceIdForFile(xmiPath);
 		if (resourceId == kInvalidMacSongResource) {
+			stop();
 			warning("MusicPlayer: no Mac SONG mapping for %s",
 					xmiPath.toString().c_str());
 			return;
@@ -369,6 +378,12 @@ void MusicPlayer::playFile(const Common::Path &xmiPath, bool loop) {
 		return;
 	}
 
+	if (!_driver)
+		return;
+
+	Common::StackLock lock(_mutex);
+	stop();
+
 	Common::File f;
 	if (!f.open(xmiPath)) {
 		warning("MusicPlayer: %s missing", xmiPath.toString().c_str());
@@ -387,14 +402,16 @@ void MusicPlayer::playFile(const Common::Path &xmiPath, bool loop) {
 		return;
 	}
 
-	startLoadedMusic(xmiPath.toString(), loop, /* smf= */ false);
+	startLoadedMusic(xmiPath.toString(), loop);
 }
 
 void MusicPlayer::playMus(uint num, bool loop) {
 	if (_isMacintosh) {
-		Common::StackLock lock(_mutex);
-		stop();
-		playMacSongResource(macSongResourceIdForMus(num), loop);
+		// London SONG ids are 1000 + the MUS number; EEM1 uses named tracks.
+		const uint16 resourceId = _isLondon
+			? (num < kInvalidMacSongResource - 1000 ? 1000 + num : kInvalidMacSongResource)
+			: macSongResourceIdForMus(num);
+		playMacSongResource(resourceId, loop);
 		return;
 	}
 
diff --git a/engines/eem/music.h b/engines/eem/music.h
index 0aec7735c32..8aea1e8486c 100644
--- a/engines/eem/music.h
+++ b/engines/eem/music.h
@@ -22,6 +22,7 @@
 #ifndef EEM_MUSIC_H
 #define EEM_MUSIC_H
 
+#include "audio/mac/halestorm.h"
 #include "audio/midiplayer.h"
 
 #include "common/array.h"
@@ -31,7 +32,8 @@
 namespace EEM {
 
 /**
- * MIDI music player. Mirrors MIDI.C in EEMCD.EXE
+ * Music player. DOS uses MIDI; Macintosh uses Halestorm with the original
+ * SONG, Midi, INST and snd resources. Mirrors MIDI.C in EEMCD.EXE
  * (_MIDIPlayFile / _MIDIPlay / _StopMIDI / _IsMIDIPlaying /
  * _StartTravelMusic family at 20a2:00e2-05c9).
  *
@@ -48,9 +50,10 @@ namespace EEM {
  *   MUS00005 — winner (_DisplayCorrect @ 1df2:0789).
  *   MUS00006 — loser  (_DisplayAlibi  @ 1df2:018a).
  */
-class MusicPlayer : public Audio::MidiPlayer {
+class MusicPlayer : public Audio::MidiPlayer, private Audio::HalestormLoader {
 public:
-	explicit MusicPlayer(bool isFloppy = false, bool isMacintosh = false);
+	explicit MusicPlayer(bool isFloppy = false, bool isMacintosh = false, bool isLondon = false);
+	~MusicPlayer() override;
 
 	/// _MIDIPlayFile @ 20a2:024c. loop=true mirrors
 	void playFile(const Common::Path &xmiPath, bool loop = false);
@@ -59,23 +62,25 @@ public:
 	/// floppy: TRAVEL-N.XMI / FANFARE2.XMI.
 	void playMus(uint num, bool loop = false);
 
+	void stop() override;
+	bool isPlaying() const;
+	void setVolume(int volume) override;
+
 	// WORKAROUND: Miles drivers handle source-channel routing themselves;
 	// bypass Audio::MidiPlayer::sendToChannel. Same as Toltecs / SAGA.
 	void send(uint32 b) override;
 
 private:
-	void playMacMidiResource(uint16 resourceId, bool loop);
 	void playMacSongResource(uint16 resourceId, bool loop);
-	void startLoadedMusic(const Common::String &name, bool loop, bool smf);
-	bool loadMacSong(uint16 resourceId, uint16 &midiId);
-	void clearMacInstrumentMap();
-	byte mapMacInstrumentToGM(byte inst, byte channel) const;
+	void startLoadedMusic(const Common::String &name, bool loop);
+	Common::SeekableReadStream *getResource(uint16 id, uint32 type) override;
 
 	bool _milesAudioMode = false;
 	const bool _isFloppy;
 	const bool _isMacintosh;
+	const bool _isLondon;
 	Common::Array<byte> _xmiData;
-	byte _macChannelInstrument[16] = {};
+	Audio::HalestormDriver *_macDriver = nullptr;
 };
 
 } // End of namespace EEM
diff --git a/engines/eem/resource.cpp b/engines/eem/resource.cpp
index fb8161d95aa..f61492637af 100644
--- a/engines/eem/resource.cpp
+++ b/engines/eem/resource.cpp
@@ -147,6 +147,76 @@ Common::SeekableReadStream *openMacResource(const Common::Path &path,
 	return openRawMacResource(path, typeId, resourceId);
 }
 
+Common::SeekableReadStream *decompressMacSound(Common::SeekableReadStream &stream) {
+	if (stream.size() < 4) {
+		warning("decompressMacSound: resource too short");
+		return nullptr;
+	}
+
+	const uint32 decodedSize = stream.readUint32BE();
+	const uint32 packedSize = (uint32)(stream.size() - stream.pos());
+	Common::Array<byte> packed;
+	packed.resize(packedSize);
+	if (stream.read(packed.data(), packedSize) != packedSize) {
+		warning("decompressMacSound: short packed read (%u bytes)", packedSize);
+		return nullptr;
+	}
+
+	byte *decoded = (byte *)malloc(decodedSize);
+	if (!decoded) {
+		warning("decompressMacSound: oom (%u bytes)", decodedSize);
+		return nullptr;
+	}
+
+	uint32 src = 0;
+	uint32 dst = 0;
+	bool ok = true;
+	while (ok && dst < decodedSize && src < packedSize) {
+		byte flags = packed[src++];
+		for (uint bit = 0; bit < 8 && dst < decodedSize; bit++, flags >>= 1) {
+			if (flags & 1) {
+				if (src >= packedSize) {
+					ok = false;
+					break;
+				}
+				decoded[dst++] = packed[src++];
+			} else {
+				if (src + 1 >= packedSize) {
+					ok = false;
+					break;
+				}
+				const uint16 token = ((uint16)packed[src] << 8) | packed[src + 1];
+				src += 2;
+				int32 copyPos = (int32)dst + (int32)(token & 0x0fff) - 0x1000;
+				uint count = ((token >> 12) & 0x0f) + 3;
+				while (count-- && dst < decodedSize) {
+					if (copyPos < 0 || (uint32)copyPos >= dst) {
+						ok = false;
+						break;
+					}
+					decoded[dst++] = decoded[copyPos++];
+				}
+			}
+		}
+	}
+
+	if (!ok || dst != decodedSize) {
+		warning("decompressMacSound: decoded %u of %u bytes", dst, decodedSize);
+		free(decoded);
+		return nullptr;
+	}
+
+	if (decodedSize != 0) {
+		byte acc = decoded[0];
+		for (uint32 i = 1; i < decodedSize; i++) {
+			acc = (byte)(acc + decoded[i]);
+			decoded[i] = acc;
+		}
+	}
+
+	return new Common::MemoryReadStream(decoded, decodedSize, DisposeAfterUse::YES);
+}
+
 bool DBDArchive::open(const Common::Path &dbdName, const Common::Path &dbxName, bool bigEndian) {
 	close();
 	_bigEndian = bigEndian;
diff --git a/engines/eem/resource.h b/engines/eem/resource.h
index 229bf707c9d..0e4567926fa 100644
--- a/engines/eem/resource.h
+++ b/engines/eem/resource.h
@@ -87,6 +87,9 @@ Common::SeekableReadStream *openMacResource(const Common::Path &path,
 											uint32 typeId,
 											uint16 resourceId);
 
+/// Decode a csnd resource to a snd resource. The caller owns the returned stream.
+Common::SeekableReadStream *decompressMacSound(Common::SeekableReadStream &stream);
+
 } // End of namespace EEM
 
 #endif




More information about the Scummvm-git-logs mailing list