Mercurial > repos > blastem
comparison render_audio.c @ 1865:4c322abd9fa5
Split generic part of audio code into a separate file so it can be used in other targets besides SDL
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Fri, 17 May 2019 08:43:30 -0700 |
parents | |
children | e4671a39d155 |
comparison
equal
deleted
inserted
replaced
1863:d60f2d7c02a5 | 1865:4c322abd9fa5 |
---|---|
1 #include <limits.h> | |
2 #include <string.h> | |
3 #include <stdlib.h> | |
4 #include <math.h> | |
5 #include "render_audio.h" | |
6 #include "util.h" | |
7 #include "config.h" | |
8 #include "blastem.h" | |
9 | |
10 static uint8_t output_channels; | |
11 static uint32_t buffer_samples, sample_rate; | |
12 static uint32_t missing_count; | |
13 | |
14 static audio_source *audio_sources[8]; | |
15 static audio_source *inactive_audio_sources[8]; | |
16 static uint8_t num_audio_sources; | |
17 static uint8_t num_inactive_audio_sources; | |
18 | |
19 static float overall_gain_mult, *mix_buf; | |
20 static int sample_size; | |
21 | |
22 typedef void (*conv_func)(float *samples, void *vstream, int sample_count); | |
23 | |
24 static void convert_null(float *samples, void *vstream, int sample_count) | |
25 { | |
26 memset(vstream, 0, sample_count * sample_size); | |
27 } | |
28 | |
29 static void convert_s16(float *samples, void *vstream, int sample_count) | |
30 { | |
31 int16_t *stream = vstream; | |
32 for (int16_t *end = stream + sample_count; stream < end; stream++, samples++) | |
33 { | |
34 float sample = *samples; | |
35 int16_t out_sample; | |
36 if (sample >= 1.0f) { | |
37 out_sample = 0x7FFF; | |
38 } else if (sample <= -1.0f) { | |
39 out_sample = -0x8000; | |
40 } else { | |
41 out_sample = sample * 0x7FFF; | |
42 } | |
43 *stream = out_sample; | |
44 } | |
45 } | |
46 | |
47 static void clamp_f32(float *samples, void *vstream, int sample_count) | |
48 { | |
49 for (; sample_count > 0; sample_count--, samples++) | |
50 { | |
51 float sample = *samples; | |
52 if (sample > 1.0f) { | |
53 sample = 1.0f; | |
54 } else if (sample < -1.0f) { | |
55 sample = -1.0f; | |
56 } | |
57 *samples = sample; | |
58 } | |
59 } | |
60 | |
61 static int32_t mix_f32(audio_source *audio, float *stream, int samples) | |
62 { | |
63 float *end = stream + samples; | |
64 int16_t *src = audio->front; | |
65 uint32_t i = audio->read_start; | |
66 uint32_t i_end = audio->read_end; | |
67 float *cur = stream; | |
68 float gain_mult = audio->gain_mult * overall_gain_mult; | |
69 size_t first_add = output_channels > 1 ? 1 : 0, second_add = output_channels > 1 ? output_channels - 1 : 1; | |
70 if (audio->num_channels == 1) { | |
71 while (cur < end && i != i_end) | |
72 { | |
73 *cur += gain_mult * ((float)src[i]) / 0x7FFF; | |
74 cur += first_add; | |
75 *cur += gain_mult * ((float)src[i++]) / 0x7FFF; | |
76 cur += second_add; | |
77 i &= audio->mask; | |
78 } | |
79 } else { | |
80 while(cur < end && i != i_end) | |
81 { | |
82 *cur += gain_mult * ((float)src[i++]) / 0x7FFF; | |
83 cur += first_add; | |
84 *cur += gain_mult * ((float)src[i++]) / 0x7FFF; | |
85 cur += second_add; | |
86 i &= audio->mask; | |
87 } | |
88 } | |
89 if (!render_is_audio_sync()) { | |
90 audio->read_start = i; | |
91 } | |
92 if (cur != end) { | |
93 debug_message("Underflow of %d samples, read_start: %d, read_end: %d, mask: %X\n", (int)(end-cur)/2, audio->read_start, audio->read_end, audio->mask); | |
94 return (cur-end)/2; | |
95 } else { | |
96 return ((i_end - i) & audio->mask) / audio->num_channels; | |
97 } | |
98 } | |
99 | |
100 static conv_func convert; | |
101 | |
102 | |
103 int mix_and_convert(unsigned char *byte_stream, int len, int *min_remaining_out) | |
104 { | |
105 int samples = len / sample_size; | |
106 float *mix_dest = mix_buf ? mix_buf : (float *)byte_stream; | |
107 memset(mix_dest, 0, samples * sizeof(float)); | |
108 int min_buffered = INT_MAX; | |
109 int min_remaining_buffer = INT_MAX; | |
110 for (uint8_t i = 0; i < num_audio_sources; i++) | |
111 { | |
112 int buffered = mix_f32(audio_sources[i], mix_dest, samples); | |
113 int remaining = (audio_sources[i]->mask + 1) / audio_sources[i]->num_channels - buffered; | |
114 min_buffered = buffered < min_buffered ? buffered : min_buffered; | |
115 min_remaining_buffer = remaining < min_remaining_buffer ? remaining : min_remaining_buffer; | |
116 audio_sources[i]->front_populated = 0; | |
117 render_buffer_consumed(audio_sources[i]); | |
118 } | |
119 convert(mix_dest, byte_stream, samples); | |
120 if (min_remaining_out) { | |
121 *min_remaining_out = min_remaining_buffer; | |
122 } | |
123 return min_buffered; | |
124 } | |
125 | |
126 uint8_t all_sources_ready(void) | |
127 { | |
128 uint8_t num_populated = 0; | |
129 num_populated = 0; | |
130 for (uint8_t i = 0; i < num_audio_sources; i++) | |
131 { | |
132 if (audio_sources[i]->front_populated) { | |
133 num_populated++; | |
134 } | |
135 } | |
136 return num_populated == num_audio_sources; | |
137 } | |
138 | |
139 #define BUFFER_INC_RES 0x40000000UL | |
140 | |
141 void render_audio_adjust_clock(audio_source *src, uint64_t master_clock, uint64_t sample_divider) | |
142 { | |
143 src->buffer_inc = ((BUFFER_INC_RES * (uint64_t)sample_rate) / master_clock) * sample_divider; | |
144 } | |
145 | |
146 void render_audio_adjust_speed(float adjust_ratio) | |
147 { | |
148 for (uint8_t i = 0; i < num_audio_sources; i++) | |
149 { | |
150 audio_sources[i]->buffer_inc = ((double)audio_sources[i]->buffer_inc) + ((double)audio_sources[i]->buffer_inc) * adjust_ratio + 0.5; | |
151 } | |
152 } | |
153 | |
154 audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels) | |
155 { | |
156 audio_source *ret = NULL; | |
157 uint32_t alloc_size = render_is_audio_sync() ? channels * buffer_samples : nearest_pow2(render_min_buffered() * 4 * channels); | |
158 render_lock_audio(); | |
159 if (num_audio_sources < 8) { | |
160 ret = calloc(1, sizeof(audio_source)); | |
161 ret->back = malloc(alloc_size * sizeof(int16_t)); | |
162 ret->front = render_is_audio_sync() ? malloc(alloc_size * sizeof(int16_t)) : ret->back; | |
163 ret->front_populated = 0; | |
164 ret->opaque = render_new_audio_opaque(); | |
165 ret->num_channels = channels; | |
166 audio_sources[num_audio_sources++] = ret; | |
167 } | |
168 render_unlock_audio(); | |
169 if (!ret) { | |
170 fatal_error("Too many audio sources!"); | |
171 } else { | |
172 render_audio_adjust_clock(ret, master_clock, sample_divider); | |
173 double lowpass_cutoff = get_lowpass_cutoff(config); | |
174 double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); | |
175 ret->dt = 1.0 / ((double)master_clock / (double)(sample_divider)); | |
176 double alpha = ret->dt / (ret->dt + rc); | |
177 ret->lowpass_alpha = (int32_t)(((double)0x10000) * alpha); | |
178 ret->buffer_pos = 0; | |
179 ret->buffer_fraction = 0; | |
180 ret->last_left = ret->last_right = 0; | |
181 ret->read_start = 0; | |
182 ret->read_end = render_is_audio_sync() ? buffer_samples * channels : 0; | |
183 ret->mask = render_is_audio_sync() ? 0xFFFFFFFF : alloc_size-1; | |
184 ret->gain_mult = 1.0f; | |
185 } | |
186 render_audio_created(ret); | |
187 | |
188 return ret; | |
189 } | |
190 | |
191 | |
192 static float db_to_mult(float gain) | |
193 { | |
194 return powf(10.0f, gain/20.0f); | |
195 } | |
196 | |
197 void render_audio_source_gaindb(audio_source *src, float gain) | |
198 { | |
199 src->gain_mult = db_to_mult(gain); | |
200 } | |
201 | |
202 void render_pause_source(audio_source *src) | |
203 { | |
204 uint8_t found = 0, remaining_sources; | |
205 render_lock_audio(); | |
206 for (uint8_t i = 0; i < num_audio_sources; i++) | |
207 { | |
208 if (audio_sources[i] == src) { | |
209 audio_sources[i] = audio_sources[--num_audio_sources]; | |
210 found = 1; | |
211 remaining_sources = num_audio_sources; | |
212 break; | |
213 } | |
214 } | |
215 | |
216 render_unlock_audio(); | |
217 if (found) { | |
218 render_source_paused(src, remaining_sources); | |
219 } | |
220 inactive_audio_sources[num_inactive_audio_sources++] = src; | |
221 } | |
222 | |
223 void render_resume_source(audio_source *src) | |
224 { | |
225 render_lock_audio(); | |
226 if (num_audio_sources < 8) { | |
227 audio_sources[num_audio_sources++] = src; | |
228 } | |
229 render_unlock_audio(); | |
230 for (uint8_t i = 0; i < num_inactive_audio_sources; i++) | |
231 { | |
232 if (inactive_audio_sources[i] == src) { | |
233 inactive_audio_sources[i] = inactive_audio_sources[--num_inactive_audio_sources]; | |
234 } | |
235 } | |
236 render_source_resumed(src); | |
237 } | |
238 | |
239 void render_free_source(audio_source *src) | |
240 { | |
241 render_pause_source(src); | |
242 | |
243 free(src->front); | |
244 if (render_is_audio_sync()) { | |
245 free(src->back); | |
246 render_free_audio_opaque(src->opaque); | |
247 } | |
248 free(src); | |
249 num_inactive_audio_sources--; | |
250 } | |
251 | |
252 static int16_t lowpass_sample(audio_source *src, int16_t last, int16_t current) | |
253 { | |
254 int32_t tmp = current * src->lowpass_alpha + last * (0x10000 - src->lowpass_alpha); | |
255 current = tmp >> 16; | |
256 return current; | |
257 } | |
258 | |
259 static void interp_sample(audio_source *src, int16_t last, int16_t current) | |
260 { | |
261 int64_t tmp = last * ((src->buffer_fraction << 16) / src->buffer_inc); | |
262 tmp += current * (0x10000 - ((src->buffer_fraction << 16) / src->buffer_inc)); | |
263 src->back[src->buffer_pos++] = tmp >> 16; | |
264 } | |
265 | |
266 static uint32_t sync_samples; | |
267 void render_put_mono_sample(audio_source *src, int16_t value) | |
268 { | |
269 value = lowpass_sample(src, src->last_left, value); | |
270 src->buffer_fraction += src->buffer_inc; | |
271 uint32_t base = render_is_audio_sync() ? 0 : src->read_end; | |
272 while (src->buffer_fraction > BUFFER_INC_RES) | |
273 { | |
274 src->buffer_fraction -= BUFFER_INC_RES; | |
275 interp_sample(src, src->last_left, value); | |
276 | |
277 if (((src->buffer_pos - base) & src->mask) >= sync_samples) { | |
278 render_do_audio_ready(src); | |
279 } | |
280 src->buffer_pos &= src->mask; | |
281 } | |
282 src->last_left = value; | |
283 } | |
284 | |
285 void render_put_stereo_sample(audio_source *src, int16_t left, int16_t right) | |
286 { | |
287 left = lowpass_sample(src, src->last_left, left); | |
288 right = lowpass_sample(src, src->last_right, right); | |
289 src->buffer_fraction += src->buffer_inc; | |
290 uint32_t base = render_is_audio_sync() ? 0 : src->read_end; | |
291 while (src->buffer_fraction > BUFFER_INC_RES) | |
292 { | |
293 src->buffer_fraction -= BUFFER_INC_RES; | |
294 | |
295 interp_sample(src, src->last_left, left); | |
296 interp_sample(src, src->last_right, right); | |
297 | |
298 if (((src->buffer_pos - base) & src->mask)/2 >= sync_samples) { | |
299 render_do_audio_ready(src); | |
300 } | |
301 src->buffer_pos &= src->mask; | |
302 } | |
303 src->last_left = left; | |
304 src->last_right = right; | |
305 } | |
306 | |
307 static void update_source(audio_source *src, double rc, uint8_t sync_changed) | |
308 { | |
309 double alpha = src->dt / (src->dt + rc); | |
310 int32_t lowpass_alpha = (int32_t)(((double)0x10000) * alpha); | |
311 src->lowpass_alpha = lowpass_alpha; | |
312 if (sync_changed) { | |
313 uint32_t alloc_size = render_is_audio_sync() ? src->num_channels * buffer_samples : nearest_pow2(render_min_buffered() * 4 * src->num_channels); | |
314 src->back = realloc(src->back, alloc_size * sizeof(int16_t)); | |
315 if (render_is_audio_sync()) { | |
316 src->front = malloc(alloc_size * sizeof(int16_t)); | |
317 } else { | |
318 free(src->front); | |
319 src->front = src->back; | |
320 } | |
321 src->mask = render_is_audio_sync() ? 0xFFFFFFFF : alloc_size-1; | |
322 src->read_start = 0; | |
323 src->read_end = render_is_audio_sync() ? buffer_samples * src->num_channels : 0; | |
324 src->buffer_pos = 0; | |
325 } | |
326 } | |
327 | |
328 uint8_t old_audio_sync; | |
329 void render_audio_initialized(render_audio_format format, uint32_t rate, uint8_t channels, uint32_t buffer_size, int sample_size_in) | |
330 { | |
331 sample_rate = rate; | |
332 output_channels = channels; | |
333 buffer_samples = buffer_size; | |
334 sample_size = sample_size_in; | |
335 if (mix_buf) { | |
336 free(mix_buf); | |
337 mix_buf = NULL; | |
338 } | |
339 switch(format) | |
340 { | |
341 case RENDER_AUDIO_S16: | |
342 convert = convert_s16; | |
343 mix_buf = calloc(output_channels * buffer_samples, sizeof(float)); | |
344 break; | |
345 case RENDER_AUDIO_FLOAT: | |
346 convert = clamp_f32; | |
347 break; | |
348 case RENDER_AUDIO_UNKNOWN: | |
349 convert = convert_null; | |
350 mix_buf = calloc(output_channels * buffer_samples, sizeof(float)); | |
351 break; | |
352 } | |
353 uint32_t syncs = render_audio_syncs_per_sec(); | |
354 if (syncs) { | |
355 sync_samples = rate / syncs; | |
356 } else { | |
357 sync_samples = buffer_samples; | |
358 } | |
359 char * gain_str = tern_find_path(config, "audio\0gain\0", TVAL_PTR).ptrval; | |
360 overall_gain_mult = db_to_mult(gain_str ? atof(gain_str) : 0.0f); | |
361 uint8_t sync_changed = old_audio_sync != render_is_audio_sync(); | |
362 old_audio_sync = render_is_audio_sync(); | |
363 double lowpass_cutoff = get_lowpass_cutoff(config); | |
364 double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); | |
365 render_lock_audio(); | |
366 for (uint8_t i = 0; i < num_audio_sources; i++) | |
367 { | |
368 update_source(audio_sources[i], rc, sync_changed); | |
369 } | |
370 render_unlock_audio(); | |
371 for (uint8_t i = 0; i < num_inactive_audio_sources; i++) | |
372 { | |
373 update_source(inactive_audio_sources[i], rc, sync_changed); | |
374 } | |
375 } |