gigatron/rom/Contrib/dhkolf/libgtemu/gtsdl.c
2025-01-28 19:17:01 +03:00

433 lines
9.4 KiB
C

/*
This file is part of libgtemu, a library for Gigatron emulation.
Copyright (C) 2019 David Heiko Kolf
Published under the BSD-2-Clause license.
https://opensource.org/licenses/BSD-2-Clause
*/
#include "gtsdl.h"
static unsigned short audiobuffers[3][1024];
static size_t firstsample[3];
static size_t lastsample[3];
static unsigned short lastsamplevalue;
static int activeplaybackbuffer;
static int activerecordingbuffer;
static void resettime (struct GTSDLState *s)
{
unsigned int currenttime = SDL_GetTicks();
s->nextsecondtime = currenttime + 1000;
s->nextframe3 = currenttime * 3 + 50; /* 1000/60 => 50/3 ms */
}
static void clearstate (struct GTSDLState *s, int standalone)
{
s->mainwin = NULL;
s->mainrenderer = NULL;
s->gamescreen = NULL;
s->audiodev = 0;
s->fps = 0;
s->standalone = standalone;
}
static SDL_Texture *createtexture (SDL_Renderer *renderer)
{
return SDL_CreateTexture(renderer,
SDL_PIXELFORMAT_RGB444,
SDL_TEXTUREACCESS_STREAMING,
GT_SCREENWIDTH * 4, GT_SCREENHEIGHT);
}
static void audiocallback (void * ud, unsigned char *stream, int len)
{
unsigned short *out = (unsigned short *) stream;
int i, outsize;
i = 0;
outsize = 0;
while (outsize < len &&
activeplaybackbuffer != activerecordingbuffer) {
int f = firstsample[activeplaybackbuffer];
int l = lastsample[activeplaybackbuffer];
for (; outsize + 1 < len && f < l; outsize+=2, f++, i++) {
out[i] = lastsamplevalue =
audiobuffers[activeplaybackbuffer][f];
}
firstsample[activeplaybackbuffer] = f;
if (f >= l) {
activeplaybackbuffer++;
if (activeplaybackbuffer >= 3) {
activeplaybackbuffer = 0;
}
}
}
for (; outsize + 1 < len; outsize+=2, i++) {
/* Avoid clicks by repeating the value of the last
sample. */
out[i] = lastsamplevalue;
}
}
SDL_AudioCallback gtsdl_getaudiocallback()
{
/* To avoid needing special compiler flags for linking as a shared
object, declare audiocallback as static and publish a function
that returns its address. */
return audiocallback;
}
static int setupaudio (struct GTSDLState *s, SDL_AudioDeviceID audiodev,
SDL_AudioSpec *audiospec)
{
if (audiodev == 0) {
/* No sound is being played but to simplify further
calculations, provide a plausible frequency. */
s->audiofreq = 48000;
return 1;
}
if (audiospec->channels != 1) {
SDL_SetError("Only a single sound channel can be used, not %d",
audiospec->channels);
return 0;
}
if (SDL_AUDIO_ISFLOAT(audiospec->format)) {
SDL_SetError("Floating point sound is not supported");
return 0;
}
if (SDL_AUDIO_BITSIZE(audiospec->format) != 16) {
SDL_SetError("Only 16 bit sounds are supported, not %d",
SDL_AUDIO_BITSIZE(audiospec->format));
return 0;
}
s->audiodev = audiodev;
s->audiofreq = audiospec->freq;
return 1;
}
int gtsdl_openwindow (struct GTSDLState *s, const char *title)
{
SDL_AudioSpec desired, obtained;
SDL_AudioDeviceID audiodev;
clearstate(s, 1);
if (SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
return 0;
}
resettime(s);
s->mainwin = SDL_CreateWindow(title,
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
GT_SCREENWIDTH * 4, GT_SCREENHEIGHT, SDL_WINDOW_SHOWN);
if (s->mainwin == NULL) {
return 0;
}
s->mainrenderer = SDL_CreateRenderer(s->mainwin, -1,
SDL_RENDERER_PRESENTVSYNC);
if (s->mainrenderer == NULL) {
return 0;
}
s->gamescreen = createtexture(s->mainrenderer);
if (s->gamescreen == NULL) {
return 0;
}
SDL_memset(audiobuffers, 0, sizeof(audiobuffers));
SDL_memset(firstsample, 0, sizeof(firstsample));
SDL_memset(lastsample, 0, sizeof(lastsample));
activeplaybackbuffer = 2;
activerecordingbuffer = 0;
lastsamplevalue = 0;
SDL_memset(&desired, 0, sizeof(desired));
desired.freq = 48000;
/* I wanted unsigned sound, but that doesn't seem to work
properly (stays silent on my computer). */
desired.format = AUDIO_S16;
desired.channels = 1;
desired.samples = 800; /* samples per frame: 48000 / 60 */
desired.callback = gtsdl_getaudiocallback();
audiodev = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained,
SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
if (!setupaudio(s, audiodev, &obtained)) {
return 0;
} else if (s->audiodev != 0) {
SDL_PauseAudioDevice(s->audiodev, 0);
}
SDL_StartTextInput();
return 1;
}
int gtsdl_setup (struct GTSDLState *s, SDL_Renderer *renderer,
SDL_AudioDeviceID audiodev, SDL_AudioSpec *audiospec)
{
clearstate(s, 0);
resettime(s);
s->gamescreen = createtexture(renderer);
if (s->gamescreen == NULL) {
return 0;
}
if (!setupaudio(s, audiodev, audiospec)) {
return 0;
}
return 1;
}
void gtsdl_close (struct GTSDLState *s)
{
if (s->gamescreen != NULL) {
SDL_DestroyTexture(s->gamescreen);
s->gamescreen = NULL;
}
if (s->standalone) {
SDL_StopTextInput();
if (s->audiodev != 0) {
SDL_CloseAudioDevice(s->audiodev);
s->audiodev = 0;
}
if (s->mainrenderer != NULL) {
SDL_DestroyRenderer(s->mainrenderer);
s->mainrenderer = NULL;
}
if (s->mainwin != NULL) {
SDL_DestroyWindow(s->mainwin);
s->mainwin = NULL;
}
SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
}
}
int gtsdl_getaudiofreq (struct GTSDLState *s)
{
return s->audiofreq;
}
SDL_Texture *gtsdl_render (struct GTSDLState *s, struct GTState *gt,
struct GTPeriph *ph)
{
void *pixels;
int pitch;
SDL_LockAudioDevice(s->audiodev);
activerecordingbuffer++;
if (activerecordingbuffer >= 3) {
activerecordingbuffer = 0;
}
if (activerecordingbuffer == activeplaybackbuffer) {
activeplaybackbuffer++;
if (activeplaybackbuffer >= 3) {
activeplaybackbuffer = 0;
}
}
firstsample[activerecordingbuffer] = 0;
lastsample[activerecordingbuffer] = 0;
SDL_UnlockAudioDevice(s->audiodev);
SDL_LockTexture(s->gamescreen, NULL, &pixels, &pitch);
gtemu_processscreen(gt, ph, pixels, pitch,
audiobuffers[activerecordingbuffer], 1024,
&lastsample[activerecordingbuffer]);
gtemu_placelights(ph, pixels, pitch, s->fps >= 59 && s->fps <= 61);
SDL_UnlockTexture(s->gamescreen);
return s->gamescreen;
}
int gtsdl_runuiframe (struct GTSDLState *s, struct GTState *gt,
struct GTPeriph *ph, SDL_Event *ev)
{
SDL_Texture *texture;
unsigned int currenttime, currenttime3;
if (s->mainrenderer == NULL) {
SDL_SetError("no standalone renderer configured");
return -1;
}
if (SDL_PollEvent(ev)) {
return 1;
}
texture = gtsdl_render(s, gt, ph);
SDL_RenderCopy(s->mainrenderer, texture, NULL, NULL);
currenttime = SDL_GetTicks();
currenttime3 = 3 * currenttime;
if (currenttime3 < s->nextframe3) {
unsigned int difftime3 = s->nextframe3 - currenttime3;
SDL_Delay(difftime3/3);
}
s->nextframe3 += 50;
if (s->nextframe3 < currenttime3) {
/* too slow, skip frames */
s->nextframe3 = currenttime3 + 20;
}
s->framecount++;
if (currenttime > s->nextsecondtime) {
s->fps = s->framecount;
s->framecount = 0;
s->nextsecondtime += 1000;
}
SDL_RenderPresent(s->mainrenderer);
return 0;
}
static int onplainkeydown (struct GTSDLState *s, struct GTState *gt,
struct GTPeriph *ph, SDL_KeyboardEvent *ev)
{
switch (ev->keysym.scancode) {
case SDL_SCANCODE_DELETE:
gt->in &= ~0x80;
return 1;
case SDL_SCANCODE_PAGEDOWN:
gt->in &= ~0x20;
return 1;
case SDL_SCANCODE_INSERT:
gt->in &= ~0x40;
return 1;
case SDL_SCANCODE_PAGEUP:
gt->in &= ~0x10;
return 1;
case SDL_SCANCODE_DOWN:
gt->in &= ~0x04;
return 1;
case SDL_SCANCODE_LEFT:
gt->in &= ~0x02;
return 1;
case SDL_SCANCODE_RIGHT:
gt->in &= ~0x01;
return 1;
case SDL_SCANCODE_UP:
gt->in &= ~0x08;
return 1;
default:
break;
}
switch (ev->keysym.sym) {
case SDLK_RETURN:
gtloader_sendkey(gt, ph, '\n');
return 1;
case SDLK_BACKSPACE:
gtloader_sendkey(gt, ph, 127);
return 1;
case SDLK_TAB:
gtloader_sendkey(gt, ph, '\t');
return 1;
default:
return 0;
}
}
static int onctrlkeydown (struct GTSDLState *s, struct GTState *gt,
struct GTPeriph *ph, SDL_KeyboardEvent *ev)
{
if (ev->keysym.sym >= 'a' && ev->keysym.sym <= 'z') {
gtloader_sendkey(gt, ph, ev->keysym.sym & 0x1f);
return 1;
}
return 0;
}
static int onkeydown (struct GTSDLState *s, struct GTState *gt,
struct GTPeriph *ph, SDL_KeyboardEvent *ev)
{
if (ev->keysym.mod == 0) {
return onplainkeydown(s, gt, ph, ev);
}
if ((ev->keysym.mod & KMOD_CTRL) && !(ev->keysym.mod & ~KMOD_CTRL)) {
return onctrlkeydown(s, gt, ph, ev);
}
return 0;
}
static int ontextinput (struct GTSDLState *s, struct GTState *gt,
struct GTPeriph *ph, SDL_TextInputEvent *ev)
{
if (SDL_GetModState() == KMOD_LALT) {
/* Alt+X should not be text input */
return 0;
}
if (ev->text[0] > 0 && ev->text[0] < 128) {
gtloader_sendkey(gt, ph, ev->text[0]);
}
return 1;
}
static int onkeyup (struct GTSDLState *s, struct GTState *gt,
SDL_KeyboardEvent *ev)
{
switch (ev->keysym.scancode) {
case SDL_SCANCODE_DELETE:
gt->in |= 0x80;
return 1;
case SDL_SCANCODE_PAGEDOWN:
gt->in |= 0x20;
return 1;
case SDL_SCANCODE_INSERT:
gt->in |= 0x40;
return 1;
case SDL_SCANCODE_PAGEUP:
gt->in |= 0x10;
return 1;
case SDL_SCANCODE_DOWN:
gt->in |= 0x04;
return 1;
case SDL_SCANCODE_LEFT:
gt->in |= 0x02;
return 1;
case SDL_SCANCODE_RIGHT:
gt->in |= 0x01;
return 1;
case SDL_SCANCODE_UP:
gt->in |= 0x08;
return 1;
default:
return 0;
}
}
int gtsdl_handleevent (struct GTSDLState *s, struct GTState *gt,
struct GTPeriph *ph, SDL_Event *ev)
{
switch (ev->type) {
case SDL_KEYDOWN:
return onkeydown(s, gt, ph, &ev->key);
case SDL_KEYUP:
return onkeyup(s, gt, &ev->key);
case SDL_TEXTINPUT:
return ontextinput(s, gt, ph, &ev->text);
default:
return 0;
}
}