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:
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace OpenRCT2
|
||||
virtual bool IsSteamOverlayAttached() abstract;
|
||||
};
|
||||
|
||||
IUiContext * CreateContext();
|
||||
IUiContext * CreateUiContext();
|
||||
IPlatformUiContext * CreatePlatformUiContext();
|
||||
}
|
||||
}
|
||||
|
||||
297
src/openrct2-ui/audio/AudioChannel.cpp
Normal file
297
src/openrct2-ui/audio/AudioChannel.cpp
Normal 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();
|
||||
}
|
||||
} }
|
||||
@@ -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; }
|
||||
|
||||
44
src/openrct2-ui/audio/AudioContext.h
Normal file
44
src/openrct2-ui/audio/AudioContext.h
Normal 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);
|
||||
}
|
||||
} }
|
||||
@@ -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);
|
||||
}
|
||||
} }
|
||||
539
src/openrct2-ui/audio/AudioMixer.cpp
Normal file
539
src/openrct2-ui/audio/AudioMixer.cpp
Normal 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;
|
||||
}
|
||||
};
|
||||
} }
|
||||
210
src/openrct2-ui/audio/FileAudioSource.cpp
Normal file
210
src/openrct2-ui/audio/FileAudioSource.cpp
Normal 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;
|
||||
}
|
||||
} }
|
||||
241
src/openrct2-ui/audio/MemoryAudioSource.cpp
Normal file
241
src/openrct2-ui/audio/MemoryAudioSource.cpp
Normal 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;
|
||||
}
|
||||
} }
|
||||
@@ -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" />
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
} }
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
} }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
} }
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user