# HG changeset patch # User Mike Pavone # Date 1380696676 25200 # Node ID 3e1573fa22cf5655f0c8c6d5112b754b5b6a589b # Parent 4b24260125f3ad6d19de3aa9d430923d0ed239f1 Implement turbo/slow motion feature that overclocks or underclocks the entire system at the push of a button diff -r 4b24260125f3 -r 3e1573fa22cf blastem.c --- a/blastem.c Wed Sep 18 19:10:54 2013 -0700 +++ b/blastem.c Tue Oct 01 23:51:16 2013 -0700 @@ -33,6 +33,8 @@ #define LINES_NTSC 262 #define LINES_PAL 312 +#define MAX_SOUND_CYCLES 100000 + uint32_t mclks_per_frame = MCLKS_LINE*LINES_NTSC; uint16_t cart[CARTRIDGE_WORDS]; @@ -206,6 +208,13 @@ void sync_sound(genesis_context * gen, uint32_t target) { //printf("YM | Cycle: %d, bpos: %d, PSG | Cycle: %d, bpos: %d\n", gen->ym->current_cycle, gen->ym->buffer_pos, gen->psg->cycles, gen->psg->buffer_pos * 2); + while (target > gen->psg->cycles && target - gen->psg->cycles > MAX_SOUND_CYCLES) { + uint32_t cur_target = gen->psg->cycles + MAX_SOUND_CYCLES; + //printf("Running PSG to cycle %d\n", cur_target); + psg_run(gen->psg, cur_target); + //printf("Running YM-2612 to cycle %d\n", cur_target); + ym_run(gen->ym, cur_target); + } psg_run(gen->psg, target); ym_run(gen->ym, target); @@ -1492,6 +1501,17 @@ return context; } +void set_speed_percent(genesis_context * context, uint32_t percent) +{ + uint32_t old_clock = context->master_clock; + context->master_clock = ((uint64_t)context->normal_clock * (uint64_t)percent) / 100; + while (context->ym->current_cycle != context->psg->cycles) { + sync_sound(context, context->psg->cycles + MCLKS_PER_PSG); + } + ym_adjust_master_clock(context->ym, context->master_clock); + psg_adjust_master_clock(context->psg, context->master_clock); +} + #define ROM_END 0x1A4 #define RAM_ID 0x1B0 #define RAM_FLAGS 0x1B2 @@ -1868,23 +1888,23 @@ render_init(width, height, title, fps, fullscreen); } vdp_context v_context; + genesis_context gen; + memset(&gen, 0, sizeof(gen)); + gen.master_clock = gen.normal_clock = fps == 60 ? MCLKS_NTSC : MCLKS_PAL; init_vdp_context(&v_context); ym2612_context y_context; - ym_init(&y_context, render_sample_rate(), fps == 60 ? MCLKS_NTSC : MCLKS_PAL, MCLKS_PER_YM, render_audio_buffer(), ym_log ? YM_OPT_WAVE_LOG : 0); + ym_init(&y_context, render_sample_rate(), gen.master_clock, MCLKS_PER_YM, render_audio_buffer(), ym_log ? YM_OPT_WAVE_LOG : 0); psg_context p_context; - psg_init(&p_context, render_sample_rate(), fps == 60 ? MCLKS_NTSC : MCLKS_PAL, MCLKS_PER_PSG, render_audio_buffer()); + psg_init(&p_context, render_sample_rate(), gen.master_clock, MCLKS_PER_PSG, render_audio_buffer()); z80_context z_context; x86_z80_options z_opts; init_x86_z80_opts(&z_opts); init_z80_context(&z_context, &z_opts); - genesis_context gen; - memset(&gen, 0, sizeof(gen)); - z_context.system = &gen; z_context.mem_pointers[0] = z80_ram; z_context.sync_cycle = z_context.target_cycle = mclks_per_frame/MCLKS_PER_Z80; diff -r 4b24260125f3 -r 3e1573fa22cf blastem.h --- a/blastem.h Wed Sep 18 19:10:54 2013 -0700 +++ b/blastem.h Tue Oct 01 23:51:16 2013 -0700 @@ -1,6 +1,6 @@ /* Copyright 2013 Michael Pavone - This file is part of BlastEm. + This file is part of BlastEm. BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text. */ #ifndef BLASTEM_H_ @@ -30,6 +30,8 @@ uint8_t *save_ram; uint32_t save_ram_mask; uint32_t save_flags; + uint32_t master_clock; //Current master clock value + uint32_t normal_clock; //Normal master clock (used to restore master clock after turbo mode) uint8_t bank_regs[8]; io_port ports[3]; } genesis_context; @@ -43,6 +45,7 @@ uint16_t read_dma_value(uint32_t address); m68k_context * debugger(m68k_context * context, uint32_t address); +void set_speed_percent(genesis_context * context, uint32_t percent); #endif //BLASTEM_H_ diff -r 4b24260125f3 -r 3e1573fa22cf default.cfg --- a/default.cfg Wed Sep 18 19:10:54 2013 -0700 +++ b/default.cfg Tue Oct 01 23:51:16 2013 -0700 @@ -19,6 +19,16 @@ u ui.enter_debugger esc ui.exit ` ui.save_state + 0 ui.set_speed.0 + 1 ui.set_speed.1 + 2 ui.set_speed.2 + 3 ui.set_speed.3 + 4 ui.set_speed.4 + 5 ui.set_speed.5 + 6 ui.set_speed.6 + 7 ui.set_speed.7 + = ui.next_speed + - ui.prev_speed } pads { 0 { @@ -53,5 +63,17 @@ buffer 512 } +clocks { + speeds { + 1 150 + 2 200 + 3 300 + 4 400 + 5 25 + 6 50 + 7 75 + } +} + default_region U diff -r 4b24260125f3 -r 3e1573fa22cf io.c --- a/io.c Wed Sep 18 19:10:54 2013 -0700 +++ b/io.c Tue Oct 01 23:51:16 2013 -0700 @@ -1,6 +1,6 @@ /* Copyright 2013 Michael Pavone - This file is part of BlastEm. + This file is part of BlastEm. BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text. */ #include "io.h" @@ -18,7 +18,10 @@ UI_DEBUG_MODE_INC, UI_DEBUG_PAL_INC, UI_ENTER_DEBUGGER, - UI_SAVE_STATE, + UI_SAVE_STATE, + UI_SET_SPEED, + UI_NEXT_SPEED, + UI_PREV_SPEED, UI_EXIT } ui_action; @@ -139,9 +142,9 @@ bind_dpad(joystick, dpad, direction, bind_type, button >> 12, button >> 8 & 0xF, button & 0xFF); } -void bind_ui(int keycode, ui_action action) +void bind_ui(int keycode, ui_action action, uint8_t param) { - bind_key(keycode, BIND_UI, action, 0, 0); + bind_key(keycode, BIND_UI, action, 0, param); } void handle_binding_down(keybinding * binding) @@ -183,6 +186,10 @@ uint8_t ui_debug_mode = 0; uint8_t ui_debug_pal = 0; +int current_speed = 0; +int num_speeds = 1; +uint32_t * speeds = NULL; + void handle_binding_up(keybinding * binding) { switch(binding->bind_type) @@ -219,6 +226,32 @@ case UI_SAVE_STATE: save_state = 1; break; + case UI_NEXT_SPEED: + current_speed++; + if (current_speed >= num_speeds) { + current_speed = 0; + } + printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]); + set_speed_percent(genesis, speeds[current_speed]); + break; + case UI_PREV_SPEED: + current_speed--; + if (current_speed < 0) { + current_speed = num_speeds - 1; + } + printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]); + set_speed_percent(genesis, speeds[current_speed]); + break; + case UI_SET_SPEED: + if (binding->value < num_speeds) { + current_speed = binding->value; + printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]); + set_speed_percent(genesis, speeds[current_speed]); + } else { + printf("Setting speed to %d\n", speeds[current_speed]); + set_speed_percent(genesis, binding->value); + } + break; case UI_EXIT: exit(0); } @@ -286,6 +319,7 @@ fprintf(stderr, "Gamepad mapping string '%s' refers to an invalid gamepad number %c\n", target, target[gpadslen]); } } else if(!memcmp(target, "ui.", strlen("ui."))) { + *padbutton_out = 0; if (!strcmp(target + 3, "vdp_debug_mode")) { *ui_out = UI_DEBUG_MODE_INC; } else if(!strcmp(target + 3, "vdp_debug_pal")) { @@ -294,6 +328,13 @@ *ui_out = UI_ENTER_DEBUGGER; } else if(!strcmp(target + 3, "save_state")) { *ui_out = UI_SAVE_STATE; + } else if(!memcmp(target + 3, "set_speed.", strlen("set_speed."))) { + *ui_out = UI_SET_SPEED; + *padbutton_out = atoi(target + 3 + strlen("set_speed.")); + } else if(!strcmp(target + 3, "next_speed")) { + *ui_out = UI_NEXT_SPEED; + } else if(!strcmp(target + 3, "prev_speed")) { + *ui_out = UI_PREV_SPEED; } else if(!strcmp(target + 3, "exit")) { *ui_out = UI_EXIT; } else { @@ -309,7 +350,7 @@ void process_keys(tern_node * cur, tern_node * special, tern_node * padbuttons, char * prefix) { - char * curstr; + char * curstr = NULL; int len; if (!cur) { return; @@ -324,8 +365,8 @@ len = 0; } curstr[len] = cur->el; + curstr[len+1] = 0; if (cur->el) { - curstr[len+1] = 0; process_keys(cur->straight.next, special, padbuttons, curstr); } else { int keycode = tern_find_int(special, curstr, 0); @@ -341,11 +382,59 @@ if (bindtype == 1) { bind_gamepad(keycode, padnum, button); } else if(bindtype == 2) { - bind_ui(keycode, ui_func); + bind_ui(keycode, ui_func, button); } } process_keys(cur->left, special, padbuttons, prefix); process_keys(cur->right, special, padbuttons, prefix); + if (curstr && len) { + free(curstr); + } +} + +void process_speeds(tern_node * cur, char * prefix) +{ + char * curstr = NULL; + int len; + if (!cur) { + return; + } + char onec[2]; + if (prefix) { + len = strlen(prefix); + curstr = malloc(len + 2); + memcpy(curstr, prefix, len); + } else { + curstr = onec; + len = 0; + } + curstr[len] = cur->el; + curstr[len+1] = 0; + if (cur->el) { + process_speeds(cur->straight.next, curstr); + } else { + int speed_index = atoi(curstr); + if (speed_index < 1) { + if (!strcmp(curstr, "0")) { + fputs("Speed index 0 cannot be set to a custom value\n", stderr); + } else { + fprintf(stderr, "%s is not a valid speed index", curstr); + } + } else { + if (speed_index >= num_speeds) { + speeds = realloc(speeds, sizeof(uint32_t) * (speed_index+1)); + for(; num_speeds < speed_index + 1; num_speeds++) { + speeds[num_speeds] = 0; + } + } + speeds[speed_index] = atoi(cur->straight.value.ptrval); + } + } + process_speeds(cur->left, prefix); + process_speeds(cur->right, prefix); + if (curstr && len) { + free(curstr); + } } void set_keybindings() @@ -425,6 +514,16 @@ } } } + tern_node * speed_nodes = tern_find_prefix(config, "clocksspeeds"); + speeds = malloc(sizeof(uint32_t)); + speeds[0] = 100; + process_speeds(speed_nodes, NULL); + for (int i = 0; i < num_speeds; i++) { + if (!speeds[i]) { + fprintf(stderr, "Speed index %d was not set to a valid percentage!", i); + speeds[i] = 100; + } + } } #define TH 0x40 diff -r 4b24260125f3 -r 3e1573fa22cf psg.c --- a/psg.c Wed Sep 18 19:10:54 2013 -0700 +++ b/psg.c Tue Oct 01 23:51:16 2013 -0700 @@ -1,6 +1,6 @@ /* Copyright 2013 Michael Pavone - This file is part of BlastEm. + This file is part of BlastEm. BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text. */ #include "psg.h" @@ -13,15 +13,23 @@ memset(context, 0, sizeof(*context)); context->audio_buffer = malloc(sizeof(*context->audio_buffer) * samples_frame); context->back_buffer = malloc(sizeof(*context->audio_buffer) * samples_frame); - double clock_rate = (double)master_clock / (double)clock_div; - context->buffer_inc = ((double)sample_rate / (double)master_clock) * clock_div; context->clock_inc = clock_div; + context->sample_rate = sample_rate; context->samples_frame = samples_frame; + psg_adjust_master_clock(context, master_clock); for (int i = 0; i < 4; i++) { context->volume[i] = 0xF; } } +#define BUFFER_INC_RES 1000000000UL + +void psg_adjust_master_clock(psg_context * context, uint32_t master_clock) +{ + uint64_t old_inc = context->buffer_inc; + context->buffer_inc = ((BUFFER_INC_RES * (uint64_t)context->sample_rate) / (uint64_t)master_clock) * (uint64_t)context->clock_inc; +} + void psg_write(psg_context * context, uint8_t value) { if (value & 0x80) { @@ -70,7 +78,7 @@ //table shamelessly swiped from PSG doc from smspower.org int16_t volume_table[16] = { 32767/PSG_VOL_DIV, 26028/PSG_VOL_DIV, 20675/PSG_VOL_DIV, 16422/PSG_VOL_DIV, 13045/PSG_VOL_DIV, 10362/PSG_VOL_DIV, - 8231/PSG_VOL_DIV, 6568/PSG_VOL_DIV, 5193/PSG_VOL_DIV, 4125/PSG_VOL_DIV, 3277/PSG_VOL_DIV, 2603/PSG_VOL_DIV, + 8231/PSG_VOL_DIV, 6568/PSG_VOL_DIV, 5193/PSG_VOL_DIV, 4125/PSG_VOL_DIV, 3277/PSG_VOL_DIV, 2603/PSG_VOL_DIV, 2067/PSG_VOL_DIV, 1642/PSG_VOL_DIV, 1304/PSG_VOL_DIV, 0 }; @@ -97,8 +105,8 @@ } } context->buffer_fraction += context->buffer_inc; - if (context->buffer_fraction >= 1.0) { - context->buffer_fraction -= 1.0; + if (context->buffer_fraction >= BUFFER_INC_RES) { + context->buffer_fraction -= BUFFER_INC_RES; int16_t acc = 0; for (int i = 0; i < 3; i++) { if (context->output_state[i]) { diff -r 4b24260125f3 -r 3e1573fa22cf psg.h --- a/psg.h Wed Sep 18 19:10:54 2013 -0700 +++ b/psg.h Tue Oct 01 23:51:16 2013 -0700 @@ -1,6 +1,6 @@ /* Copyright 2013 Michael Pavone - This file is part of BlastEm. + This file is part of BlastEm. BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text. */ #ifndef PSG_CONTEXT_H_ @@ -11,11 +11,12 @@ typedef struct { int16_t *audio_buffer; int16_t *back_buffer; - double buffer_fraction; - double buffer_inc; + uint64_t buffer_fraction; + uint64_t buffer_inc; uint32_t buffer_pos; uint32_t clock_inc; uint32_t cycles; + uint32_t sample_rate; uint32_t samples_frame; uint16_t lsfr; uint16_t counter_load[4]; @@ -30,6 +31,7 @@ void psg_init(psg_context * context, uint32_t sample_rate, uint32_t master_clock, uint32_t clock_div, uint32_t samples_frame); +void psg_adjust_master_clock(psg_context * context, uint32_t master_clock); void psg_write(psg_context * context, uint8_t value); void psg_run(psg_context * context, uint32_t cycles); diff -r 4b24260125f3 -r 3e1573fa22cf ym2612.c --- a/ym2612.c Wed Sep 18 19:10:54 2013 -0700 +++ b/ym2612.c Tue Oct 01 23:51:16 2013 -0700 @@ -1,6 +1,6 @@ /* Copyright 2013 Michael Pavone - This file is part of BlastEm. + This file is part of BlastEm. BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text. */ #include @@ -127,6 +127,13 @@ } } } +#define BUFFER_INC_RES 1000000000UL + +void ym_adjust_master_clock(ym2612_context * context, uint32_t master_clock) +{ + uint64_t old_inc = context->buffer_inc; + context->buffer_inc = ((BUFFER_INC_RES * (uint64_t)context->sample_rate) / (uint64_t)master_clock) * (uint64_t)context->clock_inc; +} void ym_init(ym2612_context * context, uint32_t sample_rate, uint32_t master_clock, uint32_t clock_div, uint32_t sample_limit, uint32_t options) { @@ -134,8 +141,10 @@ memset(context, 0, sizeof(*context)); context->audio_buffer = malloc(sizeof(*context->audio_buffer) * sample_limit*2); context->back_buffer = malloc(sizeof(*context->audio_buffer) * sample_limit*2); - context->buffer_inc = ((double)sample_rate / (double)master_clock) * clock_div * 6; + context->sample_rate = sample_rate; context->clock_inc = clock_div * 6; + ym_adjust_master_clock(context, master_clock); + context->sample_limit = sample_limit*2; context->write_cycle = CYCLE_NEVER; for (int i = 0; i < NUM_OPERATORS; i++) { @@ -451,29 +460,29 @@ context->buffer_fraction += context->buffer_inc; if (context->current_op == NUM_OPERATORS) { context->current_op = 0; - if (context->buffer_fraction > 1.0) { - context->buffer_fraction -= 1.0; - context->audio_buffer[context->buffer_pos] = 0; - context->audio_buffer[context->buffer_pos + 1] = 0; - for (int i = 0; i < NUM_CHANNELS; i++) { - int16_t value = context->channels[i].output & 0x3FE0; - if (value & 0x2000) { - value |= 0xC000; - } - if (context->channels[i].logfile) { - fwrite(&value, sizeof(value), 1, context->channels[i].logfile); - } - if (context->channels[i].lr & 0x80) { - context->audio_buffer[context->buffer_pos] += value / YM_VOLUME_DIVIDER; - } - if (context->channels[i].lr & 0x40) { - context->audio_buffer[context->buffer_pos+1] += value / YM_VOLUME_DIVIDER; - } + } + if (context->buffer_fraction > BUFFER_INC_RES) { + context->buffer_fraction -= BUFFER_INC_RES; + context->audio_buffer[context->buffer_pos] = 0; + context->audio_buffer[context->buffer_pos + 1] = 0; + for (int i = 0; i < NUM_CHANNELS; i++) { + int16_t value = context->channels[i].output & 0x3FE0; + if (value & 0x2000) { + value |= 0xC000; } - context->buffer_pos += 2; - if (context->buffer_pos == context->sample_limit) { - render_wait_ym(context); + if (context->channels[i].logfile) { + fwrite(&value, sizeof(value), 1, context->channels[i].logfile); + } + if (context->channels[i].lr & 0x80) { + context->audio_buffer[context->buffer_pos] += value / YM_VOLUME_DIVIDER; } + if (context->channels[i].lr & 0x40) { + context->audio_buffer[context->buffer_pos+1] += value / YM_VOLUME_DIVIDER; + } + } + context->buffer_pos += 2; + if (context->buffer_pos == context->sample_limit) { + render_wait_ym(context); } } } diff -r 4b24260125f3 -r 3e1573fa22cf ym2612.h --- a/ym2612.h Wed Sep 18 19:10:54 2013 -0700 +++ b/ym2612.h Tue Oct 01 23:51:16 2013 -0700 @@ -1,6 +1,6 @@ /* Copyright 2013 Michael Pavone - This file is part of BlastEm. + This file is part of BlastEm. BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text. */ #ifndef YM2612_H_ @@ -59,10 +59,11 @@ typedef struct { int16_t *audio_buffer; int16_t *back_buffer; - double buffer_fraction; - double buffer_inc; + uint64_t buffer_fraction; + uint64_t buffer_inc; uint32_t clock_inc; uint32_t buffer_pos; + uint32_t sample_rate; uint32_t sample_limit; uint32_t current_cycle; uint32_t write_cycle; @@ -93,6 +94,7 @@ } ym2612_context; void ym_init(ym2612_context * context, uint32_t sample_rate, uint32_t master_clock, uint32_t clock_div, uint32_t sample_limit, uint32_t options); +void ym_adjust_master_clock(ym2612_context * context, uint32_t master_clock); void ym_run(ym2612_context * context, uint32_t to_cycle); void ym_address_write_part1(ym2612_context * context, uint8_t address); void ym_address_write_part2(ym2612_context * context, uint8_t address);