[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