[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