[Scummvm-git-logs] scummvm master -> cd0a7d779c6f46c0fe6a03c1e6fbb32ae6abf410
NMIError
noreply at scummvm.org
Mon Jun 6 19:18:27 UTC 2022
This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
cd0a7d779c CHEWY: Finish TMF to MOD conversion
Commit: cd0a7d779c6f46c0fe6a03c1e6fbb32ae6abf410
https://github.com/scummvm/scummvm/commit/cd0a7d779c6f46c0fe6a03c1e6fbb32ae6abf410
Author: Coen Rampen (crampen at gmail.com)
Date: 2022-06-06T21:18:13+02:00
Commit Message:
CHEWY: Finish TMF to MOD conversion
This finishes the conversion code of the TMF music format to standard MOD.
Changed paths:
engines/chewy/sound.cpp
engines/chewy/sound.h
diff --git a/engines/chewy/sound.cpp b/engines/chewy/sound.cpp
index bfc6e174634..382172ec329 100644
--- a/engines/chewy/sound.cpp
+++ b/engines/chewy/sound.cpp
@@ -32,6 +32,24 @@
namespace Chewy {
+const uint8 Sound::TMF_MOD_SONG_NAME[] = {
+ 'S', 'C', 'U', 'M', 'M',
+ 'V', 'M', ' ', 'M', 'O',
+ 'D', 'U', 'L', 'E', '\0',
+ '\0', '\0', '\0', '\0', '\0'};
+const uint8 Sound::TMF_MOD_INSTRUMENT_NAME[] = {
+ 'S', 'C', 'U', 'M', 'M',
+ 'V', 'M', ' ', 'I', 'N',
+ 'S', 'T', 'R', 'U', 'M',
+ 'E', 'N', 'T', '\0', '\0',
+ '\0', '\0'};
+// TODO Verify period values used by the game; this is an educated guess.
+const uint16 Sound::TMF_MOD_PERIODS[] = {
+ 856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453,
+ 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226,
+ 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113
+};
+
Sound::Sound(Audio::Mixer *mixer) {
_mixer = mixer;
_speechRes = new SoundResource("speech.tvp");
@@ -114,30 +132,14 @@ void Sound::playMusic(int num, bool loop) {
}
void Sound::playMusic(uint8 *data, uint32 size, bool loop, DisposeAfterUse::Flag dispose) {
-#if 0
uint8 *modData = nullptr;
uint32 modSize;
- /*
- // TODO: Finish and use convertTMFToMod()
- warning("The current music playing implementation is wrong");
- modSize = size;
- modData = (uint8 *)MALLOC(modSize);
- memcpy(modData, data, size);
-
- Audio::AudioStream *stream = Audio::makeLoopingAudioStream(
- Audio::makeRawStream(modData,
- modSize, 22050, Audio::FLAG_UNSIGNED,
- dispose),
- loop ? 0 : 1);
- */
-
convertTMFToMod(data, size, modData, modSize);
Audio::AudioStream *stream = Audio::makeProtrackerStream(
- new Common::MemoryReadStream(data, size));
+ new Common::MemoryReadStream(modData, modSize));
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle, stream);
-#endif
}
void Sound::pauseMusic() {
@@ -213,58 +215,59 @@ void Sound::stopAll() {
_mixer->stopAll();
}
-void Sound::convertTMFToMod(uint8 *tmfData, uint32 tmfSize, uint8 *modData, uint32 &modSize) {
- const int maxInstruments = 31;
-
- modSize = tmfSize + 20 + maxInstruments * 22 + 4;
+void Sound::convertTMFToMod(uint8 *tmfData, uint32 tmfSize, uint8 *&modData, uint32 &modSize) {
+ // TMF fixed data is 4 + 14*31 + 130 + 4*31 = 692
+ // MOD fixed data is 20 + 30*31 + 134 = 1084
+ // Variable data size is the same, so size difference is 392 bytes.
+ modSize = tmfSize + 392;
modData = (uint8 *)MALLOC(modSize);
uint8 *tmfPtr = tmfData;
uint8 *modPtr = modData;
- const uint8 songName[20] = {
- 'S', 'C', 'U', 'M', 'M',
- 'V', 'M', ' ', 'M', 'O',
- 'D', 'U', 'L', 'E', '\0',
- '\0', '\0', '\0', '\0', '\0'
- };
- const uint8 instrumentName[22] = {
- 'S', 'C', 'U', 'M', 'M',
- 'V', 'M', ' ', 'I', 'N',
- 'S', 'T', 'R', 'U', 'M',
- 'E', 'N', 'T', '\0', '\0',
- '\0', '\0'
- };
-
+ // Check TMF fourCC.
if (READ_BE_UINT32(tmfPtr) != MKTAG('T', 'M', 'F', '\0'))
error("Corrupt TMF resource");
tmfPtr += 4;
- memcpy(modPtr, songName, 20);
+ // Write song name (not present in TMF data).
+ memcpy(modPtr, TMF_MOD_SONG_NAME, 20);
modPtr += 20;
+ // Copy instrument data.
uint8 fineTune, instVolume;
- uint16 repeatPoint, repeatLength, sampleLength;
+ uint32 repeatPoint, repeatLength, sampleLength;
+ uint32 totalSampleLength = 0;
- for (int i = 0; i < maxInstruments; i++) {
+ for (int i = 0; i < TMF_NUM_INSTRUMENTS; i++) {
fineTune = *tmfPtr++;
instVolume = *tmfPtr++;
- repeatPoint = READ_BE_UINT16(tmfPtr);
- tmfPtr += 2;
- repeatLength = READ_BE_UINT16(tmfPtr);
- tmfPtr += 2;
- sampleLength = READ_BE_UINT16(tmfPtr);
- tmfPtr += 2;
-
- memcpy(modPtr, instrumentName, 18);
+ // Repeat point, repeat length and sample length are 32 bit LE in bytes
+ // instead of 16 bit BE in words.
+ repeatPoint = READ_LE_UINT32(tmfPtr);
+ assert(repeatPoint <= 0x1FFFF && repeatPoint % 2 == 0);
+ tmfPtr += 4;
+ repeatLength = READ_LE_UINT32(tmfPtr);
+ assert(repeatLength <= 0x1FFFF && repeatLength % 2 == 0);
+ tmfPtr += 4;
+ // Sample length is at the end instead of at the start.
+ sampleLength = READ_LE_UINT32(tmfPtr);
+ assert(sampleLength <= 0x1FFFF && sampleLength % 2 == 0);
+ tmfPtr += 4;
+ totalSampleLength += sampleLength;
+
+ // Instrument name is not present in TMF data.
+ memcpy(modPtr, TMF_MOD_INSTRUMENT_NAME, 18);
modPtr += 18;
*modPtr++ = ' ';
- *modPtr++ = i / 10;
- *modPtr++ = i % 10;
+ *modPtr++ = '0' + i / 10;
+ *modPtr++ = '0' + i % 10;
*modPtr++ = '\0';
WRITE_BE_UINT16(modPtr, sampleLength / 2);
modPtr += 2;
- *modPtr++ = fineTune;
+ // Finetune is a signed nibble in MOD, but TMF uses a signed byte
+ // (within nibble range).
+ *modPtr++ = fineTune & 0x0F;
*modPtr++ = instVolume;
WRITE_BE_UINT16(modPtr, repeatPoint / 2);
modPtr += 2;
@@ -272,15 +275,58 @@ void Sound::convertTMFToMod(uint8 *tmfData, uint32 tmfSize, uint8 *modData, uint
modPtr += 2;
}
+ // Copy pattern table.
*modPtr++ = *tmfPtr++;
- *modPtr++ = *tmfPtr++;
+ // Second byte is the number of different patterns in TMF. This byte is
+ // unused in MOD (usually set to 0x7F).
+ uint8 numPatterns = *tmfPtr++;
+ *modPtr++ = 0x7F;
memcpy(modPtr, tmfPtr, 128);
modPtr += 128;
tmfPtr += 128;
+ // M.K. fourCC is not present in TMF.
WRITE_BE_UINT32(modPtr, MKTAG('M', '.', 'K', '.'));
modPtr += 4;
- // TODO: Finish this
+ // TMF has a 32 bit LE number for each instrument here; these are probably
+ // offsets for each sample. They are not present in MOD and not needed, so
+ // they are skipped.
+ tmfPtr += 4 * 31;
+
+ assert(modSize == 1084 + (numPatterns * 1024) + totalSampleLength);
+
+ // Copy pattern data.
+ uint32 channelDwords = numPatterns * 1024 / 4;
+ // TMF channel data has this format:
+ // 1 byte note (0-0x23 or 0x30 for "use previous value")
+ // 1 byte sample
+ // 2 bytes effect (byte 3 high nibble is unused)
+ for (uint32 i = 0; i < channelDwords; i++) {
+ byte note = *tmfPtr++;
+ assert(note == 0x30 || note < 36);
+ byte sample = *tmfPtr++;
+ uint16 effect = READ_BE_UINT16(tmfPtr);
+ assert((effect & 0xF000) == 0);
+ tmfPtr += 2;
+
+ // Note is converted to a MOD 12 bit period using a lookup array.
+ // Effect 12 bit value is used as-is.
+ // Sample is split into the period and effect high nibbles.
+ uint16 periodWord = (note == 0x30 ? 0 : TMF_MOD_PERIODS[note]) | ((sample & 0xF0) << 8);
+ uint16 effectWord = effect | ((sample & 0x0F) << 12);
+ WRITE_BE_UINT16(modPtr, periodWord);
+ modPtr += 2;
+ WRITE_BE_UINT16(modPtr, effectWord);
+ modPtr += 2;
+ }
+
+ // Copy sample data.
+ for (uint32 i = 0; i < totalSampleLength; i++) {
+ int sample = *tmfPtr++;
+ // Convert from unsigned to signed.
+ sample -= 0x80;
+ *modPtr++ = sample & 0xFF;
+ }
}
void Sound::waitForSpeechToFinish() {
diff --git a/engines/chewy/sound.h b/engines/chewy/sound.h
index 4bfccbf83e3..2f07cd7e246 100644
--- a/engines/chewy/sound.h
+++ b/engines/chewy/sound.h
@@ -33,6 +33,12 @@ class SoundResource;
#define MAX_SOUND_EFFECTS 14
class Sound {
+private:
+ static const int TMF_NUM_INSTRUMENTS = 31;
+ static const uint8 TMF_MOD_SONG_NAME[20];
+ static const uint8 TMF_MOD_INSTRUMENT_NAME[22];
+ static const uint16 TMF_MOD_PERIODS[36];
+
public:
Sound(Audio::Mixer *mixer);
virtual ~Sound();
@@ -91,7 +97,7 @@ private:
SoundResource *_speechRes;
SoundResource *_soundRes;
- void convertTMFToMod(uint8 *tmfData, uint32 tmfSize, uint8 *modData, uint32 &modSize);
+ void convertTMFToMod(uint8 *tmfData, uint32 tmfSize, uint8 *&modData, uint32 &modSize);
};
} // End of namespace Chewy
More information about the Scummvm-git-logs
mailing list