mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-19 08:25:35 +00:00
AGOS: Add E2/WW AdLib and MT-32 SFX and enhancements
This adds support for the AdLib and MT-32 sound effects in Elvira 2 and Waxworks. It adds an option to the UI to toggle between sampled and synthesized SFX. It also adds the following enhancements: - AdLib OPL3 mode for Elvira 2, Waxworks and Simon 1 floppy demo. This can be selected using a new UI option. - Mixed AdLib/MIDI mode for Elvira 2 and Waxworks. - Implemented "monophonic chords", a feature of the original MIDI code which would play only the highest note of a chord on AdLib. Most noticable in the Waxworks music. - Added UI option to select Simon 1 DOS music tempos. - Rewrite of the AdLib and MT-32 drivers to remove duplication and make use of features of the standard multisource drivers. - Refactored MidiPlayer to standardize interface and remove code moved to the drivers and parsers.
This commit is contained in:
parent
97745050c2
commit
66bb075f1c
@ -2,3 +2,4 @@ engines/agos/saveload.cpp
|
||||
engines/agos/animation.cpp
|
||||
engines/agos/metaengine.cpp
|
||||
engines/agos/midi.cpp
|
||||
engines/agos/detection.cpp
|
||||
|
@ -525,6 +525,7 @@ AGOSEngine::AGOSEngine(OSystem *system, const AGOSGameDescription *gd)
|
||||
// syncSoundSettings.
|
||||
_musicVolume = 192;
|
||||
_effectsVolume = 192;
|
||||
_useDigitalSfx = true;
|
||||
|
||||
_saveLoadType = 0;
|
||||
_saveLoadSlot = 0;
|
||||
@ -611,12 +612,29 @@ Common::Error AGOSEngine::init() {
|
||||
((getFeatures() & GF_TALKIE) && getPlatform() == Common::kPlatformAcorn) ||
|
||||
(getPlatform() == Common::kPlatformDOS || getPlatform() == Common::kPlatformPC98)) {
|
||||
|
||||
int ret = _midi->open(getGameType(), getPlatform(), (getFeatures() & GF_DEMO));
|
||||
int ret = _midi->open();
|
||||
if (ret)
|
||||
warning("MIDI Player init failed: \"%s\"", MidiDriver::getErrorName(ret));
|
||||
|
||||
_midiEnabled = true;
|
||||
}
|
||||
// Digital SFX are used if MIDI SFX are not available or if the "prefer
|
||||
// digital SFX" setting is set to true or is not present at all.
|
||||
// Two exceptions to this are:
|
||||
// - Elvira 2 DOS needs an optional file to enable digital SFX. If it is
|
||||
// not present, MIDI SFX are used.
|
||||
// - Simon 1 DOS floppy has only MIDI SFX.
|
||||
// Note that MIDI SFX can be safely used if the MidiPlayer failed to
|
||||
// initialize; they just will not play.
|
||||
_useDigitalSfx = !_midiEnabled || !_midi->hasMidiSfx() || !ConfMan.hasKey("prefer_digitalsfx") || ConfMan.getBool("prefer_digitalsfx");
|
||||
if ((getGameType() == GType_ELVIRA2 && getPlatform() == Common::kPlatformDOS && !SearchMan.hasFile("013.VGA")) ||
|
||||
(getGameType() == GType_SIMON1 && getPlatform() == Common::kPlatformDOS && !(getFeatures() & GF_TALKIE))) {
|
||||
_useDigitalSfx = false;
|
||||
}
|
||||
if (!_useDigitalSfx && (getGameType() == GType_ELVIRA2 || getGameType() == GType_WW) && getPlatform() == Common::kPlatformDOS) {
|
||||
// Load the MIDI SFX data file for Elvira 2 and Waxworks DOS.
|
||||
loadMidiSfx();
|
||||
}
|
||||
|
||||
// allocate buffers
|
||||
_backGroundBuf = new Graphics::Surface();
|
||||
|
@ -253,8 +253,6 @@ public:
|
||||
const char *getFileName(int type) const;
|
||||
|
||||
protected:
|
||||
void playSting(uint16 a);
|
||||
|
||||
const byte *_vcPtr; /* video code ptr */
|
||||
uint16 _vcGetOutOfCode;
|
||||
|
||||
@ -599,6 +597,7 @@ protected:
|
||||
// The current SFX and ambient volume, or the last used volume if SFX
|
||||
// and/or ambient sounds are currently muted.
|
||||
uint16 _effectsVolume;
|
||||
bool _useDigitalSfx;
|
||||
|
||||
uint8 _saveGameNameLen;
|
||||
uint16 _saveLoadRowCurPos;
|
||||
@ -653,8 +652,12 @@ protected:
|
||||
void decompressPN(Common::Stack<uint32> &dataList, uint8 *&dataOut, int &dataOutSize);
|
||||
void loadOffsets(const char *filename, int number, uint32 &file, uint32 &offset, uint32 &compressedSize, uint32 &size);
|
||||
void loadSound(uint16 sound, int16 pan, int16 vol, uint16 type);
|
||||
void playSfx(uint16 sound, uint16 freq, uint16 flags, bool canUseMidiSfx);
|
||||
void loadSound(uint16 sound, uint16 freq, uint16 flags);
|
||||
void loadMidiSfx();
|
||||
virtual void playMidiSfx(uint16 sound);
|
||||
void loadVoice(uint speechId);
|
||||
void stopAllSfx();
|
||||
|
||||
void loadSoundFile(const char *filename);
|
||||
|
||||
@ -1904,6 +1907,7 @@ protected:
|
||||
int userGameGetKey(bool *b, uint maxChar) override;
|
||||
|
||||
void playMusic(uint16 music, uint16 track) override;
|
||||
void playMidiSfx(uint16 sound) override;
|
||||
|
||||
void vcStopAnimation(uint16 zone, uint16 sprite) override;
|
||||
|
||||
|
@ -24,9 +24,11 @@
|
||||
#include "engines/advancedDetector.h"
|
||||
#include "engines/obsolete.h"
|
||||
|
||||
#include "common/config-manager.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/installshield_cab.h"
|
||||
#include "common/translation.h"
|
||||
|
||||
#include "agos/detection.h"
|
||||
#include "agos/intern_detection.h"
|
||||
@ -71,6 +73,31 @@ static const char *const directoryGlobs[] = {
|
||||
|
||||
using namespace AGOS;
|
||||
|
||||
static const ExtraGuiOption opl3Mode = {
|
||||
_s("AdLib OPL3 mode"),
|
||||
_s("When AdLib is selected, OPL3 features will be used. Depending on the \
|
||||
game, this will prevent cut-off notes, add extra notes or instruments \
|
||||
and/or add stereo."),
|
||||
"opl3_mode",
|
||||
false
|
||||
};
|
||||
|
||||
static const ExtraGuiOption useDosTempos = {
|
||||
_s("Use DOS version music tempos"),
|
||||
_s("Selecting this option will play the music using the tempos used by \
|
||||
the DOS version of the game. Otherwise, the faster tempos of the Windows \
|
||||
version will be used."),
|
||||
"dos_music_tempos",
|
||||
false
|
||||
};
|
||||
|
||||
static const ExtraGuiOption preferDigitalSfx = {
|
||||
_s("Prefer digital sound effects"),
|
||||
_s("Prefer digital sound effects instead of synthesized ones"),
|
||||
"prefer_digitalsfx",
|
||||
true
|
||||
};
|
||||
|
||||
class AgosMetaEngineDetection : public AdvancedMetaEngineDetection {
|
||||
public:
|
||||
AgosMetaEngineDetection() : AdvancedMetaEngineDetection(AGOS::gameDescriptions, sizeof(AGOS::AGOSGameDescription), agosGames) {
|
||||
@ -98,6 +125,35 @@ public:
|
||||
const DebugChannelDef *getDebugChannels() const override {
|
||||
return debugFlagList;
|
||||
}
|
||||
|
||||
const ExtraGuiOptions getExtraGuiOptions(const Common::String &target) const override {
|
||||
const Common::String gameid = ConfMan.get("gameid", target);
|
||||
const Common::String platform = ConfMan.get("platform", target);
|
||||
const Common::String extra = ConfMan.get("extra", target);
|
||||
|
||||
ExtraGuiOptions options;
|
||||
if (target.empty() || ((gameid == "elvira2" || gameid == "waxworks" || gameid == "simon1") && platform == "pc")) {
|
||||
// DOS versions of Elvira 2, Waxworks and Simon 1 can optionally
|
||||
// make use of AdLib OPL3 features.
|
||||
options.push_back(opl3Mode);
|
||||
}
|
||||
if (target.empty() || (gameid == "simon1" && ((platform == "pc" && extra != "Floppy Demo") || platform == "windows" ||
|
||||
(platform == "acorn" && extra.contains("CD"))))) {
|
||||
// Simon 1 DOS (except the floppy demo), Windows and Acorn CD can
|
||||
// choose between the DOS or Windows music tempos.
|
||||
ExtraGuiOption dosTemposOption = useDosTempos;
|
||||
// DOS tempos are default for the DOS versions; other versions use
|
||||
// the Windows tempos by default.
|
||||
dosTemposOption.defaultState = platform == "pc";
|
||||
options.push_back(dosTemposOption);
|
||||
}
|
||||
if (target.empty() || ((gameid == "elvira2" || gameid == "waxworks") && platform == "pc")) {
|
||||
// DOS versions of Elvira 2 and Waxworks can use either Adlib or
|
||||
// digital SFX.
|
||||
options.push_back(preferDigitalSfx);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
};
|
||||
|
||||
REGISTER_PLUGIN_STATIC(AGOS_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, AgosMetaEngineDetection);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -19,102 +19,78 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "agos/drivers/accolade/mididriver.h"
|
||||
#ifndef AGOS_DRIVERS_ACCOLADE_ADLIB_H
|
||||
#define AGOS_DRIVERS_ACCOLADE_ADLIB_H
|
||||
|
||||
#include "audio/fmopl.h"
|
||||
#include "audio/mididrv.h"
|
||||
#include "audio/adlib_ms.h"
|
||||
|
||||
namespace AGOS {
|
||||
|
||||
#define AGOS_ADLIB_VOICES_COUNT 11
|
||||
class MidiDriver_Accolade_AdLib : public MidiDriver_ADLIB_Multisource {
|
||||
protected:
|
||||
static const byte RHYTHM_NOTE_INSTRUMENT_TYPES[40];
|
||||
static const uint16 OPL_NOTE_FREQUENCIES_INSTR_DAT[12];
|
||||
static const uint16 OPL_NOTE_FREQUENCIES_MUSIC_DRV[12];
|
||||
|
||||
struct InstrumentEntry {
|
||||
byte reg20op1; // Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple
|
||||
byte reg40op1; // Level Key Scaling / Total Level
|
||||
byte reg60op1; // Attack Rate / Decay Rate
|
||||
byte reg80op1; // Sustain Level / Release Rate
|
||||
byte reg20op2; // Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple
|
||||
byte reg40op2; // Level Key Scaling / Total Level
|
||||
byte reg60op2; // Attack Rate / Decay Rate
|
||||
byte reg80op2; // Sustain Level / Release Rate
|
||||
byte regC0; // Feedback / Algorithm, bit 0 - set -> both operators in use
|
||||
};
|
||||
|
||||
class MidiDriver_Accolade_AdLib : public MidiDriver {
|
||||
public:
|
||||
MidiDriver_Accolade_AdLib();
|
||||
MidiDriver_Accolade_AdLib(OPL::Config::OplType oplType, bool newVersion = false);
|
||||
~MidiDriver_Accolade_AdLib() override;
|
||||
|
||||
// MidiDriver
|
||||
int open() override;
|
||||
void close() override;
|
||||
void send(uint32 b) override;
|
||||
MidiChannel *allocateChannel() override { return NULL; }
|
||||
MidiChannel *getPercussionChannel() override { return NULL; }
|
||||
using MidiDriver_ADLIB_Multisource::send;
|
||||
void send(int8 source, uint32 b) override;
|
||||
void deinitSource(uint8 source) override;
|
||||
|
||||
bool isOpen() const override { return _isOpen; }
|
||||
uint32 getBaseTempo() override { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; }
|
||||
// Read the specified data from INSTR.DAT or MUSIC.DRV.
|
||||
void readDriverData(byte *driverData, uint16 driverDataSize, bool isMusicDrv);
|
||||
|
||||
void setVolume(byte volume);
|
||||
uint32 property(int prop, uint32 param) override;
|
||||
// Returns the number of simultaneous SFX sources supported by the current
|
||||
// driver configuration.
|
||||
byte getNumberOfSfxSources();
|
||||
// Loads the specified instrument for the specified instrument source.
|
||||
void loadSfxInstrument(uint8 source, byte *instrumentData);
|
||||
// Sets the note (upper byte) and note fraction (lower byte; 1/256th notes)
|
||||
// for the specified SFX source.
|
||||
void setSfxNoteFraction(uint8 source, uint16 noteFraction);
|
||||
// Writes out the current frequency for the specified SFX source.
|
||||
void updateSfxNote(uint8 source);
|
||||
|
||||
bool setupInstruments(byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile);
|
||||
protected:
|
||||
InstrumentInfo determineInstrument(uint8 channel, uint8 source, uint8 note) override;
|
||||
|
||||
void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) override;
|
||||
uint8 allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) override;
|
||||
uint16 calculateFrequency(uint8 channel, uint8 source, uint8 note) override;
|
||||
uint8 calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, OplInstrumentDefinition &instrumentDef, uint8 operatorNum) override;
|
||||
|
||||
private:
|
||||
bool _musicDrvMode;
|
||||
void writePanning(uint8 oplChannel, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED) override;
|
||||
void writeFrequency(uint8 oplChannel, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED) override;
|
||||
|
||||
// Copies the specified instrument data (in INSTR.DAT/MUSIC.DRV format)
|
||||
// into the specified instrument definition.
|
||||
void loadInstrumentData(OplInstrumentDefinition &definition, byte *instrumentData,
|
||||
OplInstrumentRhythmType rhythmType, byte rhythmNote, bool newVersion);
|
||||
|
||||
// False if the driver should have the behavior of the Elvira 1 driver;
|
||||
// true if it should have the behavior of the Elvira 2 / Waxworks version.
|
||||
bool _newVersion;
|
||||
|
||||
// from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI channel and MT32 channel
|
||||
byte _channelMapping[AGOS_MIDI_CHANNEL_COUNT];
|
||||
// from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI instruments and MT32 instruments
|
||||
byte _instrumentMapping[AGOS_MIDI_INSTRUMENT_COUNT];
|
||||
// from INSTR.DAT/MUSIC.DRV - volume adjustment per instrument
|
||||
signed char _instrumentVolumeAdjust[AGOS_MIDI_INSTRUMENT_COUNT];
|
||||
// simple mapping between MIDI key notes and MT32 key notes
|
||||
byte _percussionKeyNoteMapping[AGOS_MIDI_KEYNOTE_COUNT];
|
||||
int8 _volumeAdjustments[128];
|
||||
// from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI channel and AdLib channel
|
||||
byte _channelRemapping[16];
|
||||
// from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI instruments and AdLib instruments
|
||||
byte _instrumentRemapping[128];
|
||||
// Points to one of the OPL_NOTE_FREQUENCIES arrays, depending on the driver version
|
||||
const uint16 *_oplNoteFrequencies;
|
||||
|
||||
// from INSTR.DAT/MUSIC.DRV - adlib instrument data
|
||||
InstrumentEntry *_instrumentTable;
|
||||
byte _instrumentCount;
|
||||
// Data used by AdLib SFX (Elvira 2 / Waxworks)
|
||||
|
||||
struct ChannelEntry {
|
||||
const InstrumentEntry *currentInstrumentPtr;
|
||||
byte currentNote;
|
||||
byte currentA0hReg;
|
||||
byte currentB0hReg;
|
||||
int16 volumeAdjust;
|
||||
byte velocity;
|
||||
|
||||
ChannelEntry() : currentInstrumentPtr(NULL), currentNote(0),
|
||||
currentA0hReg(0), currentB0hReg(0), volumeAdjust(0), velocity(0) { }
|
||||
};
|
||||
|
||||
byte _percussionReg;
|
||||
|
||||
OPL::OPL *_opl;
|
||||
int _masterVolume;
|
||||
|
||||
Common::TimerManager::TimerProc _adlibTimerProc;
|
||||
void *_adlibTimerParam;
|
||||
|
||||
bool _isOpen;
|
||||
|
||||
// stores information about all FM voice channels
|
||||
ChannelEntry _channels[AGOS_ADLIB_VOICES_COUNT];
|
||||
|
||||
void onTimer();
|
||||
|
||||
void resetAdLib();
|
||||
void resetAdLibOperatorRegisters(byte baseRegister, byte value);
|
||||
void resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value);
|
||||
|
||||
void programChange(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr);
|
||||
void programChangeSetInstrument(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr);
|
||||
void setRegister(int reg, int value);
|
||||
void noteOn(byte FMvoiceChannel, byte note, byte velocity);
|
||||
void noteOnSetVolume(byte FMvoiceChannel, byte operatorReg, byte velocity);
|
||||
void noteOff(byte FMvoiceChannel, byte note, bool dontCheckNote);
|
||||
// Instrument definition for each SFX source
|
||||
OplInstrumentDefinition _sfxInstruments[4];
|
||||
// Current MIDI note fraction (1/256th notes) for each SFX source
|
||||
byte _sfxNoteFractions[4];
|
||||
};
|
||||
|
||||
} // End of namespace AGOS
|
||||
|
||||
#endif
|
||||
|
@ -23,7 +23,11 @@
|
||||
#define AGOS_DRIVERS_ACCOLADE_MIDIDRIVER_H
|
||||
|
||||
#include "agos/agos.h"
|
||||
|
||||
#include "audio/fmopl.h"
|
||||
#include "audio/mididrv.h"
|
||||
#include "audio/mididrv_ms.h"
|
||||
|
||||
#include "common/error.h"
|
||||
|
||||
namespace AGOS {
|
||||
@ -35,8 +39,8 @@ namespace AGOS {
|
||||
|
||||
extern void MidiDriver_Accolade_readDriver(Common::String filename, MusicType requestedDriverType, byte *&driverData, uint16 &driverDataSize, bool &isMusicDrvFile);
|
||||
|
||||
extern MidiDriver *MidiDriver_Accolade_AdLib_create(Common::String driverFilename);
|
||||
extern MidiDriver *MidiDriver_Accolade_MT32_create(Common::String driverFilename);
|
||||
extern MidiDriver_Multisource *MidiDriver_Accolade_AdLib_create(Common::String driverFilename, OPL::Config::OplType oplType);
|
||||
extern MidiDriver_Multisource *MidiDriver_Accolade_MT32_create(Common::String driverFilename);
|
||||
extern MidiDriver *MidiDriverPC98_create(MidiDriver::DeviceHandle dev);
|
||||
|
||||
} // End of namespace AGOS
|
||||
|
@ -19,199 +19,154 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "agos/drivers/accolade/mididriver.h"
|
||||
#include "agos/drivers/accolade/mt32.h"
|
||||
|
||||
#include "audio/mididrv.h"
|
||||
|
||||
#include "common/config-manager.h"
|
||||
#include "agos/drivers/accolade/mididriver.h"
|
||||
#include "agos/sfxparser_accolade.h"
|
||||
|
||||
namespace AGOS {
|
||||
|
||||
MidiDriver_Accolade_MT32::MidiDriver_Accolade_MT32() {
|
||||
_driver = nullptr;
|
||||
_isOpen = false;
|
||||
_nativeMT32 = false;
|
||||
_baseFreq = 250;
|
||||
const uint8 MidiDriver_Accolade_MT32::SYSEX_INSTRUMENT_ASSIGNMENT[7] = { 0x02, 0x00, 0x18, 0x32, 0x01, 0x00, 0x01 };
|
||||
|
||||
memset(_channelMapping, 0, sizeof(_channelMapping));
|
||||
memset(_instrumentMapping, 0, sizeof(_instrumentMapping));
|
||||
MidiDriver_Accolade_MT32::MidiDriver_Accolade_MT32() : MidiDriver_MT32GM(MT_MT32) {
|
||||
Common::fill(_channelRemapping, _channelRemapping + ARRAYSIZE(_channelRemapping), 0);
|
||||
Common::fill(_instrumentRemapping, _instrumentRemapping + ARRAYSIZE(_instrumentRemapping), 0);
|
||||
Common::fill(_channelLocks, _channelLocks + ARRAYSIZE(_channelLocks), false);
|
||||
}
|
||||
|
||||
MidiDriver_Accolade_MT32::~MidiDriver_Accolade_MT32() {
|
||||
Common::StackLock lock(_mutex);
|
||||
if (_driver) {
|
||||
_driver->setTimerCallback(nullptr, nullptr);
|
||||
_driver->close();
|
||||
delete _driver;
|
||||
}
|
||||
_driver = nullptr;
|
||||
int MidiDriver_Accolade_MT32::open(MidiDriver *driver, bool nativeMT32) {
|
||||
int result = MidiDriver_MT32GM::open(driver, nativeMT32);
|
||||
|
||||
setInstrumentRemapping(_instrumentRemapping);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int MidiDriver_Accolade_MT32::open() {
|
||||
assert(!_driver);
|
||||
void MidiDriver_Accolade_MT32::send(int8 source, uint32 b) {
|
||||
byte dataChannel = b & 0xf;
|
||||
int8 outputChannel = mapSourceChannel(source, dataChannel);
|
||||
|
||||
// debugC(kDebugLevelMT32Driver, "MT32: starting driver");
|
||||
MidiChannelControlData &controlData = *_controlData[outputChannel];
|
||||
|
||||
// Setup midi driver
|
||||
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32);
|
||||
MusicType musicType = MidiDriver::getMusicType(dev);
|
||||
// Check if this event is sent by a music source and the channel is locked
|
||||
// by an SFX source.
|
||||
bool channelLockedByOtherSource = _sources[source].type != SOURCE_TYPE_SFX && _channelLocks[outputChannel];
|
||||
|
||||
// check, if we got a real MT32 (or MUNT, or MUNT over MIDI)
|
||||
switch (musicType) {
|
||||
case MT_MT32:
|
||||
_nativeMT32 = true;
|
||||
break;
|
||||
case MT_GM:
|
||||
if (ConfMan.getBool("native_mt32")) {
|
||||
_nativeMT32 = true;
|
||||
processEvent(source, b, outputChannel, controlData, channelLockedByOtherSource);
|
||||
}
|
||||
|
||||
int8 MidiDriver_Accolade_MT32::mapSourceChannel(uint8 source, uint8 dataChannel) {
|
||||
if (!_isOpen)
|
||||
// Use 1 on 1 mapping during device initialization.
|
||||
return dataChannel;
|
||||
|
||||
if (_sources[source].type == SOURCE_TYPE_SFX) {
|
||||
// Use channels 7 and 8 for SFX (sources 1 and 2).
|
||||
uint8 sfxChannel = 9 - source;
|
||||
|
||||
_allocationMutex.lock();
|
||||
|
||||
if (!_channelLocks[sfxChannel]) {
|
||||
// Lock channel
|
||||
stopAllNotes(0xFF, sfxChannel);
|
||||
_channelLocks[sfxChannel] = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
_driver = MidiDriver::createMidi(dev);
|
||||
if (!_driver)
|
||||
return 255;
|
||||
_allocationMutex.unlock();
|
||||
|
||||
int ret = _driver->open();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (_nativeMT32)
|
||||
_driver->sendMT32Reset();
|
||||
else
|
||||
_driver->sendGMReset();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MidiDriver_Accolade_MT32::close() {
|
||||
if (_driver) {
|
||||
_driver->close();
|
||||
return sfxChannel;
|
||||
} else {
|
||||
return _channelRemapping[dataChannel];
|
||||
}
|
||||
}
|
||||
|
||||
// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
|
||||
void MidiDriver_Accolade_MT32::send(uint32 b) {
|
||||
byte command = b & 0xf0;
|
||||
byte channel = b & 0xf;
|
||||
void MidiDriver_Accolade_MT32::deinitSource(uint8 source) {
|
||||
_allocationMutex.lock();
|
||||
|
||||
if (command == 0xF0) {
|
||||
if (_driver) {
|
||||
_driver->send(b);
|
||||
if (_sources[source].type == SOURCE_TYPE_SFX) {
|
||||
for (int i = 0; i < MIDI_CHANNEL_COUNT; i++) {
|
||||
if (_controlData[i]->source == source) {
|
||||
// Restore the music instrument.
|
||||
programChange(i, _controlData[i]->program, 0, *_controlData[i], false);
|
||||
// Unlock the channel.
|
||||
_channelLocks[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_allocationMutex.unlock();
|
||||
|
||||
MidiDriver_MT32GM::deinitSource(source);
|
||||
}
|
||||
|
||||
void MidiDriver_Accolade_MT32::loadSfxInstrument(uint8 source, byte *instrumentData) {
|
||||
if (!(source == 1 || source == 2)) {
|
||||
warning("MidiDriver_Accolade_MT32::loadSfxInstrument - unexpected source %d", source);
|
||||
return;
|
||||
}
|
||||
|
||||
byte mappedChannel = _channelMapping[channel];
|
||||
// Send the instrument data to the timbre memory (patch 1 or 2).
|
||||
uint32 address = (0x08 << 14) | (((source - 1) * 2) << 7);
|
||||
sysExMT32(instrumentData + 3, SfxParser_Accolade::INSTRUMENT_SIZE_MT32 - 3, address, true, true, source);
|
||||
|
||||
if (mappedChannel < AGOS_MIDI_CHANNEL_COUNT) {
|
||||
// channel mapped to an actual MIDI channel, so use that one
|
||||
b = (b & 0xFFFFFFF0) | mappedChannel;
|
||||
if (command == 0xC0) {
|
||||
// Program change
|
||||
// Figure out the requested instrument
|
||||
byte midiInstrument = (b >> 8) & 0xFF;
|
||||
byte mappedInstrument = _instrumentMapping[midiInstrument];
|
||||
|
||||
// If there is no actual MT32 (or MUNT), we make a second mapping to General MIDI instruments
|
||||
if (!_nativeMT32) {
|
||||
mappedInstrument = (MidiDriver::_mt32ToGm[mappedInstrument]);
|
||||
}
|
||||
// And replace it
|
||||
b = (b & 0xFFFF00FF) | (mappedInstrument << 8);
|
||||
}
|
||||
|
||||
if (_driver) {
|
||||
_driver->send(b);
|
||||
}
|
||||
}
|
||||
// Allocate the new patch to instrument number 0x75 or 0x76.
|
||||
byte instrNum = SFX_PROGRAM_BASE + source - 1;
|
||||
address = (0x05 << 14) | instrNum << 3;
|
||||
byte instrAssignData[7];
|
||||
Common::copy(SYSEX_INSTRUMENT_ASSIGNMENT, SYSEX_INSTRUMENT_ASSIGNMENT + ARRAYSIZE(instrAssignData), instrAssignData);
|
||||
instrAssignData[1] = source - 1;
|
||||
sysExMT32(instrAssignData, 7, address, true, true, source);
|
||||
}
|
||||
|
||||
// Called right at the start, we get an INSTR.DAT entry
|
||||
bool MidiDriver_Accolade_MT32::setupInstruments(byte *driverData, uint16 driverDataSize, bool useMusicDrvFile) {
|
||||
uint16 channelMappingOffset = 0;
|
||||
uint16 channelMappingSize = 0;
|
||||
uint16 instrumentMappingOffset = 0;
|
||||
uint16 instrumentMappingSize = 0;
|
||||
|
||||
if (!useMusicDrvFile) {
|
||||
// INSTR.DAT: we expect at least 354 bytes
|
||||
if (driverDataSize < 354)
|
||||
return false;
|
||||
|
||||
// Data is like this:
|
||||
// 128 bytes instrument mapping
|
||||
// 128 bytes instrument volume adjust (signed!) (not used for MT32)
|
||||
// 16 bytes unknown
|
||||
// 16 bytes channel mapping
|
||||
// 64 bytes key note mapping (not really used for MT32)
|
||||
// 1 byte instrument count
|
||||
// 1 byte bytes per instrument
|
||||
// x bytes no instruments used for MT32
|
||||
|
||||
channelMappingOffset = 256 + 16;
|
||||
channelMappingSize = 16;
|
||||
instrumentMappingOffset = 0;
|
||||
instrumentMappingSize = 128;
|
||||
|
||||
} else {
|
||||
// MUSIC.DRV: we expect at least 468 bytes
|
||||
if (driverDataSize < 468)
|
||||
return false;
|
||||
|
||||
channelMappingOffset = 396;
|
||||
channelMappingSize = 16;
|
||||
instrumentMappingOffset = 140;
|
||||
instrumentMappingSize = 128;
|
||||
}
|
||||
|
||||
// Channel mapping
|
||||
if (channelMappingSize) {
|
||||
// Get these 16 bytes for MIDI channel mapping
|
||||
if (channelMappingSize != sizeof(_channelMapping))
|
||||
return false;
|
||||
|
||||
memcpy(_channelMapping, driverData + channelMappingOffset, sizeof(_channelMapping));
|
||||
} else {
|
||||
// Set up straight mapping
|
||||
for (uint16 channelNr = 0; channelNr < sizeof(_channelMapping); channelNr++) {
|
||||
_channelMapping[channelNr] = channelNr;
|
||||
}
|
||||
}
|
||||
|
||||
if (instrumentMappingSize) {
|
||||
// And these for instrument mapping
|
||||
if (instrumentMappingSize > sizeof(_instrumentMapping))
|
||||
return false;
|
||||
|
||||
memcpy(_instrumentMapping, driverData + instrumentMappingOffset, instrumentMappingSize);
|
||||
}
|
||||
// Set up straight mapping for the remaining data
|
||||
for (uint16 instrumentNr = instrumentMappingSize; instrumentNr < sizeof(_instrumentMapping); instrumentNr++) {
|
||||
_instrumentMapping[instrumentNr] = instrumentNr;
|
||||
}
|
||||
return true;
|
||||
void MidiDriver_Accolade_MT32::changeSfxInstrument(uint8 source) {
|
||||
// Change to the newly loaded instrument.
|
||||
byte channel = mapSourceChannel(source, 0);
|
||||
MidiChannelControlData &controlData = *_controlData[channel];
|
||||
byte originalInstrument = controlData.program;
|
||||
programChange(channel, SFX_PROGRAM_BASE + source - 1, source, controlData);
|
||||
// Store the original instrument so it can be used when deinitializing
|
||||
// the source.
|
||||
controlData.program = originalInstrument;
|
||||
}
|
||||
|
||||
MidiDriver *MidiDriver_Accolade_MT32_create(Common::String driverFilename) {
|
||||
byte *driverData = nullptr;
|
||||
void MidiDriver_Accolade_MT32::readDriverData(byte *driverData, uint16 driverDataSize, bool newVersion) {
|
||||
uint16 minDataSize = newVersion ? 468 : 354;
|
||||
if (driverDataSize < minDataSize)
|
||||
error("ACCOLADE-ADLIB: Expected minimum driver data size of %d - got %d", minDataSize, driverDataSize);
|
||||
|
||||
// INSTR.DAT Data is like this:
|
||||
// 128 bytes instrument mapping
|
||||
// 128 bytes instrument volume adjust (signed!) (not used for MT32)
|
||||
// 16 bytes unknown
|
||||
// 16 bytes channel mapping
|
||||
// 64 bytes key note mapping (not really used for MT32)
|
||||
// 1 byte instrument count
|
||||
// 1 byte bytes per instrument
|
||||
// x bytes no instruments used for MT32
|
||||
|
||||
// music.drv is basically a driver, but with a few fixed locations for certain data
|
||||
|
||||
uint16 channelMappingOffset = newVersion ? 396 : 256 + 16;
|
||||
Common::copy(driverData + channelMappingOffset, driverData + channelMappingOffset + ARRAYSIZE(_channelRemapping), _channelRemapping);
|
||||
|
||||
uint16 instrumentMappingOffset = newVersion ? 140 : 0;
|
||||
Common::copy(driverData + instrumentMappingOffset, driverData + instrumentMappingOffset + ARRAYSIZE(_instrumentRemapping), _instrumentRemapping);
|
||||
}
|
||||
|
||||
MidiDriver_Multisource *MidiDriver_Accolade_MT32_create(Common::String driverFilename) {
|
||||
byte *driverData = nullptr;
|
||||
uint16 driverDataSize = 0;
|
||||
bool isMusicDrvFile = false;
|
||||
bool newVersion = false;
|
||||
|
||||
MidiDriver_Accolade_readDriver(driverFilename, MT_MT32, driverData, driverDataSize, isMusicDrvFile);
|
||||
MidiDriver_Accolade_readDriver(driverFilename, MT_MT32, driverData, driverDataSize, newVersion);
|
||||
if (!driverData)
|
||||
error("ACCOLADE-ADLIB: error during readDriver()");
|
||||
error("ACCOLADE-MT32: error during readDriver()");
|
||||
|
||||
MidiDriver_Accolade_MT32 *driver = new MidiDriver_Accolade_MT32();
|
||||
if (driver) {
|
||||
if (!driver->setupInstruments(driverData, driverDataSize, isMusicDrvFile)) {
|
||||
delete driver;
|
||||
driver = nullptr;
|
||||
}
|
||||
}
|
||||
if (!driver)
|
||||
error("ACCOLADE-MT32: could not create driver");
|
||||
|
||||
driver->readDriverData(driverData, driverDataSize, newVersion);
|
||||
|
||||
delete[] driverData;
|
||||
return driver;
|
||||
|
@ -19,65 +19,48 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "agos/drivers/accolade/mididriver.h"
|
||||
#ifndef AGOS_DRIVERS_ACCOLADE_MT32_H
|
||||
#define AGOS_DRIVERS_ACCOLADE_MT32_H
|
||||
|
||||
#include "audio/mididrv.h"
|
||||
|
||||
#include "common/mutex.h"
|
||||
#include "audio/mt32gm.h"
|
||||
|
||||
namespace AGOS {
|
||||
|
||||
class MidiDriver_Accolade_MT32 : public MidiDriver {
|
||||
class MidiDriver_Accolade_MT32 : public MidiDriver_MT32GM {
|
||||
protected:
|
||||
static const uint8 SFX_PROGRAM_BASE = 0x75;
|
||||
static const uint8 SYSEX_INSTRUMENT_ASSIGNMENT[7];
|
||||
|
||||
public:
|
||||
MidiDriver_Accolade_MT32();
|
||||
~MidiDriver_Accolade_MT32() override;
|
||||
|
||||
// MidiDriver
|
||||
int open() override;
|
||||
void close() override;
|
||||
bool isOpen() const override { return _isOpen; }
|
||||
int open(MidiDriver *driver, bool nativeMT32) override;
|
||||
using MidiDriver_MT32GM::send;
|
||||
void send(int8 source, uint32 b) override;
|
||||
|
||||
void send(uint32 b) override;
|
||||
int8 mapSourceChannel(uint8 source, uint8 dataChannel) override;
|
||||
void deinitSource(uint8 source) override;
|
||||
|
||||
MidiChannel *allocateChannel() override {
|
||||
if (_driver)
|
||||
return _driver->allocateChannel();
|
||||
return NULL;
|
||||
}
|
||||
MidiChannel *getPercussionChannel() override {
|
||||
if (_driver)
|
||||
return _driver->getPercussionChannel();
|
||||
return NULL;
|
||||
}
|
||||
// Read the specified data from INSTR.DAT or MUSIC.DRV.
|
||||
void readDriverData(byte *driverData, uint16 driverDataSize, bool isMusicDrv);
|
||||
|
||||
void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override {
|
||||
if (_driver)
|
||||
_driver->setTimerCallback(timer_param, timer_proc);
|
||||
}
|
||||
|
||||
uint32 getBaseTempo() override {
|
||||
if (_driver) {
|
||||
return _driver->getBaseTempo();
|
||||
}
|
||||
return 1000000 / _baseFreq;
|
||||
}
|
||||
// Loads the specified instrument for the specified instrument source.
|
||||
void loadSfxInstrument(uint8 source, byte *instrumentData);
|
||||
// Changes the channel assigned to the specified SFX source to the SFX
|
||||
// program number.
|
||||
void changeSfxInstrument(uint8 source);
|
||||
|
||||
protected:
|
||||
Common::Mutex _mutex;
|
||||
MidiDriver *_driver;
|
||||
bool _nativeMT32; // native MT32, may also be our MUNT, or MUNT over MIDI
|
||||
|
||||
bool _isOpen;
|
||||
int _baseFreq;
|
||||
|
||||
private:
|
||||
// simple mapping between MIDI channel and MT32 channel
|
||||
byte _channelMapping[AGOS_MIDI_CHANNEL_COUNT];
|
||||
byte _channelRemapping[16];
|
||||
// simple mapping between MIDI instruments and MT32 instruments
|
||||
byte _instrumentMapping[AGOS_MIDI_INSTRUMENT_COUNT];
|
||||
byte _instrumentRemapping[128];
|
||||
|
||||
public:
|
||||
bool setupInstruments(byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile);
|
||||
// Indicates if a MIDI channel is locked by an SFX source and unavailable
|
||||
// for music.
|
||||
bool _channelLocks[MIDI_CHANNEL_COUNT];
|
||||
};
|
||||
|
||||
} // End of namespace AGOS
|
||||
|
||||
#endif
|
||||
|
@ -299,7 +299,7 @@ uint8 MidiDriver_Simon1_AdLib::calculateUnscaledVolume(uint8 channel, uint8 sour
|
||||
return 0x3F - calculatedVolume;
|
||||
}
|
||||
|
||||
MidiDriver_Multisource *createMidiDriverSimon1AdLib(const char *instrumentFilename) {
|
||||
MidiDriver_Multisource *createMidiDriverSimon1AdLib(const char *instrumentFilename, OPL::Config::OplType oplType) {
|
||||
// Load instrument data.
|
||||
Common::File ibk;
|
||||
|
||||
@ -319,7 +319,7 @@ MidiDriver_Multisource *createMidiDriverSimon1AdLib(const char *instrumentFilena
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MidiDriver_Simon1_AdLib *driver = new MidiDriver_Simon1_AdLib(OPL::Config::kOpl3, instrumentData);
|
||||
MidiDriver_Simon1_AdLib *driver = new MidiDriver_Simon1_AdLib(oplType, instrumentData);
|
||||
delete[] instrumentData;
|
||||
|
||||
return driver;
|
||||
|
@ -71,7 +71,7 @@ private:
|
||||
bool _musicRhythmNotesDisabled;
|
||||
};
|
||||
|
||||
MidiDriver_Multisource *createMidiDriverSimon1AdLib(const char *instrumentFilename);
|
||||
MidiDriver_Multisource *createMidiDriverSimon1AdLib(const char *instrumentFilename, OPL::Config::OplType);
|
||||
|
||||
} // End of namespace AGOS
|
||||
|
||||
|
@ -1,38 +0,0 @@
|
||||
/* 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 "agos/drivers/simon1/adlib_win.h"
|
||||
|
||||
namespace AGOS {
|
||||
|
||||
MidiDriver_Simon1_AdLib_Windows::MidiDriver_Simon1_AdLib_Windows() : MidiDriver_ADLIB_Multisource(OPL::Config::kOpl3) { }
|
||||
|
||||
void MidiDriver_Simon1_AdLib_Windows::programChange(uint8 channel, uint8 program, uint8 source) {
|
||||
// WORKAROUND The Windows version of Simon The Sorcerer uses the MT-32 MIDI
|
||||
// data of the DOS versions, but plays this using Windows' General MIDI
|
||||
// system. As a result, the music is played using different instruments
|
||||
// than intended. This is fixed here by mapping the MT-32 instruments to
|
||||
// GM instruments using MidiDriver's standard mapping.
|
||||
uint8 gmInstrument = _mt32ToGm[program];
|
||||
MidiDriver_ADLIB_Multisource::programChange(channel, gmInstrument, source);
|
||||
}
|
||||
|
||||
} // End of namespace AGOS
|
@ -1,44 +0,0 @@
|
||||
/* 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 AGOS_SIMON1_ADLIB_WIN_H
|
||||
#define AGOS_SIMON1_ADLIB_WIN_H
|
||||
|
||||
#include "audio/adlib_ms.h"
|
||||
|
||||
namespace AGOS {
|
||||
|
||||
/**
|
||||
* AdLib MIDI driver for the Windows version of Simon The Sorcerer.
|
||||
* This driver contains a workaround for converting the MT-32 instruments to
|
||||
* the General MIDI instruments used by this driver.
|
||||
*/
|
||||
class MidiDriver_Simon1_AdLib_Windows : public MidiDriver_ADLIB_Multisource {
|
||||
public:
|
||||
MidiDriver_Simon1_AdLib_Windows();
|
||||
|
||||
protected:
|
||||
void programChange(uint8 channel, uint8 program, uint8 source) override;
|
||||
};
|
||||
|
||||
} // End of namespace AGOS
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
@ -22,6 +22,8 @@
|
||||
#ifndef AGOS_MIDI_H
|
||||
#define AGOS_MIDI_H
|
||||
|
||||
#include "agos/sfxparser_accolade.h"
|
||||
|
||||
#include "audio/mididrv.h"
|
||||
#include "audio/mididrv_ms.h"
|
||||
#include "audio/midiparser.h"
|
||||
@ -33,139 +35,95 @@ class File;
|
||||
|
||||
namespace AGOS {
|
||||
|
||||
enum kMusicMode {
|
||||
kMusicModeDisabled = 0,
|
||||
kMusicModeAccolade = 1,
|
||||
kMusicModeMilesAudio = 2,
|
||||
kMusicModeSimon1 = 3,
|
||||
kMusicModePC98 = 4
|
||||
};
|
||||
|
||||
struct MusicInfo {
|
||||
MidiParser *parser;
|
||||
byte *data;
|
||||
byte num_songs; // For Type 1 SMF resources
|
||||
byte *songs[16]; // For Type 1 SMF resources
|
||||
uint32 song_sizes[16]; // For Type 1 SMF resources
|
||||
|
||||
MidiChannel *channel[16]; // Dynamic remapping of channels to resolve conflicts
|
||||
byte volume[16]; // Current channel volume
|
||||
|
||||
MusicInfo() { clear(); }
|
||||
void clear() {
|
||||
parser = 0; data = 0; num_songs = 0;
|
||||
memset(songs, 0, sizeof(songs));
|
||||
memset(song_sizes, 0, sizeof(song_sizes));
|
||||
memset(channel, 0, sizeof(channel));
|
||||
}
|
||||
};
|
||||
|
||||
class MidiPlayer : public MidiDriver_BASE {
|
||||
class MidiPlayer {
|
||||
protected:
|
||||
// Instrument map specifically for remapping the instruments of the GM
|
||||
// version of Simon 2 track 10 subtracks 2 and 3 to MT-32.
|
||||
static byte SIMON2_TRACK10_GM_MT32_INSTRUMENT_REMAPPING[];
|
||||
static const byte SIMON2_TRACK10_GM_MT32_INSTRUMENT_REMAPPING[];
|
||||
|
||||
AGOSEngine *_vm;
|
||||
|
||||
Common::Mutex _mutex;
|
||||
// Driver used for music. This points to the same object as _driverMsMusic,
|
||||
// except if a PC-98 driver is used (these do not implement the Multisource
|
||||
// interface).
|
||||
MidiDriver *_driver;
|
||||
// Multisource driver used for music. Provides access to multisource
|
||||
// methods without casting. If this is not nullptr, it points to the same
|
||||
// object as _driver.
|
||||
MidiDriver_Multisource *_driverMsMusic;
|
||||
// Multisource driver used for sound effects. Only used for Simon The
|
||||
// Sorcerer DOS floppy AdLib sound effects.
|
||||
// Multisource driver used for sound effects. Only used for Elvira 2,
|
||||
// Waxworks and Simon The Sorcerer DOS floppy AdLib sound effects.
|
||||
// If AdLib is also used for music, this points to the same object as
|
||||
// _driverMsMusic and _driver.
|
||||
MidiDriver_Multisource *_driverMsSfx;
|
||||
bool _map_mt32_to_gm;
|
||||
bool _nativeMT32;
|
||||
|
||||
MusicInfo _music;
|
||||
MusicInfo _sfx;
|
||||
MusicInfo *_current; // Allows us to establish current context for operations.
|
||||
|
||||
// MIDI parser and data used for music.
|
||||
MidiParser *_parserMusic;
|
||||
byte *_musicData;
|
||||
// MIDI parser and data used for SFX (Simon 1 DOS floppy).
|
||||
MidiParser *_parserSfx;
|
||||
byte *_sfxData;
|
||||
// Parser used for SFX (Elvira 2 and Waxworks DOS).
|
||||
SfxParser_Accolade *_parserSfxAccolade;
|
||||
|
||||
// These are maintained for both music and SFX
|
||||
byte _masterVolume; // 0-255
|
||||
byte _musicVolume;
|
||||
byte _sfxVolume;
|
||||
bool _paused;
|
||||
|
||||
// These are only used for music.
|
||||
byte _currentTrack;
|
||||
bool _loopTrack;
|
||||
// Queued music track data (Simon 2).
|
||||
byte _queuedTrack;
|
||||
bool _loopQueuedTrack;
|
||||
|
||||
protected:
|
||||
static void onTimer(void *data);
|
||||
void clearConstructs();
|
||||
void clearConstructs(MusicInfo &info);
|
||||
void resetVolumeTable();
|
||||
|
||||
public:
|
||||
MidiPlayer(AGOSEngine *vm);
|
||||
~MidiPlayer() override;
|
||||
~MidiPlayer();
|
||||
|
||||
// Loads music data supported by the MidiParser used for the detected
|
||||
// version of the game. Specify sfx to indicate that this is a MIDI sound
|
||||
// effect.
|
||||
void loadMusic(Common::SeekableReadStream *in, int32 size = -1, bool sfx = false);
|
||||
// Creates and opens the relevant parsers and drivers for the game version
|
||||
// and selected sound device.
|
||||
int open();
|
||||
|
||||
// Loads music or SFX data supported by the MidiParser or SfxParser used
|
||||
// for the detected version of the game. Specify sfx to indicate that this
|
||||
// is a synthesized sound effect.
|
||||
void load(Common::SeekableReadStream *in, int32 size = -1, bool sfx = false);
|
||||
|
||||
/**
|
||||
* Plays the currently loaded music data. If the loaded MIDI data has
|
||||
* Plays the currently loaded music or SFX data. If the loaded data has
|
||||
* multiple tracks, specify track to select the track to play.
|
||||
*
|
||||
* @param track The track to play. Default 0.
|
||||
* @param sfx True if the SFX MIDI data should be played, otherwise the
|
||||
* loaded music data will be played. Default false.
|
||||
* @param sfx True if the SFX data should be played, otherwise the loaded
|
||||
* music data will be played. Default false.
|
||||
* @param sfxUsesRhythm True if the sound effect uses OPL rhythm
|
||||
* instruments. Default false.
|
||||
* @param queued True if this track was queued; false if it was played
|
||||
* directly. Default false.
|
||||
*/
|
||||
void play(int track = 0, bool sfx = false, bool sfxUsesRhythm = false, bool queued = false);
|
||||
|
||||
void loadSMF(Common::SeekableReadStream *in, int song, bool sfx = false);
|
||||
void loadMultipleSMF(Common::SeekableReadStream *in, bool sfx = false);
|
||||
void loadXMIDI(Common::SeekableReadStream *in, bool sfx = false);
|
||||
void loadS1D(Common::SeekableReadStream *in, bool sfx = false);
|
||||
|
||||
|
||||
// Returns true if the playback device uses MT-32 MIDI data; false it it
|
||||
// uses a different data type.
|
||||
bool usesMT32Data() const;
|
||||
bool hasAdLibSfx() const;
|
||||
// Returns true if the game version and selected sound device can use MIDI
|
||||
// (or synthesized) sound effects.
|
||||
bool hasMidiSfx() const;
|
||||
void setLoop(bool loop);
|
||||
// Activates or deactivates remapping GM to MT-32 instruments for
|
||||
// Simon 2 track 10.
|
||||
void setSimon2Remapping(bool remap);
|
||||
void startTrack(int track);
|
||||
void queueTrack(int track, bool loop);
|
||||
bool isPlaying(bool checkQueued = false);
|
||||
|
||||
void stop();
|
||||
void stopSfx();
|
||||
void stop(bool sfx = false);
|
||||
void pause(bool b);
|
||||
void fadeOut();
|
||||
|
||||
void setVolume(int musicVol, int sfxVol);
|
||||
void syncSoundSettings();
|
||||
|
||||
public:
|
||||
int open(int gameType, Common::Platform platform, bool isDemo);
|
||||
|
||||
// MidiDriver_BASE interface implementation
|
||||
void send(uint32 b) override;
|
||||
void metaEvent(byte type, byte *data, uint16 length) override;
|
||||
|
||||
private:
|
||||
kMusicMode _musicMode;
|
||||
bool _pc98;
|
||||
// The type of the music device selected for playback.
|
||||
MusicType _deviceType;
|
||||
// The type of the MIDI data of the game (MT-32 or GM).
|
||||
|
@ -41,16 +41,37 @@ private:
|
||||
byte *start, *end;
|
||||
} _loops[16];
|
||||
|
||||
// Data for monophonic chords mode.
|
||||
// If this is activated, when multiple notes are played at the same time on
|
||||
// a melodic channel (0-5), only the highest note will be played.
|
||||
// This functionality is used by Elvira 2 (although there are no chords in
|
||||
// the MIDI data), Waxworks and the Simon 1 floppy demo.
|
||||
|
||||
// The highest note played at _lastPlayedNoteTime for each channel.
|
||||
byte _highestNote[6];
|
||||
// The timestamp at which the last note was played at each channel.
|
||||
uint32 _lastPlayedNoteTime[6];
|
||||
|
||||
// True if, for notes played at the same time, only the highest note should
|
||||
// be played. If false, all notes of a chord will be sent to the driver.
|
||||
bool _monophonicChords;
|
||||
|
||||
uint32 readVLQ2(byte *&data);
|
||||
void chainEvent(EventInfo &info);
|
||||
protected:
|
||||
void parseNextEvent(EventInfo &info) override;
|
||||
bool processEvent(const EventInfo &info, bool fireEvents = true) override;
|
||||
void resetTracking() override;
|
||||
|
||||
public:
|
||||
MidiParser_S1D() : _data(nullptr), _noDelta(false) {}
|
||||
public:
|
||||
MidiParser_S1D(uint8 source = 0, bool monophonicChords = false) : MidiParser(source),
|
||||
_monophonicChords(monophonicChords), _data(nullptr), _noDelta(false) {
|
||||
Common::fill(_loops, _loops + ARRAYSIZE(_loops), Loop { 0, 0, 0 });
|
||||
Common::fill(_highestNote, _highestNote + ARRAYSIZE(_highestNote), 0);
|
||||
Common::fill(_lastPlayedNoteTime, _lastPlayedNoteTime + ARRAYSIZE(_lastPlayedNoteTime), 0);
|
||||
}
|
||||
|
||||
bool loadMusic(byte *data, uint32 size) override;
|
||||
int32 determineDataSize(Common::SeekableReadStream *stream) override;
|
||||
};
|
||||
|
||||
uint32 MidiParser_S1D::readVLQ2(byte *&data) {
|
||||
@ -66,17 +87,11 @@ uint32 MidiParser_S1D::readVLQ2(byte *&data) {
|
||||
return delta;
|
||||
}
|
||||
|
||||
void MidiParser_S1D::chainEvent(EventInfo &info) {
|
||||
// When we chain an event, we add up the old delta.
|
||||
uint32 delta = info.delta;
|
||||
parseNextEvent(info);
|
||||
info.delta += delta;
|
||||
}
|
||||
|
||||
void MidiParser_S1D::parseNextEvent(EventInfo &info) {
|
||||
info.start = _position._playPos;
|
||||
info.length = 0;
|
||||
info.delta = _noDelta ? 0 : readVLQ2(_position._playPos);
|
||||
info.noop = false;
|
||||
_noDelta = false;
|
||||
|
||||
info.event = *_position._playPos++;
|
||||
@ -126,26 +141,26 @@ void MidiParser_S1D::parseNextEvent(EventInfo &info) {
|
||||
|
||||
// Go to the start of the loop
|
||||
_position._playPos = _loops[info.channel()].start;
|
||||
info.loop = true;
|
||||
}
|
||||
} else {
|
||||
if (_loops[info.channel()].timer)
|
||||
if (_loops[info.channel()].timer) {
|
||||
_position._playPos = _loops[info.channel()].start;
|
||||
info.loop = true;
|
||||
}
|
||||
--_loops[info.channel()].timer;
|
||||
}
|
||||
}
|
||||
|
||||
// We need to read the next midi event here. Since we can not
|
||||
// safely pass this event to the MIDI event processing.
|
||||
chainEvent(info);
|
||||
// Event has been fully processed here.
|
||||
info.noop = true;
|
||||
} break;
|
||||
|
||||
case 0xB: // auto stop marker(?)
|
||||
// In case the stop mode(?) is set to 0x80 this will stop the
|
||||
// track.
|
||||
|
||||
// We need to read the next midi event here. Since we can not
|
||||
// safely pass this event to the MIDI event processing.
|
||||
chainEvent(info);
|
||||
// Event has been fully processed here.
|
||||
info.noop = true;
|
||||
break;
|
||||
|
||||
case 0xC: // program change
|
||||
@ -157,9 +172,8 @@ void MidiParser_S1D::parseNextEvent(EventInfo &info) {
|
||||
if (_loops[info.channel()].end)
|
||||
_position._playPos = _loops[info.channel()].end;
|
||||
|
||||
// We need to read the next midi event here. Since we can not
|
||||
// safely pass this event to the MIDI event processing.
|
||||
chainEvent(info);
|
||||
// Event has been fully processed here.
|
||||
info.noop = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -167,14 +181,38 @@ void MidiParser_S1D::parseNextEvent(EventInfo &info) {
|
||||
// not to be MIDI related.
|
||||
warning("MidiParser_S1D: default case %d", info.channel());
|
||||
|
||||
// We need to read the next midi event here. Since we can not
|
||||
// safely pass this event to the MIDI event processing.
|
||||
chainEvent(info);
|
||||
// Event has been fully processed here.
|
||||
info.noop = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MidiParser_S1D::processEvent(const EventInfo &info, bool fireEvents) {
|
||||
byte channel = info.channel();
|
||||
if (_monophonicChords && channel < 6 && info.command() == 0x9 && info.basic.param2 > 0) {
|
||||
// In monophonic chords mode, when multiple notes are played at the
|
||||
// same time on a melodic channel (0-5), only the highest note should
|
||||
// be played.
|
||||
if (_lastPlayedNoteTime[channel] == _position._playTick && _highestNote[channel] > info.basic.param1) {
|
||||
// This note is lower than a previously played note on the same
|
||||
// channel and with the same timestamp. Ignore it.
|
||||
return true;
|
||||
} else {
|
||||
// This note either has a different timestamp (i.e. it is not
|
||||
// played at the same time), or it is higher than the previously
|
||||
// played note.
|
||||
// Update the timestamp and note registry and play this note
|
||||
// (because the channel is monophonic, a previously played lower
|
||||
// note will be cut off).
|
||||
_lastPlayedNoteTime[channel] = _position._playTick;
|
||||
_highestNote[channel] = info.basic.param1;
|
||||
}
|
||||
}
|
||||
|
||||
return MidiParser::processEvent(info, fireEvents);
|
||||
}
|
||||
|
||||
bool MidiParser_S1D::loadMusic(byte *data, uint32 size) {
|
||||
unloadMusic();
|
||||
|
||||
@ -182,7 +220,7 @@ bool MidiParser_S1D::loadMusic(byte *data, uint32 size) {
|
||||
return false;
|
||||
|
||||
// The original actually just ignores the first two bytes.
|
||||
byte *pos = data;
|
||||
byte *pos = data + 2;
|
||||
if (*pos == 0xFC) {
|
||||
// SysEx found right at the start
|
||||
// this seems to happen since Elvira 2, we ignore it
|
||||
@ -230,6 +268,11 @@ bool MidiParser_S1D::loadMusic(byte *data, uint32 size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int32 MidiParser_S1D::determineDataSize(Common::SeekableReadStream* stream) {
|
||||
// Data size is stored in the first two bytes.
|
||||
return stream->readUint16LE() + 2;
|
||||
}
|
||||
|
||||
void MidiParser_S1D::resetTracking() {
|
||||
MidiParser::resetTracking();
|
||||
// The first event never contains any delta.
|
||||
@ -237,6 +280,6 @@ void MidiParser_S1D::resetTracking() {
|
||||
memset(_loops, 0, sizeof(_loops));
|
||||
}
|
||||
|
||||
MidiParser *MidiParser_createS1D() { return new MidiParser_S1D; }
|
||||
MidiParser *MidiParser_createS1D(uint8 source, bool monophonicChords) { return new MidiParser_S1D(source, monophonicChords); }
|
||||
|
||||
} // End of namespace AGOS
|
||||
|
@ -6,7 +6,6 @@ MODULE_OBJS := \
|
||||
drivers/accolade/pc98.o \
|
||||
drivers/accolade/mt32.o \
|
||||
drivers/simon1/adlib.o \
|
||||
drivers/simon1/adlib_win.o \
|
||||
agos.o \
|
||||
charset.o \
|
||||
charset-fontdata.o \
|
||||
@ -40,6 +39,7 @@ MODULE_OBJS := \
|
||||
script_ww.o \
|
||||
script_s1.o \
|
||||
script_s2.o \
|
||||
sfxparser_accolade.o \
|
||||
sound.o \
|
||||
string.o \
|
||||
string_pn.o \
|
||||
|
@ -137,7 +137,7 @@ void AGOSEngine::loadMusic(uint16 music, bool forceSimon2Gm) {
|
||||
uint16 indexBase = forceSimon2Gm ? MUSIC_INDEX_BASE_SIMON2_GM : _musicIndexBase;
|
||||
|
||||
_gameFile->seek(_gameOffsetsPtr[indexBase + music - 1], SEEK_SET);
|
||||
_midi->loadMusic(_gameFile);
|
||||
_midi->load(_gameFile);
|
||||
|
||||
// Activate Simon 2 GM to MT-32 remapping if we force GM, otherwise
|
||||
// deactivate it (in case it was previously activated).
|
||||
@ -279,7 +279,7 @@ void AGOSEngine_Simon1::playMusic(uint16 music, uint16 track) {
|
||||
int size = SIMON1_GMF_SIZE[music];
|
||||
|
||||
_gameFile->seek(_gameOffsetsPtr[_musicIndexBase + music], SEEK_SET);
|
||||
_midi->loadMusic(_gameFile, size);
|
||||
_midi->load(_gameFile, size);
|
||||
_midi->play();
|
||||
} else if (getPlatform() == Common::kPlatformDOS) {
|
||||
// DOS floppy version.
|
||||
@ -292,7 +292,7 @@ void AGOSEngine_Simon1::playMusic(uint16 music, uint16 track) {
|
||||
if (f.isOpen() == false)
|
||||
error("playMusic: Can't load music from '%s'", filename);
|
||||
|
||||
_midi->loadMusic(&f, f.size());
|
||||
_midi->load(&f, f.size());
|
||||
if (getFeatures() & GF_DEMO) {
|
||||
// Full version music data has a loop flag in the file header, but
|
||||
// the demo needs to have this set manually.
|
||||
@ -304,7 +304,7 @@ void AGOSEngine_Simon1::playMusic(uint16 music, uint16 track) {
|
||||
// Windows version uses SMF data in one large data file.
|
||||
_gameFile->seek(_gameOffsetsPtr[_musicIndexBase + music], SEEK_SET);
|
||||
|
||||
_midi->loadMusic(_gameFile);
|
||||
_midi->load(_gameFile);
|
||||
_midi->setLoop(true);
|
||||
|
||||
_midi->play();
|
||||
@ -315,6 +315,47 @@ void AGOSEngine_Simon1::playMusic(uint16 music, uint16 track) {
|
||||
}
|
||||
}
|
||||
|
||||
void AGOSEngine_Simon1::playMidiSfx(uint16 sound) {
|
||||
// The sound effects in floppy disk version of
|
||||
// Simon the Sorcerer 1 are only meant for AdLib
|
||||
if (!_midi->hasMidiSfx())
|
||||
return;
|
||||
|
||||
// AdLib SFX use GMF data bundled in 9 STINGSx.MUS files.
|
||||
char filename[16];
|
||||
Common::File mus_file;
|
||||
|
||||
sprintf(filename, "STINGS%i.MUS", _soundFileId);
|
||||
mus_file.open(filename);
|
||||
if (!mus_file.isOpen())
|
||||
error("playSting: Can't load sound effect from '%s'", filename);
|
||||
|
||||
// WORKAROUND Some Simon 1 DOS floppy SFX use the OPL rhythm instruments.
|
||||
// This can conflict with the music using the rhythm instruments, so the
|
||||
// original interpreter disables the music rhythm notes while a sound
|
||||
// effect is playing. However, only some sound effects use rhythm notes, so
|
||||
// in many cases this is not needed and leads to the music drums needlessly
|
||||
// being disabled.
|
||||
// To improve this, the sound effect number is checked against a list of
|
||||
// SFX using rhythm notes, and only if it is in the list the music drums
|
||||
// will be disabled while it plays.
|
||||
bool rhythmSfx = false;
|
||||
// Search for the file ID / SFX ID combination in the list of SFX that use
|
||||
// rhythm notes.
|
||||
byte sfxId = (_soundFileId << 4) | sound;
|
||||
for (int i = 0; i < ARRAYSIZE(SIMON1_RHYTHM_SFX); i++) {
|
||||
if (SIMON1_RHYTHM_SFX[i] == sfxId) {
|
||||
rhythmSfx = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_midi->stop(true);
|
||||
|
||||
_midi->load(&mus_file, mus_file.size(), true);
|
||||
_midi->play(sound, true, rhythmSfx);
|
||||
}
|
||||
|
||||
void AGOSEngine::playMusic(uint16 music, uint16 track) {
|
||||
stopMusic();
|
||||
|
||||
@ -337,9 +378,9 @@ void AGOSEngine::playMusic(uint16 music, uint16 track) {
|
||||
str = file;
|
||||
}
|
||||
|
||||
_midi->loadS1D(str);
|
||||
_midi->startTrack(0);
|
||||
_midi->startTrack(track);
|
||||
//warning("Playing track %d", music);
|
||||
_midi->load(str);
|
||||
_midi->play();
|
||||
delete str;
|
||||
}
|
||||
}
|
||||
@ -351,47 +392,6 @@ void AGOSEngine::stopMusic() {
|
||||
_mixer->stopHandle(_modHandle);
|
||||
}
|
||||
|
||||
void AGOSEngine::playSting(uint16 soundId) {
|
||||
// The sound effects in floppy disk version of
|
||||
// Simon the Sorcerer 1 are only meant for AdLib
|
||||
if (!_midi->hasAdLibSfx())
|
||||
return;
|
||||
|
||||
// AdLib SFX use GMF data bundled in 9 STINGSx.MUS files.
|
||||
char filename[16];
|
||||
Common::File mus_file;
|
||||
|
||||
sprintf(filename, "STINGS%i.MUS", _soundFileId);
|
||||
mus_file.open(filename);
|
||||
if (!mus_file.isOpen())
|
||||
error("playSting: Can't load sound effect from '%s'", filename);
|
||||
|
||||
// WORKAROUND Some Simon 1 DOS floppy SFX use the OPL rhythm instruments.
|
||||
// This can conflict with the music using the rhythm instruments, so the
|
||||
// original interpreter disables the music rhythm notes while a sound
|
||||
// effect is playing. However, only some sound effects use rhythm notes, so
|
||||
// in many cases this is not needed and leads to the music drums needlessly
|
||||
// being disabled.
|
||||
// To improve this, the sound effect number is checked against a list of
|
||||
// SFX using rhythm notes, and only if it is in the list the music drums
|
||||
// will be disabled while it plays.
|
||||
bool rhythmSfx = false;
|
||||
// Search for the file ID / SFX ID combination in the list of SFX that use
|
||||
// rhythm notes.
|
||||
byte sfxId = (_soundFileId << 4) | soundId;
|
||||
for (int i = 0; i < ARRAYSIZE(SIMON1_RHYTHM_SFX); i++) {
|
||||
if (SIMON1_RHYTHM_SFX[i] == sfxId) {
|
||||
rhythmSfx = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_midi->stopSfx();
|
||||
|
||||
_midi->loadMusic(&mus_file, mus_file.size(), true);
|
||||
_midi->play(soundId, true, rhythmSfx);
|
||||
}
|
||||
|
||||
static const byte elvira1_soundTable[100] = {
|
||||
0, 2, 0, 1, 0, 0, 0, 0, 0, 3,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
@ -571,6 +571,14 @@ void AGOSEngine::loadSound(uint16 sound, int16 pan, int16 vol, uint16 type) {
|
||||
_sound->playSfx5Data(dst, sound, pan, vol);
|
||||
}
|
||||
|
||||
void AGOSEngine::playSfx(uint16 sound, uint16 freq, uint16 flags, bool canUseMidiSfx) {
|
||||
if (_useDigitalSfx) {
|
||||
loadSound(sound, freq, flags);
|
||||
} else if (canUseMidiSfx) {
|
||||
playMidiSfx(sound);
|
||||
}
|
||||
}
|
||||
|
||||
void AGOSEngine::loadSound(uint16 sound, uint16 freq, uint16 flags) {
|
||||
byte *dst;
|
||||
uint32 offs, size = 0;
|
||||
@ -611,7 +619,6 @@ void AGOSEngine::loadSound(uint16 sound, uint16 freq, uint16 flags) {
|
||||
|
||||
if (size > _curSfxFileSize)
|
||||
error("loadSound: Reading beyond EOF (%d, %d)", size, _curSfxFileSize);
|
||||
|
||||
}
|
||||
|
||||
size = READ_BE_UINT16(dst + 2);
|
||||
@ -638,6 +645,29 @@ void AGOSEngine::loadSound(uint16 sound, uint16 freq, uint16 flags) {
|
||||
}
|
||||
}
|
||||
|
||||
void AGOSEngine::loadMidiSfx() {
|
||||
if (!_midi->hasMidiSfx())
|
||||
return;
|
||||
|
||||
Common::File fxb_file;
|
||||
|
||||
Common::String filename = getGameType() == GType_ELVIRA2 ? "MYLIB.FXB" : "WAX.FXB";
|
||||
fxb_file.open(filename);
|
||||
if (!fxb_file.isOpen())
|
||||
error("loadMidiSfx: Can't open sound effect bank '%s'", filename.c_str());
|
||||
|
||||
_midi->load(&fxb_file, fxb_file.size(), true);
|
||||
|
||||
fxb_file.close();
|
||||
}
|
||||
|
||||
void AGOSEngine::playMidiSfx(uint16 sound) {
|
||||
if (!_midi->hasMidiSfx())
|
||||
return;
|
||||
|
||||
_midi->play(sound, true);
|
||||
}
|
||||
|
||||
void AGOSEngine::loadVoice(uint speechId) {
|
||||
if (getGameType() == GType_PP && speechId == 99) {
|
||||
_sound->stopVoice();
|
||||
@ -673,4 +703,10 @@ void AGOSEngine::loadVoice(uint speechId) {
|
||||
}
|
||||
}
|
||||
|
||||
void AGOSEngine::stopAllSfx() {
|
||||
_sound->stopAllSfx();
|
||||
if (_midi->hasMidiSfx())
|
||||
_midi->stop(true);
|
||||
}
|
||||
|
||||
} // End of namespace AGOS
|
||||
|
@ -581,7 +581,7 @@ void AGOSEngine_Elvira2::oe2_ifExitLocked() {
|
||||
void AGOSEngine_Elvira2::oe2_playEffect() {
|
||||
// 174: play sound
|
||||
uint soundId = getVarOrWord();
|
||||
loadSound(soundId, 0, 0);
|
||||
playSfx(soundId, 0, 0, true);
|
||||
}
|
||||
|
||||
void AGOSEngine_Elvira2::oe2_getDollar2() {
|
||||
|
@ -378,7 +378,7 @@ void AGOSEngine_Simon1::os1_playEffect() {
|
||||
uint16 soundId = getVarOrWord();
|
||||
|
||||
if (getGameId() == GID_SIMON1DOS)
|
||||
playSting(soundId);
|
||||
playSfx(soundId, 0, 0, true);
|
||||
else
|
||||
_sound->playEffects(soundId);
|
||||
}
|
||||
|
508
engines/agos/sfxparser_accolade.cpp
Normal file
508
engines/agos/sfxparser_accolade.cpp
Normal file
@ -0,0 +1,508 @@
|
||||
/* 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 "agos/sfxparser_accolade.h"
|
||||
|
||||
#include "common/stream.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
namespace AGOS {
|
||||
|
||||
SfxParser_Accolade::SfxSlot::SfxSlot() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::SfxSlot::reset() {
|
||||
noteFractionDelta = 0;
|
||||
vibratoTime = 0;
|
||||
vibratoCounter = 0;
|
||||
vibratoDelta = 0;
|
||||
waitCounter = 0;
|
||||
loopStart = 0;
|
||||
loopCounter = 0;
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::SfxSlot::clear() {
|
||||
allocated = false;
|
||||
active = false;
|
||||
source = -1;
|
||||
scriptPos = 0;
|
||||
playTime = 0;
|
||||
lastEventTime = 0;
|
||||
lastPlayedNote = -1;
|
||||
currentNoteFraction = 0;
|
||||
programChanged = false;
|
||||
reset();
|
||||
}
|
||||
|
||||
bool SfxParser_Accolade::SfxSlot::atEndOfScript() {
|
||||
return scriptPos >= sfxData->scriptSize;
|
||||
}
|
||||
|
||||
int16 SfxParser_Accolade::SfxSlot::readScript(bool opCode) {
|
||||
if (atEndOfScript())
|
||||
error("SfxParser_Accolade::SfxData::readScript - attempt to read past the end of the script");
|
||||
|
||||
int16 data = sfxData->scriptData[scriptPos];
|
||||
scriptPos++;
|
||||
|
||||
if (opCode && (data <= 0 || data > 0xC)) {
|
||||
// Any opcode outside the range 1-B will cause the script to stop.
|
||||
data = 0xC;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
const byte SfxParser_Accolade::INSTRUMENT_SIZE_MT32;
|
||||
|
||||
SfxParser_Accolade::SfxParser_Accolade() : _driver(nullptr), _timerRate(0), _sfxData(),
|
||||
_numSfx(0), _sourceAllocations { -1, -1, -1, -1 }, _paused(false) { }
|
||||
|
||||
SfxParser_Accolade::~SfxParser_Accolade() {
|
||||
stopAll();
|
||||
|
||||
if (_sfxData) {
|
||||
delete[] _sfxData;
|
||||
_sfxData = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::load(Common::SeekableReadStream *in, int32 size) {
|
||||
// First word is the total data size.
|
||||
uint16 dataSize = in->readUint16LE();
|
||||
if (dataSize > size)
|
||||
error("SfxParser_Accolade::load - Sound effect bank lists size %d but has file size %d", dataSize, size);
|
||||
|
||||
// Next word is the number of SFX definitions.
|
||||
_numSfx = in->readUint16LE();
|
||||
_sfxData = new SfxData[_numSfx];
|
||||
|
||||
// Next is a list of start offsets for each SFX definition. Combined with
|
||||
// the total size the size of each SFX definition can be determined.
|
||||
int64 indexStartPos = in->pos();
|
||||
for (int i = 0; i < _numSfx; i++) {
|
||||
in->seek(indexStartPos + (i * 2));
|
||||
uint16 sfxDataOffset = in->readUint16LE();
|
||||
uint16 sfxDataEndOffset = i < _numSfx - 1 ? in->readUint16LE() : dataSize - 4;
|
||||
in->seek(indexStartPos + sfxDataOffset);
|
||||
uint16 sfxDataSize = sfxDataEndOffset - sfxDataOffset;
|
||||
|
||||
// Read instrument definition.
|
||||
readInstrument(&_sfxData[i], in);
|
||||
|
||||
// Instrument data size is fixed; the reset of the SFX definition is
|
||||
// the script.
|
||||
int scriptSize = sfxDataSize - INSTRUMENT_SIZE_MT32 - INSTRUMENT_SIZE_ADLIB;
|
||||
if (scriptSize < 2)
|
||||
error("SfxParser_Accolade::load - Unexpected script size %d", scriptSize);
|
||||
if (scriptSize % 2 != 0)
|
||||
warning("SfxParser_Accolade::load - Script has odd number of bytes %d", scriptSize);
|
||||
scriptSize >>= 1;
|
||||
// Script size is stored in words.
|
||||
_sfxData[i].scriptSize = scriptSize;
|
||||
|
||||
// Read each word into the script data.
|
||||
for (int j = 0; j < scriptSize; j++) {
|
||||
_sfxData[i].scriptData[j] = in->readSint16LE();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::setTimerRate(uint32 rate) {
|
||||
_timerRate = rate;
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::play(uint8 sfxNumber) {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
if (sfxNumber >= _numSfx) {
|
||||
warning("SfxParser_Accolade::play - Sound effect %d requested but bank has only %d sound effects", sfxNumber, _numSfx);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find an unallocated slot.
|
||||
SfxSlot *sfxSlot = nullptr;
|
||||
int sfxSlotNum = -1;
|
||||
for (int i = 0; i < ARRAYSIZE(_sfxSlots); i++) {
|
||||
if (!_sfxSlots[i].allocated) {
|
||||
_sfxSlots[i].allocated = true;
|
||||
sfxSlot = &_sfxSlots[i];
|
||||
sfxSlotNum = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Note that the original interpreter would only output MIDI data from 2
|
||||
// slots simultaneously, but potentially *all* SFX could be active at the
|
||||
// same time (but the same sound effect could not play more than once at
|
||||
// the same time).
|
||||
// This implementation only allows 4 SFX active at the same time, which
|
||||
// seems to be more than enough for Elvira 2 and Waxworks.
|
||||
if (!sfxSlot)
|
||||
return;
|
||||
|
||||
// Allocate a source.
|
||||
for (int i = 0; i < getNumberOfSfxSources(); i++) {
|
||||
if (_sourceAllocations[i] == -1) {
|
||||
_sourceAllocations[i] = sfxSlotNum;
|
||||
sfxSlot->source = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the SFX data and load the instrument into the allocated channel.
|
||||
sfxSlot->sfxData = &_sfxData[sfxNumber];
|
||||
sfxSlot->programChanged = loadInstrument(sfxSlot);
|
||||
|
||||
// Activate the slot to start script execution.
|
||||
sfxSlot->active = true;
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::stopAll() {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
for (int i = 0; i < ARRAYSIZE(_sfxSlots); i++) {
|
||||
if (_sfxSlots[i].active)
|
||||
stop(&_sfxSlots[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::pauseAll(bool paused) {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
if (_paused == paused)
|
||||
return;
|
||||
|
||||
_paused = paused;
|
||||
|
||||
if (_paused) {
|
||||
// Stop the current note for all active SFX.
|
||||
for (int i = 0; i < ARRAYSIZE(_sfxSlots); i++) {
|
||||
if (_sfxSlots[i].active)
|
||||
noteOff(&_sfxSlots[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::stop(SfxSlot *sfxSlot) {
|
||||
noteOff(sfxSlot);
|
||||
|
||||
// Deallocate the source.
|
||||
if (sfxSlot->source >= 0) {
|
||||
_driver->deinitSource(sfxSlot->source);
|
||||
_sourceAllocations[sfxSlot->source - 1] = -1;
|
||||
}
|
||||
|
||||
// The original interpreter would try to re-assign the source to an active
|
||||
// sound effect without a source. This is not implemented here because
|
||||
// Elvira 2 and Waxworks use SFX very sparingly, so it seems very unlikely
|
||||
// that more than 2 SFX would be active at the same time.
|
||||
|
||||
sfxSlot->clear();
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::processOpCode(SfxSlot *sfxSlot, byte opCode) {
|
||||
switch (opCode) {
|
||||
case 0x1:
|
||||
// Set note and note fraction delta.
|
||||
sfxSlot->noteFractionDelta = sfxSlot->readScript(false);
|
||||
break;
|
||||
case 0x2:
|
||||
// Clear note and note fraction delta.
|
||||
sfxSlot->noteFractionDelta = 0;
|
||||
break;
|
||||
case 0x3:
|
||||
// Set vibrato.
|
||||
int16 vibratoTime;
|
||||
vibratoTime = sfxSlot->readScript(false);
|
||||
assert(vibratoTime >= 0);
|
||||
sfxSlot->vibratoTime = vibratoTime;
|
||||
// The counter starts at half the vibrato time, which causes the note
|
||||
// frequency to move above and below the note fraction (like a sine
|
||||
// wave).
|
||||
sfxSlot->vibratoCounter = (vibratoTime >> 1) | 1;
|
||||
sfxSlot->vibratoDelta = sfxSlot->readScript(false);
|
||||
break;
|
||||
case 0x4:
|
||||
// Clear vibrato.
|
||||
sfxSlot->vibratoTime = 0;
|
||||
sfxSlot->vibratoDelta = 0;
|
||||
break;
|
||||
case 0x5:
|
||||
// Wait.
|
||||
sfxSlot->waitCounter = sfxSlot->readScript(false);
|
||||
assert(sfxSlot->waitCounter >= 0);
|
||||
break;
|
||||
case 0x6:
|
||||
// Play note.
|
||||
noteOff(sfxSlot);
|
||||
int8 note;
|
||||
note = sfxSlot->readScript(false) & 0xFF;
|
||||
assert(note >= 0 && note <= 0x7F);
|
||||
sfxSlot->currentNoteFraction = note << 8;
|
||||
noteOn(sfxSlot);
|
||||
break;
|
||||
case 0x7:
|
||||
// Loop start.
|
||||
|
||||
// Just register the loop start position.
|
||||
sfxSlot->loopStart = sfxSlot->scriptPos;
|
||||
break;
|
||||
case 0x8:
|
||||
// Loop next.
|
||||
int16 loopParam;
|
||||
loopParam = sfxSlot->readScript(false);
|
||||
assert(loopParam >= 0);
|
||||
if (sfxSlot->loopCounter == 0) {
|
||||
// Loop counter has not been set yet, so do this now.
|
||||
if (loopParam == 0)
|
||||
// Loop infinitely.
|
||||
loopParam = -1;
|
||||
sfxSlot->loopCounter = loopParam;
|
||||
// Go back to loop start.
|
||||
sfxSlot->scriptPos = sfxSlot->loopStart;
|
||||
} else {
|
||||
// Decrease loop counter, unless the loop is infinite.
|
||||
if (sfxSlot->loopCounter != -1)
|
||||
sfxSlot->loopCounter--;
|
||||
if (sfxSlot->loopCounter != 0)
|
||||
// Go back to loop start.
|
||||
sfxSlot->scriptPos = sfxSlot->loopStart;
|
||||
// Else continue the script.
|
||||
}
|
||||
break;
|
||||
case 0x9:
|
||||
// Stop the current note.
|
||||
noteOff(sfxSlot);
|
||||
break;
|
||||
case 0xA:
|
||||
// Reset sound effect data.
|
||||
sfxSlot->reset();
|
||||
// The original interpreter does this; not sure why...
|
||||
sfxSlot->vibratoCounter = 1;
|
||||
break;
|
||||
case 0xB:
|
||||
// Noop. Consumes 1 script parameter.
|
||||
sfxSlot->scriptPos++;
|
||||
break;
|
||||
case 0xC:
|
||||
default:
|
||||
// Stop the sound effect.
|
||||
stop(sfxSlot);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::noteOn(SfxSlot *sfxSlot) {
|
||||
byte note = sfxSlot->currentNoteFraction >> 8;
|
||||
if (sfxSlot->source >= 0)
|
||||
// Send a note on with maximum velocity.
|
||||
_driver->send(sfxSlot->source, 0x90 | (note << 8) | (0x7F << 16));
|
||||
sfxSlot->lastPlayedNote = note;
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::noteOff(SfxSlot *sfxSlot) {
|
||||
if (sfxSlot->lastPlayedNote < 0)
|
||||
return;
|
||||
|
||||
if (sfxSlot->source >= 0)
|
||||
// Send a note off.
|
||||
_driver->send(sfxSlot->source, 0x80 | (sfxSlot->lastPlayedNote << 8));
|
||||
sfxSlot->lastPlayedNote = -1;
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::onTimer() {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
if (_paused)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < ARRAYSIZE(_sfxSlots); i++) {
|
||||
if (!_sfxSlots[i].active)
|
||||
continue;
|
||||
|
||||
if (!_sfxSlots[i].programChanged) {
|
||||
// If the SFX instrument has not yet been set on the allocated
|
||||
// channel, wait until the driver is ready.
|
||||
if (!_driver->isReady(_sfxSlots[i].source))
|
||||
continue;
|
||||
|
||||
// Then change to the SFX instrument.
|
||||
changeInstrument(&_sfxSlots[i]);
|
||||
_sfxSlots[i].programChanged = true;
|
||||
}
|
||||
|
||||
// Note that ScummVM timer callback frequency is limited by what SDL
|
||||
// can provide, which is every 10ms minimum. The original interpreter
|
||||
// processes a script tick about every 3.25ms. So every onTimer call
|
||||
// multiple script ticks are processed at the same time. Because of
|
||||
// this, the sound effect audio is not entirely accurate.
|
||||
|
||||
// Determine the end time of the timer callback period that will be
|
||||
// processed.
|
||||
uint32 endTime = _sfxSlots[i].playTime + _timerRate;
|
||||
// Process script ticks while the sound effect remains active.
|
||||
while (_sfxSlots[i].active) {
|
||||
// Determine the end time of the script tick that will be
|
||||
// processed.
|
||||
uint32 eventTime = _sfxSlots[i].lastEventTime + SCRIPT_TIMER_RATE;
|
||||
if (eventTime > endTime)
|
||||
// Script tick end time is after this timer callback period, so
|
||||
// leave it to the next callback invocation.
|
||||
break;
|
||||
|
||||
// Process this script tick.
|
||||
_sfxSlots[i].lastEventTime = eventTime;
|
||||
|
||||
// Process vibrato counter.
|
||||
if (_sfxSlots[i].vibratoCounter > 0) {
|
||||
// Count down the vibrato counter.
|
||||
_sfxSlots[i].vibratoCounter--;
|
||||
} else {
|
||||
// Vibrato counter has reached zero. The vibrato note fraction
|
||||
// delta is negated, so that it will now be subtracted from the
|
||||
// note fraction instead of added, or the other way around.
|
||||
_sfxSlots[i].vibratoDelta = -_sfxSlots[i].vibratoDelta;
|
||||
// Reset the vibrato counter so it counts down to the next
|
||||
// delta negation.
|
||||
_sfxSlots[i].vibratoCounter = _sfxSlots[i].vibratoTime;
|
||||
}
|
||||
|
||||
// Calculate the new note and note fraction by adding the deltas.
|
||||
uint16 newNoteFraction = _sfxSlots[i].currentNoteFraction;
|
||||
newNoteFraction += _sfxSlots[i].noteFractionDelta;
|
||||
newNoteFraction += _sfxSlots[i].vibratoDelta;
|
||||
|
||||
if (newNoteFraction != _sfxSlots[i].currentNoteFraction) {
|
||||
// Update the note fraction.
|
||||
_sfxSlots[i].currentNoteFraction = newNoteFraction;
|
||||
updateNote(&_sfxSlots[i]);
|
||||
}
|
||||
|
||||
// Process the script.
|
||||
if (_sfxSlots[i].waitCounter > 0) {
|
||||
// Count down the wait counter. No script opcode will be
|
||||
// processsed.
|
||||
_sfxSlots[i].waitCounter--;
|
||||
} else if (_sfxSlots[i].atEndOfScript()) {
|
||||
// The end of the script has been reached, so stop the sound
|
||||
// effect.
|
||||
// Note that the original interpreter did not do any bounds
|
||||
// checking. Some scripts are not terminated properly and would
|
||||
// cause the original interpreter to overread into the
|
||||
// instrument data of the next sound effect.
|
||||
stop(&_sfxSlots[i]);
|
||||
} else {
|
||||
// Process the next script opcode.
|
||||
byte opCode = _sfxSlots[i].readScript(true) & 0xFF;
|
||||
processOpCode(&_sfxSlots[i], opCode);
|
||||
}
|
||||
}
|
||||
|
||||
// If the sound effect is still active, update the play timestamp.
|
||||
if (_sfxSlots[i].active)
|
||||
_sfxSlots[i].playTime = endTime;
|
||||
}
|
||||
}
|
||||
|
||||
void SfxParser_Accolade::timerCallback(void *data) {
|
||||
((SfxParser_Accolade *)data)->onTimer();
|
||||
}
|
||||
|
||||
void SfxParser_Accolade_AdLib::setMidiDriver(MidiDriver_Multisource *driver) {
|
||||
_driver = driver;
|
||||
_adLibDriver = dynamic_cast<MidiDriver_Accolade_AdLib *>(driver);
|
||||
assert(_adLibDriver);
|
||||
}
|
||||
|
||||
byte SfxParser_Accolade_AdLib::getNumberOfSfxSources() {
|
||||
// The number of available sources depends on the OPL chip emulation used.
|
||||
return _adLibDriver->getNumberOfSfxSources();
|
||||
}
|
||||
|
||||
void SfxParser_Accolade_AdLib::readInstrument(SfxData *sfxData, Common::SeekableReadStream *in) {
|
||||
in->skip(INSTRUMENT_SIZE_MT32);
|
||||
in->read(sfxData->instrumentDefinition, INSTRUMENT_SIZE_ADLIB);
|
||||
}
|
||||
|
||||
bool SfxParser_Accolade_AdLib::loadInstrument(SfxSlot *sfxSlot) {
|
||||
if (sfxSlot->source < 0)
|
||||
return true;
|
||||
|
||||
_adLibDriver->loadSfxInstrument(sfxSlot->source, sfxSlot->sfxData->instrumentDefinition);
|
||||
// No separate instrument change is necessary for AdLib, so true is
|
||||
// returned to indicate the instrument is already changed.
|
||||
return true;
|
||||
}
|
||||
|
||||
void SfxParser_Accolade_AdLib::noteOn(SfxSlot *sfxSlot) {
|
||||
if (sfxSlot->source >= 0)
|
||||
// Set the current note fraction first.
|
||||
_adLibDriver->setSfxNoteFraction(sfxSlot->source, sfxSlot->currentNoteFraction);
|
||||
// Then start the note.
|
||||
SfxParser_Accolade::noteOn(sfxSlot);
|
||||
}
|
||||
|
||||
void SfxParser_Accolade_AdLib::updateNote(SfxSlot *sfxSlot) {
|
||||
if (sfxSlot->source < 0)
|
||||
return;
|
||||
|
||||
// Set the current note fraction first.
|
||||
_adLibDriver->setSfxNoteFraction(sfxSlot->source, sfxSlot->currentNoteFraction);
|
||||
// Then update the note.
|
||||
_adLibDriver->updateSfxNote(sfxSlot->source);
|
||||
}
|
||||
|
||||
void SfxParser_Accolade_MT32::setMidiDriver(MidiDriver_Multisource *driver) {
|
||||
_driver = driver;
|
||||
_mt32Driver = dynamic_cast<MidiDriver_Accolade_MT32 *>(driver);
|
||||
assert(_mt32Driver);
|
||||
}
|
||||
|
||||
byte SfxParser_Accolade_MT32::getNumberOfSfxSources() {
|
||||
// MT-32 always uses 2 SFX sources.
|
||||
return 2;
|
||||
}
|
||||
|
||||
void SfxParser_Accolade_MT32::readInstrument(SfxData *sfxSlot, Common::SeekableReadStream *in) {
|
||||
in->read(sfxSlot->instrumentDefinition, INSTRUMENT_SIZE_MT32);
|
||||
in->skip(INSTRUMENT_SIZE_ADLIB);
|
||||
}
|
||||
|
||||
bool SfxParser_Accolade_MT32::loadInstrument(SfxSlot *sfxSlot) {
|
||||
if (sfxSlot->source < 0)
|
||||
return true;
|
||||
|
||||
_mt32Driver->loadSfxInstrument(sfxSlot->source, sfxSlot->sfxData->instrumentDefinition);
|
||||
// MT-32 requires a program change after the SysExes to load the instrument
|
||||
// have been processed. Return false to indicate this.
|
||||
return false;
|
||||
}
|
||||
|
||||
void SfxParser_Accolade_MT32::changeInstrument(SfxSlot *sfxData) {
|
||||
if (sfxData->source < 0)
|
||||
return;
|
||||
|
||||
_mt32Driver->changeSfxInstrument(sfxData->source);
|
||||
}
|
||||
|
||||
} // End of namespace AGOS
|
220
engines/agos/sfxparser_accolade.h
Normal file
220
engines/agos/sfxparser_accolade.h
Normal file
@ -0,0 +1,220 @@
|
||||
/* 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 AGOS_SFXPARSER_ACCOLADE_H
|
||||
#define AGOS_SFXPARSER_ACCOLADE_H
|
||||
|
||||
#include "agos/drivers/accolade/adlib.h"
|
||||
#include "agos/drivers/accolade/mt32.h"
|
||||
|
||||
#include "common/mutex.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
namespace AGOS {
|
||||
|
||||
class SfxParser_Accolade {
|
||||
public:
|
||||
// Size in bytes of MT-32 instrument data in the SFX data.
|
||||
static const byte INSTRUMENT_SIZE_MT32 = 0xF9;
|
||||
|
||||
protected:
|
||||
// Number of microseconds per script tick.
|
||||
static const uint16 SCRIPT_TIMER_RATE = 3250;
|
||||
// Size in bytes of AdLib instrument data in the SFX data.
|
||||
static const byte INSTRUMENT_SIZE_ADLIB = 0x09;
|
||||
// Maximum number of words in an SFX script.
|
||||
static const byte MAX_SCRIPT_SIZE = 0x30;
|
||||
// Maximum number of simultaneous sources for OPL2.
|
||||
static const byte OPL2_NUM_SOURCES = 2;
|
||||
// Maximum number of simultaneous sources for OPL3.
|
||||
static const byte OPL3_NUM_SOURCES = 4;
|
||||
|
||||
// Data for a single SFX. Taken from the game's SFX bank.
|
||||
struct SfxData {
|
||||
// The instrument data for the used sound device (OPL or MT-32).
|
||||
byte instrumentDefinition[INSTRUMENT_SIZE_MT32];
|
||||
// The SFX script.
|
||||
int16 scriptData[MAX_SCRIPT_SIZE];
|
||||
// The size in words of the SFX script.
|
||||
int scriptSize;
|
||||
};
|
||||
|
||||
// State data a SFX playback slot.
|
||||
struct SfxSlot {
|
||||
SfxSlot();
|
||||
|
||||
// The data of the SFX currently played by this slot.
|
||||
SfxData *sfxData;
|
||||
|
||||
// True if this slot has been allocated to playing a SFX.
|
||||
bool allocated;
|
||||
// True if SFX playback is active.
|
||||
bool active;
|
||||
// The source used to send data to the MIDI driver.
|
||||
int8 source;
|
||||
// Current position in the SFX script.
|
||||
byte scriptPos;
|
||||
|
||||
// Current playback time in microseconds.
|
||||
uint32 playTime;
|
||||
// The timestamp of the last processed script tick.
|
||||
uint32 lastEventTime;
|
||||
// The last MIDI note that was sent as a note on event.
|
||||
int16 lastPlayedNote;
|
||||
// The current MIDI note (upper byte) and note fraction (1/256th notes;
|
||||
// lower byte) value.
|
||||
uint16 currentNoteFraction;
|
||||
// True if the allocated channel on the MIDI device has been changed to
|
||||
// the SFX instrument.
|
||||
bool programChanged;
|
||||
|
||||
// Delta to the note fraction. This is added to/subtracted from the
|
||||
// note fraction every script tick.
|
||||
int16 noteFractionDelta;
|
||||
// The vibrato time. The number of script ticks it takes for the note
|
||||
// difference to go from lowest to highest (or the other way around).
|
||||
int16 vibratoTime;
|
||||
// The number of script ticks that have passed since the vibrato has
|
||||
// started.
|
||||
int16 vibratoCounter;
|
||||
// Vibrato delta to the note fraction. This is added to/subtracted
|
||||
// from the note fraction every script tick.
|
||||
int16 vibratoDelta;
|
||||
// The number of ticks that remain before the next script event is
|
||||
// processed.
|
||||
int16 waitCounter;
|
||||
// The script position at which the current loop has started.
|
||||
byte loopStart;
|
||||
// The number of times the looped section will be repeated (-1 for
|
||||
// infinite loop).
|
||||
int16 loopCounter;
|
||||
|
||||
// Completely clears the SFX slot data.
|
||||
void clear();
|
||||
// Resets the SFX slot data as needed by the reset opcode.
|
||||
void reset();
|
||||
// True if the current position is at the end of the script.
|
||||
bool atEndOfScript();
|
||||
// Reads the next script word. Specify the opCode flag to return a
|
||||
// valid opcode.
|
||||
int16 readScript(bool opCode);
|
||||
};
|
||||
|
||||
public:
|
||||
SfxParser_Accolade();
|
||||
virtual ~SfxParser_Accolade();
|
||||
|
||||
// Loads the specified sound effects bank (FXB file).
|
||||
void load(Common::SeekableReadStream *in, int32 size);
|
||||
|
||||
// Sets the MIDI driver that should be used to output the SFX.
|
||||
virtual void setMidiDriver(MidiDriver_Multisource *driver) = 0;
|
||||
// Sets the number of microseconds between timer callbacks.
|
||||
void setTimerRate(uint32 rate);
|
||||
|
||||
// Starts playback of the specified sound effect.
|
||||
void play(uint8 sfxNumber);
|
||||
// Stops all active SFX.
|
||||
void stopAll();
|
||||
// Pauses or unpauses all active SFX.
|
||||
void pauseAll(bool paused);
|
||||
|
||||
void onTimer();
|
||||
static void timerCallback(void *data);
|
||||
|
||||
protected:
|
||||
// Stops the sound effect playing in the specified slot.
|
||||
void stop(SfxSlot *sfxSlot);
|
||||
// Processes the specified opcode for the specified slot.
|
||||
void processOpCode(SfxSlot *sfxSlot, byte opCode);
|
||||
|
||||
// Returns the number of sources available for SFX playback.
|
||||
virtual byte getNumberOfSfxSources() = 0;
|
||||
// Reads the SFX instrument data into the specified SfxData from the
|
||||
// specified SFX bank data. This is positioned at the start of the data of
|
||||
// a sound effect in the bank; when the function returns is should be
|
||||
// positioned right after all instrument data for the sound effect.
|
||||
virtual void readInstrument(SfxData *sfxData, Common::SeekableReadStream *in) = 0;
|
||||
|
||||
// Loads the SFX instrument for the specified slot into the channel
|
||||
// allocated to the sound effect. Returns true if the channel needs to be
|
||||
// changed to the new instrument when the driver is ready.
|
||||
virtual bool loadInstrument(SfxSlot *sfxSlot) = 0;
|
||||
// Changes the channel allocated to the sound effect to the SFX instrument.
|
||||
virtual void changeInstrument(SfxSlot *sfxSlot) { };
|
||||
// Starts a note at the current note / note fraction for the slot.
|
||||
virtual void noteOn(SfxSlot *sfxSlot);
|
||||
// Stops the current note for the slot.
|
||||
virtual void noteOff(SfxSlot *sfxSlot);
|
||||
// Updates the note / note fraction for the slot.
|
||||
virtual void updateNote(SfxSlot *sfxSlot) { };
|
||||
|
||||
Common::Mutex _mutex;
|
||||
|
||||
MidiDriver_Multisource *_driver;
|
||||
uint32 _timerRate;
|
||||
|
||||
// Array of SFX data loaded from the SFX bank.
|
||||
SfxData *_sfxData;
|
||||
// The number of SFX data loaded.
|
||||
uint16 _numSfx;
|
||||
// The slots available for SFX playback.
|
||||
SfxSlot _sfxSlots[4];
|
||||
// The slot numbers allocated to the available SFX sources. -1 if no slot
|
||||
// is using the source.
|
||||
int8 _sourceAllocations[4];
|
||||
|
||||
// True if SFX playback is paused.
|
||||
bool _paused;
|
||||
};
|
||||
|
||||
class SfxParser_Accolade_AdLib : public SfxParser_Accolade {
|
||||
public:
|
||||
SfxParser_Accolade_AdLib() : _adLibDriver(nullptr) { }
|
||||
|
||||
protected:
|
||||
void setMidiDriver(MidiDriver_Multisource *driver) override;
|
||||
byte getNumberOfSfxSources() override;
|
||||
void readInstrument(SfxData *sfxData, Common::SeekableReadStream *in) override;
|
||||
bool loadInstrument(SfxSlot *sfxSlot) override;
|
||||
void noteOn(SfxSlot *sfxSlot) override;
|
||||
void updateNote(SfxSlot *sfxSlot) override;
|
||||
|
||||
MidiDriver_Accolade_AdLib *_adLibDriver;
|
||||
};
|
||||
|
||||
class SfxParser_Accolade_MT32 : public SfxParser_Accolade {
|
||||
public:
|
||||
SfxParser_Accolade_MT32() : _mt32Driver(nullptr) { }
|
||||
|
||||
protected:
|
||||
void setMidiDriver(MidiDriver_Multisource *driver) override;
|
||||
byte getNumberOfSfxSources() override;
|
||||
void readInstrument(SfxData *sfxData, Common::SeekableReadStream *in) override;
|
||||
bool loadInstrument(SfxSlot *sfxSlot) override;
|
||||
void changeInstrument(SfxSlot *sfxSlot) override;
|
||||
|
||||
MidiDriver_Accolade_MT32 *_mt32Driver;
|
||||
};
|
||||
|
||||
} // End of namespace AGOS
|
||||
|
||||
#endif
|
@ -1161,14 +1161,17 @@ void AGOSEngine::vc28_playSFX() {
|
||||
uint16 flags = vcReadNextWord();
|
||||
debug(0, "vc28_playSFX: (sound %d, channels %d, frequency %d, flags %d)", sound, chans, freq, flags);
|
||||
|
||||
loadSound(sound, freq, flags);
|
||||
// Waxworks DOS uses 2 opcodes to play SFX: this one for digital SFX and
|
||||
// vc52 for MIDI SFX. If MIDI SFX are used, this opcode should be ignored;
|
||||
// vc52 will play the corresponding MIDI SFX (if available).
|
||||
playSfx(sound, freq, flags, !(getGameType() == GType_WW && getPlatform() == Common::kPlatformDOS));
|
||||
}
|
||||
|
||||
void AGOSEngine::vc29_stopAllSounds() {
|
||||
if (getGameType() != GType_PP)
|
||||
_sound->stopVoice();
|
||||
|
||||
_sound->stopAllSfx();
|
||||
stopAllSfx();
|
||||
}
|
||||
|
||||
void AGOSEngine::vc30_setFrameRate() {
|
||||
|
@ -191,12 +191,8 @@ void AGOSEngine::vc52_playSound() {
|
||||
_sound->playEffects(sound);
|
||||
} else if (getFeatures() & GF_TALKIE) {
|
||||
_sound->playEffects(sound);
|
||||
} else if (getGameId() == GID_SIMON1DOS) {
|
||||
playSting(sound);
|
||||
} else if (getGameType() == GType_WW) {
|
||||
// TODO: Sound effects in PC version only
|
||||
} else {
|
||||
loadSound(sound, 0, 0);
|
||||
playSfx(sound, 0, 0, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user