[SOLVED] Famitone2 - MMC3 banked songs

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems.

Moderator: Moderators

User avatar
wonder
Posts: 46
Joined: Sat Aug 31, 2019 2:12 pm

[SOLVED] Famitone2 - MMC3 banked songs

Post by wonder » Wed Jul 08, 2020 9:22 am

Hi!

TL;DR: I want to play music using bank switching and I have no idea how!
SOLVED: https://github.com/brunowonder/music
Note: This is a custom solution for my MMC3 project and might not be portable!

I've been writing C code for about a year now, but this is my first attempt at doing anything with sound.

First of all this is how my MMC3 memory layout looks:

Code: Select all

MEMORY {
    #RAM Addresses:
    #----------------------------------------------------------------------------
    # Zero page
    ZP: start = $00, size = $FF, type = rw, define = yes;
    #note, the c compiler + neslib + famitone2 use about 60 zp addresses, I think
    
    ZP_RESERVED: start = $FA, size = $06, type = rw, define = yes;
    
    RAM: start = $300, size = $4C0, define = yes;

    #INES Header:
    HEADER: start = $0, size = $10, file = %O, fill = yes;

    #ROM Addresses:
    #-------------------------------------------------------------------------------
    DAT: start = $8000, size =  $2000, file = %O, fill = yes, define = no; #0
    M01: start = $8000, size =  $2000, file = %O, fill = yes, define = no; #1
    M02: start = $8000, size =  $2000, file = %O, fill = yes, define = no; #2
    M03: start = $8000, size =  $2000, file = %O, fill = yes, define = no; #3
    M04: start = $8000, size =  $2000, file = %O, fill = yes, define = no; #4
    M05: start = $8000, size =  $2000, file = %O, fill = yes, define = no; #5
    M06: start = $8000, size =  $2000, file = %O, fill = yes, define = no; #6
    M07: start = $8000, size =  $2000, file = %O, fill = yes, define = no; #7
    M08: start = $8000, size =  $2000, file = %O, fill = yes, define = no; #8
    M09: start = $8000, size =  $2000, file = %O, fill = yes, define = no; #9
    M10: start = $8000, size =  $2000, file = %O, fill = yes, define = no; #10
    M11: start = $8000, size =  $2000, file = %O, fill = yes, define = no; #11
    M12: start = $8000, size =  $2000, file = %O, fill = yes, define = no; #12
    M13: start = $8000, size =  $2000, file = %O, fill = yes, define = no; #13
    M14: start = $8000, size =  $2000, file = %O, fill = yes, define = no; #14
    M15: start = $8000, size =  $2000, file = %O, fill = yes, define = no; #15
    M16: start = $8000, size =  $2000, file = %O, fill = yes, define = no; #16    
    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  
    PRG: start = $A000, size =  $6000, file = %O, fill = yes, define = yes; #17-19
    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    CHR: start = $0000, size = $40000, file = %O, fill = yes;
}


