Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deferred VBL routines, audio, and then some more #103

Open
ggnkua opened this issue Sep 25, 2023 · 4 comments
Open

Deferred VBL routines, audio, and then some more #103

ggnkua opened this issue Sep 25, 2023 · 4 comments

Comments

@ggnkua
Copy link

ggnkua commented Sep 25, 2023

Hello,

First of all, great work on making this SDK, I'm sure you saved lots of people's hours and head scratches (mine included!).

Sorry if the title sounds a bit of a mixed bag, but all's connected as you'll see in due time.

Long story short: I made a port of an Atari 800 game (http://aim.atariscene.pl) for the Atari ST (a playable version released in July here: https://www.pouet.net/prod.php?which=94605), and while I was waiting for graphics and music, I started porting the game to other 68k platforms, NeoGeo included.

As you can imagine, I used ngdevkit to bootstrap the NegGeo port and I figured most things out, but sound was always a tricky subject. I was aware that nullsound was included, but it barely did more than boot up and play a couple of samples (not to undermine the effort that went into nullsound of course!). For me that was not enough as I wanted a way to play music (finding a musician would of course be another, bigger task!).

Eventually I remembered Leonard's demo (https://www.pouet.net/prod.php?which=59253) in which he took advantage of the SSG inside the YM2610 and played a music which consisted of a register dumped tune from the Atari ST. This got me thinking how hard it would be to adapt an ST tracker routine which generates the register changes per VBL and send them to nullsound to be forwarded to the YM.

A few weeks ago I bit the bullet and started to implement this. For prototyping I used the ST replay routine for Arkos Tracker 2 that I made some time ago (https://github.com/ggnkua/Arkos-Tracker-2-ST/) and simply replaced the YM writes with a buffer mechanism, which then sent the registers/values to the z80, and at the end of transmission play them on the YM. I had to modify nullsound quite a bit, but eventually I succeeded! https://mastodon.gamedev.place/@ggn/111081639543250982

(As an aside, Arkos Tracker 2 - https://www.julien-nevo.com/arkostracker/ - has native z80 replay routines, so I'm pretty sure that they can be adapted to nullsound quite easily. I would use them if I could, but the musician who agreed to make music for the ST version does not use that tracker, so I'll have to modify that. Perhaps I will soon get in touch with the author, see if he's interested. I think it would be a cool thing if ngdevkit suddenly has audio capabilities!)

If you listen carefully to the music on that video linked above, you might spot some glitches. These happen partially because the tune is 50Hz, so in order to make it play at a normal speed in some frames I have to skip calling the music player (i.e. every 6th frame I skip a player call). The other more serious problem is that due to the complexity of the game, the processing can take multiple VBLs to execute. So if you notice carefully you'll realise that there are many glitches happening while things are moving around. Ideally I would have wanted to execute the music player routine inside ng_wait_vblank(), but this seems to be internal to the runtime, and I can't see a hook for that so I can call my own code. So, to tie this in with the issue's subject, would you consider adding a way to run some user defined code inside ng_wait_vblank()? Something like reading a pointer which, if not null, execute the routine that it points to.

Apologies for the long message, I tried to keep this as to the point as I could! Looking forward to your reply.

(For the record, I could have simply recompiled the runtime and added this functionality, but I thought it might benefit more people. If you want, I can also create a pull request with the proposed changes above)

(Also, if you're interested in adding my really bad music hacks as an example, I can make an effort to clean them up and send them to you. Or try to integrate the z80 Arkos routines into nullsound as a module)

@dciabrin
Copy link
Owner

First of all, great work on making this SDK, I'm sure you saved lots of people's hours and head scratches (mine included!).

Thanks :)

As you can imagine, I used ngdevkit to bootstrap the NeoGeo port and I figured most things out, but sound was always a tricky subject. I was aware that nullsound was included, but it barely did more than boot up and play a couple of samples (not to undermine the effort that went into nullsound of course!). For me that was not enough as I wanted a way to play music (finding a musician would of course be another, bigger task!).

No offense, nullsound is currently more of a learning platform than a sound driver, as you explained. However, I have various bits and pieces under development that should make things way better, hopefully soon. More on that below.

A few weeks ago I bit the bullet and started to implement this. For prototyping I used the ST replay routine for Arkos Tracker 2 that I made some time ago (https://github.com/ggnkua/Arkos-Tracker-2-ST/) and simply replaced the YM writes with a buffer mechanism, which then sent the registers/values to the z80, and at the end of transmission play them on the YM. I had to modify nullsound quite a bit, but eventually I succeeded! https://mastodon.gamedev.place/@ggn/111081639543250982

Very nice!

(As an aside, Arkos Tracker 2 - https://www.julien-nevo.com/arkostracker/ - has native z80 replay routines, so I'm pretty sure that they can be adapted to nullsound quite easily. I would use them if I could, but the musician who agreed to make music for the ST version does not use that tracker, so I'll have to modify that. Perhaps I will soon get in touch with the author, see if he's interested. I think it would be a cool thing if ngdevkit suddenly has audio capabilities!)

So I have ADPCM-b support that should be pushed shortly. But more importantly, I'm working on supporting playback of VGM files. The reason being that VGM is the simplest and quickest way to drive FM, SSG and ADPCM on the YM2610, so with sound nullsound would gain primitive support for music and sfx playback.

Supporting VGM requires supporting YM2610 timers an z80 interrupts, so I'm currently working on this very topic. I have a VGM python script in the works that allows to play FM and SSG tracks (cannot mix ADPCM just yet). So progress.

Now of course VGM is just bad for a compact representation of a music. Maybe this is not an issue for anyone, in which case VGM playback will be great. But maybe it will become an issue, so I'm considering supporting the playback of chiptune tracker mods. Specifically supporting Furnace [1]. Right now, Furnace is ideal for ngdevkit because 1) it is fully open-source; 2) it supports YM2610 natively (including the SSG capabilities that are akin to what was available on the Atari ST IIUC); 3) it can already export to VGM. I currently think that Furnace is a really nice avenue for composing tracks on the neogeo.

That being said, I have just started analyzing how furnace mods translates into VGM, and how its feature like instruments and tempos maps to YM2610 ports writes and timer configurations. This has helped me to design timer and vgm support in nullsound.

If you listen carefully to the music on that video linked above, you might spot some glitches. These happen partially because the tune is 50Hz, so in order to make it play at a normal speed in some frames I have to skip calling the music player (i.e. every 6th frame I skip a player call). The other more serious problem is that due to the complexity of the game, the processing can take multiple VBLs to execute. So if you notice carefully you'll realise that there are many glitches happening while things are moving around.

I haven't seen what you did so I can't comment on it, but it sounds unexpected to me that you're having a slowdowns when your game is heavily using the 68k. Only the z80 should be able to talk to the ym2610 on the neogeo hardware, the 68k can only command the z80 to start various playback. So as long as the z80 code is optimized, the load on the 68k should never impact music playback, unlike other architectures.

Ideally I would have wanted to execute the music player routine inside ng_wait_vblank(), but this seems to be internal to the runtime, and I can't see a hook for that so I can call my own code. So, to tie this in with the issue's subject, would you consider adding a way to run some user defined code inside ng_wait_vblank()? Something like reading a pointer which, if not null, execute the routine that it points to.

That looks like the strategy that we would use on other 68k archs like atari or amiga (or even PCs), but it doesn't look to me that it's necessary to expose the VBL timer from the 68k. You'd need to configure one of the z80 timers to trigger every 60th of a second and you'd get a reliable time synchronization. That is what the exported VGM files from Furnace do and what other official neogeo sound drivers most likely do as well.

(Also, if you're interested in adding my really bad music hacks as an example, I can make an effort to clean them up and send them to you. Or try to integrate the z80 Arkos routines into nullsound as a module)

Maybe let's wait until I push my updates for timer and VGM support. If your fork supports additional features, it would be nice to see if we can merge them.

[1] https://github.com/tildearrow/furnace

@ggnkua
Copy link
Author

ggnkua commented Sep 28, 2023

Hi, thanks for the reply!

Very exciting news about adding audio, it will definitely be an awesome addition to the SDK!

As for my case, you probably misunderstood what my method (or, better: hacks!) do. The audio player actually runs on the 68k, and every frame it sends the SSG values over to the z80, which writes them to the SSG. So it is important that this runs on every vertical blank.

So what the game code does now is to run the game logic, then call ng_wait_vblank() and then the 68k music player. Since the game logic can run for more than one vertical blank, the audio player skips some ticks, so glitches can be heard.

I'm fully aware that this is really not an orthodox way of doing things, but in my case since I'm porting the game to 5 (maybe more) platforms, reusing assets is top priority. However, if you say that you will support VGM in the future I might switch to that, as the Megadrive port is certainly going to be using that format (or something close to that).

In any case, if you don't think it's a good idea to add the feature of ng_wait_vblank() executing user code then it's ok. I might modify the code and recompile myself so you won't have to worry about it :). But again, if VGM happens soon I might not even need to do that. It remains to be seen.

In any case, again thank you for the reply and sharing some of your behind the scenes work!

@dciabrin
Copy link
Owner

Heads up @ggnkua,

The very first usable commits for Furnace module playback has just landed. Modules needs to be converted in a format suitable for nullsound, called NSS.

ADPCM asset management is not handled by a dedicated tool called vgmtool.py. It packs all the samples from your Furnace modules + the samples used for your SFX into a series of VROM.
Furnace conversion is done via two scripts called furtool.py and nsstool.py, which are in charge of extracting instruments/samples and melody from Furnace module and convert them to z80 assembler file.

An example has just landed in ngdevkit-examples, right now it's the only source of doc.
A lot of sound Furnace to NSS translation remains to be done, but the example is already able to play back FM + ADPCM A/B and play SFX at the same time. So this should be suitable as a base for your development.

@ggnkua
Copy link
Author

ggnkua commented Mar 22, 2024

Thanks for the heads up!

I'll find some time to update to latest and rebuild the examples (which is always the sticky part as I have to install all the msys+dependencies yet again) and will give it a spin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants