diff --git a/Makefile b/Makefile index 670ad4415f..5d4bd045d1 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,10 @@ ifeq ($(BUILD_ALSA), 1) OBJ += alsa.o LIBS += -lasound endif +ifeq ($(BUILD_ROAR), 1) + OBJ += roar.o + LIBS += -lroar +endif ifeq ($(BUILD_OPENGL), 1) OBJ += gl.o @@ -25,7 +29,7 @@ ifeq ($(BUILD_FILTER), 1) OBJ += hqflt/hq.o endif -CFLAGS = -Wall -O3 -march=native -std=c99 +CFLAGS = -Wall -O3 -march=native -std=gnu99 diff --git a/alsa.c b/alsa.c index 4cfdd7bbb8..07d8c631f8 100644 --- a/alsa.c +++ b/alsa.c @@ -27,6 +27,7 @@ typedef struct alsa { snd_pcm_t *pcm; + bool nonblock; } alsa_t; static void* __alsa_init(const char* device, int rate, int latency) @@ -125,7 +126,8 @@ static ssize_t __alsa_write(void* data, const void* buf, size_t size) return size; } - + else if ( alsa->nonblock && frames == -EAGAIN ) + return 0; else if ( frames < 0 ) return -1; @@ -134,11 +136,18 @@ static ssize_t __alsa_write(void* data, const void* buf, size_t size) static bool __alsa_stop(void *data) { -/* int *fd = data; - ioctl(*fd, SNDCTL_DSP_RESET, 0);*/ return true; } +static void __alsa_set_nonblock_state(void *data, bool state) +{ + alsa_t *alsa = data; + if (snd_pcm_nonblock(alsa->pcm, state) < 0) + fprintf(stderr, "SSNES [ERROR]: Could not set PCM to non-blocking. Will not be able to fast-forward.\n"); + else + alsa->nonblock = state; +} + static bool __alsa_start(void *data) { return true; @@ -163,6 +172,7 @@ const audio_driver_t audio_alsa = { .write = __alsa_write, .stop = __alsa_stop, .start = __alsa_start, + .set_nonblock_state = __alsa_set_nonblock_state, .free = __alsa_free }; diff --git a/config.h b/config.h index 3456e061be..b47f623408 100644 --- a/config.h +++ b/config.h @@ -35,6 +35,7 @@ #define AUDIO_RSOUND 1 #define AUDIO_OSS 2 #define AUDIO_ALSA 3 +#define AUDIO_ROAR 4 //////////////////////// // Chooses which video and audio subsystem to use. Remember to update config.mk if you change these. @@ -92,7 +93,7 @@ static const char* audio_device = NULL; static const int out_latency = 64; // Defines the quality (and cpu reqirements) of samplerate conversion. -#define SAMPLERATE_QUALITY SRC_SINC_FASTEST +#define SAMPLERATE_QUALITY SRC_LINEAR @@ -117,6 +118,7 @@ static const struct snes_keybind snes_keybinds[] = { { SNES_DEVICE_ID_JOYPAD_DOWN, GLFW_KEY_DOWN, 11 }, { SNES_DEVICE_ID_JOYPAD_START, GLFW_KEY_ENTER, 6 }, { SNES_DEVICE_ID_JOYPAD_SELECT, GLFW_KEY_RSHIFT, 14 }, + { SNES_FAST_FORWARD_KEY, GLFW_KEY_SPACE, 9 }, { -1 } }; diff --git a/config.mk b/config.mk index d11f1f604d..c1f63e94d5 100644 --- a/config.mk +++ b/config.mk @@ -5,6 +5,7 @@ BUILD_FILTER = 0 BUILD_RSOUND = 1 BUILD_OSS = 0 BUILD_ALSA = 0 +BUILD_ROAR = 0 PREFIX = /usr diff --git a/driver.h b/driver.h index 1bb4d880b1..887c90505a 100644 --- a/driver.h +++ b/driver.h @@ -24,6 +24,9 @@ #include #include +#define SNES_FAST_FORWARD_KEY 0x666 // Hurr, durr +void set_fast_forward_button(bool state); + struct snes_keybind { int id; @@ -48,6 +51,7 @@ typedef struct audio_driver ssize_t (*write)(void* data, const void* buf, size_t size); bool (*stop)(void* data); bool (*start)(void* data); + void (*set_nonblock_state)(void* data, bool toggle); // Should we care about blocking in audio thread? Fast forwarding. void (*free)(void* data); } audio_driver_t; @@ -64,6 +68,7 @@ typedef struct video_driver void* (*init)(video_info_t *video, const input_driver_t **input); // Should the video driver act as an input driver as well? :) bool (*frame)(void* data, const uint16_t* frame, int width, int height); + void (*set_nonblock_state)(void* data, bool toggle); // Should we care about syncing to vblank? Fast forwarding. void (*free)(void* data); } video_driver_t; diff --git a/gl.c b/gl.c index 8b7adf46d9..3e25b8e5fd 100644 --- a/gl.c +++ b/gl.c @@ -21,6 +21,7 @@ #include #include "libsnes.hpp" #include +#include static GLuint texture; static uint8_t *gl_buffer; @@ -29,7 +30,7 @@ static GLuint tex_filter; typedef struct gl { - int foo; + bool vsync; } gl_t; @@ -68,11 +69,26 @@ static int16_t glfw_input_state(void *data, const struct snes_keybind *snes_keyb glfwGetJoystickButtons(joypad_id, buttons, joypad_buttons); } + // Finds fast forwarding state. + for ( i = 0; snes_keybinds[i].id != -1; i++ ) + { + if ( snes_keybinds[i].id == SNES_FAST_FORWARD_KEY ) + { + bool pressed = false; + if ( glfwGetKey(snes_keybinds[i].key) ) + pressed = true; + else if ( snes_keybinds[i].joykey < joypad_buttons && buttons[snes_keybinds[i].joykey] == GLFW_PRESS ) + pressed = true; + set_fast_forward_button(pressed); + break; + } + } + for ( i = 0; snes_keybinds[i].id != -1; i++ ) { if ( snes_keybinds[i].id == (int)id ) { - if ( glfwGetKey(snes_keybinds[i].key )) + if ( glfwGetKey(snes_keybinds[i].key) ) return 1; if ( snes_keybinds[i].joykey < joypad_buttons && buttons[snes_keybinds[i].joykey] == GLFW_PRESS ) @@ -128,6 +144,12 @@ static void GLFWCALL resize(int width, int height) glLoadIdentity(); } +static float tv_to_fps(const struct timeval *tv, const struct timeval *new_tv, int frames) +{ + float time = new_tv->tv_sec - tv->tv_sec + (new_tv->tv_usec - tv->tv_usec)/1000000.0; + return frames/time; +} + static bool gl_frame(void *data, const uint16_t* frame, int width, int height) { (void)data; @@ -160,6 +182,31 @@ static bool gl_frame(void *data, const uint16_t* frame, int width, int height) glEnd(); + // Shows FPS in taskbar. + static int frames = 0; + static struct timeval tv; + struct timeval new_tv; + + if (frames == 0) + gettimeofday(&tv, NULL); + + if ((frames % 60) == 0 && frames > 0) + { + gettimeofday(&new_tv, NULL); + struct timeval tmp_tv = { + .tv_sec = tv.tv_sec, + .tv_usec = tv.tv_usec + }; + gettimeofday(&tv, NULL); + char tmpstr[256] = {0}; + + float fps = tv_to_fps(&tmp_tv, &new_tv, 60); + + snprintf(tmpstr, sizeof(tmpstr) - 1, "SSNES || FPS: %6.1f || Frames: %d", fps, frames); + glfwSetWindowTitle(tmpstr); + } + frames++; + glfwSwapBuffers(); return true; @@ -171,10 +218,22 @@ static void gl_free(void *data) free(gl_buffer); } +static void gl_set_nonblock_state(void *data, bool state) +{ + gl_t *gl = data; + if (gl->vsync) + { + if (state) + glfwSwapInterval(0); + else + glfwSwapInterval(1); + } +} + static void* gl_init(video_info_t *video, const input_driver_t **input) { - gl_t *foo = malloc(sizeof(gl_t)); - if ( foo == NULL ) + gl_t *gl = malloc(sizeof(gl_t)); + if ( gl == NULL ) return NULL; keep_aspect = video->force_aspect; @@ -200,6 +259,7 @@ static void* gl_init(video_info_t *video, const input_driver_t **input) glfwSwapInterval(1); // Force vsync else glfwSwapInterval(0); + gl->vsync = video->vsync; gl_buffer = malloc(256 * 256 * 2 * video->input_scale * video->input_scale); if ( !gl_buffer ) @@ -225,12 +285,13 @@ static void* gl_init(video_info_t *video, const input_driver_t **input) GL_UNSIGNED_SHORT_1_5_5_5_REV, gl_buffer); *input = &input_glfw; - return foo; + return gl; } const video_driver_t video_gl = { .init = gl_init, .frame = gl_frame, + .set_nonblock_state = gl_set_nonblock_state, .free = gl_free }; diff --git a/oss.c b/oss.c index 27905b206e..b12ddebad1 100644 --- a/oss.c +++ b/oss.c @@ -22,6 +22,8 @@ #include #include #include +#include +#include static void* __oss_init(const char* device, int rate, int latency) { @@ -86,7 +88,11 @@ static ssize_t __oss_write(void* data, const void* buf, size_t size) ssize_t ret; if ( (ret = write(*fd, buf, size)) <= 0 ) + { + if ( (fcntl(*fd, F_GETFL) & O_NONBLOCK) && errno == EAGAIN ) + return 0; return -1; + } return ret; } @@ -103,6 +109,18 @@ static bool __oss_start(void *data) return true; } +static void __oss_set_nonblock_state(void *data, bool state) +{ + int *fd = data; + int rc; + if (state) + rc = fcntl(*fd, F_SETFL, fcntl(*fd, F_GETFL) | O_NONBLOCK); + else + rc = fcntl(*fd, F_SETFL, fcntl(*fd, F_GETFL) & (~O_NONBLOCK)); + if (rc != 0) + fprintf(stderr, "SSNES [ERROR]: Could not set nonblocking on OSS file descriptor. Will not be able to fast-forward.\n"); +} + static void __oss_free(void *data) { int *fd = data; @@ -117,6 +135,7 @@ const audio_driver_t audio_oss = { .write = __oss_write, .stop = __oss_stop, .start = __oss_start, + .set_nonblock_state = __oss_set_nonblock_state, .free = __oss_free }; diff --git a/roar.c b/roar.c new file mode 100644 index 0000000000..3e93ad6a81 --- /dev/null +++ b/roar.c @@ -0,0 +1,92 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - 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 . + */ + + +#include "driver.h" +#include +#include +#include +#include + +static void* __roar_init(const char* device, int rate, int latency) +{ + int err; + roar_vs_t *vss; + if ( (vss = roar_vs_new_simple(NULL, NULL, rate, 2, ROAR_CODEC_PCM_S_LE, 16, ROAR_DIR_PLAY, &err)) == NULL ) + { + fprintf(stderr, "roar_vs: \"%s\"\n", roar_vs_strerr(err)); + return NULL; + } + + return vss; +} + +static ssize_t __roar_write(void* data, const void* buf, size_t size) +{ + roar_vs_t *vss = data; + + if ( size == 0 ) + return 0; + + int err; + if (roar_vs_write(vss, buf, size, &err) < 0) + { + if (err == ROAR_ERROR_NONE) + return 0; + return -1; + } + + return size; +} + +static bool __roar_stop(void *data) +{ + return true; +} + +static void __roar_set_nonblock_state(void *data, bool state) +{ + roar_vs_t *vss = data; + if (roar_vs_blocking(vss, (state) ? ROAR_VS_FALSE : ROAR_VS_TRUE, NULL) < 0) + fprintf(stderr, "SSNES [ERROR]: Can't set nonblocking. Will not be able to fast-forward.\n"); +} + +static bool __roar_start(void *data) +{ + return true; +} + +static void __roar_free(void *data) +{ + roar_vs_t *vss = data; + roar_vs_close(vss, ROAR_VS_TRUE, NULL); +} + +const audio_driver_t audio_roar = { + .init = __roar_init, + .write = __roar_write, + .stop = __roar_stop, + .start = __roar_start, + .set_nonblock_state = __roar_set_nonblock_state, + .free = __roar_free +}; + + + + + + diff --git a/rsound.c b/rsound.c index e190e0ef91..e37b62594b 100644 --- a/rsound.c +++ b/rsound.c @@ -25,6 +25,7 @@ typedef struct rsd rsound_t *rd; int latency; int rate; + int nonblock; } rsd_t; static void* __rsd_init(const char* device, int rate, int latency) @@ -74,6 +75,9 @@ static ssize_t __rsd_write(void* data, const void* buf, size_t size) { rsd_t *rsd = data; + if ( rsd_delay_ms(rsd->rd) > rsd->latency && rsd->nonblock ) + return 0; + if ( size == 0 ) return 0; @@ -101,6 +105,12 @@ static bool __rsd_stop(void *data) return true; } +static void __rsd_set_nonblock_state(void *data, bool state) +{ + rsd_t *rsd = data; + rsd->nonblock = state; +} + static bool __rsd_start(void *data) { rsd_t *rsd = data; @@ -124,6 +134,7 @@ const audio_driver_t audio_rsound = { .write = __rsd_write, .stop = __rsd_stop, .start = __rsd_start, + .set_nonblock_state = __rsd_set_nonblock_state, .free = __rsd_free }; diff --git a/ssnes.c b/ssnes.c index 0387fb6ca7..eb85c41e13 100644 --- a/ssnes.c +++ b/ssnes.c @@ -36,6 +36,7 @@ static SRC_STATE* source = NULL; extern const audio_driver_t audio_rsound; extern const audio_driver_t audio_oss; extern const audio_driver_t audio_alsa; +extern const audio_driver_t audio_roar; extern const video_driver_t video_gl; //////////////////////////////////////////////// @@ -52,6 +53,8 @@ static driver_t driver = { .audio = &audio_oss, #elif AUDIO_DRIVER == AUDIO_ALSA .audio = &audio_alsa, +#elif AUDIO_DRIVER == AUDIO_ROAR + .audio = &audio_roar, #else #error "Define a valid audio driver in config.h" #endif @@ -70,6 +73,29 @@ static void write_file(const char* path, uint8_t* data, size_t size); static void load_save_file(const char* path, int type); static void save_file(const char* path, int type); + +// To avoid continous switching if we hold the button down, we require that the button must go from pressed, unpressed back to pressed to be able to toggle between then. + +#define AUDIO_CHUNK_SIZE_BLOCKING 64 +#define AUDIO_CHUNK_SIZE_NONBLOCKING 1024 // So we don't get complete line-noise when fast-forwarding audio. +static size_t audio_chunk_size = AUDIO_CHUNK_SIZE_BLOCKING; +void set_fast_forward_button(bool new_button_state) +{ + static bool old_button_state = false; + static bool syncing_state = false; + if (new_button_state && !old_button_state) + { + syncing_state = !syncing_state; + driver.video->set_nonblock_state(driver.video_data, syncing_state); + driver.audio->set_nonblock_state(driver.audio_data, syncing_state); + if (syncing_state) + audio_chunk_size = AUDIO_CHUNK_SIZE_NONBLOCKING; + else + audio_chunk_size = AUDIO_CHUNK_SIZE_BLOCKING; + } + old_button_state = new_button_state; +} + static void init_drivers(void) { init_video_input(); @@ -197,30 +223,28 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height) } -#define SRC_SAMPLES 64 - static void audio_sample(uint16_t left, uint16_t right) { if ( !audio_active ) return; - static float data[SRC_SAMPLES]; + static float data[AUDIO_CHUNK_SIZE_NONBLOCKING]; static int data_ptr = 0; data[data_ptr++] = (float)(*(int16_t*)&left)/0x7FFF; data[data_ptr++] = (float)(*(int16_t*)&right)/0x7FFF; - if ( data_ptr == SRC_SAMPLES ) + if ( data_ptr >= audio_chunk_size ) { - float outsamples[SRC_SAMPLES * 16]; - int16_t temp_outsamples[SRC_SAMPLES * 16]; + float outsamples[audio_chunk_size * 16]; + int16_t temp_outsamples[audio_chunk_size * 16]; SRC_DATA src_data; src_data.data_in = data; src_data.data_out = outsamples; - src_data.input_frames = SRC_SAMPLES / 2; - src_data.output_frames = SRC_SAMPLES * 8; + src_data.input_frames = audio_chunk_size / 2; + src_data.output_frames = audio_chunk_size * 8; src_data.end_of_input = 0; src_data.src_ratio = (double)out_rate / (double)in_rate; @@ -229,7 +253,10 @@ static void audio_sample(uint16_t left, uint16_t right) src_float_to_short_array(outsamples, temp_outsamples, src_data.output_frames_gen * 2); if ( driver.audio->write(driver.audio_data, temp_outsamples, src_data.output_frames_gen * 4) < 0 ) + { + fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n"); audio_active = false; + } data_ptr = 0; }