Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

556
engines/sci/sound/audio.cpp Normal file
View 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
View 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

File diff suppressed because it is too large Load Diff

660
engines/sci/sound/audio32.h Normal file
View 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

View 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);
}
}

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

316
engines/sci/sound/music.h Normal file
View 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

File diff suppressed because it is too large Load Diff

View 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

View 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
View 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