diff options
-rw-r--r-- | engines/scumm/player_appleII.cpp | 364 | ||||
-rw-r--r-- | engines/scumm/player_appleII.h | 167 | ||||
-rw-r--r-- | engines/scumm/scumm.cpp | 3 |
3 files changed, 533 insertions, 1 deletions
diff --git a/engines/scumm/player_appleII.cpp b/engines/scumm/player_appleII.cpp new file mode 100644 index 0000000000..c5f60cd700 --- /dev/null +++ b/engines/scumm/player_appleII.cpp @@ -0,0 +1,364 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include <assert.h> +#include "engines/engine.h" +#include "scumm/player_appleII.h" +#include "scumm/scumm.h" + +// CPU_CLOCK according to AppleWin +static const double CPU_CLOCK = 1020484.5; // ~ 1.02 MHz + +namespace Scumm { + +Player_AppleII::Player_AppleII(ScummEngine *scumm, Audio::Mixer *mixer) { + _speakerState = 0; + _soundNr = 0; + + _mixer = mixer; + _sampleRate = _mixer->getOutputRate(); + _vm = scumm; + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +Player_AppleII::~Player_AppleII() { + _mixer->stopHandle(_soundHandle); +} + +/* +void logSounds() { + for (nr = 1; nr < 70; ++nr) { + switch (nr) { + // non-existing or invalid + case 3: case 4: case 5: case 49: case 51: case 68: + continue; + } + + byte *data = _vm->getResourceAddress(rtSound, nr); + if (data) { + size_t size = (data[1] << 8) | data[0]; + + std::stringstream s; + s << "d:/msound/sound" << nr << ".snd"; + FILE *f = fopen(s.str().c_str(), "wb"); + fwrite(data, 1, size, f); + fclose(f); + printf("sound: %d\n", nr); + } + } +} +*/ + +void Player_AppleII::startSound(int nr) { + Common::StackLock lock(_mutex); + + _soundNr = nr; + _buffer.clear(); + + byte *data = _vm->getResourceAddress(rtSound, nr); + assert(data); + + byte *ptr1 = data + 4; + + int type = ptr1[0]; + if (type == 0) + return; + int loop = ptr1[1]; + assert(loop > 0); + ptr1 += 2; + + debug(4, "startSound %d: type %d, loop %d", + nr, type, loop); + + do { + switch (type) { + case 1: // freq up/down + soundFunc1(ptr1); + break; + case 2: // symmetric wave (~) + soundFunc2(ptr1); + break; + case 3: // asymmetric wave (__-) + soundFunc3(ptr1); + break; + case 4: // polyphone (2 voices) + soundFunc4(ptr1); + break; + case 5: // periodic noise + soundFunc5(ptr1); + break; + } + --loop; + } while (loop > 0); +} + +void Player_AppleII::stopAllSounds() { + Common::StackLock lock(_mutex); + _buffer.clear(); +} + +void Player_AppleII::stopSound(int nr) { + Common::StackLock lock(_mutex); + if (_soundNr == nr) { + _buffer.clear(); + } +} + +int Player_AppleII::getSoundStatus(int nr) const { + Common::StackLock lock(_mutex); + return (_buffer.availableSize() > 0 ? 1 : 0); +} + +int Player_AppleII::getMusicTimer() { + /* Apple-II sounds are synchronous -> no music timer */ + return 0; +} + +int Player_AppleII::readBuffer(int16 *buffer, const int numSamples) { + Common::StackLock lock(_mutex); + return _buffer.read((byte*)buffer, numSamples * 2) / 2; +} + +/************************************ + * Apple-II sound-resource parser + ************************************/ + +// toggle speaker on/off +void Player_AppleII::speakerToggle() { + _speakerState ^= 0x1; +} + +void Player_AppleII::generateSamples(int cycles) { + // sampleDiff is used to compensate fractional samples + static double sampleDiff = 0; + double fSamples = (double)cycles / CPU_CLOCK * _sampleRate + sampleDiff; + int samples = (int)(fSamples + 0.5); + sampleDiff = fSamples - samples; + + float vol = (float)_maxvol / 255; + int16 value = vol * (_speakerState ? 32767 : -32767); + for (int i = 0; i < samples; ++i) + _buffer.write(&value, sizeof(value)); +} + +void Player_AppleII::wait(int interval, int count /*y*/) { + assert(count > 0); // 0 == 256? + assert(interval > 0); // 0 == 256? + generateSamples(11 + count*(8 + 5 * interval)); +} + +void Player_AppleII::_soundFunc1(int interval /*a*/, int count /*y*/) { // D076 + assert(interval > 0); // 0 == 256? + assert(count > 0); // 0 == 256? + + for (; count >= 0; --count) { + speakerToggle(); + generateSamples(17 + 5 * interval); + } +} + +void Player_AppleII::soundFunc1(const byte *params) { // D085 + int delta = params[0]; + int count = params[1]; + byte interval = params[2]; // must be byte ("interval < delta" possible) + int limit = params[3]; + bool decInterval = (params[4] >= 0x40); + + if (decInterval) { + do { + _soundFunc1(interval, count); + interval -= delta; + } while (interval >= limit); + } else { + do { + _soundFunc1(interval, count); + interval += delta; + } while (interval < limit); + } +} + +void Player_AppleII::_soundFunc2(int interval /*a*/, int count) { // D0EF + if (interval == 0xFE) { + wait(interval, 10); + } else { + assert(count > 0); // 0 == 256? + assert(interval > 0); // 0 == 256? + + int a = (interval >> 3) + count; + for (int y = a; y > 0; --y) { + generateSamples(1292 - 5*interval); + speakerToggle(); + + generateSamples(1287 - 5*interval); + speakerToggle(); + } + } +} + +void Player_AppleII::soundFunc2(const byte *params) { // D0D6 + for (int pos = 1; pos < 256; ++pos) { + byte interval = params[pos]; + if (interval == 0xFF) + return; + _soundFunc2(interval, params[0] /*, LD12F=interval*/); + } +} + +void Player_AppleII::_soundFunc3(int interval /*a*/, int count /*LD12D*/) { // D14B + if (interval == 0xFE) { + wait(interval, 70); + } else { + assert(interval > 0); // 0 == 256? + assert(count > 0); // 0 == 256? + + for (int y = count; y > 0; --y) { + generateSamples(1289 - 5*interval); + speakerToggle(); + } + } +} + +void Player_AppleII::soundFunc3(const byte *params) { // D132 + for (int pos = 1; pos < 256; ++pos) { + byte interval = params[pos]; + if (interval == 0xFF) + return; + _soundFunc3(interval, params[0]); + } +} + +void Player_AppleII::_soundFunc4(byte param0, byte param1, byte param2) { // D1A2 + uint16 count = (-param2 << 8) | 0x3; + byte bitmask1 = 0x3; + byte bitmask2 = 0x3; + + byte updateInterval2 = param0; + if (updateInterval2 == 0) + bitmask2 = 0x0; + + byte updateInterval1 = param1; + if (updateInterval1 == 0) { + bitmask1 = 0x0; + if (bitmask2 != 0) { + bitmask1 = bitmask2; + bitmask2 = 0; + updateInterval1 = updateInterval2; + } + } + + byte speakerShiftReg = 0; + static byte updateRemain1 = 80; + static byte updateRemain2 = 10; + + while (true) { + --updateRemain1; + if (updateRemain1 == 0) { + updateRemain1 = updateInterval1; + speakerShiftReg ^= bitmask1; + } + + --updateRemain2; + if (updateRemain2 == 0) { + updateRemain2 = updateInterval2; + if (updateRemain1 != 0) { + speakerShiftReg ^= bitmask2; + } + } + + if (speakerShiftReg & 0x1) + speakerToggle(); + speakerShiftReg >>= 1; + generateSamples(40); + + ++count; + if (count == 0) { + return; + } + } +} + +void Player_AppleII::soundFunc4(const byte *params) { // D170 + while (params[0] != 0x01) { + _soundFunc4(params[0], params[1], params[2]); + params += 3; + } +} + +void Player_AppleII::_soundFunc5(int interval /*a*/, int count) { // D270 + assert(count > 0); // 0 == 256? + if (interval == 0) + interval = 256; + + for (int i = count; i > 0; --i) { + generateSamples(10 + 5*interval); + speakerToggle(); + + generateSamples(5 + 5*interval); + speakerToggle(); + } +} + +// LD000[loc] ^ LD00A[loc] +static const byte noiseTable[256] = { + 0x65, 0x1b, 0xda, 0x11, 0x61, 0xe5, 0x77, 0x57, 0x92, 0xc8, 0x51, 0x1c, 0xd4, 0x91, 0x62, 0x63, + 0x00, 0x38, 0x57, 0xd5, 0x18, 0xd8, 0xdc, 0x40, 0x03, 0x86, 0xd3, 0x2f, 0x10, 0x11, 0xd8, 0x3c, + 0xbe, 0x00, 0x19, 0xc5, 0xd2, 0xc3, 0xca, 0x34, 0x00, 0x28, 0xbf, 0xb9, 0x18, 0x20, 0x01, 0xcc, + 0xda, 0x08, 0xbc, 0x75, 0x7c, 0xb0, 0x8d, 0xe0, 0x09, 0x18, 0xbf, 0x5d, 0xe9, 0x8c, 0x75, 0x64, + 0xe5, 0xb5, 0x5d, 0xe0, 0xb7, 0x7d, 0xe9, 0x8c, 0x55, 0x65, 0xc5, 0xb5, 0x5d, 0xd8, 0x09, 0x0d, + 0x64, 0xf0, 0xf0, 0x08, 0x63, 0x03, 0x00, 0x55, 0x35, 0xc0, 0x00, 0x20, 0x74, 0xa5, 0x1e, 0xe3, + 0x00, 0x06, 0x3c, 0x52, 0xd1, 0x70, 0xd0, 0x57, 0x02, 0xf0, 0x00, 0xb6, 0xfc, 0x02, 0x11, 0x9a, + 0x3b, 0xc8, 0x38, 0xdf, 0x1a, 0xb0, 0xd1, 0xb8, 0xd0, 0x18, 0x8a, 0x4a, 0xea, 0x1b, 0x12, 0x5d, + 0x29, 0x58, 0xd8, 0x43, 0xb8, 0x2d, 0xd2, 0x61, 0x10, 0x3c, 0x0c, 0x5d, 0x1b, 0x61, 0x10, 0x3c, + 0x0a, 0x5d, 0x1d, 0x61, 0x10, 0x3c, 0x0b, 0x19, 0x88, 0x21, 0xc0, 0x21, 0x07, 0x00, 0x65, 0x62, + 0x08, 0xe9, 0x36, 0x40, 0x20, 0x41, 0x06, 0x00, 0x20, 0x00, 0x00, 0xed, 0xa3, 0x00, 0x88, 0x06, + 0x98, 0x01, 0x5d, 0x7f, 0x02, 0x1d, 0x78, 0x03, 0x60, 0xcb, 0x3a, 0x01, 0xbd, 0x78, 0x02, 0x5d, + 0x7e, 0x03, 0x1d, 0xf5, 0xa6, 0x40, 0x81, 0xb4, 0xd0, 0x8d, 0xd3, 0xd0, 0x6d, 0xd5, 0x61, 0x48, + 0x61, 0x4d, 0xd1, 0xc8, 0xb1, 0xd8, 0x69, 0xff, 0x61, 0xd9, 0xed, 0xa0, 0xfe, 0x19, 0x91, 0x37, + 0x19, 0x37, 0x00, 0xf1, 0x00, 0x01, 0x1f, 0x00, 0xad, 0xc1, 0x01, 0x01, 0x2e, 0x00, 0x40, 0xc6, + 0x7a, 0x9b, 0x95, 0x43, 0xfc, 0x18, 0xd2, 0x9e, 0x2a, 0x5a, 0x4b, 0x2a, 0xb6, 0x87, 0x30, 0x6c +}; + +byte /*a*/ Player_AppleII::noise() { // D261 + static int pos = 0; // initial value? + byte result = noiseTable[pos]; + pos = (pos + 1) % 256; + return result; +} + +void Player_AppleII::soundFunc5(const byte *params) { // D222 + const byte noiseMask[] = { + 0x3F, 0x3F, 0x7F, 0x7F, 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F + }; + + int param0 = params[0]; + assert(param0 > 0); + for (int i = 0; i < 10; ++i) { + int count = param0; + do { + _soundFunc5(noise() & noiseMask[i], 1); + --count; + } while (count > 0); + } +} + +} // End of namespace Scumm diff --git a/engines/scumm/player_appleII.h b/engines/scumm/player_appleII.h new file mode 100644 index 0000000000..545e590fdd --- /dev/null +++ b/engines/scumm/player_appleII.h @@ -0,0 +1,167 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SCUMM_PLAYER_APPLEII_H +#define SCUMM_PLAYER_APPLEII_H + +#include "common/mutex.h" +#include "common/scummsys.h" +#include "common/memstream.h" +#include "scumm/music.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/softsynth/sid.h" + +namespace Scumm { + +class ScummEngine; + +class DynamicMemoryStream { +public: + DynamicMemoryStream() : _data(0) { + clear(); + } + + ~DynamicMemoryStream() { + free(_data); + } + + void clear() { + free(_data); + _data = 0; + _capacity = 0; + _size = 0; + _ptr = 0; + _pos = 0; + _readPos = 0; + } + + void ensureCapacity(uint32 new_len) { + if (new_len <= _capacity) + return; + + byte *old_data = _data; + + _capacity *= 2; + if (_capacity < new_len + 2048) + _capacity = new_len + 2048; + _data = (byte *)malloc(_capacity); + _ptr = _data + _pos; + + if (old_data) { + // Copy old data + memcpy(_data, old_data, _size); + free(old_data); + } + + _size = new_len; + } + + uint32 availableSize() const { + if (_readPos >= _size) + return 0; + return _size - _readPos; + } + + virtual uint32 write(const void *dataPtr, uint32 dataSize) { + ensureCapacity(_pos + dataSize); + memcpy(_ptr, dataPtr, dataSize); + _ptr += dataSize; + _pos += dataSize; + if (_pos > _size) + _size = _pos; + return dataSize; + } + + uint32 read(byte *dataPtr, uint32 dataSize) const { + uint32 avail = availableSize(); + if (avail == 0) + return 0; + if (dataSize > avail) + dataSize = avail; + memcpy(dataPtr, _data + _readPos, dataSize); + _readPos += dataSize; + return dataSize; + } + +private: + mutable uint32 _readPos; + uint32 _capacity; + uint32 _size; + byte *_ptr; + byte *_data; + uint32 _pos; +}; + +class Player_AppleII : public Audio::AudioStream, public MusicEngine { +public: + Player_AppleII(ScummEngine *scumm, Audio::Mixer *mixer); + virtual ~Player_AppleII(); + + virtual void setMusicVolume(int vol) { _maxvol = vol; } + void startMusic(int songResIndex); + virtual void startSound(int sound); + virtual void stopSound(int sound); + virtual void stopAllSounds(); + virtual int getSoundStatus(int sound) const; + virtual int getMusicTimer(); + + // AudioStream API + int readBuffer(int16 *buffer, const int numSamples); + bool isStereo() const { return false; } + bool endOfData() const { return false; } + int getRate() const { return _sampleRate; } + +private: + ScummEngine *_vm; + Audio::Mixer *_mixer; + Audio::SoundHandle _soundHandle; + int _maxvol; + int _sampleRate; + Common::Mutex _mutex; + +private: + byte _speakerState; + DynamicMemoryStream _buffer; + int _soundNr; + +private: + void speakerToggle(); + void generateSamples(int cycles); + void wait(int interval, int count); + byte noise(); + + void soundFunc1(const byte *params); + void _soundFunc1(int interval, int count); + void soundFunc2(const byte *params); + void _soundFunc2(int interval, int count); + void soundFunc3(const byte *params); + void _soundFunc3(int interval, int count); + void soundFunc4(const byte *params); + void _soundFunc4(byte param0, byte param1, byte param2); + void soundFunc5(const byte *params); + void _soundFunc5(int interval, int count); +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index f94496b14b..3eea68fbbe 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -55,6 +55,7 @@ #include "scumm/player_nes.h" #include "scumm/player_sid.h" #include "scumm/player_pce.h" +#include "scumm/player_appleII.h" #include "scumm/player_v1.h" #include "scumm/player_v2.h" #include "scumm/player_v2cms.h" @@ -1797,7 +1798,7 @@ void ScummEngine::setupMusic(int midi) { if (_game.version >= 7) { // Setup for digital iMuse is performed in another place } else if (_game.platform == Common::kPlatformApple2GS && _game.version == 0){ - // TODO: Add support for music format + _musicEngine = new Player_AppleII(this, _mixer); } else if (_game.platform == Common::kPlatformC64 && _game.version <= 1) { #ifndef DISABLE_SID _musicEngine = new Player_SID(this, _mixer); |