don't click here

Sonic 1 "Mega PCM" driver

Discussion in 'Engineering & Reverse Engineering' started by vladikcomper, Jun 11, 2012.

  1. vladikcomper


    Tech Member
    Sonic Warped
    Update 2024: Mega PCM 2 was released!
    Please see this thread instead:

    Mega PCM 1.x is deprecated and isn't recommended anymore.

    * * *

    - Silent update: Download links have been restored (for the most part).
    30-Apr-2017 - Fixed all links as they went broken.
    03-Mar-2013 - Added "Fixing SEGA PCM playback" section into the "How to increase playback quality with Mega PCM" guide.
    28-Oct-2012 - New guide: "How to increase playback quality with Mega PCM".
    05-Aug-2012 - Version 1.1 is out! Check out this post for the info (clickie). Links in this post are also up to date.

    * * *

    "Mega PCM" is my new DAC driver for Sonic 1, coded from the ground up. We already have JMan's and S1HL's custom drivers, which provided us with a lot of good possibilities over the native Sonic 1's DAC driver. I personally used JMan's driver all the time, finding it the best for my purposes. However, like most Z80 drivers, it had some limits and quirks, which were impossible to overpass. All these 'quirks' are actually caused by the hardware design: the Z80 is very limited in accessing ROM sections (this is done via a small 32 KB window) and the processor has to do all DAC timing itself, which adds many difficulties into programming it.

    In my driver, I attempted to get rid of some unpleasant limits most of Z80 drivers usually have. I also aimed to code it in the most optimized way to keep playback rates high, while the code is overloaded with a lot of logic I have to program to make overpassing limits possible. To my surprise, I succeded, Mega PCM currently plays sound faster than YM's maximum output frequency (~27 kHz), so there is some room remains for future additions ;)

    The features

    • Automatic bank-switching
      Forget about the banks, put your samples where you like, how you like. You no longer have to align samples on 32 KB boundary and care if they cross the boundary.
    • Unlimited sample size
      Samples are no more limited to 32 KB. "Mega PCM" is capable of playing samples of absolutely any size, as long as it can fit your ROM space.
    • Two sound formats supported
      These are 4-bit DPCM and 8-bit PCM.
      The first format was widely used by Sonic 1 to Sonic 3K for DAC samples as it takes only half of the space a normal PCM sound would. 8-bit PCM, however, is the 'native' format for Sega's YM2612 chip, it takes more space but provides a better sound quality.
    • Extended playback controls: Stop, Pause, Loop, Priority
      "Mega PCM" can pause and continue sample playback, so if you play a long sample it won't be cut off after you pause the game.
      You can also loop samples (good for DAC-songs) and can tell "Mega PCM" not to overwrite some samples (good for in-game voice clips).
    • DAC panning
      You can store your sample to play in Left or Right headphone only.
    • Up to $5F DAC samples allowed
      For instance, JMan's driver allowed $1F DAC sample due to its usage of 8-bit pointers in the code. However, in reality DAC table size is only limited by the RAM size, but the SMPS only allows up $5F different DAC samples (slots $81-$CF).
    How-to Install "Mega PCM"

    If you got interested in trying it out, here we go!
    In this guide, I'll tell you how to put Mega PCM driver into your hack, how to improve SMPS slightly to suit new driver's needs and go wild with including new DACs!
    This guide also includes bonus: "How-to play voices in-game" (see below)

    First off, download this:
    This is almost ready to use "Mega PCM" setup with Sonic 3 samples set (except for timpani, S3's doesn't fit well here). Copy all files from my archive into your hack's folder and we'll start.

    The guide is for Hivebrain's Disassembly.

    1. Replacing old driver and driver loading routine

    Open Sonic1.asm and find 'Kos_Z80' label, you'll see the following code:

    Code (ASM):
    1. Kos_Z80:    incbin    sound\z80_1.bin
    2.         dc.w ((SegaPCM&$FF)<<8)+((SegaPCM&$FF00)>>8)
    3.         dc.b $21
    4.         dc.w (((EndOfRom-SegaPCM)&$FF)<<8)+(((EndOfRom-SegaPCM)&$FF00)>>8)
    5.         incbin    sound\z80_2.bin
    6.         even
    Delete everything, and put this instead:

    Code (ASM):
    1.     include    'MegaPCM.asm'
    Now, goto 'SoundDriverLoad' routine and replace all the code with this:

    Code (ASM):
    1. SoundDriverLoad:            ; XREF: GameClrRAM; TitleScreen
    2.         nop
    3.         move.w    #$100,d0
    4.         move.w    d0,($A11100).l
    5.         move.w    d0,($A11200).l
    6.         lea    (MegaPCM).l,a0
    7.         lea    ($A00000).l,a1
    8.         move.w    #(MegaPCM_End-MegaPCM)-1,d1
    10.     @Load:    move.b    (a0)+,(a1)+
    11.         dbf    d1,@Load
    12.         moveq    #0,d1
    13.         move.w    d1,($A11200).l
    14.         nop
    15.         nop
    16.         nop
    17.         nop
    18.         move.w    d0,($A11200).l
    19.         move.w    d1,($A11100).l
    20.         rts
    21. ; End of function SoundDriverLoad
    2. Editing SMPS to work with the driver

    Find 'sub_71B4C' label and scroll down until you see this part:

    Code (ASM):
    1.         btst    #7,($A01FFD).l
    2.         beq.s    loc_71B82
    3.         move.w    #0,($A11100).l    ; start    the Z80
    4.         nop
    5.         nop
    6.         nop
    7.         nop
    8.         nop
    9.         bra.s    sub_71B4C
    10. ; ===========================================================================
    12. loc_71B82:
    Delete or comment out this code.

    This code checked driver's status and didn't run SMPS engine until driver tells SMPS can interrupt him. "Mega PCM" doesn't need this trick anymore.

    Then, goto 'loc_71C88' label, find and delete these lines:

    Code (ASM):
    1.         btst    #3,d0
    2.         bne.s    loc_71CAC
    This removes hardcoded Timpani tempo modifier for old driver. Certain sample numbers ($88-$8B) were hardcoded to play sample $83 (timpani) with different tempos. New driver doesn't need it, as you can set this stuff in the DAC table.
    Also, 'loc_71CAC' is now unused code, so you may want to delete it as well.

    Next, find 'loc_71C44' label and in the very beginning add this code:

    Code (ASM):
    1.         move.b    ($A04000).l,d2
    2.         btst    #7,d2
    3.         bne.s    loc_71C44
    4.         move.b    #$2A,($A04000).l
    "Mega PCM" doesn't store YM register 2A before writing to port in playback loop, this is odd and getting rid of it gives a small speed-up. Register is stored before playback and driver relies that it won't be changed. However, SMPS does writes to YM, storing different registers. So, it must restore register 2A before releasing Z80 bus and continuing Z80 playback program.

    The following changes will make "Mega PCM" pause playback when the game pauses and continue it when the game unpauses.

    Find 'loc_71E7C' and at the very end, just before BRA, add this line:

    Code (ASM):
    1.         move.b    #$7F,($A01FFF).l; pause DAC
    Playing sample $7F executes pause command.

    Next, find 'loc_71EDC' and replace all the code until label 'loc_71EFE' with this:

    Code (ASM):
    1. loc_71EDC:
    2.         adda.w    d3,a5
    3.         dbf    d4,loc_71EC4
    5.         lea    $340(a6),a5
    6.         btst    #7,(a5)
    7.         beq.s    @UnpauseDAC
    8.         btst    #2,(a5)
    9.         bne.s    @UnpauseDAC
    10.         move.b    #-$4C,d0
    11.         move.b    $A(a5),d1
    12.         jsr    sub_72722(pc)
    14. @UnpauseDAC:
    15.         move.b    #0,($A01FFF).l    ; unpause DAC
    Playing sample $00 cancels pause mode.

    And the last touch, let's make playback stop properly (not only mute) when SMPS stops the sound.
    Goto 'loc_725B6' (a part of 'Sound_E4' code) and before the 'bra.w sub_729B6' add this line:

    Code (ASM):
    1.         move.b    #$80,($A01FFF).l ; stop DAC playback
    Playing sample $80 forces to stop playback

    That's it!

    How-to use "Mega PCM"

    Open MegaPCM.asm. This file includes compiled "Mega PCM" Z80-code and stores table of samples to play. To make working with driver easier, I added some constants and macros to setup table and include samples.

    Scroll down in the file until you see this:

    Code (ASM):
    1. ; ---------------------------------------------------------------
    2. ; DAC Samples Table
    3. ; ---------------------------------------------------------------
    5.     DAC_Entry    $08, Kick, dpcm            ; $81    - Kick
    6.     DAC_Entry    $08, Snare, dpcm        ; $82    - Snare
    7.     DAC_Entry    $1B, Timpani, dpcm        ; $83    - Timpani
    8.     dc.l    0,0                    ; $84    - <Free>
    9.     dc.l    0,0                    ; $85    - <Free>
    10.     dc.l    0,0                    ; $86    - <Free>
    11.     dc.l    0,0                    ; $87    - <Free>
    12.     DAC_Entry    $12, Timpani, dpcm        ; $88    - Hi-Timpani
    13.     DAC_Entry    $15, Timpani, dpcm        ; $89    - Mid-Timpani
    14.     DAC_Entry    $1B, Timpani, dpcm        ; $8A    - Mid-Low-Timpani
    15.     DAC_Entry    $1D, Timpani, dpcm        ; $8B    - Low-Timpani
    17. MegaPCM_End:
    19. ; ---------------------------------------------------------------
    20. ; DAC Samples Files
    21. ; ---------------------------------------------------------------
    23.     IncludeDAC    Kick, bin
    24.     IncludeDAC    Snare, bin
    25.     IncludeDAC    Timpani, bin
    26.     even
    First block is a DAC samples table, storing what sample to play and how to play it. It will be loaded to Z80 memory along with "Mega PCM" code.

    Second block includes sound files into your ROM. 'IncludeDAC' macro searches for file with given name and extension in 'dac' folder, which you've added to your hack among with other files.

    For example,
    Code (ASM):
    1.     IncludeDAC    Kick, bin
    will include 'dac\kick.bin' file.

    The macro includes all the files 'as is', except for files with 'wav' extension. For WAV-files, the macro cuts off the header, or you will hear a short noise when play them.

    BIN-files usually used for DPCM format. The fact "Mega PCM" plays this format means you may rip DACs from any Sonic game and play it without conversion to PCM, which makes file two times heavier, but doesn't help with the quality. If you wish to convert PCM sound to DPCM, you can use JMan's compressor:

    WAV-files should use "8-bit unsigned PCM" format. PCM sound can be also presented with 'RAW' extension (raw sound without header), some trackers support it.

    Sound frequency can be up to ~30 kHz for PCM and ~32 for DPCM (Yeah, suddenly the more complex format got a higher playback rate, seems my optimized code is way fast. Actually, the fact it processes 2 DAC writes per loop helps a lot.)
    But actually, YM2612 can't play DAC faster than ~27 kHz (it will lead to missed writes). However, emulators don't emulate this and if you don't care about playback quality on real hardware, go wild.

    DAC samples table uses 'DAC_Entry' macros to store sample, which has the following format:
    Code (ASM):
    1. DAC_Entry <pitch>, <sample name>, <flags>
    The real 8-byte format used by "Mega PCM" you can see in the macro's definition.

    Pitch sets how fast the sample sound should play. The lower pitch is, the faster it will play.

    Sample Name refers to sample name as defined by 'IncludeDAC' macro.

    Flags field accepts the following flags:
    * pcm - selects PCM format
    * dpcm - selects DPCM format
    * loop - makes sample loop itself
    * pri - priority flag, playback won't be interrupted if new sample requested. Playback will respond only to stop/pause commands in this mode.
    * panL - play in Left headphone only
    * panR - play in Right headphone only
    * panLR - play in both headphones

    You can combine flags like this: dpcm+panL+loop+pri

    Note: if no pan flags were set, "Mega PCM" won't change DAC panning, it will leave setting by SMPS.

    Bonus: How-to play voices in game

    Here is a small example of using "Mega PCM" to play voice clips during the game.

    Download this clip:
    Move it to 'dac' folder in you disassembly.

    In the very end of MegaPCM.asm file, just before the 'even', add:
    Code (ASM):
    1.     IncludeDAC    V_Hurt,wav
    Now we should add it in one of DAC table's slot, take slot $84 for example, it's free. We'll need "Mega PCM" to play this as PCM sound with high priority (or else it can be interrupted by another DACs in BGM).
    Modify the table so it will look like this:

    Code (ASM):
    1.     DAC_Entry    $08, Kick, dpcm            ; $81    - Kick
    2.     DAC_Entry    $08, Snare, dpcm        ; $82    - Snare
    3.     DAC_Entry    $1B, Timpani, dpcm        ; $83    - Timpani
    4.     DAC_Entry    $07, V_Hurt, pcm+pri        ; $84    - 'Hurt' voice
    5.     dc.l    0,0                    ; $85    - <Free>
    6.     dc.l    0,0                    ; $86    - <Free>
    7.     dc.l    0,0                    ; $87    - <Free>
    8.     DAC_Entry    $12, Timpani, dpcm        ; $88    - Hi-Timpani
    9.     DAC_Entry    $15, Timpani, dpcm        ; $89    - Mid-Timpani
    10.     DAC_Entry    $1B, Timpani, dpcm        ; $8A    - Mid-Low-Timpani
    11.     DAC_Entry    $1D, Timpani, dpcm        ; $8B    - Low-Timpani
    Done! Sample is now playable. However, we must program the game to play it. In order to do this, find 'PlaySound' routine and add a new routine above:

    Code (ASM):
    1. ; ---------------------------------------------------------------------------
    2. ; Subroutine to    play a DAC sample
    3. ; ---------------------------------------------------------------------------
    5. PlaySample:
    6.     move.w    #$100,($A11100).l    ; stop the Z80
    7. @0    btst    #0,($A11100).l
    8.     bne.s    @0
    9.     move.b    d0,$A01FFF
    10.     move.w    #0,($A11100).l
    11.     rts
    This routine will allow you to play DAC samples in game. Our new sample is $84, let's play it when Sonic's hurt.

    Goto 'Hurt_Sound' and replace
    Code (ASM):
    1.     jsr    (PlaySound_Special).l
    Code (ASM):
    1.         moveq    #$FFFFFF84,d0
    2.         jsr    PlaySample
    That's it. Enjoy!

    "Mega PCM" source code

    "Mega PCM" has an open source code, enjoy!

    Version 1.1 Source Code:
    Version 1.0 Source Code:

    I accept any ideas on improving driver's core, bug reports etc. You are free to modify driver for your purposes or create something new of it.
    Last edited: May 31, 2024
  2. Bareirito


    Sonic - Mystery of the Chaos Emeralds
    While, I'm not in the hacking mood (yet), I must say this is epic and I wonder why people doesn't get interested on this! This is wicked stuff! :)
  3. vladikcomper


    Tech Member
    Sonic Warped
    Update: Just found a small bug in "How-to play voices in-game" section.
    Playback flags were not set correctly in my example.

  4. ashthedragon


    Sonic Paradise Researcher
    Sonic Paradise & Sonic Ages
    Although I don't have the skills to use it, I think this is an absolutely brillant work! Very well done! It surely will make a lot of good to the community, and I hope lots of hacks make use of it
  5. Hitaxas


    Retro 80's themed Twitch streamer ( on hiatus) Member
    This custom driver setup is awesome! I tested it out on a fresh Sonic 1 hack, messed around with adding voices to Sonic. Impressive. I might consider switching to this driver, currently I use the S1HL driver with tons of samples added.

    Edit: I failed at reading the opening paragraph. This is a custom driver, not a modification of the original.

    Edit2: I did find an issue with this driver. The first act of a level, everything works fine, music and voices. However, the dac turns to static when the next act loads.
  6. PsychoSk8r


    PsychedelAnt | Tone Turner Oldbie
    Birmingham, UK
    30 Day Project: Revisited.A New Release!
    Really like the idea of the driver, but not too keen on the sound of the glitch above. Can anyone else confirm the glitch also? (I'm too damn lazy, and busy. Let's call it "Efficient"). =P
  7. vladikcomper


    Tech Member
    Sonic Warped
    Thanks for the replies :)

    Weird, something like this never happened when I tested it on a blank disassembly. I've just done some random tests and still no glitches happen.
    It's hard to say if it's bug of the driver or not without seeing it. This can be something wrong with SMPS or even with your code (constant noise will happen if you keep sending driver DAC requests in a loop, I.e. if code to play samples works every frame).
  8. PsychoSk8r


    PsychedelAnt | Tone Turner Oldbie
    Birmingham, UK
    30 Day Project: Revisited.A New Release!
    Well, that was exactly the info I wanted. Cheers! Gonna download this, and have some fun testing it. :D
  9. Aerosol


    Not here. Moderator
    Not where I want to be.
    Sonic (?): Coming summer of 2055...?
    Knuckles Chaotix uses 8-bit PCM samples doesn't it? So you're saying that Mega PCM can play samples of this quality in the genesis games now?
  10. Puto


    Shin'ichi Kudō, detective. Tech Member
    Portugal, Oeiras
    Part of Team Megamix, but haven't done any actual work in ages.
    We've had support for 8-bit PCM samples with jman's driver for years now. That's not news.
  11. Aerosol


    Not here. Moderator
    Not where I want to be.
    Sonic (?): Coming summer of 2055...?
    And evidently, I didn't know that. Thanks for kinda-sorta answering my question, though.
  12. vladikcomper


    Tech Member
    Sonic Warped
    I should've released it at the very beginning, but was lazy to do it...

    A clean Sonic 1 Disassembly with Mega PCM driver included:
    If you're not in mood to deal with a massive guide yet, you can check out how it looks like and even play with the driver (it's easy).
    If you're not sure if you followed some steps of the guide correctly, you may also check yourself.

    Everything is done directly as the guide in the first post says, nothing more. Section 'How-to play voices in-game' is also applied.
    For your convenience, I marked all changed lines in SMPS code (section 2 of the guide) with '++' comment, so you will find them easily. All code to be deleted was just commented out.
  13. Eduardo Knuckles

    Eduardo Knuckles

    Not a loved one, but the most hated person. Banned
    Someplace somewhere
    Project S.A.M.G.
    This sound quality was the same since Sonic The Hedgehog 1. I'm not sure if other Genesis games can handle better quality without the need of pitch changing. I think that this is the DAC's default or the maximum quality limitation itself.

    Fabulous job, man. As always, we can expect awesome stuff coming from your hands. To be honest, I got in trouble following the steps of the guide (...because its insertion was the only hard part on it), but the management is a lot easier than the management of the samples in the S1HL driver and also on a non-public driver revision I have. Thanks for post this!
  14. RetroKoH


    Project Sonic 8x16
    Fine tune this a little bit more, and I'll post this onto the wiki under the Sound Features section.
  15. vladikcomper


    Tech Member
    Sonic Warped

    Coming Soon:
    1. How to improve sample playback quality
    2. How to play digital songs with "Mega PCM"
    3. A new version of "Mega PCM" with few improvements and bug-fixes

    Note that you can play digital audio tracks even now, as "Mega PCM" can easily manage with these. First post contains enough info to get started, so if you're impatient, feel free to try this out =P

    Few words about upcoming 'How to improve sound quality' guide. Due to way Sonic 1 controls Z80, the processor is being stopped quite a lot which means, digital sound stops processing. This drops sound quality quite a lot. In my guide, I'll explain how to modify SMPS and a bit of game engine to reduce Z80 suspend times and achieve near-perfect quality results like in the video shown.
  16. vladikcomper


    Tech Member
    Sonic Warped
    Mega PCM v.1.1

    • Added a small code-fix to prevent noise and memory access errors on playing empty/broken samples

      Now if the game attempts to play empty or not existing DAC sample slot, you won't hear anything. For example, if you imported an SMPS song and few DACs are missing in your driver. The previous Mega PCM version along with most of the drivers would give out random noise here due to playback errors.

      This also helps if a slot of random broken bytes somehow gets playing. Random offsets now won't cause errors on accessing prohibited memory areas, because they are fixed as well.
    • Fixed synchronization code issue in PCM and DPCM playback loops.

      While I've been counting every cycles in my code, I slightly miscalculated in calculating synchronization code cycle worth, which caused playback loop to work 4 cycles faster when the last bank reached. Therefore, sample's last bank played slightly faster then the rest. Despite 4 cycles (NOP opcode worth) being extremely small time period for Z80, playback speed up was noticeable on high sample rates (22 kHz and higher).
    • Mega PCM now supports up to 8 MB ROMs!

      The previous version was limited to 4 MB, but that wasn't the maximum of Mega PCM addressing capabilities though. I decided to push the limit to what the driver's bank switching system was really capable of, 8 MB. This means you can fit twice as much music and samples!

      I must warn though, very few emulators support big ROMs yet. But if you're still interested in this possibility, I'll try to clue you in (see below for the list of emulators):

      So, many people think that SMD is strictly limited to 4 MB in ROM storage. Actually, the console is capable of more. But the fact is, all the official game cartridges were designed with this limit in mind, their boards just weren't meant to address more than 4 MB of ROM-space, while the console allowed to do so. It was cartridges' limit, not the console's.

      For instance, some SMD flash-cartridges (like Mega Everdrive) support ROMs above 4 MB already, showing what the console is really capable of. However, emulators have very poor support for this.

      The matter is, that limit was tied by the official Genesis manual, where the memory map for 68K had only 4 MB of memory marked for ROM and the rest memory space up to 8 MB was marked as 'reserved'. It's interesting that in the earliest versions of this manual (1988-1989 years) the ROM limit was 1 MB. Many emulators' creators decided to strictly follow the manual and intentionally limited ROM-space to 4 MB. In principle, this makes sense and none of the official games suffered, but it would be rather good if more emulators got rid of this nasty limit, this will surely open up new horizons for SMD developing, bringing console a new life.

      As for now, there are quite few emulators with big ROMs support.
      Some of them are just hacked versions of popular emulators. You can find them here:
    How to update my Mega PCM to version 1.1

    Piece of cake.
    Download this:
    Replace MegaPCM.z80 file in your disassembly with a new one from the archive.
    That's it.

    Mega PCM v.1.1 Source code

    * * *

    If you don't have Mega PCM yet, but you decided to use it, just follow the first post's instructions. Links in the post and the info is up to date as well.

    * * *

    Still coming soon (guides):

    • How to improve sample playback quality
    • How to play digital songs with "Mega PCM"
    Last edited: Nov 27, 2019
  17. Chilly Willy

    Chilly Willy

    Tech Member
    Doom 32X
    The upper "reserved" 4MBytes is for the CD. Big roms don't work with either the CD or the 32X. It's not that Sega carts couldn't address more than 4MB, it's that the bus controller in the MD doesn't supply DTACK for the reserved areas. Simply adding in more rom results in a hang if you try to use it. Big rom capable boards add extra circuitry to the cart to generate its own select and dtack signals to keep the MD from hanging. This conflicts with the CD and 32X, and so only works on the MD by itself with nothing else plugged in.

    Not that I'm saying you shouldn't support it, just adding some extra info on why there is only one big rom game (a Mortal Kombat hack), and why big roms don't work with the CD or 32X.
  18. Hivebrain


    53.4N, 1.5W
    Isn't the limit 10MB? There's an extra 2MB reserved for 32X addresses ($800000-$9FFFFF).
  19. Chilly Willy

    Chilly Willy

    Tech Member
    Doom 32X
    Yep! That's the absolute largest a rom can be without resorting to bank selection. The hardware IO starts at 10MB, so you cannot go any higher.
  20. vladikcomper


    Tech Member
    Sonic Warped
    How to increase playback quality with Mega PCM

    We all know what the SEGA screen sounds like in the original Sonic 1 title:

    But what if we try playing it during game? When massive data transfers take place between different parts of the system, the same sample will sound like this instead:

    This sample quality loss occurs with all samples during the game; this means that all of your kicks, snares, whatever, all playback scratchy.

    In this guide, I'll teach you how to solve this complex technical problem to achieve the best playback quality possible.
    There is also an attachment at the end of this post; a clean disassembly with this guide applied + a little bonus.

    After following this guide you can achieve impressive sample playback quality on Sega Genesis, like so:

    But first, in order to fix the quality loss, we must first understand why it occurs.

    The following text will contain a fair amount of theory and technical terminology. If you don't want to dive into incredible (!) shocking (!!) details, skip the next part and go straight to the second one.

    1. Theoretical explanation

    The problem here is not Mega PCM's fault, but Sonic 1's.

    As you might know, there are two processors in the Sega Genesis; the Motorola 68000 and the Zilog "Z80". The game engine itself is processed on the M68K which controls most of the system features, though the other processor (the Z80), is used in Sonic 1 for digital samples playback. My driver (Mega PCM) works solely on the Z80.

    As mentioned above, during the game, the engine has to communicate with different parts of the system, like the sound and video chips to update audio and video output. The console architecture, in turn, sets some limitations on accessing its own resources. For example, when accessing the YM2612 sound chip using the M68K, because the YM2612 is access within the Z80 memory space, the Z80 must be suspended to allow the M68K to access it.

    During the game, Sonic 1 tends to stop Z80 under the following conditions:
    1. When SMPS engine is running, as it needs access to the YM2612
    2. During DMA-transfers to VRAM or CRAM
    SMPS and DMA-transfers occur constantly at the end of every frame, and their execution can take some time. Hence, the Z80 is being stopped for quite a few times and for quite a while, and with this, Mega PCM execution stops, which means updating DAC (which is controlled entirely by Mega PCM) interrupts, causing distorted sound outputs.

    As an example, pretend that we're playing a simple sine wave. Normally, it would look like this:


    But in Sonic 1, the Z80 is being stopped quite a lot and during the stops Mega PCM isn't processing, so the wave isn't updated. If the sound chip does not receive a new wave signal, it will use the last one that was sent for playback, thus making the wave look like this instead:


    The grey areas here are the periods when the Z80 is stopped and the sound isn't being updated. If they were cut off in the graph, you'd get a proper sine wave like in the first graph, but long-lasting stops cause the wave to become deformed.

    This guide aims to shorten the times the Z80 is stopped. Unfortunately, there is no way to prevent the Z80 from being stopped, as you will need to stop it in order to allow the M68K to access the YM2612. This is why achieving a good playback quality of digital samples on a SEGA Genesis is a difficult task, that requires a lot of work.
    But here I have done everything for you, the only thing you need to do is follow my instructions below:

    We need to optimize these two mentioned factors the best way possible:
    1. SMPS
    2. DMA-transfers
    Let's start!

    2. Optimizing Z80 stops in SMPS

    Sonic 1 prefers to stop the Z80 as soon as SMPS starts and hold it stopped until the very end. This is understandable, since the sound engine may need access to the YM2612 or Z80 memory (to control Mega PCM) at anytime. However, this doesn't happen too often - most of the time SMPS is just updating its internal events and timers, as reading song files. Running their scripts takes considerably longer.
    So it tuns out that the Z80 is being stopped most of the time for no reason: access to the YM2612 occurs very rarely.

    If you stop Z80 at the very moment when certain SMPS routines need an access to YM or Z80 memory (but not for the whole time SMPS is running), you can get decent optimization. That's what this part of the guide is about.

    At first, you need to add some new macros into your disassembly. They noticeably simplify the process of changing the code, as they help to get rid of long parts of repeating code.

    Code (ASM):
    2. ; =============================================================
    3. stopZ80        macro
    4.         move.w    #$100,($A11100).l
    5.         nop
    6.         nop
    7.         nop
    9. @wait\@:    btst    #0,($A11100).l
    10.         bne.s    @wait\@
    11.         endm
    13. ; =============================================================
    15. startZ80    macro
    16.         move.w    #0,($A11100).l    ; start the Z80
    17.         endm
    19. ; =============================================================
    21. waitYM        macro
    22. @wait\@:    move.b    ($A04000).l,d2
    23.         btst    #7,d2
    24.         bne.s    @wait\@
    25.         endm
    Add these at the beginning of Sonic1.asm or where you like to keep your macros.
    Now, here we go!

    Go to label "sub_71B4C". At the very beginning, you will see the following code:

    Code (ASM):
    1.         move.w    #$100,($A11100).l    ; stop the Z80
    2.         nop
    3.         nop
    4.         nop
    This is the code to stop Z80. Thus, SMPS stops it at the beginning of its main routine execution and starts it at the end.
    You won't need this code anymore, so delete or comment it out.

    Now, go to label "loc_71C44":

    Code (ASM):
    1. loc_71C44:
    2.         move.b    ($A04000).l,d2        ;++
    3.         btst    #7,d2            ;++
    4.         bne.s    loc_71C44        ;++
    5.         move.b    #$2A,($A04000).l    ;++
    6.         move.w    #0,($A11100).l        ; start the Z80
    7.         rts
    Assuming you have installed Mega PCM already, this code will look as shown above. At the end there is a command to start Z80. The lines marked with (++) are the extra lines of code used for Mega PCM. It pulls a little trick required for Mega PCM (the description of the trick you may find in the first post). We'll need this one, but not here.

    Delete the whole code, except for the label name and an rts.

    From now on, SMPS no longer stops Z80 during its execution. But there are few places, where it's required to stop Z80.

    The first place is where SMPS sends Mega PCM a number of the sample to play. As I said, when M68K accesses Z80 memory, the Z80 itself must be stopped.

    Go to label "loc_71C88" and find the following line in the code:

    Code (ASM):
    1.         move.b    d0,($A01FFF).l
    We need the Z80 to be stopped before this command executes and to be started directly afterwards. Macros will help us to do so easily. Just replace it with:

    Code (ASM):
    1.         stopZ80
    2.         move.b    d0,($A01FFF).l
    3.         startZ80
    Another place is where SMPS commands Mege PCM to pause, unpause and stop playback.

    Go to the label "loc_71E7C" and find this line:

    Code (ASM):
    1.         move.b    #$7F,($A01FFF).l; pause DAC
    Append this command in the same way, so it will look like this:

    Code (ASM):
    1.         stopZ80
    2.         move.b    #$7F,($A01FFF).l; pause DAC
    3.         startZ80
    Now, find the label "loc_71EFE". Right above it, you'll see:

    Code (ASM):
    1.         move.b    #0,($A01FFF).l    ; unpause DAC
    Again, append this command as shown above.

    And finally, go find label "loc_725B6" and do this line:

    Code (ASM):
    1.         move.b    #$80,($A01FFF).l ; stop DAC playback
    The last thing left to do regarding SMPS, are the routines that access the YM2612.
    This time, the code replacement will be a little larger, plus we're going to add that extra code for Mega PCM, we took out in the beginning.

    Find the routine "sub_7272E" and replace the whole code with this:

    Code (ASM):
    2. sub_7272E:                ; XREF: loc_71E6A
    3.         stopZ80
    4.         waitYM
    5.         move.b    d0,($A04000).l
    6.         waitYM
    7.         move.b    d1,($A04001).l
    8.         waitYM
    9.         move.b    #$2A,($A04000).l
    10.         startZ80
    11.         rts
    12. ; End of function sub_7272E
    Then, go to "sub_72764" and replace it with:

    Code (ASM):
    1. sub_72764:                ; XREF: loc_71E6A; Sound_ChkValue; sub_7256A; sub_72764
    2.         stopZ80
    3.         waitYM
    4.         move.b    d0,($A04002).l
    5.         waitYM
    6.         move.b    d1,($A04003).l
    7.         waitYM
    8.         move.b    #$2A,($A04000).l
    9.         startZ80
    10.         rts
    11. ; End of function sub_72764
    If you've followed the above correctly, then congratulations! You've just optimized SMPS.
    Make sure you've done everything right - build the ROM and test it in emulator. The sound and the DAC samples should play as normal.

    If you have problems, you can always check youself by comparing your edits to a clean disassembly with this guide applied. You can find it below, see Attachments section at the end.

    3. Fixing SEGA PCM playback

    After playing the previous part, Puto's custom SEGA PCM playback code won't work properly, because it expects that Z80 is stopped by the time the code is executed, but it's no longer stopped by that point.

    To fix this, change the code at Sound_E1 as follows:

    Code (ASM):
    1. ; ===========================================================================
    2. ; ---------------------------------------------------------------------------
    3. ; Play "Say-gaa" PCM sound
    4. ; ---------------------------------------------------------------------------
    6. Sound_E1:
    7.     stopZ80
    8.         lea (SegaPCM).l,a2          ; Load the SEGA PCM sample into a2. It's important that we use a2 since a0 and a1 are going to be used up ahead when reading the joypad ports
    9.         move.l  #(SegaPCM_End-SegaPCM),d3           ; Load the size of the SEGA PCM sample into d3
    10.         move.b  #$2A,($A04000).l        ; $A04000 = $2A -> Write to DAC channel
    11. PlayPCM_Loop:
    12.         move.b  (a2)+,($A04001).l       ; Write the PCM data (contained in a2) to $A04001 (YM2612 register D0)
    13.         move.w  #$14,d0             ; Write the pitch ($14 in this case) to d0
    14.         dbf d0,*                ; Decrement d0; jump to itself if not 0. (for pitch control, avoids playing the sample too fast)  
    15.         sub.l   #1,d3               ; Subtract 1 from the PCM sample size
    16.         beq.s   return_PlayPCM          ; If d3 = 0, we finished playing the PCM sample, so stop playing, leave this loop, and unfreeze the 68K
    17.         lea ($FFFFF604).w,a0        ; address where JoyPad states are written
    18.         lea ($A10003).l,a1          ; address where JoyPad states are read from
    19.         jsr (Joypad_Read).w         ; Read only the first joypad port. It's important that we do NOT do the two ports, we don't have the cycles for that
    20.         btst    #7,($FFFFF604).w        ; Check for Start button
    21.         bne.s   return_PlayPCM          ; If start is pressed, stop playing, leave this loop, and unfreeze the 68K
    22.         bra.s   PlayPCM_Loop            ; Otherwise, continue playing PCM sample
    23. return_PlayPCM:
    24.     startZ80
    25.         addq.w  #4,sp
    26.         rts

    4. Optimizing VBlank routines

    Sonic 1 always tends to stop Z80 on DMA-transfers to VRAM or CRAM. But in fact, stopping processor isn't necessary in this case.

    When DMA-transfer executes, VDP freezes M68K, requests its bus and starts data transfer from any location (in RAM or ROM) to videomemory at high speeds. But the Z80 isn't affected by this as it can function normally. It is only when the Z80 wants to access 68K memory during this time (for example, if it's reading the ROM), it will freeze as well, waiting for 68K to respond. The 68K will only respond when DMA-trasfer is over and the processor continues working.

    However, even taking the case described above into account, forcing Z80 to stop doesn't make sense - it will stop itself when it's required. Moreover, the stop itself takes a while to execute, which means wasting processor cycles from both the Z80 and M68K side. But most importantly, emulators don't emulate Z80 stops during DMA. So, having excluded the forced stops that Sonic 1 performs, you'll get a huge optimization. On real hardware, you won't have optimization this big, however, you'll save some execution time.

    So, we are going to work with VBlank code. It starts from "loc_B10". Its code is quite big and includes number of routines.

    Find the label "loc_BC8", and you'll see this:

    Code (ASM):
    2.         move.w    #$100,($A11100).l
    4. loc_BC8:
    5.         btst    #0,($A11100).l
    6.         bne.s    loc_BC8
    Do you recognize the Z80 stop code here? We've seen one quite a lot already.
    Completely delete this code (including the label name and one command above it)

    Now, go to "loc_C22". Find and delete this line:

    Code (ASM):
    1.         move.w    #0,($A11100).l
    This command started Z80 after its being stopped.

    Go to "loc_C76". You'll see the Z80 stop code again:

    Code (ASM):
    1.         move.w    #$100,($A11100).l ; stop the Z80
    3. loc_C76:
    4.         btst    #0,($A11100).l    ; has Z80 stopped?
    5.         bne.s    loc_C76        ; if not, branch
    Delete it.

    Then, find "loc_D50" (it's few lines below). You'll see Z80 start command, after the stop in loc_C76.

    Code (ASM):
    1.         move.w    #0,($A11100).l
    Delete this.

    Now, you know the deal. Find and delete the similar code in the following places:

    loc_E64 (move.w #0,($A11100).l is few lines above)
    loc_1060 (few lines above)
    loc_10D4 (at the end, just before an rts)

    That's it!
    This was a hard and continuous guide, and if you successfully finished it, you deserve good praise (and a cookie). From now on, you can play digital samples and songs in near-perfect quality.

    5. Attachment

    A clean disassembly with Mega PCM installed and this guide applied:

    This one also contains a bonus: on the title screen, there plays a high-quality digital song from Sonic 1 Megahack: Ultra Edition!