Mercurial > repos > blastem
view render_audio.c @ 2069:8e51c0c3f2e3 segacd
Initial attempt at implementing the Sega CD graphics hardware
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Sun, 30 Jan 2022 19:55:33 -0800 |
parents | e4671a39d155 |
children | cfd53c94fffb |
line wrap: on
line source
#include <limits.h> #include <string.h> #include <stdlib.h> #include <math.h> #include "render_audio.h" #include "util.h" #include "config.h" #include "blastem.h" static uint8_t output_channels; static uint32_t buffer_samples, sample_rate; static audio_source *audio_sources[8]; static audio_source *inactive_audio_sources[8]; static uint8_t num_audio_sources; static uint8_t num_inactive_audio_sources; static float overall_gain_mult, *mix_buf; static int sample_size; typedef void (*conv_func)(float *samples, void *vstream, int sample_count); static void convert_null(float *samples, void *vstream, int sample_count) { memset(vstream, 0, sample_count * sample_size); } static void convert_s16(float *samples, void *vstream, int sample_count) { int16_t *stream = vstream; for (int16_t *end = stream + sample_count; stream < end; stream++, samples++) { float sample = *samples; int16_t out_sample; if (sample >= 1.0f) { out_sample = 0x7FFF; } else if (sample <= -1.0f) { out_sample = -0x8000; } else { out_sample = sample * 0x7FFF; } *stream = out_sample; } } static void clamp_f32(float *samples, void *vstream, int sample_count) { for (; sample_count > 0; sample_count--, samples++) { float sample = *samples; if (sample > 1.0f) { sample = 1.0f; } else if (sample < -1.0f) { sample = -1.0f; } *samples = sample; } } static int32_t mix_f32(audio_source *audio, float *stream, int samples) { float *end = stream + samples; int16_t *src = audio->front; uint32_t i = audio->read_start; uint32_t i_end = audio->read_end; float *cur = stream; float gain_mult = audio->gain_mult * overall_gain_mult; size_t first_add = output_channels > 1 ? 1 : 0, second_add = output_channels > 1 ? output_channels - 1 : 1; if (audio->num_channels == 1) { while (cur < end && i != i_end) { *cur += gain_mult * ((float)src[i]) / 0x7FFF; cur += first_add; *cur += gain_mult * ((float)src[i++]) / 0x7FFF; cur += second_add; i &= audio->mask; } } else { while(cur < end && i != i_end) { *cur += gain_mult * ((float)src[i++]) / 0x7FFF; cur += first_add; *cur += gain_mult * ((float)src[i++]) / 0x7FFF; cur += second_add; i &= audio->mask; } } if (!render_is_audio_sync()) { audio->read_start = i; } if (cur != end) { 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); return (cur-end)/2; } else { return ((i_end - i) & audio->mask) / audio->num_channels; } } static conv_func convert; int mix_and_convert(unsigned char *byte_stream, int len, int *min_remaining_out) { int samples = len / sample_size; float *mix_dest = mix_buf ? mix_buf : (float *)byte_stream; memset(mix_dest, 0, samples * sizeof(float)); int min_buffered = INT_MAX; int min_remaining_buffer = INT_MAX; for (uint8_t i = 0; i < num_audio_sources; i++) { int buffered = mix_f32(audio_sources[i], mix_dest, samples); int remaining = (audio_sources[i]->mask + 1) / audio_sources[i]->num_channels - buffered; min_buffered = buffered < min_buffered ? buffered : min_buffered; min_remaining_buffer = remaining < min_remaining_buffer ? remaining : min_remaining_buffer; audio_sources[i]->front_populated = 0; render_buffer_consumed(audio_sources[i]); } convert(mix_dest, byte_stream, samples); if (min_remaining_out) { *min_remaining_out = min_remaining_buffer; } return min_buffered; } uint8_t all_sources_ready(void) { uint8_t num_populated = 0; num_populated = 0; for (uint8_t i = 0; i < num_audio_sources; i++) { if (audio_sources[i]->front_populated) { num_populated++; } } return num_populated == num_audio_sources; } #define BUFFER_INC_RES 0x40000000UL void render_audio_adjust_clock(audio_source *src, uint64_t master_clock, uint64_t sample_divider) { src->buffer_inc = ((BUFFER_INC_RES * (uint64_t)sample_rate) / master_clock) * sample_divider; } void render_audio_adjust_speed(float adjust_ratio) { for (uint8_t i = 0; i < num_audio_sources; i++) { audio_sources[i]->buffer_inc = ((double)audio_sources[i]->buffer_inc) + ((double)audio_sources[i]->buffer_inc) * adjust_ratio + 0.5; } } audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels) { audio_source *ret = NULL; uint32_t alloc_size = render_is_audio_sync() ? channels * buffer_samples : nearest_pow2(render_min_buffered() * 4 * channels); render_lock_audio(); if (num_audio_sources < 8) { ret = calloc(1, sizeof(audio_source)); ret->back = malloc(alloc_size * sizeof(int16_t)); ret->front = render_is_audio_sync() ? malloc(alloc_size * sizeof(int16_t)) : ret->back; ret->front_populated = 0; ret->opaque = render_new_audio_opaque(); ret->num_channels = channels; audio_sources[num_audio_sources++] = ret; } render_unlock_audio(); if (!ret) { fatal_error("Too many audio sources!"); } else { render_audio_adjust_clock(ret, master_clock, sample_divider); double lowpass_cutoff = get_lowpass_cutoff(config); double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); ret->dt = 1.0 / ((double)master_clock / (double)(sample_divider)); double alpha = ret->dt / (ret->dt + rc); ret->lowpass_alpha = (int32_t)(((double)0x10000) * alpha); ret->buffer_pos = 0; ret->buffer_fraction = 0; ret->last_left = ret->last_right = 0; ret->read_start = 0; ret->read_end = render_is_audio_sync() ? buffer_samples * channels : 0; ret->mask = render_is_audio_sync() ? 0xFFFFFFFF : alloc_size-1; ret->gain_mult = 1.0f; } render_audio_created(ret); return ret; } static float db_to_mult(float gain) { return powf(10.0f, gain/20.0f); } void render_audio_source_gaindb(audio_source *src, float gain) { src->gain_mult = db_to_mult(gain); } void render_pause_source(audio_source *src) { uint8_t found = 0, remaining_sources; render_lock_audio(); for (uint8_t i = 0; i < num_audio_sources; i++) { if (audio_sources[i] == src) { audio_sources[i] = audio_sources[--num_audio_sources]; found = 1; remaining_sources = num_audio_sources; break; } } render_unlock_audio(); if (found) { render_source_paused(src, remaining_sources); } inactive_audio_sources[num_inactive_audio_sources++] = src; } void render_resume_source(audio_source *src) { render_lock_audio(); if (num_audio_sources < 8) { audio_sources[num_audio_sources++] = src; } render_unlock_audio(); for (uint8_t i = 0; i < num_inactive_audio_sources; i++) { if (inactive_audio_sources[i] == src) { inactive_audio_sources[i] = inactive_audio_sources[--num_inactive_audio_sources]; } } render_source_resumed(src); } void render_free_source(audio_source *src) { uint8_t found = 0; for (uint8_t i = 0; i < num_inactive_audio_sources; i++) { if (inactive_audio_sources[i] == src) { inactive_audio_sources[i] = inactive_audio_sources[--num_inactive_audio_sources]; found = 1; break; } } if (!found) { render_pause_source(src); num_inactive_audio_sources--; } free(src->front); if (render_is_audio_sync()) { free(src->back); render_free_audio_opaque(src->opaque); } free(src); } static int16_t lowpass_sample(audio_source *src, int16_t last, int16_t current) { int32_t tmp = current * src->lowpass_alpha + last * (0x10000 - src->lowpass_alpha); current = tmp >> 16; return current; } static void interp_sample(audio_source *src, int16_t last, int16_t current) { int64_t tmp = last * ((src->buffer_fraction << 16) / src->buffer_inc); tmp += current * (0x10000 - ((src->buffer_fraction << 16) / src->buffer_inc)); src->back[src->buffer_pos++] = tmp >> 16; } static uint32_t sync_samples; void render_put_mono_sample(audio_source *src, int16_t value) { value = lowpass_sample(src, src->last_left, value); src->buffer_fraction += src->buffer_inc; uint32_t base = render_is_audio_sync() ? 0 : src->read_end; while (src->buffer_fraction > BUFFER_INC_RES) { src->buffer_fraction -= BUFFER_INC_RES; interp_sample(src, src->last_left, value); if (((src->buffer_pos - base) & src->mask) >= sync_samples) { render_do_audio_ready(src); } src->buffer_pos &= src->mask; } src->last_left = value; } void render_put_stereo_sample(audio_source *src, int16_t left, int16_t right) { left = lowpass_sample(src, src->last_left, left); right = lowpass_sample(src, src->last_right, right); src->buffer_fraction += src->buffer_inc; uint32_t base = render_is_audio_sync() ? 0 : src->read_end; while (src->buffer_fraction > BUFFER_INC_RES) { src->buffer_fraction -= BUFFER_INC_RES; interp_sample(src, src->last_left, left); interp_sample(src, src->last_right, right); if (((src->buffer_pos - base) & src->mask)/2 >= sync_samples) { render_do_audio_ready(src); } src->buffer_pos &= src->mask; } src->last_left = left; src->last_right = right; } static void update_source(audio_source *src, double rc, uint8_t sync_changed) { double alpha = src->dt / (src->dt + rc); int32_t lowpass_alpha = (int32_t)(((double)0x10000) * alpha); src->lowpass_alpha = lowpass_alpha; if (sync_changed) { uint32_t alloc_size = render_is_audio_sync() ? src->num_channels * buffer_samples : nearest_pow2(render_min_buffered() * 4 * src->num_channels); src->back = realloc(src->back, alloc_size * sizeof(int16_t)); if (render_is_audio_sync()) { src->front = malloc(alloc_size * sizeof(int16_t)); } else { free(src->front); src->front = src->back; } src->mask = render_is_audio_sync() ? 0xFFFFFFFF : alloc_size-1; src->read_start = 0; src->read_end = render_is_audio_sync() ? buffer_samples * src->num_channels : 0; src->buffer_pos = 0; } } uint8_t old_audio_sync; void render_audio_initialized(render_audio_format format, uint32_t rate, uint8_t channels, uint32_t buffer_size, int sample_size_in) { sample_rate = rate; output_channels = channels; buffer_samples = buffer_size; sample_size = sample_size_in; if (mix_buf) { free(mix_buf); mix_buf = NULL; } switch(format) { case RENDER_AUDIO_S16: convert = convert_s16; mix_buf = calloc(output_channels * buffer_samples, sizeof(float)); break; case RENDER_AUDIO_FLOAT: convert = clamp_f32; break; case RENDER_AUDIO_UNKNOWN: convert = convert_null; mix_buf = calloc(output_channels * buffer_samples, sizeof(float)); break; } uint32_t syncs = render_audio_syncs_per_sec(); if (syncs) { sync_samples = rate / syncs; } else { sync_samples = buffer_samples; } char * gain_str = tern_find_path(config, "audio\0gain\0", TVAL_PTR).ptrval; overall_gain_mult = db_to_mult(gain_str ? atof(gain_str) : 0.0f); uint8_t sync_changed = old_audio_sync != render_is_audio_sync(); old_audio_sync = render_is_audio_sync(); double lowpass_cutoff = get_lowpass_cutoff(config); double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); render_lock_audio(); for (uint8_t i = 0; i < num_audio_sources; i++) { update_source(audio_sources[i], rc, sync_changed); } render_unlock_audio(); for (uint8_t i = 0; i < num_inactive_audio_sources; i++) { update_source(inactive_audio_sources[i], rc, sync_changed); } }