Added VBL, fixed sound-on-write, added .bin disk support.

This commit is contained in:
Shamus Hammons 2013-09-24 09:09:40 -05:00
parent c0001155bc
commit aec4f863c3
6 changed files with 71 additions and 214 deletions

View File

@ -21,15 +21,23 @@ autoSaveState = 1
#floppyImage1 = ./disks/temp.nib
#floppyImage1 = ./disks/temp.dsk
# 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
# Yes
#floppyImage1 = ./disks/bt2_boot.dsk
# Yes (but segfaults in the timer routine in the title screen--NB: Not anymore...)
floppyImage1 = ./disks/bt3_boot_fixed.dsk
floppyImage2 = ./disks/bt3_character_fixed.dsk
# Yes
#floppyImage1 = ./disks/bt3_boot_fixed.dsk
#floppyImage2 = ./disks/bt3_character_fixed.dsk
# Yes
#floppyImage1 = ./disks/Sabotage.dsk
# ??? (//c or //e w/128K required) (dumps to monitor)
# Yes
#floppyImage1 = ./disks/airheart.dsk
# Yes
#floppyImage1 = ./disks/drol.dsk
@ -37,10 +45,11 @@ floppyImage2 = ./disks/bt3_character_fixed.dsk
#floppyImage1 = ./disks/karateka.dsk
# Yes
#floppyImage1 = ./disks/wolfenstein_dos32.nib
# Yes, keys???
#floppyImage1 = ./disks/MidnightMagic_etc.dsk
# ??? Loads, then dumps to monitor (This is IIe or IIc only)
# Yes, keys??? (joystick only)
floppyImage1 = ./disks/MidnightMagic_etc.dsk
# Yes
#floppyImage1 = ./disks/battle_chess_1.dsk
#floppyImage2 = ./disks/battle_chess_2.dsk
# Yes
#floppyImage1 = ./disks/MoebiusI-1.dsk
#floppyImage2 = ./disks/MoebiusI-2.dsk
@ -76,6 +85,8 @@ floppyImage2 = ./disks/bt3_character_fixed.dsk
#floppyImage1 = ./disks/lode_runner.dsk
# Yes
#floppyImage1 = ./disks/championship_lode_runner.dsk
# Yes
#floppyImage1 = ./disks/championship_lode_runner.bin
# OpenGL filtering type: 1 - blurry, 0 - sharp

View File

