[Scummvm-git-logs] scummvm master -> 8fdc0580e06edadd8d71e4883990f318dd7431c3

bluegr noreply at scummvm.org
Sat Feb 21 09:24:36 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:
8fdc0580e0 AGOS: Simon1 - Support for the Desktop Tracker(DskT) format compressed mods used for music by Simon 1 for Acorn Archimed


Commit: 8fdc0580e06edadd8d71e4883990f318dd7431c3
    https://github.com/scummvm/scummvm/commit/8fdc0580e06edadd8d71e4883990f318dd7431c3
Author: Robert Megone (robert.megone at gmail.com)
Date: 2026-02-21T11:24:32+02:00

Commit Message:
AGOS: Simon1 - Support for the Desktop Tracker(DskT) format compressed mods used for music by Simon 1 for Acorn Archimedes.

Changed paths:
  A audio/mods/desktoptracker.cpp
  A audio/mods/desktoptracker.h
    audio/module.mk
    engines/agos/res_snd.cpp


diff --git a/audio/mods/desktoptracker.cpp b/audio/mods/desktoptracker.cpp
new file mode 100644
index 00000000000..0a8b045d745
--- /dev/null
+++ b/audio/mods/desktoptracker.cpp
@@ -0,0 +1,543 @@
+/* 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/>.
+ *
+ */
+
+/* Adapted from ArcTracker https://github.com/richardjwild/arctracker/ */
+
+#include "common/array.h"
+#include "common/memstream.h"
+#include "common/stream.h"
+#include "common/util.h"
+
+#include "audio/audiostream.h"
+
+namespace Audio {
+
+namespace Modules {
+
+static uint32 alignToWord(uint32 x) {
+	return (x + 1) & ~1U;
+}
+
+static uint32 readLE32FromBuf(const byte *p) {
+	return (uint32)p[0] | ((uint32)p[1] << 8) | ((uint32)p[2] << 16) | ((uint32)p[3] << 24);
+}
+
+static int8 vidcToSigned8(byte enc) {
+
+	static bool sInit = false;
+	static int8 sTable[256];
+
+	if (!sInit) {
+		const int BIAS = 0x84;
+		const int SIGN_BIT = 0x80;
+		const int QUANT_MASK = 0x0F;
+		const int SEG_MASK = 0x70;
+		const double EXPANDED_MAX = 32124.0;
+
+		for (int i = 0; i <= 127; ++i) {
+			const int mu = 127 - i;
+			const int normal = ~mu;
+			const int biased = ((normal & QUANT_MASK) << 3) + BIAS;
+			const unsigned int seg = ((unsigned int)normal & (unsigned int)SEG_MASK) >> 4;
+			const int linearPos = (normal & SIGN_BIT) ? (BIAS - (biased << (int)seg)) : ((biased << (int)seg) - BIAS);
+			const double f = (double)linearPos / EXPANDED_MAX;
+
+			const double p = f * 127.0;
+			const double n = -f * 127.0;
+			const int vpos = CLIP<int>((int)(p >= 0.0 ? (p + 0.5) : (p - 0.5)), -127, 127);
+			const int vneg = CLIP<int>((int)(n >= 0.0 ? (n + 0.5) : (n - 0.5)), -127, 127);
+
+			sTable[i * 2] = (int8)vpos;
+			sTable[(i * 2) + 1] = (int8)vneg;
+		}
+
+		sInit = true;
+	}
+
+	return sTable[enc];
+}
+
+static uint16 periodForNote(uint32 n) {
+
+	static const uint16 kPeriods[62] = {
+		0x06B0, 0x0650, 0x05F5, 0x05A0, 0x054D, 0x0501, 0x04B9, 0x0475, 0x0435, 0x03F9, 0x03C1, 0x038B,
+		0x0358, 0x0328, 0x02FA, 0x02D0, 0x02A6, 0x0280, 0x025C, 0x023A, 0x021A, 0x01FC, 0x01E0, 0x01C5,
+		0x01AC, 0x0194, 0x017D, 0x0168, 0x0153, 0x0140, 0x012E, 0x011D, 0x010D, 0x00FE, 0x00F0, 0x00E2,
+		0x00D6, 0x00CA, 0x00BE, 0x00B4, 0x00AA, 0x00A0, 0x0097, 0x008F, 0x0087, 0x007F, 0x0078, 0x0071,
+		0x006B, 0x0065, 0x005F, 0x005A, 0x0055, 0x0050, 0x004C, 0x0047, 0x0043, 0x0040, 0x003C, 0x0039,
+		0x0035, 0x0032
+	};
+	n = CLIP<uint32>(n, 0, 61);
+	return kPeriods[n];
+}
+
+struct DttHeader {
+	char name[64];
+	char author[64];
+	uint32 flags;
+	uint32 numChannels;
+	uint32 tuneLength;
+	byte initialStereo[8];
+	uint32 initialSpeed;
+	uint32 restart;
+	uint32 numPatterns;
+	uint32 numSamples;
+};
+
+struct DttSample {
+	char name[32];
+	int transpose;
+	uint8 defaultGain;
+	uint32 repeatOffset;
+	uint32 repeatLength;
+	uint32 sampleLength;
+	uint32 sampleDataOffset;
+	Common::Array<int8> pcm;
+};
+
+struct DttEffect {
+	uint8 cmd;
+	uint8 param;
+};
+
+struct DttEvent {
+	uint8 sample;
+	uint8 note;
+	DttEffect effects[4];
+	uint8 numEffects;
+};
+
+struct VoiceState {
+	int sampleIdx;
+	uint8 vol;
+	uint16 period;
+	uint32 pos16;
+	uint32 step16;
+};
+
+static uint8 mask6(uint32 x, int shift) { return (uint8)((x >> shift) & 0x3F); }
+static uint8 mask5(uint32 x, int shift) { return (uint8)((x >> shift) & 0x1F); }
+static uint8 mask8(uint32 x, int shift) { return (uint8)((x >> shift) & 0xFF); }
+
+static bool isMultipleEffect(uint32 raw0) {
+	return (raw0 & (0x1FU << 17)) != 0;
+}
+
+static void decodeEvent(const byte *p, DttEvent &out, uint32 &bytesUsed) {
+	const uint32 raw0 = readLE32FromBuf(p);
+	out.sample = mask6(raw0, 0);
+	out.note = mask6(raw0, 6);
+
+	if (isMultipleEffect(raw0)) {
+		const uint32 raw1 = readLE32FromBuf(p + 4);
+		out.numEffects = 4;
+		out.effects[0] = DttEffect{ mask5(raw0, 12), mask8(raw1, 0) };
+		out.effects[1] = DttEffect{ mask5(raw0, 17), mask8(raw1, 8) };
+		out.effects[2] = DttEffect{ mask5(raw0, 22), mask8(raw1, 16) };
+		out.effects[3] = DttEffect{ mask5(raw0, 27), mask8(raw1, 24) };
+		bytesUsed = 8;
+	} else {
+		out.numEffects = 1;
+		out.effects[0] = DttEffect{ mask5(raw0, 12), mask8(raw0, 24) };
+		out.effects[1] = DttEffect{ 0, 0 };
+		out.effects[2] = DttEffect{ 0, 0 };
+		out.effects[3] = DttEffect{ 0, 0 };
+		bytesUsed = 4;
+	}
+}
+
+class DesktopTrackerStream final : public AudioStream {
+public:
+	DesktopTrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo)
+		: _rate(rate), _stereo(false), _songPos(0), _row(0), _speed50ths(6), _samplesUntilNextRow(0), _ended(false), _sawAnyNote(false) {
+
+		if (!stream)
+			error("DesktopTrackerStream: null input stream");
+
+		stream->seek(0, SEEK_END);
+		const int32 sz = (int32)stream->pos();
+		stream->seek(0, SEEK_SET);
+		if (sz <= 0)
+			error("DesktopTrackerStream: empty input stream");
+
+		_module.resize((uint32)sz);
+		if (stream->read(_module.begin(), (uint32)sz) != (uint32)sz)
+			error("DesktopTrackerStream: short read");
+
+		delete stream;
+		stream = nullptr;
+
+		parseDskT(offs);
+
+		for (uint32 c = 0; c < 8; ++c) {
+			_voices[c].sampleIdx = -1;
+			_voices[c].vol = 0;
+			_voices[c].period = 0;
+			_voices[c].pos16 = 0;
+			_voices[c].step16 = 0;
+		}
+
+		const double secondsPerRow = (double)MAX<uint32>(_speed50ths, 1) / 50.0;
+		_samplesUntilNextRow = (int32)MAX<int>(1, (int)(secondsPerRow * (double)_rate + 0.5));
+
+	}
+
+	bool isStereo() const override { return _stereo; }
+	int getRate() const override { return _rate; }
+	bool endOfData() const override { return _ended; }
+
+	int readBuffer(int16 *buffer, const int numSamples) override {
+		if (!buffer || numSamples <= 0)
+			return 0;
+
+		for (int i = 0; i < numSamples; ++i)
+			buffer[i] = 0;
+
+		int framesLeft = numSamples;
+		while (framesLeft > 0 && !_ended) {
+			const int chunk = MIN<int>(framesLeft, MAX<int>(1, _samplesUntilNextRow));
+			mixMono(buffer, chunk);
+
+			buffer += chunk;
+			framesLeft -= chunk;
+
+			_samplesUntilNextRow -= chunk;
+			while (_samplesUntilNextRow <= 0 && !_ended) {
+				advanceRow();
+				const double secondsPerRow = (double)MAX<uint32>(_speed50ths, 1) / 50.0;
+				_samplesUntilNextRow += (int32)MAX<int>(1, (int)(secondsPerRow * (double)_rate + 0.5));
+			}
+		}
+
+		return numSamples;
+	}
+
+private:
+	void parseDskT(int offs) {
+		if ((uint32)offs >= _module.size())
+			error("DesktopTrackerStream: offs out of range");
+
+		const byte *fileBase = _module.begin();
+		const uint32 fileSize = (uint32)_module.size();
+		const byte *base = fileBase + offs;
+		const uint32 size = fileSize - (uint32)offs;
+
+		if (size < 4 + 64 + 64)
+			error("DesktopTrackerStream: module too small");
+
+		if (base[0] != 'D' || base[1] != 's' || base[2] != 'k' || base[3] != 'T')
+			error("DesktopTrackerStream: not a DskT module");
+
+		memset(&_hdr, 0, sizeof(_hdr));
+
+		const byte *p = base + 4;
+		memcpy(_hdr.name, p, 64);
+		_hdr.name[63] = 0;
+		p += 64;
+
+		memcpy(_hdr.author, p, 64);
+		_hdr.author[63] = 0;
+		p += 64;
+
+		_hdr.flags = readLE32FromBuf(p); p += 4;
+		_hdr.numChannels = readLE32FromBuf(p); p += 4;
+		_hdr.tuneLength = readLE32FromBuf(p); p += 4;
+
+		memcpy(_hdr.initialStereo, p, 8); p += 8;
+
+		_hdr.initialSpeed = readLE32FromBuf(p); p += 4;
+		_hdr.restart = readLE32FromBuf(p); p += 4;
+		_hdr.numPatterns = readLE32FromBuf(p); p += 4;
+		_hdr.numSamples = readLE32FromBuf(p); p += 4;
+
+		if (_hdr.numChannels == 0 || _hdr.numChannels > 8)
+			error("DesktopTrackerStream: unsupported channels=%u", (uint32)_hdr.numChannels);
+
+		if (_hdr.tuneLength == 0 || _hdr.tuneLength > 256)
+			error("DesktopTrackerStream: bad tune length=%u", (uint32)_hdr.tuneLength);
+
+		if (_hdr.numPatterns == 0 || _hdr.numPatterns > 1024)
+			error("DesktopTrackerStream: bad numPatterns=%u", (uint32)_hdr.numPatterns);
+
+		if (_hdr.numSamples == 0 || _hdr.numSamples > 256)
+			error("DesktopTrackerStream: bad numSamples=%u", (uint32)_hdr.numSamples);
+
+		_speed50ths = (uint32)_hdr.initialSpeed != 0 ? (uint8)_hdr.initialSpeed : (uint8)6;
+
+		const uint32 hdrSize = 4 + 64 + 64 + 4 + 4 + 4 + 8 + 4 + 4 + 4 + 4;
+		const uint32 positionsOff = hdrSize;
+		const uint32 positionsSize = (uint32)_hdr.tuneLength;
+		if (positionsOff + positionsSize > size)
+			error("DesktopTrackerStream: positions out of range");
+
+		_sequence.resize((uint32)_hdr.tuneLength);
+		for (uint32 i = 0; i < (uint32)_hdr.tuneLength; ++i)
+			_sequence[i] = base[positionsOff + i];
+
+		const uint32 patternOffsetsOff = positionsOff + alignToWord(positionsSize);
+		const uint32 patternOffsetsSize = (uint32)_hdr.numPatterns * 4U;
+		if (patternOffsetsOff + patternOffsetsSize > size)
+			error("DesktopTrackerStream: patternOffsets out of range");
+
+		_patternOffsets.resize((uint32)_hdr.numPatterns);
+		for (uint32 i = 0; i < (uint32)_hdr.numPatterns; ++i)
+			_patternOffsets[i] = readLE32FromBuf(base + patternOffsetsOff + (i * 4));
+
+		const uint32 patternLengthsOff = patternOffsetsOff + patternOffsetsSize;
+		const uint32 patternLengthsSize = (uint32)_hdr.numPatterns;
+		if (patternLengthsOff + patternLengthsSize > size)
+			error("DesktopTrackerStream: patternLengths out of range");
+
+		_patternLengths.resize((uint32)_hdr.numPatterns);
+		for (uint32 i = 0; i < (uint32)_hdr.numPatterns; ++i)
+			_patternLengths[i] = base[patternLengthsOff + i];
+
+		const uint32 samplesOff = patternLengthsOff + alignToWord(patternLengthsSize);
+		const uint32 sampleStructSize = 64;
+		const uint32 samplesSize = (uint32)_hdr.numSamples * sampleStructSize;
+		if (samplesOff + samplesSize > size)
+			error("DesktopTrackerStream: samples out of range");
+
+		_samples.resize((uint32)_hdr.numSamples);
+		for (uint32 i = 0; i < (uint32)_hdr.numSamples; ++i) {
+			const byte *sp = base + samplesOff + i * sampleStructSize;
+			DttSample &smp = _samples[i];
+			memset(smp.name, 0, sizeof(smp.name));
+
+			const uint8 note = sp[0];
+			const uint8 vol = sp[1];
+
+			const uint32 repeatOffset = readLE32FromBuf(sp + 16);
+			const uint32 repeatLength = readLE32FromBuf(sp + 20);
+			const uint32 sampleLength = readLE32FromBuf(sp + 24);
+			memcpy(smp.name, sp + 28, 32);
+			smp.name[31] = 0;
+			const uint32 sampleDataOffset = readLE32FromBuf(sp + 60);
+
+			smp.transpose = 26 - (int)note;
+			smp.defaultGain = (uint8)(vol & 0x7F);
+			smp.repeatOffset = repeatOffset;
+			smp.repeatLength = repeatLength;
+			smp.sampleLength = sampleLength;
+			smp.sampleDataOffset = sampleDataOffset;
+
+			smp.pcm.clear();
+			if (sampleDataOffset != 0 && sampleLength != 0) {
+				if (sampleDataOffset + sampleLength > fileSize) {
+					smp.sampleLength = 0;
+					smp.pcm.clear();
+				} else {
+					smp.pcm.resize(sampleLength + 1);
+					for (uint32 k = 0; k < sampleLength; ++k)
+						smp.pcm[k] = vidcToSigned8(fileBase[sampleDataOffset + k]);
+					smp.pcm[sampleLength] = (sampleLength > 0) ? smp.pcm[sampleLength - 1] : 0;
+				}
+			}
+		}
+	}
+
+	void advanceRow() {
+		if (_ended)
+			return;
+
+		if (_songPos >= (uint32)_sequence.size()) {
+			_ended = true;
+			return;
+		}
+
+		const uint32 patIdx = (uint32)_sequence[_songPos];
+		if (patIdx >= (uint32)_patternOffsets.size()) {
+			_ended = true;
+			return;
+		}
+
+		applyRow(patIdx, _row);
+
+		const uint32 rows = (uint32)_patternLengths[patIdx];
+		_row++;
+		if (_row >= rows) {
+			_row = 0;
+			_songPos++;
+			if (_songPos >= (uint32)_sequence.size())
+				_songPos = 0;
+		}
+	}
+
+	void applyRow(uint32 patIdx, uint32 row) {
+		const uint32 poff = _patternOffsets[patIdx];
+		if (poff == 0 || poff >= _module.size())
+			return;
+
+		const byte *p = _module.begin() + poff;
+		for (uint32 r = 0; r < row; ++r) {
+			for (uint32 c = 0; c < (uint32)_hdr.numChannels; ++c) {
+				DttEvent ev;
+				uint32 used = 0;
+				decodeEvent(p, ev, used);
+				p += used;
+			}
+		}
+
+		for (uint32 c = 0; c < (uint32)_hdr.numChannels; ++c) {
+			DttEvent ev;
+			uint32 used = 0;
+			decodeEvent(p, ev, used);
+			p += used;
+
+			if (ev.sample != 0 && ev.sample <= _samples.size()) {
+				const int si = (int)ev.sample - 1;
+				_voices[c].sampleIdx = si;
+				_voices[c].vol = _samples[si].defaultGain;
+				_voices[c].pos16 = 0;
+			}
+
+			for (uint32 i = 0; i < (uint32)ev.numEffects; ++i) {
+				const uint8 cmd = ev.effects[i].cmd;
+				const uint8 param = ev.effects[i].param;
+				if (cmd == 0x0C) {
+					_voices[c].vol = (uint8)MIN<uint32>(param & 0x7F, 127);
+				} else if (cmd == 0x0F) {
+					if (param != 0)
+						_speed50ths = param;
+				}
+			}
+
+			if (ev.note != 0 && _voices[c].sampleIdx >= 0) {
+
+				const DttSample &smp = _samples[(uint32)_voices[c].sampleIdx];
+				if (!_sawAnyNote) {
+					_sawAnyNote = true;
+				}
+				if (!smp.pcm.empty()) {
+					const int note = (int)ev.note + smp.transpose;
+					const uint32 nn = (uint32)CLIP<int>(note, 0, 61);
+					_voices[c].period = periodForNote(nn);
+
+					const double conv = 3273808.59375;
+					const double step = conv / ((double)_voices[c].period * (double)_rate);
+					const uint32 step16 = (uint32)MAX<uint32>(1, (uint32)(step * 65536.0 + 0.5));
+					_voices[c].step16 = step16;
+					_voices[c].pos16 = 0;
+				}
+			}
+		}
+	}
+
+	void mixMono(int16 *dst, int frames) {
+		for (int i = 0; i < frames; ++i) {
+			int mix = 0;
+
+			for (uint32 c = 0; c < (uint32)_hdr.numChannels; ++c) {
+				VoiceState &vs = _voices[c];
+				if (vs.sampleIdx < 0 || vs.step16 == 0)
+					continue;
+
+				const DttSample &smp = _samples[(uint32)vs.sampleIdx];
+				if (smp.pcm.empty() || smp.sampleLength < 2)
+					continue;
+
+				uint32 pos = vs.pos16;
+				uint32 idx = pos >> 16;
+
+				if (idx >= smp.sampleLength) {
+					if (smp.repeatLength > 2) {
+						const uint32 loopEnd = smp.repeatOffset + smp.repeatLength;
+						if (loopEnd > smp.repeatOffset) {
+							if (idx >= loopEnd)
+								idx = smp.repeatOffset + ((idx - smp.repeatOffset) % smp.repeatLength);
+						} else {
+							idx = smp.repeatOffset;
+						}
+						pos = (idx << 16) | (pos & 0xFFFF);
+					} else {
+						continue;
+					}
+				}
+
+				const uint32 i0 = (pos >> 16);
+				const uint32 frac = pos & 0xFFFF;
+
+				const int s0 = (int)smp.pcm[MIN<uint32>(i0, (uint32)smp.pcm.size() - 1)];
+				const int s1 = (int)smp.pcm[MIN<uint32>(i0 + 1, (uint32)smp.pcm.size() - 1)];
+				const int s8 = (int)((((int64)s0 * (int64)(65536 - (int)frac)) + ((int64)s1 * (int64)frac)) >> 16);
+
+				const int sample16 = s8 << 8;
+				mix += (sample16 * (int)vs.vol);
+
+				vs.pos16 = pos + vs.step16;
+			}
+
+			const int denom = MAX<int>(1, (int)_hdr.numChannels * 128);
+			mix = mix / denom;
+			mix = CLIP<int>(mix, -32768, 32767);
+			dst[i] = (int16)mix;
+		}
+	}
+
+private:
+	int _rate;
+	bool _stereo;
+
+	Common::Array<byte> _module;
+
+	DttHeader _hdr;
+
+	Common::Array<byte> _sequence;
+	Common::Array<uint32> _patternOffsets;
+	Common::Array<byte> _patternLengths;
+	Common::Array<DttSample> _samples;
+
+	uint32 _songPos;
+	uint32 _row;
+
+	uint8 _speed50ths;
+	int32 _samplesUntilNextRow;
+
+	VoiceState _voices[8];
+
+	bool _ended;
+	bool _sawAnyNote;
+};
+
+} // namespace Modules
+
+AudioStream *makeDesktopTrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo) {
+	if (!stream)
+		return nullptr;
+	return new Modules::DesktopTrackerStream(stream, offs, rate, stereo);
+}
+
+AudioStream *makeDesktopTrackerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
+	if (!stream)
+		return nullptr;
+
+	const int offs = 0;
+	const int rate = 44100;
+	const bool stereo = false;
+
+	Modules::DesktopTrackerStream *dt = new Modules::DesktopTrackerStream(stream, offs, rate, stereo);
+
+	(void)disposeAfterUse;
+
+	return dt;
+}
+
+} // namespace Audio
diff --git a/audio/mods/desktoptracker.h b/audio/mods/desktoptracker.h
new file mode 100644
index 00000000000..92d2410b5d1
--- /dev/null
+++ b/audio/mods/desktoptracker.h
@@ -0,0 +1,37 @@
+/* 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 AUDIO_MODS_DESKTOPTRACKER_H
+#define AUDIO_MODS_DESKTOPTRACKER_H
+
+#include "audio/audiostream.h"
+#include "common/stream.h"
+#include "common/types.h"
+
+namespace Audio {
+
+AudioStream *makeDesktopTrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo);
+
+AudioStream *makeDesktopTrackerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
+
+}
+
+#endif
diff --git a/audio/module.mk b/audio/module.mk
index ebafc0b3fb5..14d58f959fa 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -60,6 +60,7 @@ MODULE_OBJS := \
 	mods/rjp1.o \
 	mods/soundfx.o \
 	mods/tfmx.o \
+	mods/desktoptracker.o \
 	softsynth/cms.o \
 	softsynth/opl/dbopl.o \
 	softsynth/opl/dosbox.o \
diff --git a/engines/agos/res_snd.cpp b/engines/agos/res_snd.cpp
index 220a0474d18..45545d4daf9 100644
--- a/engines/agos/res_snd.cpp
+++ b/engines/agos/res_snd.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "common/config-manager.h"
+#include "common/array.h"
 #include "common/file.h"
 #include "common/memstream.h"
 #include "common/textconsole.h"
@@ -34,9 +35,80 @@
 
 #include "audio/audiostream.h"
 #include "audio/mods/protracker.h"
+#include "audio/mods/desktoptracker.h"
 
 namespace AGOS {
 
+
+static Common::Array<byte> unsquashAcornDesktopTracker(const byte *data, uint32 compLen) {
+
+	const byte mode = data[0];
+
+	uint32 pos = 4;
+	uint32 r6 = 4;
+
+	Common::Array<byte> out;
+
+	if (mode == 1) {
+		out.resize(compLen - 4);
+		if (compLen > 4)
+			memcpy(out.begin(), data + 4, compLen - 4);
+		return out;
+	}
+
+	uint16 bitbuf = 0;
+	uint32 bitsLeft = 0;
+
+	for (;;) {
+		if (r6 == compLen)
+			return out;
+
+		if (bitsLeft == 0) {
+
+			const byte lo = data[pos];
+			const byte hi = data[pos + 1];
+			pos += 2;
+			r6 += 2;
+
+			bitbuf = (uint16)(lo | (hi << 8));
+			bitsLeft = 16;
+
+			if (r6 == compLen)
+				return out;
+		}
+
+		const uint16 carry = (bitbuf & 1);
+		bitbuf >>= 1;
+		--bitsLeft;
+
+		if (carry == 0) {
+			if (r6 == compLen)
+				return out;
+
+			out.push_back(data[pos]);
+			++pos;
+			++r6;
+		} else {
+			if (r6 == compLen)
+				return out;
+
+			const byte b1 = data[pos];
+			const byte b2 = data[pos + 1];
+			pos += 2;
+			r6 += 2;
+
+			const uint32 length = (uint32)((b1 & 0x0F) + 1);
+			const uint32 offset = (uint32)(((b1 & 0xF0) << 4) + b2);
+
+			for (uint32 i = 0; i < length; ++i)
+				out.push_back(out[out.size() - offset]);
+		}
+
+		if (r6 >= compLen)
+			return out;
+	}
+}
+
 // This data is hardcoded in the executable.
 const int AGOSEngine_Simon1::SIMON1_GMF_SIZE[] = {
 	8900, 12166,  2848,  3442,  4034,  4508,  7064,  9730,  6014,  4742,
@@ -334,9 +406,38 @@ void AGOSEngine_Simon1::playMusic(uint16 music, uint16 track) {
 		_midi->play();
 	} else if (getPlatform() == Common::kPlatformAcorn) {
 		// Acorn floppy version.
+		// Music resources are Squash-compressed Desktop Tracker modules.
+		char filename[16];
+		Common::File f;
+		Common::sprintf_s(filename, "%dTUNE", music);
+		
+		f.open(filename);
+		if (!f.isOpen())
+			debug("playMusic(Acorn): Can't load mod from '%s'", filename);
+
+		const uint32 compressedSize = (uint32)f.size();
+		Common::Array<byte> compresedBuffer;
+		compresedBuffer.resize(compressedSize);
+		f.read(compresedBuffer.begin(), compressedSize);
+
+		Common::Array<byte> moduleData;
+		
+		moduleData = unsquashAcornDesktopTracker(compresedBuffer.begin(), compressedSize);
+		
+		if (moduleData.size() < 4 || memcmp(moduleData.begin(), "DskT", 4) != 0)
+			debug("playMusic(Acorn): Unsquashed mod does not begin with 'DskT'");
+
+		byte *modBuffer = nullptr;
+		if (!moduleData.empty()) {
+			modBuffer = new byte[moduleData.size()];
+			memcpy(modBuffer, moduleData.begin(), moduleData.size());
+		}
 
-		// TODO: Add support for Desktop Tracker format in Acorn disk version
+		Common::SeekableReadStream *memStream = new Common::MemoryReadStream(modBuffer, moduleData.size(), DisposeAfterUse::YES);
+		Audio::AudioStream *audioStream = Audio::makeDesktopTrackerStream(memStream, DisposeAfterUse::YES);
+		_mixer->playStream(Audio::Mixer::kMusicSoundType, &_modHandle, audioStream);
 	}
+
 }
 
 void AGOSEngine_Simon1::playMidiSfx(uint16 sound) {




More information about the Scummvm-git-logs mailing list