SEGMENTS {
    HEADER:   load = HEADER,             type = ro;
    LOWCODE:  load = PRG,                type = ro,                optional = yes;
    INIT:     load = PRG,                type = ro,  define = yes, optional = yes;
    RODATA:   load = DAT, start = $8000, type = ro,  define = yes;
    PRG_EXT:  load = DAT, start = $9000, type = ro,  define = yes, optional = yes;
    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    MAP_01:   load = M01, start = $8000, type = ro,  define = yes, optional = yes;
    MAP_02:   load = M02, start = $8000, type = ro,  define = yes, optional = yes;
    MAP_03:   load = M03, start = $8000, type = ro,  define = yes, optional = yes;
    MAP_04:   load = M04, start = $8000, type = ro,  define = yes, optional = yes;
    MAP_05:   load = M05, start = $8000, type = ro,  define = yes, optional = yes;
    MAP_06:   load = M06, start = $8000, type = ro,  define = yes, optional = yes;
    MAP_07:   load = M07, start = $8000, type = ro,  define = yes, optional = yes;
    MAP_08:   load = M08, start = $8000, type = ro,  define = yes, optional = yes;
    MAP_09:   load = M09, start = $8000, type = ro,  define = yes, optional = yes;
    MAP_10:   load = M10, start = $8000, type = ro,  define = yes, optional = yes;
    MAP_11:   load = M11, start = $8000, type = ro,  define = yes, optional = yes;
    MAP_12:   load = M12, start = $8000, type = ro,  define = yes, optional = yes;
    MAP_13:   load = M13, start = $8000, type = ro,  define = yes, optional = yes;
    MAP_14:   load = M14, start = $8000, type = ro,  define = yes, optional = yes;
    MAP_15:   load = M15, start = $8000, type = ro,  define = yes, optional = yes;
    MAP_16:   load = M16, start = $8000, type = ro,  define = yes, optional = yes;
    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    CODE:     load = PRG, start = $A000, type = ro,  define = yes;    
    DATA:     load = PRG, run = RAM,     type = rw,  define = yes;
    CHARS:    load = CHR,                type = ro;
    BSS:      load = RAM,                type = bss, define = yes;
    HEAP:     load = RAM,                type = bss,               optional = yes;
    ZEROPAGE: load = ZP,                 type = zp;
    ONCE:     load = PRG,                type = ro,  define = yes, optional = yes;

    STARTUP:  load = PRG, start = $f2a0, type = ro, define = yes; 
    VECTORS:  load = PRG, start = $fffa, type = ro;
}


SYMBOLS {
    __STACKSIZE__:    type = weak, value = $0020;
    __STACK_START__:  type = weak, value = $07E0;

    NES_MAPPER:       type = weak, value =  4;          # mapper number, 0 = NROM
    NES_PRG_BANKS:    type = weak, value = 10;          # number of 16K PRG banks
    NES_CHR_BANKS:    type = weak, value = 16;          # number of 8K CHR banks
    NES_MIRRORING:    type = weak, value =  1;          # 0 horizontal, 1 vertical, 8 four screen
}
So far I managed to put the song I want to play in "MAP_12" by adding the following to "music_dangerstreets.s":

Code: Select all

.segment "MAP_12"
On main() I have:

Code: Select all

#pragma code-name(push, "MAP_12")
//#link "music_dangerstreets.s"
extern const char danger_streets_music_data[];
#pragma code-name(pop)

void main(void)
{
  pal_col(1,0x04);
  pal_col(2,0x20);
  pal_col(3,0x30);
  vram_adr(NTADR_A(2,2));
  vram_write("FAMITONE2 DEMO", 14);
  
  // initialize music system
  //MMC3_PRG_8000(12);
  famitone_init(danger_streets_music_data);  
  //MMC3_PRG_8000(0);
  
  // set music callback function for NMI
  nmi_set_callback(famitone_update);
  // play music
  music_play(0);
  //enable rendering
  ppu_on_all();
  // repeat forever
  while(1);
}
The code above is based on this example:
https://8bitworkshop.com/v3.5.1/?file=f ... atform=nes

So... how can I play music stored in that MMC3 memory segment?
Is it possible to do it with just C code (or inline ASM), without changing the Famitone2 library?

Many thanks in advance.
Last edited by wonder on Fri Jul 10, 2020 9:18 am, edited 2 times in total.
Image

User avatar
aa-dav
Posts: 92
Joined: Tue Apr 14, 2020 9:45 pm
Location: Russia

Re: Famitone2 - MMC3 banked songs

Post by aa-dav » Wed Jul 08, 2020 10:02 am

I write directly in assembler, so I do not know C details.
But famitone2 make it easy in it's heart: you just need to switch to this bank before calls to famitone functions.
And important one: famitone_update.
So, looking at these code:

Code: Select all

nmi_set_callback(famitone_update);
I think you should do some trampouline function

Code: Select all

void my_nmi()
{
  switch_to_bank... // 
  famitone_update();
}
...
nmi_set_callback(my_nmi);
But maybe there is better way to organize code, I just have no experience with C on NES.

User avatar
aa-dav
Posts: 92
Joined: Tue Apr 14, 2020 9:45 pm
Location: Russia

