From f5403e079fbfa5348d1e9f084ed215dc40314655 Mon Sep 17 00:00:00 2001 From: Robert Göffringmann Date: Tue, 12 Oct 2004 15:50:00 +0000 Subject: added mp3 and ogg vorbis playback for music and speech. compression tool will follow soon. svn-id: r15531 --- sword1/music.cpp | 201 +++++++++++++++++++++++++++++++++++++++++++----------- sword1/music.h | 36 ++++++++-- sword1/sound.cpp | 71 ++++++++++++++++--- sword1/sound.h | 11 ++- sword1/sword1.cpp | 2 +- 5 files changed, 260 insertions(+), 61 deletions(-) diff --git a/sword1/music.cpp b/sword1/music.cpp index b8b6d010fe..f49704a50d 100644 --- a/sword1/music.cpp +++ b/sword1/music.cpp @@ -24,15 +24,122 @@ #include "sound/mixer.h" #include "common/util.h" #include "common/file.h" +#include "sound/mp3.h" +#include "sound/vorbis.h" namespace Sword1 { +WaveAudioStream *makeWaveStream(File *source, uint32 size) { + return new WaveAudioStream(source, size); +} + +WaveAudioStream::WaveAudioStream(File *source, uint32 pSize) { + uint32 size; + uint8 wavHeader[WAVEHEADERSIZE]; + + _sourceFile = source; + _sourceFile->incRef(); + if (_sourceFile->isOpen()) { + _sourceFile->read(wavHeader, WAVEHEADERSIZE); + _isStereo = (READ_LE_UINT16(wavHeader + 0x16) == 2); + _rate = READ_LE_UINT16(wavHeader + 0x18); + size = ((pSize) ? pSize : READ_LE_UINT32(wavHeader + 0x28)); + assert(size <= (source->size() - source->pos())); + _bitsPerSample = READ_LE_UINT16(wavHeader + 0x22); + _samplesLeft = (size * 8) / _bitsPerSample; + if ((_bitsPerSample != 16) && (_bitsPerSample != 8)) + error("WaveAudioStream: unknown wave type"); + } else { + _samplesLeft = 0; + _isStereo = false; + _bitsPerSample = 16; + _rate = 22050; + } +} + +WaveAudioStream::~WaveAudioStream(void) { + _sourceFile->decRef(); +} + +int WaveAudioStream::readBuffer(int16 *buffer, const int numSamples) { + int samples = ((int)_samplesLeft < numSamples) ? (int)_samplesLeft : numSamples; + if (_bitsPerSample == 16) + for (int cnt = 0; cnt < samples; cnt++) + *buffer++ = (int16)_sourceFile->readUint16LE(); + else + for (int cnt = 0; cnt < samples; cnt++) + *buffer++ = (int16)_sourceFile->readByte() << 8; + _samplesLeft -= samples; + return samples; +} + +bool WaveAudioStream::endOfData(void) const { + if (_samplesLeft == 0) + return true; + else + return false; +} + // This means fading takes 3 seconds. #define FADE_LENGTH 3 // These functions are only called from Music, so I'm just going to // assume that if locking is needed it has already been taken care of. +AudioStream *MusicHandle::createAudioSource(void) { + _file.seek(0); + switch (_musicMode) { +#ifdef USE_MAD + case MusicMp3: + printf("creating mp3 stream\n"); + return makeMP3Stream(&_file, _file.size()); +#endif +#ifdef USE_VORBIS + case MusicVorbis: + return makeVorbisStream(&_file, _file.size()); +#endif + case MusicWave: + return makeWaveStream(&_file, 0); + case MusicNone: // shouldn't happen + warning("createAudioSource ran into null create\n"); + return NULL; + default: + error("MusicHandle::createAudioSource: called with illegal MusicMode"); + } + return NULL; // never reached +} + +bool MusicHandle::play(const char *fileBase, bool loop) { + char fileName[30]; + stop(); + _musicMode = MusicNone; +#ifdef USE_MAD + sprintf(fileName, "%s.mp3", fileBase); + if (_file.open(fileName)) + _musicMode = MusicMp3; +#endif +#ifdef USE_VORBIS + if (!_file.isOpen()) { // mp3 doesn't exist (or not compiled with MAD support) + sprintf(fileName, "%s.ogg", fileBase); + if (_file.open(fileName)) + _musicMode = MusicVorbis; + } +#endif + if (!_file.isOpen()) { + sprintf(fileName, "%s.wav", fileBase); + if (_file.open(fileName)) + _musicMode = MusicWave; + else { + warning("Music file %s could not be opened", fileName); + return false; + } + } + _audioSource = createAudioSource(); + _looping = loop; + fadeUp(); + return true; +} + void MusicHandle::fadeDown() { if (streaming()) { if (_fading < 0) @@ -57,53 +164,67 @@ bool MusicHandle::endOfData() const { return !streaming(); } +// is we don't have an audiosource, return some dummy values. +// shouldn't happen anyways. +bool MusicHandle::streaming(void) const { + return (_audioSource) ? (!_audioSource->endOfStream()) : false; +} + +bool MusicHandle::isStereo(void) const { + return (_audioSource) ? _audioSource->isStereo() : false; +} + +int MusicHandle::getRate(void) const { + return (_audioSource) ? _audioSource->getRate() : 11025; +} + int MusicHandle::readBuffer(int16 *buffer, const int numSamples) { - int samples; - for (samples = 0; samples < numSamples && !endOfData(); samples++) { - int16 sample = _file.readUint16LE(); - if (_file.ioFailed()) { - if (!_looping) { - stop(); - sample = 0; - } else { - _file.clearIOFailed(); - _file.seek(WAVEHEADERSIZE); - sample = _file.readUint16LE(); + int totalSamples = 0; + int16 *bufStart = buffer; + if (!_audioSource) + return 0; + int expectedSamples = numSamples; + while ((expectedSamples > 0) && _audioSource) { // _audioSource becomes NULL if we reach EOF and aren't looping + int samplesReturned = _audioSource->readBuffer(buffer, expectedSamples); + buffer += samplesReturned; + totalSamples += samplesReturned; + expectedSamples -= samplesReturned; + if ((expectedSamples > 0) && _audioSource->endOfData()) { + debug(2, "Music reached EOF"); + _audioSource->endOfData(); + if (_looping) { + delete _audioSource; // recreate same source. + _audioSource = createAudioSource(); } + if ((!_looping) || (!_audioSource)) + stop(); } - if (_fading > 0) { - if (--_fading == 0) { - _looping = false; - _file.close(); - } - sample = (sample * _fading) / _fadeSamples; - } else if (_fading < 0) { - _fading--; - sample = -(sample * _fading) / _fadeSamples; - if (_fading <= -_fadeSamples) - _fading = 0; + } + // buffer was filled, now do the fading (if necessary) + int samplePos = 0; + while ((_fading > 0) && (samplePos < totalSamples)) { // fade down + bufStart[samplePos] = (bufStart[samplePos] * --_fading) / _fadeSamples; + samplePos++; + if (_fading == 0) { + stop(); + // clear the rest of the buffer + memset(bufStart + samplePos, 0, (totalSamples - samplePos) * 2); + return samplePos; } - *buffer++ = sample; } - return samples; -} - -bool MusicHandle::play(const char *filename, bool loop) { - uint8 wavHeader[WAVEHEADERSIZE]; - stop(); - if (!_file.open(filename)) { - warning("Music file %s could not be opened", filename); - return false; + while ((_fading < 0) && (samplePos < totalSamples)) { // fade up + bufStart[samplePos] = -(bufStart[samplePos] * --_fading) / _fadeSamples; + if (_fading <= -_fadeSamples) + _fading = 0; } - _file.read(wavHeader, WAVEHEADERSIZE); - _stereo = (READ_LE_UINT16(wavHeader + 0x16) == 2); - _rate = READ_LE_UINT16(wavHeader + 0x18); - _looping = loop; - fadeUp(); - return true; + return totalSamples; } void MusicHandle::stop() { + if (_audioSource) { + delete _audioSource; + _audioSource = NULL; + } if (_file.isOpen()) _file.close(); _fading = 0; @@ -183,9 +304,7 @@ void Music::startMusic(int32 tuneId, int32 loopFlag) { _handles[1].fadeDown(); newStream = 0; } - char fName[20]; - sprintf(fName, "%s.wav", _tuneList[tuneId]); - if (_handles[newStream].play(fName, loopFlag != 0)) { + if (_handles[newStream].play(_tuneList[tuneId], loopFlag != 0)) { delete _converter[newStream]; _converter[newStream] = makeRateConverter(_handles[newStream].getRate(), _mixer->getOutputRate(), _handles[newStream].isStereo(), false); } diff --git a/sword1/music.h b/sword1/music.h index c050da4b2b..bb922b24a2 100644 --- a/sword1/music.h +++ b/sword1/music.h @@ -36,27 +36,51 @@ namespace Sword1 { #define WAVEHEADERSIZE 0x2C +enum MusicMode { + MusicNone = 0, + MusicWave, + MusicMp3, + MusicVorbis +}; + +class WaveAudioStream : public AudioStream { +public: + WaveAudioStream(File *source, uint32 pSize); + virtual ~WaveAudioStream(); + virtual int readBuffer(int16 *buffer, const int numSamples); + virtual bool isStereo(void) const { return _isStereo; }; + virtual bool endOfData(void) const; + virtual int getRate(void) const { return _rate; }; +private: + File *_sourceFile; + uint32 _rate; + bool _isStereo; + uint32 _samplesLeft; + uint16 _bitsPerSample; +}; + class MusicHandle : public AudioStream { private: File _file; bool _looping; int32 _fading; int32 _fadeSamples; - int _rate; - bool _stereo; + MusicMode _musicMode; + AudioStream *_audioSource; + AudioStream *createAudioSource(void); public: - MusicHandle() : _looping(false), _fading(0) {} + MusicHandle() : _looping(false), _fading(0), _audioSource(NULL) {} virtual int readBuffer(int16 *buffer, const int numSamples); bool play(const char *filename, bool loop); void stop(); void fadeUp(); void fadeDown(); - bool streaming() const { return _file.isOpen(); } + bool streaming() const; int32 fading() { return _fading; } bool endOfData() const; bool endOfStream() const { return false; } - bool isStereo() const { return _stereo; } - int getRate() const { return _rate; } + bool isStereo() const; + int getRate() const; }; class Music { diff --git a/sword1/sound.cpp b/sword1/sound.cpp index 3c752f887e..1dbd6b7538 100644 --- a/sword1/sound.cpp +++ b/sword1/sound.cpp @@ -31,7 +31,7 @@ namespace Sword1 { #define SOUND_SPEECH_ID 1 #define SPEECH_FLAGS (SoundMixer::FLAG_16BITS | SoundMixer::FLAG_AUTOFREE | SoundMixer::FLAG_LITTLE_ENDIAN) -Sound::Sound(const char *searchPath, SoundMixer *mixer, ResMan *pResMan, bool isDemo) { +Sound::Sound(const char *searchPath, SoundMixer *mixer, ResMan *pResMan) { strcpy(_filePath, searchPath); _mixer = mixer; _resMan = pResMan; @@ -39,7 +39,6 @@ Sound::Sound(const char *searchPath, SoundMixer *mixer, ResMan *pResMan, bool is _endOfQueue = 0; _currentCowFile = 0; _speechVolL = _speechVolR = _sfxVolL = _sfxVolR = 192; - _isDemo = isDemo; } int Sound::addToQueue(int32 fxNo) { @@ -167,12 +166,35 @@ bool Sound::startSpeech(uint16 roomNo, uint16 localNo) { uint32 index = _cowHeader[locIndex + (localNo * 2) - 1]; debug(6, "startSpeech(%d, %d): locIndex %d, sampleSize %d, index %d", roomNo, localNo, locIndex, sampleSize, index); if (sampleSize) { - uint32 size; - int16 *data = uncompressSpeech(index + _cowHeaderSize, sampleSize, &size); uint8 speechVol = (_speechVolR + _speechVolL) / 2; int8 speechPan = (_speechVolR - _speechVolL) / 2; - if (data) - _mixer->playRaw(&_speechHandle, data, size, 11025, SPEECH_FLAGS, SOUND_SPEECH_ID, speechVol, speechPan); + if (_cowMode == CowWave) { + uint32 size; + int16 *data = uncompressSpeech(index + _cowHeaderSize, sampleSize, &size); + if (data) + _mixer->playRaw(&_speechHandle, data, size, 11025, SPEECH_FLAGS, SOUND_SPEECH_ID, speechVol, speechPan); + } +#ifdef USE_MAD + else if (_cowMode == CowMp3) { + warning("playing mp3: Sample(%d/%d) Index %X, Size %d", roomNo, localNo, index, sampleSize); + _cowFile.seek(index); + _mixer->playMP3(&_speechHandle, &_cowFile, sampleSize, speechVol, speechPan, SOUND_SPEECH_ID); + // with compressed audio, we can't calculate the wave volume. + // so default to talking. + for (int cnt = 0; cnt < 480; cnt++) + _waveVolume[cnt] = true; + _waveVolPos = 0; + } +#endif +#ifdef USE_VORBIS + else if (_cowMode == CowVorbis) { + _cowFile.seek(index); + _mixer->playVorbis(&_speechHandle, &_cowFile, sampleSize, speechVol, speechPan, SOUND_SPEECH_ID); + for (int cnt = 0; cnt < 480; cnt++) + _waveVolume[cnt] = true; + _waveVolPos = 0; + } +#endif return true; } else return false; @@ -187,8 +209,7 @@ int16 *Sound::uncompressSpeech(uint32 index, uint32 cSize, uint32 *size) { headerPos++; if (headerPos < 100) { int32 resSize; - // Demo uses slightly different headers - if (_isDemo) { + if (_cowMode == CowDemo) { // Demo uses slightly different headers resSize = READ_LE_UINT32(fBuf + headerPos + 6) >> 1; headerPos += 2; } else @@ -259,12 +280,40 @@ void Sound::initCowSystem(void) { /* look for speech1/2.clu in the data dir and speech/speech.clu (running from cd or using cd layout) */ - sprintf(cowName, "SPEECH%d.CLU", SwordEngine::_systemVars.currentCD); +#ifdef USE_MAD + sprintf(cowName, "SPEECH%d.CL3", SwordEngine::_systemVars.currentCD); _cowFile.open(cowName); + if (_cowFile.isOpen()) { + debug(1, "Using MP3 compressed Speech Cluster"); + _cowMode = CowMp3; + } +#endif +#ifdef USE_VORBIS + if (!_cowFile.isOpen()) { + sprintf(cowName, "SPEECH%d.CLV", SwordEngine::_systemVars.currentCD); + _cowFile.open(cowName); + if (_cowFile.isOpen()) { + debug(1, "Using Vorbis compressed Speech Cluster"); + _cowMode = CowVorbis; + } + } +#endif + if (!_cowFile.isOpen()) { + sprintf(cowName, "SPEECH%d.CLU", SwordEngine::_systemVars.currentCD); + _cowFile.open(cowName); + if (!_cowFile.isOpen()) { + _cowFile.open("speech.clu"); + } + debug(1, "Using uncompressed Speech Cluster"); + _cowMode = CowWave; + } if (!_cowFile.isOpen()) _cowFile.open("speech.clu"); - if (!_cowFile.isOpen()) - _cowFile.open("cows.mad"); + if (!_cowFile.isOpen()) { + _cowFile.open("cows.mad"); + if (_cowFile.isOpen()) + _cowMode = CowDemo; + } if (_cowFile.isOpen()) { _cowHeaderSize = _cowFile.readUint32LE(); _cowHeader = (uint32*)malloc(_cowHeaderSize); diff --git a/sword1/sound.h b/sword1/sound.h index df0d17376c..ece8f7e3e2 100644 --- a/sword1/sound.h +++ b/sword1/sound.h @@ -58,9 +58,16 @@ class ResMan; #define WAVE_VOL_TAB_LENGTH 480 #define WAVE_VOL_THRESHOLD 190000 //120000 +enum CowMode { + CowWave = 0, + CowMp3, + CowVorbis, + CowDemo +}; + class Sound { public: - Sound(const char *searchPath, SoundMixer *mixer, ResMan *pResMan, bool isDemo); + Sound(const char *searchPath, SoundMixer *mixer, ResMan *pResMan); ~Sound(void); void setSpeechVol(uint8 volL, uint8 volR) { _speechVolL = volL; _speechVolR = volR; }; void setSfxVol(uint8 volL, uint8 volR) { _sfxVolL = volL; _sfxVolR = volR; }; @@ -93,6 +100,7 @@ private: uint32 *_cowHeader; uint32 _cowHeaderSize; uint8 _currentCowFile; + CowMode _cowMode; PlayingSoundHandle _speechHandle, _fxHandle; Common::RandomSource _rnd; @@ -100,7 +108,6 @@ private: uint8 _endOfQueue; SoundMixer *_mixer; ResMan *_resMan; - bool _isDemo; char _filePath[100]; static const char _musicList[270]; static const uint16 _roomsFixedFx[TOTAL_ROOMS][TOTAL_FX_PER_ROOM]; diff --git a/sword1/sword1.cpp b/sword1/sword1.cpp index bc6e787b5a..28ea10326c 100644 --- a/sword1/sword1.cpp +++ b/sword1/sword1.cpp @@ -138,7 +138,7 @@ void SwordEngine::initialize(void) { _mouse = new Mouse(_system, _resMan, _objectMan); _screen = new Screen(_system, _resMan, _objectMan); _music = new Music(_system, _mixer); - _sound = new Sound("", _mixer, _resMan, (_features & GF_DEMO) != 0); + _sound = new Sound("", _mixer, _resMan); _menu = new Menu(_screen, _mouse); _logic = new Logic(_objectMan, _resMan, _screen, _mouse, _sound, _music, _menu, _system, _mixer); _mouse->useLogicAndMenu(_logic, _menu); -- cgit v1.2.3