MP3 cd music patch - still WIP, VBR doesn't work, compress the audio track X to MP3 CBR and name them trackX.mp3 in the game directory - only tested with Loom

svn-id: r3861
This commit is contained in:
Nicolas Bacca 2002-04-04 22:47:03 +00:00
parent 7961107156
commit cbad525cd3
7 changed files with 467 additions and 8 deletions

View file

@ -22,7 +22,7 @@
#define CD_MUSIC_H
void cd_stop();
void cd_play(int track, int num_loops, int start_frame, int end_track);
void cd_play(Scumm *s, int track, int num_loops, int start_frame, int end_track);
int cd_is_running();
void cd_music_loop();

View file

@ -2476,7 +2476,7 @@ void Scumm::decodeParseString() {
int delay = (int)(getVarOrDirectWord(0x40) * 7.5) + 10;
if (_gameId == GID_LOOM256)
cd_play(1, 0, offset, delay);
cd_play(this, 1, 0, offset, delay);
else
warning("parseString: 8");
}

306
sdl.cpp
View file

@ -676,11 +676,211 @@ static int cd_track, cd_num_loops = 0, cd_start_frame, cd_end_frame;
// time the track is expected to be finished.
static Uint32 cd_end_time, cd_stop_time, cd_next_second;
void cd_play(int track, int num_loops, int start_frame, int end_frame) {
#ifdef COMPRESSED_SOUND_FILE
// MP3 CD track support
// From xing.h in MAD
# define XING_MAGIC (('X' << 24) | ('i' << 16) | ('n' << 8) | 'g')
struct xing {
long flags; /* valid fields (see below) */
unsigned long frames; /* total number of frames */
unsigned long bytes; /* total number of bytes */
unsigned char toc[100]; /* 100-point seek table */
long scale; /* ?? */
};
enum {
XING_FRAMES = 0x00000001L,
XING_BYTES = 0x00000002L,
XING_TOC = 0x00000004L,
XING_SCALE = 0x00000008L
};
int _current_mp3_cd_track;
struct xing _vbr_header;
struct mad_header _mad_header;
long _mp3_size;
FILE *_mp3_track;
MixerChannel *_mc;
// From xing.c in MAD
int xing_parse(struct xing *xing, struct mad_bitptr ptr, unsigned int bitlen)
{
xing->flags = 0;
if (bitlen < 64 || mad_bit_read(&ptr, 32) != XING_MAGIC)
goto fail;
xing->flags = mad_bit_read(&ptr, 32);
bitlen -= 64;
if (xing->flags & XING_FRAMES) {
if (bitlen < 32)
goto fail;
xing->frames = mad_bit_read(&ptr, 32);
bitlen -= 32;
}
if (xing->flags & XING_BYTES) {
if (bitlen < 32)
goto fail;
xing->bytes = mad_bit_read(&ptr, 32);
bitlen -= 32;
}
if (xing->flags & XING_TOC) {
int i;
if (bitlen < 800)
goto fail;
for (i = 0; i < 100; ++i)
xing->toc[i] = (unsigned char)mad_bit_read(&ptr, 8);
bitlen -= 800;
}
if (xing->flags & XING_SCALE) {
if (bitlen < 32)
goto fail;
xing->scale = mad_bit_read(&ptr, 32);
bitlen -= 32;
}
return 0;
fail:
xing->flags = 0;
return -1;
}
// Borrowed from Winamp plugin in_mad.c
BOOL parse_xing_vbr_tag()
{
struct mad_stream stream;
struct mad_frame frame;
unsigned char buffer[8192];
unsigned int buflen = 0;
int count = 0, result = 0;
_vbr_header.flags = 0;
mad_stream_init(&stream);
mad_frame_init(&frame);
fseek(_mp3_track, 0, SEEK_SET);
while (1) {
if (buflen < sizeof(buffer)) {
DWORD bytes;
bytes = fread(buffer + buflen, 1, sizeof(buffer) - buflen, _mp3_track);
if (bytes <= 0) {
if (bytes == -1)
result = -1;
break;
}
buflen += bytes;
}
mad_stream_buffer(&stream, buffer, buflen);
while (1) {
if (mad_frame_decode(&frame, &stream) == -1) {
if (!MAD_RECOVERABLE(stream.error))
break;
if (stream.error != MAD_ERROR_BADCRC)
continue;
}
if (count++ ||
xing_parse(&_vbr_header, stream.anc_ptr, stream.anc_bitlen)
== -1)
break;
}
if (count || stream.error != MAD_ERROR_BUFLEN)
break;
memmove(buffer, stream.next_frame,
buflen = &buffer[buflen] - stream.next_frame);
}
if (count)
memcpy(&_mad_header, &frame.header, sizeof(mad_header));
else
result = -1;
mad_frame_finish(&frame);
mad_stream_finish(&stream);
return (result != -1);
}
uint32 calc_cd_file_offset(int start_frame) {
long offset;
if (!_vbr_header.flags) {
float frame_size;
//mad_timer_t timer; - recode with timer
/* Constant bit rate - perhaps not fully accurate, recheck */
frame_size = (float)(144 * _mad_header.bitrate / _mad_header.samplerate);
offset = (float)(float)start_frame / (float)CD_FPS * 1000 /
(float)((float)1152 / (float)_mad_header.samplerate * 1000) *
(float)(frame_size + 0.5);
}
else {
/* DOES NOT WORK AT THE MOMENT */
/* see Xing SDK */
long a;
float fa, fb, fx;
float percent = (float)start_frame / (float)CD_FPS * 1000 /
((float)((float)1152 / (float)_mad_header.samplerate * 1000) * _vbr_header.frames) *
100;
if( percent < 0.0f ) percent = 0.0f;
if( percent > 100.0f ) percent = 100.0f;
a = (int)percent;
if( a > 99 ) a = 99;
fa = _vbr_header.toc[a];
if( a < 99 ) {
fb = _vbr_header.toc[a+1];
}
else {
fb = 256.0f;
}
fx = fa + (fb-fa)*(percent-a);
offset = (int)((1.0f/256.0f)*fx*_vbr_header.bytes);
}
return offset;
}
#endif
void real_cd_play(int track, int num_loops, int start_frame, int end_frame) {
// warning("cd_play(%d,%d,%d,%d)", track, num_loops, start_frame, end_frame);
if (!cdrom) return;
scumm->_vars[14] = 0;
cd_track = track;
cd_num_loops = num_loops;
cd_start_frame = start_frame;
@ -693,6 +893,108 @@ void cd_play(int track, int num_loops, int start_frame, int end_frame) {
cdrom->track[track].length * 1000 / CD_FPS;
}
void cd_play(Scumm *s, int track, int num_loops, int start_frame, int end_frame) {
scumm->_vars[14] = 0;
#ifdef COMPRESSED_SOUND_FILE
// See if we are already playing this track, else try to open it
if (_current_mp3_cd_track != track) {
char track_name[1024];
sprintf(track_name, "%strack%d.mp3", s->_gameDataPath, track);
_mp3_track = fopen(track_name, "rb");
if (!_mp3_track) {
warning("No CD and track %d not available in mp3 format", track);
real_cd_play(track, num_loops, start_frame, end_frame);
return;
}
if (!parse_xing_vbr_tag()) {
warning("Error parsing file header - ignoring file",
track);
fclose(_mp3_track);
real_cd_play(track, num_loops, start_frame, end_frame);
return;
}
if (_vbr_header.flags) {
if (!(
(_vbr_header.flags & XING_TOC) &&
(_vbr_header.flags & XING_BYTES) &&
(_vbr_header.flags & XING_FRAMES)
)) {
warning("Missing required part of VBR header - ignoring file");
fclose(_mp3_track);
_vbr_header.flags = 0;
real_cd_play(track, num_loops, start_frame, end_frame);
return;
}
}
// Allocate the music mixer if necessary
if (!_mc) {
_mc = s->allocateMixer();
if (!_mc) {
warning("No mixer channel available for MP3 music");
real_cd_play(track, num_loops, start_frame, end_frame);
return;
}
}
fseek(_mp3_track, 0, SEEK_END);
_mp3_size = ftell(_mp3_track);
fseek(_mp3_track, 0, SEEK_SET);
_mc->type = MIXER_MP3_CDMUSIC;
_mc->sound_data.mp3_cdmusic.file = _mp3_track;
_mc->sound_data.mp3_cdmusic.playing = FALSE;
_mc->sound_data.mp3_cdmusic.buffer_size = 200000;
_mc->_sfx_sound = malloc(_mc->sound_data.mp3_cdmusic.buffer_size);
/* see if it's enough */
mad_stream_init(&_mc->sound_data.mp3.stream);
if (_mad_header.samplerate == 44100)
mad_stream_options((mad_stream*)&_mc->sound_data.mp3.stream,
MAD_OPTION_HALFSAMPLERATE);
mad_frame_init(&_mc->sound_data.mp3.frame);
mad_synth_init(&_mc->sound_data.mp3.synth);
_current_mp3_cd_track = track;
}
if (_current_mp3_cd_track == track) {
uint32 where;
// See where we want to go
where = calc_cd_file_offset(start_frame);
if (start_frame < 0 || end_frame < 0) {
warning("Negative index in frame");
return;
}
mad_timer_set(&_mc->sound_data.mp3_cdmusic.duration,
0,
end_frame,
CD_FPS);
fseek(_mp3_track, where, SEEK_SET);
_mc->sound_data.mp3_cdmusic.playing = TRUE;
return;
}
#endif
real_cd_play(track, num_loops, start_frame, end_frame);
}
// Schedule the music to be stopped after 1/10 sec, unless another
// track is started in the meantime. (On my machine, stopping and
// then restarting the CD takes a few seconds.)

142
sound.cpp
View file

@ -105,7 +105,7 @@ void Scumm::playSound(int sound) {
ptr = getResourceAddress(rtSound, sound);
if (ptr != NULL && READ_UINT32_UNALIGNED(ptr) == MKID('SOUN')) {
ptr += 8;
cd_play(ptr[16], ptr[17] == 0xff ? -1 : ptr[17],
cd_play(this, ptr[16], ptr[17] == 0xff ? -1 : ptr[17],
(ptr[18] * 60 + ptr[19]) * 75 + ptr[20], 0);
current_cd_sound = sound;
return;
@ -687,6 +687,7 @@ void MixerChannel::mix(int16 *data, uint32 len) {
clear();
#ifdef COMPRESSED_SOUND_FILE
} else {
if (type == MIXER_MP3) {
mad_fixed_t const *ch;
while (1) {
ch = sound_data.mp3.synth.pcm.samples[0] + sound_data.mp3.pos_in_frame;
@ -725,6 +726,145 @@ void MixerChannel::mix(int16 *data, uint32 len) {
sound_data.mp3.position = (unsigned char *) sound_data.mp3.stream.next_frame - (unsigned char *) _sfx_sound;
}
}
else if (type == MIXER_MP3_CDMUSIC) {
mad_fixed_t const *ch;
mad_timer_t frame_duration;
static long last_pos = 0;
if (!sound_data.mp3_cdmusic.playing)
return;
while (1) {
// See if we just skipped
if (ftell(sound_data.mp3_cdmusic.file) != last_pos) {
int skip_loop;
// Read the new data
memset(_sfx_sound, 0, sound_data.mp3_cdmusic.buffer_size + MAD_BUFFER_GUARD);
sound_data.mp3_cdmusic.size =
fread(_sfx_sound, 1, sound_data.mp3_cdmusic.buffer_size,
sound_data.mp3_cdmusic.file);
if (!sound_data.mp3_cdmusic.size) {
sound_data.mp3_cdmusic.playing = FALSE;
return;
}
last_pos = ftell(sound_data.mp3_cdmusic.file);
// Resync
mad_stream_buffer(&sound_data.mp3_cdmusic.stream,
(unsigned char*)_sfx_sound,
sound_data.mp3_cdmusic.size
);
skip_loop = 2;
while (skip_loop != 0) {
if (mad_frame_decode(&sound_data.mp3_cdmusic.frame,
&sound_data.mp3_cdmusic.stream) == 0) {
/* Do not decrease duration - see if it's a problem */
skip_loop--;
if (skip_loop == 0) {
mad_synth_frame(&sound_data.mp3_cdmusic.synth,
&sound_data.mp3_cdmusic.frame);
}
}
else {
if (!MAD_RECOVERABLE(sound_data.mp3_cdmusic.stream.error)) {
debug(1, "Unrecoverable error while skipping !");
sound_data.mp3_cdmusic.playing = FALSE;
return;
}
}
}
// We are supposed to be in synch
mad_frame_mute(&sound_data.mp3_cdmusic.frame);
mad_synth_mute(&sound_data.mp3_cdmusic.synth);
// Resume decoding
if (mad_frame_decode(&sound_data.mp3_cdmusic.frame,
&sound_data.mp3_cdmusic.stream) == 0) {
sound_data.mp3_cdmusic.position =
(unsigned char *)sound_data.mp3_cdmusic.stream.next_frame -
(unsigned char *)_sfx_sound;
sound_data.mp3_cdmusic.pos_in_frame = 0;
}
else {
sound_data.mp3_cdmusic.playing = FALSE;
return;
}
}
// Get samples, play samples ...
ch = sound_data.mp3_cdmusic.synth.pcm.samples[0] +
sound_data.mp3_cdmusic.pos_in_frame;
while ((sound_data.mp3_cdmusic.pos_in_frame <
sound_data.mp3_cdmusic.synth.pcm.length) &&
(len > 0)) {
*data++ += scale_sample(*ch++);
len--;
sound_data.mp3_cdmusic.pos_in_frame++;
}
if (len == 0) {
return;
}
// See if we have finished
// May be incorrect to check the size at the end of a frame but I suppose
// they are short enough :)
frame_duration = sound_data.mp3_cdmusic.frame.header.duration;
mad_timer_negate(&frame_duration);
mad_timer_add(&sound_data.mp3_cdmusic.duration, frame_duration);
if (mad_timer_compare(sound_data.mp3_cdmusic.duration, mad_timer_zero) < 0) {
sound_data.mp3_cdmusic.playing = FALSE;
}
if (mad_frame_decode(&sound_data.mp3_cdmusic.frame,
&sound_data.mp3_cdmusic.stream) == -1) {
if (sound_data.mp3_cdmusic.stream.error == MAD_ERROR_BUFLEN) {
int not_decoded;
if (!sound_data.mp3_cdmusic.stream.next_frame) {
memset(_sfx_sound, 0, sound_data.mp3_cdmusic.buffer_size + MAD_BUFFER_GUARD);
sound_data.mp3_cdmusic.size =
fread(_sfx_sound, 1, sound_data.mp3_cdmusic.buffer_size,
sound_data.mp3_cdmusic.file);
sound_data.mp3_cdmusic.position = 0;
not_decoded = 0;
}
else {
not_decoded = sound_data.mp3_cdmusic.stream.bufend -
sound_data.mp3_cdmusic.stream.next_frame;
memcpy(_sfx_sound, sound_data.mp3_cdmusic.stream.next_frame,
not_decoded);
sound_data.mp3_cdmusic.size =
fread((unsigned char*)_sfx_sound + not_decoded, 1,
sound_data.mp3_cdmusic.buffer_size - not_decoded,
sound_data.mp3_cdmusic.file);
}
last_pos = ftell(sound_data.mp3_cdmusic.file);
sound_data.mp3_cdmusic.stream.error = MAD_ERROR_NONE;
// Restream
mad_stream_buffer(&sound_data.mp3_cdmusic.stream,
(unsigned char*)_sfx_sound,
sound_data.mp3_cdmusic.size + not_decoded
);
if (mad_frame_decode(&sound_data.mp3_cdmusic.frame, &sound_data.mp3_cdmusic.stream) == -1) {
debug(1, "Error decoding after restream %d !", sound_data.mp3.stream.error);
}
} else if (!MAD_RECOVERABLE(sound_data.mp3.stream.error)) {
error("MAD frame decode error in MP3 CDMUSIC !");
}
}
mad_synth_frame(&sound_data.mp3_cdmusic.synth, &sound_data.mp3_cdmusic.frame);
sound_data.mp3_cdmusic.pos_in_frame = 0;
sound_data.mp3_cdmusic.position =
(unsigned char *)sound_data.mp3_cdmusic.stream.next_frame -
(unsigned char *)_sfx_sound;
}
}
}
#endif
}

18
sound.h
View file

@ -17,6 +17,9 @@
*
* Change Log:
* $Log$
* Revision 1.10 2002/04/04 22:47:03 arisme
* MP3 cd music patch - still WIP, VBR doesn't work, compress the audio track X to MP3 CBR and name them trackX.mp3 in the game directory - only tested with Loom
*
* Revision 1.9 2002/03/21 16:12:02 ender
* Move some box stuff from scumm.h to new boxes.h
* Also move some sound-related items from scumm.h to sound.h
@ -63,7 +66,8 @@ struct OffsetTable { /* Compressed Sound (.SO3) */
typedef enum { /* Mixer types */
MIXER_STANDARD,
MIXER_MP3
MIXER_MP3,
MIXER_MP3_CDMUSIC
} MixerType;
struct MixerChannel { /* Mixer Channel */
@ -86,6 +90,18 @@ struct MixerChannel { /* Mixer Channel */
uint32 position;
uint32 size;
} mp3;
struct {
struct mad_stream stream;
struct mad_frame frame;
struct mad_synth synth;
uint32 pos_in_frame;
uint32 position;
uint32 size;
uint32 buffer_size;
mad_timer_t duration;
BOOL playing;
FILE *file;
} mp3_cdmusic;
#endif
} sound_data;
void mix(int16 *data, uint32 len);

View file

@ -125,7 +125,7 @@ int mapKey(int key) {
// FIXME: CD Music Stubs
void cd_playtrack(int track, int offset, int delay) {;}
void cd_play(int track, int num_loops, int start_frame) {;}
void cd_play(Scumm *s, int track, int num_loops, int start_frame) {;}
void cd_stop() {;}
int cd_is_running() {return 0;}

View file

@ -186,7 +186,8 @@ static void create_empty_cursor(Display *display,
}
/* No CD on the iPAQ => stub functions */
void cd_play(int track, int num_loops, int start_frame, int end_frame) {
void cd_play(Scumm *s, int track, int num_loops, int start_frame, int end_frame) {
/* Insert SDL.cpp MP3 code here :) */
}
int cd_is_running(void) {
return 1;