Re: Famitone2 - MMC3 banked songs

Post by aa-dav » Wed Jul 08, 2020 10:10 am

Also, note, that if you are using DCPM channel you must place DCPM data into correct segment using second MMC3 layout. Look in famitone2 documentation for details.

User avatar
DRW
Posts: 1976
Joined: Sat Sep 07, 2013 2:59 pm

Re: Famitone2 - MMC3 banked songs

Post by DRW » Thu Jul 09, 2020 4:52 pm

This is how I do it:

First of all, you shouldn't write stuff into the files that text2data outputs. That's too much hassle since you would need to do this for every time the tool generates the song file for you.

Instead, you should include the files in another file and put the segment before that:

Code: Select all

.segment "MAP_12"
    .include "music_dangerstreets.s"
Next thing: Whenever you play a new song, just initialize the whole FamiTone library again. That's what I do.

Under normal circumstances, once the library is initialized, you can play songs by just calling FamiToneMusicPlay.
But if you have the songs in different banks and therefore different text2data output files, FamiTone can only see one of them at once anyway. So, you can just as well initialize the whole library whenever a new song is played:

Switch to the corresponding bank.
Call the FamiTone initialization functions.
Call the FamiToneMusicPlay function accordingly.
And in NMI, for FamiToneUpdate, you need to set this bank again as well. Same when you call other functions.


The big question now is: How do you use music and sound effects together?
Outside of NMI, that's no problem. After initializing FamiTone for music, you simply switch to the sound effects bank and call the sound effects initialization function etc.
Same for calling the sound effect itself: You just switch to the bank before doing so.

The tricky part comes with FamiToneUpdate: If your music and your sound effects are in different banks, what do you do? You can switch to the music bank before you call FamiToneUpdate. But what about the sound effects?

In this case, you need to change the library:
Go to the comment ;process all sound effect streams.
And before that comment, you call a self-defined macro. You can name it, for example, SWITCH_TO_SOUND_EFFECTS_BANK.
Then you define the macro in the file that includes FamiTone. And there in your macro, you switch to the sound effects bank.

Using a macro here ensures that the changes in FamiTone itself are as few as possible. You just add one line. And the actual code is done in your own files.

But yeah, if you have music and sound effects on different banks, you need to change the library. But as long as only your music is banked, you can use FamiTone unaltered and just need to call the banks before any FamiTone function call.
My game "City Trouble": www.denny-r-walter.de/city.htm

User avatar
DRW
Posts: 1976
Joined: Sat Sep 07, 2013 2:59 pm

Re: Famitone2 - MMC3 banked songs

Post by DRW » Thu Jul 09, 2020 4:56 pm

One more thing: What's with that ZP_RESERVED memory? Why does it overlap ZP? And what is it good for?

Also, the size of ZP can be $100 instead of $FF. The values go from $00 to $FF (0 to 255), so in total, those are $100 (256) bytes.
My game "City Trouble": www.denny-r-walter.de/city.htm

User avatar
wonder
Posts: 46
Joined: Sat Aug 31, 2019 2:12 pm

Re: Famitone2 - MMC3 banked songs

Post by wonder » Fri Jul 10, 2020 2:28 am

Hello again, thank you much for the answers.
What's with that ZP_RESERVED memory? Why does it overlap ZP? And what is it good for?
It's just a place where I put my debug variables, to be removed in the future.
Also, the size of ZP can be $100 instead of $FF. The values go from $00 to $FF (0 to 255), so in total, those are $100 (256) bytes.
Haha, true indeed! I'll fix that!

Regarding the Famitone part, it think the problem goes deeper than I though, as I might have mismatched assembly files:
- The entire project is based on one of the demo projects from 8bitworkshop.com
- crt0.s was modified by a friend of mine to accommodate for MMC3 usage
- neslib.s (and neslib.h) is the NesDoug version
- I don't know which famitone2.s version fits there

I think the best thing I can do now it put the (relevant part of) the project (memory layout, assembly files and main.c) on GitHub and let you guys have a look... :)
Last edited by wonder on Fri Jul 10, 2020 7:03 am, edited 1 time in total.
Image

