aboutsummaryrefslogtreecommitdiff
path: root/engines/scumm/imuse
diff options
context:
space:
mode:
authorMatthew Hoops2011-07-20 09:27:39 -0400
committerMatthew Hoops2011-07-20 09:27:39 -0400
commitad293b249e74dd1cfbdbd721d02145efbdaf9eca (patch)
treee568d96f6d7f64c5e58b4c7cd1c4fda7e649bfc7 /engines/scumm/imuse
parentd7411acc2b1c7702280dbff1c3e1bafee528184b (diff)
parente25e85fbb047fef895ede97c3c2c73451631052c (diff)
downloadscummvm-rg350-ad293b249e74dd1cfbdbd721d02145efbdaf9eca.tar.gz
scummvm-rg350-ad293b249e74dd1cfbdbd721d02145efbdaf9eca.tar.bz2
scummvm-rg350-ad293b249e74dd1cfbdbd721d02145efbdaf9eca.zip
Merge remote branch 'upstream/master' into pegasus
Diffstat (limited to 'engines/scumm/imuse')
-rw-r--r--engines/scumm/imuse/imuse.cpp166
-rw-r--r--engines/scumm/imuse/imuse.h7
-rw-r--r--engines/scumm/imuse/imuse_internal.h42
-rw-r--r--engines/scumm/imuse/imuse_part.cpp12
-rw-r--r--engines/scumm/imuse/imuse_player.cpp55
-rw-r--r--engines/scumm/imuse/instrument.cpp100
-rw-r--r--engines/scumm/imuse/instrument.h7
-rw-r--r--engines/scumm/imuse/pcspk.cpp835
-rw-r--r--engines/scumm/imuse/pcspk.h161
-rw-r--r--engines/scumm/imuse/sysex_samnmax.cpp3
-rw-r--r--engines/scumm/imuse/sysex_scumm.cpp81
11 files changed, 1280 insertions, 189 deletions
diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp
index 7d971f5ca4..27a72c2afe 100644
--- a/engines/scumm/imuse/imuse.cpp
+++ b/engines/scumm/imuse/imuse.cpp
@@ -26,6 +26,7 @@
#include "common/util.h"
#include "common/system.h"
+#include "common/endian.h"
#include "scumm/imuse/imuse.h"
#include "scumm/imuse/imuse_internal.h"
@@ -43,30 +44,31 @@ namespace Scumm {
////////////////////////////////////////
IMuseInternal::IMuseInternal() :
-_native_mt32(false),
-_enable_gs(false),
-_sc55(false),
-_midi_adlib(NULL),
-_midi_native(NULL),
-_sysex(NULL),
-_paused(false),
-_initialized(false),
-_tempoFactor(0),
-_player_limit(ARRAYSIZE(_players)),
-_recycle_players(false),
-_queue_end(0),
-_queue_pos(0),
-_queue_sound(0),
-_queue_adding(0),
-_queue_marker(0),
-_queue_cleared(0),
-_master_volume(0),
-_music_volume(0),
-_trigger_count(0),
-_snm_trigger_index(0) {
- memset(_channel_volume,0,sizeof(_channel_volume));
- memset(_channel_volume_eff,0,sizeof(_channel_volume_eff));
- memset(_volchan_table,0,sizeof(_volchan_table));
+ _native_mt32(false),
+ _enable_gs(false),
+ _sc55(false),
+ _midi_adlib(NULL),
+ _midi_native(NULL),
+ _sysex(NULL),
+ _paused(false),
+ _initialized(false),
+ _tempoFactor(0),
+ _player_limit(ARRAYSIZE(_players)),
+ _recycle_players(false),
+ _queue_end(0),
+ _queue_pos(0),
+ _queue_sound(0),
+ _queue_adding(0),
+ _queue_marker(0),
+ _queue_cleared(0),
+ _master_volume(0),
+ _music_volume(0),
+ _trigger_count(0),
+ _snm_trigger_index(0),
+ _pcSpeaker(false) {
+ memset(_channel_volume, 0, sizeof(_channel_volume));
+ memset(_channel_volume_eff, 0, sizeof(_channel_volume_eff));
+ memset(_volchan_table, 0, sizeof(_volchan_table));
}
IMuseInternal::~IMuseInternal() {
@@ -99,9 +101,15 @@ IMuseInternal::~IMuseInternal() {
}
}
-byte *IMuseInternal::findStartOfSound(int sound, int ct) {
+byte *IMuseInternal::findStartOfSound(int sound, int ct) {
int32 size, pos;
- static uint32 id[] = { 'MThd', 'FORM', 'MDhd', 'MDpg' };
+
+ static const uint32 id[] = {
+ MKTAG('M', 'T', 'h', 'd'),
+ MKTAG('F', 'O', 'R', 'M'),
+ MKTAG('M', 'D', 'h', 'd'),
+ MKTAG('M', 'D', 'p', 'g')
+ };
byte *ptr = g_scumm->_res->_types[rtSound][sound]._address;
@@ -112,7 +120,7 @@ byte *IMuseInternal::findStartOfSound(int sound, int ct) {
// Check for old-style headers first, like 'RO'
int trFlag = (kMThd | kFORM);
- if (ptr[0] == 'R' && ptr[1] == 'O'&& ptr[2] != 'L')
+ if (ptr[0] == 'R' && ptr[1] == 'O' && ptr[2] != 'L')
return ct == trFlag ? ptr : 0;
if (ptr[4] == 'S' && ptr[5] == 'O')
return ct == trFlag ? ptr + 4 : 0;
@@ -146,22 +154,22 @@ bool IMuseInternal::isMT32(int sound) {
uint32 tag = READ_BE_UINT32(ptr);
switch (tag) {
- case MKTAG('A','D','L',' '):
- case MKTAG('A','S','F','X'): // Special AD class for old AdLib sound effects
- case MKTAG('S','P','K',' '):
+ case MKTAG('A', 'D', 'L', ' '):
+ case MKTAG('A', 'S', 'F', 'X'): // Special AD class for old AdLib sound effects
+ case MKTAG('S', 'P', 'K', ' '):
return false;
- case MKTAG('A','M','I',' '):
- case MKTAG('R','O','L',' '):
+ case MKTAG('A', 'M', 'I', ' '):
+ case MKTAG('R', 'O', 'L', ' '):
return true;
- case MKTAG('M','A','C',' '): // Occurs in the Mac version of FOA and MI2
+ case MKTAG('M', 'A', 'C', ' '): // Occurs in the Mac version of FOA and MI2
return true;
- case MKTAG('G','M','D',' '):
+ case MKTAG('G', 'M', 'D', ' '):
return false;
- case MKTAG('M','I','D','I'): // Occurs in Sam & Max
+ case MKTAG('M', 'I', 'D', 'I'): // Occurs in Sam & Max
// HE games use Roland music
if (ptr[8] == 'H' && ptr[9] == 'S')
return true;
@@ -188,20 +196,20 @@ bool IMuseInternal::isMIDI(int sound) {
uint32 tag = READ_BE_UINT32(ptr);
switch (tag) {
- case MKTAG('A','D','L',' '):
- case MKTAG('A','S','F','X'): // Special AD class for old AdLib sound effects
- case MKTAG('S','P','K',' '):
+ case MKTAG('A', 'D', 'L', ' '):
+ case MKTAG('A', 'S', 'F', 'X'): // Special AD class for old AdLib sound effects
+ case MKTAG('S', 'P', 'K', ' '):
return false;
- case MKTAG('A','M','I',' '):
- case MKTAG('R','O','L',' '):
+ case MKTAG('A', 'M', 'I', ' '):
+ case MKTAG('R', 'O', 'L', ' '):
return true;
- case MKTAG('M','A','C',' '): // Occurs in the Mac version of FOA and MI2
+ case MKTAG('M', 'A', 'C', ' '): // Occurs in the Mac version of FOA and MI2
return true;
- case MKTAG('G','M','D',' '):
- case MKTAG('M','I','D','I'): // Occurs in Sam & Max
+ case MKTAG('G', 'M', 'D', ' '):
+ case MKTAG('M', 'I', 'D', 'I'): // Occurs in Sam & Max
return true;
}
@@ -374,7 +382,8 @@ int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm) {
for (i = 0; i < ARRAYSIZE(_parts); ++i)
_parts[i].saveLoadWithSerializer(ser);
- { // Load/save the instrument definitions, which were revamped with V11.
+ {
+ // Load/save the instrument definitions, which were revamped with V11.
Part *part = &_parts[0];
if (ser->getVersion() >= VER(11)) {
for (i = ARRAYSIZE(_parts); i; --i, ++part) {
@@ -460,6 +469,10 @@ uint32 IMuseInternal::property(int prop, uint32 value) {
case IMuse::PROP_GAME_ID:
_game_id = value;
break;
+
+ case IMuse::PROP_PC_SPEAKER:
+ _pcSpeaker = (value != 0);
+ break;
}
return 0;
@@ -515,7 +528,7 @@ void IMuseInternal::stopAllSounds() {
int IMuseInternal::getSoundStatus(int sound) const {
Common::StackLock lock(_mutex, "IMuseInternal::getSoundStatus()");
- return getSoundStatus_internal (sound, true);
+ return getSoundStatus_internal(sound, true);
}
int IMuseInternal::getMusicTimer() {
@@ -558,7 +571,7 @@ bool IMuseInternal::startSound_internal(int sound, int offset) {
int i;
ImTrigger *trigger = _snm_triggers;
for (i = ARRAYSIZE(_snm_triggers); i; --i, ++trigger) {
- if (trigger->sound && trigger->id && trigger->command[0] == 8 && trigger->command[1] == sound && getSoundStatus_internal (trigger->sound,true))
+ if (trigger->sound && trigger->id && trigger->command[0] == 8 && trigger->command[1] == sound && getSoundStatus_internal(trigger->sound, true))
return false;
}
@@ -656,9 +669,7 @@ int IMuseInternal::getSoundStatus_internal(int sound, bool ignoreFadeouts) const
return (sound == -1) ? 0 : get_queue_sound_status(sound);
}
-int32 IMuseInternal::doCommand_internal
- (int a, int b, int c, int d, int e, int f, int g, int h)
-{
+int32 IMuseInternal::doCommand_internal(int a, int b, int c, int d, int e, int f, int g, int h) {
int args[8];
args[0] = a;
args[1] = b;
@@ -726,7 +737,7 @@ int32 IMuseInternal::doCommand_internal(int numargs, int a[]) {
}
return -1;
case 13:
- return getSoundStatus_internal (a[1], true);
+ return getSoundStatus_internal(a[1], true);
case 14:
// Sam and Max: Parameter fade
player = findActivePlayer(a[1]);
@@ -772,8 +783,7 @@ int32 IMuseInternal::doCommand_internal(int numargs, int a[]) {
a[0] = 0;
for (i = 0; i < ARRAYSIZE(_snm_triggers); ++i) {
if (_snm_triggers[i].sound == a[1] && _snm_triggers[i].id &&
- (a[3] == -1 || _snm_triggers[i].id == a[3]))
- {
+ (a[3] == -1 || _snm_triggers[i].id == a[3])) {
++a[0];
}
}
@@ -945,7 +955,7 @@ void IMuseInternal::handle_marker(uint id, byte data) {
_trigger_count--;
_queue_cleared = false;
_queue_end = (_queue_end + 1) % ARRAYSIZE(_cmd_queue);
-
+
while (_queue_end != _queue_pos && _cmd_queue[_queue_end].array[0] == COMMAND_ID && !_queue_cleared) {
p = _cmd_queue[_queue_end].array;
doCommand_internal(p[1], p[2], p[3], p[4], p[5], p[6], p[7], 0);
@@ -995,9 +1005,9 @@ int IMuseInternal::get_queue_sound_status(int sound) const {
i = (i + 1) % ARRAYSIZE(_cmd_queue);
}
- for (i = 0; i < ARRAYSIZE (_deferredCommands); ++i) {
+ for (i = 0; i < ARRAYSIZE(_deferredCommands); ++i) {
if (_deferredCommands[i].time_left && _deferredCommands[i].a == 8 &&
- _deferredCommands[i].b == sound) {
+ _deferredCommands[i].b == sound) {
return 2;
}
}
@@ -1206,7 +1216,7 @@ int32 IMuseInternal::ImSetTrigger(int sound, int id, int a, int b, int c, int d,
// NOTE: We ONLY do this if the sound that will trigger the command is actually
// playing. Otherwise, there's a problem when exiting and re-entering the
// Bumpusville mansion. Ref Bug #780918.
- if (trig->command[0] == 8 && getSoundStatus_internal(trig->command[1],true) && getSoundStatus_internal(sound,true))
+ if (trig->command[0] == 8 && getSoundStatus_internal(trig->command[1], true) && getSoundStatus_internal(sound, true))
stopSound_internal(trig->command[1]);
return 0;
}
@@ -1239,8 +1249,7 @@ int32 IMuseInternal::ImFireAllTriggers(int sound) {
return (count > 0) ? 0 : -1;
}
-int IMuseInternal::set_channel_volume(uint chan, uint vol)
-{
+int IMuseInternal::set_channel_volume(uint chan, uint vol) {
if (chan >= 8 || vol > 127)
return -1;
@@ -1420,7 +1429,7 @@ void IMuseInternal::initMT32(MidiDriver *midi) {
// Display a welcome message on MT-32 displays.
memcpy(&buffer[0], "\x41\x10\x16\x12\x20\x00\x00", 7);
memcpy(&buffer[7], " ", 20);
- memcpy(buffer + 7 +(20 - len) / 2, info, len);
+ memcpy(buffer + 7 + (20 - len) / 2, info, len);
byte checksum = 0;
for (int i = 4; i < 27; ++i)
checksum -= buffer[i];
@@ -1466,9 +1475,9 @@ void IMuseInternal::initGM(MidiDriver *midi) {
// Set Channels 1-16 to SC-55 Map, then CM-64/32L Variation
for (i = 0; i < 16; ++i) {
- midi->send(( 127 << 16) | (0 << 8) | (0xB0 | i));
- midi->send(( 1 << 16) | (32 << 8) | (0xB0 | i));
- midi->send(( 0 << 16) | (0 << 8) | (0xC0 | i));
+ midi->send((127 << 16) | (0 << 8) | (0xB0 | i));
+ midi->send((1 << 16) | (32 << 8) | (0xB0 | i));
+ midi->send((0 << 16) | (0 << 8) | (0xC0 | i));
}
debug(2, "GS Program Change: CM-64/32L Map Selected");
@@ -1489,7 +1498,7 @@ void IMuseInternal::initGM(MidiDriver *midi) {
// Set Channels 1-16 Reverb to 64, which is the
// equivalent of MT-32 default Reverb Level 5
for (i = 0; i < 16; ++i)
- midi->send(( 64 << 16) | (91 << 8) | (0xB0 | i));
+ midi->send((64 << 16) | (91 << 8) | (0xB0 | i));
debug(2, "GM Controller 91 Change: Channels 1-16 Reverb Level is 64");
// Set Channels 1-16 Pitch Bend Sensitivity to
@@ -1630,8 +1639,8 @@ void IMuseInternal::reallocateMidiChannels(MidiDriver *midi) {
hipart = NULL;
for (i = 32, part = _parts; i; i--, part++) {
if (part->_player && part->_player->getMidiDriver() == midi &&
- !part->_percussion && part->_on &&
- !part->_mc && part->_pri_eff >= hipri) {
+ !part->_percussion && part->_on &&
+ !part->_mc && part->_pri_eff >= hipri) {
hipri = part->_pri_eff;
hipart = part;
}
@@ -1661,16 +1670,35 @@ void IMuseInternal::reallocateMidiChannels(MidiDriver *midi) {
}
}
-void IMuseInternal::setGlobalAdLibInstrument(byte slot, byte *data) {
+void IMuseInternal::setGlobalInstrument(byte slot, byte *data) {
if (slot < 32) {
- _global_adlib_instruments[slot].adlib(data);
+ if (_pcSpeaker)
+ _global_instruments[slot].pcspk(data);
+ else
+ _global_instruments[slot].adlib(data);
}
}
-void IMuseInternal::copyGlobalAdLibInstrument(byte slot, Instrument *dest) {
+void IMuseInternal::copyGlobalInstrument(byte slot, Instrument *dest) {
if (slot >= 32)
return;
- _global_adlib_instruments[slot].copy_to(dest);
+
+ // Both the AdLib code and the PC Speaker code use an all zero instrument
+ // as default in the original, thus we do the same.
+ // PC Speaker instrument size is 23, while AdLib instrument size is 30.
+ // Thus we just use a 30 byte instrument data array as default.
+ const byte defaultInstr[30] = { 0 };
+
+ if (_global_instruments[slot].isValid()) {
+ // In case we have an valid instrument set up, copy it to the part.
+ _global_instruments[slot].copy_to(dest);
+ } else if (_pcSpeaker) {
+ debug(0, "Trying to use non-existant global PC Speaker instrument %d", slot);
+ dest->pcspk(defaultInstr);
+ } else {
+ debug(0, "Trying to use non-existant global AdLib instrument %d", slot);
+ dest->adlib(defaultInstr);
+ }
}
diff --git a/engines/scumm/imuse/imuse.h b/engines/scumm/imuse/imuse.h
index 8014b13409..23449e470b 100644
--- a/engines/scumm/imuse/imuse.h
+++ b/engines/scumm/imuse/imuse.h
@@ -37,7 +37,7 @@ class Player;
class ScummEngine;
class Serializer;
-typedef void (*sysexfunc) (Player *, const byte *, uint16);
+typedef void (*sysexfunc)(Player *, const byte *, uint16);
/**
* iMuse implementation interface.
@@ -55,7 +55,8 @@ public:
PROP_GS,
PROP_LIMIT_PLAYERS,
PROP_RECYCLE_PLAYERS,
- PROP_GAME_ID
+ PROP_GAME_ID,
+ PROP_PC_SPEAKER
};
public:
@@ -66,7 +67,7 @@ public:
virtual int32 doCommand(int numargs, int args[]) = 0;
virtual int clear_queue() = 0;
virtual uint32 property(int prop, uint32 value) = 0;
- virtual void addSysexHandler (byte mfgID, sysexfunc handler) = 0;
+ virtual void addSysexHandler(byte mfgID, sysexfunc handler) = 0;
public:
virtual void startSoundWithNoteOffset(int sound, int offset) = 0;
diff --git a/engines/scumm/imuse/imuse_internal.h b/engines/scumm/imuse/imuse_internal.h
index 8808a3655a..3b0d36e119 100644
--- a/engines/scumm/imuse/imuse_internal.h
+++ b/engines/scumm/imuse/imuse_internal.h
@@ -135,7 +135,7 @@ struct ImTrigger {
int sound;
byte id;
uint16 expire;
- int command [8];
+ int command[8];
ImTrigger() { memset(this, 0, sizeof(ImTrigger)); }
};
@@ -153,12 +153,12 @@ struct CommandQueue {
//////////////////////////////////////////////////
class Player : public MidiDriver_BASE {
-/*
- * External SysEx handler functions shall each be defined in
- * a separate file. This header file shall be included at the
- * top of the file immediately following this special #define:
- * #define SYSEX_CALLBACK_FUNCTION nameOfHandlerFunction
- */
+ /*
+ * External SysEx handler functions shall each be defined in
+ * a separate file. This header file shall be included at the
+ * top of the file immediately following this special #define:
+ * #define SYSEX_CALLBACK_FUNCTION nameOfHandlerFunction
+ */
#ifdef SYSEX_CALLBACK_FUNCTION
friend void SYSEX_CALLBACK_FUNCTION(Player *, const byte *, uint16);
#endif
@@ -244,7 +244,7 @@ public:
void clear();
void clearLoop();
void fixAfterLoad();
- Part * getActivePart(uint8 part);
+ Part *getActivePart(uint8 part);
uint getBeatIndex();
int8 getDetune() const { return _detune; }
byte getEffectiveVolume() const { return _vol_eff; }
@@ -252,7 +252,7 @@ public:
MidiDriver *getMidiDriver() const { return _midi; }
int getParam(int param, byte chan);
int8 getPan() const { return _pan; }
- Part * getPart(uint8 part);
+ Part *getPart(uint8 part);
byte getPriority() const { return _priority; }
uint getTicksPerBeat() const { return TICKS_PER_BEAT; }
int8 getTranspose() const { return _transpose; }
@@ -342,6 +342,7 @@ struct Part : public Serializable {
void off();
void set_instrument(uint b);
void set_instrument(byte *data);
+ void set_instrument_pcspk(byte *data);
void load_global_instrument(byte b);
void set_transpose(int8 transpose);
@@ -375,12 +376,12 @@ class IMuseInternal : public IMuse {
friend class Player;
friend struct Part;
-/*
- * External SysEx handler functions shall each be defined in
- * a separate file. This header file shall be included at the
- * top of the file immediately following this special #define:
- * #define SYSEX_CALLBACK_FUNCTION nameOfHandlerFunction
- */
+ /*
+ * External SysEx handler functions shall each be defined in
+ * a separate file. This header file shall be included at the
+ * top of the file immediately following this special #define:
+ * #define SYSEX_CALLBACK_FUNCTION nameOfHandlerFunction
+ */
#ifdef SYSEX_CALLBACK_FUNCTION
friend void SYSEX_CALLBACK_FUNCTION(Player *, const byte *, uint16);
#endif
@@ -433,7 +434,8 @@ protected:
Player _players[8];
Part _parts[32];
- Instrument _global_adlib_instruments[32];
+ bool _pcSpeaker;
+ Instrument _global_instruments[32];
CommandQueue _cmd_queue[64];
DeferredCommand _deferredCommands[4];
@@ -449,8 +451,8 @@ protected:
enum ChunkType {
kMThd = 1,
kFORM = 2,
- kMDhd = 4, // Used in MI2 and INDY4. Contain certain start parameters (priority, volume, etc. ) for the player.
- kMDpg = 8 // These chunks exist in DOTT and SAMNMAX. They don't get processed, however.
+ kMDhd = 4, // Used in MI2 and INDY4. Contain certain start parameters (priority, volume, etc. ) for the player.
+ kMDpg = 8 // These chunks exist in DOTT and SAMNMAX. They don't get processed, however.
};
byte *findStartOfSound(int sound, int ct = (kMThd | kFORM));
@@ -498,8 +500,8 @@ protected:
int setImuseMasterVolume(uint vol);
void reallocateMidiChannels(MidiDriver *midi);
- void setGlobalAdLibInstrument(byte slot, byte *data);
- void copyGlobalAdLibInstrument(byte slot, Instrument *dest);
+ void setGlobalInstrument(byte slot, byte *data);
+ void copyGlobalInstrument(byte slot, Instrument *dest);
bool isNativeMT32() { return _native_mt32; }
protected:
diff --git a/engines/scumm/imuse/imuse_part.cpp b/engines/scumm/imuse/imuse_part.cpp
index 5df8407a96..73e7704469 100644
--- a/engines/scumm/imuse/imuse_part.cpp
+++ b/engines/scumm/imuse/imuse_part.cpp
@@ -193,14 +193,18 @@ void Part::set_onoff(bool on) {
}
}
-void Part::set_instrument(byte * data) {
- _instrument.adlib(data);
+void Part::set_instrument(byte *data) {
+ if (_se->_pcSpeaker)
+ _instrument.pcspk(data);
+ else
+ _instrument.adlib(data);
+
if (clearToTransmit())
_instrument.send(_mc);
}
void Part::load_global_instrument(byte slot) {
- _player->_se->copyGlobalAdLibInstrument(slot, &_instrument);
+ _player->_se->copyGlobalInstrument(slot, &_instrument);
if (clearToTransmit())
_instrument.send(_mc);
}
@@ -234,7 +238,7 @@ void Part::noteOn(byte note, byte velocity) {
// should be implemented as a class static var. As it is, using
// a function level static var in most cases is arcane and evil.
static byte prev_vol_eff = 128;
- if (_vol_eff != prev_vol_eff){
+ if (_vol_eff != prev_vol_eff) {
mc->volume(_vol_eff);
prev_vol_eff = _vol_eff;
}
diff --git a/engines/scumm/imuse/imuse_player.cpp b/engines/scumm/imuse/imuse_player.cpp
index 0b084f3116..73be2174cd 100644
--- a/engines/scumm/imuse/imuse_player.cpp
+++ b/engines/scumm/imuse/imuse_player.cpp
@@ -79,7 +79,7 @@ Player::Player() :
_isMT32(false),
_isMIDI(false),
_se(0),
- _vol_chan(0){
+ _vol_chan(0) {
}
Player::~Player() {
@@ -120,7 +120,7 @@ bool Player::startSound(int sound, MidiDriver *midi) {
_midi = NULL;
return false;
}
-
+
debugC(DEBUG_IMUSE, "Starting music %d", sound);
return true;
}
@@ -133,7 +133,7 @@ bool Player::isFadingOut() const {
int i;
for (i = 0; i < ARRAYSIZE(_parameterFaders); ++i) {
if (_parameterFaders[i].param == ParameterFader::pfVolume &&
- _parameterFaders[i].end == 0) {
+ _parameterFaders[i].end == 0) {
return true;
}
}
@@ -194,7 +194,7 @@ int Player::start_seq_sound(int sound, bool reset_vars) {
_parser->property(MidiParser::mpSmartJump, 1);
_parser->loadMusic(ptr, 0);
_parser->setTrack(_track_index);
-
+
ptr = _se->findStartOfSound(sound, IMuseInternal::kMDhd);
setSpeed(reset_vars ? (ptr ? (READ_BE_UINT32(&ptr[4]) && ptr[15] ? ptr[15] : 128) : 128) : _speed);
@@ -226,7 +226,7 @@ void Player::loadStartParameters(int sound) {
_pan = ptr[4];
_transpose = ptr[5];
_detune = ptr[6];
- setSpeed(ptr[7]);
+ setSpeed(ptr[7]);
}
}
}
@@ -371,11 +371,13 @@ void Player::sysEx(const byte *p, uint16 len) {
if (a != IMUSE_SYSEX_ID) {
if (a == ROLAND_SYSEX_ID) {
// Roland custom instrument definition.
- part = getPart(p[0] & 0x0F);
- if (part) {
- part->_instrument.roland(p - 1);
- if (part->clearToTransmit())
- part->_instrument.send(part->_mc);
+ if (_isMIDI || _isMT32) {
+ part = getPart(p[0] & 0x0F);
+ if (part) {
+ part->_instrument.roland(p - 1);
+ if (part->clearToTransmit())
+ part->_instrument.send(part->_mc);
+ }
}
} else if (a == YM2612_SYSEX_ID) {
// FM-TOWNS custom instrument definition
@@ -399,13 +401,13 @@ void Player::sysEx(const byte *p, uint16 len) {
if (!_scanning) {
for (a = 0; a < len + 1 && a < 19; ++a) {
- sprintf((char *)&buf[a*3], " %02X", p[a]);
+ sprintf((char *)&buf[a * 3], " %02X", p[a]);
} // next for
if (a < len + 1) {
- buf[a*3] = buf[a*3+1] = buf[a*3+2] = '.';
+ buf[a * 3] = buf[a * 3 + 1] = buf[a * 3 + 2] = '.';
++a;
} // end if
- buf[a*3] = '\0';
+ buf[a * 3] = '\0';
debugC(DEBUG_IMUSE, "[%02d] SysEx:%s", _id, buf);
}
@@ -814,7 +816,7 @@ int Player::query_part_param(int param, byte chan) {
return part->_vol;
case 16:
// FIXME: Need to know where this occurs...
-error("Trying to cast instrument (%d, %d) -- please tell Fingolfin", param, chan);
+ error("Trying to cast instrument (%d, %d) -- please tell Fingolfin", param, chan);
// In old versions of the code, this used to return part->_program.
// This was changed in revision 2.29 of imuse.cpp (where this code used
// to reside).
@@ -845,9 +847,8 @@ void Player::onTimer() {
uint beat_index = target_tick / TICKS_PER_BEAT + 1;
uint tick_index = target_tick % TICKS_PER_BEAT;
- if (_loop_counter &&(beat_index > _loop_from_beat ||
- (beat_index == _loop_from_beat && tick_index >= _loop_from_tick)))
- {
+ if (_loop_counter && (beat_index > _loop_from_beat ||
+ (beat_index == _loop_from_beat && tick_index >= _loop_from_tick))) {
_loop_counter--;
jump(_track_index, _loop_to_beat, _loop_to_tick);
}
@@ -891,15 +892,15 @@ int Player::addParameterFader(int param, int target, int time) {
// target = target * 128 / 100;
break;
- case 127:
- { // FIXME? I *think* this clears all parameter faders.
- ParameterFader *ptr = &_parameterFaders[0];
- int i;
- for (i = ARRAYSIZE(_parameterFaders); i; --i, ++ptr)
- ptr->param = 0;
- return 0;
- }
- break;
+ case 127: {
+ // FIXME? I *think* this clears all parameter faders.
+ ParameterFader *ptr = &_parameterFaders[0];
+ int i;
+ for (i = ARRAYSIZE(_parameterFaders); i; --i, ++ptr)
+ ptr->param = 0;
+ return 0;
+ }
+ break;
default:
debug(0, "Player::addParameterFader(%d, %d, %d): Unknown parameter", param, target, time);
@@ -1085,7 +1086,7 @@ void Player::saveLoadWithSerializer(Serializer *ser) {
}
ser->saveLoadEntries(this, playerEntries);
ser->saveLoadArrayOf(_parameterFaders, ARRAYSIZE(_parameterFaders),
- sizeof(ParameterFader), parameterFaderEntries);
+ sizeof(ParameterFader), parameterFaderEntries);
return;
}
diff --git a/engines/scumm/imuse/instrument.cpp b/engines/scumm/imuse/instrument.cpp
index 955700fc2b..11bb4e7605 100644
--- a/engines/scumm/imuse/instrument.cpp
+++ b/engines/scumm/imuse/instrument.cpp
@@ -114,14 +114,15 @@ roland_to_gm_map[] = {
// { "trickle4 ", ??? }
};
+// This emulates the percussion bank setup LEC used with the MT-32,
+// where notes 24 - 34 were assigned instruments without reverb.
+// It also fixes problems on GS devices that map sounds to these
+// notes by default.
const byte Instrument::_gmRhythmMap[35] = {
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 36, 37, 38, 39, 40, 41, 66, 47,
- 65, 48, 56};
- // This emulates the percussion bank setup LEC used with the MT-32,
- // where notes 24 - 34 were assigned instruments without reverb.
- // It also fixes problems on GS devices that map sounds to these
- // notes by default.
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 36, 37, 38, 39, 40, 41, 66, 47,
+ 65, 48, 56
+};
class Instrument_Program : public InstrumentInternal {
private:
@@ -136,15 +137,16 @@ public:
void copy_to(Instrument *dest) { dest->program(_program, _mt32); }
bool is_valid() {
return (_program < 128) &&
- ((_native_mt32 == _mt32) || _native_mt32
- ? (MidiDriver::_gmToMt32[_program] < 128)
- : (MidiDriver::_mt32ToGm[_program] < 128)); }
+ ((_native_mt32 == _mt32) || _native_mt32
+ ? (MidiDriver::_gmToMt32[_program] < 128)
+ : (MidiDriver::_mt32ToGm[_program] < 128));
+ }
};
class Instrument_AdLib : public InstrumentInternal {
private:
-#include "common/pack-start.h" // START STRUCT PACKING
+#include "common/pack-start.h" // START STRUCT PACKING
struct AdLibInstrument {
byte flags_1;
@@ -159,13 +161,17 @@ private:
byte waveform_2;
byte feedback;
byte flags_a;
- struct { byte a,b,c,d,e,f,g,h; } extra_a;
+ struct {
+ byte a, b, c, d, e, f, g, h;
+ } extra_a;
byte flags_b;
- struct { byte a,b,c,d,e,f,g,h; } extra_b;
+ struct {
+ byte a, b, c, d, e, f, g, h;
+ } extra_b;
byte duration;
} PACKED_STRUCT;
-#include "common/pack-end.h" // END STRUCT PACKING
+#include "common/pack-end.h" // END STRUCT PACKING
AdLibInstrument _instrument;
@@ -181,7 +187,7 @@ public:
class Instrument_Roland : public InstrumentInternal {
private:
-#include "common/pack-start.h" // START STRUCT PACKING
+#include "common/pack-start.h" // START STRUCT PACKING
struct RolandInstrument {
byte roland_id;
@@ -242,11 +248,11 @@ private:
byte checksum;
} PACKED_STRUCT;
-#include "common/pack-end.h" // END STRUCT PACKING
+#include "common/pack-end.h" // END STRUCT PACKING
RolandInstrument _instrument;
- char _instrument_name [11];
+ char _instrument_name[11];
uint8 getEquivalentGM();
@@ -259,6 +265,19 @@ public:
bool is_valid() { return (_native_mt32 ? true : (_instrument_name[0] != '\0')); }
};
+class Instrument_PcSpk : public InstrumentInternal {
+public:
+ Instrument_PcSpk(const byte *data);
+ Instrument_PcSpk(Serializer *s);
+ void saveOrLoad(Serializer *s);
+ void send(MidiChannel *mc);
+ void copy_to(Instrument *dest) { dest->pcspk((byte *)&_instrument); }
+ bool is_valid() { return true; }
+
+private:
+ byte _instrument[23];
+};
+
////////////////////////////////////////
//
// Instrument class members
@@ -299,7 +318,15 @@ void Instrument::roland(const byte *instrument) {
_instrument = new Instrument_Roland(instrument);
}
-void Instrument::saveOrLoad (Serializer *s) {
+void Instrument::pcspk(const byte *instrument) {
+ clear();
+ if (!instrument)
+ return;
+ _type = itPcSpk;
+ _instrument = new Instrument_PcSpk(instrument);
+}
+
+void Instrument::saveOrLoad(Serializer *s) {
if (s->isSaving()) {
s->saveByte(_type);
if (_instrument)
@@ -319,6 +346,9 @@ void Instrument::saveOrLoad (Serializer *s) {
case itRoland:
_instrument = new Instrument_Roland(s);
break;
+ case itPcSpk:
+ _instrument = new Instrument_PcSpk(s);
+ break;
default:
warning("No known instrument classification #%d", (int)_type);
_type = itNone;
@@ -333,8 +363,8 @@ void Instrument::saveOrLoad (Serializer *s) {
////////////////////////////////////////
Instrument_Program::Instrument_Program(byte program, bool mt32) :
-_program (program),
-_mt32 (mt32) {
+ _program(program),
+ _mt32(mt32) {
if (program > 127)
_program = 255;
}
@@ -413,7 +443,7 @@ Instrument_Roland::Instrument_Roland(const byte *data) {
Instrument_Roland::Instrument_Roland(Serializer *s) {
_instrument_name[0] = '\0';
if (!s->isSaving())
- saveOrLoad (s);
+ saveOrLoad(s);
else
memset(&_instrument, 0, sizeof(_instrument));
}
@@ -470,4 +500,32 @@ uint8 Instrument_Roland::getEquivalentGM() {
return 255;
}
+////////////////////////////////////////
+//
+// Instrument_PcSpk class members
+//
+////////////////////////////////////////
+
+Instrument_PcSpk::Instrument_PcSpk(const byte *data) {
+ memcpy(_instrument, data, sizeof(_instrument));
+}
+
+Instrument_PcSpk::Instrument_PcSpk(Serializer *s) {
+ if (!s->isSaving())
+ saveOrLoad(s);
+ else
+ memset(_instrument, 0, sizeof(_instrument));
+}
+
+void Instrument_PcSpk::saveOrLoad(Serializer *s) {
+ if (s->isSaving())
+ s->saveBytes(_instrument, sizeof(_instrument));
+ else
+ s->loadBytes(_instrument, sizeof(_instrument));
+}
+
+void Instrument_PcSpk::send(MidiChannel *mc) {
+ mc->sysEx_customInstrument('SPK ', (byte *)&_instrument);
+}
+
} // End of namespace Scumm
diff --git a/engines/scumm/imuse/instrument.h b/engines/scumm/imuse/instrument.h
index 3555d319e6..a855c64155 100644
--- a/engines/scumm/imuse/instrument.h
+++ b/engines/scumm/imuse/instrument.h
@@ -39,7 +39,6 @@ public:
virtual void send(MidiChannel *mc) = 0;
virtual void copy_to(Instrument *dest) = 0;
virtual bool is_valid() = 0;
- virtual operator int() { return 255; }
};
class Instrument {
@@ -52,10 +51,11 @@ public:
itNone = 0,
itProgram = 1,
itAdLib = 2,
- itRoland = 3
+ itRoland = 3,
+ itPcSpk = 4
};
- Instrument() : _type (0), _instrument (0) { }
+ Instrument() : _type(0), _instrument(0) { }
~Instrument() { delete _instrument; }
static void nativeMT32(bool native);
static const byte _gmRhythmMap[35];
@@ -71,6 +71,7 @@ public:
void program(byte program, bool mt32);
void adlib(const byte *instrument);
void roland(const byte *instrument);
+ void pcspk(const byte *instrument);
byte getType() { return _type; }
bool isValid() { return (_instrument ? _instrument->is_valid() : false); }
diff --git a/engines/scumm/imuse/pcspk.cpp b/engines/scumm/imuse/pcspk.cpp
new file mode 100644
index 0000000000..01e2ab3b7d
--- /dev/null
+++ b/engines/scumm/imuse/pcspk.cpp
@@ -0,0 +1,835 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "scumm/imuse/pcspk.h"
+
+#include "common/util.h"
+
+namespace Scumm {
+
+PcSpkDriver::PcSpkDriver(Audio::Mixer *mixer)
+ : MidiDriver_Emulated(mixer), _pcSpk(mixer->getOutputRate()) {
+}
+
+PcSpkDriver::~PcSpkDriver() {
+ close();
+}
+
+int PcSpkDriver::open() {
+ if (_isOpen)
+ return MERR_ALREADY_OPEN;
+
+ MidiDriver_Emulated::open();
+
+ for (uint i = 0; i < 6; ++i)
+ _channels[i].init(this, i);
+ _activeChannel = 0;
+ _effectTimer = 0;
+ _randBase = 1;
+
+ // We need to take care we only send note frequencies, when the internal
+ // settings actually changed, thus we need some extra state to keep track
+ // of that.
+ _lastActiveChannel = 0;
+ _lastActiveOut = 0;
+
+ // We set the output sound type to music here to allow sound volume
+ // adjustment. The drawback here is that we can not control the music and
+ // sfx separately here. But the AdLib output has the same issue so it
+ // should not be that bad.
+ _mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+ return 0;
+}
+
+void PcSpkDriver::close() {
+ if (!_isOpen)
+ return;
+ _isOpen = false;
+
+ _mixer->stopHandle(_mixerSoundHandle);
+}
+
+void PcSpkDriver::send(uint32 d) {
+ assert((d & 0x0F) < 6);
+ _channels[(d & 0x0F)].send(d);
+}
+
+void PcSpkDriver::sysEx_customInstrument(byte channel, uint32 type, const byte *instr) {
+ assert(channel < 6);
+ if (type == 'SPK ')
+ _channels[channel].sysEx_customInstrument(type, instr);
+}
+
+MidiChannel *PcSpkDriver::allocateChannel() {
+ for (uint i = 0; i < 6; ++i) {
+ if (_channels[i].allocate())
+ return &_channels[i];
+ }
+
+ return 0;
+}
+
+void PcSpkDriver::generateSamples(int16 *buf, int len) {
+ _pcSpk.readBuffer(buf, len);
+}
+
+void PcSpkDriver::onTimer() {
+ if (!_activeChannel)
+ return;
+
+ for (uint i = 0; i < 6; ++i) {
+ OutputChannel &out = _channels[i]._out;
+
+ if (!out.active)
+ continue;
+
+ if (out.length == 0 || --out.length != 0) {
+ if (out.unkB && out.unkC) {
+ out.unkA += out.unkB;
+ if (out.instrument)
+ out.unkE = ((int8)out.instrument[out.unkA] * out.unkC) >> 4;
+ }
+
+ ++_effectTimer;
+ if (_effectTimer > 3) {
+ _effectTimer = 0;
+
+ if (out.effectEnvelopeA.state)
+ updateEffectGenerator(_channels[i], out.effectEnvelopeA, out.effectDefA);
+ if (out.effectEnvelopeB.state)
+ updateEffectGenerator(_channels[i], out.effectEnvelopeB, out.effectDefB);
+ }
+ } else {
+ out.active = 0;
+ updateNote();
+ return;
+ }
+ }
+
+ if (_activeChannel->_tl) {
+ output((_activeChannel->_out.note << 7) + _activeChannel->_pitchBend + _activeChannel->_out.unk60 + _activeChannel->_out.unkE);
+ } else {
+ _pcSpk.stop();
+ _lastActiveChannel = 0;
+ _lastActiveOut = 0;
+ }
+}
+
+void PcSpkDriver::updateNote() {
+ uint8 priority = 0;
+ _activeChannel = 0;
+ for (uint i = 0; i < 6; ++i) {
+ if (_channels[i]._allocated && _channels[i]._out.active && _channels[i]._priority >= priority) {
+ priority = _channels[i]._priority;
+ _activeChannel = &_channels[i];
+ }
+ }
+
+ if (_activeChannel == 0 || _activeChannel->_tl == 0) {
+ _pcSpk.stop();
+ _lastActiveChannel = 0;
+ _lastActiveOut = 0;
+ } else {
+ output(_activeChannel->_pitchBend + (_activeChannel->_out.note << 7));
+ }
+}
+
+void PcSpkDriver::output(uint16 out) {
+ byte v1 = (out >> 7) & 0xFF;
+ byte v2 = (out >> 2) & 0x1E;
+
+ byte shift = _outputTable1[v1];
+ uint16 indexBase = _outputTable2[v1] << 5;
+ uint16 frequency = _frequencyTable[(indexBase + v2) / 2] >> shift;
+
+ // Only output in case the active channel changed or the frequency changed.
+ // This is not faithful to the original. Since our timings differ we would
+ // get distorted sound otherwise though.
+ if (_lastActiveChannel != _activeChannel || _lastActiveOut != out) {
+ _pcSpk.play(Audio::PCSpeaker::kWaveFormSquare, 1193180 / frequency, -1);
+ _lastActiveChannel = _activeChannel;
+ _lastActiveOut = out;
+ }
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::init(PcSpkDriver *owner, byte channel) {
+ _owner = owner;
+ _channel = channel;
+ _allocated = false;
+ memset(&_out, 0, sizeof(_out));
+}
+
+bool PcSpkDriver::MidiChannel_PcSpk::allocate() {
+ if (_allocated)
+ return false;
+
+ memset(&_out, 0, sizeof(_out));
+ memset(_instrument, 0, sizeof(_instrument));
+ _out.effectDefA.envelope = &_out.effectEnvelopeA;
+ _out.effectDefB.envelope = &_out.effectEnvelopeB;
+
+ _allocated = true;
+ return true;
+}
+
+MidiDriver *PcSpkDriver::MidiChannel_PcSpk::device() {
+ return _owner;
+}
+
+byte PcSpkDriver::MidiChannel_PcSpk::getNumber() {
+ return _channel;
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::release() {
+ _out.active = 0;
+ _allocated = false;
+ _owner->updateNote();
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::send(uint32 b) {
+ uint8 type = b & 0xF0;
+ uint8 p1 = (b >> 8) & 0xFF;
+ uint8 p2 = (b >> 16) & 0xFF;
+
+ switch (type) {
+ case 0x80:
+ noteOff(p1);
+ break;
+
+ case 0x90:
+ if (p2)
+ noteOn(p1, p2);
+ else
+ noteOff(p1);
+ break;
+
+ case 0xB0:
+ controlChange(p1, p2);
+ break;
+
+ case 0xE0:
+ pitchBend((p1 | (p2 << 7)) - 0x2000);
+ break;
+
+ default:
+ break;
+ }
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::noteOff(byte note) {
+ if (!_allocated)
+ return;
+
+ if (_sustain) {
+ if (_out.note == note)
+ _out.sustainNoteOff = 1;
+ } else {
+ if (_out.note == note) {
+ _out.active = 0;
+ _owner->updateNote();
+ }
+ }
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::noteOn(byte note, byte velocity) {
+ if (!_allocated)
+ return;
+
+ _out.note = note;
+ _out.sustainNoteOff = 0;
+ _out.length = _instrument[0];
+
+ if (_instrument[4] * 256 < ARRAYSIZE(PcSpkDriver::_outInstrumentData))
+ _out.instrument = _owner->_outInstrumentData + _instrument[4] * 256;
+ else
+ _out.instrument = 0;
+
+ _out.unkA = 0;
+ _out.unkB = _instrument[1];
+ _out.unkC = _instrument[2];
+ _out.unkE = 0;
+ _out.unk60 = 0;
+ _out.active = 1;
+
+ // In case we get a note on event on the last active channel, we reset the
+ // last active channel, thus we assure the frequency is correctly set, even
+ // when the same note was sent.
+ if (_owner->_lastActiveChannel == this) {
+ _owner->_lastActiveChannel = 0;
+ _owner->_lastActiveOut = 0;
+ }
+ _owner->updateNote();
+
+ _out.unkC += PcSpkDriver::getEffectModifier(_instrument[3] + ((velocity & 0xFE) << 4));
+ if (_out.unkC > 63)
+ _out.unkC = 63;
+
+ if ((_instrument[5] & 0x80) != 0)
+ _owner->setupEffects(*this, _out.effectEnvelopeA, _out.effectDefA, _instrument[5], _instrument + 6);
+
+ if ((_instrument[14] & 0x80) != 0)
+ _owner->setupEffects(*this, _out.effectEnvelopeB, _out.effectDefB, _instrument[14], _instrument + 15);
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::programChange(byte program) {
+ // Nothing to implement here, the iMuse code takes care of passing us the
+ // instrument data.
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::pitchBend(int16 bend) {
+ _pitchBend = (bend * _pitchBendFactor) >> 6;
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::controlChange(byte control, byte value) {
+ switch (control) {
+ case 1:
+ if (_out.effectEnvelopeA.state && _out.effectDefA.useModWheel)
+ _out.effectEnvelopeA.modWheelState = (value >> 2);
+ if (_out.effectEnvelopeB.state && _out.effectDefB.useModWheel)
+ _out.effectEnvelopeB.modWheelState = (value >> 2);
+ break;
+
+ case 7:
+ _tl = value;
+ if (_owner->_activeChannel == this) {
+ if (_tl == 0) {
+ _owner->_lastActiveChannel = 0;
+ _owner->_lastActiveOut = 0;
+ _owner->_pcSpk.stop();
+ } else {
+ _owner->output((_out.note << 7) + _pitchBend + _out.unk60 + _out.unkE);
+ }
+ }
+ break;
+
+ case 64:
+ _sustain = value;
+ if (!value && _out.sustainNoteOff) {
+ _out.active = 0;
+ _owner->updateNote();
+ }
+ break;
+
+ case 123:
+ _out.active = 0;
+ _owner->updateNote();
+ break;
+
+ default:
+ break;
+ }
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::pitchBendFactor(byte value) {
+ _pitchBendFactor = value;
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::priority(byte value) {
+ _priority = value;
+}
+
+void PcSpkDriver::MidiChannel_PcSpk::sysEx_customInstrument(uint32 type, const byte *instr) {
+ memcpy(_instrument, instr, sizeof(_instrument));
+}
+
+uint8 PcSpkDriver::getEffectModifier(uint16 level) {
+ uint8 base = level / 32;
+ uint8 index = level % 32;
+
+ if (index == 0)
+ return 0;
+
+ return (base * (index + 1)) >> 5;
+}
+
+int16 PcSpkDriver::getEffectModLevel(int16 level, int8 mod) {
+ if (!mod) {
+ return 0;
+ } else if (mod == 31) {
+ return level;
+ } else if (level < -63 || level > 63) {
+ return (mod * (level + 1)) >> 6;
+ } else if (mod < 0) {
+ if (level < 0)
+ return getEffectModifier(((-level) << 5) - mod);
+ else
+ return -getEffectModifier((level << 5) - mod);
+ } else {
+ if (level < 0)
+ return -getEffectModifier(((-level) << 5) + mod);
+ else
+ return getEffectModifier(((-level) << 5) + mod);
+ }
+}
+
+int16 PcSpkDriver::getRandMultipy(int16 input) {
+ if (_randBase & 1)
+ _randBase = (_randBase >> 1) ^ 0xB8;
+ else
+ _randBase >>= 1;
+
+ return (_randBase * input) >> 8;
+}
+
+void PcSpkDriver::setupEffects(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def, byte flags, const byte *data) {
+ def.phase = 0;
+ def.useModWheel = flags & 0x40;
+ env.loop = flags & 0x20;
+ def.type = flags & 0x1F;
+
+ env.modWheelSensitivity = 31;
+ if (def.useModWheel)
+ env.modWheelState = chan._modWheel >> 2;
+ else
+ env.modWheelState = 31;
+
+ switch (def.type) {
+ case 0:
+ env.maxLevel = 767;
+ env.startLevel = 383;
+ break;
+
+ case 1:
+ env.maxLevel = 31;
+ env.startLevel = 15;
+ break;
+
+ case 2:
+ env.maxLevel = 63;
+ env.startLevel = chan._out.unkB;
+ break;
+
+ case 3:
+ env.maxLevel = 63;
+ env.startLevel = chan._out.unkC;
+ break;
+
+ case 4:
+ env.maxLevel = 3;
+ env.startLevel = chan._instrument[4];
+ break;
+
+ case 5:
+ env.maxLevel = 62;
+ env.startLevel = 31;
+ env.modWheelState = 0;
+ break;
+
+ case 6:
+ env.maxLevel = 31;
+ env.startLevel = 0;
+ env.modWheelSensitivity = 0;
+ break;
+
+ default:
+ break;
+ }
+
+ startEffect(env, data);
+}
+
+void PcSpkDriver::startEffect(EffectEnvelope &env, const byte *data) {
+ env.state = 1;
+ env.currentLevel = 0;
+ env.modWheelLast = 31;
+ env.duration = data[0] * 63;
+
+ env.stateTargetLevels[0] = data[1];
+ env.stateTargetLevels[1] = data[3];
+ env.stateTargetLevels[2] = data[5];
+ env.stateTargetLevels[3] = data[6];
+
+ env.stateModWheelLevels[0] = data[2];
+ env.stateModWheelLevels[1] = data[4];
+ env.stateModWheelLevels[2] = 0;
+ env.stateModWheelLevels[3] = data[7];
+
+ initNextEnvelopeState(env);
+}
+
+void PcSpkDriver::initNextEnvelopeState(EffectEnvelope &env) {
+ uint8 lastState = env.state - 1;
+
+ uint16 stepCount = _effectEnvStepTable[getEffectModifier(((env.stateTargetLevels[lastState] & 0x7F) << 5) + env.modWheelSensitivity)];
+ if (env.stateTargetLevels[lastState] & 0x80)
+ stepCount = getRandMultipy(stepCount);
+ if (!stepCount)
+ stepCount = 1;
+
+ env.stateNumSteps = env.stateStepCounter = stepCount;
+
+ int16 totalChange = 0;
+ if (lastState != 2) {
+ totalChange = getEffectModLevel(env.maxLevel, (env.stateModWheelLevels[lastState] & 0x7F) - 31);
+ if (env.stateModWheelLevels[lastState] & 0x80)
+ totalChange = getRandMultipy(totalChange);
+
+ if (totalChange + env.startLevel > env.maxLevel)
+ totalChange = env.maxLevel - env.startLevel;
+ else if (totalChange + env.startLevel < 0)
+ totalChange = -env.startLevel;
+
+ totalChange -= env.currentLevel;
+ }
+
+ env.changePerStep = totalChange / stepCount;
+ if (totalChange < 0) {
+ totalChange = -totalChange;
+ env.dir = -1;
+ } else {
+ env.dir = 1;
+ }
+ env.changePerStepRem = totalChange % stepCount;
+ env.changeCountRem = 0;
+}
+
+void PcSpkDriver::updateEffectGenerator(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def) {
+ if (advanceEffectEnvelope(env, def) & 1) {
+ switch (def.type) {
+ case 0: case 1:
+ chan._out.unk60 = def.phase << 4;
+ break;
+
+ case 2:
+ chan._out.unkB = (def.phase & 0xFF) + chan._instrument[1];
+ break;
+
+ case 3:
+ chan._out.unkC = (def.phase & 0xFF) + chan._instrument[2];
+ break;
+
+ case 4:
+ if ((chan._instrument[4] + (def.phase & 0xFF)) * 256 < ARRAYSIZE(_outInstrumentData))
+ chan._out.instrument = _outInstrumentData + (chan._instrument[4] + (def.phase & 0xFF)) * 256;
+ else
+ chan._out.instrument = 0;
+ break;
+
+ case 5:
+ env.modWheelState = (def.phase & 0xFF);
+ break;
+
+ case 6:
+ env.modWheelSensitivity = (def.phase & 0xFF);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+uint8 PcSpkDriver::advanceEffectEnvelope(EffectEnvelope &env, EffectDefinition &def) {
+ if (env.duration != 0) {
+ env.duration -= 17;
+ if (env.duration <= 0) {
+ env.state = 0;
+ return 0;
+ }
+ }
+
+ uint8 changedFlags = 0;
+ int16 newLevel = env.currentLevel + env.changePerStep;
+ env.changeCountRem += env.changePerStepRem;
+ if (env.changeCountRem >= env.stateNumSteps) {
+ env.changeCountRem -= env.stateNumSteps;
+ newLevel += env.dir;
+ }
+
+ if (env.currentLevel != newLevel || env.modWheelLast != env.modWheelState) {
+ env.currentLevel = newLevel;
+ env.modWheelLast = env.modWheelState;
+
+ int16 newPhase = getEffectModLevel(newLevel, env.modWheelState);
+ if (def.phase != newPhase) {
+ changedFlags |= 1;
+ def.phase = newPhase;
+ }
+ }
+
+ --env.stateStepCounter;
+ if (!env.stateStepCounter) {
+ ++env.state;
+ if (env.state > 4) {
+ if (env.loop) {
+ env.state = 1;
+ changedFlags |= 2;
+ } else {
+ env.state = 0;
+ return changedFlags;
+ }
+ }
+
+ initNextEnvelopeState(env);
+ }
+
+ return changedFlags;
+}
+
+const byte PcSpkDriver::_outInstrumentData[1024] = {
+ 0x00, 0x03, 0x06, 0x09, 0x0C, 0x0F, 0x12, 0x15,
+ 0x18, 0x1B, 0x1E, 0x21, 0x24, 0x27, 0x2A, 0x2D,
+ 0x30, 0x33, 0x36, 0x39, 0x3B, 0x3E, 0x41, 0x43,
+ 0x46, 0x49, 0x4B, 0x4E, 0x50, 0x52, 0x55, 0x57,
+ 0x59, 0x5B, 0x5E, 0x60, 0x62, 0x64, 0x66, 0x67,
+ 0x69, 0x6B, 0x6C, 0x6E, 0x70, 0x71, 0x72, 0x74,
+ 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7B,
+ 0x7C, 0x7D, 0x7D, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E,
+ 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7D, 0x7D,
+ 0x7C, 0x7B, 0x7B, 0x7A, 0x79, 0x78, 0x77, 0x76,
+ 0x75, 0x74, 0x72, 0x71, 0x70, 0x6E, 0x6C, 0x6B,
+ 0x69, 0x67, 0x66, 0x64, 0x62, 0x60, 0x5E, 0x5B,
+ 0x59, 0x57, 0x55, 0x52, 0x50, 0x4E, 0x4B, 0x49,
+ 0x46, 0x43, 0x41, 0x3E, 0x3B, 0x39, 0x36, 0x33,
+ 0x30, 0x2D, 0x2A, 0x27, 0x24, 0x21, 0x1E, 0x1B,
+ 0x18, 0x15, 0x12, 0x0F, 0x0C, 0x09, 0x06, 0x03,
+ 0x00, 0xFD, 0xFA, 0xF7, 0xF4, 0xF1, 0xEE, 0xEB,
+ 0xE8, 0xE5, 0xE2, 0xDF, 0xDC, 0xD9, 0xD6, 0xD3,
+ 0xD0, 0xCD, 0xCA, 0xC7, 0xC5, 0xC2, 0xBF, 0xBD,
+ 0xBA, 0xB7, 0xB5, 0xB2, 0xB0, 0xAE, 0xAB, 0xA9,
+ 0xA7, 0xA5, 0xA2, 0xA0, 0x9E, 0x9C, 0x9A, 0x99,
+ 0x97, 0x95, 0x94, 0x92, 0x90, 0x8F, 0x8E, 0x8C,
+ 0x8B, 0x8A, 0x89, 0x88, 0x87, 0x86, 0x85, 0x85,
+ 0x84, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x83, 0x83,
+ 0x84, 0x85, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A,
+ 0x8B, 0x8C, 0x8E, 0x8F, 0x90, 0x92, 0x94, 0x95,
+ 0x97, 0x99, 0x9A, 0x9C, 0x9E, 0xA0, 0xA2, 0xA5,
+ 0xA7, 0xA9, 0xAB, 0xAE, 0xB0, 0xB2, 0xB5, 0xB7,
+ 0xBA, 0xBD, 0xBF, 0xC2, 0xC5, 0xC7, 0xCA, 0xCD,
+ 0xD0, 0xD3, 0xD6, 0xD9, 0xDC, 0xDF, 0xE2, 0xE5,
+ 0xE8, 0xEB, 0xEE, 0xF1, 0xF4, 0xF7, 0xFA, 0xFD,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+ 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+ 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
+ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
+ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+ 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+ 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
+ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
+ 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
+ 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7,
+ 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
+ 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7,
+ 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
+ 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7,
+ 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
+ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7,
+ 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
+ 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
+ 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
+ 0x29, 0x23, 0xBE, 0x84, 0xE1, 0x6C, 0xD6, 0xAE,
+ 0x52, 0x90, 0x49, 0xF1, 0xF1, 0xBB, 0xE9, 0xEB,
+ 0xB3, 0xA6, 0xDB, 0x3C, 0x87, 0x0C, 0x3E, 0x99,
+ 0x24, 0x5E, 0x0D, 0x1C, 0x06, 0xB7, 0x47, 0xDE,
+ 0xB3, 0x12, 0x4D, 0xC8, 0x43, 0xBB, 0x8B, 0xA6,
+ 0x1F, 0x03, 0x5A, 0x7D, 0x09, 0x38, 0x25, 0x1F,
+ 0x5D, 0xD4, 0xCB, 0xFC, 0x96, 0xF5, 0x45, 0x3B,
+ 0x13, 0x0D, 0x89, 0x0A, 0x1C, 0xDB, 0xAE, 0x32,
+ 0x20, 0x9A, 0x50, 0xEE, 0x40, 0x78, 0x36, 0xFD,
+ 0x12, 0x49, 0x32, 0xF6, 0x9E, 0x7D, 0x49, 0xDC,
+ 0xAD, 0x4F, 0x14, 0xF2, 0x44, 0x40, 0x66, 0xD0,
+ 0x6B, 0xC4, 0x30, 0xB7, 0x32, 0x3B, 0xA1, 0x22,
+ 0xF6, 0x22, 0x91, 0x9D, 0xE1, 0x8B, 0x1F, 0xDA,
+ 0xB0, 0xCA, 0x99, 0x02, 0xB9, 0x72, 0x9D, 0x49,
+ 0x2C, 0x80, 0x7E, 0xC5, 0x99, 0xD5, 0xE9, 0x80,
+ 0xB2, 0xEA, 0xC9, 0xCC, 0x53, 0xBF, 0x67, 0xD6,
+ 0xBF, 0x14, 0xD6, 0x7E, 0x2D, 0xDC, 0x8E, 0x66,
+ 0x83, 0xEF, 0x57, 0x49, 0x61, 0xFF, 0x69, 0x8F,
+ 0x61, 0xCD, 0xD1, 0x1E, 0x9D, 0x9C, 0x16, 0x72,
+ 0x72, 0xE6, 0x1D, 0xF0, 0x84, 0x4F, 0x4A, 0x77,
+ 0x02, 0xD7, 0xE8, 0x39, 0x2C, 0x53, 0xCB, 0xC9,
+ 0x12, 0x1E, 0x33, 0x74, 0x9E, 0x0C, 0xF4, 0xD5,
+ 0xD4, 0x9F, 0xD4, 0xA4, 0x59, 0x7E, 0x35, 0xCF,
+ 0x32, 0x22, 0xF4, 0xCC, 0xCF, 0xD3, 0x90, 0x2D,
+ 0x48, 0xD3, 0x8F, 0x75, 0xE6, 0xD9, 0x1D, 0x2A,
+ 0xE5, 0xC0, 0xF7, 0x2B, 0x78, 0x81, 0x87, 0x44,
+ 0x0E, 0x5F, 0x50, 0x00, 0xD4, 0x61, 0x8D, 0xBE,
+ 0x7B, 0x05, 0x15, 0x07, 0x3B, 0x33, 0x82, 0x1F,
+ 0x18, 0x70, 0x92, 0xDA, 0x64, 0x54, 0xCE, 0xB1,
+ 0x85, 0x3E, 0x69, 0x15, 0xF8, 0x46, 0x6A, 0x04,
+ 0x96, 0x73, 0x0E, 0xD9, 0x16, 0x2F, 0x67, 0x68,
+ 0xD4, 0xF7, 0x4A, 0x4A, 0xD0, 0x57, 0x68, 0x76
+};
+
+const byte PcSpkDriver::_outputTable1[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3,
+ 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5,
+ 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7
+};
+
+const byte PcSpkDriver::_outputTable2[] = {
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 0, 1, 2, 3,
+ 4, 5, 6, 7
+};
+
+const uint16 PcSpkDriver::_effectEnvStepTable[] = {
+ 1, 2, 4, 5,
+ 6, 7, 8, 9,
+ 10, 12, 14, 16,
+ 18, 21, 24, 30,
+ 36, 50, 64, 82,
+ 100, 136, 160, 192,
+ 240, 276, 340, 460,
+ 600, 860, 1200, 1600
+};
+
+const uint16 PcSpkDriver::_frequencyTable[] = {
+ 0x8E84, 0x8E00, 0x8D7D, 0x8CFA,
+ 0x8C78, 0x8BF7, 0x8B76, 0x8AF5,
+ 0x8A75, 0x89F5, 0x8976, 0x88F7,
+ 0x8879, 0x87FB, 0x877D, 0x8700,
+ 0x8684, 0x8608, 0x858C, 0x8511,
+ 0x8496, 0x841C, 0x83A2, 0x8328,
+ 0x82AF, 0x8237, 0x81BF, 0x8147,
+ 0x80D0, 0x8059, 0x7FE3, 0x7F6D,
+ 0x7EF7, 0x7E82, 0x7E0D, 0x7D99,
+ 0x7D25, 0x7CB2, 0x7C3F, 0x7BCC,
+ 0x7B5A, 0x7AE8, 0x7A77, 0x7A06,
+ 0x7995, 0x7925, 0x78B5, 0x7846,
+ 0x77D7, 0x7768, 0x76FA, 0x768C,
+ 0x761F, 0x75B2, 0x7545, 0x74D9,
+ 0x746D, 0x7402, 0x7397, 0x732C,
+ 0x72C2, 0x7258, 0x71EF, 0x7186,
+ 0x711D, 0x70B5, 0x704D, 0x6FE5,
+ 0x6F7E, 0x6F17, 0x6EB0, 0x6E4A,
+ 0x6DE5, 0x6D7F, 0x6D1A, 0x6CB5,
+ 0x6C51, 0x6BED, 0x6B8A, 0x6B26,
+ 0x6AC4, 0x6A61, 0x69FF, 0x699D,
+ 0x693C, 0x68DB, 0x687A, 0x681A,
+ 0x67BA, 0x675A, 0x66FA, 0x669B,
+ 0x663D, 0x65DF, 0x6581, 0x6523,
+ 0x64C6, 0x6469, 0x640C, 0x63B0,
+ 0x6354, 0x62F8, 0x629D, 0x6242,
+ 0x61E7, 0x618D, 0x6133, 0x60D9,
+ 0x6080, 0x6027, 0x5FCE, 0x5F76,
+ 0x5F1E, 0x5EC6, 0x5E6E, 0x5E17,
+ 0x5DC1, 0x5D6A, 0x5D14, 0x5CBE,
+ 0x5C68, 0x5C13, 0x5BBE, 0x5B6A,
+ 0x5B15, 0x5AC1, 0x5A6E, 0x5A1A,
+ 0x59C7, 0x5974, 0x5922, 0x58CF,
+ 0x587D, 0x582C, 0x57DA, 0x5789,
+ 0x5739, 0x56E8, 0x5698, 0x5648,
+ 0x55F9, 0x55A9, 0x555A, 0x550B,
+ 0x54BD, 0x546F, 0x5421, 0x53D3,
+ 0x5386, 0x5339, 0x52EC, 0x52A0,
+ 0x5253, 0x5207, 0x51BC, 0x5170,
+ 0x5125, 0x50DA, 0x5090, 0x5046,
+ 0x4FFB, 0x4FB2, 0x4F68, 0x4F1F,
+ 0x4ED6, 0x4E8D, 0x4E45, 0x4DFC,
+ 0x4DB5, 0x4D6D, 0x4D25, 0x4CDE,
+ 0x4C97, 0x4C51, 0x4C0A, 0x4BC4,
+ 0x4B7E, 0x4B39, 0x4AF3, 0x4AAE,
+ 0x4A69, 0x4A24, 0x49E0, 0x499C,
+ 0x4958, 0x4914, 0x48D1, 0x488E,
+ 0x484B, 0x4808, 0x47C6, 0x4783
+};
+
+} // End of namespace Scumm
+
diff --git a/engines/scumm/imuse/pcspk.h b/engines/scumm/imuse/pcspk.h
new file mode 100644
index 0000000000..e77ac8c1bf
--- /dev/null
+++ b/engines/scumm/imuse/pcspk.h
@@ -0,0 +1,161 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef SCUMM_IMUSE_PCSPK_H
+#define SCUMM_IMUSE_PCSPK_H
+
+#include "audio/softsynth/emumidi.h"
+#include "audio/softsynth/pcspk.h"
+
+namespace Scumm {
+
+class PcSpkDriver : public MidiDriver_Emulated {
+public:
+ PcSpkDriver(Audio::Mixer *mixer);
+ ~PcSpkDriver();
+
+ virtual int open();
+ virtual void close();
+
+ virtual void send(uint32 d);
+ virtual void sysEx_customInstrument(byte channel, uint32 type, const byte *instr);
+
+ virtual MidiChannel *allocateChannel();
+ virtual MidiChannel *getPercussionChannel() { return 0; }
+
+ bool isStereo() const { return _pcSpk.isStereo(); }
+ int getRate() const { return _pcSpk.getRate(); }
+protected:
+ void generateSamples(int16 *buf, int len);
+ void onTimer();
+
+private:
+ Audio::PCSpeaker _pcSpk;
+ int _effectTimer;
+ uint8 _randBase;
+
+ void updateNote();
+ void output(uint16 out);
+
+ static uint8 getEffectModifier(uint16 level);
+ int16 getEffectModLevel(int16 level, int8 mod);
+ int16 getRandMultipy(int16 input);
+
+ struct EffectEnvelope {
+ uint8 state;
+ int16 currentLevel;
+ int16 duration;
+ int16 maxLevel;
+ int16 startLevel;
+ uint8 loop;
+ uint8 stateTargetLevels[4];
+ uint8 stateModWheelLevels[4];
+ uint8 modWheelSensitivity;
+ uint8 modWheelState;
+ uint8 modWheelLast;
+ int16 stateNumSteps;
+ int16 stateStepCounter;
+ int16 changePerStep;
+ int8 dir;
+ int16 changePerStepRem;
+ int16 changeCountRem;
+ };
+
+ struct EffectDefinition {
+ int16 phase;
+ uint8 type;
+ uint8 useModWheel;
+ EffectEnvelope *envelope;
+ };
+
+ struct OutputChannel {
+ uint8 active;
+ uint8 note;
+ uint8 sustainNoteOff;
+ uint8 length;
+ const uint8 *instrument;
+ uint8 unkA;
+ uint8 unkB;
+ uint8 unkC;
+ int16 unkE;
+ EffectEnvelope effectEnvelopeA;
+ EffectDefinition effectDefA;
+ EffectEnvelope effectEnvelopeB;
+ EffectDefinition effectDefB;
+ int16 unk60;
+ };
+
+ struct MidiChannel_PcSpk : public MidiChannel {
+ virtual MidiDriver *device();
+ virtual byte getNumber();
+ virtual void release();
+
+ virtual void send(uint32 b);
+ virtual void noteOff(byte note);
+ virtual void noteOn(byte note, byte velocity);
+ virtual void programChange(byte program);
+ virtual void pitchBend(int16 bend);
+ virtual void controlChange(byte control, byte value);
+ virtual void pitchBendFactor(byte value);
+ virtual void priority(byte value);
+ virtual void sysEx_customInstrument(uint32 type, const byte *instr);
+
+ void init(PcSpkDriver *owner, byte channel);
+ bool allocate();
+
+ PcSpkDriver *_owner;
+ bool _allocated;
+ byte _channel;
+
+ OutputChannel _out;
+ uint8 _instrument[23];
+ uint8 _programNr;
+ uint8 _priority;
+ uint8 _tl;
+ uint8 _modWheel;
+ uint8 _sustain;
+ uint8 _pitchBendFactor;
+ int16 _pitchBend;
+ };
+
+ void setupEffects(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def, byte flags, const byte *data);
+ void startEffect(EffectEnvelope &env, const byte *data);
+ void initNextEnvelopeState(EffectEnvelope &env);
+ void updateEffectGenerator(MidiChannel_PcSpk &chan, EffectEnvelope &env, EffectDefinition &def);
+ uint8 advanceEffectEnvelope(EffectEnvelope &env, EffectDefinition &def);
+
+ MidiChannel_PcSpk _channels[6];
+ MidiChannel_PcSpk *_activeChannel;
+
+ MidiChannel_PcSpk *_lastActiveChannel;
+ uint16 _lastActiveOut;
+
+ static const byte _outInstrumentData[1024];
+ static const byte _outputTable1[];
+ static const byte _outputTable2[];
+ static const uint16 _effectEnvStepTable[];
+ static const uint16 _frequencyTable[];
+};
+
+} // End of namespace Scumm
+
+#endif
+
diff --git a/engines/scumm/imuse/sysex_samnmax.cpp b/engines/scumm/imuse/sysex_samnmax.cpp
index 4c4219e7bb..a4f525da56 100644
--- a/engines/scumm/imuse/sysex_samnmax.cpp
+++ b/engines/scumm/imuse/sysex_samnmax.cpp
@@ -53,8 +53,7 @@ void sysexHandler_SamNMax(Player *player, const byte *msg, uint16 len) {
// something magical is supposed to happen....
for (a = 0; a < ARRAYSIZE(se->_snm_triggers); ++a) {
if (se->_snm_triggers[a].sound == player->_id &&
- se->_snm_triggers[a].id == *p)
- {
+ se->_snm_triggers[a].id == *p) {
se->_snm_triggers[a].sound = se->_snm_triggers[a].id = 0;
se->doCommand(8, se->_snm_triggers[a].command);
break;
diff --git a/engines/scumm/imuse/sysex_scumm.cpp b/engines/scumm/imuse/sysex_scumm.cpp
index 4eb3bee93c..85ffc86f47 100644
--- a/engines/scumm/imuse/sysex_scumm.cpp
+++ b/engines/scumm/imuse/sysex_scumm.cpp
@@ -49,50 +49,52 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) {
switch (code = *p++) {
case 0:
// Allocate new part.
- // There are 17 bytes of useful information here.
+ // There are 8 bytes (after decoding!) of useful information here.
// Here is what we know about them so far:
- // BYTE 00: Channel #
- // BYTE 02: BIT 01(0x01): Part on?(1 = yes)
+ // BYTE 0: Channel #
+ // BYTE 1: BIT 01(0x01): Part on?(1 = yes)
// BIT 02(0x02): Reverb? (1 = yes) [bug #1088045]
- // BYTE 04: Priority adjustment [guessing]
- // BYTE 05: Volume(upper 4 bits) [guessing]
- // BYTE 06: Volume(lower 4 bits) [guessing]
- // BYTE 07: Pan(upper 4 bits) [bug #1088045]
- // BYTE 08: Pan(lower 4 bits) [bug #1088045]
- // BYTE 09: BIT 04(0x08): Percussion?(1 = yes)
- // BYTE 13: Pitchbend range(upper 4 bits) [bug #1088045]
- // BYTE 14: Pitchbend range(lower 4 bits) [bug #1088045]
- // BYTE 15: Program(upper 4 bits)
- // BYTE 16: Program(lower 4 bits)
-
- // athrxx (05-21-2011):
- // BYTE 9, 10: Transpose (if set to 0x80, this means that part->_transpose_eff will be 0 (also ignoring player->_transpose)
- // BYTE 11, 12: Detune
+ // BYTE 2: Priority adjustment
+ // BYTE 3: Volume [guessing]
+ // BYTE 4: Pan [bug #1088045]
+ // BYTE 5: BIT 8(0x80): Percussion?(1 = yes) [guessed?]
+ // BYTE 5: Transpose, if set to 0x80(=-1) it means no transpose
+ // BYTE 6: Detune
+ // BYTE 7: Pitchbend factor [bug #1088045]
+ // BYTE 8: Program
part = player->getPart(p[0] & 0x0F);
+ player->decode_sysex_bytes(p + 1, buf + 1, len - 1);
if (part) {
- part->set_onoff(p[2] & 0x01);
- part->effectLevel((p[2] & 0x02) ? 127 : 0);
- part->set_pri(p[4]);
- part->volume((p[5] & 0x0F) << 4 |(p[6] & 0x0F));
- part->set_pan((p[7] & 0x0F) << 4 | (p[8] & 0x0F));
- part->_percussion = player->_isMIDI ? ((p[9] & 0x08) > 0) : false;
- part->set_transpose((p[9] & 0x0F) << 4 | (p[10] & 0x0F));
- part->set_detune((p[11] & 0x0F) << 4 | (p[12] & 0x0F));
- part->pitchBendFactor((p[13] & 0x0F) << 4 | (p[14] & 0x0F));
+ part->set_onoff(buf[1] & 0x01);
+ part->effectLevel((buf[1] & 0x02) ? 127 : 0);
+ part->set_pri(buf[2]);
+ part->volume(buf[3]);
+ part->set_pan(buf[4]);
+ part->_percussion = player->_isMIDI ? ((buf[5] & 0x80) > 0) : false;
+ part->set_transpose(buf[5]);
+ part->set_detune(buf[6]);
+ part->pitchBendFactor(buf[7]);
if (part->_percussion) {
if (part->_mc) {
part->off();
se->reallocateMidiChannels(player->_midi);
}
} else {
- // Even in cases where a program does not seem to be specified,
- // i.e. bytes 15 and 16 are 0, we send a program change because
- // 0 is a valid program number. MI2 tests show that in such
- // cases, a regular program change message always seems to follow
- // anyway.
- if (player->_isMIDI)
- part->_instrument.program((p[15] & 0x0F) << 4 |(p[16] & 0x0F), player->_isMT32);
+ if (player->_isMIDI) {
+ // Even in cases where a program does not seem to be specified,
+ // i.e. bytes 15 and 16 are 0, we send a program change because
+ // 0 is a valid program number. MI2 tests show that in such
+ // cases, a regular program change message always seems to follow
+ // anyway.
+ part->_instrument.program(buf[8], player->_isMT32);
+ } else {
+ // Like the original we set up the instrument data of the
+ // specified program here too. In case the global
+ // instrument data is not loaded already, this will take
+ // care of setting a default instrument too.
+ se->copyGlobalInstrument(buf[8], &part->_instrument);
+ }
part->sendAll();
}
}
@@ -113,11 +115,10 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) {
++p; // Skip hardware type
part = player->getPart(a);
if (part) {
- if (len == 62) {
+ if (len == 62 || len == 48) {
player->decode_sysex_bytes(p, buf, len - 2);
part->set_instrument((byte *)buf);
} else {
- // SPK tracks have len == 48 here, and are not supported
part->programChange(254); // Must be invalid, but not 255 (which is reserved)
}
}
@@ -127,7 +128,8 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) {
p += 2; // Skip hardware type and... whatever came right before it
a = *p++;
player->decode_sysex_bytes(p, buf, len - 3);
- se->setGlobalAdLibInstrument(a, buf);
+ if (len == 63 || len == 49)
+ se->setGlobalInstrument(a, buf);
break;
case 33: // Parameter adjust
@@ -185,10 +187,9 @@ void sysexHandler_Scumm(Player *player, const byte *msg, uint16 len) {
case 80: // Loop
player->decode_sysex_bytes(p + 1, buf, len - 1);
- player->setLoop
- (READ_BE_UINT16(buf), READ_BE_UINT16(buf + 2),
- READ_BE_UINT16(buf + 4), READ_BE_UINT16(buf + 6),
- READ_BE_UINT16(buf + 8));
+ player->setLoop(READ_BE_UINT16(buf), READ_BE_UINT16(buf + 2),
+ READ_BE_UINT16(buf + 4), READ_BE_UINT16(buf + 6),
+ READ_BE_UINT16(buf + 8));
break;
case 81: // End loop