1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-21 05:53:02 +01:00

Move audio code to UI project

This commit is contained in:
Ted John
2017-03-28 19:58:15 +01:00
committed by Gymnasiast
parent 175606ae52
commit d206d181f2
27 changed files with 1647 additions and 1536 deletions

View File

@@ -20,12 +20,14 @@
#include <stdlib.h>
#include <windows.h>
#include <shellapi.h>
#include <openrct2/audio/AudioContext.h>
#include <openrct2/Context.h>
#include <openrct2/OpenRCT2.h>
#include <openrct2/ui/UiContext.h>
#include <openrct2-ui/UiContext.h>
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;

View File

@@ -602,7 +602,7 @@ private:
}
};
IUiContext * OpenRCT2::Ui::CreateContext()
IUiContext * OpenRCT2::Ui::CreateUiContext()
{
auto platformUiContext = std::unique_ptr<IPlatformUiContext>(CreatePlatformUiContext());
return new UiContext(platformUiContext.get());

View File

@@ -35,7 +35,7 @@ namespace OpenRCT2
virtual bool IsSteamOverlayAttached() abstract;
};
IUiContext * CreateContext();
IUiContext * CreateUiContext();
IPlatformUiContext * CreatePlatformUiContext();
}
}

View File

@@ -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 <openrct2/common.h>
#include <cmath>
#include <SDL.h>
#include <speex/speex_resampler.h>
#include <openrct2/core/Math.hpp>
#include <openrct2/audio/AudioChannel.h>
#include <openrct2/audio/AudioSource.h>
#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();
}
} }

View File

@@ -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; }

View File

@@ -0,0 +1,44 @@
#pragma once
#include <string>
#include <openrct2/common.h>
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);
}
} }

View File

