scummvm/audio/midiplayer.cpp
2021-12-26 18:48:43 +01:00

195 lines
4.8 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 "audio/midiplayer.h"
#include "audio/midiparser.h"
#include "common/config-manager.h"
namespace Audio {
MidiPlayer::MidiPlayer() :
_driver(nullptr),
_parser(nullptr),
_midiData(nullptr),
_isLooping(false),
_isPlaying(false),
_masterVolume(0),
_nativeMT32(false) {
memset(_channelsTable, 0, sizeof(_channelsTable));
memset(_channelsVolume, 127, sizeof(_channelsVolume));
// TODO
}
MidiPlayer::~MidiPlayer() {
// FIXME/TODO: In some engines, stop() was called first;
// in others, _driver->setTimerCallback(NULL, NULL) came first.
// Hopefully, this make no real difference, but we should
// watch out for regressions.
stop();
// Unhook & unload the driver
if (_driver) {
_driver->setTimerCallback(nullptr, nullptr);
_driver->close();
delete _driver;
_driver = nullptr;
}
}
void MidiPlayer::createDriver(int flags) {
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(flags);
_nativeMT32 = ((MidiDriver::getMusicType(dev) == MT_MT32) || ConfMan.getBool("native_mt32"));
_driver = MidiDriver::createMidi(dev);
assert(_driver);
if (_nativeMT32)
_driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
}
void MidiPlayer::setVolume(int volume) {
volume = CLIP(volume, 0, 255);
if (_masterVolume == volume)
return;
Common::StackLock lock(_mutex);
_masterVolume = volume;
for (int i = 0; i < kNumChannels; ++i) {
if (_channelsTable[i]) {
_channelsTable[i]->volume(_channelsVolume[i] * _masterVolume / 255);
}
}
}
void MidiPlayer::syncVolume() {
int volume = ConfMan.getInt("music_volume");
if (ConfMan.getBool("mute")) {
volume = -1;
}
setVolume(volume);
}
void MidiPlayer::send(uint32 b) {
byte ch = (byte)(b & 0x0F);
if ((b & 0xFFF0) == 0x07B0) {
// Adjust volume changes by master volume
byte volume = (byte)((b >> 16) & 0x7F);
_channelsVolume[ch] = volume;
volume = volume * _masterVolume / 255;
b = (b & 0xFF00FFFF) | (volume << 16);
} else if ((b & 0xFFF0) == 0x007BB0) {
// Only respond to All Notes Off if this channel
// has currently been allocated
if (!_channelsTable[ch])
return;
}
sendToChannel(ch, b);
}
void MidiPlayer::sendToChannel(byte ch, uint32 b) {
if (!_channelsTable[ch]) {
_channelsTable[ch] = (ch == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel();
// TODO: Some engines overload this method to insert code at this
// point which calls the channel's volume() method.
// Does this make sense, and should we maybe do it in general?
}
if (_channelsTable[ch]) {
_channelsTable[ch]->send(b);
}
}
void MidiPlayer::metaEvent(byte type, byte *data, uint16 length) {
switch (type) {
case 0x2F: // End of Track
endOfTrack();
break;
default:
//warning("Unhandled meta event: %02x", type);
break;
}
}
void MidiPlayer::endOfTrack() {
if (_isLooping) {
assert(_parser);
_parser->jumpToTick(0);
} else
stop();
}
void MidiPlayer::timerCallback(void *data) {
assert(data);
((MidiPlayer *)data)->onTimer();
}
void MidiPlayer::onTimer() {
Common::StackLock lock(_mutex);
// TODO: Maybe we can replace _isPlaying
// by a simple check for "_parser != 0" ?
if (_isPlaying && _parser) {
_parser->onTimer();
}
}
void MidiPlayer::stop() {
Common::StackLock lock(_mutex);
_isPlaying = false;
if (_parser) {
_parser->unloadMusic();
// FIXME/TODO: The MidiParser destructor calls allNotesOff()
// but unloadMusic also does. To suppress double notes-off,
// we reset the midi driver of _parser before deleting it.
// This smells very fishy, in any case.
_parser->setMidiDriver(nullptr);
delete _parser;
_parser = nullptr;
}
free(_midiData);
_midiData = nullptr;
}
void MidiPlayer::pause() {
// debugC(2, kDraciSoundDebugLevel, "Pausing track %d", _track);
_isPlaying = false;
setVolume(-1); // FIXME: This should be 0, shouldn't it?
}
void MidiPlayer::resume() {
// debugC(2, kDraciSoundDebugLevel, "Resuming track %d", _track);
syncVolume();
_isPlaying = true;
}
} // End of namespace Audio