Files
scummvm-cursorfix/engines/titanic/sound/music_room_handler.cpp
2026-02-02 04:50:13 +01:00

348 lines
10 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 "titanic/sound/music_room_handler.h"
#include "titanic/sound/sound_manager.h"
#include "titanic/events.h"
#include "titanic/core/project_item.h"
#include "titanic/titanic.h"
namespace Titanic {
CMusicRoomHandler::CMusicRoomHandler(CProjectItem *project, CSoundManager *soundManager) :
_project(project), _soundManager(soundManager), _active(false),
_soundHandle(-1), _waveFile(nullptr), _volume(100) {
_instrumentsActive = 0;
_isPlaying = false;
_startTicks = _soundStartTicks = 0;
Common::fill(&_instruments[0], &_instruments[4], (CMusicRoomInstrument *)nullptr);
for (int idx = 0; idx < 4; ++idx)
_songs[idx] = new CMusicSong(idx);
Common::fill(&_startPos[0], &_startPos[4], 0);
Common::fill(&_animExpiryTime[0], &_animExpiryTime[4], 0.0);
Common::fill(&_position[0], &_position[4], 0);
_audioBuffer = new CAudioBuffer(88200);
}
CMusicRoomHandler::~CMusicRoomHandler() {
stop();
for (int idx = 0; idx < 4; ++idx)
delete _songs[idx];
delete _audioBuffer;
}
CMusicRoomInstrument *CMusicRoomHandler::createInstrument(MusicInstrument instrument, int count) {
switch (instrument) {
case BELLS:
_instruments[BELLS] = new CMusicRoomInstrument(_project, _soundManager, MV_BELLS);
break;
case SNAKE:
_instruments[SNAKE] = new CMusicRoomInstrument(_project, _soundManager, MV_SNAKE);
break;
case PIANO:
_instruments[PIANO] = new CMusicRoomInstrument(_project, _soundManager, MV_PIANO);
break;
case BASS:
_instruments[BASS] = new CMusicRoomInstrument(_project, _soundManager, MV_BASS);
break;
default:
return nullptr;
}
_instruments[instrument]->setFilesCount(count);
return _instruments[instrument];
}
void CMusicRoomHandler::setup(int volume) {
_volume = volume;
_audioBuffer->reset();
for (int idx = 0; idx < 4; ++idx) {
MusicRoomInstrument &ins1 = _array1[idx];
MusicRoomInstrument &ins2 = _array2[idx];
if (ins1._directionControl == ins2._directionControl) {
_startPos[idx] = 0;
} else {
_startPos[idx] = _songs[idx]->size() - 1;
}
_position[idx] = _startPos[idx];
_animExpiryTime[idx] = 0.0;
}
_instrumentsActive = 4;
_isPlaying = true;
update();
_waveFile = _soundManager->loadMusic(_audioBuffer, DisposeAfterUse::NO);
update();
}
void CMusicRoomHandler::stop() {
if (_waveFile) {
_soundManager->stopSound(_soundHandle);
delete _waveFile;
_waveFile = nullptr;
_soundHandle = -1;
}
for (int idx = 0; idx < 4; ++idx) {
_instruments[idx]->clear();
if (_active && _instruments[idx])
_instruments[idx]->stop();
}
_instrumentsActive = 0;
_isPlaying = false;
_startTicks = _soundStartTicks = 0;
}
bool CMusicRoomHandler::checkInstrument(MusicInstrument instrument) const {
return (_array1[instrument]._speedControl + _array2[instrument]._speedControl) == 0
&& (_array1[instrument]._pitchControl + _array2[instrument]._pitchControl) == 0
&& _array1[instrument]._directionControl == _array2[instrument]._directionControl
&& _array1[instrument]._inversionControl == _array2[instrument]._inversionControl
&& _array1[instrument]._muteControl == _array2[instrument]._muteControl;
}
void CMusicRoomHandler::setSpeedControl2(MusicInstrument instrument, int value) {
if (instrument >= BELLS && instrument <= BASS && value >= -2 && value <= 2)
_array2[instrument]._speedControl = value;
}
void CMusicRoomHandler::setPitchControl2(MusicInstrument instrument, int value) {
if (instrument >= BELLS && instrument <= BASS && value >= -2 && value <= 2)
_array2[instrument]._pitchControl = value * 3;
}
void CMusicRoomHandler::setInversionControl2(MusicInstrument instrument, bool value) {
if (instrument >= BELLS && instrument <= BASS)
_array2[instrument]._inversionControl = value;
}
void CMusicRoomHandler::setDirectionControl2(MusicInstrument instrument, bool value) {
if (instrument >= BELLS && instrument <= BASS)
_array2[instrument]._directionControl = value;
}
void CMusicRoomHandler::setPitchControl(MusicInstrument instrument, int value) {
if (instrument >= BELLS && instrument <= BASS && value >= -2 && value <= 2)
_array1[instrument]._pitchControl = value * 3;
}
void CMusicRoomHandler::setSpeedControl(MusicInstrument instrument, int value) {
if (instrument >= BELLS && instrument <= BASS && value >= -2 && value <= 2)
_array1[instrument]._speedControl = value;
}
void CMusicRoomHandler::setDirectionControl(MusicInstrument instrument, bool value) {
if (instrument >= BELLS && instrument <= BASS)
_array1[instrument]._directionControl = value;
}
void CMusicRoomHandler::setInversionControl(MusicInstrument instrument, bool value) {
if (instrument >= BELLS && instrument <= BASS)
_array1[instrument]._inversionControl = value;
}
void CMusicRoomHandler::setMuteControl(MusicInstrument instrument, bool value) {
if (instrument >= BELLS && instrument <= BASS)
_array1[instrument]._muteControl = value;
}
void CMusicRoomHandler::start() {
if (_active) {
for (int idx = 0; idx < 4; ++idx)
_instruments[idx]->start();
}
}
bool CMusicRoomHandler::update() {
uint currentTicks = g_system->getMillis();
if (!_startTicks) {
start();
_startTicks = currentTicks;
} else if (!_soundStartTicks && currentTicks >= (_startTicks + 3000)) {
if (_waveFile) {
CProximity prox;
prox._channelVolume = _volume;
_soundHandle = _soundManager->playSound(*_waveFile, prox);
}
_soundStartTicks = currentTicks;
}
if (_instrumentsActive > 0) {
updateAudio();
updateInstruments();
}
return !_audioBuffer->isFinished();
}
void CMusicRoomHandler::updateAudio() {
int size = _audioBuffer->freeSize();
int count;
int16 *ptr;
if (size > 0) {
// Create a temporary buffer for merging the instruments into
int16 *audioData = new int16[size];
Common::fill(audioData, audioData + size, 0);
for (MusicInstrument instrument = BELLS; instrument <= BASS;
instrument = (MusicInstrument)((int)instrument + 1)) {
CMusicRoomInstrument *musicWave = _instruments[instrument];
// Iterate through each of the four instruments and do an additive
// read that will merge their data onto the output buffer
for (count = size, ptr = audioData; count > 0; ) {
int amount = musicWave->read(ptr, count * 2);
if (amount > 0) {
count -= amount / sizeof(uint16);
ptr += amount / sizeof(uint16);
} else if (!pollInstrument(instrument)) {
--_instrumentsActive;
break;
}
}
}
_audioBuffer->push(audioData, size);
delete[] audioData;
}
if (_instrumentsActive == 0)
// Reaching end of music
_audioBuffer->finalize();
}
void CMusicRoomHandler::updateInstruments() {
uint currentTicks = g_system->getMillis();
if (_active && _soundStartTicks) {
for (MusicInstrument instrument = BELLS; instrument <= BASS;
instrument = (MusicInstrument)((int)instrument + 1)) {
MusicRoomInstrument &ins1 = _array1[instrument];
MusicRoomInstrument &ins2 = _array2[instrument];
CMusicRoomInstrument *ins = _instruments[instrument];
// Is this about checking playback position?
if (_position[instrument] < 0 || ins1._muteControl || _position[instrument] >= _songs[instrument]->size()) {
_position[instrument] = -1;
continue;
}
double time = (double)(currentTicks - _soundStartTicks) / 1000.0 - 0.6;
double threshold = _animExpiryTime[instrument] - ins->_insStartTime;
if (time >= threshold) {
_animExpiryTime[instrument] += getAnimDuration(instrument, _position[instrument]);
const CValuePair &vp = (*_songs[instrument])[_position[instrument]];
if (vp._data != 0x7FFFFFFF) {
int amount = getPitch(instrument, _position[instrument]);
_instruments[instrument]->update(amount);
}
if (ins1._directionControl == ins2._directionControl) {
_position[instrument]++;
} else {
_position[instrument]--;
}
}
}
}
}
bool CMusicRoomHandler::pollInstrument(MusicInstrument instrument) {
int &arrIndex = _startPos[instrument];
if (arrIndex < 0) {
_instruments[instrument]->clear();
return false;
}
const CMusicSong &song = *_songs[instrument];
if (arrIndex >= song.size()) {
arrIndex = -1;
_instruments[instrument]->clear();
return false;
}
const CValuePair &vp = song[arrIndex];
uint duration = static_cast<int>(getAnimDuration(instrument, arrIndex) * 44100.0) & ~1;
if (vp._data == 0x7FFFFFFF || _array1[instrument]._muteControl)
_instruments[instrument]->reset(duration);
else
_instruments[instrument]->chooseWaveFile(getPitch(instrument, arrIndex), duration);
if (_array1[instrument]._directionControl == _array2[instrument]._directionControl) {
++arrIndex;
} else {
--arrIndex;
}
return true;
}
double CMusicRoomHandler::getAnimDuration(MusicInstrument instrument, int arrIndex) {
const CValuePair &vp = (*_songs[instrument])[arrIndex];
switch (_array1[instrument]._speedControl + _array2[instrument]._speedControl + 3) {
case 0:
return (double)vp._length * 1.5 * 0.0625 * 0.46875;
case 1:
return (double)vp._length * 1.33 * 0.0625 * 0.46875;
case 2:
return (double)vp._length * 1.25 * 0.0625 * 0.46875;
case 4:
return (double)vp._length * 0.75 * 0.0625 * 0.46875;
case 5:
return (double)vp._length * 0.67 * 0.0625 * 0.46875;
case 6:
return (double)vp._length * 0.5 * 0.0625 * 0.46875;
default:
return (double)vp._length * 1.0 * 0.0625 * 0.46875;
}
}
int CMusicRoomHandler::getPitch(MusicInstrument instrument, int arrIndex) {
const CMusicSong &song = *_songs[instrument];
const CValuePair &vp = song[arrIndex];
int val = vp._data;
const MusicRoomInstrument &ins1 = _array1[instrument];
const MusicRoomInstrument &ins2 = _array2[instrument];
if (ins1._inversionControl != ins2._inversionControl) {
val = song._minVal * 2 + song._range - val;
}
val += ins1._pitchControl + ins2._pitchControl;
return val;
}
} // End of namespace Titanic