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. 28-Oct-2012 - New guide: "How to increase playback quality with Mega PCM". 03-Mar-2013 - Added "Fixing SEGA PCM playback" section into the "How to increase playback quality with Mega PCM" guide. 30-Apr-2017 - Fixed all links as they went broken. * * * "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: https://www.dropbox.com/s/pq19s58bqqi67ab/MegaPCM_v11_Bin.7z?dl=0 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): Kos_Z80: incbin sound\z80_1.bin dc.w ((SegaPCM&$FF)<<8)+((SegaPCM&$FF00)>>8) dc.b $21 dc.w (((EndOfRom-SegaPCM)&$FF)<<8)+(((EndOfRom-SegaPCM)&$FF00)>>8) incbin sound\z80_2.bin even Delete everything, and put this instead: Code (ASM): include 'MegaPCM.asm' Now, goto 'SoundDriverLoad' routine and replace all the code with this: Code (ASM): SoundDriverLoad: ; XREF: GameClrRAM; TitleScreen nop move.w #$100,d0 move.w d0,($A11100).l move.w d0,($A11200).l lea (MegaPCM).l,a0 lea ($A00000).l,a1 move.w #(MegaPCM_End-MegaPCM)-1,d1 @Load: move.b (a0)+,(a1)+ dbf d1,@Load moveq #0,d1 move.w d1,($A11200).l nop nop nop nop move.w d0,($A11200).l move.w d1,($A11100).l rts ; 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): btst #7,($A01FFD).l beq.s loc_71B82 move.w #0,($A11100).l ; start the Z80 nop nop nop nop nop bra.s sub_71B4C ; =========================================================================== loc_71B82: Delete or comment out this code. Spoiler 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): btst #3,d0 bne.s loc_71CAC Spoiler 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): move.b ($A04000).l,d2 btst #7,d2 bne.s loc_71C44 move.b #$2A,($A04000).l Spoiler "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): move.b #$7F,($A01FFF).l; pause DAC Spoiler Playing sample $7F executes pause command. Next, find 'loc_71EDC' and replace all the code until label 'loc_71EFE' with this: Code (ASM): loc_71EDC: adda.w d3,a5 dbf d4,loc_71EC4 lea $340(a6),a5 btst #7,(a5) beq.s @UnpauseDAC btst #2,(a5) bne.s @UnpauseDAC move.b #-$4C,d0 move.b $A(a5),d1 jsr sub_72722(pc) @UnpauseDAC: move.b #0,($A01FFF).l ; unpause DAC Spoiler 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): move.b #$80,($A01FFF).l ; stop DAC playback Spoiler 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): ; --------------------------------------------------------------- ; DAC Samples Table ; --------------------------------------------------------------- DAC_Entry $08, Kick, dpcm ; $81 - Kick DAC_Entry $08, Snare, dpcm ; $82 - Snare DAC_Entry $1B, Timpani, dpcm ; $83 - Timpani dc.l 0,0 ; $84 - <Free> dc.l 0,0 ; $85 - <Free> dc.l 0,0 ; $86 - <Free> dc.l 0,0 ; $87 - <Free> DAC_Entry $12, Timpani, dpcm ; $88 - Hi-Timpani DAC_Entry $15, Timpani, dpcm ; $89 - Mid-Timpani DAC_Entry $1B, Timpani, dpcm ; $8A - Mid-Low-Timpani DAC_Entry $1D, Timpani, dpcm ; $8B - Low-Timpani MegaPCM_End: ; --------------------------------------------------------------- ; DAC Samples Files ; --------------------------------------------------------------- IncludeDAC Kick, bin IncludeDAC Snare, bin IncludeDAC Timpani, bin 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): 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: http://selbi.se.funpic.de/s1sndcomp.exe 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): 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: https://www.dropbox.com/s/5oj9ssgekt7cvv1/V_Hurt.wav?dl=0 Move it to 'dac' folder in you disassembly. In the very end of MegaPCM.asm file, just before the 'even', add: Code (ASM): 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): DAC_Entry $08, Kick, dpcm ; $81 - Kick DAC_Entry $08, Snare, dpcm ; $82 - Snare DAC_Entry $1B, Timpani, dpcm ; $83 - Timpani DAC_Entry $07, V_Hurt, pcm+pri ; $84 - 'Hurt' voice dc.l 0,0 ; $85 - <Free> dc.l 0,0 ; $86 - <Free> dc.l 0,0 ; $87 - <Free> DAC_Entry $12, Timpani, dpcm ; $88 - Hi-Timpani DAC_Entry $15, Timpani, dpcm ; $89 - Mid-Timpani DAC_Entry $1B, Timpani, dpcm ; $8A - Mid-Low-Timpani 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): ; --------------------------------------------------------------------------- ; Subroutine to play a DAC sample ; --------------------------------------------------------------------------- PlaySample: move.w #$100,($A11100).l ; stop the Z80 @0 btst #0,($A11100).l bne.s @0 move.b d0,$A01FFF move.w #0,($A11100).l 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): jsr (PlaySound_Special).l with Code (ASM): moveq #$FFFFFF84,d0 jsr PlaySample That's it. Enjoy! "Mega PCM" source code "Mega PCM" has an open source code, enjoy! Version 1.1 Source Code: https://www.dropbox.com/s/z15jzka83fkvvkt/MegaPCM_v11_SourceCode.7z?dl=0 Version 1.0 Source Code: https://www.dropbox.com/s/meksller11xkkde/MegaPCM_v10_SourceCode.7z?dl=0 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.