diff --git a/audio/filters/Makefile b/audio/filters/Makefile new file mode 100644 index 0000000000..5d2eddf639 --- /dev/null +++ b/audio/filters/Makefile @@ -0,0 +1,91 @@ +compiler := gcc +extra_flags := +use_neon := 0 +release := release +DYLIB := so + +ifeq ($(platform),) +platform = unix +ifeq ($(shell uname -a),) + platform = win +else ifneq ($(findstring MINGW,$(shell uname -a)),) + platform = win +else ifneq ($(findstring Darwin,$(shell uname -a)),) + platform = osx + arch = intel +ifeq ($(shell uname -p),powerpc) + arch = ppc +endif +else ifneq ($(findstring win,$(shell uname -a)),) + platform = win +endif +endif + +ifeq ($(platform),gcc) +extra_rules_gcc := $(shell $(compiler) -dumpmachine) +endif + +ifneq (,$(findstring armv7,$(extra_rules_gcc))) +extra_flags += -mcpu=cortex-a9 -mtune=cortex-a9 -mfpu=neon +use_neon := 1 +endif + +ifneq (,$(findstring hardfloat,$(extra_rules_gcc))) +extra_flags += -mfloat-abi=hard +endif + +ifeq (release,$(build)) +extra_flags += -O2 +endif + +ifeq (debug,$(build)) +extra_flags += -O0 -g +endif + +ldflags := -shared -Wl,--version-script=link.T + +ifeq ($(platform), unix) +DYLIB = so +else ifeq ($(platform), osx) +compiler := $(CC) +DYLIB = dylib +ldflags := -dynamiclib +else +extra_flags += -static-libgcc -static-libstdc++ +DYLIB = dll +endif + +CC := $(compiler) +CXX := $(subst CC,++,$(compiler)) -std=gnu++0x +flags := -fPIC $(extra_flags) +asflags := -fPIC $(extra_flags) +objects := +flags += -std=c99 + + +ifeq (1,$(use_neon)) +ASMFLAGS := -INEON/asm +asflags += -mfpu=neon +endif + +objects += eq.$(DYLIB) iir.$(DYLIB) phaser.$(DYLIB) reverb.$(DYLIB) volume.$(DYLIB) wah.$(DYLIB) + +all: build; + +%.o: %.S + $(CC) -c -o $@ $(asflags) $(ASMFLAGS) $< + +%.o: %.c + $(CC) -c -o $@ $(flags) $< + +%.$(DYLIB): %.o + $(CC) -o $@ $(ldflags) $(flags) $^ + +build: $(objects) + +clean: + rm -f *.o + rm -f *.$(DYLIB) + +strip: + strip -s *.$(DYLIB) diff --git a/audio/filters/echo_sse.c b/audio/filters/echo_sse.c new file mode 100644 index 0000000000..bfb9989e43 --- /dev/null +++ b/audio/filters/echo_sse.c @@ -0,0 +1,206 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2014 - Daniel De Matteis + * + * RetroArch 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include "rarch_dsp.h" + +#include + +// 4 source echo. + +#ifdef __GNUC__ +#define ALIGNED __attribute__((aligned(16))) +#else +#define ALIGNED +#endif + +#define ECHO_MS 150 +#define AMP 0.0 + +struct echo_filter_data +{ + float echo_buffer[4][0x10000] ALIGNED; + float buffer[4096] ALIGNED; + float scratch_buf[4] ALIGNED; + + unsigned buf_size[4]; + unsigned ptr[4]; + + unsigned scratch_ptr; + __m128 amp[4] ALIGNED; + __m128 feedback ALIGNED; + float input_rate; +}; + +void echo_init(void *data) +{ + unsigned i, j; + struct echo_filter_data *echo = (struct echo_filter_data*)data; + + for (i = 0; i < 4; i++) + { + echo->ptr[i] = 0.0f; + echo->amp[i] = _mm_set1_ps(AMP); + } + + echo->scratch_ptr = 0; + echo->feedback = _mm_set1_ps(0.0f); + echo->input_rate = 32000.0; + + for (i = 0; i < 4; i++) + { + echo->scratch_buf[i] = 0.0f; + + for (j = 0; j < 0x10000; j++) + echo->echo_buffer[i][j] = 0.0f; + } + + for (i = 0; i < 4096; i++) + echo->buffer[i] = 0.0f; +} + +unsigned echo_sse2_process(void *data, const float *input, unsigned frames) +{ + unsigned frames_out, i; + float *buffer_out; + struct echo_filter_data *echo = (struct echo_filter_data*)data; + + frames_out = 0; + buffer_out = echo->buffer; + + __m128 amp[4] = { + echo->amp[0], + echo->amp[1], + echo->amp[2], + echo->amp[3], + }; + + __m128 feedback = echo->feedback; + +#define DO_FILTER() \ + __m128 result[4]; \ + __m128 echo_[4]; \ + for (i = 0; i < 4; i++) \ + { \ + echo_[i] = _mm_load_ps(echo->echo_buffer[i] + echo->ptr[i]); \ + result[i] = _mm_mul_ps(amp[i], echo_[i]); \ + } \ + __m128 final_result = _mm_add_ps(_mm_add_ps(result[0], result[1]), _mm_add_ps(result[2], result[3])); \ + __m128 feedback_result = _mm_mul_ps(feedback, final_result); \ + final_result = _mm_add_ps(reg, final_result); \ + feedback_result = _mm_add_ps(reg, feedback_result); \ + for (i = 0; i < 4; i++) \ + _mm_store_ps(echo->echo_buffer[i] + echo->ptr[i], feedback_result); \ + _mm_store_ps(buffer_out, final_result); \ + for (i = 0; i < 4; i++) \ + echo->ptr[i] = (echo->ptr[i] + 4) % echo->buf_size[i] + + + // Fill up scratch buffer and flush. + if (echo->scratch_ptr) + { + for (i = echo->scratch_ptr; i < 4; i += 2) + { + echo->scratch_buf[i] = *input++; + echo->scratch_buf[i + 1] = *input++; + frames--; + } + + echo->scratch_ptr = 0; + + __m128 reg = _mm_load_ps(echo->scratch_buf); + + DO_FILTER(); + + frames_out += 2; + buffer_out += 4; + } + + // Main processing. + for (i = 0; (i + 4) <= (frames * 2); i += 4, input += 4, buffer_out += 4, frames_out += 2) + { + __m128 reg = _mm_loadu_ps(input); // Might not be aligned. + DO_FILTER(); + } + + // Flush rest to scratch buffer. + for (; i < (frames * 2); i++) + echo->scratch_buf[echo->scratch_ptr++] = *input++; + + return frames_out; +} + +static void dsp_process(void *data, rarch_dsp_output_t *output, + const rarch_dsp_input_t *input) +{ + struct echo_filter_data *echo = (struct echo_filter_data*)data; + output->samples = echo->buffer; + output->frames = echo_sse2_process(echo, input->samples, input->frames); +} + +static void dsp_free(void *data) +{ + struct echo_filter_data *echo = (struct echo_filter_data*)data; + + if (echo) + free(echo); +} + +static void *dsp_init(const rarch_dsp_info_t *info) +{ + struct echo_filter_data *echo = (struct echo_filter_data*)calloc(1, sizeof(*echo));; + + if (!echo) + return NULL; + + for (unsigned i = 0; i < 4; i++) + echo->buf_size[i] = ECHO_MS * (info->input_rate * 2) / 1000; + + echo_init(echo); + + echo->input_rate = info->input_rate; + + fprintf(stderr, "[Echo] loaded!\n"); + + return echo; +} + +static void dsp_config(void *data) +{ + (void)data; +} + +static const rarch_dsp_plugin_t dsp_plug = { + dsp_init, + dsp_process, + dsp_free, + RARCH_DSP_API_VERSION, + dsp_config, + "Echo plugin (SSE2)" +}; + +RARCH_API_EXPORT const rarch_dsp_plugin_t* RARCH_API_CALLTYPE + rarch_dsp_plugin_init(void) +{ + return &dsp_plug; +} + diff --git a/audio/filters/echo_sse.cpp b/audio/filters/echo_sse.cpp new file mode 100644 index 0000000000..e80b89fdc2 --- /dev/null +++ b/audio/filters/echo_sse.cpp @@ -0,0 +1,193 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2014 - Daniel De Matteis + * + * RetroArch 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include "rarch_dsp.h" +#include +#include +#include + +#include + +// 4 source echo. + +#define ALIGNED __attribute__((aligned(16))) // Should use C++11 alignas(), but doesn't seem to work :( + +#define ECHO_MS 150 +#define AMP 0.0 + +struct EchoFilter +{ + float echo_buffer[4][0x10000] ALIGNED; + float buffer[4096] ALIGNED; + float scratch_buf[4] ALIGNED; + + unsigned buf_size[4]; + unsigned ptr[4]; + + unsigned scratch_ptr; + __m128 amp[4] ALIGNED; + __m128 feedback ALIGNED; + float input_rate; + + EchoFilter() + { + unsigned i, j; + + for (i = 0; i < 4; i++) + { + ptr[i] = 0.0f; + amp[i] = _mm_set1_ps(AMP); + } + + scratch_ptr = 0; + feedback = _mm_set1_ps(0.0f); + + input_rate = 32000.0; + + for (i = 0; i < 4; i++) + { + scratch_buf[i] = 0.0f; + + for (j = 0; j < 0x10000; j++) + echo_buffer[i][j] = 0.0f; + } + for (i = 0; i < 4096; i++) + buffer[i] = 0.0f; + } + + ~EchoFilter() + { + } + + unsigned Process(const float *input, unsigned frames) + { + unsigned frames_out = 0; + float *buffer_out = buffer; + + __m128 amp[4] = { + this->amp[0], + this->amp[1], + this->amp[2], + this->amp[3], + }; + + __m128 feedback = this->feedback; + +#define DO_FILTER() \ + __m128 result[4]; \ + __m128 echo_[4]; \ + for (unsigned i = 0; i < 4; i++) \ + { \ + echo_[i] = _mm_load_ps(echo_buffer[i] + ptr[i]); \ + result[i] = _mm_mul_ps(amp[i], echo_[i]); \ + } \ + __m128 final_result = _mm_add_ps(_mm_add_ps(result[0], result[1]), _mm_add_ps(result[2], result[3])); \ + __m128 feedback_result = _mm_mul_ps(feedback, final_result); \ + final_result = _mm_add_ps(reg, final_result); \ + feedback_result = _mm_add_ps(reg, feedback_result); \ + for (unsigned i = 0; i < 4; i++) \ + _mm_store_ps(echo_buffer[i] + ptr[i], feedback_result); \ + _mm_store_ps(buffer_out, final_result); \ + for (unsigned i = 0; i < 4; i++) \ + ptr[i] = (ptr[i] + 4) % buf_size[i] + + + // Fill up scratch buffer and flush. + if (scratch_ptr) + { + for (unsigned i = scratch_ptr; i < 4; i += 2) + { + scratch_buf[i] = *input++; + scratch_buf[i + 1] = *input++; + frames--; + } + + scratch_ptr = 0; + + __m128 reg = _mm_load_ps(scratch_buf); + + DO_FILTER(); + + frames_out += 2; + buffer_out += 4; + } + + // Main processing. + unsigned i; + for (i = 0; (i + 4) <= (frames * 2); i += 4, input += 4, buffer_out += 4, frames_out += 2) + { + __m128 reg = _mm_loadu_ps(input); // Might not be aligned. + DO_FILTER(); + } + + // Flush rest to scratch buffer. + for (; i < (frames * 2); i++) + scratch_buf[scratch_ptr++] = *input++; + + return frames_out; + } +}; + +static void dsp_process(void *data, rarch_dsp_output_t *output, + const rarch_dsp_input_t *input) +{ + EchoFilter *echo = reinterpret_cast(data); + output->samples = echo->buffer; + + output->frames = echo->Process(input->samples, input->frames); +} + +static void dsp_free(void *data) +{ + delete reinterpret_cast(data); +} + +static void *dsp_init(const rarch_dsp_info_t *info) +{ + EchoFilter *echo = new EchoFilter; + + echo->input_rate = info->input_rate; + + for (unsigned i = 0; i < 4; i++) + echo->buf_size[i] = ECHO_MS * (info->input_rate * 2) / 1000; + + fprintf(stderr, "[Echo] loaded!\n"); + + return echo; +} + +static void dsp_config(void *) +{} + +static const rarch_dsp_plugin_t dsp_plug = { + dsp_init, + dsp_process, + dsp_free, + RARCH_DSP_API_VERSION, + dsp_config, + "Echo plugin (SSE2)" +}; + +RARCH_API_EXPORT const rarch_dsp_plugin_t* RARCH_API_CALLTYPE + rarch_dsp_plugin_init(void) { return &dsp_plug; } + diff --git a/audio/filters/eq.c b/audio/filters/eq.c new file mode 100644 index 0000000000..37b35d328c --- /dev/null +++ b/audio/filters/eq.c @@ -0,0 +1,413 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2014 - Daniel De Matteis + * + * RetroArch 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + * + */ + +#include "rarch_dsp.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265 +#endif + +#ifndef EQ_COEFF_SIZE +#define EQ_COEFF_SIZE 256 +#endif + +#ifndef EQ_FILT_SIZE +#define EQ_FILT_SIZE (EQ_COEFF_SIZE * 2) +#endif + +typedef struct dsp_eq_state dsp_eq_state_t; + +static complex float phase_lut[2 * EQ_FILT_SIZE + 1]; +static complex float * const phase_lut_ptr = phase_lut + EQ_FILT_SIZE; + +static void generate_phase_lut(void) +{ + int i; + for (i = -EQ_FILT_SIZE; i <= EQ_FILT_SIZE; i++) + { + float phase = (float)i / EQ_FILT_SIZE; + phase_lut_ptr[i] = cexpf(M_PI * I * phase); + } +} + +static inline unsigned bitrange(unsigned len) +{ + unsigned ret = 0; + while ((len >>= 1)) + ret++; + + return ret; +} + +static inline unsigned bitswap(unsigned i, unsigned range) +{ + unsigned ret, shifts; + ret = 0; + for (shifts = 0; shifts < range; shifts++) + ret |= i & (1 << (range - shifts - 1)) ? (1 << shifts) : 0; + + return ret; +} + +// When interleaving the butterfly buffer, addressing puts bits in reverse. +// [0, 1, 2, 3, 4, 5, 6, 7] => [0, 4, 2, 6, 1, 5, 3, 7] +static void interleave(complex float *butterfly_buf, size_t samples) +{ + unsigned range, i, target; + range = bitrange(samples); + for (i = 0; i < samples; i++) + { + target = bitswap(i, range); + if (target > i) + { + complex float tmp = butterfly_buf[target]; + butterfly_buf[target] = butterfly_buf[i]; + butterfly_buf[i] = tmp; + } + } +} + +static void butterfly(complex float *a, complex float *b, complex float mod) +{ + complex float a_, b_; + mod *= *b; + a_ = *a + mod; + b_ = *a - mod; + *a = a_; + *b = b_; +} + +static void butterflies(complex float *butterfly_buf, int phase_dir, size_t step_size, size_t samples) +{ + unsigned i, j; + int phase_step; + for (i = 0; i < samples; i += 2 * step_size) + { + phase_step = EQ_FILT_SIZE * phase_dir / (int)step_size; + for (j = i; j < i + step_size; j++) + butterfly(&butterfly_buf[j], &butterfly_buf[j + step_size], phase_lut_ptr[phase_step * (int)(j - i)]); + } +} + +static void calculate_fft_butterfly(complex float *butterfly_buf, size_t samples) +{ + unsigned step_size; + // Interleave buffer to work with FFT. + interleave(butterfly_buf, samples); + + // Fly, lovely butterflies! :D + for (step_size = 1; step_size < samples; step_size *= 2) + butterflies(butterfly_buf, -1, step_size, samples); +} + +static void calculate_fft(const float *data, complex float *butterfly_buf, size_t samples) +{ + unsigned i, step_size; + for (i = 0; i < samples; i++) + butterfly_buf[i] = data[i]; + + // Interleave buffer to work with FFT. + interleave(butterfly_buf, samples); + + // Fly, lovely butterflies! :D + for (step_size = 1; step_size < samples; step_size *= 2) + butterflies(butterfly_buf, -1, step_size, samples); +} + +static void calculate_ifft(complex float *butterfly_buf, size_t samples) +{ + unsigned step_size, i; + float factor; + + // Interleave buffer to work with FFT. + interleave(butterfly_buf, samples); + + // Fly, lovely butterflies! In opposite direction! :D + for (step_size = 1; step_size < samples; step_size *= 2) + butterflies(butterfly_buf, 1, step_size, samples); + + factor = 1.0 / samples; + for (i = 0; i < samples; i++) + butterfly_buf[i] *= factor; +} + +struct eq_band +{ + float gain; + unsigned min_bin; + unsigned max_bin; +}; + +struct dsp_eq_state +{ + struct eq_band *bands; + unsigned num_bands; + + complex float fft_coeffs[EQ_FILT_SIZE]; + float cosine_window[EQ_COEFF_SIZE]; + + float last_buf[EQ_COEFF_SIZE]; + float stage_buf[EQ_FILT_SIZE]; + unsigned stage_ptr; +}; + +static void calculate_band_range(struct eq_band *band, float norm_freq) +{ + unsigned max_bin = (unsigned)round(norm_freq * EQ_COEFF_SIZE); + + band->gain = 1.0; + band->max_bin = max_bin; +} + +static void recalculate_fft_filt(dsp_eq_state_t *eq) +{ + unsigned i, j, start, end; + complex float freq_response[EQ_FILT_SIZE] = {0.0f}; + + for (i = 0; i < eq->num_bands; i++) + { + for (j = eq->bands[i].min_bin; j <= eq->bands[i].max_bin; j++) + freq_response[j] = eq->bands[i].gain; + } + + memset(eq->fft_coeffs, 0, sizeof(eq->fft_coeffs)); + + for (start = 1, end = EQ_COEFF_SIZE - 1; start < EQ_COEFF_SIZE / 2; start++, end--) + freq_response[end] = freq_response[start]; + + calculate_ifft(freq_response, EQ_COEFF_SIZE); + + // ifftshift(). Needs to be done for some reason ... TODO: Figure out why :D + memcpy(eq->fft_coeffs + EQ_COEFF_SIZE / 2, freq_response + 0, EQ_COEFF_SIZE / 2 * sizeof(complex float)); + memcpy(eq->fft_coeffs + 0, freq_response + EQ_COEFF_SIZE / 2, EQ_COEFF_SIZE / 2 * sizeof(complex float)); + + for (i = 0; i < EQ_COEFF_SIZE; i++) + eq->fft_coeffs[i] *= eq->cosine_window[i]; + + calculate_fft_butterfly(eq->fft_coeffs, EQ_FILT_SIZE); +} + +static void dsp_eq_free(dsp_eq_state_t *eq) +{ + if (eq) + { + if (eq->bands) + free(eq->bands); + free(eq); + } +} + +static dsp_eq_state_t *dsp_eq_new(float input_rate, const float *bands, unsigned num_bands) +{ + unsigned i; + dsp_eq_state_t *state; + + for (i = 1; i < num_bands; i++) + { + if (bands[i] <= bands[i - 1]) + return NULL; + } + + if (num_bands < 2) + return NULL; + + state = (dsp_eq_state_t*)calloc(1, sizeof(*state)); + if (!state) + return NULL; + + state->num_bands = num_bands; + + state->bands = (struct eq_band*)calloc(num_bands, sizeof(struct eq_band)); + if (!state->bands) + goto error; + + calculate_band_range(&state->bands[0], ((bands[0] + bands[1]) / 2.0) / input_rate); + state->bands[0].min_bin = 0; + + for (i = 1; i < num_bands - 1; i++) + { + calculate_band_range(&state->bands[i], ((bands[i + 1] + bands[i + 0]) / 2.0) / input_rate); + state->bands[i].min_bin = state->bands[i - 1].max_bin + 1; + + if (state->bands[i].max_bin < state->bands[i].min_bin) + fprintf(stderr, "[Equalizer]: Band @ %.2f Hz does not have enough spectral resolution to fit.\n", bands[i]); + } + + state->bands[num_bands - 1].max_bin = EQ_COEFF_SIZE / 2; + state->bands[num_bands - 1].min_bin = state->bands[num_bands - 2].max_bin + 1; + state->bands[num_bands - 1].gain = 1.0f; + + for (i = 0; i < EQ_COEFF_SIZE; i++) + state->cosine_window[i] = cosf(M_PI * (i + 0.5 - EQ_COEFF_SIZE / 2) / EQ_COEFF_SIZE); + + generate_phase_lut(); + recalculate_fft_filt(state); + + return state; + +error: + dsp_eq_free(state); + return NULL; +} + +#if 0 +static void dsp_eq_set_gain(dsp_eq_state_t *eq, unsigned band, float gain) +{ + assert(band < eq->num_bands); + + eq->bands[band].gain = gain; + recalculate_fft_filt(eq); +} +#endif + +static size_t dsp_eq_process(dsp_eq_state_t *eq, float *output, size_t out_samples, + const float *input, size_t in_samples, unsigned stride) +{ + size_t written = 0; + while (in_samples) + { + unsigned i; + size_t to_read = EQ_COEFF_SIZE - eq->stage_ptr; + + if (to_read > in_samples) + to_read = in_samples; + + for (i = 0; i < to_read; i++, input += stride) + eq->stage_buf[eq->stage_ptr + i] = *input; + + in_samples -= to_read; + eq->stage_ptr += to_read; + + if (eq->stage_ptr >= EQ_COEFF_SIZE) + { + complex float butterfly_buf[EQ_FILT_SIZE]; + if (out_samples < EQ_COEFF_SIZE) + return written; + + calculate_fft(eq->stage_buf, butterfly_buf, EQ_FILT_SIZE); + for (i = 0; i < EQ_FILT_SIZE; i++) + butterfly_buf[i] *= eq->fft_coeffs[i]; + + calculate_ifft(butterfly_buf, EQ_FILT_SIZE); + + for (i = 0; i < EQ_COEFF_SIZE; i++, output += stride, out_samples--, written++) + *output = crealf(butterfly_buf[i]) + eq->last_buf[i]; + + for (i = 0; i < EQ_COEFF_SIZE; i++) + eq->last_buf[i] = crealf(butterfly_buf[i + EQ_COEFF_SIZE]); + + eq->stage_ptr = 0; + } + } + + return written; +} + + +#if 0 +static float db2gain(float val) +{ + return powf(10.0, val / 20.0); +} + +static float noise(void) +{ + return 2.0 * ((float)(rand()) / RAND_MAX - 0.5); +} +#endif + +struct equalizer_filter_data +{ + dsp_eq_state_t *eq_l; + dsp_eq_state_t *eq_r; + float out_buffer[8092]; +}; + +static size_t equalizer_process(void *data, const float *in, unsigned frames) +{ + struct equalizer_filter_data *eq = (struct equalizer_filter_data*)data; + + size_t written = dsp_eq_process(eq->eq_l, eq->out_buffer + 0, 4096, in + 0, frames, 2); + dsp_eq_process(eq->eq_r, eq->out_buffer + 1, 4096, in + 1, frames, 2); + + return written; +} + +static void *dsp_init(const rarch_dsp_info_t *info) +{ + const float bands[] = { 30, 80, 150, 250, 500, 800, 1000, 2000, 3000, 5000, 8000, 10000, 12000, 15000 }; + struct equalizer_filter_data *eq = (struct equalizer_filter_data*)calloc(1, sizeof(*eq)); + + if (!eq) + return NULL; + + eq->eq_l = dsp_eq_new(info->input_rate, bands, sizeof(bands) / sizeof(bands[0])); + eq->eq_r = dsp_eq_new(info->input_rate, bands, sizeof(bands) / sizeof(bands[0])); + + return eq; +} + +static void dsp_process(void *data, rarch_dsp_output_t *output, + const rarch_dsp_input_t *input) +{ + struct equalizer_filter_data *eq = (struct equalizer_filter_data*)data; + + output->samples = eq->out_buffer; + size_t out_frames = equalizer_process(eq, input->samples, input->frames); + output->frames = out_frames; +} + +static void dsp_free(void *data) +{ + struct equalizer_filter_data *eq = (struct equalizer_filter_data*)data; + + if (eq) + { + dsp_eq_free(eq->eq_l); + dsp_eq_free(eq->eq_r); + free(eq); + } +} + +static void dsp_config(void *data) +{ + (void)data; +} + +const rarch_dsp_plugin_t dsp_plug = { + dsp_init, + dsp_process, + dsp_free, + RARCH_DSP_API_VERSION, + dsp_config, + "Equalizer" +}; + +RARCH_API_EXPORT const rarch_dsp_plugin_t* RARCH_API_CALLTYPE rarch_dsp_plugin_init(void) +{ + return &dsp_plug; +} + diff --git a/audio/filters/iir.c b/audio/filters/iir.c new file mode 100644 index 0000000000..0cdcc8e080 --- /dev/null +++ b/audio/filters/iir.c @@ -0,0 +1,381 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2014 - Daniel De Matteis + * Copyright (C) 2012-2014 - Brad Miller + * + * RetroArch 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + * + */ + +#include "rarch_dsp.h" +#include +#include +#include +#include +#include + +#ifdef __SSE2__ +#include +#endif + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795 +#endif + +#define sqr(a) ((a) * (a)) + +struct iir_filter +{ +#ifdef __SSE2__ + __m128 fir_coeff[2]; + __m128 fir_buf[2]; + + __m128 iir_coeff; + __m128 iir_buf; +#endif + float pf_freq, pf_qfact, pf_gain; + int type, pf_q_is_bandwidth; + float xn1,xn2,yn1,yn2; + float omega, cs, a1pha, beta, b0, b1, b2, a0, a1,a2, A, sn; +}; + +struct iir_filter_data +{ + struct iir_filter iir_l __attribute__((aligned(16))); + struct iir_filter iir_r __attribute__((aligned(16))); + float buf[4096] __attribute__((aligned(16))); + int rate; + unsigned type; +}; + +/* filter types */ +enum +{ + LPF, /* low pass filter */ + HPF, /* High pass filter */ + BPCSGF,/* band pass filter 1 */ + BPZPGF,/* band pass filter 2 */ + APF, /* Allpass filter*/ + NOTCH, /* Notch Filter */ + RIAA_phono, /* RIAA record/tape deemphasis */ + PEQ, /* Peaking band EQ filter */ + BBOOST, /* Bassboost filter */ + LSH, /* Low shelf filter */ + HSH, /* High shelf filter */ + RIAA_CD /* CD de-emphasis */ +}; + +//lynched from SoX >w> +static void iir_make_poly_from_roots(double const * roots, size_t num_roots, float * poly) +{ + size_t i, j; + poly[0] = 1; + poly[1] = -roots[0]; + memset(poly + 2, 0, (num_roots + 1 - 2) * sizeof(*poly)); + for (i = 1; i < num_roots; ++i) + for (j = num_roots; j > 0; --j) + poly[j] -= poly[j - 1] * roots[i]; +} + +static void iir_init(void *data, int samplerate, int filter_type) +{ + struct iir_filter *iir = (struct iir_filter*)data; + + if (!iir) + return; + + iir->xn1=0; + iir->xn2=0; + iir->yn1=0; + iir->yn2=0; + iir->omega = 2 * M_PI * iir->pf_freq/samplerate; + iir->cs = cos(iir->omega); + iir->sn = sin(iir->omega); + iir->a1pha = iir->sn / (2.0 * iir->pf_qfact); + iir->A = exp(log(10.0) * iir->pf_gain / 40); + iir->beta = sqrt(iir->A + iir->A); + //Set up filter coefficients according to type + switch (filter_type) + { + case LPF: + iir->b0 = (1.0 - iir->cs) / 2.0 ; + iir->b1 = 1.0 - iir->cs ; + iir->b2 = (1.0 - iir->cs) / 2.0 ; + iir->a0 = 1.0 + iir->a1pha ; + iir->a1 = -2.0 * iir->cs ; + iir->a2 = 1.0 - iir->a1pha ; + break; + case HPF: + iir->b0 = (1.0 + iir->cs) / 2.0 ; + iir->b1 = -(1.0 + iir->cs) ; + iir->b2 = (1.0 + iir->cs) / 2.0 ; + iir->a0 = 1.0 + iir->a1pha ; + iir->a1 = -2.0 * iir->cs ; + iir->a2 = 1.0 - iir->a1pha ; + break; + case APF: + iir->b0 = 1.0 - iir->a1pha; + iir->b1 = -2.0 * iir->cs; + iir->b2 = 1.0 + iir->a1pha; + iir->a0 = 1.0 + iir->a1pha; + iir->a1 = -2.0 * iir->cs; + iir->a2 = 1.0 - iir->a1pha; + break; + case BPZPGF: + iir->b0 = iir->a1pha ; + iir->b1 = 0.0 ; + iir->b2 = -iir->a1pha ; + iir->a0 = 1.0 + iir->a1pha ; + iir->a1 = -2.0 * iir->cs ; + iir->a2 = 1.0 - iir->a1pha ; + break; + case BPCSGF: + iir->b0=iir->sn/2.0; + iir->b1=0.0; + iir->b2=-iir->sn/2; + iir->a0=1.0+iir->a1pha; + iir->a1=-2.0*iir->cs; + iir->a2=1.0-iir->a1pha; + break; + case NOTCH: + iir->b0 = 1; + iir->b1 = -2 * iir->cs; + iir->b2 = 1; + iir->a0 = 1 + iir->a1pha; + iir->a1 = -2 * iir->cs; + iir->a2 = 1 - iir->a1pha; + break; + case RIAA_phono: /* http://www.dsprelated.com/showmessage/73300/3.php */ + if (samplerate == 44100) { + static const double zeros[] = {-0.2014898, 0.9233820}; + static const double poles[] = {0.7083149, 0.9924091}; + iir_make_poly_from_roots(zeros, (size_t)2, &iir->b0); + iir_make_poly_from_roots(poles, (size_t)2, &iir->a0); + } + else if (samplerate == 48000) { + static const double zeros[] = {-0.1766069, 0.9321590}; + static const double poles[] = {0.7396325, 0.9931330}; + iir_make_poly_from_roots(zeros, (size_t)2, &iir->b0); + iir_make_poly_from_roots(poles, (size_t)2, &iir->a0); + } + else if (samplerate == 88200) { + static const double zeros[] = {-0.1168735, 0.9648312}; + static const double poles[] = {0.8590646, 0.9964002}; + iir_make_poly_from_roots(zeros, (size_t)2, &iir->b0); + iir_make_poly_from_roots(poles, (size_t)2, &iir->a0); + } + else if (samplerate == 96000) { + static const double zeros[] = {-0.1141486, 0.9676817}; + static const double poles[] = {0.8699137, 0.9966946}; + iir_make_poly_from_roots(zeros, (size_t)2, &iir->b0); + iir_make_poly_from_roots(poles, (size_t)2, &iir->a0); + } + { /* Normalise to 0dB at 1kHz (Thanks to Glenn Davis) */ + double y = 2 * M_PI * 1000 / samplerate ; + double b_re = iir->b0 + iir->b1 * cos(-y) + iir->b2 * cos(-2 * y); + double a_re = iir->a0 + iir->a1 * cos(-y) + iir->a2 * cos(-2 * y); + double b_im = iir->b1 * sin(-y) + iir->b2 * sin(-2 * y); + double a_im = iir->a1 * sin(-y) + iir->a2 * sin(-2 * y); + double g = 1 / sqrt((sqr(b_re) + sqr(b_im)) / (sqr(a_re) + sqr(a_im))); + iir->b0 *= g; + iir->b1 *= g; + iir->b2 *= g; + } + break; + case PEQ: + iir->b0 = 1 + iir->a1pha * iir->A ; + iir->b1 = -2 * iir->cs ; + iir->b2 = 1 - iir->a1pha * iir->A ; + iir->a0 = 1 + iir->a1pha / iir->A ; + iir->a1 = -2 * iir->cs ; + iir->a2 = 1 - iir->a1pha / iir->A ; + break; + case BBOOST: + iir->beta = sqrt((iir->A * iir->A + 1) / 1.0 - (pow((iir->A - 1), 2))); + iir->b0 = iir->A * ((iir->A + 1) - (iir->A - 1) * iir->cs + iir->beta * iir->sn); + iir->b1 = 2 * iir->A * ((iir->A - 1) - (iir->A + 1) * iir->cs); + iir->b2 = iir->A * ((iir->A + 1) - (iir->A - 1) * iir->cs - iir->beta * iir->sn); + iir->a0 = ((iir->A + 1) + (iir->A - 1) * iir->cs + iir->beta * iir->sn); + iir->a1 = -2 * ((iir->A - 1) + (iir->A + 1) * iir->cs); + iir->a2 = (iir->A + 1) + (iir->A - 1) * iir->cs - iir->beta * iir->sn; + break; + case LSH: + iir->b0 = iir->A * ((iir->A + 1) - (iir->A - 1) * iir->cs + iir->beta * iir->sn); + iir->b1 = 2 * iir->A * ((iir->A - 1) - (iir->A + 1) * iir->cs); + iir->b2 = iir->A * ((iir->A + 1) - (iir->A - 1) * iir->cs - iir->beta * iir->sn); + iir->a0 = (iir->A + 1) + (iir->A - 1) * iir->cs + iir->beta * iir->sn; + iir->a1 = -2 * ((iir->A - 1) + (iir->A + 1) * iir->cs); + iir->a2 = (iir->A + 1) + (iir->A - 1) * iir->cs - iir->beta * iir->sn; + break; + case RIAA_CD: + iir->omega = 2 * M_PI * 5283 / samplerate; + iir->cs = cos(iir->omega); + iir->sn = sin(iir->omega); + iir->a1pha = iir->sn / (2.0 * 0.4845); + iir->A = exp(log(10.0) * -9.477 / 40); + iir->beta = sqrt(iir->A + iir->A); + case HSH: + iir->b0 = iir->A * ((iir->A + 1) + (iir->A - 1) * iir->cs + iir->beta * iir->sn); + iir->b1 = -2 * iir->A * ((iir->A - 1) + (iir->A + 1) * iir->cs); + iir->b2 = iir->A * ((iir->A + 1) + (iir->A - 1) * iir->cs - iir->beta * iir->sn); + iir->a0 = (iir->A + 1) - (iir->A - 1) * iir->cs + iir->beta * iir->sn; + iir->a1 = 2 * ((iir->A - 1) - (iir->A + 1) * iir->cs); + iir->a2 = (iir->A + 1) - (iir->A - 1) * iir->cs - iir->beta * iir->sn; + break; + default: + break; + } + +#ifdef __SSE2__ + iir->fir_coeff[0] = _mm_set_ps(iir->b1 / iir->a0, iir->b1 / iir->a0, iir->b0 / iir->a0, iir->b0 / iir->a0); + iir->fir_coeff[1] = _mm_set_ps(0.0f, 0.0f, iir->b2 / iir->a0, iir->b2 / iir->a0); + iir->iir_coeff = _mm_set_ps(-iir->a2 / iir->a0, -iir->a2 / iir->a0, -iir->a1 / iir->a0, -iir->a1 / iir->a0); +#endif +} + +#ifdef __SSE2__ +static void iir_process_batch(void *data, float *out, const float *in, unsigned frames) +{ + struct iir_filter *iir = (struct iir_filter*)data; + + __m128 fir_coeff[2] = { iir->fir_coeff[0], iir->fir_coeff[1] }; + __m128 iir_coeff = iir->iir_coeff; + __m128 fir_buf[2] = { iir->fir_buf[0], iir->fir_buf[1] }; + __m128 iir_buf = iir->iir_buf; + + for (unsigned i = 0; (i + 4) <= (2 * frames); in += 4, i += 4, out += 4) + { + __m128 input = _mm_loadu_ps(in); + + fir_buf[1] = _mm_shuffle_ps(fir_buf[0], fir_buf[1], _MM_SHUFFLE(1, 0, 3, 2)); + fir_buf[0] = _mm_shuffle_ps(input, fir_buf[0], _MM_SHUFFLE(1, 0, 1, 0)); + + __m128 res[3] = { + _mm_mul_ps(fir_buf[0], fir_coeff[0]), + _mm_mul_ps(fir_buf[1], fir_coeff[1]), + _mm_mul_ps(iir_buf, iir_coeff), + }; + + __m128 result = _mm_add_ps(_mm_add_ps(res[0], res[1]), res[2]); + result = _mm_add_ps(result, _mm_shuffle_ps(result, result, _MM_SHUFFLE(0, 0, 3, 2))); + + iir_buf = _mm_shuffle_ps(result, iir_buf, _MM_SHUFFLE(1, 0, 1, 0)); + + fir_buf[1] = _mm_shuffle_ps(fir_buf[0], fir_buf[1], _MM_SHUFFLE(1, 0, 3, 2)); + fir_buf[0] = _mm_shuffle_ps(input, fir_buf[0], _MM_SHUFFLE(1, 0, 3, 2)); + + res[0] = _mm_mul_ps(fir_buf[0], fir_coeff[0]); + res[1] = _mm_mul_ps(fir_buf[1], fir_coeff[1]); + res[2] = _mm_mul_ps(iir_buf, iir_coeff); + + __m128 result2 = _mm_add_ps(_mm_add_ps(res[0], res[1]), res[2]); + result2 = _mm_add_ps(result2, _mm_shuffle_ps(result2, result2, _MM_SHUFFLE(0, 0, 3, 2))); + + iir_buf = _mm_shuffle_ps(result2, iir_buf, _MM_SHUFFLE(1, 0, 1, 0)); + + _mm_store_ps(out, _mm_shuffle_ps(result, result2, _MM_SHUFFLE(1, 0, 1, 0))); + } + + iir->fir_buf[0] = fir_buf[0]; + iir->fir_buf[1] = fir_buf[1]; + iir->iir_buf = iir_buf; +} +#else +static float iir_process(void *data, float samp) +{ + struct iir_filter *iir = (struct iir_filter*)data; + + float out, in = 0; + in = samp; + out = (iir->b0 * in + iir->b1 * iir->xn1 + iir->b2 * iir->xn2 - iir->a1 * iir->yn1 - iir->a2 * iir->yn2) / iir->a0; + iir->xn2 = iir->xn1; + iir->xn1 = in; + iir->yn2 = iir->yn1; + iir->yn1 = out; + return out; +} +#endif + +static void* dsp_init(const rarch_dsp_info_t *info) +{ + struct iir_filter_data *iir = (struct iir_filter_data*)calloc(1, sizeof(*iir)); + + if (!iir) + return NULL; + + iir->rate = info->input_rate; + iir->type = 0; + iir->iir_l.pf_freq = 1024; + iir->iir_l.pf_qfact = 0.707; + iir->iir_l.pf_gain = 0; + iir_init(&iir->iir_l, info->input_rate, 0); + iir->iir_r.pf_freq = 1024; + iir->iir_r.pf_qfact = 0.707; + iir->iir_r.pf_gain = 0; + iir_init(&iir->iir_r, info->input_rate, 0); + + return iir; +} + +static void dsp_process(void *data, rarch_dsp_output_t *output, + const rarch_dsp_input_t *input) +{ + struct iir_filter_data *iir = (struct iir_filter_data*)data; + + output->samples = iir->buf; + +#ifdef __SSE2__ + iir_process_batch(&iir->iir_l, iir->buf, input->samples, input->frames); +#else + int num_samples = input->frames * 2; + for (int i = 0; ibuf[i] = iir_process(&iir->iir_l, input->samples[i]); + i++; + iir->buf[i] = iir_process(&iir->iir_r, input->samples[i]); + i++; + } +#endif + + output->frames = input->frames; +} + +static void dsp_free(void *data) +{ + struct iir_filter_data *iir = (struct iir_filter_data*)data; + + if (iir) + free(iir); +} + +static void dsp_config(void* data) +{ +} + +const rarch_dsp_plugin_t dsp_plug = { + dsp_init, + dsp_process, + dsp_free, + RARCH_DSP_API_VERSION, + dsp_config, +#ifdef __SSE2__ + "IIR filter set (SSE2)" +#else + "IIR filter set" +#endif +}; + + +RARCH_API_EXPORT const rarch_dsp_plugin_t* RARCH_API_CALLTYPE rarch_dsp_plugin_init(void) +{ + return &dsp_plug; +} diff --git a/audio/filters/link.T b/audio/filters/link.T new file mode 100644 index 0000000000..478b8b5053 --- /dev/null +++ b/audio/filters/link.T @@ -0,0 +1,4 @@ +{ + global: softfilter_*; + local: *; +}; diff --git a/audio/filters/phaser.c b/audio/filters/phaser.c new file mode 100644 index 0000000000..5e922b584c --- /dev/null +++ b/audio/filters/phaser.c @@ -0,0 +1,184 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2014 - Daniel De Matteis + * Copyright (C) 2012-2014 - Brad Miller + * + * RetroArch 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + * + */ + +#include "rarch_dsp.h" +#include +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795 +#endif + +#define PHASERLFOSHAPE 4.0 +#define PHASER_LFOSKIPSAMPLES 20 + +struct phaser_filter +{ + float freq; + float startphase; + float fb; + int depth; + int stages; + int drywet; + unsigned long skipcount; + float old[24]; + float gain; + float fbout; + float lfoskip; + float phase; +}; + +struct phaser_filter_data +{ + struct phaser_filter phase_l; + struct phaser_filter phase_r; + float buf[4096]; +}; + +static void phaser_init(void *data, int samplerate) +{ + int j; + struct phaser_filter *phaser = (struct phaser_filter*)data; + + phaser->skipcount = 0; + phaser->gain = 0.0; + phaser->fbout = 0.0; + phaser->lfoskip = phaser->freq * 2 * M_PI / samplerate; + phaser->phase = phaser->startphase * M_PI / 180; + for (j = 0; j < phaser->stages; j++) + phaser->old[j] = 0; +} + +static float phaser_process(void *data, float in) +{ + float m, tmp, out; + int j; + struct phaser_filter *phaser = (struct phaser_filter*)data; + + m = in + phaser->fbout * phaser->fb / 100; + + if (((phaser->skipcount++) % PHASER_LFOSKIPSAMPLES) == 0) + { + phaser->gain = (1 + cos(phaser->skipcount * phaser->lfoskip + phaser->phase)) / 2; + phaser->gain =(exp(phaser->gain * PHASERLFOSHAPE) - 1) / (exp(PHASERLFOSHAPE)-1); + phaser->gain = 1 - phaser->gain / 255 * phaser->depth; + } + for (j = 0; j < phaser->stages; j++) + { + tmp = phaser->old[j]; + phaser->old[j] = phaser->gain * tmp + m; + m = tmp - phaser->gain * phaser->old[j]; + } + phaser->fbout = m; + out = (m * phaser->drywet + in * (255 - phaser->drywet)) / 255; + if (out < -1.0) out = -1.0; + if (out > 1.0) out = 1.0; + return out; +} + +static void* dsp_init(const rarch_dsp_info_t *info) +{ + float freq, startphase, fb; + int depth, stages, drywet; + struct phaser_filter_data *phaser; + + freq = 0.4; + startphase = 0; + fb = 0; + depth = 100; + stages = 2; + drywet = 128; + + phaser = (struct phaser_filter_data*)calloc(1, sizeof(*phaser)); + + if (!phaser) + return NULL; + + phaser->phase_l.freq = freq; + phaser->phase_l.startphase = startphase; + phaser->phase_l.fb = fb; + phaser->phase_l.depth = depth; + phaser->phase_l.stages = stages; + phaser->phase_l.drywet = drywet; + phaser_init(&phaser->phase_l, info->input_rate); + + phaser->phase_r.freq = freq; + phaser->phase_r.startphase = startphase; + phaser->phase_r.fb = fb; + phaser->phase_r.depth = depth; + phaser->phase_r.stages = stages; + phaser->phase_r.drywet = drywet; + phaser_init(&phaser->phase_r, info->input_rate); + + return phaser; +} + +static void dsp_process(void *data, rarch_dsp_output_t *output, + const rarch_dsp_input_t *input) +{ + int i, num_samples; + struct phaser_filter_data *phaser = (struct phaser_filter_data*)data; + + output->samples = phaser->buf; + num_samples = input->frames * 2; + for (i = 0; ibuf[i] = phaser_process(&phaser->phase_l, input->samples[i]); + i++; + phaser->buf[i] = phaser_process(&phaser->phase_r, input->samples[i]); + i++; + } + output->frames = input->frames; +} + +static void dsp_free(void *data) +{ + struct phaser_filter_data *phaser = (struct phaser_filter_data*)data; + + if (phaser) + { + int j; + for (j = 0; j < phaser->phase_l.stages; j++) + phaser->phase_l.old[j] = 0; + for (j = 0; j < phaser->phase_r.stages; j++) + phaser->phase_r.old[j] = 0; + free(phaser); + } +} + +static void dsp_config(void *data) +{ + (void)data; +} + +const rarch_dsp_plugin_t dsp_plug = { + dsp_init, + dsp_process, + dsp_free, + RARCH_DSP_API_VERSION, + dsp_config, + "Phaser plugin" +}; + +RARCH_API_EXPORT const rarch_dsp_plugin_t* RARCH_API_CALLTYPE rarch_dsp_plugin_init(void) +{ + return &dsp_plug; +} + diff --git a/audio/filters/rarch_dsp.h b/audio/filters/rarch_dsp.h new file mode 100644 index 0000000000..b54efaea31 --- /dev/null +++ b/audio/filters/rarch_dsp.h @@ -0,0 +1,125 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2014 - Daniel De Matteis + * + * RetroArch 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + * + */ + +#ifndef __RARCH_DSP_PLUGIN_H +#define __RARCH_DSP_PLUGIN_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 +#ifdef RARCH_DLL_IMPORT +#define RARCH_API_EXPORT __declspec(dllimport) +#else +#define RARCH_API_EXPORT __declspec(dllexport) +#endif +#define RARCH_API_CALLTYPE __cdecl +#else +#define RARCH_API_EXPORT +#define RARCH_API_CALLTYPE +#endif + +#ifndef RARCH_FALSE +#define RARCH_FALSE 0 +#endif + +#ifndef RARCH_TRUE +#define RARCH_TRUE 1 +#endif + +#define RARCH_DSP_API_VERSION 5 + +typedef struct rarch_dsp_info +{ + // Input sample rate that the DSP plugin receives. + float input_rate; +} rarch_dsp_info_t; + +typedef struct rarch_dsp_output +{ + // The DSP plugin has to provide the buffering for the output samples. + // This is for performance reasons to avoid redundant copying of data. + // The samples are laid out in interleaving order: LRLRLRLR + // The range of the samples are [-1.0, 1.0]. + // This range cannot be exceeded without horrible audio glitches. + const float *samples; + + // Frames which the DSP plugin outputted for the current process. + // One frame is here defined as a combined sample of + // left and right channels. + // (I.e. 44.1kHz, 16bit stereo will have + // 88.2k samples/sec and 44.1k frames/sec.) + unsigned frames; +} rarch_dsp_output_t; + +typedef struct rarch_dsp_input +{ + // Input data for the DSP. The samples are interleaved in order: LRLRLRLR + const float *samples; + + // Number of frames for input data. + // One frame is here defined as a combined sample of + // left and right channels. + // (I.e. 44.1kHz, 16bit stereo will have + // 88.2k samples/sec and 44.1k frames/sec.) + unsigned frames; +} rarch_dsp_input_t; + +typedef struct rarch_dsp_plugin +{ + // Creates a handle of the plugin. Returns NULL if failed. + void *(*init)(const rarch_dsp_info_t *info); + + // Processes input data. + // The plugin is allowed to return variable sizes for output data. + void (*process)(void *data, rarch_dsp_output_t *output, + const rarch_dsp_input_t *input); + + // Frees the handle. + void (*free)(void *data); + + // API version used to compile the plugin. + // Used to detect mismatches in API. + // Must be set to RARCH_DSP_API_VERSION on compile. + int api_version; + + // Signal plugin that it may open a configuring window or + // something similiar. The behavior of this function + // is thus plugin dependent. Implementing this is optional, + // and can be set to NULL. + void (*config)(void *data); + + // Human readable identification string. + const char *ident; + + // Called every frame, allows creating a GUI main loop in the main thread. + // GUI events can be processed here in a non-blocking fashion. + // Can be set to NULL to ignore it. + void (*events)(void *data); +} rarch_dsp_plugin_t; + +// Called by RetroArch at startup to get the callback struct. +// This is NOT dynamically allocated! +RARCH_API_EXPORT const rarch_dsp_plugin_t* RARCH_API_CALLTYPE + rarch_dsp_plugin_init(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/audio/filters/reverb.c b/audio/filters/reverb.c new file mode 100644 index 0000000000..2b6289c7e4 --- /dev/null +++ b/audio/filters/reverb.c @@ -0,0 +1,387 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2014 - Daniel De Matteis + * Copyright (C) 2012-2014 - Brad Miller + * + * RetroArch 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + * + */ + +#include "rarch_dsp.h" +#include +#include +#include + +#define NUMCOMBS 8 +#define NUMALLPASSES 4 +#define MUTED 0 +#define FIXEDGAIN 0.015f +#define SCALEWET 3 +#define SCALEDRY 2 +#define SCALEDAMP 0.4f +#define SCALEROOM 0.28f +#define OFFSETROOM 0.7f +#define INITIALROOM 0.5f +#define INITIALDAMP 0.5f +#define INITIALWET (1 / SCALEWET) +#define INITIALDRY 0 +#define INITIALWIDTH 1 +#define INITIALMODE 0 +#define FREEZEMODE 0.5f + +#define COMBTUNINGL1 1116 +#define COMBTUNINGL2 1188 +#define COMBTUNINGL3 1277 +#define COMBTUNINGL4 1356 +#define COMBTUNINGL5 1422 +#define COMBTUNINGL6 1491 +#define COMBTUNINGL7 1557 +#define COMBTUNINGL8 1617 +#define ALLPASSTUNINGL1 556 +#define ALLPASSTUNINGL2 441 +#define ALLPASSTUNINGL3 341 +#define ALLPASSTUNINGL4 225 + +struct comb +{ + float feedback; + float filterstore; + float damp1; + float damp2; + float *buffer; + int bufsize; + int bufidx; +}; + +struct allpass +{ + float feedback; + float *buffer; + int bufsize; + int bufidx; +}; + +struct revmodel +{ + float gain; + float roomsize, roomsize1; + float damp, damp1; + float wet, wet1, wet2; + float dry; + float width; + float mode; + + struct comb combL[NUMCOMBS]; + + struct allpass allpassL[NUMALLPASSES]; + + float bufcombL1[COMBTUNINGL1]; + float bufcombL2[COMBTUNINGL2]; + float bufcombL3[COMBTUNINGL3]; + float bufcombL4[COMBTUNINGL4]; + float bufcombL5[COMBTUNINGL5]; + float bufcombL6[COMBTUNINGL6]; + float bufcombL7[COMBTUNINGL7]; + float bufcombL8[COMBTUNINGL8]; + + float bufallpassL1[ALLPASSTUNINGL1]; + float bufallpassL2[ALLPASSTUNINGL2]; + float bufallpassL3[ALLPASSTUNINGL3]; + float bufallpassL4[ALLPASSTUNINGL4]; +}; + +// FIXME: Fix this really ugly hack +static inline float undenormalise(void *sample) +{ + if (((*(unsigned int*)sample) & 0x7f800000) == 0) + return 0.0f; + return *(float*)sample; +} + +static inline float comb_process(void *data, float input) +{ + struct comb *comb = (struct comb*)data; + float output; + + output = comb->buffer[comb->bufidx]; + undenormalise(&output); + + comb->filterstore = (output * comb->damp2) + (comb->filterstore * comb->damp1); + undenormalise(&comb->filterstore); + + comb->buffer[comb->bufidx] = input + (comb->filterstore * comb->feedback); + + if (++comb->bufidx >= comb->bufsize) + comb->bufidx = 0; + + return output; +} + + +static inline float allpass_process(void *data, float input) +{ + struct allpass *allpass = (struct allpass*)data; + float output, bufout; + + bufout = allpass->buffer[allpass->bufidx]; + undenormalise(&bufout); + + output = -input + bufout; + allpass->buffer[allpass->bufidx] = input + (bufout * allpass->feedback); + + if (++allpass->bufidx >= allpass->bufsize) + allpass->bufidx = 0; + + return output; +} + +static float revmodel_getmode(float mode) +{ + if (mode >= FREEZEMODE) + return 1; + else + return 0; +} + +static void revmodel_update(void *data) +{ + int i; + struct revmodel *rev = (struct revmodel*)data; + + rev->wet1 = rev->wet * (rev->width / 2 + 0.5f); + + if (rev->mode >= FREEZEMODE) + { + rev->roomsize1 = 1; + rev->damp1 = 0; + rev->gain = MUTED; + } + else + { + rev->roomsize1 = rev->roomsize; + rev->damp1 = rev->damp; + rev->gain = FIXEDGAIN; + } + + for (i = 0; i < NUMCOMBS; i++) + rev->combL[i].feedback = rev->roomsize1; + + for (i = 0; i < NUMCOMBS; i++) + { + rev->combL[i].damp1 = rev->damp1; + rev->combL[i].damp2 = 1 - rev->damp1; + } +} + +static void revmodel_set(void *data, float drytime, + float wettime, float damping, float roomwidth, float roomsize) +{ + int i, j; + struct revmodel *rev = (struct revmodel*)data; + + rev->wet = wettime; + revmodel_update(rev); + + rev->roomsize = roomsize; + revmodel_update(rev); + + rev->dry = drytime; + + rev->damp = damping; + revmodel_update(rev); + + rev->width = roomwidth; + revmodel_update(rev); + + rev->mode = INITIALMODE; + revmodel_update(rev); + + if (revmodel_getmode(rev->mode) >= FREEZEMODE) + return; + + for (i = 0; i < NUMCOMBS; i++) + { + for (j = 0; j < rev->combL[i].bufsize; j++) + rev->combL[i].buffer[j] = 0; + } + + for (i = 0; i < NUMALLPASSES; i++) + { + for (j = 0; j < rev->allpassL[i].bufsize; j++) + rev->allpassL[i].buffer[j] = 0; + } +} + +static void revmodel_init(void *data) +{ + struct revmodel *rev = (struct revmodel*)data; + + rev->combL[0].filterstore = 0; + rev->combL[0].bufidx = 0; + rev->combL[0].buffer = (float*)rev->bufcombL1; + rev->combL[0].bufsize = COMBTUNINGL1; + rev->combL[1].filterstore = 0; + rev->combL[1].bufidx = 0; + rev->combL[1].buffer = (float*)rev->bufcombL2; + rev->combL[1].bufsize = COMBTUNINGL2; + rev->combL[2].filterstore = 0; + rev->combL[2].bufidx = 0; + rev->combL[2].buffer = (float*)rev->bufcombL3; + rev->combL[2].bufsize = COMBTUNINGL3; + rev->combL[3].filterstore = 0; + rev->combL[3].bufidx = 0; + rev->combL[3].buffer = (float*)rev->bufcombL4; + rev->combL[3].bufsize = COMBTUNINGL4; + rev->combL[4].filterstore = 0; + rev->combL[4].bufidx = 0; + rev->combL[4].buffer = (float*)rev->bufcombL5; + rev->combL[4].bufsize = COMBTUNINGL5; + rev->combL[5].filterstore = 0; + rev->combL[5].bufidx = 0; + rev->combL[5].buffer = (float*)rev->bufcombL6; + rev->combL[5].bufsize = COMBTUNINGL6; + rev->combL[6].filterstore = 0; + rev->combL[6].bufidx = 0; + rev->combL[6].buffer = (float*)rev->bufcombL7; + rev->combL[6].bufsize = COMBTUNINGL7; + rev->combL[7].filterstore = 0; + rev->combL[7].bufidx = 0; + rev->combL[7].buffer = (float*)rev->bufcombL8; + rev->combL[7].bufsize = COMBTUNINGL8; + + rev->allpassL[0].bufidx = 0; + rev->allpassL[0].buffer = (float*)rev->bufallpassL1; + rev->allpassL[0].bufsize = ALLPASSTUNINGL1; + rev->allpassL[0].feedback = 0.5f; + rev->allpassL[1].bufidx = 0; + rev->allpassL[1].buffer = (float*)rev->bufallpassL2; + rev->allpassL[1].bufsize = ALLPASSTUNINGL2; + rev->allpassL[1].feedback = 0.5f; + rev->allpassL[2].bufidx = 0; + rev->allpassL[2].buffer = (float*)rev->bufallpassL3; + rev->allpassL[2].bufsize = ALLPASSTUNINGL3; + rev->allpassL[2].feedback = 0.5f; + rev->allpassL[3].bufidx = 0; + rev->allpassL[3].buffer = (float*)rev->bufallpassL4; + rev->allpassL[3].bufsize = ALLPASSTUNINGL4; + rev->allpassL[3].feedback = 0.5f; + +} + +static float revmodel_process(void *data, float in) +{ + float samp, mono_out, mono_in, input; + int i; + struct revmodel *rev = (struct revmodel*)data; + + samp = in; + mono_out = 0.0f; + mono_in = samp; + input = (mono_in) * rev->gain; + + for(i=0; i < NUMCOMBS; i++) + mono_out += comb_process(&rev->combL[i], input); + for(i = 0; i < NUMALLPASSES; i++) + mono_out = allpass_process(&rev->allpassL[i], mono_out); + samp = mono_in * rev->dry + mono_out * rev->wet1; + return samp; +} + + +#define REVMODEL_GETWET(revmodel) (revmodel->wet / SCALEWET) +#define REVMODEL_GETROOMSIZE(revmodel) ((revmodel->roomsize - OFFSETROOM) / SCALEROOM) +#define REVMODEL_GETDRY(revmodel) (revmodel->dry / SCALEDRY) +#define REVMODEL_GETWIDTH(revmodel) (revmodel->width) + + + +struct reverb_filter_data +{ + struct revmodel rev_l; + struct revmodel rev_r; + float buf[4096]; +}; + + +static void* dsp_init(const rarch_dsp_info_t *info) +{ + float drytime, wettime, damping, roomwidth, roomsize; + (void)info; + + drytime = 0.43; + wettime = 0.57; + damping = 0.45; + roomwidth = 0.56; + roomsize = 0.56; + + struct reverb_filter_data *reverb = (struct reverb_filter_data*)calloc(1, sizeof(*reverb)); + + if (!reverb) + return NULL; + + revmodel_init(&reverb->rev_l); + revmodel_set(&reverb->rev_l, INITIALDRY, + INITIALWET * SCALEWET, INITIALDAMP * SCALEDAMP, INITIALWIDTH, (INITIALROOM * SCALEROOM) + OFFSETROOM); + revmodel_set(&reverb->rev_l, drytime, wettime, damping, roomwidth, roomsize); + + revmodel_init(&reverb->rev_r); + revmodel_set(&reverb->rev_r, INITIALDRY, + INITIALWET * SCALEWET, INITIALDAMP * SCALEDAMP, INITIALWIDTH, (INITIALROOM * SCALEROOM) + OFFSETROOM); + revmodel_set(&reverb->rev_r, drytime, wettime, damping, roomwidth, roomsize); + + return reverb; +} + +static void dsp_process(void *data, rarch_dsp_output_t *output, + const rarch_dsp_input_t *input) +{ + int i, num_samples; + struct reverb_filter_data *reverb = (struct reverb_filter_data*)data; + + output->samples = reverb->buf; + num_samples = input->frames * 2; + for (i = 0; i < num_samples;) + { + reverb->buf[i] = revmodel_process(&reverb->rev_l, input->samples[i]); + i++; + reverb->buf[i] = revmodel_process(&reverb->rev_r, input->samples[i]); + i++; + } + output->frames = input->frames; +} + +static void dsp_free(void *data) +{ + struct reverb_filter_data *rev = (struct reverb_filter_data*)data; + + if (rev) + free(rev); +} + +static void dsp_config(void *data) +{ + (void)data; +} + +const rarch_dsp_plugin_t dsp_plug = { + dsp_init, + dsp_process, + dsp_free, + RARCH_DSP_API_VERSION, + dsp_config, + "Reverberatation plugin" +}; + +RARCH_API_EXPORT const rarch_dsp_plugin_t* RARCH_API_CALLTYPE rarch_dsp_plugin_init(void) +{ + return &dsp_plug; +} diff --git a/audio/filters/volume.c b/audio/filters/volume.c new file mode 100644 index 0000000000..22bbbb1c3a --- /dev/null +++ b/audio/filters/volume.c @@ -0,0 +1,121 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2014 - Daniel De Matteis + * + * RetroArch 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + * + */ + +#include "rarch_dsp.h" +#include +#include +#include + +struct volume_filter_data +{ +#ifdef __GNUC__ + float buf[4096] __attribute__((aligned(16))); +#else + float buf[4096]; +#endif + float m_vol; + float m_pan_vol_l; + float m_pan_vol_r; +}; + +#if 0 +static void pan2gain(float &left, float &right, int val) +{ + left = (100 - val) / 100.0f; + right = (val + 100) / 100.0f; + if (left > 1.0) + left = 1.0; + if (right > 1.0) + right = 1.0; +} + +static float db2gain(float val) +{ + return powf(10.0, val / 20.0); +} +#endif + +void volume_process(void *data, const float *in, unsigned frames) +{ + float vol_left, vol_right; + unsigned i; + struct volume_filter_data *vol = (struct volume_filter_data*)data; + + if (!vol) + return; + + vol_left = vol->m_vol * vol->m_pan_vol_l; + vol_right = vol->m_vol * vol->m_pan_vol_r; + + for (i = 0; i < frames; i++) + { + vol->buf[(i << 1) + 0] = in[(i << 1) + 0] * vol_left; + vol->buf[(i << 1) + 1] = in[(i << 1) + 1] * vol_right; + } +} + +static void *dsp_init(const rarch_dsp_info_t *info) +{ + struct volume_filter_data *vol = (struct volume_filter_data*)calloc(1, sizeof(*vol)); + (void)info; + + if (!vol) + return NULL; + + vol->m_vol = 1.0; + vol->m_pan_vol_l = 1.0; + vol->m_pan_vol_r = 1.0; + + return vol; +} + +static void dsp_process(void *data, rarch_dsp_output_t *output, + const rarch_dsp_input_t *input) +{ + struct volume_filter_data *vol = (struct volume_filter_data*)data; + + output->samples = vol->buf; + volume_process(vol, input->samples, input->frames); + output->frames = input->frames; +} + +static void dsp_free(void *data) +{ + struct volume_filter_data *vol = (struct volume_filter_data*)data; + + if (vol) + free(vol); +} + +static void dsp_config(void *data) +{ +} + +const rarch_dsp_plugin_t dsp_plug = { + dsp_init, + dsp_process, + dsp_free, + RARCH_DSP_API_VERSION, + dsp_config, + "Volume plugin" +}; + +RARCH_API_EXPORT const rarch_dsp_plugin_t* RARCH_API_CALLTYPE rarch_dsp_plugin_init(void) +{ + return &dsp_plug; +} + diff --git a/audio/filters/wah.c b/audio/filters/wah.c new file mode 100644 index 0000000000..5b2db248e3 --- /dev/null +++ b/audio/filters/wah.c @@ -0,0 +1,176 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2014 - Daniel De Matteis + * Copyright (C) 2012-2014 - Brad Miller + * + * RetroArch 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + * + */ + +#include "rarch_dsp.h" +#include +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795 +#endif + +#ifndef LFOSKIPSAMPLES +#define LFOSKIPSAMPLES 30 +#endif + +struct wahwah_filter +{ + float phase; + float lfoskip; + unsigned long skipcount; + float xn1, xn2, yn1, yn2; + float b0, b1, b2, a0, a1, a2; + float freq, startphase; + float depth, freqofs, res; +}; + +struct wahwah_filter_data +{ + struct wahwah_filter wah_l; + struct wahwah_filter wah_r; + float buf[4096]; +}; + +static void wahwah_init(void *data, int samplerate) +{ + struct wahwah_filter *wah = (struct wahwah_filter*)data; + + wah->lfoskip = wah->freq * 2 * M_PI / samplerate; + wah->skipcount = 0; + wah->xn1 = 0; + wah->xn2 = 0; + wah->yn1 = 0; + wah->yn2 = 0; + wah->b0 = 0; + wah->b1 = 0; + wah->b2 = 0; + wah->a0 = 0; + wah->a1 = 0; + wah->a2 = 0; + wah->phase = wah->startphase * M_PI / 180; +} + +static float wahwah_process(void *data, float samp) +{ + float frequency, omega, sn, cs, alpha; + float in, out; + struct wahwah_filter *wah = (struct wahwah_filter*)data; + + in = samp; + if ((wah->skipcount++) % LFOSKIPSAMPLES == 0) + { + frequency = (1 + cos(wah->skipcount * wah->lfoskip + wah->phase)) / 2; + frequency = frequency * wah->depth * (1 - wah->freqofs) + wah->freqofs; + frequency = exp((frequency - 1) * 6); + omega = M_PI * frequency; + sn = sin(omega); + cs = cos(omega); + alpha = sn / (2 * wah->res); + wah->b0 = (1 - cs) / 2; + wah->b1 = 1 - cs; + wah->b2 = (1 - cs) / 2; + wah->a0 = 1 + alpha; + wah->a1 = -2 * cs; + wah->a2 = 1 - alpha; + } + + out = (wah->b0 * in + wah->b1 * wah->xn1 + wah->b2 * wah->xn2 - wah->a1 * wah->yn1 - wah->a2 * wah->yn2) / wah->a0; + wah->xn2 = wah->xn1; + wah->xn1 = in; + wah->yn2 = wah->yn1; + wah->yn1 = out; + samp = out; + return samp; +} + +static void *dsp_init(const rarch_dsp_info_t *info) +{ + float freq = 1.5; + float startphase = 0.0; + float res = 2.5; + float depth = 0.70; + float freqofs = 0.30; + + struct wahwah_filter_data *wah = (struct wahwah_filter_data*)calloc(1, sizeof(*wah)); + + if (!wah) + return NULL; + + wah->wah_l.depth = depth; + wah->wah_l.freqofs = freqofs; + wah->wah_l.freq = freq; + wah->wah_l.startphase = startphase; + wah->wah_l.res = res; + wahwah_init(&wah->wah_l, info->input_rate); + + wah->wah_r.depth = depth; + wah->wah_r.freqofs = freqofs; + wah->wah_r.freq = freq; + wah->wah_r.startphase = startphase; + wah->wah_r.res = res; + wahwah_init(&wah->wah_r, info->input_rate); + + return wah; +} + +static void dsp_process(void *data, rarch_dsp_output_t *output, + const rarch_dsp_input_t *input) +{ + int num_samples, i; + struct wahwah_filter_data *wah = (struct wahwah_filter_data*)data; + + output->samples = wah->buf; + num_samples = input->frames * 2; + + for (i = 0; i < num_samples;) + { + wah->buf[i] = wahwah_process(&wah->wah_l, input->samples[i]); + i++; + wah->buf[i] = wahwah_process(&wah->wah_r, input->samples[i]); + i++; + } + output->frames = input->frames; +} + +static void dsp_free(void *data) +{ + struct wahwah_filter_data *wah = (struct wahwah_filter_data*)data; + + if (wah) + free(wah); +} + +static void dsp_config(void *data) +{ +} + +const rarch_dsp_plugin_t dsp_plug = { + dsp_init, + dsp_process, + dsp_free, + RARCH_DSP_API_VERSION, + dsp_config, + "Wah plugin" +}; + +RARCH_API_EXPORT const rarch_dsp_plugin_t* RARCH_API_CALLTYPE rarch_dsp_plugin_init(void) +{ + return &dsp_plug; +}