Codechange: Use CargoArray for linkgraph refresher. (#13165)
[openttd-github.git] / src / sound / xaudio2_s.cpp
blob4b8d8236a6f18e243950c7129a4bb333083702c5
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD 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, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file xaudio2_s.cpp XAudio2 sound driver. */
10 #include "../stdafx.h"
11 #include "../openttd.h"
12 #include "../driver.h"
13 #include "../mixer.h"
14 #include "../debug.h"
15 #include "../core/alloc_func.hpp"
16 #include "../core/bitmath_func.hpp"
17 #include "../core/math_func.hpp"
19 // Windows 8 SDK required for XAudio2
20 #undef NTDDI_VERSION
21 #undef _WIN32_WINNT
23 #define NTDDI_VERSION NTDDI_WIN8
24 #define _WIN32_WINNT _WIN32_WINNT_WIN8
26 #include "xaudio2_s.h"
28 #include <windows.h>
29 #include <mmsystem.h>
30 #include <wrl\client.h>
31 #include <xaudio2.h>
33 using Microsoft::WRL::ComPtr;
35 #include "../os/windows/win32.h"
36 #include "../safeguards.h"
38 // Definition of the "XAudio2Create" call used to initialise XAudio2
39 typedef HRESULT(__stdcall *API_XAudio2Create)(_Outptr_ IXAudio2 **ppXAudio2, UINT32 Flags, XAUDIO2_PROCESSOR XAudio2Processor);
41 static FSoundDriver_XAudio2 iFSoundDriver_XAudio2;
43 /**
44 * Implementation of the IXAudio2VoiceCallback interface.
45 * Provides buffered audio to XAudio2 from the OpenTTD mixer.
47 class StreamingVoiceContext : public IXAudio2VoiceCallback
49 private:
50 int bufferLength;
51 char *buffer;
53 public:
54 IXAudio2SourceVoice *SourceVoice;
56 StreamingVoiceContext(int bufferLength)
58 this->bufferLength = bufferLength;
59 this->buffer = MallocT<char>(bufferLength);
62 virtual ~StreamingVoiceContext()
64 free(this->buffer);
67 HRESULT SubmitBuffer()
69 // Ensure we do have a valid voice
70 if (this->SourceVoice == nullptr)
72 return E_FAIL;
75 MxMixSamples(this->buffer, this->bufferLength / 4);
77 XAUDIO2_BUFFER buf = { 0 };
78 buf.AudioBytes = this->bufferLength;
79 buf.pAudioData = (const BYTE *) this->buffer;
81 return SourceVoice->SubmitSourceBuffer(&buf);
84 STDMETHOD_(void, OnVoiceProcessingPassStart)(UINT32) override
88 STDMETHOD_(void, OnVoiceProcessingPassEnd)() override
92 STDMETHOD_(void, OnStreamEnd)() override
96 STDMETHOD_(void, OnBufferStart)(void*) override
100 STDMETHOD_(void, OnBufferEnd)(void*) override
102 SubmitBuffer();
105 STDMETHOD_(void, OnLoopEnd)(void*) override
109 STDMETHOD_(void, OnVoiceError)(void*, HRESULT) override
114 static HMODULE _xaudio_dll_handle;
115 static IXAudio2SourceVoice *_source_voice = nullptr;
116 static IXAudio2MasteringVoice *_mastering_voice = nullptr;
117 static ComPtr<IXAudio2> _xaudio2;
118 static StreamingVoiceContext *_voice_context = nullptr;
120 /** Create XAudio2 context with SEH exception checking. */
121 static HRESULT CreateXAudio(API_XAudio2Create xAudio2Create)
123 HRESULT hr;
124 __try {
125 UINT32 flags = 0;
126 hr = xAudio2Create(_xaudio2.GetAddressOf(), flags, XAUDIO2_DEFAULT_PROCESSOR);
127 } __except (EXCEPTION_EXECUTE_HANDLER) {
128 hr = GetExceptionCode();
131 return hr;
135 * Initialises the XAudio2 driver.
137 * @param parm Driver parameters.
138 * @return An error message if unsuccessful, or std::nullopt otherwise.
141 std::optional<std::string_view> SoundDriver_XAudio2::Start(const StringList &parm)
143 HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
145 if (FAILED(hr))
147 Debug(driver, 0, "xaudio2_s: CoInitializeEx failed ({:08x})", (uint)hr);
148 return "Failed to initialise COM";
151 _xaudio_dll_handle = LoadLibraryA(XAUDIO2_DLL_A);
153 if (_xaudio_dll_handle == nullptr)
155 CoUninitialize();
157 Debug(driver, 0, "xaudio2_s: Unable to load " XAUDIO2_DLL_A);
158 return "Failed to load XAudio2 DLL";
161 API_XAudio2Create xAudio2Create = (API_XAudio2Create) GetProcAddress(_xaudio_dll_handle, "XAudio2Create");
163 if (xAudio2Create == nullptr)
165 FreeLibrary(_xaudio_dll_handle);
166 CoUninitialize();
168 Debug(driver, 0, "xaudio2_s: Unable to find XAudio2Create function in DLL");
169 return "Failed to load XAudio2 DLL";
172 // Create the XAudio engine
173 hr = CreateXAudio(xAudio2Create);
175 if (FAILED(hr))
177 FreeLibrary(_xaudio_dll_handle);
178 CoUninitialize();
180 Debug(driver, 0, "xaudio2_s: XAudio2Create failed ({:08x})", (uint)hr);
181 return "Failed to inititialise the XAudio2 engine";
184 // Create a mastering voice
185 hr = _xaudio2->CreateMasteringVoice(&_mastering_voice);
187 if (FAILED(hr))
189 _xaudio2.Reset();
190 FreeLibrary(_xaudio_dll_handle);
191 CoUninitialize();
193 Debug(driver, 0, "xaudio2_s: CreateMasteringVoice failed ({:08x})", (uint)hr);
194 return "Failed to create a mastering voice";
197 // Create a source voice to stream our audio
198 WAVEFORMATEX wfex;
200 wfex.wFormatTag = WAVE_FORMAT_PCM;
201 wfex.nChannels = 2;
202 wfex.wBitsPerSample = 16;
203 wfex.nSamplesPerSec = GetDriverParamInt(parm, "hz", 44100);
204 wfex.nBlockAlign = (wfex.nChannels * wfex.wBitsPerSample) / 8;
205 wfex.nAvgBytesPerSec = wfex.nSamplesPerSec * wfex.nBlockAlign;
207 // Limit buffer size to prevent overflows
208 int bufsize = GetDriverParamInt(parm, "samples", 1024);
209 bufsize = std::min<int>(bufsize, UINT16_MAX);
211 _voice_context = new StreamingVoiceContext(bufsize * 4);
213 if (_voice_context == nullptr)
215 _mastering_voice->DestroyVoice();
216 _xaudio2.Reset();
217 FreeLibrary(_xaudio_dll_handle);
218 CoUninitialize();
220 return "Failed to create streaming voice context";
223 hr = _xaudio2->CreateSourceVoice(&_source_voice, &wfex, 0, 1.0f, _voice_context);
225 if (FAILED(hr))
227 _mastering_voice->DestroyVoice();
228 _xaudio2.Reset();
229 FreeLibrary(_xaudio_dll_handle);
230 CoUninitialize();
232 Debug(driver, 0, "xaudio2_s: CreateSourceVoice failed ({:08x})", (uint)hr);
233 return "Failed to create a source voice";
236 _voice_context->SourceVoice = _source_voice;
237 hr = _source_voice->Start(0, 0);
239 if (FAILED(hr))
241 Debug(driver, 0, "xaudio2_s: _source_voice->Start failed ({:08x})", (uint)hr);
243 Stop();
244 return "Failed to start the source voice";
247 MxInitialize(wfex.nSamplesPerSec);
249 // Submit the first buffer
250 hr = _voice_context->SubmitBuffer();
252 if (FAILED(hr))
254 Debug(driver, 0, "xaudio2_s: _voice_context->SubmitBuffer failed ({:08x})", (uint)hr);
256 Stop();
257 return "Failed to submit the first audio buffer";
260 return std::nullopt;
264 * Terminates the XAudio2 driver.
266 void SoundDriver_XAudio2::Stop()
268 // Clean up XAudio2
269 _source_voice->DestroyVoice();
271 delete _voice_context;
272 _voice_context = nullptr;
274 _mastering_voice->DestroyVoice();
276 _xaudio2.Reset();
278 FreeLibrary(_xaudio_dll_handle);
279 CoUninitialize();