diff --git a/Makefile b/Makefile index 8544b2f2b6..9b51a3883d 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ include config.mk TARGET = ssnes tools/ssnes-joyconfig -OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o rewind.o movie.o gfx/gfx_common.o ups.o bps.o strl.o getopt.o screenshot.o audio/hermite.o audio/utils.o +OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o rewind.o movie.o gfx/gfx_common.o ups.o bps.o strl.o getopt.o screenshot.o audio/utils.o JOYCONFIG_OBJ = tools/ssnes-joyconfig.o conf/config_file.o strl.o HEADERS = $(wildcard */*.h) $(wildcard *.h) @@ -174,6 +174,12 @@ ifeq ($(HAVE_PYTHON), 1) OBJ += gfx/py_state/py_state.o endif +ifeq ($(HAVE_SINC), 1) + OBJ += audio/sinc.o +else + OBJ += audio/hermite.o +endif + ifneq ($(V),1) Q := @ endif diff --git a/audio/oss.c b/audio/oss.c index fe9be0de44..5de75475dc 100644 --- a/audio/oss.c +++ b/audio/oss.c @@ -55,18 +55,15 @@ static void *oss_init(const char *device, unsigned rate, unsigned latency) if ((*fd = open(oss_device, O_WRONLY)) < 0) { free(fd); + perror("open"); return NULL; } - int frags = (latency * rate * 4)/(1000 * (1 << 10)); + int frags = (latency * rate * 4) / (1000 * (1 << 10)); int frag = (frags << 16) | 10; if (ioctl(*fd, SNDCTL_DSP_SETFRAGMENT, &frag) < 0) - { - close(*fd); - free(fd); - return NULL; - } + SSNES_WARN("Cannot set fragment sizes. Latency might not be as expected ...\n"); int channels = 2; int format = is_little_endian() ? @@ -76,6 +73,7 @@ static void *oss_init(const char *device, unsigned rate, unsigned latency) { close(*fd); free(fd); + perror("ioctl"); return NULL; } @@ -83,6 +81,7 @@ static void *oss_init(const char *device, unsigned rate, unsigned latency) { close(*fd); free(fd); + perror("ioctl"); return NULL; } @@ -91,6 +90,7 @@ static void *oss_init(const char *device, unsigned rate, unsigned latency) { close(*fd); free(fd); + perror("ioctl"); return NULL; } diff --git a/audio/sinc.c b/audio/sinc.c new file mode 100644 index 0000000000..bdb4d16050 --- /dev/null +++ b/audio/sinc.c @@ -0,0 +1,266 @@ +/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010-2012 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES 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. + * + * SSNES 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 SSNES. + * If not, see . + */ + +// Bog-standard windowed SINC implementation. +// Only suitable as an upsampler, as there is no low-pass filter stage. + +#include "resampler.h" +#include +#include +#include +#include +#include + +#if __SSE__ +#include +#endif + +#define PHASE_BITS 8 +#define SUBPHASE_BITS 16 + +#define PHASES (1 << PHASE_BITS) +#define PHASES_SHIFT (SUBPHASE_BITS) +#define SUBPHASES (1 << SUBPHASE_BITS) +#define SUBPHASES_SHIFT 0 +#define SUBPHASES_MASK ((1 << SUBPHASE_BITS) - 1) +#define PHASES_WRAP (1 << (PHASE_BITS + SUBPHASE_BITS)) +#define FRAMES_SHIFT (PHASE_BITS + SUBPHASE_BITS) + +#define SIDELOBES 8 + +struct ssnes_resampler +{ + float phase_table[PHASES + 1][SIDELOBES]; + float delta_table[PHASES + 1][SIDELOBES]; + float buffer_l[2 * SIDELOBES]; + float buffer_r[2 * SIDELOBES]; + + uint32_t time; +}; + +static inline double sinc(double val) +{ + if (fabs(val) < 0.00001) + return 1.0; + else + return sin(val) / val; +} + +static inline double blackman(double index) +{ + index *= 0.5; + index += 0.5; + + double alpha = 0.16; + double a0 = (1.0 - alpha) / 2.0; + double a1 = 0.5; + double a2 = alpha / 2.0; + + return a0 - a1 * cos(2.0 * M_PI * index) + a2 * cos(4.0 * M_PI * index); +} + +static void init_sinc_table(ssnes_resampler_t *resamp) +{ + for (unsigned i = 0; i <= PHASES; i++) + { + for (unsigned j = 0; j < SIDELOBES; j++) + { + double sinc_phase = M_PI * ((double)i / PHASES + (double)j); + resamp->phase_table[i][j] = sinc(sinc_phase) * blackman(sinc_phase / SIDELOBES); + } + } + + // Optimize linear interpolation. + for (unsigned i = 0; i < PHASES; i++) + for (unsigned j = 0; j < SIDELOBES; j++) + resamp->delta_table[i][j] = resamp->phase_table[i + 1][j] - resamp->phase_table[i][j]; +} + +ssnes_resampler_t *resampler_new(void) +{ + ssnes_resampler_t *re = memalign(16, sizeof(*re)); + if (!re) + return NULL; + + memset(re, 0, sizeof(*re)); + + init_sinc_table(re); + return re; +} + +#if __SSE__ +static void process_sinc(ssnes_resampler_t *resamp, float * restrict out_buffer) +{ + __m128 sum_l = _mm_setzero_ps(); + __m128 sum_r = _mm_setzero_ps(); + + const float *buffer_l = resamp->buffer_l; + const float *buffer_r = resamp->buffer_r; + + unsigned phase = resamp->time >> PHASES_SHIFT; + unsigned delta = (resamp->time >> SUBPHASES_SHIFT) & SUBPHASES_MASK; + __m128 delta_f = _mm_set1_ps((float)delta / SUBPHASES); + + const float *phase_table = resamp->phase_table[phase]; + const float *delta_table = resamp->delta_table[phase]; + + __m128 sinc_vals[SIDELOBES / 4]; + for (unsigned i = 0; i < SIDELOBES; i += 4) + { + __m128 phases = _mm_load_ps(phase_table + i); + __m128 deltas = _mm_load_ps(delta_table + i); + sinc_vals[i / 4] = _mm_add_ps(phases, _mm_mul_ps(deltas, delta_f)); + } + + // Older data. + for (unsigned i = 0; i < SIDELOBES; i += 4) + { + __m128 buf_l = _mm_loadr_ps(buffer_l + SIDELOBES - 4 - i); + sum_l = _mm_add_ps(sum_l, _mm_mul_ps(buf_l, sinc_vals[i / 4])); + + __m128 buf_r = _mm_loadr_ps(buffer_r + SIDELOBES - 4 - i); + sum_r = _mm_add_ps(sum_r, _mm_mul_ps(buf_r, sinc_vals[i / 4])); + } + + // Newer data. + unsigned reverse_phase = PHASES_WRAP - resamp->time; + phase = reverse_phase >> PHASES_SHIFT; + delta = (reverse_phase >> SUBPHASES_SHIFT) & SUBPHASES_MASK; + delta_f = _mm_set1_ps((float)delta / SUBPHASES); + + phase_table = resamp->phase_table[phase]; + delta_table = resamp->delta_table[phase]; + + for (unsigned i = 0; i < SIDELOBES; i += 4) + { + __m128 phases = _mm_load_ps(phase_table + i); + __m128 deltas = _mm_load_ps(delta_table + i); + sinc_vals[i / 4] = _mm_add_ps(phases, _mm_mul_ps(deltas, delta_f)); + } + + for (unsigned i = 0; i < SIDELOBES; i += 4) + { + __m128 buf_l = _mm_load_ps(buffer_l + SIDELOBES + i); + sum_l = _mm_add_ps(sum_l, _mm_mul_ps(buf_l, sinc_vals[i / 4])); + + __m128 buf_r = _mm_load_ps(buffer_r + SIDELOBES + i); + sum_r = _mm_add_ps(sum_r, _mm_mul_ps(buf_r, sinc_vals[i / 4])); + } + + // This can be done faster with _mm_hadd_ps(), but it's SSE3 :( + __m128 sum_shuf_l = _mm_shuffle_ps(sum_l, sum_r, _MM_SHUFFLE(1, 0, 1, 0)); + __m128 sum_shuf_r = _mm_shuffle_ps(sum_l, sum_r, _MM_SHUFFLE(3, 2, 3, 2)); + __m128 sum = _mm_add_ps(sum_shuf_l, sum_shuf_r); + + union + { + float f[4]; + __m128 v; + } u; + u.v = sum; + + out_buffer[0] = u.f[0] + u.f[1]; + out_buffer[1] = u.f[2] + u.f[3]; +} +#else // Plain ol' C99 +static void process_sinc(struct maru_resampler *resamp, float * restrict out_buffer) +{ + float sum_l = 0.0f; + float sum_r = 0.0f; + const float *buffer_l = resamp->buffer_l; + const float *buffer_r = resamp->buffer_r; + + unsigned phase = resamp->time >> PHASES_SHIFT; + unsigned delta = (resamp->time >> SUBPHASES_SHIFT) & SUBPHASES_MASK; + float delta_f = (float)delta / SUBPHASES; + + const float *phase_table = resamp->phase_table[phase]; + const float *delta_table = resamp->delta_table[phase]; + + float sinc_vals[SIDELOBES]; + for (unsigned i = 0; i < SIDELOBES; i++) + sinc_vals[i] = phase_table[i] + delta_f * delta_table[i]; + + // Older data. + for (unsigned i = 0; i < SIDELOBES; i++) + { + sum_l += buffer_l[SIDELOBES - 1 - i] * sinc_vals[i]; + sum_r += buffer_r[SIDELOBES - 1 - i] * sinc_vals[i]; + } + + // Newer data. + unsigned reverse_phase = PHASES_WRAP - resamp->time; + phase = reverse_phase >> PHASES_SHIFT; + delta = (reverse_phase >> SUBPHASES_SHIFT) & SUBPHASES_MASK; + delta_f = (float)delta / SUBPHASES; + + phase_table = resamp->phase_table[phase]; + delta_table = resamp->delta_table[phase]; + + for (unsigned i = 0; i < SIDELOBES; i++) + sinc_vals[i] = phase_table[i] + delta_f * delta_table[i]; + + for (unsigned i = 0; i < SIDELOBES; i++) + { + sum_l += buffer_l[SIDELOBES + i] * sinc_vals[i]; + sum_r += buffer_r[SIDELOBES + i] * sinc_vals[i]; + } + + out_buffer[0] = sum_l; + out_buffer[1] = sum_r; +} +#endif + +void resampler_process(ssnes_resampler_t *re, struct resampler_data *data) +{ + uint32_t ratio = PHASES_WRAP / data->ratio; + + const float *input = data->data_in; + float *output = data->data_out; + size_t frames = data->input_frames; + size_t out_frames = 0; + + while (frames) + { + process_sinc(re, output); + output += 2; + out_frames++; + + re->time += ratio; + while (re->time >= PHASES_WRAP) + { + memmove(re->buffer_l, re->buffer_l + 1, + sizeof(re->buffer_l) - sizeof(float)); + memmove(re->buffer_r, re->buffer_r + 1, + sizeof(re->buffer_r) - sizeof(float)); + + re->buffer_l[2 * SIDELOBES - 1] = *input++; + re->buffer_r[2 * SIDELOBES - 1] = *input++; + + re->time -= PHASES_WRAP; + frames--; + } + } + + data->output_frames = out_frames; +} + +void resampler_free(ssnes_resampler_t *re) +{ + free(re); +} + diff --git a/qb/config.libs.sh b/qb/config.libs.sh index a9dcd3c640..655ffc29f4 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -136,7 +136,7 @@ check_pkgconf PYTHON python3 add_define_make OS $OS # Creates config.mk and config.h. -VARS="ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK COREAUDIO PULSE SDL OPENGL DYLIB GETOPT_LONG THREADS CG XML SDL_IMAGE DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL SWSCALE CONFIGFILE FREETYPE XVIDEO X11 XEXT NETPLAY SOCKET_LEGACY FBO STRL PYTHON FFMPEG_ALLOC_CONTEXT3 FFMPEG_AVCODEC_OPEN2 FFMPEG_AVIO_OPEN FFMPEG_AVFORMAT_WRITE_HEADER FFMPEG_AVFORMAT_NEW_STREAM FFMPEG_AVCODEC_ENCODE_AUDIO2 X264RGB" +VARS="ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK COREAUDIO PULSE SDL OPENGL DYLIB GETOPT_LONG THREADS CG XML SDL_IMAGE DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL SWSCALE CONFIGFILE FREETYPE XVIDEO X11 XEXT NETPLAY SOCKET_LEGACY FBO STRL PYTHON FFMPEG_ALLOC_CONTEXT3 FFMPEG_AVCODEC_OPEN2 FFMPEG_AVIO_OPEN FFMPEG_AVFORMAT_WRITE_HEADER FFMPEG_AVFORMAT_NEW_STREAM FFMPEG_AVCODEC_ENCODE_AUDIO2 X264RGB SINC" create_config_make config.mk $VARS create_config_header config.h $VARS diff --git a/qb/config.params.sh b/qb/config.params.sh index 5d397d4857..f2be650466 100644 --- a/qb/config.params.sh +++ b/qb/config.params.sh @@ -31,3 +31,4 @@ add_command_line_enable FREETYPE "Enable FreeType support" auto add_command_line_enable XVIDEO "Enable XVideo support" auto add_command_line_enable SDL_IMAGE "Enable SDL_image support" auto add_command_line_enable PYTHON "Enable Python 3 support for shaders" auto +add_command_line_enable SINC "Enable Blackman SINC resampler" no