Initial commit
This commit is contained in:
78
engines/ultima/ultima8/audio/audio_channel.cpp
Normal file
78
engines/ultima/ultima8/audio/audio_channel.cpp
Normal 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
|
||||
67
engines/ultima/ultima8/audio/audio_channel.h
Normal file
67
engines/ultima/ultima8/audio/audio_channel.h
Normal 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
|
||||
148
engines/ultima/ultima8/audio/audio_mixer.cpp
Normal file
148
engines/ultima/ultima8/audio/audio_mixer.cpp
Normal 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
|
||||
72
engines/ultima/ultima8/audio/audio_mixer.h
Normal file
72
engines/ultima/ultima8/audio/audio_mixer.h
Normal 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
|
||||
668
engines/ultima/ultima8/audio/audio_process.cpp
Normal file
668
engines/ultima/ultima8/audio/audio_process.cpp
Normal 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
|
||||
152
engines/ultima/ultima8/audio/audio_process.h
Normal file
152
engines/ultima/ultima8/audio/audio_process.h
Normal 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
|
||||
39
engines/ultima/ultima8/audio/audio_sample.cpp
Normal file
39
engines/ultima/ultima8/audio/audio_sample.cpp
Normal 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
|
||||
69
engines/ultima/ultima8/audio/audio_sample.h
Normal file
69
engines/ultima/ultima8/audio/audio_sample.h
Normal 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
|
||||
263
engines/ultima/ultima8/audio/cru_music_process.cpp
Normal file
263
engines/ultima/ultima8/audio/cru_music_process.cpp
Normal 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
|
||||
101
engines/ultima/ultima8/audio/cru_music_process.h
Normal file
101
engines/ultima/ultima8/audio/cru_music_process.h
Normal 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
|
||||
266
engines/ultima/ultima8/audio/midi_player.cpp
Normal file
266
engines/ultima/ultima8/audio/midi_player.cpp
Normal 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
|
||||
132
engines/ultima/ultima8/audio/midi_player.h
Normal file
132
engines/ultima/ultima8/audio/midi_player.h
Normal 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
|
||||
244
engines/ultima/ultima8/audio/music_flex.cpp
Normal file
244
engines/ultima/ultima8/audio/music_flex.cpp
Normal 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
|
||||
78
engines/ultima/ultima8/audio/music_flex.h
Normal file
78
engines/ultima/ultima8/audio/music_flex.h
Normal 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
|
||||
71
engines/ultima/ultima8/audio/music_process.cpp
Normal file
71
engines/ultima/ultima8/audio/music_process.cpp
Normal 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
|
||||
96
engines/ultima/ultima8/audio/music_process.h
Normal file
96
engines/ultima/ultima8/audio/music_process.h
Normal 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
|
||||
52
engines/ultima/ultima8/audio/raw_audio_sample.cpp
Normal file
52
engines/ultima/ultima8/audio/raw_audio_sample.cpp
Normal 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
|
||||
45
engines/ultima/ultima8/audio/raw_audio_sample.h
Normal file
45
engines/ultima/ultima8/audio/raw_audio_sample.h
Normal 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
|
||||
246
engines/ultima/ultima8/audio/sonarc_audio_sample.cpp
Normal file
246
engines/ultima/ultima8/audio/sonarc_audio_sample.cpp
Normal 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
|
||||
64
engines/ultima/ultima8/audio/sonarc_audio_sample.h
Normal file
64
engines/ultima/ultima8/audio/sonarc_audio_sample.h
Normal 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
|
||||
127
engines/ultima/ultima8/audio/sound_flex.cpp
Normal file
127
engines/ultima/ultima8/audio/sound_flex.cpp
Normal 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
|
||||
62
engines/ultima/ultima8/audio/sound_flex.h
Normal file
62
engines/ultima/ultima8/audio/sound_flex.h
Normal 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
|
||||
121
engines/ultima/ultima8/audio/speech_flex.cpp
Normal file
121
engines/ultima/ultima8/audio/speech_flex.cpp
Normal 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
|
||||
48
engines/ultima/ultima8/audio/speech_flex.h
Normal file
48
engines/ultima/ultima8/audio/speech_flex.h
Normal 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
|
||||
308
engines/ultima/ultima8/audio/u8_music_process.cpp
Normal file
308
engines/ultima/ultima8/audio/u8_music_process.cpp
Normal 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
|
||||
134
engines/ultima/ultima8/audio/u8_music_process.h
Normal file
134
engines/ultima/ultima8/audio/u8_music_process.h
Normal 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
|
||||
Reference in New Issue
Block a user