diff --git a/src/openrct2-dll/openrct2-dll.cpp b/src/openrct2-dll/openrct2-dll.cpp index c15683e233..83ca557473 100644 --- a/src/openrct2-dll/openrct2-dll.cpp +++ b/src/openrct2-dll/openrct2-dll.cpp @@ -20,12 +20,14 @@ #include #include #include +#include #include #include #include #include using namespace OpenRCT2; +using namespace OpenRCT2::Audio; using namespace OpenRCT2::Ui; #define DLLEXPORT extern "C" __declspec(dllexport) @@ -43,13 +45,15 @@ DLLEXPORT int LaunchOpenRCT2(int argc, wchar_t * * argvW) return -1; } - IUiContext * uiContext = OpenRCT2::Ui::CreateContext(); - IContext * context = OpenRCT2::CreateContext(uiContext); + IAudioContext * audioContext = CreateAudioContext(); + IUiContext * uiContext = CreateUiContext(); + IContext * context = OpenRCT2::CreateContext(audioContext, uiContext); int exitCode = context->RunOpenRCT2(argc, argv); delete context; delete uiContext; + delete audioContext; FreeCommandLineArgs(argc, argv); return exitCode; diff --git a/src/openrct2-ui/UiContext.cpp b/src/openrct2-ui/UiContext.cpp index 9126c7eb69..021ccfe29a 100644 --- a/src/openrct2-ui/UiContext.cpp +++ b/src/openrct2-ui/UiContext.cpp @@ -602,7 +602,7 @@ private: } }; -IUiContext * OpenRCT2::Ui::CreateContext() +IUiContext * OpenRCT2::Ui::CreateUiContext() { auto platformUiContext = std::unique_ptr(CreatePlatformUiContext()); return new UiContext(platformUiContext.get()); diff --git a/src/openrct2-ui/UiContext.h b/src/openrct2-ui/UiContext.h index 7712bdf935..3d46d201bc 100644 --- a/src/openrct2-ui/UiContext.h +++ b/src/openrct2-ui/UiContext.h @@ -35,7 +35,7 @@ namespace OpenRCT2 virtual bool IsSteamOverlayAttached() abstract; }; - IUiContext * CreateContext(); + IUiContext * CreateUiContext(); IPlatformUiContext * CreatePlatformUiContext(); } } diff --git a/src/openrct2-ui/audio/AudioChannel.cpp b/src/openrct2-ui/audio/AudioChannel.cpp new file mode 100644 index 0000000000..ae2037a534 --- /dev/null +++ b/src/openrct2-ui/audio/AudioChannel.cpp @@ -0,0 +1,297 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 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. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include +#include +#include +#include +#include +#include +#include +#include "AudioContext.h" +#include "AudioFormat.h" + +namespace OpenRCT2 { namespace Audio +{ + class AudioChannelImpl : public IAudioChannel + { + private: + IAudioSource * _source = nullptr; + SpeexResamplerState * _resampler = nullptr; + + sint32 _group = MIXER_GROUP_SOUND; + double _rate = 0; + uint64 _offset = 0; + sint32 _loop = 0; + + sint32 _volume = 1; + float _volume_l = 0.f; + float _volume_r = 0.f; + float _oldvolume_l = 0.f; + float _oldvolume_r = 0.f; + sint32 _oldvolume = 0; + float _pan = 0; + + bool _stopping = false; + bool _done = true; + bool _deleteondone = false; + bool _deletesourceondone = false; + + public: + AudioChannelImpl() + { + SetRate(1); + SetVolume(SDL_MIX_MAXVOLUME); + SetPan(0.5f); + } + + ~AudioChannelImpl() override + { + if (_resampler != nullptr) + { + speex_resampler_destroy(_resampler); + _resampler = nullptr; + } + if (_deletesourceondone) + { + delete _source; + } + } + + IAudioSource * GetSource() const override + { + return _source; + } + + SpeexResamplerState * GetResampler() const override + { + return _resampler; + } + + void SetResampler(SpeexResamplerState * value) override + { + _resampler = value; + } + + sint32 GetGroup() const override + { + return _group; + } + + void SetGroup(sint32 group) override + { + _group = group; + } + + double GetRate() const override + { + return _rate; + } + + void SetRate(double rate) override + { + _rate = Math::Max(0.001, rate); + } + + uint64 GetOffset() const override + { + return _offset; + } + + bool SetOffset(uint64 offset) override + { + if (_source != nullptr && offset < _source->GetLength()) + { + AudioFormat format = _source->GetFormat(); + sint32 samplesize = format.channels * format.BytesPerSample(); + _offset = (offset / samplesize) * samplesize; + return true; + } + return false; + } + + virtual sint32 GetLoop() const override + { + return _loop; + } + + virtual void SetLoop(sint32 value) override + { + _loop = value; + } + + sint32 GetVolume() const override + { + return _volume; + } + + float GetVolumeL() const override + { + return _volume_l; + } + + float GetVolumeR() const override + { + return _volume_r; + } + + float GetOldVolumeL() const override + { + return _oldvolume_l; + } + + float GetOldVolumeR() const override + { + return _oldvolume_r; + } + + sint32 GetOldVolume() const override + { + return _oldvolume; + } + + void SetVolume(sint32 volume) override + { + _volume = Math::Clamp(0, volume, SDL_MIX_MAXVOLUME); + } + + float GetPan() const override + { + return _pan; + } + + void SetPan(float pan) override + { + _pan = Math::Clamp(0.0f, pan, 1.0f); + double decibels = (std::abs(_pan - 0.5) * 2.0) * 100.0; + double attenuation = pow(10, decibels / 20.0); + if (_pan <= 0.5) + { + _volume_l = 1.0; + _volume_r = (float)(1.0 / attenuation); + } + else + { + _volume_r = 1.0; + _volume_l = (float)(1.0 / attenuation); + } + } + + bool IsStopping() const override + { + return _stopping; + } + + void SetStopping(bool value) override + { + _stopping = value; + } + + bool IsDone() const override + { + return _done; + } + + void SetDone(bool value) override + { + _done = value; + } + + bool DeleteOnDone() const override + { + return _deleteondone; + } + + void SetDeleteOnDone(bool value) override + { + _deleteondone = value; + } + + void SetDeleteSourceOnDone(bool value) override + { + _deletesourceondone = value; + } + + bool IsPlaying() const override + { + return !_done; + } + + void Play(IAudioSource * source, sint32 loop) override + { + _source = source; + _loop = loop; + _offset = 0; + _done = false; + } + + void UpdateOldVolume() override + { + _oldvolume = _volume; + _oldvolume_l = _volume_l; + _oldvolume_r = _volume_r; + } + + AudioFormat GetFormat() const override + { + AudioFormat result = { 0 }; + if (_source != nullptr) + { + result = _source->GetFormat(); + } + return result; + } + + size_t Read(void * dst, size_t len) override + { + size_t bytesRead = 0; + size_t bytesToRead = len; + while (bytesToRead > 0 && !_done) + { + size_t readLen = _source->Read(dst, _offset, bytesToRead); + if (readLen > 0) + { + dst = (void *)((uintptr_t)dst + readLen); + bytesToRead -= readLen; + bytesRead += readLen; + _offset += readLen; + } + if (_offset >= _source->GetLength()) + { + if (_loop == 0) + { + _done = true; + } + else if (_loop == MIXER_LOOP_INFINITE) + { + _offset = 0; + } + else + { + _loop--; + _offset = 0; + } + } + } + return bytesRead; + } + }; + + IAudioChannel * AudioChannel::Create() + { + return new (std::nothrow) AudioChannelImpl(); + } +} } diff --git a/src/openrct2-ui/audio/AudioContext.cpp b/src/openrct2-ui/audio/AudioContext.cpp index bc7d838940..bec4079fa6 100644 --- a/src/openrct2-ui/audio/AudioContext.cpp +++ b/src/openrct2-ui/audio/AudioContext.cpp @@ -25,6 +25,15 @@ namespace OpenRCT2 { namespace Audio { } + virtual void SetOutputDevice(const char * deviceName) override + { + } + + virtual IAudioSource * CreateStreamFromWAV(const std::string &path) override + { + return nullptr; + } + void StartTitleMusic() override { } IAudioChannel * PlaySound(sint32 soundId, sint32 volume, sint32 pan) override { return nullptr; } diff --git a/src/openrct2-ui/audio/AudioContext.h b/src/openrct2-ui/audio/AudioContext.h new file mode 100644 index 0000000000..b523e06edd --- /dev/null +++ b/src/openrct2-ui/audio/AudioContext.h @@ -0,0 +1,44 @@ + +#pragma once + +#include +#include + +namespace OpenRCT2 { namespace Audio +{ + struct AudioFormat; + interface IAudioSource; + +#pragma pack(push, 1) + struct WaveFormat + { + uint16 encoding; + uint16 channels; + uint32 frequency; + uint32 byterate; + uint16 blockalign; + uint16 bitspersample; + }; + assert_struct_size(WaveFormat, 16); + + struct WaveFormatEx + { + uint16 encoding; + uint16 channels; + uint32 frequency; + uint32 byterate; + uint16 blockalign; + uint16 bitspersample; + uint16 extrasize; + }; + assert_struct_size(WaveFormatEx, 18); +#pragma pack(pop) + + namespace AudioSource + { + IAudioSource * CreateMemoryFromCSS1(const std::string &path, size_t index, const AudioFormat * targetFormat = nullptr); + IAudioSource * CreateMemoryFromWAV(const std::string &path, const AudioFormat * targetFormat = nullptr); + IAudioSource * CreateStreamFromWAV(const std::string &path); + IAudioSource * CreateStreamFromWAV(SDL_RWops * rw); + } +} } diff --git a/src/openrct2/audio/AudioFormat.h b/src/openrct2-ui/audio/AudioFormat.h similarity index 50% rename from src/openrct2/audio/AudioFormat.h rename to src/openrct2-ui/audio/AudioFormat.h index 59f9d8bc08..fb31ab503d 100644 --- a/src/openrct2/audio/AudioFormat.h +++ b/src/openrct2-ui/audio/AudioFormat.h @@ -16,38 +16,41 @@ #pragma once -#include "../common.h" +#include #include -/** - * Represents the size, frequency and number of channels for - * an audio stream or buffer. - */ -struct AudioFormat +namespace OpenRCT2 { namespace Audio { - sint32 freq; - SDL_AudioFormat format; - sint32 channels; - - sint32 BytesPerSample() const + /** + * Represents the size, frequency and number of channels for + * an audio stream or buffer. + */ + struct AudioFormat { - return (SDL_AUDIO_BITSIZE(format)) / 8; + sint32 freq; + SDL_AudioFormat format; + sint32 channels; + + sint32 BytesPerSample() const + { + return (SDL_AUDIO_BITSIZE(format)) / 8; + } + + sint32 GetByteRate() const + { + return BytesPerSample() * channels; + } + }; + + inline bool operator ==(const AudioFormat& lhs, const AudioFormat& rhs) + { + return lhs.freq == rhs.freq && + lhs.format == rhs.format && + lhs.channels == rhs.channels; } - sint32 GetByteRate() const + inline bool operator !=(const AudioFormat& lhs, const AudioFormat& rhs) { - return BytesPerSample() * channels; + return !(lhs == rhs); } -}; - -inline bool operator ==(const AudioFormat& lhs, const AudioFormat& rhs) -{ - return lhs.freq == rhs.freq && - lhs.format == rhs.format && - lhs.channels == rhs.channels; -} - -inline bool operator !=(const AudioFormat& lhs, const AudioFormat& rhs) -{ - return !(lhs == rhs); -} +} } diff --git a/src/openrct2-ui/audio/AudioMixer.cpp b/src/openrct2-ui/audio/AudioMixer.cpp new file mode 100644 index 0000000000..075ac6a7b4 --- /dev/null +++ b/src/openrct2-ui/audio/AudioMixer.cpp @@ -0,0 +1,539 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 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. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "AudioContext.h" +#include "AudioFormat.h" + +extern "C" +{ + #include + #include + #include + #include + #include +} + +namespace OpenRCT2 { namespace Audio +{ + + struct Buffer + { + private: + void * _data = nullptr; + size_t _capacity = 0; + + public: + void * GetData() const { return _data; } + void * GetData() { return _data; } + size_t GetCapacity() const { return _capacity; } + + ~Buffer() + { + Free(); + } + + void Free() + { + Memory::Free(_data); + _data = nullptr; + _capacity = 0; + } + + void EnsureCapacity(size_t capacity) + { + if (_capacity < capacity) + { + _capacity = capacity; + _data = Memory::Reallocate(_data, capacity); + } + } + }; + + class AudioMixer final : public IAudioMixer + { + private: + IAudioSource * _nullSource = nullptr; + + SDL_AudioDeviceID _deviceId = 0; + AudioFormat _format = { 0 }; + std::list _channels; + float _volume = 1.0f; + float _adjustSoundVolume = 0.0f; + float _adjustMusicVolume = 0.0f; + uint8 _settingSoundVolume = 0xFF; + uint8 _settingMusicVolume = 0xFF; + + IAudioSource * _css1Sources[SOUND_MAXID] = { nullptr }; + IAudioSource * _musicSources[PATH_ID_END] = { nullptr }; + + Buffer _channelBuffer; + Buffer _convertBuffer; + Buffer _effectBuffer; + + public: + AudioMixer() + { + _nullSource = AudioSource::CreateNull(); + } + + ~AudioMixer() + { + Close(); + delete _nullSource; + } + + void Init(const char* device) override + { + Close(); + + SDL_AudioSpec want = { 0 }; + want.freq = 44100; + want.format = AUDIO_S16SYS; + want.channels = 2; + want.samples = 1024; + want.callback = [](void * arg, uint8 * dst, sint32 length) -> void + { + auto mixer = static_cast(arg); + mixer->GetNextAudioChunk(dst, (size_t)length); + }; + want.userdata = this; + + SDL_AudioSpec have; + _deviceId = SDL_OpenAudioDevice(device, 0, &want, &have, 0); + _format.format = have.format; + _format.channels = have.channels; + _format.freq = have.freq; + + LoadAllSounds(); + + SDL_PauseAudioDevice(_deviceId, 0); + } + + void Close() override + { + // Free channels + Lock(); + for (IAudioChannel * channel : _channels) + { + delete channel; + } + _channels.clear(); + Unlock(); + + SDL_CloseAudioDevice(_deviceId); + + // Free sources + for (size_t i = 0; i < Util::CountOf(_css1Sources); i++) + { + if (_css1Sources[i] != _nullSource) + { + SafeDelete(_css1Sources[i]); + } + } + for (size_t i = 0; i < Util::CountOf(_musicSources); i++) + { + if (_musicSources[i] != _nullSource) + { + SafeDelete(_musicSources[i]); + } + } + + // Free buffers + _channelBuffer.Free(); + _convertBuffer.Free(); + _effectBuffer.Free(); + } + + void Lock() override + { + SDL_LockAudioDevice(_deviceId); + } + + void Unlock() override + { + SDL_UnlockAudioDevice(_deviceId); + } + + IAudioChannel * Play(IAudioSource * source, sint32 loop, bool deleteondone, bool deletesourceondone) override + { + Lock(); + IAudioChannel * channel = AudioChannel::Create(); + if (channel != nullptr) + { + channel->Play(source, loop); + channel->SetDeleteOnDone(deleteondone); + channel->SetDeleteSourceOnDone(deletesourceondone); + _channels.push_back(channel); + } + Unlock(); + return channel; + } + + void Stop(IAudioChannel * channel) override + { + Lock(); + channel->SetStopping(true); + Unlock(); + } + + bool LoadMusic(size_t pathId) override + { + bool result = false; + if (pathId < Util::CountOf(_musicSources)) + { + IAudioSource * source = _musicSources[pathId]; + if (source == nullptr) + { + const utf8 * path = get_file_path((sint32)pathId); + source = AudioSource::CreateMemoryFromWAV(path, &_format); + if (source == nullptr) + { + source = _nullSource; + } + _musicSources[pathId] = source; + } + result = source != _nullSource; + } + return result; + } + + void SetVolume(float volume) override + { + _volume = volume; + } + + IAudioSource * GetSoundSource(sint32 id) override + { + return _css1Sources[id]; + } + + IAudioSource * GetMusicSource(sint32 id) override + { + return _musicSources[id]; + } + + private: + void LoadAllSounds() + { + const utf8 * css1Path = get_file_path(PATH_ID_CSS1); + for (size_t i = 0; i < Util::CountOf(_css1Sources); i++) + { + auto source = AudioSource::CreateMemoryFromCSS1(css1Path, i, &_format); + if (source == nullptr) + { + source = _nullSource; + } + _css1Sources[i] = source; + } + } + + void GetNextAudioChunk(uint8 * dst, size_t length) + { + UpdateAdjustedSound(); + + // Zero the output buffer + Memory::Set(dst, 0, length); + + // Mix channels onto output buffer + std::list::iterator it = _channels.begin(); + while (it != _channels.end()) + { + IAudioChannel * channel = *it; + + sint32 group = channel->GetGroup(); + if (group != MIXER_GROUP_SOUND || gConfigSound.sound_enabled) + { + MixChannel(channel, dst, length); + } + if ((channel->IsDone() && channel->DeleteOnDone()) || channel->IsStopping()) + { + delete channel; + it = _channels.erase(it); + } + else + { + it++; + } + } + } + + void UpdateAdjustedSound() + { + // Did the volume level get changed? Recalculate level in this case. + if (_settingSoundVolume != gConfigSound.sound_volume) + { + _settingSoundVolume = gConfigSound.sound_volume; + _adjustSoundVolume = powf(_settingSoundVolume / 100.f, 10.f / 6.f); + } + if (_settingMusicVolume != gConfigSound.ride_music_volume) + { + _settingMusicVolume = gConfigSound.ride_music_volume; + _adjustMusicVolume = powf(_settingMusicVolume / 100.f, 10.f / 6.f); + } + } + + void MixChannel(IAudioChannel * channel, uint8 * data, size_t length) + { + sint32 byteRate = _format.GetByteRate(); + sint32 numSamples = (sint32)(length / byteRate); + double rate = 1; + if (_format.format == AUDIO_S16SYS) + { + rate = channel->GetRate(); + } + + bool mustConvert = false; + SDL_AudioCVT cvt; + cvt.len_ratio = 1; + AudioFormat streamformat = channel->GetFormat(); + if (streamformat != _format) + { + if (SDL_BuildAudioCVT(&cvt, streamformat.format, streamformat.channels, streamformat.freq, _format.format, _format.channels, _format.freq) == -1) + { + // Unable to convert channel data + return; + } + mustConvert = true; + } + + // Read raw PCM from channel + sint32 readSamples = (sint32)(numSamples * rate); + size_t readLength = (size_t)(readSamples / cvt.len_ratio) * byteRate; + _channelBuffer.EnsureCapacity(readLength); + size_t bytesRead = channel->Read(_channelBuffer.GetData(), readLength); + + // Convert data to required format if necessary + void * buffer = nullptr; + size_t bufferLen = 0; + if (mustConvert) + { + if (Convert(&cvt, _channelBuffer.GetData(), bytesRead)) + { + buffer = cvt.buf; + bufferLen = cvt.len_cvt; + } + else + { + return; + } + } + else + { + buffer = _channelBuffer.GetData(); + bufferLen = bytesRead; + } + + // Apply effects + if (rate != 1) + { + sint32 srcSamples = (sint32)(bufferLen / byteRate); + sint32 dstSamples = numSamples; + bufferLen = ApplyResample(channel, buffer, srcSamples, dstSamples); + buffer = _effectBuffer.GetData(); + } + + // Apply panning and volume + ApplyPan(channel, buffer, bufferLen, byteRate); + sint32 mixVolume = ApplyVolume(channel, buffer, bufferLen); + + // Finally mix on to destination buffer + size_t dstLength = Math::Min(length, bufferLen); + SDL_MixAudioFormat(data, (const uint8 *)buffer, _format.format, (uint32)dstLength, mixVolume); + + channel->UpdateOldVolume(); + } + + /** + * Resample the given buffer into _effectBuffer. + * Assumes that srcBuffer is the same format as _format. + */ + size_t ApplyResample(IAudioChannel * channel, const void * srcBuffer, sint32 srcSamples, sint32 dstSamples) + { + sint32 byteRate = _format.GetByteRate(); + + // Create resampler + SpeexResamplerState * resampler = channel->GetResampler(); + if (resampler == nullptr) + { + resampler = speex_resampler_init(_format.channels, _format.freq, _format.freq, 0, 0); + channel->SetResampler(resampler); + } + speex_resampler_set_rate(resampler, srcSamples, dstSamples); + + // Ensure destination buffer is large enough + size_t effectBufferReqLen = dstSamples * byteRate; + _effectBuffer.EnsureCapacity(effectBufferReqLen); + + uint32 inLen = srcSamples; + uint32 outLen = dstSamples; + speex_resampler_process_interleaved_int( + resampler, + (const spx_int16_t *)srcBuffer, + &inLen, + (spx_int16_t *)_effectBuffer.GetData(), + &outLen); + + return outLen * byteRate; + } + + void ApplyPan(const IAudioChannel * channel, void * buffer, size_t len, size_t sampleSize) + { + if (channel->GetPan() != 0.5f && _format.channels == 2) + { + switch (_format.format) { + case AUDIO_S16SYS: + EffectPanS16(channel, (sint16 *)buffer, (sint32)(len / sampleSize)); + break; + case AUDIO_U8: + EffectPanU8(channel, (uint8 *)buffer, (sint32)(len / sampleSize)); + break; + } + } + } + + sint32 ApplyVolume(const IAudioChannel * channel, void * buffer, size_t len) + { + float volumeAdjust = _volume; + volumeAdjust *= (gConfigSound.master_volume / 100.0f); + switch (channel->GetGroup()) { + case MIXER_GROUP_SOUND: + volumeAdjust *= _adjustSoundVolume; + + // Cap sound volume on title screen so music is more audible + if (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) + { + volumeAdjust = Math::Min(volumeAdjust, 0.75f); + } + break; + case MIXER_GROUP_RIDE_MUSIC: + volumeAdjust *= _adjustMusicVolume; + break; + } + + sint32 startVolume = (sint32)(channel->GetOldVolume() * volumeAdjust); + sint32 endVolume = (sint32)(channel->GetVolume() * volumeAdjust); + if (channel->IsStopping()) + { + endVolume = 0; + } + + sint32 mixVolume = (sint32)(channel->GetVolume() * volumeAdjust); + if (startVolume != endVolume) + { + // Set to max since we are adjusting the volume ourselves + mixVolume = SDL_MIX_MAXVOLUME; + + // Fade between volume levels to smooth out sound and minimize clicks from sudden volume changes + sint32 fadeLength = (sint32)len / _format.BytesPerSample(); + switch (_format.format) { + case AUDIO_S16SYS: + EffectFadeS16((sint16 *)buffer, fadeLength, startVolume, endVolume); + break; + case AUDIO_U8: + EffectFadeU8((uint8 *)buffer, fadeLength, startVolume, endVolume); + break; + } + } + return mixVolume; + } + + static void EffectPanS16(const IAudioChannel * channel, sint16 * data, sint32 length) + { + const float dt = 1.0f / (length * 2); + float volumeL = channel->GetOldVolumeL(); + float volumeR = channel->GetOldVolumeR(); + const float d_left = dt * (channel->GetVolumeL() - channel->GetOldVolumeL()); + const float d_right = dt * (channel->GetVolumeR() - channel->GetOldVolumeR()); + + for (sint32 i = 0; i < length * 2; i += 2) + { + data[i] = (sint16)(data[i] * volumeL); + data[i + 1] = (sint16)(data[i + 1] * volumeR); + volumeL += d_left; + volumeR += d_right; + } + } + + static void EffectPanU8(const IAudioChannel * channel, uint8 * data, sint32 length) + { + float volumeL = channel->GetVolumeL(); + float volumeR = channel->GetVolumeR(); + float oldVolumeL = channel->GetOldVolumeL(); + float oldVolumeR = channel->GetOldVolumeR(); + + for (sint32 i = 0; i < length * 2; i += 2) + { + float t = (float)i / (length * 2); + data[i] = (uint8)(data[i] * ((1.0 - t) * oldVolumeL + t * volumeL)); + data[i + 1] = (uint8)(data[i + 1] * ((1.0 - t) * oldVolumeR + t * volumeR)); + } + } + + static void EffectFadeS16(sint16 * data, sint32 length, sint32 startvolume, sint32 endvolume) + { + float startvolume_f = (float)startvolume / SDL_MIX_MAXVOLUME; + float endvolume_f = (float)endvolume / SDL_MIX_MAXVOLUME; + for (sint32 i = 0; i < length; i++) + { + float t = (float)i / length; + data[i] = (sint16)(data[i] * ((1 - t) * startvolume_f + t * endvolume_f)); + } + } + + static void EffectFadeU8(uint8* data, sint32 length, sint32 startvolume, sint32 endvolume) + { + float startvolume_f = (float)startvolume / SDL_MIX_MAXVOLUME; + float endvolume_f = (float)endvolume / SDL_MIX_MAXVOLUME; + for (sint32 i = 0; i < length; i++) + { + float t = (float)i / length; + data[i] = (uint8)(data[i] * ((1 - t) * startvolume_f + t * endvolume_f)); + } + } + + bool Convert(SDL_AudioCVT * cvt, const void * src, size_t len) + { + // tofix: there seems to be an issue with converting audio using SDL_ConvertAudio in the callback vs preconverted, can cause pops and static depending on sample rate and channels + bool result = false; + if (len != 0 && cvt->len_mult != 0) + { + size_t reqConvertBufferCapacity = len * cvt->len_mult; + _convertBuffer.EnsureCapacity(reqConvertBufferCapacity); + Memory::Copy(_convertBuffer.GetData(), src, len); + + cvt->len = (sint32)len; + cvt->buf = (uint8 *)_convertBuffer.GetData(); + if (SDL_ConvertAudio(cvt) >= 0) + { + result = true; + } + } + return result; + } + }; +} } diff --git a/src/openrct2-ui/audio/FileAudioSource.cpp b/src/openrct2-ui/audio/FileAudioSource.cpp new file mode 100644 index 0000000000..70ce35f0d1 --- /dev/null +++ b/src/openrct2-ui/audio/FileAudioSource.cpp @@ -0,0 +1,210 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 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. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include +#include +#include +#include +#include "AudioContext.h" +#include "AudioFormat.h" + +namespace OpenRCT2 { namespace Audio +{ + /** + * An audio source where raw PCM data is streamed directly from + * a file. + */ + class FileAudioSource final : public IAudioSource + { + private: + AudioFormat _format = { 0 }; + SDL_RWops * _rw = nullptr; + uint64 _dataBegin = 0; + uint64 _dataLength = 0; + + public: + ~FileAudioSource() + { + Unload(); + } + + uint64 GetLength() override + { + return _dataLength; + } + + AudioFormat GetFormat() override + { + return _format; + } + + size_t Read(void * dst, uint64 offset, size_t len) override + { + size_t bytesRead = 0; + sint64 currentPosition = SDL_RWtell(_rw); + if (currentPosition != -1) + { + size_t bytesToRead = (size_t)Math::Min(len, _dataLength - offset); + sint64 dataOffset = _dataBegin + offset; + if (currentPosition != dataOffset) + { + sint64 newPosition = SDL_RWseek(_rw, dataOffset, SEEK_SET); + if (newPosition == -1) + { + return 0; + } + } + bytesRead = SDL_RWread(_rw, dst, 1, bytesToRead); + } + return bytesRead; + } + + bool LoadWAV(SDL_RWops * rw) + { + const uint32 DATA = 0x61746164; + const uint32 FMT = 0x20746D66; + const uint32 RIFF = 0x46464952; + const uint32 WAVE = 0x45564157; + const uint16 pcmformat = 0x0001; + + Unload(); + + if (rw == nullptr) + { + return false; + } + _rw = rw; + + uint32 chunkId = SDL_ReadLE32(rw); + if (chunkId != RIFF) + { + log_verbose("Not a WAV file"); + return false; + } + + // Read and discard chunk size + SDL_ReadLE32(rw); + uint32 chunkFormat = SDL_ReadLE32(rw); + if (chunkFormat != WAVE) + { + log_verbose("Not in WAVE format"); + return false; + } + + uint32 fmtChunkSize = FindChunk(rw, FMT); + if (!fmtChunkSize) + { + log_verbose("Could not find FMT chunk"); + return false; + } + + uint64 chunkStart = SDL_RWtell(rw); + + WaveFormat waveFormat; + SDL_RWread(rw, &waveFormat, sizeof(waveFormat), 1); + SDL_RWseek(rw, chunkStart + fmtChunkSize, RW_SEEK_SET); + if (waveFormat.encoding != pcmformat) { + log_verbose("Not in proper format"); + return false; + } + + _format.freq = waveFormat.frequency; + switch (waveFormat.bitspersample) { + case 8: + _format.format = AUDIO_U8; + break; + case 16: + _format.format = AUDIO_S16LSB; + break; + default: + log_verbose("Invalid bits per sample"); + return false; + break; + } + _format.channels = waveFormat.channels; + + uint32 dataChunkSize = FindChunk(rw, DATA); + if (dataChunkSize == 0) + { + log_verbose("Could not find DATA chunk"); + return false; + } + + _dataLength = dataChunkSize; + _dataBegin = SDL_RWtell(rw); + return true; + } + + private: + uint32 FindChunk(SDL_RWops * rw, uint32 wantedId) + { + uint32 subchunkId = SDL_ReadLE32(rw); + uint32 subchunkSize = SDL_ReadLE32(rw); + if (subchunkId == wantedId) + { + return subchunkSize; + } + const uint32 FACT = 0x74636166; + const uint32 LIST = 0x5453494c; + const uint32 BEXT = 0x74786562; + const uint32 JUNK = 0x4B4E554A; + while (subchunkId == FACT || subchunkId == LIST || subchunkId == BEXT || subchunkId == JUNK) + { + SDL_RWseek(rw, subchunkSize, RW_SEEK_CUR); + subchunkId = SDL_ReadLE32(rw); + subchunkSize = SDL_ReadLE32(rw); + if (subchunkId == wantedId) + { + return subchunkSize; + } + } + return 0; + } + + void Unload() + { + if (_rw != nullptr) + { + SDL_RWclose(_rw); + _rw = nullptr; + } + _dataBegin = 0; + _dataLength = 0; + } + }; + + IAudioSource * AudioSource::CreateStreamFromWAV(const std::string &path) + { + IAudioSource * source = nullptr; + SDL_RWops* rw = SDL_RWFromFile(path.c_str(), "rb"); + if (rw != nullptr) + { + return AudioSource::CreateStreamFromWAV(rw); + } + return source; + } + + IAudioSource * AudioSource::CreateStreamFromWAV(SDL_RWops * rw) + { + auto source = new FileAudioSource(); + if (!source->LoadWAV(rw)) + { + delete source; + source = nullptr; + } + return source; + } +} } diff --git a/src/openrct2-ui/audio/MemoryAudioSource.cpp b/src/openrct2-ui/audio/MemoryAudioSource.cpp new file mode 100644 index 0000000000..efe3d6cc3d --- /dev/null +++ b/src/openrct2-ui/audio/MemoryAudioSource.cpp @@ -0,0 +1,241 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 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. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include +#include +#include +#include +#include +#include +#include "AudioContext.h" +#include "AudioFormat.h" + +namespace OpenRCT2 { namespace Audio +{ + /** + * An audio source where raw PCM data is initially loaded into RAM from + * a file and then streamed. + */ + class MemoryAudioSource final : public IAudioSource + { + private: + AudioFormat _format = { 0 }; + uint8 * _data = nullptr; + size_t _length = 0; + bool _isSDLWav = false; + + public: + ~MemoryAudioSource() + { + Unload(); + } + + uint64 GetLength() override + { + return _length; + } + + AudioFormat GetFormat() override + { + return _format; + } + + size_t Read(void * dst, uint64 offset, size_t len) override + { + size_t bytesToRead = 0; + if (offset < _length) + { + bytesToRead = (size_t)Math::Min(len, _length - offset); + Memory::Copy(dst, _data + offset, bytesToRead); + } + return bytesToRead; + } + + bool LoadWAV(const utf8 * path) + { + log_verbose("MemoryAudioSource::LoadWAV(%s)", path); + + Unload(); + + bool result = false; + SDL_RWops * rw = SDL_RWFromFile(path, "rb"); + if (rw != nullptr) + { + SDL_AudioSpec audiospec = { 0 }; + uint32 audioLen; + SDL_AudioSpec * spec = SDL_LoadWAV_RW(rw, false, &audiospec, &_data, &audioLen); + if (spec != nullptr) + { + _format.freq = spec->freq; + _format.format = spec->format; + _format.channels = spec->channels; + _length = audioLen; + _isSDLWav = true; + result = true; + } + else + { + log_verbose("Error loading %s, unsupported WAV format", path); + } + SDL_RWclose(rw); + } + else + { + log_verbose("Error loading %s", path); + } + return result; + } + + bool LoadCSS1(const utf8 * path, size_t index) + { + log_verbose("MemoryAudioSource::LoadCSS1(%s, %d)", path, index); + + Unload(); + + bool result = false; + SDL_RWops * rw = SDL_RWFromFile(path, "rb"); + if (rw != nullptr) + { + uint32 numSounds; + SDL_RWread(rw, &numSounds, sizeof(numSounds), 1); + if (index < numSounds) + { + SDL_RWseek(rw, index * 4, RW_SEEK_CUR); + + uint32 pcmOffset; + SDL_RWread(rw, &pcmOffset, sizeof(pcmOffset), 1); + SDL_RWseek(rw, pcmOffset, RW_SEEK_SET); + + uint32 pcmSize; + SDL_RWread(rw, &pcmSize, sizeof(pcmSize), 1); + _length = pcmSize; + + WaveFormatEx waveFormat; + SDL_RWread(rw, &waveFormat, sizeof(waveFormat), 1); + _format.freq = waveFormat.frequency; + _format.format = AUDIO_S16LSB; + _format.channels = waveFormat.channels; + + _data = new (std::nothrow) uint8[_length]; + if (_data != nullptr) + { + SDL_RWread(rw, _data, _length, 1); + result = true; + } + else + { + log_verbose("Unable to allocate data"); + } + } + SDL_RWclose(rw); + } + else + { + log_verbose("Unable to load %s", path); + } + return result; + } + + bool Convert(const AudioFormat * format) + { + if (*format != _format) + { + SDL_AudioCVT cvt; + if (SDL_BuildAudioCVT(&cvt, _format.format, _format.channels, _format.freq, format->format, format->channels, format->freq) >= 0) + { + cvt.len = (sint32)_length; + cvt.buf = new uint8[cvt.len * cvt.len_mult]; + Memory::Copy(cvt.buf, _data, _length); + if (SDL_ConvertAudio(&cvt) >= 0) + { + Unload(); + _data = cvt.buf; + _length = cvt.len_cvt; + _format = *format; + return true; + } + else + { + delete[] cvt.buf; + } + } + } + return false; + } + + private: + void Unload() + { + if (_data != nullptr) + { + if (_isSDLWav) + { + SDL_FreeWAV(_data); + } + else + { + delete[] _data; + } + _data = nullptr; + } + _isSDLWav = false; + _length = 0; + } + }; + + IAudioSource * AudioSource::CreateMemoryFromCSS1(const std::string &path, size_t index, const AudioFormat * targetFormat) + { + auto source = new MemoryAudioSource(); + if (source->LoadCSS1(path.c_str(), index)) + { + if (targetFormat != nullptr) + { + if (!source->Convert(targetFormat)) + { + delete source; + source = nullptr; + } + } + } + else + { + delete source; + source = nullptr; + } + return source; + } + + IAudioSource * AudioSource::CreateMemoryFromWAV(const std::string &path, const AudioFormat * targetFormat) + { + auto source = new MemoryAudioSource(); + if (source->LoadWAV(path.c_str())) + { + if (targetFormat != nullptr) + { + if (!source->Convert(targetFormat)) + { + SafeDelete(source); + } + } + } + else + { + delete source; + source = nullptr; + } + return source; + } +} } diff --git a/src/openrct2-ui/libopenrct2ui.vcxproj b/src/openrct2-ui/libopenrct2ui.vcxproj index a537a7195b..709b590cbc 100644 --- a/src/openrct2-ui/libopenrct2ui.vcxproj +++ b/src/openrct2-ui/libopenrct2ui.vcxproj @@ -22,7 +22,11 @@ + + + + @@ -42,6 +46,8 @@ + + diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index f8065494ba..2670be1938 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -55,6 +55,7 @@ extern "C" } using namespace OpenRCT2; +using namespace OpenRCT2::Audio; using namespace OpenRCT2::Ui; namespace OpenRCT2 @@ -66,7 +67,8 @@ namespace OpenRCT2 constexpr static uint32 UPDATE_TIME_MS = 25; // Dependencies - IUiContext * const _uiContext = nullptr; + IAudioContext * const _audioContext = nullptr; + IUiContext * const _uiContext = nullptr; // Services IPlatformEnvironment * _env = nullptr; @@ -89,8 +91,9 @@ namespace OpenRCT2 static Context * Instance; public: - Context(IUiContext * uiContext) - : _uiContext(uiContext) + Context(IAudioContext * audioContext, IUiContext * uiContext) + : _audioContext(audioContext), + _uiContext(uiContext) { Instance = this; } @@ -110,6 +113,11 @@ namespace OpenRCT2 Instance = nullptr; } + IAudioContext * GetAudioContext() override + { + return _audioContext; + } + IUiContext * GetUiContext() override { return _uiContext; @@ -556,12 +564,12 @@ namespace OpenRCT2 IContext * CreateContext() { - return new Context(nullptr); + return new Context(nullptr, nullptr); } - IContext * CreateContext(IUiContext * uiContext) + IContext * CreateContext(Audio::IAudioContext * audioContext, IUiContext * uiContext) { - return new Context(uiContext); + return new Context(audioContext, uiContext); } IContext * GetContext() diff --git a/src/openrct2/Context.h b/src/openrct2/Context.h index 219a52265f..a238343370 100644 --- a/src/openrct2/Context.h +++ b/src/openrct2/Context.h @@ -57,6 +57,11 @@ enum namespace OpenRCT2 { + namespace Audio + { + interface IAudioContext; + } + namespace Ui { interface IUiContext; @@ -69,13 +74,15 @@ namespace OpenRCT2 { virtual ~IContext() = default; - virtual Ui::IUiContext * GetUiContext() abstract; + virtual Audio::IAudioContext * GetAudioContext() abstract; + virtual Ui::IUiContext * GetUiContext() abstract; + virtual sint32 RunOpenRCT2(int argc, char * * argv) abstract; virtual void Finish() abstract; }; IContext * CreateContext(); - IContext * CreateContext(Ui::IUiContext * uiContext); + IContext * CreateContext(Audio::IAudioContext * audioContext, Ui::IUiContext * uiContext); IContext * GetContext(); } diff --git a/src/openrct2/audio/AudioChannel.cpp b/src/openrct2/audio/AudioChannel.cpp deleted file mode 100644 index 1faac86448..0000000000 --- a/src/openrct2/audio/AudioChannel.cpp +++ /dev/null @@ -1,290 +0,0 @@ -#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers -/***************************************************************************** - * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. - * - * OpenRCT2 is the work of many authors, a full list can be found in contributors.md - * For more information, visit https://github.com/OpenRCT2/OpenRCT2 - * - * OpenRCT2 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. - * - * A full copy of the GNU General Public License can be found in licence.txt - *****************************************************************************/ -#pragma endregion - -#include -#include -#include "../core/Math.hpp" -#include "AudioChannel.h" -#include "AudioSource.h" - -class AudioChannelImpl : public IAudioChannel -{ -private: - IAudioSource * _source = nullptr; - SpeexResamplerState * _resampler = nullptr; - - sint32 _group = MIXER_GROUP_SOUND; - double _rate = 0; - uint64 _offset = 0; - sint32 _loop = 0; - - sint32 _volume = 1; - float _volume_l = 0.f; - float _volume_r = 0.f; - float _oldvolume_l = 0.f; - float _oldvolume_r = 0.f; - sint32 _oldvolume = 0; - float _pan = 0; - - bool _stopping = false; - bool _done = true; - bool _deleteondone = false; - bool _deletesourceondone = false; - -public: - AudioChannelImpl() - { - SetRate(1); - SetVolume(SDL_MIX_MAXVOLUME); - SetPan(0.5f); - } - - ~AudioChannelImpl() override - { - if (_resampler != nullptr) - { - speex_resampler_destroy(_resampler); - _resampler = nullptr; - } - if (_deletesourceondone) - { - delete _source; - } - } - - IAudioSource * GetSource() const override - { - return _source; - } - - SpeexResamplerState * GetResampler() const override - { - return _resampler; - } - - void SetResampler(SpeexResamplerState * value) override - { - _resampler = value; - } - - sint32 GetGroup() const override - { - return _group; - } - - void SetGroup(sint32 group) override - { - _group = group; - } - - double GetRate() const override - { - return _rate; - } - - void SetRate(double rate) override - { - _rate = Math::Max(0.001, rate); - } - - uint64 GetOffset() const override - { - return _offset; - } - - bool SetOffset(uint64 offset) override - { - if (_source != nullptr && offset < _source->GetLength()) - { - AudioFormat format = _source->GetFormat(); - sint32 samplesize = format.channels * format.BytesPerSample(); - _offset = (offset / samplesize) * samplesize; - return true; - } - return false; - } - - virtual sint32 GetLoop() const override - { - return _loop; - } - - virtual void SetLoop(sint32 value) override - { - _loop = value; - } - - sint32 GetVolume() const override - { - return _volume; - } - - float GetVolumeL() const override - { - return _volume_l; - } - - float GetVolumeR() const override - { - return _volume_r; - } - - float GetOldVolumeL() const override - { - return _oldvolume_l; - } - - float GetOldVolumeR() const override - { - return _oldvolume_r; - } - - sint32 GetOldVolume() const override - { - return _oldvolume; - } - - void SetVolume(sint32 volume) override - { - _volume = Math::Clamp(0, volume, SDL_MIX_MAXVOLUME); - } - - float GetPan() const override - { - return _pan; - } - - void SetPan(float pan) override - { - _pan = Math::Clamp(0.0f, pan, 1.0f); - double decibels = (std::abs(_pan - 0.5) * 2.0) * 100.0; - double attenuation = pow(10, decibels / 20.0); - if (_pan <= 0.5) - { - _volume_l = 1.0; - _volume_r = (float)(1.0 / attenuation); - } - else - { - _volume_r = 1.0; - _volume_l = (float)(1.0 / attenuation); - } - } - - bool IsStopping() const override - { - return _stopping; - } - - void SetStopping(bool value) override - { - _stopping = value; - } - - bool IsDone() const override - { - return _done; - } - - void SetDone(bool value) override - { - _done = value; - } - - bool DeleteOnDone() const override - { - return _deleteondone; - } - - void SetDeleteOnDone(bool value) override - { - _deleteondone = value; - } - - void SetDeleteSourceOnDone(bool value) override - { - _deletesourceondone = value; - } - - bool IsPlaying() const override - { - return !_done; - } - - void Play(IAudioSource * source, sint32 loop) override - { - _source = source; - _loop = loop; - _offset = 0; - _done = false; - } - - void UpdateOldVolume() override - { - _oldvolume = _volume; - _oldvolume_l = _volume_l; - _oldvolume_r = _volume_r; - } - - AudioFormat GetFormat() const override - { - AudioFormat result = { 0 }; - if (_source != nullptr) - { - result = _source->GetFormat(); - } - return result; - } - - size_t Read(void * dst, size_t len) override - { - size_t bytesRead = 0; - size_t bytesToRead = len; - while (bytesToRead > 0 && !_done) - { - size_t readLen = _source->Read(dst, _offset, bytesToRead); - if (readLen > 0) - { - dst = (void *)((uintptr_t)dst + readLen); - bytesToRead -= readLen; - bytesRead += readLen; - _offset += readLen; - } - if (_offset >= _source->GetLength()) - { - if (_loop == 0) - { - _done = true; - } - else if (_loop == MIXER_LOOP_INFINITE) - { - _offset = 0; - } - else - { - _loop--; - _offset = 0; - } - } - } - return bytesRead; - } -}; - -IAudioChannel * AudioChannel::Create() -{ - return new (std::nothrow) AudioChannelImpl(); -} diff --git a/src/openrct2/audio/AudioChannel.h b/src/openrct2/audio/AudioChannel.h index b185bf1382..3c1c2ef3df 100644 --- a/src/openrct2/audio/AudioChannel.h +++ b/src/openrct2/audio/AudioChannel.h @@ -16,70 +16,70 @@ #pragma once -#include #include "../common.h" -#include "AudioFormat.h" -#include "AudioMixer.h" -interface IAudioSource; - -/** - * Represents an audio channel that represents an audio source - * and a number of properties such as volume, pan and loop information. - */ -interface IAudioChannel +namespace OpenRCT2 { namespace Audio { - virtual ~IAudioChannel() = default; + interface IAudioSource; - virtual IAudioSource * GetSource() const abstract; + /** + * Represents an audio channel that represents an audio source + * and a number of properties such as volume, pan and loop information. + */ + interface IAudioChannel + { + virtual ~IAudioChannel() = default; - virtual SpeexResamplerState * GetResampler() const abstract; - virtual void SetResampler(SpeexResamplerState * value) abstract; + virtual IAudioSource * GetSource() const abstract; - virtual sint32 GetGroup() const abstract; - virtual void SetGroup(sint32 group) abstract; + // virtual SpeexResamplerState * GetResampler() const abstract; + // virtual void SetResampler(SpeexResamplerState * value) abstract; - virtual double GetRate() const abstract; - virtual void SetRate(double rate) abstract; + virtual sint32 GetGroup() const abstract; + virtual void SetGroup(sint32 group) abstract; - virtual uint64 GetOffset() const abstract; - virtual bool SetOffset(uint64 offset) abstract; + virtual double GetRate() const abstract; + virtual void SetRate(double rate) abstract; - virtual sint32 GetLoop() const abstract; - virtual void SetLoop(sint32 value) abstract; + virtual uint64 GetOffset() const abstract; + virtual bool SetOffset(uint64 offset) abstract; - virtual sint32 GetVolume() const abstract; - virtual float GetVolumeL() const abstract; - virtual float GetVolumeR() const abstract; - virtual float GetOldVolumeL() const abstract; - virtual float GetOldVolumeR() const abstract; - virtual sint32 GetOldVolume() const abstract; - virtual void SetVolume(sint32 volume) abstract; + virtual sint32 GetLoop() const abstract; + virtual void SetLoop(sint32 value) abstract; - virtual float GetPan() const abstract; - virtual void SetPan(float pan) abstract; + virtual sint32 GetVolume() const abstract; + virtual float GetVolumeL() const abstract; + virtual float GetVolumeR() const abstract; + virtual float GetOldVolumeL() const abstract; + virtual float GetOldVolumeR() const abstract; + virtual sint32 GetOldVolume() const abstract; + virtual void SetVolume(sint32 volume) abstract; - virtual bool IsStopping() const abstract; - virtual void SetStopping(bool value) abstract; + virtual float GetPan() const abstract; + virtual void SetPan(float pan) abstract; - virtual bool IsDone() const abstract; - virtual void SetDone(bool value) abstract; + virtual bool IsStopping() const abstract; + virtual void SetStopping(bool value) abstract; - virtual bool DeleteOnDone() const abstract; - virtual void SetDeleteOnDone(bool value) abstract; + virtual bool IsDone() const abstract; + virtual void SetDone(bool value) abstract; - virtual void SetDeleteSourceOnDone(bool value) abstract; + virtual bool DeleteOnDone() const abstract; + virtual void SetDeleteOnDone(bool value) abstract; - virtual bool IsPlaying() const abstract; + virtual void SetDeleteSourceOnDone(bool value) abstract; - virtual void Play(IAudioSource * source, sint32 loop = MIXER_LOOP_NONE) abstract; - virtual void UpdateOldVolume() abstract; + virtual bool IsPlaying() const abstract; - virtual AudioFormat GetFormat() const abstract; - virtual size_t Read(void * dst, size_t len) abstract; -}; + virtual void Play(IAudioSource * source, sint32 loop = 0) abstract; + virtual void UpdateOldVolume() abstract; -namespace AudioChannel -{ - IAudioChannel * Create(); -} + // virtual AudioFormat GetFormat() const abstract; + virtual size_t Read(void * dst, size_t len) abstract; + }; + + namespace AudioChannel + { + IAudioChannel * Create(); + } +} } diff --git a/src/openrct2/audio/AudioContext.h b/src/openrct2/audio/AudioContext.h index 3f749baf79..45f5ad10d7 100644 --- a/src/openrct2/audio/AudioContext.h +++ b/src/openrct2/audio/AudioContext.h @@ -16,6 +16,7 @@ #pragma once +#include #include "../common.h" namespace OpenRCT2 @@ -23,6 +24,8 @@ namespace OpenRCT2 namespace Audio { interface IAudioChannel; + interface IAudioMixer; + interface IAudioSource; /** * Audio services for playing music and sound effects. @@ -31,6 +34,13 @@ namespace OpenRCT2 { virtual ~IAudioContext() = default; + virtual IAudioMixer * GetMixer() abstract; + + virtual void SetOutputDevice(const char * deviceName) abstract; + + virtual IAudioSource * CreateStreamFromWAV(const std::string &path) abstract; + + virtual void StartTitleMusic() abstract; virtual IAudioChannel * PlaySound(sint32 soundId, sint32 volume, sint32 pan) abstract; @@ -48,5 +58,7 @@ namespace OpenRCT2 virtual void StopTitleMusic() abstract; virtual void StopVehicleSounds() abstract; }; + + IAudioContext * CreateAudioContext(); } } diff --git a/src/openrct2/audio/AudioMixer.cpp b/src/openrct2/audio/AudioMixer.cpp index 72453547ed..7fcbe7355e 100644 --- a/src/openrct2/audio/AudioMixer.cpp +++ b/src/openrct2/audio/AudioMixer.cpp @@ -1,549 +1,53 @@ #pragma region Copyright (c) 2014-2016 OpenRCT2 Developers /***************************************************************************** - * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. - * - * OpenRCT2 is the work of many authors, a full list can be found in contributors.md - * For more information, visit https://github.com/OpenRCT2/OpenRCT2 - * - * OpenRCT2 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. - * - * A full copy of the GNU General Public License can be found in licence.txt - *****************************************************************************/ +* OpenRCT2, an open source clone of Roller Coaster Tycoon 2. +* +* OpenRCT2 is the work of many authors, a full list can be found in contributors.md +* For more information, visit https://github.com/OpenRCT2/OpenRCT2 +* +* OpenRCT2 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. +* +* A full copy of the GNU General Public License can be found in licence.txt +*****************************************************************************/ #pragma endregion -#include -#include "../core/Guard.hpp" -#include "../core/Math.hpp" -#include "../core/Memory.hpp" -#include "../core/Util.hpp" +#include "../config/Config.h" +#include "../Context.h" +#include "audio.h" #include "AudioChannel.h" +#include "AudioContext.h" #include "AudioMixer.h" #include "AudioSource.h" extern "C" { - #include "../config/Config.h" - #include "../localisation/localisation.h" - #include "../OpenRCT2.h" - #include "../platform/platform.h" #include "../rct2.h" - #include "audio.h" } -IAudioMixer * gMixer; +#define SDL_MIX_MAXVOLUME 128 -struct Buffer +using namespace OpenRCT2; +using namespace OpenRCT2::Audio; + +static IAudioMixer * GetMixer() { -private: - void * _data = nullptr; - size_t _capacity = 0; - -public: - void * GetData() const { return _data; } - void * GetData() { return _data; } - size_t GetCapacity() const { return _capacity; } - - ~Buffer() - { - Free(); - } - - void Free() - { - Memory::Free(_data); - _data = nullptr; - _capacity = 0; - } - - void EnsureCapacity(size_t capacity) - { - if (_capacity < capacity) - { - _capacity = capacity; - _data = Memory::Reallocate(_data, capacity); - } - } -}; - -class AudioMixer final : public IAudioMixer -{ -private: - IAudioSource * _nullSource = nullptr; - - SDL_AudioDeviceID _deviceId = 0; - AudioFormat _format = { 0 }; - std::list _channels; - float _volume = 1.0f; - float _adjustSoundVolume = 0.0f; - float _adjustMusicVolume = 0.0f; - uint8 _settingSoundVolume = 0xFF; - uint8 _settingMusicVolume = 0xFF; - - IAudioSource * _css1Sources[SOUND_MAXID] = { nullptr }; - IAudioSource * _musicSources[PATH_ID_END] = { nullptr }; - - Buffer _channelBuffer; - Buffer _convertBuffer; - Buffer _effectBuffer; - -public: - AudioMixer() - { - _nullSource = AudioSource::CreateNull(); - } - - ~AudioMixer() - { - Close(); - delete _nullSource; - } - - void Init(const char* device) override - { - Close(); - - SDL_AudioSpec want = { 0 }; - want.freq = 44100; - want.format = AUDIO_S16SYS; - want.channels = 2; - want.samples = 1024; - want.callback = [](void * arg, uint8 * dst, sint32 length) -> void - { - auto mixer = static_cast(arg); - mixer->GetNextAudioChunk(dst, (size_t)length); - }; - want.userdata = this; - - SDL_AudioSpec have; - _deviceId = SDL_OpenAudioDevice(device, 0, &want, &have, 0); - _format.format = have.format; - _format.channels = have.channels; - _format.freq = have.freq; - - LoadAllSounds(); - - SDL_PauseAudioDevice(_deviceId, 0); - } - - void Close() override - { - // Free channels - Lock(); - for (IAudioChannel * channel : _channels) - { - delete channel; - } - _channels.clear(); - Unlock(); - - SDL_CloseAudioDevice(_deviceId); - - // Free sources - for (size_t i = 0; i < Util::CountOf(_css1Sources); i++) - { - if (_css1Sources[i] != _nullSource) - { - SafeDelete(_css1Sources[i]); - } - } - for (size_t i = 0; i < Util::CountOf(_musicSources); i++) - { - if (_musicSources[i] != _nullSource) - { - SafeDelete(_musicSources[i]); - } - } - - // Free buffers - _channelBuffer.Free(); - _convertBuffer.Free(); - _effectBuffer.Free(); - } - - void Lock() override - { - SDL_LockAudioDevice(_deviceId); - } - - void Unlock() override - { - SDL_UnlockAudioDevice(_deviceId); - } - - IAudioChannel * Play(IAudioSource * source, sint32 loop, bool deleteondone, bool deletesourceondone) override - { - Lock(); - IAudioChannel * channel = AudioChannel::Create(); - if (channel != nullptr) - { - channel->Play(source, loop); - channel->SetDeleteOnDone(deleteondone); - channel->SetDeleteSourceOnDone(deletesourceondone); - _channels.push_back(channel); - } - Unlock(); - return channel; - } - - void Stop(IAudioChannel * channel) override - { - Lock(); - channel->SetStopping(true); - Unlock(); - } - - bool LoadMusic(size_t pathId) override - { - bool result = false; - if (pathId < Util::CountOf(_musicSources)) - { - IAudioSource * source = _musicSources[pathId]; - if (source == nullptr) - { - const utf8 * path = get_file_path((sint32)pathId); - source = AudioSource::CreateMemoryFromWAV(path, &_format); - if (source == nullptr) - { - source = _nullSource; - } - _musicSources[pathId] = source; - } - result = source != _nullSource; - } - return result; - } - - void SetVolume(float volume) override - { - _volume = volume; - } - - IAudioSource * GetSoundSource(sint32 id) override - { - return _css1Sources[id]; - } - - IAudioSource * GetMusicSource(sint32 id) override - { - return _musicSources[id]; - } - -private: - void LoadAllSounds() - { - const utf8 * css1Path = get_file_path(PATH_ID_CSS1); - for (size_t i = 0; i < Util::CountOf(_css1Sources); i++) - { - auto source = AudioSource::CreateMemoryFromCSS1(css1Path, i, &_format); - if (source == nullptr) - { - source = _nullSource; - } - _css1Sources[i] = source; - } - } - - void GetNextAudioChunk(uint8 * dst, size_t length) - { - UpdateAdjustedSound(); - - // Zero the output buffer - Memory::Set(dst, 0, length); - - // Mix channels onto output buffer - std::list::iterator it = _channels.begin(); - while (it != _channels.end()) - { - IAudioChannel * channel = *it; - - sint32 group = channel->GetGroup(); - if (group != MIXER_GROUP_SOUND || gConfigSound.sound_enabled) - { - MixChannel(channel, dst, length); - } - if ((channel->IsDone() && channel->DeleteOnDone()) || channel->IsStopping()) - { - delete channel; - it = _channels.erase(it); - } - else - { - it++; - } - } - } - - void UpdateAdjustedSound() - { - // Did the volume level get changed? Recalculate level in this case. - if (_settingSoundVolume != gConfigSound.sound_volume) - { - _settingSoundVolume = gConfigSound.sound_volume; - _adjustSoundVolume = powf(_settingSoundVolume / 100.f, 10.f / 6.f); - } - if (_settingMusicVolume != gConfigSound.ride_music_volume) - { - _settingMusicVolume = gConfigSound.ride_music_volume; - _adjustMusicVolume = powf(_settingMusicVolume / 100.f, 10.f / 6.f); - } - } - - void MixChannel(IAudioChannel * channel, uint8 * data, size_t length) - { - sint32 byteRate = _format.GetByteRate(); - sint32 numSamples = (sint32)(length / byteRate); - double rate = 1; - if (_format.format == AUDIO_S16SYS) - { - rate = channel->GetRate(); - } - - bool mustConvert = false; - SDL_AudioCVT cvt; - cvt.len_ratio = 1; - AudioFormat streamformat = channel->GetFormat(); - if (streamformat != _format) - { - if (SDL_BuildAudioCVT(&cvt, streamformat.format, streamformat.channels, streamformat.freq, _format.format, _format.channels, _format.freq) == -1) - { - // Unable to convert channel data - return; - } - mustConvert = true; - } - - // Read raw PCM from channel - sint32 readSamples = (sint32)(numSamples * rate); - size_t readLength = (size_t)(readSamples / cvt.len_ratio) * byteRate; - _channelBuffer.EnsureCapacity(readLength); - size_t bytesRead = channel->Read(_channelBuffer.GetData(), readLength); - - // Convert data to required format if necessary - void * buffer = nullptr; - size_t bufferLen = 0; - if (mustConvert) - { - if (Convert(&cvt, _channelBuffer.GetData(), bytesRead)) - { - buffer = cvt.buf; - bufferLen = cvt.len_cvt; - } - else - { - return; - } - } - else - { - buffer = _channelBuffer.GetData(); - bufferLen = bytesRead; - } - - // Apply effects - if (rate != 1) - { - sint32 srcSamples = (sint32)(bufferLen / byteRate); - sint32 dstSamples = numSamples; - bufferLen = ApplyResample(channel, buffer, srcSamples, dstSamples); - buffer = _effectBuffer.GetData(); - } - - // Apply panning and volume - ApplyPan(channel, buffer, bufferLen, byteRate); - sint32 mixVolume = ApplyVolume(channel, buffer, bufferLen); - - // Finally mix on to destination buffer - size_t dstLength = Math::Min(length, bufferLen); - SDL_MixAudioFormat(data, (const uint8 *)buffer, _format.format, (uint32)dstLength, mixVolume); - - channel->UpdateOldVolume(); - } - - /** - * Resample the given buffer into _effectBuffer. - * Assumes that srcBuffer is the same format as _format. - */ - size_t ApplyResample(IAudioChannel * channel, const void * srcBuffer, sint32 srcSamples, sint32 dstSamples) - { - sint32 byteRate = _format.GetByteRate(); - - // Create resampler - SpeexResamplerState * resampler = channel->GetResampler(); - if (resampler == nullptr) - { - resampler = speex_resampler_init(_format.channels, _format.freq, _format.freq, 0, 0); - channel->SetResampler(resampler); - } - speex_resampler_set_rate(resampler, srcSamples, dstSamples); - - // Ensure destination buffer is large enough - size_t effectBufferReqLen = dstSamples * byteRate; - _effectBuffer.EnsureCapacity(effectBufferReqLen); - - uint32 inLen = srcSamples; - uint32 outLen = dstSamples; - speex_resampler_process_interleaved_int( - resampler, - (const spx_int16_t *)srcBuffer, - &inLen, - (spx_int16_t *)_effectBuffer.GetData(), - &outLen); - - return outLen * byteRate; - } - - void ApplyPan(const IAudioChannel * channel, void * buffer, size_t len, size_t sampleSize) - { - if (channel->GetPan() != 0.5f && _format.channels == 2) - { - switch (_format.format) { - case AUDIO_S16SYS: - EffectPanS16(channel, (sint16 *)buffer, (sint32)(len / sampleSize)); - break; - case AUDIO_U8: - EffectPanU8(channel, (uint8 *)buffer, (sint32)(len / sampleSize)); - break; - } - } - } - - sint32 ApplyVolume(const IAudioChannel * channel, void * buffer, size_t len) - { - float volumeAdjust = _volume; - volumeAdjust *= (gConfigSound.master_volume / 100.0f); - switch (channel->GetGroup()) { - case MIXER_GROUP_SOUND: - volumeAdjust *= _adjustSoundVolume; - - // Cap sound volume on title screen so music is more audible - if (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) - { - volumeAdjust = Math::Min(volumeAdjust, 0.75f); - } - break; - case MIXER_GROUP_RIDE_MUSIC: - volumeAdjust *= _adjustMusicVolume; - break; - } - - sint32 startVolume = (sint32)(channel->GetOldVolume() * volumeAdjust); - sint32 endVolume = (sint32)(channel->GetVolume() * volumeAdjust); - if (channel->IsStopping()) - { - endVolume = 0; - } - - sint32 mixVolume = (sint32)(channel->GetVolume() * volumeAdjust); - if (startVolume != endVolume) - { - // Set to max since we are adjusting the volume ourselves - mixVolume = SDL_MIX_MAXVOLUME; - - // Fade between volume levels to smooth out sound and minimize clicks from sudden volume changes - sint32 fadeLength = (sint32)len / _format.BytesPerSample(); - switch (_format.format) { - case AUDIO_S16SYS: - EffectFadeS16((sint16 *)buffer, fadeLength, startVolume, endVolume); - break; - case AUDIO_U8: - EffectFadeU8((uint8 *)buffer, fadeLength, startVolume, endVolume); - break; - } - } - return mixVolume; - } - - static void EffectPanS16(const IAudioChannel * channel, sint16 * data, sint32 length) - { - const float dt = 1.0f / (length * 2); - float volumeL = channel->GetOldVolumeL(); - float volumeR = channel->GetOldVolumeR(); - const float d_left = dt * (channel->GetVolumeL() - channel->GetOldVolumeL()); - const float d_right = dt * (channel->GetVolumeR() - channel->GetOldVolumeR()); - - for (sint32 i = 0; i < length * 2; i += 2) - { - data[i] = (sint16)(data[i] * volumeL); - data[i + 1] = (sint16)(data[i + 1] * volumeR); - volumeL += d_left; - volumeR += d_right; - } - } - - static void EffectPanU8(const IAudioChannel * channel, uint8 * data, sint32 length) - { - float volumeL = channel->GetVolumeL(); - float volumeR = channel->GetVolumeR(); - float oldVolumeL = channel->GetOldVolumeL(); - float oldVolumeR = channel->GetOldVolumeR(); - - for (sint32 i = 0; i < length * 2; i += 2) - { - float t = (float)i / (length * 2); - data[i] = (uint8)(data[i] * ((1.0 - t) * oldVolumeL + t * volumeL)); - data[i + 1] = (uint8)(data[i + 1] * ((1.0 - t) * oldVolumeR + t * volumeR)); - } - } - - static void EffectFadeS16(sint16 * data, sint32 length, sint32 startvolume, sint32 endvolume) - { - float startvolume_f = (float)startvolume / SDL_MIX_MAXVOLUME; - float endvolume_f = (float)endvolume / SDL_MIX_MAXVOLUME; - for (sint32 i = 0; i < length; i++) - { - float t = (float)i / length; - data[i] = (sint16)(data[i] * ((1 - t) * startvolume_f + t * endvolume_f)); - } - } - - static void EffectFadeU8(uint8* data, sint32 length, sint32 startvolume, sint32 endvolume) - { - float startvolume_f = (float)startvolume / SDL_MIX_MAXVOLUME; - float endvolume_f = (float)endvolume / SDL_MIX_MAXVOLUME; - for (sint32 i = 0; i < length; i++) - { - float t = (float)i / length; - data[i] = (uint8)(data[i] * ((1 - t) * startvolume_f + t * endvolume_f)); - } - } - - bool Convert(SDL_AudioCVT * cvt, const void * src, size_t len) - { - // tofix: there seems to be an issue with converting audio using SDL_ConvertAudio in the callback vs preconverted, can cause pops and static depending on sample rate and channels - bool result = false; - if (len != 0 && cvt->len_mult != 0) - { - size_t reqConvertBufferCapacity = len * cvt->len_mult; - _convertBuffer.EnsureCapacity(reqConvertBufferCapacity); - Memory::Copy(_convertBuffer.GetData(), src, len); - - cvt->len = (sint32)len; - cvt->buf = (uint8 *)_convertBuffer.GetData(); - if (SDL_ConvertAudio(cvt) >= 0) - { - result = true; - } - } - return result; - } -}; + IAudioContext * audioContext = GetContext()->GetAudioContext(); + return audioContext->GetMixer(); +} void Mixer_Init(const char * device) { - if (!gOpenRCT2Headless) - { - gMixer = new AudioMixer(); - gMixer->Init(device); - } + IAudioContext * audioContext = GetContext()->GetAudioContext(); + audioContext->SetOutputDevice(device); } void * Mixer_Play_Effect(size_t id, sint32 loop, sint32 volume, float pan, double rate, sint32 deleteondone) { IAudioChannel * channel = nullptr; - if (!gOpenRCT2Headless && gConfigSound.sound_enabled) + if (gConfigSound.sound_enabled) { if (id >= SOUND_MAXID) { @@ -551,7 +55,7 @@ void * Mixer_Play_Effect(size_t id, sint32 loop, sint32 volume, float pan, doubl } else { - IAudioMixer * mixer = gMixer; + IAudioMixer * mixer = GetMixer(); mixer->Lock(); IAudioSource * source = mixer->GetSoundSource((sint32)id); channel = mixer->Play(source, loop, deleteondone != 0, false); @@ -569,131 +73,95 @@ void * Mixer_Play_Effect(size_t id, sint32 loop, sint32 volume, float pan, doubl void Mixer_Stop_Channel(void * channel) { - if (!gOpenRCT2Headless) - { - gMixer->Stop(static_cast(channel)); - } + GetMixer()->Stop(static_cast(channel)); } void Mixer_Channel_Volume(void * channel, sint32 volume) { - if (!gOpenRCT2Headless) - { - gMixer->Lock(); - static_cast(channel)->SetVolume(volume); - gMixer->Unlock(); - } + IAudioMixer * audioMixer = GetMixer(); + audioMixer->Lock(); + static_cast(channel)->SetVolume(volume); + audioMixer->Unlock(); } void Mixer_Channel_Pan(void * channel, float pan) { - if (!gOpenRCT2Headless) - { - gMixer->Lock(); - static_cast(channel)->SetPan(pan); - gMixer->Unlock(); - } + IAudioMixer * audioMixer = GetMixer(); + audioMixer->Lock(); + static_cast(channel)->SetPan(pan); + audioMixer->Unlock(); } void Mixer_Channel_Rate(void* channel, double rate) { - if (!gOpenRCT2Headless) - { - gMixer->Lock(); - static_cast(channel)->SetRate(rate); - gMixer->Unlock(); - } + IAudioMixer * audioMixer = GetMixer(); + audioMixer->Lock(); + static_cast(channel)->SetRate(rate); + audioMixer->Unlock(); } sint32 Mixer_Channel_IsPlaying(void * channel) { - bool isPlaying = false; - if (!gOpenRCT2Headless) - { - isPlaying = static_cast(channel)->IsPlaying(); - } - return isPlaying; + return static_cast(channel)->IsPlaying(); } uint64 Mixer_Channel_GetOffset(void * channel) { - uint64 offset = 0; - if (!gOpenRCT2Headless) - { - offset = static_cast(channel)->GetOffset(); - } - return offset; + return static_cast(channel)->GetOffset(); } sint32 Mixer_Channel_SetOffset(void * channel, uint64 offset) { - sint32 result = 0; - if (!gOpenRCT2Headless) - { - result = static_cast(channel)->SetOffset(offset); - } - return result; + return static_cast(channel)->SetOffset(offset); } void Mixer_Channel_SetGroup(void * channel, sint32 group) { - if (!gOpenRCT2Headless) - { - static_cast(channel)->SetGroup(group); - } + static_cast(channel)->SetGroup(group); } void * Mixer_Play_Music(sint32 pathId, sint32 loop, sint32 streaming) { IAudioChannel * channel = nullptr; - if (!gOpenRCT2Headless) + IAudioMixer * mixer = GetMixer(); + if (streaming) { - IAudioMixer * mixer = gMixer; - if (streaming) - { - const utf8 * filename = get_file_path(pathId); + const utf8 * path = get_file_path(pathId); - SDL_RWops* rw = SDL_RWFromFile(filename, "rb"); - if (rw != nullptr) + IAudioContext * audioContext = GetContext()->GetAudioContext(); + IAudioSource * source = audioContext->CreateStreamFromWAV(path); + if (source != nullptr) + { + channel = mixer->Play(source, loop, false, true); + if (channel == nullptr) { - auto source = AudioSource::CreateStreamFromWAV(rw); - if (source != nullptr) - { - channel = mixer->Play(source, loop, false, true); - if (channel == nullptr) - { - delete source; - } - } + delete source; } } - else + } + else + { + if (mixer->LoadMusic(pathId)) { - if (mixer->LoadMusic(pathId)) - { - IAudioSource * source = mixer->GetMusicSource(pathId); - channel = mixer->Play(source, MIXER_LOOP_INFINITE, false, false); - } - } - if (channel != nullptr) - { - channel->SetGroup(MIXER_GROUP_RIDE_MUSIC); + IAudioSource * source = mixer->GetMusicSource(pathId); + channel = mixer->Play(source, MIXER_LOOP_INFINITE, false, false); } } + if (channel != nullptr) + { + channel->SetGroup(MIXER_GROUP_RIDE_MUSIC); + } return channel; } void Mixer_SetVolume(float volume) { - if (!gOpenRCT2Headless) - { - gMixer->SetVolume(volume); - } + GetMixer()->SetVolume(volume); } sint32 DStoMixerVolume(sint32 volume) { - return (sint32)(SDL_MIX_MAXVOLUME * (SDL_pow(10, (float)volume / 2000))); + return (sint32)(SDL_MIX_MAXVOLUME * (std::pow(10.0f, (float)volume / 2000))); } float DStoMixerPan(sint32 pan) @@ -705,4 +173,3 @@ double DStoMixerRate(sint32 frequency) { return (double)frequency / 22050; } - diff --git a/src/openrct2/audio/AudioMixer.h b/src/openrct2/audio/AudioMixer.h index 085e911795..51c65d3db3 100644 --- a/src/openrct2/audio/AudioMixer.h +++ b/src/openrct2/audio/AudioMixer.h @@ -14,8 +14,7 @@ *****************************************************************************/ #pragma endregion -#ifndef _MIXER_H_ -#define _MIXER_H_ +#pragma once #include "../common.h" @@ -31,59 +30,61 @@ enum MIXER_GROUP #ifdef __cplusplus -interface IAudioSource; -interface IAudioChannel; - -/** - * Provides an audio stream by mixing multiple audio channels together. - */ -interface IAudioMixer +namespace OpenRCT2 { namespace Audio { - virtual ~IAudioMixer() = default; + interface IAudioSource; + interface IAudioChannel; - virtual void Init(const char * device) abstract; - virtual void Close() abstract; - virtual void Lock() abstract; - virtual void Unlock() abstract; - virtual IAudioChannel * Play(IAudioSource * source, sint32 loop, bool deleteondone, bool deletesourceondone) abstract; - virtual void Stop(IAudioChannel * channel) abstract; - virtual bool LoadMusic(size_t pathid) abstract; - virtual void SetVolume(float volume) abstract; + /** + * Provides an audio stream by mixing multiple audio channels together. + */ + interface IAudioMixer + { + virtual ~IAudioMixer() = default; - virtual IAudioSource * GetSoundSource(sint32 id) abstract; - virtual IAudioSource * GetMusicSource(sint32 id) abstract; -}; + virtual void Init(const char * device) abstract; + virtual void Close() abstract; + virtual void Lock() abstract; + virtual void Unlock() abstract; + virtual IAudioChannel * Play(IAudioSource * source, sint32 loop, bool deleteondone, bool deletesourceondone) abstract; + virtual void Stop(IAudioChannel * channel) abstract; + virtual bool LoadMusic(size_t pathid) abstract; + virtual void SetVolume(float volume) abstract; + virtual IAudioSource * GetSoundSource(sint32 id) abstract; + virtual IAudioSource * GetMusicSource(sint32 id) abstract; + }; +} } + +#endif + +#ifdef __cplusplus extern "C" { #endif + #ifndef DSBPAN_LEFT + #define DSBPAN_LEFT -10000 + #endif + #ifndef DSBPAN_RIGHT + #define DSBPAN_RIGHT 10000 + #endif -#ifndef DSBPAN_LEFT -#define DSBPAN_LEFT -10000 -#endif -#ifndef DSBPAN_RIGHT -#define DSBPAN_RIGHT 10000 -#endif - -void Mixer_Init(const char* device); -void* Mixer_Play_Effect(size_t id, sint32 loop, sint32 volume, float pan, double rate, sint32 deleteondone); -void Mixer_Stop_Channel(void* channel); -void Mixer_Channel_Volume(void* channel, sint32 volume); -void Mixer_Channel_Pan(void* channel, float pan); -void Mixer_Channel_Rate(void* channel, double rate); -sint32 Mixer_Channel_IsPlaying(void* channel); -uint64 Mixer_Channel_GetOffset(void* channel); -sint32 Mixer_Channel_SetOffset(void* channel, uint64 offset); -void Mixer_Channel_SetGroup(void* channel, sint32 group); -void* Mixer_Play_Music(sint32 pathId, sint32 loop, sint32 streaming); -void Mixer_SetVolume(float volume); - -sint32 DStoMixerVolume(sint32 volume); -float DStoMixerPan(sint32 pan); -double DStoMixerRate(sint32 frequency); + void Mixer_Init(const char * device); + void* Mixer_Play_Effect(size_t id, sint32 loop, sint32 volume, float pan, double rate, sint32 deleteondone); + void Mixer_Stop_Channel(void* channel); + void Mixer_Channel_Volume(void* channel, sint32 volume); + void Mixer_Channel_Pan(void* channel, float pan); + void Mixer_Channel_Rate(void* channel, double rate); + sint32 Mixer_Channel_IsPlaying(void* channel); + uint64 Mixer_Channel_GetOffset(void* channel); + sint32 Mixer_Channel_SetOffset(void* channel, uint64 offset); + void Mixer_Channel_SetGroup(void* channel, sint32 group); + void* Mixer_Play_Music(sint32 pathId, sint32 loop, sint32 streaming); + void Mixer_SetVolume(float volume); + sint32 DStoMixerVolume(sint32 volume); + float DStoMixerPan(sint32 pan); + double DStoMixerRate(sint32 frequency); #ifdef __cplusplus } #endif - -#endif diff --git a/src/openrct2/audio/AudioSource.h b/src/openrct2/audio/AudioSource.h index 879424507f..3cdcb193df 100644 --- a/src/openrct2/audio/AudioSource.h +++ b/src/openrct2/audio/AudioSource.h @@ -17,25 +17,24 @@ #pragma once #include "../common.h" -#include "AudioFormat.h" #include "AudioMixer.h" -/** - * Represents a readable source of audio PCM data. - */ -interface IAudioSource +namespace OpenRCT2 { namespace Audio { - virtual ~IAudioSource() = default; + /** + * Represents a readable source of audio PCM data. + */ + interface IAudioSource + { + virtual ~IAudioSource() = default; - virtual uint64 GetLength() abstract; - virtual AudioFormat GetFormat() abstract; - virtual size_t Read(void * dst, uint64 offset, size_t len) abstract; -}; + virtual uint64 GetLength() abstract; + // virtual AudioFormat GetFormat() abstract; + virtual size_t Read(void * dst, uint64 offset, size_t len) abstract; + }; -namespace AudioSource -{ - IAudioSource * CreateNull(); - IAudioSource * CreateMemoryFromCSS1(const utf8 * path, size_t index, const AudioFormat * targetFormat = nullptr); - IAudioSource * CreateMemoryFromWAV(const utf8 * path, const AudioFormat * targetFormat = nullptr); - IAudioSource * CreateStreamFromWAV(SDL_RWops * rw); -} + namespace AudioSource + { + IAudioSource * CreateNull(); + } +} } diff --git a/src/openrct2/audio/FileAudioSource.cpp b/src/openrct2/audio/FileAudioSource.cpp deleted file mode 100644 index 5441fe063f..0000000000 --- a/src/openrct2/audio/FileAudioSource.cpp +++ /dev/null @@ -1,204 +0,0 @@ -#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers -/***************************************************************************** - * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. - * - * OpenRCT2 is the work of many authors, a full list can be found in contributors.md - * For more information, visit https://github.com/OpenRCT2/OpenRCT2 - * - * OpenRCT2 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. - * - * A full copy of the GNU General Public License can be found in licence.txt - *****************************************************************************/ -#pragma endregion - -#include "../core/Math.hpp" -#include "AudioSource.h" - -#pragma pack(push, 1) - struct WaveFormat - { - uint16 encoding; - uint16 channels; - uint32 frequency; - uint32 byterate; - uint16 blockalign; - uint16 bitspersample; - }; - assert_struct_size(WaveFormat, 16); -#pragma pack(pop) - -/** - * An audio source where raw PCM data is streamed directly from - * a file. - */ -class FileAudioSource final : public IAudioSource -{ -private: - AudioFormat _format = { 0 }; - SDL_RWops * _rw = nullptr; - uint64 _dataBegin = 0; - uint64 _dataLength = 0; - -public: - ~FileAudioSource() - { - Unload(); - } - - uint64 GetLength() override - { - return _dataLength; - } - - AudioFormat GetFormat() override - { - return _format; - } - - size_t Read(void * dst, uint64 offset, size_t len) override - { - size_t bytesRead = 0; - sint64 currentPosition = SDL_RWtell(_rw); - if (currentPosition != -1) - { - size_t bytesToRead = (size_t)Math::Min(len, _dataLength - offset); - sint64 dataOffset = _dataBegin + offset; - if (currentPosition != dataOffset) - { - sint64 newPosition = SDL_RWseek(_rw, dataOffset, SEEK_SET); - if (newPosition == -1) - { - return 0; - } - } - bytesRead = SDL_RWread(_rw, dst, 1, bytesToRead); - } - return bytesRead; - } - - bool LoadWAV(SDL_RWops * rw) - { - const uint32 DATA = 0x61746164; - const uint32 FMT = 0x20746D66; - const uint32 RIFF = 0x46464952; - const uint32 WAVE = 0x45564157; - const uint16 pcmformat = 0x0001; - - Unload(); - - if (rw == nullptr) - { - return false; - } - _rw = rw; - - uint32 chunkId = SDL_ReadLE32(rw); - if (chunkId != RIFF) - { - log_verbose("Not a WAV file"); - return false; - } - - // Read and discard chunk size - SDL_ReadLE32(rw); - uint32 chunkFormat = SDL_ReadLE32(rw); - if (chunkFormat != WAVE) - { - log_verbose("Not in WAVE format"); - return false; - } - - uint32 fmtChunkSize = FindChunk(rw, FMT); - if (!fmtChunkSize) - { - log_verbose("Could not find FMT chunk"); - return false; - } - - uint64 chunkStart = SDL_RWtell(rw); - - WaveFormat waveFormat; - SDL_RWread(rw, &waveFormat, sizeof(waveFormat), 1); - SDL_RWseek(rw, chunkStart + fmtChunkSize, RW_SEEK_SET); - if (waveFormat.encoding != pcmformat) { - log_verbose("Not in proper format"); - return false; - } - - _format.freq = waveFormat.frequency; - switch (waveFormat.bitspersample) { - case 8: - _format.format = AUDIO_U8; - break; - case 16: - _format.format = AUDIO_S16LSB; - break; - default: - log_verbose("Invalid bits per sample"); - return false; - break; - } - _format.channels = waveFormat.channels; - - uint32 dataChunkSize = FindChunk(rw, DATA); - if (dataChunkSize == 0) - { - log_verbose("Could not find DATA chunk"); - return false; - } - - _dataLength = dataChunkSize; - _dataBegin = SDL_RWtell(rw); - return true; - } - -private: - uint32 FindChunk(SDL_RWops * rw, uint32 wantedId) - { - uint32 subchunkId = SDL_ReadLE32(rw); - uint32 subchunkSize = SDL_ReadLE32(rw); - if (subchunkId == wantedId) - { - return subchunkSize; - } - const uint32 FACT = 0x74636166; - const uint32 LIST = 0x5453494c; - const uint32 BEXT = 0x74786562; - const uint32 JUNK = 0x4B4E554A; - while (subchunkId == FACT || subchunkId == LIST || subchunkId == BEXT || subchunkId == JUNK) - { - SDL_RWseek(rw, subchunkSize, RW_SEEK_CUR); - subchunkId = SDL_ReadLE32(rw); - subchunkSize = SDL_ReadLE32(rw); - if (subchunkId == wantedId) - { - return subchunkSize; - } - } - return 0; - } - - void Unload() - { - if (_rw != nullptr) - { - SDL_RWclose(_rw); - _rw = nullptr; - } - _dataBegin = 0; - _dataLength = 0; - } -}; - -IAudioSource * AudioSource::CreateStreamFromWAV(SDL_RWops * rw) -{ - auto source = new FileAudioSource(); - if (!source->LoadWAV(rw)) - { - SafeDelete(source); - } - return source; -} diff --git a/src/openrct2/audio/MemoryAudioSource.cpp b/src/openrct2/audio/MemoryAudioSource.cpp deleted file mode 100644 index 18a2099e18..0000000000 --- a/src/openrct2/audio/MemoryAudioSource.cpp +++ /dev/null @@ -1,245 +0,0 @@ -#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers -/***************************************************************************** - * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. - * - * OpenRCT2 is the work of many authors, a full list can be found in contributors.md - * For more information, visit https://github.com/OpenRCT2/OpenRCT2 - * - * OpenRCT2 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. - * - * A full copy of the GNU General Public License can be found in licence.txt - *****************************************************************************/ -#pragma endregion - -#include "../core/Math.hpp" -#include "../core/Memory.hpp" -#include "AudioMixer.h" -#include "AudioSource.h" - -#pragma pack(push, 1) - struct WaveFormatEx - { - uint16 encoding; - uint16 channels; - uint32 frequency; - uint32 byterate; - uint16 blockalign; - uint16 bitspersample; - uint16 extrasize; - }; - assert_struct_size(WaveFormatEx, 18); -#pragma pack(pop) - -/** - * An audio source where raw PCM data is initially loaded into RAM from - * a file and then streamed. - */ -class MemoryAudioSource final : public IAudioSource -{ -private: - AudioFormat _format = { 0 }; - uint8 * _data = nullptr; - size_t _length = 0; - bool _isSDLWav = false; - -public: - ~MemoryAudioSource() - { - Unload(); - } - - uint64 GetLength() override - { - return _length; - } - - AudioFormat GetFormat() override - { - return _format; - } - - size_t Read(void * dst, uint64 offset, size_t len) override - { - size_t bytesToRead = 0; - if (offset < _length) - { - bytesToRead = (size_t)Math::Min(len, _length - offset); - Memory::Copy(dst, _data + offset, bytesToRead); - } - return bytesToRead; - } - - bool LoadWAV(const utf8 * path) - { - log_verbose("MemoryAudioSource::LoadWAV(%s)", path); - - Unload(); - - bool result = false; - SDL_RWops * rw = SDL_RWFromFile(path, "rb"); - if (rw != nullptr) - { - SDL_AudioSpec audiospec = { 0 }; - uint32 audioLen; - SDL_AudioSpec * spec = SDL_LoadWAV_RW(rw, false, &audiospec, &_data, &audioLen); - if (spec != nullptr) - { - _format.freq = spec->freq; - _format.format = spec->format; - _format.channels = spec->channels; - _length = audioLen; - _isSDLWav = true; - result = true; - } - else - { - log_verbose("Error loading %s, unsupported WAV format", path); - } - SDL_RWclose(rw); - } - else - { - log_verbose("Error loading %s", path); - } - return result; - } - - bool LoadCSS1(const utf8 * path, size_t index) - { - log_verbose("MemoryAudioSource::LoadCSS1(%s, %d)", path, index); - - Unload(); - - bool result = false; - SDL_RWops * rw = SDL_RWFromFile(path, "rb"); - if (rw != nullptr) - { - uint32 numSounds; - SDL_RWread(rw, &numSounds, sizeof(numSounds), 1); - if (index < numSounds) - { - SDL_RWseek(rw, index * 4, RW_SEEK_CUR); - - uint32 pcmOffset; - SDL_RWread(rw, &pcmOffset, sizeof(pcmOffset), 1); - SDL_RWseek(rw, pcmOffset, RW_SEEK_SET); - - uint32 pcmSize; - SDL_RWread(rw, &pcmSize, sizeof(pcmSize), 1); - _length = pcmSize; - - WaveFormatEx waveFormat; - SDL_RWread(rw, &waveFormat, sizeof(waveFormat), 1); - _format.freq = waveFormat.frequency; - _format.format = AUDIO_S16LSB; - _format.channels = waveFormat.channels; - - _data = new (std::nothrow) uint8[_length]; - if (_data != nullptr) - { - SDL_RWread(rw, _data, _length, 1); - result = true; - } - else - { - log_verbose("Unable to allocate data"); - } - } - SDL_RWclose(rw); - } - else - { - log_verbose("Unable to load %s", path); - } - return result; - } - - bool Convert(const AudioFormat * format) - { - if (*format != _format) - { - SDL_AudioCVT cvt; - if (SDL_BuildAudioCVT(&cvt, _format.format, _format.channels, _format.freq, format->format, format->channels, format->freq) >= 0) - { - cvt.len = (sint32)_length; - cvt.buf = new uint8[cvt.len * cvt.len_mult]; - Memory::Copy(cvt.buf, _data, _length); - if (SDL_ConvertAudio(&cvt) >= 0) - { - Unload(); - _data = cvt.buf; - _length = cvt.len_cvt; - _format = *format; - return true; - } - else - { - delete[] cvt.buf; - } - } - } - return false; - } - -private: - void Unload() - { - if (_data != nullptr) - { - if (_isSDLWav) - { - SDL_FreeWAV(_data); - } - else - { - delete[] _data; - } - _data = nullptr; - } - _isSDLWav = false; - _length = 0; - } -}; - -IAudioSource * AudioSource::CreateMemoryFromCSS1(const utf8 * path, size_t index, const AudioFormat * targetFormat) -{ - auto source = new MemoryAudioSource(); - if (source->LoadCSS1(path, index)) - { - if (targetFormat != nullptr) - { - if (!source->Convert(targetFormat)) - { - SafeDelete(source); - } - } - } - else - { - SafeDelete(source); - } - return source; -} - -IAudioSource * AudioSource::CreateMemoryFromWAV(const utf8 * path, const AudioFormat * targetFormat) -{ - auto source = new MemoryAudioSource(); - if (source->LoadWAV(path)) - { - if (targetFormat != nullptr) - { - if (!source->Convert(targetFormat)) - { - SafeDelete(source); - } - } - } - else - { - SafeDelete(source); - } - return source; -} diff --git a/src/openrct2/audio/NullAudioSource.cpp b/src/openrct2/audio/NullAudioSource.cpp index 8edd5adbb4..92112459d2 100644 --- a/src/openrct2/audio/NullAudioSource.cpp +++ b/src/openrct2/audio/NullAudioSource.cpp @@ -16,29 +16,27 @@ #include "AudioSource.h" -/** - * An audio source representing silence. - */ -class NullAudioSource : public IAudioSource +namespace OpenRCT2 { namespace Audio { -public: - uint64 GetLength() override + /** + * An audio source representing silence. + */ + class NullAudioSource : public IAudioSource { - return 0; - } + public: + uint64 GetLength() override + { + return 0; + } - AudioFormat GetFormat() override + size_t Read(void * dst, uint64 offset, size_t len) override + { + return 0; + } + }; + + IAudioSource * AudioSource::CreateNull() { - return { 0 }; + return new NullAudioSource(); } - - size_t Read(void * dst, uint64 offset, size_t len) override - { - return 0; - } -}; - -IAudioSource * AudioSource::CreateNull() -{ - return new NullAudioSource(); -} +} } diff --git a/src/openrct2/audio/audio.cpp b/src/openrct2/audio/audio.cpp index 1717c6fc54..8b034aab63 100644 --- a/src/openrct2/audio/audio.cpp +++ b/src/openrct2/audio/audio.cpp @@ -318,15 +318,10 @@ void audio_stop_all_music_and_sounds() audio_stop_title_music(); audio_stop_vehicle_sounds(); audio_stop_ride_music(); - audio_stop_crowd_sound(); + peep_stop_crowd_noise(); audio_stop_rain_sound(); } -void audio_stop_crowd_sound() -{ - audio_stop_channel(&gCrowdSoundChannel); -} - void audio_stop_title_music() { audio_stop_channel(&gTitleMusicChannel); @@ -393,7 +388,7 @@ void audio_init_ride_sounds(sint32 device) void audio_close() { - audio_stop_crowd_sound(); + peep_stop_crowd_noise(); audio_stop_title_music(); audio_stop_ride_music(); audio_stop_rain_sound(); @@ -414,7 +409,7 @@ void audio_pause_sounds() gGameSoundsOff = true; audio_stop_vehicle_sounds(); audio_stop_ride_music(); - audio_stop_crowd_sound(); + peep_stop_crowd_noise(); audio_stop_rain_sound(); } diff --git a/src/openrct2/audio/audio.h b/src/openrct2/audio/audio.h index 86e3c9fcd1..0e96de0265 100644 --- a/src/openrct2/audio/audio.h +++ b/src/openrct2/audio/audio.h @@ -14,12 +14,16 @@ *****************************************************************************/ #pragma endregion -#ifndef _AUDIO_H_ -#define _AUDIO_H_ +#pragma once #include "../common.h" #include "../world/sprite.h" +#ifdef __cplusplus +extern "C" +{ +#endif + #define AUDIO_DEVICE_NAME_SIZE 256 #define AUDIO_MAX_RIDE_MUSIC 2 #define AUDIO_MAX_VEHICLE_SOUNDS 14 @@ -163,7 +167,6 @@ typedef enum RCT2_SOUND { extern audio_device *gAudioDevices; extern sint32 gAudioDeviceCount; extern sint32 gAudioCurrentDevice; -extern void *gCrowdSoundChannel; extern bool gGameSoundsOff; extern void *gRainSoundChannel; extern rct_ride_music gRideMusicList[AUDIO_MAX_RIDE_MUSIC]; @@ -247,11 +250,6 @@ void audio_quit(); */ void audio_start_title_music(); /** -* Stops the crowd sound effect from playing. -* rct2: 0x006BD07F -*/ -void audio_stop_crowd_sound(); -/** * Stops the rain sound effect from playing. */ void audio_stop_rain_sound(); @@ -283,4 +281,6 @@ void audio_unpause_sounds(); void audio_stop_all_music_and_sounds(); +#ifdef __cplusplus +} #endif diff --git a/src/openrct2/peep/peep.c b/src/openrct2/peep/peep.c index a92c51fe84..f4e9bc39a0 100644 --- a/src/openrct2/peep/peep.c +++ b/src/openrct2/peep/peep.c @@ -118,6 +118,8 @@ enum { F1EE18_RIDE_ENTRANCE = 1 << 3, }; +static void * _crowdSoundChannel = NULL; + static void sub_68F41A(rct_peep *peep, sint32 index); static void peep_update(rct_peep *peep); static sint32 peep_has_empty_container(rct_peep* peep); @@ -6888,6 +6890,14 @@ void peep_problem_warnings_update() } } +void peep_stop_crowd_noise() +{ + if (_crowdSoundChannel != NULL) { + Mixer_Stop_Channel(_crowdSoundChannel); + _crowdSoundChannel = NULL; + } +} + /** * * rct2: 0x006BD18A @@ -6938,9 +6948,9 @@ void peep_update_crowd_noise() visiblePeeps = (visiblePeeps / 2) - 6; if (visiblePeeps < 0) { // Mute crowd noise - if (gCrowdSoundChannel) { - Mixer_Stop_Channel(gCrowdSoundChannel); - gCrowdSoundChannel = 0; + if (_crowdSoundChannel != NULL) { + Mixer_Stop_Channel(_crowdSoundChannel); + _crowdSoundChannel = NULL; } } else { sint32 volume; @@ -6952,14 +6962,14 @@ void peep_update_crowd_noise() volume = (((207360000 - volume) >> viewport->zoom) - 207360000) / 65536 - 150; // Load and play crowd noise if needed and set volume - if (!gCrowdSoundChannel) { - gCrowdSoundChannel = Mixer_Play_Music(PATH_ID_CSS2, MIXER_LOOP_INFINITE, false); - if (gCrowdSoundChannel) { - Mixer_Channel_SetGroup(gCrowdSoundChannel, MIXER_GROUP_SOUND); + if (_crowdSoundChannel == NULL) { + _crowdSoundChannel = Mixer_Play_Music(PATH_ID_CSS2, MIXER_LOOP_INFINITE, false); + if (_crowdSoundChannel != NULL) { + Mixer_Channel_SetGroup(_crowdSoundChannel, MIXER_GROUP_SOUND); } } - if (gCrowdSoundChannel) { - Mixer_Channel_Volume(gCrowdSoundChannel, DStoMixerVolume(volume)); + if (_crowdSoundChannel != NULL) { + Mixer_Channel_Volume(_crowdSoundChannel, DStoMixerVolume(volume)); } } } diff --git a/src/openrct2/peep/peep.h b/src/openrct2/peep/peep.h index 44eb172549..280ec6906f 100644 --- a/src/openrct2/peep/peep.h +++ b/src/openrct2/peep/peep.h @@ -717,6 +717,7 @@ sint32 peep_get_staff_count(); sint32 peep_can_be_picked_up(rct_peep* peep); void peep_update_all(); void peep_problem_warnings_update(); +void peep_stop_crowd_noise(); void peep_update_crowd_noise(); void peep_update_days_in_queue(); void peep_applause(); diff --git a/src/openrct2/platform/shared.c b/src/openrct2/platform/shared.c index 62ab7fcfb4..00cf819890 100644 --- a/src/openrct2/platform/shared.c +++ b/src/openrct2/platform/shared.c @@ -17,7 +17,6 @@ #include #include #include "../audio/audio.h" -#include "../audio/AudioMixer.h" #include "../config/Config.h" #include "../Context.h" #include "../drawing/drawing.h"