@ -198,6 +198,9 @@ WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
Execute65C02(&mainCPU, cycles);
WriteSampleToBuffer();
// Dunno if this is correct (seems to be close enough)...
vbl = (i < 670 ? true : false);
}
#endif
@ -421,24 +424,7 @@ WriteLog("80COL (read)\n");
}
else if ((addr & 0xFFF0) == 0xC030) // Read $C030-$C03F
{
/*
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());
ToggleSpeaker();
//should it return something else here???
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...
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)
{
#ifdef SOFT_SWITCH_DEBUGGING

View File

@ -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 };
char FloppyDrive::nameBuf[MAX_PATH];
// FloppyDrive class implementation...
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
}
FloppyDrive::~FloppyDrive()
{
if (disk[0])
@ -60,6 +62,7 @@ FloppyDrive::~FloppyDrive()
delete[] disk[1];
}
bool FloppyDrive::LoadImage(const char * filename, uint8_t driveNum/*= 0*/)
{
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;
}
bool FloppyDrive::SaveImage(uint8_t driveNum/*= 0*/)
{
if (driveNum > 1)
@ -153,6 +157,7 @@ bool FloppyDrive::SaveImage(uint8_t driveNum/*= 0*/)
return true;
}
bool FloppyDrive::SaveImageAs(const char * filename, uint8_t driveNum/*= 0*/)
{
//WARNING: Buffer overflow possibility
@ -161,6 +166,7 @@ bool FloppyDrive::SaveImageAs(const char * filename, uint8_t driveNum/*= 0*/)
return SaveImage(driveNum);
}
void FloppyDrive::CreateBlankImage(uint8_t driveNum/*= 0*/)
{
if (disk[driveNum] != NULL)
@ -176,6 +182,7 @@ void FloppyDrive::CreateBlankImage(uint8_t driveNum/*= 0*/)
SpawnMessage("New blank image inserted in drive %u...", driveNum);
}
void FloppyDrive::SwapImages(void)
{
uint8_t nybblizedImageTmp[232960];
@ -211,6 +218,7 @@ void FloppyDrive::SwapImages(void)
SpawnMessage("Drive 0: %s...", imageName[0]);
}
void FloppyDrive::DetectImageType(const char * filename, uint8_t driveNum)
{
diskType[driveNum] = DT_UNKNOWN;
@ -254,10 +262,11 @@ WRT to the disk image itself.
}
// Actually, it just might matter WRT to nybblyzing/denybblyzing
// Here, we check for BT3
//Nope, no change...
//diskType[driveNum] = DT_PRODOS;
NybblizeImage(driveNum);
}
else if (diskSize[driveNum] == 143488)
{
diskType[driveNum] = DT_DOS33_HDR;
NybblizeImage(driveNum);
}
@ -265,9 +274,11 @@ WRT to the disk image itself.
WriteLog("FLOPPY: Detected image type %s...\n", (diskType[driveNum] == DT_NYBBLE ?
"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)
{
// 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)
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)
bytes += (poSector[sector] * 256) + (trk * 256 * 16);
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)
{
uint8_t decodeNybble[0x80] = {
@ -462,6 +476,8 @@ void FloppyDrive::DenybblizeImage(uint8_t driveNum)
if (diskType[driveNum] == DT_DOS33)
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)
bytes += (poSector[sector] * 256) + (trk * 256 * 16);
else
@ -472,6 +488,7 @@ void FloppyDrive::DenybblizeImage(uint8_t driveNum)
}
}
const char * FloppyDrive::GetImageName(uint8_t driveNum/*= 0*/)
{
// Set up a zero-length string for return value
@ -508,6 +525,7 @@ const char * FloppyDrive::GetImageName(uint8_t driveNum/*= 0*/)
return nameBuf;
}
void FloppyDrive::EjectImage(uint8_t driveNum/*= 0*/)
{
// 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...
}
bool FloppyDrive::DriveIsEmpty(uint8_t driveNum/*= 0*/)
{
if (driveNum > 1)
@ -539,6 +558,7 @@ bool FloppyDrive::DriveIsEmpty(uint8_t driveNum/*= 0*/)
return (imageName[driveNum][0] == 0 ? true : false);
}
bool FloppyDrive::DiskIsWriteProtected(uint8_t driveNum/*= 0*/)
{
if (driveNum > 1)
@ -550,6 +570,7 @@ bool FloppyDrive::DiskIsWriteProtected(uint8_t driveNum/*= 0*/)
return writeProtected[driveNum];
}
void FloppyDrive::SetWriteProtect(bool state, uint8_t driveNum/*= 0*/)
{
if (driveNum > 1)
@ -606,18 +627,21 @@ SpawnMessage("Stepping to track %u...", track);
// return something if read mode...
}
void FloppyDrive::ControlMotor(uint8_t addr)
{
// $C0E8 - 9
motorOn = addr;
}
void FloppyDrive::DriveEnable(uint8_t addr)
{
// $C0EA - B
activeDrive = addr;
}
uint8_t FloppyDrive::ReadWrite(void)
{
SpawnMessage("%u:%sing %s track %u, sector %u...", activeDrive,
@ -651,24 +675,28 @@ Which we now do. :-)
return diskByte;
}
uint8_t FloppyDrive::GetLatchValue(void)
{
// $C0ED
return latchValue;
}
void FloppyDrive::SetLatchValue(uint8_t value)
{
// $C0ED
latchValue = value;
}
void FloppyDrive::SetReadMode(void)
{
// $C0EE
ioMode = IO_MODE_READ;
}
void FloppyDrive::SetWriteMode(void)
{
// $C0EF

View File

@ -14,7 +14,7 @@
#endif
#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
{

View File

@ -17,7 +17,7 @@
// STILL TO DO:
//
// - 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"
@ -31,27 +31,10 @@
//#define DEBUG
//#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 (48000.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)
// ~ 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 (32768)
@ -131,9 +114,7 @@ void SoundDone(void)
{
if (soundInitialized)
{
// SDL_PauseAudio(true);
SDL_PauseAudioDevice(device, 1);
// SDL_CloseAudio();
SDL_CloseAudioDevice(device);
SDL_DestroyCond(conditional);
SDL_DestroyMutex(mutex);
@ -184,22 +165,22 @@ static void SDLSoundCallback(void * /*userdata*/, Uint8 * buffer8, int length8)
uint32_t length = (uint32_t)length8 / 2;
//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++)
buffer[i] = soundBuffer[i];
// Fill buffer with last value
// memset(buffer + soundBufferPos, (uint8_t)sample, length - soundBufferPos);
for(uint32_t i=soundBufferPos; i<length; i++)
buffer[i] = sample;
soundBufferPos = 0; // Reset soundBufferPos to start of buffer...
// Reset soundBufferPos to start of buffer...
soundBufferPos = 0;
}
else
{
// Fill sound buffer with frame buffered sound
// memcpy(buffer, soundBuffer, length);
for(uint32_t i=0; i<length; i++)
buffer[i] = soundBuffer[i];
@ -225,7 +206,7 @@ void WriteSampleToBuffer(void)
//WriteLog("WriteSampleToBuffer(): SDL_mutexP(mutex2)\n");
SDL_mutexP(mutex2);
// This should almost never happen, but...
// This should almost never happen, but, if it does...
while (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
// 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)
void ToggleSpeaker(void)
{
if (!soundInitialized)
return;
// HandleBuffer(elapsedCycles);
speakerState = !speakerState;
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)
{
// Currently set for 16-bit samples
@ -363,66 +253,3 @@ uint8_t GetVolume(void)
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()...)
*/

View File

@ -19,10 +19,8 @@ void SoundInit(void);
void SoundDone(void);
void SoundPause(void);
void SoundResume(void);
void ToggleSpeaker(uint64_t elapsedCycles);
void ToggleSpeaker(void);
void WriteSampleToBuffer(void);
//void AddToSoundTimeBase(uint64_t cycles);
void AdjustLastToggleCycles(uint64_t elapsedCycles);
void VolumeUp(void);
void VolumeDown(void);
uint8_t GetVolume(void);