231 lines
6.8 KiB
C++
231 lines
6.8 KiB
C++
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "agi/agi.h"
|
|
|
|
#include "agi/sound_2gs.h"
|
|
#include "agi/sound_a2.h"
|
|
#include "agi/sound_coco3.h"
|
|
#include "agi/sound_midi.h"
|
|
#include "agi/sound_sarien.h"
|
|
#include "agi/sound_pcjr.h"
|
|
|
|
#include "common/textconsole.h"
|
|
#include "audio/mixer.h"
|
|
|
|
namespace Agi {
|
|
|
|
SoundGen::SoundGen(AgiBase *vm, Audio::Mixer *pMixer) : _vm(vm), _mixer(pMixer) {
|
|
_sampleRate = pMixer->getOutputRate();
|
|
_soundHandle = new Audio::SoundHandle();
|
|
}
|
|
|
|
SoundGen::~SoundGen() {
|
|
delete _soundHandle;
|
|
}
|
|
|
|
//
|
|
// TODO: add support for variable sampling rate in the output device
|
|
//
|
|
|
|
AgiSound *AgiSound::createFromRawResource(uint8 *data, uint32 len, int resnum, int soundemu, bool isAgiV1) {
|
|
if (data == nullptr || len < 2) // Check for too small resource or no resource at all
|
|
return nullptr;
|
|
|
|
// Handle platform-specific formats that can't be detected by contents.
|
|
// These formats have no headers or predictable first bytes.
|
|
if (soundemu == SOUND_EMU_APPLE2) {
|
|
return new AgiSound(resnum, data, len, AGI_SOUND_APPLE2);
|
|
}
|
|
if (soundemu == SOUND_EMU_COCO3) {
|
|
return new AgiSound(resnum, data, len, AGI_SOUND_COCO3);
|
|
}
|
|
|
|
// Handle AGIv1; this format has no header or predictable first bytes.
|
|
// Must occur after platform check; Apple II always uses its format.
|
|
if (isAgiV1) {
|
|
return new PCjrSound(resnum, data, len, AGI_SOUND_4CHN);
|
|
}
|
|
|
|
uint16 type = READ_LE_UINT16(data);
|
|
switch (type) { // Create a sound object based on the type
|
|
case AGI_SOUND_SAMPLE:
|
|
return new IIgsSample(resnum, data, len, type);
|
|
case AGI_SOUND_MIDI:
|
|
return new IIgsMidi(resnum, data, len, type);
|
|
case AGI_SOUND_4CHN:
|
|
if (soundemu == SOUND_EMU_MIDI) {
|
|
return new AgiSound(resnum, data, len, type);
|
|
} else {
|
|
return new PCjrSound(resnum, data, len, type);
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
warning("Sound resource (%d) has unknown type (0x%04x). Not using the sound", resnum, type);
|
|
return nullptr;
|
|
}
|
|
|
|
PCjrSound::PCjrSound(byte resourceNr, byte *data, uint32 length, uint16 type) :
|
|
AgiSound(resourceNr, data, length, type) {
|
|
|
|
bool isValid = (_type == AGI_SOUND_4CHN) && (_data != nullptr) && (_length >= 2);
|
|
if (!isValid) // Check for errors
|
|
warning("Error creating PCjr 4-channel sound from resource %d (Type %d, length %d)", _resourceNr, _type, _length);
|
|
}
|
|
|
|
const uint8 *PCjrSound::getVoicePointer(uint voiceNum) {
|
|
assert(voiceNum < 4);
|
|
uint16 voiceStartOffset = READ_LE_UINT16(_data + voiceNum * 2);
|
|
|
|
return _data + voiceStartOffset;
|
|
}
|
|
|
|
void SoundMgr::unloadSound(int resnum) {
|
|
if (_vm->_game.dirSound[resnum].flags & RES_LOADED) {
|
|
if (_vm->_game.sounds[resnum]->isPlaying()) {
|
|
_vm->_game.sounds[resnum]->stop();
|
|
}
|
|
|
|
// Release the sound resource's data
|
|
delete _vm->_game.sounds[resnum];
|
|
_vm->_game.sounds[resnum] = nullptr;
|
|
_vm->_game.dirSound[resnum].flags &= ~RES_LOADED;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Start playing a sound resource. The logic here is that when the sound is
|
|
* finished we set the given flag to be true. This way the condition can be
|
|
* detected by the game. On the other hand, if the game wishes to start
|
|
* playing a new sound before the current one is finished, we also let it
|
|
* do that.
|
|
* @param resnum the sound resource number
|
|
* @param flag the flag that is wished to be set true when finished
|
|
*/
|
|
void SoundMgr::startSound(int resnum, int flag) {
|
|
AgiSound *sound = _vm->_game.sounds[resnum];
|
|
debugC(3, kDebugLevelSound, "startSound(resnum = %d, flag = %d, type = %d)", resnum, flag, sound ? sound->type() : 0);
|
|
if (sound == nullptr) {
|
|
warning("startSound: sound %d does not exist", resnum);
|
|
return;
|
|
}
|
|
|
|
stopSound();
|
|
|
|
// This check handles an Apple IIgs sample with an invalid header
|
|
if (!sound->isValid()) {
|
|
warning("startSound: sound %d is invalid", resnum);
|
|
return;
|
|
}
|
|
|
|
sound->play();
|
|
_playingSound = resnum;
|
|
_soundGen->play(resnum);
|
|
|
|
// Reset the flag
|
|
_endflag = flag;
|
|
|
|
_vm->setFlagOrVar(_endflag, false);
|
|
}
|
|
|
|
void SoundMgr::stopSound() {
|
|
debugC(3, kDebugLevelSound, "stopSound() --> %d", _playingSound);
|
|
|
|
if (_playingSound != -1) {
|
|
if (_vm->_game.sounds[_playingSound]) // sanity checking
|
|
_vm->_game.sounds[_playingSound]->stop();
|
|
_soundGen->stop();
|
|
_playingSound = -1;
|
|
}
|
|
|
|
// This is needed all the time, some games wait until music got played and when a sound/music got stopped early
|
|
// it would otherwise block the game (for example Death Angel jingle in back door poker room in Police Quest 1, room 71)
|
|
if (_endflag != -1) {
|
|
_vm->setFlagOrVar(_endflag, true);
|
|
}
|
|
|
|
_endflag = -1;
|
|
}
|
|
|
|
// FIXME: This is called from SoundGen classes on unsynchronized background threads.
|
|
void SoundMgr::soundIsFinished() {
|
|
if (_endflag != -1)
|
|
_vm->setFlagOrVar(_endflag, true);
|
|
|
|
if (_playingSound != -1)
|
|
_vm->_game.sounds[_playingSound]->stop();
|
|
_playingSound = -1;
|
|
_endflag = -1;
|
|
}
|
|
|
|
SoundMgr::SoundMgr(AgiBase *agi, Audio::Mixer *pMixer) {
|
|
_vm = agi;
|
|
_endflag = -1;
|
|
_playingSound = -1;
|
|
|
|
// FIXME: AGIv1 sounds are only supported by SoundGenPCJr, so we must not
|
|
// use SoundGenSarien for those games or it will crash because it expects
|
|
// the later AGI sound format. This means we cannot currently play these
|
|
// sounds in PC Speaker mode (SOUND_EMU_PC) or accept SOUND_EMU_NONE,
|
|
// because SoundGenSarien supports these but SoundGenPCJr does not.
|
|
if (agi->getVersion() <= 0x2001 && agi->getPlatform() == Common::kPlatformDOS) {
|
|
if (_vm->_soundemu != SOUND_EMU_PCJR) {
|
|
warning("Unsupported sound emulation %d for AGIv1 sounds, using PCjr", _vm->_soundemu);
|
|
_vm->_soundemu = SOUND_EMU_PCJR;
|
|
}
|
|
}
|
|
|
|
switch (_vm->_soundemu) {
|
|
default:
|
|
case SOUND_EMU_NONE:
|
|
case SOUND_EMU_AMIGA:
|
|
case SOUND_EMU_MAC:
|
|
case SOUND_EMU_PC:
|
|
_soundGen = new SoundGenSarien(_vm, pMixer);
|
|
break;
|
|
case SOUND_EMU_PCJR:
|
|
_soundGen = new SoundGenPCJr(_vm, pMixer);
|
|
break;
|
|
case SOUND_EMU_APPLE2:
|
|
_soundGen = new SoundGenA2(_vm, pMixer);
|
|
break;
|
|
case SOUND_EMU_APPLE2GS:
|
|
_soundGen = new SoundGen2GS(_vm, pMixer);
|
|
break;
|
|
case SOUND_EMU_COCO3:
|
|
_soundGen = new SoundGenCoCo3(_vm, pMixer);
|
|
break;
|
|
case SOUND_EMU_MIDI:
|
|
_soundGen = new SoundGenMIDI(_vm, pMixer);
|
|
break;
|
|
}
|
|
}
|
|
|
|
SoundMgr::~SoundMgr() {
|
|
stopSound();
|
|
|
|
delete _soundGen;
|
|
}
|
|
|
|
} // End of namespace Agi
|