[Scummvm-devel] [ANN] MP3 patch for ScummVM
Lionel Ulmer
lionel.ulmer at free.fr
Thu Feb 28 13:29:07 CET 2002
Hi all,
I did a patch enabling one to play a 'MP3ized' version of the .SOU file
(thus having a full CD install of Day of the Tentacle fitting in a measly 55
megs instead of about 300).
The patch is attached to this mail for review by people of this list. If the
'core' developpers (Ludde / Yazoo / Endy) find it acceptable, I will commit
it to the CVS tree.
Now the question is how to distribute my 'convert a .SOU to .SO3' file.
Create a 'tool' subdirectory in the ScummVM distribution ?
Anyway, going to tweak things a little bit :-)
Lionel (enjoying now the full-CD version on his iPAQ :-) )
--
Lionel Ulmer - http://www.bbrox.org/
-------------- next part --------------
Index: scumm.h
===================================================================
RCS file: /cvsroot/scummvm/scummvm/scumm.h,v
retrieving revision 1.52
diff -u -r1.52 scumm.h
--- scumm.h 23 Feb 2002 23:23:28 -0000 1.52
+++ scumm.h 28 Feb 2002 21:15:28 -0000
@@ -20,7 +20,9 @@
*/
#include "scummsys.h"
-
+#ifdef COMPRESSED_SOUND_FILE
+#include <mad.h>
+#endif
#define SCUMMVM_VERSION "0.1.0 devel"
@@ -718,13 +720,32 @@
};
};
+typedef enum {
+ MIXER_STANDARD,
+ MIXER_MP3
+} MixerType;
+
struct MixerChannel {
void *_sfx_sound;
- uint32 _sfx_pos;
- uint32 _sfx_size;
- uint32 _sfx_fp_speed;
- uint32 _sfx_fp_pos;
-
+ MixerType type;
+ union {
+ struct {
+ uint32 _sfx_pos;
+ uint32 _sfx_size;
+ uint32 _sfx_fp_speed;
+ uint32 _sfx_fp_pos;
+ } standard;
+#ifdef COMPRESSED_SOUND_FILE
+ struct {
+ struct mad_stream stream;
+ struct mad_frame frame;
+ struct mad_synth synth;
+ uint32 pos_in_frame;
+ uint32 position;
+ uint32 size;
+ } mp3;
+#endif
+ } sound_data;
void mix(int16 *data, uint32 len);
void clear();
};
@@ -790,6 +811,15 @@
Point lr;
};
+#ifdef COMPRESSED_SOUND_FILE
+struct OffsetTable {
+ int org_offset;
+ int new_offset;
+ int num_tags;
+ int compressed_size;
+};
+#endif
+
struct Scumm {
uint32 _features;
const char *_gameText;
@@ -1102,6 +1132,11 @@
OpcodeProc getOpcode(int i) { return _opcodes[i]; }
+#ifdef COMPRESSED_SOUND_FILE
+ OffsetTable *offset_table;
+ int num_sound_effects;
+#endif
+
void openRoom(int room);
void deleteRoomOffsets();
void readRoomsOffsets();
@@ -1791,7 +1826,7 @@
void startTalkSound(uint32 a, uint32 b, int mode);
void stopTalkSound();
bool isMouthSyncOff(uint pos);
- void startSfxSound(void *file);
+ void startSfxSound(void *file, int size);
void *openSfxFile();
void resourceStats();
bool isCostumeInUse(int i);
@@ -1837,6 +1872,9 @@
MixerChannel *allocateMixer();
bool isSfxFinished();
void playSfxSound(void *sound, uint32 size, uint rate);
+#ifdef COMPRESSED_SOUND_FILE
+ void playSfxSound_MP3(void *sound, uint32 size);
+#endif
void stopSfxSound();
void mixWaves(int16 *sounds, int len);
Index: sound.cpp
===================================================================
RCS file: /cvsroot/scummvm/scummvm/sound.cpp,v
retrieving revision 1.18
diff -u -r1.18 sound.cpp
--- sound.cpp 24 Feb 2002 17:25:02 -0000 1.18
+++ sound.cpp 28 Feb 2002 21:15:28 -0000
@@ -138,31 +138,63 @@
}
}
+#ifdef COMPRESSED_SOUND_FILE
+static int compar(const void *a, const void *b) {
+ return ((OffsetTable *) a)->org_offset - ((OffsetTable *) b)->org_offset;
+}
+#endif
+
void Scumm::startTalkSound(uint32 offset, uint32 b, int mode) {
- int num, i;
+ int num = 0, i;
byte file_byte,file_byte_2;
+ int size;
if (!_sfxFile) {
warning("startTalkSound: SFX file is not open");
return;
}
- fileSeek((FILE*)_sfxFile, offset + 8, SEEK_SET);
- i = 0;
if (b>8) {
num = (b-8)>>1;
- do {
- fileRead((FILE*)_sfxFile, &file_byte, sizeof(file_byte));
- fileRead((FILE*)_sfxFile, &file_byte_2, sizeof(file_byte_2));
- _mouthSyncTimes[i++] = file_byte | (file_byte_2<<8);
- } while (--num);
+ }
+
+#ifdef COMPRESSED_SOUND_FILE
+ if (offset_table != NULL) {
+ OffsetTable *result, key;
+
+ key.org_offset = offset - 1; /* No idea why I need this '- 1' here.... But it works with it :-) */
+ result = (OffsetTable *) bsearch(&key, offset_table, num_sound_effects, sizeof(OffsetTable), compar);
+ if (result == NULL) {
+ warning("startTalkSound: did not find sound at offset %d !", offset);
+ return;
+ }
+ if (2 * num != result->num_tags) {
+ warning("startTalkSound: number of tags do not match (%d - %d) !", b, result->num_tags);
+ num = result->num_tags;
+ }
+ offset = result->new_offset;
+ size = result->compressed_size;
+ } else
+#endif
+ {
+ offset += 8;
+ size = -1;
+ }
+
+ fileSeek((FILE*)_sfxFile, offset, SEEK_SET);
+ i = 0;
+ while (num > 0) {
+ fileRead((FILE*)_sfxFile, &file_byte, sizeof(file_byte));
+ fileRead((FILE*)_sfxFile, &file_byte_2, sizeof(file_byte_2));
+ _mouthSyncTimes[i++] = file_byte | (file_byte_2<<8);
+ num--;
}
_mouthSyncTimes[i] = 0xFFFF;
_sfxMode = mode;
_curSoundPos = 0;
_mouthSyncMode = true;
- startSfxSound(_sfxFile);
+ startSfxSound(_sfxFile, size);
}
void Scumm::stopTalkSound() {
@@ -291,7 +323,7 @@
* A mapping between roland instruments and GM instruments
* is needed.
*/
-
+
void Scumm::setupSound() {
SoundEngine *se = (SoundEngine*)_soundEngine;
if (se)
@@ -313,7 +345,7 @@
};
-void Scumm::startSfxSound(void *file) {
+void Scumm::startSfxSound(void *file, int file_size) {
char ident[8];
int block_type;
byte work[8];
@@ -321,6 +353,19 @@
int rate,comp;
byte *data;
+#ifdef COMPRESSED_SOUND_FILE
+ if (file_size > 0) {
+ data = (byte *) calloc(file_size + MAD_BUFFER_GUARD, 1);
+
+ if (fread(data, file_size, 1, (FILE*) file) != 1) {
+ /* no need to free the memory since error will shut down */
+ error("startSfxSound: cannot read %d bytes", size);
+ return;
+ }
+ playSfxSound_MP3(data, file_size);
+ return;
+ }
+#endif
if ( fread(ident, 8, 1, (FILE*)file) != 1)
goto invalid;
@@ -350,7 +395,7 @@
warning("startSfxSound: Unsupported compression type %d", comp);
return;
}
-
+
data = (byte*) malloc(size);
if (data==NULL) {
error("startSfxSound: out of memory");
@@ -364,10 +409,26 @@
}
for(i=0;i<size; i++)
data[i] ^= 0x80;
-
+
playSfxSound(data, size, 1000000 / (256 - rate) );
}
+
+#ifdef COMPRESSED_SOUND_FILE
+static int get_int(FILE *f) {
+ int ret = 0;
+ for (int size = 0; size < 4; size++) {
+ int c = fgetc(f);
+ if (c == EOF) {
+ error("Unexpected end of file !!!");
+ }
+ ret <<= 8;
+ ret |= c;
+ }
+ return ret;
+}
+#endif
+
void *Scumm::openSfxFile() {
char buf[50];
FILE *file;
@@ -376,10 +437,54 @@
* That way, you can keep .sou files for multiple games in the
* same directory */
+#ifdef COMPRESSED_SOUND_FILE
+ offset_table = NULL;
+
+ sprintf(buf, "%s.so3", _exe_name);
+ file = fopen(buf, "rb");
+ if (!file)
+ file = fopen("monster.so3", "rb");
+
+ if (file != NULL) {
+ /* Now load the 'offset' index in memory to be able to find the MP3 data
+
+ The format of the .SO3 file is easy :
+ - number of bytes of the 'index' part
+ - N times the following fields (4 bytes each) :
+ + offset in the original sound file
+ + offset of the MP3 data in the .SO3 file WITHOUT taking into account
+ the index field
+ + the number of 'tags'
+ + the size of the MP3 data
+ - and then N times :
+ + the tags
+ + the MP3 data
+ */
+ int size, compressed_offset;
+ OffsetTable *cur;
+
+ compressed_offset = get_int(file);
+ offset_table = (OffsetTable *) malloc(compressed_offset);
+ num_sound_effects = compressed_offset / 16;
+
+ size = compressed_offset;
+ cur = offset_table;
+ while (size > 0) {
+ cur[0].org_offset = get_int(file);
+ cur[0].new_offset = get_int(file) + compressed_offset;
+ cur[0].num_tags = get_int(file);
+ cur[0].compressed_size = get_int(file);
+ size -= 4 * 4;
+ cur++;
+ }
+ return file;
+ }
+#endif
sprintf(buf, "%s.sou", _exe_name);
file = fopen(buf, "rb");
if (!file)
file = fopen("monster.sou", "rb");
+
return file;
}
@@ -413,6 +518,27 @@
return true;
}
+#ifdef COMPRESSED_SOUND_FILE
+void Scumm::playSfxSound_MP3(void *sound, uint32 size) {
+ MixerChannel *mc = allocateMixer();
+
+ if (!mc) {
+ warning("No mixer channel available");
+ return;
+ }
+
+ mc->type = MIXER_MP3;
+ mc->_sfx_sound = sound;
+
+ mad_stream_init(&mc->sound_data.mp3.stream);
+ mad_frame_init(&mc->sound_data.mp3.frame);
+ mad_synth_init(&mc->sound_data.mp3.synth);
+ mc->sound_data.mp3.position = 0;
+ mc->sound_data.mp3.pos_in_frame = 0xFFFFFFFF;
+ mc->sound_data.mp3.size = size;
+}
+#endif
+
void Scumm::playSfxSound(void *sound, uint32 size, uint rate) {
MixerChannel *mc = allocateMixer();
@@ -421,47 +547,114 @@
return;
}
+ mc->type = MIXER_STANDARD;
mc->_sfx_sound = sound;
- mc->_sfx_pos = 0;
- mc->_sfx_fp_speed = (1<<16) * rate / 22050;
- mc->_sfx_fp_pos = 0;
+ mc->sound_data.standard._sfx_pos = 0;
+ mc->sound_data.standard._sfx_fp_speed = (1<<16) * rate / 22050;
+ mc->sound_data.standard._sfx_fp_pos = 0;
while (size&0xFFFF0000) size>>=1, rate>>=1;
- mc->_sfx_size = size * 22050 / rate;
+ mc->sound_data.standard._sfx_size = size * 22050 / rate;
}
-void MixerChannel::mix(int16 *data, uint32 len) {
- int8 *s;
- uint32 fp_pos, fp_speed;
+#ifdef COMPRESSED_SOUND_FILE
+static inline int scale_sample(mad_fixed_t sample)
+{
+ /* round */
+ sample += (1L << (MAD_F_FRACBITS - 16));
- if (!_sfx_sound)
- return;
- if (len > _sfx_size)
- len = _sfx_size;
- _sfx_size -= len;
-
- s = (int8*)_sfx_sound + _sfx_pos;
- fp_pos = _sfx_fp_pos;
- fp_speed = _sfx_fp_speed;
+ /* clip */
+ if (sample >= MAD_F_ONE)
+ sample = MAD_F_ONE - 1;
+ else if (sample < -MAD_F_ONE)
+ sample = -MAD_F_ONE;
- do {
- fp_pos += fp_speed;
- *data++ += (*s<<6);
- s += fp_pos >> 16;
- fp_pos &= 0x0000FFFF;
- } while (--len);
-
- _sfx_pos = s - (int8*)_sfx_sound;
- _sfx_fp_speed = fp_speed;
- _sfx_fp_pos = fp_pos;
+ /* quantize and scale to not saturate when mixing a lot of channels */
+ return sample >> (MAD_F_FRACBITS + 2 - 16);
+}
+#endif
- if (!_sfx_size)
+void MixerChannel::mix(int16 *data, uint32 len) {
+ if (!_sfx_sound)
+ return;
+
+#ifdef COMPRESSED_SOUND_FILE
+ if (type == MIXER_STANDARD) {
+#endif
+ int8 *s;
+ uint32 fp_pos, fp_speed;
+
+ if (len > sound_data.standard._sfx_size)
+ len = sound_data.standard._sfx_size;
+ sound_data.standard._sfx_size -= len;
+
+ s = (int8*)_sfx_sound + sound_data.standard._sfx_pos;
+ fp_pos = sound_data.standard._sfx_fp_pos;
+ fp_speed = sound_data.standard._sfx_fp_speed;
+
+ do {
+ fp_pos += fp_speed;
+ *data++ += (*s<<6);
+ s += fp_pos >> 16;
+ fp_pos &= 0x0000FFFF;
+ } while (--len);
+
+ sound_data.standard._sfx_pos = s - (int8*)_sfx_sound;
+ sound_data.standard._sfx_fp_speed = fp_speed;
+ sound_data.standard._sfx_fp_pos = fp_pos;
+
+ if (!sound_data.standard._sfx_size)
+ clear();
+#ifdef COMPRESSED_SOUND_FILE
+ } else {
+ mad_fixed_t const *ch;
+ while (1) {
+ ch = sound_data.mp3.synth.pcm.samples[0] + sound_data.mp3.pos_in_frame;
+ while ((sound_data.mp3.pos_in_frame < sound_data.mp3.synth.pcm.length) &&
+ (len > 0)) {
+ *data++ += scale_sample(*ch++);
+ sound_data.mp3.pos_in_frame++;
+ len--;
+ }
+ if (len == 0) return;
+
+ if (sound_data.mp3.position >= sound_data.mp3.size) {
+ clear();
+ return;
+ }
+
+ mad_stream_buffer(&sound_data.mp3.stream,
+ ((unsigned char *) _sfx_sound) + sound_data.mp3.position,
+ sound_data.mp3.size + MAD_BUFFER_GUARD - sound_data.mp3.position);
+
+ if (mad_frame_decode(&sound_data.mp3.frame, &sound_data.mp3.stream) == -1) {
+ /* End of audio... */
+ if (sound_data.mp3.stream.error == MAD_ERROR_BUFLEN) {
clear();
+ return;
+ } else if (!MAD_RECOVERABLE(sound_data.mp3.stream.error)) {
+ error("MAD frame decode error !");
+ }
+ }
+ mad_synth_frame(&sound_data.mp3.synth, &sound_data.mp3.frame);
+ sound_data.mp3.pos_in_frame = 0;
+ sound_data.mp3.position = (unsigned char *) sound_data.mp3.stream.next_frame - (unsigned char *) _sfx_sound;
+ }
+ }
+#endif
}
void MixerChannel::clear() {
free(_sfx_sound);
_sfx_sound = NULL;
+
+#ifdef COMPRESSED_SOUND_FILE
+ if (type == MIXER_MP3) {
+ mad_synth_finish(&sound_data.mp3.synth);
+ mad_frame_finish(&sound_data.mp3.frame);
+ mad_stream_finish(&sound_data.mp3.stream);
+ }
+#endif
}
void Scumm::mixWaves(int16 *sounds, int len) {
More information about the Scummvm-devel
mailing list