yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
audio_backend.cc
Go to the documentation of this file.
1// audio_backend.cc - Audio Backend Implementation
2
4
5#include <SDL.h>
6#include <algorithm>
7#include <vector>
8#include "util/log.h"
9
10namespace yaze {
11namespace emu {
12namespace audio {
13
14// ============================================================================
15// SDL2AudioBackend Implementation
16// ============================================================================
17
21
23 if (initialized_) {
24 LOG_WARN("AudioBackend", "Already initialized, shutting down first");
25 Shutdown();
26 }
27
28 config_ = config;
29
30 SDL_AudioSpec want, have;
31 SDL_memset(&want, 0, sizeof(want));
32
33 want.freq = config.sample_rate;
34 want.format = (config.format == SampleFormat::INT16) ? AUDIO_S16 : AUDIO_F32;
35 want.channels = config.channels;
36 want.samples = config.buffer_frames;
37 want.callback = nullptr; // Use queue-based audio
38
39 device_id_ = SDL_OpenAudioDevice(nullptr, 0, &want, &have, 0);
40
41 if (device_id_ == 0) {
42 LOG_ERROR("AudioBackend", "Failed to open SDL audio device: %s", SDL_GetError());
43 return false;
44 }
45
46 device_format_ = have.format;
47 device_channels_ = have.channels;
48 device_freq_ = have.freq;
49
50 // Verify we got what we asked for
51 if (have.freq != want.freq || have.channels != want.channels) {
52 LOG_WARN("AudioBackend",
53 "Audio spec mismatch - wanted %dHz %dch, got %dHz %dch",
54 want.freq, want.channels, have.freq, have.channels);
55 // Update config with actual values
56 config_.sample_rate = have.freq;
57 config_.channels = have.channels;
58 }
59
60 LOG_INFO("AudioBackend", "SDL2 audio initialized: %dHz, %d channels, %d samples buffer",
61 have.freq, have.channels, have.samples);
62
63 initialized_ = true;
66 if (audio_stream_) {
67 SDL_FreeAudioStream(audio_stream_);
68 audio_stream_ = nullptr;
69 }
70 stream_buffer_.clear();
71
72 // Start playback immediately (unpause)
73 SDL_PauseAudioDevice(device_id_, 0);
74
75 return true;
76}
77
79 if (!initialized_) return;
80
81 if (audio_stream_) {
82 SDL_FreeAudioStream(audio_stream_);
83 audio_stream_ = nullptr;
84 }
87 stream_buffer_.clear();
88
89 if (device_id_ != 0) {
90 SDL_PauseAudioDevice(device_id_, 1);
91 SDL_CloseAudioDevice(device_id_);
92 device_id_ = 0;
93 }
94
95 initialized_ = false;
96 LOG_INFO("AudioBackend", "SDL2 audio shut down");
97}
98
100 if (!initialized_) return;
101 SDL_PauseAudioDevice(device_id_, 0);
102}
103
105 if (!initialized_) return;
106 SDL_PauseAudioDevice(device_id_, 1);
107}
108
110 if (!initialized_) return;
111 Clear();
112 SDL_PauseAudioDevice(device_id_, 1);
113}
114
116 if (!initialized_) return;
117 SDL_ClearQueuedAudio(device_id_);
118 if (audio_stream_) {
119 SDL_AudioStreamClear(audio_stream_);
120 }
121}
122
123bool SDL2AudioBackend::QueueSamples(const int16_t* samples, int num_samples) {
124 if (!initialized_ || !samples) return false;
125
126 // OPTIMIZATION: Skip volume scaling if volume is 100% (common case)
127 if (volume_ == 1.0f) {
128 // Fast path: No volume adjustment needed
129 int result = SDL_QueueAudio(device_id_, samples, num_samples * sizeof(int16_t));
130 if (result < 0) {
131 LOG_ERROR("AudioBackend", "SDL_QueueAudio failed: %s", SDL_GetError());
132 return false;
133 }
134 return true;
135 }
136
137 // Slow path: Volume scaling required
138 // Use thread-local buffer to avoid repeated allocations
139 thread_local std::vector<int16_t> scaled_samples;
140
141 // Resize only if needed (avoid reallocation on every call)
142 if (scaled_samples.size() < static_cast<size_t>(num_samples)) {
143 scaled_samples.resize(num_samples);
144 }
145
146 // Apply volume scaling with SIMD-friendly loop
147 for (int i = 0; i < num_samples; ++i) {
148 int32_t scaled = static_cast<int32_t>(samples[i] * volume_);
149 // Clamp to prevent overflow
150 if (scaled > 32767) scaled = 32767;
151 else if (scaled < -32768) scaled = -32768;
152 scaled_samples[i] = static_cast<int16_t>(scaled);
153 }
154
155 int result = SDL_QueueAudio(device_id_, scaled_samples.data(),
156 num_samples * sizeof(int16_t));
157 if (result < 0) {
158 LOG_ERROR("AudioBackend", "SDL_QueueAudio failed: %s", SDL_GetError());
159 return false;
160 }
161
162 return true;
163}
164
165bool SDL2AudioBackend::QueueSamples(const float* samples, int num_samples) {
166 if (!initialized_ || !samples) return false;
167
168 // Convert float to int16
169 std::vector<int16_t> int_samples(num_samples);
170 for (int i = 0; i < num_samples; ++i) {
171 float scaled = std::clamp(samples[i] * volume_, -1.0f, 1.0f);
172 int_samples[i] = static_cast<int16_t>(scaled * 32767.0f);
173 }
174
175 return QueueSamples(int_samples.data(), num_samples);
176}
177
178bool SDL2AudioBackend::QueueSamplesNative(const int16_t* samples,
179 int frames_per_channel, int channels,
180 int native_rate) {
181 if (!initialized_ || samples == nullptr) {
182 return false;
183 }
184
185 if (!audio_stream_enabled_ || audio_stream_ == nullptr) {
186 return false;
187 }
188
189 if (native_rate != stream_native_rate_ ||
190 channels != config_.channels) {
191 SetAudioStreamResampling(true, native_rate, channels);
192 if (audio_stream_ == nullptr) {
193 return false;
194 }
195 }
196
197 const int bytes_in =
198 frames_per_channel * channels * static_cast<int>(sizeof(int16_t));
199
200 if (SDL_AudioStreamPut(audio_stream_, samples, bytes_in) < 0) {
201 LOG_ERROR("AudioBackend", "SDL_AudioStreamPut failed: %s", SDL_GetError());
202 return false;
203 }
204
205 const int available_bytes = SDL_AudioStreamAvailable(audio_stream_);
206 if (available_bytes < 0) {
207 LOG_ERROR("AudioBackend", "SDL_AudioStreamAvailable failed: %s", SDL_GetError());
208 return false;
209 }
210
211 if (available_bytes == 0) {
212 return true;
213 }
214
215 const int available_samples = available_bytes / static_cast<int>(sizeof(int16_t));
216 if (static_cast<int>(stream_buffer_.size()) < available_samples) {
217 stream_buffer_.resize(available_samples);
218 }
219
220 if (SDL_AudioStreamGet(audio_stream_, stream_buffer_.data(), available_bytes) < 0) {
221 LOG_ERROR("AudioBackend", "SDL_AudioStreamGet failed: %s", SDL_GetError());
222 return false;
223 }
224
225 return QueueSamples(stream_buffer_.data(), available_samples);
226}
227
229 AudioStatus status;
230
231 if (!initialized_) return status;
232
233 status.is_playing = (SDL_GetAudioDeviceStatus(device_id_) == SDL_AUDIO_PLAYING);
234 status.queued_bytes = SDL_GetQueuedAudioSize(device_id_);
235
236 // Calculate queued frames (each frame = channels * sample_size)
237 int bytes_per_frame = config_.channels *
239 status.queued_frames = status.queued_bytes / bytes_per_frame;
240
241 // Check for underrun (queue too low while playing)
242 if (status.is_playing && status.queued_frames < 100) {
243 status.has_underrun = true;
244 }
245
246 return status;
247}
248
250 return initialized_;
251}
252
256
257void SDL2AudioBackend::SetAudioStreamResampling(bool enable, int native_rate,
258 int channels) {
259 if (!initialized_) return;
260
261 if (!enable) {
262 if (audio_stream_) {
263 SDL_FreeAudioStream(audio_stream_);
264 audio_stream_ = nullptr;
265 }
266 audio_stream_enabled_ = false;
268 stream_buffer_.clear();
269 return;
270 }
271
272 const bool needs_recreate =
273 (audio_stream_ == nullptr) || (stream_native_rate_ != native_rate) ||
274 (channels != config_.channels);
275
276 if (!needs_recreate) {
278 return;
279 }
280
281 if (audio_stream_) {
282 SDL_FreeAudioStream(audio_stream_);
283 audio_stream_ = nullptr;
284 }
285
286 audio_stream_ = SDL_NewAudioStream(AUDIO_S16, channels, native_rate,
289 if (!audio_stream_) {
290 LOG_ERROR("AudioBackend", "SDL_NewAudioStream failed: %s", SDL_GetError());
291 audio_stream_enabled_ = false;
293 return;
294 }
295
296 SDL_AudioStreamClear(audio_stream_);
298 stream_native_rate_ = native_rate;
299 stream_buffer_.clear();
300}
301
302void SDL2AudioBackend::SetVolume(float volume) {
303 volume_ = std::clamp(volume, 0.0f, 1.0f);
304}
305
307 return volume_;
308}
309
310// ============================================================================
311// AudioBackendFactory Implementation
312// ============================================================================
313
314std::unique_ptr<IAudioBackend> AudioBackendFactory::Create(BackendType type) {
315 switch (type) {
317 return std::make_unique<SDL2AudioBackend>();
318
320 // TODO: Implement null backend for testing
321 LOG_WARN("AudioBackend", "NULL backend not yet implemented, using SDL2");
322 return std::make_unique<SDL2AudioBackend>();
323
324 default:
325 LOG_ERROR("AudioBackend", "Unknown backend type, using SDL2");
326 return std::make_unique<SDL2AudioBackend>();
327 }
328}
329
330} // namespace audio
331} // namespace emu
332} // namespace yaze
static std::unique_ptr< IAudioBackend > Create(BackendType type)
AudioConfig GetConfig() const override
void SetVolume(float volume) override
bool Initialize(const AudioConfig &config) override
bool IsInitialized() const override
std::vector< int16_t > stream_buffer_
AudioStatus GetStatus() const override
bool QueueSamplesNative(const int16_t *samples, int frames_per_channel, int channels, int native_rate) override
bool QueueSamples(const int16_t *samples, int num_samples) override
void SetAudioStreamResampling(bool enable, int native_rate, int channels) override
#define LOG_ERROR(category, format,...)
Definition log.h:110
#define LOG_WARN(category, format,...)
Definition log.h:108
#define LOG_INFO(category, format,...)
Definition log.h:106
Main namespace for the application.
Definition controller.cc:20