Newer
Older
import { PALETTES, PALETTES_MAP } from "./palettes";
import { base64ToBuffer, bufferToBase64 } from "./util";
import {
DebugAudio,
import {
Cartridge,
default as _wasm,
GameBoy,
PadKey,
GameBoyMode,
GameBoySpeed
import info from "../package.json";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
/**
* The frequency at which the Game Boy emulator should
* run "normally".
*/
/**
* The frequency at witch the the visual loop is going to
* run, increasing this value will have a consequence in
* the visual frames per second (FPS) of emulation.
*/
/**
* The frequency of the pause polling update operation,
* increasing this value will make resume from emulation
* paused state fasted.
*/
const DISPLAY_WIDTH = 160;
const DISPLAY_HEIGHT = 144;
const DISPLAY_SCALE = 2;
/**
* The rate at which the local storage RAM state flush
* operation is going to be performed, this value is the
* number of seconds in between flush operations (eg: 5 seconds).
*/
const STORE_RATE = 5;
/**
* The sample rate that is going to be used for FPS calculus,
* meaning that every N seconds we will calculate the number
* of frames rendered divided by the N seconds.
*/
const FPS_SAMPLE_RATE = 3;
const KEYS_NAME: Record<string, number> = {
ArrowUp: PadKey.Up,
ArrowDown: PadKey.Down,
ArrowLeft: PadKey.Left,
ArrowRight: PadKey.Right,
Start: PadKey.Start,
Select: PadKey.Select,
A: PadKey.A,
B: PadKey.B
};
const ROM_PATH = require("../../../res/roms/demo/pocket.gb");
/**
* Enumeration with the values for the complete set of available
* serial devices that can be used in the emulator.
*/
export enum SerialDevice {
Null = "null",
Logger = "logger",
Printer = "printer"
}
/**
* Top level class that controls the emulator behaviour
* and "joins" all the elements together to bring input/output
* of the associated machine.
*/
export class GameboyEmulator extends EmulatorBase implements Emulator {
/**
* The Game Boy engine (probably coming from WASM) that
* is going to be used for the emulation.
*/
private gameBoy: GameBoy | null = null;
/**
* The descriptive name of the engine that is currently
* in use to emulate the system.
*/
private _engine: string | null = null;
/**
* If the GB running mode should be automatically inferred
* from the GBC flag in the cartridge. Meaning that if the
* cartridge is a GBC compatible or GBC only the GBC emulation
* mode is going to be used, otherwise the DMG mode is used
* instead. This should provide an optimal usage experience.
*/
private autoMode = false;
private logicFrequency = LOGIC_HZ;
private visualFrequency = VISUAL_HZ;
private idleFrequency = IDLE_HZ;
private paused = false;
private nextTickTime = 0;
private fps = 0;
private frameStart: number = EmulatorBase.now();
private frameCount = 0;
private paletteIndex = 0;
private storeCycles: number = LOGIC_HZ * STORE_RATE;
private romName: string | null = null;
private romData: Uint8Array | null = null;
private cartridge: Cartridge | null = null;
private _serialDevice: SerialDevice = SerialDevice.Null;
/**
* Associative map for extra settings to be used in
* opaque local storage operations, associated setting
* name with its value as a string.
*/
private extraSettings: Record<string, string> = {};
constructor(extraSettings = {}) {
super();
this.extraSettings = extraSettings;
}
/**
* Runs the initialization and main loop execution for
* the Game Boy emulator.
* The main execution of this function should be an
* infinite loop running machine `tick` operations.
*
* @param options The set of options that are going to be
* used in he Game Boy emulator initialization.
*/
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
async main({ romUrl }: { romUrl?: string }) {
// initializes the WASM module, this is required
// so that the global symbols become available
await wasm();
// boots the emulator subsystem with the initial
// ROM retrieved from a remote data source
await this.boot({ loadRom: true, romPath: romUrl ?? undefined });
// the counter that controls the overflowing cycles
// from tick to tick operation
let pending = 0;
// runs the sequence as an infinite loop, running
// the associated CPU cycles accordingly
while (true) {
// in case the machine is paused we must delay the execution
// a little bit until the paused state is recovered
if (this.paused) {
await new Promise((resolve) => {
setTimeout(resolve, 1000 / this.idleFrequency);
});
continue;
}
// obtains the current time, this value is going
// to be used to compute the need for tick computation
let currentTime = EmulatorBase.now();
Loading
Loading full blame...