[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