Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima8/misc/common_types.h"
#include "ultima/ultima8/audio/audio_channel.h"
#include "ultima/ultima8/audio/audio_process.h"
#include "ultima/ultima8/audio/audio_sample.h"
#include "common/memstream.h"
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
namespace Ultima {
namespace Ultima8 {
AudioChannel::AudioChannel(Audio::Mixer *mixer, uint32 sampleRate, bool stereo) :
_mixer(mixer), _priority(0) {
}
AudioChannel::~AudioChannel(void) {
}
void AudioChannel::playSample(AudioSample *sample, int loop, int priority, bool isSpeech, uint32 pitchShift, byte volume, int8 balance) {
if (!sample)
return;
_priority = priority;
// Create the _sample
Audio::SeekableAudioStream *audioStream = sample->makeStream();
int loops = loop;
if (loop == -1) {
// loop forever
loops = 0;
}
Audio::AudioStream *stream = (loop <= 1 && loop != -1) ?
(Audio::AudioStream *)audioStream :
new Audio::LoopingAudioStream(audioStream, loops);
_mixer->stopHandle(_soundHandle);
_mixer->playStream(isSpeech ? Audio::Mixer::kSpeechSoundType : Audio::Mixer::kSFXSoundType, &_soundHandle, stream, -1, volume, balance);
if (pitchShift != AudioProcess::PITCH_SHIFT_NONE)
_mixer->setChannelRate(_soundHandle, stream->getRate() * pitchShift / AudioProcess::PITCH_SHIFT_NONE);
}
bool AudioChannel::isPlaying() {
return _mixer->isSoundHandleActive(_soundHandle);
}
void AudioChannel::stop() {
_mixer->stopHandle(_soundHandle);
}
void AudioChannel::setPaused(bool paused) {
_mixer->pauseHandle(_soundHandle, paused);
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,67 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_AUDIO_AUDIOCHANNEL_H
#define ULTIMA8_AUDIO_AUDIOCHANNEL_H
#include "audio/mixer.h"
namespace Ultima {
namespace Ultima8 {
class AudioSample;
class AudioChannel {
private:
Audio::SoundHandle _soundHandle;
Audio::Mixer *_mixer;
int _priority;
public:
AudioChannel(Audio::Mixer *mixer, uint32 sampleRate, bool stereo);
~AudioChannel(void);
void stop();
void playSample(AudioSample *sample, int loop, int priority,
bool isSpeech, uint32 pitchShift, byte volume, int8 balance);
bool isPlaying();
void setVolume(byte volume, int8 balance) {
_mixer->setChannelVolume(_soundHandle, volume);
_mixer->setChannelBalance(_soundHandle, balance);
}
void setPriority(int priority) {
_priority = priority;
}
int getPriority() const {
return _priority;
}
void setPaused(bool paused);
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,148 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima8/audio/audio_mixer.h"
#include "ultima/ultima8/audio/audio_process.h"
#include "ultima/ultima8/audio/u8_music_process.h"
#include "ultima/ultima8/audio/cru_music_process.h"
#include "ultima/ultima8/audio/audio_channel.h"
#include "ultima/ultima8/audio/midi_player.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/ultima8.h"
namespace Ultima {
namespace Ultima8 {
AudioMixer *AudioMixer::_audioMixer = nullptr;
static const uint32 SAMPLE_RATE = 22050;
static const int BASE_CHANNEL_COUNT = 16;
static const int AMBIENT_CHANNEL_COUNT = 4;
static const int TOTAL_CHANNEL_COUNT = BASE_CHANNEL_COUNT + AMBIENT_CHANNEL_COUNT;
AudioMixer::AudioMixer(Audio::Mixer *mixer) : _mixer(mixer), _midiPlayer(nullptr) {
_audioMixer = this;
_channels.resize(TOTAL_CHANNEL_COUNT);
for (int idx = 0; idx < TOTAL_CHANNEL_COUNT; ++idx)
_channels[idx] = new AudioChannel(_mixer, SAMPLE_RATE, true);
debug(1, "Creating AudioMixer...");
}
void AudioMixer::createProcesses() {
Kernel *kernel = Kernel::get_instance();
// Create the Audio Process
kernel->addProcess(new AudioProcess());
// Create the Music Process
if (GAME_IS_U8) {
kernel->addProcess(new U8MusicProcess(_midiPlayer));
} else if (GAME_IS_CRUSADER) {
kernel->addProcess(new CruMusicProcess());
}
}
AudioMixer::~AudioMixer(void) {
_audioMixer = nullptr;
debug(1, "Destroying AudioMixer...");
closeMidiOutput();
for (int idx = 0; idx < TOTAL_CHANNEL_COUNT; ++idx)
delete _channels[idx];
}
void AudioMixer::reset() {
_mixer->stopAll();
}
int AudioMixer::playSample(AudioSample *sample, int loop, int priority, bool isSpeech, uint32 pitch_shift, byte volume, int8 balance, bool ambient) {
int lowest = -1;
int lowprior = 65536;
int i;
const int minchan = (ambient ? BASE_CHANNEL_COUNT : 0);
const int maxchan = (ambient ? TOTAL_CHANNEL_COUNT : BASE_CHANNEL_COUNT);
for (i = minchan; i < maxchan; i++) {
if (!_channels[i]->isPlaying()) {
lowest = i;
break;
}
else if (_channels[i]->getPriority() < priority) {
lowprior = _channels[i]->getPriority();
lowest = i;
}
}
if (i != maxchan || lowprior < priority)
_channels[lowest]->playSample(sample, loop, priority, isSpeech, pitch_shift, volume, balance);
else
lowest = -1;
return lowest;
}
bool AudioMixer::isPlaying(int chan) {
if (chan >= TOTAL_CHANNEL_COUNT || chan < 0)
return false;
bool playing = _channels[chan]->isPlaying();
return playing;
}
void AudioMixer::stopSample(int chan) {
if (chan >= TOTAL_CHANNEL_COUNT || chan < 0)
return;
_channels[chan]->stop();
}
void AudioMixer::setPaused(int chan, bool paused) {
if (chan >= TOTAL_CHANNEL_COUNT || chan < 0)
return;
_channels[chan]->setPaused(paused);
}
void AudioMixer::setVolume(int chan, byte volume, int8 balance) {
if (chan >= TOTAL_CHANNEL_COUNT || chan < 0)
return;
_channels[chan]->setVolume(volume, balance);
}
void AudioMixer::openMidiOutput() {
_midiPlayer = new MidiPlayer();
}
void AudioMixer::closeMidiOutput() {
delete _midiPlayer;
_midiPlayer = nullptr;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,72 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_AUDIO_AUDIOMIXER_H
#define ULTIMA8_AUDIO_AUDIOMIXER_H
#include "audio/mixer.h"
#include "audio/mididrv.h"
#include "common/array.h"
namespace Ultima {
namespace Ultima8 {
class MidiPlayer;
class AudioChannel;
class AudioSample;
class AudioMixer {
private:
static AudioMixer *_audioMixer;
Audio::Mixer *_mixer;
MidiPlayer *_midiPlayer;
Common::Array<AudioChannel *> _channels;
public:
AudioMixer(Audio::Mixer *mixer);
~AudioMixer();
MidiPlayer *getMidiPlayer() const {
return _midiPlayer;
}
static AudioMixer *get_instance() {
return _audioMixer;
}
void reset();
void createProcesses();
int playSample(AudioSample *sample, int loop, int priority, bool isSpeech, uint32 pitch_shift, byte volume, int8 balance, bool ambient);
bool isPlaying(int chan);
void stopSample(int chan);
void setPaused(int chan, bool paused);
void setVolume(int chan, byte volume, int8 balance);
void openMidiOutput();
void closeMidiOutput();
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,668 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima8/audio/audio_process.h"
#include "ultima/ultima8/usecode/uc_machine.h"
#include "ultima/ultima8/games/game_data.h"
#include "ultima/ultima8/audio/speech_flex.h"
#include "ultima/ultima8/audio/audio_mixer.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/world/item.h"
#include "ultima/ultima8/world/camera_process.h"
namespace Ultima {
namespace Ultima8 {
// p_dynamic_class stuff
DEFINE_RUNTIME_CLASSTYPE_CODE(AudioProcess)
AudioProcess *AudioProcess::_theAudioProcess = nullptr;
const uint32 AudioProcess::PITCH_SHIFT_NONE = 0x10000;
AudioProcess::AudioProcess(void) : _paused(0) {
_theAudioProcess = this;
_type = 1; // persistent
}
AudioProcess::~AudioProcess(void) {
_theAudioProcess = nullptr;
}
bool AudioProcess::calculateSoundVolume(ObjId objId, int16 &volume, int8 &balance) const {
Item *item = getItem(objId);
if (!item) {
volume = 255;
balance = 0;
return false;
}
// Need to get items relative coords from avatar
Point3 a = CameraProcess::GetCameraLocation();
Point3 i = item->getLocationAbsolute();
i.x -= a.x;
i.y -= a.y;
i.z -= a.z;
//
// Convert to screenspace
//
// Note that this should also correct for Crusader too.
//
int x = (i.x - i.y) / 4;
int y = (i.x + i.y) / 8 - i.z;
// Fall off over 350 pixels
int limit = 350 * 350;
int dist = limit - (x * x + y * y);
dist = (dist * 256) / limit;
volume = CLIP(dist, 0, 255); // range is 0 ~ 255
int b = (x * 127) / 160;
balance = CLIP(b, -127, 127); // range is -127 ~ +127
return true;
}
void AudioProcess::run() {
AudioMixer *mixer = AudioMixer::get_instance();
// Update the channels
Std::list<SampleInfo>::iterator it;
for (it = _sampleInfo.begin(); it != _sampleInfo.end();) {
bool finished = false;
if (!mixer->isPlaying(it->_channel)) {
if (it->_sfxNum == -1)
finished = !continueSpeech(*it);
else
finished = true;
}
if (it->_loops == -1) {
// check if an ever-looping sfx for an item has left the
// fast area.. if so we are "finished".
Item *item = getItem(it->_objId);
if (item && !item->hasFlags(Item::FLG_FASTAREA) && mixer->isPlaying(it->_channel)) {
finished = true;
mixer->stopSample(it->_channel);
}
}
if (finished)
it = _sampleInfo.erase(it);
else {
if (it->_sfxNum != -1 && it->_objId) {
calculateSoundVolume(it->_objId, it->_calcVol, it->_balance);
}
mixer->setVolume(it->_channel, (it->_calcVol * it->_volume) / 256, it->_balance);
++it;
}
}
}
bool AudioProcess::continueSpeech(SampleInfo &si) {
assert(si._sfxNum == -1);
SpeechFlex *speechflex;
speechflex = GameData::get_instance()->getSpeechFlex(si._priority);
if (!speechflex) return false;
if (si._curSpeechEnd >= si._barked.size()) return false;
si._curSpeechStart = si._curSpeechEnd;
int index = speechflex->getIndexForPhrase(si._barked,
si._curSpeechStart,
si._curSpeechEnd);
if (!index) return false;
AudioSample *sample = speechflex->getSample(index);
if (!sample) return false;
// hack to prevent playSample from deleting 'si'
si._channel = -1;
int channel = playSample(sample, 200, 0, true);
if (channel == -1)
return false;
si._channel = channel;
return true;
}
void AudioProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeByte(static_cast<uint8>(_sampleInfo.size()));
for (const auto &si : _sampleInfo) {
ws->writeUint16LE(si._sfxNum);
ws->writeUint16LE(si._priority);
ws->writeUint16LE(si._objId);
ws->writeUint16LE(si._loops);
ws->writeUint32LE(si._pitchShift);
ws->writeUint16LE(si._volume);
if (si._sfxNum == -1) { // Speech
ws->writeUint32LE(static_cast<uint32>(si._barked.size()));
ws->write(si._barked.c_str(), static_cast<uint32>(si._barked.size()));
}
}
}
bool AudioProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
uint32 count = rs->readByte();
while (count--) {
int16 sfxNum = rs->readUint16LE();
int16 priority = rs->readUint16LE();
int16 objId = rs->readUint16LE();
int16 loops = rs->readUint16LE();
uint32 pitchShift = rs->readUint32LE();
uint16 volume = rs->readUint16LE();
if (sfxNum != -1) { // SFX
int16 calcVol = 0;
int8 balance = 0;
if (objId != 0) {
calcVol = 255;
}
// Note: Small inconsistency for backward compatibility - reload ambient sounds as non-ambient.
playSFX(sfxNum, priority, objId, loops, false, pitchShift, volume, calcVol, balance, false);
} else { // Speech
uint32 slen = rs->readUint32LE();
char *buf = new char[slen + 1];
rs->read(buf, slen);
buf[slen] = 0;
Std::string text = buf;
delete[] buf;
playSpeech(text, priority, objId, pitchShift, volume);
}
}
return true;
}
int AudioProcess::playSample(AudioSample *sample, int priority, int loops, bool isSpeech, uint32 pitchShift, int16 volume, int8 balance, bool ambient) {
AudioMixer *mixer = AudioMixer::get_instance();
int channel = mixer->playSample(sample, loops, priority, isSpeech, pitchShift, volume, balance, ambient);
if (channel == -1) return channel;
// Erase old sample using channel (if any)
Std::list<SampleInfo>::iterator it;
for (it = _sampleInfo.begin(); it != _sampleInfo.end();) {
if (it->_channel == channel) {
it = _sampleInfo.erase(it);
} else {
++it;
}
}
return channel;
}
void AudioProcess::playSFX(int sfxNum, int priority, ObjId objId, int loops,
bool no_duplicates, uint32 pitchShift, uint16 volume,
int16 calcVol, int8 balance, bool ambient) {
SoundFlex *soundflx = GameData::get_instance()->getSoundFlex();
AudioMixer *mixer = AudioMixer::get_instance();
if (no_duplicates) {
Std::list<SampleInfo>::iterator it;
for (it = _sampleInfo.begin(); it != _sampleInfo.end();) {
if (it->_sfxNum == sfxNum && it->_objId == objId &&
it->_loops == loops) {
// Exactly the same (and playing) so just return
//if (it->priority == priority)
if (mixer->isPlaying(it->_channel)) {
debug(1, "Sound %d already playing on obj %u", sfxNum, objId);
return;
} else {
it = _sampleInfo.erase(it);
continue;
}
}
++it;
}
}
AudioSample *sample = soundflx->getSample(sfxNum);
if (!sample) return;
if (calcVol == -1) {
calculateSoundVolume(objId, calcVol, balance);
}
int channel = playSample(sample, priority, loops, false, pitchShift, (calcVol * volume) / 256, balance, ambient);
if (channel == -1) return;
// Update list
_sampleInfo.push_back(SampleInfo(sfxNum, priority, objId, loops, channel, pitchShift, volume, calcVol, balance, ambient));
}
void AudioProcess::stopSFX(int sfxNum, ObjId objId) {
AudioMixer *mixer = AudioMixer::get_instance();
Std::list<SampleInfo>::iterator it;
for (it = _sampleInfo.begin(); it != _sampleInfo.end();) {
if ((sfxNum == -1 || it->_sfxNum == sfxNum)
&& it->_objId == objId) {
if (mixer->isPlaying(it->_channel)) mixer->stopSample(it->_channel);
it = _sampleInfo.erase(it);
} else {
++it;
}
}
}
bool AudioProcess::isSFXPlaying(int sfxNum) {
AudioMixer *mixer = AudioMixer::get_instance();
for (const auto &si : _sampleInfo) {
if (si._sfxNum == sfxNum && mixer->isPlaying(si._channel))
return true;
}
return false;
}
bool AudioProcess::isSFXPlayingForObject(int sfxNum, ObjId objId) {
AudioMixer *mixer = AudioMixer::get_instance();
for (const auto &si : _sampleInfo) {
if ((si._sfxNum == sfxNum || sfxNum == -1) && (objId == si._objId) && mixer->isPlaying(si._channel))
return true;
}
return false;
}
void AudioProcess::setVolumeSFX(int sfxNum, uint8 volume) {
AudioMixer *mixer = AudioMixer::get_instance();
for (auto &si : _sampleInfo) {
if (si._sfxNum == sfxNum && si._sfxNum != -1) {
si._volume = volume;
calculateSoundVolume(si._objId, si._calcVol, si._balance);
mixer->setVolume(si._channel, (si._calcVol * si._volume) / 256, si._balance);
}
}
}
void AudioProcess::setVolumeForObjectSFX(ObjId objId, int sfxNum, uint8 volume) {
AudioMixer *mixer = AudioMixer::get_instance();
for (auto &si : _sampleInfo) {
if (si._sfxNum == sfxNum && si._sfxNum != -1 && objId == si._objId) {
si._volume = volume;
calculateSoundVolume(si._objId, si._calcVol, si._balance);
mixer->setVolume(si._channel, (si._calcVol * si._volume) / 256, si._balance);
}
}
}
//
// Speech
//
bool AudioProcess::playSpeech(const Std::string &barked, int shapeNum, ObjId objId, uint32 pitchShift, uint16 volume) {
SpeechFlex *speechflex = GameData::get_instance()->getSpeechFlex(shapeNum);
if (!speechflex) return false;
AudioMixer *mixer = AudioMixer::get_instance();
Std::list<SampleInfo>::iterator it;
for (it = _sampleInfo.begin(); it != _sampleInfo.end();) {
if (it->_sfxNum == -1 && it->_barked == barked &&
it->_priority == shapeNum && it->_objId == objId) {
if (mixer->isPlaying(it->_channel)) {
debug(1, "Speech already playing");
return true;
} else {
it = _sampleInfo.erase(it);
continue;
}
}
++it;
}
uint32 speech_start = 0;
uint32 speech_end;
int index = speechflex->getIndexForPhrase(barked, speech_start, speech_end);
if (!index) return false;
AudioSample *sample = speechflex->getSample(index);
if (!sample) return false;
int channel = playSample(sample, 200, 0, true, pitchShift, volume, volume);
if (channel == -1) return false;
// Update list
_sampleInfo.push_back(SampleInfo(barked, shapeNum, objId, channel,
speech_start, speech_end, pitchShift, volume, 255, 0, false));
return true;
}
uint32 AudioProcess::getSpeechLength(const Std::string &barked, int shapenum) const {
SpeechFlex *speechflex = GameData::get_instance()->getSpeechFlex(shapenum);
if (!speechflex) return 0;
return speechflex->getSpeechLength(barked);
}
void AudioProcess::stopSpeech(const Std::string &barked, int shapenum, ObjId objId) {
AudioMixer *mixer = AudioMixer::get_instance();
Std::list<SampleInfo>::iterator it;
for (it = _sampleInfo.begin(); it != _sampleInfo.end();) {
if (it->_sfxNum == -1 && it->_priority == shapenum &&
it->_objId == objId && it->_barked == barked) {
if (mixer->isPlaying(it->_channel)) mixer->stopSample(it->_channel);
it = _sampleInfo.erase(it);
} else {
++it;
}
}
}
bool AudioProcess::isSpeechPlaying(const Std::string &barked, int shapeNum) {
Std::list<SampleInfo>::iterator it;
for (auto &si : _sampleInfo) {
if (si._sfxNum == -1 && si._priority == shapeNum &&
si._barked == barked) {
return true;
}
}
return false;
}
void AudioProcess::pauseAllSamples() {
_paused++;
if (_paused != 1) return;
AudioMixer *mixer = AudioMixer::get_instance();
Std::list<SampleInfo>::iterator it;
for (it = _sampleInfo.begin(); it != _sampleInfo.end();) {
if (mixer->isPlaying(it->_channel)) {
mixer->setPaused(it->_channel, true);
++it;
} else {
it = _sampleInfo.erase(it);
}
}
}
void AudioProcess::unpauseAllSamples() {
_paused--;
if (_paused != 0) return;
AudioMixer *mixer = AudioMixer::get_instance();
Std::list<SampleInfo>::iterator it;
for (it = _sampleInfo.begin(); it != _sampleInfo.end();) {
if (mixer->isPlaying(it->_channel)) {
mixer->setPaused(it->_channel, false);
++it;
} else {
it = _sampleInfo.erase(it);
}
}
}
void AudioProcess::stopAllExceptSpeech() {
AudioMixer *mixer = AudioMixer::get_instance();
Std::list<SampleInfo>::iterator it;
for (it = _sampleInfo.begin(); it != _sampleInfo.end();) {
if (it->_barked.empty()) {
if (mixer->isPlaying(it->_channel)) mixer->stopSample(it->_channel);
it = _sampleInfo.erase(it);
} else {
++it;
}
}
}
//
// Intrinsics
//
uint32 AudioProcess::I_playSFX(const uint8 *args, unsigned int argsize) {
ARG_SINT16(sfxNum);
int16 priority = 0x60;
if (argsize >= 4) {
ARG_SINT16(priority_);
priority = priority_;
}
ObjId objId = 0;
if (argsize == 6) {
ARG_OBJID(objectId);
objId = objectId;
}
AudioProcess *ap = AudioProcess::get_instance();
if (ap)
ap->playSFX(sfxNum, priority, objId, 0);
else
warning("No AudioProcess");
return 0;
}
uint32 AudioProcess::I_playAmbientSFX(const uint8 *args, unsigned int argsize) {
ARG_SINT16(sfxNum);
int16 priority = 0x60;
if (argsize >= 4) {
ARG_SINT16(priority_);
priority = priority_;
}
ObjId objId = 0;
if (argsize == 6) {
ARG_OBJID(objectId);
objId = objectId;
}
AudioProcess *ap = AudioProcess::get_instance();
if (ap)
ap->playSFX(sfxNum, priority, objId, -1, true, PITCH_SHIFT_NONE, 0xff, true);
else
warning("No AudioProcess");
return 0;
}
uint32 AudioProcess::I_playSFXCru(const uint8 *args, unsigned int argsize) {
ARG_ITEM_FROM_PTR(item)
ARG_SINT16(sfxNum);
if (!item) {
warning("I_playSFXCru: Couldn't get item %d", id_item);
} else {
AudioProcess *ap = AudioProcess::get_instance();
if (ap) {
// Crusader stops any existing item sounds before starting the next.
ap->stopSFX(-1, item->getObjId());
ap->playSFX(sfxNum, 0x10, item->getObjId(), 0, true, PITCH_SHIFT_NONE, 0x80, false);
} else {
warning("I_playSFXCru Error: No AudioProcess");
}
}
return 0;
}
uint32 AudioProcess::I_playAmbientSFXCru(const uint8 *args, unsigned int argsize) {
// Similar to I_playAmbientSFX, but the params are different.
ARG_ITEM_FROM_PTR(item)
ARG_SINT16(sfxNum);
if (!item) {
warning("I_playAmbientSFXCru: Couldn't get item %d", id_item);
} else {
AudioProcess *ap = AudioProcess::get_instance();
if (ap)
ap->playSFX(sfxNum, 0x10, item->getObjId(), -1, true, PITCH_SHIFT_NONE, 0xff, true);
else
warning("I_playAmbientSFXCru Error: No AudioProcess");
}
return 0;
}
uint32 AudioProcess::I_isSFXPlaying(const uint8 *args, unsigned int argsize) {
ARG_SINT16(sfxNum);
AudioProcess *ap = AudioProcess::get_instance();
if (ap)
return ap->isSFXPlaying(sfxNum);
else
warning("No AudioProcess");
return 0;
}
uint32 AudioProcess::I_isSFXPlayingForObject(const uint8 *args, unsigned int argsize) {
ARG_ITEM_FROM_PTR(item)
ARG_SINT16(sfxNum);
if (!item) {
warning("I_isSFXPlayingForObject: Couldn't get item");
} else {
AudioProcess *ap = AudioProcess::get_instance();
if (ap)
return ap->isSFXPlayingForObject(sfxNum, item->getObjId());
else
warning("I_isSFXPlayingForObject Error: No AudioProcess");
}
return 0;
}
uint32 AudioProcess::I_setVolumeSFX(const uint8 *args, unsigned int /*argsize*/) {
// Sets volume for last played instances of sfxNum (???)
ARG_SINT16(sfxNum);
ARG_UINT8(volume);
AudioProcess *ap = AudioProcess::get_instance();
if (ap)
ap->setVolumeSFX(sfxNum, volume);
else
warning("No AudioProcess");
return 0;
}
uint32 AudioProcess::I_setVolumeForObjectSFX(const uint8 *args, unsigned int /*argsize*/) {
// Sets volume for last played instances of sfxNum on object
ARG_ITEM_FROM_PTR(item);
ARG_SINT16(sfxNum);
ARG_UINT8(volume);
if (!item) {
warning("I_setVolumeForObjectSFX: Couldn't get item");
} else {
AudioProcess *ap = AudioProcess::get_instance();
if (ap)
ap->setVolumeForObjectSFX(item->getObjId(), sfxNum, volume);
else
warning("I_setVolumeForObjectSFX: No AudioProcess");
}
return 0;
}
uint32 AudioProcess::I_stopSFX(const uint8 *args, unsigned int argsize) {
ARG_SINT16(sfxNum);
ObjId objId = 0;
if (argsize == 4) {
ARG_OBJID(objectId);
objId = objectId;
}
AudioProcess *ap = AudioProcess::get_instance();
if (ap)
ap->stopSFX(sfxNum, objId);
else
warning("No AudioProcess");
return 0;
}
uint32 AudioProcess::I_stopSFXCru(const uint8 *args, unsigned int argsize) {
int16 sfxNum = -1;
ARG_ITEM_FROM_PTR(item);
if (!item) {
warning("Invalid item in I_stopSFXCru");
return 0;
}
if (argsize == 6) {
ARG_SINT16(sfxNumber);
sfxNum = sfxNumber;
}
AudioProcess *ap = AudioProcess::get_instance();
if (ap)
ap->stopSFX(sfxNum, item->getObjId());
else
warning("No AudioProcess");
return 0;
}
uint32 AudioProcess::I_stopAllSFX(const uint8 * /*args*/, unsigned int /*argsize*/) {
AudioProcess *ap = AudioProcess::get_instance();
// Not *exactly* the same, but close enough for this intrinsic.
if (ap)
ap->stopAllExceptSpeech();
else
warning("No AudioProcess");
return 0;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,152 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_AUDIO_AUDIOPROCESS_H
#define ULTIMA8_AUDIO_AUDIOPROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/usecode/intrinsics.h"
#include "ultima/shared/std/string.h"
#include "ultima/ultima8/misc/classtype.h"
namespace Ultima {
namespace Ultima8 {
class AudioSample;
class AudioProcess : public Process {
public:
static const uint32 PITCH_SHIFT_NONE;
struct SampleInfo {
int32 _sfxNum;
int32 _priority;
ObjId _objId;
int32 _loops;
int32 _channel;
Std::string _barked;
uint32 _curSpeechStart, _curSpeechEnd;
uint32 _pitchShift; // PITCH_SHIFT_NONE is normal
uint16 _volume; // 0-255
int16 _calcVol;
int8 _balance;
bool _ambient;
SampleInfo() : _sfxNum(-1) { }
SampleInfo(int32 s, int32 p, ObjId o, int32 l, int32 c, uint32 ps, uint16 v, int16 cv, int8 bal, bool ambient) :
_sfxNum(s), _priority(p), _objId(o), _loops(l), _channel(c),
_pitchShift(ps), _volume(v), _calcVol(cv), _balance(bal),
_curSpeechStart(0), _curSpeechEnd(0), _ambient(ambient) { }
SampleInfo(const Std::string &b, int32 shpnum, ObjId o, int32 c,
uint32 s, uint32 e, uint32 ps, uint16 v, int16 cv, int8 bal, bool ambient) :
_sfxNum(-1), _priority(shpnum), _objId(o), _loops(0), _channel(c), _barked(b),
_curSpeechStart(s), _curSpeechEnd(e), _pitchShift(ps), _volume(v),
_calcVol(cv), _balance(bal), _ambient(ambient) { }
};
Std::list<SampleInfo> _sampleInfo;
public:
// p_dynamic_class stuff
ENABLE_RUNTIME_CLASSTYPE()
AudioProcess(void);
~AudioProcess(void) override;
//! Get the current instance of the Audio Processes
static AudioProcess *get_instance() {
return _theAudioProcess;
}
INTRINSIC(I_playSFX);
INTRINSIC(I_playAmbientSFX);
INTRINSIC(I_playSFXCru);
INTRINSIC(I_playAmbientSFXCru);
INTRINSIC(I_isSFXPlaying);
INTRINSIC(I_isSFXPlayingForObject);
INTRINSIC(I_setVolumeSFX);
INTRINSIC(I_setVolumeForObjectSFX);
INTRINSIC(I_stopSFX);
INTRINSIC(I_stopSFXCru);
INTRINSIC(I_stopAllSFX);
void run() override;
void playSFX(int sfxNum, int priority, ObjId objId, int loops,
bool no_duplicates, uint32 pitchShift,
uint16 volume, int16 calcVol, int8 balance,
bool ambient);
void playSFX(int sfxNum, int priority, ObjId objId, int loops,
bool no_duplicates = false, uint32 pitchShift = PITCH_SHIFT_NONE,
uint16 volume = 0x80, bool ambient = false) {
playSFX(sfxNum, priority, objId, loops, no_duplicates, pitchShift, volume, -1, 0, ambient);
}
//! stop sfx on object. set sfxNum = -1 to stop all for object.
void stopSFX(int sfxNum, ObjId objId);
bool isSFXPlaying(int sfxNum);
bool isSFXPlayingForObject(int sfxNum, ObjId objId);
void setVolumeSFX(int sfxNum, uint8 volume);
void setVolumeForObjectSFX(ObjId objId, int sfxNum, uint8 volume);
bool playSpeech(const Std::string &barked, int shapenum, ObjId objId,
uint32 pitchShift = PITCH_SHIFT_NONE, uint16 volume = 255);
void stopSpeech(const Std::string &barked, int shapenum, ObjId objId);
bool isSpeechPlaying(const Std::string &barked, int shapenum);
//! get length (in milliseconds) of speech
uint32 getSpeechLength(const Std::string &barked, int shapenum) const;
//! play a sample (without storing a SampleInfo)
//! returns channel sample is played on, or -1
int playSample(AudioSample *sample, int priority, int loops, bool isSpeech = false,
uint32 pitchShift = PITCH_SHIFT_NONE, int16 volume = 255,
int8 balance = 0, bool ambient = false);
//! pause all currently playing samples
void pauseAllSamples();
//! unpause all currently playing samples
void unpauseAllSamples();
//! stop all samples except speech
void stopAllExceptSpeech();
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
private:
uint32 _paused;
//! play the next speech sample for the text in this SampleInfo
//! note: si is reused if successful
//! returns true if there was speech left to play, or false if finished
bool continueSpeech(SampleInfo &si);
bool calculateSoundVolume(ObjId objId, int16 &volume, int8 &balance) const;
static AudioProcess *_theAudioProcess;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,39 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima8/misc/common_types.h"
#include "ultima/ultima8/audio/audio_sample.h"
namespace Ultima {
namespace Ultima8 {
AudioSample::AudioSample(const uint8 *buffer, uint32 size, uint32 bits, bool stereo, bool deleteBuffer) :
_sampleRate(0), _bits(bits), _stereo(stereo), _length(0),
_bufferSize(size), _buffer(buffer), _deleteBuffer(deleteBuffer) {
}
AudioSample::~AudioSample(void) {
if (_deleteBuffer)
delete [] _buffer;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,69 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_AUDIO_AUDIOSAMPLE_H
#define ULTIMA8_AUDIO_AUDIOSAMPLE_H
namespace Audio {
class SeekableAudioStream;
}
namespace Ultima {
namespace Ultima8 {
class AudioSample {
protected:
uint32 _sampleRate;
uint32 _bits;
bool _stereo;
uint32 _length;
uint32 _bufferSize;
uint8 const *_buffer;
bool _deleteBuffer;
public:
AudioSample(const uint8 *buffer, uint32 size, uint32 bits, bool stereo, bool deleteBuffer);
virtual ~AudioSample(void);
inline uint32 getRate() const {
return _sampleRate;
}
inline uint32 getBits() const {
return _bits;
}
inline bool isStereo() const {
return _stereo;
}
//! get AudioSample _length (in samples)
inline uint32 getLength() const {
return _length;
}
virtual Audio::SeekableAudioStream *makeStream() const = 0;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,263 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/file.h"
#include "common/system.h"
#include "common/config-manager.h"
#include "ultima/ultima8/ultima8.h"
#include "ultima/ultima8/audio/cru_music_process.h"
#include "audio/mods/mod_xm_s3m.h"
#include "ultima/ultima8/world/world.h"
#include "ultima/ultima8/world/current_map.h"
namespace Ultima {
namespace Ultima8 {
static const int MAX_TRACK_REMORSE = 21;
static const int MAX_TRACK_REGRET = 22;
// NOTE: The order of these lists has to be the same as the original games
// as they come as numbers from the usecode.
static const char *const TRACK_FILE_NAMES_REMORSE[] = {
nullptr,
"M01",
"M02",
"M03",
"M04",
"M05",
"M06",
"M07",
"M08",
"M09",
"M10",
"M11",
"M12",
"M13",
"M14",
"M15",
"M16A",
"M16B",
"M16C",
"cred",
"menu",
"buyme" // for demo
};
static const char *const TRACK_FILE_NAMES_REGRET[] = {
nullptr,
"ninth",
"phil",
"straight",
"party",
"demo",
"stint",
"mk",
"space2",
"d3",
"space",
"rhythm",
"intent",
"m03",
"silver",
"m01",
"techno",
"cred",
"regret",
"m13",
"retro",
"metal",
"xmas" // for christmas easter egg
};
static const int REGRET_MAP_TRACKS[] = {
0, 1, 10, 2, 0, 3, 11, 4,
16, 5, 20, 6, 0, 7, 13, 8,
15, 9, 12, 10, 19, 14, 21, 0};
DEFINE_RUNTIME_CLASSTYPE_CODE(CruMusicProcess)
CruMusicProcess::CruMusicProcess() : MusicProcess(), _currentTrack(0), _savedTrack(0), _m16offset(0) {
_maxTrack = (GAME_IS_REMORSE ? MAX_TRACK_REMORSE : MAX_TRACK_REGRET);
_trackNames = (GAME_IS_REMORSE ? TRACK_FILE_NAMES_REMORSE
: TRACK_FILE_NAMES_REGRET);
}
CruMusicProcess::~CruMusicProcess() {
// We shouldn't need to do anything here - the mixer will
// clean up the stream for us.
}
void CruMusicProcess::playMusic(int track) {
if (GAME_IS_REGRET && track == 0x45) {
// Play the default track for the current map
uint32 curmap = World::get_instance()->getCurrentMap()->getNum();
if (curmap < ARRAYSIZE(REGRET_MAP_TRACKS)) {
track = REGRET_MAP_TRACKS[curmap];
} else {
track = 0;
}
// Regret has a Christmas music easter egg.
if (!GAME_IS_DEMO) {
TimeDate t;
g_system->getTimeAndDate(t);
if ((t.tm_mon == 11 && t.tm_mday >= 24) || ConfMan.getBool("always_christmas")) {
track = 22;
}
}
}
playMusic_internal(track);
}
void CruMusicProcess::playCombatMusic(int track) {
// Only U8 has combat music.. ignore it.
}
void CruMusicProcess::queueMusic(int track) {
playMusic_internal(track);
}
void CruMusicProcess::unqueueMusic() {
}
void CruMusicProcess::restoreMusic() {
}
void CruMusicProcess::saveTrackState() {
assert(!_savedTrack);
_savedTrack = _currentTrack;
}
void CruMusicProcess::restoreTrackState() {
int saved = _savedTrack;
_savedTrack = 0;
playMusic_internal(saved);
}
void CruMusicProcess::playMusic_internal(int track) {
if (track < 0 || track > _maxTrack) {
warning("Not playing track %d (max is %d)", track, _maxTrack);
playMusic_internal(0);
return;
}
if (GAME_IS_REMORSE && track == 16) {
// Loop through m16a / m16b / m16c
track += _m16offset;
_m16offset = (_m16offset + 1) % 4;
}
Audio::Mixer *mixer = Ultima8Engine::get_instance()->_mixer;
assert(mixer);
if (track == _currentTrack && (track == 0 || mixer->isSoundHandleActive(_soundHandle)))
// Already playing what we want.
return;
mixer->stopHandle(_soundHandle);
_soundHandle = Audio::SoundHandle();
_currentTrack = track;
if (track > 0) {
// TODO: It's a bit ugly having this here. Should be in GameData.
const Std::string fname = Std::string::format("sound/%s.amf", _trackNames[track]);
auto *rs = new Common::File();
if (!rs->open(Common::Path(fname))) {
// This happens in No Regret demo.
warning("Couldn't load AMF file: %s", fname.c_str());
delete rs;
return;
}
Audio::AudioStream *stream = Audio::makeModXmS3mStream(rs, DisposeAfterUse::YES);
if (!stream) {
error("Couldn't create stream from AMF file: %s", fname.c_str());
return;
}
mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, stream, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::YES);
}
}
void CruMusicProcess::run() {
Audio::Mixer *mixer = Ultima8Engine::get_instance()->_mixer;
assert(mixer);
if (mixer->isSoundHandleActive(_soundHandle)) {
return;
}
// Hit end of stream, play it again. This normally won't happen because
// the mods should loop infinitely, but just in case.
playMusic_internal(_currentTrack);
}
void CruMusicProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeUint32LE(static_cast<uint32>(_currentTrack));
ws->writeUint32LE(static_cast<uint32>(_savedTrack));
ws->writeByte(_m16offset);
}
bool CruMusicProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_currentTrack = static_cast<int32>(rs->readUint32LE());
_savedTrack = static_cast<int32>(rs->readUint32LE());
_m16offset = rs->readByte();
_theMusicProcess = this;
// Slight hack - resuming from savegame we want to restore the game
// track (not the menu track)
if (_savedTrack)
restoreTrackState();
return true;
}
bool CruMusicProcess::isPlaying() {
Audio::Mixer *mixer = Ultima8Engine::get_instance()->_mixer;
return _currentTrack != 0 && mixer && mixer->isSoundHandleActive(_soundHandle);
}
void CruMusicProcess::pauseMusic() {
Audio::Mixer *mixer = Ultima8Engine::get_instance()->_mixer;
assert(mixer);
if (mixer->isSoundHandleActive(_soundHandle))
mixer->pauseHandle(_soundHandle, true);
}
void CruMusicProcess::unpauseMusic() {
Audio::Mixer *mixer = Ultima8Engine::get_instance()->_mixer;
assert(mixer);
if (mixer->isSoundHandleActive(_soundHandle))
mixer->pauseHandle(_soundHandle, false);
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,101 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_AUDIO_CRUMUSICPROCESS_H
#define ULTIMA8_AUDIO_CRUMUSICPROCESS_H
#include "ultima/ultima8/audio/music_process.h"
#include "ultima/ultima8/misc/classtype.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
namespace Ultima {
namespace Ultima8 {
class Debugger;
class MidiPlayer;
class CruMusicProcess : public MusicProcess {
friend class Debugger;
protected:
//! Play a music track
//! \param track The track number to play. Pass 0 to stop music
void playMusic_internal(int track) override;
private:
int _currentTrack; //! Currently playing track (don't save)
int _savedTrack;
uint8 _m16offset;
Audio::SoundHandle _soundHandle;
// These are both initialized in constructor and do not need to be saved.
int _maxTrack;
const char *const *_trackNames;
public:
CruMusicProcess();
~CruMusicProcess() override;
ENABLE_RUNTIME_CLASSTYPE()
//! Play some background music. Does not change the current track if combat music is active. If another track is currently queued, just queues this track for play.
void playMusic(int track) override;
//! Play some combat music - the last played track will be remembered
void playCombatMusic(int track) override;
//! Queue a track to start once the current one finishes
void queueMusic(int track) override;
//! Clear any queued track (does not affect currently playing track)
void unqueueMusic() override;
//! Restore the last requested non-combat track (eg, at the end of combat)
void restoreMusic() override;
//! Fading is not used, so this does nothing
void fadeMusic(uint16 length) override { };
//! Fading is not used, so this returns false
bool isFading() override { return false; };
//! Save the current track state - used when the menu is opened
void saveTrackState() override;
//! Bring back the track state from before it was put on hold
void restoreTrackState() override;
//! Is a track currently playing?
bool isPlaying() override;
//! Pause the currently playing track
void pauseMusic() override;
//! Resume the current track after pausing
void unpauseMusic() override;
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,266 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima8/audio/midi_player.h"
#include "ultima/ultima8/ultima8.h"
#include "ultima/ultima8/audio/music_flex.h"
#include "ultima/ultima8/games/game_data.h"
#include "audio/midiparser.h"
#include "audio/miles.h"
namespace Ultima {
namespace Ultima8 {
byte MidiPlayer::_callbackData[2];
MidiPlayer::MidiPlayer() : _parser(nullptr), _transitionParser(nullptr), _playingTransition(false) {
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
MusicType musicType = MidiDriver::getMusicType(dev);
switch (musicType) {
case MT_ADLIB:
MusicFlex *musicFlex;
musicFlex = GameData::get_instance()->getMusic();
_driver = Audio::MidiDriver_Miles_AdLib_create("", "", musicFlex->getAdlibTimbres(), nullptr);
break;
case MT_MT32:
case MT_GM:
_driver = Audio::MidiDriver_Miles_MIDI_create(MT_GM, "");
break;
default:
_driver = new MidiDriver_NULL_Multisource();
break;
}
_isFMSynth = (musicType == MT_ADLIB);
_callbackData[0] = 0;
_callbackData[1] = 0;
if (_driver) {
int retValue = _driver->open();
if (retValue == 0) {
_driver->property(MidiDriver::PROP_USER_VOLUME_SCALING, true);
_driver->setTimerCallback(this, &timerCallback);
syncSoundSettings();
} else {
delete _driver;
_driver = nullptr;
}
}
}
MidiPlayer::~MidiPlayer() {
if (_parser) {
_parser->unloadMusic();
delete _parser;
}
if (_transitionParser) {
_transitionParser->unloadMusic();
delete _transitionParser;
}
if (_driver) {
_driver->close();
delete _driver;
}
}
void MidiPlayer::load(byte *data, size_t size, int seqNo) {
if (!_driver)
return;
assert(seqNo == 0 || seqNo == 1);
if (_parser) {
_parser->unloadMusic();
delete _parser;
_parser = nullptr;
}
if (size < 4)
error("load() wrong music resource size");
if (READ_BE_UINT32(data) != MKTAG('F', 'O', 'R', 'M')) {
warning("load() Unexpected signature");
} else {
_parser = MidiParser::createParser_XMIDI(xmidiCallback, _callbackData + seqNo, 0);
_parser->setMidiDriver(_driver);
_parser->setTimerRate(_driver->getBaseTempo());
_parser->property(MidiParser::mpSendSustainOffOnNotesOff, 1);
_parser->property(MidiParser::mpDisableAutoStartPlayback, 1);
if (!_parser->loadMusic(data, size))
error("load() wrong music resource");
}
}
void MidiPlayer::loadTransitionData(byte* data, size_t size) {
if (!_driver)
return;
if (size < 4)
error("loadTransitionData() wrong music resource size");
if (READ_BE_UINT32(data) != MKTAG('F', 'O', 'R', 'M'))
error("loadTransitionData() Unexpected signature");
_transitionParser = MidiParser::createParser_XMIDI(nullptr, nullptr, 0);
_transitionParser->setMidiDriver(_driver);
_transitionParser->setTimerRate(_driver->getBaseTempo());
_transitionParser->property(MidiParser::mpDisableAutoStartPlayback, 1);
if (!_transitionParser->loadMusic(data, size))
error("loadTransitionData() wrong music resource");
}
void MidiPlayer::play(int trackNo, int branchIndex) {
if (!_parser || !_driver)
return;
if (!_parser->setTrack(trackNo)) {
warning("play() invalid track number %i", trackNo);
return;
}
if (branchIndex >= 0) {
if (!_parser->jumpToIndex(branchIndex, false)) {
warning("play() invalid branch index %i", branchIndex);
// Track will play from the beginning instead
}
}
// Abort any active fades and reset the source volume to neutral.
if (_driver->isFading(0))
_driver->abortFade(0);
_driver->resetSourceVolume(0);
if (_transitionParser) {
_transitionParser->stopPlaying();
_playingTransition = false;
}
if (!_parser->startPlaying()) {
warning("play() failed to start playing");
}
}
void MidiPlayer::playTransition(int trackNo, bool overlay) {
if (!overlay && _parser)
_parser->stopPlaying();
if (!_transitionParser) {
warning("playTransition() transition data not loaded");
if (_parser)
_parser->stopPlaying();
return;
}
_transitionParser->setTrack(trackNo);
if (overlay)
_transitionParser->setTempo(_driver->getBaseTempo() * 2);
_transitionParser->property(MidiParser::mpDisableAllNotesOffMidiEvents, overlay);
_transitionParser->startPlaying();
_playingTransition = true;
}
void MidiPlayer::stop() {
if (_parser)
_parser->stopPlaying();
if (_transitionParser) {
_transitionParser->stopPlaying();
_playingTransition = false;
}
}
void MidiPlayer::pause(bool pause) {
if (pause) {
if (_parser)
_parser->pausePlaying();
if (_transitionParser)
_transitionParser->pausePlaying();
} else {
if (_parser)
_parser->resumePlaying();
if (_transitionParser)
_transitionParser->resumePlaying();
}
}
bool MidiPlayer::isPlaying() {
return (_parser && _parser->isPlaying()) || _playingTransition;
}
void MidiPlayer::startFadeOut(uint16 length) {
if (_driver)
_driver->startFade(0, 1500, 0);
}
bool MidiPlayer::isFading() {
return _driver && _driver->isFading(0);
}
void MidiPlayer::syncSoundSettings() {
if (_driver)
_driver->syncSoundSettings();
}
bool MidiPlayer::hasBranchIndex(uint8 index) {
return _parser && _parser->hasJumpIndex(index);
}
void MidiPlayer::setLooping(bool loop) {
if (_parser)
_parser->property(MidiParser::mpAutoLoop, loop);
}
void MidiPlayer::xmidiCallback(byte eventData, void *data) {
if (data == nullptr)
return;
*static_cast<byte*>(data) = eventData;
}
void MidiPlayer::onTimer() {
if (_parser)
_parser->onTimer();
if (_transitionParser) {
_transitionParser->onTimer();
if (_playingTransition && !_transitionParser->isPlaying()) {
// Transition has finished.
if (_parser)
// Stop the main track (which is still playing if the
// transition was overlaid).
_parser->stopPlaying();
_playingTransition = false;
}
}
}
void MidiPlayer::timerCallback(void *data) {
((MidiPlayer *)data)->onTimer();
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,132 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_AUDIO_MIDI_PLAYER_H
#define ULTIMA8_AUDIO_MIDI_PLAYER_H
#include "audio/mixer.h"
#include "audio/mididrv_ms.h"
#include "audio/midiparser.h"
namespace Ultima {
namespace Ultima8 {
class MidiPlayer {
public:
MidiPlayer();
~MidiPlayer();
/**
* Load the specified music data
*/
void load(byte *data, size_t size, int seqNo);
/**
* Load the XMIDI data containing the transition tracks.
* Call this function before calling playTransition.
*/
void loadTransitionData(byte *data, size_t size);
/**
* Play the specified music track, starting at the
* specified branch. Use branchNo -1 to start from the
* beginning.
*/
void play(int trackNo, int branchNo);
/**
* Plays the specified transition track. If overlay is specified, the
* transition is overlaid on the currently playing music track and this
* track is stopped when the transition ends. If overlay is not specified,
* the currently playing music track is stopped before the transition is
* started.
*/
void playTransition(int trackNo, bool overlay);
/**
* Stop the currently playing track.
*/
void stop();
/**
* Pause or resume playback of the current track.
*/
void pause(bool pause);
/**
* Returns true if a track is playing.
*/
bool isPlaying();
/**
* Starts a fade-out of the specified duration (in milliseconds).
*/
void startFadeOut(uint16 length);
/**
* Returns true if the music is currently fading.
*/
bool isFading();
/**
* Synchronizes the user volume settings with those of the game.
*/
void syncSoundSettings();
/**
* Sets whether the music should loop
*/
void setLooping(bool loop);
/**
* Returns true if the current music track has a branch
* defined for the specified index.
*/
bool hasBranchIndex(uint8 index);
bool isFMSynth() const {
return _isFMSynth;
};
static void xmidiCallback(byte eventData, void *data);
byte getSequenceCallbackData(int seq) const {
assert(seq == 0 || seq == 1);
return _callbackData[seq];
}
void onTimer();
static void timerCallback(void *data);
private:
MidiDriver_Multisource *_driver;
MidiParser *_parser;
MidiParser *_transitionParser;
bool _isFMSynth;
bool _playingTransition;
static byte _callbackData[2];
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,244 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/shared/std/string.h"
#include "ultima/ultima8/misc/debugger.h"
#include "ultima/ultima8/audio/music_flex.h"
#include "common/memstream.h"
namespace Ultima {
namespace Ultima8 {
MusicFlex::MusicFlex(Common::SeekableReadStream *rs) : Archive(rs) {
memset(_info, 0, sizeof(SongInfo *) * 128);
_songs = new XMidiData *[_count];
memset(_songs, 0, sizeof(XMidiData *) * _count);
loadSongInfo();
}
MusicFlex::~MusicFlex() {
uint32 i;
for (i = 0; i < 128; i++) {
delete _info[i];
}
for (i = 0; i < _count; i++) {
delete _songs[i];
}
delete [] _songs;
}
MusicFlex::SongInfo::SongInfo() : _numMeasures(0), _loopJump(0) {
memset(_filename, 0, 17);
memset(_transitions, 0, 128 * sizeof(int *));
}
MusicFlex::SongInfo::~SongInfo() {
for (int i = 0; i < 128; i++) {
delete [] _transitions[i];
}
}
MusicFlex::XMidiData *MusicFlex::getXMidi(uint32 index) {
if (index >= _count)
return nullptr;
cache(index);
return _songs[index];
}
const MusicFlex::SongInfo *MusicFlex::getSongInfo(uint32 index) const {
if (index > 127)
return nullptr;
return _info[index];
}
void MusicFlex::cache(uint32 index) {
if (index >= _count) return;
uint32 size;
uint8 *data = getRawObject(index, &size);
if (!data) {
// Note: multiple sorcerer scenes (such as MALCHIR::03F2)
// request track 122, which is blank in the Gold Edition
// music flex.
warning("Unable to cache song %d from sound/music.flx", index);
return;
}
_songs[index] = new XMidiData(data, size);
}
void MusicFlex::uncache(uint32 index) {
if (index >= _count) return;
delete _songs[index];
_songs[index] = nullptr;
}
bool MusicFlex::isCached(uint32 index) const {
if (index >= _count) return false;
return (_songs[index] != nullptr);
}
Common::SeekableReadStream *MusicFlex::getAdlibTimbres() {
uint32 size;
const uint8 *data = getRawObject(259, &size);
return new Common::MemoryReadStream(data, size, DisposeAfterUse::YES);
}
void MusicFlex::loadSongInfo() {
uint32 size;
const uint8 *buf = getRawObject(0, &size);
if (!buf || !size) {
error("Unable to load song info from sound/music.flx");
}
Common::MemoryReadStream ds(buf, size);
Std::string line;
// Read first section till we hit a #
for (;;) {
line = ds.readLine();
// We have hit the end of the section
if (line.at(0) == '#') break;
Std::string::size_type begIdx, endIdx;
// Find the first not space, which will get us the name
begIdx = line.findFirstNotOf(' ');
endIdx = line.findFirstOf(' ', begIdx);
Std::string name = line.substr(begIdx, endIdx - begIdx);
// Now find the first not space after the name, which will get us the num
begIdx = line.findFirstNotOf(' ', endIdx);
endIdx = line.findFirstOf(' ', begIdx);
int num = line.at(begIdx);
// Now number of measures
begIdx = line.findFirstNotOf(' ', endIdx);
endIdx = line.findFirstOf(' ', begIdx);
int measures = atoi(line.substr(begIdx, endIdx - begIdx).c_str());
// Now finally _loopJump
begIdx = line.findFirstNotOf(' ', endIdx);
endIdx = line.findFirstOf(' ', begIdx);
int loopJump = atoi(line.substr(begIdx, endIdx - begIdx).c_str());
// Uh oh
if (num < 0 || num > 127)
error("Invalid Section 1 song _info data. num out of range");
if (_info[num])
error("Invalid Section 1 song _info data. num already defined");
_info[num] = new SongInfo();
strncpy(_info[num]->_filename, name.c_str(), 16);
_info[num]->_numMeasures = measures;
_info[num]->_loopJump = loopJump;
};
// Read 'Section2', or more like skip it, since it's only trans.xmi
// Read first section till we hit a #
for (;;) {
line = ds.readLine();
// We have hit the end of the section
if (line.at(0) == '#') break;
}
// Skip 'Section3'
for (;;) {
line = ds.readLine();
// We have hit the end of the section
if (line.at(0) == '#') break;
}
// Read 'Section4' (trans _info)
for (;;) {
line = ds.readLine();
// We have hit the end of the section
if (line.at(0) == '#') break;
Std::string::size_type begIdx, endIdx;
// Get 'from' name
begIdx = line.findFirstNotOf(' ');
endIdx = line.findFirstOf(' ', begIdx);
Std::string from = line.substr(begIdx, endIdx - begIdx);
// Get 'to' name
begIdx = line.findFirstNotOf(' ', endIdx);
endIdx = line.findFirstOf(' ', begIdx);
Std::string to = line.substr(begIdx, endIdx - begIdx);
// Find index of from name
int fi;
for (fi = 0; fi < 128; fi++) {
if (_info[fi] && from == _info[fi]->_filename) break;
}
if (fi == 128)
error("Invalid Section 4 song _info data. Unable to find 'from' index (%s)", from.c_str());
// Find index of to name
int ti;
for (ti = 0; ti < 128; ti++) {
if (_info[ti] && to == _info[ti]->_filename) break;
}
if (ti == 128)
error("Invalid Section 4 song _info data. Unable to find 'to' index (%s)", to.c_str());
// Allocate Transition _info
_info[fi]->_transitions[ti] = new int[_info[fi]->_numMeasures];
// Now attempt to read the trans _info for the
for (int m = 0; m < _info[fi]->_numMeasures; m++) {
// Get trans _info name
begIdx = line.findFirstNotOf(' ', endIdx);
endIdx = line.findFirstOf(' ', begIdx);
if (begIdx == Std::string::npos)
error("Invalid Section 4 song _info data. Unable to read _transitions for all measures");
Std::string trans = line.substr(begIdx, endIdx - begIdx);
const char *str = trans.c_str();
int num = 0;
// Overlayed
if (*str == '!')
num = 0 - atoi(str + 1);
else
num = atoi(str);
_info[fi]->_transitions[ti][m] = num;
}
}
// Skip all remaining sections
delete[] buf;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,78 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_AUDIO_MUSICFLEX_H
#define ULTIMA8_AUDIO_MUSICFLEX_H
#include "ultima/ultima8/filesys/archive.h"
namespace Ultima {
namespace Ultima8 {
class MusicFlex : public Archive {
public:
struct SongInfo {
SongInfo();
~SongInfo();
char _filename[17];
int _numMeasures;
int _loopJump;
int *_transitions[128];
};
struct XMidiData {
XMidiData(byte *data, uint32 size) : _data(data), _size(size)
{}
byte *_data;
uint32 _size;
};
MusicFlex(Common::SeekableReadStream *rs);
~MusicFlex() override;
//! Get an xmidi
XMidiData *getXMidi(uint32 index);
//! Get song info
const SongInfo *getSongInfo(uint32 index) const;
//! Get the Adlib Timbres (index 259)
Common::SeekableReadStream *getAdlibTimbres();
void cache(uint32 index) override;
void uncache(uint32 index) override;
bool isCached(uint32 index) const override;
private:
SongInfo *_info[128];
XMidiData **_songs;
//! Load the song info
void loadSongInfo();
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,71 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima8/audio/music_process.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(MusicProcess)
MusicProcess *MusicProcess::_theMusicProcess = nullptr;
MusicProcess::MusicProcess() {
_theMusicProcess = this;
_type = 1; // persistent
setRunPaused();
}
MusicProcess::~MusicProcess() {
_theMusicProcess = nullptr;
}
uint32 MusicProcess::I_stopMusic(const uint8 * /*args*/,
unsigned int /*argsize*/) {
if (_theMusicProcess) _theMusicProcess->playMusic_internal(0);
return 0;
}
uint32 MusicProcess::I_playMusic(const uint8 *args,
unsigned int /*argsize*/) {
ARG_UINT8(song);
if (_theMusicProcess) _theMusicProcess->playMusic(song & 0x7F);
return 0;
}
uint32 MusicProcess::I_pauseMusic(const uint8 *args,
unsigned int /*argsize*/) {
// This is only used in Crusader: No Regret.
if (_theMusicProcess) _theMusicProcess->pauseMusic();
return 0;
}
uint32 MusicProcess::I_unpauseMusic(const uint8 *args,
unsigned int /*argsize*/) {
// This is only used in Crusader: No Regret.
if (_theMusicProcess) _theMusicProcess->unpauseMusic();
return 0;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,96 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_AUDIO_MUSICPROCESS_H
#define ULTIMA8_AUDIO_MUSICPROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/usecode/intrinsics.h"
#include "ultima/ultima8/misc/classtype.h"
#include "audio/mididrv.h"
namespace Ultima {
namespace Ultima8 {
class Debugger;
class MidiPlayer;
class MusicProcess : public Process {
friend class Debugger;
protected:
//! Play a music track
//! \param track The track number to play. Pass 0 to stop music
virtual void playMusic_internal(int track) = 0;
static MusicProcess *_theMusicProcess;
public:
MusicProcess();
~MusicProcess() override;
ENABLE_RUNTIME_CLASSTYPE()
//! Get the current instance of the Music Processes
static MusicProcess *get_instance() {
return _theMusicProcess;
}
//! Play some background music. Does not change the current track if combat music is active. If another track is currently queued, just queues this track for play.
virtual void playMusic(int track) = 0;
//! Play some combat music - the last played track will be remembered
virtual void playCombatMusic(int track) = 0;
//! Queue a track to start once the current one finishes
virtual void queueMusic(int track) = 0;
//! Clear any queued track (does not affect currently playing track)
virtual void unqueueMusic() = 0;
//! Restore the last requested non-combat track (eg, at the end of combat)
virtual void restoreMusic() = 0;
//! Fades out the music over the specified time (in milliseconds)
virtual void fadeMusic(uint16 length) = 0;
//! Returns true if the music is currently fading
virtual bool isFading() = 0;
//! Save the current track state - used when the menu is opened
virtual void saveTrackState() = 0;
//! Bring back the track state from before it was put on hold
virtual void restoreTrackState() = 0;
//! Is a track currently playing?
virtual bool isPlaying() = 0;
//! Pause the currently playing track
virtual void pauseMusic() = 0;
//! Resume the current track after pausing
virtual void unpauseMusic() = 0;
INTRINSIC(I_playMusic);
INTRINSIC(I_stopMusic);
INTRINSIC(I_pauseMusic);
INTRINSIC(I_unpauseMusic);
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,52 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima8/misc/common_types.h"
#include "ultima/ultima8/audio/raw_audio_sample.h"
#include "common/memstream.h"
#include "audio/decoders/raw.h"
namespace Ultima {
namespace Ultima8 {
RawAudioSample::RawAudioSample(const uint8 *buffer, uint32 size, uint32 rate,
bool signedData, bool stereo)
: AudioSample(buffer, size, 8, stereo, false), _signedData(signedData) {
_sampleRate = rate;
_length = size;
}
RawAudioSample::~RawAudioSample() {
}
Audio::SeekableAudioStream *RawAudioSample::makeStream() const {
Common::MemoryReadStream *stream = new Common::MemoryReadStream(_buffer, _bufferSize, DisposeAfterUse::NO);
byte flags = 0;
if (isStereo())
flags |= Audio::FLAG_STEREO;
if (!_signedData)
flags |= Audio::FLAG_UNSIGNED;
return Audio::makeRawStream(stream, getRate(), flags, DisposeAfterUse::YES);
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,45 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_AUDIO_RAWAUDIOSAMPLE_H
#define ULTIMA8_AUDIO_RAWAUDIOSAMPLE_H
#include "ultima/ultima8/audio/audio_sample.h"
namespace Ultima {
namespace Ultima8 {
class RawAudioSample : public AudioSample {
public:
RawAudioSample(const uint8 *buffer, uint32 size,
uint32 rate, bool signeddata, bool stereo);
~RawAudioSample() override;
Audio::SeekableAudioStream *makeStream() const override;
protected:
bool _signedData;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,246 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima8/misc/common_types.h"
#include "ultima/ultima8/audio/sonarc_audio_sample.h"
#include "common/memstream.h"
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
namespace Ultima {
namespace Ultima8 {
bool SonarcAudioSample::_generatedOneTable = false;
int SonarcAudioSample::_oneTable[256];
SonarcAudioSample::SonarcAudioSample(uint8 const *buffer, uint32 size) :
AudioSample(buffer, size, 8, false, true), _srcOffset(0x20) {
if (!_generatedOneTable) GenerateOneTable();
_length = *_buffer;
_length |= *(_buffer + 1) << 8;
_length |= *(_buffer + 2) << 16;
_length |= *(_buffer + 3) << 24;
_sampleRate = *(_buffer + 4);
_sampleRate |= *(_buffer + 5) << 8;
// Get frame bytes... we need to compensate for 'large' files
uint32 frame_bytes = *(_buffer + _srcOffset);
frame_bytes |= (*(_buffer + _srcOffset + 1)) << 8;
if (frame_bytes == 0x20 && _length > 32767) {
_srcOffset += 0x100;
}
// Get Num Frame Samples
_frameSize = *(_buffer + _srcOffset + 2);
_frameSize |= (*(_buffer + _srcOffset + 3)) << 8;
}
SonarcAudioSample::~SonarcAudioSample(void) {
}
//
// Sonarc Audio Decompressor
//
void SonarcAudioSample::GenerateOneTable() {
// _oneTable[x] gives the number of consecutive 1's on the low side of x
for (int i = 0; i < 256; ++i)
_oneTable[i] = 0;
for (int power = 2; power < 32; power *= 2)
for (int col = power - 1; col < 16; col += power)
for (int row = 0; row < 16; ++row)
_oneTable[row * 16 + col]++;
for (int i = 0; i < 16; ++i)
_oneTable[i * 16 + 15] += _oneTable[i];
}
void SonarcAudioSample::decode_EC(int mode, int samplecount,
const uint8 *source, int sourcesize,
uint8 *dest) {
bool zerospecial = false;
uint32 data = 0;
int inputbits = 0; // current 'fill rate' of data window
if (mode >= 7) {
mode -= 7;
zerospecial = true;
}
while (samplecount) {
// fill data window
while (sourcesize && inputbits <= 24) {
data |= (*source++) << inputbits;
sourcesize--;
inputbits += 8;
}
if (zerospecial && !(data & 0x1)) {
*dest++ = 0x80; // output zero
data >>= 1;
inputbits--;
} else {
if (zerospecial) {
data >>= 1; // strip one
inputbits--;
}
uint8 lowByte = data & 0xFF;
int ones = _oneTable[lowByte];
if (ones == 0) {
data >>= 1; // strip zero
// low byte contains (mode+1) _bits of the sample
const uint8 usample = data & 0xFF;
int8 sample = usample << (7 - mode);
sample >>= (7 - mode); // sign extend
*dest++ = (uint8)(sample + 0x80);
data >>= mode + 1;
inputbits -= mode + 2;
} else if (ones < 7 - mode) {
data >>= ones + 1; // strip ones and zero
// low byte contains (mode+ones) _bits of the sample
const uint8 usample = data & 0xFF;
int8 sample = usample << (7 - mode - ones);
sample &= 0x7F;
if (!(sample & 0x40))
sample |= 0x80; // reconstruct sign bit
sample >>= (7 - mode - ones); // sign extend
*dest++ = (uint8)(sample + 0x80);
data >>= (mode + ones);
inputbits -= mode + 2 * ones + 1;
} else {
data >>= (7 - mode); // strip ones
// low byte contains 7 _bits of the sample
int8 sample = data & 0xFF;
sample &= 0x7F;
if (!(sample & 0x40))
sample |= 0x80; // reconstruct sign bit
*dest++ = (uint8)(sample + 0x80);
data >>= 7;
inputbits -= 2 * 7 - mode;
}
}
samplecount--;
}
}
void SonarcAudioSample::decode_LPC(int order, int nsamples,
uint8 *dest, const uint8 *factors) {
uint8 *startdest = dest;
dest -= order;
// basic linear predictive (de)coding
// the errors this produces are fixed by decode_EC
for (int i = 0; i < nsamples; ++i) {
uint8 *loopdest = dest++;
int accum = 0;
for (int j = order - 1; j >= 0; --j) {
int8 val1 = (loopdest < startdest) ? 0 : (*loopdest);
loopdest++;
val1 ^= 0x80;
int16 val2 = factors[j * 2] + (factors[j * 2 + 1] << 8);
accum += (int)val1 * val2;
}
accum += 0x00000800;
*loopdest -= (int8)((accum >> 12) & 0xFF);
}
}
int SonarcAudioSample::audio_decode(const uint8 *source, uint8 *dest) {
int size = source[0] + (source[1] << 8);
uint16 checksum = 0;
for (int i = 0; i < size / 2; ++i) {
uint16 val = source[2 * i] + (source[2 * i + 1] << 8);
checksum ^= val;
}
if (checksum != 0xACED) return -1;
int order = source[7];
int mode = source[6] - 8;
int samplecount = source[2] + (source[3] << 8);
decode_EC(mode, samplecount,
source + 8 + 2 * order, size - 8 - 2 * order,
dest);
decode_LPC(order, samplecount, dest, source + 8);
// Try to fix a number of clipped samples
for (int i = 1; i < samplecount; ++i)
if (dest[i] == 0 && dest[i - 1] > 192) dest[i] = 0xFF;
return 0;
}
uint32 SonarcAudioSample::decompressFrame(SonarcDecompData *decomp, uint8 *samples) const {
if (decomp->_pos == _bufferSize) return 0;
if (decomp->_samplePos == _length) return 0;
// Get Frame size
uint32 frame_bytes = *(_buffer + decomp->_pos);
frame_bytes |= (*(_buffer + decomp->_pos + 1)) << 8;
// Get Num Frame Samples
uint32 frame_samples = *(_buffer + decomp->_pos + 2);
frame_samples |= (*(_buffer + decomp->_pos + 3)) << 8;
audio_decode(_buffer + decomp->_pos, samples);
decomp->_pos += frame_bytes;
decomp->_samplePos += frame_samples;
return frame_samples;
}
Audio::SeekableAudioStream *SonarcAudioSample::makeStream() const {
// Init the _sample decompressor
SonarcDecompData decomp;
decomp._pos = _srcOffset;
decomp._samplePos = 0;
// Get the data for the _sample
Common::MemoryWriteStreamDynamic streamData(DisposeAfterUse::NO);
uint8 *framePtr = new uint8[_frameSize * 2];
uint32 frameSize;
while ((frameSize = decompressFrame(&decomp, framePtr)) != 0)
streamData.write(framePtr, frameSize);
delete[] framePtr;
// Create the _sample
return Audio::makeRawStream(
new Common::MemoryReadStream(streamData.getData(), streamData.size(), DisposeAfterUse::YES),
getRate(),
isStereo() ? Audio::FLAG_STEREO | Audio::FLAG_UNSIGNED : Audio::FLAG_UNSIGNED,
DisposeAfterUse::YES);
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,64 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_AUDIO_SONARCAUDIOSAMPLE_H
#define ULTIMA8_AUDIO_SONARCAUDIOSAMPLE_H
#include "ultima/ultima8/audio/audio_sample.h"
namespace Ultima {
namespace Ultima8 {
class SonarcAudioSample : public AudioSample {
struct SonarcDecompData {
uint32 _pos;
uint32 _samplePos;
};
static bool _generatedOneTable;
static int _oneTable[256];
static void GenerateOneTable();
static void decode_EC(int mode, int samplecount,
const uint8 *source, int sourcesize,
uint8 *dest);
static void decode_LPC(int order, int nsamples,
uint8 *dest, const uint8 *factors);
static int audio_decode(const uint8 *source, uint8 *dest);
int _frameSize;
uint32 _srcOffset;
public:
SonarcAudioSample(const uint8 *buffer, uint32 size);
~SonarcAudioSample(void) override;
Audio::SeekableAudioStream *makeStream() const override;
private:
uint32 decompressFrame(SonarcDecompData *decompData, uint8 *samples) const;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,127 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima8/misc/debugger.h"
#include "ultima/ultima8/audio/sound_flex.h"
#include "ultima/ultima8/audio/sonarc_audio_sample.h"
#include "ultima/ultima8/audio/raw_audio_sample.h"
#include "common/memstream.h"
namespace Ultima {
namespace Ultima8 {
SoundFlex::SoundFlex(Common::SeekableReadStream *rs) : Archive(rs), _samples(nullptr) {
uint32 size = 0;
uint8 *buf = getRawObject(0, &size);
if (!size || !buf) {
warning("couldn't load sound flex");
return;
}
Common::MemoryReadStream st(buf, size);
_index.push_back(SoundFlexEntry(""));
if (buf[0] == 0xFF) {
// Crusader flex has an index in the first object with the format:
// [00 or FF] [ 3 bytes, often 'oB0' or 'pB0' ] [ null-terminated name ]
// read this data in and work out how to interpret it - probably tells
// some info about how to play back the raw sounds (eg, loop points?)
while (!st.eos() && _index.size() < _count) {
uint32 data = st.readUint32LE();
Std::string str;
char c = st.readByte();
while (c != 0 && !st.eos()) {
str.push_back(c);
c = st.readByte();
}
_index.push_back(SoundFlexEntry(str.c_str(), data));
}
} else {
// In U8 the first object just has 8-byte names.
char name[9] = {0};
int entries = MIN(size / 8, _count);
for (int i = 0; i < entries; i++) {
st.read(name, 8);
_index.push_back(SoundFlexEntry(name));
}
}
}
SoundFlex::~SoundFlex() {
Archive::uncache();
delete [] _samples;
}
AudioSample *SoundFlex::getSample(uint32 index) {
if (index >= _count)
return nullptr;
cache(index);
return _samples[index];
}
void SoundFlex::cache(uint32 index) {
if (index >= _count) return;
if (!_samples) {
_samples = new AudioSample * [_count];
memset(_samples, 0, sizeof(AudioSample *) * _count);
}
if (_samples[index]) return;
// This will cache the data
uint32 size;
uint8 *buf = getRawObject(index, &size);
if (!buf || !size) return;
if (strncmp(reinterpret_cast<const char *>(buf), "ASFX", 4) == 0) {
// After the 32 byte header, ASFX (crusader audio) is just raw 11025 data
if (index < _index.size()) {
const SoundFlexEntry &entry = _index[index];
debug(6, "SoundFlex: Playing sfx %d (%s) with data 0x%04X", index, entry._name.c_str(), entry._data);
}
_samples[index] = new RawAudioSample(buf + 32, size - 32, 11025, true, false);
} else {
_samples[index] = new SonarcAudioSample(buf, size);
}
}
void SoundFlex::uncache(uint32 index) {
if (index >= _count) return;
if (!_samples) return;
delete _samples[index];
_samples[index] = nullptr;
}
bool SoundFlex::isCached(uint32 index) const {
if (index >= _count) return false;
if (!_samples) return false;
return (_samples[index] != nullptr);
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,62 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_AUDIO_SOUNDFLEX_H
#define ULTIMA8_AUDIO_SOUNDFLEX_H
#include "ultima/shared/std/string.h"
#include "ultima/ultima8/filesys/archive.h"
namespace Ultima {
namespace Ultima8 {
class AudioSample;
class SoundFlexEntry {
public:
SoundFlexEntry(const char *name, uint32 data) : _name(name), _data(data) {}
SoundFlexEntry(const char *name) : _name(name), _data(0) {}
Std::string _name;
uint32 _data;
};
class SoundFlex : protected Archive {
public:
SoundFlex(Common::SeekableReadStream *rs);
~SoundFlex() override;
//! Get an audiosample
AudioSample *getSample(uint32 index);
void cache(uint32 index) override;
void uncache(uint32 index) override;
bool isCached(uint32 index) const override;
private:
AudioSample **_samples;
Std::vector<SoundFlexEntry> _index;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,121 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima8/misc/debugger.h"
#include "ultima/ultima8/misc/common_types.h"
#include "ultima/ultima8/audio/speech_flex.h"
#include "ultima/ultima8/audio/audio_sample.h"
namespace Ultima {
namespace Ultima8 {
SpeechFlex::SpeechFlex(Common::SeekableReadStream *rs) : SoundFlex(rs) {
uint32 size = getRawSize(0);
const uint8 *buf = getRawObject(0);
const char *cbuf = reinterpret_cast<const char *>(buf);
// Note: stream holds multiple null-terminated strings.
unsigned int off = 0;
while (off < size) {
unsigned int slen = 0;
while (off + slen < size && cbuf[off + slen])
slen++;
Std::string text(cbuf + off, slen);
text.replace('\t', ' ');
off += slen + 1;
Std::string::size_type pos1 = text.findFirstNotOf(' ');
if (pos1 == Std::string::npos) {
text = "";
}
else {
Std::string::size_type pos2 = text.findLastNotOf(' ');
text = text.substr(pos1, pos2 - pos1 + 1);
}
debug(6, "Found string: \"%s\"", text.c_str());
_phrases.push_back(text);
}
delete [] buf;
}
SpeechFlex::~SpeechFlex(void) {
}
int SpeechFlex::getIndexForPhrase(const Std::string &phrase,
uint32 start, uint32 &end) const {
int i = 1;
Std::string text = phrase.substr(start);
text.replace('\t', ' ');
Std::string::size_type pos1 = text.findFirstNotOf(' ');
if (pos1 == Std::string::npos)
return 0;
Std::string::size_type pos2 = text.findLastNotOf(' ');
text = text.substr(pos1, pos2 - pos1 + 1);
debug(6, "Looking for string: \"%s\"", text.c_str());
for (const auto &p : _phrases) {
if (!p.empty() && text.hasPrefixIgnoreCase(p)) {
debug(6, "Found: %d", i);
end = p.size() + start + pos1;
if (end >= start + pos2)
end = phrase.size();
return i;
}
i++;
}
debug(6, "Not found");
return 0;
}
uint32 SpeechFlex::getSpeechLength(const Std::string &phrase) {
uint32 start = 0, end = 0;
uint32 length = 0;
while (end < phrase.size()) {
start = end;
int index = getIndexForPhrase(phrase, start, end);
if (!index) break;
const AudioSample *sample = getSample(index);
if (!sample) break;
uint32 samples = sample->getLength();
uint32 rate = sample->getRate();
bool stereo = sample->isStereo();
if (stereo) rate *= 2;
length += (samples * 1000) / rate;
}
return length;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,48 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_AUDIO_SPEECHFLEX_H
#define ULTIMA8_AUDIO_SPEECHFLEX_H
#include "ultima/ultima8/audio/sound_flex.h"
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Ultima8 {
class SpeechFlex : public SoundFlex {
Std::vector<Std::string> _phrases;
public:
SpeechFlex(Common::SeekableReadStream *rs);
~SpeechFlex(void) override;
int getIndexForPhrase(const Std::string &phrase,
uint32 start, uint32 &end) const;
//! get total length (in milliseconds) of speech for a phrase
uint32 getSpeechLength(const Std::string &phrase);
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,308 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima8/audio/u8_music_process.h"
#include "ultima/ultima8/games/game_data.h"
#include "ultima/ultima8/audio/music_flex.h"
#include "ultima/ultima8/audio/midi_player.h"
#include "ultima/ultima8/audio/audio_mixer.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(U8MusicProcess)
U8MusicProcess::U8MusicProcess() : _midiPlayer(nullptr), _state(PLAYBACK_NORMAL),
_currentTrack(0), _combatMusicActive(false),
_savedTrackState(nullptr) {
memset(_songBranches, (byte)-1, 128 * sizeof(int));
}
U8MusicProcess::U8MusicProcess(MidiPlayer *player) : _midiPlayer(player),
_state(PLAYBACK_NORMAL), _currentTrack(0), _combatMusicActive(false),
_savedTrackState(nullptr) {
memset(_songBranches, (byte)-1, 128 * sizeof(int));
_theMusicProcess = this;
_type = 1; // persistent
setRunPaused();
// Now get the transition midi
MusicFlex *musicflex = GameData::get_instance()->getMusic();
int xmidi_index = _midiPlayer->isFMSynth() ? 260 : 258;
MusicFlex::XMidiData *xmidi = musicflex->getXMidi(xmidi_index);
_midiPlayer->loadTransitionData(xmidi->_data, xmidi->_size);
}
U8MusicProcess::~U8MusicProcess() {
if (_savedTrackState)
delete _savedTrackState;
if (_midiPlayer)
_midiPlayer->stop();
_theMusicProcess = nullptr;
}
void U8MusicProcess::playMusic(int track) {
_trackState._lastRequest = track;
if (_combatMusicActive)
return;
if (_trackState._queued) {
_trackState._queued = track;
return;
}
playMusic_internal(track);
}
void U8MusicProcess::playCombatMusic(int track) {
_combatMusicActive = (track != 0);
playMusic_internal(track);
}
void U8MusicProcess::queueMusic(int track) {
if (_trackState._wanted != track) {
_trackState._queued = track;
}
}
void U8MusicProcess::unqueueMusic() {
_trackState._queued = 0;
}
void U8MusicProcess::restoreMusic() {
_combatMusicActive = false;
if (_trackState._queued) {
_trackState._queued = _trackState._lastRequest;
return;
}
playMusic_internal(_trackState._lastRequest);
}
void U8MusicProcess::fadeMusic(uint16 length) {
if (_midiPlayer && _midiPlayer->isPlaying())
_midiPlayer->startFadeOut(length);
}
bool U8MusicProcess::isFading() {
return _midiPlayer && _midiPlayer->isFading();
}
void U8MusicProcess::getTrackState(TrackState &trackState) const {
trackState = _trackState;
}
void U8MusicProcess::setTrackState(const TrackState &trackState) {
_trackState = trackState;
_state = PLAYBACK_PLAY_WANTED;
}
void U8MusicProcess::saveTrackState() {
assert(!_savedTrackState);
_savedTrackState = new TrackState(_trackState);
}
void U8MusicProcess::restoreTrackState() {
if (_savedTrackState == nullptr)
return;
_trackState = *_savedTrackState;
_state = PLAYBACK_PLAY_WANTED;
delete _savedTrackState;
_savedTrackState = nullptr;
}
void U8MusicProcess::playMusic_internal(int track) {
if (track < 0 || track >= 128) {
playMusic_internal(0);
return;
}
MusicFlex *musicflex = GameData::get_instance()->getMusic();
// No current track if not playing
if (_midiPlayer && !_midiPlayer->isPlaying())
_trackState._wanted = _currentTrack = 0;
// It's already playing and we are not transitioning
if (_currentTrack == track && _state == PLAYBACK_NORMAL) {
return;
} else if (_currentTrack == 0 || _state != PLAYBACK_NORMAL || !_midiPlayer) {
_trackState._wanted = track;
_state = PLAYBACK_PLAY_WANTED;
} else {
// We want to do a transition
const MusicFlex::SongInfo *info = musicflex->getSongInfo(_currentTrack);
uint32 measure = _midiPlayer->getSequenceCallbackData(0);
// No transition info, or invalid measure, so fast change
if (!info || (measure >= (uint32)info->_numMeasures) ||
!info->_transitions[track] || !info->_transitions[track][measure]) {
_currentTrack = 0;
if (track == 0) {
_trackState._wanted = 0;
_state = PLAYBACK_PLAY_WANTED;
} else {
playMusic_internal(track);
}
return;
}
// Get transition info
int trans = info->_transitions[track][measure];
bool overlay = false;
if (trans < 0) {
trans = (-trans) - 1;
overlay = true;
} else {
trans = trans - 1;
}
warning("Doing a MIDI transition! trans: %d overlay: %d", trans, overlay);
_midiPlayer->playTransition(trans, overlay);
_trackState._wanted = track;
_state = PLAYBACK_TRANSITION;
}
}
void U8MusicProcess::run() {
switch (_state) {
case PLAYBACK_NORMAL:
if (_midiPlayer && !_midiPlayer->isPlaying() && _trackState._queued) {
_trackState._wanted = _trackState._queued;
_state = PLAYBACK_PLAY_WANTED;
_trackState._queued = 0;
}
break;
case PLAYBACK_TRANSITION:
if (!_midiPlayer || !_midiPlayer->isPlaying()) {
// Transition has finished. Play the next track.
_state = PLAYBACK_PLAY_WANTED;
}
break;
case PLAYBACK_PLAY_WANTED: {
if (_midiPlayer)
_midiPlayer->stop();
MusicFlex::XMidiData *xmidi = nullptr;
if (_trackState._wanted) {
int xmidi_index = _trackState._wanted;
if (_midiPlayer && _midiPlayer->isFMSynth())
xmidi_index += 128;
xmidi = GameData::get_instance()->getMusic()->getXMidi(xmidi_index);
}
if (xmidi && xmidi->_data) {
if (_midiPlayer) {
// if there's a track queued, only play this one once
bool repeat = (_trackState._queued == 0);
_midiPlayer->load(xmidi->_data, xmidi->_size, 0);
_midiPlayer->setLooping(repeat);
if (_songBranches[_trackState._wanted] >= 0 && !_midiPlayer->hasBranchIndex(_songBranches[_trackState._wanted])) {
if (_songBranches[_trackState._wanted] == 0) {
// This track does not have any branches.
_songBranches[_trackState._wanted] = -1;
} else {
// Current branch is past the end of the list of branches. Reset to 0.
_songBranches[_trackState._wanted] = 0;
}
}
_midiPlayer->play(0, _songBranches[_trackState._wanted]);
}
_currentTrack = _trackState._wanted;
// Start this track at a different point (branch) next time
_songBranches[_trackState._wanted]++;
} else {
_currentTrack = _trackState._wanted = 0;
}
_state = PLAYBACK_NORMAL;
}
break;
}
}
void U8MusicProcess::saveData(Common::WriteStream *ws) {
MusicProcess::saveData(ws);
// When saving the game we want to remember the track state
// from before the menu was opened
const TrackState *stateToSave = _savedTrackState;
if (stateToSave == nullptr)
stateToSave = &_trackState;
ws->writeUint32LE(static_cast<uint32>(stateToSave->_wanted));
ws->writeUint32LE(static_cast<uint32>(stateToSave->_lastRequest));
ws->writeUint32LE(static_cast<uint32>(stateToSave->_queued));
}
bool U8MusicProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!MusicProcess::loadData(rs, version)) return false;
_trackState._wanted = static_cast<int32>(rs->readUint32LE());
if (version >= 4) {
_trackState._lastRequest = static_cast<int32>(rs->readUint32LE());
_trackState._queued = static_cast<int32>(rs->readUint32LE());
} else {
_trackState._lastRequest = _trackState._wanted;
_trackState._queued = 0;
}
_state = PLAYBACK_PLAY_WANTED;
_theMusicProcess = this;
_midiPlayer = AudioMixer::get_instance()->getMidiPlayer();
return true;
}
bool U8MusicProcess::isPlaying() {
return _currentTrack != 0;
}
void U8MusicProcess::pauseMusic() {
// probably no real use for this?
warning("TODO: U8MusicProcess::pauseMusic Implement me.");
}
void U8MusicProcess::unpauseMusic() {
// probably no real use for this?
warning("TODO: U8MusicProcess::unpauseMusic Implement me.");
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,134 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_AUDIO_U8MUSICPROCESS_H
#define ULTIMA8_AUDIO_U8MUSICPROCESS_H
#include "ultima/ultima8/audio/music_process.h"
#include "ultima/ultima8/misc/classtype.h"
#include "audio/mididrv.h"
namespace Ultima {
namespace Ultima8 {
class Debugger;
class MidiPlayer;
class U8MusicProcess : public MusicProcess {
friend class Debugger;
enum PlaybackStates {
PLAYBACK_NORMAL = 1,
PLAYBACK_TRANSITION = 2,
PLAYBACK_PLAY_WANTED = 3
};
public:
//! The saveable part of track state
struct TrackState {
//! Track we want to play
int _wanted;
//! Last requested track that was not a temporary (ie, combat) track
int _lastRequest;
//! Track queued to start after current
int _queued;
TrackState() : _wanted(0), _lastRequest(0), _queued(0) { }
TrackState(int wanted, int lastRequest, int queued) :
_wanted(wanted), _lastRequest(lastRequest), _queued(queued) { }
};
private:
//! Play a music track
//! \param track The track number to play. Pass 0 to stop music
void playMusic_internal(int track) override;
MidiPlayer *_midiPlayer;
PlaybackStates _state;
//! The branch (starting point) to use for each music track
int _songBranches[128];
int _currentTrack; //! Currently playing track (don't save)
TrackState _trackState;
//! The track state temporarily saved when using the menu etc
TrackState *_savedTrackState;
//! Is the current music "combat" music
bool _combatMusicActive;
public:
U8MusicProcess();
U8MusicProcess(MidiPlayer *player); // Note that this does NOT delete the driver
~U8MusicProcess() override;
ENABLE_RUNTIME_CLASSTYPE()
//! Get the current instance of the Music Processes
static MusicProcess *get_instance() {
return _theMusicProcess;
}
//! Play some background music. Does not change the current track if combat music is active. If another track is currently queued, just queues this track for play.
void playMusic(int track) override;
//! Play some combat music - the last played track will be remembered
void playCombatMusic(int track) override;
//! Queue a track to start once the current one finishes
void queueMusic(int track) override;
//! Clear any queued track (does not affect currently playing track)
void unqueueMusic() override;
//! Restore the last requested non-combat track (eg, at the end of combat)
void restoreMusic() override;
//! Fades out the music over the specified time (in milliseconds)
void fadeMusic(uint16 length) override;
//! Returns true if the music is currently fading
bool isFading() override;
//! Save the current track state - used when the menu is opened
void saveTrackState() override;
//! Bring back the track state from before it was put on hold
void restoreTrackState() override;
//! Get the state of tracks (wanted, requested, queued)
void getTrackState(TrackState &trackState) const;
void setTrackState(const TrackState &state);
//! Is a track currently playing?
bool isPlaying() override;
//! Pause the currently playing track
void pauseMusic() override;
//! Resume the current track after pausing
void unpauseMusic() override;
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif