Initial commit
This commit is contained in:
556
engines/sci/sound/audio.cpp
Normal file
556
engines/sci/sound/audio.cpp
Normal file
@@ -0,0 +1,556 @@
|
||||
/* 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 "sci/resource/resource.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
#include "sci/engine/seg_manager.h"
|
||||
#include "sci/sound/audio.h"
|
||||
|
||||
#include "backends/audiocd/audiocd.h"
|
||||
|
||||
#include "common/config-manager.h"
|
||||
#include "common/file.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/system.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/decoders/aiff.h"
|
||||
#include "audio/decoders/flac.h"
|
||||
#include "audio/decoders/mac_snd.h"
|
||||
#include "audio/decoders/mp3.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
#include "audio/decoders/vorbis.h"
|
||||
#include "audio/decoders/wave.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
AudioPlayer::AudioPlayer(ResourceManager *resMan) : _resMan(resMan), _audioRate(11025),
|
||||
_audioCdStart(0), _initCD(false), _playCounter(0) {
|
||||
|
||||
_mixer = g_system->getMixer();
|
||||
_wPlayFlag = false;
|
||||
}
|
||||
|
||||
AudioPlayer::~AudioPlayer() {
|
||||
stopAllAudio();
|
||||
}
|
||||
|
||||
void AudioPlayer::stopAllAudio() {
|
||||
stopAudio();
|
||||
if (_audioCdStart > 0)
|
||||
audioCdStop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the sciAudio calls in fanmade games.
|
||||
* sciAudio is an external .NET library for playing MP3 files in fanmade games.
|
||||
* It runs in the background, and obtains sound commands from the
|
||||
* currently running game via text files (called "conductor files").
|
||||
* For further info, check: https://sciprogramming.com/community/index.php?topic=634.0
|
||||
*/
|
||||
void AudioPlayer::handleFanmadeSciAudio(reg_t sciAudioObject, SegManager *segMan) {
|
||||
// TODO: This is a bare bones implementation. Only the play/playx and stop commands
|
||||
// are handled for now - the other commands haven't been observed in any fanmade game
|
||||
// yet. All the volume related and fading functionality is currently missing.
|
||||
Kernel *kernel = g_sci->getKernel();
|
||||
|
||||
// get the sciAudio command from the "command" selector.
|
||||
// this property is a string in 1.0 and an integer in 1.1.
|
||||
enum FanmadeSciAudioCommand {
|
||||
kFanmadeSciAudioCommandNone = -1,
|
||||
kFanmadeSciAudioCommandPlayX,
|
||||
kFanmadeSciAudioCommandPlay,
|
||||
kFanmadeSciAudioCommandStop
|
||||
};
|
||||
FanmadeSciAudioCommand sciAudioCommand = kFanmadeSciAudioCommandNone;
|
||||
reg_t commandReg = readSelector(segMan, sciAudioObject, kernel->findSelector("command"));
|
||||
Common::String commandString;
|
||||
if (commandReg.isNumber()) {
|
||||
// sciAudio 1.1
|
||||
sciAudioCommand = (FanmadeSciAudioCommand)commandReg.toUint16();
|
||||
} else {
|
||||
// sciAudio 1.0
|
||||
commandString = segMan->getString(commandReg);
|
||||
if (commandString == "playx") {
|
||||
sciAudioCommand = kFanmadeSciAudioCommandPlayX;
|
||||
} else if (commandString == "play") {
|
||||
sciAudioCommand = kFanmadeSciAudioCommandPlay;
|
||||
} else if (commandString == "stop") {
|
||||
sciAudioCommand = kFanmadeSciAudioCommandStop;
|
||||
}
|
||||
}
|
||||
|
||||
if (sciAudioCommand == kFanmadeSciAudioCommandPlayX ||
|
||||
sciAudioCommand == kFanmadeSciAudioCommandPlay) {
|
||||
reg_t fileNameReg = readSelector(segMan, sciAudioObject, kernel->findSelector("fileName"));
|
||||
Common::String fileName = segMan->getString(fileNameReg);
|
||||
|
||||
reg_t loopCountReg = readSelector(segMan, sciAudioObject, kernel->findSelector("loopCount"));
|
||||
int16 loopCount;
|
||||
if (loopCountReg.isNumber()) {
|
||||
// sciAudio 1.1
|
||||
loopCount = loopCountReg.toSint16();
|
||||
} else {
|
||||
// sciAudio 1.0
|
||||
Common::String loopCountStr = segMan->getString(loopCountReg);
|
||||
loopCount = atoi(loopCountStr.c_str());
|
||||
}
|
||||
|
||||
// Adjust loopCount for ScummVM's LoopingAudioStream semantics
|
||||
if (loopCount == -1) {
|
||||
loopCount = 0; // loop endlessly
|
||||
} else if (loopCount >= 0) {
|
||||
// sciAudio loopCount == 0 -> play 1 time -> ScummVM's loopCount should be 1
|
||||
// sciAudio loopCount == 1 -> play 2 times -> ScummVM's loopCount should be 2
|
||||
loopCount++;
|
||||
} else {
|
||||
loopCount = 1; // play once in case the value makes no sense
|
||||
}
|
||||
|
||||
// Determine sound type
|
||||
Audio::Mixer::SoundType soundType = Audio::Mixer::kSFXSoundType;
|
||||
if (fileName.hasPrefix("music"))
|
||||
soundType = Audio::Mixer::kMusicSoundType;
|
||||
else if (fileName.hasPrefix("speech"))
|
||||
soundType = Audio::Mixer::kSpeechSoundType;
|
||||
|
||||
// Determine compression
|
||||
uint32 audioCompressionType = 0;
|
||||
if ((fileName.hasSuffix(".mp3")) || (fileName.hasSuffix(".sciAudio")) || (fileName.hasSuffix(".sciaudio"))) {
|
||||
audioCompressionType = MKTAG('M','P','3',' ');
|
||||
} else if (fileName.hasSuffix(".wav")) {
|
||||
audioCompressionType = MKTAG('W','A','V',' ');
|
||||
} else if (fileName.hasSuffix(".aiff")) {
|
||||
audioCompressionType = MKTAG('A','I','F','F');
|
||||
} else {
|
||||
error("sciAudio: unsupported file type");
|
||||
}
|
||||
|
||||
Common::File *sciAudioFile = new Common::File();
|
||||
// Replace backwards slashes
|
||||
for (uint i = 0; i < fileName.size(); i++) {
|
||||
if (fileName[i] == '\\')
|
||||
fileName.setChar('/', i);
|
||||
}
|
||||
sciAudioFile->open(Common::Path("sciAudio/" + fileName, '/'));
|
||||
|
||||
Audio::RewindableAudioStream *audioStream = nullptr;
|
||||
|
||||
switch (audioCompressionType) {
|
||||
case MKTAG('M','P','3',' '):
|
||||
#ifdef USE_MAD
|
||||
audioStream = Audio::makeMP3Stream(sciAudioFile, DisposeAfterUse::YES);
|
||||
#endif
|
||||
break;
|
||||
case MKTAG('W','A','V',' '):
|
||||
audioStream = Audio::makeWAVStream(sciAudioFile, DisposeAfterUse::YES);
|
||||
break;
|
||||
case MKTAG('A','I','F','F'):
|
||||
audioStream = Audio::makeAIFFStream(sciAudioFile, DisposeAfterUse::YES);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!audioStream) {
|
||||
error("sciAudio: requested compression not compiled into ScummVM");
|
||||
}
|
||||
|
||||
// We only support one audio handle
|
||||
_mixer->playStream(soundType, &_audioHandle,
|
||||
Audio::makeLoopingAudioStream((Audio::RewindableAudioStream *)audioStream, loopCount));
|
||||
} else if (sciAudioCommand == kFanmadeSciAudioCommandStop) {
|
||||
_mixer->stopHandle(_audioHandle);
|
||||
} else {
|
||||
if (commandReg.isNumber()) {
|
||||
warning("Unhandled sciAudio command: %u", commandReg.getOffset());
|
||||
} else {
|
||||
warning("Unhandled sciAudio command: %s", commandString.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int AudioPlayer::startAudio(uint16 module, uint32 number) {
|
||||
int sampleLen;
|
||||
Audio::AudioStream *audioStream = getAudioStream(number, module, &sampleLen);
|
||||
|
||||
if (audioStream) {
|
||||
_wPlayFlag = false;
|
||||
Audio::Mixer::SoundType soundType = (module == 65535) ? Audio::Mixer::kSFXSoundType : Audio::Mixer::kSpeechSoundType;
|
||||
_mixer->playStream(soundType, &_audioHandle, audioStream);
|
||||
return sampleLen;
|
||||
} else {
|
||||
// Don't throw a warning in this case. getAudioStream() already has. Some games
|
||||
// do miss audio entries (perhaps because of a typo, or because they were simply
|
||||
// forgotten).
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int AudioPlayer::wPlayAudio(uint16 module, uint32 tuple) {
|
||||
// Get the audio sample length and set the wPlay flag so we return 0 on
|
||||
// position. SSCI pre-loads the audio here, but it's much easier for us to
|
||||
// just get the sample length and return that. wPlayAudio should *not*
|
||||
// actually start the sample.
|
||||
|
||||
int sampleLen = 0;
|
||||
Audio::AudioStream *audioStream = getAudioStream(tuple, module, &sampleLen);
|
||||
if (!audioStream)
|
||||
warning("wPlayAudio: unable to create stream for audio tuple %d, module %d", tuple, module);
|
||||
delete audioStream;
|
||||
_wPlayFlag = true;
|
||||
return sampleLen;
|
||||
}
|
||||
|
||||
void AudioPlayer::stopAudio() {
|
||||
_mixer->stopHandle(_audioHandle);
|
||||
}
|
||||
|
||||
void AudioPlayer::pauseAudio() {
|
||||
_mixer->pauseHandle(_audioHandle, true);
|
||||
}
|
||||
|
||||
void AudioPlayer::resumeAudio() {
|
||||
_mixer->pauseHandle(_audioHandle, false);
|
||||
}
|
||||
|
||||
int AudioPlayer::getAudioPosition() {
|
||||
if (_mixer->isSoundHandleActive(_audioHandle))
|
||||
return _mixer->getSoundElapsedTime(_audioHandle) * 6 / 100; // return elapsed time in ticks
|
||||
else if (_wPlayFlag)
|
||||
return 0; // Sound has "loaded" so return that it hasn't started
|
||||
else
|
||||
return -1; // Sound finished
|
||||
}
|
||||
|
||||
enum SolFlags {
|
||||
kSolFlagCompressed = 1 << 0,
|
||||
kSolFlagUnknown = 1 << 1,
|
||||
kSolFlag16Bit = 1 << 2,
|
||||
kSolFlagIsSigned = 1 << 3
|
||||
};
|
||||
|
||||
// FIXME: Move this to sound/adpcm.cpp?
|
||||
// Note that the 16-bit version is also used in coktelvideo.cpp
|
||||
static const uint16 tableDPCM16[128] = {
|
||||
0x0000, 0x0008, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0060, 0x0070, 0x0080,
|
||||
0x0090, 0x00A0, 0x00B0, 0x00C0, 0x00D0, 0x00E0, 0x00F0, 0x0100, 0x0110, 0x0120,
|
||||
0x0130, 0x0140, 0x0150, 0x0160, 0x0170, 0x0180, 0x0190, 0x01A0, 0x01B0, 0x01C0,
|
||||
0x01D0, 0x01E0, 0x01F0, 0x0200, 0x0208, 0x0210, 0x0218, 0x0220, 0x0228, 0x0230,
|
||||
0x0238, 0x0240, 0x0248, 0x0250, 0x0258, 0x0260, 0x0268, 0x0270, 0x0278, 0x0280,
|
||||
0x0288, 0x0290, 0x0298, 0x02A0, 0x02A8, 0x02B0, 0x02B8, 0x02C0, 0x02C8, 0x02D0,
|
||||
0x02D8, 0x02E0, 0x02E8, 0x02F0, 0x02F8, 0x0300, 0x0308, 0x0310, 0x0318, 0x0320,
|
||||
0x0328, 0x0330, 0x0338, 0x0340, 0x0348, 0x0350, 0x0358, 0x0360, 0x0368, 0x0370,
|
||||
0x0378, 0x0380, 0x0388, 0x0390, 0x0398, 0x03A0, 0x03A8, 0x03B0, 0x03B8, 0x03C0,
|
||||
0x03C8, 0x03D0, 0x03D8, 0x03E0, 0x03E8, 0x03F0, 0x03F8, 0x0400, 0x0440, 0x0480,
|
||||
0x04C0, 0x0500, 0x0540, 0x0580, 0x05C0, 0x0600, 0x0640, 0x0680, 0x06C0, 0x0700,
|
||||
0x0740, 0x0780, 0x07C0, 0x0800, 0x0900, 0x0A00, 0x0B00, 0x0C00, 0x0D00, 0x0E00,
|
||||
0x0F00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000
|
||||
};
|
||||
|
||||
static const byte tableDPCM8[8] = {0, 1, 2, 3, 6, 10, 15, 21};
|
||||
|
||||
static void deDPCM16(byte *soundBuf, Common::SeekableReadStream &audioStream, uint32 n) {
|
||||
int16 *out = (int16 *) soundBuf;
|
||||
|
||||
int32 s = 0;
|
||||
for (uint32 i = 0; i < n; i++) {
|
||||
byte b = audioStream.readByte();
|
||||
if (b & 0x80)
|
||||
s -= tableDPCM16[b & 0x7f];
|
||||
else
|
||||
s += tableDPCM16[b];
|
||||
|
||||
s = CLIP<int32>(s, -32768, 32767);
|
||||
*out++ = TO_LE_16(s);
|
||||
}
|
||||
}
|
||||
|
||||
static void deDPCM8Nibble(byte *soundBuf, int32 &s, byte b) {
|
||||
if (b & 8) {
|
||||
s -= tableDPCM8[7 - (b & 7)];
|
||||
} else
|
||||
s += tableDPCM8[b & 7];
|
||||
s = CLIP<int32>(s, 0, 255);
|
||||
*soundBuf = s;
|
||||
}
|
||||
|
||||
static void deDPCM8(byte *soundBuf, Common::SeekableReadStream &audioStream, uint32 n) {
|
||||
int32 s = 0x80;
|
||||
|
||||
for (uint i = 0; i < n; i++) {
|
||||
byte b = audioStream.readByte();
|
||||
|
||||
deDPCM8Nibble(soundBuf++, s, b >> 4);
|
||||
deDPCM8Nibble(soundBuf++, s, b & 0xf);
|
||||
}
|
||||
}
|
||||
|
||||
// Sierra SOL audio file reader
|
||||
// Check here for more info: https://wiki.multimedia.cx/index.php?title=Sierra_Audio
|
||||
static bool readSOLHeader(Common::SeekableReadStream *audioStream, int headerSize, uint32 &size, uint16 &audioRate, byte &audioFlags, uint32 resSize) {
|
||||
if (headerSize != 7 && headerSize != 11 && headerSize != 12) {
|
||||
warning("SOL audio header of size %i not supported", headerSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 tag = audioStream->readUint32BE();
|
||||
|
||||
if (tag != MKTAG('S','O','L',0)) {
|
||||
warning("No 'SOL' FourCC found");
|
||||
return false;
|
||||
}
|
||||
|
||||
audioRate = audioStream->readUint16LE();
|
||||
audioFlags = audioStream->readByte();
|
||||
|
||||
// For the QFG3 demo format, just use the resource size
|
||||
// Otherwise, load it from the header
|
||||
if (headerSize == 7)
|
||||
size = resSize;
|
||||
else
|
||||
size = audioStream->readUint32LE();
|
||||
return true;
|
||||
}
|
||||
|
||||
static byte *readSOLAudio(Common::SeekableReadStream *audioStream, uint32 &size, byte audioFlags, byte &flags) {
|
||||
byte *buffer;
|
||||
|
||||
// Convert the SOL stream flags to our own format
|
||||
flags = 0;
|
||||
if (audioFlags & kSolFlag16Bit)
|
||||
flags |= Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
|
||||
|
||||
if (!(audioFlags & kSolFlagIsSigned))
|
||||
flags |= Audio::FLAG_UNSIGNED;
|
||||
|
||||
if (audioFlags & kSolFlagCompressed) {
|
||||
buffer = (byte *)malloc(size * 2);
|
||||
assert(buffer);
|
||||
|
||||
if (audioFlags & kSolFlag16Bit)
|
||||
deDPCM16(buffer, *audioStream, size);
|
||||
else
|
||||
deDPCM8(buffer, *audioStream, size);
|
||||
|
||||
size *= 2;
|
||||
} else {
|
||||
// We assume that the sound data is raw PCM
|
||||
buffer = (byte *)malloc(size);
|
||||
assert(buffer);
|
||||
audioStream->read(buffer, size);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
Audio::RewindableAudioStream *AudioPlayer::getAudioStream(uint32 number, uint32 volume, int *sampleLen) {
|
||||
Audio::SeekableAudioStream *audioSeekStream = nullptr;
|
||||
Audio::RewindableAudioStream *audioStream = nullptr;
|
||||
uint32 size = 0;
|
||||
byte *data = nullptr;
|
||||
byte flags = 0;
|
||||
Sci::Resource *audioRes;
|
||||
|
||||
*sampleLen = 0;
|
||||
|
||||
if (volume == 65535) {
|
||||
audioRes = _resMan->findResource(ResourceId(kResourceTypeAudio, number), false);
|
||||
if (!audioRes) {
|
||||
warning("Failed to find audio entry %i", number);
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
audioRes = _resMan->findResource(ResourceId(kResourceTypeAudio36, volume, number), false);
|
||||
if (!audioRes) {
|
||||
warning("Failed to find audio entry (%i, %i, %i, %i, %i)", volume, (number >> 24) & 0xff,
|
||||
(number >> 16) & 0xff, (number >> 8) & 0xff, number & 0xff);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// We copy over the audio data in our own buffer. We have to do
|
||||
// this, because ResourceManager may free the original data late,
|
||||
// and audio decompression may work on-the-fly instead.
|
||||
byte *audioBuffer = (byte *)malloc(audioRes->size());
|
||||
assert(audioBuffer);
|
||||
audioRes->unsafeCopyDataTo(audioBuffer);
|
||||
Common::SeekableReadStream *memoryStream = new Common::MemoryReadStream(audioBuffer, audioRes->size(), DisposeAfterUse::YES);
|
||||
|
||||
byte audioFlags;
|
||||
uint32 audioCompressionType = audioRes->getAudioCompressionType();
|
||||
|
||||
if (audioCompressionType) {
|
||||
// Compressed audio made by our tool
|
||||
switch (audioCompressionType) {
|
||||
#ifdef USE_MAD
|
||||
case MKTAG('M','P','3',' '):
|
||||
audioSeekStream = Audio::makeMP3Stream(memoryStream, DisposeAfterUse::YES);
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_VORBIS
|
||||
case MKTAG('O','G','G',' '):
|
||||
audioSeekStream = Audio::makeVorbisStream(memoryStream, DisposeAfterUse::YES);
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_FLAC
|
||||
case MKTAG('F','L','A','C'):
|
||||
audioSeekStream = Audio::makeFLACStream(memoryStream, DisposeAfterUse::YES);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
error("Compressed audio file encountered, but no decoder compiled in for: '%s'", tag2str(audioCompressionType));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Original source file
|
||||
if (audioRes->size() > 6 &&
|
||||
(audioRes->getUint8At(0) & 0x7f) == kResourceTypeAudio &&
|
||||
audioRes->getUint32BEAt(2) == MKTAG('S','O','L',0)) {
|
||||
// SCI1.1
|
||||
delete memoryStream;
|
||||
const uint8 headerSize = audioRes->getUint8At(1);
|
||||
Common::MemoryReadStream headerStream = audioRes->subspan(kResourceHeaderSize, headerSize).toStream();
|
||||
|
||||
if (readSOLHeader(&headerStream, headerSize, size, _audioRate, audioFlags, audioRes->size())) {
|
||||
Common::MemoryReadStream dataStream(audioRes->subspan(kResourceHeaderSize + headerSize).toStream());
|
||||
data = readSOLAudio(&dataStream, size, audioFlags, flags);
|
||||
audioSeekStream = Audio::makeRawStream(data, size, _audioRate, flags);
|
||||
}
|
||||
} else if (audioRes->size() > 4 && audioRes->getUint32BEAt(0) == MKTAG('R','I','F','F')) {
|
||||
// WAVE detected
|
||||
|
||||
// Calculate samplelen from WAVE header
|
||||
int waveSize = 0, waveRate = 0;
|
||||
byte waveFlags = 0;
|
||||
bool ret = Audio::loadWAVFromStream(*memoryStream, waveSize, waveRate, waveFlags);
|
||||
if (!ret)
|
||||
error("Failed to load WAV from stream");
|
||||
|
||||
*sampleLen = ((waveFlags & Audio::FLAG_16BITS) ? (waveSize >> 1) : waveSize) * 60 / waveRate;
|
||||
|
||||
memoryStream->seek(0, SEEK_SET);
|
||||
audioStream = Audio::makeWAVStream(memoryStream, DisposeAfterUse::YES);
|
||||
} else if (audioRes->size() > 14 &&
|
||||
audioRes->getUint16BEAt(0) == 1 &&
|
||||
audioRes->getUint16BEAt(2) == 1 &&
|
||||
audioRes->getUint16BEAt(4) == 5 &&
|
||||
audioRes->getUint32BEAt(10) == 0x00018051) {
|
||||
|
||||
// Mac snd detected
|
||||
audioSeekStream = Audio::makeMacSndStream(memoryStream, DisposeAfterUse::YES);
|
||||
if (!audioSeekStream)
|
||||
error("Failed to load Mac sound stream");
|
||||
|
||||
} else {
|
||||
// SCI1 raw audio
|
||||
flags = Audio::FLAG_UNSIGNED;
|
||||
_audioRate = 11025;
|
||||
audioSeekStream = Audio::makeRawStream(memoryStream, _audioRate, flags);
|
||||
}
|
||||
}
|
||||
|
||||
if (audioSeekStream) {
|
||||
*sampleLen = (audioSeekStream->getLength().msecs() * 60) / 1000; // we translate msecs to ticks
|
||||
audioStream = audioSeekStream;
|
||||
}
|
||||
|
||||
// We have to make sure that we don't depend on resource manager pointers
|
||||
// after this point, because the actual audio resource may get unloaded by
|
||||
// resource manager at any time.
|
||||
return audioStream;
|
||||
}
|
||||
|
||||
int AudioPlayer::audioCdPlay(int track, int start, int duration) {
|
||||
if (!_initCD) {
|
||||
// Initialize CD mode if we haven't already
|
||||
g_system->getAudioCDManager()->open();
|
||||
_initCD = true;
|
||||
}
|
||||
|
||||
if (getSciVersion() == SCI_VERSION_1_1) {
|
||||
// King's Quest VI CD Audio format
|
||||
_audioCdStart = g_system->getMillis();
|
||||
|
||||
// Subtract one from track. KQ6 starts at track 1, while ScummVM
|
||||
// ignores the data track and considers track 2 to be track 1.
|
||||
return g_system->getAudioCDManager()->play(track - 1, 1, start, duration) ? 1 : 0;
|
||||
} else {
|
||||
// Jones in the Fast Lane and Mothergoose256 CD Audio format
|
||||
uint32 length = 0;
|
||||
|
||||
audioCdStop();
|
||||
|
||||
Common::File audioMap;
|
||||
if(!audioMap.open("cdaudio.map"))
|
||||
error("Could not open cdaudio.map");
|
||||
|
||||
while (audioMap.pos() < audioMap.size()) {
|
||||
uint16 res = audioMap.readUint16LE();
|
||||
res &= 0x1fff; // Upper bits are always set in Mothergoose256
|
||||
uint32 startFrame = audioMap.readUint16LE();
|
||||
startFrame += audioMap.readByte() << 16;
|
||||
audioMap.readByte(); // Unknown, always 0x20 in Jones, 0x04 in Mothergoose256
|
||||
length = audioMap.readUint16LE();
|
||||
length += audioMap.readByte() << 16;
|
||||
audioMap.readByte(); // Unknown, always 0x00
|
||||
|
||||
// The track is the resource value in the map
|
||||
if (res == track) {
|
||||
g_system->getAudioCDManager()->play(1, 1, startFrame, length);
|
||||
_audioCdStart = g_system->getMillis();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
audioMap.close();
|
||||
|
||||
return length * 60 / 75; // return sample length in ticks
|
||||
}
|
||||
}
|
||||
|
||||
void AudioPlayer::audioCdStop() {
|
||||
_audioCdStart = 0;
|
||||
g_system->getAudioCDManager()->stop();
|
||||
}
|
||||
|
||||
void AudioPlayer::audioCdUpdate() {
|
||||
g_system->getAudioCDManager()->update();
|
||||
}
|
||||
|
||||
int AudioPlayer::audioCdPosition() {
|
||||
// Return -1 if the sample is done playing. Converting to frames to compare.
|
||||
if (((g_system->getMillis() - _audioCdStart) * 75 / 1000) >= (uint32)g_system->getAudioCDManager()->getStatus().duration)
|
||||
return -1;
|
||||
|
||||
// Return the position otherwise (in ticks).
|
||||
return (g_system->getMillis() - _audioCdStart) * 60 / 1000;
|
||||
}
|
||||
|
||||
void AudioPlayer::incrementPlayCounter() {
|
||||
_playCounter++;
|
||||
}
|
||||
|
||||
uint16 AudioPlayer::getPlayCounter() {
|
||||
return _playCounter;
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
95
engines/sci/sound/audio.h
Normal file
95
engines/sci/sound/audio.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/* Sound engine */
|
||||
#ifndef SCI_SOUND_AUDIO_H
|
||||
#define SCI_SOUND_AUDIO_H
|
||||
|
||||
#include "sci/engine/vm_types.h"
|
||||
#include "audio/mixer.h"
|
||||
|
||||
namespace Audio {
|
||||
class RewindableAudioStream;
|
||||
} // End of namespace Audio
|
||||
|
||||
namespace Sci {
|
||||
|
||||
enum AudioCommands {
|
||||
kSciAudioWPlay = 1, /* Loads an audio stream */
|
||||
kSciAudioPlay = 2, /* Plays an audio stream */
|
||||
kSciAudioStop = 3, /* Stops an audio stream */
|
||||
kSciAudioPause = 4, /* Pauses an audio stream */
|
||||
kSciAudioResume = 5, /* Resumes an audio stream */
|
||||
kSciAudioPosition = 6, /* Return current position in audio stream */
|
||||
kSciAudioRate = 7, /* Return audio rate */
|
||||
kSciAudioVolume = 8, /* Return audio volume */
|
||||
kSciAudioLanguage = 9, /* Return audio language */
|
||||
kSciAudioCD = 10 /* Plays SCI1.1 CD audio */
|
||||
};
|
||||
|
||||
#define AUDIO_VOLUME_MAX 127
|
||||
|
||||
class Resource;
|
||||
class ResourceId;
|
||||
class ResourceManager;
|
||||
class SegManager;
|
||||
|
||||
class AudioPlayer {
|
||||
public:
|
||||
AudioPlayer(ResourceManager *resMan);
|
||||
~AudioPlayer();
|
||||
|
||||
void setAudioRate(uint16 rate) { _audioRate = rate; }
|
||||
Audio::SoundHandle *getAudioHandle() { return &_audioHandle; }
|
||||
Audio::RewindableAudioStream *getAudioStream(uint32 number, uint32 volume, int *sampleLen);
|
||||
int getAudioPosition();
|
||||
int startAudio(uint16 module, uint32 tuple);
|
||||
int wPlayAudio(uint16 module, uint32 tuple);
|
||||
void stopAudio();
|
||||
void pauseAudio();
|
||||
void resumeAudio();
|
||||
|
||||
void handleFanmadeSciAudio(reg_t sciAudioObject, SegManager *segMan);
|
||||
|
||||
int audioCdPlay(int track, int start, int duration);
|
||||
void audioCdStop();
|
||||
void audioCdUpdate();
|
||||
int audioCdPosition();
|
||||
|
||||
void stopAllAudio();
|
||||
|
||||
void incrementPlayCounter();
|
||||
uint16 getPlayCounter();
|
||||
|
||||
private:
|
||||
ResourceManager *_resMan;
|
||||
uint16 _audioRate;
|
||||
Audio::SoundHandle _audioHandle;
|
||||
Audio::Mixer *_mixer;
|
||||
uint32 _audioCdStart;
|
||||
bool _wPlayFlag;
|
||||
bool _initCD;
|
||||
uint16 _playCounter;
|
||||
};
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_SOUND_AUDIO_H
|
||||
1459
engines/sci/sound/audio32.cpp
Normal file
1459
engines/sci/sound/audio32.cpp
Normal file
File diff suppressed because it is too large
Load Diff
660
engines/sci/sound/audio32.h
Normal file
660
engines/sci/sound/audio32.h
Normal file
@@ -0,0 +1,660 @@
|
||||
/* 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 SCI_SOUND_AUDIO32_H
|
||||
#define SCI_SOUND_AUDIO32_H
|
||||
|
||||
#include "audio/audiostream.h" // for AudioStream, SeekableAudioStream (...
|
||||
#include "audio/mixer.h" // for Mixer, SoundHandle
|
||||
#include "audio/rate.h" // for Audio::st_volume_t, RateConverter
|
||||
#include "common/array.h" // for Array
|
||||
#include "common/mutex.h" // for StackLock, Mutex
|
||||
#include "common/scummsys.h" // for int16, uint8, uint32, uint16
|
||||
#include "sci/resource/resource.h" // for ResourceId
|
||||
#include "sci/engine/state.h" // for EngineState
|
||||
#include "sci/engine/vm_types.h" // for reg_t, NULL_REG
|
||||
#include "sci/video/robot_decoder.h" // for RobotAudioStream
|
||||
|
||||
namespace Sci {
|
||||
class Console;
|
||||
|
||||
bool detectSolAudio(Common::SeekableReadStream &stream);
|
||||
bool detectWaveAudio(Common::SeekableReadStream &stream);
|
||||
|
||||
#pragma mark AudioChannel
|
||||
|
||||
/**
|
||||
* An audio channel used by the software SCI mixer.
|
||||
*/
|
||||
struct AudioChannel {
|
||||
/**
|
||||
* The ID of the resource loaded into this channel.
|
||||
*/
|
||||
ResourceId id;
|
||||
|
||||
/**
|
||||
* The resource loaded into this channel. The resource is owned by
|
||||
* ResourceManager.
|
||||
*/
|
||||
Resource *resource;
|
||||
|
||||
/**
|
||||
* The audio stream loaded into this channel. Can cast to
|
||||
* `SeekableAudioStream` for normal channels and `RobotAudioStream` for
|
||||
* robot channels.
|
||||
*/
|
||||
Common::ScopedPtr<Audio::AudioStream> stream;
|
||||
|
||||
/**
|
||||
* The converter used to transform and merge the input stream into the
|
||||
* mixer's output buffer.
|
||||
*/
|
||||
Common::ScopedPtr<Audio::RateConverter> converter;
|
||||
|
||||
/**
|
||||
* Duration of the channel, in ticks.
|
||||
*/
|
||||
uint32 duration;
|
||||
|
||||
/**
|
||||
* The tick when the channel was started.
|
||||
*/
|
||||
uint32 startedAtTick;
|
||||
|
||||
/**
|
||||
* The tick when the channel was paused.
|
||||
*/
|
||||
uint32 pausedAtTick;
|
||||
|
||||
/**
|
||||
* The time, in ticks, that the channel fade began. If 0, the channel is not
|
||||
* being faded.
|
||||
*/
|
||||
uint32 fadeStartTick;
|
||||
|
||||
/**
|
||||
* The start volume of a fade.
|
||||
*/
|
||||
int fadeStartVolume;
|
||||
|
||||
/**
|
||||
* The total length of the fade, in ticks.
|
||||
*/
|
||||
uint32 fadeDuration;
|
||||
|
||||
/**
|
||||
* The end volume of a fade.
|
||||
*/
|
||||
int fadeTargetVolume;
|
||||
|
||||
/**
|
||||
* Whether or not the channel should be stopped and freed when the fade is
|
||||
* complete.
|
||||
*/
|
||||
bool stopChannelOnFade;
|
||||
|
||||
/**
|
||||
* Whether or not this channel contains a Robot audio block.
|
||||
*/
|
||||
bool robot;
|
||||
|
||||
/**
|
||||
* For digital sound effects, the related VM Sound::nodePtr object for the
|
||||
* sound.
|
||||
*/
|
||||
reg_t soundNode;
|
||||
|
||||
/**
|
||||
* The playback volume, from 1 to 127 inclusive.
|
||||
*/
|
||||
int volume;
|
||||
|
||||
/**
|
||||
* The amount to pan to the right, from 0 to 100. 50 is centered, -1 is not
|
||||
* panned.
|
||||
*/
|
||||
int pan;
|
||||
|
||||
AudioChannel &operator=(AudioChannel &other) {
|
||||
id = other.id;
|
||||
resource = other.resource;
|
||||
stream.reset(other.stream.release());
|
||||
converter.reset(other.converter.release());
|
||||
duration = other.duration;
|
||||
startedAtTick = other.startedAtTick;
|
||||
pausedAtTick = other.pausedAtTick;
|
||||
fadeStartTick = other.fadeStartTick;
|
||||
fadeStartVolume = other.fadeStartVolume;
|
||||
fadeDuration = other.fadeDuration;
|
||||
fadeTargetVolume = other.fadeTargetVolume;
|
||||
stopChannelOnFade = other.stopChannelOnFade;
|
||||
robot = other.robot;
|
||||
soundNode = other.soundNode;
|
||||
volume = other.volume;
|
||||
pan = other.pan;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
#pragma mark -
|
||||
|
||||
/**
|
||||
* Special audio channel indexes used to select a channel for digital audio
|
||||
* playback.
|
||||
*/
|
||||
enum AudioChannelIndex {
|
||||
kRobotChannel = -3,
|
||||
kNoExistingChannel = -2,
|
||||
kAllChannels = -1
|
||||
};
|
||||
|
||||
/**
|
||||
* Audio32 acts as a permanent audio stream into the system mixer and provides
|
||||
* digital audio services for the SCI32 engine, since the system mixer does not
|
||||
* support all the features of SCI.
|
||||
*/
|
||||
class Audio32 : public Audio::AudioStream, public Common::Serializable {
|
||||
public:
|
||||
Audio32(ResourceManager *resMan);
|
||||
~Audio32() override;
|
||||
|
||||
void saveLoadWithSerializer(Common::Serializer &s) override;
|
||||
|
||||
enum {
|
||||
/**
|
||||
* The maximum channel volume.
|
||||
*/
|
||||
kMaxVolume = 127,
|
||||
|
||||
kMonitorAudioFlagSci3 = 0x80
|
||||
};
|
||||
|
||||
private:
|
||||
ResourceManager *_resMan;
|
||||
Audio::Mixer *_mixer;
|
||||
Audio::SoundHandle _handle;
|
||||
Common::Mutex _mutex;
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark AudioStream implementation
|
||||
public:
|
||||
int readBuffer(Audio::st_sample_t *buffer, const int numSamples) override;
|
||||
bool isStereo() const override { return true; }
|
||||
int getRate() const override { return _mixer->getOutputRate(); }
|
||||
bool endOfData() const override { return _numActiveChannels == 0; }
|
||||
bool endOfStream() const override { return false; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* Determines the number of channels that will be mixed together during a
|
||||
* call to readBuffer.
|
||||
*/
|
||||
int16 getNumChannelsToMix() const;
|
||||
|
||||
/**
|
||||
* Determines whether or not the given audio channel will be mixed into the
|
||||
* output stream.
|
||||
*/
|
||||
bool channelShouldMix(const AudioChannel &channel) const;
|
||||
|
||||
/**
|
||||
* Mixes audio from the given source stream into the target buffer using the
|
||||
* given rate converter.
|
||||
*/
|
||||
int writeAudioInternal(Audio::AudioStream &sourceStream, Audio::RateConverter &converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume);
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Channel management
|
||||
public:
|
||||
/**
|
||||
* Gets the number of currently active channels.
|
||||
*/
|
||||
inline uint8 getNumActiveChannels() const {
|
||||
Common::StackLock lock(_mutex);
|
||||
return _numActiveChannels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of currently active channels that are playing from
|
||||
* unlocked resources.
|
||||
*
|
||||
* @note In SSCI, this function would actually return the number of channels
|
||||
* whose audio data were not loaded into memory. In practice, the signal for
|
||||
* placing audio data into memory was a call to kLock, so since we do not
|
||||
* follow how SSCI works when it comes to resource management, the lock
|
||||
* state is used as an (apparently) successful proxy for this information
|
||||
* instead.
|
||||
*/
|
||||
uint8 getNumUnlockedChannels() const;
|
||||
|
||||
/**
|
||||
* Finds a channel that is already configured for the given audio sample.
|
||||
*
|
||||
* @param startIndex The location of the audio resource information in the
|
||||
* arguments list.
|
||||
*/
|
||||
int16 findChannelByArgs(EngineState *s, int argc, const reg_t *argv, const int startIndex, const reg_t soundNode) const;
|
||||
|
||||
/**
|
||||
* Finds a channel that is already configured for the given audio sample.
|
||||
*/
|
||||
int16 findChannelById(const ResourceId resourceId, const reg_t soundNode = NULL_REG) const;
|
||||
|
||||
/**
|
||||
* Sets or clears a lock on the given resource ID.
|
||||
*/
|
||||
void lockResource(const ResourceId resourceId, const bool lock);
|
||||
|
||||
private:
|
||||
typedef Common::Array<ResourceId> LockList;
|
||||
typedef Common::Array<Resource *> UnlockList;
|
||||
|
||||
/**
|
||||
* The audio channels.
|
||||
*/
|
||||
Common::Array<AudioChannel> _channels;
|
||||
|
||||
/**
|
||||
* The number of active audio channels in the mixer. Being active is not the
|
||||
* same as playing; active channels may be paused.
|
||||
*/
|
||||
uint8 _numActiveChannels;
|
||||
|
||||
/**
|
||||
* Whether or not we are in the audio thread.
|
||||
*
|
||||
* This flag is used instead of passing a parameter to `freeUnusedChannels`
|
||||
* because a parameter would require forwarding through the public method
|
||||
* `stop`, and there is not currently any reason for this implementation
|
||||
* detail to be exposed.
|
||||
*/
|
||||
bool _inAudioThread;
|
||||
|
||||
/**
|
||||
* The list of resources from freed channels that need to be unlocked from
|
||||
* the main thread.
|
||||
*/
|
||||
UnlockList _resourcesToUnlock;
|
||||
|
||||
/**
|
||||
* The list of resource IDs that have been locked by game scripts.
|
||||
*/
|
||||
LockList _lockedResourceIds;
|
||||
|
||||
/**
|
||||
* Gets the audio channel at the given index.
|
||||
*/
|
||||
inline AudioChannel &getChannel(const int16 channelIndex) {
|
||||
Common::StackLock lock(_mutex);
|
||||
assert(channelIndex >= 0 && channelIndex < _numActiveChannels);
|
||||
return _channels[channelIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the audio channel at the given index.
|
||||
*/
|
||||
inline const AudioChannel &getChannel(const int16 channelIndex) const {
|
||||
Common::StackLock lock(_mutex);
|
||||
assert(channelIndex >= 0 && channelIndex < _numActiveChannels);
|
||||
return _channels[channelIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees all non-looping channels that have reached the end of their stream.
|
||||
*/
|
||||
void freeUnusedChannels();
|
||||
|
||||
/**
|
||||
* Frees resources allocated to the given channel.
|
||||
*/
|
||||
void freeChannel(const int16 channelIndex);
|
||||
|
||||
/**
|
||||
* Unlocks all resources that were freed by the audio thread.
|
||||
*/
|
||||
void unlockResources();
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Script compatibility
|
||||
public:
|
||||
/**
|
||||
* Gets the (fake) sample rate of the hardware DAC. For script compatibility
|
||||
* only.
|
||||
*/
|
||||
inline uint16 getSampleRate() const {
|
||||
return _globalSampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the (fake) sample rate of the hardware DAC. For script compatibility
|
||||
* only.
|
||||
*/
|
||||
void setSampleRate(uint16 rate);
|
||||
|
||||
/**
|
||||
* Gets the (fake) bit depth of the hardware DAC. For script compatibility
|
||||
* only.
|
||||
*/
|
||||
inline uint8 getBitDepth() const {
|
||||
return _globalBitDepth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the (fake) sample rate of the hardware DAC. For script compatibility
|
||||
* only.
|
||||
*/
|
||||
void setBitDepth(uint8 depth);
|
||||
|
||||
/**
|
||||
* Gets the (fake) number of output (speaker) channels of the hardware DAC.
|
||||
* For script compatibility only.
|
||||
*/
|
||||
inline uint8 getNumOutputChannels() const {
|
||||
return _globalNumOutputChannels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the (fake) number of output (speaker) channels of the hardware DAC.
|
||||
* For script compatibility only.
|
||||
*/
|
||||
void setNumOutputChannels(int16 numChannels);
|
||||
|
||||
/**
|
||||
* Gets the (fake) number of preloaded channels. For script compatibility
|
||||
* only.
|
||||
*/
|
||||
inline uint8 getPreload() const {
|
||||
return _preload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the (fake) number of preloaded channels. For script compatibility
|
||||
* only.
|
||||
*/
|
||||
inline void setPreload(uint8 preload) {
|
||||
_preload = preload;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* The hardware DAC sample rate. Stored only for script compatibility.
|
||||
*/
|
||||
uint16 _globalSampleRate;
|
||||
|
||||
/**
|
||||
* The maximum allowed sample rate of the system mixer. Stored only for
|
||||
* script compatibility.
|
||||
*/
|
||||
uint16 _maxAllowedSampleRate;
|
||||
|
||||
/**
|
||||
* The hardware DAC bit depth. Stored only for script compatibility.
|
||||
*/
|
||||
uint8 _globalBitDepth;
|
||||
|
||||
/**
|
||||
* The maximum allowed bit depth of the system mixer. Stored only for script
|
||||
* compatibility.
|
||||
*/
|
||||
uint8 _maxAllowedBitDepth;
|
||||
|
||||
/**
|
||||
* The hardware DAC output (speaker) channel configuration. Stored only for
|
||||
* script compatibility.
|
||||
*/
|
||||
uint8 _globalNumOutputChannels;
|
||||
|
||||
/**
|
||||
* The maximum allowed number of output (speaker) channels of the system
|
||||
* mixer. Stored only for script compatibility.
|
||||
*/
|
||||
uint8 _maxAllowedOutputChannels;
|
||||
|
||||
/**
|
||||
* The number of audio channels that should have their data preloaded into
|
||||
* memory instead of streaming from disk. 1 = all channels, 2 = 2nd active
|
||||
* channel and above, etc. Stored only for script compatibility.
|
||||
*/
|
||||
uint8 _preload;
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Robot
|
||||
public:
|
||||
bool playRobotAudio(const RobotAudioStream::RobotAudioPacket &packet);
|
||||
bool queryRobotAudio(RobotAudioStream::StreamState &outStatus) const;
|
||||
bool finishRobotAudio();
|
||||
bool stopRobotAudio();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Finds a channel that is configured for robot playback.
|
||||
*/
|
||||
int16 findRobotChannel() const;
|
||||
|
||||
/**
|
||||
* When true, channels marked as robot audio will not be played.
|
||||
*/
|
||||
bool _robotAudioPaused;
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Playback
|
||||
public:
|
||||
/**
|
||||
* Starts or resumes playback of an audio channel.
|
||||
*/
|
||||
uint16 play(int16 channelIndex, const ResourceId resourceId, const bool autoPlay, const bool loop, const int16 volume, const reg_t soundNode, const bool monitor);
|
||||
|
||||
/**
|
||||
* Resumes playback of a paused audio channel, or of the entire audio
|
||||
* player.
|
||||
*/
|
||||
bool resume(const int16 channelIndex);
|
||||
bool resume(const ResourceId resourceId, const reg_t soundNode = NULL_REG) {
|
||||
Common::StackLock lock(_mutex);
|
||||
return resume(findChannelById(resourceId, soundNode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses an audio channel, or the entire audio player.
|
||||
*/
|
||||
bool pause(const int16 channelIndex);
|
||||
bool pause(const ResourceId resourceId, const reg_t soundNode = NULL_REG) {
|
||||
Common::StackLock lock(_mutex);
|
||||
return pause(findChannelById(resourceId, soundNode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops and unloads an audio channel, or the entire audio player.
|
||||
*/
|
||||
int16 stop(const int16 channelIndex);
|
||||
int16 stop(const ResourceId resourceId, const reg_t soundNode = NULL_REG) {
|
||||
Common::StackLock lock(_mutex);
|
||||
return stop(findChannelById(resourceId, soundNode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts playback of the given audio resource.
|
||||
*/
|
||||
uint16 restart(const ResourceId resourceId, const bool autoPlay, const bool loop, const int16 volume, const reg_t soundNode, const bool monitor);
|
||||
|
||||
/**
|
||||
* Returns the playback position for the given channel number, in ticks.
|
||||
*/
|
||||
int16 getPosition(const int16 channelIndex) const;
|
||||
int16 getPosition(const ResourceId resourceId, const reg_t soundNode = NULL_REG) {
|
||||
Common::StackLock lock(_mutex);
|
||||
return getPosition(findChannelById(resourceId, soundNode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not the given channel should loop.
|
||||
*/
|
||||
void setLoop(const int16 channelIndex, const bool loop);
|
||||
void setLoop(const ResourceId resourceId, const reg_t soundNode, const bool loop) {
|
||||
Common::StackLock lock(_mutex);
|
||||
setLoop(findChannelById(resourceId, soundNode), loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the stereo panning for the given channel.
|
||||
*/
|
||||
void setPan(const int16 channelIndex, const int16 pan) {
|
||||
Common::StackLock lock(_mutex);
|
||||
getChannel(channelIndex).pan = pan;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* The tick when audio was globally paused.
|
||||
*/
|
||||
uint32 _pausedAtTick;
|
||||
|
||||
/**
|
||||
* The tick when audio was globally started.
|
||||
*/
|
||||
uint32 _startedAtTick;
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Effects
|
||||
public:
|
||||
/**
|
||||
* Gets the volume for a given channel. Passing `kAllChannels` will get the
|
||||
* global volume.
|
||||
*/
|
||||
int16 getVolume(const int16 channelIndex) const;
|
||||
int16 getVolume(const ResourceId resourceId, const reg_t soundNode) const {
|
||||
Common::StackLock lock(_mutex);
|
||||
return getVolume(findChannelById(resourceId, soundNode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the volume of an audio channel. Passing `kAllChannels` will set the
|
||||
* global volume.
|
||||
*/
|
||||
void setVolume(const int16 channelIndex, int16 volume);
|
||||
void setVolume(const ResourceId resourceId, const reg_t soundNode, const int16 volume) {
|
||||
Common::StackLock lock(_mutex);
|
||||
setVolume(findChannelById(resourceId, soundNode), volume);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the master volume for digital audio playback.
|
||||
*/
|
||||
void setMasterVolume(const int16 volume) {
|
||||
_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate an immediate fade of the given channel.
|
||||
*/
|
||||
bool fadeChannel(const int16 channelIndex, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade);
|
||||
bool fadeChannel(const ResourceId resourceId, const reg_t soundNode, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade) {
|
||||
Common::StackLock lock(_mutex);
|
||||
return fadeChannel(findChannelById(resourceId, soundNode), targetVolume, speed, steps, stopAfterFade);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether attenuated mixing mode is active.
|
||||
*/
|
||||
inline bool getAttenuatedMixing() const {
|
||||
return _attenuatedMixing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the attenuated mixing mode.
|
||||
*/
|
||||
void setAttenuatedMixing(bool attenuated) {
|
||||
Common::StackLock lock(_mutex);
|
||||
_attenuatedMixing = attenuated;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* If true, audio will be mixed by reducing the target buffer by half every
|
||||
* time a new channel is mixed in. The final channel is not attenuated.
|
||||
*/
|
||||
bool _attenuatedMixing;
|
||||
|
||||
/**
|
||||
* When true, a modified attenuation algorithm is used (`A/4 + B`) instead
|
||||
* of standard linear attenuation (`A/2 + B/2`).
|
||||
*/
|
||||
bool _useModifiedAttenuation;
|
||||
|
||||
/**
|
||||
* Processes an audio fade for the given channel.
|
||||
*
|
||||
* @returns true if the fade was completed and the channel was stopped.
|
||||
*/
|
||||
bool processFade(const int16 channelIndex);
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Signal monitoring
|
||||
public:
|
||||
/**
|
||||
* Returns whether the currently monitored audio channel contains any signal
|
||||
* within the next audio frame.
|
||||
*/
|
||||
bool hasSignal() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* The index of the channel being monitored for signal, or -1 if no channel
|
||||
* is monitored. When a channel is monitored, it also causes the engine to
|
||||
* play only the monitored channel.
|
||||
*/
|
||||
int16 _monitoredChannelIndex;
|
||||
|
||||
/**
|
||||
* The data buffer holding decompressed audio data for the channel that will
|
||||
* be monitored for an audio signal.
|
||||
*/
|
||||
Common::Array<Audio::st_sample_t> _monitoredBuffer;
|
||||
|
||||
/**
|
||||
* The number of valid audio samples in the signal monitoring buffer.
|
||||
*/
|
||||
int _numMonitoredSamples;
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Kernel
|
||||
public:
|
||||
reg_t kernelPlay(const bool autoPlay, EngineState *s, const int argc, const reg_t *const argv);
|
||||
reg_t kernelStop(EngineState *s, const int argc, const reg_t *const argv);
|
||||
reg_t kernelPause(EngineState *s, const int argc, const reg_t *const argv);
|
||||
reg_t kernelResume(EngineState *s, const int argc, const reg_t *const argv);
|
||||
reg_t kernelPosition(EngineState *s, const int argc, const reg_t *const argv);
|
||||
reg_t kernelVolume(EngineState *s, const int argc, const reg_t *const argv);
|
||||
reg_t kernelMixing(const int argc, const reg_t *const argv);
|
||||
reg_t kernelFade(EngineState *s, const int argc, const reg_t *const argv);
|
||||
void kernelLoop(EngineState *s, const int argc, const reg_t *const argv);
|
||||
void kernelPan(EngineState *s, const int argc, const reg_t *const argv);
|
||||
void kernelPanOff(EngineState *s, const int argc, const reg_t *const argv);
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Debugging
|
||||
public:
|
||||
void printAudioList(Console *con) const;
|
||||
};
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_SOUND_AUDIO32_H
|
||||
399
engines/sci/sound/decoders/sol.cpp
Normal file
399
engines/sci/sound/decoders/sol.cpp
Normal file
@@ -0,0 +1,399 @@
|
||||
/* 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/audiostream.h"
|
||||
#include "audio/rate.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
#include "common/substream.h"
|
||||
#include "common/util.h"
|
||||
#include "sci/sci.h"
|
||||
#include "sci/engine/features.h"
|
||||
#include "sci/sound/decoders/sol.h"
|
||||
#include "sci/resource/resource.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
// Note that the 16-bit version is also used in coktelvideo.cpp
|
||||
static const uint16 tableDPCM16[128] = {
|
||||
0x0000, 0x0008, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0060, 0x0070, 0x0080,
|
||||
0x0090, 0x00A0, 0x00B0, 0x00C0, 0x00D0, 0x00E0, 0x00F0, 0x0100, 0x0110, 0x0120,
|
||||
0x0130, 0x0140, 0x0150, 0x0160, 0x0170, 0x0180, 0x0190, 0x01A0, 0x01B0, 0x01C0,
|
||||
0x01D0, 0x01E0, 0x01F0, 0x0200, 0x0208, 0x0210, 0x0218, 0x0220, 0x0228, 0x0230,
|
||||
0x0238, 0x0240, 0x0248, 0x0250, 0x0258, 0x0260, 0x0268, 0x0270, 0x0278, 0x0280,
|
||||
0x0288, 0x0290, 0x0298, 0x02A0, 0x02A8, 0x02B0, 0x02B8, 0x02C0, 0x02C8, 0x02D0,
|
||||
0x02D8, 0x02E0, 0x02E8, 0x02F0, 0x02F8, 0x0300, 0x0308, 0x0310, 0x0318, 0x0320,
|
||||
0x0328, 0x0330, 0x0338, 0x0340, 0x0348, 0x0350, 0x0358, 0x0360, 0x0368, 0x0370,
|
||||
0x0378, 0x0380, 0x0388, 0x0390, 0x0398, 0x03A0, 0x03A8, 0x03B0, 0x03B8, 0x03C0,
|
||||
0x03C8, 0x03D0, 0x03D8, 0x03E0, 0x03E8, 0x03F0, 0x03F8, 0x0400, 0x0440, 0x0480,
|
||||
0x04C0, 0x0500, 0x0540, 0x0580, 0x05C0, 0x0600, 0x0640, 0x0680, 0x06C0, 0x0700,
|
||||
0x0740, 0x0780, 0x07C0, 0x0800, 0x0900, 0x0A00, 0x0B00, 0x0C00, 0x0D00, 0x0E00,
|
||||
0x0F00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000
|
||||
};
|
||||
|
||||
// Each 4-bit nibble indexes into this table to refer to one of the 16 delta values.
|
||||
// deDPCM8Nibble() currently uses the first 8 values, with logic to order the negative
|
||||
// deltas differently depending on the exact encoding used.
|
||||
// deDPCM8NibbleWithRepair() uses the whole table, since it matches the order of "old"
|
||||
// encoding as-is. This saves a tiny bit of computation in the more complex function.
|
||||
static const int8 tableDPCM8[16] = {
|
||||
0, 1, 2, 3, 6, 10, 15, 21, -21, -15, -10, -6, -3, -2, -1, -0
|
||||
};
|
||||
|
||||
/**
|
||||
* Decompresses one channel of 16-bit DPCM compressed audio.
|
||||
*/
|
||||
static void deDPCM16Channel(int16 *out, int16 &sample, uint8 delta) {
|
||||
int32 nextSample = sample;
|
||||
if (delta & 0x80) {
|
||||
nextSample -= tableDPCM16[delta & 0x7f];
|
||||
} else {
|
||||
nextSample += tableDPCM16[delta];
|
||||
}
|
||||
|
||||
// Emulating x86 16-bit signed register overflow
|
||||
if (nextSample > 32767) {
|
||||
nextSample -= 65536;
|
||||
} else if (nextSample < -32768) {
|
||||
nextSample += 65536;
|
||||
}
|
||||
|
||||
*out = sample = nextSample;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses 16-bit DPCM compressed audio. Each byte read
|
||||
* outputs one sample into the decompression buffer.
|
||||
*/
|
||||
static void deDPCM16Mono(int16 *out, Common::ReadStream &audioStream, const uint32 numBytes, int16 &sample) {
|
||||
for (uint32 i = 0; i < numBytes; ++i) {
|
||||
const uint8 delta = audioStream.readByte();
|
||||
deDPCM16Channel(out++, sample, delta);
|
||||
}
|
||||
}
|
||||
|
||||
// Used by Robot
|
||||
void deDPCM16Mono(int16 *out, const byte *in, const uint32 numBytes, int16 &sample) {
|
||||
for (uint32 i = 0; i < numBytes; ++i) {
|
||||
const uint8 delta = *in++;
|
||||
deDPCM16Channel(out++, sample, delta);
|
||||
}
|
||||
}
|
||||
|
||||
static void deDPCM16Stereo(int16 *out, Common::ReadStream &audioStream, const uint32 numBytes, int16 &sampleL, int16 &sampleR) {
|
||||
assert((numBytes % 2) == 0);
|
||||
for (uint32 i = 0; i < numBytes / 2; ++i) {
|
||||
deDPCM16Channel(out++, sampleL, audioStream.readByte());
|
||||
deDPCM16Channel(out++, sampleR, audioStream.readByte());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses one half of an 8-bit DPCM compressed audio
|
||||
* byte.
|
||||
*/
|
||||
template <bool OLD>
|
||||
static void deDPCM8Nibble(int16 *out, uint8 &sample, uint8 delta) {
|
||||
const uint8 lastSample = sample;
|
||||
if (delta & 8) {
|
||||
sample -= tableDPCM8[OLD ? (7 - (delta & 7)) : (delta & 7)];
|
||||
} else {
|
||||
sample += tableDPCM8[delta & 7];
|
||||
}
|
||||
*out = ((lastSample + sample) << 7) ^ 0x8000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses one half of an 8-bit DPCM compressed audio
|
||||
* byte. Attempts to repair overflows on the fly.
|
||||
*/
|
||||
static void deDPCM8NibbleWithRepair(int16 *const out, uint8 &sample, const uint8 delta,
|
||||
uint8 &repairState, uint8 &preRepairSample) {
|
||||
const uint8 lastSample = sample;
|
||||
|
||||
// In Gabriel Knight: Sins of the Fathers CD, the DPCM8-encoded speech contains overflows.
|
||||
// Overflows wrap from positive to negative, or negative to positive. This continues
|
||||
// until the wave settles at a new (wrapped) zero DC offset.
|
||||
|
||||
// We can't look ahead to see where the wave "reconnects" to valid data, so we
|
||||
// decay the wave on an artificial slope until it does. This seems to take on average
|
||||
// around 9-10 samples. The slope value below was chosen through analysing spectrographs
|
||||
// of the decoded/repaired wave data to find the slope which removed the pops most
|
||||
// cleanly across a test selection of game speech.
|
||||
|
||||
#define REPAIR_SLOPE 12
|
||||
|
||||
switch (repairState) {
|
||||
case 0: {
|
||||
const int16 newSampleOverflow = (int16)sample + tableDPCM8[delta & 15];
|
||||
|
||||
if (newSampleOverflow > 255) {
|
||||
// Positive overflow has occurred; begin artificial negative slope.
|
||||
repairState = 1;
|
||||
sample = lastSample - REPAIR_SLOPE;
|
||||
// We also begin tracking the un-repaired waveform, so we can tell when to stop.
|
||||
preRepairSample = (uint8)newSampleOverflow;
|
||||
|
||||
debugC(1, kDebugLevelSound, "DPCM8 OVERFLOW (+)");
|
||||
|
||||
} else if (newSampleOverflow < 0) {
|
||||
// Negative overflow has occurred; begin artificial positive slope.
|
||||
repairState = 2;
|
||||
sample = lastSample + REPAIR_SLOPE;
|
||||
// We also begin tracking the un-repaired waveform, so we can tell when to stop.
|
||||
preRepairSample = (uint8)newSampleOverflow;
|
||||
|
||||
debugC(1, kDebugLevelSound, "DPCM8 OVERFLOW (-)");
|
||||
|
||||
} else {
|
||||
sample = (uint8)newSampleOverflow;
|
||||
}
|
||||
} break;
|
||||
case 1: {
|
||||
// Check for a slope wrap. This circumstance should never happen in reality;
|
||||
// the unrepaired wave would somehow need to be stuck near minimum
|
||||
// value over the entire course of the slope.
|
||||
if (lastSample < REPAIR_SLOPE)
|
||||
warning("Negative slope wrap!");
|
||||
|
||||
const uint8 slopeSample = lastSample - REPAIR_SLOPE;
|
||||
preRepairSample += tableDPCM8[delta & 15];
|
||||
|
||||
// Stop the repair if the artificial slope has intersected with real data.
|
||||
if (preRepairSample >= slopeSample) {
|
||||
// Return to real data.
|
||||
repairState = 0;
|
||||
sample = preRepairSample;
|
||||
} else {
|
||||
sample = slopeSample;
|
||||
}
|
||||
} break;
|
||||
case 2: {
|
||||
// Check for a slope wrap. This circumstance should never happen in reality;
|
||||
// the unrepaired wave would somehow need to be stuck near maximum
|
||||
// value over the entire course of the slope.
|
||||
if (lastSample > (255 - REPAIR_SLOPE))
|
||||
warning("Positive slope wrap!");
|
||||
|
||||
const uint8 slopeSample = lastSample + REPAIR_SLOPE;
|
||||
preRepairSample += tableDPCM8[delta & 15];
|
||||
|
||||
// Stop the repair if the artificial slope has intersected with real data.
|
||||
if (preRepairSample <= slopeSample) {
|
||||
// Return to real data.
|
||||
repairState = 0;
|
||||
sample = preRepairSample;
|
||||
} else {
|
||||
sample = slopeSample;
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
warning("Invalid repair state!");
|
||||
repairState = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
*out = ((lastSample + sample) << 7) ^ 0x8000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses 8-bit DPCM compressed audio. Each byte read
|
||||
* outputs two samples into the decompression buffer.
|
||||
*/
|
||||
template <bool OLD>
|
||||
static void deDPCM8Mono(int16 *out, Common::ReadStream &audioStream, const uint32 numBytes, uint8 &sample,
|
||||
const bool popfixEnabled, uint8 &repairState, uint8 &preRepairSample) {
|
||||
if (popfixEnabled) {
|
||||
for (uint32 i = 0; i < numBytes; ++i) {
|
||||
const uint8 delta = audioStream.readByte();
|
||||
deDPCM8NibbleWithRepair(out++, sample, delta >> 4, repairState, preRepairSample);
|
||||
deDPCM8NibbleWithRepair(out++, sample, delta & 0xf, repairState, preRepairSample);
|
||||
}
|
||||
} else {
|
||||
for (uint32 i = 0; i < numBytes; ++i) {
|
||||
const uint8 delta = audioStream.readByte();
|
||||
deDPCM8Nibble<OLD>(out++, sample, delta >> 4);
|
||||
deDPCM8Nibble<OLD>(out++, sample, delta & 0xf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void deDPCM8Stereo(int16 *out, Common::ReadStream &audioStream, uint32 numBytes, uint8 &sampleL, uint8 &sampleR) {
|
||||
for (uint32 i = 0; i < numBytes; ++i) {
|
||||
const uint8 delta = audioStream.readByte();
|
||||
deDPCM8Nibble<false>(out++, sampleL, delta >> 4);
|
||||
deDPCM8Nibble<false>(out++, sampleR, delta & 0xf);
|
||||
}
|
||||
}
|
||||
|
||||
# pragma mark -
|
||||
|
||||
template<bool STEREO, bool S16BIT, bool OLDDPCM8>
|
||||
SOLStream<STEREO, S16BIT, OLDDPCM8>::SOLStream(Common::SeekableReadStream *stream, const DisposeAfterUse::Flag disposeAfterUse, const uint16 sampleRate, const int32 rawDataSize) :
|
||||
_stream(stream, disposeAfterUse),
|
||||
_sampleRate(sampleRate),
|
||||
// SSCI aligns the size of SOL data to 32 bits
|
||||
_rawDataSize(rawDataSize & ~3),
|
||||
// The pop fix is only verified with (relevant to?) "old" DPCM8, so we enforce that here.
|
||||
_popfixDPCM8(g_sci->_features->useAudioPopfix() && OLDDPCM8) {
|
||||
if (S16BIT) {
|
||||
_dpcmCarry16.l = _dpcmCarry16.r = 0;
|
||||
} else {
|
||||
_dpcmCarry8.l = _dpcmCarry8.r = 0x80;
|
||||
}
|
||||
|
||||
const uint8 compressionRatio = 2;
|
||||
const uint8 numChannels = STEREO ? 2 : 1;
|
||||
const uint8 bytesPerSample = S16BIT ? 2 : 1;
|
||||
_length = ((uint64)_rawDataSize * compressionRatio * 1000) / (_sampleRate * numChannels * bytesPerSample);
|
||||
}
|
||||
|
||||
template <bool STEREO, bool S16BIT, bool OLDDPCM8>
|
||||
bool SOLStream<STEREO, S16BIT, OLDDPCM8>::seek(const Audio::Timestamp &where) {
|
||||
if (where != 0) {
|
||||
// In order to seek in compressed SOL files, all previous bytes must be
|
||||
// known since it uses differential compression. Therefore, only seeking
|
||||
// to the beginning is supported now (SSCI does not offer seeking
|
||||
// anyway)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (S16BIT) {
|
||||
_dpcmCarry16.l = _dpcmCarry16.r = 0;
|
||||
} else {
|
||||
_dpcmCarry8.l = _dpcmCarry8.r = 0x80;
|
||||
}
|
||||
|
||||
return _stream->seek(0, SEEK_SET);
|
||||
}
|
||||
|
||||
template <bool STEREO, bool S16BIT, bool OLDDPCM8>
|
||||
Audio::Timestamp SOLStream<STEREO, S16BIT, OLDDPCM8>::getLength() const {
|
||||
return _length;
|
||||
}
|
||||
|
||||
template <bool STEREO, bool S16BIT, bool OLDDPCM8>
|
||||
int SOLStream<STEREO, S16BIT, OLDDPCM8>::readBuffer(int16 *buffer, const int numSamples) {
|
||||
// Reading an odd number of 8-bit samples will result in a loss of samples
|
||||
// since one byte represents two samples and we do not store the second
|
||||
// nibble in this case; it should never happen in reality
|
||||
assert(S16BIT || (numSamples % 2) == 0);
|
||||
|
||||
const int samplesPerByte = S16BIT ? 1 : 2;
|
||||
|
||||
int32 bytesToRead = numSamples / samplesPerByte;
|
||||
if (_stream->pos() + bytesToRead > _rawDataSize) {
|
||||
bytesToRead = _rawDataSize - _stream->pos();
|
||||
}
|
||||
|
||||
if (S16BIT) {
|
||||
if (STEREO) {
|
||||
deDPCM16Stereo(buffer, *_stream, bytesToRead, _dpcmCarry16.l, _dpcmCarry16.r);
|
||||
} else {
|
||||
deDPCM16Mono(buffer, *_stream, bytesToRead, _dpcmCarry16.l);
|
||||
}
|
||||
} else {
|
||||
if (STEREO) {
|
||||
deDPCM8Stereo(buffer, *_stream, bytesToRead, _dpcmCarry8.l, _dpcmCarry8.r);
|
||||
} else {
|
||||
deDPCM8Mono<OLDDPCM8>(buffer, *_stream, bytesToRead, _dpcmCarry8.l,
|
||||
_popfixDPCM8.enabled, _popfixDPCM8.state, _popfixDPCM8.preRepairSample);
|
||||
}
|
||||
}
|
||||
|
||||
const int samplesRead = bytesToRead * samplesPerByte;
|
||||
return samplesRead;
|
||||
}
|
||||
|
||||
template <bool STEREO, bool S16BIT, bool OLDDPCM8>
|
||||
bool SOLStream<STEREO, S16BIT, OLDDPCM8>::isStereo() const {
|
||||
return STEREO;
|
||||
}
|
||||
|
||||
template <bool STEREO, bool S16BIT, bool OLDDPCM8>
|
||||
int SOLStream<STEREO, S16BIT, OLDDPCM8>::getRate() const {
|
||||
return _sampleRate;
|
||||
}
|
||||
|
||||
template <bool STEREO, bool S16BIT, bool OLDDPCM8>
|
||||
bool SOLStream<STEREO, S16BIT, OLDDPCM8>::endOfData() const {
|
||||
return _stream->eos() || _stream->pos() >= _rawDataSize;
|
||||
}
|
||||
|
||||
template <bool STEREO, bool S16BIT, bool OLDDPCM8>
|
||||
bool SOLStream<STEREO, S16BIT, OLDDPCM8>::rewind() {
|
||||
return seek(0);
|
||||
}
|
||||
|
||||
Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
|
||||
int32 initialPosition = stream->pos();
|
||||
|
||||
byte header[6];
|
||||
if (stream->read(header, sizeof(header)) != sizeof(header)) {
|
||||
stream->seek(initialPosition, SEEK_SET);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if ((header[0] & 0x7f) != kResourceTypeAudio || READ_BE_UINT32(header + 2) != MKTAG('S', 'O', 'L', 0)) {
|
||||
stream->seek(initialPosition, SEEK_SET);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const uint8 headerSize = header[1] + kResourceHeaderSize;
|
||||
const uint16 sampleRate = stream->readUint16LE();
|
||||
const byte flags = stream->readByte();
|
||||
const uint32 dataSize = stream->readUint32LE();
|
||||
|
||||
initialPosition += headerSize;
|
||||
|
||||
if (flags & kCompressed) {
|
||||
if (flags & kStereo && flags & k16Bit) {
|
||||
return new SOLStream<true, true, false>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), DisposeAfterUse::YES, sampleRate, dataSize);
|
||||
} else if (flags & kStereo) {
|
||||
if (getSciVersion() < SCI_VERSION_2_1_EARLY) {
|
||||
error("SCI2 and earlier did not support stereo SOL audio");
|
||||
}
|
||||
|
||||
return new SOLStream<true, false, false>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), DisposeAfterUse::YES, sampleRate, dataSize);
|
||||
} else if (flags & k16Bit) {
|
||||
return new SOLStream<false, true, false>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), DisposeAfterUse::YES, sampleRate, dataSize);
|
||||
} else {
|
||||
if (getSciVersion() < SCI_VERSION_2_1_EARLY) {
|
||||
return new SOLStream<false, false, true>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), DisposeAfterUse::YES, sampleRate, dataSize);
|
||||
} else {
|
||||
return new SOLStream<false, false, false>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), DisposeAfterUse::YES, sampleRate, dataSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte rawFlags = Audio::FLAG_LITTLE_ENDIAN;
|
||||
if (flags & k16Bit) {
|
||||
rawFlags |= Audio::FLAG_16BITS;
|
||||
} else {
|
||||
rawFlags |= Audio::FLAG_UNSIGNED;
|
||||
}
|
||||
|
||||
if (flags & kStereo) {
|
||||
rawFlags |= Audio::FLAG_STEREO;
|
||||
}
|
||||
|
||||
return Audio::makeRawStream(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), sampleRate, rawFlags, disposeAfterUse);
|
||||
}
|
||||
}
|
||||
98
engines/sci/sound/decoders/sol.h
Normal file
98
engines/sci/sound/decoders/sol.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/* 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 SCI_SOUND_DECODERS_SOL_H
|
||||
#define SCI_SOUND_DECODERS_SOL_H
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
enum SOLFlags {
|
||||
kCompressed = 1,
|
||||
k16Bit = 4,
|
||||
kStereo = 16
|
||||
};
|
||||
|
||||
template <bool STEREO, bool S16BIT, bool OLDDPCM8>
|
||||
class SOLStream : public Audio::SeekableAudioStream {
|
||||
private:
|
||||
/**
|
||||
* Read stream containing possibly-compressed SOL audio.
|
||||
*/
|
||||
Common::DisposablePtr<Common::SeekableReadStream> _stream;
|
||||
|
||||
/**
|
||||
* Sample rate of audio data.
|
||||
*/
|
||||
uint16 _sampleRate;
|
||||
|
||||
/**
|
||||
* The raw (possibly-compressed) size of audio data in the stream.
|
||||
*/
|
||||
int32 _rawDataSize;
|
||||
|
||||
/**
|
||||
* DPCM8 pop (overflow) fix working data:
|
||||
* - Whether or not the fix is enabled, set once on construction.
|
||||
* - The current state of the repair (0 = inactive, 1 = positive, 2 = negative).
|
||||
* - The sample state without repair, to detect where repairing should stop.
|
||||
*/
|
||||
struct PopFixData {
|
||||
const bool enabled;
|
||||
uint8 state;
|
||||
uint8 preRepairSample;
|
||||
|
||||
PopFixData(const bool e):
|
||||
enabled(e), state(0), preRepairSample(0) {}
|
||||
} _popfixDPCM8;
|
||||
|
||||
/**
|
||||
* The last sample from the previous DPCM decode.
|
||||
*/
|
||||
union {
|
||||
struct { int16 l; int16 r; } _dpcmCarry16;
|
||||
struct { uint8 l; uint8 r; } _dpcmCarry8;
|
||||
};
|
||||
|
||||
/**
|
||||
* The calculated length of the stream.
|
||||
*/
|
||||
Audio::Timestamp _length;
|
||||
|
||||
bool seek(const Audio::Timestamp &where) override;
|
||||
Audio::Timestamp getLength() const override;
|
||||
int readBuffer(int16 *buffer, const int numSamples) override;
|
||||
bool isStereo() const override;
|
||||
int getRate() const override;
|
||||
bool endOfData() const override;
|
||||
bool rewind() override;
|
||||
|
||||
public:
|
||||
SOLStream(Common::SeekableReadStream *stream, const DisposeAfterUse::Flag disposeAfterUse, const uint16 sampleRate, const int32 rawDataSize);
|
||||
};
|
||||
|
||||
Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_SOUND_DECODERS_SOL_H
|
||||
969
engines/sci/sound/drivers/adlib.cpp
Normal file
969
engines/sci/sound/drivers/adlib.cpp
Normal file
@@ -0,0 +1,969 @@
|
||||
/* 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 "sci/sci.h"
|
||||
|
||||
#include "common/file.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#include "audio/fmopl.h"
|
||||
#include "audio/mididrv.h"
|
||||
|
||||
#include "sci/resource/resource.h"
|
||||
#include "sci/sound/drivers/mididriver.h"
|
||||
#include "sci/util.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
#ifdef __DC__
|
||||
#define STEREO false
|
||||
#else
|
||||
#define STEREO true
|
||||
#endif
|
||||
|
||||
class MidiDriver_AdLib : public MidiDriver {
|
||||
public:
|
||||
enum {
|
||||
kVoices = 9,
|
||||
kRhythmKeys = 62
|
||||
};
|
||||
|
||||
MidiDriver_AdLib(SciVersion version) : _version(version), _isSCI0(version < SCI_VERSION_1_EARLY), _playSwitch(true), _masterVolume(15),
|
||||
_numVoiceMax(version == SCI_VERSION_0_EARLY ? 8 : kVoices), _rhythmKeyMap(), _opl(nullptr), _adlibTimerParam(nullptr), _adlibTimerProc(nullptr), _stereo(false), _isOpen(false) { }
|
||||
~MidiDriver_AdLib() override { }
|
||||
|
||||
// MidiDriver
|
||||
int open() override { return -1; } // Dummy implementation (use openAdLib)
|
||||
int openAdLib();
|
||||
void close() override;
|
||||
void send(uint32 b) override;
|
||||
void initTrack(SciSpan<const byte> &header);
|
||||
|
||||
MidiChannel *allocateChannel() override { return nullptr; }
|
||||
MidiChannel *getPercussionChannel() override { return nullptr; }
|
||||
bool isOpen() const override { return _isOpen; }
|
||||
uint32 getBaseTempo() override { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; }
|
||||
|
||||
// MidiDriver
|
||||
void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) override;
|
||||
|
||||
void onTimer();
|
||||
|
||||
void setVolume(byte volume);
|
||||
void playSwitch(bool play);
|
||||
bool loadResource(const SciSpan<const byte> &data);
|
||||
uint32 property(int prop, uint32 param) override;
|
||||
|
||||
bool useRhythmChannel() const { return _rhythmKeyMap; }
|
||||
|
||||
private:
|
||||
enum ChannelID {
|
||||
kLeftChannel = 1,
|
||||
kRightChannel = 2
|
||||
};
|
||||
|
||||
struct AdLibOperator {
|
||||
bool amplitudeMod;
|
||||
bool vibrato;
|
||||
bool envelopeType;
|
||||
bool kbScaleRate;
|
||||
byte frequencyMult; // (0-15)
|
||||
byte kbScaleLevel; // (0-3)
|
||||
byte totalLevel; // (0-63, 0=max, 63=min)
|
||||
byte attackRate; // (0-15)
|
||||
byte decayRate; // (0-15)
|
||||
byte sustainLevel; // (0-15)
|
||||
byte releaseRate; // (0-15)
|
||||
byte waveForm; // (0-3)
|
||||
};
|
||||
|
||||
struct AdLibModulator {
|
||||
byte feedback; // (0-7)
|
||||
bool algorithm;
|
||||
};
|
||||
|
||||
struct AdLibPatch {
|
||||
AdLibOperator op[2];
|
||||
AdLibModulator mod;
|
||||
};
|
||||
|
||||
struct Channel {
|
||||
uint8 patch; // Patch setting
|
||||
uint8 volume; // Channel volume (0-63)
|
||||
uint8 pan; // Pan setting (0-127, 64 is center)
|
||||
uint8 holdPedal; // Hold pedal setting (0 to 63 is off, 127 to 64 is on)
|
||||
uint8 extraVoices; // The number of additional voices this channel optimally needs
|
||||
uint16 pitchWheel; // Pitch wheel setting (0-16383, 8192 is center)
|
||||
uint8 lastVoice; // Last voice used for this MIDI channel
|
||||
bool enableVelocity; // Enable velocity control (SCI0)
|
||||
uint8 voices; // Number of voices currently used by this channel
|
||||
uint8 mappedVoices; // Number of voices currently mapped to this channel
|
||||
|
||||
Channel() : patch(0), volume(63), pan(64), holdPedal(0), extraVoices(0),
|
||||
pitchWheel(8192), lastVoice(0), enableVelocity(false), voices(0),
|
||||
mappedVoices(0) { }
|
||||
};
|
||||
|
||||
struct AdLibVoice {
|
||||
int8 channel; // MIDI channel that is currently using this voice, or -1
|
||||
int8 mappedChannel; // MIDI channel that this voice is mapped to, or -1
|
||||
int8 note; // Currently playing MIDI note or -1
|
||||
int patch; // Currently playing patch or -1
|
||||
uint8 velocity; // Note velocity
|
||||
bool isSustained; // Flag indicating a note that is being sustained by the hold pedal
|
||||
uint16 age; // Age of the current note
|
||||
|
||||
AdLibVoice() : channel(-1), mappedChannel(-1), note(-1), patch(-1), velocity(0), isSustained(false), age(0) { }
|
||||
};
|
||||
|
||||
bool _stereo;
|
||||
bool _isSCI0;
|
||||
SciVersion _version;
|
||||
OPL::OPL *_opl;
|
||||
bool _isOpen;
|
||||
bool _playSwitch;
|
||||
int _masterVolume;
|
||||
const uint8 _numVoiceMax;
|
||||
Channel _channels[MIDI_CHANNELS];
|
||||
AdLibVoice _voices[kVoices];
|
||||
Common::SpanOwner<SciSpan<const byte> > _rhythmKeyMap;
|
||||
Common::Array<AdLibPatch> _patches;
|
||||
Common::List<int> _voiceQueue;
|
||||
|
||||
Common::TimerManager::TimerProc _adlibTimerProc;
|
||||
void *_adlibTimerParam;
|
||||
|
||||
void loadInstrument(const SciSpan<const byte> &ins);
|
||||
void voiceOn(int voice, int note, int velocity);
|
||||
void voiceOff(int voice);
|
||||
void setPatch(int voice, int patch);
|
||||
void setNote(int voice, int note, bool key);
|
||||
void setVelocity(int voice);
|
||||
void setOperator(int oper, AdLibOperator &op);
|
||||
void setRegister(int reg, int value, int channels = kLeftChannel | kRightChannel);
|
||||
void renewNotes(int channel, bool key);
|
||||
void noteOn(int channel, int note, int velocity);
|
||||
void noteOff(int channel, int note);
|
||||
int findVoice(int channel);
|
||||
int findVoiceLateSci11(int channel);
|
||||
void voiceMapping(int channel, int voices);
|
||||
void assignVoices(int channel, int voices);
|
||||
void releaseVoices(int channel, int voices);
|
||||
void donateVoices();
|
||||
void queueMoveToBack(int voice);
|
||||
void setVelocityReg(int regOffset, int velocity, int kbScaleLevel, int pan);
|
||||
int calcVelocity(int voice, int op);
|
||||
};
|
||||
|
||||
class MidiPlayer_AdLib : public MidiPlayer {
|
||||
public:
|
||||
MidiPlayer_AdLib(SciVersion soundVersion) : MidiPlayer(soundVersion) { _driver = new MidiDriver_AdLib(soundVersion); }
|
||||
~MidiPlayer_AdLib() override {
|
||||
delete _driver;
|
||||
_driver = nullptr;
|
||||
}
|
||||
|
||||
int open(ResourceManager *resMan) override;
|
||||
void close() override;
|
||||
|
||||
byte getPlayId() const override;
|
||||
int getPolyphony() const override { return MidiDriver_AdLib::kVoices; }
|
||||
bool hasRhythmChannel() const override { return false; }
|
||||
void setVolume(byte volume) override { static_cast<MidiDriver_AdLib *>(_driver)->setVolume(volume); }
|
||||
void playSwitch(bool play) override { static_cast<MidiDriver_AdLib *>(_driver)->playSwitch(play); }
|
||||
void initTrack(SciSpan<const byte> &header) override { static_cast<MidiDriver_AdLib *>(_driver)->initTrack(header); }
|
||||
int getLastChannel() const override { return (static_cast<const MidiDriver_AdLib *>(_driver)->useRhythmChannel() ? 8 : 15); }
|
||||
};
|
||||
|
||||
static const byte registerOffset[MidiDriver_AdLib::kVoices] = {
|
||||
0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12
|
||||
};
|
||||
|
||||
static const byte velocityMap1[64] = {
|
||||
0x00, 0x0c, 0x0d, 0x0e, 0x0f, 0x11, 0x12, 0x13,
|
||||
0x14, 0x16, 0x17, 0x18, 0x1a, 0x1b, 0x1c, 0x1d,
|
||||
0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
|
||||
0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2d, 0x2d, 0x2e,
|
||||
0x2f, 0x30, 0x31, 0x32, 0x32, 0x33, 0x34, 0x34,
|
||||
0x35, 0x36, 0x36, 0x37, 0x38, 0x38, 0x39, 0x3a,
|
||||
0x3b, 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d,
|
||||
0x3d, 0x3e, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f
|
||||
};
|
||||
|
||||
static const byte velocityMap2[64] = {
|
||||
0x00, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
|
||||
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x21,
|
||||
0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
|
||||
0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x2f, 0x30,
|
||||
0x31, 0x32, 0x32, 0x33, 0x34, 0x34, 0x35, 0x36,
|
||||
0x36, 0x37, 0x38, 0x38, 0x39, 0x39, 0x3a, 0x3a,
|
||||
0x3b, 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d,
|
||||
0x3d, 0x3e, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f
|
||||
};
|
||||
|
||||
// One octave with three pitch wheel positions after each note
|
||||
static const int adlibFreq[48] = {
|
||||
0x157, 0x15c, 0x161, 0x166, 0x16b, 0x171, 0x176, 0x17b,
|
||||
0x181, 0x186, 0x18c, 0x192, 0x198, 0x19e, 0x1a4, 0x1aa,
|
||||
0x1b0, 0x1b6, 0x1bd, 0x1c3, 0x1ca, 0x1d0, 0x1d7, 0x1de,
|
||||
0x1e5, 0x1ec, 0x1f3, 0x1fa, 0x202, 0x209, 0x211, 0x218,
|
||||
0x220, 0x228, 0x230, 0x238, 0x241, 0x249, 0x252, 0x25a,
|
||||
0x263, 0x26c, 0x275, 0x27e, 0x287, 0x290, 0x29a, 0x2a4
|
||||
};
|
||||
|
||||
int MidiDriver_AdLib::openAdLib() {
|
||||
_stereo = STEREO;
|
||||
|
||||
debug(3, "ADLIB: Starting driver in %s mode", (_isSCI0 ? "SCI0" : "SCI1"));
|
||||
|
||||
// Fill in the voice queue
|
||||
for (int i = 0; i < kVoices; ++i)
|
||||
_voiceQueue.push_back(i);
|
||||
|
||||
_opl = OPL::Config::create(_stereo ? OPL::Config::kDualOpl2 : OPL::Config::kOpl2);
|
||||
|
||||
// Try falling back to mono, thus plain OPL2 emulator, when no Dual OPL2 is available.
|
||||
if (!_opl && _stereo) {
|
||||
_stereo = false;
|
||||
_opl = OPL::Config::create(OPL::Config::kOpl2);
|
||||
}
|
||||
|
||||
if (!_opl)
|
||||
return -1;
|
||||
|
||||
if (!_opl->init()) {
|
||||
delete _opl;
|
||||
_opl = nullptr;
|
||||
return -1;
|
||||
}
|
||||
|
||||
setRegister(0xBD, 0);
|
||||
setRegister(0x08, 0);
|
||||
setRegister(0x01, 0x20);
|
||||
|
||||
_isOpen = true;
|
||||
|
||||
_opl->start(new Common::Functor0Mem<void, MidiDriver_AdLib>(this, &MidiDriver_AdLib::onTimer));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::close() {
|
||||
delete _opl;
|
||||
_rhythmKeyMap.clear();
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::setVolume(byte volume) {
|
||||
_masterVolume = volume;
|
||||
renewNotes(-1, true);
|
||||
}
|
||||
|
||||
// MIDI messages can be found at https://web.archive.org/web/20120128110425/http://www.midi.org/techspecs/midimessages.php
|
||||
void MidiDriver_AdLib::send(uint32 b) {
|
||||
byte command = b & 0xf0;
|
||||
byte channel = b & 0xf;
|
||||
byte op1 = (b >> 8) & 0xff;
|
||||
byte op2 = (b >> 16) & 0xff;
|
||||
|
||||
switch (command) {
|
||||
case 0x80:
|
||||
noteOff(channel, op1);
|
||||
break;
|
||||
case 0x90:
|
||||
noteOn(channel, op1, op2);
|
||||
break;
|
||||
case 0xb0:
|
||||
switch (op1) {
|
||||
case 0x07:
|
||||
_channels[channel].volume = op2 >> 1;
|
||||
renewNotes(channel, true);
|
||||
break;
|
||||
case 0x0a:
|
||||
_channels[channel].pan = op2;
|
||||
renewNotes(channel, true);
|
||||
break;
|
||||
case 0x40:
|
||||
_channels[channel].holdPedal = op2;
|
||||
if (op2 == 0) {
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
if ((_voices[i].channel == channel) && _voices[i].isSustained)
|
||||
voiceOff(i);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x4b:
|
||||
#ifndef ADLIB_DISABLE_VOICE_MAPPING
|
||||
voiceMapping(channel, op2);
|
||||
#endif
|
||||
break;
|
||||
case 0x4e:
|
||||
_channels[channel].enableVelocity = op2;
|
||||
break;
|
||||
case SCI_MIDI_CHANNEL_NOTES_OFF:
|
||||
for (int i = 0; i < kVoices; i++)
|
||||
if ((_voices[i].channel == channel) && (_voices[i].note != -1))
|
||||
voiceOff(i);
|
||||
break;
|
||||
default:
|
||||
//warning("ADLIB: ignoring MIDI command %02x %02x %02x", command | channel, op1, op2);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 0xc0:
|
||||
_channels[channel].patch = op1;
|
||||
break;
|
||||
// The original AdLib driver from sierra ignores aftertouch completely, so should we
|
||||
case 0xa0: // Polyphonic key pressure (aftertouch)
|
||||
case 0xd0: // Channel pressure (aftertouch)
|
||||
break;
|
||||
case 0xe0:
|
||||
_channels[channel].pitchWheel = (op1 & 0x7f) | ((op2 & 0x7f) << 7);
|
||||
renewNotes(channel, true);
|
||||
break;
|
||||
default:
|
||||
warning("ADLIB: Unknown event %02x", command);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::initTrack(SciSpan<const byte> &header) {
|
||||
if (!_isOpen || !_isSCI0)
|
||||
return;
|
||||
|
||||
uint8 readPos = 0;
|
||||
uint8 caps = header.getInt8At(readPos++);
|
||||
if (caps != 0 && (_version == SCI_VERSION_0_EARLY || caps != 2))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < kVoices; ++i) {
|
||||
_voices[i].channel = _voices[i].mappedChannel = _voices[i].note = -1;
|
||||
_voices[i].isSustained = false;
|
||||
_voices[i].patch = 13;
|
||||
_voices[i].velocity = 0;
|
||||
_voices[i].age = 0;
|
||||
}
|
||||
|
||||
int numVoices = 0;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
_channels[i].patch = 13;
|
||||
_channels[i].extraVoices = 0;
|
||||
_channels[i].mappedVoices = 0;
|
||||
|
||||
if (_version == SCI_VERSION_0_LATE) {
|
||||
uint8 num = header.getInt8At(readPos++) & 0x7F;
|
||||
uint8 flags = header.getInt8At(readPos++);
|
||||
if ((flags & 0x04) && num)
|
||||
assignVoices(i, num);
|
||||
} else {
|
||||
uint8 val = header.getInt8At(readPos++);
|
||||
if (val & 0x01) {
|
||||
uint8 num = val >> 4;
|
||||
if (!(val & 0x08) && num && num != 0x0F) {
|
||||
while (num--) {
|
||||
if (numVoices >= _numVoiceMax)
|
||||
continue;
|
||||
_voices[numVoices++].mappedChannel = i;
|
||||
_channels[i].mappedVoices++;
|
||||
}
|
||||
}
|
||||
} else if (val & 0x08) {
|
||||
debugC(9, kDebugLevelSound, "MidiDriver_AdLib::initTrack(): Control channel found: 0x%.02x", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
|
||||
_adlibTimerProc = timerProc;
|
||||
_adlibTimerParam = timerParam;
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::onTimer() {
|
||||
if (_adlibTimerProc)
|
||||
(*_adlibTimerProc)(_adlibTimerParam);
|
||||
|
||||
// Increase the age of the notes
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
if (_voices[i].note != -1)
|
||||
_voices[i].age++;
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::loadInstrument(const SciSpan<const byte> &ins) {
|
||||
AdLibPatch patch;
|
||||
|
||||
// Set data for the operators
|
||||
for (int i = 0; i < 2; i++) {
|
||||
const byte *op = ins.getUnsafeDataAt(i * 13, 13);
|
||||
patch.op[i].kbScaleLevel = op[0] & 0x3;
|
||||
patch.op[i].frequencyMult = op[1] & 0xf;
|
||||
patch.op[i].attackRate = op[3] & 0xf;
|
||||
patch.op[i].sustainLevel = op[4] & 0xf;
|
||||
patch.op[i].envelopeType = op[5];
|
||||
patch.op[i].decayRate = op[6] & 0xf;
|
||||
patch.op[i].releaseRate = op[7] & 0xf;
|
||||
patch.op[i].totalLevel = op[8] & 0x3f;
|
||||
patch.op[i].amplitudeMod = op[9];
|
||||
patch.op[i].vibrato = op[10];
|
||||
patch.op[i].kbScaleRate = op[11];
|
||||
}
|
||||
patch.op[0].waveForm = ins[26] & 0x3;
|
||||
patch.op[1].waveForm = ins[27] & 0x3;
|
||||
|
||||
// Set data for the modulator
|
||||
patch.mod.feedback = ins[2] & 0x7;
|
||||
patch.mod.algorithm = !ins[12]; // Flag is inverted
|
||||
|
||||
_patches.push_back(patch);
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::voiceMapping(int channel, int voices) {
|
||||
int curVoices = 0;
|
||||
|
||||
for (int i = 0; i < _numVoiceMax; i++)
|
||||
if (_voices[i].mappedChannel == channel)
|
||||
curVoices++;
|
||||
|
||||
curVoices += _channels[channel].extraVoices;
|
||||
|
||||
if (curVoices < voices) {
|
||||
debug(3, "ADLIB: assigning %i additional voices to channel %i", voices - curVoices, channel);
|
||||
assignVoices(channel, voices - curVoices);
|
||||
} else if (curVoices > voices) {
|
||||
debug(3, "ADLIB: releasing %i voices from channel %i", curVoices - voices, channel);
|
||||
releaseVoices(channel, curVoices - voices);
|
||||
donateVoices();
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::assignVoices(int channel, int voices) {
|
||||
assert(voices > 0);
|
||||
|
||||
for (int i = 0; i < _numVoiceMax; i++)
|
||||
if (_voices[i].mappedChannel == -1) {
|
||||
if (_voices[i].note != -1) // Late SCI1.1, stop note on unmapped channel
|
||||
voiceOff(i);
|
||||
_voices[i].mappedChannel = channel;
|
||||
++_channels[channel].mappedVoices;
|
||||
if (--voices == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
// This is already too advanced for SCI0...
|
||||
if (!_isSCI0)
|
||||
_channels[channel].extraVoices += voices;
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::releaseVoices(int channel, int voices) {
|
||||
if (_channels[channel].extraVoices >= voices) {
|
||||
_channels[channel].extraVoices -= voices;
|
||||
return;
|
||||
}
|
||||
|
||||
voices -= _channels[channel].extraVoices;
|
||||
_channels[channel].extraVoices = 0;
|
||||
|
||||
for (int i = 0; i < _numVoiceMax; i++) {
|
||||
if ((_voices[i].mappedChannel == channel) && (_voices[i].note == -1)) {
|
||||
_voices[i].mappedChannel = -1;
|
||||
--_channels[channel].mappedVoices;
|
||||
if (--voices == 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _numVoiceMax; i++) {
|
||||
if (_voices[i].mappedChannel == channel) {
|
||||
voiceOff(i);
|
||||
_voices[i].mappedChannel = -1;
|
||||
--_channels[channel].mappedVoices;
|
||||
if (--voices == 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::donateVoices() {
|
||||
if (_isSCI0)
|
||||
return;
|
||||
|
||||
int freeVoices = 0;
|
||||
|
||||
for (int i = 0; i < kVoices; i++)
|
||||
if (_voices[i].mappedChannel == -1)
|
||||
freeVoices++;
|
||||
|
||||
if (freeVoices == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < MIDI_CHANNELS; i++) {
|
||||
if (_channels[i].extraVoices >= freeVoices) {
|
||||
assignVoices(i, freeVoices);
|
||||
_channels[i].extraVoices -= freeVoices;
|
||||
return;
|
||||
} else if (_channels[i].extraVoices > 0) {
|
||||
assignVoices(i, _channels[i].extraVoices);
|
||||
freeVoices -= _channels[i].extraVoices;
|
||||
_channels[i].extraVoices = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::renewNotes(int channel, bool key) {
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
// Update all notes playing this channel
|
||||
if ((channel == -1) || (_voices[i].channel == channel)) {
|
||||
if (_voices[i].note != -1)
|
||||
setNote(i, _voices[i].note, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::noteOn(int channel, int note, int velocity) {
|
||||
if (velocity == 0)
|
||||
return noteOff(channel, note);
|
||||
|
||||
velocity >>= 1;
|
||||
|
||||
// Check for playable notes
|
||||
if ((note < 12) || (note > 107))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
if ((_voices[i].channel == channel) && (_voices[i].note == note)) {
|
||||
voiceOff(i);
|
||||
voiceOn(i, note, velocity);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int voice = _rhythmKeyMap ? findVoiceLateSci11(channel) : findVoice(channel);
|
||||
|
||||
if (voice == -1) {
|
||||
debug(3, "ADLIB: failed to find free voice assigned to channel %i", channel);
|
||||
return;
|
||||
}
|
||||
|
||||
voiceOn(voice, note, velocity);
|
||||
}
|
||||
|
||||
int MidiDriver_AdLib::findVoice(int channel) {
|
||||
int voice = -1;
|
||||
int oldestVoice = -1;
|
||||
uint32 oldestAge = 0;
|
||||
|
||||
// Try to find a voice assigned to this channel that is free (round-robin)
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
int v = (_channels[channel].lastVoice + i + 1) % kVoices;
|
||||
|
||||
if (_voices[v].mappedChannel == channel) {
|
||||
if (_voices[v].note == -1) {
|
||||
voice = v;
|
||||
_voices[voice].channel = channel;
|
||||
break;
|
||||
}
|
||||
|
||||
// We also keep track of the oldest note in case the search fails
|
||||
// Notes started in the current time slice will not be selected
|
||||
if (_voices[v].age >= oldestAge) {
|
||||
oldestAge = _voices[v].age;
|
||||
oldestVoice = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (voice == -1) {
|
||||
if (!oldestAge)
|
||||
return -1;
|
||||
voiceOff(oldestVoice);
|
||||
voice = oldestVoice;
|
||||
_voices[voice].channel = channel;
|
||||
}
|
||||
|
||||
_channels[channel].lastVoice = voice;
|
||||
|
||||
return voice;
|
||||
}
|
||||
|
||||
int MidiDriver_AdLib::findVoiceLateSci11(int channel) {
|
||||
Common::List<int>::const_iterator it;
|
||||
|
||||
// Search for unused voice
|
||||
for (it = _voiceQueue.begin(); it != _voiceQueue.end(); ++it) {
|
||||
int voice = *it;
|
||||
if (_voices[voice].note == -1 && _voices[voice].patch == _channels[channel].patch) {
|
||||
_voices[voice].channel = channel;
|
||||
return voice;
|
||||
}
|
||||
}
|
||||
|
||||
// Same as before, minus the program check
|
||||
for (it = _voiceQueue.begin(); it != _voiceQueue.end(); ++it) {
|
||||
int voice = *it;
|
||||
if (_voices[voice].note == -1) {
|
||||
_voices[voice].channel = channel;
|
||||
return voice;
|
||||
}
|
||||
}
|
||||
|
||||
// Search for channel with highest excess of voices
|
||||
int maxExceed = 0;
|
||||
int maxExceedChan = 0;
|
||||
for (uint i = 0; i < MIDI_CHANNELS; ++i) {
|
||||
if (_channels[i].voices > _channels[i].mappedVoices) {
|
||||
int exceed = _channels[i].voices - _channels[i].mappedVoices;
|
||||
if (exceed > maxExceed) {
|
||||
maxExceed = exceed;
|
||||
maxExceedChan = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop voice on channel with highest excess if possible, otherwise stop
|
||||
// note on this channel.
|
||||
int stopChan = (maxExceed > 0) ? maxExceedChan : channel;
|
||||
|
||||
for (it = _voiceQueue.begin(); it != _voiceQueue.end(); ++it) {
|
||||
int voice = *it;
|
||||
if (_voices[voice].channel == stopChan) {
|
||||
voiceOff(voice);
|
||||
_voices[voice].channel = channel;
|
||||
return voice;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::queueMoveToBack(int voice) {
|
||||
_voiceQueue.remove(voice);
|
||||
_voiceQueue.push_back(voice);
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::noteOff(int channel, int note) {
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
if ((_voices[i].channel == channel) && (_voices[i].note == note)) {
|
||||
if (_channels[channel].holdPedal)
|
||||
_voices[i].isSustained = true;
|
||||
else
|
||||
voiceOff(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::voiceOn(int voice, int note, int velocity) {
|
||||
int channel = _voices[voice].channel;
|
||||
int patch = _channels[channel].patch;
|
||||
|
||||
_voices[voice].age = 0;
|
||||
++_channels[channel].voices;
|
||||
queueMoveToBack(voice);
|
||||
|
||||
if ((channel == 9) && _rhythmKeyMap) {
|
||||
patch = CLIP(note, 27, 88) + 101;
|
||||
}
|
||||
|
||||
// Set patch if different from current patch
|
||||
if (patch != _voices[voice].patch && _playSwitch)
|
||||
setPatch(voice, patch);
|
||||
|
||||
_voices[voice].velocity = velocity;
|
||||
setNote(voice, note, true);
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::voiceOff(int voice) {
|
||||
int channel = _voices[voice].channel;
|
||||
|
||||
_voices[voice].isSustained = false;
|
||||
setNote(voice, _voices[voice].note, 0);
|
||||
_voices[voice].note = -1;
|
||||
_voices[voice].age = 0;
|
||||
queueMoveToBack(voice);
|
||||
--_channels[channel].voices;
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::setNote(int voice, int note, bool key) {
|
||||
int channel = _voices[voice].channel;
|
||||
|
||||
if ((channel == 9) && _rhythmKeyMap)
|
||||
note = _rhythmKeyMap[CLIP(note, 27, 88) - 27];
|
||||
|
||||
_voices[voice].note = note;
|
||||
|
||||
int index = note << 2;
|
||||
uint16 pitchWheel = _channels[channel].pitchWheel;
|
||||
int sign;
|
||||
|
||||
if (pitchWheel == 0x2000) {
|
||||
pitchWheel = 0;
|
||||
sign = 0;
|
||||
} else if (pitchWheel > 0x2000) {
|
||||
pitchWheel -= 0x2000;
|
||||
sign = 1;
|
||||
} else {
|
||||
pitchWheel = 0x2000 - pitchWheel;
|
||||
sign = -1;
|
||||
}
|
||||
|
||||
pitchWheel /= 171;
|
||||
|
||||
if (sign == 1)
|
||||
index += pitchWheel;
|
||||
else
|
||||
index -= pitchWheel;
|
||||
|
||||
if (index > 0x1fc) // Limit to max MIDI note (<< 2)
|
||||
index = 0x1fc;
|
||||
|
||||
if (index < 0) // Not in SSCI
|
||||
index = 0;
|
||||
|
||||
int freq = adlibFreq[index % 48];
|
||||
|
||||
setRegister(0xA0 + voice, freq & 0xff);
|
||||
|
||||
int oct = index / 48;
|
||||
if (oct > 0)
|
||||
--oct;
|
||||
|
||||
if (oct > 7) // Not in SSCI
|
||||
oct = 7;
|
||||
|
||||
setRegister(0xB0 + voice, (key << 5) | (oct << 2) | (freq >> 8));
|
||||
setVelocity(voice);
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::setVelocity(int voice) {
|
||||
AdLibPatch &patch = _patches[_voices[voice].patch];
|
||||
int pan = _channels[_voices[voice].channel].pan;
|
||||
setVelocityReg(registerOffset[voice] + 3, calcVelocity(voice, 1), patch.op[1].kbScaleLevel, pan);
|
||||
|
||||
// In AM mode we need to set the level for both operators
|
||||
if (_patches[_voices[voice].patch].mod.algorithm == 1)
|
||||
setVelocityReg(registerOffset[voice], calcVelocity(voice, 0), patch.op[0].kbScaleLevel, pan);
|
||||
}
|
||||
|
||||
int MidiDriver_AdLib::calcVelocity(int voice, int op) {
|
||||
if (_isSCI0) {
|
||||
int velocity = _masterVolume;
|
||||
|
||||
if (velocity > 0)
|
||||
velocity += 3;
|
||||
|
||||
if (velocity > 15)
|
||||
velocity = 15;
|
||||
|
||||
int insVelocity;
|
||||
if (_channels[_voices[voice].channel].enableVelocity)
|
||||
insVelocity = _voices[voice].velocity;
|
||||
else
|
||||
insVelocity = 63 - _patches[_voices[voice].patch].op[op].totalLevel;
|
||||
|
||||
// Note: Later SCI0 has a static table that is close to this formula, but not exactly the same.
|
||||
// Early SCI0 does (velocity * (insVelocity / 15))
|
||||
return velocity * insVelocity / 15;
|
||||
} else {
|
||||
AdLibOperator &oper = _patches[_voices[voice].patch].op[op];
|
||||
int velocity = _channels[_voices[voice].channel].volume + 1;
|
||||
velocity = velocity * (velocityMap1[_voices[voice].velocity] + 1) / 64;
|
||||
velocity = velocity * (_masterVolume + 1) / 16;
|
||||
|
||||
if (--velocity < 0)
|
||||
velocity = 0;
|
||||
|
||||
return velocityMap2[velocity] * (63 - oper.totalLevel) / 63;
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::setVelocityReg(int regOffset, int velocity, int kbScaleLevel, int pan) {
|
||||
if (!_playSwitch)
|
||||
velocity = 0;
|
||||
|
||||
if (_stereo) {
|
||||
int velLeft = velocity;
|
||||
int velRight = velocity;
|
||||
|
||||
if (pan > 0x40)
|
||||
velLeft = velLeft * (0x7f - pan) / 0x3f;
|
||||
else if (pan < 0x40)
|
||||
velRight = velRight * pan / 0x40;
|
||||
|
||||
setRegister(0x40 + regOffset, (kbScaleLevel << 6) | (63 - velLeft), kLeftChannel);
|
||||
setRegister(0x40 + regOffset, (kbScaleLevel << 6) | (63 - velRight), kRightChannel);
|
||||
} else {
|
||||
setRegister(0x40 + regOffset, (kbScaleLevel << 6) | (63 - velocity));
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::setPatch(int voice, int patch) {
|
||||
if ((patch < 0) || ((uint)patch >= _patches.size())) {
|
||||
warning("ADLIB: Invalid patch %i requested", patch);
|
||||
// Substitute instrument 0
|
||||
patch = 0;
|
||||
}
|
||||
|
||||
_voices[voice].patch = patch;
|
||||
AdLibModulator &mod = _patches[patch].mod;
|
||||
|
||||
// Set the common settings for both operators
|
||||
setOperator(registerOffset[voice], _patches[patch].op[0]);
|
||||
setOperator(registerOffset[voice] + 3, _patches[patch].op[1]);
|
||||
|
||||
// Set the additional settings for the modulator
|
||||
byte algorithm = mod.algorithm ? 1 : 0;
|
||||
setRegister(0xC0 + voice, (mod.feedback << 1) | algorithm);
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::setOperator(int reg, AdLibOperator &op) {
|
||||
setRegister(0x40 + reg, (op.kbScaleLevel << 6) | op.totalLevel);
|
||||
setRegister(0x60 + reg, (op.attackRate << 4) | op.decayRate);
|
||||
setRegister(0x80 + reg, (op.sustainLevel << 4) | op.releaseRate);
|
||||
setRegister(0x20 + reg, (op.amplitudeMod << 7) | (op.vibrato << 6)
|
||||
| (op.envelopeType << 5) | (op.kbScaleRate << 4) | op.frequencyMult);
|
||||
setRegister(0xE0 + reg, op.waveForm);
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::setRegister(int reg, int value, int channels) {
|
||||
if (channels & kLeftChannel) {
|
||||
_opl->write(0x220, reg);
|
||||
_opl->write(0x221, value);
|
||||
}
|
||||
|
||||
if (_stereo) {
|
||||
if (channels & kRightChannel) {
|
||||
_opl->write(0x222, reg);
|
||||
_opl->write(0x223, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_AdLib::playSwitch(bool play) {
|
||||
_playSwitch = play;
|
||||
renewNotes(-1, play);
|
||||
}
|
||||
|
||||
bool MidiDriver_AdLib::loadResource(const SciSpan<const byte> &data) {
|
||||
const uint32 size = data.size();
|
||||
if (size != 1344 && size != 2690 && size != 5382) {
|
||||
error("ADLIB: Unsupported patch format (%u bytes)", size);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 48; i++)
|
||||
loadInstrument(data.subspan(28 * i));
|
||||
|
||||
if (size == 1344) {
|
||||
byte dummy[28] = {0};
|
||||
|
||||
// Only 48 instruments, add dummies
|
||||
for (int i = 0; i < 48; i++)
|
||||
loadInstrument(SciSpan<const byte>(dummy, sizeof(dummy)));
|
||||
} else if (size == 2690) {
|
||||
for (int i = 48; i < 96; i++)
|
||||
loadInstrument(data.subspan(2 + (28 * i)));
|
||||
} else {
|
||||
// SCI1.1 and later
|
||||
for (int i = 48; i < 190; i++) {
|
||||
loadInstrument(data.subspan(28 * i));
|
||||
}
|
||||
|
||||
_rhythmKeyMap->allocateFromSpan(data.subspan(5320, kRhythmKeys));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 MidiDriver_AdLib::property(int prop, uint32 param) {
|
||||
switch(prop) {
|
||||
case MIDI_PROP_MASTER_VOLUME:
|
||||
if (param != 0xffff)
|
||||
_masterVolume = param;
|
||||
return _masterVolume;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int MidiPlayer_AdLib::open(ResourceManager *resMan) {
|
||||
assert(resMan != nullptr);
|
||||
|
||||
// Load up the patch.003 file, parse out the instruments
|
||||
Resource *res = resMan->findResource(ResourceId(kResourceTypePatch, 3), false);
|
||||
bool ok = false;
|
||||
|
||||
if (res) {
|
||||
ok = static_cast<MidiDriver_AdLib *>(_driver)->loadResource(*res);
|
||||
} else {
|
||||
// Early SCI0 games have the sound bank embedded in the AdLib driver
|
||||
|
||||
Common::File f;
|
||||
|
||||
if (f.open("ADL.DRV")) {
|
||||
int size = f.size();
|
||||
const uint patchSize = 1344;
|
||||
|
||||
// Note: Funseeker's Guide also has another version of adl.drv, 8803 bytes.
|
||||
// This isn't supported, but it's not really used anywhere, as that demo
|
||||
// doesn't have sound anyway.
|
||||
if (size == 5684 || size == 5720 || size == 5727) {
|
||||
ok = f.seek(0x45a);
|
||||
if (ok) {
|
||||
Common::SpanOwner<SciSpan<const byte> > patchData;
|
||||
patchData->allocateFromStream(f, patchSize);
|
||||
ok = static_cast<MidiDriver_AdLib *>(_driver)->loadResource(*patchData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
warning("ADLIB: Failed to load patch.003");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return static_cast<MidiDriver_AdLib *>(_driver)->openAdLib();
|
||||
}
|
||||
|
||||
void MidiPlayer_AdLib::close() {
|
||||
if (_driver) {
|
||||
_driver->close();
|
||||
}
|
||||
}
|
||||
|
||||
byte MidiPlayer_AdLib::getPlayId() const {
|
||||
switch (_version) {
|
||||
case SCI_VERSION_0_EARLY:
|
||||
return 0x09;
|
||||
case SCI_VERSION_0_LATE:
|
||||
return 0x04;
|
||||
default:
|
||||
return 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
MidiPlayer *MidiPlayer_AdLib_create(SciVersion _soundVersion) {
|
||||
return new MidiPlayer_AdLib(_soundVersion);
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
930
engines/sci/sound/drivers/amigamac0.cpp
Normal file
930
engines/sci/sound/drivers/amigamac0.cpp
Normal file
@@ -0,0 +1,930 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// TODO: Original Mac driver allows the interpreter to play notes. Channel
|
||||
// is allocated via controller 0x4D. Find out if this is used. This would
|
||||
// allow for music and sfx to be played simultaneously.
|
||||
|
||||
// FIXME: SQ3, LSL2 and HOYLE1 for Amiga don't seem to load any
|
||||
// patches, even though patches are present. Later games do load
|
||||
// patches, but include disabled patches with a 'd' appended to the
|
||||
// filename, e.g. sound.010d. For SQ3, LSL2 and HOYLE1, we should
|
||||
// probably disable patch loading. Maybe the original interpreter
|
||||
// loads these disabled patches under some specific condition?
|
||||
|
||||
#include "sci/sound/drivers/mididriver.h"
|
||||
#include "sci/sound/drivers/macmixer.h"
|
||||
#include "sci/resource/resource.h"
|
||||
|
||||
#include "common/file.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/mutex.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/util.h"
|
||||
#include "audio/mods/paula.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
class MidiPlayer_AmigaMac0 : public MidiPlayer {
|
||||
public:
|
||||
enum {
|
||||
kVoices = 4,
|
||||
kBaseFreq = 60
|
||||
};
|
||||
|
||||
MidiPlayer_AmigaMac0(SciVersion version, Audio::Mixer *mixer, Common::Mutex &mutex);
|
||||
virtual ~MidiPlayer_AmigaMac0();
|
||||
|
||||
// MidiPlayer
|
||||
void close() override;
|
||||
void send(uint32 b) override;
|
||||
void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) override;
|
||||
uint32 getBaseTempo() override { return (1000000 + kBaseFreq / 2) / kBaseFreq; }
|
||||
byte getPlayId() const override { return 0x40; }
|
||||
int getPolyphony() const override { return kVoices; }
|
||||
bool hasRhythmChannel() const override { return false; }
|
||||
void setVolume(byte volume) override;
|
||||
int getVolume() override;
|
||||
void playSwitch(bool play) override;
|
||||
void initTrack(SciSpan<const byte> &trackData) override;
|
||||
|
||||
protected:
|
||||
bool _playSwitch;
|
||||
uint _masterVolume;
|
||||
|
||||
Audio::Mixer *_mixer;
|
||||
Audio::SoundHandle _mixerSoundHandle;
|
||||
Common::TimerManager::TimerProc _timerProc;
|
||||
void *_timerParam;
|
||||
bool _isOpen;
|
||||
|
||||
void freeInstruments();
|
||||
void onTimer();
|
||||
|
||||
struct Instrument {
|
||||
Instrument() :
|
||||
name(),
|
||||
loop(false),
|
||||
fixedNote(false),
|
||||
seg2Offset(0),
|
||||
seg3Offset(0),
|
||||
samples(nullptr),
|
||||
transpose(0),
|
||||
envelope() {}
|
||||
|
||||
~Instrument() { delete[] samples; }
|
||||
|
||||
char name[31];
|
||||
bool loop;
|
||||
bool fixedNote;
|
||||
uint32 seg2Offset;
|
||||
uint32 seg3Offset;
|
||||
const byte *samples;
|
||||
int16 transpose;
|
||||
|
||||
struct Envelope {
|
||||
byte skip;
|
||||
int8 step;
|
||||
byte target;
|
||||
} envelope[4];
|
||||
};
|
||||
|
||||
Common::Array<const Instrument *> _instruments;
|
||||
|
||||
class Voice {
|
||||
public:
|
||||
Voice(MidiPlayer_AmigaMac0 &driver, byte id) :
|
||||
_patch(0),
|
||||
_note(-1),
|
||||
_velocity(0),
|
||||
_pitch(0),
|
||||
_instrument(nullptr),
|
||||
_loop(false),
|
||||
_envState(0),
|
||||
_envCntDown(0),
|
||||
_envCurVel(0),
|
||||
_volume(0),
|
||||
_id(id),
|
||||
_driver(driver) {}
|
||||
|
||||
virtual ~Voice() {}
|
||||
|
||||
virtual void noteOn(int8 note, int8 velocity) = 0;
|
||||
virtual void noteOff(int8 note) = 0;
|
||||
virtual void setPitchWheel(int16 pitch) {}
|
||||
|
||||
virtual void stop() = 0;
|
||||
virtual void setEnvelopeVolume(byte volume) = 0;
|
||||
|
||||
void processEnvelope();
|
||||
|
||||
byte _patch;
|
||||
int8 _note;
|
||||
byte _velocity;
|
||||
uint16 _pitch;
|
||||
|
||||
const Instrument *_instrument;
|
||||
bool _loop;
|
||||
|
||||
byte _envState;
|
||||
byte _envCntDown;
|
||||
int8 _envCurVel;
|
||||
|
||||
byte _volume;
|
||||
byte _id;
|
||||
|
||||
private:
|
||||
MidiPlayer_AmigaMac0 &_driver;
|
||||
};
|
||||
|
||||
Common::Array<Voice *> _voices;
|
||||
typedef Common::Array<Voice *>::const_iterator VoiceIt;
|
||||
|
||||
Voice *_channels[MIDI_CHANNELS];
|
||||
|
||||
Common::Mutex &_mixMutex;
|
||||
Common::Mutex _timerMutex;
|
||||
};
|
||||
|
||||
MidiPlayer_AmigaMac0::MidiPlayer_AmigaMac0(SciVersion version, Audio::Mixer *mixer, Common::Mutex &mutex) :
|
||||
MidiPlayer(version),
|
||||
_playSwitch(true),
|
||||
_masterVolume(15),
|
||||
_mixer(mixer),
|
||||
_mixerSoundHandle(),
|
||||
_timerProc(),
|
||||
_timerParam(nullptr),
|
||||
_isOpen(false),
|
||||
_channels(),
|
||||
_mixMutex(mutex) {}
|
||||
|
||||
MidiPlayer_AmigaMac0::~MidiPlayer_AmigaMac0() {
|
||||
close();
|
||||
}
|
||||
|
||||
void MidiPlayer_AmigaMac0::close() {
|
||||
if (!_isOpen)
|
||||
return;
|
||||
|
||||
_mixer->stopHandle(_mixerSoundHandle);
|
||||
|
||||
for (uint ci = 0; ci < ARRAYSIZE(_channels); ++ci)
|
||||
_channels[ci] = nullptr;
|
||||
|
||||
for (VoiceIt v = _voices.begin(); v != _voices.end(); ++v)
|
||||
delete *v;
|
||||
_voices.clear();
|
||||
|
||||
freeInstruments();
|
||||
|
||||
_isOpen = false;
|
||||
}
|
||||
|
||||
void MidiPlayer_AmigaMac0::setVolume(byte volume) {
|
||||
Common::StackLock lock(_mixMutex);
|
||||
_masterVolume = CLIP<byte>(volume, 0, 15);
|
||||
}
|
||||
|
||||
int MidiPlayer_AmigaMac0::getVolume() {
|
||||
Common::StackLock lock(_mixMutex);
|
||||
return _masterVolume;
|
||||
}
|
||||
|
||||
void MidiPlayer_AmigaMac0::playSwitch(bool play) {
|
||||
Common::StackLock lock(_mixMutex);
|
||||
_playSwitch = play;
|
||||
}
|
||||
|
||||
void MidiPlayer_AmigaMac0::initTrack(SciSpan<const byte>& header) {
|
||||
if (!_isOpen)
|
||||
return;
|
||||
|
||||
uint8 readPos = 0;
|
||||
const uint8 caps = header.getInt8At(readPos++);
|
||||
|
||||
// We only implement the MIDI functionality here, samples are
|
||||
// handled by the generic sample code
|
||||
if (caps != 0)
|
||||
return;
|
||||
|
||||
Common::StackLock lock(_mixMutex);
|
||||
|
||||
uint vi = 0;
|
||||
|
||||
for (uint i = 0; i < 15; ++i) {
|
||||
readPos++;
|
||||
const uint8 flags = header.getInt8At(readPos++);
|
||||
|
||||
if ((flags & getPlayId()) && (vi < kVoices))
|
||||
_channels[i] = _voices[vi++];
|
||||
else
|
||||
_channels[i] = nullptr;
|
||||
}
|
||||
|
||||
_channels[15] = nullptr;
|
||||
|
||||
for (VoiceIt it = _voices.begin(); it != _voices.end(); ++it) {
|
||||
Voice *voice = *it;
|
||||
voice->stop();
|
||||
voice->_note = -1;
|
||||
voice->_envState = 0;
|
||||
voice->_pitch = 0x2000;
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_AmigaMac0::freeInstruments() {
|
||||
for (Common::Array<const Instrument *>::iterator it = _instruments.begin(); it != _instruments.end(); ++it)
|
||||
delete *it;
|
||||
|
||||
_instruments.clear();
|
||||
}
|
||||
|
||||
void MidiPlayer_AmigaMac0::onTimer() {
|
||||
_mixMutex.unlock();
|
||||
_timerMutex.lock();
|
||||
|
||||
if (_timerProc)
|
||||
(*_timerProc)(_timerParam);
|
||||
|
||||
_timerMutex.unlock();
|
||||
_mixMutex.lock();
|
||||
|
||||
for (VoiceIt it = _voices.begin(); it != _voices.end(); ++it)
|
||||
(*it)->processEnvelope();
|
||||
}
|
||||
|
||||
void MidiPlayer_AmigaMac0::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
|
||||
Common::StackLock lock(_timerMutex);
|
||||
_timerProc = timerProc;
|
||||
_timerParam = timerParam;
|
||||
}
|
||||
|
||||
void MidiPlayer_AmigaMac0::send(uint32 b) {
|
||||
Common::StackLock lock(_mixMutex);
|
||||
|
||||
byte command = b & 0xf0;
|
||||
byte channel = b & 0xf;
|
||||
byte op1 = (b >> 8) & 0xff;
|
||||
byte op2 = (b >> 16) & 0xff;
|
||||
|
||||
Voice *voice = _channels[channel];
|
||||
|
||||
if (!voice)
|
||||
return;
|
||||
|
||||
switch(command) {
|
||||
case 0x80:
|
||||
voice->noteOff(op1);
|
||||
break;
|
||||
case 0x90:
|
||||
voice->noteOn(op1, op2);
|
||||
break;
|
||||
case 0xb0:
|
||||
// Not in original driver
|
||||
if (op1 == 0x7b && voice->_note != -1 && voice->_envState < 4)
|
||||
voice->noteOff(voice->_note);
|
||||
break;
|
||||
case 0xc0:
|
||||
voice->_patch = op1;
|
||||
break;
|
||||
case 0xe0:
|
||||
voice->setPitchWheel((op2 << 7) | op1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_AmigaMac0::Voice::processEnvelope() {
|
||||
if (_envState == 0 || _envState == 3)
|
||||
return;
|
||||
|
||||
if (_envState == 6) {
|
||||
stop();
|
||||
_envState = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_envCntDown == 0) {
|
||||
const uint envIdx = (_envState > 3 ? _envState - 2 : _envState - 1);
|
||||
|
||||
_envCntDown = _instrument->envelope[envIdx].skip;
|
||||
int8 velocity = _envCurVel;
|
||||
|
||||
if (velocity <= 0) {
|
||||
stop();
|
||||
_envState = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (velocity > 63)
|
||||
velocity = 63;
|
||||
|
||||
if (!_driver._playSwitch)
|
||||
velocity = 0;
|
||||
|
||||
setEnvelopeVolume(velocity);
|
||||
|
||||
const int8 step = _instrument->envelope[envIdx].step;
|
||||
if (step < 0) {
|
||||
_envCurVel -= step;
|
||||
if (_envCurVel > _instrument->envelope[envIdx].target) {
|
||||
_envCurVel = _instrument->envelope[envIdx].target;
|
||||
++_envState;
|
||||
}
|
||||
} else {
|
||||
_envCurVel -= step;
|
||||
if (_envCurVel < _instrument->envelope[envIdx].target) {
|
||||
_envCurVel = _instrument->envelope[envIdx].target;
|
||||
++_envState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
--_envCntDown;
|
||||
}
|
||||
|
||||
class MidiPlayer_Mac0 : public Mixer_Mac<MidiPlayer_Mac0>, public MidiPlayer_AmigaMac0 {
|
||||
public:
|
||||
MidiPlayer_Mac0(SciVersion version, Audio::Mixer *mixer, Mode mode);
|
||||
|
||||
// MidiPlayer
|
||||
int open(ResourceManager *resMan) override;
|
||||
void setVolume(byte volume) override;
|
||||
|
||||
// MidiDriver
|
||||
void close() override;
|
||||
|
||||
// Mixer_Mac
|
||||
static int8 applyChannelVolume(byte volume, byte sample);
|
||||
void interrupt() { onTimer(); }
|
||||
void onChannelFinished(uint channel);
|
||||
|
||||
private:
|
||||
enum {
|
||||
kStepTableSize = 84
|
||||
};
|
||||
|
||||
template <Mode mode>
|
||||
void generateSamples(int16 *buf, int len);
|
||||
|
||||
struct MacInstrument : public Instrument {
|
||||
MacInstrument() :
|
||||
Instrument(),
|
||||
endOffset(0) {}
|
||||
|
||||
uint32 endOffset;
|
||||
};
|
||||
|
||||
class MacVoice : public Voice {
|
||||
public:
|
||||
MacVoice(MidiPlayer_Mac0 &driver, byte id) :
|
||||
Voice(driver, id),
|
||||
_macDriver(driver) {}
|
||||
|
||||
private:
|
||||
void noteOn(int8 note, int8 velocity) override;
|
||||
void noteOff(int8 note) override;
|
||||
|
||||
void stop() override;
|
||||
void setEnvelopeVolume(byte volume) override;
|
||||
|
||||
void calcVoiceStep();
|
||||
|
||||
MidiPlayer_Mac0 &_macDriver;
|
||||
};
|
||||
|
||||
bool loadInstruments(Common::SeekableReadStream &patch);
|
||||
|
||||
ufrac_t _stepTable[kStepTableSize];
|
||||
};
|
||||
|
||||
MidiPlayer_Mac0::MidiPlayer_Mac0(SciVersion version, Audio::Mixer *mixer, Mixer_Mac<MidiPlayer_Mac0>::Mode mode) :
|
||||
Mixer_Mac<MidiPlayer_Mac0>(mode),
|
||||
MidiPlayer_AmigaMac0(version, mixer, _mutex) {
|
||||
|
||||
for (uint i = 0; i < kStepTableSize; ++i)
|
||||
_stepTable[i] = round(0x2000 * pow(2.0, i / 12.0));
|
||||
}
|
||||
|
||||
int MidiPlayer_Mac0::open(ResourceManager *resMan) {
|
||||
if (_isOpen)
|
||||
return MidiDriver::MERR_ALREADY_OPEN;
|
||||
|
||||
Resource *patch = g_sci->getResMan()->findResource(ResourceId(kResourceTypePatch, 200), false);
|
||||
if (!patch) {
|
||||
warning("MidiPlayer_Mac0: Failed to open patch 200");
|
||||
return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
Common::MemoryReadStream stream(patch->toStream());
|
||||
if (!loadInstruments(stream)) {
|
||||
freeInstruments();
|
||||
return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
for (byte vi = 0; vi < kVoices; ++vi)
|
||||
_voices.push_back(new MacVoice(*this, vi));
|
||||
|
||||
startMixer();
|
||||
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO);
|
||||
|
||||
_isOpen = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MidiPlayer_Mac0::setVolume(byte volume) {
|
||||
MidiPlayer_AmigaMac0::setVolume(volume);
|
||||
setMixerVolume(volume / 2 + 1);
|
||||
}
|
||||
|
||||
void MidiPlayer_Mac0::close() {
|
||||
MidiPlayer_AmigaMac0::close();
|
||||
stopMixer();
|
||||
}
|
||||
|
||||
int8 MidiPlayer_Mac0::applyChannelVolume(byte volume, byte sample) {
|
||||
int8 signedSample = sample - 0x80;
|
||||
|
||||
if (volume == 0)
|
||||
return 0;
|
||||
|
||||
if (volume == 63)
|
||||
return signedSample;
|
||||
|
||||
if (signedSample >= 0)
|
||||
return (signedSample * volume + 32) / 64;
|
||||
else
|
||||
return ~((~signedSample * volume + 32) / 64);
|
||||
}
|
||||
|
||||
void MidiPlayer_Mac0::onChannelFinished(uint channel) {
|
||||
_voices[channel]->_envState = 0;
|
||||
}
|
||||
|
||||
void MidiPlayer_Mac0::MacVoice::stop() {
|
||||
_macDriver.resetChannel(_id);
|
||||
}
|
||||
|
||||
void MidiPlayer_Mac0::MacVoice::calcVoiceStep() {
|
||||
int8 note = _note;
|
||||
|
||||
if (_instrument->fixedNote)
|
||||
note = 72;
|
||||
|
||||
int16 index = note + _instrument->transpose;
|
||||
|
||||
index -= 24;
|
||||
|
||||
while (index < 0)
|
||||
index += 12;
|
||||
|
||||
while (index >= kStepTableSize)
|
||||
index -= 12;
|
||||
|
||||
_macDriver.setChannelStep(_id, _macDriver._stepTable[index]);
|
||||
}
|
||||
|
||||
void MidiPlayer_Mac0::MacVoice::setEnvelopeVolume(byte volume) {
|
||||
if (_macDriver._masterVolume == 0 || !_macDriver._playSwitch)
|
||||
volume = 0;
|
||||
_macDriver.setChannelVolume(_id, volume * _volume >> 6);
|
||||
}
|
||||
|
||||
void MidiPlayer_Mac0::MacVoice::noteOn(int8 note, int8 velocity) {
|
||||
if (velocity == 0) {
|
||||
noteOff(note);
|
||||
return;
|
||||
}
|
||||
|
||||
stop();
|
||||
_envState = 0;
|
||||
|
||||
if (!_macDriver._instruments[_patch]) // Not in original driver
|
||||
return;
|
||||
|
||||
_instrument = _macDriver._instruments[_patch];
|
||||
|
||||
_velocity = velocity;
|
||||
_volume = velocity >> 1;
|
||||
_envCurVel = 64;
|
||||
_envCntDown = 0;
|
||||
_loop = _instrument->loop;
|
||||
_note = note;
|
||||
|
||||
calcVoiceStep();
|
||||
|
||||
const MacInstrument *ins = static_cast<const MacInstrument *>(_instrument);
|
||||
|
||||
if (_loop) {
|
||||
_envState = 1;
|
||||
_macDriver.setChannelData(_id, ins->samples, 0, ins->seg3Offset, ins->seg3Offset - ins->seg2Offset);
|
||||
} else {
|
||||
_macDriver.setChannelData(_id, ins->samples, 0, ins->endOffset);
|
||||
}
|
||||
|
||||
setEnvelopeVolume(63);
|
||||
}
|
||||
|
||||
void MidiPlayer_Mac0::MacVoice::noteOff(int8 note) {
|
||||
if (_note == note) {
|
||||
if (_envState != 0) {
|
||||
_envState = 4;
|
||||
_envCntDown = 0;
|
||||
}
|
||||
// Original driver doesn't reset note anywhere that I could find,
|
||||
// but this seems like a good place to do that
|
||||
_note = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool MidiPlayer_Mac0::loadInstruments(Common::SeekableReadStream &patch) {
|
||||
char name[33];
|
||||
|
||||
if (patch.read(name, 8) < 8 || strncmp(name, "X1iUo123", 8) != 0) {
|
||||
warning("MidiPlayer_Mac0: Incorrect ID string in patch bank");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (patch.read(name, 32) < 32) {
|
||||
warning("MidiPlayer_Mac0: Error reading patch bank");
|
||||
return false;
|
||||
}
|
||||
name[32] = 0;
|
||||
|
||||
debugC(kDebugLevelSound, "Bank: '%s'", name);
|
||||
|
||||
_instruments.resize(128);
|
||||
|
||||
for (byte i = 0; i < 128; i++) {
|
||||
patch.seek(40 + i * 4);
|
||||
uint32 offset = patch.readUint32BE();
|
||||
|
||||
if (offset == 0) {
|
||||
_instruments[i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
patch.seek(offset);
|
||||
|
||||
MacInstrument *instrument = new MacInstrument();
|
||||
_instruments[i] = instrument;
|
||||
|
||||
patch.readUint16BE(); // index
|
||||
|
||||
const uint16 flags = patch.readUint16BE();
|
||||
instrument->loop = flags & 1;
|
||||
instrument->fixedNote = !(flags & 2);
|
||||
|
||||
instrument->seg2Offset = patch.readUint32BE();
|
||||
instrument->seg3Offset = patch.readUint32BE();
|
||||
instrument->endOffset = patch.readUint32BE();
|
||||
|
||||
instrument->transpose = patch.readUint16BE();
|
||||
|
||||
for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
|
||||
instrument->envelope[stage].skip = patch.readByte();
|
||||
|
||||
for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
|
||||
instrument->envelope[stage].step = patch.readByte();
|
||||
|
||||
// In the original, it uses the stage 0 step as the stage 3 target,
|
||||
// but we (most likely) don't have to replicate this bug.
|
||||
for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
|
||||
instrument->envelope[stage].target = patch.readByte();
|
||||
|
||||
patch.read(instrument->name, 30);
|
||||
instrument->name[30] = 0;
|
||||
|
||||
debugC(kDebugLevelSound, "\tInstrument[%d]: '%s'", i, instrument->name);
|
||||
debugC(kDebugLevelSound, "\t\tSegment offsets: %d, %d, %d", instrument->seg2Offset, instrument->seg3Offset, instrument->endOffset);
|
||||
debugC(kDebugLevelSound, "\t\tTranspose = %d, Fixed note = %d, Loop = %d", instrument->transpose, instrument->fixedNote, instrument->loop);
|
||||
debugC(kDebugLevelSound, "\t\tEnvelope:");
|
||||
for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
|
||||
debugC(kDebugLevelSound, "\t\t\tStage %d: skip %d, step %d, target %d", stage, instrument->envelope[stage].skip, instrument->envelope[stage].step, instrument->envelope[stage].target);
|
||||
|
||||
uint32 sampleSize = (instrument->loop ? instrument->seg3Offset : instrument->endOffset) + 1111;
|
||||
byte *samples = new byte[sampleSize];
|
||||
patch.read(samples, sampleSize);
|
||||
instrument->samples = samples;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class MidiPlayer_Amiga0 : public Audio::Paula, public MidiPlayer_AmigaMac0 {
|
||||
public:
|
||||
MidiPlayer_Amiga0(SciVersion version, Audio::Mixer *mixer);
|
||||
|
||||
// MidiPlayer
|
||||
int open(ResourceManager *resMan) override;
|
||||
|
||||
// MidiDriver
|
||||
void close() override;
|
||||
|
||||
// Audio::Paula
|
||||
void interrupt() override { onTimer(); }
|
||||
|
||||
private:
|
||||
struct AmigaInstrument : public Instrument {
|
||||
AmigaInstrument() :
|
||||
Instrument(),
|
||||
seg1Size(0),
|
||||
seg2Size(0),
|
||||
seg3Size(0) {}
|
||||
|
||||
int16 seg1Size;
|
||||
int16 seg2Size;
|
||||
int16 seg3Size;
|
||||
};
|
||||
|
||||
class AmigaVoice : public Voice {
|
||||
public:
|
||||
AmigaVoice(MidiPlayer_Amiga0 &driver, uint id) :
|
||||
Voice(driver, id),
|
||||
_amigaDriver(driver) {}
|
||||
|
||||
private:
|
||||
void noteOn(int8 note, int8 velocity) override;
|
||||
void noteOff(int8 note) override;
|
||||
void setPitchWheel(int16 pitch) override;
|
||||
|
||||
void stop() override;
|
||||
void setEnvelopeVolume(byte volume) override;
|
||||
|
||||
void calcVoiceStep();
|
||||
|
||||
MidiPlayer_Amiga0 &_amigaDriver;
|
||||
};
|
||||
|
||||
uint _defaultInstrument;
|
||||
bool _isEarlyDriver;
|
||||
|
||||
bool loadInstruments(Common::SeekableReadStream &patch);
|
||||
|
||||
uint16 _periodTable[333];
|
||||
};
|
||||
|
||||
MidiPlayer_Amiga0::MidiPlayer_Amiga0(SciVersion version, Audio::Mixer *mixer) :
|
||||
Audio::Paula(true, mixer->getOutputRate(), mixer->getOutputRate() / kBaseFreq),
|
||||
MidiPlayer_AmigaMac0(version, mixer, _mutex),
|
||||
_defaultInstrument(0),
|
||||
_isEarlyDriver(false) {
|
||||
|
||||
// These values are close, but not identical to the original
|
||||
for (int i = 0; i < ARRAYSIZE(_periodTable); ++i)
|
||||
_periodTable[i] = 3579545 / 20000.0 / pow(2.0, (i - 308) / 48.0);
|
||||
}
|
||||
|
||||
void MidiPlayer_Amiga0::AmigaVoice::setEnvelopeVolume(byte volume) {
|
||||
// Early games ignore note velocity for envelope-enabled notes
|
||||
if (_amigaDriver._isEarlyDriver)
|
||||
_amigaDriver.setChannelVolume(_id, volume * _amigaDriver._masterVolume >> 4);
|
||||
else
|
||||
_amigaDriver.setChannelVolume(_id, (volume * _amigaDriver._masterVolume >> 4) * _volume >> 6);
|
||||
}
|
||||
|
||||
int MidiPlayer_Amiga0::open(ResourceManager *resMan) {
|
||||
if (_isOpen)
|
||||
return MidiDriver::MERR_ALREADY_OPEN;
|
||||
|
||||
_isEarlyDriver = g_sci->getGameId() == GID_LSL2 || g_sci->getGameId() == GID_SQ3;
|
||||
|
||||
Common::File file;
|
||||
|
||||
if (!file.open("bank.001")) {
|
||||
warning("MidiPlayer_Amiga0: Failed to open bank.001");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadInstruments(file)) {
|
||||
freeInstruments();
|
||||
return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
for (byte vi = 0; vi < NUM_VOICES; ++vi)
|
||||
_voices.push_back(new AmigaVoice(*this, vi));
|
||||
|
||||
startPaula();
|
||||
// Enable reverse stereo to counteract Audio::Paula's reverse stereo
|
||||
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO, false, true);
|
||||
_isOpen = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MidiPlayer_Amiga0::close() {
|
||||
MidiPlayer_AmigaMac0::close();
|
||||
clearVoices();
|
||||
stopPaula();
|
||||
}
|
||||
|
||||
void MidiPlayer_Amiga0::AmigaVoice::stop() {
|
||||
_amigaDriver.clearVoice(_id);
|
||||
}
|
||||
|
||||
void MidiPlayer_Amiga0::AmigaVoice::calcVoiceStep() {
|
||||
int8 note = _note;
|
||||
|
||||
if (_instrument->fixedNote)
|
||||
note = 101;
|
||||
|
||||
int16 index = (note + _instrument->transpose) * 4;
|
||||
|
||||
if (_pitch >= 0x2000)
|
||||
index += (_pitch - 0x2000) / 171;
|
||||
else
|
||||
index -= (0x2000 - _pitch) / 171;
|
||||
|
||||
// For very high notes, the original driver reads out of bounds
|
||||
// (see e.g. SQ3 intro sequence). We compute the period for
|
||||
// these notes. The original hardware would not be able to
|
||||
// handle these very low periods, but Audio::Paula doesn't
|
||||
// seem to mind.
|
||||
|
||||
while (index < 96)
|
||||
index += 48;
|
||||
|
||||
index -= 96;
|
||||
|
||||
while (index >= ARRAYSIZE(_amigaDriver._periodTable))
|
||||
index -= 48;
|
||||
|
||||
_amigaDriver.setChannelPeriod(_id, _amigaDriver._periodTable[index]);
|
||||
}
|
||||
|
||||
void MidiPlayer_Amiga0::AmigaVoice::noteOn(int8 note, int8 velocity) {
|
||||
if (velocity == 0) {
|
||||
noteOff(note);
|
||||
return;
|
||||
}
|
||||
|
||||
_instrument = _amigaDriver._instruments[_patch];
|
||||
|
||||
// Default to the first instrument in the bank
|
||||
if (!_instrument)
|
||||
_instrument = _amigaDriver._instruments[_amigaDriver._defaultInstrument];
|
||||
|
||||
_velocity = velocity;
|
||||
_volume = velocity >> 1;
|
||||
_loop = _instrument->loop;
|
||||
_note = note;
|
||||
|
||||
stop();
|
||||
_envState = 0;
|
||||
|
||||
calcVoiceStep();
|
||||
|
||||
const AmigaInstrument *ins = static_cast<const AmigaInstrument *>(_instrument);
|
||||
|
||||
const int8 *seg1 = (const int8 *)ins->samples;
|
||||
const int8 *seg2 = seg1;
|
||||
int16 seg1Size = ins->seg1Size;
|
||||
seg2 += ins->seg2Offset & 0xfffe;
|
||||
int16 seg2Size = ins->seg2Size;
|
||||
|
||||
if (!_loop) {
|
||||
seg1Size = seg1Size + seg2Size + ins->seg3Size;
|
||||
seg2 = nullptr;
|
||||
seg2Size = 0;
|
||||
}
|
||||
|
||||
if (ins->envelope[0].skip != 0 && _loop) {
|
||||
_envCurVel = _volume;
|
||||
_envCntDown = 0;
|
||||
_envState = 1;
|
||||
}
|
||||
|
||||
_amigaDriver.setChannelData(_id, seg1, seg2, seg1Size * 2, seg2Size * 2);
|
||||
if (_amigaDriver._playSwitch)
|
||||
_amigaDriver.setChannelVolume(_id, _amigaDriver._masterVolume * _volume >> 4);
|
||||
}
|
||||
|
||||
void MidiPlayer_Amiga0::AmigaVoice::noteOff(int8 note) {
|
||||
if (_note == note) {
|
||||
if (_envState != 0) {
|
||||
_envCurVel = _instrument->envelope[1].target;
|
||||
_envState = 4;
|
||||
}
|
||||
// Original driver doesn't reset note anywhere that I could find,
|
||||
// but this seems like a good place to do that
|
||||
_note = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_Amiga0::AmigaVoice::setPitchWheel(int16 pitch) {
|
||||
if (_amigaDriver._isEarlyDriver)
|
||||
return;
|
||||
|
||||
_pitch = pitch;
|
||||
|
||||
if (_note != -1)
|
||||
calcVoiceStep();
|
||||
}
|
||||
|
||||
bool MidiPlayer_Amiga0::loadInstruments(Common::SeekableReadStream &patch) {
|
||||
char name[31];
|
||||
|
||||
if (patch.read(name, 8) < 8 || strncmp(name, "X0iUo123", 8) != 0) {
|
||||
warning("MidiPlayer_Amiga0: Incorrect ID string in patch bank");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (patch.read(name, 30) < 30) {
|
||||
warning("MidiPlayer_Amiga0: Error reading patch bank");
|
||||
return false;
|
||||
}
|
||||
name[30] = 0;
|
||||
|
||||
debugC(kDebugLevelSound, "Bank: '%s'", name);
|
||||
|
||||
_instruments.resize(128);
|
||||
|
||||
const uint16 instrumentCount = patch.readUint16BE();
|
||||
|
||||
if (instrumentCount == 0) {
|
||||
warning("MidiPlayer_Amiga0: No instruments found in patch bank");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < instrumentCount; ++i) {
|
||||
AmigaInstrument *instrument = new AmigaInstrument();
|
||||
|
||||
const uint16 patchIdx = patch.readUint16BE();
|
||||
_instruments[patchIdx] = instrument;
|
||||
|
||||
if (i == 0)
|
||||
_defaultInstrument = patchIdx;
|
||||
|
||||
patch.read(instrument->name, 30);
|
||||
instrument->name[30] = 0;
|
||||
const uint16 flags = patch.readUint16BE();
|
||||
instrument->loop = flags & 1;
|
||||
instrument->fixedNote = !(flags & 2);
|
||||
instrument->transpose = patch.readSByte();
|
||||
instrument->seg1Size = patch.readSint16BE();
|
||||
instrument->seg2Offset = patch.readUint32BE();
|
||||
instrument->seg2Size = patch.readSint16BE();
|
||||
instrument->seg3Offset = patch.readUint32BE();
|
||||
instrument->seg3Size = patch.readSint16BE();
|
||||
|
||||
// There's some envelope-related bugs here in the original, these were not replicated
|
||||
for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
|
||||
instrument->envelope[stage].skip = patch.readByte();
|
||||
|
||||
for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
|
||||
instrument->envelope[stage].step = patch.readByte();
|
||||
|
||||
for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
|
||||
instrument->envelope[stage].target = patch.readByte();
|
||||
|
||||
int32 sampleSize = instrument->seg1Size + instrument->seg2Size + instrument->seg3Size;
|
||||
sampleSize <<= 1;
|
||||
byte *samples = new byte[sampleSize];
|
||||
patch.read(samples, sampleSize);
|
||||
instrument->samples = samples;
|
||||
|
||||
if (patch.eos() || patch.err()) {
|
||||
warning("MidiPlayer_Amiga0: Error reading patch bank");
|
||||
return false;
|
||||
}
|
||||
|
||||
debugC(kDebugLevelSound, "\tInstrument[%d]: '%s'", patchIdx, instrument->name);
|
||||
debugC(kDebugLevelSound, "\t\tSegment 1: offset 0, size %d", instrument->seg1Size * 2);
|
||||
debugC(kDebugLevelSound, "\t\tSegment 2: offset %d, size %d", instrument->seg2Offset, instrument->seg2Size * 2);
|
||||
debugC(kDebugLevelSound, "\t\tSegment 3: offset %d, size %d", instrument->seg3Offset, instrument->seg3Size * 2);
|
||||
debugC(kDebugLevelSound, "\t\tTranspose = %d, Fixed note = %d, Loop = %d", instrument->transpose, instrument->fixedNote, instrument->loop);
|
||||
debugC(kDebugLevelSound, "\t\tEnvelope:");
|
||||
for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
|
||||
debugC(kDebugLevelSound, "\t\t\tStage %d: skip %d, step %d, target %d", stage, instrument->envelope[stage].skip, instrument->envelope[stage].step, instrument->envelope[stage].target);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MidiPlayer *MidiPlayer_AmigaMac0_create(SciVersion version, Common::Platform platform) {
|
||||
if (platform == Common::kPlatformMacintosh)
|
||||
return new MidiPlayer_Mac0(version, g_system->getMixer(), MidiPlayer_Mac0::kModeHq);
|
||||
else
|
||||
return new MidiPlayer_Amiga0(version, g_system->getMixer());
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
1317
engines/sci/sound/drivers/amigamac1.cpp
Normal file
1317
engines/sci/sound/drivers/amigamac1.cpp
Normal file
File diff suppressed because it is too large
Load Diff
496
engines/sci/sound/drivers/casio.cpp
Normal file
496
engines/sci/sound/drivers/casio.cpp
Normal file
@@ -0,0 +1,496 @@
|
||||
/* 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 "sci/sound/drivers/mididriver.h"
|
||||
|
||||
#include "audio/casio.h"
|
||||
|
||||
#include "sci/resource/resource.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
class MidiDriver_Casio : public ::MidiDriver_Casio {
|
||||
protected:
|
||||
// The instrument number used for the slap bass instrument on MT-540.
|
||||
static const uint8 SLAP_BASS_INSTRUMENT_MT540;
|
||||
// The instrument number used for the slap bass instrument on CT-460 and
|
||||
// CSM-1.
|
||||
static const uint8 SLAP_BASS_INSTRUMENT_CT460;
|
||||
static const uint8 PATCH_RESOURCE_SIZE;
|
||||
|
||||
public:
|
||||
MidiDriver_Casio(MusicType midiType) : ::MidiDriver_Casio(midiType),
|
||||
_highSplitInstOutputChannel(-1), _rhythmChannelMapped(false), _playSwitch(true) {
|
||||
Common::fill(_instrumentRemapping, _instrumentRemapping + ARRAYSIZE(_instrumentRemapping), 0);
|
||||
setInstrumentRemapping(_instrumentRemapping);
|
||||
_rhythmNoteRemapping = new byte[128];
|
||||
|
||||
Common::fill(_instrumentFixedNotes, _instrumentFixedNotes + ARRAYSIZE(_instrumentFixedNotes), 0);
|
||||
Common::fill(_channelMap, _channelMap + ARRAYSIZE(_channelMap), 0);
|
||||
Common::fill(_channelFixedNotes, _channelFixedNotes + ARRAYSIZE(_channelFixedNotes), 0);
|
||||
|
||||
_sendUntrackedNoteOff = false;
|
||||
}
|
||||
~MidiDriver_Casio() {
|
||||
delete[] _rhythmNoteRemapping;
|
||||
}
|
||||
|
||||
bool loadResource(const SciSpan<const byte> &data, MusicType midiType = MT_AUTO);
|
||||
void initTrack(SciSpan<const byte> &header);
|
||||
|
||||
void playSwitch(bool play);
|
||||
|
||||
protected:
|
||||
void noteOn(byte outputChannel, byte note, byte velocity, int8 source) override;
|
||||
void programChange(byte outputChannel, byte patchId, int8 source, bool applyRemapping = true) override;
|
||||
void programChange(byte outputChannel, byte patchId, int8 source, bool applyRemapping, bool applyBassSwap);
|
||||
|
||||
int8 mapSourceChannel(uint8 source, uint8 dataChannel) override;
|
||||
byte mapInstrument(byte program, bool applyRemapping) override;
|
||||
int8 mapNote(byte outputChannel, byte note) override;
|
||||
bool isRhythmChannel(uint8 outputChannel) override;
|
||||
byte calculateVelocity(int8 source, byte velocity) override;
|
||||
|
||||
byte _instrumentRemapping[128];
|
||||
// If > 0, a fixed note value should be played for the corresponding
|
||||
// instrument instead of the MIDI event note value.
|
||||
byte _instrumentFixedNotes[0x60];
|
||||
// Tracks the output channel which is currently being used by the "high"
|
||||
// split bass instrument (if any). Will be either -1 or 2.
|
||||
int8 _highSplitInstOutputChannel;
|
||||
|
||||
int8 _channelMap[16];
|
||||
// True if the rhythm channel has been mapped to output channel 3.
|
||||
bool _rhythmChannelMapped;
|
||||
// The fixed note that needs to be played on each output channel instead of
|
||||
// the MIDI event note value (or 0 if there is no fixed note).
|
||||
byte _channelFixedNotes[4];
|
||||
bool _playSwitch;
|
||||
};
|
||||
|
||||
class MidiPlayer_Casio : public MidiPlayer {
|
||||
public:
|
||||
static const uint8 RESOURCE_HEADER_FLAG;
|
||||
|
||||
protected:
|
||||
static const byte PATCH_RESOURCE_MT540;
|
||||
static const byte PATCH_RESOURCE_CT460;
|
||||
|
||||
public:
|
||||
MidiPlayer_Casio(SciVersion soundVersion, MusicType midiType);
|
||||
~MidiPlayer_Casio() override;
|
||||
|
||||
int open(ResourceManager *resMan) override;
|
||||
void close() override;
|
||||
|
||||
byte getPlayId() const override;
|
||||
int getPolyphony() const override;
|
||||
bool hasRhythmChannel() const override;
|
||||
void setVolume(byte volume) override;
|
||||
void playSwitch(bool play) override;
|
||||
void initTrack(SciSpan<const byte> &header) override;
|
||||
int getLastChannel() const override;
|
||||
|
||||
void send(uint32 b) override;
|
||||
|
||||
protected:
|
||||
MidiDriver_Casio *_casioDriver;
|
||||
MusicType _midiType;
|
||||
};
|
||||
|
||||
const uint8 MidiDriver_Casio::SLAP_BASS_INSTRUMENT_MT540 = 0x14;
|
||||
const uint8 MidiDriver_Casio::SLAP_BASS_INSTRUMENT_CT460 = 0x1E;
|
||||
|
||||
const uint8 MidiDriver_Casio::PATCH_RESOURCE_SIZE = 0xE9;
|
||||
|
||||
bool MidiDriver_Casio::loadResource(const SciSpan<const byte> &data, MusicType midiType) {
|
||||
if (midiType != MT_AUTO) {
|
||||
if (!(midiType == MT_MT540 || midiType == MT_CT460)) {
|
||||
error("CASIO: Unsupported music data type %i", midiType);
|
||||
}
|
||||
_midiType = midiType;
|
||||
}
|
||||
|
||||
const uint32 size = data.size();
|
||||
if (size != PATCH_RESOURCE_SIZE) {
|
||||
error("CASIO: Unsupported patch format (%u bytes)", size);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 dataIndex = 0;
|
||||
for (int i = 0; i < 0x60; i++) {
|
||||
_instrumentRemapping[i] = data.getUint8At(dataIndex++);
|
||||
_instrumentFixedNotes[i] = data.getUint8At(dataIndex++);
|
||||
}
|
||||
for (int i = 0; i < 0x29; i++) {
|
||||
_rhythmNoteRemapping[0x23 + i] = data.getUint8At(dataIndex++);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MidiDriver_Casio::initTrack(SciSpan<const byte> &header) {
|
||||
if (!_isOpen)
|
||||
return;
|
||||
|
||||
Common::fill(_channelMap, _channelMap + ARRAYSIZE(_channelMap), -1);
|
||||
Common::fill(_rhythmChannel, _rhythmChannel + ARRAYSIZE(_rhythmChannel), false);
|
||||
Common::fill(_channelFixedNotes, _channelFixedNotes + ARRAYSIZE(_channelFixedNotes), 0);
|
||||
_rhythmChannelMapped = false;
|
||||
|
||||
uint8 readPos = 0;
|
||||
uint8 caps = header.getInt8At(readPos++);
|
||||
if (caps != 0 && caps != 2)
|
||||
// Not a supported sound resource type.
|
||||
return;
|
||||
|
||||
uint8 numChannels = 16;
|
||||
if (caps == 2)
|
||||
// Digital sound data on channel 15; don't use this channel.
|
||||
numChannels--;
|
||||
|
||||
byte outputChannel = 0;
|
||||
for (int i = 0; i < numChannels; i++) {
|
||||
bool rhythmChannel = ((header.getInt8At(readPos++) & 0x80) > 0);
|
||||
bool deviceFlag = ((header.getInt8At(readPos++) & MidiPlayer_Casio::RESOURCE_HEADER_FLAG) > 0);
|
||||
if (!deviceFlag)
|
||||
// Data channel is not used for Casio devices.
|
||||
continue;
|
||||
|
||||
if (rhythmChannel) {
|
||||
if (!_rhythmChannelMapped) {
|
||||
if (outputChannel == 4) {
|
||||
// The rhythm channel has already been assigned to a melodic
|
||||
// instrument. This means that more than 4 channels have
|
||||
// been flagged for Casio, which should not happen, but
|
||||
// clear the existing channel mapping just in case.
|
||||
for (int j = 0; j < numChannels; j++) {
|
||||
if (_channelMap[j] == 3)
|
||||
_channelMap[j] = -1;
|
||||
}
|
||||
}
|
||||
_channelMap[i] = 3;
|
||||
programChange(3, _midiType == MusicType::MT_MT540 ? RHYTHM_INSTRUMENT_MT540 : RHYTHM_INSTRUMENT_CT460, 0, false);
|
||||
_rhythmChannelMapped = true;
|
||||
}
|
||||
} else if (outputChannel < (_rhythmChannelMapped ? 3 : 4)) {
|
||||
_channelMap[i] = outputChannel++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_Casio::playSwitch(bool play) {
|
||||
_playSwitch = play;
|
||||
if (!_playSwitch)
|
||||
stopAllNotes(0xFF, 0xFF);
|
||||
}
|
||||
|
||||
void MidiDriver_Casio::noteOn(byte outputChannel, byte note, byte velocity, int8 source) {
|
||||
if (velocity == 0) {
|
||||
// Note on with velocity 0 is a note off.
|
||||
noteOff(outputChannel, MIDI_COMMAND_NOTE_ON, note, velocity, source);
|
||||
return;
|
||||
}
|
||||
|
||||
_mutex.lock();
|
||||
|
||||
// Check if there is an available voice for this note.
|
||||
int polyphonyCount = 0;
|
||||
for (int i = 0; i < ARRAYSIZE(_activeNotes); i++) {
|
||||
// Note that this check ignores sustained notes; original driver does
|
||||
// this too.
|
||||
if (_activeNotes[i].channel == outputChannel && !_activeNotes[i].sustained) {
|
||||
polyphonyCount++;
|
||||
}
|
||||
}
|
||||
if (polyphonyCount >= CASIO_CHANNEL_POLYPHONY[outputChannel]) {
|
||||
// Maximum channel polyphony has been reached. Don't play this note.
|
||||
_mutex.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
::MidiDriver_Casio::noteOn(outputChannel, note, velocity, source);
|
||||
|
||||
_mutex.unlock();
|
||||
}
|
||||
|
||||
void MidiDriver_Casio::programChange(byte outputChannel, byte patchId, int8 source, bool applyRemapping) {
|
||||
programChange(outputChannel, patchId, source, applyRemapping, true);
|
||||
}
|
||||
|
||||
void MidiDriver_Casio::programChange(byte outputChannel, byte patchId, int8 source, bool applyRemapping, bool applyBassSwap) {
|
||||
if ((_rhythmChannelMapped && outputChannel == 3) || outputChannel >= 4)
|
||||
// Ignore program change on the rhythm channel or on unused channels.
|
||||
return;
|
||||
|
||||
// Apply instrument mapping.
|
||||
byte mappedInstrument = mapInstrument(patchId, applyRemapping);
|
||||
|
||||
// The Casio devices have an instrument (at 0x12 / 0x1C) which combines two
|
||||
// different bass instruments with a split note range. SCI assigns a
|
||||
// separate number to the slap bass instrument, which is on the "high" note
|
||||
// range (0x14 / 0x1E). Check for this number.
|
||||
if (mappedInstrument == (_deviceType == MT_MT540 ? SLAP_BASS_INSTRUMENT_MT540 : SLAP_BASS_INSTRUMENT_CT460)) {
|
||||
// The "high" split instrument (slap bass) has been selected.
|
||||
// Set the channel using this instrument so notes can be remapped to
|
||||
// the correct range.
|
||||
_highSplitInstOutputChannel = 2; // Output channel is set to 2 below.
|
||||
// Set the actual instrument number used by the Casio devices.
|
||||
mappedInstrument = (_deviceType == MT_MT540 ? BASS_INSTRUMENT_MT540 : BASS_INSTRUMENT_CT460);
|
||||
} else if (_highSplitInstOutputChannel == outputChannel) {
|
||||
// The instrument on this channel is changed from the "high" split
|
||||
// instrument to a different instrument. Reset the output channel
|
||||
// variable.
|
||||
_highSplitInstOutputChannel = -1;
|
||||
}
|
||||
|
||||
// If the bass instrument is set on any channel, SCI always moves this
|
||||
// instrument to output channel 2. This is probably because the Casio
|
||||
// devices have a limited fixed polyphony on each channel: 6, 4,
|
||||
// 2 and 4 respectively. Moving the bass to channel 2 overcomes this
|
||||
// limitation somewhat, because this channel has the lowest polyphony and
|
||||
// the bass doesn't tend to play chords.
|
||||
// Check if the bass instrument is set to a channel other than 2, and
|
||||
// move it to channel 2 if necessary.
|
||||
if (applyBassSwap && mappedInstrument == (_deviceType == MT_MT540 ? BASS_INSTRUMENT_MT540 : BASS_INSTRUMENT_CT460) && outputChannel != 2) {
|
||||
_mutex.lock();
|
||||
|
||||
int currentDataChannel = -1;
|
||||
int currentTargetDataChannel = -1;
|
||||
for (int i = 0; i < MIDI_CHANNEL_COUNT; i++) {
|
||||
if (_channelMap[i] == outputChannel) {
|
||||
currentDataChannel = i;
|
||||
} else if (_channelMap[i] == 2) {
|
||||
currentTargetDataChannel = i;
|
||||
}
|
||||
}
|
||||
|
||||
// These data channels should always be mapped, but check it just in case.
|
||||
if (currentDataChannel >= 0 && currentTargetDataChannel >= 0) {
|
||||
// The original drivers do not stop all notes before swapping channels.
|
||||
// This could potentially cause hanging notes, so this is done here to
|
||||
// be safe. Instead, the original drivers swap out the registered
|
||||
// active notes between the channels. This does not accomplish anything
|
||||
// other than putting the driver state out of sync with the device
|
||||
// state.
|
||||
stopAllNotes(source, outputChannel);
|
||||
stopAllNotes(source, 2);
|
||||
|
||||
_channelMap[currentDataChannel] = 2;
|
||||
_channelMap[currentTargetDataChannel] = outputChannel;
|
||||
|
||||
programChange(outputChannel, _instruments[2], source, applyRemapping, false);
|
||||
|
||||
outputChannel = 2;
|
||||
}
|
||||
|
||||
_mutex.unlock();
|
||||
}
|
||||
|
||||
// Register the new instrument.
|
||||
_instruments[outputChannel] = patchId;
|
||||
|
||||
_channelFixedNotes[outputChannel] = (patchId < ARRAYSIZE(_instrumentFixedNotes) ? _instrumentFixedNotes[patchId] : 0);
|
||||
|
||||
_rhythmChannel[outputChannel] =
|
||||
mappedInstrument == (_deviceType == MT_MT540 ? RHYTHM_INSTRUMENT_MT540 : RHYTHM_INSTRUMENT_CT460);
|
||||
|
||||
_driver->send(MIDI_COMMAND_PROGRAM_CHANGE | outputChannel | (mappedInstrument << 8));
|
||||
}
|
||||
|
||||
int8 MidiDriver_Casio::mapSourceChannel(uint8 source, uint8 dataChannel) {
|
||||
// Only source 0 is used by this driver.
|
||||
return _channelMap[dataChannel];
|
||||
}
|
||||
|
||||
byte MidiDriver_Casio::mapInstrument(byte program, bool applyRemapping) {
|
||||
byte mappedInstrument = ::MidiDriver_Casio::mapInstrument(program, applyRemapping);
|
||||
|
||||
if (applyRemapping) {
|
||||
// Correct remapping of the extra SCI slap bass instrument.
|
||||
if (_midiType == MT_MT540 && _deviceType == MT_CT460 &&
|
||||
mappedInstrument == INSTRUMENT_REMAPPING_MT540_TO_CT460[SLAP_BASS_INSTRUMENT_MT540]) {
|
||||
// For the MT-540, SCI uses 0x14 as the slap bass instrument, which
|
||||
// actually is the honky-tonk piano. If the instrument has been mapped
|
||||
// to the CT-460 honky-tonk piano, correct it to the CT-460 SCI slap
|
||||
// bass.
|
||||
mappedInstrument = SLAP_BASS_INSTRUMENT_CT460;
|
||||
} else if (_midiType == MT_CT460 && _deviceType == MT_MT540 && mappedInstrument == SLAP_BASS_INSTRUMENT_CT460) {
|
||||
// For the CT-460, SCI uses 0x1E as the slap bass instrument, which
|
||||
// is unused, so it will not be remapped. Manually remap it here to
|
||||
// the MT-540 SCI slap bass instrument.
|
||||
mappedInstrument = SLAP_BASS_INSTRUMENT_MT540;
|
||||
}
|
||||
}
|
||||
|
||||
return mappedInstrument;
|
||||
}
|
||||
|
||||
int8 MidiDriver_Casio::mapNote(byte outputChannel, byte note) {
|
||||
if (!isRhythmChannel(outputChannel) && outputChannel < 4) {
|
||||
if (_highSplitInstOutputChannel == outputChannel) {
|
||||
// The slap bass instrument has been set on this output channel.
|
||||
// Transpose the note up to the range used by this instrument.
|
||||
byte transposedNote = note + 0x18;
|
||||
if (transposedNote < 0x3C)
|
||||
transposedNote += 0xC;
|
||||
return transposedNote;
|
||||
}
|
||||
|
||||
// Check if the MIDI event note should be replaced by a fixed note.
|
||||
byte fixedNote = _channelFixedNotes[outputChannel];
|
||||
return fixedNote > 0 ? fixedNote : note;
|
||||
}
|
||||
|
||||
// Apply rhythm note mapping.
|
||||
return ::MidiDriver_Casio::mapNote(outputChannel, note);
|
||||
}
|
||||
|
||||
bool MidiDriver_Casio::isRhythmChannel(uint8 outputChannel) {
|
||||
// SCI only uses channel 3 as the rhythm channel. If the drum instrument is
|
||||
// set on a different channel, it is for a sound effect and rhythm note
|
||||
// remapping should not be applied.
|
||||
return _rhythmChannelMapped && outputChannel == 3;
|
||||
}
|
||||
|
||||
byte MidiDriver_Casio::calculateVelocity(int8 source, byte velocity) {
|
||||
if (!_playSwitch)
|
||||
return 0;
|
||||
|
||||
return ::MidiDriver_Casio::calculateVelocity(source, velocity);
|
||||
}
|
||||
|
||||
const uint8 MidiPlayer_Casio::RESOURCE_HEADER_FLAG = 0x08;
|
||||
|
||||
const byte MidiPlayer_Casio::PATCH_RESOURCE_MT540 = 4;
|
||||
const byte MidiPlayer_Casio::PATCH_RESOURCE_CT460 = 7;
|
||||
|
||||
MidiPlayer_Casio::MidiPlayer_Casio(SciVersion soundVersion, MusicType midiType) : MidiPlayer(soundVersion) {
|
||||
_casioDriver = new MidiDriver_Casio(midiType);
|
||||
_driver = _casioDriver;
|
||||
_midiType = midiType;
|
||||
}
|
||||
|
||||
MidiPlayer_Casio::~MidiPlayer_Casio() {
|
||||
delete _casioDriver;
|
||||
_casioDriver = nullptr;
|
||||
_driver = nullptr;
|
||||
}
|
||||
|
||||
int MidiPlayer_Casio::open(ResourceManager* resMan) {
|
||||
if (_version < SCI_VERSION_0_LATE || _version > SCI_VERSION_01) {
|
||||
warning("CASIO: Unsupported SCI version");
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert(resMan != nullptr);
|
||||
|
||||
// WORKAROUND The Casio devices have a bass instrument which combines two
|
||||
// instruments on different note ranges. To make the second instrument
|
||||
// (slap bass) selectable, SCI assigns a new instrument number to this
|
||||
// instrument. On the MT-540 Sierra used instrument 0x14 (20), probably
|
||||
// because they thought this was the first unused instrument number.
|
||||
// However, besides the 20 instruments selectable on the keyboard, the
|
||||
// device has 10 more instruments which can only be selected via MIDI.
|
||||
// The result of this is that the instrument which actually uses number
|
||||
// 0x14 on the MT-540 (honky-tonk piano) cannot be used by the instrument
|
||||
// mapping. Sierra worked around this by using the normal piano instead of
|
||||
// the honky-tonk piano for the MT-540. This affects at least Hoyle 1.
|
||||
// The CT-460 and CSM-1 are not affected by this issue because Sierra used
|
||||
// instrument 0x1E (30) as the slap bass instrument. To fix this problem,
|
||||
// the CT-460 instrument mapping is also used for the MT-540, with the
|
||||
// output instruments remapped to their MT-540 equivalents.
|
||||
|
||||
// Load the CT-460 patch resource.
|
||||
int patchResource = PATCH_RESOURCE_CT460;
|
||||
_midiType = MusicType::MT_CT460;
|
||||
Resource *res = resMan->findResource(ResourceId(kResourceTypePatch, patchResource), false);
|
||||
bool ok = false;
|
||||
|
||||
if (res) {
|
||||
ok = _casioDriver->loadResource(*res, _midiType);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
// CT-460 patch resource not present. Fall back to the MT-540 resource.
|
||||
MusicType altMidiType = MT_MT540;
|
||||
int altPatchResource = PATCH_RESOURCE_CT460;
|
||||
warning("CASIO: Failed to load patch.00%i - falling back to patch.00%i", patchResource, altPatchResource);
|
||||
res = resMan->findResource(ResourceId(kResourceTypePatch, altPatchResource), false);
|
||||
|
||||
if (res) {
|
||||
ok = _casioDriver->loadResource(*res, altMidiType);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
warning("CASIO: Failed to load fallback patch.00%i", altPatchResource);
|
||||
return -1;
|
||||
}
|
||||
|
||||
_midiType = altMidiType;
|
||||
}
|
||||
|
||||
return _casioDriver->open();
|
||||
}
|
||||
|
||||
void MidiPlayer_Casio::close() {
|
||||
_driver->close();
|
||||
}
|
||||
|
||||
byte MidiPlayer_Casio::getPlayId() const {
|
||||
return RESOURCE_HEADER_FLAG;
|
||||
}
|
||||
|
||||
int MidiPlayer_Casio::getPolyphony() const {
|
||||
return 16;
|
||||
}
|
||||
|
||||
bool MidiPlayer_Casio::hasRhythmChannel() const {
|
||||
// Only use the rhythm channel if it has the Casio flag set.
|
||||
return false;
|
||||
}
|
||||
|
||||
void MidiPlayer_Casio::setVolume(byte volume) {
|
||||
_casioDriver->setSourceVolume(0, volume);
|
||||
}
|
||||
|
||||
void MidiPlayer_Casio::playSwitch(bool play) {
|
||||
_casioDriver->playSwitch(play);
|
||||
}
|
||||
|
||||
void MidiPlayer_Casio::initTrack(SciSpan<const byte> &header) {
|
||||
_casioDriver->initTrack(header);
|
||||
}
|
||||
|
||||
int MidiPlayer_Casio::getLastChannel() const {
|
||||
// Not relevant for SCI0.
|
||||
return 15;
|
||||
}
|
||||
|
||||
void MidiPlayer_Casio::send(uint32 b) {
|
||||
_driver->send(0, b);
|
||||
}
|
||||
|
||||
MidiPlayer *MidiPlayer_Casio_create(SciVersion version, MusicType midiType) {
|
||||
return new MidiPlayer_Casio(version, midiType);
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
1375
engines/sci/sound/drivers/cms.cpp
Normal file
1375
engines/sci/sound/drivers/cms.cpp
Normal file
File diff suppressed because it is too large
Load Diff
809
engines/sci/sound/drivers/fb01.cpp
Normal file
809
engines/sci/sound/drivers/fb01.cpp
Normal file
@@ -0,0 +1,809 @@
|
||||
/* 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 "sci/sci.h"
|
||||
|
||||
#include "sci/resource/resource.h"
|
||||
#include "sci/sound/drivers/mididriver.h"
|
||||
#include "sci/util.h"
|
||||
|
||||
#include "common/file.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/mutex.h"
|
||||
|
||||
// The original driver uses the master volume setting of the hardware device by sending sysex messages
|
||||
// (applies to SCI0 and SCI1). We have a software mastervolume implementation instead. I don't know the
|
||||
// reason for this. Nor do I know whether our software effective volume calculation matches the device's
|
||||
// internal volume calculation algorithm.
|
||||
// I add the original style master volume handling, but make it optional via a #define
|
||||
#define HARDWARE_MASTERVOLUME
|
||||
|
||||
namespace Sci {
|
||||
|
||||
static byte volumeTable[64] = {
|
||||
0x00, 0x10, 0x14, 0x18, 0x1f, 0x26, 0x2a, 0x2e,
|
||||
0x2f, 0x32, 0x33, 0x33, 0x34, 0x35, 0x35, 0x36,
|
||||
0x36, 0x37, 0x37, 0x38, 0x38, 0x38, 0x39, 0x39,
|
||||
0x39, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3b, 0x3b,
|
||||
0x3b, 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c,
|
||||
0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3e, 0x3e, 0x3e,
|
||||
0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
|
||||
0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f
|
||||
};
|
||||
|
||||
class MidiPlayer_Fb01 : public MidiPlayer {
|
||||
public:
|
||||
enum {
|
||||
kVoices = 8,
|
||||
kMaxSysExSize = 264
|
||||
};
|
||||
|
||||
MidiPlayer_Fb01(SciVersion version);
|
||||
~MidiPlayer_Fb01() override;
|
||||
|
||||
int open(ResourceManager *resMan) override;
|
||||
void close() override;
|
||||
void initTrack(SciSpan<const byte>& header) override;
|
||||
void send(uint32 b) override;
|
||||
void sysEx(const byte *msg, uint16 length) override;
|
||||
bool hasRhythmChannel() const override { return false; }
|
||||
byte getPlayId() const override;
|
||||
int getPolyphony() const override { return _version <= SCI_VERSION_0_LATE ? 8 : 9; }
|
||||
void setVolume(byte volume) override;
|
||||
int getVolume() override;
|
||||
void playSwitch(bool play) override;
|
||||
|
||||
bool isOpen() const { return _isOpen; }
|
||||
|
||||
const char *reportMissingFiles() override { return _missingFiles; }
|
||||
|
||||
private:
|
||||
void noteOn(int channel, int note, int velocity);
|
||||
void noteOff(int channel, int note);
|
||||
void setPatch(int channel, int patch);
|
||||
void controlChange(int channel, int control, int value);
|
||||
|
||||
void setVoiceParam(byte voice, byte param, byte value);
|
||||
void setSystemParam(byte sysChan, byte param, byte value);
|
||||
void sendVoiceData(byte instrument, const SciSpan<const byte> &data);
|
||||
void sendBanks(const SciSpan<const byte> &data);
|
||||
void storeVoiceData(byte instrument, byte bank, byte index);
|
||||
void initVoices();
|
||||
|
||||
void voiceOn(int voice, int note, int velocity);
|
||||
void voiceOff(int voice);
|
||||
int findVoice(int channel);
|
||||
void voiceMapping(int channel, int voices);
|
||||
void assignVoices(int channel, int voices);
|
||||
void releaseVoices(int channel, int voices);
|
||||
void donateVoices();
|
||||
void sendToChannel(byte channel, byte command, byte op1, byte op2);
|
||||
|
||||
struct Channel {
|
||||
uint8 patch; // Patch setting
|
||||
uint8 volume; // Channel volume (0-63)
|
||||
uint8 pan; // Pan setting (0-127, 64 is center)
|
||||
uint8 holdPedal; // Hold pedal setting (0 to 63 is off, 127 to 64 is on)
|
||||
uint8 extraVoices; // The number of additional voices this channel optimally needs
|
||||
uint16 pitchWheel; // Pitch wheel setting (0-16383, 8192 is center)
|
||||
uint8 lastVoice; // Last voice used for this MIDI channel
|
||||
bool enableVelocity; // Enable velocity control (SCI0)
|
||||
|
||||
Channel() : patch(0), volume(127), pan(64), holdPedal(0), extraVoices(0),
|
||||
pitchWheel(8192), lastVoice(0), enableVelocity(false) { }
|
||||
};
|
||||
|
||||
struct Voice {
|
||||
int8 channel; // MIDI channel that this voice is assigned to or -1
|
||||
uint8 poly; // Number of hardware voices (SCI0); for SCI1 we just set this to 1
|
||||
int8 note; // Currently playing MIDI note or -1
|
||||
int bank; // Current bank setting or -1
|
||||
int patch; // Currently playing patch or -1
|
||||
//uint8 velocity; // Note velocity
|
||||
//bool isSustained; // Flag indicating a note that is being sustained by the hold pedal
|
||||
uint16 age; // Age of the current note
|
||||
|
||||
Voice() : channel(-1), note(-1), bank(-1), patch(-1), /*velocity(0), isSustained(false),*/ age(0), poly(1) { }
|
||||
};
|
||||
|
||||
bool _playSwitch;
|
||||
int _masterVolume;
|
||||
int _numParts;
|
||||
|
||||
bool _isOpen;
|
||||
|
||||
Channel _channels[16];
|
||||
Voice _voices[kVoices];
|
||||
|
||||
Common::TimerManager::TimerProc _timerProc;
|
||||
void *_timerParam;
|
||||
static void midiTimerCallback(void *p);
|
||||
void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override;
|
||||
|
||||
const char *_missingFiles;
|
||||
static const char _requiredFiles[2][12];
|
||||
|
||||
byte _sysExBuf[kMaxSysExSize];
|
||||
};
|
||||
|
||||
MidiPlayer_Fb01::MidiPlayer_Fb01(SciVersion version) : MidiPlayer(version), _playSwitch(true), _masterVolume(15), _timerParam(nullptr), _timerProc(nullptr),
|
||||
_numParts(version > SCI_VERSION_0_LATE ? kVoices : 0), _isOpen(false), _missingFiles(nullptr) {
|
||||
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI);
|
||||
_driver = MidiDriver::createMidi(dev);
|
||||
|
||||
_sysExBuf[0] = 0x43;
|
||||
_sysExBuf[1] = 0x75;
|
||||
}
|
||||
|
||||
MidiPlayer_Fb01::~MidiPlayer_Fb01() {
|
||||
if (_driver)
|
||||
_driver->setTimerCallback(nullptr, nullptr);
|
||||
close();
|
||||
delete _driver;
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::voiceMapping(int channel, int voices) {
|
||||
if (_version <= SCI_VERSION_0_LATE) {
|
||||
// The original SCI0 drivers don't do any software voice mapping. Only inside the device...
|
||||
for (int i = 0; i < _numParts; ++i) {
|
||||
if (_voices[i].channel == channel && _voices[i].poly != voices) {
|
||||
_voices[i].poly = voices;
|
||||
setVoiceParam(i, 0, voices);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int curVoices = 0;
|
||||
|
||||
for (int i = 0; i < kVoices; i++)
|
||||
if (_voices[i].channel == channel)
|
||||
curVoices++;
|
||||
|
||||
curVoices += _channels[channel].extraVoices;
|
||||
|
||||
if (curVoices < voices) {
|
||||
debug(3, "FB-01: assigning %i additional voices to channel %i", voices - curVoices, channel);
|
||||
assignVoices(channel, voices - curVoices);
|
||||
} else if (curVoices > voices) {
|
||||
debug(3, "FB-01: releasing %i voices from channel %i", curVoices - voices, channel);
|
||||
releaseVoices(channel, curVoices - voices);
|
||||
donateVoices();
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::assignVoices(int channel, int voices) {
|
||||
assert(voices > 0);
|
||||
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
if (_voices[i].channel == -1) {
|
||||
_voices[i].channel = channel;
|
||||
if (_voices[i].note != -1)
|
||||
voiceOff(i);
|
||||
if (--voices == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_channels[channel].extraVoices += voices;
|
||||
setPatch(channel, _channels[channel].patch);
|
||||
sendToChannel(channel, 0xe0, _channels[channel].pitchWheel & 0x7f, _channels[channel].pitchWheel >> 7);
|
||||
controlChange(channel, 0x07, _channels[channel].volume);
|
||||
controlChange(channel, 0x0a, _channels[channel].pan);
|
||||
controlChange(channel, 0x40, _channels[channel].holdPedal);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::releaseVoices(int channel, int voices) {
|
||||
if (_channels[channel].extraVoices >= voices) {
|
||||
_channels[channel].extraVoices -= voices;
|
||||
return;
|
||||
}
|
||||
|
||||
voices -= _channels[channel].extraVoices;
|
||||
_channels[channel].extraVoices = 0;
|
||||
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
if ((_voices[i].channel == channel) && (_voices[i].note == -1)) {
|
||||
_voices[i].channel = -1;
|
||||
if (--voices == 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
if (_voices[i].channel == channel) {
|
||||
voiceOff(i);
|
||||
_voices[i].channel = -1;
|
||||
if (--voices == 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::donateVoices() {
|
||||
int freeVoices = 0;
|
||||
|
||||
for (int i = 0; i < kVoices; i++)
|
||||
if (_voices[i].channel == -1)
|
||||
freeVoices++;
|
||||
|
||||
if (freeVoices == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < MIDI_CHANNELS; i++) {
|
||||
if (_channels[i].extraVoices >= freeVoices) {
|
||||
assignVoices(i, freeVoices);
|
||||
_channels[i].extraVoices -= freeVoices;
|
||||
return;
|
||||
} else if (_channels[i].extraVoices > 0) {
|
||||
assignVoices(i, _channels[i].extraVoices);
|
||||
freeVoices -= _channels[i].extraVoices;
|
||||
_channels[i].extraVoices = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int MidiPlayer_Fb01::findVoice(int channel) {
|
||||
int voice = -1;
|
||||
int oldestVoice = -1;
|
||||
uint32 oldestAge = 0;
|
||||
|
||||
// Try to find a voice assigned to this channel that is free (round-robin)
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
int v = (_channels[channel].lastVoice + i + 1) % kVoices;
|
||||
|
||||
if (_voices[v].channel == channel) {
|
||||
if (_voices[v].note == -1) {
|
||||
voice = v;
|
||||
break;
|
||||
}
|
||||
|
||||
// We also keep track of the oldest note in case the search fails
|
||||
// Notes started in the current time slice will not be selected
|
||||
if (_voices[v].age > oldestAge) {
|
||||
oldestAge = _voices[v].age;
|
||||
oldestVoice = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (voice == -1) {
|
||||
if (oldestVoice >= 0) {
|
||||
voiceOff(oldestVoice);
|
||||
voice = oldestVoice;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
_channels[channel].lastVoice = voice;
|
||||
return voice;
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::sendToChannel(byte channel, byte command, byte op1, byte op2) {
|
||||
for (int i = 0; i < _numParts; i++) {
|
||||
// Send command to all voices assigned to this channel
|
||||
// I case of SCI0 the voice mapping is done inside the device.
|
||||
if (_voices[i].channel == channel)
|
||||
_driver->send(command | (_version <= SCI_VERSION_0_LATE ? channel : i), op1, op2);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::setPatch(int channel, int patch) {
|
||||
int bank = 0;
|
||||
|
||||
if (_version <= SCI_VERSION_0_LATE && channel == 15) {
|
||||
// The original driver has some parsing related handling for program 127.
|
||||
// We can't handle that here.
|
||||
return;
|
||||
}
|
||||
|
||||
_channels[channel].patch = patch;
|
||||
|
||||
if (patch >= 48) {
|
||||
patch -= 48;
|
||||
bank = 1;
|
||||
}
|
||||
|
||||
for (int voice = 0; voice < _numParts; voice++) {
|
||||
if (_voices[voice].channel == channel) {
|
||||
if (_voices[voice].bank != bank) {
|
||||
_voices[voice].bank = bank;
|
||||
setVoiceParam(voice, 4, bank);
|
||||
}
|
||||
_driver->send(0xc0 | (_version <= SCI_VERSION_0_LATE ? channel : voice), patch, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::voiceOn(int voice, int note, int velocity) {
|
||||
if (_playSwitch) {
|
||||
_voices[voice].note = note;
|
||||
_voices[voice].age = 0;
|
||||
_driver->send(0x90 | voice, note, velocity);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::voiceOff(int voice) {
|
||||
_voices[voice].note = -1;
|
||||
_driver->send(0xb0 | voice, 0x7b, 0x00);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::noteOff(int channel, int note) {
|
||||
int voice;
|
||||
for (voice = 0; voice < kVoices; voice++) {
|
||||
if ((_voices[voice].channel == channel) && (_voices[voice].note == note)) {
|
||||
voiceOff(voice);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::noteOn(int channel, int note, int velocity) {
|
||||
if (velocity == 0)
|
||||
return noteOff(channel, note);
|
||||
|
||||
if (_version > SCI_VERSION_0_LATE)
|
||||
velocity >>= 1;
|
||||
|
||||
int voice;
|
||||
for (voice = 0; voice < kVoices; voice++) {
|
||||
if ((_voices[voice].channel == channel) && (_voices[voice].note == note)) {
|
||||
voiceOff(voice);
|
||||
|
||||
// Original bug #1: The table is only applied here, but not to the voiceOn() call below.
|
||||
// Original bug #2: The velocity value also gets another right shift although the table
|
||||
// size of the volume table is 64 and not 32.
|
||||
// --> disable this ? It certainly can't do any good.
|
||||
if (_version > SCI_VERSION_0_LATE)
|
||||
velocity = volumeTable[velocity >> 1] << 1;
|
||||
|
||||
voiceOn(voice, note, velocity);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
voice = findVoice(channel);
|
||||
|
||||
if (voice == -1) {
|
||||
debug(3, "FB-01: failed to find free voice assigned to channel %i", channel);
|
||||
return;
|
||||
}
|
||||
|
||||
voiceOn(voice, note, velocity);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::controlChange(int channel, int control, int value) {
|
||||
// Events for the control channel shouldn't arrive here.
|
||||
// The original driver handles some specific parsing related midi events
|
||||
// sent on channel 15, but we (hopefully) do that in the SCI music engine.
|
||||
if (_version <= SCI_VERSION_0_LATE && channel == 15)
|
||||
return;
|
||||
|
||||
switch (control) {
|
||||
case 0x07: {
|
||||
_channels[channel].volume = value;
|
||||
|
||||
if (_version > SCI_VERSION_0_LATE)
|
||||
value = volumeTable[value >> 1] << 1;
|
||||
|
||||
#ifdef HARDWARE_MASTERVOLUME
|
||||
sendToChannel(channel, 0xb0, control, value);
|
||||
#else
|
||||
byte vol = _masterVolume;
|
||||
|
||||
if (vol > 0)
|
||||
vol = CLIP<byte>(vol + 3, 0, 15);
|
||||
|
||||
sendToChannel(channel, 0xb0, control, (value * vol / 15) & 0x7f);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 0x0a:
|
||||
_channels[channel].pan = value;
|
||||
sendToChannel(channel, 0xb0, control, value);
|
||||
break;
|
||||
case 0x40:
|
||||
_channels[channel].holdPedal = value;
|
||||
sendToChannel(channel, 0xb0, control, value);
|
||||
break;
|
||||
case 0x4b:
|
||||
voiceMapping(channel, value);
|
||||
break;
|
||||
case 0x7b:
|
||||
for (int i = 0; i < _numParts; i++) {
|
||||
if ((_voices[i].channel == channel) && (_voices[i].note != -1)) {
|
||||
_voices[i].note = -1;
|
||||
sendToChannel(channel, 0xb0, control, value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
sendToChannel(channel, 0xb0, control, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::send(uint32 b) {
|
||||
byte command = b & 0xf0;
|
||||
byte channel = b & 0xf;
|
||||
byte op1 = (b >> 8) & 0x7f;
|
||||
byte op2 = (b >> 16) & 0x7f;
|
||||
|
||||
if (_version <= SCI_VERSION_0_LATE && command != 0xB0 && command != 0xC0) {
|
||||
// Since the voice mapping takes place inside the hardware, most messages
|
||||
// are simply passed through. Channel 15 is never assigned to a part but is
|
||||
// used for certain parsing related events which we cannot handle here.
|
||||
// Just making sure that no nonsense is sent to the device...
|
||||
if (channel != 15)
|
||||
sendToChannel(channel, command, op1, op2);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (command) {
|
||||
case 0x80:
|
||||
noteOff(channel, op1);
|
||||
break;
|
||||
case 0x90:
|
||||
noteOn(channel, op1, op2);
|
||||
break;
|
||||
case 0xb0:
|
||||
controlChange(channel, op1, op2);
|
||||
break;
|
||||
case 0xc0:
|
||||
setPatch(channel, op1);
|
||||
break;
|
||||
case 0xe0:
|
||||
_channels[channel].pitchWheel = (op1 & 0x7f) | ((op2 & 0x7f) << 7);
|
||||
sendToChannel(channel, command, op1, op2);
|
||||
break;
|
||||
default:
|
||||
warning("FB-01: Ignoring MIDI event %02x %02x %02x", command | channel, op1, op2);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::setVolume(byte volume) {
|
||||
_masterVolume = volume;
|
||||
#ifdef HARDWARE_MASTERVOLUME
|
||||
setSystemParam(0, 0x24, (CLIP<byte>(_masterVolume + 3, 0, 15) << 3) + 7);
|
||||
#else
|
||||
for (uint i = 0; i < MIDI_CHANNELS; i++)
|
||||
controlChange(i, 0x07, _channels[i].volume & 0x7f);
|
||||
#endif
|
||||
}
|
||||
|
||||
int MidiPlayer_Fb01::getVolume() {
|
||||
return _masterVolume;
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::playSwitch(bool play) {
|
||||
_playSwitch = play;
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::midiTimerCallback(void *p) {
|
||||
if (!p)
|
||||
return;
|
||||
|
||||
MidiPlayer_Fb01 *m = (MidiPlayer_Fb01 *)p;
|
||||
|
||||
if (!m->isOpen())
|
||||
return;
|
||||
|
||||
// Increase the age of the notes
|
||||
for (int i = 0; i < kVoices; i++) {
|
||||
if (m->_voices[i].note != -1)
|
||||
m->_voices[i].age++;
|
||||
}
|
||||
|
||||
if (m->_timerProc)
|
||||
m->_timerProc(m->_timerParam);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
|
||||
_driver->setTimerCallback(nullptr, nullptr);
|
||||
|
||||
_timerParam = timer_param;
|
||||
_timerProc = timer_proc;
|
||||
|
||||
_driver->setTimerCallback(this, midiTimerCallback);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::sendBanks(const SciSpan<const byte> &data) {
|
||||
if (data.size() < 3072)
|
||||
error("Failed to read FB-01 patch");
|
||||
|
||||
// SSCI sends bank dumps containing 48 instruments at once. We cannot do that
|
||||
// due to the limited maximum SysEx length. Instead we send the instruments
|
||||
// one by one and store them in the banks.
|
||||
for (int i = 0; i < 48; i++) {
|
||||
sendVoiceData(0, data.subspan(i * 64));
|
||||
storeVoiceData(0, 0, i);
|
||||
}
|
||||
|
||||
// Send second bank if available
|
||||
if (data.size() < 6146)
|
||||
return;
|
||||
|
||||
if (data.getUint16BEAt(3072) != 0xabcd)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 48; i++) {
|
||||
sendVoiceData(0, data.subspan(3074 + i * 64));
|
||||
storeVoiceData(0, 1, i);
|
||||
}
|
||||
}
|
||||
|
||||
int MidiPlayer_Fb01::open(ResourceManager *resMan) {
|
||||
assert(resMan != nullptr);
|
||||
|
||||
int retval = _driver->open();
|
||||
if (retval != 0) {
|
||||
warning("Failed to open MIDI driver");
|
||||
return retval;
|
||||
}
|
||||
|
||||
// Set system channel to 0.
|
||||
setSystemParam(0, 0x20, 0);
|
||||
|
||||
// Turn off memory protection
|
||||
setSystemParam(0, 0x21, 0);
|
||||
|
||||
Resource *res = resMan->findResource(ResourceId(kResourceTypePatch, 2), false);
|
||||
|
||||
if (res) {
|
||||
sendBanks(*res);
|
||||
} else {
|
||||
// Early SCI0 games have the sound bank embedded in the IMF driver.
|
||||
// Note that these games didn't actually support the FB-01 as a device,
|
||||
// but the IMF, which is the same device on an ISA card. Check:
|
||||
// https://en.wikipedia.org/wiki/IBM_Music_Feature_Card
|
||||
|
||||
warning("FB-01 patch file not found, attempting to load sound bank from IMF.DRV");
|
||||
// Try to load sound bank from IMF.DRV
|
||||
Common::File f;
|
||||
|
||||
if (f.open("IMF.DRV")) {
|
||||
Common::SpanOwner<SciSpan<const byte> > buf;
|
||||
buf->allocateFromStream(f);
|
||||
|
||||
// Search for start of sound bank
|
||||
uint offset;
|
||||
for (offset = 0; offset < buf->size() - 7; ++offset) {
|
||||
if (!strncmp((const char *)buf->getUnsafeDataAt(offset, 7), "SIERRA ", 7))
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip to voice data
|
||||
offset += 0x20;
|
||||
|
||||
if (offset >= buf->size())
|
||||
error("Failed to locate start of FB-01 sound bank");
|
||||
|
||||
// The newer IMF.DRV versions without the sound bank will still have the 32 byte
|
||||
// "SIERRA 1" header, but after that they want to send the patch.002 resource.
|
||||
// These driver version are easy to identify by their small size.
|
||||
if (buf->subspan(offset).size() < 3072) {
|
||||
_missingFiles = _requiredFiles[1];
|
||||
return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
sendBanks(buf->subspan(offset));
|
||||
} else {
|
||||
_missingFiles = _version == SCI_VERSION_0_EARLY ? _requiredFiles[0] : _requiredFiles[1];
|
||||
return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
||||
}
|
||||
}
|
||||
|
||||
// Set up voices to use MIDI channels 0 - 7
|
||||
for (int i = 0; i < kVoices; i++)
|
||||
setVoiceParam(i, 1, i);
|
||||
|
||||
initVoices();
|
||||
|
||||
// Set master volume
|
||||
setSystemParam(0, 0x24, 0x7f);
|
||||
|
||||
_isOpen = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::close() {
|
||||
if (_driver)
|
||||
_driver->setTimerCallback(nullptr, nullptr);
|
||||
_isOpen = false;
|
||||
if (_driver)
|
||||
_driver->close();
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::initTrack(SciSpan<const byte>& header) {
|
||||
if (!_isOpen || _version > SCI_VERSION_0_LATE)
|
||||
return;
|
||||
|
||||
uint8 readPos = 0;
|
||||
uint8 caps = header.getInt8At(readPos++);
|
||||
if (caps != 0 && (_version == SCI_VERSION_0_EARLY || caps != 2))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
_voices[i] = Voice();
|
||||
|
||||
_numParts = 0;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if (_version == SCI_VERSION_0_LATE) {
|
||||
uint8 num = header.getInt8At(readPos++) & 0x7F;
|
||||
uint8 flags = header.getInt8At(readPos++);
|
||||
|
||||
if (flags & 0x02) {
|
||||
_voices[_numParts].channel = i;
|
||||
_voices[_numParts].poly = num;
|
||||
_numParts++;
|
||||
}
|
||||
} else {
|
||||
uint8 val = header.getInt8At(readPos++);
|
||||
if (val & 0x01) {
|
||||
if (val & 0x08) {
|
||||
if (val >> 4)
|
||||
debugC(9, kDebugLevelSound, "MidiPlayer_Fb01::initTrack(): Unused rhythm channel found: 0x%.02x", i);
|
||||
} else if ((val >> 4) != 0x0F) {
|
||||
_voices[_numParts].channel = i;
|
||||
_voices[_numParts].poly = val >> 4;
|
||||
_numParts++;
|
||||
}
|
||||
} else if (val & 0x08) {
|
||||
debugC(9, kDebugLevelSound, "MidiPlayer_Fb01::initTrack(): Control channel found: 0x%.02x", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _numParts; ++i)
|
||||
setVoiceParam(i, 1, _voices[i].channel);
|
||||
|
||||
initVoices();
|
||||
|
||||
setVolume(_masterVolume);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::setVoiceParam(byte voice, byte param, byte value) {
|
||||
_sysExBuf[2] = 0x00;
|
||||
_sysExBuf[3] = 0x18 | voice;
|
||||
_sysExBuf[4] = param;
|
||||
_sysExBuf[5] = value;
|
||||
|
||||
_driver->sysEx(_sysExBuf, 6);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::setSystemParam(byte sysChan, byte param, byte value) {
|
||||
_sysExBuf[2] = sysChan;
|
||||
_sysExBuf[3] = 0x10;
|
||||
_sysExBuf[4] = param;
|
||||
_sysExBuf[5] = value;
|
||||
|
||||
sysEx(_sysExBuf, 6);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::sendVoiceData(byte instrument, const SciSpan<const byte> &data) {
|
||||
_sysExBuf[2] = 0x00;
|
||||
_sysExBuf[3] = 0x08 | instrument;
|
||||
_sysExBuf[4] = 0x00;
|
||||
_sysExBuf[5] = 0x00;
|
||||
_sysExBuf[6] = 0x01;
|
||||
_sysExBuf[7] = 0x00;
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
_sysExBuf[8 + i * 2] = data[i] & 0xf;
|
||||
_sysExBuf[8 + i * 2 + 1] = data[i] >> 4;
|
||||
}
|
||||
|
||||
byte checksum = 0;
|
||||
for (int i = 8; i < 136; i++)
|
||||
checksum += _sysExBuf[i];
|
||||
|
||||
_sysExBuf[136] = (-checksum) & 0x7f;
|
||||
|
||||
sysEx(_sysExBuf, 137);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::storeVoiceData(byte instrument, byte bank, byte index) {
|
||||
_sysExBuf[2] = 0x00;
|
||||
_sysExBuf[3] = 0x28 | instrument;
|
||||
_sysExBuf[4] = 0x40;
|
||||
_sysExBuf[5] = (bank > 0 ? 48 : 0) + index;
|
||||
|
||||
sysEx(_sysExBuf, 6);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::initVoices() {
|
||||
int i = 2;
|
||||
_sysExBuf[i++] = 0x70;
|
||||
|
||||
// Set all MIDI channels to 0 voices
|
||||
for (int j = 0; j < MIDI_CHANNELS; j++) {
|
||||
_sysExBuf[i++] = 0x70 | j;
|
||||
_sysExBuf[i++] = 0x00;
|
||||
_sysExBuf[i++] = 0x00;
|
||||
}
|
||||
|
||||
// Set up the MIDI channels we will be using
|
||||
for (int j = 0; j < _numParts; j++) {
|
||||
int8 chan = _version > SCI_VERSION_0_LATE ? j : _voices[j].channel;
|
||||
// One voice
|
||||
_sysExBuf[i++] = 0x70 | chan;
|
||||
_sysExBuf[i++] = 0x00;
|
||||
_sysExBuf[i++] = _voices[j].poly;
|
||||
|
||||
// Full range of keys
|
||||
_sysExBuf[i++] = 0x70 | chan;
|
||||
_sysExBuf[i++] = 0x02;
|
||||
_sysExBuf[i++] = 0x7f;
|
||||
_sysExBuf[i++] = 0x70 | chan;
|
||||
_sysExBuf[i++] = 0x03;
|
||||
_sysExBuf[i++] = 0x00;
|
||||
|
||||
// Voice bank 0
|
||||
_sysExBuf[i++] = 0x70 | chan;
|
||||
_sysExBuf[i++] = 0x04;
|
||||
_sysExBuf[i++] = 0x00;
|
||||
|
||||
// Voice 10
|
||||
_sysExBuf[i++] = 0x70 | chan;
|
||||
_sysExBuf[i++] = 0x05;
|
||||
_sysExBuf[i++] = 0x0a;
|
||||
}
|
||||
|
||||
sysEx(_sysExBuf, i);
|
||||
}
|
||||
|
||||
void MidiPlayer_Fb01::sysEx(const byte *msg, uint16 length) {
|
||||
_driver->sysEx(msg, length);
|
||||
|
||||
// Wait the time it takes to send the SysEx data
|
||||
uint32 delay = (length + 2) * 1000 / 3125;
|
||||
|
||||
delay += 10;
|
||||
|
||||
g_system->delayMillis(delay);
|
||||
}
|
||||
|
||||
byte MidiPlayer_Fb01::getPlayId() const {
|
||||
switch (_version) {
|
||||
case SCI_VERSION_0_EARLY:
|
||||
return 0x09;
|
||||
case SCI_VERSION_0_LATE:
|
||||
return 0x02;
|
||||
default:
|
||||
return 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
const char MidiPlayer_Fb01::_requiredFiles[2][12] = {
|
||||
"'IMF.DRV'",
|
||||
"'PATCH.002'"
|
||||
};
|
||||
|
||||
MidiPlayer *MidiPlayer_Fb01_create(SciVersion version) {
|
||||
return new MidiPlayer_Fb01(version);
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#undef HARDWARE_MASTERVOLUME
|
||||
667
engines/sci/sound/drivers/fmtowns.cpp
Normal file
667
engines/sci/sound/drivers/fmtowns.cpp
Normal file
@@ -0,0 +1,667 @@
|
||||
/* 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 "sci/sci.h"
|
||||
|
||||
#include "common/file.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#include "audio/softsynth/fmtowns_pc98/towns_audio.h"
|
||||
|
||||
#include "sci/resource/resource.h"
|
||||
#include "sci/sound/drivers/mididriver.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
class MidiDriver_FMTowns;
|
||||
|
||||
class TownsChannel {
|
||||
public:
|
||||
TownsChannel(MidiDriver_FMTowns *driver, uint8 id);
|
||||
~TownsChannel() {}
|
||||
|
||||
void noteOff();
|
||||
void noteOn(uint8 note, uint8 velo);
|
||||
void pitchBend(int16 val);
|
||||
void updateVolume();
|
||||
void updateDuration();
|
||||
|
||||
uint8 _assign;
|
||||
uint8 _note;
|
||||
uint8 _sustain;
|
||||
uint16 _duration;
|
||||
|
||||
private:
|
||||
uint8 _id;
|
||||
uint8 _velo;
|
||||
uint8 _program;
|
||||
|
||||
MidiDriver_FMTowns *_drv;
|
||||
};
|
||||
|
||||
class TownsMidiPart {
|
||||
friend class MidiDriver_FMTowns;
|
||||
public:
|
||||
TownsMidiPart(MidiDriver_FMTowns *driver, uint8 id);
|
||||
~TownsMidiPart() {}
|
||||
|
||||
void noteOff(uint8 note);
|
||||
void noteOn(uint8 note, uint8 velo);
|
||||
void controlChangeVolume(uint8 vol);
|
||||
void controlChangeSustain(uint8 sus);
|
||||
void controlChangePolyphony(uint8 numChan);
|
||||
void controlChangeAllNotesOff();
|
||||
void programChange(uint8 prg);
|
||||
void pitchBend(int16 val);
|
||||
|
||||
void addChannels(int num);
|
||||
void dropChannels(int num);
|
||||
|
||||
uint8 currentProgram() const;
|
||||
|
||||
private:
|
||||
int allocateChannel();
|
||||
|
||||
uint8 _id;
|
||||
uint8 _program;
|
||||
uint8 _volume;
|
||||
uint8 _sustain;
|
||||
uint8 _chanMissing;
|
||||
int16 _pitchBend;
|
||||
uint8 _outChan;
|
||||
|
||||
MidiDriver_FMTowns *_drv;
|
||||
};
|
||||
|
||||
class MidiDriver_FMTowns : public MidiDriver, public TownsAudioInterfacePluginDriver {
|
||||
friend class TownsChannel;
|
||||
friend class TownsMidiPart;
|
||||
public:
|
||||
MidiDriver_FMTowns(Audio::Mixer *mixer, SciVersion version);
|
||||
~MidiDriver_FMTowns() override;
|
||||
|
||||
int open() override;
|
||||
void loadInstruments(const SciSpan<const uint8> &data);
|
||||
bool isOpen() const override { return _isOpen; }
|
||||
void close() override;
|
||||
|
||||
void send(uint32 b) override;
|
||||
|
||||
uint32 property(int prop, uint32 param) override;
|
||||
void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override;
|
||||
|
||||
void setSoundOn(bool toggle);
|
||||
|
||||
uint32 getBaseTempo() override;
|
||||
MidiChannel *allocateChannel() override { return nullptr; }
|
||||
MidiChannel *getPercussionChannel() override { return nullptr; }
|
||||
|
||||
void timerCallback(int timerId) override;
|
||||
|
||||
private:
|
||||
int getChannelVolume(uint8 midiPart);
|
||||
void addMissingChannels();
|
||||
|
||||
void updateParser();
|
||||
void updateChannels();
|
||||
|
||||
Common::TimerManager::TimerProc _timerProc;
|
||||
void *_timerProcPara;
|
||||
|
||||
TownsMidiPart **_parts;
|
||||
TownsChannel **_out;
|
||||
|
||||
uint8 _masterVolume;
|
||||
|
||||
bool _soundOn;
|
||||
|
||||
bool _isOpen;
|
||||
bool _ready;
|
||||
|
||||
const uint16 _baseTempo;
|
||||
SciVersion _version;
|
||||
|
||||
TownsAudioInterface *_intf;
|
||||
};
|
||||
|
||||
class MidiPlayer_FMTowns : public MidiPlayer {
|
||||
public:
|
||||
MidiPlayer_FMTowns(SciVersion version);
|
||||
~MidiPlayer_FMTowns() override;
|
||||
|
||||
int open(ResourceManager *resMan) override;
|
||||
|
||||
bool hasRhythmChannel() const override;
|
||||
byte getPlayId() const override;
|
||||
int getPolyphony() const override;
|
||||
void playSwitch(bool play) override;
|
||||
|
||||
private:
|
||||
MidiDriver_FMTowns *_townsDriver;
|
||||
};
|
||||
|
||||
TownsChannel::TownsChannel(MidiDriver_FMTowns *driver, uint8 id) : _drv(driver), _id(id), _assign(0xff), _note(0xff), _velo(0), _sustain(0), _duration(0), _program(0xff) {
|
||||
}
|
||||
|
||||
void TownsChannel::noteOn(uint8 note, uint8 velo) {
|
||||
_duration = 0;
|
||||
|
||||
if (_drv->_version != SCI_VERSION_1_EARLY) {
|
||||
if (_program != _drv->_parts[_assign]->currentProgram() && _drv->_soundOn) {
|
||||
_program = _drv->_parts[_assign]->currentProgram();
|
||||
_drv->_intf->callback(4, _id, _program);
|
||||
}
|
||||
}
|
||||
|
||||
_note = note;
|
||||
_velo = velo;
|
||||
_drv->_intf->callback(1, _id, _note, _velo);
|
||||
}
|
||||
|
||||
void TownsChannel::noteOff() {
|
||||
if (_sustain)
|
||||
return;
|
||||
|
||||
_drv->_intf->callback(2, _id);
|
||||
_note = 0xff;
|
||||
_duration = 0;
|
||||
}
|
||||
|
||||
void TownsChannel::pitchBend(int16 val) {
|
||||
_drv->_intf->callback(7, _id, val);
|
||||
}
|
||||
|
||||
void TownsChannel::updateVolume() {
|
||||
if (_assign > 15 && _drv->_version != SCI_VERSION_1_EARLY)
|
||||
return;
|
||||
_drv->_intf->callback(8, _id, _drv->getChannelVolume((_drv->_version == SCI_VERSION_1_EARLY) ? 0 : _assign));
|
||||
}
|
||||
|
||||
void TownsChannel::updateDuration() {
|
||||
if (_note != 0xff)
|
||||
_duration++;
|
||||
}
|
||||
|
||||
TownsMidiPart::TownsMidiPart(MidiDriver_FMTowns *driver, uint8 id) : _drv(driver), _id(id), _program(0), _volume(0x3f), _sustain(0), _chanMissing(0), _pitchBend(0x2000), _outChan(0) {
|
||||
}
|
||||
|
||||
void TownsMidiPart::noteOff(uint8 note) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if ((_drv->_out[i]->_assign != _id && _drv->_version != SCI_VERSION_1_EARLY) || _drv->_out[i]->_note != note)
|
||||
continue;
|
||||
if (_sustain)
|
||||
_drv->_out[i]->_sustain = 1;
|
||||
else
|
||||
_drv->_out[i]->noteOff();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void TownsMidiPart::noteOn(uint8 note, uint8 velo) {
|
||||
if (note < 12 || note > 107)
|
||||
return;
|
||||
|
||||
if (velo == 0) {
|
||||
noteOff(note);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_drv->_version != SCI_VERSION_1_EARLY)
|
||||
velo >>= 1;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if ((_drv->_out[i]->_assign != _id && _drv->_version != SCI_VERSION_1_EARLY) || _drv->_out[i]->_note != note)
|
||||
continue;
|
||||
_drv->_out[i]->_sustain = 0;
|
||||
_drv->_out[i]->noteOff();
|
||||
_drv->_out[i]->noteOn(note, velo);
|
||||
return;
|
||||
}
|
||||
|
||||
int chan = allocateChannel();
|
||||
if (chan != -1)
|
||||
_drv->_out[chan]->noteOn(note, velo);
|
||||
}
|
||||
|
||||
void TownsMidiPart::controlChangeVolume(uint8 vol) {
|
||||
if (_drv->_version == SCI_VERSION_1_EARLY)
|
||||
return;
|
||||
|
||||
_volume = vol >> 1;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_drv->_out[i]->_assign == _id)
|
||||
_drv->_out[i]->updateVolume();
|
||||
}
|
||||
}
|
||||
|
||||
void TownsMidiPart::controlChangeSustain(uint8 sus) {
|
||||
if (_drv->_version == SCI_VERSION_1_EARLY)
|
||||
return;
|
||||
|
||||
_sustain = sus;
|
||||
if (_sustain)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_drv->_out[i]->_assign == _id && _drv->_out[i]->_sustain) {
|
||||
_drv->_out[i]->_sustain = 0;
|
||||
_drv->_out[i]->noteOff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TownsMidiPart::controlChangePolyphony(uint8 numChan) {
|
||||
if (_drv->_version == SCI_VERSION_1_EARLY)
|
||||
return;
|
||||
|
||||
uint8 numAssigned = 0;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_drv->_out[i]->_assign == _id)
|
||||
numAssigned++;
|
||||
}
|
||||
|
||||
numAssigned += _chanMissing;
|
||||
if (numAssigned < numChan) {
|
||||
addChannels(numChan - numAssigned);
|
||||
} else if (numAssigned > numChan) {
|
||||
dropChannels(numAssigned - numChan);
|
||||
_drv->addMissingChannels();
|
||||
}
|
||||
}
|
||||
|
||||
void TownsMidiPart::controlChangeAllNotesOff() {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if ((_drv->_out[i]->_assign == _id || _drv->_version == SCI_VERSION_1_EARLY) && _drv->_out[i]->_note != 0xff)
|
||||
_drv->_out[i]->noteOff();
|
||||
}
|
||||
}
|
||||
|
||||
void TownsMidiPart::programChange(uint8 prg) {
|
||||
_program = prg;
|
||||
}
|
||||
|
||||
void TownsMidiPart::pitchBend(int16 val) {
|
||||
_pitchBend = val;
|
||||
val -= 0x2000;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
// Strangely, the early version driver applies the setting to channel 0 only.
|
||||
if (_drv->_out[i]->_assign == _id || (_drv->_version == SCI_VERSION_1_EARLY && i == 0))
|
||||
_drv->_out[i]->pitchBend(val);
|
||||
}
|
||||
}
|
||||
|
||||
void TownsMidiPart::addChannels(int num) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_drv->_out[i]->_assign != 0xff)
|
||||
continue;
|
||||
|
||||
_drv->_out[i]->_assign = _id;
|
||||
_drv->_out[i]->updateVolume();
|
||||
|
||||
if (_drv->_out[i]->_note != 0xff)
|
||||
_drv->_out[i]->noteOff();
|
||||
|
||||
if (!--num)
|
||||
break;
|
||||
}
|
||||
|
||||
_chanMissing += num;
|
||||
programChange(_program);
|
||||
pitchBend(_pitchBend);
|
||||
controlChangeVolume(_volume << 1);
|
||||
}
|
||||
|
||||
void TownsMidiPart::dropChannels(int num) {
|
||||
if (_chanMissing == num) {
|
||||
_chanMissing = 0;
|
||||
return;
|
||||
} else if (_chanMissing > num) {
|
||||
_chanMissing -= num;
|
||||
return;
|
||||
}
|
||||
|
||||
num -= _chanMissing;
|
||||
_chanMissing = 0;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_drv->_out[i]->_assign != _id || _drv->_out[i]->_note != 0xff)
|
||||
continue;
|
||||
_drv->_out[i]->_assign = 0xff;
|
||||
if (!--num)
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_drv->_out[i]->_assign != _id)
|
||||
continue;
|
||||
_drv->_out[i]->_sustain = 0;
|
||||
_drv->_out[i]->noteOff();
|
||||
_drv->_out[i]->_assign = 0xff;
|
||||
if (!--num)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint8 TownsMidiPart::currentProgram() const {
|
||||
return _program;
|
||||
}
|
||||
|
||||
int TownsMidiPart::allocateChannel() {
|
||||
int chan = _outChan;
|
||||
int ovrChan = 0;
|
||||
int ld = 0;
|
||||
bool found = false;
|
||||
|
||||
for (bool loop = true; loop; ) {
|
||||
if (++chan == 6)
|
||||
chan = 0;
|
||||
|
||||
if (chan == _outChan)
|
||||
loop = false;
|
||||
|
||||
if (_id == _drv->_out[chan]->_assign || _drv->_version == SCI_VERSION_1_EARLY) {
|
||||
if (_drv->_out[chan]->_note == 0xff) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (_drv->_out[chan]->_duration >= ld) {
|
||||
ld = _drv->_out[chan]->_duration;
|
||||
ovrChan = chan;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
if (!ld)
|
||||
return -1;
|
||||
chan = ovrChan;
|
||||
_drv->_out[chan]->_sustain = 0;
|
||||
_drv->_out[chan]->noteOff();
|
||||
}
|
||||
|
||||
_outChan = chan;
|
||||
return chan;
|
||||
}
|
||||
|
||||
MidiDriver_FMTowns::MidiDriver_FMTowns(Audio::Mixer *mixer, SciVersion version) : _version(version), _timerProc(nullptr), _timerProcPara(nullptr), _baseTempo(10080), _ready(false), _isOpen(false), _masterVolume(0x0f), _soundOn(true) {
|
||||
_intf = new TownsAudioInterface(mixer, this, true);
|
||||
_out = new TownsChannel*[6];
|
||||
for (int i = 0; i < 6; i++)
|
||||
_out[i] = new TownsChannel(this, i);
|
||||
_parts = new TownsMidiPart*[16];
|
||||
for (int i = 0; i < 16; i++)
|
||||
_parts[i] = new TownsMidiPart(this, i);
|
||||
}
|
||||
|
||||
MidiDriver_FMTowns::~MidiDriver_FMTowns() {
|
||||
delete _intf;
|
||||
|
||||
if (_parts) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
delete _parts[i];
|
||||
_parts[i] = nullptr;
|
||||
}
|
||||
delete[] _parts;
|
||||
_parts = nullptr;
|
||||
}
|
||||
|
||||
if (_out) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
delete _out[i];
|
||||
_out[i] = nullptr;
|
||||
}
|
||||
delete[] _out;
|
||||
_out = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int MidiDriver_FMTowns::open() {
|
||||
if (_isOpen)
|
||||
return MERR_ALREADY_OPEN;
|
||||
|
||||
if (!_ready) {
|
||||
if (!_intf->init())
|
||||
return MERR_CANNOT_CONNECT;
|
||||
|
||||
_intf->callback(0);
|
||||
|
||||
_intf->callback(21, 255, 1);
|
||||
_intf->callback(21, 0, 1);
|
||||
_intf->callback(22, 255, 221);
|
||||
|
||||
_intf->callback(33, 8);
|
||||
_intf->setSoundEffectChanMask(~0x3f);
|
||||
|
||||
_ready = true;
|
||||
}
|
||||
|
||||
_isOpen = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::loadInstruments(const SciSpan<const uint8> &data) {
|
||||
enum {
|
||||
fmDataSize = 48
|
||||
};
|
||||
|
||||
if (data.size()) {
|
||||
SciSpan<const uint8> instrumentData = data.subspan(6);
|
||||
for (int i = 0; i < 128; i++, instrumentData += fmDataSize) {
|
||||
_intf->callback(5, 0, i, instrumentData.getUnsafeDataAt(0, fmDataSize));
|
||||
}
|
||||
}
|
||||
|
||||
_intf->callback(70, 3);
|
||||
property(MIDI_PROP_MASTER_VOLUME, _masterVolume);
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::close() {
|
||||
_isOpen = false;
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::send(uint32 b) {
|
||||
if (!_isOpen)
|
||||
return;
|
||||
|
||||
byte para2 = (b >> 16) & 0xFF;
|
||||
byte para1 = (b >> 8) & 0xFF;
|
||||
byte cmd = b & 0xF0;
|
||||
|
||||
TownsMidiPart *chan = _parts[b & 0x0F];
|
||||
|
||||
switch (cmd) {
|
||||
case 0x80:
|
||||
chan->noteOff(para1);
|
||||
break;
|
||||
case 0x90:
|
||||
chan->noteOn(para1, para2);
|
||||
break;
|
||||
case 0xb0:
|
||||
switch (para1) {
|
||||
case 7:
|
||||
chan->controlChangeVolume(para2);
|
||||
break;
|
||||
case 64:
|
||||
chan->controlChangeSustain(para2);
|
||||
break;
|
||||
case SCI_MIDI_SET_POLYPHONY:
|
||||
chan->controlChangePolyphony(para2);
|
||||
break;
|
||||
case SCI_MIDI_CHANNEL_NOTES_OFF:
|
||||
chan->controlChangeAllNotesOff();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 0xc0:
|
||||
chan->programChange(para1);
|
||||
break;
|
||||
case 0xe0:
|
||||
chan->pitchBend(para1 | (para2 << 7));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 MidiDriver_FMTowns::property(int prop, uint32 param) {
|
||||
switch(prop) {
|
||||
case MIDI_PROP_MASTER_VOLUME:
|
||||
if (param != 0xffff) {
|
||||
_masterVolume = param;
|
||||
for (int i = 0; i < 6; i++)
|
||||
_out[i]->updateVolume();
|
||||
}
|
||||
return _masterVolume;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
|
||||
_timerProc = timer_proc;
|
||||
_timerProcPara = timer_param;
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::setSoundOn(bool toggle) {
|
||||
_soundOn = toggle;
|
||||
}
|
||||
|
||||
uint32 MidiDriver_FMTowns::getBaseTempo() {
|
||||
return _baseTempo;
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::timerCallback(int timerId) {
|
||||
if (!_isOpen)
|
||||
return;
|
||||
|
||||
switch (timerId) {
|
||||
case 1:
|
||||
updateParser();
|
||||
updateChannels();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int MidiDriver_FMTowns::getChannelVolume(uint8 midiPart) {
|
||||
static const uint8 volumeTable[] = { 0x00, 0x0D, 0x1B, 0x28, 0x36, 0x43, 0x51, 0x5F, 0x63, 0x67, 0x6B, 0x6F, 0x73, 0x77, 0x7B, 0x7F };
|
||||
int tableIndex = (_version == SCI_VERSION_1_EARLY) ? _masterVolume : (_parts[midiPart]->_volume * (_masterVolume + 1)) >> 6;
|
||||
assert(tableIndex < 16);
|
||||
return volumeTable[tableIndex];
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::addMissingChannels() {
|
||||
uint8 avlChan = 0;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (_out[i]->_assign == 0xff)
|
||||
avlChan++;
|
||||
}
|
||||
|
||||
if (!avlChan)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (!_parts[i]->_chanMissing)
|
||||
continue;
|
||||
|
||||
if (_parts[i]->_chanMissing < avlChan) {
|
||||
avlChan -= _parts[i]->_chanMissing;
|
||||
uint8 m = _parts[i]->_chanMissing;
|
||||
_parts[i]->_chanMissing = 0;
|
||||
_parts[i]->addChannels(m);
|
||||
} else {
|
||||
_parts[i]->_chanMissing -= avlChan;
|
||||
_parts[i]->addChannels(avlChan);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::updateParser() {
|
||||
if (_timerProc)
|
||||
_timerProc(_timerProcPara);
|
||||
}
|
||||
|
||||
void MidiDriver_FMTowns::updateChannels() {
|
||||
for (int i = 0; i < 6; i++)
|
||||
_out[i]->updateDuration();
|
||||
}
|
||||
|
||||
MidiPlayer_FMTowns::MidiPlayer_FMTowns(SciVersion version) : MidiPlayer(version) {
|
||||
_driver = _townsDriver = new MidiDriver_FMTowns(g_system->getMixer(), version);
|
||||
}
|
||||
|
||||
MidiPlayer_FMTowns::~MidiPlayer_FMTowns() {
|
||||
delete _driver;
|
||||
}
|
||||
|
||||
int MidiPlayer_FMTowns::open(ResourceManager *resMan) {
|
||||
int result = MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
||||
if (_townsDriver) {
|
||||
result = _townsDriver->open();
|
||||
if (!result && _version == SCI_VERSION_1_LATE) {
|
||||
Resource *res = resMan->findResource(ResourceId(kResourceTypePatch, 8), false);
|
||||
if (res != nullptr) {
|
||||
_townsDriver->loadInstruments(*res);
|
||||
} else {
|
||||
warning("MidiPlayer_FMTowns: Failed to open patch 8");
|
||||
result = MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MidiPlayer_FMTowns::hasRhythmChannel() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte MidiPlayer_FMTowns::getPlayId() const {
|
||||
return (_version == SCI_VERSION_1_EARLY) ? 0x00 : 0x16;
|
||||
}
|
||||
|
||||
int MidiPlayer_FMTowns::getPolyphony() const {
|
||||
// WORKAROUND:
|
||||
// I set the return value to 16 for SCI_VERSION_1_EARLY here, which fixes music playback in Mixed Up Mothergoose.
|
||||
// This has been broken since the introduction of SciMusic::remapChannels() and the corresponding code.
|
||||
// The original code of Mixed Up Mothergoose code doesn't have the remapping and doesn't seem to check the polyphony
|
||||
// setting ever. So the value of 1 was probably incorrect.
|
||||
return (_version == SCI_VERSION_1_EARLY) ? 16 : 6;
|
||||
}
|
||||
|
||||
void MidiPlayer_FMTowns::playSwitch(bool play) {
|
||||
if (_townsDriver)
|
||||
_townsDriver->setSoundOn(play);
|
||||
}
|
||||
|
||||
MidiPlayer *MidiPlayer_FMTowns_create(SciVersion _soundVersion) {
|
||||
return new MidiPlayer_FMTowns(_soundVersion);
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
223
engines/sci/sound/drivers/gm_names.h
Normal file
223
engines/sci/sound/drivers/gm_names.h
Normal file
@@ -0,0 +1,223 @@
|
||||
/* 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 SCI_SOUND_DRIVERS_GM_NAMES_H
|
||||
#define SCI_SOUND_DRIVERS_GM_NAMES_H
|
||||
|
||||
namespace Sci {
|
||||
|
||||
// These tables are only used for debugging. Don't include them for devices
|
||||
// with not enough available memory (e.g. phones), where REDUCE_MEMORY_USAGE
|
||||
// is defined
|
||||
#ifndef REDUCE_MEMORY_USAGE
|
||||
|
||||
static const char *const GmInstrumentNames[] = {
|
||||
/*000*/ "Acoustic Grand Piano",
|
||||
/*001*/ "Bright Acoustic Piano",
|
||||
/*002*/ "Electric Grand Piano",
|
||||
/*003*/ "Honky-tonk Piano",
|
||||
/*004*/ "Electric Piano 1",
|
||||
/*005*/ "Electric Piano 2",
|
||||
/*006*/ "Harpsichord",
|
||||
/*007*/ "Clavinet",
|
||||
/*008*/ "Celesta",
|
||||
/*009*/ "Glockenspiel",
|
||||
/*010*/ "Music Box",
|
||||
/*011*/ "Vibraphone",
|
||||
/*012*/ "Marimba",
|
||||
/*013*/ "Xylophone",
|
||||
/*014*/ "Tubular Bells",
|
||||
/*015*/ "Dulcimer",
|
||||
/*016*/ "Drawbar Organ",
|
||||
/*017*/ "Percussive Organ",
|
||||
/*018*/ "Rock Organ",
|
||||
/*019*/ "Church Organ",
|
||||
/*020*/ "Reed Organ",
|
||||
/*021*/ "Accordion",
|
||||
/*022*/ "Harmonica",
|
||||
/*023*/ "Tango Accordion",
|
||||
/*024*/ "Acoustic Guitar (nylon)",
|
||||
/*025*/ "Acoustic Guitar (steel)",
|
||||
/*026*/ "Electric Guitar (jazz)",
|
||||
/*027*/ "Electric Guitar (clean)",
|
||||
/*028*/ "Electric Guitar (muted)",
|
||||
/*029*/ "Overdriven Guitar",
|
||||
/*030*/ "Distortion Guitar",
|
||||
/*031*/ "Guitar Harmonics",
|
||||
/*032*/ "Acoustic Bass",
|
||||
/*033*/ "Electric Bass (finger)",
|
||||
/*034*/ "Electric Bass (pick)",
|
||||
/*035*/ "Fretless Bass",
|
||||
/*036*/ "Slap Bass 1",
|
||||
/*037*/ "Slap Bass 2",
|
||||
/*038*/ "Synth Bass 1",
|
||||
/*039*/ "Synth Bass 2",
|
||||
/*040*/ "Violin",
|
||||
/*041*/ "Viola",
|
||||
/*042*/ "Cello",
|
||||
/*043*/ "Contrabass",
|
||||
/*044*/ "Tremolo Strings",
|
||||
/*045*/ "Pizzicato Strings",
|
||||
/*046*/ "Orchestral Harp",
|
||||
/*047*/ "Timpani",
|
||||
/*048*/ "String Ensemble 1",
|
||||
/*049*/ "String Ensemble 2",
|
||||
/*050*/ "SynthStrings 1",
|
||||
/*051*/ "SynthStrings 2",
|
||||
/*052*/ "Choir Aahs",
|
||||
/*053*/ "Voice Oohs",
|
||||
/*054*/ "Synth Voice",
|
||||
/*055*/ "Orchestra Hit",
|
||||
/*056*/ "Trumpet",
|
||||
/*057*/ "Trombone",
|
||||
/*058*/ "Tuba",
|
||||
/*059*/ "Muted Trumpet",
|
||||
/*060*/ "French Horn",
|
||||
/*061*/ "Brass Section",
|
||||
/*062*/ "SynthBrass 1",
|
||||
/*063*/ "SynthBrass 2",
|
||||
/*064*/ "Soprano Sax",
|
||||
/*065*/ "Alto Sax",
|
||||
/*066*/ "Tenor Sax",
|
||||
/*067*/ "Baritone Sax",
|
||||
/*068*/ "Oboe",
|
||||
/*069*/ "English Horn",
|
||||
/*070*/ "Bassoon",
|
||||
/*071*/ "Clarinet",
|
||||
/*072*/ "Piccolo",
|
||||
/*073*/ "Flute",
|
||||
/*074*/ "Recorder",
|
||||
/*075*/ "Pan Flute",
|
||||
/*076*/ "Blown Bottle",
|
||||
/*077*/ "Shakuhachi",
|
||||
/*078*/ "Whistle",
|
||||
/*079*/ "Ocarina",
|
||||
/*080*/ "Lead 1 (square)",
|
||||
/*081*/ "Lead 2 (sawtooth)",
|
||||
/*082*/ "Lead 3 (calliope)",
|
||||
/*083*/ "Lead 4 (chiff)",
|
||||
/*084*/ "Lead 5 (charang)",
|
||||
/*085*/ "Lead 6 (voice)",
|
||||
/*086*/ "Lead 7 (fifths)",
|
||||
/*087*/ "Lead 8 (bass+lead)",
|
||||
/*088*/ "Pad 1 (new age)",
|
||||
/*089*/ "Pad 2 (warm)",
|
||||
/*090*/ "Pad 3 (polysynth)",
|
||||
/*091*/ "Pad 4 (choir)",
|
||||
/*092*/ "Pad 5 (bowed)",
|
||||
/*093*/ "Pad 6 (metallic)",
|
||||
/*094*/ "Pad 7 (halo)",
|
||||
/*095*/ "Pad 8 (sweep)",
|
||||
/*096*/ "FX 1 (rain)",
|
||||
/*097*/ "FX 2 (soundtrack)",
|
||||
/*098*/ "FX 3 (crystal)",
|
||||
/*099*/ "FX 4 (atmosphere)",
|
||||
/*100*/ "FX 5 (brightness)",
|
||||
/*101*/ "FX 6 (goblins)",
|
||||
/*102*/ "FX 7 (echoes)",
|
||||
/*103*/ "FX 8 (sci-fi)",
|
||||
/*104*/ "Sitar",
|
||||
/*105*/ "Banjo",
|
||||
/*106*/ "Shamisen",
|
||||
/*107*/ "Koto",
|
||||
/*108*/ "Kalimba",
|
||||
/*109*/ "Bag pipe",
|
||||
/*110*/ "Fiddle",
|
||||
/*111*/ "Shannai",
|
||||
/*112*/ "Tinkle Bell",
|
||||
/*113*/ "Agogo",
|
||||
/*114*/ "Steel Drums",
|
||||
/*115*/ "Woodblock",
|
||||
/*116*/ "Taiko Drum",
|
||||
/*117*/ "Melodic Tom",
|
||||
/*118*/ "Synth Drum",
|
||||
/*119*/ "Reverse Cymbal",
|
||||
/*120*/ "Guitar Fret Noise",
|
||||
/*121*/ "Breath Noise",
|
||||
/*122*/ "Seashore",
|
||||
/*123*/ "Bird Tweet",
|
||||
/*124*/ "Telephone Ring",
|
||||
/*125*/ "Helicopter",
|
||||
/*126*/ "Applause",
|
||||
/*127*/ "Gunshot"
|
||||
};
|
||||
|
||||
// The GM Percussion map is downwards compatible to the MT32 map, which is used in SCI
|
||||
static const char *const GmPercussionNames[] = {
|
||||
/*00*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/*10*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/*20*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/*30*/ 0, 0, 0, 0, 0,
|
||||
// The preceding percussions are not covered by the GM standard
|
||||
/*35*/ "Acoustic Bass Drum",
|
||||
/*36*/ "Bass Drum 1",
|
||||
/*37*/ "Side Stick",
|
||||
/*38*/ "Acoustic Snare",
|
||||
/*39*/ "Hand Clap",
|
||||
/*40*/ "Electric Snare",
|
||||
/*41*/ "Low Floor Tom",
|
||||
/*42*/ "Closed Hi-Hat",
|
||||
/*43*/ "High Floor Tom",
|
||||
/*44*/ "Pedal Hi-Hat",
|
||||
/*45*/ "Low Tom",
|
||||
/*46*/ "Open Hi-Hat",
|
||||
/*47*/ "Low-Mid Tom",
|
||||
/*48*/ "Hi-Mid Tom",
|
||||
/*49*/ "Crash Cymbal 1",
|
||||
/*50*/ "High Tom",
|
||||
/*51*/ "Ride Cymbal 1",
|
||||
/*52*/ "Chinese Cymbal",
|
||||
/*53*/ "Ride Bell",
|
||||
/*54*/ "Tambourine",
|
||||
/*55*/ "Splash Cymbal",
|
||||
/*56*/ "Cowbell",
|
||||
/*57*/ "Crash Cymbal 2",
|
||||
/*58*/ "Vibraslap",
|
||||
/*59*/ "Ride Cymbal 2",
|
||||
/*60*/ "Hi Bongo",
|
||||
/*61*/ "Low Bongo",
|
||||
/*62*/ "Mute Hi Conga",
|
||||
/*63*/ "Open Hi Conga",
|
||||
/*64*/ "Low Conga",
|
||||
/*65*/ "High Timbale",
|
||||
/*66*/ "Low Timbale",
|
||||
/*67*/ "High Agogo",
|
||||
/*68*/ "Low Agogo",
|
||||
/*69*/ "Cabasa",
|
||||
/*70*/ "Maracas",
|
||||
/*71*/ "Short Whistle",
|
||||
/*72*/ "Long Whistle",
|
||||
/*73*/ "Short Guiro",
|
||||
/*74*/ "Long Guiro",
|
||||
/*75*/ "Claves",
|
||||
/*76*/ "Hi Wood Block",
|
||||
/*77*/ "Low Wood Block",
|
||||
/*78*/ "Mute Cuica",
|
||||
/*79*/ "Open Cuica",
|
||||
/*80*/ "Mute Triangle",
|
||||
/*81*/ "Open Triangle"
|
||||
};
|
||||
|
||||
#endif // REDUCE_MEMORY_USAGE
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_SOUND_DRIVERS_GM_NAMES_H
|
||||
272
engines/sci/sound/drivers/macmixer.h
Normal file
272
engines/sci/sound/drivers/macmixer.h
Normal file
@@ -0,0 +1,272 @@
|
||||
/* 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/audiostream.h"
|
||||
#include "audio/mixer.h"
|
||||
#include "common/frac.h"
|
||||
#include "common/mutex.h"
|
||||
#include "common/system.h"
|
||||
|
||||
#ifndef SCI_SOUND_DRIVERS_MACMIXER_H
|
||||
#define SCI_SOUND_DRIVERS_MACMIXER_H
|
||||
|
||||
namespace Sci {
|
||||
|
||||
// Unsigned version of frac_t
|
||||
typedef uint32 ufrac_t;
|
||||
static inline ufrac_t uintToUfrac(uint16 value) { return value << FRAC_BITS; }
|
||||
static inline uint16 ufracToUint(ufrac_t value) { return value >> FRAC_BITS; }
|
||||
|
||||
template <typename T>
|
||||
class Mixer_Mac : public Audio::AudioStream {
|
||||
public:
|
||||
enum {
|
||||
kChannels = 4,
|
||||
kInterruptFreq = 60
|
||||
};
|
||||
|
||||
enum Mode {
|
||||
kModeAuthentic,
|
||||
kModeHq,
|
||||
kModeHqStereo
|
||||
};
|
||||
|
||||
Mixer_Mac(Mode mode);
|
||||
void startMixer();
|
||||
void stopMixer();
|
||||
void setMixerVolume(byte volume) { _mixVolume = volume; }
|
||||
void resetChannel(uint channel);
|
||||
void resetChannels();
|
||||
// NOTE: Last sample accessed is data[endOffset + 1] in kModeHq(Stereo)
|
||||
void setChannelData(uint channel, const byte *data, uint16 startOffset, uint16 endOffset, uint16 loopLength = 0);
|
||||
void setChannelStep(uint channel, ufrac_t step);
|
||||
void setChannelVolume(uint channel, byte volume);
|
||||
void setChannelPan(uint channel, byte pan);
|
||||
|
||||
// AudioStream
|
||||
bool isStereo() const override { return _mode == kModeHqStereo; }
|
||||
int getRate() const override { return (_mode == kModeAuthentic ? 11127 : g_system->getMixer()->getOutputRate()); }
|
||||
int readBuffer(int16 *data, const int numSamples) override;
|
||||
bool endOfData() const override { return false; }
|
||||
|
||||
Common::Mutex _mutex;
|
||||
|
||||
private:
|
||||
template <Mode mode>
|
||||
void generateSamples(int16 *buf, int len);
|
||||
|
||||
struct Channel {
|
||||
ufrac_t pos;
|
||||
ufrac_t step;
|
||||
const byte *data;
|
||||
uint16 endOffset;
|
||||
uint16 loopLength;
|
||||
byte volume;
|
||||
int8 pan;
|
||||
};
|
||||
|
||||
ufrac_t _nextTick;
|
||||
ufrac_t _samplesPerTick;
|
||||
bool _isPlaying;
|
||||
const Mode _mode;
|
||||
Channel _mixChannels[kChannels];
|
||||
byte _mixVolume;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
Mixer_Mac<T>::Mixer_Mac(Mode mode) :
|
||||
_nextTick(0),
|
||||
_samplesPerTick(0),
|
||||
_mode(mode),
|
||||
_isPlaying(false),
|
||||
_mixChannels(),
|
||||
_mixVolume(8) {}
|
||||
|
||||
template <typename T>
|
||||
void Mixer_Mac<T>::startMixer() {
|
||||
_nextTick = _samplesPerTick = uintToUfrac(getRate() / kInterruptFreq) + uintToUfrac(getRate() % kInterruptFreq) / kInterruptFreq;
|
||||
|
||||
resetChannels();
|
||||
_isPlaying = true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Mixer_Mac<T>::stopMixer() {
|
||||
resetChannels();
|
||||
_isPlaying = false;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Mixer_Mac<T>::setChannelData(uint channel, const byte *data, uint16 startOffset, uint16 endOffset, uint16 loopLength) {
|
||||
assert(channel < kChannels);
|
||||
|
||||
Channel &ch = _mixChannels[channel];
|
||||
|
||||
ch.data = data;
|
||||
ch.pos = uintToUfrac(startOffset);
|
||||
ch.endOffset = endOffset;
|
||||
ch.loopLength = loopLength;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Mixer_Mac<T>::setChannelStep(uint channel, ufrac_t step) {
|
||||
assert(channel < kChannels);
|
||||
|
||||
if (_mode == kModeAuthentic) {
|
||||
_mixChannels[channel].step = step;
|
||||
} else {
|
||||
// We could take 11127Hz here, but it appears the original steps were
|
||||
// computed for 11000Hz
|
||||
// FIXME: One or two more bits of step precision might be nice here
|
||||
_mixChannels[channel].step = (ufrac_t)(step * 11000ULL / getRate());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Mixer_Mac<T>::setChannelVolume(uint channel, byte volume) {
|
||||
assert(channel < kChannels);
|
||||
_mixChannels[channel].volume = volume;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Mixer_Mac<T>::setChannelPan(uint channel, byte pan) {
|
||||
assert(channel < kChannels);
|
||||
_mixChannels[channel].pan = pan;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename Mixer_Mac<T>::Mode mode>
|
||||
void Mixer_Mac<T>::generateSamples(int16 *data, int len) {
|
||||
for (int i = 0; i < len; ++i) {
|
||||
int32 mixL = 0;
|
||||
int32 mixR = 0;
|
||||
|
||||
for (int ci = 0; ci < kChannels; ++ci) {
|
||||
Channel &ch = _mixChannels[ci];
|
||||
|
||||
if (!ch.data)
|
||||
continue;
|
||||
|
||||
const uint16 curOffset = ufracToUint(ch.pos);
|
||||
|
||||
if (mode == kModeHq || mode == kModeHqStereo) {
|
||||
int32 sample = (ch.data[curOffset] - 0x80) << 8;
|
||||
// Since _extraSamples > 0, we can safely access this sample
|
||||
const int32 sample2 = (ch.data[curOffset + 1] - 0x80) << 8;
|
||||
sample += fracToInt((sample2 - sample) * (ch.pos & FRAC_LO_MASK));
|
||||
sample *= ch.volume;
|
||||
|
||||
if (mode == kModeHqStereo) {
|
||||
mixL += sample * (127 - ch.pan) / (63 * 64);
|
||||
mixR += sample * ch.pan / (63 * 64);
|
||||
} else {
|
||||
mixL += sample / 63;
|
||||
}
|
||||
} else {
|
||||
mixL += static_cast<T *>(this)->applyChannelVolume(ch.volume, ch.data[curOffset]) << 8;
|
||||
}
|
||||
|
||||
ch.pos += ch.step;
|
||||
|
||||
if (ufracToUint(ch.pos) > ch.endOffset) {
|
||||
if (ch.loopLength > 0) {
|
||||
do {
|
||||
ch.pos -= uintToUfrac(ch.loopLength);
|
||||
} while (ufracToUint(ch.pos) > ch.endOffset);
|
||||
} else {
|
||||
static_cast<T *>(this)->onChannelFinished(ci);
|
||||
ch.data = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*data++ = (int16)CLIP<int32>(mixL, -32768, 32767) * _mixVolume / 8;
|
||||
if (mode == kModeHqStereo)
|
||||
*data++ = (int16)CLIP<int32>(mixR, -32768, 32767) * _mixVolume / 8;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int Mixer_Mac<T>::readBuffer(int16 *data, const int numSamples) {
|
||||
// Would probably be better inside generateSamples, but let's follow Audio::Paula
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
if (!_isPlaying) {
|
||||
memset(data, 0, numSamples * 2);
|
||||
return numSamples;
|
||||
}
|
||||
|
||||
const int stereoFactor = isStereo() ? 2 : 1;
|
||||
int len = numSamples / stereoFactor;
|
||||
|
||||
do {
|
||||
int step = len;
|
||||
if (step > ufracToUint(_nextTick))
|
||||
step = ufracToUint(_nextTick);
|
||||
|
||||
switch (_mode) {
|
||||
case kModeAuthentic:
|
||||
generateSamples<kModeAuthentic>(data, step);
|
||||
break;
|
||||
case kModeHq:
|
||||
generateSamples<kModeHq>(data, step);
|
||||
break;
|
||||
case kModeHqStereo:
|
||||
generateSamples<kModeHqStereo>(data, step);
|
||||
}
|
||||
|
||||
_nextTick -= uintToUfrac(step);
|
||||
if (ufracToUint(_nextTick) == 0) {
|
||||
static_cast<T *>(this)->interrupt();
|
||||
_nextTick += _samplesPerTick;
|
||||
}
|
||||
|
||||
data += step * stereoFactor;
|
||||
len -= step;
|
||||
} while (len);
|
||||
|
||||
return numSamples;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Mixer_Mac<T>::resetChannel(uint channel) {
|
||||
assert(channel < kChannels);
|
||||
|
||||
Channel &ch = _mixChannels[channel];
|
||||
|
||||
ch.pos = 0;
|
||||
ch.step = 0;
|
||||
ch.data = nullptr;
|
||||
ch.endOffset = 0;
|
||||
ch.loopLength = 0;
|
||||
ch.volume = 0;
|
||||
ch.pan = 64;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Mixer_Mac<T>::resetChannels() {
|
||||
for (uint ci = 0; ci < kChannels; ++ci)
|
||||
resetChannel(ci);
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_SOUND_DRIVERS_MACMIXER_H
|
||||
373
engines/sci/sound/drivers/map-mt32-to-gm.h
Normal file
373
engines/sci/sound/drivers/map-mt32-to-gm.h
Normal file
@@ -0,0 +1,373 @@
|
||||
/* 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 SCI_SOUND_DRIVERS_MAP_MT32_TO_GM_H
|
||||
#define SCI_SOUND_DRIVERS_MAP_MT32_TO_GM_H
|
||||
|
||||
namespace Sci {
|
||||
|
||||
#include "common/list.h"
|
||||
|
||||
// Patch not mapped
|
||||
#define MIDI_UNMAPPED 0xff
|
||||
// Patch mapped to rhythm key
|
||||
#define MIDI_MAPPED_TO_RHYTHM 0xfe
|
||||
|
||||
struct Mt32ToGmMap {
|
||||
const char *name;
|
||||
uint8 gmInstr;
|
||||
uint8 gmRhythmKey;
|
||||
};
|
||||
|
||||
/*******************************************
|
||||
* Fancy instrument mappings begin here... *
|
||||
*******************************************/
|
||||
|
||||
|
||||
static const Mt32ToGmMap Mt32PresetTimbreMaps[] = {
|
||||
/*000*/ {"AcouPiano1", 0, MIDI_UNMAPPED},
|
||||
/*001*/ {"AcouPiano2", 1, MIDI_UNMAPPED},
|
||||
/*002*/ {"AcouPiano3", 0, MIDI_UNMAPPED},
|
||||
/*003*/ {"ElecPiano1", 4, MIDI_UNMAPPED},
|
||||
/*004*/ {"ElecPiano2", 5, MIDI_UNMAPPED},
|
||||
/*005*/ {"ElecPiano3", 4, MIDI_UNMAPPED},
|
||||
/*006*/ {"ElecPiano4", 5, MIDI_UNMAPPED},
|
||||
/*007*/ {"Honkytonk ", 3, MIDI_UNMAPPED},
|
||||
/*008*/ {"Elec Org 1", 16, MIDI_UNMAPPED},
|
||||
/*009*/ {"Elec Org 2", 17, MIDI_UNMAPPED},
|
||||
/*010*/ {"Elec Org 3", 18, MIDI_UNMAPPED},
|
||||
/*011*/ {"Elec Org 4", 18, MIDI_UNMAPPED},
|
||||
/*012*/ {"Pipe Org 1", 19, MIDI_UNMAPPED},
|
||||
/*013*/ {"Pipe Org 2", 19, MIDI_UNMAPPED},
|
||||
/*014*/ {"Pipe Org 3", 20, MIDI_UNMAPPED},
|
||||
/*015*/ {"Accordion ", 21, MIDI_UNMAPPED},
|
||||
/*016*/ {"Harpsi 1 ", 6, MIDI_UNMAPPED},
|
||||
/*017*/ {"Harpsi 2 ", 6, MIDI_UNMAPPED},
|
||||
/*018*/ {"Harpsi 3 ", 6, MIDI_UNMAPPED},
|
||||
/*019*/ {"Clavi 1 ", 7, MIDI_UNMAPPED},
|
||||
/*020*/ {"Clavi 2 ", 7, MIDI_UNMAPPED},
|
||||
/*021*/ {"Clavi 3 ", 7, MIDI_UNMAPPED},
|
||||
/*022*/ {"Celesta 1 ", 8, MIDI_UNMAPPED},
|
||||
/*023*/ {"Celesta 2 ", 8, MIDI_UNMAPPED},
|
||||
/*024*/ {"Syn Brass1", 62, MIDI_UNMAPPED},
|
||||
/*025*/ {"Syn Brass2", 63, MIDI_UNMAPPED},
|
||||
/*026*/ {"Syn Brass3", 62, MIDI_UNMAPPED},
|
||||
/*027*/ {"Syn Brass4", 63, MIDI_UNMAPPED},
|
||||
/*028*/ {"Syn Bass 1", 38, MIDI_UNMAPPED},
|
||||
/*029*/ {"Syn Bass 2", 39, MIDI_UNMAPPED},
|
||||
/*030*/ {"Syn Bass 3", 38, MIDI_UNMAPPED},
|
||||
/*031*/ {"Syn Bass 4", 39, MIDI_UNMAPPED},
|
||||
/*032*/ {"Fantasy ", 88, MIDI_UNMAPPED},
|
||||
/*033*/ {"Harmo Pan ", 89, MIDI_UNMAPPED},
|
||||
/*034*/ {"Chorale ", 52, MIDI_UNMAPPED},
|
||||
/*035*/ {"Glasses ", 98, MIDI_UNMAPPED},
|
||||
/*036*/ {"Soundtrack", 97, MIDI_UNMAPPED},
|
||||
/*037*/ {"Atmosphere", 99, MIDI_UNMAPPED},
|
||||
/*038*/ {"Warm Bell ", 89, MIDI_UNMAPPED},
|
||||
/*039*/ {"Funny Vox ", 85, MIDI_UNMAPPED},
|
||||
/*040*/ {"Echo Bell ", 39, MIDI_UNMAPPED},
|
||||
/*041*/ {"Ice Rain ", 101, MIDI_UNMAPPED},
|
||||
/*042*/ {"Oboe 2001 ", 68, MIDI_UNMAPPED},
|
||||
/*043*/ {"Echo Pan ", 87, MIDI_UNMAPPED},
|
||||
/*044*/ {"DoctorSolo", 86, MIDI_UNMAPPED},
|
||||
/*045*/ {"Schooldaze", 103, MIDI_UNMAPPED},
|
||||
/*046*/ {"BellSinger", 88, MIDI_UNMAPPED},
|
||||
/*047*/ {"SquareWave", 80, MIDI_UNMAPPED},
|
||||
/*048*/ {"Str Sect 1", 48, MIDI_UNMAPPED},
|
||||
/*049*/ {"Str Sect 2", 48, MIDI_UNMAPPED},
|
||||
/*050*/ {"Str Sect 3", 49, MIDI_UNMAPPED},
|
||||
/*051*/ {"Pizzicato ", 45, MIDI_UNMAPPED},
|
||||
/*052*/ {"Violin 1 ", 40, MIDI_UNMAPPED},
|
||||
/*053*/ {"Violin 2 ", 40, MIDI_UNMAPPED},
|
||||
/*054*/ {"Cello 1 ", 42, MIDI_UNMAPPED},
|
||||
/*055*/ {"Cello 2 ", 42, MIDI_UNMAPPED},
|
||||
/*056*/ {"Contrabass", 43, MIDI_UNMAPPED},
|
||||
/*057*/ {"Harp 1 ", 46, MIDI_UNMAPPED},
|
||||
/*058*/ {"Harp 2 ", 46, MIDI_UNMAPPED},
|
||||
/*059*/ {"Guitar 1 ", 24, MIDI_UNMAPPED},
|
||||
/*060*/ {"Guitar 2 ", 25, MIDI_UNMAPPED},
|
||||
/*061*/ {"Elec Gtr 1", 26, MIDI_UNMAPPED},
|
||||
/*062*/ {"Elec Gtr 2", 27, MIDI_UNMAPPED},
|
||||
/*063*/ {"Sitar ", 104, MIDI_UNMAPPED},
|
||||
/*064*/ {"Acou Bass1", 32, MIDI_UNMAPPED},
|
||||
/*065*/ {"Acou Bass2", 33, MIDI_UNMAPPED},
|
||||
/*066*/ {"Elec Bass1", 34, MIDI_UNMAPPED},
|
||||
/*067*/ {"Elec Bass2", 39, MIDI_UNMAPPED},
|
||||
/*068*/ {"Slap Bass1", 36, MIDI_UNMAPPED},
|
||||
/*069*/ {"Slap Bass2", 37, MIDI_UNMAPPED},
|
||||
/*070*/ {"Fretless 1", 35, MIDI_UNMAPPED},
|
||||
/*071*/ {"Fretless 2", 35, MIDI_UNMAPPED},
|
||||
/*072*/ {"Flute 1 ", 73, MIDI_UNMAPPED},
|
||||
/*073*/ {"Flute 2 ", 73, MIDI_UNMAPPED},
|
||||
/*074*/ {"Piccolo 1 ", 72, MIDI_UNMAPPED},
|
||||
/*075*/ {"Piccolo 2 ", 72, MIDI_UNMAPPED},
|
||||
/*076*/ {"Recorder ", 74, MIDI_UNMAPPED},
|
||||
/*077*/ {"Panpipes ", 75, MIDI_UNMAPPED},
|
||||
/*078*/ {"Sax 1 ", 64, MIDI_UNMAPPED},
|
||||
/*079*/ {"Sax 2 ", 65, MIDI_UNMAPPED},
|
||||
/*080*/ {"Sax 3 ", 66, MIDI_UNMAPPED},
|
||||
/*081*/ {"Sax 4 ", 67, MIDI_UNMAPPED},
|
||||
/*082*/ {"Clarinet 1", 71, MIDI_UNMAPPED},
|
||||
/*083*/ {"Clarinet 2", 71, MIDI_UNMAPPED},
|
||||
/*084*/ {"Oboe ", 68, MIDI_UNMAPPED},
|
||||
/*085*/ {"Engl Horn ", 69, MIDI_UNMAPPED},
|
||||
/*086*/ {"Bassoon ", 70, MIDI_UNMAPPED},
|
||||
/*087*/ {"Harmonica ", 22, MIDI_UNMAPPED},
|
||||
/*088*/ {"Trumpet 1 ", 56, MIDI_UNMAPPED},
|
||||
/*089*/ {"Trumpet 2 ", 56, MIDI_UNMAPPED},
|
||||
/*090*/ {"Trombone 1", 57, MIDI_UNMAPPED},
|
||||
/*091*/ {"Trombone 2", 57, MIDI_UNMAPPED},
|
||||
/*092*/ {"Fr Horn 1 ", 60, MIDI_UNMAPPED},
|
||||
/*093*/ {"Fr Horn 2 ", 60, MIDI_UNMAPPED},
|
||||
/*094*/ {"Tuba ", 58, MIDI_UNMAPPED},
|
||||
/*095*/ {"Brs Sect 1", 61, MIDI_UNMAPPED},
|
||||
/*096*/ {"Brs Sect 2", 61, MIDI_UNMAPPED},
|
||||
/*097*/ {"Vibe 1 ", 11, MIDI_UNMAPPED},
|
||||
/*098*/ {"Vibe 2 ", 11, MIDI_UNMAPPED},
|
||||
/*099*/ {"Syn Mallet", 15, MIDI_UNMAPPED},
|
||||
/*100*/ {"Wind Bell ", 88, MIDI_UNMAPPED},
|
||||
/*101*/ {"Glock ", 9, MIDI_UNMAPPED},
|
||||
/*102*/ {"Tube Bell ", 14, MIDI_UNMAPPED},
|
||||
/*103*/ {"Xylophone ", 13, MIDI_UNMAPPED},
|
||||
/*104*/ {"Marimba ", 12, MIDI_UNMAPPED},
|
||||
/*105*/ {"Koto ", 107, MIDI_UNMAPPED},
|
||||
/*106*/ {"Sho ", 111, MIDI_UNMAPPED},
|
||||
/*107*/ {"Shakuhachi", 77, MIDI_UNMAPPED},
|
||||
/*108*/ {"Whistle 1 ", 78, MIDI_UNMAPPED},
|
||||
/*109*/ {"Whistle 2 ", 78, MIDI_UNMAPPED},
|
||||
/*110*/ {"BottleBlow", 76, MIDI_UNMAPPED},
|
||||
/*111*/ {"BreathPipe", 121, MIDI_UNMAPPED},
|
||||
/*112*/ {"Timpani ", 47, MIDI_UNMAPPED},
|
||||
/*113*/ {"MelodicTom", 117, MIDI_UNMAPPED},
|
||||
/*114*/ {"Deep Snare", MIDI_MAPPED_TO_RHYTHM, 38},
|
||||
/*115*/ {"Elec Perc1", 115, MIDI_UNMAPPED}, // ?
|
||||
/*116*/ {"Elec Perc2", 118, MIDI_UNMAPPED}, // ?
|
||||
/*117*/ {"Taiko ", 116, MIDI_UNMAPPED},
|
||||
/*118*/ {"Taiko Rim ", 118, MIDI_UNMAPPED},
|
||||
/*119*/ {"Cymbal ", MIDI_MAPPED_TO_RHYTHM, 51},
|
||||
/*120*/ {"Castanets ", MIDI_MAPPED_TO_RHYTHM, 75}, // approximation
|
||||
/*121*/ {"Triangle ", 112, MIDI_UNMAPPED},
|
||||
/*122*/ {"Orche Hit ", 55, MIDI_UNMAPPED},
|
||||
/*123*/ {"Telephone ", 124, MIDI_UNMAPPED},
|
||||
/*124*/ {"Bird Tweet", 123, MIDI_UNMAPPED},
|
||||
/*125*/ {"OneNoteJam", 8, MIDI_UNMAPPED}, // approximation
|
||||
/*126*/ {"WaterBells", 98, MIDI_UNMAPPED},
|
||||
/*127*/ {"JungleTune", 75, MIDI_UNMAPPED} // approximation
|
||||
};
|
||||
|
||||
static const Mt32ToGmMap Mt32RhythmTimbreMaps[] = {
|
||||
/*00*/ {"Acou BD ", MIDI_MAPPED_TO_RHYTHM, 35},
|
||||
/*01*/ {"Acou SD ", MIDI_MAPPED_TO_RHYTHM, 38},
|
||||
/*02*/ {"Acou HiTom", 117, 50},
|
||||
/*03*/ {"AcouMidTom", 117, 47},
|
||||
/*04*/ {"AcouLowTom", 117, 41},
|
||||
/*05*/ {"Elec SD ", MIDI_MAPPED_TO_RHYTHM, 40},
|
||||
/*06*/ {"Clsd HiHat", MIDI_MAPPED_TO_RHYTHM, 42},
|
||||
/*07*/ {"OpenHiHat1", MIDI_MAPPED_TO_RHYTHM, 46},
|
||||
/*08*/ {"Crash Cym ", MIDI_MAPPED_TO_RHYTHM, 49},
|
||||
/*09*/ {"Ride Cym ", MIDI_MAPPED_TO_RHYTHM, 51},
|
||||
/*10*/ {"Rim Shot ", MIDI_MAPPED_TO_RHYTHM, 37},
|
||||
/*11*/ {"Hand Clap ", MIDI_MAPPED_TO_RHYTHM, 39},
|
||||
/*12*/ {"Cowbell ", MIDI_MAPPED_TO_RHYTHM, 56},
|
||||
/*13*/ {"Mt HiConga", MIDI_MAPPED_TO_RHYTHM, 62},
|
||||
/*14*/ {"High Conga", MIDI_MAPPED_TO_RHYTHM, 63},
|
||||
/*15*/ {"Low Conga ", MIDI_MAPPED_TO_RHYTHM, 64},
|
||||
/*16*/ {"Hi Timbale", MIDI_MAPPED_TO_RHYTHM, 65},
|
||||
/*17*/ {"LowTimbale", MIDI_MAPPED_TO_RHYTHM, 66},
|
||||
/*18*/ {"High Bongo", MIDI_MAPPED_TO_RHYTHM, 60},
|
||||
/*19*/ {"Low Bongo ", MIDI_MAPPED_TO_RHYTHM, 61},
|
||||
/*20*/ {"High Agogo", 113, 67},
|
||||
/*21*/ {"Low Agogo ", 113, 68},
|
||||
/*22*/ {"Tambourine", MIDI_MAPPED_TO_RHYTHM, 54},
|
||||
/*23*/ {"Claves ", MIDI_MAPPED_TO_RHYTHM, 75},
|
||||
/*24*/ {"Maracas ", MIDI_MAPPED_TO_RHYTHM, 70},
|
||||
/*25*/ {"SmbaWhis L", 78, 72},
|
||||
/*26*/ {"SmbaWhis S", 78, 71},
|
||||
/*27*/ {"Cabasa ", MIDI_MAPPED_TO_RHYTHM, 69},
|
||||
/*28*/ {"Quijada ", MIDI_MAPPED_TO_RHYTHM, 73},
|
||||
/*29*/ {"OpenHiHat2", MIDI_MAPPED_TO_RHYTHM, 44}
|
||||
};
|
||||
|
||||
static const uint8 Mt32PresetRhythmKeymap[] = {
|
||||
MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED,
|
||||
MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED,
|
||||
MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED,
|
||||
MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, 35, 36, 37, 38, 39,
|
||||
40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
|
||||
50, 51, MIDI_UNMAPPED, MIDI_UNMAPPED, 54, MIDI_UNMAPPED, 56, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED,
|
||||
60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
|
||||
70, 71, 72, 73, MIDI_UNMAPPED, 75, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED,
|
||||
MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED,
|
||||
MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED,
|
||||
MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED,
|
||||
MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED,
|
||||
MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED, MIDI_UNMAPPED
|
||||
};
|
||||
|
||||
/* +++ - Don't change unless you've got a good reason
|
||||
++ - Looks good, sounds ok
|
||||
+ - Not too bad, but is it right?
|
||||
? - Where do I map this one?
|
||||
?? - Any good ideas?
|
||||
??? - I'm clueless?
|
||||
R - Rhythm...
|
||||
*/
|
||||
static const Mt32ToGmMap Mt32MemoryTimbreMaps[] = {
|
||||
{"AccPnoKA2 ", 1, MIDI_UNMAPPED}, // ++ (KQ1)
|
||||
{"Acou BD ", MIDI_MAPPED_TO_RHYTHM, 35}, // R (PQ2)
|
||||
{"Acou SD ", MIDI_MAPPED_TO_RHYTHM, 38}, // R (PQ2)
|
||||
{"AcouPnoKA ", 0, MIDI_UNMAPPED}, // ++ (KQ1)
|
||||
{"BASS ", 32, MIDI_UNMAPPED}, // + (LSL3)
|
||||
{"BASSOONPCM", 70, MIDI_UNMAPPED}, // + (LB1)
|
||||
{"BEACH WAVE", 122, MIDI_UNMAPPED}, // + (LSL3)
|
||||
{"BagPipes ", 109, MIDI_UNMAPPED},
|
||||
{"BassPizzMS", 45, MIDI_UNMAPPED}, // ++ (QFG1)
|
||||
{"BassoonKA ", 70, MIDI_UNMAPPED}, // ++ (KQ1)
|
||||
{"Bell MS", 112, MIDI_UNMAPPED}, // ++ (Iceman)
|
||||
{"Bells MS", 112, MIDI_UNMAPPED}, // + (QFG1)
|
||||
{"Big Bell ", 14, MIDI_UNMAPPED}, // + (LB1)
|
||||
{"Bird Tweet", 123, MIDI_UNMAPPED},
|
||||
{"BrsSect MS", 61, MIDI_UNMAPPED}, // +++ (Iceman)
|
||||
{"CLAPPING ", 126, MIDI_UNMAPPED}, // ++ (LSL3)
|
||||
{"Cabasa ", MIDI_MAPPED_TO_RHYTHM, 69}, // R (Hoyle)
|
||||
{"Calliope ", 82, MIDI_UNMAPPED}, // +++ (QFG1)
|
||||
{"CelticHarp", 46, MIDI_UNMAPPED}, // ++ (Camelot)
|
||||
{"Chicago MS", 1, MIDI_UNMAPPED}, // ++ (Iceman)
|
||||
{"Chop ", 117, MIDI_UNMAPPED},
|
||||
{"Chorale MS", 52, MIDI_UNMAPPED}, // + (Camelot)
|
||||
{"ClarinetMS", 71, MIDI_UNMAPPED},
|
||||
{"Claves ", MIDI_MAPPED_TO_RHYTHM, 75}, // R (PQ2)
|
||||
{"Claw MS", 118, MIDI_UNMAPPED}, // + (QFG1)
|
||||
{"ClockBell ", 14, MIDI_UNMAPPED}, // + (LB1)
|
||||
{"ConcertCym", MIDI_MAPPED_TO_RHYTHM, 55}, // R ? (KQ1)
|
||||
{"Conga MS", MIDI_MAPPED_TO_RHYTHM, 64}, // R (QFG1)
|
||||
{"CoolPhone ", 124, MIDI_UNMAPPED}, // ++ (LSL3)
|
||||
{"CracklesMS", 115, MIDI_UNMAPPED}, // ? (Camelot, QFG1)
|
||||
{"CreakyD MS", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ??? (KQ1)
|
||||
{"Cricket ", 120, MIDI_UNMAPPED}, // ? (LB1)
|
||||
{"CrshCymbMS", MIDI_MAPPED_TO_RHYTHM, 57}, // R +++ (Iceman)
|
||||
{"CstlGateMS", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (QFG1)
|
||||
{"CymSwellMS", MIDI_MAPPED_TO_RHYTHM, 55}, // R ? (Camelot, QFG1)
|
||||
{"CymbRollKA", MIDI_MAPPED_TO_RHYTHM, 57}, // R ? (KQ1)
|
||||
{"Cymbal Lo ", MIDI_UNMAPPED, MIDI_UNMAPPED}, // R ? (LSL3)
|
||||
{"card ", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (Hoyle)
|
||||
{"DirtGtr MS", 30, MIDI_UNMAPPED}, // + (Iceman)
|
||||
{"DirtGtr2MS", 29, MIDI_UNMAPPED}, // + (Iceman)
|
||||
{"E Bass MS", 33, MIDI_UNMAPPED}, // + (SQ3)
|
||||
{"ElecBassMS", 33, MIDI_UNMAPPED},
|
||||
{"ElecGtr MS", 27, MIDI_UNMAPPED}, // ++ (Iceman)
|
||||
{"EnglHornMS", 69, MIDI_UNMAPPED},
|
||||
{"FantasiaKA", 88, MIDI_UNMAPPED},
|
||||
{"Fantasy ", 99, MIDI_UNMAPPED}, // + (PQ2)
|
||||
{"Fantasy2MS", 99, MIDI_UNMAPPED}, // ++ (Camelot, QFG1)
|
||||
{"Filter MS", 95, MIDI_UNMAPPED}, // +++ (Iceman)
|
||||
{"Filter2 MS", 95, MIDI_UNMAPPED}, // ++ (Iceman)
|
||||
{"Flame2 MS", 121, MIDI_UNMAPPED}, // ? (QFG1)
|
||||
{"Flames MS", 121, MIDI_UNMAPPED}, // ? (QFG1)
|
||||
{"Flute MS", 73, MIDI_UNMAPPED}, // +++ (QFG1)
|
||||
{"FogHorn MS", 58, MIDI_UNMAPPED},
|
||||
{"FrHorn1 MS", 60, MIDI_UNMAPPED}, // +++ (QFG1)
|
||||
{"FunnyTrmp ", 56, MIDI_UNMAPPED}, // ++ (LB1)
|
||||
{"GameSnd MS", 80, MIDI_UNMAPPED},
|
||||
{"Glock MS", 9, MIDI_UNMAPPED}, // +++ (QFG1)
|
||||
{"Gunshot ", 127, MIDI_UNMAPPED}, // +++ (LB1)
|
||||
{"Hammer MS", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (QFG1)
|
||||
{"Harmonica2", 22, MIDI_UNMAPPED}, // +++ (LB1)
|
||||
{"Harpsi 1 ", 6, MIDI_UNMAPPED}, // + (Hoyle)
|
||||
{"Harpsi 2 ", 6, MIDI_UNMAPPED}, // +++ (LB1)
|
||||
{"Heart MS", 116, MIDI_UNMAPPED}, // ? (Iceman)
|
||||
{"Horse1 MS", 115, MIDI_UNMAPPED}, // ? (Camelot, QFG1)
|
||||
{"Horse2 MS", 115, MIDI_UNMAPPED}, // ? (Camelot, QFG1)
|
||||
{"InHale MS", 121, MIDI_UNMAPPED}, // ++ (Iceman)
|
||||
{"KNIFE ", 120, MIDI_UNMAPPED}, // ? (LSL3)
|
||||
{"KenBanjo ", 105, MIDI_UNMAPPED}, // +++ (LB1)
|
||||
{"Kiss MS", 25, MIDI_UNMAPPED}, // ++ (QFG1)
|
||||
{"KongHit ", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ??? (KQ1)
|
||||
{"Koto ", 107, MIDI_UNMAPPED}, // +++ (PQ2)
|
||||
{"Laser MS", 81, MIDI_UNMAPPED}, // ?? (QFG1)
|
||||
{"Meeps MS", 62, MIDI_UNMAPPED}, // ? (QFG1)
|
||||
{"MTrak MS", 62, MIDI_UNMAPPED}, // ?? (Iceman)
|
||||
{"MachGun MS", 127, MIDI_UNMAPPED}, // ? (Iceman)
|
||||
{"OCEANSOUND", 122, MIDI_UNMAPPED}, // + (LSL3)
|
||||
{"Oboe 2001 ", 68, MIDI_UNMAPPED}, // + (PQ2)
|
||||
{"Ocean MS", 122, MIDI_UNMAPPED}, // + (Iceman)
|
||||
{"PPG 2.3 MS", 75, MIDI_UNMAPPED}, // ? (Iceman)
|
||||
{"PianoCrank", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (LB1)
|
||||
{"PicSnareMS", MIDI_MAPPED_TO_RHYTHM, 40}, // R ? (Iceman)
|
||||
{"PiccoloKA ", 72, MIDI_UNMAPPED}, // +++ (KQ1)
|
||||
{"PinkBassMS", 39, MIDI_UNMAPPED},
|
||||
{"Pizz2 ", 45, MIDI_UNMAPPED}, // ++ (LB1)
|
||||
{"Portcullis", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (KQ1)
|
||||
{"Raspbry MS", 81, MIDI_UNMAPPED}, // ? (QFG1)
|
||||
{"RatSqueek ", 72, MIDI_UNMAPPED}, // ? (LauraBow1, Camelot)
|
||||
{"Record78 ", MIDI_UNMAPPED, MIDI_UNMAPPED}, // +++ (LB1)
|
||||
{"RecorderMS", 74, MIDI_UNMAPPED}, // +++ (Camelot)
|
||||
{"Red Baron ", 125, MIDI_UNMAPPED}, // ? (LB1)
|
||||
{"ReedPipMS ", 20, MIDI_UNMAPPED}, // +++ (Camelot)
|
||||
{"RevCymb MS", 119, MIDI_UNMAPPED},
|
||||
{"RifleShot ", 127, MIDI_UNMAPPED}, // + (LB1)
|
||||
{"RimShot MS", MIDI_MAPPED_TO_RHYTHM, 37}, // R
|
||||
{"SHOWER ", 52, MIDI_UNMAPPED}, // ? (LSL3)
|
||||
{"SQ Bass MS", 32, MIDI_UNMAPPED}, // + (SQ3)
|
||||
{"ShakuVibMS", 79, MIDI_UNMAPPED}, // + (Iceman)
|
||||
{"SlapBassMS", 36, MIDI_UNMAPPED}, // +++ (Iceman)
|
||||
{"Snare MS", MIDI_MAPPED_TO_RHYTHM, 38}, // R (QFG1)
|
||||
{"Some Birds", 123, MIDI_UNMAPPED}, // + (LB1)
|
||||
{"Sonar MS", 78, MIDI_UNMAPPED}, // ? (Iceman)
|
||||
{"Soundtrk2 ", 97, MIDI_UNMAPPED}, // +++ (LB1)
|
||||
{"Soundtrack", 97, MIDI_UNMAPPED}, // ++ (Camelot)
|
||||
{"SqurWaveMS", 80, MIDI_UNMAPPED},
|
||||
{"StabBassMS", 34, MIDI_UNMAPPED}, // + (Iceman)
|
||||
{"SteelDrmMS", 114, MIDI_UNMAPPED}, // +++ (Iceman)
|
||||
{"StrSect1MS", 48, MIDI_UNMAPPED}, // ++ (QFG1)
|
||||
{"String MS", 45, MIDI_UNMAPPED}, // + (Camelot)
|
||||
{"Syn-Choir ", 91, MIDI_UNMAPPED},
|
||||
{"Syn Brass4", 63, MIDI_UNMAPPED}, // ++ (PQ2)
|
||||
{"SynBass MS", 38, MIDI_UNMAPPED},
|
||||
{"SwmpBackgr", 120, MIDI_UNMAPPED}, // ?? (LB1, QFG1)
|
||||
{"T-Bone2 MS", 57, MIDI_UNMAPPED}, // +++ (QFG1)
|
||||
{"Taiko ", 116, 35}, // +++ (Camelot)
|
||||
{"Taiko Rim ", 118, 37}, // +++ (LSL3)
|
||||
{"Timpani1 ", 47, MIDI_UNMAPPED}, // +++ (LB1)
|
||||
{"Tom MS", 117, 48}, // +++ (Iceman)
|
||||
{"Toms MS", 117, 48}, // +++ (Camelot, QFG1)
|
||||
{"Tpt1prtl ", 56, MIDI_UNMAPPED}, // +++ (KQ1)
|
||||
{"TriangleMS", 112, 81}, // R (Camelot)
|
||||
{"Trumpet 1 ", 56, MIDI_UNMAPPED}, // +++ (Camelot)
|
||||
{"Type MS", MIDI_MAPPED_TO_RHYTHM, 39}, // + (Iceman)
|
||||
{"Warm Pad" , 89, MIDI_UNMAPPED}, // ++ (PQ3)
|
||||
{"WaterBells", 98, MIDI_UNMAPPED}, // + (PQ2)
|
||||
{"WaterFallK", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (KQ1)
|
||||
{"Whiporill ", 123, MIDI_UNMAPPED}, // + (LB1)
|
||||
{"Wind ", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (LB1)
|
||||
{"Wind MS", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (QFG1, Iceman)
|
||||
{"Wind2 MS", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (Camelot)
|
||||
{"Woodpecker", 115, MIDI_UNMAPPED}, // ? (LB1)
|
||||
{"WtrFall MS", MIDI_UNMAPPED, MIDI_UNMAPPED}, // ? (Camelot, QFG1, Iceman)
|
||||
{0, 0, 0}
|
||||
};
|
||||
|
||||
typedef Common::List<Mt32ToGmMap> Mt32ToGmMapList;
|
||||
extern Mt32ToGmMapList *Mt32dynamicMappings;
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_SOUND_DRIVERS_MAP_MT32_TO_GM_H
|
||||
1484
engines/sci/sound/drivers/midi.cpp
Normal file
1484
engines/sci/sound/drivers/midi.cpp
Normal file
File diff suppressed because it is too large
Load Diff
155
engines/sci/sound/drivers/mididriver.h
Normal file
155
engines/sci/sound/drivers/mididriver.h
Normal file
@@ -0,0 +1,155 @@
|
||||
/* 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 SCI_SOUND_DRIVERS_MIDIDRIVER_H
|
||||
#define SCI_SOUND_DRIVERS_MIDIDRIVER_H
|
||||
|
||||
#include "sci/sci.h"
|
||||
#include "sci/util.h"
|
||||
#include "audio/mididrv.h"
|
||||
#include "common/error.h"
|
||||
#include "common/platform.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
// Music patches in SCI games:
|
||||
// ===========================
|
||||
// 1.pat - MT-32 driver music patch
|
||||
// 2.pat - Yamaha FB01 driver music patch
|
||||
// 3.pat - Adlib driver music patch
|
||||
// 4.pat - Casio MT-540 (in earlier SCI0 games)
|
||||
// 4.pat - GM driver music patch (in later games that support GM)
|
||||
// 7.pat (newer) / patch.200 (older) - Mac driver music patch / Casio CSM-1
|
||||
// 9.pat (newer) / patch.005 (older) - Amiga driver music patch
|
||||
// 98.pat - Unknown, found in later SCI1.1 games. A MIDI format patch
|
||||
// 101.pat - CMS/PCjr driver music patch.
|
||||
// Only later PCjr drivers use this patch, earlier ones don't use a patch
|
||||
// bank.001 - older SCI0 Amiga instruments
|
||||
|
||||
class ResourceManager;
|
||||
|
||||
enum {
|
||||
MIDI_CHANNELS = 16,
|
||||
MIDI_PROP_MASTER_VOLUME = 0
|
||||
};
|
||||
|
||||
#define MIDI_RHYTHM_CHANNEL 9
|
||||
|
||||
/* Special SCI sound stuff */
|
||||
|
||||
#define SCI_MIDI_TIME_EXPANSION_PREFIX 0xF8
|
||||
#define SCI_MIDI_TIME_EXPANSION_LENGTH 240
|
||||
|
||||
#define SCI_MIDI_EOT 0xFC
|
||||
#define SCI_MIDI_SET_SIGNAL 0xCF
|
||||
#define SCI_MIDI_SET_POLYPHONY 0x4B
|
||||
#define SCI_MIDI_RESET_ON_SUSPEND 0x4C
|
||||
#define SCI_MIDI_CHANNEL_MUTE 0x4E
|
||||
#define SCI_MIDI_SET_REVERB 0x50
|
||||
#define SCI_MIDI_HOLD 0x52
|
||||
#define SCI_MIDI_CUMULATIVE_CUE 0x60
|
||||
#define SCI_MIDI_CHANNEL_SOUND_OFF 0x78 /* all-sound-off for Bn */
|
||||
#define SCI_MIDI_CHANNEL_NOTES_OFF 0x7B /* all-notes-off for Bn */
|
||||
|
||||
#define SCI_MIDI_SET_SIGNAL_LOOP 0x7F
|
||||
/* If this is the parameter of 0xCF, the loop point is set here */
|
||||
|
||||
#define SCI_MIDI_CONTROLLER(status) ((status & 0xF0) == 0xB0)
|
||||
|
||||
class MidiPlayer : public MidiDriver_BASE {
|
||||
protected:
|
||||
MidiDriver *_driver;
|
||||
int8 _reverb;
|
||||
|
||||
public:
|
||||
MidiPlayer(SciVersion version) : _driver(0), _reverb(-1), _version(version) { }
|
||||
|
||||
int open() {
|
||||
ResourceManager *resMan = g_sci->getResMan(); // HACK
|
||||
return open(resMan);
|
||||
}
|
||||
virtual int open(ResourceManager *resMan) { return _driver->open(); }
|
||||
virtual void close() { _driver->close(); }
|
||||
void send(uint32 b) override { _driver->send(b); }
|
||||
virtual uint32 getBaseTempo() { return _driver->getBaseTempo(); }
|
||||
virtual bool hasRhythmChannel() const = 0;
|
||||
virtual void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { _driver->setTimerCallback(timer_param, timer_proc); }
|
||||
|
||||
virtual byte getPlayId() const = 0;
|
||||
virtual int getPolyphony() const = 0;
|
||||
virtual int getFirstChannel() const { return 0; }
|
||||
virtual int getLastChannel() const { return 15; }
|
||||
|
||||
virtual void setVolume(byte volume) {
|
||||
if(_driver)
|
||||
_driver->property(MIDI_PROP_MASTER_VOLUME, volume);
|
||||
}
|
||||
|
||||
virtual int getVolume() {
|
||||
return _driver ? _driver->property(MIDI_PROP_MASTER_VOLUME, 0xffff) : 0;
|
||||
}
|
||||
|
||||
// Returns the current reverb, or -1 when no reverb is active
|
||||
int8 getReverb() const { return _reverb; }
|
||||
// Sets the current reverb, used mainly in MT-32
|
||||
virtual void setReverb(int8 reverb) { _reverb = reverb; }
|
||||
|
||||
virtual void playSwitch(bool play) {
|
||||
if (!play) {
|
||||
// Send "All Sound Off" on all channels
|
||||
for (int i = 0; i < MIDI_CHANNELS; ++i)
|
||||
_driver->send(0xb0 + i, SCI_MIDI_CHANNEL_NOTES_OFF, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Prepares the driver for the playback of SCI0 midi tracks.
|
||||
// The main purpose is the assignment of voices ("hardware" sound channels) to the 16 midi parts.
|
||||
// This is basically the predecessor of the 0x4B midi event.
|
||||
// Some drivers also do other things in here.
|
||||
virtual void initTrack(SciSpan<const byte> &) {}
|
||||
|
||||
// There are several sound drivers which weren' part of the
|
||||
// original game setup and came in the form of aftermarket patches.
|
||||
// This method allows each driver to report missing patch or other
|
||||
// required files which will then be displayed in an error dialog box.
|
||||
// The method returns only a single string (instead of a string list),
|
||||
// because no more than two files will be required.
|
||||
virtual const char *reportMissingFiles() { return 0; }
|
||||
|
||||
protected:
|
||||
SciVersion _version;
|
||||
};
|
||||
|
||||
extern MidiPlayer *MidiPlayer_AdLib_create(SciVersion version);
|
||||
extern MidiPlayer *MidiPlayer_AmigaMac0_create(SciVersion version, Common::Platform platform);
|
||||
extern MidiPlayer *MidiPlayer_AmigaMac1_create(SciVersion version, Common::Platform platform);
|
||||
extern MidiPlayer *MidiPlayer_PCJr_create(SciVersion version);
|
||||
extern MidiPlayer *MidiPlayer_PCSpeaker_create(SciVersion version);
|
||||
extern MidiPlayer *MidiPlayer_CMS_create(SciVersion version);
|
||||
extern MidiPlayer *MidiPlayer_Midi_create(SciVersion version);
|
||||
extern MidiPlayer *MidiPlayer_Fb01_create(SciVersion version);
|
||||
extern MidiPlayer *MidiPlayer_Casio_create(SciVersion version, MusicType midiType);
|
||||
extern MidiPlayer *MidiPlayer_FMTowns_create(SciVersion version);
|
||||
extern MidiPlayer *MidiPlayer_PC9801_create(SciVersion version);
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_SOUND_DRIVERS_MIDIDRIVER_H
|
||||
1718
engines/sci/sound/drivers/pc9801.cpp
Normal file
1718
engines/sci/sound/drivers/pc9801.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1020
engines/sci/sound/drivers/pcjr.cpp
Normal file
1020
engines/sci/sound/drivers/pcjr.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1059
engines/sci/sound/midiparser_sci.cpp
Normal file
1059
engines/sci/sound/midiparser_sci.cpp
Normal file
File diff suppressed because it is too large
Load Diff
134
engines/sci/sound/midiparser_sci.h
Normal file
134
engines/sci/sound/midiparser_sci.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 SCI_SOUND_MIDIPARSER_H
|
||||
#define SCI_SOUND_MIDIPARSER_H
|
||||
|
||||
#include "sci/resource/resource.h"
|
||||
#include "sci/sound/music.h"
|
||||
#include "audio/midiparser.h"
|
||||
|
||||
/*
|
||||
Sound drivers info: (from driver cmd0)
|
||||
AdLib/SB : track 0 , voices 9 , patch 3 ah=1
|
||||
ProAudioSp: track 0 , voices 9 , patch 3 ah=17
|
||||
GenerlMIDI: track 7 , voices 32, patch 4 ah=1 SCI1.1
|
||||
Game Blast: track 9 , voices 12, patch 101 ah=1
|
||||
MT-32 : track 12, voices 32, patch 1 ah=1
|
||||
PC Speaker: track 18, voices 1 , patch 0xFF ah=1
|
||||
Tandy : track 19, voices 3 , patch 101 ah=1
|
||||
IBM PS/1 : track 19, voices 3 , patch 101 ah=1
|
||||
|
||||
*/
|
||||
|
||||
namespace Sci {
|
||||
|
||||
/**
|
||||
* An extended standard MIDI (SMF) parser. Sierra used an extra channel
|
||||
* with special commands for extended functionality and animation syncing.
|
||||
* Refer to MidiParser_SMF() in /sound/midiparser_smf.cpp for the standard
|
||||
* MIDI (SMF) parser functionality that the SCI MIDI parser is based on
|
||||
*/
|
||||
class MidiParser_SCI : public MidiParser {
|
||||
public:
|
||||
MidiParser_SCI(SciVersion soundVersion, SciMusic *music);
|
||||
~MidiParser_SCI() override;
|
||||
|
||||
void mainThreadBegin();
|
||||
void mainThreadEnd();
|
||||
|
||||
bool loadMusic(SoundResource::Track *track, MusicEntry *psnd, int channelFilterMask, SciVersion soundVersion);
|
||||
bool loadMusic(const byte *, uint32) override {
|
||||
return false;
|
||||
}
|
||||
void initTrack();
|
||||
void sendInitCommands();
|
||||
void unloadMusic() override;
|
||||
void setMasterVolume(byte masterVolume);
|
||||
void setVolume(byte volume);
|
||||
void stop() {
|
||||
_abortParse = true;
|
||||
allNotesOff();
|
||||
}
|
||||
void pause() {
|
||||
allNotesOff();
|
||||
if (_resetOnPause)
|
||||
jumpToTick(0);
|
||||
}
|
||||
|
||||
void allNotesOff() override;
|
||||
|
||||
const SciSpan<const byte> &getMixedData() const { return *_mixedData; }
|
||||
byte getSongReverb();
|
||||
|
||||
void sendFromScriptToDriver(uint32 midi);
|
||||
void sendToDriver(uint32 midi) override;
|
||||
void sendToDriver(byte status, byte firstOp, byte secondOp) {
|
||||
sendToDriver(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16));
|
||||
}
|
||||
|
||||
void remapChannel(int channel, int devChannel);
|
||||
|
||||
protected:
|
||||
void parseNextEvent(EventInfo &info) override;
|
||||
bool processEvent(const EventInfo &info, bool fireEvents = true) override;
|
||||
void midiMixChannels();
|
||||
void midiFilterChannels(int channelMask);
|
||||
byte midiGetNextChannel(long ticker);
|
||||
void resetStateTracking();
|
||||
void trackState(uint32 midi);
|
||||
void sendToDriver_raw(uint32 midi);
|
||||
|
||||
SciMusic *_music;
|
||||
|
||||
// this is set, when main thread calls us -> we send commands to queue instead to driver
|
||||
bool _mainThreadCalled;
|
||||
|
||||
SciVersion _soundVersion;
|
||||
Common::SpanOwner<SciSpan<const byte> > _mixedData;
|
||||
SoundResource::Track *_track;
|
||||
MusicEntry *_pSnd;
|
||||
uint32 _loopTick;
|
||||
byte _masterVolume; // the overall master volume (same for all tracks)
|
||||
byte _volume; // the global volume of the current track
|
||||
|
||||
bool _resetOnPause;
|
||||
|
||||
bool _channelUsed[16];
|
||||
int16 _channelRemap[16];
|
||||
byte _channelVolume[16];
|
||||
|
||||
struct ChannelState {
|
||||
int8 _modWheel;
|
||||
int8 _pan;
|
||||
int8 _patch;
|
||||
int8 _note;
|
||||
bool _sustain;
|
||||
int16 _pitchWheel;
|
||||
int8 _voices;
|
||||
};
|
||||
|
||||
ChannelState _channelState[16];
|
||||
};
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_SOUND_MIDIPARSER_H
|
||||
1607
engines/sci/sound/music.cpp
Normal file
1607
engines/sci/sound/music.cpp
Normal file
File diff suppressed because it is too large
Load Diff
316
engines/sci/sound/music.h
Normal file
316
engines/sci/sound/music.h
Normal file
@@ -0,0 +1,316 @@
|
||||
/* 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 SCI_SOUND_MUSIC_H
|
||||
#define SCI_SOUND_MUSIC_H
|
||||
|
||||
#include "common/serializer.h"
|
||||
#include "common/mutex.h"
|
||||
|
||||
#include "audio/mixer.h"
|
||||
|
||||
#include "sci/sci.h"
|
||||
#include "sci/resource/resource.h"
|
||||
#include "sci/sound/drivers/mididriver.h"
|
||||
#ifdef ENABLE_SCI32
|
||||
#include "sci/sound/audio32.h"
|
||||
#endif
|
||||
|
||||
namespace Audio {
|
||||
class LoopingAudioStream;
|
||||
class RewindableAudioStream;
|
||||
}
|
||||
|
||||
namespace Sci {
|
||||
|
||||
enum SoundStatus {
|
||||
kSoundStopped = 0,
|
||||
kSoundInitialized = 1,
|
||||
kSoundPaused = 2,
|
||||
kSoundPlaying = 3
|
||||
};
|
||||
|
||||
#define MUSIC_VOLUME_DEFAULT 127
|
||||
#define MUSIC_VOLUME_MAX 127
|
||||
#define MUSIC_MASTERVOLUME_DEFAULT 15
|
||||
#define MUSIC_MASTERVOLUME_MAX 15
|
||||
|
||||
class MidiParser_SCI;
|
||||
class SegManager;
|
||||
|
||||
typedef Common::Array<uint16> SignalQueue;
|
||||
|
||||
|
||||
struct MusicEntryChannel {
|
||||
// Channel info
|
||||
int8 _prio; // 0 = essential; lower is higher priority
|
||||
int8 _voices;
|
||||
bool _dontRemap;
|
||||
bool _dontMap;
|
||||
uint8 _mute;
|
||||
};
|
||||
|
||||
|
||||
class MusicEntry : public Common::Serializable {
|
||||
public:
|
||||
// Do not get these directly for the sound objects!
|
||||
// It's a bad idea, as the sound code (i.e. the SciMusic
|
||||
// class) should be as separate as possible from the rest
|
||||
// of the engine
|
||||
|
||||
reg_t soundObj;
|
||||
|
||||
SoundResource *soundRes;
|
||||
uint16 resourceId;
|
||||
|
||||
int time; // "tim"estamp to indicate in which order songs have been added
|
||||
|
||||
uint16 dataInc;
|
||||
uint16 ticker;
|
||||
uint16 signal;
|
||||
int16 priority; // must be int16, at least in Laura Bow 1, main music (object conMusic) uses priority -1
|
||||
uint16 loop;
|
||||
int16 volume;
|
||||
int16 hold;
|
||||
int8 reverb;
|
||||
bool playBed;
|
||||
bool overridePriority; // Use soundObj's priority instead of resource's
|
||||
|
||||
int16 pauseCounter;
|
||||
uint sampleLoopCounter;
|
||||
|
||||
byte fadeTo;
|
||||
short fadeStep;
|
||||
uint32 fadeTicker;
|
||||
uint32 fadeTickerStep;
|
||||
bool fadeSetVolume;
|
||||
bool fadeCompleted;
|
||||
bool stopAfterFading;
|
||||
|
||||
SoundStatus status;
|
||||
|
||||
Audio::Mixer::SoundType soundType;
|
||||
|
||||
int _usedChannels[16];
|
||||
MusicEntryChannel _chan[16];
|
||||
MidiParser_SCI *pMidiParser;
|
||||
|
||||
// this is used for storing signals, when the current signal is not yet
|
||||
// sent to the scripts. We shouldn't need to save it, this normally only
|
||||
// happens in rare situations like lb1, knocking on the door in the attic
|
||||
SignalQueue signalQueue;
|
||||
|
||||
// TODO: We need to revise how we store the different
|
||||
// audio stream objects we require.
|
||||
Audio::RewindableAudioStream *pStreamAud;
|
||||
Audio::LoopingAudioStream *pLoopStream;
|
||||
Audio::SoundHandle hCurrentAud;
|
||||
bool isSample;
|
||||
|
||||
public:
|
||||
MusicEntry();
|
||||
~MusicEntry() override;
|
||||
|
||||
void doFade();
|
||||
void onTimer();
|
||||
void setSignal(int signal);
|
||||
|
||||
void saveLoadWithSerializer(Common::Serializer &ser) override;
|
||||
};
|
||||
|
||||
struct DeviceChannelUsage {
|
||||
MusicEntry *_song;
|
||||
int _channel;
|
||||
bool operator==(const DeviceChannelUsage& other) const { return _song == other._song && _channel == other._channel; }
|
||||
bool operator!=(const DeviceChannelUsage& other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
struct ChannelRemapping {
|
||||
DeviceChannelUsage _map[16];
|
||||
int _prio[16];
|
||||
int _voices[16];
|
||||
bool _dontRemap[16];
|
||||
int _freeVoices;
|
||||
|
||||
void clear();
|
||||
void swap(int i, int j);
|
||||
void evict(int i);
|
||||
ChannelRemapping& operator=(ChannelRemapping& other);
|
||||
int lowestPrio() const;
|
||||
};
|
||||
|
||||
struct MidiCommand {
|
||||
enum CmdType {
|
||||
kTypeMidiMessage = 0,
|
||||
kTypeTrackInit
|
||||
};
|
||||
// Keeping this very simple, due to the very limited purpose of it.
|
||||
MidiCommand(CmdType type, uint32 val) : _type(type), _dataPtr(0), _dataVal(val) {}
|
||||
MidiCommand(CmdType type, void *ptr) : _type(type), _dataPtr(ptr), _dataVal(0) {}
|
||||
CmdType _type;
|
||||
void *_dataPtr;
|
||||
uint32 _dataVal;
|
||||
};
|
||||
|
||||
typedef Common::Array<MusicEntry *> MusicList;
|
||||
typedef Common::Array<MidiCommand> MidiCommandQueue;
|
||||
|
||||
class SciMusic : public Common::Serializable {
|
||||
|
||||
public:
|
||||
SciMusic(SciVersion soundVersion, bool useDigitalSFX);
|
||||
~SciMusic() override;
|
||||
|
||||
void init();
|
||||
|
||||
void onTimer();
|
||||
void putMidiCommandInQueue(byte status, byte firstOp, byte secondOp);
|
||||
void putMidiCommandInQueue(uint32 midi);
|
||||
void putTrackInitCommandInQueue(MusicEntry *psnd);
|
||||
void removeTrackInitCommandsFromQueue(MusicEntry *psnd);
|
||||
private:
|
||||
static void miditimerCallback(void *p);
|
||||
void sendMidiCommandsFromQueue();
|
||||
|
||||
public:
|
||||
void clearPlayList();
|
||||
void pauseAll(bool pause);
|
||||
bool isAllPaused() const { return (_globalPause > 0); }
|
||||
void resetGlobalPauseCounter();
|
||||
void stopAll();
|
||||
void stopAllSamples();
|
||||
|
||||
// sound and midi functions
|
||||
void soundInitSnd(MusicEntry *pSnd);
|
||||
void soundPlay(MusicEntry *pSnd, bool restoring = false);
|
||||
void soundStop(MusicEntry *pSnd);
|
||||
void soundKill(MusicEntry *pSnd);
|
||||
void soundPause(MusicEntry *pSnd);
|
||||
void soundResume(MusicEntry *pSnd);
|
||||
void soundToggle(MusicEntry *pSnd, bool pause);
|
||||
void soundSetVolume(MusicEntry *pSnd, byte volume);
|
||||
void soundSetSampleVolume(MusicEntry *pSnd, byte volume);
|
||||
void soundSetPriority(MusicEntry *pSnd, byte prio);
|
||||
uint16 soundGetMasterVolume();
|
||||
void soundSetMasterVolume(uint16 vol);
|
||||
uint16 soundGetSoundOn() const { return _soundOn; }
|
||||
void soundSetSoundOn(bool soundOnFlag);
|
||||
uint16 soundGetVoices();
|
||||
uint32 soundGetTempo() const { return _dwTempo; }
|
||||
MusicType soundGetMusicType() const { return _musicType; }
|
||||
|
||||
bool isSoundActive(MusicEntry *pSnd) {
|
||||
assert(pSnd->pStreamAud != 0);
|
||||
return _pMixer->isSoundHandleActive(pSnd->hCurrentAud);
|
||||
}
|
||||
|
||||
void updateAudioStreamTicker(MusicEntry *pSnd) {
|
||||
assert(pSnd->pStreamAud != 0);
|
||||
pSnd->ticker = (uint16)(_pMixer->getSoundElapsedTime(pSnd->hCurrentAud) * 0.06);
|
||||
}
|
||||
|
||||
MusicEntry *getSlot(reg_t obj);
|
||||
MusicEntry *getFirstSlotWithStatus(SoundStatus status);
|
||||
|
||||
void pushBackSlot(MusicEntry *slotEntry) {
|
||||
Common::StackLock lock(_mutex);
|
||||
_playList.push_back(slotEntry);
|
||||
if (_soundVersion <= SCI_VERSION_0_LATE) // I limit this to SCI0, since it always inserts the nodes at the correct position, but no idea about >=SCI1
|
||||
sortPlayList();
|
||||
}
|
||||
|
||||
void printPlayList(Console *con);
|
||||
void printSongInfo(reg_t obj, Console *con);
|
||||
|
||||
// The following two methods are NOT thread safe - make sure that
|
||||
// the mutex is always locked before calling them
|
||||
MusicList::iterator getPlayListStart() { return _playList.begin(); }
|
||||
MusicList::iterator getPlayListEnd() { return _playList.end(); }
|
||||
|
||||
void sendMidiCommand(uint32 cmd);
|
||||
void sendMidiCommand(MusicEntry *pSnd, uint32 cmd);
|
||||
|
||||
void setGlobalReverb(int8 reverb);
|
||||
int8 getGlobalReverb() { return _globalReverb; }
|
||||
|
||||
byte getCurrentReverb();
|
||||
|
||||
void needsRemap() { _needsRemap = true; }
|
||||
|
||||
void saveLoadWithSerializer(Common::Serializer &ser) override;
|
||||
|
||||
// Mutex for music code. Used to guard access to the song playlist, to the
|
||||
// MIDI parser and to the MIDI driver/player. We use a reference to
|
||||
// the mixer's internal mutex to avoid deadlocks which sometimes occur when
|
||||
// different threads lock each other up in different mutexes.
|
||||
Common::Mutex &_mutex;
|
||||
|
||||
protected:
|
||||
void sortPlayList();
|
||||
|
||||
SciVersion _soundVersion;
|
||||
|
||||
Audio::Mixer *_pMixer;
|
||||
MidiPlayer *_pMidiDrv;
|
||||
|
||||
uint32 _dwTempo;
|
||||
// If true and a sound has a digital track, the sound from the AdLib track is played
|
||||
bool _useDigitalSFX;
|
||||
|
||||
// remapping:
|
||||
void remapChannels(bool mainThread = true);
|
||||
ChannelRemapping *determineChannelMap();
|
||||
void resetDeviceChannel(int devChannel, bool mainThread);
|
||||
|
||||
public:
|
||||
// The parsers need to know this for the dontMap channels...
|
||||
bool isDeviceChannelMapped(int devChannel) const;
|
||||
|
||||
bool isDigitalSamplePlaying() const;
|
||||
|
||||
private:
|
||||
MusicList _playList;
|
||||
bool _soundOn;
|
||||
byte _masterVolume;
|
||||
MusicEntry *_usedChannel[16];
|
||||
int8 _channelRemap[16];
|
||||
int8 _globalReverb;
|
||||
bool _needsRemap;
|
||||
int _globalPause;
|
||||
bool _needsResume;
|
||||
|
||||
DeviceChannelUsage _channelMap[16];
|
||||
|
||||
MidiCommandQueue _queuedCommands;
|
||||
MusicType _musicType;
|
||||
|
||||
int _driverFirstChannel;
|
||||
int _driverLastChannel;
|
||||
|
||||
MusicEntry *_currentlyPlayingSample;
|
||||
|
||||
int _timeCounter; // Used to keep track of the order in which MusicEntries
|
||||
// are added, for priority purposes.
|
||||
};
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_SOUND_MUSIC_H
|
||||
1038
engines/sci/sound/soundcmd.cpp
Normal file
1038
engines/sci/sound/soundcmd.cpp
Normal file
File diff suppressed because it is too large
Load Diff
134
engines/sci/sound/soundcmd.h
Normal file
134
engines/sci/sound/soundcmd.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 SCI_SOUND_SOUNDCMD_H
|
||||
#define SCI_SOUND_SOUNDCMD_H
|
||||
|
||||
#include "common/list.h"
|
||||
#include "audio/mididrv.h" // for MusicType
|
||||
#include "sci/engine/state.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
class Console;
|
||||
class SciMusic;
|
||||
class SoundCommandParser;
|
||||
class MusicEntry;
|
||||
|
||||
class SoundCommandParser {
|
||||
public:
|
||||
SoundCommandParser(ResourceManager *resMan, SegManager *segMan, Kernel *kernel, AudioPlayer *audio, SciVersion soundVersion);
|
||||
~SoundCommandParser();
|
||||
|
||||
// Functions used for game state loading
|
||||
void clearPlayList();
|
||||
void syncPlayList(Common::Serializer &s);
|
||||
void reconstructPlayList();
|
||||
|
||||
// Functions used for the ScummVM menus
|
||||
void setMasterVolume(int vol);
|
||||
void pauseAll(bool pause);
|
||||
void resetGlobalPauseCounter();
|
||||
bool isGlobalPauseActive() const;
|
||||
#ifdef ENABLE_SCI32
|
||||
void setVolume(const reg_t obj, const int vol);
|
||||
#endif
|
||||
|
||||
// Debug console functions
|
||||
void startNewSound(int number);
|
||||
void stopAllSounds();
|
||||
void stopAllSamples();
|
||||
void printPlayList(Console *con);
|
||||
void printSongInfo(reg_t obj, Console *con);
|
||||
|
||||
void processPlaySound(reg_t obj, bool playBed, bool restoring = false);
|
||||
void processStopSound(reg_t obj, bool sampleFinishedPlaying);
|
||||
void initSoundResource(MusicEntry *newSound);
|
||||
|
||||
MusicType getMusicType() const;
|
||||
|
||||
ResourceType getSoundResourceType(const uint16 resourceNo) const {
|
||||
if (_useDigitalSFX && _resMan->testResource(ResourceId(kResourceTypeAudio, resourceNo)))
|
||||
return kResourceTypeAudio;
|
||||
else
|
||||
return kResourceTypeSound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes the current state of the music list to the rest of the engine, so that
|
||||
* the changes that the sound thread makes to the music are registered with the engine
|
||||
* scripts. In SCI0, we invoke this from kAnimate (which is called very often). SCI01
|
||||
* and later have a specific callback function, cmdUpdateCues, which is called regularly
|
||||
* by the engine scripts themselves, so the engine itself polls for changes to the music
|
||||
*/
|
||||
void updateSci0Cues();
|
||||
|
||||
bool isDigitalSamplePlaying() const;
|
||||
|
||||
reg_t kDoSoundInit(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundPlay(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundMute(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundPause(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundResumeAfterRestore(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundStop(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundStopAll(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundDispose(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundMasterVolume(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundFade(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundGetPolyphony(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundUpdate(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundUpdateCues(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundSendMidi(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundGlobalReverb(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundSetHold(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundGetAudioCapability(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundSetVolume(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundSetPriority(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundSetLoop(EngineState *s, int argc, reg_t *argv);
|
||||
reg_t kDoSoundSuspend(EngineState *s, int argc, reg_t *argv);
|
||||
|
||||
private:
|
||||
ResourceManager *_resMan;
|
||||
SegManager *_segMan;
|
||||
Kernel *_kernel;
|
||||
SciMusic *_music;
|
||||
AudioPlayer *_audio;
|
||||
SciVersion _soundVersion;
|
||||
// If true and an alternative digital sound effect exists, the digital
|
||||
// sound effect is preferred instead
|
||||
bool _useDigitalSFX;
|
||||
|
||||
void processInitSound(reg_t obj);
|
||||
void processDisposeSound(reg_t obj);
|
||||
void processUpdateCues(reg_t obj);
|
||||
uint16 getSoundResourceId(reg_t obj);
|
||||
|
||||
/**
|
||||
* Returns true if the sound is already playing and shouldn't be interrupted.
|
||||
* This is a workaround for known buggy scripts that accidentally rely on
|
||||
* the time it took Sierra's interpreter to load a sound and begin playing.
|
||||
*/
|
||||
bool isUninterruptableSoundPlaying(reg_t obj);
|
||||
};
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_SOUND_SOUNDCMD_H
|
||||
75
engines/sci/sound/sync.cpp
Normal file
75
engines/sci/sound/sync.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
/* 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 "sci/engine/kernel.h"
|
||||
#include "sci/util.h"
|
||||
#include "sync.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
Sync::Sync(ResourceManager *resMan, SegManager *segMan) :
|
||||
_resMan(resMan),
|
||||
_segMan(segMan),
|
||||
_resource(nullptr),
|
||||
_offset(0) {}
|
||||
|
||||
Sync::~Sync() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void Sync::start(const ResourceId id, const reg_t syncObjAddr) {
|
||||
_resource = _resMan->findResource(id, true);
|
||||
_offset = 0;
|
||||
|
||||
if (_resource) {
|
||||
writeSelectorValue(_segMan, syncObjAddr, SELECTOR(syncCue), 0);
|
||||
} else {
|
||||
warning("Sync::start: failed to find resource %s", id.toString().c_str());
|
||||
// Notify the scripts to stop sound sync
|
||||
writeSelectorValue(_segMan, syncObjAddr, SELECTOR(syncCue), SIGNAL_OFFSET);
|
||||
}
|
||||
}
|
||||
|
||||
void Sync::next(const reg_t syncObjAddr) {
|
||||
if (_resource && (_offset < _resource->size() - 1)) {
|
||||
int16 syncCue = -1;
|
||||
int16 syncTime = _resource->getInt16SEAt(_offset);
|
||||
|
||||
_offset += 2;
|
||||
|
||||
if ((syncTime != -1) && (_offset < _resource->size() - 1)) {
|
||||
syncCue = _resource->getInt16SEAt(_offset);
|
||||
_offset += 2;
|
||||
}
|
||||
|
||||
writeSelectorValue(_segMan, syncObjAddr, SELECTOR(syncTime), syncTime);
|
||||
writeSelectorValue(_segMan, syncObjAddr, SELECTOR(syncCue), syncCue);
|
||||
}
|
||||
}
|
||||
|
||||
void Sync::stop() {
|
||||
if (_resource) {
|
||||
_resMan->unlockResource(_resource);
|
||||
_resource = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Sci
|
||||
61
engines/sci/sound/sync.h
Normal file
61
engines/sci/sound/sync.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/* 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 SCI_SOUND_SYNC_H
|
||||
#define SCI_SOUND_SYNC_H
|
||||
|
||||
#include "sci/engine/selector.h"
|
||||
#include "sci/engine/vm_types.h"
|
||||
|
||||
namespace Sci {
|
||||
|
||||
enum AudioSyncCommands {
|
||||
kSciAudioSyncStart = 0,
|
||||
kSciAudioSyncNext = 1,
|
||||
kSciAudioSyncStop = 2
|
||||
};
|
||||
|
||||
class Resource;
|
||||
class ResourceManager;
|
||||
class SegManager;
|
||||
|
||||
/**
|
||||
* Sync class, kDoSync and relevant functions for SCI games.
|
||||
* Provides AV synchronization for animations.
|
||||
*/
|
||||
class Sync {
|
||||
SegManager *_segMan;
|
||||
ResourceManager *_resMan;
|
||||
Resource *_resource;
|
||||
uint _offset;
|
||||
|
||||
public:
|
||||
Sync(ResourceManager *resMan, SegManager *segMan);
|
||||
~Sync();
|
||||
|
||||
void start(const ResourceId id, const reg_t syncObjAddr);
|
||||
void next(const reg_t syncObjAddr);
|
||||
void stop();
|
||||
};
|
||||
|
||||
} // End of namespace Sci
|
||||
|
||||
#endif // SCI_SOUND_SYNC_H
|
||||
Reference in New Issue
Block a user