NANCY: Add initial sound support

Added a sound manager and hooked the Logo and Scene classes to it; also implemented the PlayDigiSoundAndDie action record. As a result the first part of the intro and some of the sounds in the first area can now play properly.
This commit is contained in:
fracturehill
2021-03-24 17:20:46 +01:00
committed by Eugene Sandulenko
parent e12f6b6753
commit f4d8368bd4
10 changed files with 146 additions and 35 deletions
+33 -1
View File
@@ -26,6 +26,9 @@
#include "engines/nancy/logic.h"
#include "engines/nancy/nancy.h"
#include "engines/nancy/graphics.h"
#include "engines/nancy/audio.h"
#include "common/str.h"
namespace Nancy {
@@ -71,6 +74,7 @@ void HotMultiframeSceneChange::execute(NancyEngine *engine) {
switch (state) {
case kBegin:
// turn main rendering on
state = kRun;
// fall through
case kRun:
hasHotspot = false;
@@ -97,6 +101,7 @@ void Hot1FrSceneChange::execute(NancyEngine *engine) {
switch (state) {
case kBegin:
hotspot = hotspotDesc.coords;
state = kRun;
// fall through
case kRun:
if (hotspotDesc.frameID == engine->playState.currentViewFrame) {
@@ -469,7 +474,34 @@ uint16 ShowInventoryItem::readData(Common::SeekableReadStream &stream) {
}
uint16 PlayDigiSoundAndDie::readData(Common::SeekableReadStream &stream) {
return readRaw(stream, 0x2B); // TODO
char str[10];
stream.read(str, 10);
filename = Common::String(str);
id = stream.readSint16LE();
stream.skip(4);
numLoops = stream.readUint16LE();
stream.skip(4);
volume = stream.readUint16LE();
stream.skip(6);
SceneChange::readData(stream);
return 0x2B;
}
void PlayDigiSoundAndDie::execute(NancyEngine *engine){
switch (state) {
case kBegin:
engine->sound->loadSound(filename, id, numLoops, volume);
state = kRun;
break;
case kRun:
if (!engine->sound->isSoundPlaying(id)) {
state = kEnd;
}
break;
case kEnd:
SceneChange::execute(engine);
break;
}
}
uint16 PlaySoundPanFrameAnchorAndDie::readData(Common::SeekableReadStream &stream) {
+9 -1
View File
@@ -337,10 +337,18 @@ public:
virtual uint16 readData(Common::SeekableReadStream &stream) override;
};
class PlayDigiSoundAndDie : public ActionRecord {
class PlayDigiSoundAndDie : public SceneChange {
public:
virtual uint16 readData(Common::SeekableReadStream &stream) override;
virtual void execute(NancyEngine *engine) override;
// TODO subclass into Play and Stop (?)
Common::String filename;
int16 id = -1; // 0xA
uint16 numLoops = 0; // 0x10
uint16 volume = 0; // 0x16, between 0 and 60
// ...
// SceneChange elements at 0x1E
};
class PlaySoundPanFrameAnchorAndDie : public ActionRecord {
+52
View File
@@ -20,6 +20,10 @@
*
*/
#include "engines/nancy/audio.h"
#include "engines/nancy/nancy.h"
#include "common/system.h"
#include "common/debug.h"
#include "common/textconsole.h"
#include "common/stream.h"
@@ -172,4 +176,52 @@ Audio::SeekableAudioStream *makeHISStream(Common::SeekableReadStream *stream, Di
return Audio::makeVorbisStream(subStream, DisposeAfterUse::YES);
}
SoundManager::SoundManager(NancyEngine *engine) :
_engine(engine) {
_mixer = _engine->_system->getMixer();
}
// Combine load and play until i find a reason not to
void SoundManager::loadSound(Common::String &name, int16 id, uint16 numLoops, uint16 volume) {
if (_mixer->isSoundHandleActive(handles[id])) {
_mixer->stopHandle(handles[id]);
}
Common::SeekableReadStream *mSnd = SearchMan.createReadStreamForMember(name + ".his");
if (mSnd) {
Audio::RewindableAudioStream *aStr = makeHISStream(mSnd, DisposeAfterUse::YES);
if (aStr) {
Audio::AudioStream *aStrLoop = Audio::makeLoopingAudioStream(aStr, numLoops);
_engine->_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &handles[id], aStrLoop, -1, volume * 255 / 60);
names[id] = name;
}
}
}
void SoundManager::stopSound(int16 id) {
if (isSoundPlaying(id)) {
_mixer->stopHandle(handles[id]);
}
names[id] = Common::String();
}
bool SoundManager::isSoundPlaying(int16 id) {
if (id >= 0 && id < 20) {
return _mixer->isSoundHandleActive(handles[id]);
}
return false;
}
// Returns whether the exception was skipped
bool SoundManager::stopAllSounds(Common::String *except) {
bool ret = false;
for (uint i = 0; i < 20; ++i) {
if (except == nullptr || names[i] != *except) {
stopSound(i);
} else {
ret = true;
}
}
return ret;
}
} // End of namespace Nancy
+26 -4
View File
@@ -23,6 +23,11 @@
#ifndef NANCY_AUDIO_H
#define NANCY_AUDIO_H
#include "common/types.h"
#include "common/str.h"
#include "audio/mixer.h"
namespace Common {
class SeekableReadStream;
}
@@ -31,14 +36,31 @@ namespace Audio {
class SeekableAudioStream;
}
namespace DisposeAfterUse {
enum Flag;
}
namespace Nancy {
class NancyEngine;
Audio::SeekableAudioStream *makeHISStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
class SoundManager {
public:
enum SoundLoopType { kLoop = 0, kOneShot = 1 };
SoundManager(NancyEngine *engine);
~SoundManager() =default;
void loadSound(Common::String &name, int16 id, uint16 numLoops = 0, uint16 volume = 60);
void stopSound(int16 id);
bool isSoundPlaying(int16 id);
bool stopAllSounds(Common::String *except = nullptr);
private:
NancyEngine *_engine;
Audio::Mixer *_mixer;
Audio::SoundHandle handles[20];
Common::String names[20];
};
} // End of namespace Nancy
#endif // NANCY_AUDIO_H
+4 -1
View File
@@ -42,9 +42,12 @@ SceneSummary::SceneSummary(Common::SeekableReadStream *stream) {
stream->seek(3, SEEK_CUR);
videoFormat = stream->readUint16LE();
stream->read(buf, 9);
stream->read(buf, 10);
buf[9] = 0;
audioFile = Common::String(buf);
audioID = stream->readSint16LE();
stream->skip(0xE);
audioVolume = stream->readUint16LE();
stream->seek(0x72);
verticalScrollDelta = stream->readUint16LE();
+2
View File
@@ -45,6 +45,8 @@ struct SceneSummary {
//
uint16 videoFormat; // 0x3E, value is 1 or 2
Common::String audioFile; // 0x40
int16 audioID; // 0x4A
uint16 audioVolume; // 0x5A
//
uint16 verticalScrollDelta; // 0x72
uint16 horizontalEdgeSize; // 0x74
+6 -9
View File
@@ -63,15 +63,12 @@ void LogoSequence::init() {
}
void LogoSequence::startSound() {
Common::SeekableReadStream *mSnd = SearchMan.createReadStreamForMember(_engine->_menuSound.name + ".his");
if (mSnd) {
Audio::RewindableAudioStream *aStr = makeHISStream(mSnd, DisposeAfterUse::YES);
if (aStr) {
Audio::AudioStream *aStrLoop = Audio::makeLoopingAudioStream(aStr, 0);
Audio::SoundHandle handle;
_engine->_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &handle, aStrLoop);
}
}
Common::SeekableReadStream *msnd = _engine->getBootChunkStream("MSND");
char name[10];
msnd->seek(0);
msnd->read(name, 10);
Common::String sname(name);
_engine->sound->loadSound(sname, 0);
_startTicks = _engine->_system->getMillis();
_state = kRun;
+3 -10
View File
@@ -29,6 +29,7 @@
#include "engines/nancy/scene.h"
#include "engines/nancy/graphics.h"
#include "engines/nancy/input.h"
#include "engines/nancy/audio.h"
#include "common/system.h"
#include "common/random.h"
@@ -77,6 +78,7 @@ NancyEngine::NancyEngine(OSystem *syst, const NancyGameDescription *gd) :
sceneManager = new SceneManager(this);
graphics = new GraphicsManager(this);
input = new InputManager(this);
sound = new SoundManager(this);
launchConsole = false;
}
@@ -90,6 +92,7 @@ NancyEngine::~NancyEngine() {
delete logic;
delete sceneManager;
delete input;
delete sound;
}
GUI::Debugger *NancyEngine::getDebugger() {
@@ -184,7 +187,6 @@ void NancyEngine::bootGameEngine() {
if (!boot->load())
error("Failed to load boot script");
preloadCals(*boot);
readSound(*boot, "MSND", _menuSound);
addBootChunk("BSUM", boot->getChunkStream("BSUM"));
readBootSummary(*boot);
@@ -323,15 +325,6 @@ void NancyEngine::readImageList(const IFF &boot, const Common::String &prefix, I
}
}
void NancyEngine::readSound(const IFF &boot, const Common::String &name, NancyEngine::Sound &sound) {
Common::SeekableReadStream *stream = boot.getChunkStream(name);
if (!stream)
error("Failed to read BOOT %s", name.c_str());
sound.name = readFilename(stream);
}
class NancyEngine_v0 : public NancyEngine {
public:
NancyEngine_v0(OSystem *syst, const NancyGameDescription *gd) : NancyEngine(syst, gd) { }
+2 -6
View File
@@ -69,6 +69,7 @@ class SceneManager;
class Logic;
class GraphicsManager;
class InputManager;
class SoundManager;
class NancyEngine : public Engine {
public:
@@ -115,6 +116,7 @@ public:
SceneManager *sceneManager;
GraphicsManager *graphics;
InputManager *input;
SoundManager *sound;
// Contains all player data
PlayState playState;
@@ -138,10 +140,6 @@ protected:
uint16 height;
};
struct Sound {
Common::String name;
};
enum GameState {
kBoot,
kPartnerLogo, // v2 only
@@ -173,12 +171,10 @@ protected:
ImageList _objects;
uint16 _firstSceneID;
int32 _fontSize;
Sound _menuSound;
GameFlow _gameFlow;
void preloadCals(const IFF &boot);
void readImageList(const IFF &boot, const Common::String &prefix, ImageList &list);
void readSound(const IFF &boot, const Common::String &name, Sound &sound);
Common::String readFilename(Common::ReadStream *stream) const;
virtual uint getFilenameLen() const = 0;
+9 -3
View File
@@ -27,6 +27,7 @@
#include "engines/nancy/logic.h"
#include "engines/nancy/graphics.h"
#include "engines/nancy/input.h"
#include "engines/nancy/audio.h"
#include "common/memstream.h"
#include "common/rect.h"
@@ -49,8 +50,13 @@ void SceneManager::process() {
load();
break;
case kStartSound:
// TODO
break;
// Stop all sounds, unless the new scene's background music matches the last one's;
// in that case, stop everything except the background music
if (!_engine->sound->stopAllSounds(&currentScene.audioFile)) {
_engine->sound->loadSound(currentScene.audioFile, currentScene.audioID, 0, currentScene.audioVolume);
}
_state = kRun;
// fall through
case kRun:
run();
break;
@@ -202,7 +208,7 @@ void SceneManager::load() {
_engine->playState.lastDrawnViewFrame = -1;
_engine->input->setPointerBitmap(0, 0, false);
_state = kRun; // TODO temp, is actually StartSound
_state = kStartSound; // TODO temp, is actually StartSound
}
void SceneManager::run() {