Files
2026-02-02 04:50:13 +01:00

631 lines
16 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
//define MIX_INIT_FLUIDSYNTH MIX_INIT_MID // renamed with SDL2_mixer >= 2.0.2
#include "audio/mixer.h"
#include "audio/audiostream.h"
#include "audio/mididrv.h"
#include "audio/midiparser.h"
#include "audio/decoders/raw.h"
#include "audio/decoders/wave.h"
#include "common/memstream.h"
#include "awe/aifc_player.h"
#include "awe/sound.h"
#include "awe/sfx_player.h"
namespace Awe {
void Sound::playMusic(const char *path, int loops) {
warning("TODO: playMusic");
}
void Sound::playSfxMusic(int num) {
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle, _sfxStream, -1, 255, 0, DisposeAfterUse::YES, true);
_sfx->play(_mixer->getOutputRate());
}
void Sound::playAifcMusic(const char *path, uint32 offset) {
warning("TODO: playAifcMusic");
}
void Sound::stopMusic() {
warning("TODO: stopMusic");
}
void Sound::stopAifcMusic() {
warning("TODO: stopAifcMusic");
}
void Sound::stopAll() {
stopSfxMusic();
_mixer->stopAll();
}
void Sound::setPlayer(SfxPlayer *player) {
_sfx = player;
_sfxStream = new SfxMusicStream(player);
}
void Sound::stopSfxMusic() {
if (_mixer->isSoundHandleActive(_musicHandle)) {
_mixer->stopHandle(_musicHandle);
}
}
void Sound::preloadSoundAiff(byte num, const byte *data) {
warning("TODO: preloadSoundAiff");
}
void Sound::playSoundRaw(byte channel, const byte *data, size_t size,
int freq, byte volume) {
assert(channel < MAX_CHANNELS);
// Used for looping sounds, e.g. the waterfall
// uint16 length = READ_BE_UINT16(data) * 2;
// uint16 loopLength = READ_BE_UINT16(data + 2) * 2;
Common::MemoryReadStream *stream =
new Common::MemoryReadStream(data + 8, size - 8);
Audio::AudioStream *sound =
Audio::makeRawStream(stream, freq,
0,
DisposeAfterUse::YES);
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_channels[channel],
sound, -1, 255, 0, DisposeAfterUse::YES);
}
void Sound::playSoundWav(byte channel, const byte *data, size_t size,
uint16 freq, byte volume, byte loop) {
assert(channel < MAX_CHANNELS);
Common::MemoryReadStream *stream =
new Common::MemoryReadStream(data, size);
Audio::AudioStream *sound = Audio::makeWAVStream(
stream, DisposeAfterUse::YES);
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_channels[channel],
sound, -1, 255, 0, DisposeAfterUse::YES);
}
void Sound::playSoundAiff(byte channel, byte num, byte volume) {
warning("TODO: playSoundAiff");
}
void Sound::stopSound(byte channel) {
assert(channel < MAX_CHANNELS);
_mixer->stopHandle(_channels[channel]);
}
#ifdef TODO
enum {
TAG_RIFF = 0x46464952,
TAG_WAVE = 0x45564157,
TAG_fmt = 0x20746D66,
TAG_data = 0x61746164
};
static const bool kAmigaStereoChannels = false; // 0,3:left 1,2:right
static int16 toS16(int a) {
return ((a << 8) | a) - 32768;
}
static int16 mixS16(int sample1, int sample2) {
const int sample = sample1 + sample2;
return sample < -32768 ? -32768 : ((sample > 32767 ? 32767 : sample));
}
struct MixerChannel {
const byte *_data;
Frac _pos;
uint32 _len;
uint32 _loopLen, _loopPos;
int _volume;
void (MixerChannel:: *_mixWav)(int16 *sample, int count);
void initRaw(const byte *data, int freq, int volume, int mixingFreq) {
_data = data + 8;
_pos.reset(freq, mixingFreq);
const int len = READ_BE_UINT16(data) * 2;
_loopLen = READ_BE_UINT16(data + 2) * 2;
_loopPos = _loopLen ? len : 0;
_len = len;
_volume = volume;
}
void initWav(const byte *data, int freq, int volume, int mixingFreq, int len, bool bits16, bool stereo, bool loop) {
_data = data;
_pos.reset(freq, mixingFreq);
_len = len;
_loopLen = loop ? len : 0;
_loopPos = 0;
_volume = volume;
_mixWav = bits16 ? (stereo ? &MixerChannel::mixWav<16, true> : &MixerChannel::mixWav<16, false>) : (stereo ? &MixerChannel::mixWav<8, true> : &MixerChannel::mixWav<8, false>);
}
void mixRaw(int16 &sample) {
if (_data) {
uint32 pos = _pos.getInt();
_pos.offset += _pos.inc;
if (_loopLen != 0) {
if (pos >= _loopPos + _loopLen) {
pos = _loopPos;
_pos.offset = (_loopPos << Frac::BITS) + _pos.inc;
}
} else {
if (pos >= _len) {
_data = 0;
return;
}
}
sample = mixS16(sample, toS16(_data[pos] ^ 0x80) * _volume / 64);
}
}
template<int bits, bool stereo>
void mixWav(int16 *samples, int count) {
for (int i = 0; i < count; i += 2) {
uint32 pos = _pos.getInt();
_pos.offset += _pos.inc;
if (pos >= _len) {
if (_loopLen != 0) {
pos = 0;
_pos.offset = _pos.inc;
} else {
_data = 0;
break;
}
}
if (stereo) {
pos *= 2;
}
int valueL;
if (bits == 8) { // U8
valueL = toS16(_data[pos]) * _volume / 64;
} else { // S16
valueL = ((int16)READ_LE_UINT16(&_data[pos * sizeof(int16)])) * _volume / 64;
}
*samples = mixS16(*samples, valueL);
++samples;
int valueR;
if (!stereo) {
valueR = valueL;
} else {
if (bits == 8) { // U8
valueR = toS16(_data[pos + 1]) * _volume / 64;
} else { // S16
valueR = ((int16)READ_LE_UINT16(&_data[(pos + 1) * sizeof(int16)])) * _volume / 64;
}
}
*samples = mixS16(*samples, valueR);
++samples;
}
}
};
static const byte *loadWav(const byte *data, int &freq, int &len, bool &bits16, bool &stereo) {
uint32 riffMagic = READ_LE_UINT32(data);
if (riffMagic != TAG_RIFF) return 0;
uint32 riffLength = READ_LE_UINT32(data + 4);
uint32 waveMagic = READ_LE_UINT32(data + 8);
if (waveMagic != TAG_WAVE) return 0;
uint32 offset = 12;
uint32 chunkMagic, chunkLength = 0;
// find fmt chunk
do {
offset += chunkLength + (chunkLength & 1);
if (offset >= riffLength) return 0;
chunkMagic = READ_LE_UINT32(data + offset);
chunkLength = READ_LE_UINT32(data + offset + 4);
offset += 8;
} while (chunkMagic != TAG_fmt);
if (chunkLength < 14) return 0;
if (offset + chunkLength >= riffLength) return 0;
// read format
int formatTag = READ_LE_UINT16(data + offset);
int channels = READ_LE_UINT16(data + offset + 2);
int samplesPerSec = READ_LE_UINT32(data + offset + 4);
int bitsPerSample = 0;
if (chunkLength >= 16) {
bitsPerSample = READ_LE_UINT16(data + offset + 14);
} else if (formatTag == 1 && channels != 0) {
int blockAlign = READ_LE_UINT16(data + offset + 12);
bitsPerSample = (blockAlign * 8) / channels;
}
// check supported format
if ((formatTag != 1) || // PCM
(channels != 1 && channels != 2) || // mono or stereo
(bitsPerSample != 8 && bitsPerSample != 16)) { // 8bit or 16bit
warning("Unsupported wave file");
return 0;
}
// find data chunk
do {
offset += chunkLength + (chunkLength & 1);
if (offset >= riffLength) return 0;
chunkMagic = READ_LE_UINT32(data + offset);
chunkLength = READ_LE_UINT32(data + offset + 4);
offset += 8;
} while (chunkMagic != TAG_data);
uint32 lengthSamples = chunkLength;
if (offset + lengthSamples - 4 > riffLength) {
lengthSamples = riffLength + 4 - offset;
}
if (channels == 2) lengthSamples >>= 1;
if (bitsPerSample == 16) lengthSamples >>= 1;
freq = samplesPerSec;
len = lengthSamples;
bits16 = (bitsPerSample == 16);
stereo = (channels == 2);
return data + offset;
}
struct Mixer_impl {
static const int kMixFreq = 44100;
static const SDL_AudioFormat kMixFormat = AUDIO_S16SYS;
static const int kMixSoundChannels = 2;
static const int kMixBufSize = 4096;
static const int kMixChannels = 4;
Mix_Chunk *_sounds[kMixChannels];
Mix_Music *_music;
MixerChannel _channels[kMixChannels];
SfxPlayer *_sfx;
std::map<int, Mix_Chunk *> _preloads; // AIFF preloads (3DO)
void init(MixerType mixerType) {
memset(_sounds, 0, sizeof(_sounds));
_music = 0;
memset(_channels, 0, sizeof(_channels));
for (int i = 0; i < kMixChannels; ++i) {
_channels[i]._mixWav = &MixerChannel::mixWav<8, false>;
}
_sfx = 0;
Mix_Init(MIX_INIT_OGG | MIX_INIT_FLUIDSYNTH);
if (Mix_OpenAudio(kMixFreq, kMixFormat, kMixSoundChannels, kMixBufSize) < 0) {
warning("Mix_OpenAudio failed: %s", Mix_GetError());
}
switch (mixerType) {
case kMixerTypeRaw:
Mix_HookMusic(mixAudio, this);
break;
case kMixerTypeWav:
Mix_SetPostMix(mixAudioWav, this);
break;
case kMixerTypeAiff:
Mix_AllocateChannels(kMixChannels);
break;
default:
break;
}
}
void quit() {
stopAll();
Mix_CloseAudio();
Mix_Quit();
}
void update() {
for (int i = 0; i < kMixChannels; ++i) {
if (_sounds[i] && !Mix_Playing(i)) {
freeSound(i);
}
}
}
void playSoundRaw(byte channel, const byte *data, int freq, byte volume) {
SDL_LockAudio();
_channels[channel].initRaw(data, freq, volume, kMixFreq);
SDL_UnlockAudio();
}
void playSoundWav(byte channel, const byte *data, int freq, byte volume, bool loop) {
int wavFreq, len;
bool bits16, stereo;
const byte *wavData = loadWav(data, wavFreq, len, bits16, stereo);
if (!wavData) return;
if (wavFreq == 22050 || wavFreq == 44100 || wavFreq == 48000) {
freq = (int)(freq * (wavFreq / 9943.0f));
}
SDL_LockAudio();
_channels[channel].initWav(wavData, freq, volume, kMixFreq, len, bits16, stereo, loop);
SDL_UnlockAudio();
}
void playSound(byte channel, int volume, Mix_Chunk *chunk, int loops = 0) {
stopSound(channel);
if (chunk) {
Mix_PlayChannel(channel, chunk, loops);
}
setChannelVolume(channel, volume);
_sounds[channel] = chunk;
}
void stopSound(byte channel) {
SDL_LockAudio();
_channels[channel]._data = 0;
SDL_UnlockAudio();
Mix_HaltChannel(channel);
freeSound(channel);
}
void freeSound(int channel) {
Mix_FreeChunk(_sounds[channel]);
_sounds[channel] = 0;
}
void setChannelVolume(byte channel, byte volume) {
SDL_LockAudio();
_channels[channel]._volume = volume;
SDL_UnlockAudio();
Mix_Volume(channel, volume * MIX_MAX_VOLUME / 63);
}
void playMusic(const char *path, int loops = 0) {
stopMusic();
_music = Mix_LoadMUS(path);
if (_music) {
Mix_VolumeMusic(MIX_MAX_VOLUME / 2);
Mix_PlayMusic(_music, loops);
} else {
warning("Failed to load music '%s', %s", path, Mix_GetError());
}
}
void stopMusic() {
Mix_HaltMusic();
Mix_FreeMusic(_music);
_music = 0;
}
static void mixAifcPlayer(void *data, byte *s16buf, int len) {
((AifcPlayer *)data)->readSamples((int16 *)s16buf, len / 2);
}
void playAifcMusic(AifcPlayer *aifc) {
Mix_HookMusic(mixAifcPlayer, aifc);
}
void stopAifcMusic() {
Mix_HookMusic(0, 0);
}
void playSfxMusic(SfxPlayer *sfx) {
SDL_LockAudio();
_sfx = sfx;
_sfx->play(kMixFreq);
SDL_UnlockAudio();
}
void stopSfxMusic() {
SDL_LockAudio();
if (_sfx) {
_sfx->stop();
_sfx = 0;
}
SDL_UnlockAudio();
}
void mixChannels(int16 *samples, int count) {
if (kAmigaStereoChannels) {
for (int i = 0; i < count; i += 2) {
_channels[0].mixRaw(*samples);
_channels[3].mixRaw(*samples);
++samples;
_channels[1].mixRaw(*samples);
_channels[2].mixRaw(*samples);
++samples;
}
} else {
for (int i = 0; i < count; i += 2) {
for (int j = 0; j < kMixChannels; ++j) {
_channels[j].mixRaw(samples[i]);
}
samples[i + 1] = samples[i];
}
}
}
static void mixAudio(void *data, byte *s16buf, int len) {
memset(s16buf, 0, len);
Mixer_impl *mixer = (Mixer_impl *)data;
mixer->mixChannels((int16 *)s16buf, len / sizeof(int16));
if (mixer->_sfx) {
mixer->_sfx->readSamples((int16 *)s16buf, len / sizeof(int16));
}
}
void mixChannelsWav(int16 *samples, int count) {
for (int i = 0; i < kMixChannels; ++i) {
if (_channels[i]._data) {
(_channels[i].*_channels[i]._mixWav)(samples, count);
}
}
}
static void mixAudioWav(void *data, byte *s16buf, int len) {
Mixer_impl *mixer = (Mixer_impl *)data;
mixer->mixChannelsWav((int16 *)s16buf, len / sizeof(int16));
}
void stopAll() {
for (int i = 0; i < kMixChannels; ++i) {
stopSound(i);
}
stopMusic();
stopSfxMusic();
for (std::map<int, Mix_Chunk *>::iterator it = _preloads.begin(); it != _preloads.end(); ++it) {
debugC(kDebugSound, "Flush preload %d", it->first);
Mix_FreeChunk(it->second);
}
_preloads.clear();
}
void preloadSoundAiff(int num, const byte *data) {
if (_preloads.find(num) != _preloads.end()) {
warning("AIFF sound %d is already preloaded", num);
} else {
const uint32 size = READ_BE_UINT32(data + 4) + 8;
SDL_RWops *rw = SDL_RWFromConstMem(data, size);
Mix_Chunk *chunk = Mix_LoadWAV_RW(rw, 1);
_preloads[num] = chunk;
}
}
void playSoundAiff(int channel, int num, int volume) {
if (_preloads.find(num) == _preloads.end()) {
warning("AIFF sound %d is not preloaded", num);
} else {
Mix_Chunk *chunk = _preloads[num];
Mix_PlayChannel(channel, chunk, 0);
Mix_Volume(channel, volume * MIX_MAX_VOLUME / 63);
}
}
};
Mixer::Mixer(SfxPlayer *sfx)
: _aifc(0), _sfx(sfx) {
}
void Mixer::init(MixerType mixerType) {
_impl = new Mixer_impl();
_impl->init(mixerType);
}
void Mixer::quit() {
stopAll();
if (_impl) {
_impl->quit();
delete _impl;
}
delete _aifc;
}
void Mixer::update() {
if (_impl) {
_impl->update();
}
}
void Mixer::playSoundRaw(byte channel, const byte *data, uint16 freq, byte volume) {
debugC(kDebugSound, "Mixer::playChannel(%d, %d, %d)", channel, freq, volume);
if (_impl) {
return _impl->playSoundRaw(channel, data, freq, volume);
}
}
void Mixer::playSoundWav(byte channel, const byte *data, uint16 freq, byte volume, byte loop) {
debugC(kDebugSound, "Mixer::playSoundWav(%d, %d, %d)", channel, volume, loop);
if (_impl) {
return _impl->playSoundWav(channel, data, freq, volume, loop);
}
}
void Mixer::stopSound(byte channel) {
debugC(kDebugSound, "Mixer::stopChannel(%d)", channel);
if (_impl) {
return _impl->stopSound(channel);
}
}
void Mixer::setChannelVolume(byte channel, byte volume) {
debugC(kDebugSound, "Mixer::setChannelVolume(%d, %d)", channel, volume);
if (_impl) {
return _impl->setChannelVolume(channel, volume);
}
}
void Mixer::playMusic(const char *path, byte loop) {
debugC(kDebugSound, "Mixer::playMusic(%s, %d)", path, loop);
if (_impl) {
return _impl->playMusic(path, (loop != 0) ? -1 : 0);
}
}
void Mixer::stopMusic() {
debugC(kDebugSound, "Mixer::stopMusic()");
if (_impl) {
return _impl->stopMusic();
}
}
void Mixer::playAifcMusic(const char *path, uint32 offset) {
debugC(kDebugSound, "Mixer::playAifcMusic(%s)", path);
if (!_aifc) {
_aifc = new AifcPlayer();
}
if (_impl) {
_impl->stopAifcMusic();
if (_aifc->play(Mixer_impl::kMixFreq, path, offset)) {
_impl->playAifcMusic(_aifc);
}
}
}
void Mixer::stopAifcMusic() {
debugC(kDebugSound, "Mixer::stopAifcMusic()");
if (_impl && _aifc) {
_aifc->stop();
_impl->stopAifcMusic();
}
}
void Mixer::playSfxMusic(int num) {
debugC(kDebugSound, "Mixer::playSfxMusic(%d)", num);
if (_impl && _sfx) {
return _impl->playSfxMusic(_sfx);
}
}
void Mixer::stopSfxMusic() {
debugC(kDebugSound, "Mixer::stopSfxMusic()");
if (_impl && _sfx) {
return _impl->stopSfxMusic();
}
}
void Mixer::stopAll() {
debugC(kDebugSound, "Mixer::stopAll()");
if (_impl) {
return _impl->stopAll();
}
}
void Mixer::preloadSoundAiff(byte num, const byte *data) {
debugC(kDebugSound, "Mixer::preloadSoundAiff(num:%d, data:%p)", num, data);
if (_impl) {
return _impl->preloadSoundAiff(num, data);
}
}
void Mixer::playSoundAiff(byte channel, byte num, byte volume) {
debugC(kDebugSound, "Mixer::playSoundAiff()");
if (_impl) {
return _impl->playSoundAiff(channel, num, volume);
}
}
#endif
} // namespace Awe