aboutsummaryrefslogtreecommitdiff
path: root/engines/gob
diff options
context:
space:
mode:
Diffstat (limited to 'engines/gob')
-rw-r--r--engines/gob/detection_tables.h4
-rw-r--r--engines/gob/draw_v1.cpp2
-rw-r--r--engines/gob/draw_v2.cpp2
-rw-r--r--engines/gob/global.cpp1
-rw-r--r--engines/gob/global.h1
-rw-r--r--engines/gob/gob.h4
-rw-r--r--engines/gob/init.h1
-rw-r--r--engines/gob/init_fascin.cpp6
-rw-r--r--engines/gob/init_geisha.cpp7
-rw-r--r--engines/gob/init_v1.cpp2
-rw-r--r--engines/gob/init_v2.cpp2
-rw-r--r--engines/gob/inter_fascin.cpp3
-rw-r--r--engines/gob/inter_geisha.cpp5
-rw-r--r--engines/gob/inter_v1.cpp34
-rw-r--r--engines/gob/minigames/geisha/meter.cpp4
-rw-r--r--engines/gob/minigames/geisha/penetration.cpp581
-rw-r--r--engines/gob/minigames/geisha/penetration.h95
-rw-r--r--engines/gob/minigames/geisha/submarine.cpp9
-rw-r--r--engines/gob/minigames/geisha/submarine.h2
-rw-r--r--engines/gob/module.mk2
-rw-r--r--engines/gob/mult.cpp3
-rw-r--r--engines/gob/palanim.cpp93
-rw-r--r--engines/gob/sound/adlib.cpp1044
-rw-r--r--engines/gob/sound/adlib.h312
-rw-r--r--engines/gob/sound/adlplayer.cpp257
-rw-r--r--engines/gob/sound/adlplayer.h85
-rw-r--r--engines/gob/sound/musplayer.cpp391
-rw-r--r--engines/gob/sound/musplayer.h109
-rw-r--r--engines/gob/sound/sound.cpp90
-rw-r--r--engines/gob/sound/sound.h21
30 files changed, 2265 insertions, 907 deletions
diff --git a/engines/gob/detection_tables.h b/engines/gob/detection_tables.h
index 7aa58b9b97..77b54a19cd 100644
--- a/engines/gob/detection_tables.h
+++ b/engines/gob/detection_tables.h
@@ -34,7 +34,7 @@ static const GOBGameDescription gameDescriptions[] = {
GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH)
},
kGameTypeGob1,
- kFeaturesEGA,
+ kFeaturesEGA | kFeaturesAdLib,
0, 0, 0
},
{
@@ -48,7 +48,7 @@ static const GOBGameDescription gameDescriptions[] = {
GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH)
},
kGameTypeGob1,
- kFeaturesEGA,
+ kFeaturesEGA | kFeaturesAdLib,
0, 0, 0
},
{ // Supplied by Theruler76 in bug report #1201233
diff --git a/engines/gob/draw_v1.cpp b/engines/gob/draw_v1.cpp
index fb15fdbc19..878c1dc265 100644
--- a/engines/gob/draw_v1.cpp
+++ b/engines/gob/draw_v1.cpp
@@ -123,7 +123,7 @@ void Draw_v1::animateCursor(int16 cursor) {
(cursorIndex + 1) * _cursorWidth - 1,
_cursorHeight - 1, 0, 0);
CursorMan.replaceCursor(_scummvmCursor->getData(),
- _cursorWidth, _cursorHeight, hotspotX, hotspotY, 0, 1, &_vm->getPixelFormat());
+ _cursorWidth, _cursorHeight, hotspotX, hotspotY, 0, false, &_vm->getPixelFormat());
if (_frontSurface != _backSurface) {
_showCursor = 3;
diff --git a/engines/gob/draw_v2.cpp b/engines/gob/draw_v2.cpp
index ab9a90de8f..d9b7a12639 100644
--- a/engines/gob/draw_v2.cpp
+++ b/engines/gob/draw_v2.cpp
@@ -161,7 +161,7 @@ void Draw_v2::animateCursor(int16 cursor) {
keyColor = _cursorKeyColors[cursorIndex];
CursorMan.replaceCursor(_scummvmCursor->getData(),
- _cursorWidth, _cursorHeight, hotspotX, hotspotY, keyColor, 1, &_vm->getPixelFormat());
+ _cursorWidth, _cursorHeight, hotspotX, hotspotY, keyColor, false, &_vm->getPixelFormat());
if (_doCursorPalettes && _doCursorPalettes[cursorIndex]) {
CursorMan.replaceCursorPalette(_cursorPalettes + (cursorIndex * 256 * 3),
diff --git a/engines/gob/global.cpp b/engines/gob/global.cpp
index 1264c09860..87656a5fad 100644
--- a/engines/gob/global.cpp
+++ b/engines/gob/global.cpp
@@ -111,7 +111,6 @@ Global::Global(GobEngine *vm) : _vm(vm) {
_dontSetPalette = false;
_debugFlag = 0;
- _inVM = 0;
_inter_animDataSize = 10;
diff --git a/engines/gob/global.h b/engines/gob/global.h
index fa2f2c9637..175331dd83 100644
--- a/engines/gob/global.h
+++ b/engines/gob/global.h
@@ -127,7 +127,6 @@ public:
SurfacePtr _primarySurfDesc;
int16 _debugFlag;
- int16 _inVM;
int16 _inter_animDataSize;
diff --git a/engines/gob/gob.h b/engines/gob/gob.h
index ea2323807a..6277585015 100644
--- a/engines/gob/gob.h
+++ b/engines/gob/gob.h
@@ -50,6 +50,10 @@ class StaticTextWidget;
* - Bargon Attack
* - Lost in Time
* - The Bizarre Adventures of Woodruff and the Schnibble
+ * - Fascination
+ * - Urban Runner
+ * - Bambou le sauveur de la jungle
+ * - Geisha
*/
namespace Gob {
diff --git a/engines/gob/init.h b/engines/gob/init.h
index 946a3fa4f1..ac460fd654 100644
--- a/engines/gob/init.h
+++ b/engines/gob/init.h
@@ -62,7 +62,6 @@ public:
~Init_Geisha();
void initVideo();
- void initGame();
};
class Init_v2 : public Init_v1 {
diff --git a/engines/gob/init_fascin.cpp b/engines/gob/init_fascin.cpp
index b87d816406..e6d82faa68 100644
--- a/engines/gob/init_fascin.cpp
+++ b/engines/gob/init_fascin.cpp
@@ -44,10 +44,10 @@ void Init_Fascination::updateConfig() {
}
void Init_Fascination::initGame() {
-// HACK - Suppress ADLIB_FLAG as the MDY/TBR player is not working. suppress
-// the PC Speaker too, as the script checks in the intro for it's presence
+// HACK - Suppress
+// the PC Speaker, as the script checks in the intro for it's presence
// to play or not some noices.
- _vm->_global->_soundFlags = MIDI_FLAG | BLASTER_FLAG;
+ _vm->_global->_soundFlags = MIDI_FLAG | BLASTER_FLAG | ADLIB_FLAG;
Init::initGame();
}
diff --git a/engines/gob/init_geisha.cpp b/engines/gob/init_geisha.cpp
index b5bbcff400..01081a5af6 100644
--- a/engines/gob/init_geisha.cpp
+++ b/engines/gob/init_geisha.cpp
@@ -44,11 +44,4 @@ void Init_Geisha::initVideo() {
_vm->_draw->_transparentCursor = 1;
}
-void Init_Geisha::initGame() {
- // HACK - Since the MDY/TBR player is not working, claim we have no AdLib
- _vm->_global->_soundFlags = 0;
-
- Init::initGame();
-}
-
} // End of namespace Gob
diff --git a/engines/gob/init_v1.cpp b/engines/gob/init_v1.cpp
index 25d521aca6..a8e8cbe2c3 100644
--- a/engines/gob/init_v1.cpp
+++ b/engines/gob/init_v1.cpp
@@ -41,8 +41,6 @@ void Init_v1::initVideo() {
_vm->_global->_mousePresent = 1;
- _vm->_global->_inVM = 0;
-
if ((_vm->_global->_videoMode == 0x13) && !_vm->isEGA())
_vm->_global->_colorCount = 256;
diff --git a/engines/gob/init_v2.cpp b/engines/gob/init_v2.cpp
index 1289d561ea..c204b04a40 100644
--- a/engines/gob/init_v2.cpp
+++ b/engines/gob/init_v2.cpp
@@ -45,8 +45,6 @@ void Init_v2::initVideo() {
_vm->_global->_mousePresent = 1;
- _vm->_global->_inVM = 0;
-
_vm->_global->_colorCount = 16;
if (!_vm->isEGA() &&
((_vm->getPlatform() == Common::kPlatformPC) ||
diff --git a/engines/gob/inter_fascin.cpp b/engines/gob/inter_fascin.cpp
index 081b48fbad..001ec06635 100644
--- a/engines/gob/inter_fascin.cpp
+++ b/engines/gob/inter_fascin.cpp
@@ -248,12 +248,11 @@ void Inter_Fascination::oFascin_playTira(OpGobParams &params) {
void Inter_Fascination::oFascin_loadExtasy(OpGobParams &params) {
_vm->_sound->adlibLoadTBR("extasy.tbr");
_vm->_sound->adlibLoadMDY("extasy.mdy");
+ _vm->_sound->adlibSetRepeating(-1);
}
void Inter_Fascination::oFascin_adlibPlay(OpGobParams &params) {
-#ifdef ENABLE_FASCIN_ADLIB
_vm->_sound->adlibPlay();
-#endif
}
void Inter_Fascination::oFascin_adlibStop(OpGobParams &params) {
diff --git a/engines/gob/inter_geisha.cpp b/engines/gob/inter_geisha.cpp
index 75204a3f55..8a4d4246b6 100644
--- a/engines/gob/inter_geisha.cpp
+++ b/engines/gob/inter_geisha.cpp
@@ -298,9 +298,8 @@ void Inter_Geisha::oGeisha_loadTitleMusic(OpGobParams &params) {
}
void Inter_Geisha::oGeisha_playMusic(OpGobParams &params) {
- // TODO: The MDYPlayer is still broken!
- warning("Geisha Stub: oGeisha_playMusic");
- // _vm->_sound->adlibPlay();
+ _vm->_sound->adlibSetRepeating(-1);
+ _vm->_sound->adlibPlay();
}
void Inter_Geisha::oGeisha_stopMusic(OpGobParams &params) {
diff --git a/engines/gob/inter_v1.cpp b/engines/gob/inter_v1.cpp
index 4aa54f720b..6fc472a0ac 100644
--- a/engines/gob/inter_v1.cpp
+++ b/engines/gob/inter_v1.cpp
@@ -286,10 +286,40 @@ void Inter_v1::o1_loadMult() {
}
void Inter_v1::o1_playMult() {
- int16 checkEscape;
+ // NOTE: The EGA version of Gobliiins has an MDY tune.
+ // While the original doesn't play it, we do.
+ bool isGob1EGAIntro = _vm->getGameType() == kGameTypeGob1 &&
+ _vm->isEGA() &&
+ _vm->_game->_script->pos() == 1010 &&
+ _vm->isCurrentTot("intro.tot") &&
+ VAR(57) != 0xFFFFFFFF &&
+ _vm->_dataIO->hasFile("goblins.mdy") &&
+ _vm->_dataIO->hasFile("goblins.tbr");
+
+ int16 checkEscape = _vm->_game->_script->readInt16();
+
+ if (isGob1EGAIntro) {
+ _vm->_sound->adlibLoadTBR("goblins.tbr");
+ _vm->_sound->adlibLoadMDY("goblins.mdy");
+ _vm->_sound->adlibSetRepeating(-1);
+
+ _vm->_sound->adlibPlay();
+ }
- checkEscape = _vm->_game->_script->readInt16();
_vm->_mult->playMult(VAR(57), -1, checkEscape, 0);
+
+ if (isGob1EGAIntro) {
+
+ // User didn't escape the intro mult, wait for an escape here
+ if (VAR(57) != 0xFFFFFFFF) {
+ while (_vm->_util->getKey() != kKeyEscape) {
+ _vm->_util->processInput();
+ _vm->_util->longDelay(1);
+ }
+ }
+
+ _vm->_sound->adlibUnload();
+ }
}
void Inter_v1::o1_freeMultKeys() {
diff --git a/engines/gob/minigames/geisha/meter.cpp b/engines/gob/minigames/geisha/meter.cpp
index 719ecf3d18..7ec3119866 100644
--- a/engines/gob/minigames/geisha/meter.cpp
+++ b/engines/gob/minigames/geisha/meter.cpp
@@ -67,7 +67,7 @@ int32 Meter::increase(int32 n) {
if (n < 0)
return decrease(-n);
- int32 overflow = MAX(0, (_value + n) - _maxValue);
+ int32 overflow = MAX<int32>(0, (_value + n) - _maxValue);
int32 value = CLIP<int32>(_value + n, 0, _maxValue);
if (_value == value)
@@ -83,7 +83,7 @@ int32 Meter::decrease(int32 n) {
if (n < 0)
return increase(-n);
- int32 underflow = -MIN(0, _value - n);
+ int32 underflow = -MIN<int32>(0, _value - n);
int32 value = CLIP<int32>(_value - n, 0, _maxValue);
if (_value == value)
diff --git a/engines/gob/minigames/geisha/penetration.cpp b/engines/gob/minigames/geisha/penetration.cpp
index 9791757984..3be9f1f651 100644
--- a/engines/gob/minigames/geisha/penetration.cpp
+++ b/engines/gob/minigames/geisha/penetration.cpp
@@ -55,12 +55,24 @@ enum Sprite {
kSpriteFloor = 30,
kSpriteWall = 31,
kSpriteMouthBite = 32,
- kSpriteMouthKiss = 33
+ kSpriteMouthKiss = 33,
+ kSpriteBulletN = 65,
+ kSpriteBulletS = 66,
+ kSpriteBulletW = 67,
+ kSpriteBulletE = 68,
+ kSpriteBulletSW = 85,
+ kSpriteBulletSE = 86,
+ kSpriteBulletNW = 87,
+ kSpriteBulletNE = 88
};
enum Animation {
- kAnimationMouthKiss = 33,
- kAnimationMouthBite = 34
+ kAnimationEnemyRound = 0,
+ kAnimationEnemyRoundExplode = 1,
+ kAnimationEnemySquare = 2,
+ kAnimationEnemySquareExplode = 3,
+ kAnimationMouthKiss = 33,
+ kAnimationMouthBite = 34
};
static const int kMapTileWidth = 24;
@@ -353,12 +365,55 @@ static const char *kStrings[kLanguageCount][kStringCount] = {
}
};
-Penetration::Position::Position(uint16 pX, uint16 pY) : x(pX), y(pY) {
+
+Penetration::MapObject::MapObject(uint16 tX, uint16 tY, uint16 mX, uint16 mY, uint16 w, uint16 h) :
+ tileX(tX), tileY(tY), mapX(mX), mapY(mY), width(w), height(h) {
+
+ isBlocking = true;
+}
+
+Penetration::MapObject::MapObject(uint16 tX, uint16 tY, uint16 w, uint16 h) :
+ tileX(tX), tileY(tY), width(w), height(h) {
+
+ isBlocking = true;
+
+ setMapFromTilePosition();
+}
+
+void Penetration::MapObject::setTileFromMapPosition() {
+ tileX = (mapX + (width / 2)) / kMapTileWidth;
+ tileY = (mapY + (height / 2)) / kMapTileHeight;
+}
+
+void Penetration::MapObject::setMapFromTilePosition() {
+ mapX = tileX * kMapTileWidth;
+ mapY = tileY * kMapTileHeight;
+}
+
+bool Penetration::MapObject::isIn(uint16 mX, uint16 mY) const {
+ if ((mX < mapX) || (mY < mapY))
+ return false;
+ if ((mX > (mapX + width - 1)) || (mY > (mapY + height - 1)))
+ return false;
+
+ return true;
+}
+
+bool Penetration::MapObject::isIn(uint16 mX, uint16 mY, uint16 w, uint16 h) const {
+ return isIn(mX , mY ) ||
+ isIn(mX + w - 1, mY ) ||
+ isIn(mX , mY + h - 1) ||
+ isIn(mX + w - 1, mY + h - 1);
+}
+
+bool Penetration::MapObject::isIn(const MapObject &obj) const {
+ return isIn(obj.mapX, obj.mapY, obj.width, obj.height);
}
-Penetration::ManagedMouth::ManagedMouth(uint16 pX, uint16 pY, MouthType t) :
- Position(pX, pY), mouth(0), type(t) {
+Penetration::ManagedMouth::ManagedMouth(uint16 tX, uint16 tY, MouthType t) :
+ MapObject(tX, tY, 0, 0), mouth(0), type(t) {
+
}
Penetration::ManagedMouth::~ManagedMouth() {
@@ -366,9 +421,9 @@ Penetration::ManagedMouth::~ManagedMouth() {
}
-Penetration::ManagedSub::ManagedSub(uint16 pX, uint16 pY) : Position(pX, pY), sub(0) {
- mapX = x * kMapTileWidth;
- mapY = y * kMapTileHeight;
+Penetration::ManagedSub::ManagedSub(uint16 tX, uint16 tY) :
+ MapObject(tX, tY, kMapTileWidth, kMapTileHeight), sub(0) {
+
}
Penetration::ManagedSub::~ManagedSub() {
@@ -376,6 +431,34 @@ Penetration::ManagedSub::~ManagedSub() {
}
+Penetration::ManagedEnemy::ManagedEnemy() : MapObject(0, 0, 0, 0), enemy(0), dead(false) {
+}
+
+Penetration::ManagedEnemy::~ManagedEnemy() {
+ delete enemy;
+}
+
+void Penetration::ManagedEnemy::clear() {
+ delete enemy;
+
+ enemy = 0;
+}
+
+
+Penetration::ManagedBullet::ManagedBullet() : MapObject(0, 0, 0, 0), bullet(0) {
+}
+
+Penetration::ManagedBullet::~ManagedBullet() {
+ delete bullet;
+}
+
+void Penetration::ManagedBullet::clear() {
+ delete bullet;
+
+ bullet = 0;
+}
+
+
Penetration::Penetration(GobEngine *vm) : _vm(vm), _background(0), _sprites(0), _objects(0), _sub(0),
_shieldMeter(0), _healthMeter(0), _floor(0), _isPlaying(false) {
@@ -415,6 +498,8 @@ bool Penetration::play(bool hasAccessPass, bool hasMaxEnergy, bool testMode) {
_vm->_video->retrace();
while (!_vm->shouldQuit() && !_quit && !isDead() && !hasWon()) {
+ enemiesCreate();
+ bulletsMove();
updateAnims();
// Draw, fade in if necessary and wait for the end of the frame
@@ -428,7 +513,13 @@ bool Penetration::play(bool hasAccessPass, bool hasMaxEnergy, bool testMode) {
// Handle the sub movement
handleSub();
+ // Handle the enemies movement
+ enemiesMove();
+
checkExited();
+
+ if (_shotCoolDown > 0)
+ _shotCoolDown--;
}
deinit();
@@ -449,11 +540,12 @@ void Penetration::cheatWin() {
void Penetration::init() {
// Load sounds
- _vm->_sound->sampleLoad(&_soundShield, SOUND_SND, "boucl.snd");
- _vm->_sound->sampleLoad(&_soundBite , SOUND_SND, "pervet.snd");
- _vm->_sound->sampleLoad(&_soundKiss , SOUND_SND, "baise.snd");
- _vm->_sound->sampleLoad(&_soundShoot , SOUND_SND, "tirgim.snd");
- _vm->_sound->sampleLoad(&_soundExit , SOUND_SND, "trouve.snd");
+ _vm->_sound->sampleLoad(&_soundShield , SOUND_SND, "boucl.snd");
+ _vm->_sound->sampleLoad(&_soundBite , SOUND_SND, "pervet.snd");
+ _vm->_sound->sampleLoad(&_soundKiss , SOUND_SND, "baise.snd");
+ _vm->_sound->sampleLoad(&_soundShoot , SOUND_SND, "tirgim.snd");
+ _vm->_sound->sampleLoad(&_soundExit , SOUND_SND, "trouve.snd");
+ _vm->_sound->sampleLoad(&_soundExplode, SOUND_SND, "virmor.snd");
_quit = false;
for (int i = 0; i < kKeyCount; i++)
@@ -477,6 +569,8 @@ void Penetration::init() {
_floor = 0;
+ _shotCoolDown = 0;
+
createMap();
}
@@ -486,6 +580,7 @@ void Penetration::deinit() {
_soundKiss.free();
_soundShoot.free();
_soundExit.free();
+ _soundExplode.free();
clearMap();
@@ -500,10 +595,18 @@ void Penetration::clearMap() {
_mapAnims.clear();
_anims.clear();
+ _blockingObjects.clear();
+
+ _walls.clear();
_exits.clear();
_shields.clear();
_mouths.clear();
+ for (int i = 0; i < kEnemyCount; i++)
+ _enemies[i].clear();
+ for (int i = 0; i < kMaxBulletCount; i++)
+ _bullets[i].clear();
+
delete _sub;
_sub = 0;
@@ -526,13 +629,9 @@ void Penetration::createMap() {
for (int x = 0; x < kMapWidth; x++) {
const byte mapTile = mapTiles[y * kMapWidth + x];
- bool *walkMap = _walkMap + (y * kMapWidth + x);
-
const int posX = kPlayAreaBorderWidth + x * kMapTileWidth;
const int posY = kPlayAreaBorderHeight + y * kMapTileHeight;
- *walkMap = true;
-
switch (mapTile) {
case 0: // Floor
_sprites->draw(*_map, kSpriteFloor, posX, posY);
@@ -542,18 +641,18 @@ void Penetration::createMap() {
exitWorks = _hasAccessPass;
if (exitWorks) {
- _exits.push_back(Position(x, y));
_sprites->draw(*_map, kSpriteExit, posX, posY);
+ _exits.push_back(MapObject(x, y, 0, 0));
} else {
_sprites->draw(*_map, kSpriteWall, posX, posY);
- *walkMap = false;
+ _walls.push_back(MapObject(x, y, kMapTileWidth, kMapTileHeight));
}
break;
case 50: // Wall
_sprites->draw(*_map, kSpriteWall, posX, posY);
- *walkMap = false;
+ _walls.push_back(MapObject(x, y, kMapTileWidth, kMapTileHeight));
break;
case 51: // Regular exit
@@ -563,11 +662,11 @@ void Penetration::createMap() {
exitWorks = _testMode || (_floor < 2) || _hasAccessPass;
if (exitWorks) {
- _exits.push_back(Position(x, y));
_sprites->draw(*_map, kSpriteExit, posX, posY);
+ _exits.push_back(MapObject(x, y, 0, 0));
} else {
_sprites->draw(*_map, kSpriteWall, posX, posY);
- *walkMap = false;
+ _walls.push_back(MapObject(x, y, kMapTileWidth, kMapTileHeight));
}
break;
@@ -603,7 +702,7 @@ void Penetration::createMap() {
_map->fillRect(posX + 4, posY + 8, posX + 7, posY + 18, kColorFloor); // Area left to shield
_map->fillRect(posX + 17, posY + 8, posX + 20, posY + 18, kColorFloor); // Area right to shield
- _shields.push_back(Position(x, y));
+ _shields.push_back(MapObject(x, y, 0, 0));
break;
case 57: // Start position
@@ -623,10 +722,42 @@ void Penetration::createMap() {
if (!_sub)
error("Geisha: No starting position in floor %d (testmode: %d)", _floor, _testMode);
- for (Common::List<ManagedMouth>::iterator m = _mouths.begin(); m != _mouths.end(); m++)
+ // Walls
+ for (Common::List<MapObject>::iterator w = _walls.begin(); w != _walls.end(); ++w)
+ _blockingObjects.push_back(&*w);
+
+ // Mouths
+ for (Common::List<ManagedMouth>::iterator m = _mouths.begin(); m != _mouths.end(); ++m)
_mapAnims.push_back(m->mouth);
+ // Sub
+ _blockingObjects.push_back(_sub);
_anims.push_back(_sub->sub);
+
+ // Moving enemies
+ for (int i = 0; i < kEnemyCount; i++) {
+ _enemies[i].enemy = new ANIObject(*_objects);
+
+ _enemies[i].enemy->setPause(true);
+ _enemies[i].enemy->setVisible(false);
+
+ _enemies[i].isBlocking = false;
+
+ _blockingObjects.push_back(&_enemies[i]);
+ _mapAnims.push_back(_enemies[i].enemy);
+ }
+
+ // Bullets
+ for (int i = 0; i < kMaxBulletCount; i++) {
+ _bullets[i].bullet = new ANIObject(*_sprites);
+
+ _bullets[i].bullet->setPause(true);
+ _bullets[i].bullet->setVisible(false);
+
+ _bullets[i].isBlocking = false;
+
+ _mapAnims.push_back(_bullets[i].bullet);
+ }
}
void Penetration::drawFloorText() {
@@ -741,6 +872,105 @@ void Penetration::initScreen() {
_vm->_draw->dirtiedRect(_vm->_draw->_backSurface, 0, 0, 319, 199);
}
+void Penetration::enemiesCreate() {
+ for (int i = 0; i < kEnemyCount; i++) {
+ ManagedEnemy &enemy = _enemies[i];
+
+ if (enemy.enemy->isVisible())
+ continue;
+
+ enemy.enemy->setAnimation((i & 1) ? kAnimationEnemySquare : kAnimationEnemyRound);
+ enemy.enemy->setMode(ANIObject::kModeContinuous);
+ enemy.enemy->setPause(false);
+ enemy.enemy->setVisible(true);
+
+ int16 width, height;
+ enemy.enemy->getFrameSize(width, height);
+
+ enemy.width = width;
+ enemy.height = height;
+
+ do {
+ enemy.mapX = _vm->_util->getRandom(kMapWidth) * kMapTileWidth + 2;
+ enemy.mapY = _vm->_util->getRandom(kMapHeight) * kMapTileHeight + 4;
+ enemy.setTileFromMapPosition();
+ } while (isBlocked(enemy, enemy.mapX, enemy.mapY));
+
+ const int posX = kPlayAreaBorderWidth + enemy.mapX;
+ const int posY = kPlayAreaBorderHeight + enemy.mapY;
+
+ enemy.enemy->setPosition(posX, posY);
+
+ enemy.isBlocking = true;
+ enemy.dead = false;
+ }
+}
+
+void Penetration::enemyMove(ManagedEnemy &enemy, int x, int y) {
+ if ((x == 0) && (y == 0))
+ return;
+
+ MapObject *blockedBy;
+ findPath(enemy, x, y, &blockedBy);
+
+ enemy.setTileFromMapPosition();
+
+ const int posX = kPlayAreaBorderWidth + enemy.mapX;
+ const int posY = kPlayAreaBorderHeight + enemy.mapY;
+
+ enemy.enemy->setPosition(posX, posY);
+
+ if (blockedBy == _sub)
+ enemyAttack(enemy);
+}
+
+void Penetration::enemiesMove() {
+ for (int i = 0; i < kEnemyCount; i++) {
+ ManagedEnemy &enemy = _enemies[i];
+
+ if (!enemy.enemy->isVisible() || enemy.dead)
+ continue;
+
+ int x = 0, y = 0;
+
+ if (enemy.mapX > _sub->mapX)
+ x = -8;
+ else if (enemy.mapX < _sub->mapX)
+ x = 8;
+
+ if (enemy.mapY > _sub->mapY)
+ y = -8;
+ else if (enemy.mapY < _sub->mapY)
+ y = 8;
+
+ enemyMove(enemy, x, y);
+ }
+}
+
+void Penetration::enemyAttack(ManagedEnemy &enemy) {
+ // If we have shields, the enemy explodes at them, taking a huge chunk of energy with it.
+ // Otherwise, the enemy nibbles a small amount of health away.
+
+ if (_shieldMeter->getValue() > 0) {
+ enemyExplode(enemy);
+
+ healthLose(80);
+ } else
+ healthLose(5);
+}
+
+void Penetration::enemyExplode(ManagedEnemy &enemy) {
+ enemy.dead = true;
+ enemy.isBlocking = false;
+
+ bool isSquare = enemy.enemy->getAnimation() == kAnimationEnemySquare;
+
+ enemy.enemy->setAnimation(isSquare ? kAnimationEnemySquareExplode : kAnimationEnemyRoundExplode);
+ enemy.enemy->setMode(ANIObject::kModeOnce);
+
+ _vm->_sound->blasterPlay(&_soundExplode, 1, 0);
+}
+
void Penetration::checkInput() {
Common::Event event;
Common::EventManager *eventMan = g_system->getEventManager();
@@ -785,13 +1015,6 @@ void Penetration::checkInput() {
}
}
-bool Penetration::isWalkable(int16 x, int16 y) const {
- if ((x < 0) || (x >= kMapWidth) || (y < 0) || (y >= kMapHeight))
- return false;
-
- return _walkMap[y * kMapWidth + x];
-}
-
void Penetration::handleSub() {
int x, y;
Submarine::Direction direction = getDirection(x, y);
@@ -802,34 +1025,86 @@ void Penetration::handleSub() {
subShoot();
}
-void Penetration::subMove(int x, int y, Submarine::Direction direction) {
- if (!_sub->sub->canMove())
- return;
+bool Penetration::isBlocked(const MapObject &self, int16 x, int16 y, MapObject **blockedBy) {
+
+ if ((x < 0) || (y < 0))
+ return true;
+ if (((x + self.width - 1) >= (kMapWidth * kMapTileWidth)) ||
+ ((y + self.height - 1) >= (kMapHeight * kMapTileHeight)))
+ return true;
+
+ MapObject checkSelf(0, 0, self.width, self.height);
+
+ checkSelf.mapX = x;
+ checkSelf.mapY = y;
+
+ for (Common::List<MapObject *>::iterator o = _blockingObjects.begin(); o != _blockingObjects.end(); ++o) {
+ MapObject &obj = **o;
+
+ if (&obj == &self)
+ continue;
- // Limit the movement to walkable tiles
+ if (!obj.isBlocking)
+ continue;
+
+ if (obj.isIn(checkSelf) || checkSelf.isIn(obj)) {
+ if (blockedBy && !*blockedBy)
+ *blockedBy = &obj;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void Penetration::findPath(MapObject &obj, int x, int y, MapObject **blockedBy) {
+ if (blockedBy)
+ *blockedBy = 0;
+
+ while ((x != 0) || (y != 0)) {
+ uint16 oldX = obj.mapX;
+ uint16 oldY = obj.mapY;
+
+ uint16 newX = obj.mapX;
+ if (x > 0) {
+ newX++;
+ x--;
+ } else if (x < 0) {
+ newX--;
+ x++;
+ }
+
+ if (!isBlocked(obj, newX, obj.mapY, blockedBy))
+ obj.mapX = newX;
+
+ uint16 newY = obj.mapY;
+ if (y > 0) {
+ newY++;
+ y--;
+ } else if (y < 0) {
+ newY--;
+ y++;
+ }
- int16 minX = 0;
- if (!isWalkable(_sub->x - 1, _sub->y))
- minX = _sub->x * kMapTileWidth;
+ if (!isBlocked(obj, obj.mapX, newY, blockedBy))
+ obj.mapY = newY;
- int16 maxX = kMapWidth * kMapTileWidth;
- if (!isWalkable(_sub->x + 1, _sub->y))
- maxX = _sub->x * kMapTileWidth;
+ if ((obj.mapX == oldX) && (obj.mapY == oldY))
+ break;
+ }
+}
- int16 minY = 0;
- if (!isWalkable(_sub->x, _sub->y - 1))
- minY = _sub->y * kMapTileHeight;
+void Penetration::subMove(int x, int y, Submarine::Direction direction) {
+ if (!_sub->sub->canMove())
+ return;
- int16 maxY = kMapHeight * kMapTileHeight;
- if (!isWalkable(_sub->x, _sub->y + 1))
- maxY = _sub->y * kMapTileHeight;
+ if ((x == 0) && (y == 0))
+ return;
- _sub->mapX = CLIP<int16>(_sub->mapX + x, minX, maxX);
- _sub->mapY = CLIP<int16>(_sub->mapY + y, minY, maxY);
+ findPath(*_sub, x, y);
- // The tile the sub is on is where its mid-point is
- _sub->x = (_sub->mapX + (kMapTileWidth / 2)) / kMapTileWidth;
- _sub->y = (_sub->mapY + (kMapTileHeight / 2)) / kMapTileHeight;
+ _sub->setTileFromMapPosition();
_sub->sub->turn(direction);
@@ -842,9 +1117,185 @@ void Penetration::subShoot() {
if (!_sub->sub->canMove() || _sub->sub->isShooting())
return;
- _sub->sub->shoot();
+ if (_shotCoolDown > 0)
+ return;
+
+ // Creating a bullet
+ int slot = findEmptyBulletSlot();
+ if (slot < 0)
+ return;
+
+ ManagedBullet &bullet = _bullets[slot];
+ bullet.bullet->setAnimation(directionToBullet(_sub->sub->getDirection()));
+
+ setBulletPosition(*_sub, bullet);
+
+ const int posX = kPlayAreaBorderWidth + bullet.mapX;
+ const int posY = kPlayAreaBorderHeight + bullet.mapY;
+
+ bullet.bullet->setPosition(posX, posY);
+ bullet.bullet->setVisible(true);
+
+ // Shooting
+ _sub->sub->shoot();
_vm->_sound->blasterPlay(&_soundShoot, 1, 0);
+
+ _shotCoolDown = 3;
+}
+
+void Penetration::setBulletPosition(const ManagedSub &sub, ManagedBullet &bullet) const {
+ bullet.mapX = sub.mapX;
+ bullet.mapY= sub.mapY;
+
+ int16 sWidth, sHeight;
+ sub.sub->getFrameSize(sWidth, sHeight);
+
+ int16 bWidth, bHeight;
+ bullet.bullet->getFrameSize(bWidth, bHeight);
+
+ switch (sub.sub->getDirection()) {
+ case Submarine::kDirectionN:
+ bullet.mapX += sWidth / 2;
+ bullet.mapY -= bHeight;
+
+ bullet.deltaX = 0;
+ bullet.deltaY = -8;
+ break;
+
+ case Submarine::kDirectionNE:
+ bullet.mapX += sWidth;
+ bullet.mapY -= bHeight * 2;
+
+ bullet.deltaX = 8;
+ bullet.deltaY = -8;
+ break;
+
+ case Submarine::kDirectionE:
+ bullet.mapX += sWidth;
+ bullet.mapY += sHeight / 2 - bHeight;
+
+ bullet.deltaX = 8;
+ bullet.deltaY = 0;
+ break;
+
+ case Submarine::kDirectionSE:
+ bullet.mapX += sWidth;
+ bullet.mapY += sHeight;
+
+ bullet.deltaX = 8;
+ bullet.deltaY = 8;
+ break;
+
+ case Submarine::kDirectionS:
+ bullet.mapX += sWidth / 2;
+ bullet.mapY += sHeight;
+
+ bullet.deltaX = 0;
+ bullet.deltaY = 8;
+ break;
+
+ case Submarine::kDirectionSW:
+ bullet.mapX -= bWidth;
+ bullet.mapY += sHeight;
+
+ bullet.deltaX = -8;
+ bullet.deltaY = 8;
+ break;
+
+ case Submarine::kDirectionW:
+ bullet.mapX -= bWidth;
+ bullet.mapY += sHeight / 2 - bHeight;
+
+ bullet.deltaX = -8;
+ bullet.deltaY = 0;
+ break;
+
+ case Submarine::kDirectionNW:
+ bullet.mapX -= bWidth;
+ bullet.mapY -= bHeight;
+
+ bullet.deltaX = -8;
+ bullet.deltaY = -8;
+ break;
+
+ default:
+ break;
+ }
+}
+
+uint16 Penetration::directionToBullet(Submarine::Direction direction) const {
+ switch (direction) {
+ case Submarine::kDirectionN:
+ return kSpriteBulletN;
+
+ case Submarine::kDirectionNE:
+ return kSpriteBulletNE;
+
+ case Submarine::kDirectionE:
+ return kSpriteBulletE;
+
+ case Submarine::kDirectionSE:
+ return kSpriteBulletSE;
+
+ case Submarine::kDirectionS:
+ return kSpriteBulletS;
+
+ case Submarine::kDirectionSW:
+ return kSpriteBulletSW;
+
+ case Submarine::kDirectionW:
+ return kSpriteBulletW;
+
+ case Submarine::kDirectionNW:
+ return kSpriteBulletNW;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+int Penetration::findEmptyBulletSlot() const {
+ for (int i = 0; i < kMaxBulletCount; i++)
+ if (!_bullets[i].bullet->isVisible())
+ return i;
+
+ return -1;
+}
+
+void Penetration::bulletsMove() {
+ for (int i = 0; i < kMaxBulletCount; i++)
+ if (_bullets[i].bullet->isVisible())
+ bulletMove(_bullets[i]);
+}
+
+void Penetration::bulletMove(ManagedBullet &bullet) {
+ MapObject *blockedBy;
+ findPath(bullet, bullet.deltaX, bullet.deltaY, &blockedBy);
+
+ if (blockedBy) {
+ checkShotEnemy(*blockedBy);
+ bullet.bullet->setVisible(false);
+ return;
+ }
+
+ const int posX = kPlayAreaBorderWidth + bullet.mapX;
+ const int posY = kPlayAreaBorderHeight + bullet.mapY;
+
+ bullet.bullet->setPosition(posX, posY);
+}
+
+void Penetration::checkShotEnemy(MapObject &shotObject) {
+ for (int i = 0; i < kEnemyCount; i++) {
+ ManagedEnemy &enemy = _enemies[i];
+
+ if ((&enemy == &shotObject) && !enemy.dead && enemy.enemy->isVisible()) {
+ enemyExplode(enemy);
+ return;
+ }
+ }
}
Submarine::Direction Penetration::getDirection(int &x, int &y) const {
@@ -872,8 +1323,8 @@ Submarine::Direction Penetration::getDirection(int &x, int &y) const {
}
void Penetration::checkShields() {
- for (Common::List<Position>::iterator pos = _shields.begin(); pos != _shields.end(); ++pos) {
- if ((pos->x == _sub->x) && (pos->y == _sub->y)) {
+ for (Common::List<MapObject>::iterator s = _shields.begin(); s != _shields.end(); ++s) {
+ if ((s->tileX == _sub->tileX) && (s->tileY == _sub->tileY)) {
// Charge shields
_shieldMeter->setMaxValue();
@@ -881,11 +1332,8 @@ void Penetration::checkShields() {
_vm->_sound->blasterPlay(&_soundShield, 1, 0);
// Erase the shield from the map
- const int mapX = kPlayAreaBorderWidth + pos->x * kMapTileWidth;
- const int mapY = kPlayAreaBorderHeight + pos->y * kMapTileHeight;
- _sprites->draw(*_map, 30, mapX, mapY);
-
- _shields.erase(pos);
+ _sprites->draw(*_map, 30, s->mapX + kPlayAreaBorderWidth, s->mapY + kPlayAreaBorderHeight);
+ _shields.erase(s);
break;
}
}
@@ -896,8 +1344,8 @@ void Penetration::checkMouths() {
if (!m->mouth->isDeactivated())
continue;
- if ((( m->x == _sub->x) && (m->y == _sub->y)) ||
- (((m->x + 1) == _sub->x) && (m->y == _sub->y))) {
+ if ((( m->tileX == _sub->tileX) && (m->tileY == _sub->tileY)) ||
+ (((m->tileX + 1) == _sub->tileX) && (m->tileY == _sub->tileY))) {
m->mouth->activate();
@@ -917,10 +1365,9 @@ void Penetration::checkExits() {
if (!_sub->sub->canMove())
return;
- for (Common::List<Position>::iterator e = _exits.begin(); e != _exits.end(); ++e) {
- if ((e->x == _sub->x) && (e->y == _sub->y)) {
- _sub->mapX = e->x * kMapTileWidth;
- _sub->mapY = e->y * kMapTileHeight;
+ for (Common::List<MapObject>::iterator e = _exits.begin(); e != _exits.end(); ++e) {
+ if ((e->tileX == _sub->tileX) && (e->tileY == _sub->tileY)) {
+ _sub->setMapFromTilePosition();
_sub->sub->leave();
diff --git a/engines/gob/minigames/geisha/penetration.h b/engines/gob/minigames/geisha/penetration.h
index 0336ef8dcb..50004eba8e 100644
--- a/engines/gob/minigames/geisha/penetration.h
+++ b/engines/gob/minigames/geisha/penetration.h
@@ -65,11 +65,30 @@ private:
static const byte kPalettes[kFloorCount][3 * kPaletteSize];
static const byte kMaps[kModeCount][kFloorCount][kMapWidth * kMapHeight];
- struct Position {
- uint16 x;
- uint16 y;
+ static const int kEnemyCount = 9;
+ static const int kMaxBulletCount = 10;
- Position(uint16 pX, uint16 pY);
+ struct MapObject {
+ uint16 tileX;
+ uint16 tileY;
+
+ uint16 mapX;
+ uint16 mapY;
+
+ uint16 width;
+ uint16 height;
+
+ bool isBlocking;
+
+ MapObject(uint16 tX, uint16 tY, uint16 mX, uint16 mY, uint16 w, uint16 h);
+ MapObject(uint16 tX, uint16 tY, uint16 w, uint16 h);
+
+ void setTileFromMapPosition();
+ void setMapFromTilePosition();
+
+ bool isIn(uint16 mX, uint16 mY) const;
+ bool isIn(uint16 mX, uint16 mY, uint16 w, uint16 h) const;
+ bool isIn(const MapObject &obj) const;
};
enum MouthType {
@@ -77,24 +96,43 @@ private:
kMouthTypeKiss
};
- struct ManagedMouth : public Position {
+ struct ManagedMouth : public MapObject {
Mouth *mouth;
+
MouthType type;
- ManagedMouth(uint16 pX, uint16 pY, MouthType t);
+ ManagedMouth(uint16 tX, uint16 tY, MouthType t);
~ManagedMouth();
};
- struct ManagedSub : public Position {
+ struct ManagedSub : public MapObject {
Submarine *sub;
- uint16 mapX;
- uint16 mapY;
-
- ManagedSub(uint16 pX, uint16 pY);
+ ManagedSub(uint16 tX, uint16 tY);
~ManagedSub();
+ };
+
+ struct ManagedEnemy : public MapObject {
+ ANIObject *enemy;
+
+ bool dead;
- void setPosition(uint16 pX, uint16 pY);
+ ManagedEnemy();
+ ~ManagedEnemy();
+
+ void clear();
+ };
+
+ struct ManagedBullet : public MapObject {
+ ANIObject *bullet;
+
+ int16 deltaX;
+ int16 deltaY;
+
+ ManagedBullet();
+ ~ManagedBullet();
+
+ void clear();
};
enum Keys {
@@ -130,19 +168,27 @@ private:
uint8 _floor;
Surface *_map;
- bool _walkMap[kMapWidth * kMapHeight];
ManagedSub *_sub;
- Common::List<Position> _exits;
- Common::List<Position> _shields;
+ Common::List<MapObject> _walls;
+ Common::List<MapObject> _exits;
+ Common::List<MapObject> _shields;
Common::List<ManagedMouth> _mouths;
+ ManagedEnemy _enemies[kEnemyCount];
+ ManagedBullet _bullets[kMaxBulletCount];
+
+ Common::List<MapObject *> _blockingObjects;
+
+ uint8 _shotCoolDown;
+
SoundDesc _soundShield;
SoundDesc _soundBite;
SoundDesc _soundKiss;
SoundDesc _soundShoot;
SoundDesc _soundExit;
+ SoundDesc _soundExplode;
bool _isPlaying;
@@ -161,17 +207,26 @@ private:
void drawFloorText();
void drawEndText();
+ bool isBlocked(const MapObject &self, int16 x, int16 y, MapObject **blockedBy = 0);
+ void findPath(MapObject &obj, int x, int y, MapObject **blockedBy = 0);
+
void updateAnims();
void checkInput();
+ Submarine::Direction getDirection(int &x, int &y) const;
+
void handleSub();
void subMove(int x, int y, Submarine::Direction direction);
void subShoot();
- Submarine::Direction getDirection(int &x, int &y) const;
+ int findEmptyBulletSlot() const;
+ uint16 directionToBullet(Submarine::Direction direction) const;
+ void setBulletPosition(const ManagedSub &sub, ManagedBullet &bullet) const;
- bool isWalkable(int16 x, int16 y) const;
+ void bulletsMove();
+ void bulletMove(ManagedBullet &bullet);
+ void checkShotEnemy(MapObject &shotObject);
void checkExits();
void checkShields();
@@ -182,6 +237,12 @@ private:
void checkExited();
+ void enemiesCreate();
+ void enemiesMove();
+ void enemyMove(ManagedEnemy &enemy, int x, int y);
+ void enemyAttack(ManagedEnemy &enemy);
+ void enemyExplode(ManagedEnemy &enemy);
+
bool isDead() const;
bool hasWon() const;
diff --git a/engines/gob/minigames/geisha/submarine.cpp b/engines/gob/minigames/geisha/submarine.cpp
index 9c12a56a85..bf15306e5a 100644
--- a/engines/gob/minigames/geisha/submarine.cpp
+++ b/engines/gob/minigames/geisha/submarine.cpp
@@ -51,13 +51,17 @@ enum Animation {
};
-Submarine::Submarine(const ANIFile &ani) : ANIObject(ani), _state(kStateMove) {
+Submarine::Submarine(const ANIFile &ani) : ANIObject(ani), _state(kStateMove), _direction(kDirectionNone) {
turn(kDirectionN);
}
Submarine::~Submarine() {
}
+Submarine::Direction Submarine::getDirection() const {
+ return _direction;
+}
+
void Submarine::turn(Direction to) {
// Nothing to do
if ((to == kDirectionNone) || ((_state == kStateMove) && (_direction == to)))
@@ -90,6 +94,9 @@ void Submarine::shoot() {
}
void Submarine::die() {
+ if (!canMove())
+ return;
+
_state = kStateDie;
setAnimation(directionToExplode(_direction));
diff --git a/engines/gob/minigames/geisha/submarine.h b/engines/gob/minigames/geisha/submarine.h
index 8a6d679bdd..a6eae57095 100644
--- a/engines/gob/minigames/geisha/submarine.h
+++ b/engines/gob/minigames/geisha/submarine.h
@@ -47,6 +47,8 @@ public:
Submarine(const ANIFile &ani);
~Submarine();
+ Direction getDirection() const;
+
/** Turn to the specified direction. */
void turn(Direction to);
diff --git a/engines/gob/module.mk b/engines/gob/module.mk
index b9680fad6b..7c5d7de158 100644
--- a/engines/gob/module.mk
+++ b/engines/gob/module.mk
@@ -103,6 +103,8 @@ MODULE_OBJS := \
sound/sounddesc.o \
sound/pcspeaker.o \
sound/adlib.o \
+ sound/musplayer.o \
+ sound/adlplayer.o \
sound/infogrames.o \
sound/protracker.o \
sound/soundmixer.o \
diff --git a/engines/gob/mult.cpp b/engines/gob/mult.cpp
index 06a7130cef..b3d7ea6263 100644
--- a/engines/gob/mult.cpp
+++ b/engines/gob/mult.cpp
@@ -366,10 +366,11 @@ void Mult::doPalAnim() {
palPtr->blue, 0, 0x13);
palPtr = _vm->_global->_pPaletteDesc->vgaPal;
- for (_counter = 0; _counter < 16; _counter++, palPtr++)
+ for (_counter = 0; _counter < 16; _counter++, palPtr++) {
_vm->_global->_redPalette[_counter] = palPtr->red;
_vm->_global->_greenPalette[_counter] = palPtr->green;
_vm->_global->_bluePalette[_counter] = palPtr->blue;
+ }
} else
_vm->_video->setFullPalette(_vm->_global->_pPaletteDesc);
diff --git a/engines/gob/palanim.cpp b/engines/gob/palanim.cpp
index 8a5327c3f1..f90b141725 100644
--- a/engines/gob/palanim.cpp
+++ b/engines/gob/palanim.cpp
@@ -75,47 +75,28 @@ bool PalAnim::fadeStepColor(int color) {
bool PalAnim::fadeStep(int16 oper) {
bool stop = true;
- byte newRed;
- byte newGreen;
- byte newBlue;
if (oper == 0) {
- if (_vm->_global->_setAllPalette) {
- if (_vm->_global->_inVM != 0)
- error("PalAnim::fadeStep(): _vm->_global->_inVM != 0 not supported");
-
- for (int i = 0; i < 256; i++) {
- newRed = fadeColor(_vm->_global->_redPalette[i], _toFadeRed[i]);
- newGreen = fadeColor(_vm->_global->_greenPalette[i], _toFadeGreen[i]);
- newBlue = fadeColor(_vm->_global->_bluePalette[i], _toFadeBlue[i]);
-
- if ((_vm->_global->_redPalette[i] != newRed) ||
- (_vm->_global->_greenPalette[i] != newGreen) ||
- (_vm->_global->_bluePalette[i] != newBlue)) {
-
- _vm->_video->setPalElem(i, newRed, newGreen, newBlue, 0, 0x13);
-
- _vm->_global->_redPalette[i] = newRed;
- _vm->_global->_greenPalette[i] = newGreen;
- _vm->_global->_bluePalette[i] = newBlue;
- stop = false;
- }
- }
- } else {
- for (int i = 0; i < 16; i++) {
-
- _vm->_video->setPalElem(i,
- fadeColor(_vm->_global->_redPalette[i], _toFadeRed[i]),
- fadeColor(_vm->_global->_greenPalette[i], _toFadeGreen[i]),
- fadeColor(_vm->_global->_bluePalette[i], _toFadeBlue[i]),
- -1, _vm->_global->_videoMode);
-
- if ((_vm->_global->_redPalette[i] != _toFadeRed[i]) ||
- (_vm->_global->_greenPalette[i] != _toFadeGreen[i]) ||
- (_vm->_global->_bluePalette[i] != _toFadeBlue[i]))
- stop = false;
+ int colorCount = _vm->_global->_setAllPalette ? _vm->_global->_colorCount : 256;
+
+ for (int i = 0; i < colorCount; i++) {
+ byte newRed = fadeColor(_vm->_global->_redPalette [i], _toFadeRed [i]);
+ byte newGreen = fadeColor(_vm->_global->_greenPalette[i], _toFadeGreen[i]);
+ byte newBlue = fadeColor(_vm->_global->_bluePalette [i], _toFadeBlue [i]);
+
+ if ((_vm->_global->_redPalette [i] != newRed ) ||
+ (_vm->_global->_greenPalette[i] != newGreen) ||
+ (_vm->_global->_bluePalette [i] != newBlue)) {
+
+ _vm->_video->setPalElem(i, newRed, newGreen, newBlue, 0, 0x13);
+
+ _vm->_global->_redPalette [i] = newRed;
+ _vm->_global->_greenPalette[i] = newGreen;
+ _vm->_global->_bluePalette [i] = newBlue;
+ stop = false;
}
}
+
} else if ((oper > 0) && (oper < 4))
stop = fadeStepColor(oper - 1);
@@ -124,44 +105,18 @@ bool PalAnim::fadeStep(int16 oper) {
void PalAnim::fade(Video::PalDesc *palDesc, int16 fadeV, int16 allColors) {
bool stop;
- int16 i;
if (_vm->shouldQuit())
return;
_fadeValue = (fadeV < 0) ? -fadeV : 2;
- if (!_vm->_global->_setAllPalette) {
- if (!palDesc) {
- for (i = 0; i < 16; i++) {
- _toFadeRed[i] = 0;
- _toFadeGreen[i] = 0;
- _toFadeBlue[i] = 0;
- }
- } else {
- for (i = 0; i < 16; i++) {
- _toFadeRed[i] = palDesc->vgaPal[i].red;
- _toFadeGreen[i] = palDesc->vgaPal[i].green;
- _toFadeBlue[i] = palDesc->vgaPal[i].blue;
- }
- }
- } else {
- if (_vm->_global->_inVM != 0)
- error("PalAnim::fade(): _vm->_global->_inVM != 0 is not supported");
-
- if (!palDesc) {
- for (i = 0; i < 256; i++) {
- _toFadeRed[i] = 0;
- _toFadeGreen[i] = 0;
- _toFadeBlue[i] = 0;
- }
- } else {
- for (i = 0; i < 256; i++) {
- _toFadeRed[i] = palDesc->vgaPal[i].red;
- _toFadeGreen[i] = palDesc->vgaPal[i].green;
- _toFadeBlue[i] = palDesc->vgaPal[i].blue;
- }
- }
+ int colorCount = _vm->_global->_setAllPalette ? _vm->_global->_colorCount : 256;
+
+ for (int i = 0; i < colorCount; i++) {
+ _toFadeRed [i] = (palDesc == 0) ? 0 : palDesc->vgaPal[i].red;
+ _toFadeGreen[i] = (palDesc == 0) ? 0 : palDesc->vgaPal[i].green;
+ _toFadeBlue [i] = (palDesc == 0) ? 0 : palDesc->vgaPal[i].blue;
}
if (allColors == 0) {
diff --git a/engines/gob/sound/adlib.cpp b/engines/gob/sound/adlib.cpp
index f1ab2a2d79..d9fc362547 100644
--- a/engines/gob/sound/adlib.cpp
+++ b/engines/gob/sound/adlib.cpp
@@ -20,771 +20,621 @@
*
*/
-#include "common/debug.h"
-#include "common/file.h"
-#include "common/endian.h"
+#include "common/util.h"
#include "common/textconsole.h"
+#include "common/debug.h"
+#include "common/config-manager.h"
+
+#include "audio/fmopl.h"
#include "gob/gob.h"
#include "gob/sound/adlib.h"
namespace Gob {
-const unsigned char AdLib::_operators[] = {0, 1, 2, 8, 9, 10, 16, 17, 18};
-const unsigned char AdLib::_volRegNums[] = {
- 3, 4, 5,
- 11, 12, 13,
- 19, 20, 21
+static const int kPitchTom = 24;
+static const int kPitchTomToSnare = 7;
+static const int kPitchSnareDrum = kPitchTom + kPitchTomToSnare;
+
+
+// Is the operator a modulator (0) or a carrier (1)?
+const uint8 AdLib::kOperatorType[kOperatorCount] = {
+ 0, 0, 0, 1, 1, 1,
+ 0, 0, 0, 1, 1, 1,
+ 0, 0, 0, 1, 1, 1
+};
+
+// Operator number to register offset on the OPL
+const uint8 AdLib::kOperatorOffset[kOperatorCount] = {
+ 0, 1, 2, 3, 4, 5,
+ 8, 9, 10, 11, 12, 13,
+ 16, 17, 18, 19, 20, 21
+};
+
+// For each operator, the voice it belongs to
+const uint8 AdLib::kOperatorVoice[kOperatorCount] = {
+ 0, 1, 2,
+ 0, 1, 2,
+ 3, 4, 5,
+ 3, 4, 5,
+ 6, 7, 8,
+ 6, 7, 8,
+};
+
+// Voice to operator set, for the 9 melodyvoices (only 6 useable in percussion mode)
+const uint8 AdLib::kVoiceMelodyOperator[kOperatorsPerVoice][kMelodyVoiceCount] = {
+ {0, 1, 2, 6, 7, 8, 12, 13, 14},
+ {3, 4, 5, 9, 10, 11, 15, 16, 17}
};
-AdLib::AdLib(Audio::Mixer &mixer) : _mixer(&mixer) {
- init();
+// Voice to operator set, for the 5 percussion voices (only useable in percussion mode)
+const uint8 AdLib::kVoicePercussionOperator[kOperatorsPerVoice][kPercussionVoiceCount] = {
+ {12, 16, 14, 17, 13},
+ {15, 0, 0, 0, 0}
+};
+
+// Mask bits to set each percussion instrument on/off
+const byte AdLib::kPercussionMasks[kPercussionVoiceCount] = {0x10, 0x08, 0x04, 0x02, 0x01};
+
+// Default instrument presets
+const uint16 AdLib::kPianoParams [kOperatorsPerVoice][kParamCount] = {
+ { 1, 1, 3, 15, 5, 0, 1, 3, 15, 0, 0, 0, 1, 0},
+ { 0, 1, 1, 15, 7, 0, 2, 4, 0, 0, 0, 1, 0, 0} };
+const uint16 AdLib::kBaseDrumParams[kOperatorsPerVoice][kParamCount] = {
+ { 0, 0, 0, 10, 4, 0, 8, 12, 11, 0, 0, 0, 1, 0 },
+ { 0, 0, 0, 13, 4, 0, 6, 15, 0, 0, 0, 0, 1, 0 } };
+const uint16 AdLib::kSnareDrumParams[kParamCount] = {
+ 0, 12, 0, 15, 11, 0, 8, 5, 0, 0, 0, 0, 0, 0 };
+const uint16 AdLib::kTomParams [kParamCount] = {
+ 0, 4, 0, 15, 11, 0, 7, 5, 0, 0, 0, 0, 0, 0 };
+const uint16 AdLib::kCymbalParams [kParamCount] = {
+ 0, 1, 0, 15, 11, 0, 5, 5, 0, 0, 0, 0, 0, 0 };
+const uint16 AdLib::kHihatParams [kParamCount] = {
+ 0, 1, 0, 15, 11, 0, 7, 5, 0, 0, 0, 0, 0, 0 };
+
+
+AdLib::AdLib(Audio::Mixer &mixer) : _mixer(&mixer), _opl(0),
+ _toPoll(0), _repCount(0), _first(true), _playing(false), _ended(true) {
+
+ _rate = _mixer->getOutputRate();
+
+ initFreqs();
+
+ createOPL();
+ initOPL();
+
+ _mixer->playStream(Audio::Mixer::kMusicSoundType, &_handle,
+ this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
}
AdLib::~AdLib() {
- Common::StackLock slock(_mutex);
-
_mixer->stopHandle(_handle);
- OPLDestroy(_opl);
- if (_data && _freeData)
- delete[] _data;
-}
-void AdLib::init() {
- _index = -1;
- _data = 0;
- _playPos = 0;
- _dataSize = 0;
+ delete _opl;
+}
- _rate = _mixer->getOutputRate();
+// Creates the OPL. Try to use the DOSBox emulator, unless that one is not compiled in,
+// or the user explicitly wants the MAME emulator. The MAME one is slightly buggy, leading
+// to some wrong sounds, especially noticeable in the title music of Gobliins 2, so we
+// really don't want to use it, if we can help it.
+void AdLib::createOPL() {
+ Common::String oplDriver = ConfMan.get("opl_driver");
- _opl = makeAdLibOPL(_rate);
+ if (oplDriver.empty() || (oplDriver == "auto") || (OPL::Config::parse(oplDriver) == -1)) {
+ // User has selected OPL driver auto detection or an invalid OPL driver.
+ // Set it to our preferred driver (DOSBox), if we can.
- _first = true;
- _ended = false;
- _playing = false;
+ if (OPL::Config::parse("db") <= 0) {
+ warning("The DOSBox AdLib emulator is not compiled in. Please keep in mind that the MAME one is buggy");
+ } else
+ oplDriver = "db";
- _freeData = false;
+ } else if (oplDriver == "mame") {
+ // User has selected the MAME OPL driver. It is buggy, so warn the user about that.
- _repCount = -1;
- _samplesTillPoll = 0;
+ warning("You have selected the MAME AdLib emulator. It is buggy; AdLib music might be slightly glitchy now");
+ }
- for (int i = 0; i < 16; i ++)
- _pollNotes[i] = 0;
- setFreqs();
+ _opl = OPL::Config::create(OPL::Config::parse(oplDriver), OPL::Config::kOpl2);
+ if (!_opl || !_opl->init(_rate)) {
+ delete _opl;
- _mixer->playStream(Audio::Mixer::kMusicSoundType, &_handle,
- this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+ error("Could not create an AdLib emulator");
+ }
}
int AdLib::readBuffer(int16 *buffer, const int numSamples) {
Common::StackLock slock(_mutex);
- int samples;
- int render;
- if (!_playing || (numSamples < 0)) {
+ // Nothing to do, fill with silence
+ if (!_playing) {
memset(buffer, 0, numSamples * sizeof(int16));
return numSamples;
}
- if (_first) {
- memset(buffer, 0, numSamples * sizeof(int16));
- pollMusic();
- return numSamples;
- }
- samples = numSamples;
+ // Read samples from the OPL, polling in more music when necessary
+ uint32 samples = numSamples;
while (samples && _playing) {
- if (_samplesTillPoll) {
- render = (samples > _samplesTillPoll) ? (_samplesTillPoll) : (samples);
+ if (_toPoll) {
+ const uint32 render = MIN(samples, _toPoll);
+
+ _opl->readBuffer(buffer, render);
+
+ buffer += render;
samples -= render;
- _samplesTillPoll -= render;
- YM3812UpdateOne(_opl, buffer, render);
- buffer += render;
+ _toPoll -= render;
+
} else {
- pollMusic();
+ // Song ended, fill the rest with silence
if (_ended) {
memset(buffer, 0, samples * sizeof(int16));
samples = 0;
+ break;
}
+
+ // Poll more music
+ _toPoll = pollMusic(_first);
+ _first = false;
}
}
+ // Song ended, loop if requested
if (_ended) {
- _first = true;
- _ended = false;
+ _toPoll = 0;
- rewind();
+ // _repCount == 0: No looping (anymore); _repCount < 0: Infinite looping
+ if (_repCount != 0) {
+ if (_repCount > 0)
+ _repCount--;
+
+ _first = true;
+ _ended = false;
- _samplesTillPoll = 0;
- if (_repCount == -1) {
- reset();
- setVoices();
- } else if (_repCount > 0) {
- _repCount--;
reset();
- setVoices();
- }
- else
+ rewind();
+ } else
_playing = false;
}
- return numSamples;
-}
-void AdLib::writeOPL(byte reg, byte val) {
- debugC(6, kDebugSound, "AdLib::writeOPL (%02X, %02X)", reg, val);
- OPLWriteReg(_opl, reg, val);
+ return numSamples;
}
-void AdLib::setFreqs() {
- byte lin;
- byte col;
- long val = 0;
-
- // Run through the 11 channels
- for (lin = 0; lin < 11; lin ++) {
- _notes[lin] = 0;
- _notCol[lin] = 0;
- _notLin[lin] = 0;
- _notOn[lin] = false;
- }
-
- // Run through the 25 lines
- for (lin = 0; lin < 25; lin ++) {
- // Run through the 12 columns
- for (col = 0; col < 12; col ++) {
- if (!col)
- val = (((0x2710L + lin * 0x18) * 0xCB78 / 0x3D090) << 0xE) *
- 9 / 0x1B503;
- _freqs[lin][col] = (short)((val + 4) >> 3);
- val = val * 0x6A / 0x64;
- }
- }
+bool AdLib::isStereo() const {
+ return _opl->isStereo();
}
-void AdLib::reset() {
- _first = true;
- OPLResetChip(_opl);
- _samplesTillPoll = 0;
-
- setFreqs();
- // Set frequencies and octave to 0; notes off
- for (int i = 0; i < 9; i++) {
- writeOPL(0xA0 | i, 0);
- writeOPL(0xB0 | i, 0);
- writeOPL(0xE0 | _operators[i] , 0);
- writeOPL(0xE0 |(_operators[i] + 3), 0);
- }
-
- // Authorize the control of the waveformes
- writeOPL(0x01, 0x20);
-}
-
-void AdLib::setKey(byte voice, byte note, bool on, bool spec) {
- short freq = 0;
- short octa = 0;
-
- // Instruction AX
- if (spec) {
- // 0x7F donne 0x16B;
- // 7F
- // << 7 = 3F80
- // + E000 = 11F80
- // & FFFF = 1F80
- // * 19 = 31380
- // / 2000 = 18 => Ligne 18h, colonne 0 => freq 16B
-
- // 0x3A donne 0x2AF;
- // 3A
- // << 7 = 1D00
- // + E000 = FD00 negatif
- // * 19 = xB500
- // / 2000 = -2 => Ligne 17h, colonne -1
-
- // 2E
- // << 7 = 1700
- // + E000 = F700 negatif
- // * 19 = x1F00
- // / 2000 =
- short a;
- short lin;
- short col;
-
- a = (note << 7) + 0xE000; // Volontairement tronque
- a = (short)((long)a * 25 / 0x2000);
- if (a < 0) {
- col = - ((24 - a) / 25);
- lin = (-a % 25);
- if (lin)
- lin = 25 - lin;
- }
- else {
- col = a / 25;
- lin = a % 25;
- }
-
- _notCol[voice] = col;
- _notLin[voice] = lin;
- note = _notes[voice];
- }
- // Instructions 0X 9X 8X
- else {
- note -= 12;
- _notOn[voice] = on;
- }
-
- _notes[voice] = note;
- note += _notCol[voice];
- note = MIN((byte) 0x5F, note);
- octa = note / 12;
- freq = _freqs[_notLin[voice]][note - octa * 12];
-
- writeOPL(0xA0 + voice, freq & 0xFF);
- writeOPL(0xB0 + voice, (freq >> 8) | (octa << 2) | (0x20 * (on ? 1 : 0)));
-
- if (!freq)
- warning("AdLib::setKey Voice %d, note %02X unknown", voice, note);
+bool AdLib::endOfData() const {
+ return !_playing;
}
-void AdLib::setVolume(byte voice, byte volume) {
- debugC(6, kDebugSound, "AdLib::setVolume(%d, %d)", voice, volume);
- //assert(voice >= 0 && voice <= 9);
- volume = 0x3F - ((volume * 0x7E) + 0x7F) / 0xFE;
- writeOPL(0x40 + _volRegNums[voice], volume);
+bool AdLib::endOfStream() const {
+ return false;
}
-void AdLib::pollMusic() {
- if ((_playPos > (_data + _dataSize)) && (_dataSize != 0xFFFFFFFF)) {
- _ended = true;
- return;
- }
-
- interpret();
-}
-
-void AdLib::unload() {
- _playing = false;
- _index = -1;
-
- if (_data && _freeData)
- delete[] _data;
-
- _freeData = false;
+int AdLib::getRate() const {
+ return _rate;
}
bool AdLib::isPlaying() const {
return _playing;
}
-bool AdLib::getRepeating() const {
- return _repCount != 0;
+int32 AdLib::getRepeating() const {
+ Common::StackLock slock(_mutex);
+
+ return _repCount;
}
void AdLib::setRepeating(int32 repCount) {
+ Common::StackLock slock(_mutex);
+
_repCount = repCount;
}
-int AdLib::getIndex() const {
- return _index;
+uint32 AdLib::getSamplesPerSecond() const {
+ return _rate * (isStereo() ? 2 : 1);
}
void AdLib::startPlay() {
- if (_data) _playing = true;
+ Common::StackLock slock(_mutex);
+
+ _playing = true;
+ _ended = false;
+ _first = true;
+
+ reset();
+ rewind();
}
void AdLib::stopPlay() {
Common::StackLock slock(_mutex);
+
+ end(true);
+
_playing = false;
}
-ADLPlayer::ADLPlayer(Audio::Mixer &mixer) : AdLib(mixer) {
-}
+void AdLib::writeOPL(byte reg, byte val) {
+ debugC(6, kDebugSound, "AdLib::writeOPL (%02X, %02X)", reg, val);
-ADLPlayer::~ADLPlayer() {
+ _opl->writeReg(reg, val);
}
-bool ADLPlayer::load(const char *fileName) {
- Common::File song;
+void AdLib::reset() {
+ allOff();
+ initOPL();
+}
- unload();
- song.open(fileName);
- if (!song.isOpen())
- return false;
+void AdLib::allOff() {
+ // NOTE: Explicit casts are necessary, because of 5.16 paragraph 4 of the C++ standard
+ int numVoices = isPercussionMode() ? (int)kMaxVoiceCount : (int)kMelodyVoiceCount;
- _freeData = true;
- _dataSize = song.size();
- _data = new byte[_dataSize];
- song.read(_data, _dataSize);
- song.close();
+ for (int i = 0; i < numVoices; i++)
+ noteOff(i);
+}
+void AdLib::end(bool killRepeat) {
reset();
- setVoices();
- _playPos = _data + 3 + (_data[1] + 1) * 0x38;
- return true;
+ _ended = true;
+
+ if (killRepeat)
+ _repCount = 0;
}
-bool ADLPlayer::load(byte *data, uint32 size, int index) {
- unload();
- _repCount = 0;
+void AdLib::initOPL() {
+ _tremoloDepth = false;
+ _vibratoDepth = false;
+ _keySplit = false;
- _dataSize = size;
- _data = data;
- _index = index;
+ _enableWaveSelect = true;
- reset();
- setVoices();
- _playPos = _data + 3 + (_data[1] + 1) * 0x38;
+ for (int i = 0; i < kMaxVoiceCount; i++) {
+ _voiceNote[i] = 0;
+ _voiceOn [i] = 0;
+ }
+
+ _opl->reset();
+
+ initOperatorVolumes();
+ resetFreqs();
+
+ setPercussionMode(false);
+
+ setTremoloDepth(false);
+ setVibratoDepth(false);
+ setKeySplit(false);
+
+ for(int i = 0; i < kMelodyVoiceCount; i++)
+ voiceOff(i);
- return true;
+ setPitchRange(1);
+
+ enableWaveSelect(true);
}
-void ADLPlayer::unload() {
- AdLib::unload();
+bool AdLib::isPercussionMode() const {
+ return _percussionMode;
}
-void ADLPlayer::interpret() {
- unsigned char instr;
- byte channel;
- byte note;
- byte volume;
- uint16 tempo;
+void AdLib::setPercussionMode(bool percussion) {
+ if (percussion) {
+ voiceOff(kVoiceBaseDrum);
+ voiceOff(kVoiceSnareDrum);
+ voiceOff(kVoiceTom);
- // First tempo, we'll ignore it...
- if (_first) {
- tempo = *(_playPos++);
- // Tempo on 2 bytes
- if (tempo & 0x80)
- tempo = ((tempo & 3) << 8) | *(_playPos++);
- }
- _first = false;
-
- // Instruction
- instr = *(_playPos++);
- channel = instr & 0x0F;
-
- switch (instr & 0xF0) {
- // Note on + Volume
- case 0x00:
- note = *(_playPos++);
- _pollNotes[channel] = note;
- setVolume(channel, *(_playPos++));
- setKey(channel, note, true, false);
- break;
- // Note on
- case 0x90:
- note = *(_playPos++);
- _pollNotes[channel] = note;
- setKey(channel, note, true, false);
- break;
- // Last note off
- case 0x80:
- note = _pollNotes[channel];
- setKey(channel, note, false, false);
- break;
- // Frequency on/off
- case 0xA0:
- note = *(_playPos++);
- setKey(channel, note, _notOn[channel], true);
- break;
- // Volume
- case 0xB0:
- volume = *(_playPos++);
- setVolume(channel, volume);
- break;
- // Program change
- case 0xC0:
- setVoice(channel, *(_playPos++), false);
- break;
- // Special
- case 0xF0:
- switch (instr & 0x0F) {
- case 0xF: // End instruction
- _ended = true;
- _samplesTillPoll = 0;
- return;
- default:
- warning("ADLPlayer: Unknown special command %X, stopping playback",
- instr & 0x0F);
- _repCount = 0;
- _ended = true;
- break;
- }
- break;
- default:
- warning("ADLPlayer: Unknown command %X, stopping playback",
- instr & 0xF0);
- _repCount = 0;
- _ended = true;
- break;
+ /* set the frequency for the last 4 percussion voices: */
+ setFreq(kVoiceTom, kPitchTom, 0);
+ setFreq(kVoiceSnareDrum, kPitchSnareDrum, 0);
}
- // Temporization
- tempo = *(_playPos++);
- // End tempo
- if (tempo == 0xFF) {
- _ended = true;
- return;
- }
- // Tempo on 2 bytes
- if (tempo & 0x80)
- tempo = ((tempo & 3) << 8) | *(_playPos++);
- if (!tempo)
- tempo ++;
+ _percussionMode = percussion;
+ _percussionBits = 0;
- _samplesTillPoll = tempo * (_rate / 1000);
+ initOperatorParams();
+ writeTremoloVibratoDepthPercMode();
}
-void ADLPlayer::reset() {
- AdLib::reset();
+void AdLib::enableWaveSelect(bool enable) {
+ _enableWaveSelect = enable;
+
+ for (int i = 0; i < kOperatorCount; i++)
+ writeOPL(0xE0 + kOperatorOffset[i], 0);
+
+ writeOPL(0x011, _enableWaveSelect ? 0x20 : 0);
}
-void ADLPlayer::rewind() {
- _playPos = _data + 3 + (_data[1] + 1) * 0x38;
+void AdLib::setPitchRange(uint8 range) {
+ _pitchRange = CLIP<uint8>(range, 0, 12);
+ _pitchRangeStep = _pitchRange * kPitchStepCount;
}
-void ADLPlayer::setVoices() {
- // Definitions of the 9 instruments
- for (int i = 0; i < 9; i++)
- setVoice(i, i, true);
+void AdLib::setTremoloDepth(bool tremoloDepth) {
+ _tremoloDepth = tremoloDepth;
+
+ writeTremoloVibratoDepthPercMode();
}
-void ADLPlayer::setVoice(byte voice, byte instr, bool set) {
- uint16 strct[27];
- byte channel;
- byte *dataPtr;
+void AdLib::setVibratoDepth(bool vibratoDepth) {
+ _vibratoDepth = vibratoDepth;
- // i = 0 : 0 1 2 3 4 5 6 7 8 9 10 11 12 26
- // i = 1 : 13 14 15 16 17 18 19 20 21 22 23 24 25 27
- for (int i = 0; i < 2; i++) {
- dataPtr = _data + 3 + instr * 0x38 + i * 0x1A;
- for (int j = 0; j < 27; j++) {
- strct[j] = READ_LE_UINT16(dataPtr);
- dataPtr += 2;
- }
- channel = _operators[voice] + i * 3;
- writeOPL(0xBD, 0x00);
- writeOPL(0x08, 0x00);
- writeOPL(0x40 | channel, ((strct[0] & 3) << 6) | (strct[8] & 0x3F));
- if (!i)
- writeOPL(0xC0 | voice,
- ((strct[2] & 7) << 1) | (1 - (strct[12] & 1)));
- writeOPL(0x60 | channel, ((strct[3] & 0xF) << 4) | (strct[6] & 0xF));
- writeOPL(0x80 | channel, ((strct[4] & 0xF) << 4) | (strct[7] & 0xF));
- writeOPL(0x20 | channel, ((strct[9] & 1) << 7) |
- ((strct[10] & 1) << 6) | ((strct[5] & 1) << 5) |
- ((strct[11] & 1) << 4) | (strct[1] & 0xF));
- if (!i)
- writeOPL(0xE0 | channel, (strct[26] & 3));
- else
- writeOPL(0xE0 | channel, (strct[14] & 3));
- if (i && set)
- writeOPL(0x40 | channel, 0);
+ writeTremoloVibratoDepthPercMode();
+}
+
+void AdLib::setKeySplit(bool keySplit) {
+ _keySplit = keySplit;
+
+ writeKeySplit();
+}
+
+void AdLib::setVoiceTimbre(uint8 voice, const uint16 *params) {
+ const uint16 *params0 = params;
+ const uint16 *params1 = params + kParamCount - 1;
+ const uint16 *waves = params + 2 * (kParamCount - 1);
+
+ const int voicePerc = voice - kVoiceBaseDrum;
+
+ if (!isPercussionMode() || (voice < kVoiceBaseDrum)) {
+ setOperatorParams(kVoiceMelodyOperator[0][voice], params0, waves[0]);
+ setOperatorParams(kVoiceMelodyOperator[1][voice], params1, waves[1]);
+ } else if (voice == kVoiceBaseDrum) {
+ setOperatorParams(kVoicePercussionOperator[0][voicePerc], params0, waves[0]);
+ setOperatorParams(kVoicePercussionOperator[1][voicePerc], params1, waves[1]);
+ } else {
+ setOperatorParams(kVoicePercussionOperator[0][voicePerc], params0, waves[0]);
}
}
+void AdLib::setVoiceVolume(uint8 voice, uint8 volume) {
+ int oper;
+
+ const int voicePerc = voice - kVoiceBaseDrum;
-MDYPlayer::MDYPlayer(Audio::Mixer &mixer) : AdLib(mixer) {
- init();
+ if (!isPercussionMode() || (voice < kVoiceBaseDrum))
+ oper = kVoiceMelodyOperator[1][ voice];
+ else
+ oper = kVoicePercussionOperator[voice == kVoiceBaseDrum ? 1 : 0][voicePerc];
+
+ _operatorVolume[oper] = MIN<uint8>(volume, kMaxVolume);
+ writeKeyScaleLevelVolume(oper);
}
-MDYPlayer::~MDYPlayer() {
+void AdLib::bendVoicePitch(uint8 voice, uint16 pitchBend) {
+ if (isPercussionMode() && (voice > kVoiceBaseDrum))
+ return;
+
+ changePitch(voice, MIN<uint16>(pitchBend, kMaxPitch));
+ setFreq(voice, _voiceNote[voice], _voiceOn[voice]);
}
-void MDYPlayer::init() {
- _soundMode = 0;
+void AdLib::noteOn(uint8 voice, uint8 note) {
+ note = MAX<int>(0, note - (kStandardMidC - kOPLMidC));
+
+ if (isPercussionMode() && (voice >= kVoiceBaseDrum)) {
+
+ if (voice == kVoiceBaseDrum) {
+ setFreq(kVoiceBaseDrum , note , false);
+ } else if (voice == kVoiceTom) {
+ setFreq(kVoiceTom , note , false);
+ setFreq(kVoiceSnareDrum, note + kPitchTomToSnare, false);
+ }
+
+ _percussionBits |= kPercussionMasks[voice - kVoiceBaseDrum];
+ writeTremoloVibratoDepthPercMode();
- _timbres = 0;
- _tbrCount = 0;
- _tbrStart = 0;
- _timbresSize = 0;
+ } else
+ setFreq(voice, note, true);
}
-bool MDYPlayer::loadMDY(Common::SeekableReadStream &stream) {
- unloadMDY();
+void AdLib::noteOff(uint8 voice) {
+ if (isPercussionMode() && (voice >= kVoiceBaseDrum)) {
+ _percussionBits &= ~kPercussionMasks[voice - kVoiceBaseDrum];
+ writeTremoloVibratoDepthPercMode();
+ } else
+ setFreq(voice, _voiceNote[voice], false);
+}
- _freeData = true;
+void AdLib::writeKeyScaleLevelVolume(uint8 oper) {
+ uint16 volume = 0;
- byte mdyHeader[70];
- stream.read(mdyHeader, 70);
+ volume = (63 - (_operatorParams[oper][kParamLevel] & 0x3F)) * _operatorVolume[oper];
+ volume = 63 - ((2 * volume + kMaxVolume) / (2 * kMaxVolume));
- _tickBeat = mdyHeader[36];
- _beatMeasure = mdyHeader[37];
- _totalTick = mdyHeader[38] + (mdyHeader[39] << 8) + (mdyHeader[40] << 16) + (mdyHeader[41] << 24);
- _dataSize = mdyHeader[42] + (mdyHeader[43] << 8) + (mdyHeader[44] << 16) + (mdyHeader[45] << 24);
- _nrCommand = mdyHeader[46] + (mdyHeader[47] << 8) + (mdyHeader[48] << 16) + (mdyHeader[49] << 24);
-// _soundMode is either 0 (melodic) or 1 (percussive)
- _soundMode = mdyHeader[58];
- assert((_soundMode == 0) || (_soundMode == 1));
+ uint8 keyScale = _operatorParams[oper][kParamKeyScaleLevel] << 6;
- _pitchBendRangeStep = 25*mdyHeader[59];
- _basicTempo = mdyHeader[60] + (mdyHeader[61] << 8);
+ writeOPL(0x40 + kOperatorOffset[oper], volume | keyScale);
+}
- if (_pitchBendRangeStep < 25)
- _pitchBendRangeStep = 25;
- else if (_pitchBendRangeStep > 300)
- _pitchBendRangeStep = 300;
+void AdLib::writeKeySplit() {
+ writeOPL(0x08, _keySplit ? 0x40 : 0);
+}
- _data = new byte[_dataSize];
- stream.read(_data, _dataSize);
+void AdLib::writeFeedbackFM(uint8 oper) {
+ if (kOperatorType[oper] == 1)
+ return;
- reset();
- _playPos = _data;
+ uint8 value = 0;
- return true;
+ value |= _operatorParams[oper][kParamFeedback] << 1;
+ value |= _operatorParams[oper][kParamFM] ? 0 : 1;
+
+ writeOPL(0xC0 + kOperatorVoice[oper], value);
}
-bool MDYPlayer::loadMDY(const char *fileName) {
- Common::File song;
+void AdLib::writeAttackDecay(uint8 oper) {
+ uint8 value = 0;
+
+ value |= _operatorParams[oper][kParamAttack] << 4;
+ value |= _operatorParams[oper][kParamDecay] & 0x0F;
- song.open(fileName);
- if (!song.isOpen())
- return false;
+ writeOPL(0x60 + kOperatorOffset[oper], value);
+}
- bool loaded = loadMDY(song);
+void AdLib::writeSustainRelease(uint8 oper) {
+ uint8 value = 0;
- song.close();
+ value |= _operatorParams[oper][kParamSustain] << 4;
+ value |= _operatorParams[oper][kParamRelease] & 0x0F;
- return loaded;
+ writeOPL(0x80 + kOperatorOffset[oper], value);
}
-bool MDYPlayer::loadTBR(Common::SeekableReadStream &stream) {
- unloadTBR();
+void AdLib::writeTremoloVibratoSustainingKeyScaleRateFreqMulti(uint8 oper) {
+ uint8 value = 0;
- _timbresSize = stream.size();
+ value |= _operatorParams[oper][kParamAM] ? 0x80 : 0;
+ value |= _operatorParams[oper][kParamVib] ? 0x40 : 0;
+ value |= _operatorParams[oper][kParamSustaining] ? 0x20 : 0;
+ value |= _operatorParams[oper][kParamKeyScaleRate] ? 0x10 : 0;
+ value |= _operatorParams[oper][kParamFreqMulti] & 0x0F;
- _timbres = new byte[_timbresSize];
- stream.read(_timbres, _timbresSize);
+ writeOPL(0x20 + kOperatorOffset[oper], value);
+}
- reset();
- setVoices();
+void AdLib::writeTremoloVibratoDepthPercMode() {
+ uint8 value = 0;
+
+ value |= _tremoloDepth ? 0x80 : 0;
+ value |= _vibratoDepth ? 0x40 : 0;
+ value |= isPercussionMode() ? 0x20 : 0;
+ value |= _percussionBits;
- return true;
+ writeOPL(0xBD, value);
}
-bool MDYPlayer::loadTBR(const char *fileName) {
- Common::File timbres;
+void AdLib::writeWaveSelect(uint8 oper) {
+ uint8 wave = 0;
+ if (_enableWaveSelect)
+ wave = _operatorParams[oper][kParamWaveSelect] & 0x03;
- timbres.open(fileName);
- if (!timbres.isOpen())
- return false;
+ writeOPL(0xE0 + kOperatorOffset[ oper], wave);
+}
- bool loaded = loadTBR(timbres);
+void AdLib::writeAllParams(uint8 oper) {
+ writeTremoloVibratoDepthPercMode();
+ writeKeySplit();
+ writeKeyScaleLevelVolume(oper);
+ writeFeedbackFM(oper);
+ writeAttackDecay(oper);
+ writeSustainRelease(oper);
+ writeTremoloVibratoSustainingKeyScaleRateFreqMulti(oper);
+ writeWaveSelect(oper);
+}
- timbres.close();
+void AdLib::initOperatorParams() {
+ for (int i = 0; i < kOperatorCount; i++)
+ setOperatorParams(i, kPianoParams[kOperatorType[i]], kPianoParams[kOperatorType[i]][kParamCount - 1]);
- return loaded;
+ if (isPercussionMode()) {
+ setOperatorParams(12, kBaseDrumParams [0], kBaseDrumParams [0][kParamCount - 1]);
+ setOperatorParams(15, kBaseDrumParams [1], kBaseDrumParams [1][kParamCount - 1]);
+ setOperatorParams(16, kSnareDrumParams , kSnareDrumParams [kParamCount - 1]);
+ setOperatorParams(14, kTomParams , kTomParams [kParamCount - 1]);
+ setOperatorParams(17, kCymbalParams , kCymbalParams [kParamCount - 1]);
+ setOperatorParams(13, kHihatParams , kHihatParams [kParamCount - 1]);
+ }
}
-void MDYPlayer::unload() {
- unloadTBR();
- unloadMDY();
+void AdLib::initOperatorVolumes() {
+ for(int i = 0; i < kOperatorCount; i++)
+ _operatorVolume[i] = kMaxVolume;
}
-void MDYPlayer::unloadMDY() {
- AdLib::unload();
+void AdLib::setOperatorParams(uint8 oper, const uint16 *params, uint8 wave) {
+ byte *operParams = _operatorParams[oper];
+
+ for (int i = 0; i < (kParamCount - 1); i++)
+ operParams[i] = params[i];
+
+ operParams[kParamCount - 1] = wave & 0x03;
+
+ writeAllParams(oper);
+}
+
+void AdLib::voiceOff(uint8 voice) {
+ writeOPL(0xA0 + voice, 0);
+ writeOPL(0xB0 + voice, 0);
}
-void MDYPlayer::unloadTBR() {
- delete[] _timbres;
+int32 AdLib::calcFreq(int32 deltaDemiToneNum, int32 deltaDemiToneDenom) {
+ int32 freq = 0;
- _timbres = 0;
- _timbresSize = 0;
+ freq = ((deltaDemiToneDenom * 100) + 6 * deltaDemiToneNum) * 52088;
+ freq /= deltaDemiToneDenom * 2500;
+
+ return (freq * 147456) / 111875;
}
-void MDYPlayer::interpret() {
- unsigned char instr;
- byte channel;
- byte note;
- byte volume;
- uint8 tempoMult, tempoFrac;
- uint8 ctrlByte1, ctrlByte2;
- uint8 timbre;
+void AdLib::setFreqs(uint16 *freqs, int32 num, int32 denom) {
+ int32 val = calcFreq(num, denom);
-// TODO : Verify the loop for percussive mode (11 ?)
- if (_first) {
- for (int i = 0; i < 9; i ++)
- setVolume(i, 0);
+ *freqs++ = (4 + val) >> 3;
-// TODO : Set pitch range
+ for (int i = 1; i < kHalfToneCount; i++) {
+ val = (val * 106) / 100;
- _tempo = _basicTempo;
- _wait = *(_playPos++);
- _first = false;
+ *freqs++ = (4 + val) >> 3;
}
- do {
- instr = *_playPos;
- debugC(6, kDebugSound, "MDYPlayer::interpret instr 0x%X", instr);
- switch (instr) {
- case 0xF8:
- _wait = *(_playPos++);
- break;
- case 0xFC:
- _ended = true;
- _samplesTillPoll = 0;
- return;
- case 0xF0:
- _playPos++;
- ctrlByte1 = *(_playPos++);
- ctrlByte2 = *(_playPos++);
- debugC(6, kDebugSound, "MDYPlayer::interpret ctrlBytes 0x%X 0x%X", ctrlByte1, ctrlByte2);
- if (ctrlByte1 != 0x7F || ctrlByte2 != 0) {
- _playPos -= 2;
- while (*(_playPos++) != 0xF7)
- ;
- } else {
- tempoMult = *(_playPos++);
- tempoFrac = *(_playPos++);
- _tempo = _basicTempo * tempoMult + (unsigned)(((long)_basicTempo * tempoFrac) >> 7);
- _playPos++;
- }
- _wait = *(_playPos++);
- break;
- default:
- if (instr >= 0x80) {
- _playPos++;
- }
- channel = (int)(instr & 0x0f);
-
- switch (instr & 0xf0) {
- case 0x90:
- note = *(_playPos++);
- volume = *(_playPos++);
- _pollNotes[channel] = note;
- setVolume(channel, volume);
- setKey(channel, note, true, false);
- break;
- case 0x80:
- _playPos += 2;
- note = _pollNotes[channel];
- setKey(channel, note, false, false);
- break;
- case 0xA0:
- setVolume(channel, *(_playPos++));
- break;
- case 0xC0:
- timbre = *(_playPos++);
- setVoice(channel, timbre, false);
- break;
- case 0xE0:
- warning("MDYPlayer: Pitch bend not yet implemented");
+}
- note = *(_playPos)++;
- note += (unsigned)(*(_playPos++)) << 7;
+void AdLib::initFreqs() {
+ const int numStep = 100 / kPitchStepCount;
- setKey(channel, note, _notOn[channel], true);
+ for (int i = 0; i < kPitchStepCount; i++)
+ setFreqs(_freqs[i], i * numStep, 100);
- break;
- case 0xB0:
- _playPos += 2;
- break;
- case 0xD0:
- _playPos++;
- break;
- default:
- warning("MDYPlayer: Bad MIDI instr byte: 0%X", instr);
- while ((*_playPos) < 0x80)
- _playPos++;
- if (*_playPos != 0xF8)
- _playPos--;
- break;
- } //switch instr & 0xF0
- _wait = *(_playPos++);
- break;
- } //switch instr
- } while (_wait == 0);
-
- if (_wait == 0xF8) {
- _wait = 0xF0;
- if (*_playPos != 0xF8)
- _wait += *(_playPos++) & 0x0F;
+ resetFreqs();
+}
+
+void AdLib::resetFreqs() {
+ for (int i = 0; i < kMaxVoiceCount; i++) {
+ _freqPtr [i] = _freqs[0];
+ _halfToneOffset[i] = 0;
}
-// _playPos++;
- _samplesTillPoll = _wait * (_rate / 1000);
}
-void MDYPlayer::reset() {
- AdLib::reset();
+void AdLib::changePitch(uint8 voice, uint16 pitchBend) {
+
+ int full = 0;
+ int frac = 0;
+ int amount = ((pitchBend - kMidPitch) * _pitchRangeStep) / kMidPitch;
+
+ if (amount >= 0) {
+ // Bend up
+
+ full = amount / kPitchStepCount;
+ frac = amount % kPitchStepCount;
-// _soundMode 1 : Percussive mode.
- if (_soundMode == 1) {
- writeOPL(0xA6, 0);
- writeOPL(0xB6, 0);
- writeOPL(0xA7, 0);
- writeOPL(0xB7, 0);
- writeOPL(0xA8, 0);
- writeOPL(0xB8, 0);
+ } else {
+ // Bend down
+
+ amount = kPitchStepCount - 1 - amount;
+
+ full = -(amount / kPitchStepCount);
+ frac = (amount - kPitchStepCount + 1) % kPitchStepCount;
+ if (frac)
+ frac = kPitchStepCount - frac;
-// TODO set the correct frequency for the last 4 percussive voices
}
+
+ _halfToneOffset[voice] = full;
+ _freqPtr [voice] = _freqs[frac];
}
-void MDYPlayer::rewind() {
- _playPos = _data;
-}
-
-void MDYPlayer::setVoices() {
- byte *timbrePtr;
-
- timbrePtr = _timbres;
- debugC(6, kDebugSound, "MDYPlayer::setVoices TBR version: %X.%X", timbrePtr[0], timbrePtr[1]);
- timbrePtr += 2;
-
- _tbrCount = READ_LE_UINT16(timbrePtr);
- debugC(6, kDebugSound, "MDYPlayer::setVoices Timbres counter: %d", _tbrCount);
- timbrePtr += 2;
- _tbrStart = READ_LE_UINT16(timbrePtr);
-
- timbrePtr += 2;
- for (int i = 0; i < _tbrCount; i++)
- setVoice(i, i, true);
-}
-
-void MDYPlayer::setVoice(byte voice, byte instr, bool set) {
-// uint16 strct[27];
- uint8 strct[27];
- byte channel;
- byte *timbrePtr;
- char timbreName[10];
-
- timbreName[9] = '\0';
- for (int j = 0; j < 9; j++)
- timbreName[j] = _timbres[6 + j + (instr * 9)];
- debugC(6, kDebugSound, "MDYPlayer::setVoice Loading timbre %s", timbreName);
-
- // i = 0 : 0 1 2 3 4 5 6 7 8 9 10 11 12 26
- // i = 1 : 13 14 15 16 17 18 19 20 21 22 23 24 25 27
- for (int i = 0; i < 2; i++) {
- timbrePtr = _timbres + _tbrStart + instr * 0x38 + i * 0x1A;
- for (int j = 0; j < 27; j++) {
- if (timbrePtr >= (_timbres + _timbresSize)) {
- warning("MDYPlayer: Instrument %d out of range (%d, %d)", instr,
- (uint32) (timbrePtr - _timbres), _timbresSize);
- strct[j] = 0;
- } else
- //strct[j] = READ_LE_UINT16(timbrePtr);
- strct[j] = timbrePtr[0];
- //timbrePtr += 2;
- timbrePtr++;
- }
- channel = _operators[voice] + i * 3;
- writeOPL(0xBD, 0x00);
- writeOPL(0x08, 0x00);
- writeOPL(0x40 | channel, ((strct[0] & 3) << 6) | (strct[8] & 0x3F));
- if (!i)
- writeOPL(0xC0 | voice,
- ((strct[2] & 7) << 1) | (1 - (strct[12] & 1)));
- writeOPL(0x60 | channel, ((strct[3] & 0xF) << 4) | (strct[6] & 0xF));
- writeOPL(0x80 | channel, ((strct[4] & 0xF) << 4) | (strct[7] & 0xF));
- writeOPL(0x20 | channel, ((strct[9] & 1) << 7) |
- ((strct[10] & 1) << 6) | ((strct[5] & 1) << 5) |
- ((strct[11] & 1) << 4) | (strct[1] & 0xF));
- if (!i)
- writeOPL(0xE0 | channel, (strct[26] & 3));
- else {
- writeOPL(0xE0 | channel, (strct[14] & 3));
- writeOPL(0x40 | channel, 0);
- }
- }
+void AdLib::setFreq(uint8 voice, uint16 note, bool on) {
+ _voiceOn [voice] = on;
+ _voiceNote[voice] = note;
+
+ note = CLIP<int>(note + _halfToneOffset[voice], 0, kNoteCount - 1);
+
+ uint16 freq = _freqPtr[voice][note % kHalfToneCount];
+
+ uint8 value = 0;
+ value |= on ? 0x20 : 0;
+ value |= ((note / kHalfToneCount) << 2) | ((freq >> 8) & 0x03);
+
+ writeOPL(0xA0 + voice, freq);
+ writeOPL(0xB0 + voice, value);
}
} // End of namespace Gob
diff --git a/engines/gob/sound/adlib.h b/engines/gob/sound/adlib.h
index 934e9966eb..bd1778d2ed 100644
--- a/engines/gob/sound/adlib.h
+++ b/engines/gob/sound/adlib.h
@@ -24,148 +24,282 @@
#define GOB_SOUND_ADLIB_H
#include "common/mutex.h"
+
#include "audio/audiostream.h"
#include "audio/mixer.h"
-#include "audio/fmopl.h"
-namespace Gob {
+namespace OPL {
+ class OPL;
+}
-class GobEngine;
+namespace Gob {
+/** Base class for a player of an AdLib music format. */
class AdLib : public Audio::AudioStream {
public:
AdLib(Audio::Mixer &mixer);
virtual ~AdLib();
- bool isPlaying() const;
- int getIndex() const;
- bool getRepeating() const;
+ bool isPlaying() const; ///< Are we currently playing?
+ int32 getRepeating() const; ///< Return number of times left to loop.
+ /** Set the loop counter.
+ *
+ * @param repCount Number of times to loop (i.e. number of additional
+ * paythroughs to the first one, not overall).
+ * A negative value means infinite looping.
+ */
void setRepeating(int32 repCount);
void startPlay();
void stopPlay();
- virtual void unload();
-
// AudioStream API
int readBuffer(int16 *buffer, const int numSamples);
- bool isStereo() const { return false; }
- bool endOfData() const { return !_playing; }
- bool endOfStream() const { return false; }
- int getRate() const { return _rate; }
+ bool isStereo() const;
+ bool endOfData() const;
+ bool endOfStream() const;
+ int getRate() const;
protected:
- static const unsigned char _operators[];
- static const unsigned char _volRegNums [];
+ enum kVoice {
+ kVoiceMelody0 = 0,
+ kVoiceMelody1 = 1,
+ kVoiceMelody2 = 2,
+ kVoiceMelody3 = 3,
+ kVoiceMelody4 = 4,
+ kVoiceMelody5 = 5,
+ kVoiceMelody6 = 6, // Only available in melody mode.
+ kVoiceMelody7 = 7, // Only available in melody mode.
+ kVoiceMelody8 = 8, // Only available in melody mode.
+ kVoiceBaseDrum = 6, // Only available in percussion mode.
+ kVoiceSnareDrum = 7, // Only available in percussion mode.
+ kVoiceTom = 8, // Only available in percussion mode.
+ kVoiceCymbal = 9, // Only available in percussion mode.
+ kVoiceHihat = 10 // Only available in percussion mode.
+ };
+
+ /** Operator parameters. */
+ enum kParam {
+ kParamKeyScaleLevel = 0,
+ kParamFreqMulti = 1,
+ kParamFeedback = 2,
+ kParamAttack = 3,
+ kParamSustain = 4,
+ kParamSustaining = 5,
+ kParamDecay = 6,
+ kParamRelease = 7,
+ kParamLevel = 8,
+ kParamAM = 9,
+ kParamVib = 10,
+ kParamKeyScaleRate = 11,
+ kParamFM = 12,
+ kParamWaveSelect = 13
+ };
+
+ static const int kOperatorCount = 18; ///< Number of operators.
+ static const int kParamCount = 14; ///< Number of operator parameters.
+ static const int kPitchStepCount = 25; ///< Number of pitch bend steps in a half tone.
+ static const int kOctaveCount = 8; ///< Number of octaves we can play.
+ static const int kHalfToneCount = 12; ///< Number of half tones in an octave.
+
+ static const int kOperatorsPerVoice = 2; ///< Number of operators per voice.
+
+ static const int kMelodyVoiceCount = 9; ///< Number of melody voices.
+ static const int kPercussionVoiceCount = 5; ///< Number of percussion voices.
+ static const int kMaxVoiceCount = 11; ///< Max number of voices.
+
+ /** Number of notes we can play. */
+ static const int kNoteCount = kHalfToneCount * kOctaveCount;
+
+ static const int kMaxVolume = 0x007F;
+ static const int kMaxPitch = 0x3FFF;
+ static const int kMidPitch = 0x2000;
+
+ static const int kStandardMidC = 60; ///< A mid C in standard MIDI.
+ static const int kOPLMidC = 48; ///< A mid C for the OPL.
+
+
+ /** Return the number of samples per second. */
+ uint32 getSamplesPerSecond() const;
+
+ /** Write a value into an OPL register. */
+ void writeOPL(byte reg, byte val);
+
+ /** Signal that the playback ended.
+ *
+ * @param killRepeat Explicitly request that the song is not to be looped.
+ */
+ void end(bool killRepeat = false);
+
+ /** The callback function that's called for polling more AdLib commands.
+ *
+ * @param first Is this the first poll since the start of the song?
+ * @return The number of samples until the next poll.
+ */
+ virtual uint32 pollMusic(bool first) = 0;
+
+ /** Rewind the song. */
+ virtual void rewind() = 0;
+
+ /** Return whether we're in percussion mode. */
+ bool isPercussionMode() const;
+
+ /** Set percussion or melody mode. */
+ void setPercussionMode(bool percussion);
+
+ /** Enable/Disable the wave select operator parameters.
+ *
+ * When disabled, all operators use the sine wave, regardless of the parameter.
+ */
+ void enableWaveSelect(bool enable);
+
+ /** Change the pitch bend range.
+ *
+ * @param range The range in half tones from 1 to 12 inclusive.
+ * See bendVoicePitch() for how this works in practice.
+ */
+ void setPitchRange(uint8 range);
+
+ /** Set the tremolo (amplitude vibrato) depth.
+ *
+ * @param tremoloDepth false: 1.0dB, true: 4.8dB.
+ */
+ void setTremoloDepth(bool tremoloDepth);
+
+ /** Set the frequency vibrato depth.
+ *
+ * @param vibratoDepth false: 7 cent, true: 14 cent. 1 cent = 1/100 half tone.
+ */
+ void setVibratoDepth(bool vibratoDepth);
+
+ /** Set the keyboard split point. */
+ void setKeySplit(bool keySplit);
+
+ /** Set the timbre of a voice.
+ *
+ * Layout of the operator parameters is as follows:
+ * - First 13 parameter for the first operator
+ * - First 13 parameter for the second operator
+ * - 14th parameter (wave select) for the first operator
+ * - 14th parameter (wave select) for the second operator
+ */
+ void setVoiceTimbre(uint8 voice, const uint16 *params);
+
+ /** Set a voice's volume. */
+ void setVoiceVolume(uint8 voice, uint8 volume);
+
+ /** Bend a voice's pitch.
+ *
+ * The pitchBend parameter is a value between 0 (full down) and kMaxPitch (full up).
+ * The actual frequency depends on the pitch range set previously by setPitchRange(),
+ * with full down being -range half tones and full up range half tones.
+ */
+ void bendVoicePitch(uint8 voice, uint16 pitchBend);
+
+ /** Switch a voice on.
+ *
+ * Plays one of the kNoteCount notes. However, the valid range of a note is between
+ * 0 and 127, of which only 12 to 107 are audible.
+ */
+ void noteOn(uint8 voice, uint8 note);
+
+ /** Switch a voice off. */
+ void noteOff(uint8 voice);
+
+private:
+ static const uint8 kOperatorType [kOperatorCount];
+ static const uint8 kOperatorOffset[kOperatorCount];
+ static const uint8 kOperatorVoice [kOperatorCount];
+
+ static const uint8 kVoiceMelodyOperator [kOperatorsPerVoice][kMelodyVoiceCount];
+ static const uint8 kVoicePercussionOperator[kOperatorsPerVoice][kPercussionVoiceCount];
+
+ static const byte kPercussionMasks[kPercussionVoiceCount];
+
+ static const uint16 kPianoParams [kOperatorsPerVoice][kParamCount];
+ static const uint16 kBaseDrumParams [kOperatorsPerVoice][kParamCount];
+
+ static const uint16 kSnareDrumParams[kParamCount];
+ static const uint16 kTomParams [kParamCount];
+ static const uint16 kCymbalParams [kParamCount];
+ static const uint16 kHihatParams [kParamCount];
+
Audio::Mixer *_mixer;
Audio::SoundHandle _handle;
- FM_OPL *_opl;
+ OPL::OPL *_opl;
Common::Mutex _mutex;
uint32 _rate;
- byte *_data;
- byte *_playPos;
- uint32 _dataSize;
-
- short _freqs[25][12];
- byte _notes[11];
- byte _notCol[11];
- byte _notLin[11];
- bool _notOn[11];
- byte _pollNotes[16];
+ uint32 _toPoll;
- int _samplesTillPoll;
int32 _repCount;
- bool _playing;
bool _first;
+ bool _playing;
bool _ended;
- bool _freeData;
+ bool _tremoloDepth;
+ bool _vibratoDepth;
+ bool _keySplit;
- int _index;
+ bool _enableWaveSelect;
- unsigned char _wait;
- uint8 _tickBeat;
- uint8 _beatMeasure;
- uint32 _totalTick;
- uint32 _nrCommand;
- uint16 _pitchBendRangeStep;
- uint16 _basicTempo, _tempo;
+ bool _percussionMode;
+ byte _percussionBits;
- void writeOPL(byte reg, byte val);
- void setFreqs();
- void setKey(byte voice, byte note, bool on, bool spec);
- void setVolume(byte voice, byte volume);
- void pollMusic();
+ uint8 _pitchRange;
+ uint16 _pitchRangeStep;
- virtual void interpret() = 0;
+ uint8 _voiceNote[kMaxVoiceCount]; // Last note of each voice
+ uint8 _voiceOn [kMaxVoiceCount]; // Whether each voice is currently on
- virtual void reset();
- virtual void rewind() = 0;
- virtual void setVoices() = 0;
+ uint8 _operatorVolume[kOperatorCount]; // Volume of each operator
-private:
- void init();
-};
+ byte _operatorParams[kOperatorCount][kParamCount]; // All operator parameters
-class ADLPlayer : public AdLib {
-public:
- ADLPlayer(Audio::Mixer &mixer);
- ~ADLPlayer();
+ uint16 _freqs[kPitchStepCount][kHalfToneCount];
+ uint16 *_freqPtr[kMaxVoiceCount];
- bool load(const char *fileName);
- bool load(byte *data, uint32 size, int index = -1);
+ int _halfToneOffset[kMaxVoiceCount];
- void unload();
-protected:
- void interpret();
+ void createOPL();
+ void initOPL();
void reset();
- void rewind();
+ void allOff();
- void setVoices();
- void setVoice(byte voice, byte instr, bool set);
-};
+ // Write global parameters into the OPL
+ void writeTremoloVibratoDepthPercMode();
+ void writeKeySplit();
-class MDYPlayer : public AdLib {
-public:
- MDYPlayer(Audio::Mixer &mixer);
- ~MDYPlayer();
-
- bool loadMDY(const char *fileName);
- bool loadMDY(Common::SeekableReadStream &stream);
- bool loadTBR(const char *fileName);
- bool loadTBR(Common::SeekableReadStream &stream);
-
- void unload();
-
-protected:
- byte _soundMode;
-
- byte *_timbres;
- uint16 _tbrCount;
- uint16 _tbrStart;
- uint32 _timbresSize;
+ // Write operator parameters into the OPL
+ void writeWaveSelect(uint8 oper);
+ void writeTremoloVibratoSustainingKeyScaleRateFreqMulti(uint8 oper);
+ void writeSustainRelease(uint8 oper);
+ void writeAttackDecay(uint8 oper);
+ void writeFeedbackFM(uint8 oper);
+ void writeKeyScaleLevelVolume(uint8 oper);
+ void writeAllParams(uint8 oper);
- void interpret();
+ void initOperatorParams();
+ void initOperatorVolumes();
+ void setOperatorParams(uint8 oper, const uint16 *params, uint8 wave);
- void reset();
- void rewind();
+ void voiceOff(uint8 voice);
- void setVoices();
- void setVoice(byte voice, byte instr, bool set);
+ void initFreqs();
+ void setFreqs(uint16 *freqs, int32 num, int32 denom);
+ int32 calcFreq(int32 deltaDemiToneNum, int32 deltaDemiToneDenom);
+ void resetFreqs();
- void unloadTBR();
- void unloadMDY();
+ void changePitch(uint8 voice, uint16 pitchBend);
-private:
- void init();
+ void setFreq(uint8 voice, uint16 note, bool on);
};
} // End of namespace Gob
diff --git a/engines/gob/sound/adlplayer.cpp b/engines/gob/sound/adlplayer.cpp
new file mode 100644
index 0000000000..ee23191c0d
--- /dev/null
+++ b/engines/gob/sound/adlplayer.cpp
@@ -0,0 +1,257 @@
+/* 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 "common/stream.h"
+#include "common/memstream.h"
+#include "common/textconsole.h"
+
+#include "gob/sound/adlplayer.h"
+
+namespace Gob {
+
+ADLPlayer::ADLPlayer(Audio::Mixer &mixer) : AdLib(mixer),
+ _songData(0), _songDataSize(0), _playPos(0) {
+
+}
+
+ADLPlayer::~ADLPlayer() {
+ unload();
+}
+
+void ADLPlayer::unload() {
+ stopPlay();
+
+ _timbres.clear();
+
+ delete[] _songData;
+
+ _songData = 0;
+ _songDataSize = 0;
+
+ _playPos = 0;
+}
+
+uint32 ADLPlayer::pollMusic(bool first) {
+ if (_timbres.empty() || !_songData || !_playPos || (_playPos >= (_songData + _songDataSize))) {
+ end();
+ return 0;
+ }
+
+ // We'll ignore the first delay
+ if (first)
+ _playPos += (*_playPos & 0x80) ? 2 : 1;
+
+ byte cmd = *_playPos++;
+
+ // Song end marker
+ if (cmd == 0xFF) {
+ end();
+ return 0;
+ }
+
+ // Set the instrument that should be modified
+ if (cmd == 0xFE)
+ _modifyInstrument = *_playPos++;
+
+ if (cmd >= 0xD0) {
+ // Modify an instrument
+
+ if (_modifyInstrument == 0xFF)
+ warning("ADLPlayer: No instrument to modify");
+ else if (_modifyInstrument >= _timbres.size())
+ warning("ADLPlayer: Can't modify invalid instrument %d (%d)", _modifyInstrument, _timbres.size());
+ else
+ _timbres[_modifyInstrument].params[_playPos[0]] = _playPos[1];
+
+ _playPos += 2;
+
+ // If we currently have that instrument loaded, reload it
+ for (int i = 0; i < kMaxVoiceCount; i++)
+ if (_currentInstruments[i] == _modifyInstrument)
+ setInstrument(i, _modifyInstrument);
+ } else {
+ // Voice command
+
+ uint8 voice = cmd & 0x0F;
+ uint8 note, volume;
+
+ switch (cmd & 0xF0) {
+ case 0x00: // Note on with volume
+ note = *_playPos++;
+ volume = *_playPos++;
+
+ setVoiceVolume(voice, volume);
+ noteOn(voice, note);
+ break;
+
+ case 0xA0: // Pitch bend
+ bendVoicePitch(voice, ((uint16)*_playPos++) << 7);
+ break;
+
+ case 0xB0: // Set volume
+ setVoiceVolume(voice, *_playPos++);
+ break;
+
+ case 0xC0: // Set instrument
+ setInstrument(voice, *_playPos++);
+ break;
+
+ case 0x90: // Note on
+ noteOn(voice, *_playPos++);
+ break;
+
+ case 0x80: // Note off
+ noteOff(voice);
+ break;
+
+ default:
+ warning("ADLPlayer: Unsupported command: 0x%02X. Stopping playback.", cmd);
+ end(true);
+ return 0;
+ }
+ }
+
+ uint16 delay = *_playPos++;
+
+ if (delay & 0x80)
+ delay = ((delay & 3) << 8) | *_playPos++;
+
+ return getSampleDelay(delay);
+}
+
+uint32 ADLPlayer::getSampleDelay(uint16 delay) const {
+ if (delay == 0)
+ return 0;
+
+ return ((uint32)delay * getSamplesPerSecond()) / 1000;
+}
+
+void ADLPlayer::rewind() {
+ // Reset song data
+ _playPos = _songData;
+
+ // Set melody/percussion mode
+ setPercussionMode(_soundMode != 0);
+
+ // Reset instruments
+ for (Common::Array<Timbre>::iterator t = _timbres.begin(); t != _timbres.end(); ++t)
+ memcpy(t->params, t->startParams, kOperatorsPerVoice * kParamCount * sizeof(uint16));
+
+ for (int i = 0; i < kMaxVoiceCount; i++)
+ _currentInstruments[i] = 0;
+
+ // Reset voices
+ int numVoice = MIN<int>(_timbres.size(), _soundMode ? (int)kMaxVoiceCount : (int)kMelodyVoiceCount);
+ for (int i = 0; i < numVoice; i++) {
+ setInstrument(i, _currentInstruments[i]);
+ setVoiceVolume(i, kMaxVolume);
+ }
+
+ _modifyInstrument = 0xFF;
+}
+
+bool ADLPlayer::load(Common::SeekableReadStream &adl) {
+ unload();
+
+ int timbreCount;
+ if (!readHeader(adl, timbreCount)) {
+ unload();
+ return false;
+ }
+
+ if (!readTimbres(adl, timbreCount) || !readSongData(adl) || adl.err()) {
+ unload();
+ return false;
+ }
+
+ rewind();
+
+ return true;
+}
+
+bool ADLPlayer::readHeader(Common::SeekableReadStream &adl, int &timbreCount) {
+ // Sanity check
+ if (adl.size() < 60) {
+ warning("ADLPlayer::readHeader(): File too small (%d)", adl.size());
+ return false;
+ }
+
+ _soundMode = adl.readByte();
+ timbreCount = adl.readByte() + 1;
+
+ adl.skip(1);
+
+ return true;
+}
+
+bool ADLPlayer::readTimbres(Common::SeekableReadStream &adl, int timbreCount) {
+ _timbres.resize(timbreCount);
+ for (Common::Array<Timbre>::iterator t = _timbres.begin(); t != _timbres.end(); ++t) {
+ for (int i = 0; i < (kOperatorsPerVoice * kParamCount); i++)
+ t->startParams[i] = adl.readUint16LE();
+ }
+
+ if (adl.err()) {
+ warning("ADLPlayer::readTimbres(): Read failed");
+ return false;
+ }
+
+ return true;
+}
+
+bool ADLPlayer::readSongData(Common::SeekableReadStream &adl) {
+ _songDataSize = adl.size() - adl.pos();
+ _songData = new byte[_songDataSize];
+
+ if (adl.read(_songData, _songDataSize) != _songDataSize) {
+ warning("ADLPlayer::readSongData(): Read failed");
+ return false;
+ }
+
+ return true;
+}
+
+bool ADLPlayer::load(const byte *data, uint32 dataSize, int index) {
+ unload();
+
+ Common::MemoryReadStream stream(data, dataSize);
+ if (!load(stream))
+ return false;
+
+ _index = index;
+ return true;
+}
+
+void ADLPlayer::setInstrument(int voice, int instrument) {
+ if ((voice >= kMaxVoiceCount) || ((uint)instrument >= _timbres.size()))
+ return;
+
+ _currentInstruments[voice] = instrument;
+
+ setVoiceTimbre(voice, _timbres[instrument].params);
+}
+
+int ADLPlayer::getIndex() const {
+ return _index;
+}
+
+} // End of namespace Gob
diff --git a/engines/gob/sound/adlplayer.h b/engines/gob/sound/adlplayer.h
new file mode 100644
index 0000000000..9596447bbc
--- /dev/null
+++ b/engines/gob/sound/adlplayer.h
@@ -0,0 +1,85 @@
+/* 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 GOB_SOUND_ADLPLAYER_H
+#define GOB_SOUND_ADLPLAYER_H
+
+#include "common/array.h"
+
+#include "gob/sound/adlib.h"
+
+namespace Common {
+ class SeekableReadStream;
+}
+
+namespace Gob {
+
+/** A player for Coktel Vision's ADL music format. */
+class ADLPlayer : public AdLib {
+public:
+ ADLPlayer(Audio::Mixer &mixer);
+ ~ADLPlayer();
+
+ bool load(Common::SeekableReadStream &adl);
+ bool load(const byte *data, uint32 dataSize, int index = -1);
+ void unload();
+
+ int getIndex() const;
+
+protected:
+ // AdLib interface
+ uint32 pollMusic(bool first);
+ void rewind();
+
+private:
+ struct Timbre {
+ uint16 startParams[kOperatorsPerVoice * kParamCount];
+ uint16 params[kOperatorsPerVoice * kParamCount];
+ };
+
+ uint8 _soundMode;
+
+ Common::Array<Timbre> _timbres;
+
+ byte *_songData;
+ uint32 _songDataSize;
+
+ const byte *_playPos;
+
+ int _index;
+
+ uint8 _modifyInstrument;
+ uint16 _currentInstruments[kMaxVoiceCount];
+
+
+ void setInstrument(int voice, int instrument);
+
+ bool readHeader (Common::SeekableReadStream &adl, int &timbreCount);
+ bool readTimbres (Common::SeekableReadStream &adl, int timbreCount);
+ bool readSongData(Common::SeekableReadStream &adl);
+
+ uint32 getSampleDelay(uint16 delay) const;
+};
+
+} // End of namespace Gob
+
+#endif // GOB_SOUND_ADLPLAYER_H
diff --git a/engines/gob/sound/musplayer.cpp b/engines/gob/sound/musplayer.cpp
new file mode 100644
index 0000000000..3e41dc6ed1
--- /dev/null
+++ b/engines/gob/sound/musplayer.cpp
@@ -0,0 +1,391 @@
+/* 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 "common/stream.h"
+#include "common/textconsole.h"
+
+#include "gob/sound/musplayer.h"
+
+namespace Gob {
+
+MUSPlayer::MUSPlayer(Audio::Mixer &mixer) : AdLib(mixer),
+ _songData(0), _songDataSize(0), _playPos(0), _songID(0) {
+
+}
+
+MUSPlayer::~MUSPlayer() {
+ unload();
+}
+
+void MUSPlayer::unload() {
+ stopPlay();
+
+ unloadSND();
+ unloadMUS();
+}
+
+uint32 MUSPlayer::getSampleDelay(uint16 delay) const {
+ if (delay == 0)
+ return 0;
+
+ uint32 freq = (_ticksPerBeat * _tempo) / 60;
+
+ return ((uint32)delay * getSamplesPerSecond()) / freq;
+}
+
+void MUSPlayer::skipToTiming() {
+ while (*_playPos < 0x80)
+ _playPos++;
+
+ if (*_playPos != 0xF8)
+ _playPos--;
+}
+
+uint32 MUSPlayer::pollMusic(bool first) {
+ if (_timbres.empty() || !_songData || !_playPos || (_playPos >= (_songData + _songDataSize))) {
+ end();
+ return 0;
+ }
+
+ if (first)
+ return getSampleDelay(*_playPos++);
+
+ uint16 delay = 0;
+ while (delay == 0) {
+ byte cmd = *_playPos;
+
+ // Delay overflow
+ if (cmd == 0xF8) {
+ _playPos++;
+ delay = 0xF8;
+ break;
+ }
+
+ // Song end marker
+ if (cmd == 0xFC) {
+ end();
+ return 0;
+ }
+
+ // Global command
+ if (cmd == 0xF0) {
+ _playPos++;
+
+ byte type1 = *_playPos++;
+ byte type2 = *_playPos++;
+
+ if ((type1 == 0x7F) && (type2 == 0)) {
+ // Tempo change, as a fraction of the base tempo
+
+ uint32 num = *_playPos++;
+ uint32 denom = *_playPos++;
+
+ _tempo = _baseTempo * num + ((_baseTempo * denom) >> 7);
+
+ _playPos++;
+ } else {
+
+ // Unsupported global command, skip it
+ _playPos -= 2;
+ while(*_playPos++ != 0xF7)
+ ;
+ }
+
+ delay = *_playPos++;
+ break;
+ }
+
+ // Voice command
+
+ if (cmd >= 0x80) {
+ _playPos++;
+
+ _lastCommand = cmd;
+ } else
+ cmd = _lastCommand;
+
+ uint8 voice = cmd & 0x0F;
+ uint8 note, volume;
+ uint16 pitch;
+
+ switch (cmd & 0xF0) {
+ case 0x80: // Note off
+ _playPos += 2;
+ noteOff(voice);
+ break;
+
+ case 0x90: // Note on
+ note = *_playPos++;
+ volume = *_playPos++;
+
+ if (volume) {
+ setVoiceVolume(voice, volume);
+ noteOn(voice, note);
+ } else
+ noteOff(voice);
+ break;
+
+ case 0xA0: // Set volume
+ setVoiceVolume(voice, *_playPos++);
+ break;
+
+ case 0xB0:
+ _playPos += 2;
+ break;
+
+ case 0xC0: // Set instrument
+ setInstrument(voice, *_playPos++);
+ break;
+
+ case 0xD0:
+ _playPos++;
+ break;
+
+ case 0xE0: // Pitch bend
+ pitch = *_playPos++;
+ pitch += *_playPos++ << 7;
+ bendVoicePitch(voice, pitch);
+ break;
+
+ default:
+ warning("MUSPlayer: Unsupported command: 0x%02X", cmd);
+ skipToTiming();
+ break;
+ }
+
+ delay = *_playPos++;
+ }
+
+ if (delay == 0xF8) {
+ delay = 240;
+
+ if (*_playPos != 0xF8)
+ delay += *_playPos++;
+ }
+
+ return getSampleDelay(delay);
+}
+
+void MUSPlayer::rewind() {
+ _playPos = _songData;
+ _tempo = _baseTempo;
+
+ _lastCommand = 0;
+
+ setPercussionMode(_soundMode != 0);
+ setPitchRange(_pitchBendRange);
+}
+
+bool MUSPlayer::loadSND(Common::SeekableReadStream &snd) {
+ unloadSND();
+
+ int timbreCount, timbrePos;
+ if (!readSNDHeader(snd, timbreCount, timbrePos))
+ return false;
+
+ if (!readSNDTimbres(snd, timbreCount, timbrePos) || snd.err()) {
+ unloadSND();
+ return false;
+ }
+
+ return true;
+}
+
+bool MUSPlayer::readString(Common::SeekableReadStream &stream, Common::String &string, byte *buffer, uint size) {
+ if (stream.read(buffer, size) != size)
+ return false;
+
+ buffer[size] = '\0';
+
+ string = (char *) buffer;
+
+ return true;
+}
+
+bool MUSPlayer::readSNDHeader(Common::SeekableReadStream &snd, int &timbreCount, int &timbrePos) {
+ // Sanity check
+ if (snd.size() <= 6) {
+ warning("MUSPlayer::readSNDHeader(): File too small (%d)", snd.size());
+ return false;
+ }
+
+ // Version
+ const uint8 versionMajor = snd.readByte();
+ const uint8 versionMinor = snd.readByte();
+
+ if ((versionMajor != 1) && (versionMinor != 0)) {
+ warning("MUSPlayer::readSNDHeader(): Unsupported version %d.%d", versionMajor, versionMinor);
+ return false;
+ }
+
+ // Number of timbres and where they start
+ timbreCount = snd.readUint16LE();
+ timbrePos = snd.readUint16LE();
+
+ const uint16 minTimbrePos = 6 + timbreCount * 9;
+
+ // Sanity check
+ if (timbrePos < minTimbrePos) {
+ warning("MUSPlayer::readSNDHeader(): Timbre offset too small: %d < %d", timbrePos, minTimbrePos);
+ return false;
+ }
+
+ const uint32 timbreParametersSize = snd.size() - timbrePos;
+ const uint32 paramSize = kOperatorsPerVoice * kParamCount * sizeof(uint16);
+
+ // Sanity check
+ if (timbreParametersSize != (timbreCount * paramSize)) {
+ warning("MUSPlayer::loadSND(): Timbre parameters size mismatch: %d != %d",
+ timbreParametersSize, timbreCount * paramSize);
+ return false;
+ }
+
+ return true;
+}
+
+bool MUSPlayer::readSNDTimbres(Common::SeekableReadStream &snd, int timbreCount, int timbrePos) {
+ _timbres.resize(timbreCount);
+
+ // Read names
+ byte nameBuffer[10];
+ for (Common::Array<Timbre>::iterator t = _timbres.begin(); t != _timbres.end(); ++t) {
+ if (!readString(snd, t->name, nameBuffer, 9)) {
+ warning("MUSPlayer::readMUSTimbres(): Failed to read timbre name");
+ return false;
+ }
+ }
+
+ if (!snd.seek(timbrePos)) {
+ warning("MUSPlayer::readMUSTimbres(): Failed to seek to timbres");
+ return false;
+ }
+
+ // Read parameters
+ for (Common::Array<Timbre>::iterator t = _timbres.begin(); t != _timbres.end(); ++t) {
+ for (int i = 0; i < (kOperatorsPerVoice * kParamCount); i++)
+ t->params[i] = snd.readUint16LE();
+ }
+
+ return true;
+}
+
+bool MUSPlayer::loadMUS(Common::SeekableReadStream &mus) {
+ unloadMUS();
+
+ if (!readMUSHeader(mus) || !readMUSSong(mus) || mus.err()) {
+ unloadMUS();
+ return false;
+ }
+
+ rewind();
+
+ return true;
+}
+
+bool MUSPlayer::readMUSHeader(Common::SeekableReadStream &mus) {
+ // Sanity check
+ if (mus.size() <= 6)
+ return false;
+
+ // Version
+ const uint8 versionMajor = mus.readByte();
+ const uint8 versionMinor = mus.readByte();
+
+ if ((versionMajor != 1) && (versionMinor != 0)) {
+ warning("MUSPlayer::readMUSHeader(): Unsupported version %d.%d", versionMajor, versionMinor);
+ return false;
+ }
+
+ _songID = mus.readUint32LE();
+
+ byte nameBuffer[31];
+ if (!readString(mus, _songName, nameBuffer, 30)) {
+ warning("MUSPlayer::readMUSHeader(): Failed to read the song name");
+ return false;
+ }
+
+ _ticksPerBeat = mus.readByte();
+ _beatsPerMeasure = mus.readByte();
+
+ mus.skip(4); // Length of song in ticks
+
+ _songDataSize = mus.readUint32LE();
+
+ mus.skip(4); // Number of commands
+ mus.skip(8); // Unused
+
+ _soundMode = mus.readByte();
+ _pitchBendRange = mus.readByte();
+ _baseTempo = mus.readUint16LE();
+
+ mus.skip(8); // Unused
+
+ return true;
+}
+
+bool MUSPlayer::readMUSSong(Common::SeekableReadStream &mus) {
+ const uint32 realSongDataSize = mus.size() - mus.pos();
+
+ if (realSongDataSize < _songDataSize) {
+ warning("MUSPlayer::readMUSSong(): File too small for the song data: %d < %d", realSongDataSize, _songDataSize);
+ return false;
+ }
+
+ _songData = new byte[_songDataSize];
+
+ if (mus.read(_songData, _songDataSize) != _songDataSize) {
+ warning("MUSPlayer::readMUSSong(): Read failed");
+ return false;
+ }
+
+ return true;
+}
+
+void MUSPlayer::unloadSND() {
+ _timbres.clear();
+}
+
+void MUSPlayer::unloadMUS() {
+ delete[] _songData;
+
+ _songData = 0;
+ _songDataSize = 0;
+
+ _playPos = 0;
+}
+
+uint32 MUSPlayer::getSongID() const {
+ return _songID;
+}
+
+const Common::String &MUSPlayer::getSongName() const {
+ return _songName;
+}
+
+void MUSPlayer::setInstrument(uint8 voice, uint8 instrument) {
+ if (instrument >= _timbres.size())
+ return;
+
+ setVoiceTimbre(voice, _timbres[instrument].params);
+}
+
+} // End of namespace Gob
diff --git a/engines/gob/sound/musplayer.h b/engines/gob/sound/musplayer.h
new file mode 100644
index 0000000000..6cc2a2d2ca
--- /dev/null
+++ b/engines/gob/sound/musplayer.h
@@ -0,0 +1,109 @@
+/* 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 GOB_SOUND_MUSPLAYER_H
+#define GOB_SOUND_MUSPLAYER_H
+
+#include "common/str.h"
+#include "common/array.h"
+
+#include "gob/sound/adlib.h"
+
+namespace Common {
+ class SeekableReadStream;
+}
+
+namespace Gob {
+
+/** A player for the AdLib MUS format, with the instrument information in SND files.
+ *
+ * In the Gob engine, those files are usually named .MDY and .TBR instead.
+ */
+class MUSPlayer : public AdLib {
+public:
+ MUSPlayer(Audio::Mixer &mixer);
+ ~MUSPlayer();
+
+ /** Load the instruments (.SND or .TBR) */
+ bool loadSND(Common::SeekableReadStream &snd);
+ /** Load the melody (.MUS or .MDY) */
+ bool loadMUS(Common::SeekableReadStream &mus);
+
+ void unload();
+
+ uint32 getSongID() const;
+ const Common::String &getSongName() const;
+
+protected:
+ // AdLib interface
+ uint32 pollMusic(bool first);
+ void rewind();
+
+private:
+ struct Timbre {
+ Common::String name;
+
+ uint16 params[kOperatorsPerVoice * kParamCount];
+ };
+
+ Common::Array<Timbre> _timbres;
+
+ byte *_songData;
+ uint32 _songDataSize;
+
+ const byte *_playPos;
+
+ uint32 _songID;
+ Common::String _songName;
+
+ uint8 _ticksPerBeat;
+ uint8 _beatsPerMeasure;
+
+ uint8 _soundMode;
+ uint8 _pitchBendRange;
+
+ uint16 _baseTempo;
+
+ uint16 _tempo;
+
+ byte _lastCommand;
+
+
+ void unloadSND();
+ void unloadMUS();
+
+ bool readSNDHeader (Common::SeekableReadStream &snd, int &timbreCount, int &timbrePos);
+ bool readSNDTimbres(Common::SeekableReadStream &snd, int timbreCount, int timbrePos);
+
+ bool readMUSHeader(Common::SeekableReadStream &mus);
+ bool readMUSSong (Common::SeekableReadStream &mus);
+
+ uint32 getSampleDelay(uint16 delay) const;
+ void setInstrument(uint8 voice, uint8 instrument);
+ void skipToTiming();
+
+ static bool readString(Common::SeekableReadStream &stream, Common::String &string, byte *buffer, uint size);
+};
+
+} // End of namespace Gob
+
+#endif // GOB_SOUND_MUSPLAYER_H
diff --git a/engines/gob/sound/sound.cpp b/engines/gob/sound/sound.cpp
index bfe0394390..9f72d1a98f 100644
--- a/engines/gob/sound/sound.cpp
+++ b/engines/gob/sound/sound.cpp
@@ -30,7 +30,8 @@
#include "gob/sound/pcspeaker.h"
#include "gob/sound/soundblaster.h"
-#include "gob/sound/adlib.h"
+#include "gob/sound/adlplayer.h"
+#include "gob/sound/musplayer.h"
#include "gob/sound/infogrames.h"
#include "gob/sound/protracker.h"
#include "gob/sound/cdrom.h"
@@ -50,6 +51,8 @@ Sound::Sound(GobEngine *vm) : _vm(vm) {
_hasAdLib = (!_vm->_noMusic && _vm->hasAdLib());
+ _hasAdLibBg = _hasAdLib;
+
if (!_vm->_noMusic && (_vm->getPlatform() == Common::kPlatformAmiga)) {
_infogrames = new Infogrames(*_vm->_mixer);
_protracker = new Protracker(*_vm->_mixer);
@@ -131,10 +134,7 @@ void Sound::sampleFree(SoundDesc *sndDesc, bool noteAdLib, int index) {
if (noteAdLib) {
if (_adlPlayer)
if ((index == -1) || (_adlPlayer->getIndex() == index))
- _adlPlayer->stopPlay();
- if (_mdyPlayer)
- if ((index == -1) || (_mdyPlayer->getIndex() == index))
- _mdyPlayer->stopPlay();
+ _adlPlayer->unload();
}
} else {
@@ -235,7 +235,17 @@ bool Sound::adlibLoadADL(const char *fileName) {
debugC(1, kDebugSound, "AdLib: Loading ADL data (\"%s\")", fileName);
- return _adlPlayer->load(fileName);
+ Common::SeekableReadStream *stream = _vm->_dataIO->getFile(fileName);
+ if (!stream) {
+ warning("Can't open ADL file \"%s\"", fileName);
+ return false;
+ }
+
+ bool loaded = _adlPlayer->load(*stream);
+
+ delete stream;
+
+ return loaded;
}
bool Sound::adlibLoadADL(byte *data, uint32 size, int index) {
@@ -266,8 +276,7 @@ bool Sound::adlibLoadMDY(const char *fileName) {
if (!_hasAdLib)
return false;
- if (!_mdyPlayer)
- _mdyPlayer = new MDYPlayer(*_vm->_mixer);
+ createMDYPlayer();
debugC(1, kDebugSound, "AdLib: Loading MDY data (\"%s\")", fileName);
@@ -277,7 +286,7 @@ bool Sound::adlibLoadMDY(const char *fileName) {
return false;
}
- bool loaded = _mdyPlayer->loadMDY(*stream);
+ bool loaded = _mdyPlayer->loadMUS(*stream);
delete stream;
@@ -288,8 +297,7 @@ bool Sound::adlibLoadTBR(const char *fileName) {
if (!_hasAdLib)
return false;
- if (!_mdyPlayer)
- _mdyPlayer = new MDYPlayer(*_vm->_mixer);
+ createMDYPlayer();
Common::SeekableReadStream *stream = _vm->_dataIO->getFile(fileName);
if (!stream) {
@@ -299,7 +307,7 @@ bool Sound::adlibLoadTBR(const char *fileName) {
debugC(1, kDebugSound, "AdLib: Loading MDY instruments (\"%s\")", fileName);
- bool loaded = _mdyPlayer->loadTBR(*stream);
+ bool loaded = _mdyPlayer->loadSND(*stream);
delete stream;
@@ -310,28 +318,23 @@ void Sound::adlibPlayTrack(const char *trackname) {
if (!_hasAdLib)
return;
- if (!_adlPlayer)
- _adlPlayer = new ADLPlayer(*_vm->_mixer);
+ createADLPlayer();
if (_adlPlayer->isPlaying())
return;
- debugC(1, kDebugSound, "AdLib: Playing ADL track \"%s\"", trackname);
-
- _adlPlayer->unload();
- _adlPlayer->load(trackname);
- _adlPlayer->startPlay();
+ if (adlibLoadADL(trackname))
+ adlibPlay();
}
void Sound::adlibPlayBgMusic() {
- if (!_hasAdLib)
+ if (!_hasAdLib || _hasAdLibBg)
return;
- if (!_adlPlayer)
- _adlPlayer = new ADLPlayer(*_vm->_mixer);
+ createADLPlayer();
static const char *const tracksMac[] = {
-// "musmac1.adl", // TODO: This track isn't played correctly at all yet
+// "musmac1.adl", // This track seems to be missing instruments...
"musmac2.adl",
"musmac3.adl",
"musmac4.adl",
@@ -347,13 +350,18 @@ void Sound::adlibPlayBgMusic() {
"musmac5.mid"
};
- if (_vm->getPlatform() == Common::kPlatformWindows) {
- int track = _vm->_util->getRandom(ARRAYSIZE(tracksWin));
- adlibPlayTrack(tracksWin[track]);
- } else {
- int track = _vm->_util->getRandom(ARRAYSIZE(tracksMac));
- adlibPlayTrack(tracksMac[track]);
+ const char *track = 0;
+ if (_vm->getPlatform() == Common::kPlatformWindows)
+ track = tracksWin[ARRAYSIZE(tracksWin)];
+ else
+ track = tracksMac[_vm->_util->getRandom(ARRAYSIZE(tracksMac))];
+
+ if (!track || !_vm->_dataIO->hasFile(track)) {
+ _hasAdLibBg = false;
+ return;
}
+
+ adlibPlayTrack(track);
}
void Sound::adlibPlay() {
@@ -398,13 +406,11 @@ int Sound::adlibGetIndex() const {
if (_adlPlayer)
return _adlPlayer->getIndex();
- if (_mdyPlayer)
- return _mdyPlayer->getIndex();
return -1;
}
-bool Sound::adlibGetRepeating() const {
+int32 Sound::adlibGetRepeating() const {
if (!_hasAdLib)
return false;
@@ -719,4 +725,24 @@ void Sound::bgUnshade() {
_bgatmos->unshade();
}
+void Sound::createMDYPlayer() {
+ if (_mdyPlayer)
+ return;
+
+ delete _adlPlayer;
+ _adlPlayer = 0;
+
+ _mdyPlayer = new MUSPlayer(*_vm->_mixer);
+}
+
+void Sound::createADLPlayer() {
+ if (_adlPlayer)
+ return;
+
+ delete _mdyPlayer;
+ _mdyPlayer= 0;
+
+ _adlPlayer = new ADLPlayer(*_vm->_mixer);
+}
+
} // End of namespace Gob
diff --git a/engines/gob/sound/sound.h b/engines/gob/sound/sound.h
index 585cf36703..064a249253 100644
--- a/engines/gob/sound/sound.h
+++ b/engines/gob/sound/sound.h
@@ -32,7 +32,7 @@ class GobEngine;
class PCSpeaker;
class SoundBlaster;
class ADLPlayer;
-class MDYPlayer;
+class MUSPlayer;
class Infogrames;
class Protracker;
class CDROM;
@@ -92,7 +92,7 @@ public:
bool adlibIsPlaying() const;
int adlibGetIndex() const;
- bool adlibGetRepeating() const;
+ int32 adlibGetRepeating() const;
void adlibSetRepeating(int32 repCount);
@@ -142,17 +142,30 @@ private:
GobEngine *_vm;
bool _hasAdLib;
+ bool _hasAdLibBg;
SoundDesc _sounds[kSoundsCount];
+ // Speaker
PCSpeaker *_pcspeaker;
+
+ // PCM based
SoundBlaster *_blaster;
+ BackgroundAtmosphere *_bgatmos;
+
+ // AdLib
+ MUSPlayer *_mdyPlayer;
ADLPlayer *_adlPlayer;
- MDYPlayer *_mdyPlayer;
+
+ // Amiga Paula
Infogrames *_infogrames;
Protracker *_protracker;
+
+ // Audio CD
CDROM *_cdrom;
- BackgroundAtmosphere *_bgatmos;
+
+ void createMDYPlayer();
+ void createADLPlayer();
};
} // End of namespace Gob