Files
scummvm-cursorfix/engines/ultima/ultima8/audio/u8_music_process.cpp
2026-02-02 04:50:13 +01:00

309 lines
8.2 KiB
C++

/* 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