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