mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
- Fixes LSL6 CD (SCI16 version) Electroshock freeze, bug #15165 - Improves LB2 CD "Ra Ra Amon Ra" chant scene Neither of these scenes are perfectly reproduced in ScummVM yet, but they are better than they were. They accidentally rely on precise undocumented/undiscovered audio driver behavior when digital sound and speech are played at the same time and interrupt each other. These scripts do conflicting things that had no effect in the original floppy versions, but caused conflicts once CD speech was added later. They still appeared to work, but only by accident, and so no one would have noticed the problems at the time. When LSL6 was ported to SCI32, the Electroshock script was discovered and rewritten to be normal. This commit implements two missing pieces of interpreter behavior: 1. processUpdateCues stops the sound if it's a digital sample and the audio driver's AudioLoc function returns -1. To replicate this, we query our two separate components that play digital samples and speech. 2. kDoAudio(AudioLoc) returns the audio driver's AudioLoc result. SSCI's audio driver could have been playing a digital sample or speech, but we do these in two separate components, and we were only querying one of them. Now we query both and produce the same results as the original.
316 lines
8.5 KiB
C++
316 lines
8.5 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/>.
|
|
*
|
|
*/
|
|
|
|
#ifndef SCI_SOUND_MUSIC_H
|
|
#define SCI_SOUND_MUSIC_H
|
|
|
|
#include "common/serializer.h"
|
|
#include "common/mutex.h"
|
|
|
|
#include "audio/mixer.h"
|
|
|
|
#include "sci/sci.h"
|
|
#include "sci/resource/resource.h"
|
|
#include "sci/sound/drivers/mididriver.h"
|
|
#ifdef ENABLE_SCI32
|
|
#include "sci/sound/audio32.h"
|
|
#endif
|
|
|
|
namespace Audio {
|
|
class LoopingAudioStream;
|
|
class RewindableAudioStream;
|
|
}
|
|
|
|
namespace Sci {
|
|
|
|
enum SoundStatus {
|
|
kSoundStopped = 0,
|
|
kSoundInitialized = 1,
|
|
kSoundPaused = 2,
|
|
kSoundPlaying = 3
|
|
};
|
|
|
|
#define MUSIC_VOLUME_DEFAULT 127
|
|
#define MUSIC_VOLUME_MAX 127
|
|
#define MUSIC_MASTERVOLUME_DEFAULT 15
|
|
#define MUSIC_MASTERVOLUME_MAX 15
|
|
|
|
class MidiParser_SCI;
|
|
class SegManager;
|
|
|
|
typedef Common::Array<uint16> SignalQueue;
|
|
|
|
|
|
struct MusicEntryChannel {
|
|
// Channel info
|
|
int8 _prio; // 0 = essential; lower is higher priority
|
|
int8 _voices;
|
|
bool _dontRemap;
|
|
bool _dontMap;
|
|
uint8 _mute;
|
|
};
|
|
|
|
|
|
class MusicEntry : public Common::Serializable {
|
|
public:
|
|
// Do not get these directly for the sound objects!
|
|
// It's a bad idea, as the sound code (i.e. the SciMusic
|
|
// class) should be as separate as possible from the rest
|
|
// of the engine
|
|
|
|
reg_t soundObj;
|
|
|
|
SoundResource *soundRes;
|
|
uint16 resourceId;
|
|
|
|
int time; // "tim"estamp to indicate in which order songs have been added
|
|
|
|
uint16 dataInc;
|
|
uint16 ticker;
|
|
uint16 signal;
|
|
int16 priority; // must be int16, at least in Laura Bow 1, main music (object conMusic) uses priority -1
|
|
uint16 loop;
|
|
int16 volume;
|
|
int16 hold;
|
|
int8 reverb;
|
|
bool playBed;
|
|
bool overridePriority; // Use soundObj's priority instead of resource's
|
|
|
|
int16 pauseCounter;
|
|
uint sampleLoopCounter;
|
|
|
|
byte fadeTo;
|
|
short fadeStep;
|
|
uint32 fadeTicker;
|
|
uint32 fadeTickerStep;
|
|
bool fadeSetVolume;
|
|
bool fadeCompleted;
|
|
bool stopAfterFading;
|
|
|
|
SoundStatus status;
|
|
|
|
Audio::Mixer::SoundType soundType;
|
|
|
|
int _usedChannels[16];
|
|
MusicEntryChannel _chan[16];
|
|
MidiParser_SCI *pMidiParser;
|
|
|
|
// this is used for storing signals, when the current signal is not yet
|
|
// sent to the scripts. We shouldn't need to save it, this normally only
|
|
// happens in rare situations like lb1, knocking on the door in the attic
|
|
SignalQueue signalQueue;
|
|
|
|
// TODO: We need to revise how we store the different
|
|
// audio stream objects we require.
|
|
Audio::RewindableAudioStream *pStreamAud;
|
|
Audio::LoopingAudioStream *pLoopStream;
|
|
Audio::SoundHandle hCurrentAud;
|
|
bool isSample;
|
|
|
|
public:
|
|
MusicEntry();
|
|
~MusicEntry() override;
|
|
|
|
void doFade();
|
|
void onTimer();
|
|
void setSignal(int signal);
|
|
|
|
void saveLoadWithSerializer(Common::Serializer &ser) override;
|
|
};
|
|
|
|
struct DeviceChannelUsage {
|
|
MusicEntry *_song;
|
|
int _channel;
|
|
bool operator==(const DeviceChannelUsage& other) const { return _song == other._song && _channel == other._channel; }
|
|
bool operator!=(const DeviceChannelUsage& other) const { return !(*this == other); }
|
|
};
|
|
|
|
struct ChannelRemapping {
|
|
DeviceChannelUsage _map[16];
|
|
int _prio[16];
|
|
int _voices[16];
|
|
bool _dontRemap[16];
|
|
int _freeVoices;
|
|
|
|
void clear();
|
|
void swap(int i, int j);
|
|
void evict(int i);
|
|
ChannelRemapping& operator=(ChannelRemapping& other);
|
|
int lowestPrio() const;
|
|
};
|
|
|
|
struct MidiCommand {
|
|
enum CmdType {
|
|
kTypeMidiMessage = 0,
|
|
kTypeTrackInit
|
|
};
|
|
// Keeping this very simple, due to the very limited purpose of it.
|
|
MidiCommand(CmdType type, uint32 val) : _type(type), _dataPtr(0), _dataVal(val) {}
|
|
MidiCommand(CmdType type, void *ptr) : _type(type), _dataPtr(ptr), _dataVal(0) {}
|
|
CmdType _type;
|
|
void *_dataPtr;
|
|
uint32 _dataVal;
|
|
};
|
|
|
|
typedef Common::Array<MusicEntry *> MusicList;
|
|
typedef Common::Array<MidiCommand> MidiCommandQueue;
|
|
|
|
class SciMusic : public Common::Serializable {
|
|
|
|
public:
|
|
SciMusic(SciVersion soundVersion, bool useDigitalSFX);
|
|
~SciMusic() override;
|
|
|
|
void init();
|
|
|
|
void onTimer();
|
|
void putMidiCommandInQueue(byte status, byte firstOp, byte secondOp);
|
|
void putMidiCommandInQueue(uint32 midi);
|
|
void putTrackInitCommandInQueue(MusicEntry *psnd);
|
|
void removeTrackInitCommandsFromQueue(MusicEntry *psnd);
|
|
private:
|
|
static void miditimerCallback(void *p);
|
|
void sendMidiCommandsFromQueue();
|
|
|
|
public:
|
|
void clearPlayList();
|
|
void pauseAll(bool pause);
|
|
bool isAllPaused() const { return (_globalPause > 0); }
|
|
void resetGlobalPauseCounter();
|
|
void stopAll();
|
|
void stopAllSamples();
|
|
|
|
// sound and midi functions
|
|
void soundInitSnd(MusicEntry *pSnd);
|
|
void soundPlay(MusicEntry *pSnd, bool restoring = false);
|
|
void soundStop(MusicEntry *pSnd);
|
|
void soundKill(MusicEntry *pSnd);
|
|
void soundPause(MusicEntry *pSnd);
|
|
void soundResume(MusicEntry *pSnd);
|
|
void soundToggle(MusicEntry *pSnd, bool pause);
|
|
void soundSetVolume(MusicEntry *pSnd, byte volume);
|
|
void soundSetSampleVolume(MusicEntry *pSnd, byte volume);
|
|
void soundSetPriority(MusicEntry *pSnd, byte prio);
|
|
uint16 soundGetMasterVolume();
|
|
void soundSetMasterVolume(uint16 vol);
|
|
uint16 soundGetSoundOn() const { return _soundOn; }
|
|
void soundSetSoundOn(bool soundOnFlag);
|
|
uint16 soundGetVoices();
|
|
uint32 soundGetTempo() const { return _dwTempo; }
|
|
MusicType soundGetMusicType() const { return _musicType; }
|
|
|
|
bool isSoundActive(MusicEntry *pSnd) {
|
|
assert(pSnd->pStreamAud != 0);
|
|
return _pMixer->isSoundHandleActive(pSnd->hCurrentAud);
|
|
}
|
|
|
|
void updateAudioStreamTicker(MusicEntry *pSnd) {
|
|
assert(pSnd->pStreamAud != 0);
|
|
pSnd->ticker = (uint16)(_pMixer->getSoundElapsedTime(pSnd->hCurrentAud) * 0.06);
|
|
}
|
|
|
|
MusicEntry *getSlot(reg_t obj);
|
|
MusicEntry *getFirstSlotWithStatus(SoundStatus status);
|
|
|
|
void pushBackSlot(MusicEntry *slotEntry) {
|
|
Common::StackLock lock(_mutex);
|
|
_playList.push_back(slotEntry);
|
|
if (_soundVersion <= SCI_VERSION_0_LATE) // I limit this to SCI0, since it always inserts the nodes at the correct position, but no idea about >=SCI1
|
|
sortPlayList();
|
|
}
|
|
|
|
void printPlayList(Console *con);
|
|
void printSongInfo(reg_t obj, Console *con);
|
|
|
|
// The following two methods are NOT thread safe - make sure that
|
|
// the mutex is always locked before calling them
|
|
MusicList::iterator getPlayListStart() { return _playList.begin(); }
|
|
MusicList::iterator getPlayListEnd() { return _playList.end(); }
|
|
|
|
void sendMidiCommand(uint32 cmd);
|
|
void sendMidiCommand(MusicEntry *pSnd, uint32 cmd);
|
|
|
|
void setGlobalReverb(int8 reverb);
|
|
int8 getGlobalReverb() { return _globalReverb; }
|
|
|
|
byte getCurrentReverb();
|
|
|
|
void needsRemap() { _needsRemap = true; }
|
|
|
|
void saveLoadWithSerializer(Common::Serializer &ser) override;
|
|
|
|
// Mutex for music code. Used to guard access to the song playlist, to the
|
|
// MIDI parser and to the MIDI driver/player. We use a reference to
|
|
// the mixer's internal mutex to avoid deadlocks which sometimes occur when
|
|
// different threads lock each other up in different mutexes.
|
|
Common::Mutex &_mutex;
|
|
|
|
protected:
|
|
void sortPlayList();
|
|
|
|
SciVersion _soundVersion;
|
|
|
|
Audio::Mixer *_pMixer;
|
|
MidiPlayer *_pMidiDrv;
|
|
|
|
uint32 _dwTempo;
|
|
// If true and a sound has a digital track, the sound from the AdLib track is played
|
|
bool _useDigitalSFX;
|
|
|
|
// remapping:
|
|
void remapChannels(bool mainThread = true);
|
|
ChannelRemapping *determineChannelMap();
|
|
void resetDeviceChannel(int devChannel, bool mainThread);
|
|
|
|
public:
|
|
// The parsers need to know this for the dontMap channels...
|
|
bool isDeviceChannelMapped(int devChannel) const;
|
|
|
|
bool isDigitalSamplePlaying() const;
|
|
|
|
private:
|
|
MusicList _playList;
|
|
bool _soundOn;
|
|
byte _masterVolume;
|
|
MusicEntry *_usedChannel[16];
|
|
int8 _channelRemap[16];
|
|
int8 _globalReverb;
|
|
bool _needsRemap;
|
|
int _globalPause;
|
|
bool _needsResume;
|
|
|
|
DeviceChannelUsage _channelMap[16];
|
|
|
|
MidiCommandQueue _queuedCommands;
|
|
MusicType _musicType;
|
|
|
|
int _driverFirstChannel;
|
|
int _driverLastChannel;
|
|
|
|
MusicEntry *_currentlyPlayingSample;
|
|
|
|
int _timeCounter; // Used to keep track of the order in which MusicEntries
|
|
// are added, for priority purposes.
|
|
};
|
|
|
|
} // End of namespace Sci
|
|
|
|
#endif // SCI_SOUND_MUSIC_H
|