433 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			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;
 | 
						|
	}
 | 
						|
}
 | 
						|
 |