diff options
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | common/scummsys.h | 4 | ||||
-rw-r--r-- | scumm/sound.cpp | 298 | ||||
-rw-r--r-- | scumm/sound.h | 58 | ||||
-rw-r--r-- | sound/mixer.cpp | 100 | ||||
-rw-r--r-- | sound/mixer.h | 29 |
6 files changed, 401 insertions, 94 deletions
@@ -36,9 +36,13 @@ OBJS = # DEFINES += -DDUMP_SCRIPTS # Uncomment this to activate the MAD lib for compressed sound files -DEFINES += -DCOMPRESSED_SOUND_FILE +DEFINES += -DUSE_MAD LIBS += -lmad +# Uncomment this to activate the Ogg Vorbis lib for compressed sound files +# DEFINES += -DUSE_VORBIS +# LIBS += -lvorbisfile -lvorbis + # Uncomment this to activate the ALSA lib for midi # DEFINES += -DUSE_ALSA # LIBS += -lasound diff --git a/common/scummsys.h b/common/scummsys.h index fda37b18bd..0067d62d60 100644 --- a/common/scummsys.h +++ b/common/scummsys.h @@ -31,6 +31,10 @@ typedef int bool; const bool true(1), false(0); #endif /* HAVE_NO_BOOL */ +#if defined(USE_MAD) || defined(USE_VORBIS) +#define COMPRESSED_SOUND_FILE +#endif + #if defined(_MSC_VER) //#pragma warning (disable: 4244) diff --git a/scumm/sound.cpp b/scumm/sound.cpp index a8449a98c9..1c852fd016 100644 --- a/scumm/sound.cpp +++ b/scumm/sound.cpp @@ -738,7 +738,7 @@ int Sound::startSfxSound(File *file, int file_size) { int rate, comp; byte *data; -#ifdef COMPRESSED_SOUND_FILE +#ifdef USE_MAD if (file_size > 0) { data = (byte *)calloc(file_size + MAD_BUFFER_GUARD, 1); @@ -1116,7 +1116,7 @@ int Sound::playSfxSound(void *sound, uint32 size, uint rate, bool isUnsigned) { } int Sound::playSfxSound_MP3(void *sound, uint32 size) { -#ifdef COMPRESSED_SOUND_FILE +#ifdef USE_MAD if (_soundsPaused) return -1; return _scumm->_mixer->playMP3(NULL, sound, size, SoundMixer::FLAG_AUTOFREE); @@ -1219,16 +1219,11 @@ int Sound::getCachedTrack(int track) { char track_name[1024]; File * file = new File(); int current_index; - struct mad_stream stream; - struct mad_frame frame; - unsigned char buffer[8192]; - unsigned int buflen = 0; - int count = 0; // See if we find the track in the cache for (i = 0; i < CACHE_TRACKS; i++) if (_cached_tracks[i] == track) { - if (_mp3_tracks[i]) + if (_track_info[i]) return i; else return -1; @@ -1237,21 +1232,119 @@ int Sound::getCachedTrack(int track) { _current_cache %= CACHE_TRACKS; // Not found, see if it exists + + // First, delete the previous track info object + delete _track_info[current_index]; + _track_info[current_index] = NULL; + + _cached_tracks[current_index] = track; + +#ifdef USE_MAD sprintf(track_name, "track%d.mp3", track); file->open(track_name, _scumm->getGameDataPath()); - _cached_tracks[current_index] = track; - /* First, close the previous file */ - if (_mp3_tracks[current_index]) - _mp3_tracks[current_index]->close(); + if (file->isOpen()) { + _track_info[current_index] = new MP3TrackInfo(file); + if (_track_info[current_index]->error()) { + delete _track_info[current_index]; + _track_info[current_index] = NULL; + return -1; + } + return current_index; + } +#endif + +#ifdef USE_VORBIS + sprintf(track_name, "track%d.ogg", track); + file->open(track_name, _scumm->getGameDataPath()); + + if (file->isOpen()) { + _track_info[current_index] = new VorbisTrackInfo(file); + if (_track_info[current_index]->error()) { + delete _track_info[current_index]; + _track_info[current_index] = NULL; + return -1; + } + return current_index; + } +#endif + + debug(1, "Track %d not available in compressed format", track); + return -1; +} + +int Sound::playMP3CDTrack(int track, int num_loops, int start, int delay) { + int index; + _scumm->_vars[_scumm->VAR_MI1_TIMER] = 0; + + if (_soundsPaused) + return 0; + + if ((num_loops == 0) && (start == 0)) { + return 0; + } + + index = getCachedTrack(track); + if (index < 0) + return -1; + + if (_dig_cd_playing) + _scumm->_mixer->stop(_dig_cd_index); + _dig_cd_index = _track_info[index]->play(_scumm->_mixer, start, delay); + _dig_cd_playing = true; + _dig_cd_track = track; + _dig_cd_num_loops = num_loops; + _dig_cd_start = start; + _dig_cd_delay = delay; + return 0; +} + +int Sound::stopMP3CD() { + if (_dig_cd_playing == true) { + _scumm->_mixer->stop(_dig_cd_index); + _dig_cd_playing = false; + _dig_cd_track = 0; + _dig_cd_num_loops = 0; + _dig_cd_start = 0; + _dig_cd_delay = 0; + return 0; + } + return -1; +} + +int Sound::pollMP3CD() { + if (_dig_cd_playing == true) + return 1; + return 0; +} + +int Sound::updateMP3CD() { + if (_dig_cd_playing == false) + return -1; - _mp3_tracks[current_index] = NULL; - if (file->isOpen() == false) { - // This warning is pretty pointless. - debug(1, "Track %d not available in mp3 format", track); + if (_scumm->_mixer->_channels[_dig_cd_index] == NULL) { + warning("Error in MP3 decoding"); return -1; } + if (_scumm->_mixer->_channels[_dig_cd_index]->soundFinished()) { + if (_dig_cd_num_loops == -1 || --_dig_cd_num_loops > 0) + playMP3CDTrack(_dig_cd_track, _dig_cd_num_loops, _dig_cd_start, _dig_cd_delay); + else + stopMP3CD(); + } + + return 0; +} + +#ifdef USE_MAD +Sound::MP3TrackInfo::MP3TrackInfo(File *file) { + struct mad_stream stream; + struct mad_frame frame; + unsigned char buffer[8192]; + unsigned int buflen = 0; + int count = 0; + // Check the format and bitrate mad_stream_init(&stream); mad_frame_init(&frame); @@ -1263,7 +1356,7 @@ int Sound::getCachedTrack(int track) { bytes = file->read(buffer + buflen, sizeof(buffer) - buflen); if (bytes <= 0) { if (bytes == -1) { - warning("Invalid format for track %d", track); + warning("Invalid file format"); goto error; } break; @@ -1295,105 +1388,154 @@ int Sound::getCachedTrack(int track) { } if (count) - memcpy(&_mad_header[current_index], &frame.header, sizeof(mad_header)); + memcpy(&_mad_header, &frame.header, sizeof(mad_header)); else { - warning("Invalid format for track %d", track); + warning("Invalid file format"); goto error; } mad_frame_finish(&frame); mad_stream_finish(&stream); // Get file size - _mp3_size[current_index] = file->size(); - _mp3_tracks[current_index] = file; - - return current_index; + _size = file->size(); + _file = file; + _error_flag = false; + return; error: mad_frame_finish(&frame); mad_stream_finish(&stream); + _error_flag = true; delete file; - - return -1; } -int Sound::playMP3CDTrack(int track, int num_loops, int start, int delay) { - int index; +int Sound::MP3TrackInfo::play(SoundMixer *mixer, int start, int delay) { unsigned int offset; mad_timer_t duration; - _scumm->_vars[_scumm->VAR_MI1_TIMER] = 0; - - if (_soundsPaused) - return 0; - - if ((num_loops == 0) && (start == 0)) { - return 0; - } - - index = getCachedTrack(track); - if (index < 0) - return -1; // Calc offset. As all bitrates are in kilobit per seconds, the division by 200 is always exact - offset = (start * (_mad_header[index].bitrate / (8 * 25))) / 3; + offset = (start * (_mad_header.bitrate / (8 * 25))) / 3; // Calc delay if (!delay) { - mad_timer_set(&duration, (_mp3_size[index] * 8) / _mad_header[index].bitrate, - (_mp3_size[index] * 8) % _mad_header[index].bitrate, _mad_header[index].bitrate); + mad_timer_set(&duration, (_size * 8) / _mad_header.bitrate, + (_size * 8) % _mad_header.bitrate, _mad_header.bitrate); } else { mad_timer_set(&duration, delay / 75, delay % 75, 75); } // Go - _mp3_tracks[index]->seek(offset, SEEK_SET); - - if (_mp3_cd_playing == true) - _scumm->_mixer->stop(_mp3_index); - _mp3_index = _scumm->_mixer->playMP3CDTrack(NULL, _mp3_tracks[index], duration); - _mp3_cd_playing = true; - _mp3_cd_track = track; - _mp3_cd_num_loops = num_loops; - _mp3_cd_start = start; - _mp3_cd_delay = delay; - return 0; + _file->seek(offset, SEEK_SET); + + return mixer->playMP3CDTrack(NULL, _file, duration); } -int Sound::stopMP3CD() { - if (_mp3_cd_playing == true) { - _scumm->_mixer->stop(_mp3_index); - _mp3_cd_playing = false; - _mp3_cd_track = 0; - _mp3_cd_num_loops = 0; - _mp3_cd_start = 0; - _mp3_cd_delay = 0; - return 0; +Sound::MP3TrackInfo::~MP3TrackInfo() { + if (! _error_flag) + _file->close(); +} + +#endif + +#ifdef USE_VORBIS +// These are wrapper functions to allow using a File object to +// provide data to the OggVorbis_File object. + +struct file_info { + File *file; + int start, curr_pos; + size_t len; +}; + +static size_t read_wrap(void *ptr, size_t size, size_t nmemb, void *datasource) { + file_info *f = (file_info *) datasource; + int result; + + nmemb *= size; + if (f->curr_pos > (int) f->len) + nmemb = 0; + else if (nmemb > f->len - f->curr_pos) + nmemb = f->len - f->curr_pos; + result = f->file->read(ptr, nmemb); + if (result == -1) { + f->curr_pos = f->file->pos() - f->start; + return (size_t) -1; + } + else { + f->curr_pos += result; + return result / size; } - return -1; } -int Sound::pollMP3CD() { - if (_mp3_cd_playing == true) - return 1; +static int seek_wrap(void *datasource, ogg_int64_t offset, int whence) { + file_info *f = (file_info *) datasource; + + if (whence == SEEK_SET) + offset += f->start; + else if (whence == SEEK_END) { + offset += f->start + f->len; + whence = SEEK_SET; + } + + f->file->seek(offset, whence); + f->curr_pos = f->file->pos() - f->start; + return f->curr_pos; +} + +static int close_wrap(void *datasource) { + file_info *f = (file_info *) datasource; + + f->file->close(); + delete f; return 0; } -int Sound::updateMP3CD() { - if (_mp3_cd_playing == false) - return -1; +static long tell_wrap(void *datasource) { + file_info *f = (file_info *) datasource; - if (_scumm->_mixer->_channels[_mp3_index] == NULL) { - warning("Error in MP3 decoding"); - return -1; + return f->file->pos(); +} + +static ov_callbacks File_wrap = { + read_wrap, seek_wrap, close_wrap, tell_wrap +}; + +Sound::VorbisTrackInfo::VorbisTrackInfo(File *file) { + file_info *f = new file_info; + + f->file = file; + f->start = 0; + f->len = file->size(); + f->curr_pos = file->pos(); + + if (ov_open_callbacks((void *) f, &_ov_file, NULL, 0, File_wrap) < 0) { + warning("Invalid file format"); + _error_flag = true; + delete f; + delete file; } + else { + _error_flag = false; + _file = file; - if (_scumm->_mixer->_channels[_mp3_index]->soundFinished()) { - if (_mp3_cd_num_loops == -1 || --_mp3_cd_num_loops > 0) - playMP3CDTrack(_mp3_cd_track, _mp3_cd_num_loops, _mp3_cd_start, _mp3_cd_delay); - else - stopMP3CD(); + // Check the file format + if (ov_info(&_ov_file, -1)->rate != 22050) + warning("Vorbis code currently only supports files encoded at 22050 Hz"); } +} - return 0; +int Sound::VorbisTrackInfo::play(SoundMixer *mixer, int start, int delay) { + ov_time_seek(&_ov_file, start / 75.0); + return mixer->playVorbisCDTrack(NULL, &_ov_file, delay / 75.0); +} + +Sound::VorbisTrackInfo::~VorbisTrackInfo() { + if (! _error_flag) { + ov_clear(&_ov_file); + delete _file; + } } + +#endif + #endif diff --git a/scumm/sound.h b/scumm/sound.h index 2f4ddec5c8..635fda5531 100644 --- a/scumm/sound.h +++ b/scumm/sound.h @@ -65,24 +65,62 @@ enum { uint16 _mouthSyncTimes[52]; uint _curSoundPos; +#ifdef COMPRESSED_SOUND_FILE MP3OffsetTable *offset_table; // SO3 MP3 compressed audio int num_sound_effects; // SO3 MP3 compressed audio -#ifdef COMPRESSED_SOUND_FILE #define CACHE_TRACKS 10 /* used for mp3 CD music */ int _cached_tracks[CACHE_TRACKS]; - struct mad_header _mad_header[CACHE_TRACKS]; - long _mp3_size[CACHE_TRACKS]; - File *_mp3_tracks[CACHE_TRACKS]; - int _mp3_index; - int _mp3_cd_track; - int _mp3_cd_start; - int _mp3_cd_delay; - int _mp3_cd_num_loops; - bool _mp3_cd_playing; + int _dig_cd_index; + int _dig_cd_track; + int _dig_cd_start; + int _dig_cd_delay; + int _dig_cd_num_loops; + bool _dig_cd_playing; + + class DigitalTrackInfo { + public: + virtual bool error() = 0; + virtual int play(SoundMixer *mixer, int start, int delay) = 0; + virtual ~DigitalTrackInfo() { } + }; + + DigitalTrackInfo *_track_info[CACHE_TRACKS]; + +#ifdef USE_MAD + class MP3TrackInfo : public DigitalTrackInfo { + private: + struct mad_header _mad_header; + long _size; + File *_file; + bool _error_flag; + + public: + MP3TrackInfo(File *file); + ~MP3TrackInfo(); + bool error() { return _error_flag; } + int play(SoundMixer *mixer, int start, int delay); + }; +#endif + +#ifdef USE_VORBIS + class VorbisTrackInfo : public DigitalTrackInfo { + private: + File *_file; + OggVorbis_File _ov_file; + bool _error_flag; + + public: + VorbisTrackInfo(File *file); + ~VorbisTrackInfo(); + bool error() { return _error_flag; } + int play(SoundMixer *mixer, int start, int delay); + }; +#endif + #endif Scumm * _scumm; diff --git a/sound/mixer.cpp b/sound/mixer.cpp index c742fb153e..e79a91aa98 100644 --- a/sound/mixer.cpp +++ b/sound/mixer.cpp @@ -120,7 +120,7 @@ void SoundMixer::beginSlots(int index) { _beginSlots = index; } -#ifdef COMPRESSED_SOUND_FILE +#ifdef USE_MAD int SoundMixer::playMP3(PlayingSoundHandle * handle, void *sound, uint32 size, byte flags) { for (int i = _beginSlots; i != NUM_CHANNELS; i++) { if (_channels[i] == NULL) { @@ -144,6 +144,19 @@ int SoundMixer::playMP3CDTrack(PlayingSoundHandle * handle, File * file, mad_tim } #endif +#ifdef USE_VORBIS +int SoundMixer::playVorbisCDTrack(PlayingSoundHandle * handle, OggVorbis_File * ov_file, double duration) { + for (int i = _beginSlots; i != NUM_CHANNELS; i++) { + if (_channels[i] == NULL) { + return insertAt(handle, i, new ChannelVorbis(this, ov_file, duration)); + } + } + + warning("SoundMixer::out of mixer slots"); + return -1; +} +#endif + void SoundMixer::mix(int16 *buf, uint len) { if (_paused) { memset(buf, 0, 2 * len * sizeof(int16)); @@ -713,7 +726,7 @@ void SoundMixer::ChannelStream::realDestroy() { delete this; } -#ifdef COMPRESSED_SOUND_FILE +#ifdef USE_MAD SoundMixer::ChannelMP3::ChannelMP3(SoundMixer * mixer, void * sound, uint size, byte flags) { _mixer = mixer; _flags = flags; @@ -962,3 +975,86 @@ void SoundMixer::ChannelMP3CDMusic::realDestroy() { } #endif + +#ifdef USE_VORBIS +SoundMixer::ChannelVorbis::ChannelVorbis(SoundMixer * mixer, OggVorbis_File * ov_file, double duration) { + _mixer = mixer; + _ov_file = ov_file; + + if (duration) + _end_pos = ov_time_tell(ov_file) + duration; + else + _end_pos = 0; + + _eof_flag = false; + _toBeDestroyed = false; +} + +void SoundMixer::ChannelVorbis::mix(int16 * data, uint len) { + if (_toBeDestroyed) { + realDestroy(); + return; + } + + if (_eof_flag) { + memset(data, 0, sizeof(int16) * 2 * len); + return; + } + + int channels = ov_info(_ov_file, -1)->channels; + uint len_left = len * channels * 2; + int16 *samples = new int16[len_left / 2]; + char *read_pos = (char *) samples; + int volume = _mixer->_musicVolume; + + // Read the samples + while (len_left > 0) { + long result = ov_read(_ov_file, read_pos, len_left, +#ifdef SCUMM_BIG_ENDIAN + 1, +#else + 0, +#endif + 2, 1, NULL); + if (result == 0) { + _eof_flag = true; + memset(read_pos, 0, len_left); + break; + } + else if (result < 0) { + debug(1, "Decode error %d in Vorbis file", result); + // Don't delete it yet, that causes problems in + // the CD player emulation code. + _eof_flag = true; + memset(read_pos, 0, len_left); + break; + } + else { + len_left -= result; + read_pos += result; + } + } + + // Mix the samples in + for (uint i = 0; i < len; i++) { + int16 sample = (int16) ((int32) samples[i * channels] * volume / 256); + *data++ += sample; + if (channels > 1) + sample = (int16) ((int32) samples[i * channels + 1] * volume / 256); + *data++ += sample; + } + + delete [] samples; +} + +void SoundMixer::ChannelVorbis::realDestroy() { + _mixer->unInsert(this); + delete this; +} + +bool SoundMixer::ChannelVorbis::soundFinished() { + return _eof_flag || (_end_pos > 0 && + ov_time_tell(_ov_file) >= _end_pos); +} + +#endif diff --git a/sound/mixer.h b/sound/mixer.h index fd947502ca..1fecef27a2 100644 --- a/sound/mixer.h +++ b/sound/mixer.h @@ -25,10 +25,14 @@ #include <stdio.h> -#ifdef COMPRESSED_SOUND_FILE +#ifdef USE_MAD #include <mad.h> #endif +#ifdef USE_VORBIS +#include <vorbis/vorbisfile.h> +#endif + #include "common/scummsys.h" #include "common/system.h" @@ -92,7 +96,7 @@ private: void realDestroy(); }; -#ifdef COMPRESSED_SOUND_FILE +#ifdef USE_MAD class ChannelMP3 : public Channel { SoundMixer * _mixer; @@ -138,6 +142,22 @@ private: #endif +#ifdef USE_VORBIS + class ChannelVorbis : public Channel { + SoundMixer * _mixer; + OggVorbis_File * _ov_file; + double _end_pos; + bool _eof_flag; + + public: + ChannelVorbis(SoundMixer * mixer, OggVorbis_File * ov_file, double duration); + + void mix(int16 * data, uint len); + void realDestroy(); + bool soundFinished(); + }; +#endif + static void onGenerateSamples(void * s, byte * samples, int len); public: @@ -187,10 +207,13 @@ public: int playRaw(PlayingSoundHandle * handle, void * sound, uint32 size, uint rate, byte flags, int id); int playStream(PlayingSoundHandle * handle, int index, void * sound, uint32 size, uint rate, byte flags, int32 timeout = 3, int32 buffer_size = 2000000); -#ifdef COMPRESSED_SOUND_FILE +#ifdef USE_MAD int playMP3(PlayingSoundHandle * handle, void * sound, uint32 size, byte flags); int playMP3CDTrack(PlayingSoundHandle * handle, File * file, mad_timer_t duration); #endif +#ifdef USE_VORBIS + int playVorbisCDTrack(PlayingSoundHandle * handle, OggVorbis_File * ov_file, double duration); +#endif /* Premix procedure, useful when using fmopl adlib */ void setupPremix(void * param, PremixProc * proc); |