diff --git a/apple2.cfg b/apple2.cfg index 07c414c..faf073e 100755 --- a/apple2.cfg +++ b/apple2.cfg @@ -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 diff --git a/src/apple2.cpp b/src/apple2.cpp index 1eeb7e8..e1cde17 100755 --- a/src/apple2.cpp +++ b/src/apple2.cpp @@ -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 diff --git a/src/floppy.cpp b/src/floppy.cpp index 193e5cb..b014af0 100755 --- a/src/floppy.cpp +++ b/src/floppy.cpp @@ -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 diff --git a/src/floppy.h b/src/floppy.h index fd3c06e..12b5e98 100755 --- a/src/floppy.h +++ b/src/floppy.h @@ -14,7 +14,7 @@ #endif #include -enum { DT_UNKNOWN, DT_DOS33, DT_PRODOS, DT_NYBBLE }; +enum { DT_UNKNOWN, DT_DOS33, DT_DOS33_HDR, DT_PRODOS, DT_NYBBLE }; class FloppyDrive { diff --git a/src/sound.cpp b/src/sound.cpp index 4cab23e..bcee383 100755 --- a/src/sound.cpp +++ b/src/sound.cpp @@ -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= (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()...) -*/ - diff --git a/src/sound.h b/src/sound.h index 49dca4b..e0ea978 100755 --- a/src/sound.h +++ b/src/sound.h @@ -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);