mirror of
https://github.com/libretro/fixGB.git
synced 2024-11-26 18:50:22 +00:00
early push to at least have most things secured
This commit is contained in:
parent
84bb28dbad
commit
42df24cb69
@ -1,2 +1,2 @@
|
||||
# fixGB
|
||||
yet another GB Emulator
|
||||
This is still quite early in development so it cant do all that much, its essentially the GB version of my NES Emulator fixNES.
|
328
alhelpers.c
Normal file
328
alhelpers.c
Normal file
@ -0,0 +1,328 @@
|
||||
/*
|
||||
* OpenAL Helpers
|
||||
*
|
||||
* Copyright (c) 2011 by Chris Robinson <chris.kcat@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/* This file contains routines to help with some menial OpenAL-related tasks,
|
||||
* such as opening a device and setting up a context, closing the device and
|
||||
* destroying its context, converting between frame counts and byte lengths,
|
||||
* finding an appropriate buffer format, and getting readable strings for
|
||||
* channel configs and sample types. */
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/alext.h"
|
||||
|
||||
#include "alhelpers.h"
|
||||
|
||||
|
||||
/* InitAL opens the default device and sets up a context using default
|
||||
* attributes, making the program ready to call OpenAL functions. */
|
||||
int InitAL(void)
|
||||
{
|
||||
ALCdevice *device;
|
||||
ALCcontext *ctx;
|
||||
|
||||
/* Open and initialize a device with default settings */
|
||||
device = alcOpenDevice(NULL);
|
||||
if(!device)
|
||||
{
|
||||
fprintf(stderr, "Could not open a device!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
ctx = alcCreateContext(device, NULL);
|
||||
if(ctx == NULL || alcMakeContextCurrent(ctx) == ALC_FALSE)
|
||||
{
|
||||
if(ctx != NULL)
|
||||
alcDestroyContext(ctx);
|
||||
alcCloseDevice(device);
|
||||
fprintf(stderr, "Could not set a context!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
//printf("Opened \"%s\"\n", alcGetString(device, ALC_DEVICE_SPECIFIER));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* CloseAL closes the device belonging to the current context, and destroys the
|
||||
* context. */
|
||||
void CloseAL(void)
|
||||
{
|
||||
ALCdevice *device;
|
||||
ALCcontext *ctx;
|
||||
|
||||
ctx = alcGetCurrentContext();
|
||||
if(ctx == NULL)
|
||||
return;
|
||||
|
||||
device = alcGetContextsDevice(ctx);
|
||||
//printf("Closed \"%s\"\n", alcGetString(device, ALC_DEVICE_SPECIFIER));
|
||||
|
||||
alcMakeContextCurrent(NULL);
|
||||
alcDestroyContext(ctx);
|
||||
alcCloseDevice(device);
|
||||
}
|
||||
|
||||
|
||||
/* GetFormat retrieves a compatible buffer format given the channel config and
|
||||
* sample type. If an alIsBufferFormatSupportedSOFT-compatible function is
|
||||
* provided, it will be called to find the closest-matching format from
|
||||
* AL_SOFT_buffer_samples. Returns AL_NONE (0) if no supported format can be
|
||||
* found. */
|
||||
ALenum GetFormat(ALenum channels, ALenum type, LPALISBUFFERFORMATSUPPORTEDSOFT palIsBufferFormatSupportedSOFT)
|
||||
{
|
||||
ALenum format = AL_NONE;
|
||||
|
||||
/* If using AL_SOFT_buffer_samples, try looking through its formats */
|
||||
if(palIsBufferFormatSupportedSOFT)
|
||||
{
|
||||
/* AL_SOFT_buffer_samples is more lenient with matching formats. The
|
||||
* specified sample type does not need to match the returned format,
|
||||
* but it is nice to try to get something close. */
|
||||
if(type == AL_UNSIGNED_BYTE_SOFT || type == AL_BYTE_SOFT)
|
||||
{
|
||||
if(channels == AL_MONO_SOFT) format = AL_MONO8_SOFT;
|
||||
else if(channels == AL_STEREO_SOFT) format = AL_STEREO8_SOFT;
|
||||
else if(channels == AL_QUAD_SOFT) format = AL_QUAD8_SOFT;
|
||||
else if(channels == AL_5POINT1_SOFT) format = AL_5POINT1_8_SOFT;
|
||||
else if(channels == AL_6POINT1_SOFT) format = AL_6POINT1_8_SOFT;
|
||||
else if(channels == AL_7POINT1_SOFT) format = AL_7POINT1_8_SOFT;
|
||||
}
|
||||
else if(type == AL_UNSIGNED_SHORT_SOFT || type == AL_SHORT_SOFT)
|
||||
{
|
||||
if(channels == AL_MONO_SOFT) format = AL_MONO16_SOFT;
|
||||
else if(channels == AL_STEREO_SOFT) format = AL_STEREO16_SOFT;
|
||||
else if(channels == AL_QUAD_SOFT) format = AL_QUAD16_SOFT;
|
||||
else if(channels == AL_5POINT1_SOFT) format = AL_5POINT1_16_SOFT;
|
||||
else if(channels == AL_6POINT1_SOFT) format = AL_6POINT1_16_SOFT;
|
||||
else if(channels == AL_7POINT1_SOFT) format = AL_7POINT1_16_SOFT;
|
||||
}
|
||||
else if(type == AL_UNSIGNED_BYTE3_SOFT || type == AL_BYTE3_SOFT ||
|
||||
type == AL_UNSIGNED_INT_SOFT || type == AL_INT_SOFT ||
|
||||
type == AL_FLOAT_SOFT || type == AL_DOUBLE_SOFT)
|
||||
{
|
||||
if(channels == AL_MONO_SOFT) format = AL_MONO32F_SOFT;
|
||||
else if(channels == AL_STEREO_SOFT) format = AL_STEREO32F_SOFT;
|
||||
else if(channels == AL_QUAD_SOFT) format = AL_QUAD32F_SOFT;
|
||||
else if(channels == AL_5POINT1_SOFT) format = AL_5POINT1_32F_SOFT;
|
||||
else if(channels == AL_6POINT1_SOFT) format = AL_6POINT1_32F_SOFT;
|
||||
else if(channels == AL_7POINT1_SOFT) format = AL_7POINT1_32F_SOFT;
|
||||
}
|
||||
|
||||
if(format != AL_NONE && !palIsBufferFormatSupportedSOFT(format))
|
||||
format = AL_NONE;
|
||||
|
||||
/* A matching format was not found or supported. Try 32-bit float. */
|
||||
if(format == AL_NONE)
|
||||
{
|
||||
if(channels == AL_MONO_SOFT) format = AL_MONO32F_SOFT;
|
||||
else if(channels == AL_STEREO_SOFT) format = AL_STEREO32F_SOFT;
|
||||
else if(channels == AL_QUAD_SOFT) format = AL_QUAD32F_SOFT;
|
||||
else if(channels == AL_5POINT1_SOFT) format = AL_5POINT1_32F_SOFT;
|
||||
else if(channels == AL_6POINT1_SOFT) format = AL_6POINT1_32F_SOFT;
|
||||
else if(channels == AL_7POINT1_SOFT) format = AL_7POINT1_32F_SOFT;
|
||||
|
||||
if(format != AL_NONE && !palIsBufferFormatSupportedSOFT(format))
|
||||
format = AL_NONE;
|
||||
}
|
||||
/* 32-bit float not supported. Try 16-bit int. */
|
||||
if(format == AL_NONE)
|
||||
{
|
||||
if(channels == AL_MONO_SOFT) format = AL_MONO16_SOFT;
|
||||
else if(channels == AL_STEREO_SOFT) format = AL_STEREO16_SOFT;
|
||||
else if(channels == AL_QUAD_SOFT) format = AL_QUAD16_SOFT;
|
||||
else if(channels == AL_5POINT1_SOFT) format = AL_5POINT1_16_SOFT;
|
||||
else if(channels == AL_6POINT1_SOFT) format = AL_6POINT1_16_SOFT;
|
||||
else if(channels == AL_7POINT1_SOFT) format = AL_7POINT1_16_SOFT;
|
||||
|
||||
if(format != AL_NONE && !palIsBufferFormatSupportedSOFT(format))
|
||||
format = AL_NONE;
|
||||
}
|
||||
/* 16-bit int not supported. Try 8-bit int. */
|
||||
if(format == AL_NONE)
|
||||
{
|
||||
if(channels == AL_MONO_SOFT) format = AL_MONO8_SOFT;
|
||||
else if(channels == AL_STEREO_SOFT) format = AL_STEREO8_SOFT;
|
||||
else if(channels == AL_QUAD_SOFT) format = AL_QUAD8_SOFT;
|
||||
else if(channels == AL_5POINT1_SOFT) format = AL_5POINT1_8_SOFT;
|
||||
else if(channels == AL_6POINT1_SOFT) format = AL_6POINT1_8_SOFT;
|
||||
else if(channels == AL_7POINT1_SOFT) format = AL_7POINT1_8_SOFT;
|
||||
|
||||
if(format != AL_NONE && !palIsBufferFormatSupportedSOFT(format))
|
||||
format = AL_NONE;
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
/* We use the AL_EXT_MCFORMATS extension to provide output of Quad, 5.1,
|
||||
* and 7.1 channel configs, AL_EXT_FLOAT32 for 32-bit float samples, and
|
||||
* AL_EXT_DOUBLE for 64-bit float samples. */
|
||||
if(type == AL_UNSIGNED_BYTE_SOFT)
|
||||
{
|
||||
if(channels == AL_MONO_SOFT)
|
||||
format = AL_FORMAT_MONO8;
|
||||
else if(channels == AL_STEREO_SOFT)
|
||||
format = AL_FORMAT_STEREO8;
|
||||
else if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
|
||||
{
|
||||
if(channels == AL_QUAD_SOFT)
|
||||
format = alGetEnumValue("AL_FORMAT_QUAD8");
|
||||
else if(channels == AL_5POINT1_SOFT)
|
||||
format = alGetEnumValue("AL_FORMAT_51CHN8");
|
||||
else if(channels == AL_6POINT1_SOFT)
|
||||
format = alGetEnumValue("AL_FORMAT_61CHN8");
|
||||
else if(channels == AL_7POINT1_SOFT)
|
||||
format = alGetEnumValue("AL_FORMAT_71CHN8");
|
||||
}
|
||||
}
|
||||
else if(type == AL_SHORT_SOFT)
|
||||
{
|
||||
if(channels == AL_MONO_SOFT)
|
||||
format = AL_FORMAT_MONO16;
|
||||
else if(channels == AL_STEREO_SOFT)
|
||||
format = AL_FORMAT_STEREO16;
|
||||
else if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
|
||||
{
|
||||
if(channels == AL_QUAD_SOFT)
|
||||
format = alGetEnumValue("AL_FORMAT_QUAD16");
|
||||
else if(channels == AL_5POINT1_SOFT)
|
||||
format = alGetEnumValue("AL_FORMAT_51CHN16");
|
||||
else if(channels == AL_6POINT1_SOFT)
|
||||
format = alGetEnumValue("AL_FORMAT_61CHN16");
|
||||
else if(channels == AL_7POINT1_SOFT)
|
||||
format = alGetEnumValue("AL_FORMAT_71CHN16");
|
||||
}
|
||||
}
|
||||
else if(type == AL_FLOAT_SOFT && alIsExtensionPresent("AL_EXT_FLOAT32"))
|
||||
{
|
||||
if(channels == AL_MONO_SOFT)
|
||||
format = alGetEnumValue("AL_FORMAT_MONO_FLOAT32");
|
||||
else if(channels == AL_STEREO_SOFT)
|
||||
format = alGetEnumValue("AL_FORMAT_STEREO_FLOAT32");
|
||||
else if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
|
||||
{
|
||||
if(channels == AL_QUAD_SOFT)
|
||||
format = alGetEnumValue("AL_FORMAT_QUAD32");
|
||||
else if(channels == AL_5POINT1_SOFT)
|
||||
format = alGetEnumValue("AL_FORMAT_51CHN32");
|
||||
else if(channels == AL_6POINT1_SOFT)
|
||||
format = alGetEnumValue("AL_FORMAT_61CHN32");
|
||||
else if(channels == AL_7POINT1_SOFT)
|
||||
format = alGetEnumValue("AL_FORMAT_71CHN32");
|
||||
}
|
||||
}
|
||||
else if(type == AL_DOUBLE_SOFT && alIsExtensionPresent("AL_EXT_DOUBLE"))
|
||||
{
|
||||
if(channels == AL_MONO_SOFT)
|
||||
format = alGetEnumValue("AL_FORMAT_MONO_DOUBLE");
|
||||
else if(channels == AL_STEREO_SOFT)
|
||||
format = alGetEnumValue("AL_FORMAT_STEREO_DOUBLE");
|
||||
}
|
||||
|
||||
/* NOTE: It seems OSX returns -1 from alGetEnumValue for unknown enums, as
|
||||
* opposed to 0. Correct it. */
|
||||
if(format == -1)
|
||||
format = 0;
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
|
||||
void AL_APIENTRY wrap_BufferSamples(ALuint buffer, ALuint samplerate,
|
||||
ALenum internalformat, ALsizei samples,
|
||||
ALenum channels, ALenum type,
|
||||
const ALvoid *data)
|
||||
{
|
||||
alBufferData(buffer, internalformat, data,
|
||||
FramesToBytes(samples, channels, type),
|
||||
samplerate);
|
||||
}
|
||||
|
||||
|
||||
const char *ChannelsName(ALenum chans)
|
||||
{
|
||||
switch(chans)
|
||||
{
|
||||
case AL_MONO_SOFT: return "Mono";
|
||||
case AL_STEREO_SOFT: return "Stereo";
|
||||
case AL_REAR_SOFT: return "Rear";
|
||||
case AL_QUAD_SOFT: return "Quadraphonic";
|
||||
case AL_5POINT1_SOFT: return "5.1 Surround";
|
||||
case AL_6POINT1_SOFT: return "6.1 Surround";
|
||||
case AL_7POINT1_SOFT: return "7.1 Surround";
|
||||
}
|
||||
return "Unknown Channels";
|
||||
}
|
||||
|
||||
const char *TypeName(ALenum type)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case AL_BYTE_SOFT: return "S8";
|
||||
case AL_UNSIGNED_BYTE_SOFT: return "U8";
|
||||
case AL_SHORT_SOFT: return "S16";
|
||||
case AL_UNSIGNED_SHORT_SOFT: return "U16";
|
||||
case AL_INT_SOFT: return "S32";
|
||||
case AL_UNSIGNED_INT_SOFT: return "U32";
|
||||
case AL_FLOAT_SOFT: return "Float32";
|
||||
case AL_DOUBLE_SOFT: return "Float64";
|
||||
}
|
||||
return "Unknown Type";
|
||||
}
|
||||
|
||||
|
||||
ALsizei FramesToBytes(ALsizei size, ALenum channels, ALenum type)
|
||||
{
|
||||
switch(channels)
|
||||
{
|
||||
case AL_MONO_SOFT: size *= 1; break;
|
||||
case AL_STEREO_SOFT: size *= 2; break;
|
||||
case AL_REAR_SOFT: size *= 2; break;
|
||||
case AL_QUAD_SOFT: size *= 4; break;
|
||||
case AL_5POINT1_SOFT: size *= 6; break;
|
||||
case AL_6POINT1_SOFT: size *= 7; break;
|
||||
case AL_7POINT1_SOFT: size *= 8; break;
|
||||
}
|
||||
|
||||
switch(type)
|
||||
{
|
||||
case AL_BYTE_SOFT: size *= sizeof(ALbyte); break;
|
||||
case AL_UNSIGNED_BYTE_SOFT: size *= sizeof(ALubyte); break;
|
||||
case AL_SHORT_SOFT: size *= sizeof(ALshort); break;
|
||||
case AL_UNSIGNED_SHORT_SOFT: size *= sizeof(ALushort); break;
|
||||
case AL_INT_SOFT: size *= sizeof(ALint); break;
|
||||
case AL_UNSIGNED_INT_SOFT: size *= sizeof(ALuint); break;
|
||||
case AL_FLOAT_SOFT: size *= sizeof(ALfloat); break;
|
||||
case AL_DOUBLE_SOFT: size *= sizeof(ALdouble); break;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
ALsizei BytesToFrames(ALsizei size, ALenum channels, ALenum type)
|
||||
{
|
||||
return size / FramesToBytes(1, channels, type);
|
||||
}
|
43
alhelpers.h
Normal file
43
alhelpers.h
Normal file
@ -0,0 +1,43 @@
|
||||
#ifndef ALHELPERS_H
|
||||
#define ALHELPERS_H
|
||||
|
||||
#include "AL/alc.h"
|
||||
#include "AL/al.h"
|
||||
#include "AL/alext.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/* Some helper functions to get the name from the channel and type enums. */
|
||||
const char *ChannelsName(ALenum chans);
|
||||
const char *TypeName(ALenum type);
|
||||
|
||||
/* Helpers to convert frame counts and byte lengths. */
|
||||
ALsizei FramesToBytes(ALsizei size, ALenum channels, ALenum type);
|
||||
ALsizei BytesToFrames(ALsizei size, ALenum channels, ALenum type);
|
||||
|
||||
/* Retrieves a compatible buffer format given the channel configuration and
|
||||
* sample type. If an alIsBufferFormatSupportedSOFT-compatible function is
|
||||
* provided, it will be called to find the closest-matching format from
|
||||
* AL_SOFT_buffer_samples. Returns AL_NONE (0) if no supported format can be
|
||||
* found. */
|
||||
ALenum GetFormat(ALenum channels, ALenum type, LPALISBUFFERFORMATSUPPORTEDSOFT palIsBufferFormatSupportedSOFT);
|
||||
|
||||
/* Loads samples into a buffer using the standard alBufferData call, but with a
|
||||
* LPALBUFFERSAMPLESSOFT-compatible prototype. Assumes internalformat is valid
|
||||
* for alBufferData, and that channels and type match it. */
|
||||
void AL_APIENTRY wrap_BufferSamples(ALuint buffer, ALuint samplerate,
|
||||
ALenum internalformat, ALsizei samples,
|
||||
ALenum channels, ALenum type,
|
||||
const ALvoid *data);
|
||||
|
||||
/* Easy device init/deinit functions. InitAL returns 0 on success. */
|
||||
int InitAL(void);
|
||||
void CloseAL(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* ALHELPERS_H */
|
613
apu.c
Normal file
613
apu.c
Normal file
@ -0,0 +1,613 @@
|
||||
/*
|
||||
* Copyright (C) 2017 FIX94
|
||||
*
|
||||
* This software may be modified and distributed under the terms
|
||||
* of the MIT license. See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
#include <malloc.h>
|
||||
#include "apu.h"
|
||||
#include "audio.h"
|
||||
#include "mem.h"
|
||||
#include "cpu.h"
|
||||
|
||||
#define P1_ENABLE (1<<0)
|
||||
#define P2_ENABLE (1<<1)
|
||||
#define TRI_ENABLE (1<<2)
|
||||
#define NOISE_ENABLE (1<<3)
|
||||
#define DMC_ENABLE (1<<4)
|
||||
|
||||
#define PULSE_CONST_V (1<<4)
|
||||
#define PULSE_HALT_LOOP (1<<5)
|
||||
|
||||
#define TRI_HALT_LOOP (1<<7)
|
||||
|
||||
#define DMC_HALT_LOOP (1<<6)
|
||||
#define DMC_IRQ_ENABLE (1<<7)
|
||||
|
||||
static uint8_t APU_IO_Reg[0x40];
|
||||
|
||||
static float lpVal;
|
||||
static float hpVal;
|
||||
static float *apuOutBuf;
|
||||
static uint32_t apuBufSize;
|
||||
static uint32_t apuBufSizeBytes;
|
||||
static uint32_t curBufPos;
|
||||
static uint32_t apuFrequency;
|
||||
static uint16_t freq1;
|
||||
static uint16_t freq2;
|
||||
static uint16_t triFreq;
|
||||
static uint16_t noiseFreq;
|
||||
static uint16_t noiseShiftReg;
|
||||
static uint16_t dmcFreq;
|
||||
static uint16_t dmcAddr, dmcLen, dmcSampleBuf;
|
||||
static uint16_t dmcCurAddr, dmcCurLen;
|
||||
static uint8_t p1LengthCtr, p2LengthCtr, noiseLengthCtr;
|
||||
static uint8_t triLinearCtr, triCurLinearCtr;
|
||||
static uint16_t triLengthCtr;
|
||||
static uint8_t dmcVol, dmcCurVol;
|
||||
static uint8_t dmcSampleRemain;
|
||||
static uint8_t triVolShift;
|
||||
static uint16_t modeCurCtr = 0;
|
||||
static uint16_t p1freqCtr, p2freqCtr, triFreqCtr, noiseFreqCtr;
|
||||
static uint8_t p1Cycle, p2Cycle, triCycle;
|
||||
static uint8_t modePos = 0;
|
||||
static bool p1haltloop, p2haltloop, trihaltloop, noisehaltloop;
|
||||
static bool dmcstart;
|
||||
static bool dmcirqenable;
|
||||
static bool trireload;
|
||||
static bool noiseMode1;
|
||||
static bool apu_enable_irq;
|
||||
|
||||
static envelope_t p1Env, p2Env, noiseEnv;
|
||||
|
||||
typedef struct _sweep_t {
|
||||
bool enabled;
|
||||
bool start;
|
||||
bool negative;
|
||||
bool mute;
|
||||
bool chan1;
|
||||
uint8_t period;
|
||||
uint8_t divider;
|
||||
uint8_t shift;
|
||||
} sweep_t;
|
||||
|
||||
static sweep_t p1Sweep, p2Sweep;
|
||||
|
||||
static float pulseLookupTbl[32];
|
||||
static float tndLookupTbl[204];
|
||||
|
||||
//used externally
|
||||
const uint8_t lengthLookupTbl[0x20] = {
|
||||
10,254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14,
|
||||
12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30
|
||||
};
|
||||
|
||||
//used externally
|
||||
const uint8_t pulseSeqs[4][8] = {
|
||||
{ 0, 1, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 1, 1, 0, 0, 0, 0, 0 },
|
||||
{ 0, 1, 1, 1, 1, 0, 0, 0 },
|
||||
{ 1, 0, 0, 1, 1, 1, 1, 1 },
|
||||
};
|
||||
|
||||
static const uint16_t noisePeriodNtsc[8] = {
|
||||
8, 16, 32, 48, 64, 80, 96, 112,
|
||||
};
|
||||
|
||||
//used externally
|
||||
const uint16_t *noisePeriod;
|
||||
|
||||
static const uint8_t *p1seq = pulseSeqs[0],
|
||||
*p2seq = pulseSeqs[1];
|
||||
//extern bool dmc_interrupt;
|
||||
|
||||
#define M_2_PI 6.28318530717958647692
|
||||
|
||||
void apuInitBufs()
|
||||
{
|
||||
noisePeriod = noisePeriodNtsc;
|
||||
//effective frequency for 60.000Hz Video out
|
||||
apuFrequency = 1053360;
|
||||
double dt = 1.0/((double)apuFrequency);
|
||||
//LP at 22kHz
|
||||
double rc = 1.0/(M_2_PI * 22000.0);
|
||||
lpVal = dt / (rc + dt);
|
||||
//HP at 40Hz
|
||||
rc = 1.0/(M_2_PI * 40.0);
|
||||
hpVal = rc / (rc + dt);
|
||||
|
||||
apuBufSize = apuFrequency/60;
|
||||
apuBufSizeBytes = apuBufSize*sizeof(float);
|
||||
|
||||
apuOutBuf = (float*)malloc(apuBufSizeBytes);
|
||||
|
||||
/* https://wiki.nesdev.com/w/index.php/APU_Mixer#Lookup_Table */
|
||||
uint8_t i;
|
||||
for(i = 0; i < 32; i++)
|
||||
pulseLookupTbl[i] = 95.52 / ((8128.0 / i) + 100);
|
||||
for(i = 0; i < 204; i++)
|
||||
tndLookupTbl[i] = 163.67 / ((24329.0 / i) + 100);
|
||||
}
|
||||
|
||||
void apuDeinitBufs()
|
||||
{
|
||||
if(apuOutBuf)
|
||||
free(apuOutBuf);
|
||||
apuOutBuf = NULL;
|
||||
}
|
||||
|
||||
void apuInit()
|
||||
{
|
||||
memset(APU_IO_Reg,0,0x40);
|
||||
memset(apuOutBuf, 0, apuBufSizeBytes);
|
||||
curBufPos = 0;
|
||||
|
||||
freq1 = 0; freq2 = 0; triFreq = 0; noiseFreq = 0, dmcFreq = 0;
|
||||
noiseShiftReg = 1;
|
||||
p1LengthCtr = 0; p2LengthCtr = 0;
|
||||
noiseLengthCtr = 0; triLengthCtr = 0;
|
||||
triLinearCtr = 0; triCurLinearCtr = 0;
|
||||
dmcAddr = 0, dmcLen = 0, dmcVol = 0; dmcSampleBuf = 0;
|
||||
dmcCurAddr = 0, dmcCurLen = 0; dmcCurVol = 0;
|
||||
dmcSampleRemain = 0;
|
||||
p1freqCtr = 0; p2freqCtr = 0; triFreqCtr = 0, noiseFreqCtr = 0;
|
||||
p1Cycle = 0; p2Cycle = 0; triCycle = 0;
|
||||
triVolShift = 0;
|
||||
|
||||
memset(&p1Env,0,sizeof(envelope_t));
|
||||
memset(&p2Env,0,sizeof(envelope_t));
|
||||
memset(&noiseEnv,0,sizeof(envelope_t));
|
||||
|
||||
memset(&p1Sweep,0,sizeof(sweep_t));
|
||||
p1Sweep.chan1 = true; //for negative sweep
|
||||
memset(&p2Sweep,0,sizeof(sweep_t));
|
||||
p2Sweep.chan1 = false;
|
||||
|
||||
p1haltloop = false; p2haltloop = false;
|
||||
trihaltloop = false; noisehaltloop = false;
|
||||
dmcstart = false;
|
||||
dmcirqenable = false;
|
||||
trireload = false;
|
||||
noiseMode1 = false;
|
||||
//4017 starts out as 0, so enable
|
||||
apu_enable_irq = true;
|
||||
}
|
||||
|
||||
extern uint32_t cpu_oam_dma;
|
||||
void apuClockTimers()
|
||||
{
|
||||
if(p1freqCtr)
|
||||
p1freqCtr--;
|
||||
if(p1freqCtr == 0)
|
||||
{
|
||||
if(freq1)
|
||||
p1freqCtr = (2048-freq1)*4;
|
||||
p1Cycle++;
|
||||
if(p1Cycle >= 8)
|
||||
p1Cycle = 0;
|
||||
}
|
||||
|
||||
if(p2freqCtr)
|
||||
p2freqCtr--;
|
||||
if(p2freqCtr == 0)
|
||||
{
|
||||
if(freq2)
|
||||
p2freqCtr = (2048-freq2)*4;
|
||||
p2Cycle++;
|
||||
if(p2Cycle >= 8)
|
||||
p2Cycle = 0;
|
||||
}
|
||||
|
||||
if(triFreqCtr)
|
||||
triFreqCtr--;
|
||||
if(triFreqCtr == 0)
|
||||
{
|
||||
triFreqCtr = (2048-triFreq)*2;
|
||||
triCycle++;
|
||||
if(triCycle >= 32)
|
||||
triCycle = 0;
|
||||
}
|
||||
|
||||
if(noiseFreqCtr)
|
||||
noiseFreqCtr--;
|
||||
if(noiseFreqCtr == 0)
|
||||
{
|
||||
noiseFreqCtr = noiseFreq;
|
||||
uint8_t cmpBit = noiseMode1 ? (noiseShiftReg>>6)&1 : (noiseShiftReg>>1)&1;
|
||||
uint8_t cmpRes = (noiseShiftReg&1)^cmpBit;
|
||||
noiseShiftReg >>= 1;
|
||||
noiseShiftReg |= cmpRes<<14;
|
||||
}
|
||||
}
|
||||
|
||||
static float lastHPOut = 0, lastLPOut = 0;
|
||||
static uint8_t lastP1Out = 0, lastP2Out = 0, lastTriOut = 0, lastNoiseOut = 0;
|
||||
|
||||
extern bool emuSkipVsync, emuSkipFrame;
|
||||
|
||||
bool apuCycle()
|
||||
{
|
||||
if(curBufPos == apuBufSize)
|
||||
{
|
||||
int updateRes = audioUpdate();
|
||||
if(updateRes == 0)
|
||||
{
|
||||
emuSkipFrame = false;
|
||||
emuSkipVsync = false;
|
||||
return false;
|
||||
}
|
||||
if(updateRes > 6)
|
||||
{
|
||||
emuSkipVsync = true;
|
||||
emuSkipFrame = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
emuSkipFrame = false;
|
||||
if(updateRes > 2)
|
||||
emuSkipVsync = true;
|
||||
else
|
||||
emuSkipVsync = false;
|
||||
}
|
||||
curBufPos = 0;
|
||||
}
|
||||
uint8_t p1Out = lastP1Out, p2Out = lastP2Out,
|
||||
triOut = lastTriOut, noiseOut = lastNoiseOut;
|
||||
if(p1LengthCtr && ((APU_IO_Reg[0x25]|(APU_IO_Reg[0x25]>>4)) & P1_ENABLE))
|
||||
{
|
||||
if(p1seq[p1Cycle] && !p1Sweep.mute && freq1 >= 0x100 && freq1 < 0x7FF)
|
||||
lastP1Out = p1Out = p1Env.vol;
|
||||
else
|
||||
p1Out = 0;
|
||||
}
|
||||
if(p2LengthCtr && ((APU_IO_Reg[0x25]|(APU_IO_Reg[0x25]>>4)) & P2_ENABLE))
|
||||
{
|
||||
if(p2seq[p2Cycle] && freq2 >= 0x100 && freq2 < 0x7FF)
|
||||
lastP2Out = p2Out = p2Env.vol;
|
||||
else
|
||||
p2Out = 0;
|
||||
}
|
||||
if(triLengthCtr && triCurLinearCtr && ((APU_IO_Reg[0x25]|(APU_IO_Reg[0x25]>>4)) & TRI_ENABLE))
|
||||
{
|
||||
uint8_t v = APU_IO_Reg[0x30+(triCycle>>1)];
|
||||
if((triCycle&1)==0)
|
||||
v >>= 4;
|
||||
else
|
||||
v &= 0xF;
|
||||
v>>=triVolShift;
|
||||
if(v)// && triFreq >= 2)
|
||||
lastTriOut = triOut = v;
|
||||
else
|
||||
triOut = 0;
|
||||
}
|
||||
if(noiseLengthCtr && ((APU_IO_Reg[0x25]|(APU_IO_Reg[0x25]>>4)) & NOISE_ENABLE))
|
||||
{
|
||||
if((noiseShiftReg&1) == 0 && noiseFreq > 0)
|
||||
lastNoiseOut = noiseOut = noiseEnv.vol;
|
||||
else
|
||||
noiseOut = 0;
|
||||
}
|
||||
float curIn = pulseLookupTbl[p1Out + p2Out] + tndLookupTbl[(3*triOut) + (2*noiseOut) + dmcVol];
|
||||
float curLPout = lastLPOut+(lpVal*(curIn-lastLPOut));
|
||||
float curHPOut = hpVal*(lastHPOut+curLPout-curIn);
|
||||
//set output
|
||||
apuOutBuf[curBufPos] = -curHPOut;
|
||||
lastLPOut = curLPout;
|
||||
lastHPOut = curHPOut;
|
||||
curBufPos++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void doEnvelopeLogic(envelope_t *env)
|
||||
{
|
||||
if(env->divider == 0)
|
||||
{
|
||||
if(env->period)
|
||||
{
|
||||
if(env->modeadd)
|
||||
{
|
||||
if(env->vol < 15)
|
||||
env->vol++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(env->vol > 0)
|
||||
env->vol--;
|
||||
}
|
||||
}
|
||||
env->divider = env->period;
|
||||
}
|
||||
else
|
||||
env->divider--;
|
||||
//too slow on its own?
|
||||
//env->envelope = (env->constant ? env->vol : env->decay);
|
||||
}
|
||||
|
||||
void sweepUpdateFreq(sweep_t *sw, uint16_t *freq)
|
||||
{
|
||||
uint16_t inFreq = *freq;
|
||||
if(sw->shift > 0)
|
||||
{
|
||||
if(sw->negative)
|
||||
{
|
||||
inFreq -= (inFreq >> sw->shift);
|
||||
if(sw->chan1 == true) inFreq--;
|
||||
}
|
||||
else
|
||||
inFreq += (inFreq >> sw->shift);
|
||||
}
|
||||
if(inFreq > 0x100 && (inFreq < 0x7FF))
|
||||
{
|
||||
sw->mute = false;
|
||||
if(sw->enabled && sw->shift)
|
||||
*freq = inFreq;
|
||||
}
|
||||
else
|
||||
sw->mute = true;
|
||||
}
|
||||
|
||||
void doSweepLogic(sweep_t *sw, uint16_t *freq)
|
||||
{
|
||||
if(sw->start)
|
||||
{
|
||||
uint8_t prevDiv = sw->divider;
|
||||
sw->divider = sw->period;
|
||||
sw->start = false;
|
||||
if(prevDiv == 0)
|
||||
sweepUpdateFreq(sw, freq);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(sw->divider == 0)
|
||||
{
|
||||
sweepUpdateFreq(sw, freq);
|
||||
sw->divider = sw->period;
|
||||
}
|
||||
else
|
||||
sw->divider--;
|
||||
}
|
||||
//gets clocked too little on its own?
|
||||
/*if(inFreq < 8 || (inFreq >= 0x7FF))
|
||||
sw->mute = true;
|
||||
else
|
||||
sw->mute = false;*/
|
||||
}
|
||||
|
||||
void apuClockA()
|
||||
{
|
||||
if(p1LengthCtr)
|
||||
{
|
||||
doSweepLogic(&p1Sweep, &freq1);
|
||||
if(!p1haltloop)
|
||||
p1LengthCtr--;
|
||||
}
|
||||
if(p2LengthCtr && !p2haltloop)
|
||||
p2LengthCtr--;
|
||||
if(triLengthCtr && !trihaltloop)
|
||||
triLengthCtr--;
|
||||
if(noiseLengthCtr && !noisehaltloop)
|
||||
noiseLengthCtr--;
|
||||
}
|
||||
|
||||
void apuClockB()
|
||||
{
|
||||
if(p1LengthCtr)
|
||||
doEnvelopeLogic(&p1Env);
|
||||
if(p2LengthCtr)
|
||||
doEnvelopeLogic(&p2Env);
|
||||
if(noiseLengthCtr)
|
||||
doEnvelopeLogic(&noiseEnv);
|
||||
}
|
||||
|
||||
//extern bool apu_interrupt;
|
||||
|
||||
void apuLenCycle()
|
||||
{
|
||||
if(modeCurCtr)
|
||||
modeCurCtr--;
|
||||
if(modeCurCtr == 0)
|
||||
{
|
||||
apuClockA();
|
||||
modePos++;
|
||||
if(modePos >= 4)
|
||||
{
|
||||
apuClockB();
|
||||
modePos = 0;
|
||||
}
|
||||
modeCurCtr = 4096;
|
||||
}
|
||||
}
|
||||
|
||||
void apuSet8(uint8_t reg, uint8_t val)
|
||||
{
|
||||
//printf("APU %02x %02x\n", reg, val);
|
||||
APU_IO_Reg[reg] = val;
|
||||
if(reg == 0x10)
|
||||
{
|
||||
//printf("P1 sweep %02x\n", val);
|
||||
p1Sweep.enabled = true;//((val&0x80) != 0);
|
||||
p1Sweep.shift = val&7;
|
||||
p1Sweep.period = (val>>4)&7;
|
||||
//if(p1Sweep.period == 0)
|
||||
// p1Sweep.period = 8;
|
||||
p1Sweep.negative = ((val&0x8) != 0);
|
||||
p1Sweep.start = true;
|
||||
if(freq1 > 0x100 && (freq1 < 0x7FF))
|
||||
p1Sweep.mute = false; //to be safe
|
||||
doSweepLogic(&p1Sweep, &freq1);
|
||||
}
|
||||
else if(reg == 0x11)
|
||||
{
|
||||
p1seq = pulseSeqs[val>>6];
|
||||
p1LengthCtr = 64-(val&0x3F);
|
||||
if(freq1 > 0x100 && (freq1 < 0x7FF))
|
||||
p1Sweep.mute = false; //to be safe
|
||||
}
|
||||
else if(reg == 0x12)
|
||||
{
|
||||
p1Env.vol = (val>>4)&0xF;
|
||||
p1Env.modeadd = (val&8)!=0;
|
||||
p1Env.period = val&7;
|
||||
//if(p1Env.period==0)
|
||||
// p1Env.period=8;
|
||||
p1Env.divider = p1Env.period;
|
||||
if(freq1 > 0x100 && (freq1 < 0x7FF))
|
||||
p1Sweep.mute = false; //to be safe
|
||||
}
|
||||
else if(reg == 0x13)
|
||||
{
|
||||
freq1 = ((freq1&~0xFF) | val);
|
||||
if(freq1 > 0x100 && (freq1 < 0x7FF))
|
||||
p1Sweep.mute = false; //to be safe
|
||||
}
|
||||
else if(reg == 0x14)
|
||||
{
|
||||
p1haltloop = ((val&(1<<6)) == 0);
|
||||
if(val&(1<<7))
|
||||
{
|
||||
if(p1LengthCtr == 0)
|
||||
p1LengthCtr = 64;
|
||||
if(freq1 > 0x100 && (freq1 < 0x7FF))
|
||||
p1Sweep.mute = false; //to be safe
|
||||
doSweepLogic(&p1Sweep, &freq1);
|
||||
}
|
||||
freq1 = (freq1&0xFF) | ((val&7)<<8);
|
||||
//printf("P1 new freq %04x\n", freq1);
|
||||
}
|
||||
else if(reg == 0x16)
|
||||
{
|
||||
p2seq = pulseSeqs[val>>6];
|
||||
p2LengthCtr = 64-(val&0x3F);
|
||||
}
|
||||
else if(reg == 0x17)
|
||||
{
|
||||
p2Env.vol = (val>>4)&0xF;
|
||||
p2Env.modeadd = (val&8)!=0;
|
||||
p2Env.period = val&7;
|
||||
//if(p2Env.period==0)
|
||||
// p2Env.period=8;
|
||||
p2Env.divider = p2Env.period;
|
||||
}
|
||||
else if(reg == 0x18)
|
||||
{
|
||||
freq2 = ((freq2&~0xFF) | val);
|
||||
}
|
||||
else if(reg == 0x19)
|
||||
{
|
||||
p2haltloop = ((val&(1<<6)) == 0);
|
||||
if(val&(1<<7))
|
||||
{
|
||||
if(p2LengthCtr == 0)
|
||||
p2LengthCtr = 64;
|
||||
}
|
||||
freq2 = (freq2&0xFF) | ((val&7)<<8);
|
||||
//printf("P2 new freq %04x\n", freq2);
|
||||
}
|
||||
else if(reg == 0x1A)
|
||||
triCurLinearCtr = ((val&0x80)!=0);
|
||||
else if(reg == 0x1B)
|
||||
triLengthCtr = 256-val;
|
||||
else if(reg == 0x1C)
|
||||
{
|
||||
//printf("TRIVolShift %i\n", (val>>5)&3);
|
||||
switch((val>>5)&3)
|
||||
{
|
||||
case 0:
|
||||
triVolShift=4;
|
||||
break;
|
||||
case 1:
|
||||
triVolShift=0;
|
||||
break;
|
||||
case 2:
|
||||
triVolShift=1;
|
||||
break;
|
||||
case 3:
|
||||
triVolShift=2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(reg == 0x1D)
|
||||
{
|
||||
//printf("TRI time low %02x\n", val);
|
||||
triFreq = ((triFreq&~0xFF) | val);
|
||||
}
|
||||
else if(reg == 0x1E)
|
||||
{
|
||||
trihaltloop = ((val&(1<<6)) == 0);
|
||||
if(val&(1<<7))
|
||||
{
|
||||
if(triLengthCtr == 0)
|
||||
triLengthCtr = 256;
|
||||
}
|
||||
triFreq = (triFreq&0xFF) | ((val&7)<<8);
|
||||
//printf("TRI new freq %04x\n", triFreq);
|
||||
}
|
||||
else if(reg == 0x20)
|
||||
{
|
||||
noiseLengthCtr = 64-(val&0x3F);
|
||||
}
|
||||
else if(reg == 0x21)
|
||||
{
|
||||
noiseEnv.vol = (val>>4)&0xF;
|
||||
noiseEnv.modeadd = (val&8)!=0;
|
||||
noiseEnv.period=val&7;
|
||||
//if(noiseEnv.period==0)
|
||||
// noiseEnv.period=8;
|
||||
noiseEnv.divider = noiseEnv.period;
|
||||
}
|
||||
else if(reg == 0x22)
|
||||
{
|
||||
if((val>>4)<14)
|
||||
noiseFreq = noisePeriod[val&0x7]<<(val>>4);
|
||||
else
|
||||
noiseFreq = 0;
|
||||
noiseMode1 = ((val&0x8) != 0);
|
||||
}
|
||||
else if(reg == 0x23)
|
||||
{
|
||||
noisehaltloop = ((val&(1<<6)) == 0);
|
||||
if(val&(1<<7))
|
||||
{
|
||||
if(noiseLengthCtr == 0)
|
||||
noiseLengthCtr = 64;
|
||||
}
|
||||
}
|
||||
else if(reg == 0x25)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t apuGet8(uint8_t reg)
|
||||
{
|
||||
//printf("%08x\n", reg);
|
||||
/*if(reg == 0x15)
|
||||
{
|
||||
//uint8_t intrflags = ((apu_interrupt<<6) | (dmc_interrupt<<7));
|
||||
//uint8_t apuretval = ((p1LengthCtr > 0) | ((p2LengthCtr > 0)<<1) | ((triLengthCtr > 0)<<2) | ((noiseLengthCtr > 0)<<3) | ((dmcCurLen > 0)<<4) | intrflags);
|
||||
//printf("Get 0x15 %02x\n",apuretval);
|
||||
//apu_interrupt = false;
|
||||
return 0;//apuretval;
|
||||
}*/
|
||||
return APU_IO_Reg[reg];
|
||||
}
|
||||
|
||||
uint8_t *apuGetBuf()
|
||||
{
|
||||
return (uint8_t*)apuOutBuf;
|
||||
}
|
||||
|
||||
uint32_t apuGetBufSize()
|
||||
{
|
||||
return apuBufSizeBytes;
|
||||
}
|
||||
|
||||
uint32_t apuGetFrequency()
|
||||
{
|
||||
return apuFrequency;
|
||||
}
|
35
apu.h
Normal file
35
apu.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2017 FIX94
|
||||
*
|
||||
* This software may be modified and distributed under the terms
|
||||
* of the MIT license. See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
#ifndef _apu_h_
|
||||
#define _apu_h_
|
||||
|
||||
#define NUM_BUFFERS 10
|
||||
|
||||
void apuInitBufs();
|
||||
void apuDeinitBufs();
|
||||
void apuInit();
|
||||
bool apuCycle();
|
||||
void apuClockTimers();
|
||||
uint8_t *apuGetBuf();
|
||||
uint32_t apuGetBufSize();
|
||||
uint32_t apuGetFrequency();
|
||||
void apuSet8(uint8_t reg, uint8_t val);
|
||||
uint8_t apuGet8(uint8_t reg);
|
||||
void apuLenCycle();
|
||||
|
||||
|
||||
typedef struct _envelope_t {
|
||||
bool modeadd;
|
||||
uint8_t vol;
|
||||
uint8_t period;
|
||||
uint8_t divider;
|
||||
} envelope_t;
|
||||
|
||||
void doEnvelopeLogic(envelope_t *env);
|
||||
|
||||
#endif
|
224
audio.c
Normal file
224
audio.c
Normal file
@ -0,0 +1,224 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <malloc.h>
|
||||
#include <math.h>
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/alext.h"
|
||||
#include "alhelpers.h"
|
||||
#include "apu.h"
|
||||
#if WINDOWS_BUILD
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static LPALBUFFERSAMPLESSOFT alBufferSamplesSOFT = wrap_BufferSamples;
|
||||
static LPALISBUFFERFORMATSUPPORTEDSOFT alIsBufferFormatSupportedSOFT;
|
||||
|
||||
typedef struct StreamPlayer {
|
||||
/* These are the buffers and source to play out through OpenAL with */
|
||||
ALuint buffers[NUM_BUFFERS];
|
||||
ALuint source;
|
||||
|
||||
/* Handle for the audio file */
|
||||
//FilePtr file;
|
||||
|
||||
/* The format of the output stream */
|
||||
ALenum format;
|
||||
ALenum channels;
|
||||
ALenum type;
|
||||
ALuint rate;
|
||||
} StreamPlayer;
|
||||
|
||||
static StreamPlayer *NewPlayer(void);
|
||||
static void DeletePlayer(StreamPlayer *player);
|
||||
|
||||
/* Creates a new player object, and allocates the needed OpenAL source and
|
||||
* buffer objects. Error checking is simplified for the purposes of this
|
||||
* example, and will cause an abort if needed. */
|
||||
static StreamPlayer *NewPlayer(void)
|
||||
{
|
||||
StreamPlayer *player;
|
||||
|
||||
player = malloc(sizeof(*player));
|
||||
//assert(player != NULL);
|
||||
|
||||
memset(player, 0, sizeof(*player));
|
||||
|
||||
/* Generate the buffers and source */
|
||||
alGenBuffers(NUM_BUFFERS, player->buffers);
|
||||
//assert(alGetError() == AL_NO_ERROR && "Could not create buffers");
|
||||
|
||||
alGenSources(1, &player->source);
|
||||
//assert(alGetError() == AL_NO_ERROR && "Could not create source");
|
||||
|
||||
/* Set parameters so mono sources play out the front-center speaker and
|
||||
* won't distance attenuate. */
|
||||
alSource3i(player->source, AL_POSITION, 0, 0, -1);
|
||||
alSourcei(player->source, AL_SOURCE_RELATIVE, AL_TRUE);
|
||||
alSourcei(player->source, AL_ROLLOFF_FACTOR, 0);
|
||||
//assert(alGetError() == AL_NO_ERROR && "Could not set source parameters");
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
/* Destroys a player object, deleting the source and buffers. No error handling
|
||||
* since these calls shouldn't fail with a properly-made player object. */
|
||||
static void DeletePlayer(StreamPlayer *player)
|
||||
{
|
||||
// ClosePlayerFile(player);
|
||||
|
||||
alDeleteSources(1, &player->source);
|
||||
alDeleteBuffers(NUM_BUFFERS, player->buffers);
|
||||
if(alGetError() != AL_NO_ERROR)
|
||||
fprintf(stderr, "Failed to delete object IDs\n");
|
||||
|
||||
memset(player, 0, sizeof(*player));
|
||||
free(player);
|
||||
}
|
||||
|
||||
static int StartPlayer(StreamPlayer *player)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
/* Rewind the source position and clear the buffer queue */
|
||||
alSourceRewind(player->source);
|
||||
alSourcei(player->source, AL_BUFFER, 0);
|
||||
|
||||
/* Fill the buffer queue with empty data */
|
||||
for(i = 0;i < NUM_BUFFERS;i++)
|
||||
{
|
||||
uint8_t *data;
|
||||
|
||||
/* Get some data to give it to the buffer */
|
||||
data = apuGetBuf();
|
||||
if(!data) break;
|
||||
|
||||
alBufferSamplesSOFT(player->buffers[i], player->rate, player->format,
|
||||
BytesToFrames(apuGetBufSize(), player->channels, player->type),
|
||||
player->channels, player->type, data);
|
||||
}
|
||||
if(alGetError() != AL_NO_ERROR)
|
||||
{
|
||||
fprintf(stderr, "Error buffering for playback\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Now queue and start playback! */
|
||||
alSourceQueueBuffers(player->source, i, player->buffers);
|
||||
alSourcePlay(player->source);
|
||||
if(alGetError() != AL_NO_ERROR)
|
||||
{
|
||||
fprintf(stderr, "Error starting playback\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
StreamPlayer *player = NULL;
|
||||
|
||||
int audioInit()
|
||||
{
|
||||
if(InitAL() != 0)
|
||||
goto error;
|
||||
|
||||
if(alIsExtensionPresent("AL_SOFT_buffer_samples"))
|
||||
{
|
||||
alBufferSamplesSOFT = alGetProcAddress("alBufferSamplesSOFT");
|
||||
alIsBufferFormatSupportedSOFT = alGetProcAddress("alIsBufferFormatSupportedSOFT");
|
||||
}
|
||||
|
||||
player = NewPlayer();
|
||||
|
||||
player->channels = AL_MONO_SOFT;
|
||||
player->rate = apuGetFrequency();
|
||||
player->type = AL_FLOAT_SOFT;
|
||||
|
||||
player->format = GetFormat(player->channels, player->type, alIsBufferFormatSupportedSOFT);
|
||||
if(player->format == 0)
|
||||
{
|
||||
fprintf(stderr, "Unsupported format (%s, %s)\n",
|
||||
ChannelsName(player->channels), TypeName(player->type));
|
||||
goto error;
|
||||
}
|
||||
StartPlayer(player);
|
||||
return 0;
|
||||
|
||||
error:
|
||||
return 1;
|
||||
}
|
||||
|
||||
int audioUpdate()
|
||||
{
|
||||
ALint processed = 0, state;
|
||||
|
||||
/* Get relevant source info */
|
||||
alGetSourcei(player->source, AL_SOURCE_STATE, &state);
|
||||
alGetSourcei(player->source, AL_BUFFERS_PROCESSED, &processed);
|
||||
if(alGetError() != AL_NO_ERROR)
|
||||
{
|
||||
fprintf(stderr, "Error checking source state\n");
|
||||
return 0;
|
||||
}
|
||||
if(!processed)
|
||||
return 0;
|
||||
|
||||
/* Unqueue and handle processed buffer */
|
||||
ALuint bufid;
|
||||
uint8_t *data;
|
||||
|
||||
alSourceUnqueueBuffers(player->source, 1, &bufid);
|
||||
|
||||
/* Read the next chunk of data, refill the buffer, and queue it
|
||||
* back on the source */
|
||||
data = apuGetBuf();
|
||||
if(data != NULL)
|
||||
{
|
||||
alBufferSamplesSOFT(bufid, player->rate, player->format,
|
||||
BytesToFrames(apuGetBufSize(), player->channels, player->type),
|
||||
player->channels, player->type, data);
|
||||
alSourceQueueBuffers(player->source, 1, &bufid);
|
||||
}
|
||||
if(alGetError() != AL_NO_ERROR)
|
||||
{
|
||||
fprintf(stderr, "Error buffering data\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Make sure the source hasn't underrun */
|
||||
if(state != AL_PLAYING && state != AL_PAUSED)
|
||||
{
|
||||
//ALint queued;
|
||||
|
||||
alSourcePlay(player->source);
|
||||
if(alGetError() != AL_NO_ERROR)
|
||||
{
|
||||
fprintf(stderr, "Error restarting playback\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
void audioDeinit()
|
||||
{
|
||||
if(player)
|
||||
{
|
||||
DeletePlayer(player);
|
||||
player = NULL;
|
||||
}
|
||||
CloseAL();
|
||||
}
|
||||
|
||||
void audioSleep()
|
||||
{
|
||||
#if WINDOWS_BUILD
|
||||
Sleep(1);
|
||||
#else
|
||||
usleep(1000);
|
||||
#endif
|
||||
}
|
10
audio.h
Normal file
10
audio.h
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
#ifndef _audio_h_
|
||||
#define _audio_h_
|
||||
|
||||
int audioInit();
|
||||
int audioUpdate();
|
||||
void audioDeinit();
|
||||
void audioSleep();
|
||||
|
||||
#endif
|
2
build_windows_console.bat
Normal file
2
build_windows_console.bat
Normal file
@ -0,0 +1,2 @@
|
||||
gcc -DWINDOWS_BUILD main.c apu.c audio.c alhelpers.c cpu.c mem.c ppu.c input.c -DFREEGLUT_STATIC -lfreeglut_static -lopenal32 -lopengl32 -lglu32 -lgdi32 -lwinmm -Wall -Wextra -O3 -msse -mfpmath=sse -ffast-math -s -o fixGB
|
||||
pause
|
15
cpu.h
Normal file
15
cpu.h
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (C) 2017 FIX94
|
||||
*
|
||||
* This software may be modified and distributed under the terms
|
||||
* of the MIT license. See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
#ifndef _cpu_c_
|
||||
#define _cpu_h_
|
||||
|
||||
void cpuInit();
|
||||
bool cpuCycle();
|
||||
uint16_t cpuCurPC();
|
||||
|
||||
#endif
|
64
input.c
Normal file
64
input.c
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2017 FIX94
|
||||
*
|
||||
* This software may be modified and distributed under the terms
|
||||
* of the MIT license. See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
#include "input.h"
|
||||
|
||||
//used externally by main.c
|
||||
uint8_t inValReads[8];
|
||||
uint8_t modeSelect = 0;
|
||||
|
||||
#define DEBUG_INPUT 0
|
||||
|
||||
void inputInit()
|
||||
{
|
||||
memset(inValReads, 0, 8);
|
||||
}
|
||||
|
||||
void inputSet8(uint8_t in)
|
||||
{
|
||||
modeSelect = ((~in)>>4)&0x3;
|
||||
#if DEBUG_INPUT
|
||||
printf("Set %02x->%02x\n",in,modeSelect);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t inputGet8()
|
||||
{
|
||||
uint8_t outVal = 0;
|
||||
if(modeSelect == 2)
|
||||
{
|
||||
if(inValReads[BUTTON_A])
|
||||
outVal |= 1;
|
||||
if(inValReads[BUTTON_B])
|
||||
outVal |= 2;
|
||||
if(inValReads[BUTTON_SELECT])
|
||||
outVal |= 4;
|
||||
if(inValReads[BUTTON_START])
|
||||
outVal |= 8;
|
||||
}
|
||||
else if(modeSelect == 1)
|
||||
{
|
||||
if(inValReads[BUTTON_RIGHT])
|
||||
outVal |= 1;
|
||||
if(inValReads[BUTTON_LEFT])
|
||||
outVal |= 2;
|
||||
if(inValReads[BUTTON_UP])
|
||||
outVal |= 4;
|
||||
if(inValReads[BUTTON_DOWN])
|
||||
outVal |= 8;
|
||||
}
|
||||
return ~outVal;
|
||||
}
|
||||
|
||||
bool inputAny()
|
||||
{
|
||||
return !!(inValReads[BUTTON_A]|inValReads[BUTTON_B]|inValReads[BUTTON_SELECT]|inValReads[BUTTON_START]
|
||||
|inValReads[BUTTON_RIGHT]|inValReads[BUTTON_LEFT]|inValReads[BUTTON_UP]|inValReads[BUTTON_DOWN]);
|
||||
}
|
25
input.h
Normal file
25
input.h
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2017 FIX94
|
||||
*
|
||||
* This software may be modified and distributed under the terms
|
||||
* of the MIT license. See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
#ifndef _INPUT_H_
|
||||
#define _INPUT_H_
|
||||
|
||||
#define BUTTON_A 0
|
||||
#define BUTTON_B 1
|
||||
#define BUTTON_SELECT 2
|
||||
#define BUTTON_START 3
|
||||
#define BUTTON_UP 4
|
||||
#define BUTTON_DOWN 5
|
||||
#define BUTTON_LEFT 6
|
||||
#define BUTTON_RIGHT 7
|
||||
|
||||
void inputInit();
|
||||
uint8_t inputGet8();
|
||||
void inputSet8(uint8_t in);
|
||||
bool inputAny();
|
||||
|
||||
#endif
|
661
main.c
Normal file
661
main.c
Normal file
@ -0,0 +1,661 @@
|
||||
/*
|
||||
* Copyright (C) 2017 FIX94
|
||||
*
|
||||
* This software may be modified and distributed under the terms
|
||||
* of the MIT license. See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <malloc.h>
|
||||
#include <inttypes.h>
|
||||
#include <GL/glut.h>
|
||||
#include <GL/glext.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
//#include "mapper.h"
|
||||
#include "cpu.h"
|
||||
#include "input.h"
|
||||
#include "ppu.h"
|
||||
#include "mem.h"
|
||||
//#include "fm2play.h"
|
||||
#include "apu.h"
|
||||
#include "audio.h"
|
||||
/*#include "audio_fds.h"
|
||||
#include "audio_vrc7.h"*/
|
||||
|
||||
#define DEBUG_HZ 0
|
||||
#define DEBUG_MAIN_CALLS 0
|
||||
#define DEBUG_KEY 0
|
||||
#define DEBUG_LOAD_INFO 1
|
||||
|
||||
static const char *VERSION_STRING = "fixGB";
|
||||
|
||||
static void gbEmuDisplayFrame(void);
|
||||
static void gbEmuMainLoop(void);
|
||||
static void gbEmuDeinit(void);
|
||||
|
||||
static void gbEmuHandleKeyDown(unsigned char key, int x, int y);
|
||||
static void gbEmuHandleKeyUp(unsigned char key, int x, int y);
|
||||
static void gbEmuHandleSpecialDown(int key, int x, int y);
|
||||
static void gbEmuHandleSpecialUp(int key, int x, int y);
|
||||
|
||||
/*static */uint8_t *emuGBROM = NULL;
|
||||
static char *emuSaveName = NULL;
|
||||
static uint8_t *emuPrgRAM = NULL;
|
||||
static uint32_t emuPrgRAMsize = 0;
|
||||
//used externally
|
||||
uint8_t *textureImage = NULL;
|
||||
bool nesPause = false;
|
||||
bool ppuDebugPauseFrame = false;
|
||||
bool doOverscan = true;
|
||||
bool gbEmuGBSPlayback = false;
|
||||
|
||||
static bool inPause = false;
|
||||
static bool inOverscanToggle = false;
|
||||
static bool inResize = false;
|
||||
|
||||
#if WINDOWS_BUILD
|
||||
#include <windows.h>
|
||||
typedef bool (APIENTRY *PFNWGLSWAPINTERVALEXTPROC) (int interval);
|
||||
PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL;
|
||||
#if DEBUG_HZ
|
||||
static DWORD emuFrameStart = 0;
|
||||
static DWORD emuTimesCalled = 0;
|
||||
static DWORD emuTotalElapsed = 0;
|
||||
#endif
|
||||
#if DEBUG_MAIN_CALLS
|
||||
static DWORD emuMainFrameStart = 0;
|
||||
static DWORD emuMainTimesCalled = 0;
|
||||
static DWORD emuMainTimesSkipped = 0;
|
||||
static DWORD emuMainTotalElapsed = 0;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define VISIBLE_DOTS 160
|
||||
#define VISIBLE_LINES 144
|
||||
|
||||
static const uint32_t visibleImg = VISIBLE_DOTS*VISIBLE_LINES*4;
|
||||
static uint8_t scaleFactor = 2;
|
||||
static bool emuSaveEnabled = false;
|
||||
static uint32_t mainLoopRuns;
|
||||
static uint16_t mainLoopPos;
|
||||
//from input.c
|
||||
extern uint8_t inValReads[8];
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
puts(VERSION_STRING);
|
||||
if(argc >= 2 && (strstr(argv[1],".gb") != NULL || strstr(argv[1],".GB") != NULL))
|
||||
{
|
||||
FILE *gbF = fopen(argv[1],"rb");
|
||||
if(!gbF) return EXIT_SUCCESS;
|
||||
fseek(gbF,0,SEEK_END);
|
||||
size_t fsize = ftell(gbF);
|
||||
rewind(gbF);
|
||||
emuGBROM = malloc(fsize);
|
||||
fread(emuGBROM,1,fsize,gbF);
|
||||
fclose(gbF);
|
||||
//uint8_t mapper = ((emuGBROM[6] & 0xF0) >> 4) | ((emuGBROM[7] & 0xF0));
|
||||
emuSaveEnabled = (emuGBROM[6] & (1<<1)) != 0;
|
||||
bool trainer = (emuGBROM[6] & (1<<2)) != 0;
|
||||
uint32_t prgROMsize = emuGBROM[4] * 0x4000;
|
||||
uint32_t chrROMsize = emuGBROM[5] * 0x2000;
|
||||
emuPrgRAMsize = emuGBROM[8] * 0x2000;
|
||||
if(emuPrgRAMsize == 0) emuPrgRAMsize = 0x2000;
|
||||
emuPrgRAM = malloc(emuPrgRAMsize);
|
||||
uint8_t *prgROM = emuGBROM+16;
|
||||
if(trainer)
|
||||
{
|
||||
memcpy(emuPrgRAM+0x1000,prgROM,0x200);
|
||||
prgROM += 512;
|
||||
}
|
||||
uint8_t *chrROM = NULL;
|
||||
if(chrROMsize)
|
||||
{
|
||||
chrROM = emuGBROM+16+prgROMsize;
|
||||
if(trainer) chrROM += 512;
|
||||
}
|
||||
apuInitBufs();
|
||||
cpuInit();
|
||||
ppuInit();
|
||||
memInit();
|
||||
apuInit();
|
||||
inputInit();
|
||||
#if DEBUG_LOAD_INFO
|
||||
printf("Read in %s\n", argv[1]);
|
||||
//printf("Used Mapper: %i\n", mapper);
|
||||
//printf("PRG: 0x%x bytes PRG RAM: 0x%x bytes CHR: 0x%x bytes\n", prgROMsize, emuPrgRAMsize, chrROMsize);
|
||||
#endif
|
||||
/*if(!mapperInit(mapper, prgROM, prgROMsize, emuPrgRAM, emuPrgRAMsize, chrROM, chrROMsize))
|
||||
{
|
||||
printf("Mapper init failed!\n");
|
||||
free(emuGBROM);
|
||||
emuGBROM = NULL;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
if(emuGBROM[6] & 8)
|
||||
ppuSetNameTbl4Screen();
|
||||
else if(emuGBROM[6] & 1)
|
||||
ppuSetNameTblVertical();
|
||||
else
|
||||
ppuSetNameTblHorizontal();
|
||||
#if DEBUG_LOAD_INFO
|
||||
printf("Trainer: %i Saving: %i VRAM Mode: %s\n", trainer, emuSaveEnabled, (emuGBROM[6] & 1) ? "Vertical" :
|
||||
((!(emuGBROM[6] & 1)) ? "Horizontal" : "4-Screen"));
|
||||
#endif
|
||||
if(emuSaveEnabled)
|
||||
{
|
||||
emuSaveName = strdup(argv[1]);
|
||||
memcpy(emuSaveName+strlen(emuSaveName)-3,"sav",3);
|
||||
FILE *save = fopen(emuSaveName, "rb");
|
||||
if(save)
|
||||
{
|
||||
fread(emuPrgRAM,1,emuPrgRAMsize,save);
|
||||
fclose(save);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
/*else if(argc >= 2 && (strstr(argv[1],".gbs") != NULL || strstr(argv[1],".GBS") != NULL))
|
||||
{
|
||||
FILE *gbF = fopen(argv[1],"rb");
|
||||
if(!gbF) return EXIT_SUCCESS;
|
||||
fseek(gbF,0,SEEK_END);
|
||||
size_t fsize = ftell(gbF);
|
||||
rewind(gbF);
|
||||
emuGBROM = malloc(fsize);
|
||||
fread(emuGBROM,1,fsize,gbF);
|
||||
fclose(gbF);
|
||||
emuPrgRAMsize = 0x2000;
|
||||
emuPrgRAM = malloc(emuPrgRAMsize);
|
||||
if(!mapperInitGBS(emuGBROM, fsize, emuPrgRAM, emuPrgRAMsize))
|
||||
{
|
||||
printf("GBS init failed!\n");
|
||||
free(emuGBROM);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
gbEmuGBSPlayback = true;
|
||||
}*/
|
||||
if(emuGBROM == NULL)
|
||||
return EXIT_SUCCESS;
|
||||
#if WINDOWS_BUILD
|
||||
#if DEBUG_HZ
|
||||
emuFrameStart = GetTickCount();
|
||||
#endif
|
||||
#if DEBUG_MAIN_CALLS
|
||||
emuMainFrameStart = GetTickCount();
|
||||
#endif
|
||||
#endif
|
||||
textureImage = malloc(visibleImg);
|
||||
memset(textureImage,0,visibleImg);
|
||||
//make sure image is visible
|
||||
uint32_t i;
|
||||
for(i = 0; i < visibleImg; i+=4)
|
||||
textureImage[i+3] = 0xFF;
|
||||
/*cpuCycleTimer = nesPAL ? 16 : 12;
|
||||
//do one scanline per idle loop
|
||||
ppuCycleTimer = nesPAL ? 5 : 4;
|
||||
mainLoopRuns = nesPAL ? DOTS*ppuCycleTimer : DOTS*ppuCycleTimer;
|
||||
mainLoopPos = mainLoopRuns;*/
|
||||
//do one scanline per idle loop
|
||||
mainLoopRuns = 70224;
|
||||
mainLoopPos = mainLoopRuns;
|
||||
glutInit(&argc, argv);
|
||||
glutInitWindowSize(VISIBLE_DOTS*scaleFactor, VISIBLE_LINES*scaleFactor);
|
||||
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
|
||||
glutCreateWindow(VERSION_STRING);
|
||||
audioInit();
|
||||
atexit(&gbEmuDeinit);
|
||||
glutKeyboardFunc(&gbEmuHandleKeyDown);
|
||||
glutKeyboardUpFunc(&gbEmuHandleKeyUp);
|
||||
glutSpecialFunc(&gbEmuHandleSpecialDown);
|
||||
glutSpecialUpFunc(&gbEmuHandleSpecialUp);
|
||||
glutDisplayFunc(&gbEmuDisplayFrame);
|
||||
glutIdleFunc(&gbEmuMainLoop);
|
||||
#if WINDOWS_BUILD
|
||||
/* Enable OpenGL VSync */
|
||||
wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
|
||||
wglSwapIntervalEXT(1);
|
||||
#endif
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, 4, VISIBLE_DOTS, VISIBLE_LINES, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, textureImage);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glShadeModel(GL_FLAT);
|
||||
|
||||
glutMainLoop();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static volatile bool emuRenderFrame = false;
|
||||
|
||||
static void gbEmuDeinit(void)
|
||||
{
|
||||
//printf("\n");
|
||||
emuRenderFrame = false;
|
||||
audioDeinit();
|
||||
apuDeinitBufs();
|
||||
if(emuGBROM != NULL)
|
||||
free(emuGBROM);
|
||||
emuGBROM = NULL;
|
||||
if(emuPrgRAM != NULL)
|
||||
{
|
||||
if(emuSaveEnabled)
|
||||
{
|
||||
FILE *save = fopen(emuSaveName, "wb");
|
||||
if(save)
|
||||
{
|
||||
fwrite(emuPrgRAM,1,emuPrgRAMsize,save);
|
||||
fclose(save);
|
||||
}
|
||||
}
|
||||
free(emuPrgRAM);
|
||||
}
|
||||
emuPrgRAM = NULL;
|
||||
if(textureImage != NULL)
|
||||
free(textureImage);
|
||||
textureImage = NULL;
|
||||
//printf("Bye!\n");
|
||||
}
|
||||
|
||||
//used externally
|
||||
bool emuSkipVsync = false;
|
||||
bool emuSkipFrame = false;
|
||||
|
||||
//static uint32_t mCycles = 0;
|
||||
//static bool emuApuDoCycle = false;
|
||||
|
||||
static uint16_t mainClock = 1;
|
||||
static uint16_t memClock = 1;
|
||||
//static uint16_t vrc7Clock = 1;
|
||||
|
||||
static void gbEmuMainLoop(void)
|
||||
{
|
||||
do
|
||||
{
|
||||
if((!emuSkipVsync && emuRenderFrame) || nesPause)
|
||||
{
|
||||
#if (WINDOWS_BUILD && DEBUG_MAIN_CALLS)
|
||||
emuMainTimesSkipped++;
|
||||
#endif
|
||||
audioSleep();
|
||||
return;
|
||||
}
|
||||
if(mainClock == 4)
|
||||
{
|
||||
if(!apuCycle())
|
||||
{
|
||||
#if (WINDOWS_BUILD && DEBUG_MAIN_CALLS)
|
||||
emuMainTimesSkipped++;
|
||||
#endif
|
||||
audioSleep();
|
||||
return;
|
||||
}
|
||||
//main CPU clock
|
||||
if(!cpuCycle())
|
||||
{
|
||||
memDumpMainMem();
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
if(memClock == 4)
|
||||
{
|
||||
memClockTimers();
|
||||
memClock = 1;
|
||||
}
|
||||
else
|
||||
memClock++;
|
||||
//channel timer updates
|
||||
apuLenCycle();
|
||||
/*//mapper related irqs
|
||||
if(mapperCycle != NULL)
|
||||
mapperCycle();*/
|
||||
//mCycles++;
|
||||
mainClock = 1;
|
||||
}
|
||||
else
|
||||
mainClock++;
|
||||
apuClockTimers();
|
||||
if(!ppuCycle())
|
||||
exit(EXIT_SUCCESS);
|
||||
if(!gbEmuGBSPlayback && ppuDrawDone())
|
||||
{
|
||||
//printf("%i\n",mCycles);
|
||||
//mCycles = 0;
|
||||
emuRenderFrame = true;
|
||||
#if (WINDOWS_BUILD && DEBUG_HZ)
|
||||
emuTimesCalled++;
|
||||
DWORD end = GetTickCount();
|
||||
emuTotalElapsed += end - emuFrameStart;
|
||||
if(emuTotalElapsed >= 1000)
|
||||
{
|
||||
printf("\r%iHz ", emuTimesCalled);
|
||||
emuTimesCalled = 0;
|
||||
emuTotalElapsed = 0;
|
||||
}
|
||||
emuFrameStart = end;
|
||||
#endif
|
||||
glutPostRedisplay();
|
||||
if(ppuDebugPauseFrame)
|
||||
nesPause = true;
|
||||
}
|
||||
}
|
||||
while(mainLoopPos--);
|
||||
mainLoopPos = mainLoopRuns;
|
||||
#if (WINDOWS_BUILD && DEBUG_MAIN_CALLS)
|
||||
emuMainTimesCalled++;
|
||||
DWORD end = GetTickCount();
|
||||
emuMainTotalElapsed += end - emuMainFrameStart;
|
||||
if(emuMainTotalElapsed >= 1000)
|
||||
{
|
||||
printf("\r%i calls, %i skips ", emuMainTimesCalled, emuMainTimesSkipped);
|
||||
emuMainTimesCalled = 0;
|
||||
emuMainTimesSkipped = 0;
|
||||
emuMainTotalElapsed = 0;
|
||||
}
|
||||
emuMainFrameStart = end;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void gbEmuHandleKeyDown(unsigned char key, int x, int y)
|
||||
{
|
||||
(void)x;
|
||||
(void)y;
|
||||
switch (key)
|
||||
{
|
||||
case 'y':
|
||||
case 'z':
|
||||
case 'Y':
|
||||
case 'Z':
|
||||
#if DEBUG_KEY
|
||||
if(inValReads[BUTTON_A]==0)
|
||||
printf("a\n");
|
||||
#endif
|
||||
inValReads[BUTTON_A]=1;
|
||||
break;
|
||||
case 'x':
|
||||
case 'X':
|
||||
#if DEBUG_KEY
|
||||
if(inValReads[BUTTON_B]==0)
|
||||
printf("b\n");
|
||||
#endif
|
||||
inValReads[BUTTON_B]=1;
|
||||
break;
|
||||
case 's':
|
||||
case 'S':
|
||||
#if DEBUG_KEY
|
||||
if(inValReads[BUTTON_SELECT]==0)
|
||||
printf("sel\n");
|
||||
#endif
|
||||
inValReads[BUTTON_SELECT]=1;
|
||||
break;
|
||||
case 'a':
|
||||
case 'A':
|
||||
#if DEBUG_KEY
|
||||
if(inValReads[BUTTON_START]==0)
|
||||
printf("start\n");
|
||||
#endif
|
||||
inValReads[BUTTON_START]=1;
|
||||
break;
|
||||
case '\x1B': //Escape
|
||||
memDumpMainMem();
|
||||
exit(EXIT_SUCCESS);
|
||||
break;
|
||||
case 'p':
|
||||
case 'P':
|
||||
if(!inPause)
|
||||
{
|
||||
#if DEBUG_KEY
|
||||
printf("pause\n");
|
||||
#endif
|
||||
inPause = true;
|
||||
nesPause ^= true;
|
||||
}
|
||||
break;
|
||||
case '1':
|
||||
if(!inResize)
|
||||
{
|
||||
inResize = true;
|
||||
glutReshapeWindow(VISIBLE_DOTS*1, VISIBLE_LINES*1);
|
||||
}
|
||||
break;
|
||||
case '2':
|
||||
if(!inResize)
|
||||
{
|
||||
inResize = true;
|
||||
glutReshapeWindow(VISIBLE_DOTS*2, VISIBLE_LINES*2);
|
||||
}
|
||||
break;
|
||||
case '3':
|
||||
if(!inResize)
|
||||
{
|
||||
inResize = true;
|
||||
glutReshapeWindow(VISIBLE_DOTS*3, VISIBLE_LINES*3);
|
||||
}
|
||||
break;
|
||||
case '4':
|
||||
if(!inResize)
|
||||
{
|
||||
inResize = true;
|
||||
glutReshapeWindow(VISIBLE_DOTS*4, VISIBLE_LINES*4);
|
||||
}
|
||||
break;
|
||||
case '5':
|
||||
if(!inResize)
|
||||
{
|
||||
inResize = true;
|
||||
glutReshapeWindow(VISIBLE_DOTS*5, VISIBLE_LINES*5);
|
||||
}
|
||||
break;
|
||||
case '6':
|
||||
if(!inResize)
|
||||
{
|
||||
inResize = true;
|
||||
glutReshapeWindow(VISIBLE_DOTS*6, VISIBLE_LINES*6);
|
||||
}
|
||||
break;
|
||||
case '7':
|
||||
if(!inResize)
|
||||
{
|
||||
inResize = true;
|
||||
glutReshapeWindow(VISIBLE_DOTS*7, VISIBLE_LINES*7);
|
||||
}
|
||||
break;
|
||||
case '8':
|
||||
if(!inResize)
|
||||
{
|
||||
inResize = true;
|
||||
glutReshapeWindow(VISIBLE_DOTS*8, VISIBLE_LINES*8);
|
||||
}
|
||||
break;
|
||||
case '9':
|
||||
if(!inResize)
|
||||
{
|
||||
inResize = true;
|
||||
glutReshapeWindow(VISIBLE_DOTS*9, VISIBLE_LINES*9);
|
||||
}
|
||||
break;
|
||||
case 'o':
|
||||
case 'O':
|
||||
if(!inOverscanToggle)
|
||||
{
|
||||
inOverscanToggle = true;
|
||||
doOverscan ^= true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void gbEmuHandleKeyUp(unsigned char key, int x, int y)
|
||||
{
|
||||
(void)x;
|
||||
(void)y;
|
||||
switch (key)
|
||||
{
|
||||
case 'y':
|
||||
case 'z':
|
||||
case 'Y':
|
||||
case 'Z':
|
||||
#if DEBUG_KEY
|
||||
printf("a up\n");
|
||||
#endif
|
||||
inValReads[BUTTON_A]=0;
|
||||
break;
|
||||
case 'x':
|
||||
case 'X':
|
||||
#if DEBUG_KEY
|
||||
printf("b up\n");
|
||||
#endif
|
||||
inValReads[BUTTON_B]=0;
|
||||
break;
|
||||
case 's':
|
||||
case 'S':
|
||||
#if DEBUG_KEY
|
||||
printf("sel up\n");
|
||||
#endif
|
||||
inValReads[BUTTON_SELECT]=0;
|
||||
break;
|
||||
case 'a':
|
||||
case 'A':
|
||||
#if DEBUG_KEY
|
||||
printf("start up\n");
|
||||
#endif
|
||||
inValReads[BUTTON_START]=0;
|
||||
break;
|
||||
case 'p':
|
||||
case 'P':
|
||||
#if DEBUG_KEY
|
||||
printf("pause up\n");
|
||||
#endif
|
||||
inPause=false;
|
||||
break;
|
||||
case '1': case '2': case '3':
|
||||
case '4': case '5': case '6':
|
||||
case '7': case '8': case '9':
|
||||
inResize = false;
|
||||
break;
|
||||
case 'o':
|
||||
case 'O':
|
||||
inOverscanToggle = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void gbEmuHandleSpecialDown(int key, int x, int y)
|
||||
{
|
||||
(void)x;
|
||||
(void)y;
|
||||
switch(key)
|
||||
{
|
||||
case GLUT_KEY_UP:
|
||||
#if DEBUG_KEY
|
||||
if(inValReads[BUTTON_UP]==0)
|
||||
printf("up\n");
|
||||
#endif
|
||||
inValReads[BUTTON_UP]=1;
|
||||
break;
|
||||
case GLUT_KEY_DOWN:
|
||||
#if DEBUG_KEY
|
||||
if(inValReads[BUTTON_DOWN]==0)
|
||||
printf("down\n");
|
||||
#endif
|
||||
inValReads[BUTTON_DOWN]=1;
|
||||
break;
|
||||
case GLUT_KEY_LEFT:
|
||||
#if DEBUG_KEY
|
||||
if(inValReads[BUTTON_LEFT]==0)
|
||||
printf("left\n");
|
||||
#endif
|
||||
inValReads[BUTTON_LEFT]=1;
|
||||
break;
|
||||
case GLUT_KEY_RIGHT:
|
||||
#if DEBUG_KEY
|
||||
if(inValReads[BUTTON_RIGHT]==0)
|
||||
printf("right\n");
|
||||
#endif
|
||||
inValReads[BUTTON_RIGHT]=1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void gbEmuHandleSpecialUp(int key, int x, int y)
|
||||
{
|
||||
(void)x;
|
||||
(void)y;
|
||||
switch(key)
|
||||
{
|
||||
case GLUT_KEY_UP:
|
||||
#if DEBUG_KEY
|
||||
printf("up up\n");
|
||||
#endif
|
||||
inValReads[BUTTON_UP]=0;
|
||||
break;
|
||||
case GLUT_KEY_DOWN:
|
||||
#if DEBUG_KEY
|
||||
printf("down up\n");
|
||||
#endif
|
||||
inValReads[BUTTON_DOWN]=0;
|
||||
break;
|
||||
case GLUT_KEY_LEFT:
|
||||
#if DEBUG_KEY
|
||||
printf("left up\n");
|
||||
#endif
|
||||
inValReads[BUTTON_LEFT]=0;
|
||||
break;
|
||||
case GLUT_KEY_RIGHT:
|
||||
#if DEBUG_KEY
|
||||
printf("right up\n");
|
||||
#endif
|
||||
inValReads[BUTTON_RIGHT]=0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void gbEmuDisplayFrame()
|
||||
{
|
||||
if(emuRenderFrame)
|
||||
{
|
||||
if(emuSkipFrame)
|
||||
{
|
||||
emuRenderFrame = false;
|
||||
return;
|
||||
}
|
||||
if(textureImage != NULL)
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, 4, VISIBLE_DOTS, VISIBLE_LINES, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, textureImage);
|
||||
emuRenderFrame = false;
|
||||
}
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
glOrtho(0, glutGet(GLUT_WINDOW_WIDTH), 0, glutGet(GLUT_WINDOW_HEIGHT), -1, 1);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
||||
double upscaleVal = round((((double)glutGet(GLUT_WINDOW_HEIGHT))/((double)VISIBLE_LINES))*20.0)/20.0;
|
||||
double windowMiddle = ((double)glutGet(GLUT_WINDOW_WIDTH))/2.0;
|
||||
double drawMiddle = (((double)VISIBLE_DOTS)*upscaleVal)/2.0;
|
||||
double drawHeight = ((double)VISIBLE_LINES)*upscaleVal;
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
glTexCoord2f(0,0); glVertex2f(windowMiddle-drawMiddle,drawHeight);
|
||||
glTexCoord2f(1,0); glVertex2f(windowMiddle+drawMiddle,drawHeight);
|
||||
glTexCoord2f(1,1); glVertex2f(windowMiddle+drawMiddle,0);
|
||||
glTexCoord2f(0,1); glVertex2f(windowMiddle-drawMiddle,0);
|
||||
glEnd();
|
||||
|
||||
glutSwapBuffers();
|
||||
}
|
223
mem.c
Normal file
223
mem.c
Normal file
@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright (C) 2017 FIX94
|
||||
*
|
||||
* This software may be modified and distributed under the terms
|
||||
* of the MIT license. See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
#include "cpu.h"
|
||||
#include "ppu.h"
|
||||
#include "apu.h"
|
||||
#include "input.h"
|
||||
|
||||
static uint8_t Main_Mem[0x2000];
|
||||
static uint8_t High_Mem[0x80];
|
||||
static uint8_t memLastVal;
|
||||
static uint8_t irqEnableReg;
|
||||
static uint8_t irqFlagsReg;
|
||||
|
||||
static uint8_t divRegVal;
|
||||
static uint8_t divRegClock;
|
||||
static uint8_t timerReg;
|
||||
static uint8_t timerRegVal;
|
||||
static uint8_t timerResetVal;
|
||||
static uint8_t timerRegClock;
|
||||
static uint8_t timerRegTimer;
|
||||
static uint8_t cBank;
|
||||
static uint32_t bankMask;
|
||||
static bool timerRegEnable;
|
||||
|
||||
extern uint8_t *emuGBROM;
|
||||
|
||||
void memInit()
|
||||
{
|
||||
bankMask = (0x8000<<(emuGBROM[0x148]&0xF))-1;
|
||||
memset(Main_Mem,0,0x2000);
|
||||
memset(High_Mem,0,0x80);
|
||||
memLastVal = 0;
|
||||
irqEnableReg = 0;
|
||||
irqFlagsReg = 0;
|
||||
cBank = 1;
|
||||
divRegVal = 0;
|
||||
divRegClock = 1;
|
||||
timerReg = 0;
|
||||
timerRegVal = 0;
|
||||
timerResetVal = 0;
|
||||
timerRegClock = 1;
|
||||
timerRegTimer = 64; //262144 / 64 = 4096
|
||||
timerRegEnable = false;
|
||||
}
|
||||
|
||||
uint8_t memGet8(uint16_t addr)
|
||||
{
|
||||
uint8_t val = memLastVal;
|
||||
//printf("memGet8 %04x\n", addr);
|
||||
if(addr < 0x4000)
|
||||
val = emuGBROM[addr];
|
||||
else if(addr < 0x8000)
|
||||
val = emuGBROM[((cBank<<14)+(addr&0x3FFF))&bankMask];
|
||||
else if(addr >= 0x8000 && addr < 0xA000)
|
||||
val = ppuGet8(addr);
|
||||
else if(addr >= 0xC000 && addr < 0xFE00)
|
||||
val = Main_Mem[addr&0x1FFF];
|
||||
else if(addr >= 0xFE00 && addr < 0xFEA0)
|
||||
val = ppuGet8(addr);
|
||||
else if(addr == 0xFF00)
|
||||
val = inputGet8();
|
||||
else if(addr == 0xFF04)
|
||||
val = divRegVal;
|
||||
else if(addr == 0xFF05)
|
||||
val = timerRegVal;
|
||||
else if(addr == 0xFF06)
|
||||
val = timerResetVal;
|
||||
else if(addr == 0xFF07)
|
||||
val = timerReg;
|
||||
else if(addr == 0xFF0F)
|
||||
{
|
||||
val = irqFlagsReg;
|
||||
//printf("memGet8 %04x %02x\n", addr, val);
|
||||
}
|
||||
else if(addr >= 0xFF10 && addr < 0xFF40)
|
||||
val = apuGet8(addr&0xFF);
|
||||
else if(addr >= 0xFF40 && addr < 0xFF70)
|
||||
val = ppuGet8(addr);
|
||||
else if(addr >= 0xFF80 && addr < 0xFFFF)
|
||||
val = High_Mem[addr&0x7F];
|
||||
else if(addr == 0xFFFF)
|
||||
val = irqEnableReg;
|
||||
memLastVal = val;
|
||||
return val;
|
||||
}
|
||||
extern uint32_t cpu_oam_dma;
|
||||
extern bool cpu_odd_cycle;
|
||||
void memSet8(uint16_t addr, uint8_t val)
|
||||
{
|
||||
if(addr >= 0x2000 && addr < 0x4000)
|
||||
{
|
||||
//printf("%02x\n",val);
|
||||
cBank = val;
|
||||
if(cBank == 0)
|
||||
cBank = 1;
|
||||
}
|
||||
if(addr >= 0x8000 && addr < 0xA000)
|
||||
ppuSet8(addr, val);
|
||||
else if(addr >= 0xC000 && addr < 0xFE00)
|
||||
Main_Mem[addr&0x1FFF] = val;
|
||||
else if(addr >= 0xFE00 && addr < 0xFEA0)
|
||||
ppuSet8(addr, val);
|
||||
else if(addr == 0xFF00)
|
||||
inputSet8(val);
|
||||
else if(addr == 0xFF04)
|
||||
divRegVal = 0; //writing any val resets to 0
|
||||
else if(addr == 0xFF05)
|
||||
timerRegVal = 0; //not sure
|
||||
else if(addr == 0xFF06)
|
||||
timerResetVal = val;
|
||||
else if(addr == 0xFF07)
|
||||
{
|
||||
//if(val != 0)
|
||||
// printf("memSet8 %04x %02x\n", addr, val);
|
||||
timerReg = val; //for readback
|
||||
timerRegEnable = ((val&4)!=0);
|
||||
if((val&3)==0) //0 for 4096 Hz
|
||||
timerRegTimer = 64; //262144 / 64 = 4096
|
||||
else if((val&3)==1) //1 for 262144 Hz
|
||||
timerRegTimer = 1; //262144 / 1 = 262144
|
||||
else if((val&3)==2) //2 for 65536 Hz
|
||||
timerRegTimer = 4; //262144 / 4 = 65536
|
||||
else if((val&3)==3) //3 for 16384 Hz
|
||||
timerRegTimer = 16; //262144 / 16 = 16384
|
||||
}
|
||||
else if(addr == 0xFF0F)
|
||||
{
|
||||
//printf("memSet8 %04x %02x\n", addr, val);
|
||||
irqFlagsReg = val;
|
||||
}
|
||||
else if(addr >= 0xFF10 && addr < 0xFF40)
|
||||
apuSet8(addr&0xFF, val);
|
||||
else if(addr >= 0xFF40 && addr < 0xFF70)
|
||||
ppuSet8(addr, val);
|
||||
else if(addr >= 0xFF80 && addr < 0xFFFF)
|
||||
High_Mem[addr&0x7F] = val;
|
||||
else if(addr == 0xFFFF)
|
||||
{
|
||||
//printf("memSet8 %04x %02x\n", addr, val);
|
||||
irqEnableReg = val;
|
||||
}
|
||||
memLastVal = val;
|
||||
}
|
||||
|
||||
uint8_t memGetCurIrqList()
|
||||
{
|
||||
return (irqEnableReg & irqFlagsReg);
|
||||
}
|
||||
|
||||
void memClearCurIrqList(uint8_t num)
|
||||
{
|
||||
irqFlagsReg &= ~num;
|
||||
}
|
||||
|
||||
void memEnableVBlankIrq()
|
||||
{
|
||||
irqFlagsReg |= 1;
|
||||
}
|
||||
|
||||
void memEnableStatIrq()
|
||||
{
|
||||
irqFlagsReg |= 2;
|
||||
}
|
||||
|
||||
#define DEBUG_MEM_DUMP 1
|
||||
|
||||
void memDumpMainMem()
|
||||
{
|
||||
#if DEBUG_MEM_DUMP
|
||||
FILE *f = fopen("MainMem.bin","wb");
|
||||
if(f)
|
||||
{
|
||||
fwrite(Main_Mem,1,0x2000,f);
|
||||
fclose(f);
|
||||
}
|
||||
f = fopen("HighMem.bin","wb");
|
||||
if(f)
|
||||
{
|
||||
fwrite(High_Mem,1,0x80,f);
|
||||
fclose(f);
|
||||
}
|
||||
ppuDumpMem();
|
||||
#endif
|
||||
}
|
||||
|
||||
//clocked at 262144 Hz
|
||||
void memClockTimers()
|
||||
{
|
||||
//clocked at 16384 Hz (262144 / 16 = 16384)
|
||||
if(divRegClock == 16)
|
||||
{
|
||||
divRegVal++;
|
||||
divRegClock = 1;
|
||||
}
|
||||
else
|
||||
divRegClock++;
|
||||
|
||||
if(!timerRegEnable)
|
||||
return;
|
||||
|
||||
//clocked at specified rate
|
||||
if(timerRegClock == timerRegTimer)
|
||||
{
|
||||
timerRegVal++;
|
||||
if(timerRegVal == 0) //set on overflow
|
||||
{
|
||||
//printf("Timer interrupt\n");
|
||||
timerRegVal = timerResetVal;
|
||||
irqFlagsReg |= 4;
|
||||
}
|
||||
timerRegClock = 1;
|
||||
}
|
||||
else
|
||||
timerRegClock++;
|
||||
}
|
23
mem.h
Normal file
23
mem.h
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2017 FIX94
|
||||
*
|
||||
* This software may be modified and distributed under the terms
|
||||
* of the MIT license. See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
#ifndef _mem_h_
|
||||
#define _mem_h_
|
||||
|
||||
void memInit();
|
||||
uint8_t memGet8(uint16_t addr);
|
||||
void memSet8(uint16_t addr, uint8_t val);
|
||||
void memSet16(uint16_t addr, uint16_t val);
|
||||
void memDumpMainMem();
|
||||
void memClockTimers();
|
||||
|
||||
uint8_t memGetCurIrqList();
|
||||
void memClearCurIrqList(uint8_t num);
|
||||
void memEnableVBlankIrq();
|
||||
void memEnableStatIrq();
|
||||
|
||||
#endif
|
407
ppu.c
Normal file
407
ppu.c
Normal file
@ -0,0 +1,407 @@
|
||||
/*
|
||||
* Copyright (C) 2017 FIX94
|
||||
*
|
||||
* This software may be modified and distributed under the terms
|
||||
* of the MIT license. See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
#include "cpu.h"
|
||||
#include "ppu.h"
|
||||
#include "mem.h"
|
||||
|
||||
//FF40
|
||||
#define PPU_BG_ENABLE (1<<0)
|
||||
#define PPU_SPRITE_ENABLE (1<<1)
|
||||
#define PPU_SPRITE_8_16 (1<<2)
|
||||
#define PPU_BG_TILEMAP_UP (1<<3)
|
||||
#define PPU_BG_TILEDAT_LOW (1<<4)
|
||||
#define PPU_WINDOW_ENABLE (1<<5)
|
||||
#define PPU_WINDOW_TILEMAP_UP (1<<6)
|
||||
#define PPU_ENABLE (1<<7)
|
||||
|
||||
//FF41
|
||||
#define PPU_LINEMATCH (1<<2)
|
||||
#define PPU_HBLANK_IRQ (1<<3)
|
||||
#define PPU_VBLANK_IRQ (1<<4)
|
||||
#define PPU_OAM_IRQ (1<<5)
|
||||
#define PPU_LINEMATCH_IRQ (1<<6)
|
||||
|
||||
//sprite byte 3
|
||||
#define PPU_SPRITE_PAL (1<<4)
|
||||
#define PPU_SPRITE_FLIP_X (1<<5)
|
||||
#define PPU_SPRITE_FLIP_Y (1<<6)
|
||||
#define PPU_SPRITE_PRIO (1<<7)
|
||||
|
||||
extern uint8_t *textureImage;
|
||||
|
||||
static uint32_t ppuClock;
|
||||
static uint32_t ppuTestClock;
|
||||
static uint8_t ppuMode;
|
||||
static uint8_t ppuDots;
|
||||
static uint8_t ppuOAMpos;
|
||||
static uint8_t ppuOAM2pos;
|
||||
static uint8_t PPU_Reg[12];
|
||||
static uint8_t PPU_OAM[0xA0];
|
||||
static uint8_t PPU_OAM2[0x28];
|
||||
static uint8_t PPU_VRAM[0x2000];
|
||||
static bool ppuFrameDone;
|
||||
static bool ppuVBlank;
|
||||
static bool ppuVBlankTriggered;
|
||||
|
||||
void ppuInit()
|
||||
{
|
||||
ppuClock = 0;
|
||||
ppuTestClock = 0;
|
||||
ppuMode = 0;
|
||||
ppuDots = 0;
|
||||
ppuOAMpos = 0;
|
||||
ppuOAM2pos = 0;
|
||||
ppuFrameDone = false;
|
||||
ppuVBlank = false;
|
||||
ppuVBlankTriggered = false;
|
||||
memset(PPU_Reg,0,12);
|
||||
memset(PPU_OAM,0,0xA0);
|
||||
memset(PPU_OAM2,0,0x28);
|
||||
memset(PPU_VRAM,0,0x2000);
|
||||
//From GB Bootrom
|
||||
PPU_Reg[0] = 0x91;
|
||||
PPU_Reg[7] = 0xFC;
|
||||
PPU_Reg[8] = 0xFF;
|
||||
PPU_Reg[9] = 0xFF;
|
||||
}
|
||||
|
||||
bool ppuCycle()
|
||||
{
|
||||
if(!(PPU_Reg[0] & PPU_ENABLE))
|
||||
return true;
|
||||
if(PPU_Reg[4] < 144)
|
||||
{
|
||||
if(ppuClock == 0)
|
||||
{
|
||||
ppuOAMpos = 0; //Reset check pos
|
||||
ppuOAM2pos = 0; //Reset array pos
|
||||
ppuMode = 2; //OAM
|
||||
if(PPU_Reg[1]&PPU_OAM_IRQ)
|
||||
{
|
||||
//printf("OAM STAT IRQ\n");
|
||||
memEnableStatIrq();
|
||||
}
|
||||
}
|
||||
if(ppuClock == 80)
|
||||
{
|
||||
ppuDots = 0; //Reset Draw Pos
|
||||
ppuMode = 3; //Main Mode
|
||||
}
|
||||
if(ppuClock == 252)
|
||||
{
|
||||
ppuMode = 0; //HBlank
|
||||
if(PPU_Reg[1]&PPU_HBLANK_IRQ)
|
||||
{
|
||||
//printf("HBlank STAT IRQ\n");
|
||||
memEnableStatIrq();
|
||||
}
|
||||
}
|
||||
//do OAM updates?
|
||||
if(ppuClock < 80 && ((ppuClock&1) == 0) && ppuOAM2pos < 10)
|
||||
{
|
||||
uint8_t OAMcYpos = PPU_OAM[(ppuOAMpos<<2)];
|
||||
if(OAMcYpos < 160)
|
||||
{
|
||||
int16_t cmpPos = ((int16_t)OAMcYpos)-16;
|
||||
uint8_t cSpriteAdd = (PPU_Reg[0] & PPU_SPRITE_8_16) ? 16 : 8;
|
||||
if(cmpPos <= PPU_Reg[4] && (cmpPos+cSpriteAdd) > PPU_Reg[4])
|
||||
{
|
||||
memcpy(PPU_OAM2+(ppuOAM2pos<<2), PPU_OAM+(ppuOAMpos<<2), 4);
|
||||
ppuOAM2pos++;
|
||||
}
|
||||
}
|
||||
ppuOAMpos++;
|
||||
}
|
||||
//draw point?
|
||||
if(ppuClock >= 80 && ppuClock < 240)
|
||||
{
|
||||
//makes it possible to draw 160x144 in here :)
|
||||
uint8_t ChrRegA = 0, ChrRegB = 0, color = 0, tCol = 0;
|
||||
if(PPU_Reg[0]&PPU_BG_ENABLE)
|
||||
{
|
||||
uint8_t bgXPos = ppuDots+PPU_Reg[3];
|
||||
uint8_t bgYPos = PPU_Reg[4]+PPU_Reg[2];
|
||||
uint16_t vramTilePos = ((PPU_Reg[0]&PPU_BG_TILEMAP_UP)?0x1C00:0x1800)+(((bgXPos/8)+(bgYPos/8*32))&0x3FFF);
|
||||
if(PPU_Reg[0]&PPU_BG_TILEDAT_LOW)
|
||||
{
|
||||
uint8_t tVal = PPU_VRAM[vramTilePos&0x1FFF];
|
||||
uint16_t tPos = tVal*16;
|
||||
tPos+=(bgYPos&7)*2;
|
||||
ChrRegA = PPU_VRAM[(tPos)&0x1FFF];
|
||||
ChrRegB = PPU_VRAM[(tPos+1)&0x1FFF];
|
||||
}
|
||||
else
|
||||
{
|
||||
int8_t tVal = (int8_t)PPU_VRAM[vramTilePos&0x1FFF];
|
||||
int16_t tPos = tVal*16;
|
||||
tPos+=(bgYPos&7)*2;
|
||||
ChrRegA = PPU_VRAM[(0x1000+tPos)&0x1FFF];
|
||||
ChrRegB = PPU_VRAM[(0x1000+tPos+1)&0x1FFF];
|
||||
}
|
||||
if(ChrRegA & (0x80>>(bgXPos&7)))
|
||||
color |= 1;
|
||||
if(ChrRegB & (0x80>>(bgXPos&7)))
|
||||
color |= 2;
|
||||
//again, not sure
|
||||
tCol = (~(PPU_Reg[7]>>(color*2)))&3;
|
||||
}
|
||||
if(PPU_Reg[0]&PPU_WINDOW_ENABLE && (PPU_Reg[0xB]) <= ppuDots+7 && PPU_Reg[0xA] <= PPU_Reg[4])
|
||||
{
|
||||
uint8_t windowXPos = ppuDots+7-PPU_Reg[0xB];
|
||||
uint8_t windowYPos = PPU_Reg[4]-PPU_Reg[0xA];
|
||||
uint16_t vramTilePos = ((PPU_Reg[0]&PPU_WINDOW_TILEMAP_UP)?0x1C00:0x1800)+(((windowXPos/8)+(windowYPos/8*32))&0x3FF);
|
||||
if(PPU_Reg[0]&PPU_BG_TILEDAT_LOW)
|
||||
{
|
||||
uint8_t tVal = PPU_VRAM[vramTilePos&0x1FFF];
|
||||
uint16_t tPos = tVal*16;
|
||||
tPos+=(windowYPos&7)*2;
|
||||
ChrRegA = PPU_VRAM[(tPos)&0x1FFF];
|
||||
ChrRegB = PPU_VRAM[(tPos+1)&0x1FFF];
|
||||
}
|
||||
else
|
||||
{
|
||||
int8_t tVal = (int8_t)PPU_VRAM[vramTilePos&0x1FFF];
|
||||
int16_t tPos = tVal*16;
|
||||
tPos+=(windowYPos&7)*2;
|
||||
ChrRegA = PPU_VRAM[(0x1000+tPos)&0x1FFF];
|
||||
ChrRegB = PPU_VRAM[(0x1000+tPos+1)&0x1FFF];
|
||||
}
|
||||
color = 0;
|
||||
if(ChrRegA & (0x80>>(windowXPos&7)))
|
||||
color |= 1;
|
||||
if(ChrRegB & (0x80>>(windowXPos&7)))
|
||||
color |= 2;
|
||||
//again, not sure
|
||||
tCol = (~(PPU_Reg[7]>>(color*2)))&3;
|
||||
}
|
||||
if(PPU_Reg[0]&PPU_SPRITE_ENABLE)
|
||||
{
|
||||
uint8_t i;
|
||||
uint8_t cSpriteAnd = (PPU_Reg[0] & PPU_SPRITE_8_16) ? 15 : 7;
|
||||
for(i = 0; i < ppuOAM2pos; i++)
|
||||
{
|
||||
uint8_t OAMcXpos = PPU_OAM2[(i<<2)+1];
|
||||
if(OAMcXpos >= 168)
|
||||
continue;
|
||||
int16_t cmpPos = ((int16_t)OAMcXpos)-8;
|
||||
if(cmpPos <= ppuDots && (cmpPos+8) > ppuDots)
|
||||
{
|
||||
uint8_t cSpriteByte3 = PPU_OAM2[(i<<2)+3];
|
||||
uint8_t tVal = PPU_OAM2[(i<<2)+2];
|
||||
uint16_t tPos = tVal*16;
|
||||
|
||||
uint8_t OAMcYpos = PPU_OAM2[(i<<2)];
|
||||
uint8_t cmpYPos = OAMcYpos-16;
|
||||
uint8_t cSpriteY = (PPU_Reg[4] - cmpYPos)&cSpriteAnd;
|
||||
uint8_t cSpriteAdd = 0; //used to select which 8 by 16 tile
|
||||
if(cSpriteY > 7) //8 by 16 select
|
||||
{
|
||||
cSpriteAdd = 16;
|
||||
cSpriteY &= 7;
|
||||
}
|
||||
if(cSpriteByte3 & PPU_SPRITE_FLIP_Y)
|
||||
{
|
||||
cSpriteY ^= 7;
|
||||
if(PPU_Reg[0] & PPU_SPRITE_8_16)
|
||||
cSpriteAdd ^= 16; //8 by 16 select
|
||||
}
|
||||
tPos+=(cSpriteY)*2;
|
||||
|
||||
ChrRegA = PPU_VRAM[(tPos+cSpriteAdd)&0x1FFF];
|
||||
ChrRegB = PPU_VRAM[(tPos+cSpriteAdd+1)&0x1FFF];
|
||||
|
||||
uint8_t cSpriteX = (ppuDots - OAMcXpos)&7;
|
||||
if(cSpriteByte3 & PPU_SPRITE_FLIP_X)
|
||||
cSpriteX ^= 7;
|
||||
uint8_t sprCol = 0;
|
||||
if(ChrRegA & (0x80>>cSpriteX))
|
||||
sprCol |= 1;
|
||||
if(ChrRegB & (0x80>>cSpriteX))
|
||||
sprCol |= 2;
|
||||
|
||||
//done looking at sprites, we have to
|
||||
//always return the first one we find
|
||||
if(sprCol != 0)
|
||||
{
|
||||
//sprite has highest priority, return sprite
|
||||
if((cSpriteByte3 & PPU_SPRITE_PRIO) == 0)
|
||||
{
|
||||
if(cSpriteByte3 & PPU_SPRITE_PAL)
|
||||
tCol = (~(PPU_Reg[9]>>(sprCol*2)))&3;
|
||||
else
|
||||
tCol = (~(PPU_Reg[8]>>(sprCol*2)))&3;
|
||||
break;
|
||||
} //sprite has low priority and BG is not 0, return BG
|
||||
else if((color&3) != 0)
|
||||
break;
|
||||
//background is 0 so return sprite
|
||||
if(cSpriteByte3 & PPU_SPRITE_PAL)
|
||||
tCol = (~(PPU_Reg[9]>>(sprCol*2)))&3;
|
||||
else
|
||||
tCol = (~(PPU_Reg[8]>>(sprCol*2)))&3;
|
||||
break;
|
||||
}
|
||||
//Sprite is 0, keep looking for sprites
|
||||
}
|
||||
}
|
||||
}
|
||||
uint8_t draw = (tCol == 0) ? 0 : (tCol == 1) ? 0x55 : (tCol == 2) ? 0xAA : 0xFF;
|
||||
{
|
||||
size_t drawPos = (ppuDots*4)+(PPU_Reg[4]*160*4);
|
||||
textureImage[drawPos] = draw;
|
||||
textureImage[drawPos+1] = draw;
|
||||
textureImage[drawPos+2] = draw;
|
||||
}
|
||||
ppuDots++;
|
||||
}
|
||||
}
|
||||
ppuTestClock++;
|
||||
ppuClock++;
|
||||
if(ppuClock == 456)
|
||||
{
|
||||
ppuClock = 0;
|
||||
PPU_Reg[4]++;
|
||||
if(PPU_Reg[4]==PPU_Reg[5])
|
||||
{
|
||||
if(PPU_Reg[1]&PPU_LINEMATCH_IRQ)
|
||||
{
|
||||
//printf("Line STAT IRQ at %i\n",PPU_Reg[4]);
|
||||
memEnableStatIrq();
|
||||
}
|
||||
}
|
||||
if(PPU_Reg[4] == 144)
|
||||
{
|
||||
ppuMode = 1; //VBlank
|
||||
ppuVBlank = true;
|
||||
memEnableVBlankIrq();
|
||||
if(PPU_Reg[1]&PPU_VBLANK_IRQ)
|
||||
{
|
||||
//printf("VBlank STAT IRQ\n");
|
||||
memEnableStatIrq();
|
||||
}
|
||||
//printf("VBlank Start\n");
|
||||
}
|
||||
if(PPU_Reg[4] == 154)
|
||||
{
|
||||
PPU_Reg[4] = 0; //Draw Done!
|
||||
extern int testCounter;
|
||||
//printf("Draw Done %i %i\n",testCounter,ppuTestClock);
|
||||
testCounter = 0;
|
||||
ppuTestClock = 0;
|
||||
ppuFrameDone = true;
|
||||
ppuVBlank = false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ppuDrawDone()
|
||||
{
|
||||
if(ppuFrameDone)
|
||||
{
|
||||
//printf("%i\n",ppuCycles);
|
||||
//ppuCycles = 0;
|
||||
ppuFrameDone = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t ppuGet8(uint16_t addr)
|
||||
{
|
||||
uint8_t val = 0;
|
||||
if(addr >= 0x8000 && addr < 0xA000)
|
||||
val = PPU_VRAM[addr&0x1FFF];
|
||||
else if(addr >= 0xFE00 && addr < 0xFEA0)
|
||||
val = PPU_OAM[addr&0xFF];
|
||||
else if(addr >= 0xFF40 && addr < 0xFF4C)
|
||||
{
|
||||
if(addr == 0xFF41)
|
||||
{
|
||||
if(!(PPU_Reg[0] & PPU_ENABLE))
|
||||
val = 0; //This is not all that clear anywhere...
|
||||
else
|
||||
val = (PPU_Reg[addr&0xF]&(~7))|(ppuMode&3)|((PPU_Reg[4]==PPU_Reg[5])?PPU_LINEMATCH:0);
|
||||
}
|
||||
else
|
||||
val = PPU_Reg[addr&0xF];
|
||||
//if(addr != 0xFF44)
|
||||
// printf("at instr %04x:ppuGet8(%04x, %02x)\n",cpuCurPC(),addr,val);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
void ppuSet8(uint16_t addr, uint8_t val)
|
||||
{
|
||||
if(addr >= 0x8000 && addr < 0xA000)
|
||||
PPU_VRAM[addr&0x1FFF] = val;
|
||||
else if(addr >= 0xFE00 && addr < 0xFEA0)
|
||||
PPU_OAM[addr&0xFF] = val;
|
||||
else if(addr >= 0xFF40 && addr < 0xFF4C)
|
||||
{
|
||||
if(addr == 0xFF46) //OAM DMA
|
||||
{
|
||||
if(val < 0xFE)
|
||||
{
|
||||
uint8_t i;
|
||||
for(i = 0; i < 0xA0; i++)
|
||||
PPU_OAM[i] = memGet8((val<<8) | i);
|
||||
}
|
||||
}
|
||||
else if(addr != 0xFF44) //line count fully RO
|
||||
{
|
||||
if(addr == 0xFF41) //STAT RO Regs
|
||||
PPU_Reg[addr&0xF] = (val&(~7));
|
||||
else //other R/W regs
|
||||
PPU_Reg[addr&0xF] = val;
|
||||
//if(addr == 0xFF40)
|
||||
// printf("ppuSet8(%04x, %02x)\n",addr,val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ppuDumpMem()
|
||||
{
|
||||
FILE *f = fopen("PPU_VRAM.bin","wb");
|
||||
if(f)
|
||||
{
|
||||
fwrite(PPU_VRAM,1,0x2000,f);
|
||||
fclose(f);
|
||||
}
|
||||
f = fopen("PPU_OAM.bin","wb");
|
||||
if(f)
|
||||
{
|
||||
fwrite(PPU_OAM,1,0xA0,f);
|
||||
fclose(f);
|
||||
}
|
||||
/*f = fopen("PPU_Sprites.bin","wb");
|
||||
if(f)
|
||||
{
|
||||
fwrite(PPU_Sprites,1,0x20,f);
|
||||
fclose(f);
|
||||
}*/
|
||||
}
|
||||
|
||||
bool ppuInVBlank()
|
||||
{
|
||||
if(ppuVBlank)
|
||||
{
|
||||
if(ppuVBlankTriggered == false)
|
||||
{
|
||||
ppuVBlankTriggered = true;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
ppuVBlankTriggered = false;
|
||||
return false;
|
||||
}
|
19
ppu.h
Normal file
19
ppu.h
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2017 FIX94
|
||||
*
|
||||
* This software may be modified and distributed under the terms
|
||||
* of the MIT license. See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
#ifndef _ppu_h_
|
||||
#define _ppu_h_
|
||||
|
||||
void ppuInit();
|
||||
bool ppuCycle();
|
||||
bool ppuDrawDone();
|
||||
uint8_t ppuGet8(uint16_t addr);
|
||||
void ppuSet8(uint16_t addr, uint8_t val);
|
||||
bool ppuInVBlank();
|
||||
void ppuDumpMem();
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user