User avatar
wonder
Posts: 46
Joined: Sat Aug 31, 2019 2:12 pm

Re: Famitone2 - MMC3 banked songs

Post by wonder » Fri Jul 10, 2020 5:05 am

Alright, I managed to put the project on a separate public repo:
https://github.com/brunowonder/music

You can load it into 8bitworkshop by doing the following:
1) Open https://8bitworkshop.com/v3.5.2/?platform=nes
2) Close the tour (if necessary)
3) From the top-left menu select Sync --> Import Project from GitHub
4) Paste: https://github.com/brunowonder/music
5) If you get an error, select main.c from the files drop-down menu

Right now the music data is in RODATA, so no bank switching yet.

Note that 'famitone2.s' comes from: https://8bitworkshop.com/v3.5.1/?file=f ... atform=nes
I had to comment out a few lines to make it "work". As you can hear, the music plays too fast when compared to the link provided above.


As I mentioned before, I think the files are incompatible with each other (neslib, famitone2, crt0).
Image

User avatar
aa-dav
Posts: 92
Joined: Tue Apr 14, 2020 9:45 pm
Location: Russia

Re: Famitone2 - MMC3 banked songs

Post by aa-dav » Fri Jul 10, 2020 6:51 am

wonder wrote:
Fri Jul 10, 2020 5:05 am
As I mentioned before, I think the files are incompatible with each other (neslib, famitone2, crt0).
Looks like choosing PAL region instead of NTSC or vice versa.
I'll try to check it now...

User avatar
aa-dav
Posts: 92
Joined: Tue Apr 14, 2020 9:45 pm
Location: Russia

Re: Famitone2 - MMC3 banked songs

Post by aa-dav » Fri Jul 10, 2020 7:11 am

Hm... neslib calls FamiToneUpdate itself. So you do not need to call it manually at all.
Just remove setting nmi_handler and tempo will be ok. However music sounds bad, but I don't know if it just online emulator fault.

User avatar
wonder
Posts: 46
Joined: Sat Aug 31, 2019 2:12 pm

Re: Famitone2 - MMC3 banked songs

Post by wonder » Fri Jul 10, 2020 7:50 am

aa-dav wrote:
Fri Jul 10, 2020 7:11 am
Hm... neslib calls FamiToneUpdate itself. So you do not need to call it manually at all.
Just remove setting nmi_handler and tempo will be ok. However music sounds bad, but I don't know if it just online emulator fault.
OMG it worked!!!! Thank you so much for looking into it!! :beer: :beer: :beer: :D
I was looking for a solution in all the wrong places! :lol:

This emulator in not very good, I'll try it on MESEN now.
EDIT: I think it sounds more or less the same in Nestopia, maybe a little bit better in MESEN.
Image

User avatar
wonder
Posts: 46
Joined: Sat Aug 31, 2019 2:12 pm

Re: Famitone2 - MMC3 banked songs

Post by wonder » Fri Jul 10, 2020 8:03 am

Alright!!! Banked music works!!! :P :beer: :beer: :beer:

Changes are already committed in the repository:
https://github.com/brunowonder/music

Code: Select all

extern char danger_streets_music_data[];

void __fastcall__ upd(void) {
  MMC3_PRG_8000(12);
  famitone_update();
  MMC3_PRG_8000(0);
}

void main(void)
{
  // set music callback function for NMI
  nmi_set_callback(upd);
  
  // initialize music system
  MMC3_PRG_8000(12);
  famitone_init(danger_streets_music_data);
  MMC3_PRG_8000(0);
  
  // play music
  MMC3_PRG_8000(12);
  music_play(0);
  MMC3_PRG_8000(0);
  
  // repeat forever
  while(1);
}
Now I need to take into account what DRW mentioned on their post:
DRW wrote:
Thu Jul 09, 2020 4:52 pm
The big question now is: How do you use music and sound effects together?
Outside of NMI, that's no problem. After initializing FamiTone for music, you simply switch to the sound effects bank and call the sound effects initialization function etc.
Same for calling the sound effect itself: You just switch to the bank before doing so.

