mirror of
https://github.com/robmcmullen/apple2.git
synced 2024-06-08 19:29:31 +00:00
Added VBL, fixed sound-on-write, added .bin disk support.
This commit is contained in:
parent
c0001155bc
commit
aec4f863c3
25
apple2.cfg
25
apple2.cfg
|
@ -21,15 +21,23 @@ autoSaveState = 1
|
||||||
#floppyImage1 = ./disks/temp.nib
|
#floppyImage1 = ./disks/temp.nib
|
||||||
#floppyImage1 = ./disks/temp.dsk
|
#floppyImage1 = ./disks/temp.dsk
|
||||||
# Yes
|
# Yes
|
||||||
|
#floppyImage1 = ./disks/Gumball (Mr. Krac-Man and The Disk Jockey crack).dsk
|
||||||
|
# Yes
|
||||||
|
#floppyImage1 = ./disks/prince_of_persia_boot.dsk
|
||||||
|
#floppyImage2 = ./disks/prince_of_persia_a.dsk
|
||||||
|
# Yes
|
||||||
|
#floppyImage1 = ./disks/Oregon Trail (Disk 1 of 2).dsk
|
||||||
|
#floppyImage2 = ./disks/Oregon Trail (Disk 2 of 2).dsk
|
||||||
|
# Yes
|
||||||
#floppyImage1 = ./disks/bt1_boot.dsk
|
#floppyImage1 = ./disks/bt1_boot.dsk
|
||||||
# Yes
|
# Yes
|
||||||
#floppyImage1 = ./disks/bt2_boot.dsk
|
#floppyImage1 = ./disks/bt2_boot.dsk
|
||||||
# Yes (but segfaults in the timer routine in the title screen--NB: Not anymore...)
|
# Yes
|
||||||
floppyImage1 = ./disks/bt3_boot_fixed.dsk
|
#floppyImage1 = ./disks/bt3_boot_fixed.dsk
|
||||||
floppyImage2 = ./disks/bt3_character_fixed.dsk
|
#floppyImage2 = ./disks/bt3_character_fixed.dsk
|
||||||
# Yes
|
# Yes
|
||||||
#floppyImage1 = ./disks/Sabotage.dsk
|
#floppyImage1 = ./disks/Sabotage.dsk
|
||||||
# ??? (//c or //e w/128K required) (dumps to monitor)
|
# Yes
|
||||||
#floppyImage1 = ./disks/airheart.dsk
|
#floppyImage1 = ./disks/airheart.dsk
|
||||||
# Yes
|
# Yes
|
||||||
#floppyImage1 = ./disks/drol.dsk
|
#floppyImage1 = ./disks/drol.dsk
|
||||||
|
@ -37,10 +45,11 @@ floppyImage2 = ./disks/bt3_character_fixed.dsk
|
||||||
#floppyImage1 = ./disks/karateka.dsk
|
#floppyImage1 = ./disks/karateka.dsk
|
||||||
# Yes
|
# Yes
|
||||||
#floppyImage1 = ./disks/wolfenstein_dos32.nib
|
#floppyImage1 = ./disks/wolfenstein_dos32.nib
|
||||||
# Yes, keys???
|
# Yes, keys??? (joystick only)
|
||||||
#floppyImage1 = ./disks/MidnightMagic_etc.dsk
|
floppyImage1 = ./disks/MidnightMagic_etc.dsk
|
||||||
# ??? Loads, then dumps to monitor (This is IIe or IIc only)
|
# Yes
|
||||||
#floppyImage1 = ./disks/battle_chess_1.dsk
|
#floppyImage1 = ./disks/battle_chess_1.dsk
|
||||||
|
#floppyImage2 = ./disks/battle_chess_2.dsk
|
||||||
# Yes
|
# Yes
|
||||||
#floppyImage1 = ./disks/MoebiusI-1.dsk
|
#floppyImage1 = ./disks/MoebiusI-1.dsk
|
||||||
#floppyImage2 = ./disks/MoebiusI-2.dsk
|
#floppyImage2 = ./disks/MoebiusI-2.dsk
|
||||||
|
@ -76,6 +85,8 @@ floppyImage2 = ./disks/bt3_character_fixed.dsk
|
||||||
#floppyImage1 = ./disks/lode_runner.dsk
|
#floppyImage1 = ./disks/lode_runner.dsk
|
||||||
# Yes
|
# Yes
|
||||||
#floppyImage1 = ./disks/championship_lode_runner.dsk
|
#floppyImage1 = ./disks/championship_lode_runner.dsk
|
||||||
|
# Yes
|
||||||
|
#floppyImage1 = ./disks/championship_lode_runner.bin
|
||||||
|
|
||||||
|
|
||||||
# OpenGL filtering type: 1 - blurry, 0 - sharp
|
# OpenGL filtering type: 1 - blurry, 0 - sharp
|
||||||
|
|
|
@ -198,6 +198,9 @@ WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
|
||||||
|
|
||||||
Execute65C02(&mainCPU, cycles);
|
Execute65C02(&mainCPU, cycles);
|
||||||
WriteSampleToBuffer();
|
WriteSampleToBuffer();
|
||||||
|
|
||||||
|
// Dunno if this is correct (seems to be close enough)...
|
||||||
|
vbl = (i < 670 ? true : false);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -421,24 +424,7 @@ WriteLog("80COL (read)\n");
|
||||||
}
|
}
|
||||||
else if ((addr & 0xFFF0) == 0xC030) // Read $C030-$C03F
|
else if ((addr & 0xFFF0) == 0xC030) // Read $C030-$C03F
|
||||||
{
|
{
|
||||||
/*
|
ToggleSpeaker();
|
||||||
This is problematic, mainly because the v65C02 removes actual cycles run after each call.
|
|
||||||
Therefore, we don't really have a reliable method of sending a timestamp to the sound routine.
|
|
||||||
How to fix?
|
|
||||||
|
|
||||||
What we need to send is a delta T value but related to the IRQ buffer routine. E.g., if the buffer
|
|
||||||
hasn't had any changes in it then we just fill it with the last sample value and are done. Then
|
|
||||||
we need to adjust our delta T accordingly. What we could do is keep a running total of time since the
|
|
||||||
last change and adjust it accordingly, i.e., whenever a sound IRQ happens.
|
|
||||||
How to keep track?
|
|
||||||
|
|
||||||
Have deltaT somewhere. Then, whenever there's a toggle, backfill buffer with last spkr state and reset
|
|
||||||
deltaT to zero. In the sound IRQ, if deltaT > buffer size, then subtract buffer size from deltaT. (?)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
||||||
ToggleSpeaker(GetCurrentV65C02Clock());
|
|
||||||
//should it return something else here???
|
//should it return something else here???
|
||||||
return 0x00;
|
return 0x00;
|
||||||
}
|
}
|
||||||
|
@ -1040,6 +1026,13 @@ WriteLog("ALTCHARSET on (write)\n");
|
||||||
//But leaving this out seems to fuck up the key handling of some games...
|
//But leaving this out seems to fuck up the key handling of some games...
|
||||||
keyDown = false;
|
keyDown = false;
|
||||||
}
|
}
|
||||||
|
else if ((addr & 0xFFF0) == 0xC030) // Read $C030-$C03F
|
||||||
|
{
|
||||||
|
//Likewise, the speaker is supposed to do nothing if you write to it, and
|
||||||
|
//for the same reason. But without this, you get no sound in David's
|
||||||
|
//Midnight Magic...
|
||||||
|
ToggleSpeaker();
|
||||||
|
}
|
||||||
else if (addr == 0xC050)
|
else if (addr == 0xC050)
|
||||||
{
|
{
|
||||||
#ifdef SOFT_SWITCH_DEBUGGING
|
#ifdef SOFT_SWITCH_DEBUGGING
|
||||||
|
|
|
@ -39,6 +39,7 @@ uint8_t FloppyDrive::poSector[16] = {
|
||||||
0x0, 0x8, 0x1, 0x9, 0x2, 0xA, 0x3, 0xB, 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF };
|
0x0, 0x8, 0x1, 0x9, 0x2, 0xA, 0x3, 0xB, 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF };
|
||||||
char FloppyDrive::nameBuf[MAX_PATH];
|
char FloppyDrive::nameBuf[MAX_PATH];
|
||||||
|
|
||||||
|
|
||||||
// FloppyDrive class implementation...
|
// FloppyDrive class implementation...
|
||||||
|
|
||||||
FloppyDrive::FloppyDrive(): motorOn(0), activeDrive(0), ioMode(IO_MODE_READ), phase(0), track(0)
|
FloppyDrive::FloppyDrive(): motorOn(0), activeDrive(0), ioMode(IO_MODE_READ), phase(0), track(0)
|
||||||
|
@ -51,6 +52,7 @@ FloppyDrive::FloppyDrive(): motorOn(0), activeDrive(0), ioMode(IO_MODE_READ), ph
|
||||||
imageName[0][0] = imageName[1][0] = 0; // Zero out filenames
|
imageName[0][0] = imageName[1][0] = 0; // Zero out filenames
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
FloppyDrive::~FloppyDrive()
|
FloppyDrive::~FloppyDrive()
|
||||||
{
|
{
|
||||||
if (disk[0])
|
if (disk[0])
|
||||||
|
@ -60,6 +62,7 @@ FloppyDrive::~FloppyDrive()
|
||||||
delete[] disk[1];
|
delete[] disk[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool FloppyDrive::LoadImage(const char * filename, uint8_t driveNum/*= 0*/)
|
bool FloppyDrive::LoadImage(const char * filename, uint8_t driveNum/*= 0*/)
|
||||||
{
|
{
|
||||||
WriteLog("FLOPPY: Attempting to load image '%s' in drive #%u.\n", filename, driveNum);
|
WriteLog("FLOPPY: Attempting to load image '%s' in drive #%u.\n", filename, driveNum);
|
||||||
|
@ -112,6 +115,7 @@ bool FloppyDrive::LoadImage(const char * filename, uint8_t driveNum/*= 0*/)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool FloppyDrive::SaveImage(uint8_t driveNum/*= 0*/)
|
bool FloppyDrive::SaveImage(uint8_t driveNum/*= 0*/)
|
||||||
{
|
{
|
||||||
if (driveNum > 1)
|
if (driveNum > 1)
|
||||||
|
@ -153,6 +157,7 @@ bool FloppyDrive::SaveImage(uint8_t driveNum/*= 0*/)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool FloppyDrive::SaveImageAs(const char * filename, uint8_t driveNum/*= 0*/)
|
bool FloppyDrive::SaveImageAs(const char * filename, uint8_t driveNum/*= 0*/)
|
||||||
{
|
{
|
||||||
//WARNING: Buffer overflow possibility
|
//WARNING: Buffer overflow possibility
|
||||||
|
@ -161,6 +166,7 @@ bool FloppyDrive::SaveImageAs(const char * filename, uint8_t driveNum/*= 0*/)
|
||||||
return SaveImage(driveNum);
|
return SaveImage(driveNum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void FloppyDrive::CreateBlankImage(uint8_t driveNum/*= 0*/)
|
void FloppyDrive::CreateBlankImage(uint8_t driveNum/*= 0*/)
|
||||||
{
|
{
|
||||||
if (disk[driveNum] != NULL)
|
if (disk[driveNum] != NULL)
|
||||||
|
@ -176,6 +182,7 @@ void FloppyDrive::CreateBlankImage(uint8_t driveNum/*= 0*/)
|
||||||
SpawnMessage("New blank image inserted in drive %u...", driveNum);
|
SpawnMessage("New blank image inserted in drive %u...", driveNum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void FloppyDrive::SwapImages(void)
|
void FloppyDrive::SwapImages(void)
|
||||||
{
|
{
|
||||||
uint8_t nybblizedImageTmp[232960];
|
uint8_t nybblizedImageTmp[232960];
|
||||||
|
@ -211,6 +218,7 @@ void FloppyDrive::SwapImages(void)
|
||||||
SpawnMessage("Drive 0: %s...", imageName[0]);
|
SpawnMessage("Drive 0: %s...", imageName[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void FloppyDrive::DetectImageType(const char * filename, uint8_t driveNum)
|
void FloppyDrive::DetectImageType(const char * filename, uint8_t driveNum)
|
||||||
{
|
{
|
||||||
diskType[driveNum] = DT_UNKNOWN;
|
diskType[driveNum] = DT_UNKNOWN;
|
||||||
|
@ -254,10 +262,11 @@ WRT to the disk image itself.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actually, it just might matter WRT to nybblyzing/denybblyzing
|
// Actually, it just might matter WRT to nybblyzing/denybblyzing
|
||||||
// Here, we check for BT3
|
NybblizeImage(driveNum);
|
||||||
//Nope, no change...
|
}
|
||||||
//diskType[driveNum] = DT_PRODOS;
|
else if (diskSize[driveNum] == 143488)
|
||||||
|
{
|
||||||
|
diskType[driveNum] = DT_DOS33_HDR;
|
||||||
NybblizeImage(driveNum);
|
NybblizeImage(driveNum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,9 +274,11 @@ WRT to the disk image itself.
|
||||||
|
|
||||||
WriteLog("FLOPPY: Detected image type %s...\n", (diskType[driveNum] == DT_NYBBLE ?
|
WriteLog("FLOPPY: Detected image type %s...\n", (diskType[driveNum] == DT_NYBBLE ?
|
||||||
"Nybble image" : (diskType[driveNum] == DT_DOS33 ?
|
"Nybble image" : (diskType[driveNum] == DT_DOS33 ?
|
||||||
"DOS 3.3 image" : (diskType[driveNum] == DT_PRODOS ? "ProDOS image" : "unknown"))));
|
"DOS 3.3 image" : (diskType[driveNum] == DT_DOS33_HDR ?
|
||||||
|
"DOS 3.3 image (headered)" : (diskType[driveNum] == DT_PRODOS ? "ProDOS image" : "unknown")))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void FloppyDrive::NybblizeImage(uint8_t driveNum)
|
void FloppyDrive::NybblizeImage(uint8_t driveNum)
|
||||||
{
|
{
|
||||||
// Format of a sector is header (23) + nybbles (343) + footer (30) = 396
|
// Format of a sector is header (23) + nybbles (343) + footer (30) = 396
|
||||||
|
@ -319,6 +330,8 @@ void FloppyDrive::NybblizeImage(uint8_t driveNum)
|
||||||
|
|
||||||
if (diskType[driveNum] == DT_DOS33)
|
if (diskType[driveNum] == DT_DOS33)
|
||||||
bytes += (doSector[sector] * 256) + (trk * 256 * 16);
|
bytes += (doSector[sector] * 256) + (trk * 256 * 16);
|
||||||
|
else if (diskType[driveNum] == DT_DOS33_HDR)
|
||||||
|
bytes += (doSector[sector] * 256) + (trk * 256 * 16) + 128;
|
||||||
else if (diskType[driveNum] == DT_PRODOS)
|
else if (diskType[driveNum] == DT_PRODOS)
|
||||||
bytes += (poSector[sector] * 256) + (trk * 256 * 16);
|
bytes += (poSector[sector] * 256) + (trk * 256 * 16);
|
||||||
else
|
else
|
||||||
|
@ -371,6 +384,7 @@ WriteLog("FL: i = %u, img[i] = %02X, diskbyte = %02X\n", i, img[i], diskbyte[img
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void FloppyDrive::DenybblizeImage(uint8_t driveNum)
|
void FloppyDrive::DenybblizeImage(uint8_t driveNum)
|
||||||
{
|
{
|
||||||
uint8_t decodeNybble[0x80] = {
|
uint8_t decodeNybble[0x80] = {
|
||||||
|
@ -462,6 +476,8 @@ void FloppyDrive::DenybblizeImage(uint8_t driveNum)
|
||||||
|
|
||||||
if (diskType[driveNum] == DT_DOS33)
|
if (diskType[driveNum] == DT_DOS33)
|
||||||
bytes += (doSector[sector] * 256) + (trk * 256 * 16);
|
bytes += (doSector[sector] * 256) + (trk * 256 * 16);
|
||||||
|
else if (diskType[driveNum] == DT_DOS33_HDR)
|
||||||
|
bytes += (doSector[sector] * 256) + (trk * 256 * 16) + 128;
|
||||||
else if (diskType[driveNum] == DT_PRODOS)
|
else if (diskType[driveNum] == DT_PRODOS)
|
||||||
bytes += (poSector[sector] * 256) + (trk * 256 * 16);
|
bytes += (poSector[sector] * 256) + (trk * 256 * 16);
|
||||||
else
|
else
|
||||||
|
@ -472,6 +488,7 @@ void FloppyDrive::DenybblizeImage(uint8_t driveNum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const char * FloppyDrive::GetImageName(uint8_t driveNum/*= 0*/)
|
const char * FloppyDrive::GetImageName(uint8_t driveNum/*= 0*/)
|
||||||
{
|
{
|
||||||
// Set up a zero-length string for return value
|
// Set up a zero-length string for return value
|
||||||
|
@ -508,6 +525,7 @@ const char * FloppyDrive::GetImageName(uint8_t driveNum/*= 0*/)
|
||||||
return nameBuf;
|
return nameBuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void FloppyDrive::EjectImage(uint8_t driveNum/*= 0*/)
|
void FloppyDrive::EjectImage(uint8_t driveNum/*= 0*/)
|
||||||
{
|
{
|
||||||
// Probably want to save a dirty image... ;-)
|
// Probably want to save a dirty image... ;-)
|
||||||
|
@ -527,6 +545,7 @@ void FloppyDrive::EjectImage(uint8_t driveNum/*= 0*/)
|
||||||
memset(nybblizedImage[driveNum], 0xFF, 232960); // Doesn't matter if 00s or FFs...
|
memset(nybblizedImage[driveNum], 0xFF, 232960); // Doesn't matter if 00s or FFs...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool FloppyDrive::DriveIsEmpty(uint8_t driveNum/*= 0*/)
|
bool FloppyDrive::DriveIsEmpty(uint8_t driveNum/*= 0*/)
|
||||||
{
|
{
|
||||||
if (driveNum > 1)
|
if (driveNum > 1)
|
||||||
|
@ -539,6 +558,7 @@ bool FloppyDrive::DriveIsEmpty(uint8_t driveNum/*= 0*/)
|
||||||
return (imageName[driveNum][0] == 0 ? true : false);
|
return (imageName[driveNum][0] == 0 ? true : false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool FloppyDrive::DiskIsWriteProtected(uint8_t driveNum/*= 0*/)
|
bool FloppyDrive::DiskIsWriteProtected(uint8_t driveNum/*= 0*/)
|
||||||
{
|
{
|
||||||
if (driveNum > 1)
|
if (driveNum > 1)
|
||||||
|
@ -550,6 +570,7 @@ bool FloppyDrive::DiskIsWriteProtected(uint8_t driveNum/*= 0*/)
|
||||||
return writeProtected[driveNum];
|
return writeProtected[driveNum];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void FloppyDrive::SetWriteProtect(bool state, uint8_t driveNum/*= 0*/)
|
void FloppyDrive::SetWriteProtect(bool state, uint8_t driveNum/*= 0*/)
|
||||||
{
|
{
|
||||||
if (driveNum > 1)
|
if (driveNum > 1)
|
||||||
|
@ -606,18 +627,21 @@ SpawnMessage("Stepping to track %u...", track);
|
||||||
// return something if read mode...
|
// return something if read mode...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void FloppyDrive::ControlMotor(uint8_t addr)
|
void FloppyDrive::ControlMotor(uint8_t addr)
|
||||||
{
|
{
|
||||||
// $C0E8 - 9
|
// $C0E8 - 9
|
||||||
motorOn = addr;
|
motorOn = addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void FloppyDrive::DriveEnable(uint8_t addr)
|
void FloppyDrive::DriveEnable(uint8_t addr)
|
||||||
{
|
{
|
||||||
// $C0EA - B
|
// $C0EA - B
|
||||||
activeDrive = addr;
|
activeDrive = addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
uint8_t FloppyDrive::ReadWrite(void)
|
uint8_t FloppyDrive::ReadWrite(void)
|
||||||
{
|
{
|
||||||
SpawnMessage("%u:%sing %s track %u, sector %u...", activeDrive,
|
SpawnMessage("%u:%sing %s track %u, sector %u...", activeDrive,
|
||||||
|
@ -651,24 +675,28 @@ Which we now do. :-)
|
||||||
return diskByte;
|
return diskByte;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
uint8_t FloppyDrive::GetLatchValue(void)
|
uint8_t FloppyDrive::GetLatchValue(void)
|
||||||
{
|
{
|
||||||
// $C0ED
|
// $C0ED
|
||||||
return latchValue;
|
return latchValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void FloppyDrive::SetLatchValue(uint8_t value)
|
void FloppyDrive::SetLatchValue(uint8_t value)
|
||||||
{
|
{
|
||||||
// $C0ED
|
// $C0ED
|
||||||
latchValue = value;
|
latchValue = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void FloppyDrive::SetReadMode(void)
|
void FloppyDrive::SetReadMode(void)
|
||||||
{
|
{
|
||||||
// $C0EE
|
// $C0EE
|
||||||
ioMode = IO_MODE_READ;
|
ioMode = IO_MODE_READ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void FloppyDrive::SetWriteMode(void)
|
void FloppyDrive::SetWriteMode(void)
|
||||||
{
|
{
|
||||||
// $C0EF
|
// $C0EF
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
#endif
|
#endif
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
enum { DT_UNKNOWN, DT_DOS33, DT_PRODOS, DT_NYBBLE };
|
enum { DT_UNKNOWN, DT_DOS33, DT_DOS33_HDR, DT_PRODOS, DT_NYBBLE };
|
||||||
|
|
||||||
class FloppyDrive
|
class FloppyDrive
|
||||||
{
|
{
|
||||||
|
|
187
src/sound.cpp
187
src/sound.cpp
|
@ -17,7 +17,7 @@
|
||||||
// STILL TO DO:
|
// STILL TO DO:
|
||||||
//
|
//
|
||||||
// - Figure out why it's losing samples (Bard's Tale) [DONE]
|
// - Figure out why it's losing samples (Bard's Tale) [DONE]
|
||||||
// - Figure out why it's playing too fast
|
// - Figure out why it's playing too fast [DONE]
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "sound.h"
|
#include "sound.h"
|
||||||
|
@ -31,27 +31,10 @@
|
||||||
//#define DEBUG
|
//#define DEBUG
|
||||||
//#define WRITE_OUT_WAVE
|
//#define WRITE_OUT_WAVE
|
||||||
|
|
||||||
// This is odd--seems to be working properly now! Maybe a bug in the SDL sound code?
|
|
||||||
// Actually, it still doesn't sound right... Sounds too slow now. :-/
|
|
||||||
// But then again, it's difficult to tell. Sometimes it slows waaaaaay down, but generally
|
|
||||||
// seems to be OK other than that
|
|
||||||
// Also, it could be that the discrepancy in pitch is due to the V65C02 and it's lack of
|
|
||||||
// cycle accuracy...
|
|
||||||
|
|
||||||
//#define SAMPLE_RATE (44100.0)
|
//#define SAMPLE_RATE (44100.0)
|
||||||
#define SAMPLE_RATE (48000.0)
|
#define SAMPLE_RATE (48000.0)
|
||||||
#define SAMPLES_PER_FRAME (SAMPLE_RATE / 60.0)
|
#define SAMPLES_PER_FRAME (SAMPLE_RATE / 60.0)
|
||||||
// This works for AppleWin but not here... ??? WHY ???
|
|
||||||
// ~ 21
|
|
||||||
#define CYCLES_PER_SAMPLE (1024000.0 / SAMPLE_RATE)
|
#define CYCLES_PER_SAMPLE (1024000.0 / SAMPLE_RATE)
|
||||||
// ~ 17 (lower pitched than above...!)
|
|
||||||
// Makes sense, as this is the divisor for # of cycles passed
|
|
||||||
//#define CYCLES_PER_SAMPLE (800000.0 / SAMPLE_RATE)
|
|
||||||
// This seems about right, compared to AppleWin--but AW runs @ 1.024 MHz
|
|
||||||
// 23 (1.024) vs. 20 (0.900)
|
|
||||||
//#define CYCLES_PER_SAMPLE (900000.0 / SAMPLE_RATE)
|
|
||||||
//nope, too high #define CYCLES_PER_SAMPLE (960000.0 / SAMPLE_RATE)
|
|
||||||
//#define CYCLES_PER_SAMPLE 21
|
|
||||||
//#define SOUND_BUFFER_SIZE (8192)
|
//#define SOUND_BUFFER_SIZE (8192)
|
||||||
#define SOUND_BUFFER_SIZE (32768)
|
#define SOUND_BUFFER_SIZE (32768)
|
||||||
|
|
||||||
|
@ -131,9 +114,7 @@ void SoundDone(void)
|
||||||
{
|
{
|
||||||
if (soundInitialized)
|
if (soundInitialized)
|
||||||
{
|
{
|
||||||
// SDL_PauseAudio(true);
|
|
||||||
SDL_PauseAudioDevice(device, 1);
|
SDL_PauseAudioDevice(device, 1);
|
||||||
// SDL_CloseAudio();
|
|
||||||
SDL_CloseAudioDevice(device);
|
SDL_CloseAudioDevice(device);
|
||||||
SDL_DestroyCond(conditional);
|
SDL_DestroyCond(conditional);
|
||||||
SDL_DestroyMutex(mutex);
|
SDL_DestroyMutex(mutex);
|
||||||
|
@ -184,22 +165,22 @@ static void SDLSoundCallback(void * /*userdata*/, Uint8 * buffer8, int length8)
|
||||||
uint32_t length = (uint32_t)length8 / 2;
|
uint32_t length = (uint32_t)length8 / 2;
|
||||||
|
|
||||||
//WriteLog("SDLSoundCallback(): filling buffer...\n");
|
//WriteLog("SDLSoundCallback(): filling buffer...\n");
|
||||||
if (soundBufferPos < length) // The sound buffer is starved...
|
if (soundBufferPos < length)
|
||||||
{
|
{
|
||||||
|
// The sound buffer is starved...
|
||||||
for(uint32_t i=0; i<soundBufferPos; i++)
|
for(uint32_t i=0; i<soundBufferPos; i++)
|
||||||
buffer[i] = soundBuffer[i];
|
buffer[i] = soundBuffer[i];
|
||||||
|
|
||||||
// Fill buffer with last value
|
// Fill buffer with last value
|
||||||
// memset(buffer + soundBufferPos, (uint8_t)sample, length - soundBufferPos);
|
|
||||||
for(uint32_t i=soundBufferPos; i<length; i++)
|
for(uint32_t i=soundBufferPos; i<length; i++)
|
||||||
buffer[i] = sample;
|
buffer[i] = sample;
|
||||||
|
|
||||||
soundBufferPos = 0; // Reset soundBufferPos to start of buffer...
|
// Reset soundBufferPos to start of buffer...
|
||||||
|
soundBufferPos = 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Fill sound buffer with frame buffered sound
|
// Fill sound buffer with frame buffered sound
|
||||||
// memcpy(buffer, soundBuffer, length);
|
|
||||||
for(uint32_t i=0; i<length; i++)
|
for(uint32_t i=0; i<length; i++)
|
||||||
buffer[i] = soundBuffer[i];
|
buffer[i] = soundBuffer[i];
|
||||||
|
|
||||||
|
@ -225,7 +206,7 @@ void WriteSampleToBuffer(void)
|
||||||
//WriteLog("WriteSampleToBuffer(): SDL_mutexP(mutex2)\n");
|
//WriteLog("WriteSampleToBuffer(): SDL_mutexP(mutex2)\n");
|
||||||
SDL_mutexP(mutex2);
|
SDL_mutexP(mutex2);
|
||||||
|
|
||||||
// This should almost never happen, but...
|
// This should almost never happen, but, if it does...
|
||||||
while (soundBufferPos >= (SOUND_BUFFER_SIZE - 1))
|
while (soundBufferPos >= (SOUND_BUFFER_SIZE - 1))
|
||||||
{
|
{
|
||||||
//WriteLog("WriteSampleToBuffer(): Waiting for sound thread. soundBufferPos=%i, SOUNDBUFFERSIZE-1=%i\n", soundBufferPos, SOUND_BUFFER_SIZE-1);
|
//WriteLog("WriteSampleToBuffer(): Waiting for sound thread. soundBufferPos=%i, SOUNDBUFFERSIZE-1=%i\n", soundBufferPos, SOUND_BUFFER_SIZE-1);
|
||||||
|
@ -242,107 +223,16 @@ void WriteSampleToBuffer(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Need some interface functions here to take care of flipping the
|
void ToggleSpeaker(void)
|
||||||
// waveform at the correct time in the sound stream...
|
|
||||||
|
|
||||||
/*
|
|
||||||
Maybe set up a buffer 1 frame long (44100 / 60 = 735 bytes per frame)
|
|
||||||
|
|
||||||
Hmm. That's smaller than the sound buffer 2048 bytes... (About 2.75 frames needed to fill)
|
|
||||||
|
|
||||||
So... I guess what we could do is this:
|
|
||||||
|
|
||||||
-- Execute V65C02 for one frame. The read/writes at I/O address $C030 fill up the buffer
|
|
||||||
to the current time position.
|
|
||||||
-- The sound callback function copies the pertinent area out of the buffer, resets
|
|
||||||
the time position back (or copies data down from what it took out)
|
|
||||||
*/
|
|
||||||
|
|
||||||
void HandleBuffer(uint64_t elapsedCycles)
|
|
||||||
{
|
|
||||||
// Step 1: Calculate delta time
|
|
||||||
uint64_t deltaCycles = elapsedCycles - lastToggleCycles;
|
|
||||||
|
|
||||||
// Step 2: Calculate new buffer position
|
|
||||||
uint32_t currentPos = (uint32_t)((double)deltaCycles / CYCLES_PER_SAMPLE);
|
|
||||||
|
|
||||||
// Step 3: Make sure there's room for it
|
|
||||||
// We need to lock since we touch both soundBuffer and soundBufferPos
|
|
||||||
SDL_mutexP(mutex2);
|
|
||||||
|
|
||||||
while ((soundBufferPos + currentPos) > (SOUND_BUFFER_SIZE - 1))
|
|
||||||
{
|
|
||||||
SDL_mutexV(mutex2); // Release it so sound thread can get it,
|
|
||||||
SDL_mutexP(mutex); // Must lock the mutex for the cond to work properly...
|
|
||||||
SDL_CondWait(conditional, mutex); // Sleep/wait for the sound thread
|
|
||||||
SDL_mutexV(mutex); // Must unlock the mutex for the cond to work properly...
|
|
||||||
SDL_mutexP(mutex2); // Re-lock it until we're done with it...
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 4: Backfill and adjust lastToggleCycles
|
|
||||||
// currentPos is position from "zero" or soundBufferPos...
|
|
||||||
currentPos += soundBufferPos;
|
|
||||||
|
|
||||||
#ifdef WRITE_OUT_WAVE
|
|
||||||
uint32_t sbpSave = soundBufferPos;
|
|
||||||
#endif
|
|
||||||
// Backfill with current toggle state
|
|
||||||
while (soundBufferPos < currentPos)
|
|
||||||
soundBuffer[soundBufferPos++] = sample;
|
|
||||||
|
|
||||||
#ifdef WRITE_OUT_WAVE
|
|
||||||
fwrite(&soundBuffer[sbpSave], sizeof(int16_t), currentPos - sbpSave, fp);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SDL_mutexV(mutex2);
|
|
||||||
lastToggleCycles = elapsedCycles;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ToggleSpeaker(uint64_t elapsedCycles)
|
|
||||||
{
|
{
|
||||||
if (!soundInitialized)
|
if (!soundInitialized)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// HandleBuffer(elapsedCycles);
|
|
||||||
speakerState = !speakerState;
|
speakerState = !speakerState;
|
||||||
sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]);
|
sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void AdjustLastToggleCycles(uint64_t elapsedCycles)
|
|
||||||
{
|
|
||||||
if (!soundInitialized)
|
|
||||||
return;
|
|
||||||
/*
|
|
||||||
BOOKKEEPING
|
|
||||||
|
|
||||||
We need to know the following:
|
|
||||||
|
|
||||||
o Where in the sound buffer the base or "zero" time is
|
|
||||||
o At what CPU timestamp the speaker was last toggled
|
|
||||||
NOTE: we keep things "right" by advancing this number every frame, even
|
|
||||||
if nothing happened! That way, we can keep track without having
|
|
||||||
to detect whether or not several frames have gone by without any
|
|
||||||
activity.
|
|
||||||
|
|
||||||
How to do it:
|
|
||||||
|
|
||||||
Every time the speaker is toggled, we move the base or "zero" time to the
|
|
||||||
current spot in the buffer. We also backfill the buffer up to that point with
|
|
||||||
the old toggle value. The next time the speaker is toggled, we measure the
|
|
||||||
difference in time between the last time it was toggled (the "zero") and now,
|
|
||||||
and repeat the cycle.
|
|
||||||
|
|
||||||
We handle dead spots by backfilling the buffer with the current toggle value
|
|
||||||
every frame--this way we don't have to worry about keeping current time and
|
|
||||||
crap like that. So, we have to move the "zero" the right amount, just like
|
|
||||||
in ToggleSpeaker(), and backfill only without toggling.
|
|
||||||
*/
|
|
||||||
HandleBuffer(elapsedCycles);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void VolumeUp(void)
|
void VolumeUp(void)
|
||||||
{
|
{
|
||||||
// Currently set for 16-bit samples
|
// Currently set for 16-bit samples
|
||||||
|
@ -363,66 +253,3 @@ uint8_t GetVolume(void)
|
||||||
return ampPtr;
|
return ampPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
HOW IT WORKS
|
|
||||||
|
|
||||||
the main thread adds the amount of cpu time elapsed to samplebase. togglespeaker uses
|
|
||||||
samplebase + current cpu time to find appropriate spot in buffer. it then fills the
|
|
||||||
buffer up to the current time with the old toggle value before flipping it. the sound
|
|
||||||
irq takes what it needs from the sound buffer and then adjusts both the buffer and
|
|
||||||
samplebase back the appropriate amount.
|
|
||||||
|
|
||||||
|
|
||||||
A better way might be as follows:
|
|
||||||
|
|
||||||
Keep timestamp array of speaker toggle times. In the sound routine, unpack as many as will
|
|
||||||
fit into the given buffer and keep going. Have the toggle function check to see if the
|
|
||||||
buffer is full, and if it is, way for a signal from the interrupt that there's room for
|
|
||||||
more. Can keep a circular buffer. Also, would need a timestamp buffer on the order of 2096
|
|
||||||
samples *in theory* could toggle each sample
|
|
||||||
|
|
||||||
Instead of a timestamp, just keep a delta. That way, don't need to deal with wrapping and
|
|
||||||
all that (though the timestamp could wrap--need to check into that)
|
|
||||||
|
|
||||||
Need to consider corner cases where a sound IRQ happens but no speaker toggle happened.
|
|
||||||
|
|
||||||
If (delta > SAMPLES_PER_FRAME) then
|
|
||||||
|
|
||||||
Here's the relevant cases:
|
|
||||||
|
|
||||||
delta < SAMPLES_PER_FRAME -> Change happened within this time frame, so change buffer
|
|
||||||
frame came and went, no change -> fill buffer with last value
|
|
||||||
How to detect: Have bool bufferWasTouched = true when ToggleSpeaker() is called.
|
|
||||||
Clear bufferWasTouched each frame.
|
|
||||||
|
|
||||||
Two major cases here:
|
|
||||||
|
|
||||||
o Buffer is touched on current frame
|
|
||||||
o Buffer is untouched on current frame
|
|
||||||
|
|
||||||
In the first case, it doesn't matter too much if the previous frame was touched or not,
|
|
||||||
we don't really care except in finding the correct spot in the buffer to put our change
|
|
||||||
in. In the second case, we need to tell the IRQ that nothing happened and to continue
|
|
||||||
to output the same value.
|
|
||||||
|
|
||||||
SO: How to synchronize the regular frame buffer with the IRQ buffer?
|
|
||||||
|
|
||||||
What happens:
|
|
||||||
Sound IRQ --> Every 1024 sample period (@ 44.1 KHz = 0.0232s)
|
|
||||||
Emulation --> Render a frame --> 1/60 sec --> 735 samples
|
|
||||||
--> sound buffer is filled
|
|
||||||
|
|
||||||
Since the emulation is faster than the SIRQ the sound buffer should fill up
|
|
||||||
prior to dumping it to the sound card.
|
|
||||||
|
|
||||||
Problem is this: If silence happens for a long time then ToggleSpeaker is never
|
|
||||||
called and the sound buffer has stale data; at least until soundBufferPos goes to
|
|
||||||
zero and stays there...
|
|
||||||
|
|
||||||
BUT this should be handled correctly by toggling the speaker value *after* filling
|
|
||||||
the sound buffer...
|
|
||||||
|
|
||||||
Still getting random clicks when running...
|
|
||||||
(This may be due to the lock/unlock sound happening in ToggleSpeaker()...)
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
|
@ -19,10 +19,8 @@ void SoundInit(void);
|
||||||
void SoundDone(void);
|
void SoundDone(void);
|
||||||
void SoundPause(void);
|
void SoundPause(void);
|
||||||
void SoundResume(void);
|
void SoundResume(void);
|
||||||
void ToggleSpeaker(uint64_t elapsedCycles);
|
void ToggleSpeaker(void);
|
||||||
void WriteSampleToBuffer(void);
|
void WriteSampleToBuffer(void);
|
||||||
//void AddToSoundTimeBase(uint64_t cycles);
|
|
||||||
void AdjustLastToggleCycles(uint64_t elapsedCycles);
|
|
||||||
void VolumeUp(void);
|
void VolumeUp(void);
|
||||||
void VolumeDown(void);
|
void VolumeDown(void);
|
||||||
uint8_t GetVolume(void);
|
uint8_t GetVolume(void);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user