@@ -16,38 +16,41 @@
#pragma once
#include "../common.h"
#include <openrct2/common.h>
#include <SDL.h>
/**
* 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);
}
} }

View File

@@ -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 <openrct2/common.h>
#include <SDL.h>
#include <speex/speex_resampler.h>
#include <list>
#include <openrct2/core/Guard.hpp>
#include <openrct2/core/Math.hpp>
#include <openrct2/core/Memory.hpp>
#include <openrct2/core/Util.hpp>
#include <openrct2/audio/audio.h>
#include <openrct2/audio/AudioChannel.h>
#include <openrct2/audio/AudioMixer.h>
#include <openrct2/audio/AudioSource.h>
#include "AudioContext.h"
#include "AudioFormat.h"
extern "C"
{
#include <openrct2/config/Config.h>
#include <openrct2/localisation/localisation.h>
#include <openrct2/OpenRCT2.h>
#include <openrct2/platform/platform.h>
#include <openrct2/rct2.h>
}
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<IAudioChannel *> _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<AudioMixer *>(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<IAudioChannel *>::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;
}
};
} }

View File

@@ -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 <openrct2/common.h>
#include <SDL.h>
#include <openrct2/core/Math.hpp>
#include <openrct2/audio/AudioSource.h>
#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<uint64>(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;
}
} }

View File

@@ -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 <openrct2/common.h>
#include <SDL.h>
#include <openrct2/core/Math.hpp>
#include <openrct2/core/Memory.hpp>
#include <openrct2/audio/AudioMixer.h>
#include <openrct2/audio/AudioSource.h>
#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<uint64>(len, _length - offset);
Memory::Copy<void>(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;
}
} }

View File

@@ -22,7 +22,11 @@
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="audio\AudioChannel.cpp" />
<ClCompile Include="audio\AudioContext.cpp" />
<ClCompile Include="audio\AudioMixer.cpp" />
<ClCompile Include="audio\FileAudioSource.cpp" />
<ClCompile Include="audio\MemoryAudioSource.cpp" />
<ClCompile Include="CursorData.cpp" />
<ClCompile Include="CursorRepository.cpp" />
<ClCompile Include="drawing\engines\opengl\CopyFramebufferShader.cpp" />
@@ -42,6 +46,8 @@
<ClCompile Include="UiContext.Win32.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="audio\AudioContext.h" />
<ClInclude Include="audio\AudioFormat.h" />
<ClInclude Include="CursorRepository.h" />
<ClInclude Include="drawing\engines\DrawingEngines.h" />
<ClInclude Include="drawing\engines\opengl\CopyFramebufferShader.h" />

View File

@@ -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()

View File

@@ -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();
}

View File

@@ -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 <cmath>
#include <speex/speex_resampler.h>
#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();
}

View File

@@ -16,70 +16,70 @@
#pragma once
#include <speex/speex_resampler.h>
#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();
}
} }

View File

@@ -16,6 +16,7 @@
#pragma once
#include <string>
#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();
}
}

View File

@@ -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 <list>
#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<IAudioChannel *> _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<AudioMixer *>(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<IAudioChannel *>::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<IAudioChannel*>(channel));
}
GetMixer()->Stop(static_cast<IAudioChannel*>(channel));
}
void Mixer_Channel_Volume(void * channel, sint32 volume)
{
if (!gOpenRCT2Headless)
{
gMixer->Lock();
static_cast<IAudioChannel*>(channel)->SetVolume(volume);
gMixer->Unlock();
}
IAudioMixer * audioMixer = GetMixer();
audioMixer->Lock();
static_cast<IAudioChannel*>(channel)->SetVolume(volume);
audioMixer->Unlock();
}
void Mixer_Channel_Pan(void * channel, float pan)
{
if (!gOpenRCT2Headless)
{
gMixer->Lock();
static_cast<IAudioChannel*>(channel)->SetPan(pan);
gMixer->Unlock();
}
IAudioMixer * audioMixer = GetMixer();
audioMixer->Lock();
static_cast<IAudioChannel*>(channel)->SetPan(pan);
audioMixer->Unlock();
}
void Mixer_Channel_Rate(void* channel, double rate)
{
if (!gOpenRCT2Headless)
{
gMixer->Lock();
static_cast<IAudioChannel*>(channel)->SetRate(rate);
gMixer->Unlock();
}
IAudioMixer * audioMixer = GetMixer();
audioMixer->Lock();
static_cast<IAudioChannel*>(channel)->SetRate(rate);
audioMixer->Unlock();
}
sint32 Mixer_Channel_IsPlaying(void * channel)
{
bool isPlaying = false;
if (!gOpenRCT2Headless)
{
isPlaying = static_cast<IAudioChannel*>(channel)->IsPlaying();
}
return isPlaying;
return static_cast<IAudioChannel*>(channel)->IsPlaying();
}
uint64 Mixer_Channel_GetOffset(void * channel)
{
uint64 offset = 0;
if (!gOpenRCT2Headless)
{
offset = static_cast<IAudioChannel*>(channel)->GetOffset();
}
return offset;
return static_cast<IAudioChannel*>(channel)->GetOffset();
}
sint32 Mixer_Channel_SetOffset(void * channel, uint64 offset)
{
sint32 result = 0;
if (!gOpenRCT2Headless)
{
result = static_cast<IAudioChannel*>(channel)->SetOffset(offset);
}
return result;
return static_cast<IAudioChannel*>(channel)->SetOffset(offset);
}
void Mixer_Channel_SetGroup(void * channel, sint32 group)
{
if (!gOpenRCT2Headless)
{
static_cast<IAudioChannel *>(channel)->SetGroup(group);
}
static_cast<IAudioChannel *>(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;
}

View File

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

View File

@@ -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();
}
} }

View File

@@ -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<uint64>(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;
}

View File

@@ -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<uint64>(len, _length - offset);
Memory::Copy<void>(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;
}

View File

@@ -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();
}
} }

View File

@@ -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();
}

View File

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

View File

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

View File

@@ -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();

View File

@@ -17,7 +17,6 @@
#include <stdlib.h>
#include <time.h>
#include "../audio/audio.h"
#include "../audio/AudioMixer.h"
#include "../config/Config.h"
#include "../Context.h"
#include "../drawing/drawing.h"