| Lesson P11
Tilemap graphics on the MSX2
The MSX2 VDP is a bitmap based system, however it's not memory mapped, which means we still have to access it with OUT commands...
It does,however, have the ability to give commands to the VDP, and the VDP will carry out those tasks for us without the Z80 doing any work! Effectively we're doing multi threading on the 8-bit!
In todays lesson, we'll look at some code that mimics the Tilemaps we've already looked at, and uses the HMMM copy command to copy 8x8 chunks of memory quickly on the VDP!
|the MSX2 VDP uses
various registers to control it's settings... we operate these via OUT
It also has some status registers, which we will need to check to see if our jobs are finished, as it can only do one task at a time!
The table here is just a summary for quick reference.
If you want to know all the details, you should download the v9938 documentation here
The VDP uses registers numbered 0-46...
To Set a register, first send the value to put in the register to the control port, then send the register number +128 to the control port.
EG lets set Reg 15 to 2...
There are some special registers
R#15 sets the Status register
R#17 sets the indirect register... this allows us to set a range of registers in one go via port &9B... this allows us to use a sequence of OUTI commands to set registers, when we're giving the VDP jobs to do we usually need to set 32-46... so we set the indirect register to 32... then OUTI away!
We only need to use 3 commands today...
HMMV is a flood fill command, it clears the screen quickly and gets pixels to screen faster than any other command
HMMC is used to copy byte data from the normal Z80 memory to the VDP ram - we use this to create our tiles! - it's the slowest of our commands
HMMM this is our workhorse... once our tiles are in VRAM, it gets them on our screen.. it's a bit slower than HMMV, but much much faster than HMMC
We don't use it today, but and alternative to HMMM is LMMM - it does the same job, but can do transparency... its slower than HMMM, but is the best way to do sprites with transparent backgrounds.
We have to check CE to see if it's 0 - if it isn't the VDP is busy and we have to wait as it's doing another command, we need to select S#2
If it's busy the VDP can still do direct memory access while it's busy though, and ChibiAkumas uses this to draw the background gradient with the Z80 WHILE the VDP is blitting the mountain tiles, and flood filling the black areas of the background!
The default Interrupt handler relies on S#0 selected... this is because the MSX will fire interrupts indefinitely until S#0 is read... therefore we MUST disable interrupts until S#0 is selected again, or write our own interrupt handler!
Here are the commands we'll be looking at today!
Here are the main registers we'll be looking at today for our commands!
Here are the Status Registers we need:
Special Register Selection
We select our Status register, and the AutoIncrement Indirect port (&9B) with the registers below:
|The bad news is this is just the
beginning... check out the MSX page, or get the V9938 manual to see the
The good news is you can just use the code provided in these tutorials to do the work for you, and the V9990 version will work exactly the same for the V9K accelerator
|When we call our
commands today, we need to pass them the address of a set of
definitions our functions will push these values into the registers.
Here is an example of one of the larger commands that uses many of the registers... some (Like HMMV / Fill) don't need SX and SY
and remember, if you use my functions, you can support the V9990 with the same byte definitions as this - where needed, the V9K code will convert them to the format needed for the V9990!
|Before we can do
any commands, we need to check if the VDP is busy... to do this we need
to select status register 2! (S#2)... the selected status register is
defined by register R#15
We do this by sending the number of the Status Register (2 for S#2) we want to select to the control port (&99)
Now we need to tell the VDP the register we want to change (15 for R#15)... we have to add 128 to the number of the register we want to change
Now we need to check if CE (bit 0) is 0 - if it's 1 we need to wait some more... we read in the Status register from the control port (&99)
We use RRA to pop off Bit 0, if it's zero (No Carry) we don't need to wait any more, if it's 1, we need to keep waiting...
Remember, We must disable interrupts while we're doing this! - once we're done, we need to set the Selected Status Register to 0
|We're going to
write the contents of our definitions to the registers (Shown in
Tile_MyHMMM above), we want to fill them all one after another, and we
can do this with the Indirect register
We're going to use the Indirect register port (&9B)... to select the register we want to start filling, we set R#17 to 32 (SY)... each OUTI will then fill registers R#32, R#33, R#34 and so on...
in this example we want to do 15 OUTI's... which will make our code a bit messy, so we specify them as bytecode (&ED,&A3) is the bytecode for an OUTI command!
The code to the right does HMMM and HMMC, but HMMV is basically the same, just starting from R#36, and with fewer OUTI commands - because we don't need SX and SY... the important thing is the command that goes into R#46 ... and that's in our Tile_HMMM definition as the last byte... It DOES need to be the last byte - as the VDP starts processing
|We can even use the indirect register as a
fixed register pointer...
If we set R#17 to 32+128 then all our OUT commands to port &9B will go to register R#32 ...
Adding 128 to the register number we write to R#17 does the job of telling the VDP not to increment!
|First let's do the most basic thing, and Clear the
Before we can do anything else, we need to make sure the VDP isn't busy doing other things, we'll use VDP_FirmwareSafeWait... this function will loop until the VDP is ready, and will keep the status register correct so the interrupt handler doesn't have problems.
The best command for this is HMMV... it fills a square area with a single byte... we'll define a set of HMMV bytes in 'CLS_MyHMMV
We'll set DX and DY to 0 - to select the top left corner of the screen... then we'll set NX to 256 and NY to 192 (the size of the screen)
We want to fill the screen with color 0, so we set the Byte (R#44) to &00
We can then use VDP_HMMV which will push our CLS_MyHMMV definitions into the registers and start the job!
|Once again, we're
showing the text and Chibiko bitmap... note we've not set any color
information in this example..
If you want to see colored tiles, please take a look at my GrimeZ80 project, which uses this tile code, and Tile color palettes!
|We can create the
bitmap data using my 'AkuSprite'
editor (included in Sources.7z)... For this example you want to use 'Save RAW
This will export the bitmap as a 16 color bitmap, split into simulated 'tiles'
|Our tiles are held
in our program memory (the normal 64k of the Z80)... but we need to get
them to the VDP VRAM to use them later.
We do this with the HMMC command....
The VDP has a virtual screen of 256x1024... the area (0,0)-(255,191) is show on screen, if we used a double buffer, it would use the area (0,256)-(256,447)
For simplicity, we'll just use the area (0,512)-(256,1023) for out tiles!
with 256 pixels on each line, that means we can store 32 tiles per line, so we need to take the first 5 bits of the tile number, and multiply them by 8 to get the X position to store out tile.
The Y position will be in the remaining 3 bits, if we multiply them by 8, and add 512, that will be our Y co-ordinate for the tile...
Of course each tile is 8x8... and since we're only using a single byte in this calculation, we're only supporting up to 256 tiles.
We call HMMC, which will set up the VDP to receive more data ... annoyingly we need to give it the first byte in MyHMMCByte.
once we've called call VDP_HMMC_Generated ... we can send the rest of our data, we just do this by OUTing the data to data port (&9B)
|There is an alternative version of this in in
it's called 'CopyBWTilesToVdp' which uses 2 bit data from our font to define tiles...
it's basically the same but has conversions to change the 1bpp data into 4bpp... please see the sources.7z if you want to see it.
|When we want to get
a tile to the screen, we use 'CopyTileToScreen'
This function will set the tile at X,Y position in D,E to tile number HL... This is achieved by copying an 8x8 area from our defined tiles (in the off screen area at Ypos 512+) to the screen.
The X,Y position is in 8 pixel units, so the screen is defined as (0,0)-(32,24)... in this way we can use the MSX2 or V9K as a tilemap in the same way as the MSX1
We effectively reverse the calculations we did before, and work out the tile position in the definitions, and copy it to the screen.
|When we want to
draw the Chibiko bitmap, we will use
the 'FillAreaWithTiles function... this takes an XY position
in BC, a Width and Height in HL, and a start tile number in DE...
This function is almost identical to the previous ones, we just use CopyTileToScreen to do our work instead of an OUT command with the MSX1/SMS or a LD (HL) command on the Gameboy...
if we call the routine with BC set to &0303 , HL set to &0606 and DE set to 128... the following tiles will be set on screen
| Lesson P12
Joypad reading on Master System,GameGear, Gameboy and Gameboy Color
Reading in the controller on a game system like the Gameboy, Mastersystem or Game-gear is much easier than full-keyboard systems like the CPC and MSX...
We're going to read in all the buttons, and store them in registers, in the identical format as we did on the other systems, allowing us to use the GB/SMS/SGG in the same way, as the other systems!... We'll set bits 0-7 to U-D-L-R-F1-F2-F3-Pause in H for player 1, and L for player 2 (where available) ... and bit that is 1 is a button that is not pressed.. 0 means a button is held down
|Joypad reading on the Master System and Game Gear|
|Although they're usually the same, Joypad
reading is one of the times the SMS and GG are quite different!
The SMS has two joypads... and the GG has an extra button (Start!) on a different port! so the commands we run are quite different depending on the machine we're using.
|The GameGear and
Master System use 3 ports in total, but there are differences!
The Gamegear has an extra button! 'Start' accessible from bit 7 of Port &00... The Gamegear does not have any player 2 controls
The Mastersystem has no start button, but it does have a second player paddle!
note:Button TH has no use on the normal SMS gamepads, so is not used in these examples.
|We'll read Player 2
need 2 of the bits from &DC, and 4 bits from &DD... we
need to swap the order of the &DC bits...
As we have no Fire 3 or Select, we need to set the top 2 bits to 1 with an OR command
Player 2 is saved in L...
Next we handle player 1, we read in 6 bits from &DC, and add in the topmost bit of port &00 (Start) on the GameGear ... there is no start button on the Master System
Player 1 is saved in H...
This returns HL
|Joypad reading on the Gameboy and Gameboy Color|
|As the Gameboy does not use OUT commands, the
joypad controls are mapped to memory address &FF00...
Strangely it's split into two halves, and we have to write to this address first, to select which half we want to read!
|Joypad reading is
all done with memory address &FF00
Only Bits 0-3 in this address contain the state of the buttons, we first need to select which half of the joystick we want to read.
If we write %11101111 to &FF00 (Bit 4 is zero) ... we will select the direction controls... and the next read from &FF00 will get Down, Up, Left, Right in bits 0-3
If we write %11011111 to &FF00 (Bit 5 is zero) ... we will select the button controls... and the next read from &FF00 will get Start, Select, Button B, Button A in bits 0-3
|We start by reading
in the direction controls.
So we set bit 4 to 0, and write to &FF00
You'll notice LR and UD are in the wrong order... we use L as a temporary register to swap these around.
We then set bit 5 to 0, and write to &FF00 so we can read in the buttons
We then shift in the button bits into H.
Finally we write 255 to L, as there is no player 2 controller.
| Lesson P13
- Palette definitions on the Amstrad CPC and CPC+
We've looked at how to draw bitmaps and text on all our systems, but we need to make sure the system color palette matches what we need for our bitmap.
The CPC+ has better colors than the CPC... and it's the PLUS that we'll use as the palette template for all our other systems!
|We're going to use 1 nibble per color channel, in the
format -GRB ... this is the native palette for the CPC+ ... but we're
convert it so we can use this one definition on all our systems...
making supporting so many systems much easier.
for example &000F is bright blue, and &0800 is 'Middle Green"
Some systems have 4 colors (0-3), others 16 (4-15)... and we can also use color 16 to set the border color where appropriate
We're going to use the command 'SetPalette' on all systems, for example, to set Color 1 to G=16 R=8 B=4... we can do:
|By default The CPC+ features are disabled, leaving us
only a regular CPC... to use the PLUS features, we need to send a
series of 17 bytes to the CRTC at port &BCxx
once the CPC+ features are on, a special bank of 16k 'ASIC' memory will become available, which we can write to between &4000-&7FFF in the same way as our normal memory.
To Turn it on we use:
To turn it off we use:
NOTE: When using the ASIC memory, We should make sure that we're using the 128k banks are disabled, or it can cause problems with some 128k ram upgrades. Mode C1/C3 are OK to use as they page in at &C000
|The bytes we have to send look strange, but
don't think changing them will have any effect - if you change
them they will not work, and the bytes have no particular
When the Plus Came out, AMS lied and claimed only cartridge games could use the plus features, so they we're intentionally random so no-one could figure out how to use CPC+ features on non-cartridge games! Fortunately we know them now, so we can use PLUS on our tape and disk games!
|We have to page in
the plus registers so we can write our palette definition into the PLUS
Each color definition is 2 bytes, and there are definitions for the main 16 colors, the border, and CPC+ Hardware Sprites
We send two bytes in the format &-GRB ... where the - nibble is unused, G is Green, R is Red and B is blue
|We're using one Nibble per color, so we have a
total possible palette of 4096 colors available to us.
We're actually going to use this color definition format for all our systems - some systems support fewer colors, and some support more, but one nibble per color is a good balance and gives us plenty of colors for smooth fading, but doesn't go overboard!
|When we're going to
palette definition on the regular CPC, we're only going to use the top
2 bits of the palette definition, so our RGB definitions should
use F,8,0 .. anything in between will round DOWN!
Setting the color involves two commands
ld bc,&7fpp - where pp is the palette number
out (c),a - where a is the hardware color
We keep the hardware colors in a lookup table, which we use to covert the PLUS format colors for the Classic CPC!
|We need to read in
nibbles, and bit shift them into the appropriate positions, we then use
as an offset for the Lookup table we just saw
To get the correct line, we need to multiply the green component by 9, and the Red component by 3, then we just add the blue (times 1!)
This gives us the correct position for the closest hardware color the CPC can do...
We'll use similar code later again on other 'fixed palette' systems that can't do RGB... and you can easily convert this code to use these palette definitions on systems that aren't even covered by these tutorials.
setting the colors at the hardware level, so we're using the firmware
color codes, but if you prefer, you could use the system
numbers (0-26) and use Firmware calls to set the palette if you
These tutorials tend not to use the firmware, but if it's up to you, so use whatever you prefer... but it's not possible to set PLUS colors via a firmware call!
| Lesson P14
- Palette definitions on the Enterprise and Sam Coupe
Now Lets take a look at the Enterprise, and see how to use our CPC+ style color definitions to define the palette on the Enterprise.
|The Enterprise Palette definitions|
|The Enterprise uses 8 bits for each color definition
Red and Green have 3 bits defining them, and Blue has only 2... which is quite unbalanced!
Also rather oddly the rightmost bits are actually the highest in significance, which is kind of backwards!
according to the Enterprise documentation the Calculation for the resulting RGB value is as follows:
total RED = ( r2 * 4 + r1 * 2 + r0 ) / 7These definitions are stored in memory in the LPT block, we just need to change that memory to set the new palette values
|Palette Definition as it appears in the LPT:
|And to make things
even stranger, we can only specify
exact colors for the first 8 colors... the second 8 use a 5 bit 'Bias'
,and pads out the lowest bits with the "Color Number" (0-7 for colors
This is controlled by port &80 on the system, so we just OUT our new fixbias byte to that port to change the color
This effectively means we are forced to have colors 8-15 as a "rainbow", but we can tint it a bit, or make it brighter or darker.
|FIXBIAS on port &80:
Effective palette definition of colors 8-15:
Magenta = from palette number bits (values 0-7)
Green = from FIXBIAS
|To convert our -GRB
format palette which uses one nibble per color, we just split out the
colors, then shift 3 of the Red and Green bits, and just 2 of the Blue
bits into a new byte, then save that into the LPT memory for that
Of course, we can only do this for the first 8 colors!
|When we want to set
the top 8 colors in 16 color mode, we can OUT 5 bits to the FIXBIAS
register at port &80
Really, we'll want them to be a brighter, darker or tinted copy of the first colors.
Enterprise has superior graphics and color to the CPC, so porting Mode
1 games is really easy! when it comes to Mode 0, however, the
Enterprise is more limited, because we can't set the 2nd 8 colors as
easily on the Enterprise as the CPC,
Of course if we're designing a new game then we would plan for this, and design our palette accordingly!
|The Sam Coupe Palette definitions|
|The Sam Coupe uses
a 128 color palette, the RGB part of
a color is defined by 2 bits per channel, and an extra 1 bit is used as
a 'Bright' bit, which increases the brightness by half a bit
The effective color palette is defined as:
Each of the 16 colors is controlled by a different port on the SAM, we OUT our new color byte to that port...
If we look at the port numbers in decimal, they don't make much sense, but if we look at them as 16 bit HEX, we'll see that the High byte is the color number, and the low byte is &F8
|converting our -RGB
per channel format to the SAM format is relatively easy, all
do is pop off 2 bits from each channel into our destination byte into
the correct locations...
the only tricky bit is what to do with the 'bright bit'... we could just ignore it, but that would mean bright white would be slightly dark....
In this example, what I've done is set the 'bright bit' when the 2nd bit of each color nibble is set for all three channels...
"BRIGHT" bit is tricky because we need to convert our RGB value some
how, Here we've set it when all 3 channels have bit 2 as on
This may make the colors seem darker than they should be in some cases but some kind of trade of was needed.
Of course you may prefer to set the bit if any of the colors have this bit set, or if 2 of 3 are, but you'll have to change the code!
| Lesson P15
- Palette definitions on the MSX2 and V9990
The MSX2 and V9990 are fairly similar, and Easy!
The MSX2 uses 3 bits per channel, and the V9990 uses 5 bits per channel, so we'll need to do a bit of conversion of our 1 nibble per pixel palette
|When we call our
routine, we have the color number in
A, and the -GRB palette definition in HL
To set a palette on the MSX2, we need to set register 16 (The indirect palette register) to the number of the palette we want to select.
Then we need to send two bytes in the correct format to the palette port on port &9A
We need to make some changes to our GRB data... we only need 3 of the 4 bits, and we need to shift the remaining bits one to the right, but apart from that, the data is in the correct format!
|We need to send two consecutive bytes to the Palette
|We need to use
Register 14 to tell the VDP what color we want to change... we have to
shift this two bits to the left.
When we do this, we also tell it what color channel (RGB) we're going to send.. .we're going to leave this as 00 - as it auto increments, and we're going to send all 3 channels.
|Color Selection (Register 14)
0=red 1=Green 2=blue
|We need to split
out our color
channels and send them one by one, also, although our definition uses
one nibble (4 bits) per channel, the V9K needs 5 bits... so we need to
shift one bit to the left
We just send the three bytes to the V9K Palette port (&61) and the color will be set!
As we send each byte, the Auto increment means the channel changes, and once we've set a whole 'Color' it automatically moves to the next color, so its easy to set all the colors in one go!
|The MSX2 9938 needs 3 bits per channel...
the V9K needs 5 per channel...
We're using 4 bits per channel (like the CPC+)... so it's a good middle ground between the other systems... don't you think?
early development of ChibiAkumas, the MSX2 3 bit color definitions were
used for the V9K,and the CPC+ used separate definitions... but this
meant the V9K fades were noticeably inferior to the CPC+...
The developer switched the code, so the V9K AND MSX2 use the CPC+ palette definitions, and that's why we're using it for ALL the systems in these tutorials!
| Lesson P16
- Palette definitions on the Sega Master System and Game Gear
Although the GG and SMS are identical in most cases, color depth is one time there is a big difference between the two...
Despite being the portable system with the smaller screen, the GG uses 4 bits per color channel, whereas the SMS uses only 2! this means there are a few differences between how we set the palette on these systems.
|The GameGear and
Mastersystem use 2x 16 color
Background Tiles can use either of the 2 palettes... sprites always use the second one.
The Mastersystem uses just 2 bits per color channel... meaning 1 byte per definition
The GameGear uses 1 nibble per channel.. meaning 2 bytes per definition... this means the memory locations are different on the SMS and GG
To set one of the bytes that make up the palette definition, we write to an address &C0xx - where xx is the byte of the palette definition we want to change...
Note, this isn't literally part of the 16k of VRAM - the &C0 is more like a command to tell the VDP to change a color.
|SMS Palette Definition:
GG Palette Definition:
command will take a color number in the Accumulator, and a -GRB color
definition in HL
To start, we need to select the memory address - because the Game Gear has 2 bytes per definition, we need to double A on the GG
We use the PrepareVram command to send the address to the VDP - which sends the address to vdpControl (&BF) - which tells the VDP what to do with the data it gets next
|On the SMS we need
the top two bits of each nibble in our definition...
we just merge them all together, and send them to the vdpData port (&BE) to set the color definition!
|The GG can take all
4 bits of our definition!
However our definition is in the format -GRB ... and the GG wants it in -BGR format...
We need to shift some of the bits of our definition around to get them in the format the GG wants... then we just send the two bytes it needs to the VDP data port!
superior palette of the GG is one of the reasons it was possible (via a
converter) to play SMS games on a GameGear, but not the other way round!
even stranger... the 16 bit sega genesis uses only 3 bits per channel... giving the GG 4096 colors compared to the Genesis' 512 ... so arguably the GG has better color than the Genesis!
| Lesson P17
- Palette definitions on the Gameboy and Gameboy Color
We're using our 1 nibble (4 bits) per channel -GRB color definitions... actually the Gameboy Color has 5 bits per channel!
Unfortunately the black and white Gameboy cannot do any real sort of color - it only has 4 shades 0,1,2 and 3 (0=light, because all pixels are off)... I did try to write a converter, but it doesn:t really work well. As the GBC tiles have 4 colors, and there are 4 shades, we'll map each color to a shade - either dark->light or vice versa!
|The Classic Gameboy uses 4 colors for tiles... and
there are 4 possible colors that can be used.
These colors are defined by 3 memory addresses...
&FF47 defines the tilemap colors
&FF48 defines sprite colors 0
&FF49 defines sprite colors 1
Just write a byte in the correct format to these addresses to set each color option
You can see we have 2 choices for sprite data... also you should note that color 0 (C0) in sprites is transparent.
All 3 use the same color definitions, 2 bits in the byte define colors C0-C3... and those bytes can be 0-3 (00,01,10 or 11)
|The Gameboy Color
has 8 palettes of 4 colors available for tiles and sprites... each
colors is defined by 15 bits... 5 per channel
First we have to select the palette and color we want to change, by writing an appropriate byte to &FF68... Note we also have to set Bit 0 correctly... Our palette definition contains two bytes..a High and Low pair... if we're writing the Low byte bit 7 should be 0... if we're writing the High byte, bit 7 should be 1
Once we've selected the palette destination, we then write the new palette byte to &FF69
We then need to write another byte to &FF68
Rather than doing two writes to &FF68, it's easier to use the Autoinc Bit (bit 7)... this will allow us to write the Low bit, and then the High bit to &FF69... without a second write to &FF68
&FF68 - Select Palette
P: Palette (0-7)
C: Color (0-3)
B: Byte being written to &FF69 (0=L ,1=H)
&FF69 - Set Byte of color
L / H Byte depending of &FF68
|Because we have
such simple color options we'll just define two palette choices,
Dark will set the background to black, the 3rd color to white, and the other two in between...
Light will do the opposite!
In both cases "Object Palette 1" (the alternate sprite palette) will be the opposite colors of the main palette 0
|Remember Color 0 of the sprites will be transparent, so you may want to customize this depending on your color needs!
GrimeZ80 didn't use sprites - it only used tiles, but we'll look at how to use hardware sprites later in the series!
|We're providing a color number in A, and a 1 nibble per
channel definition in -GRB format...
We need to convert it to 5 bit BGR format - so we need to shift the color bits around
We also need to double A because the color number starts in Bit 1 of the data we we write to the palette selector &FF68.
We need to write our Palette selector to &FF68., with Bit 0 =0 ... we also set Bit 7 =1 to turn on autoincrement... because we're going to write the low byte first.
We've used the special GBZ80 command LDI, so HL will now have changed to &FF69
We've told the graphics hardware we're about to write the LOW byte of the new palette to &FF69... and we'll do that first.
Because we've turned on Autoincrement, we can just write the second HIGH byte to &FF69 straight away without changing the selected palette address... we could even set more colors if we wanted.
|When we call this function with A=0... HL will be used to define color 0 in palette 0... A=3 will define color 3 in Palette 0... A=4 will define color 0 in Palette 1... this will continue for all 8 (0-7) palettes for a total of 32 colors.|
| Lesson P18
- Making Sound with the AY on the Amstrad CPC, MSX,ZX Spectrum.... and NeoGeo + Atari ST!!
It's time to move on! We've looked as graphics, and input, but now we need to complete our game... and what game will be complete with Sound Effects!
To start we'll look at the AY sound chip... the exact same chip exists on the CPC,MSX, speccy and even the Atari ST! (no that's not a z80 system!) ...
Also, The NeoGeo also has a YM2610 which is backwards compatible with the AY!... and although it's main cpu is a 68000... its sound chip is a Z80!
Because the chip is the same, we just need to change the code that addresses the registers and gets the data to them in the correct way for the hardware!
|In these tutorials we're going
to create an 'amazing' new sound API to rival Directsound!!!... well at
least the functionality won't break like Directsound 3D did!
Well, no it won't... what it will do is take a byte value from 0-255, and make a sound of selectable pitch, with noise or half volume in a similar way on all our systems!
This was created for Grime Z80, and allows common sound code to give similar effects on all the systems!
All we do is load the accumulator with a value, and call ChibiSound!
Of course, this won't be enough to make music (Hint: try Arkostracker!) but it will give us some simple SFX, and make it easy to compare doing simple tasks on our various systems!
|Envelope||How the sound changes over time (on the AY - Volume over time)|
|Mixer||Turn channels on or off|
|Noise||Distorted sounds - used for explosions, drum and symbol sounds|
|Tone||Pitch of the sound|
|The AY Sound chip is controlled
by 14 internal registers that define the sounds it makes... these are
built into the chip itself, and we usually access it by 2 ports
controlled by OUT commands... one selects the register, the other takes
the new value for the register.
The procedure is simple, first we send the register number to the register select port
Then we send the new value to the data port.
before we look at examples of how to make sounds, lets look at the way we access these ports on the systems we'll be looking at, because it's a little different on each!
On the MSX things are easy... the Data port is &A1, and the register select port is &A0
|The ZX Spectrum
On the Spectrum the procedure is the same, but the ports are different... and of course the Speccy uses 16 bit port numbers... the Data port is &BFFD, and the Register Select is &FFFD
|The Amstrad CPC
The Amstrad CPC likes to make us suffer!... the AY chip is connected to the "PPI"... so if we want to get to the AY, we need to work for it
We need to do a complex sequence of commands to get the PPI to send it's data to the AY, including "Inactive" commands, which are needed to "Reset" the chip between commands...
The PPI has 4 ports in total, but we only need two of them to control the AY
&F680 selects to Write to the register
&F600 sets the PPI to 'Inactive'... we need to do this at certain times to get it to work
&F4xx will send data to the PPI
On the NeoGeo data port is number 5... and the register select port is port 4
The Z80 cpu has its own program code, and data is sent to it using a shared 'Port'... we can only send one byte at a time.... which is good, because Chibisound only uses one byte...
Unfortunately commands 0-31 are reserved for system use... so we'll split the Chibisound byte into two nibbles to send it from the 68000 to the Z80
|The Atari ST
Why's the Atari in here?... well I'm planning a 68000 tutorial series next year, and don't want to make the same video twice!
The AY ports are memory mapped on the 68000, so we just write a Byte to $FF8800 to set the register number... and $FF8802 to set the new value for that register!
NeoGeo sound chip is backwards compatible with the AY... and cannot be
accessed by the 68000, so we have to get the Z80 CPU to do the sound work...
The Z80 has its own program code contained in a ROM and separate memory - and we have to pass data between the Z80 and 68000 with a single byte RW port!
Writes to Z80 port &0C can be read from 68000 port $320000 , and writes to 68000 port $32000 can be read from port &00
|If we want to do anything, we need to turn a channel on!
The AY has 3 sound channels, A, B and C... rather strangely 0 turns a channel on, and 1 mutes it
We turn them on using the Mixer on Reg 7...
Bits 0-2 are the normal sounds for channels A-C
Bits 3-5 turn noise on for channels A-C
in this example, we'll turn on channel A
|now we need to select a Pitch...
pitches are defined by 12 bits... so each channel has two registers to define a pitch... channel A uses 0 and 1
In this example, we'll set a fairly middle tone for channel A.
|The last thing we need to do is set a volume for channel A... this is done with Register 8
A volume can be from 0-16... where 16 is loudest... the sound will now play!
We can also turn on an envelope for the channel if we want a more complex sound...
|Envelopes are outside the scope of this tutorial, because the author is too stupid to understand them properly!
please see the AY sound chip PDF if you think you're brainy enough!
Essentially an envelope is selected with Register 13... a speed for the envelope with 11 and 12... and it applies for any channel with the envelope bit set in that channel's volume register!
|if we turned on noise for a channel using the Mixer a noise element will be added to the channel...
In this example we've set bit 3 to Zero... turning on noise for channel A
|All the channels share a common noise setting... to set the noise "Pitch" we use register 6... if takes a 5 bit value, which defines the noise... smaller numbers are higher pitch!|
|We're now in a position to code Chibisound!
Our Accumulator byte format is NVPPPPP
where P= pitch , V = Loud volume, and N is the noise bit
We split up PPPPP, and use som,e of the bits to set registers 0 and 1 (Pitch of Channel A)
If N is set, we turn the noise channel on using the mixer (0=on) on register 7, then we set a noise frequency using register 6
if N is not set, we only turn on channel A using the mixer on register 7
we also need to set the volume of the channel, otherwise it won't make a sound using register 8 (volume for channel A) %00001111 is the loudest.
if A is Zero... we mute all the channels using the mixer - this will stop all sounds
| Lesson P19
- Sound on the Elan Enterprise
The Enterprise is pretty similar to the AY... in the fact it has 3 tone channels, and one noise channel, however it does not have any envelopes... Fortunately that won't affect our ChibiSound, but it could make creating music tricky!
The Enterprise does beat the AY for volume options... we can define LR volume independently for each of the 4 channels (3 tone + noise)
|&A0||Channel 0 Tone L||LLLLLLLL||L=Tone Low Byte… Lower values=Higher tone|
|&A1||Channel 0 Tone H||RPCCHHHH||H=Tone High Bits / polynomial Counter / Ring Modulator (CH2) / highPass Filter (CH1)|
|&A2||Channel 1 Tone L||LLLL||L=Tone Low Byte… Lower values=Higher tone|
|&A3||Channel 1 Tone H||RPCCHHHH||H=Tone High Bits / polynomial Counter / Ring Modulator (CHN) / highPass Filter (CH2)|
|&A4||Channel 2 Tone L||LLLL||L=Tone Low Byte… Lower values=Higher tone|
|&A5||Channel 2 Tone H||RPCCHHHH||H=Tone High Bits / polynomial Counter / Ring Modulator (CH0) / highPass Filter (CHN)|
|&A6||Noise Channel frequency||RHLBCCNN||Noise (0=31khz 1-3=Channel 0-2 link) / polynomial Counter/ swap Bits 7 & 17 of pc / Lowpass /Highpass / Ring modulator|
|&A7||Sync & Interrupt rate||-IIDDSSS||Interrupts (0=1khz,1=50hz,2=tone0,3=tone1) D=da ladder (speccy 48k emu) / Sync for tone 0,1,2 (1=hold 0=run)|
|&A8||Tone Channel 0 L Amplitude||--VVVVVV||D/A ladder (tape port, Speaker)|
|&A9||Tone Channel 1 L Amplitude||--VVVVVV|
|&AA||Tone Channel 2 L Amplitude||--VVVVVV|
|&AB||Noise Channel L Amplitude||--VVVVVV|
|&AC||Tone Channel 0 R Amplitude||--VVVVVV||D/A ladder (Speaker R)|
|&AD||Tone Channel 1 RAmplitude||--VVVVVV|
|&AE||Tone Channel 2 R Amplitude||--VVVVVV|
|&AF||Noise Channel R Amplitude||--VVVVVV|
|The Enterprise has 3 tone channels, each one has 12 bits defining the pitch of the tone (Lower numbers=higher pitch)
All the channels are stereo, and the Volume is defined by two 6 bit registers.
Despite not having envelopes, we do have some other options... Polynomial Counter Distortion values 0-3 for Off,4bit ,5bit or 7bit... a HighPass filter, and a Ring Modulator
The HighPass Filter links to another Channel.... Channel 0 links to Channel 1.... Channel 1 links to Channel 2.... Channel 2 links to the Noise Channel
The Ring Modulator also links to another Channel.... Channel 0 links to Channel 2.... Channel 1 links to the Noise Channel .... Channel 2 links to Channel 0
|Try different options for the Polynomial Counter! they'll make the sound different... of course, if you want to use the Ring Modulator or High Pass, you'll have to use an extra channel to link to.|
|The Noise Channel's frequency is
defined by 2 bits... 0 will give 31khz noise... 1-3 will link the
noise frequency to that of channel 0-2... note Channel 0-2 can be
muted, and the noise will work!
We also have some extra options! we have the RingPass and HighPass filter, and also a LowPass Filter...
Finally we can use the Bit swap option (bit 5) this will swap bit 7 and 17 in the noise generation... the result is the noise sounds more "Synthetic"
|They aren't very useful, but there are some extra options on the Enterprise for sound...
We can change the rate interrupts occur, and even have them linked to the tone channels
Also we can make the Enterprise emulate a spectrum Beeper speaker (noooo!)
Finally, we can 'Pause' the tone channels, by setting them to 1 in the first 3 bits of this option
|Our Accumulator byte format is NVPPPPP
where P= pitch , V = Loud volume, and N is the noise bit
The Enterprise is pretty easy for us to get ChibiSound Working on!
If the sound is off (0) we just mute the Noise and Tone 0 by setting both channels volume bits to 0
For everything else we split up our 6 bit tone definition, and use the bits to define the two tone registers of Channel 0
We use the 1 Volume bit to define a low or high volume...
If Noise is not enabled, we're done
If noise IS enabled, we mute the Tone channel (though we do need it to some extent!)
We then set the Noise volume, and enable Noise linked to channel 0 by writing 1 to port &A6
This will make the noise change pitch as we require!
forget! The Enterprise has a pretty fine AY emulator! Coded by
Geco based on IstvanV's original it's part of SymAMP, and was used to
get ArkosPlayer working on the Enterprise, for the EP128 port of
Of course, ArkosPlayer is 10000x better than ChibiSound, but ChibiSound will be created for every system covered in these tutorials, not just Z80 systems!
|ZX Spectrum 48k - the lowly Beeper Sound Chip:|
|The 48k spectrum has no AY... it only has a "Beeper"
The "Beeper" sound chip is incredibly crude... it is controlled by bit 5 of the port &FE... by turning it on and off we can make simple sounds...
See the example to the right... by changing the pause (caused by BC) we can change the pitch of the sound... 3000 will be a relatively low pitch... 500 will be higher...
Some clever programs even manage to "Fake" multiple sound channels!
The big disadvantage to all this is that the CPU will be busy during the whole time, so the Beeper chip isn't very helpful, and we'll want to use the AY sound chip on the 128k systems... but on the 48k machines, it's all we've got!
|As well as
the 'Beeper' Port &FExx also controls the border... by flipping a
bit of the border color when we toggle the beep, we can get some fun
border effects when sound plays...
So while the sound may be kinda bad, at least we get some snazzy stuff to look at!