Version 1.0 released! See readme.md file.

This commit is contained in:
Dave Bernazzani 2025-04-14 07:25:48 -04:00
parent bdde6bed8c
commit 28d72d5ddb
10 changed files with 836 additions and 961 deletions

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -198,6 +198,10 @@ u16 mixer_read __attribute__((section(".dtcm"))) = 0;
u16 mixer_write __attribute__((section(".dtcm"))) = 0;
s16 mixer[WAVE_DIRECT_BUF_SIZE+1];
// The games normally run at the proper 100% speed, but user can override from 80% to 120%
u16 GAME_SPEED_PAL[] __attribute__((section(".dtcm"))) = {655, 596, 547, 728, 818 };
// -------------------------------------------------------------------------------------------
// maxmod will call this routine when the buffer is half-empty and requests that
// we fill the sound buffer with more samples. They will request 'len' samples and
@ -270,6 +274,32 @@ ITCM_CODE void processDirectAudio(void)
}
}
// -----------------------------------------------------------------------------------------------
// The user can override the core emulation speed from 80% to 120% to make games play faster/slow
// than normal. We must adjust the MaxMode sample frequency to match or else we will not have the
// proper number of samples in our sound buffer... this isn't perfect but it's reasonably good!
// -----------------------------------------------------------------------------------------------
static u8 last_game_speed = 0;
static u32 sample_rate_adjust[] = {100, 110, 120, 90, 80};
void newStreamSampleRate(void)
{
if (last_game_speed != myConfig.gameSpeed)
{
last_game_speed = myConfig.gameSpeed;
mmStreamClose();
// Adjust the sample rate to match the core emulation speed... user can override from 80% to 120%
int new_sample_rate = (sample_rate * sample_rate_adjust[myConfig.gameSpeed]) / 100;
myStream.sampling_rate = new_sample_rate; // sample_rate for the ZX to match the AY/Beeper drivers
myStream.buffer_length = buffer_size; // buffer length = (512+16)
myStream.callback = OurSoundMixer; // set callback function
myStream.format = MM_STREAM_16BIT_STEREO; // format = stereo 16-bit
myStream.timer = MM_TIMER0; // use hardware timer 0
myStream.manual = false; // use automatic filling
mmStreamOpen(&myStream);
}
}
// -------------------------------------------------------------------------------------------
// Setup the maxmod audio stream - this will be a 16-bit Stereo PCM output at 55KHz which
// sounds about right for the ZX Spectrum AY chip (we mix in beeper tones as well)
@ -957,7 +987,7 @@ u8 __attribute__((noinline)) handle_meta_key(u8 meta_key)
break;
case MENU_CHOICE_CASSETTE:
if (speccy_mode <= MODE_SNA) // Only show if we have a tape loaded
if ((speccy_mode <= MODE_SNA) || (speccy_mode == MODE_BIOS)) // Only show if we have a tape loaded
{
CassetteMenu();
}
@ -1110,6 +1140,8 @@ void SpeccySE_main(void)
timingFrames = 0;
emuFps=0;
newStreamSampleRate();
// Force the sound engine to turn on when we start emulation
bStartSoundEngine = 10;
@ -1160,7 +1192,19 @@ void SpeccySE_main(void)
{
if (--bStartIn == 0)
{
tape_play();
// ---------------------------------------------------------
// If we are running in ZX81 mode, we now copy the .P file
// into memory and the ZX81 emulation will take over...
// ---------------------------------------------------------
if (speccy_mode == MODE_ZX81)
{
u8 *ptr = MemoryMap[16393>>14] + (16393&0x3FFF);
memcpy(ptr, ROM_Memory+0x4000, last_file_size-0x4000);
}
else // Otherwise, play the ZX Spectrum tape!
{
tape_play();
}
}
}
@ -1181,9 +1225,15 @@ void SpeccySE_main(void)
{
BufferKey('J'); BufferKey(KBD_KEY_SYMBOL); BufferKey('P'); BufferKey(KBD_KEY_SYMBOL); BufferKey('P'); BufferKey(KBD_KEY_RET);
}
if (myConfig.autoLoad && (myConfig.tapeSpeed == 0)) bStartIn = 2; // Start tape in 2 frames...
if (myConfig.autoLoad && (myConfig.tapeSpeed == 0)) bStartIn = 2; // Start tape in 2 seconds...
}
}
else if (speccy_mode == MODE_ZX81)
{
BufferKey('6'); BufferKey(254); BufferKey('6'); BufferKey(254); BufferKey('6'); BufferKey(254); BufferKey(KBD_KEY_RET); BufferKey(255); BufferKey(255); BufferKey(255); BufferKey(255); BufferKey(255);
BufferKey('M'); BufferKey(255); BufferKey('5'); BufferKey(254); BufferKey('0'); BufferKey(254); BufferKey('0'); BufferKey(254); BufferKey('0'); BufferKey(254); BufferKey(KBD_KEY_RET); BufferKey(255);
bStartIn = 10; // Start P-File in 10 seconds... (it takes 6-7 seconds to process those keys above... slow processing on the ZX81 emulation)
}
}
}
}
@ -1206,7 +1256,7 @@ void SpeccySE_main(void)
//
// This is how we time frame-to frame to keep the game running at 50FPS
// ----------------------------------------------------------------------
while (TIMER2_DATA < 655*(timingFrames+1))
while (TIMER2_DATA < GAME_SPEED_PAL[myConfig.gameSpeed]*(timingFrames+1))
{
if (myGlobalConfig.showFPS == 2) break; // If Full Speed, break out...
if (tape_is_playing())

View File

@ -106,6 +106,7 @@ extern u32 DX, DY;
#define MODE_SNA 5
#define MODE_Z80 6
#define MODE_BIOS 7
#define MODE_ZX81 8
#define WAITVBL swiWaitForVBlank(); swiWaitForVBlank(); swiWaitForVBlank(); swiWaitForVBlank(); swiWaitForVBlank();

View File

@ -344,39 +344,57 @@ void speccySEFindFiles(u8 bTapeOnly)
else {
if ((strlen(szFile)>4) && (strlen(szFile)<(MAX_FILENAME_LEN-4)) && (szFile[0] != '.') && (szFile[0] != '_')) // For MAC don't allow files starting with an underscore
{
if (!bTapeOnly) // If we're loading tape files only, exclude .z80 and .sna snapshots
{
if ( (strcasecmp(strrchr(szFile, '.'), ".z80") == 0) ) {
if (bTapeOnly == 2) // Load P files only
{
if ( (strcasecmp(strrchr(szFile, '.'), ".p") == 0) ) {
strcpy(gpFic[uNbFile].szName,szFile);
gpFic[uNbFile].uType = SPECCY_FILE;
uNbFile++;
countZX++;
}
if ( (strcasecmp(strrchr(szFile, '.'), ".sna") == 0) ) {
}
else
{
if (!bTapeOnly) // If we're loading tape files only, exclude .z80 and .sna snapshots
{
if ( (strcasecmp(strrchr(szFile, '.'), ".z80") == 0) ) {
strcpy(gpFic[uNbFile].szName,szFile);
gpFic[uNbFile].uType = SPECCY_FILE;
uNbFile++;
countZX++;
}
if ( (strcasecmp(strrchr(szFile, '.'), ".sna") == 0) ) {
strcpy(gpFic[uNbFile].szName,szFile);
gpFic[uNbFile].uType = SPECCY_FILE;
uNbFile++;
countZX++;
}
if ( (strcasecmp(strrchr(szFile, '.'), ".rom") == 0) ) {
strcpy(gpFic[uNbFile].szName,szFile);
gpFic[uNbFile].uType = SPECCY_FILE;
uNbFile++;
countZX++;
}
if ( (strcasecmp(strrchr(szFile, '.'), ".z81") == 0) ) {
strcpy(gpFic[uNbFile].szName,szFile);
gpFic[uNbFile].uType = SPECCY_FILE;
uNbFile++;
countZX++;
}
}
if ( (strcasecmp(strrchr(szFile, '.'), ".tap") == 0) ) {
strcpy(gpFic[uNbFile].szName,szFile);
gpFic[uNbFile].uType = SPECCY_FILE;
uNbFile++;
countZX++;
}
if ( (strcasecmp(strrchr(szFile, '.'), ".rom") == 0) ) {
if ( (strcasecmp(strrchr(szFile, '.'), ".tzx") == 0) ) {
strcpy(gpFic[uNbFile].szName,szFile);
gpFic[uNbFile].uType = SPECCY_FILE;
uNbFile++;
countZX++;
}
}
if ( (strcasecmp(strrchr(szFile, '.'), ".tap") == 0) ) {
strcpy(gpFic[uNbFile].szName,szFile);
gpFic[uNbFile].uType = SPECCY_FILE;
uNbFile++;
countZX++;
}
if ( (strcasecmp(strrchr(szFile, '.'), ".tzx") == 0) ) {
strcpy(gpFic[uNbFile].szName,szFile);
gpFic[uNbFile].uType = SPECCY_FILE;
uNbFile++;
countZX++;
}
}
}
}
@ -781,6 +799,23 @@ void Sinclair1(void)
myConfig.keymap[11] = 31; // NDS SELECT mapped to '1'
}
// 5 (left), 6 (down), 7 (up), 8 (right)
void Cursors(void)
{
myConfig.keymap[0] = 37; // UP
myConfig.keymap[1] = 36; // DOWN
myConfig.keymap[2] = 35; // LEFT
myConfig.keymap[3] = 38; // RIGHT
myConfig.keymap[4] = 44; // Return
myConfig.keymap[5] = 43; // Space
myConfig.keymap[6] = 43; // Space
myConfig.keymap[7] = 43; // Space
myConfig.keymap[8] = 41; // NDS R Button mapped to SHIFT
myConfig.keymap[9] = 42; // NDS L Button mapped to SYMBOL
myConfig.keymap[10] = 40; // NDS START mapped to '0'
myConfig.keymap[11] = 31; // NDS SELECT mapped to '1'
}
void SetDefaultGlobalConfig(void)
{
@ -803,7 +838,7 @@ void SetDefaultGameConfig(void)
myConfig.dpad = DPAD_NORMAL; // Normal DPAD use - mapped to joystick
myConfig.autoLoad = 1; // Default is to to auto-load TAP and TZX games
myConfig.loadAs = 0; // Default load is 48K
myConfig.reserved2 = 0;
myConfig.gameSpeed = 0; // Default is 100% game speed
myConfig.reserved3 = 0;
myConfig.reserved4 = 0;
myConfig.reserved5 = 0;
@ -891,8 +926,10 @@ const struct options_t Option_Table[2][20] =
{"AUTO STOP", {"NO", "YES"}, &myConfig.autoStop, 2},
{"AUTO FIRE", {"OFF", "ON"}, &myConfig.autoFire, 2},
{"TAPE SPEED", {"NORMAL", "ACCELERATED"}, &myConfig.tapeSpeed, 2},
{"GAME SPEED", {"100%", "110%", "120%", "90%", "80%"}, &myConfig.gameSpeed, 5},
{"BUS CONTEND", {"NORMAL", "LIGHT", "HEAVY"}, &myConfig.contention, 3},
{"NDS D-PAD", {"NORMAL", "DIAGONALS", "CHUCKIE"}, &myConfig.dpad, 3},
{NULL, {"", ""}, NULL, 1},
},
// Global Options
@ -1058,14 +1095,15 @@ void DisplayKeymapName(u32 uY)
u8 keyMapType = 0;
void SwapKeymap(void)
{
keyMapType = (keyMapType+1) % 5;
keyMapType = (keyMapType+1) % 6;
switch (keyMapType)
{
case 0: MapPlayer1(); DSPrint(10,3,0,("KEMPSTON P1")); break;
case 1: Sinclair1(); DSPrint(10,3,0,("SINCLAIR P1")); break;
case 2: MapQAOP(); DSPrint(10,3,0,(" QAOP ")); break;
case 3: MapWASD(); DSPrint(10,3,0,(" WASD ")); break;
case 4: MapZXSpace(); DSPrint(10,3,0,(" ZX SPACE ")); break;
case 2: Cursors(); DSPrint(10,3,0,(" CURSORS ")); break;
case 3: MapQAOP(); DSPrint(10,3,0,(" QAOP ")); break;
case 4: MapWASD(); DSPrint(10,3,0,(" WASD ")); break;
case 5: MapZXSpace(); DSPrint(10,3,0,(" ZX SPACE ")); break;
}
WAITVBL;WAITVBL;WAITVBL;WAITVBL;
DSPrint(10,3,0,(" "));
@ -1306,6 +1344,8 @@ void ReadFileCRCAndConfig(void)
if (strstr(gpFic[ucGameChoice].szName, ".TZX") != 0) speccy_mode = MODE_TZX;
if (strstr(gpFic[ucGameChoice].szName, ".rom") != 0) speccy_mode = MODE_BIOS;
if (strstr(gpFic[ucGameChoice].szName, ".ROM") != 0) speccy_mode = MODE_BIOS;
if (strstr(gpFic[ucGameChoice].szName, ".z81") != 0) speccy_mode = MODE_ZX81;
if (strstr(gpFic[ucGameChoice].szName, ".Z81") != 0) speccy_mode = MODE_ZX81;
FindConfig(); // Try to find keymap and config for this file...
}
@ -1589,7 +1629,9 @@ void ProcessBufferedKeys(void)
{
buf_held = BufferedKeys[BufferedKeysReadIdx];
BufferedKeysReadIdx = (BufferedKeysReadIdx+1) % 32;
if (buf_held == 255) {buf_held = 0; next_dampen_time=60;} else next_dampen_time = 10;
if (buf_held == 255) {buf_held = 0; next_dampen_time=30;}
else if (buf_held == 254) {buf_held = 0; next_dampen_time=20;}
else next_dampen_time = 10;
} else buf_held = 0;
dampen = 0;
}

View File

@ -90,7 +90,7 @@ struct __attribute__((__packed__)) Config_t
u8 dpad;
u8 autoLoad;
u8 loadAs;
u8 reserved2;
u8 gameSpeed;
u8 reserved3;
u8 reserved4;
u8 reserved5;

View File

@ -263,7 +263,7 @@ void zx_bank(u8 new_bank)
if (portFD & 0x20) return; // Lock out - no more bank swaps allowed
// Map in the correct bios segment... make sure this isn't a diagnostic ROM
if (speccy_mode != MODE_BIOS)
if ((speccy_mode != MODE_BIOS) && (speccy_mode != MODE_ZX81))
{
MemoryMap[0] = SpectrumBios128 + ((new_bank & 0x10) ? 0x4000 : 0x0000);
}
@ -670,6 +670,20 @@ void speccy_reset(void)
MemoryMap[3] = RAM_Memory128 + (0 * 0x4000) + 0x0000; // Bank 0
}
}
else if (speccy_mode == MODE_ZX81)
{
// Move the BIOS/Diagnostic ROM into memory...
memcpy(RAM_Memory, ROM_Memory, 0x4000); // Load diagnostics ROM into place - the rest of the file is the P-File
// And force 128K mode needed for ZX81 emulation
zx_128k_mode = 1;
myConfig.loadAs = 1;
// Now set the memory map to point to the right banks...
MemoryMap[1] = RAM_Memory128 + (5 * 0x4000) + 0x0000; // Bank 5
MemoryMap[2] = RAM_Memory128 + (2 * 0x4000) + 0x0000; // Bank 2
MemoryMap[3] = RAM_Memory128 + (0 * 0x4000) + 0x0000; // Bank 0
}
else if (speccy_mode < MODE_SNA) // TAP or TZX file - 48K or 128K
{
// BIOS will be loaded further below...
@ -765,7 +779,7 @@ void speccy_reset(void)
}
}
if (speccy_mode != MODE_BIOS)
if ((speccy_mode != MODE_BIOS) && (speccy_mode != MODE_ZX81))
{
// Load the correct BIOS into place... either 48K Spectrum or 128K
if (zx_128k_mode) memcpy(RAM_Memory, SpectrumBios128, 0x4000); // Load ZX 128K BIOS into place

View File

@ -13,6 +13,7 @@ Features :
* Loads .TZX files up to 640K total length (can swap tapes mid-game)
* Loads .SNA snapshots (48K only)
* Loads .Z80 snapshots (V1, V2 and V3 formats, 48K or 128K)
* Loads .Z81 files for ZX81 emulation (see below)
* Loads .ROM files up to 16K in place of standard BIOS (diagnostics, etc)
* Supports .POK files (same name as base game and stored in POK subdir)
* Kempston and Sinclair joystick support
@ -150,7 +151,16 @@ under that as you wish. The emulator can support a file listing of up
to 2000 files with names no longer than 160 characters (so please keep
your filenames on the shorter side... although the emulator can scroll
the filename, there only about 30 characters can be shown on the screen
at a time).
at a time).
One option that is of particular note is the ability to run the game
at a speed other than normal 100%. Some games were designed to run
a bit too fast to be enjoyable. Other games were a bit too slow. Using
this optional adjustment, you can run a game anywhere from 80% of full
speed (slower than normal) to 120% (faster than normal). The sound driver
should auto-adjust and while the music / sounds will sound faster/slower,
it should match the core emulation speed perfectly. This can be adjusted
on a per-game basis.
As for the per-game options, you can set things like the auto fire
for the joystick and the aforementioned CHUCKIE mode of joystick d-pad
@ -192,7 +202,6 @@ an alternate "[a]" version will load. Usually one tape image dump is
as good as any other - but keep searching and put yourself together a
library of known good working images for Speccy-SE.
ROM Support :
-----------------------
The emulator allows you to load a .ROM file directly into the same memory
@ -200,6 +209,28 @@ location as the BIOS (+0000 to +4000). Only up to 16K can be loaded in this
way. This is mainly used to load diagnostic test programs such as the
amazing RETROLEUM DIAGROM.
ZX81 Support :
-----------------------
The emulator supports the Paul Farrow ZX81 emulator for the ZX 128k machines.
http://www.fruitcake.plus.com/Sinclair/Interface2/Cartridges/Interface2_RC_New_ZX81.htm
To make this work, download the 16K Interface 2 ROM for the emulator - either Edition 2
or Edition 3 (do not use Edition 1 or the 'bugfix version'). Take this ROM file
and concatenate it with a ZX81 .p file for the game you want to play.
So let's say you want to play the original ZX81 Mazogs. Obtain the mazogs.p file and the
aforementioned ZX81 emulator ROM and do the following:
Linux: cat S128_ZX81_ED2_ROM.bin mazogs.p > mazogs.z81
Windows: copy /b S128_ZX81_ED2_ROM.bin mazogs.p mazogs.z81
This will produce a .z81 file that is roughly 25K in size... it contains the emulator + the game .p file
in one binary image. This .z81 file is now loadable directly into Speccy-SE - when you pick the game, it
will automatically insert the keystrokes needed to get the emulator running. This takes about 10 seconds...
don't touch any virtual keys until the ZX81 game is fully loaded.
POK Support :
-----------------------
The emulator supports .pok files. The .pok file should have the same base