The tricky part comes with FamiToneUpdate: If your music and your sound effects are in different banks, what do you do? You can switch to the music bank before you call FamiToneUpdate. But what about the sound effects?

In this case, you need to change the library:
Go to the comment ;process all sound effect streams.
And before that comment, you call a self-defined macro. You can name it, for example, SWITCH_TO_SOUND_EFFECTS_BANK.
Then you define the macro in the file that includes FamiTone. And there in your macro, you switch to the sound effects bank.

Using a macro here ensures that the changes in FamiTone itself are as few as possible. You just add one line. And the actual code is done in your own files.
Image

User avatar
wonder
Posts: 46
Joined: Sat Aug 31, 2019 2:12 pm

Re: Famitone2 - MMC3 banked songs

Post by wonder » Fri Jul 10, 2020 9:14 am

...and done! MMC3 banked sound effects and music, all working together!! :D

Image

Thank you so much for your help guys!!! :beer: :beer: :beer:

PS: I don't like the way famitone, neslib and crt0 depend on each other, so I might try to do some clean-up next week.
Image

User avatar
aa-dav
Posts: 92
Joined: Tue Apr 14, 2020 9:45 pm
Location: Russia

Re: Famitone2 - MMC3 banked songs

Post by aa-dav » Fri Jul 10, 2020 5:23 pm

wonder wrote:
Fri Jul 10, 2020 9:14 am
...
PS: I don't like the way famitone, neslib and crt0 depend on each other, so I might try to do some clean-up next week.
Yes, it is what I want to recommend.
neslib has interface/link to famitone2, so you should carefully choose how to modify all of it for MMC3.
I would make minimal architecture changes.
For example to contain all chages in neslib.s, not commenting call to FamiToneUpdate, but placing bank switch there.
So your main.s will look almost like regular CC65 neslib user code and famitone2.s will be intact.

User avatar
DRW
Posts: 1976
Joined: Sat Sep 07, 2013 2:59 pm

Re: Famitone2 - MMC3 banked songs

Post by DRW » Fri Jul 10, 2020 8:51 pm

wonder wrote:
Fri Jul 10, 2020 9:14 am
PS: I don't like the way famitone, neslib and crt0 depend on each other, so I might try to do some clean-up next week.
If you want my suggestion: Ditch neslib and that crt0 file.

FamiTone is one thing. It's a music library that can convert FamiTracker songs into an efficient format. Writing this yourself requires not only knowledge about the technical details of the APU. It requires knowledge about non-NES-programming stuff, like instruments in music and the way FamiTracker works.
So, writing a music library is a whole project in its own right and I can't blame anyone if he uses a finished sound library. As a programmer, while it can be expected from me to read about what writing to each APU address does, I don't have to know what an arpeggio works like or what a pitch is, so I leave that to a programmer who knows the NES and music.

But that neslib, as far as I know, simply encapsulates the basic NES functions, like controller reading, waiting for NMI etc. And I think those are things that every NES programmer should know about and understand in its entirety and he should be able to write it himself.

I mean, it's not a Windows game where stuff like DirectX is pretty much a requirement since you just cannot do this all by hand. NES programming requires you to work with low level stuff anyway, so you can just as well learn the basic things yourself instead of having a library abstract this away from you.
Since we're not talking about some game physics or data compression library, but really about the barebones NES-specific stuff, I would never use an external library for this. If someone wants to be an NES programmer, he shouldn't replace lesson 101 with a finished library, just to save a week of development.
My game "City Trouble": www.denny-r-walter.de/city.htm

User avatar
DRW
Posts: 1976
Joined: Sat Sep 07, 2013 2:59 pm

Re: [SOLVED] Famitone2 - MMC3 banked songs

Post by DRW » Fri Jul 10, 2020 9:11 pm

By the way:
ZP And ZP_RESERVED overlap each other, so it might happen that two variables use the same memory location and overwrite each other.
Why don't you simply use another segment in ZP for your debug variables?

And why does your RAM have such a strange size?
My game "City Trouble": www.denny-r-walter.de/city.htm

Post Reply