/*********************************************************** From: Germans DM Subject: SCC/PSG emulation stuff.... Here are the files and changes for the SCC/PSG emulation in fMSX ehm, 1.0 I think for Unix. I made this under Linux 1.2.1 using GCC version 2.5.8, but the code isn't specific for this, so I hope you can use it in the MSDOS and maybe Windows ports as well. Here is a short explaination of what the SCC actually does, so it's easyer to find bugs. The SCC is a 5-channel 32-byte signed looped sample sound generator. It has 4 sample "memory" (you can't really call 32 bytes memory, can you :)) areas to use and this is the reason why channels 4 and 5 (usually called channels 7 and 8 because with Konami music, PSG takes channels 1..3) use the same sample. These samples can be played at various frequencies (the same formula is used as with the PSG) and at various volumes (also the same as on PSG). Inherently however, the sound generated by the SCC is usually much louder, so if emulating this on another soundcard or machine keep it in mind. Controlling all this is done by memory access to locations 9800..98FF on one of the ROM-maps in the Konami ROMs. I guess you have more information on that, but it only seems to work if the line: if((ROMMapper[I][3]==0x3F)&&((Addr&0xFF00)==0x9800)) is changed into: if((ROMMapper[I][3]!=0x3F)&&((Addr&0xFF00)==0x9800)) I don't know why, maybe the bits in this location are turned around just like the SSL, or maybe it's some other value you need to check for instead of 0x3F. Also, the SCC thus has 256 registers instead of 16, so some changes are needed throughout MSX.c to do that as well. I did that in my own version, but I won't distribute anything before you incorporate this in the official version. The SCC locations are/do the following: 9800..981F = data area for signed looped sample #1 (channel 4) 9820..983F = data area for signed looped sample #2 (channel 5) 9840..985F = data area for signed looped sample #3 (channel 6) 9860..987F = data area for signed looped sample #4 (channels 7 and 8) 9880 = frequency for channel 4 (Z80 word) 9882 = frequency for channel 5 (Z80 word) 9884 = frequency for channel 6 (Z80 word) 9886 = frequency for channel 7 (Z80 word) 9888 = frequency for channel 8 (Z80 word) 988A = volume for channel 4 (0..15) 988B = volume for channel 5 (0..15) 988C = volume for channel 6 (0..15) 988D = volume for channel 7 (0..15) 988E = volume for channel 8 (0..15) 988F = turn on/off channels 9890..98FF = undefined in original versions (up til Space Manbow) The turn on/off channel bits (988F) are as follows: 7 6 5 4 3 2 1 0 x x x ch. 8 ch. 7 ch. 6 ch. 5 ch. 4 The '1' bits mean channel is on and the '0' bits mean channel is off. This seems more logical than with the PSG where it is exactly the other way round, but I've heard that has something to do with electronics. Anyway, the GUS has a big bank of DRAM with 32 channels which each have poin- ters into this DRAM to play their samples (looped or not, signed or not). So it is theoretically (and also practically) possible to map the area from 9800 to 987F DIRECTLY into GUS memory (which ofcourse speeds up things a bit). For SoundBlaster I'd say it's easy to just setup some sort of home-brew mixer that creates the final output data by adding the 5 channels. This is rather time- consuming though which is prettymuch a burdon when also needing this time for Z80 and VDP emulation, so maybe it's easyer to emulate this with AdLib some- how. On to the implementation. I've made a new module called SCCGUS.c which handles all the I/O to the GUS. It's very important under Linux that the user has root priviledges (either is root herself or with chmod +s became root temporarily by running the program) because the code is writing to I/O ports directly. I think there are official GUS handlers under Linux, but I haven't figured those out yet. The first part of SCCGUS are just some basic routines to control the GUS via direct I/O. This is basic GUS code, nothing special. Then, with gus_init and the correct I/O baseport (it can be probed, but I'm a lazy coder) everything is set up for PSG and SCC emulation. I generated a square wave for the PSG and set up the sample pointers of the PSG channels to this area, then I set up the SCC sample pointers to some area after that where the MSX program can put it's internal sample data. Finally, I reset the values and everything is set up. gus_done just turns off the speaker. do_freq calculates the final value that has to be sent to the GUS as good as possible with integers (assumed are 32-bit integers here). Then it writes the frequency to the desired channel. This routine is the same for PSG and SCC emulation because they use the same frequency formula. do_vol sets the volume of the GUS directly to a given SCC volume. Again here it's usually done with fancy volume slides to prevent the ticking, but an SCC is no SCC without proper ticking, so I left out the fancy smooth stuff and just set the volume. The GUS uses a logarithmic volume scale, so you need to add something to a volume to get it twice as loud instead of multiplying the volume by 2. I just made an approximation by defining an equal range from A000 to BFFF for the SCC volumes. A000 is very quiet and BFFF is reasonably loud. Experiment with this if you want, but keep in mind that when using extreme volumes like 0000 or FFFF, the GUS generates strange bleeps and squeaks because it can't handle the abrupt change. do_psg_vol does exactly the same, but for the region 7000 to 8FFF which is quite silent. This is about the difference in volume between SCC and PSG, but maybe my stereo is not so good, so I guess you can check it with the real thing yourself. do_key changes the on- or off-state of the channels by comparing the current state with the new state (to prevent TOO much ticking) and then changing the different ones. do_psg_key does the same for PSG. SCCOut is your defined routine to set the SCC registers. It calls some of the above routines accordingly (this all speaks for itself when you look in the code). PSGOut, same thing. That's all. I hope this helps and keep in touch if you are going to release a new version. Let's keep MSX alive and kicking, Desmond Germans (Simm / Analogue) germans@cs.vu.nl npgerman@nat.vu.nl Jaagweg 15 1452 PB Ilpendam The Netherlands ***********************************************************/ /* SCC/PSG emulation on GUS for fMSX Unix/X version */ /* by Desmond Germans (Simm / Analogue) */ #include "Z80.h" /* for those byte/word thingies */ #include #include /* for ioperm */ #include /* GUESS! */ /* GUS GF1 relative addresses */ #define GF1_STATUS 0x0006 #define GF1_CURVOX 0x0102 #define GF1_CMD 0x0103 #define GF1_DATALO 0x0104 #define GF1_DATAHI 0x0105 #define GF1_DRAM 0x0107 /* GUS GF1 commands */ #define DMACTL 0x41 #define DRAMLO 0x43 #define DRAMHI 0x44 #define TIMCTL 0x45 #define SMPCTL 0x49 #define RESET 0x4C #define VOXCTL 0x00 #define FRQCTL 0x01 #define BEGHI 0x02 #define BEGLO 0x03 #define ENDHI 0x04 #define ENDLO 0x05 #define VOLRAT 0x06 #define VOLBEG 0x07 #define VOLEND 0x08 #define SETVOL 0x09 #define GETVOL 0x89 #define SETHI 0x0A #define SETLO 0x0B #define GETHI 0x8A #define GETLO 0x8B #define PANPOS 0x0C #define VOLCTL 0x0D #define ACTVOX 0x0E #define IRQSRC 0x8F /* GUS GF1 values */ #define SPKON 1 #define SPKOFF 3 #define VOXON 0 #define VOXONLOOP 8 #define VOXOFF 3 #define VOLONDOWN 64 #define VOLONUP 0 #define VOLOFF 3 /* GF1 addresses of user's computer */ word gf1_base; word gf1_status; word gf1_curvox; word gf1_cmd; word gf1_datalo; word gf1_datahi; word gf1_dram; word scc_freq[8]; /* actually scc_and_also_psg_frequency[8] */ byte scc_vol[8]; byte scc_state; byte psg_state; int scc_permission; /*---------------------------------------------------------------------------*/ /* GUS BASIC ROUTINES */ /*---------------------------------------------------------------------------*/ void gus_outb(byte reg,byte data) { outb(reg,gf1_cmd); outb(data,gf1_datahi); } void gus_outw(byte reg,word data) { outb(reg,gf1_cmd); outw(data,gf1_datalo); } byte gus_inb(byte reg) { outb(reg,gf1_cmd); return inb(gf1_datahi); } word gus_inw(byte reg) { outb(reg,gf1_cmd); return inw(gf1_datalo); } void gus_voice(byte voice) { outb(voice,gf1_curvox); inb(gf1_status); inb(gf1_status); inb(gf1_status); inb(gf1_status); inb(gf1_status); inb(gf1_status); inb(gf1_status); } void gus_poke(unsigned address,byte data) { word addrlo = (word)(address & 0x0000FFFF); word addrhi = (word)((address >> 16) & 0x0000000F); gus_outw(DRAMLO,addrlo); gus_outb(DRAMHI,addrhi); outb(data,gf1_dram); } void gus_wait() { inb(gf1_status); inb(gf1_status); inb(gf1_status); inb(gf1_status); inb(gf1_status); inb(gf1_status); inb(gf1_status); } int gus_status() { return inb(gf1_status); } void gus_speaker_on() { outb(SPKON,gf1_base); } void gus_speaker_off() { outb(SPKOFF,gf1_base); } void gus_reset() { int i; gus_outb(RESET,0); gus_wait(); gus_outb(RESET,1); gus_wait(); gus_outb(DMACTL,0); gus_outb(TIMCTL,0); gus_outb(SMPCTL,0); gus_outb(ACTVOX,0xCE); gus_status(); gus_inb(DMACTL); gus_inb(SMPCTL); gus_inb(IRQSRC); for(i = 0; i < 14; i++) { gus_voice(i); gus_outb(VOXCTL,VOXOFF); gus_outb(VOLCTL,VOLOFF); } gus_inb(DMACTL); gus_inb(SMPCTL); gus_inb(IRQSRC); gus_outb(RESET,7); for(i = 0; i < 14; i++) { gus_voice(i); gus_outb(VOLRAT,63); gus_outw(SETVOL,28000); gus_outb(PANPOS,(i&1)?3:12); } } int gus_init(word base_port) { int i; scc_permission = 0; if(ioperm(0x220,0x3FF-0x220,1)) return 0; scc_permission = 1; gf1_base = base_port; gf1_status = base_port + GF1_STATUS; gf1_curvox = base_port + GF1_CURVOX; gf1_cmd = base_port + GF1_CMD; gf1_datalo = base_port + GF1_DATALO; gf1_datahi = base_port + GF1_DATAHI; gf1_dram = base_port + GF1_DRAM; gus_reset(); /* setup PSG sample regions */ /* square wave */ for(i = 0; i < 16; i++) gus_poke(i,127); for(i = 16; i < 32; i++) gus_poke(i,128); gus_voice(0); gus_outw(VOXCTL,VOXOFF); gus_outw(SETLO,0x0000); gus_outw(SETHI,0x0000); gus_outw(BEGLO,0x0000); gus_outw(BEGHI,0x0000); gus_outw(ENDLO,0x3E00); gus_outw(ENDHI,0x0000); gus_voice(1); gus_outw(VOXCTL,VOXOFF); gus_outw(SETLO,0x0000); gus_outw(SETHI,0x0000); gus_outw(BEGLO,0x0000); gus_outw(BEGHI,0x0000); gus_outw(ENDLO,0x3E00); gus_outw(ENDHI,0x0000); gus_voice(2); gus_outw(VOXCTL,VOXOFF); gus_outw(SETLO,0x0000); gus_outw(SETHI,0x0000); gus_outw(BEGLO,0x0000); gus_outw(BEGHI,0x0000); gus_outw(ENDLO,0x3E00); gus_outw(ENDHI,0x0000); /* setup SCC sample regions */ gus_voice(3); gus_outw(VOXCTL,VOXOFF); gus_outw(SETLO,0x0000); gus_outw(SETHI,0x0008); gus_outw(BEGLO,0x0000); gus_outw(BEGHI,0x0008); gus_outw(ENDLO,0x3E00); gus_outw(ENDHI,0x0008); gus_voice(4); gus_outw(VOXCTL,VOXOFF); gus_outw(SETLO,0x4000); gus_outw(SETHI,0x0008); gus_outw(BEGLO,0x4000); gus_outw(BEGHI,0x0008); gus_outw(ENDLO,0x7E00); gus_outw(ENDHI,0x0008); gus_voice(5); gus_outw(VOXCTL,VOXOFF); gus_outw(SETLO,0x8000); gus_outw(SETHI,0x0008); gus_outw(BEGLO,0x8000); gus_outw(BEGHI,0x0008); gus_outw(ENDLO,0xBE00); gus_outw(ENDHI,0x0008); gus_voice(6); gus_outw(VOXCTL,VOXOFF); gus_outw(SETLO,0xC000); gus_outw(SETHI,0x0008); gus_outw(BEGLO,0xC000); gus_outw(BEGHI,0x0008); gus_outw(ENDLO,0xFE00); gus_outw(ENDHI,0x0008); gus_voice(7); /* remember, 6 and 7 have the same sample */ gus_outw(VOXCTL,VOXOFF); gus_outw(SETLO,0xC000); gus_outw(SETHI,0x0008); gus_outw(BEGLO,0xC000); gus_outw(BEGHI,0x0008); gus_outw(ENDLO,0xFE00); gus_outw(ENDHI,0x0008); /* now GUS memory from 0x00000400..0x0000047F is a direct map of SCC registers 00..7F */ for(i = 0; i < 8; i++) { scc_freq[i] = 0; scc_vol[i] = 0; } gus_speaker_on(); scc_state = 0; psg_state = 0x3F; return 1; } void gus_done() { if(!scc_permission) return; gus_speaker_off(); } void do_freq(byte channel) { word gus_freq; /* official formula to calculate frequency: gus = ((((111760 / number) * 512) / 44100) * 2 */ if(scc_freq[channel]) gus_freq = ((57221120 / scc_freq[channel]) / 1378) << 1; else gus_freq = 0; gus_voice(channel); gus_outw(FRQCTL,gus_freq); gus_wait(); } void do_vol(byte channel,byte volume) { gus_voice(channel); gus_outw(SETVOL,0xA000 + ((word)volume << 10)); gus_wait(); } void do_psg_vol(byte channel,byte volume) { gus_voice(channel); gus_outw(SETVOL,0x7000 + ((word)volume << 10)); gus_wait(); } void do_key(byte bits) { if(scc_state == bits) return; gus_voice(3); if((scc_state & 1) != (bits & 1)) if(bits & 1) gus_outb(VOXCTL,VOXONLOOP); else gus_outb(VOXCTL,VOXOFF); gus_voice(4); if((scc_state & 2) != (bits & 2)) if(bits & 2) gus_outb(VOXCTL,VOXONLOOP); else gus_outb(VOXCTL,VOXOFF); gus_voice(5); if((scc_state & 4) != (bits & 4)) if(bits & 4) gus_outb(VOXCTL,VOXONLOOP); else gus_outb(VOXCTL,VOXOFF); gus_voice(6); if((scc_state & 8) != (bits & 8)) if(bits & 8) gus_outb(VOXCTL,VOXONLOOP); else gus_outb(VOXCTL,VOXOFF); gus_voice(7); if((scc_state & 16) != (bits & 16)) if(bits & 16) gus_outb(VOXCTL,VOXONLOOP); else gus_outb(VOXCTL,VOXOFF); scc_state = bits; } void do_psg_key(byte bits) { if(psg_state == (bits & 0x3F)) return; gus_voice(0); if((psg_state & 1) != (bits & 1)) if(bits & 1) gus_outb(VOXCTL,VOXOFF); else gus_outb(VOXCTL,VOXONLOOP); gus_voice(1); if((psg_state & 2) != (bits & 2)) if(bits & 2) gus_outb(VOXCTL,VOXOFF); else gus_outb(VOXCTL,VOXONLOOP); gus_voice(2); if((psg_state & 4) != (bits & 4)) if(bits & 4) gus_outb(VOXCTL,VOXOFF); else gus_outb(VOXCTL,VOXONLOOP); psg_state = bits & 0x3F; } void SCCOut(byte R,byte V) { if(!(R & 0x80)) /* set sample for the channels */ gus_poke((word)R + 0x0400,V); switch(R) { /* set frequency for the channels */ case 0x80: scc_freq[3] = (scc_freq[3] & 0xFF00) | V; do_freq(3); break; case 0x81: scc_freq[3] = (scc_freq[3] & 0x00FF) | ((word)V << 8); do_freq(3); break; case 0x82: scc_freq[4] = (scc_freq[4] & 0xFF00) | V; do_freq(4); break; case 0x83: scc_freq[4] = (scc_freq[4] & 0x00FF) | ((word)V << 8); do_freq(4); break; case 0x84: scc_freq[5] = (scc_freq[5] & 0xFF00) | V; do_freq(5); break; case 0x85: scc_freq[5] = (scc_freq[5] & 0x00FF) | ((word)V << 8); do_freq(5); break; case 0x86: scc_freq[6] = (scc_freq[6] & 0xFF00) | V; do_freq(6); break; case 0x87: scc_freq[6] = (scc_freq[6] & 0x00FF) | ((word)V << 8); do_freq(6); break; case 0x88: scc_freq[7] = (scc_freq[7] & 0xFF00) | V; do_freq(7); break; case 0x89: scc_freq[7] = (scc_freq[7] & 0x00FF) | ((word)V << 8); do_freq(7); break; /* set volume for the channels */ case 0x8A: do_vol(3,V); break; case 0x8B: do_vol(4,V); break; case 0x8C: do_vol(5,V); break; case 0x8D: do_vol(6,V); break; case 0x8E: do_vol(7,V); break; /* set on/off bits */ case 0x8F: do_key(V); break; } } void PSGOut(byte R,byte V) { switch(R) { case 0: scc_freq[0] = (scc_freq[0] & 0xFF00) | V; do_freq(0); break; case 1: scc_freq[0] = (scc_freq[0] & 0x00FF) | ((word)(V & 15) << 8); do_freq(0); break; case 2: scc_freq[1] = (scc_freq[1] & 0xFF00) | V; do_freq(1); break; case 3: scc_freq[1] = (scc_freq[1] & 0x00FF) | ((word)(V & 15) << 8); do_freq(1); break; case 4: scc_freq[2] = (scc_freq[2] & 0xFF00) | V; do_freq(2); break; case 5: scc_freq[2] = (scc_freq[2] & 0x00FF) | ((word)(V & 15) << 8); do_freq(2); break; case 7: do_psg_key(V); break; case 8: do_psg_vol(0,V); break; case 9: do_psg_vol(1,V); break; case 10: do_psg_vol(2,V); break; } }