Game development for NES in C. Chapters 22-23. Appendix 1 - Mappers and Digital Sound

Original author: Nesdoug
  • Transfer
  • Tutorial

Here is information not included in the main cycle, but too valuable to ignore.


<<< previous next >>>


image
A source


The topic of mappers - coprocessors in a cartridge almost completely fell out of our discussion. If you need to make a game larger than 0x8000 bytes in size, then the standard features of the console are not enough for this. Mapper allows you to switch memory banks in the game, and cc65 can work with it. The most popular mapper is MMC3. In addition to switching memory banks, it has a line counter.


To play your own game on a real console, you can use a flash cartridge like Everdrive or PowerPak. You can still flash the EPROM chip and make your own board for it, or sacrifice another cartridge, but this requires specific skills, and it is not available to me now.


The 3 most significant bits in the $ 2001 register include color channel brightness enhancement. Bits are inverted - setting all bits to evenly reduce the brightness of the entire screen. All palette options in the picture.


image


Standard Library C


Ullrich von Bassewitz ported the entire C standard library and something else to cc65. Now there is division and multiplication of numbers, albeit very slow. Perhaps tabular computing will be faster.
#include “..\include\stdlib.h”
Now you can use the work with memory: calloc, malloc, free and realloc. When testing these features, some difficulties were found - it was necessary to determine both __STACK_SIZE__ and __STACKSIZE__ in the config. Very similar to a typo in the library, but both options are found in the documentation. To work with the allocator, you also need to define HEAP .


I strongly recommend not to mess with all this, it is slow even in comparison with C code, where memory is allocated statically. Another of the interesting functions in the library is the rand and srand functions for pseudo-random numbers and qsort for sorting.
#include “..\include\cc65.h”
So cc65_sin and cc65_cos are imported - sine and cosine.
#include “..\include\zlib.h”
And so - work with compression. Not experienced at all.


Interrupts - IRQ


The principle of operation is the same as with NMI: when it is triggered, control is transferred to the interrupt handler. The handler pointer is in the interrupt vector at the addresses $ FFFE- $ FFFF. There are three ways to cause an interrupt:


  • BRK instruction in code, opcode # 00
  • DMC audio channel interruption is on, it is triggered when the sample ends
  • interruption can be called by the mapper, for example, MMC3 can handle row counts like this

Only the third method looks useful, it really allows you to change the PPU settings in the process of rendering the frame - you can combine the background and all that.


Some emulators cut 8 pixels at the top and bottom of the screen. NES produces 240 lines, but televisions of that era often lost the edges of the screen. In short, do not put in this area something critical to the game.


Reading and writing PPU data works approximately the same. We write the high byte of the address in $ 2006, then the low byte there, and READ from $ 2007 (LDA $ 2007). But we must remember that the first reading from PPU always gives garbage. In addition to working with palettes at the addresses $ 3F00- $ 3FFF, there the first reading gives the correct data. So you need to read twice. I am not a NES developer and I have no idea why it works like this. Let me remind you once again that all the work of PPU must be done during V-blank.


Work with DMC


A little more details on how to add music to the game using Famitone2 and Famitracker. If you want to do without libraries, the sequence of actions is something like this:


How to play a sample from memory
*((unsigned char*)0x4015) = 0x0f; // выключить DMC
// регистр управляет включением каналв, DMC управляется битом 0x10
*((unsigned char*)0x4010) = 0x0f; // частота семплирования, 0x0f - максимальная
ADDRESS = 0xf000; // адрес DMC-семпла в ROM
// семплы должны быть в диапазоне адресов $C000-$FFFF в ROM
*((unsigned char*)0x4012) = (ADDRESS & 0x3fff) >> 6; // 0xf000 => 0xc0
LENGTH = 0x0101; // длина семпла
*((unsigned char*)0x4013) = LENGTH >> 4; // 0x0101 => 0x10
*((unsigned char*)0x4015) = 0x1f; // снова включить DMC
//при включении канала начинает играть семпл

Famitone2 does it all by himself. In any case, the samples should be short - I usually use 0.1-0.5 seconds. If you need to increase the length, you will have to reduce the frequency, and this dramatically spoils the quality. It should also be borne in mind that the DMC channel is approximately twice as quiet as other channels.


Let's try to add all this to the game. I will use licensed for non-commercial use samples from some old collection. I cut them in length and imported them into Famitracker:


image


Next you need to save them as DMC files. Each sample is attached to the instrument key, and you can compose them into a track in the DPCM column. Then it must be exported to .txt. The text2data program from Famitone2 / tools converts it to .s and .dmc files.
text2data DMCmusic.txt -ca65


Next you need to enable the DMC and SoundFx channels in Famitone2. This is in the .define section of the reset.s file. Samples will be located in memory starting with the address $ F000, this is an option FT_DPCM_OFF. The samples themselves are located at the end of the reset.s file, in the .segment “SAMPLES” file. His address must also be specified in .CFG. Then Famitone2 does its own thing and switches the samples with the procedure from the famitone.s file. I also added non-DMC effects for the voice acting of the jump and converted them through famitone2 / tools / nsf2data - this technique has already been discussed before.


Dropbox
Github


And now you can add DMC effects. I leave the same track, but I transfer the drums to the noise channel - it will not be interrupted during effects. In Famitone2, this is done in much the same way.


image


To call these effects from the code, you need to call the procedures from famitone2.s by labels, using the fastcall function:
void __fastcall__ DMC_PLAY(unsigned char effect);


The effect number must be embedded in one byte and correspond to the number of the DMC sample. They need to be checked with the generated DMCmusic2.s file, there may be surprises:


image


25 and 27 turned out because the samples are tied to the 25th and 27th note of the row.
And then it’s very simple:
DMC_PLAY(27);


One plays when jumping, and the second when you start.


Dropbox
Github


Also popular now: