So, get this: in Sonic 2, if you go to the sound test and play sound 7A, you get this horrible version of the Sega chant, but it sounds fine on the Sega screen. If you have an ear for it, apparently even the DAC drums are like that. As Vladikcomper said before, this is because of the Z80 being stopped frequently. Thing is, there are many times when it's being stopped for no flaming reason. For the most part, anyway. Here, we'll be doing what we can to fix that. Unfortunately, hardware users won't see much benefit from this. The Sega sound only sounds slightly less strangled. It seems DMA still does a huge number on sound quality. Emulators sound better, though. What can't be helped is the DAC playback. As, no matter how much we optimise things on the 68k side, the Z80 will interrupt itself with V-Int, butchering the DAC quality. The Sega sound works around that by disabling interrupts, but that comes at the cost of pausing the entire sound driver. That said, hope you like splash screens. If that's your cup of tea, onto the guide! First let's get something minor out of the way. Replacing the current music pause/unpause system The way the current system works will interfere with our upcoming modifications, so we're effectively gonna cut it out. But we still need the basic functionality, so we'll replace it with something more... direct. Open s2.constants.asm, and find the definitions of MusID_Pause and MusID_Unpause. Make them $7F and $80, respectively. Now find the only use of MusID_Pause in s2.asm. It should be but a single instruction: Code (ASM): move.b #MusID_Pause,(Music_to_play).w ; pause music The address it writes to is part of a relay system we're going to be removing, sndDriverInput. You can study it yourself to see how it operates, but all you need to know is that it has some special-case code for MusID_Pause/Unpause, which writes $7F or $80 to zStopMusic. So, to take sndDriverInput out of the equation, we'll do this ourselves, which is why we changed MusID_Pause/Unpause's definitions from a sound ID to the data they'd eventually represent. Surround this lone instruction with stopZ80 and startZ80 (since the 68k can't write to the Z80's address space otherwise), and change the address '(Music_to_play).w' to '(Z80_RAM+zStopMusic).l'. Done. Now onto MusID_Unpause. You should know the drill. It's exactly the same: stick a stopZ80 before the instruction, and a startZ80 after it; then change the destination address to '(Z80_RAM+zStopMusic).l'. Do this for all instances. It's safe to build and test, but hang on! None of the sound commands are working! Music doesn't fade, the Sega chant doesn't play... At least the new pause works, but we gotta fix that bug! Open s2.sounddriver.asm. Again, there's some special-case code that we gotta deal with. Go to zPlaySoundByIndex, and you'll find that it uses MusID_Pause. It's part of a check to invalidate sound IDs $FE and $FF, which used to be the original MusID_Pause/Unpause. That isn't the case anymore, and the constant is no longer appropriate, causing the sound commands to be invalidated, as the original MusID_Pause/Unpause would have. Simply remove the check and the following conditional return, and you'll be as good as gold. One last little change to make, though: what's gonna happen to sound IDs $FE and $FF? They're now unused, and are even buggy: try building your ROM and playing those sounds in the sound test. That isn't normal. The fix is simple: shift the sound commands to occupy the now-unused slots. A nice bonus to this is that you free up two sound IDs! Back in s2.constants.asm, add 2 to all the sound commands: from MusID_StopSFX to MusID_Stop. That's that done. There's still one last bug, but that'll be fixed later on. Optimising Z80 stops There are a lot of times where the Z80 is stopped, even when it doesn't need to be. For example, joypad-related reads and writes. We can do without all that unnecessary junk stopping the Z80. We'll also be removing the stops and starts used for sndDriverInput, which is actually an area where the Z80 should be stopped, but since we'll be removing that, we won't be needing the Z80 to be stopped either. Remember to not remove the ones you added when replacing the pause system. Look for Z80 stops and starts here: VintSub0 loc_4C4 loc_54A Vint0_noWater VintSub14 VintSub8 loc_748 Vint10_specialStage loc_86E VintSubA SS_PNTA_Transfer_Table VintSub1A VintSubC loc_BD6 VintSub18 VintSub16 Do_ControllerPal H_Int JoypadInit ClearScreen EndingSequence Removing the 68k-side sound queuing system The sound driver itself already has a queuing system, so all this one serves to do is waste cycles and time: as you may have seen when removing all those Z80 stops, sndDriverInput is ran, and pauses the Z80, on every V-Int, and even on H-Int. That's a lot of time for the Z80 to be stopped, so let's get rid of that. Find and delete sndDriverInput, then remove every branch and jump to it. Bonus: in doing that, we've eliminated that last bug I mentioned earlier. Of course, in doing that, we've also effectively destroyed the sound queuing. But we'll fix that. Overhauling PlaySound subroutines What happens more often? V-Int, or a sound being queued? A frame passing by, or Sonic picking up a ring? For limiting Z80 idling, PlaySound is simply more appropriate. In fact, S3K has it this way (not that its PCM playback is much better. See the above section on Z80 stops). What we're going to do is merge sndDriverInput and the PlaySound subroutines, regaining the functionality using code that isn't run so excessively often. Let's start from the top: with PlayMusic. Replace it with this: Code (ASM): ; sub_135E: PlayMusic: stopZ80 ; Stop the Z80 so the 68k can write to Z80 RAM cmpi.b #$80,(Z80_RAM+zQueueToPlay).l ; If this (zQueueToPlay) isn't $80, the driver is processing a previous sound request. bne.s + ; So we'll put this sound in a backup queue move.b d0,(Z80_RAM+zQueueToPlay).l ; Queue sound startZ80 ; Start the Z80 back up again so the sound driver can continue functioning rts + ;.usebackupqueue move.b d0,(Z80_RAM+zSFXUnknown).l ; Queue sound startZ80 ; Start the Z80 back up again so the sound driver can continue functioning rts ; End of function PlayMusic Next up is PlaySound. Replace it with this: Code (ASM): ; sub_137C: PlaySoundLocal: tst.b render_flags(a0) bpl.s ++ ; sub_1370 PlaySound: stopZ80 ; Stop the Z80 so the 68k can write to Z80 RAM move.b d0,(Z80_RAM+zSFXToPlay).l ; Queue sound startZ80 ; Start the Z80 back up again so the sound driver can continue functioning + rts ; End of function PlaySound And, finally, PlaySoundStereo: Code (ASM): ; sub_1376: PlaySoundStereo: stopZ80 ; Stop the Z80 so the 68k can write to Z80 RAM move.b d0,(Z80_RAM+zSFXStereoToPlay).l ; Queue sound startZ80 ; Start the Z80 back up again so the sound driver can continue functioning rts ; End of function PlaySoundStereo Delete the original PlaySoundLocal. Note that if you followed my previous guide on extending the sound IDs from starting at $80 to $00, then those 'cmpi.b #$80's should be replaced with 'tst.b's. Repairing the unused queue Now, note that we're making use of an unused sound queue in PlayMusic as a 'backup queue'. This is to replace a lost functionality, where sndDriverInput would 'nag' the sound driver to ensure that a sound would be queued in the next frame if zQueueToPlay isn't currently available. The problem is that, like the one in Sonic 1's driver, this unused queue is half-implemented. Without a backup queue, music writes would be missed, so we've gotta repair this one. In s2.sounddriver.asm, find zInitMusicPlayback. In there, you'll see some data being backed up, including two queues: the SFX queue and the stereo queue. Note that the unused queue is not. We'll have to add that. Underneath the third 'push bc', add this: Code (Text): ld c,(ix+0Bh) ; unused queue slot push bc And, underneath the line '; Restore those queue/flags:', add this: Code (Text): pop bc ld (ix+0Bh),c ; unused queue slot And with that, we're done. Conclusion Try playing the Sega sound in the sound test now (remembering that its ID is now 7C). This time, after the work we've done, it's closer to how it is on the Sega screen. Now PCM playback is a bit more consistent in its quality. You've also freed up two sound IDs, and you've freed up RAM variables Music_to_play through Music_to_play_2. Note that the entire guide has consisted of improving the PCM playback through the 68k side of things. The driver itself is pretty much untouched. Like I said, straight up improving the PCM stream loop would require modifications to the actual loop. There is one last thing you may need to do. For me, in Regen, the Sega chant on the Sega screen plays itself twice if I let it run. Removing the sound under loc_3A3E6 fixes it. I'm cautious of recommending it because I don't know what the code is meant to do in the first place. Normally, if the Sega chant sound is queued while the Sega chant sound is playing, it's ignored. But it seems the improved playback causes the sound to end without delay, so this new sound plays just as the first ends, making it play twice.