mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-04 01:46:42 +00:00
17f4e65aff
The first track in the LotT introduction fades out before the second track starts. The fade-out is implemented by reducing the velocity of the new notes that are being played. Because this does not affect notes that are already playing, the fade-out effect is not very good. I've replaced this by a fade-out using MIDI channel volume.
1022 lines
29 KiB
C++
1022 lines
29 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 2
|
|
* 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, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "lure/sound.h"
|
|
#include "lure/game.h"
|
|
#include "lure/lure.h"
|
|
#include "lure/memory.h"
|
|
#include "lure/res.h"
|
|
#include "lure/room.h"
|
|
#include "lure/surface.h"
|
|
|
|
#include "common/algorithm.h"
|
|
#include "common/config-manager.h"
|
|
#include "common/endian.h"
|
|
#include "audio/midiparser.h"
|
|
|
|
namespace Common {
|
|
DECLARE_SINGLETON(Lure::SoundManager);
|
|
}
|
|
|
|
namespace Lure {
|
|
|
|
//#define SOUND_CROP_CHANNELS
|
|
|
|
SoundManager::SoundManager() {
|
|
Disk &disk = Disk::getReference();
|
|
|
|
int index;
|
|
_descs = disk.getEntry(SOUND_DESC_RESOURCE_ID);
|
|
_numDescs = _descs->size() / sizeof(SoundDescResource);
|
|
_soundData = NULL;
|
|
_paused = false;
|
|
|
|
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32);
|
|
_isRoland = MidiDriver::getMusicType(dev) != MT_ADLIB;
|
|
_nativeMT32 = ((MidiDriver::getMusicType(dev) == MT_MT32) || ConfMan.getBool("native_mt32"));
|
|
|
|
Common::fill(_channelsInUse, _channelsInUse + NUM_CHANNELS, false);
|
|
Common::fill(_sourcesInUse, _sourcesInUse + LURE_MAX_SOURCES, false);
|
|
|
|
if (_isRoland) {
|
|
_driver = _mt32Driver = new MidiDriver_MT32GM(MT_MT32);
|
|
} else {
|
|
_driver = MidiDriver::createMidi(dev);
|
|
}
|
|
int statusCode = _driver->open();
|
|
if (statusCode)
|
|
error("Sound driver returned error code %d", statusCode);
|
|
|
|
if (!_isRoland) {
|
|
for (index = 0; index < NUM_CHANNELS; ++index) {
|
|
_channelsInner[index].midiChannel = _driver->allocateChannel();
|
|
/* 90 is power on default for midi compliant devices */
|
|
_channelsInner[index].volume = 90;
|
|
}
|
|
}
|
|
|
|
syncSounds();
|
|
}
|
|
|
|
SoundManager::~SoundManager() {
|
|
if (_driver)
|
|
_driver->setTimerCallback(this, NULL);
|
|
|
|
removeSounds();
|
|
_activeSounds.clear();
|
|
|
|
_soundMutex.lock();
|
|
_playingSounds.clear();
|
|
_soundMutex.unlock();
|
|
|
|
delete _descs;
|
|
delete _soundData;
|
|
|
|
if (_driver) {
|
|
_driver->stopAllNotes();
|
|
_driver->close();
|
|
delete _driver;
|
|
_driver = NULL;
|
|
}
|
|
}
|
|
|
|
void SoundManager::saveToStream(Common::WriteStream *stream) {
|
|
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::saveToStream");
|
|
SoundListIterator i;
|
|
|
|
for (i = _activeSounds.begin(); i != _activeSounds.end(); ++i) {
|
|
stream->writeByte((*i)->soundNumber);
|
|
}
|
|
stream->writeByte(0xff);
|
|
}
|
|
|
|
void SoundManager::loadFromStream(Common::ReadStream *stream) {
|
|
// Stop any existing sounds playing
|
|
killSounds();
|
|
|
|
// Load any playing sounds
|
|
uint8 soundNumber;
|
|
while ((soundNumber = stream->readByte()) != 0xff) {
|
|
uint8 soundIndex = descIndexOf(soundNumber);
|
|
if (soundIndex != 0xff) {
|
|
// Make sure that the sound is allowed to be restored
|
|
SoundDescResource &rec = soundDescs()[soundIndex];
|
|
if ((rec.flags & SF_RESTORE) != 0)
|
|
// Requeue the sound for playing
|
|
addSound(soundIndex, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SoundManager::loadSection(uint16 sectionId) {
|
|
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::loadSection = %xh", sectionId);
|
|
killSounds();
|
|
|
|
if (_soundData) {
|
|
delete _soundData;
|
|
_driver->setTimerCallback(this, NULL);
|
|
}
|
|
|
|
_soundData = Disk::getReference().getEntry(sectionId);
|
|
_soundsTotal = *_soundData->data();
|
|
|
|
_driver->setTimerCallback(this, &onTimer);
|
|
}
|
|
|
|
bool SoundManager::initCustomTimbres(bool canAbort) {
|
|
if (!_isRoland || !_nativeMT32 || _mt32Driver == NULL)
|
|
return false;
|
|
|
|
if (!_soundData)
|
|
error("SoundManager::initCustomTimbres - sound section has not been specified");
|
|
|
|
// Locate timbre data
|
|
uint32 headerSize = READ_LE_UINT32(_soundData->data() + 2); // Skip past the number of sounds
|
|
uint16 timbreDataHeaderOffset = _soundsTotal * 4 + 2;
|
|
if (timbreDataHeaderOffset + 6u > headerSize) {
|
|
warning("SoundManager::initCustomTimbres - could not find timbre data header");
|
|
return false;
|
|
}
|
|
|
|
uint32 timbreDataOffset = READ_LE_UINT32(_soundData->data() + timbreDataHeaderOffset + 2); // Skip past end of header mark
|
|
if (timbreDataOffset + 17259 > _soundData->size()) {
|
|
warning("SoundManager::initCustomTimbres - timbre data smaller than expected");
|
|
return false;
|
|
}
|
|
byte *timbreData = _soundData->data() + timbreDataOffset;
|
|
|
|
AudioInitIcon *icon = new AudioInitIcon();
|
|
icon->show();
|
|
uint32 iconTime = g_system->getMillis();
|
|
|
|
// Send SysExes
|
|
|
|
// System Area
|
|
uint32 address = 0x10 << 14; // 10 00 00
|
|
static const uint8 systemAreaSysExLengths[5] = { 1, 3, 9, 9, 1 };
|
|
for (int i = 0; i < 5; ++i) {
|
|
_mt32Driver->sysExMT32(timbreData, systemAreaSysExLengths[i], address, true);
|
|
address += systemAreaSysExLengths[i];
|
|
timbreData += systemAreaSysExLengths[i];
|
|
}
|
|
// Patch Temporary Area
|
|
address = 0x03 << 14; // 03 00 00
|
|
int sysexLength = 16;
|
|
for (int i = 0; i < 8; ++i) {
|
|
_mt32Driver->sysExMT32(timbreData, sysexLength, address, true);
|
|
address += sysexLength;
|
|
timbreData += sysexLength;
|
|
}
|
|
// Timbre Memory
|
|
address = 0x08 << 14; // 08 00 00
|
|
sysexLength = 246;
|
|
for (int i = 0; i < 64; ++i) {
|
|
_mt32Driver->sysExMT32(timbreData, sysexLength, address, true);
|
|
address += 256;
|
|
timbreData += sysexLength;
|
|
}
|
|
// Patch Memory
|
|
address = 0x05 << 14; // 05 00 00
|
|
sysexLength = 8;
|
|
for (int i = 0; i < 128; ++i) {
|
|
_mt32Driver->sysExMT32(timbreData, sysexLength, address, true);
|
|
address += sysexLength;
|
|
timbreData += sysexLength;
|
|
}
|
|
// Rhythm Part Setup Temporary Area
|
|
address = 0x03 << 14 | 0x01 << 7 | 0x10; // 03 01 10
|
|
sysexLength = 4;
|
|
for (int i = 0; i < 85; ++i) {
|
|
_mt32Driver->sysExMT32(timbreData, sysexLength, address, true);
|
|
address += sysexLength;
|
|
timbreData += sysexLength;
|
|
}
|
|
|
|
// Wait until SysExes have been transmitted.
|
|
bool result = false;
|
|
while (!_mt32Driver->isReady()) {
|
|
Events &events = Events::getReference();
|
|
|
|
if (events.interruptableDelay(10)) {
|
|
if (LureEngine::getReference().shouldQuit() ||
|
|
(canAbort && events.type() == Common::EVENT_KEYDOWN && events.event().kbd.keycode == 27)) {
|
|
// User has quit the game or pressed Escape.
|
|
_mt32Driver->clearSysExQueue();
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Blink the audio initialization icon every 500 ms
|
|
if (g_system->getMillis() > iconTime + 500) {
|
|
icon->toggleVisibility();
|
|
iconTime = g_system->getMillis();
|
|
}
|
|
}
|
|
|
|
icon->hide();
|
|
delete icon;
|
|
|
|
return result;
|
|
}
|
|
|
|
void SoundManager::bellsBodge() {
|
|
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::bellsBodge");
|
|
Resources &res = Resources::getReference();
|
|
Room &room = Room::getReference();
|
|
|
|
RoomData *roomData = res.getRoom(room.roomNumber());
|
|
if (roomData && roomData->areaFlag != res.fieldList().getField(AREA_FLAG)) {
|
|
res.fieldList().setField(AREA_FLAG, roomData->areaFlag);
|
|
|
|
switch (roomData->areaFlag) {
|
|
case 0:
|
|
killSound(1);
|
|
break;
|
|
case 1:
|
|
addSound(2);
|
|
killSound(33);
|
|
break;
|
|
case 2:
|
|
setVolume(0, 15);
|
|
// fall through
|
|
default:
|
|
killSound(1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SoundManager::killSounds() {
|
|
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::killSounds");
|
|
|
|
// Stop the player playing all sounds
|
|
musicInterface_KillAll();
|
|
|
|
// Clear the active sounds
|
|
_activeSounds.clear();
|
|
Common::fill(_channelsInUse, _channelsInUse + NUM_CHANNELS, false);
|
|
}
|
|
|
|
void SoundManager::addSound(uint8 soundIndex, bool tidyFlag) {
|
|
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::addSound index=%d", soundIndex);
|
|
Game &game = Game::getReference();
|
|
|
|
if (tidyFlag)
|
|
tidySounds();
|
|
|
|
if (game.preloadFlag())
|
|
// Don't add a sound if in room preloading
|
|
return;
|
|
|
|
SoundDescResource &rec = soundDescs()[soundIndex];
|
|
int numChannels;
|
|
|
|
if (_isRoland)
|
|
numChannels = (rec.numChannels & 3);
|
|
else
|
|
numChannels = ((rec.numChannels >> 2) & 3) + 1;
|
|
|
|
int channelCtr, channelCtr2;
|
|
for (channelCtr = 0; channelCtr <= (NUM_CHANNELS - numChannels); ++channelCtr) {
|
|
for (channelCtr2 = 0; channelCtr2 < numChannels; ++channelCtr2)
|
|
if (_channelsInUse[channelCtr + channelCtr2])
|
|
break;
|
|
|
|
if (channelCtr2 == numChannels)
|
|
break;
|
|
}
|
|
|
|
if (channelCtr > (NUM_CHANNELS - numChannels)) {
|
|
// No channels free
|
|
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::addSound - no channels free");
|
|
return;
|
|
}
|
|
|
|
// Mark the found channels as in use
|
|
Common::fill(_channelsInUse+channelCtr, _channelsInUse+channelCtr + numChannels, true);
|
|
|
|
SoundDescResource *newEntry = new SoundDescResource();
|
|
newEntry->soundNumber = rec.soundNumber;
|
|
newEntry->channel = channelCtr;
|
|
newEntry->numChannels = numChannels;
|
|
newEntry->flags = rec.flags;
|
|
|
|
if (_isRoland)
|
|
newEntry->volume = rec.volume;
|
|
else /* resource volumes do not seem to work well with our AdLib emu */
|
|
newEntry->volume = 240; /* 255 causes clipping with AdLib */
|
|
|
|
_activeSounds.push_back(SoundList::value_type(newEntry));
|
|
|
|
musicInterface_Play(rec.soundNumber, channelCtr, false, numChannels, newEntry->volume);
|
|
}
|
|
|
|
void SoundManager::addSound2(uint8 soundIndex) {
|
|
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::addSound2 index=%d", soundIndex);
|
|
tidySounds();
|
|
|
|
if (soundIndex == 6) {
|
|
// Chinese torture
|
|
stopSound(6); // sometimes its still playing when restarted
|
|
addSound(6, false);
|
|
} else {
|
|
SoundDescResource &descEntry = soundDescs()[soundIndex];
|
|
SoundDescResource *rec = findSound(descEntry.soundNumber);
|
|
if (rec == NULL)
|
|
// Sound isn't active, so go and add it
|
|
addSound(soundIndex, false);
|
|
}
|
|
}
|
|
|
|
|
|
void SoundManager::stopSound(uint8 soundIndex) {
|
|
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::stopSound index=%d", soundIndex);
|
|
SoundDescResource &rec = soundDescs()[soundIndex];
|
|
musicInterface_Stop(rec.soundNumber);
|
|
}
|
|
|
|
void SoundManager::killSound(uint8 soundNumber) {
|
|
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::stopSound soundNumber=%d", soundNumber);
|
|
musicInterface_Stop(soundNumber);
|
|
}
|
|
|
|
void SoundManager::setVolume(uint8 soundNumber, uint8 volume) {
|
|
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::setVolume soundNumber=%d, volume=%d",
|
|
soundNumber, volume);
|
|
musicInterface_TidySounds();
|
|
|
|
SoundDescResource *entry = findSound(soundNumber);
|
|
if (entry)
|
|
musicInterface_SetVolume(entry->channel, volume);
|
|
}
|
|
|
|
uint8 SoundManager::descIndexOf(uint8 soundNumber) {
|
|
SoundDescResource *rec = soundDescs();
|
|
|
|
for (uint8 index = 0; index < _numDescs; ++index, ++rec) {
|
|
if (rec->soundNumber == soundNumber)
|
|
return index;
|
|
}
|
|
|
|
return 0xff; // Couldn't find entry
|
|
}
|
|
|
|
// Used to sync the volume for all channels with the Config Manager
|
|
//
|
|
void SoundManager::syncSounds() {
|
|
musicInterface_TidySounds();
|
|
|
|
bool mute = false;
|
|
if (ConfMan.hasKey("mute"))
|
|
mute = ConfMan.getBool("mute");
|
|
_musicVolume = mute ? 0 : MIN(256, ConfMan.getInt("music_volume"));
|
|
_sfxVolume = mute ? 0 : MIN(256, ConfMan.getInt("sfx_volume"));
|
|
|
|
if (_isRoland) {
|
|
_mt32Driver->syncSoundSettings();
|
|
} else {
|
|
_soundMutex.lock();
|
|
MusicListIterator i;
|
|
for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
|
|
// FIXME This should not override the sound resource volume
|
|
// on the MidiMusic object.
|
|
if ((*i)->isMusic())
|
|
(*i)->setVolume(_musicVolume);
|
|
else
|
|
(*i)->setVolume(_sfxVolume);
|
|
}
|
|
_soundMutex.unlock();
|
|
}
|
|
}
|
|
|
|
SoundDescResource *SoundManager::findSound(uint8 soundNumber) {
|
|
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::findSound soundNumber=%d", soundNumber);
|
|
SoundListIterator i;
|
|
|
|
for (i = _activeSounds.begin(); i != _activeSounds.end(); ++i) {
|
|
SoundDescResource *rec = (*i).get();
|
|
|
|
if (rec->soundNumber == soundNumber) {
|
|
debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "SoundManager::findSound - sound found");
|
|
return rec;
|
|
}
|
|
}
|
|
|
|
// Signal that sound wasn't found
|
|
debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "SoundManager::findSound - sound not found");
|
|
return NULL;
|
|
}
|
|
|
|
void SoundManager::tidySounds() {
|
|
debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "SoundManager::tidySounds");
|
|
SoundListIterator i = _activeSounds.begin();
|
|
|
|
while (i != _activeSounds.end()) {
|
|
SoundDescResource const &rec = **i;
|
|
|
|
if (musicInterface_CheckPlaying(rec.soundNumber))
|
|
// Still playing, so move to next entry
|
|
++i;
|
|
else {
|
|
// Mark the channels that it used as now being free
|
|
Common::fill(_channelsInUse + rec.channel, _channelsInUse + rec.channel + rec.numChannels, false);
|
|
|
|
i = _activeSounds.erase(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SoundManager::removeSounds() {
|
|
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::removeSounds");
|
|
bellsBodge();
|
|
|
|
SoundListIterator i = _activeSounds.begin();
|
|
|
|
while (i != _activeSounds.end()) {
|
|
SoundDescResource const &rec = **i;
|
|
|
|
if ((rec.flags & SF_IN_USE) != 0)
|
|
musicInterface_Stop(rec.soundNumber);
|
|
|
|
++i;
|
|
}
|
|
}
|
|
|
|
void SoundManager::restoreSounds() {
|
|
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::restoreSounds");
|
|
SoundListIterator i = _activeSounds.begin();
|
|
|
|
while (i != _activeSounds.end()) {
|
|
SoundDescResource const &rec = **i;
|
|
|
|
if ((rec.numChannels != 0) && ((rec.flags & SF_RESTORE) != 0)) {
|
|
Common::fill(_channelsInUse + rec.channel, _channelsInUse + rec.channel + rec.numChannels, true);
|
|
|
|
musicInterface_Play(rec.soundNumber, rec.channel, false, rec.numChannels, rec.volume);
|
|
}
|
|
|
|
++i;
|
|
}
|
|
}
|
|
|
|
bool SoundManager::fadeOut() {
|
|
debugC(ERROR_BASIC, kLureDebugSounds, "SoundManager::fadeOut");
|
|
|
|
Events &events = Events::getReference();
|
|
bool result = false;
|
|
|
|
// Fade out all the active sounds
|
|
musicInterface_TidySounds();
|
|
|
|
if (_isRoland) {
|
|
_mt32Driver->startFade(3000, 0);
|
|
while (_mt32Driver->isFading()) {
|
|
if (events.interruptableDelay(100)) {
|
|
result = ((events.type() == Common::EVENT_KEYDOWN && events.event().kbd.keycode == 27) ||
|
|
LureEngine::getReference().shouldQuit());
|
|
_mt32Driver->abortFade();
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
bool inProgress = true;
|
|
while (inProgress) {
|
|
inProgress = false;
|
|
|
|
_soundMutex.lock();
|
|
MusicListIterator i;
|
|
for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
|
|
MidiMusic &music = **i;
|
|
if (music.getVolume() > 0) {
|
|
inProgress = true;
|
|
music.setVolume(music.getVolume() >= 10 ? music.getVolume() - 10 : 0);
|
|
}
|
|
}
|
|
|
|
_soundMutex.unlock();
|
|
g_system->delayMillis(10);
|
|
}
|
|
}
|
|
|
|
// Kill all the sounds
|
|
musicInterface_KillAll();
|
|
|
|
if (_isRoland)
|
|
_mt32Driver->setSourceVolume(MidiDriver_MT32GM::DEFAULT_SOURCE_NEUTRAL_VOLUME);
|
|
|
|
return result;
|
|
}
|
|
|
|
void SoundManager::pause() {
|
|
_paused = true;
|
|
|
|
_soundMutex.lock();
|
|
|
|
MusicListIterator i;
|
|
for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
|
|
(**i).pauseMusic();
|
|
}
|
|
|
|
_soundMutex.unlock();
|
|
|
|
// Terminate any hanging notes, just in case
|
|
_driver->stopAllNotes();
|
|
}
|
|
|
|
void SoundManager::resume() {
|
|
_paused = false;
|
|
|
|
_soundMutex.lock();
|
|
|
|
MusicListIterator i;
|
|
for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
|
|
(**i).resumeMusic();
|
|
}
|
|
|
|
_soundMutex.unlock();
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
// musicInterface_Play
|
|
// Play the specified sound
|
|
|
|
void SoundManager::musicInterface_Play(uint8 soundNumber, uint8 channelNumber, bool isMusic, uint8 numChannels, uint8 volume) {
|
|
debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "musicInterface_Play soundNumber=%d, channel=%d",
|
|
soundNumber, channelNumber);
|
|
Game &game = Game::getReference();
|
|
|
|
if (!_soundData)
|
|
error("Sound section has not been specified");
|
|
|
|
uint8 soundNum = soundNumber & 0x7f;
|
|
if (soundNum > _soundsTotal)
|
|
error("Invalid sound index %d requested", soundNum);
|
|
|
|
if (_driver == NULL)
|
|
// Only play sounds if a sound driver is active
|
|
return;
|
|
|
|
// Most significant bit indicates if the track should loop or not
|
|
bool loop = (soundNumber & 0x80) != 0;
|
|
|
|
if (!game.soundFlag())
|
|
// Don't play sounds if sound is turned off
|
|
return;
|
|
|
|
uint32 dataOfs = READ_LE_UINT32(_soundData->data() + soundNum * 4 + 2);
|
|
uint8 *soundStart = _soundData->data() + dataOfs;
|
|
uint32 dataSize;
|
|
|
|
if (soundNum == _soundsTotal - 1)
|
|
dataSize = _soundData->size() - dataOfs;
|
|
else {
|
|
uint32 nextDataOfs = READ_LE_UINT32(_soundData->data() + (soundNum + 1) * 4 + 2);
|
|
dataSize = nextDataOfs - dataOfs;
|
|
}
|
|
|
|
// Note: the original interpreter seems to keep track of the "volume"
|
|
// (velocity) adjustment last used for each MIDI channel. The volume
|
|
// is not set in the few instances where musicInterface_Play is used
|
|
// directly to play a sound instead of addSound (mostly cutscenes).
|
|
// As a result, the volume adjustment is used that was last set on
|
|
// the MIDI channel by whatever sound played there previously.
|
|
// I think this is unintentional, so in ScummVM volume is set to 80h
|
|
// (neutral) by default when calling musicInterface_Play without
|
|
// specifying volume.
|
|
_soundMutex.lock();
|
|
int8 source = -1;
|
|
if (_isRoland) {
|
|
if (isMusic) {
|
|
source = 0;
|
|
} else {
|
|
for (int i = 1; i < LURE_MAX_SOURCES; ++i) {
|
|
if (!_sourcesInUse[i]) {
|
|
source = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (source == -1)
|
|
warning("Insufficient sources to play sound %i", soundNumber);
|
|
else
|
|
_sourcesInUse[source] = true;
|
|
}
|
|
MidiMusic *sound = new MidiMusic(_driver, _channelsInner, channelNumber, soundNum,
|
|
isMusic, loop, source, numChannels, soundStart, dataSize, volume);
|
|
_playingSounds.push_back(MusicList::value_type(sound));
|
|
_soundMutex.unlock();
|
|
}
|
|
|
|
// musicInterface_Stop
|
|
// Stops the specified sound from playing
|
|
|
|
void SoundManager::musicInterface_Stop(uint8 soundNumber) {
|
|
debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "musicInterface_Stop soundNumber=%d", soundNumber);
|
|
musicInterface_TidySounds();
|
|
uint8 soundNum = soundNumber & 0x7f;
|
|
|
|
_soundMutex.lock();
|
|
MusicListIterator i;
|
|
for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
|
|
if ((*i)->soundNumber() == soundNum) {
|
|
if ((*i)->source() >= 0)
|
|
_sourcesInUse[(*i)->source()] = false;
|
|
_playingSounds.erase(i);
|
|
break;
|
|
}
|
|
}
|
|
_soundMutex.unlock();
|
|
}
|
|
|
|
// musicInterface_CheckPlaying
|
|
// Returns true if a sound is still playing
|
|
|
|
bool SoundManager::musicInterface_CheckPlaying(uint8 soundNumber) {
|
|
debugC(ERROR_DETAILED, kLureDebugSounds, "musicInterface_CheckPlaying soundNumber=%d", soundNumber);
|
|
musicInterface_TidySounds();
|
|
uint8 soundNum = soundNumber & 0x7f;
|
|
bool result = false;
|
|
|
|
_soundMutex.lock();
|
|
MusicListIterator i;
|
|
for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
|
|
if ((*i)->soundNumber() == soundNum) {
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
_soundMutex.unlock();
|
|
|
|
return result;
|
|
}
|
|
|
|
// musicInterface_SetVolume
|
|
// Sets the volume of the specified channel
|
|
|
|
void SoundManager::musicInterface_SetVolume(uint8 channelNum, uint8 volume) {
|
|
debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "musicInterface_SetVolume channel=%d, volume=%d",
|
|
channelNum, volume);
|
|
musicInterface_TidySounds();
|
|
|
|
_soundMutex.lock();
|
|
MusicListIterator i;
|
|
for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
|
|
MidiMusic &music = **i;
|
|
if (music.channelNumber() == channelNum)
|
|
music.setVolume(volume);
|
|
}
|
|
_soundMutex.unlock();
|
|
}
|
|
|
|
// musicInterface_KillAll
|
|
// Stops all currently active sounds playing
|
|
|
|
void SoundManager::musicInterface_KillAll() {
|
|
debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "musicInterface_KillAll");
|
|
musicInterface_TidySounds();
|
|
|
|
_soundMutex.lock();
|
|
MusicListIterator i;
|
|
for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
|
|
(*i)->stopMusic();
|
|
}
|
|
|
|
Common::fill(_sourcesInUse, _sourcesInUse + LURE_MAX_SOURCES, false);
|
|
_playingSounds.clear();
|
|
_activeSounds.clear();
|
|
_soundMutex.unlock();
|
|
}
|
|
|
|
// musicInterface_ContinuePlaying
|
|
// The original player used this method for any sound managers needing continual calls
|
|
|
|
void SoundManager::musicInterface_ContinuePlaying() {
|
|
// No implementation needed
|
|
}
|
|
|
|
/*
|
|
* TL;DR: TrashReverb does not seem to work correctly in the original interpreter
|
|
* and I don't know what the developer's intentions were, so I've disabled it in
|
|
* ScummVM.
|
|
*
|
|
* In the original interpreter, TrashReverb sends a SysEx to the MT-32 which sets
|
|
* the reverb parameters to mode Room, time 1, level 0. This practically turns off
|
|
* reverb. It is triggered by opening a door in an outdoors location in the town.
|
|
* The SysEx is sent when the door sound starts, and again 40ms after the first
|
|
* one. TrashReverb also seems to be triggered when a door is opened offscreen (by
|
|
* an NPC).
|
|
* This means that, as soon as you enter town, reverb is turned off when the first
|
|
* door is opened. It is not turned back on. Reverb is restored only when you quit
|
|
* and restart the game (during MT-32 initialization); it is then turned off again
|
|
* when the first door outside in town is opened. Turning off the reverb
|
|
* repeatedly (twice whenever a door is opened) does not seem to accomplish
|
|
* anything.
|
|
* The best explanation for this behavior I can come up with is that the developer
|
|
* intended to disable reverb while playing a door opening sound outdoors. The
|
|
* second SysEx when opening a door was meant to turn reverb back on, but turns it
|
|
* off again because of a bug. Also, TrashReverb being triggered by doors opening
|
|
* offscreen would be a bug. There is a third problem: the door opening sound lasts
|
|
* much longer than 40 ms, so turning reverb back on 40 ms after starting the door
|
|
* opening sound still results in a noticable reverb. All in all this explanation
|
|
* is not entirely convicing.
|
|
* Another explanation would be that reverb was only meant to be on for the first
|
|
* part of the game and should be turned off from the town onwards (this is what
|
|
* the implementation in the original interpreter effectively does). However, why
|
|
* disable reverb when opening a door, and not f.e. when the player first enters
|
|
* a town screen? And why is it not immediately turned off when a player loads a
|
|
* savegame of a point later in the game? And why is reverb repeatedly disabled?
|
|
* This does not make much sense either.
|
|
* All in all, I am convinced that this functionality does not work correctly in
|
|
* the original interpreter, but I don't know what the developer's intentions
|
|
* were either. So I can't make a proper implementation for this, and I think it
|
|
* is best left disabled.
|
|
*/
|
|
|
|
// musicInterface_TrashReverb
|
|
// Trashes reverb on actively playing sounds
|
|
|
|
void SoundManager::musicInterface_TrashReverb() {
|
|
debugC(ERROR_INTERMEDIATE, kLureDebugSounds, "musicInterface_TrashReverb");
|
|
|
|
/*
|
|
// TODO Should this do anything on AdLib? It does not have reverb AFAIK
|
|
if (_isRoland) {
|
|
// Set reverb parameters to mode Room, time 1, level 0
|
|
static const byte sysExData[] = { 0x00, 0x00, 0x00 };
|
|
_mt32Driver->sysExMT32(sysExData, 3, 0x10 << 14 | 0x00 << 7 | 0x01);
|
|
}
|
|
*/
|
|
}
|
|
|
|
// musicInterface_KillAll
|
|
// Scans all the active sounds and deallocates any objects that have finished playing
|
|
|
|
void SoundManager::musicInterface_TidySounds() {
|
|
debugC(ERROR_DETAILED, kLureDebugSounds, "musicInterface_TidySounds");
|
|
|
|
_soundMutex.lock();
|
|
MusicListIterator i = _playingSounds.begin();
|
|
while (i != _playingSounds.end()) {
|
|
if (!(*i)->isPlaying()) {
|
|
if ((*i)->source() >= 0)
|
|
_sourcesInUse[(*i)->source()] = false;
|
|
i = _playingSounds.erase(i);
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
_soundMutex.unlock();
|
|
}
|
|
|
|
void SoundManager::onTimer(void *data) {
|
|
SoundManager *snd = (SoundManager *) data;
|
|
snd->doTimer();
|
|
}
|
|
|
|
void SoundManager::doTimer() {
|
|
if (_paused)
|
|
return;
|
|
|
|
_soundMutex.lock();
|
|
|
|
MusicListIterator i;
|
|
for (i = _playingSounds.begin(); i != _playingSounds.end(); ++i) {
|
|
MidiMusic &music = **i;
|
|
if (music.isPlaying())
|
|
music.onTimer();
|
|
}
|
|
|
|
_soundMutex.unlock();
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
MidiMusic::MidiMusic(MidiDriver *driver, ChannelEntry channels[NUM_CHANNELS],
|
|
uint8 channelNum, uint8 soundNum, bool isMus, bool loop, int8 source, uint8 numChannels, void *soundData, uint32 size, uint8 volume) {
|
|
_driver = driver;
|
|
assert(_driver);
|
|
_mt32Driver = dynamic_cast<MidiDriver_MT32GM *>(_driver);
|
|
assert(!Sound.isRoland() || _mt32Driver);
|
|
_source = source;
|
|
_channels = channels;
|
|
_soundNumber = soundNum;
|
|
_channelNumber = channelNum;
|
|
_isMusic = isMus;
|
|
_loop = loop;
|
|
|
|
_numChannels = numChannels;
|
|
_volume = 0;
|
|
|
|
// Set sound resource volume (default is 80h - neutral).
|
|
// TODO AdLib currently does not use sound resource volume, so use fixed 240.
|
|
setVolume(Sound.isRoland() ? volume : 240);
|
|
|
|
_parser = MidiParser::createParser_SMF(source);
|
|
_parser->setMidiDriver(this);
|
|
_parser->setTimerRate(_driver->getBaseTempo());
|
|
// All Notes Off on all channels does not work with multiple MIDI sources
|
|
_parser->property(MidiParser::mpDisableAllNotesOffMidiEvents, 1);
|
|
_parser->property(MidiParser::mpAutoLoop, _loop);
|
|
|
|
_soundData = (uint8 *)soundData;
|
|
_soundSize = size;
|
|
|
|
// Check whether the music data is compressed - if so, decompress it for the duration
|
|
// of playing the sound
|
|
|
|
_decompressedSound = NULL;
|
|
if ((*_soundData == 'C') || (*_soundData == 'c')) {
|
|
uint32 packedSize = size - 0x201;
|
|
_decompressedSound = Memory::allocate(packedSize * 2);
|
|
|
|
uint16 *data = (uint16 *)(_soundData + 1);
|
|
uint16 *dataDest = (uint16 *) _decompressedSound->data();
|
|
byte *idx = ((byte *)data) + 0x200;
|
|
|
|
for (uint i = 0; i < packedSize; i++)
|
|
#if defined(SCUMM_NEED_ALIGNMENT)
|
|
memcpy(dataDest++, (byte *)((byte *)data + *(idx + i) * sizeof(uint16)), sizeof(uint16));
|
|
#else
|
|
*dataDest++ = data[*(idx + i)];
|
|
#endif
|
|
|
|
_soundData = _decompressedSound->data() + ((*_soundData == 'c') ? 1 : 0);
|
|
_soundSize = _decompressedSound->size();
|
|
}
|
|
|
|
playMusic();
|
|
}
|
|
|
|
MidiMusic::~MidiMusic() {
|
|
_parser->unloadMusic();
|
|
if (Sound.isRoland() && _isPlaying)
|
|
_mt32Driver->deinitSource(_source);
|
|
delete _parser;
|
|
delete _decompressedSound;
|
|
}
|
|
|
|
void MidiMusic::setVolume(int volume) {
|
|
volume = CLIP(volume, 0, 255);
|
|
|
|
if (_volume == volume)
|
|
return;
|
|
|
|
_volume = volume;
|
|
|
|
// MT-32 MIDI data sets channel volume using control change,
|
|
// so this is only needed for AdLib.
|
|
if (!Sound.isRoland()) {
|
|
volume *= _isMusic ? Sound.musicVolume() : Sound.sfxVolume();
|
|
|
|
for (int i = 0; i < _numChannels; ++i) {
|
|
if (_channels[_channelNumber + i].midiChannel != NULL)
|
|
_channels[_channelNumber + i].midiChannel->volume(
|
|
_channels[_channelNumber + i].volume *
|
|
volume / 65025);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MidiMusic::playMusic() {
|
|
debugC(ERROR_DETAILED, kLureDebugSounds, "MidiMusic::PlayMusic playing sound %d", _soundNumber);
|
|
if (Sound.isRoland() && !_isMusic)
|
|
_mt32Driver->allocateSourceChannels(_source, _numChannels);
|
|
_parser->loadMusic(_soundData, _soundSize);
|
|
_parser->setTrack(0);
|
|
_isPlaying = true;
|
|
}
|
|
|
|
void MidiMusic::send(uint32 b) {
|
|
send(-1, b);
|
|
}
|
|
|
|
void MidiMusic::send(int8 source, uint32 b) {
|
|
byte channel;
|
|
if (Sound.isRoland()) {
|
|
// Channel mapping is handled by the driver
|
|
channel = b & 0x0F;
|
|
} else {
|
|
// Remap data channel to (one of) the channel(s) assigned to this player
|
|
#ifdef SOUND_CROP_CHANNELS
|
|
if ((b & 0xF) >= _numChannels) return;
|
|
channel = _channelNumber + (byte)(b & 0x0F);
|
|
#else
|
|
channel = _channelNumber + ((byte)(b & 0x0F) % _numChannels);
|
|
#endif
|
|
|
|
if ((channel >= NUM_CHANNELS) || (_channels[channel].midiChannel == NULL))
|
|
return;
|
|
}
|
|
|
|
if ((b & 0xFFF0) == 0x07B0) {
|
|
// Adjust volume changes by song and master volume
|
|
byte volume = (byte)((b >> 16) & 0x7F);
|
|
_channels[channel].volume = volume;
|
|
if (!Sound.isRoland()) {
|
|
// Scale volume for AdLib only.
|
|
// MT-32 sound resource volume is applied to note velocity,
|
|
// and user volume scaling is handled by the driver.
|
|
// TODO AdLib might use velocity for sound resource volume
|
|
// as well.
|
|
uint16 master_volume = _isMusic ? Sound.musicVolume() : Sound.sfxVolume();
|
|
volume = volume * _volume * master_volume / 65025;
|
|
}
|
|
b = (b & 0xFF00FFFF) | (volume << 16);
|
|
} else if ((b & 0xFFF0) == 0x18B0) {
|
|
if (Sound.isRoland())
|
|
// Some tracks use CC 18. This is undefined in the MIDI standard
|
|
// and does nothing on an MT-32. Not sending this to the device
|
|
// in case it is a GM device with non-standard behavior for this CC.
|
|
return;
|
|
} else if ((b & 0xFFF0) == 0x007BB0) {
|
|
// No implementation
|
|
} else if (((b & 0xF0) == 0x90)) {
|
|
// Note On
|
|
if (Sound.isRoland()) {
|
|
// Scale velocity with sound resource volume
|
|
byte velocity = (b >> 16) & 0x7F;
|
|
velocity = (velocity * _volume) >> 7;
|
|
if (velocity > 0x7F) velocity = 0x7F;
|
|
b = (b & 0xFF00FFFF) | (velocity << 16);
|
|
}
|
|
} else if (((b & 0xF0) == 0x80)) {
|
|
// Note Off
|
|
if (Sound.isRoland()) {
|
|
// Strip velocity
|
|
b &= 0xFF00FFFF;
|
|
}
|
|
} else if (((b & 0xF0) == 0xD0)) {
|
|
// Channel aftertouch
|
|
if (Sound.isRoland()) {
|
|
// Some tracks contain aftertouch events, but the MT-32 does
|
|
// not support this and the original interpreter does not send
|
|
// them to the device.
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (Sound.isRoland()) {
|
|
_driver->send(source, b);
|
|
} else {
|
|
_channels[channel].midiChannel->send(b);
|
|
}
|
|
}
|
|
|
|
void MidiMusic::metaEvent(byte type, byte *data, uint16 length) {
|
|
metaEvent(-1, type, data, length);
|
|
}
|
|
|
|
void MidiMusic::metaEvent(int8 source, byte type, byte *data, uint16 length) {
|
|
//Only thing we care about is End of Track.
|
|
if (type != 0x2F)
|
|
return;
|
|
|
|
_driver->metaEvent(source, type, data, length);
|
|
stopMusic();
|
|
}
|
|
|
|
void MidiMusic::onTimer() {
|
|
if (_isPlaying)
|
|
_parser->onTimer();
|
|
}
|
|
|
|
void MidiMusic::stopMusic() {
|
|
debugC(ERROR_DETAILED, kLureDebugSounds, "MidiMusic::stopMusic sound %d", _soundNumber);
|
|
_isPlaying = false;
|
|
_parser->unloadMusic();
|
|
if (Sound.isRoland())
|
|
_mt32Driver->deinitSource(_source);
|
|
}
|
|
|
|
void MidiMusic::pauseMusic() {
|
|
_parser->pausePlaying();
|
|
}
|
|
|
|
void MidiMusic::resumeMusic() {
|
|
_parser->resumePlaying();
|
|
}
|
|
|
|
} // End of namespace Lure
|