From c987d6aaf0744e088c3119d7270e8a02ad47044a Mon Sep 17 00:00:00 2001 From: Kari Salminen Date: Wed, 15 Aug 2007 22:00:31 +0000 Subject: Added rudimentary classes for different AGI sound resources (IIgsMidi, IIgsSample, PCjrSound). Made existing code to at least work with PCjrSound. svn-id: r28630 --- engines/agi/agi.cpp | 2 +- engines/agi/agi.h | 2 +- engines/agi/agi_v2.cpp | 3 +- engines/agi/agi_v3.cpp | 3 +- engines/agi/sound.cpp | 161 +++++++++++++++++++++++++++---------------------- engines/agi/sound.h | 60 +++++++++++++++--- 6 files changed, 147 insertions(+), 84 deletions(-) diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp index ee3e2f36e0..ec8c819ab3 100644 --- a/engines/agi/agi.cpp +++ b/engines/agi/agi.cpp @@ -370,7 +370,7 @@ int AgiEngine::agiInit() { memset(&_game.views[i], 0, sizeof(struct AgiView)); memset(&_game.pictures[i], 0, sizeof(struct AgiPicture)); memset(&_game.logics[i], 0, sizeof(struct AgiLogic)); - memset(&_game.sounds[i], 0, sizeof(class AgiSound)); + memset(&_game.sounds[i], 0, sizeof(class AgiSound *)); // _game.sounds contains pointers now memset(&_game.dirView[i], 0, sizeof(struct AgiDir)); memset(&_game.dirPic[i], 0, sizeof(struct AgiDir)); memset(&_game.dirLogic[i], 0, sizeof(struct AgiDir)); diff --git a/engines/agi/agi.h b/engines/agi/agi.h index ae880da82c..046688f240 100644 --- a/engines/agi/agi.h +++ b/engines/agi/agi.h @@ -525,7 +525,7 @@ struct AgiGame { AgiPicture pictures[MAX_DIRS]; /**< AGI picture resources */ AgiLogic logics[MAX_DIRS]; /**< AGI logic resources */ AgiView views[MAX_DIRS]; /**< AGI view resources */ - AgiSound sounds[MAX_DIRS]; /**< AGI sound resources */ + AgiSound *sounds[MAX_DIRS]; /**< Pointers to AGI sound resources */ /* view table */ VtEntry viewTable[MAX_VIEWTABLE]; diff --git a/engines/agi/agi_v2.cpp b/engines/agi/agi_v2.cpp index 1cd9126350..593ca40384 100644 --- a/engines/agi/agi_v2.cpp +++ b/engines/agi/agi_v2.cpp @@ -254,7 +254,8 @@ int AgiLoader_v2::loadResource(int t, int n) { data = loadVolRes(&_vm->_game.dirSound[n]); if (data != NULL) { - _vm->_game.sounds[n].rdata = data; + // Freeing of the raw resource from memory is delegated to the createFromRawResource-function + _vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, *_vm->_sound); _vm->_game.dirSound[n].flags |= RES_LOADED; } else { ec = errBadResource; diff --git a/engines/agi/agi_v3.cpp b/engines/agi/agi_v3.cpp index c6fb850c60..dc419a7afe 100644 --- a/engines/agi/agi_v3.cpp +++ b/engines/agi/agi_v3.cpp @@ -345,7 +345,8 @@ int AgiLoader_v3::loadResource(int t, int n) { data = loadVolRes(&_vm->_game.dirSound[n]); if (data != NULL) { - _vm->_game.sounds[n].rdata = data; + // Freeing of the raw resource from memory is delegated to the createFromRawResource-function + _vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, *_vm->_sound); _vm->_game.dirSound[n].flags |= RES_LOADED; } else { ec = errBadResource; diff --git a/engines/agi/sound.cpp b/engines/agi/sound.cpp index f95ad1c923..0fe294af45 100644 --- a/engines/agi/sound.cpp +++ b/engines/agi/sound.cpp @@ -42,6 +42,81 @@ static bool g_useChorus = true; /* TODO: add support for variable sampling rate in the output device */ +AgiSound *AgiSound::createFromRawResource(uint8 *data, uint32 len, int resnum, SoundMgr &manager) { + if (data == NULL || len < 2) return NULL; // Check for too small resource or no resource at all + uint16 type = READ_LE_UINT16(data); + + switch (type) { // Create a sound object based on the type + case AGI_SOUND_SAMPLE : return new IIgsSample(data, len, resnum, manager); + case AGI_SOUND_MIDI : return new IIgsMidi (data, len, resnum, manager); + case AGI_SOUND_4CHN : return new PCjrSound (data, len, resnum, manager); + } + + warning("Sound resource (%d) has unknown type (0x%04x). Not using the sound", resnum, type); + return NULL; +} + +IIgsMidi::IIgsMidi(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : AgiSound(manager) { + _data = data; // Save the resource pointer + _len = len; // Save the resource's length + _type = READ_LE_UINT16(data); // Read sound resource's type + _isValid = (_type == AGI_SOUND_MIDI) && (_data != NULL) && (_len >= 2); + + if (!_isValid) // Check for errors + warning("Error creating Apple IIGS midi sound from resource %d (Type %d, length %d)", resnum, _type, len); +} + +PCjrSound::PCjrSound(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : AgiSound(manager) { + _data = data; // Save the resource pointer + _len = len; // Save the resource's length + _type = READ_LE_UINT16(data); // Read sound resource's type + _isValid = (_type == AGI_SOUND_4CHN) && (_data != NULL) && (_len >= 2); + + if (!_isValid) // Check for errors + warning("Error creating PCjr 4-channel sound from resource %d (Type %d, length %d)", resnum, _type, len); +} + +const uint8 *PCjrSound::getVoicePointer(uint voiceNum) { + assert(voiceNum >= 0 && voiceNum < 4); + uint16 voiceStartOffset = READ_LE_UINT16(_data + voiceNum * 2); + return _data + voiceStartOffset; +} + +IIgsSample::IIgsSample(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : AgiSound(manager) { + Common::MemoryReadStream stream(data, len, true); + + // Check that the header was read ok and that it's of the correct type + if (_header.read(stream) && _header.type == AGI_SOUND_SAMPLE) { // An Apple IIGS AGI sample resource + uint32 sampleStartPos = stream.pos(); + uint32 tailLen = stream.size() - sampleStartPos; + + if (tailLen < _header.sampleSize) { // Check if there's no room for the sample data in the stream + // Apple IIGS Manhunter I: Sound resource 16 has only 16074 bytes + // of sample data although header says it should have 16384 bytes. + warning("Apple IIGS sample (%d) too short (%d bytes. Should be %d bytes). Using the part that's left", + resnum, tailLen, _header.sampleSize); + _header.sampleSize = (uint16) tailLen; // Use the part that's left + } + + if (_header.pitch > 0x7F) { // Check if the pitch is invalid + warning("Apple IIGS sample (%d) has too high pitch (0x%02x)", resnum, _header.pitch); + _header.pitch &= 0x7F; // Apple IIGS AGI probably did it this way too + } + + // Finalize the header info using the 8-bit unsigned sample data + _header.finalize(stream); + + // Convert sample data from 8-bit unsigned to 16-bit signed format + stream.seek(sampleStartPos); + _sample = new int16[_header.sampleSize]; + if (_sample != NULL) + _isValid = _manager.convertWave(stream, _sample, _header.sampleSize); + } + + if (!_isValid) // Check for errors + warning("Error creating Apple IIGS sample from resource %d (Type %d, length %d)", resnum, _header.type, len); +} + /** Reads an Apple IIGS envelope from then given stream. */ bool IIgsEnvelope::read(Common::SeekableReadStream &stream) { for (int segNum = 0; segNum < ENVELOPE_SEGMENT_COUNT; segNum++) { @@ -187,69 +262,10 @@ bool SoundMgr::finalizeInstruments(Common::SeekableReadStream &uint8Wave) { return true; } -/** - * Load an Apple IIGS AGI sample resource from the given stream and - * create an AudioStream out of it. - * - * @param stream The source stream. - * @param resnum Sound resource number. Optional. Used for error messages. - * @return A non-null AudioStream pointer if successful, NULL otherwise. - * @note In case of failure (i.e. NULL is returned), stream is reset back - * to its original position and its I/O failed -status is cleared. - * TODO: Add better handling of invalid resource number when printing error messages. - * TODO: Add support for looping sounds. - * FIXME: Fix sample rate calculation, it's probably not accurate at the moment. - */ -Audio::AudioStream *SoundMgr::makeIIgsSampleStream(Common::SeekableReadStream &stream, int resnum) { - const uint32 startPos = stream.pos(); - IIgsSampleHeader header; - Audio::AudioStream *result = NULL; - bool readHeaderOk = header.read(stream); - - // Check that the header was read ok and that it's of the correct type - // and that there's room for the sample data in the stream. - if (readHeaderOk && header.type == AGI_SOUND_SAMPLE) { // An Apple IIGS AGI sample resource - uint32 tailLen = stream.size() - stream.pos(); - if (tailLen < header.sampleSize) { // Check if there's no room for the sample data in the stream - // Apple IIGS Manhunter I: Sound resource 16 has only 16074 bytes - // of sample data although header says it should have 16384 bytes. - warning("Apple IIGS sample (%d) too short (%d bytes. Should be %d bytes). Using the part that's left", resnum, tailLen, header.sampleSize); - header.sampleSize = (uint16) tailLen; // Use the part that's left - } - if (header.pitch > 0x7F) { // Check if the pitch is invalid - warning("Apple IIGS sample (%d) has too high pitch (0x%02x)", resnum, header.pitch); - header.pitch &= 0x7F; // Apple IIGS AGI probably did it this way too - } - // Allocate memory for the sample data and read it in - byte *sampleData = (byte *) malloc(header.sampleSize); - uint32 readBytes = stream.read(sampleData, header.sampleSize); - if (readBytes == header.sampleSize) { // Check that we got all the data we requested - // Create a stream out of the read sample data (Needed by the finalize-function) - Common::MemoryReadStream sampleStream(sampleData, readBytes); - header.finalize(sampleStream); - // Make an audio stream from the mono, 8 bit, unsigned input data - byte flags = Audio::Mixer::FLAG_AUTOFREE | Audio::Mixer::FLAG_UNSIGNED; - int rate = (int) (1076 * pow(SEMITONE, header.pitch)); - result = Audio::makeLinearInputStream(sampleData, header.sampleSize, rate, flags, 0, 0); - } else // Couldn't read enough data, so let's delete the sample data buffer - delete sampleData; - } - - // If couldn't make a sample out of the input stream for any reason then - // rewind back to stream's starting position and clear I/O failed -status. - if (result == NULL) { - stream.seek(startPos); - stream.clearIOFailed(); - } - - return result; -} - static int playing; static ChannelInfo chn[NUM_CHANNELS]; static int endflag = -1; static int playingSound = -1; -static uint8 *song; static uint8 env; @@ -300,13 +316,13 @@ static int noteToPeriod(int note) { void SoundMgr::unloadSound(int resnum) { if (_vm->_game.dirSound[resnum].flags & RES_LOADED) { - if (_vm->_game.sounds[resnum].isPlaying()) { - _vm->_game.sounds[resnum].stop(); + if (_vm->_game.sounds[resnum]->isPlaying()) { + _vm->_game.sounds[resnum]->stop(); } - /* Release RAW data for sound */ - free(_vm->_game.sounds[resnum].rdata); - _vm->_game.sounds[resnum].rdata = NULL; + // Release the sound resource's data + delete _vm->_game.sounds[resnum]; + _vm->_game.sounds[resnum] = NULL; _vm->_game.dirSound[resnum].flags &= ~RES_LOADED; } } @@ -314,23 +330,21 @@ void SoundMgr::unloadSound(int resnum) { void SoundMgr::startSound(int resnum, int flag) { int i, type; - if (_vm->_game.sounds[resnum].isPlaying()) + if (_vm->_game.sounds[resnum] != NULL && _vm->_game.sounds[resnum]->isPlaying()) return; stopSound(); - if (_vm->_game.sounds[resnum].rdata == NULL) + if (_vm->_game.sounds[resnum] == NULL) // Is this needed at all? return; - type = READ_LE_UINT16(_vm->_game.sounds[resnum].rdata); + type = _vm->_game.sounds[resnum]->type(); if (type != AGI_SOUND_SAMPLE && type != AGI_SOUND_MIDI && type != AGI_SOUND_4CHN) return; - _vm->_game.sounds[resnum].play(); - _vm->_game.sounds[resnum].type = type; + _vm->_game.sounds[resnum]->play(); playingSound = resnum; - song = (uint8 *)_vm->_game.sounds[resnum].rdata; switch (type) { #if 0 @@ -365,6 +379,7 @@ void SoundMgr::startSound(int resnum, int flag) { break; #endif case AGI_SOUND_4CHN: + PCjrSound *pcjrSound = (PCjrSound *) _vm->_game.sounds[resnum]; /* Initialize channel info */ for (i = 0; i < NUM_CHANNELS; i++) { chn[i].type = type; @@ -375,7 +390,7 @@ void SoundMgr::startSound(int resnum, int flag) { } chn[i].ins = waveform; chn[i].size = WAVEFORM_SIZE; - chn[i].ptr = song + READ_LE_UINT16(song + i * 2); + chn[i].ptr = pcjrSound->getVoicePointer(i % 4); chn[i].timer = 0; chn[i].vol = 0; chn[i].end = 0; @@ -399,7 +414,7 @@ void SoundMgr::stopSound() { stopNote(i); if (playingSound != -1) { - _vm->_game.sounds[playingSound].stop(); + _vm->_game.sounds[playingSound]->stop(); playingSound = -1; } } @@ -611,7 +626,7 @@ void SoundMgr::playSound() { _vm->setflag(endflag, true); if (playingSound != -1) - _vm->_game.sounds[playingSound].stop(); + _vm->_game.sounds[playingSound]->stop(); playingSound = -1; endflag = -1; } diff --git a/engines/agi/sound.h b/engines/agi/sound.h index ccc63d2fdc..058f4ab67e 100644 --- a/engines/agi/sound.h +++ b/engines/agi/sound.h @@ -162,7 +162,7 @@ struct AgiNote { uint8 attenuation; ///< Note volume attenuation (4-bit) /** Reads an AgiNote through the given pointer. */ - void read(uint8 *ptr) { + void read(const uint8 *ptr) { duration = READ_LE_UINT16(ptr); uint16 freqByte0 = *(ptr + 2); // Bits 4-9 of the frequency divisor uint16 freqByte1 = *(ptr + 3); // Bits 0-3 of the frequency divisor @@ -180,7 +180,7 @@ struct ChannelInfo { #define AGI_SOUND_MIDI 0x0002 #define AGI_SOUND_4CHN 0x0008 uint32 type; - uint8 *ptr; // Pointer to the AgiNote data + const uint8 *ptr; // Pointer to the AgiNote data int16 *ins; int32 size; uint32 phase; @@ -199,20 +199,66 @@ struct ChannelInfo { uint32 env; }; +class SoundMgr; + /** * AGI sound resource structure. */ class AgiSound { public: - uint32 flen; /**< size of raw data */ - uint8 *rdata; /**< raw sound data */ - uint16 type; /**< sound resource type */ - + AgiSound(SoundMgr &manager) : _manager(manager), _isPlaying(false), _isValid(false) {} virtual void play() { _isPlaying = true; } virtual void stop() { _isPlaying = false; } virtual bool isPlaying() { return _isPlaying; } + virtual uint16 type() = 0; + + /** + * A named constructor for creating different types of AgiSound objects + * from a raw sound resource. + * + * NOTE: This function should take responsibility for freeing the raw resource + * from memory using free() or delegate the responsibility onwards to some other + * function! + */ + static AgiSound *createFromRawResource(uint8 *data, uint32 len, int resnum, SoundMgr &manager); + +protected: + SoundMgr &_manager; ///< AGI sound manager object + bool _isPlaying; ///< Is the sound playing? + bool _isValid; ///< Is this a valid sound object? +}; + +class PCjrSound : public AgiSound { +public: + PCjrSound(uint8 *data, uint32 len, int resnum, SoundMgr &manager); + ~PCjrSound() { if (_data != NULL) free(_data); } + virtual uint16 type() { return _type; } + const uint8 *getVoicePointer(uint voiceNum); +protected: + uint8 *_data; ///< Raw sound resource data + uint32 _len; ///< Length of the raw sound resource + uint16 _type; ///< Sound resource type +}; + +class IIgsMidi : public AgiSound { +public: + IIgsMidi(uint8 *data, uint32 len, int resnum, SoundMgr &manager); + ~IIgsMidi() { if (_data != NULL) free(_data); } + virtual uint16 type() { return _type; } +protected: + uint8 *_data; ///< Raw sound resource data + uint32 _len; ///< Length of the raw sound resource + uint16 _type; ///< Sound resource type +}; + +class IIgsSample : public AgiSound { +public: + IIgsSample(uint8 *data, uint32 len, int resnum, SoundMgr &manager); + ~IIgsSample() { delete[] _sample; } + virtual uint16 type() { return _header.type; } protected: - bool _isPlaying; ///< Is the sound playing? + IIgsSampleHeader _header; ///< Apple IIGS AGI sample header + int16 *_sample; ///< Sample data (16-bit signed format) }; /** Apple IIGS AGI instrument set information. */ -- cgit v1.2.3