# HG changeset patch # User Mike Pavone # Date 1369377762 25200 # Node ID 15dd6418fe678f6a090d96bc7f473d866c9551a4 # Parent a60e527cd21f482fac76fd636871e244b59830e7 Initial PSG support. Mostly works, noise channel is borked though. diff -r a60e527cd21f -r 15dd6418fe67 Makefile --- a/Makefile Wed May 22 09:37:02 2013 -0700 +++ b/Makefile Thu May 23 23:42:42 2013 -0700 @@ -7,8 +7,8 @@ all : dis trans stateview blastem -blastem : blastem.o 68kinst.o gen_x86.o m68k_to_x86.o z80inst.o z80_to_x86.o x86_backend.o runtime.o zruntime.o mem.o vdp.o ym2612.o render_sdl.o - $(CC) -o blastem blastem.o 68kinst.o gen_x86.o m68k_to_x86.o z80inst.o z80_to_x86.o x86_backend.o runtime.o zruntime.o mem.o vdp.o ym2612.o render_sdl.o `pkg-config --libs $(LIBS)` +blastem : blastem.o 68kinst.o gen_x86.o m68k_to_x86.o z80inst.o z80_to_x86.o x86_backend.o runtime.o zruntime.o mem.o vdp.o ym2612.o psg.o render_sdl.o + $(CC) -o blastem blastem.o 68kinst.o gen_x86.o m68k_to_x86.o z80inst.o z80_to_x86.o x86_backend.o runtime.o zruntime.o mem.o vdp.o ym2612.o psg.o render_sdl.o `pkg-config --libs $(LIBS)` dis : dis.o 68kinst.o $(CC) -o dis dis.o 68kinst.o diff -r a60e527cd21f -r 15dd6418fe67 blastem.c --- a/blastem.c Wed May 22 09:37:02 2013 -0700 +++ b/blastem.c Thu May 23 23:42:42 2013 -0700 @@ -14,6 +14,7 @@ #define Z80_RAM_BYTES 8 * 1024 #define MCLKS_PER_68K 7 #define MCLKS_PER_Z80 15 +#define MCLKS_PER_PSG (MCLKS_PER_Z80*16) //TODO: Figure out the exact value for this #define CYCLE_NEVER 0xFFFFFFFF #define LINES_NTSC 262 @@ -200,6 +201,8 @@ gen->ym->current_cycle -= mclks_per_frame/MCLKS_PER_68K; //printf("reached frame end | 68K Cycles: %d, MCLK Cycles: %d\n", context->current_cycle, mclks); vdp_run_context(v_context, mclks_per_frame); + psg_run(gen->psg, mclks/MCLKS_PER_PSG); + gen->psg->cycles -= mclks_per_frame/MCLKS_PER_PSG; if (!headless) { break_on_sync |= wait_render_frame(v_context, frame_limit); } @@ -228,6 +231,7 @@ } else { //printf("running VDP for %d cycles\n", mclks - v_context->cycles); vdp_run_context(v_context, mclks); + psg_run(gen->psg, mclks/MCLKS_PER_PSG); } if (context->int_ack) { vdp_int_ack(v_context, context->int_ack); @@ -318,7 +322,9 @@ context->current_cycle = v_context->cycles / MCLKS_PER_68K; } } else if (vdp_port < 0x18) { - //TODO: Implement PSG + genesis_context * gen = context->system; + psg_run(gen->psg, (context->current_cycle * MCLKS_PER_68K) / MCLKS_PER_PSG); + psg_write(gen->psg, value); } else { //TODO: Implement undocumented test register(s) } @@ -327,7 +333,7 @@ m68k_context * vdp_port_write_b(uint32_t vdp_port, m68k_context * context, uint8_t value) { - return vdp_port_write(vdp_port, context, value | value << 8); + return vdp_port_write(vdp_port, context, vdp_port < 0x10 ? value | value << 8 : value); } uint16_t vdp_port_read(uint32_t vdp_port, m68k_context * context) @@ -1385,6 +1391,9 @@ } } +#define PSG_CLKS_NTSC (3579545/16) +#define PSG_CLKS_PAL (3546893/16) + int main(int argc, char ** argv) { if (argc < 2) { @@ -1456,12 +1465,13 @@ update_title(); width = width < 320 ? 320 : width; height = height < 240 ? (width/320) * 240 : height; - if (!headless) { - render_init(width, height, title); - } + uint32_t fps = 60; if (version_reg & 0x40) { mclks_per_frame = MCLKS_LINE * LINES_PAL; - render_fps(50); + fps = 50; + } + if (!headless) { + render_init(width, height, title, fps); } vdp_context v_context; @@ -1470,6 +1480,9 @@ ym2612_context y_context; ym_init(&y_context); + psg_context p_context; + psg_init(&p_context, render_sample_rate(), fps == 60 ? PSG_CLKS_NTSC : PSG_CLKS_PAL, render_audio_buffer()); + z80_context z_context; x86_z80_options z_opts; init_x86_z80_opts(&z_opts); @@ -1486,6 +1499,7 @@ gen.z80 = &z_context; gen.vdp = &v_context; gen.ym = &y_context; + gen.psg = &p_context; genesis = &gen; int fname_size = strlen(argv[1]); diff -r a60e527cd21f -r 15dd6418fe67 blastem.h --- a/blastem.h Wed May 22 09:37:02 2013 -0700 +++ b/blastem.h Thu May 23 23:42:42 2013 -0700 @@ -6,6 +6,7 @@ #include "z80_to_x86.h" #include "ym2612.h" #include "vdp.h" +#include "psg.h" typedef struct { uint32_t th_counter; @@ -24,6 +25,7 @@ z80_context *z80; vdp_context *vdp; ym2612_context *ym; + psg_context *psg; uint8_t *save_ram; uint32_t save_ram_mask; uint32_t save_flags; diff -r a60e527cd21f -r 15dd6418fe67 psg.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/psg.c Thu May 23 23:42:42 2013 -0700 @@ -0,0 +1,112 @@ +#include "psg.h" +#include "render.h" +#include +#include + +void psg_init(psg_context * context, uint32_t sample_rate, uint32_t clock_rate, uint32_t samples_frame) +{ + 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); + context->buffer_inc = (double)sample_rate / (double)clock_rate; + context->samples_frame = samples_frame; + for (int i = 0; i < 4; i++) { + context->volume[i] = 0xF; + } +} + +void psg_write(psg_context * context, uint8_t value) +{ + if (value & 0x80) { + context->latch = value & 0x70; + uint8_t channel = value >> 5 & 0x3; + if (value & 0x10) { + context->volume[channel] = value & 0xF; + } else { + if (channel == 3) { + switch(value & 0x3) + { + case 0: + case 1: + case 2: + context->counter_load[3] = 0x10 << (value & 0x3); + context->noise_use_tone = 0; + break; + default: + context->counter_load[3] = context->counter_load[2]; + context->noise_use_tone = 1; + } + context->noise_type = value & 0x4; + context->lsfr = 0x8000; + } else { + context->counter_load[channel] = (context->counter_load[channel] & 0x3F0) | (value & 0xF); + if (channel == 2 && context->noise_use_tone) { + context->counter_load[3] = context->counter_load[2]; + } + } + } + } else { + if (!(context->latch & 0x10)) { + uint8_t channel = context->latch >> 5 & 0x3; + if (channel != 3) { + context->counter_load[channel] = (value << 4 & 0x3F0) | (context->counter_load[channel] & 0xF); + if (channel == 2 && context->noise_use_tone) { + context->counter_load[3] = context->counter_load[2]; + } + } + } + } +} + +#define PSG_VOL_DIV 2 + +//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, + 2067/PSG_VOL_DIV, 1642/PSG_VOL_DIV, 1304/PSG_VOL_DIV, 0 +}; + +void psg_run(psg_context * context, uint32_t cycles) +{ + while (context->cycles < cycles) { + for (int i = 0; i < 4; i++) { + if (context->counters[i]) { + context->counters[i] -= 1; + } + if (!context->counters[i]) { + context->counters[i] = context->counter_load[i]; + context->output_state[i] = !context->output_state[i]; + if (i == 3 && context->output_state[i]) { + context->noise_out = context->lsfr & 1; + context->lsfr = (context->lsfr >> 1) | (context->lsfr << 15); + if (context->noise_type) { + //white noise + if (context->lsfr & 0x40) { + context->lsfr ^= 0x8000; + } + } + } + } + } + context->buffer_fraction += context->buffer_inc; + if (context->buffer_fraction >= 1.0) { + context->buffer_fraction -= 1.0; + int16_t acc = 0; + for (int i = 0; i < 3; i++) { + if (context->output_state[i]) { + acc += volume_table[context->volume[i]]; + } + } + if (context->noise_out) { + acc += volume_table[context->volume[3]]; + } + context->audio_buffer[context->buffer_pos++] = acc; + if (context->buffer_pos == context->samples_frame) { + render_wait_audio(context); + } + } + context->cycles++; + } +} + diff -r a60e527cd21f -r 15dd6418fe67 psg.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/psg.h Thu May 23 23:42:42 2013 -0700 @@ -0,0 +1,32 @@ +#ifndef PSG_CONTEXT_H_ +#define PSG_CONTEXT_H_ + +#include + +typedef struct { + int16_t *audio_buffer; + int16_t *back_buffer; + double buffer_fraction; + double buffer_inc; + uint32_t buffer_pos; + uint32_t back_pos; + uint32_t cycles; + uint32_t samples_frame; + uint16_t lsfr; + uint16_t counter_load[4]; + uint16_t counters[4]; + uint8_t volume[4]; + uint8_t output_state[4]; + uint8_t noise_out; + uint8_t noise_use_tone; + uint8_t noise_type; + uint8_t latch; +} psg_context; + + +void psg_init(psg_context * context, uint32_t sample_rate, uint32_t clock_rate, uint32_t samples_frame); +void psg_write(psg_context * context, uint8_t value); +void psg_run(psg_context * context, uint32_t cycles); + +#endif //PSG_CONTEXT_H_ + diff -r a60e527cd21f -r 15dd6418fe67 render.h --- a/render.h Wed May 22 09:37:02 2013 -0700 +++ b/render.h Thu May 23 23:42:42 2013 -0700 @@ -2,11 +2,15 @@ #define RENDER_SDL_H_ #include "vdp.h" -void render_init(int width, int height, char * title); +#include "psg.h" +void render_init(int width, int height, char * title, uint32_t fps); void render_context(vdp_context * context); void render_wait_quit(vdp_context * context); +void render_wait_audio(psg_context * context); int wait_render_frame(vdp_context * context, int frame_limit); void render_fps(uint32_t fps); +uint32_t render_audio_buffer(); +uint32_t render_sample_rate(); #endif //RENDER_SDL_H_ diff -r a60e527cd21f -r 15dd6418fe67 render_sdl.c --- a/render_sdl.c Wed May 22 09:37:02 2013 -0700 +++ b/render_sdl.c Thu May 23 23:42:42 2013 -0700 @@ -16,9 +16,41 @@ uint32_t min_delay; uint32_t frame_delay = 1000/60; -void render_init(int width, int height, char * title) +int16_t * current_audio = NULL; +int16_t * next_audio = NULL; + +uint32_t buffer_samples, sample_rate; +uint32_t missing_count; + +SDL_mutex * audio_mutex; +SDL_cond * audio_ready; +SDL_cond * audio_cond; + +void audio_callback(void * userdata, uint8_t *byte_stream, int len) { - if (SDL_Init(SDL_INIT_VIDEO) < 0) { + //puts("audio_callback"); + int16_t * stream = (int16_t *)byte_stream; + int samples = len/(sizeof(int16_t)*2); + int16_t * source_buf; + + SDL_LockMutex(audio_mutex); + while (!current_audio) { + SDL_CondWait(audio_ready, audio_mutex); + } + source_buf = current_audio; + current_audio = NULL; + SDL_CondSignal(audio_cond); + SDL_UnlockMutex(audio_mutex); + + for (int i = 0; i < samples; i++) { + *(stream++) = source_buf[i]; + *(stream++) = source_buf[i]; + } +} + +void render_init(int width, int height, char * title, uint32_t fps) +{ + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError()); exit(1); } @@ -64,6 +96,29 @@ min_delay = 1; } printf("minimum delay: %d\n", min_delay); + + frame_delay = 1000/fps; + + audio_mutex = SDL_CreateMutex(); + audio_cond = SDL_CreateCond(); + audio_ready = SDL_CreateCond(); + + SDL_AudioSpec desired, actual; + desired.freq = 48000; + desired.format = AUDIO_S16SYS; + desired.channels = 2; + desired.samples = 1024; + desired.callback = audio_callback; + desired.userdata = NULL; + + if (SDL_OpenAudio(&desired, &actual) < 0) { + fprintf(stderr, "Unable to open SDL audio: %s\n", SDL_GetError()); + exit(1); + } + buffer_samples = actual.samples; + sample_rate = actual.freq; + printf("Initialized audio at frequency %d with a %d sample buffer\n", actual.freq, actual.samples); + SDL_PauseAudio(0); } void render_context(vdp_context * context) @@ -230,135 +285,141 @@ #define BUTTON_START 0x20 #define BUTTON_C 0x20 +int32_t handle_event(SDL_Event *event) +{ + FILE * outfile; + switch (event->type) { + case SDL_KEYDOWN: + switch(event->key.keysym.sym) + { + case SDLK_LEFTBRACKET: + render_dbg++; + if (render_dbg == 4) { + render_dbg = 0; + } + break; + case SDLK_RIGHTBRACKET: + debug_pal++; + if (debug_pal == 4) { + debug_pal = 0; + } + break; + case SDLK_t: + /*outfile = fopen("state.gst", "wb"); + fwrite("GST\0\0\0\xE0\x40", 1, 8, outfile); + vdp_save_state(context, outfile); + fclose(outfile); + puts("state saved to state.gst");*/ + break; + case SDLK_u: + return 1; + case SDLK_RETURN: + gamepad_1.input[GAMEPAD_TH0] |= BUTTON_START; + break; + case SDLK_UP: + gamepad_1.input[GAMEPAD_TH0] |= DPAD_UP; + gamepad_1.input[GAMEPAD_TH1] |= DPAD_UP; + break; + case SDLK_DOWN: + gamepad_1.input[GAMEPAD_TH0] |= DPAD_DOWN; + gamepad_1.input[GAMEPAD_TH1] |= DPAD_DOWN; + break; + case SDLK_LEFT: + gamepad_1.input[GAMEPAD_TH1] |= DPAD_LEFT; + break; + case SDLK_RIGHT: + gamepad_1.input[GAMEPAD_TH1] |= DPAD_RIGHT; + break; + case SDLK_a: + gamepad_1.input[GAMEPAD_TH0] |= BUTTON_A; + //printf("BUTTON_A Dn | GAMEPAD_TH0: %X\n", gamepad_1.input[GAMEPAD_TH0]); + break; + case SDLK_s: + gamepad_1.input[GAMEPAD_TH1] |= BUTTON_B; + gamepad_1.input[GAMEPAD_EXTRA] |= BUTTON_B; + break; + case SDLK_d: + gamepad_1.input[GAMEPAD_TH1] |= BUTTON_C; + gamepad_1.input[GAMEPAD_EXTRA] |= BUTTON_C; + break; + case SDLK_q: + gamepad_1.input[GAMEPAD_EXTRA] |= BUTTON_X; + break; + case SDLK_w: + gamepad_1.input[GAMEPAD_EXTRA] |= BUTTON_Y; + break; + case SDLK_e: + gamepad_1.input[GAMEPAD_EXTRA] |= BUTTON_Z; + break; + case SDLK_f: + gamepad_1.input[GAMEPAD_EXTRA] |= BUTTON_MODE; + break; + } + break; + case SDL_KEYUP: + switch(event->key.keysym.sym) + { + case SDLK_RETURN: + gamepad_1.input[GAMEPAD_TH0] &= ~BUTTON_START; + break; + case SDLK_UP: + gamepad_1.input[GAMEPAD_TH0] &= ~DPAD_UP; + gamepad_1.input[GAMEPAD_TH1] &= ~DPAD_UP; + break; + case SDLK_DOWN: + gamepad_1.input[GAMEPAD_TH0] &= ~DPAD_DOWN; + gamepad_1.input[GAMEPAD_TH1] &= ~DPAD_DOWN; + break; + case SDLK_LEFT: + gamepad_1.input[GAMEPAD_TH1] &= ~DPAD_LEFT; + break; + case SDLK_RIGHT: + gamepad_1.input[GAMEPAD_TH1] &= ~DPAD_RIGHT; + break; + case SDLK_a: + gamepad_1.input[GAMEPAD_TH0] &= ~BUTTON_A; + //printf("BUTTON_A Up | GAMEPAD_TH0: %X\n", gamepad_1.input[GAMEPAD_TH0]); + break; + case SDLK_s: + gamepad_1.input[GAMEPAD_TH1] &= ~BUTTON_B; + gamepad_1.input[GAMEPAD_EXTRA] &= ~BUTTON_B; + break; + case SDLK_d: + gamepad_1.input[GAMEPAD_TH1] &= ~BUTTON_C; + gamepad_1.input[GAMEPAD_EXTRA] &= ~BUTTON_C; + break; + case SDLK_q: + gamepad_1.input[GAMEPAD_EXTRA] &= ~BUTTON_X; + break; + case SDLK_w: + gamepad_1.input[GAMEPAD_EXTRA] &= ~BUTTON_Y; + break; + case SDLK_e: + gamepad_1.input[GAMEPAD_EXTRA] &= ~BUTTON_Z; + break; + case SDLK_f: + gamepad_1.input[GAMEPAD_EXTRA] &= ~BUTTON_MODE; + break; + } + break; + case SDL_QUIT: + puts(""); + exit(0); + } + return 0; +} + uint32_t frame_counter = 0; uint32_t start = 0; int wait_render_frame(vdp_context * context, int frame_limit) { - FILE * outfile; SDL_Event event; int ret = 0; while(SDL_PollEvent(&event)) { - switch (event.type) { - case SDL_KEYDOWN: - switch(event.key.keysym.sym) - { - case SDLK_LEFTBRACKET: - render_dbg++; - if (render_dbg == 4) { - render_dbg = 0; - } - break; - case SDLK_RIGHTBRACKET: - debug_pal++; - if (debug_pal == 4) { - debug_pal = 0; - } - break; - case SDLK_t: - outfile = fopen("state.gst", "wb"); - fwrite("GST\0\0\0\xE0\x40", 1, 8, outfile); - vdp_save_state(context, outfile); - fclose(outfile); - puts("state saved to state.gst"); - break; - case SDLK_u: - ret = 1; - break; - case SDLK_RETURN: - gamepad_1.input[GAMEPAD_TH0] |= BUTTON_START; - break; - case SDLK_UP: - gamepad_1.input[GAMEPAD_TH0] |= DPAD_UP; - gamepad_1.input[GAMEPAD_TH1] |= DPAD_UP; - break; - case SDLK_DOWN: - gamepad_1.input[GAMEPAD_TH0] |= DPAD_DOWN; - gamepad_1.input[GAMEPAD_TH1] |= DPAD_DOWN; - break; - case SDLK_LEFT: - gamepad_1.input[GAMEPAD_TH1] |= DPAD_LEFT; - break; - case SDLK_RIGHT: - gamepad_1.input[GAMEPAD_TH1] |= DPAD_RIGHT; - break; - case SDLK_a: - gamepad_1.input[GAMEPAD_TH0] |= BUTTON_A; - //printf("BUTTON_A Dn | GAMEPAD_TH0: %X\n", gamepad_1.input[GAMEPAD_TH0]); - break; - case SDLK_s: - gamepad_1.input[GAMEPAD_TH1] |= BUTTON_B; - gamepad_1.input[GAMEPAD_EXTRA] |= BUTTON_B; - break; - case SDLK_d: - gamepad_1.input[GAMEPAD_TH1] |= BUTTON_C; - gamepad_1.input[GAMEPAD_EXTRA] |= BUTTON_C; - break; - case SDLK_q: - gamepad_1.input[GAMEPAD_EXTRA] |= BUTTON_X; - break; - case SDLK_w: - gamepad_1.input[GAMEPAD_EXTRA] |= BUTTON_Y; - break; - case SDLK_e: - gamepad_1.input[GAMEPAD_EXTRA] |= BUTTON_Z; - break; - case SDLK_f: - gamepad_1.input[GAMEPAD_EXTRA] |= BUTTON_MODE; - break; - } - break; - case SDL_KEYUP: - switch(event.key.keysym.sym) - { - case SDLK_RETURN: - gamepad_1.input[GAMEPAD_TH0] &= ~BUTTON_START; - break; - case SDLK_UP: - gamepad_1.input[GAMEPAD_TH0] &= ~DPAD_UP; - gamepad_1.input[GAMEPAD_TH1] &= ~DPAD_UP; - break; - case SDLK_DOWN: - gamepad_1.input[GAMEPAD_TH0] &= ~DPAD_DOWN; - gamepad_1.input[GAMEPAD_TH1] &= ~DPAD_DOWN; - break; - case SDLK_LEFT: - gamepad_1.input[GAMEPAD_TH1] &= ~DPAD_LEFT; - break; - case SDLK_RIGHT: - gamepad_1.input[GAMEPAD_TH1] &= ~DPAD_RIGHT; - break; - case SDLK_a: - gamepad_1.input[GAMEPAD_TH0] &= ~BUTTON_A; - //printf("BUTTON_A Up | GAMEPAD_TH0: %X\n", gamepad_1.input[GAMEPAD_TH0]); - break; - case SDLK_s: - gamepad_1.input[GAMEPAD_TH1] &= ~BUTTON_B; - gamepad_1.input[GAMEPAD_EXTRA] &= ~BUTTON_B; - break; - case SDLK_d: - gamepad_1.input[GAMEPAD_TH1] &= ~BUTTON_C; - gamepad_1.input[GAMEPAD_EXTRA] &= ~BUTTON_C; - break; - case SDLK_q: - gamepad_1.input[GAMEPAD_EXTRA] &= ~BUTTON_X; - break; - case SDLK_w: - gamepad_1.input[GAMEPAD_EXTRA] &= ~BUTTON_Y; - break; - case SDLK_e: - gamepad_1.input[GAMEPAD_EXTRA] &= ~BUTTON_Z; - break; - case SDLK_f: - gamepad_1.input[GAMEPAD_EXTRA] &= ~BUTTON_MODE; - break; - } - break; - case SDL_QUIT: - puts(""); - exit(0); - } + ret = handle_event(&event); } if (frame_limit) { + puts("evil frame limit"); //TODO: Adjust frame delay so we actually get 60 FPS rather than 62.5 FPS uint32_t current = SDL_GetTicks(); uint32_t desired = last_frame + frame_delay; @@ -387,9 +448,34 @@ return ret; } +void render_wait_audio(psg_context * context) +{ + SDL_LockMutex(audio_mutex); + while (current_audio != NULL) { + SDL_CondWait(audio_cond, audio_mutex); + } + current_audio = context->audio_buffer; + SDL_CondSignal(audio_ready); + + context->audio_buffer = context->back_buffer; + context->back_buffer = current_audio; + SDL_UnlockMutex(audio_mutex); + context->buffer_pos = 0; +} + void render_fps(uint32_t fps) { frame_delay = 1000/fps; } +uint32_t render_audio_buffer() +{ + return buffer_samples; +} +uint32_t render_sample_rate() +{ + return sample_rate; +} + +