Jump to content
ZeroByte

Demo: Realtime AdLib emulation on YM2151

Recommended Posts

https://youtu.be/tPZNQcl0JYE

I've written a proof-of-concept program that uses the actual AdLib music data from Wolfenstein3d and plays it back on the YM2151 chip by translating the register writes from OPL commands into their equivalent OPM (2151) commands if possible.

There is still work to be done. The vibrato effects are definitely doable, but the translation is going to take some consideration on my part, as the two chips do it in very different ways. The Wolf3d title screen music does not make use of percussion mode (as far as I could tell) but obviously a true translation driver must be able to handle those, and I think I'll work on doing that with the VERA PSG voices.

I will post a link to the sources once I get the ability to load the titlescreen graphics, etc. from within the program itself. (my vload() function doesn't seem to work properly).

  • Like 8
  • Thanks 1

Share this post


Link to post
Share on other sites

While cleaning up the code for sharing, I realized that this program is easily broken down into two parts: the AdLib emulation engine and the Id music player which "uses" the AdLib engine. So essentially, I could evolve the AdLib emulation into a standalone "library" which I think might be useful for anyone wanting to use AdLib data on the X16.

I'm not sure what all "features" it should have in library form, but it's worth pondering. The emulation layer would basically just act like the YM. It wouldn't have any concept about playing back music, playback frame rates, etc - it would just be a virtual OPL chip. Thus - offer AdLibWrite(reg,value) and immediately poke the corresponding values into the appropriate slots of the YM, and offer Adlib2YM(reg,value) which returns a list of reg,value pairs that you may send to the YM at your leisure.

The main thing I'm unhappy with in my code is that I ended up using a LUT for converting OPL frequencies into YM frequencies because doing it on the fly is going to require some floating point math. (Maybe I can hook some of BASIC's functions for this?) There's nothing wrong with LUTs for performance purposes, but this one is 16KB and it's in main RAM at the moment- so clearly this wouldn't work for a library. It also must keep a 256 byte shadow of the YM registers as well as shadows of a few of the OPL registers. Right now it's pretty memory hungry for something that's "just playing back sounds." The YM shadows seem inescapable, but it might be possible to eliminate them and just accept that redundant writes to the YM are happening.

  • Like 2

Share this post


Link to post
Share on other sites

OMG!! This is soo cool! 🙂 🙂

Kudos for making this work so well already.

Regards,

Jeffrey

Share this post


Link to post
Share on other sites
Posted (edited)

The code's getting close to clean enough for publication - I've been twiddling around with a php script to extract any particular song and build the program with that one so you can play back any one song. Some of the other tunes don't come out quite as well as the title screen music did - but most sound very good, and two of them are too large to fit into memory along with all my LUTs and code. (I'm not using banked memory because using that would've slowed me down a lot last weekend) - once I push the C source code, I'll start working on the ASM version of the driver as it stands. (I haven't tried supporting more OPL features / accuracy yet). The next thing I'm working on driver-wise is shrinking the LUT way way down by doing some light-weight interpolation.

I have to be careful though because the relationship between pitch and frequency is exponential/logarithmic and thus not a cheap calculation to do - but I'm thinking of a smaller LUT to get close to the final value where the error between linear and exponential translation should be small enough not to notice in the results.

But doing this is important, because if I can get the code's entire scratch space to fit into a single HI-RAM bank (not counting music data), then I'd consider that good enough to use as a general-purpose driver. The music data loading / streaming to the driver would be your game's responsibility, @Jeffrey, but I bet you're up to the task.

My work with the PHP file should make that job a lot easier for you, as I've spent a lot of time documenting the parser and exactly how to read the WL1 files to extract the music. The script also can extract AdLib SFX, but since my driver ignores OPL voice 0, and all SFX use that channel exclusively, they don't work. But in theory if the code ignored voice 8 instead, then the SFX _should_ work. If you do flesh out your raycast demo into a working engine, then you'd have to decide how to handle the fact that there aren't enough voices in OPM to do it all at once.

Edited by ZeroByte

Share this post


Link to post
Share on other sites
2 hours ago, ZeroByte said:

The code's getting close to clean enough for publication - I've been twiddling around with a php script to extract any particular song and build the program with that one so you can play back any one song. Some of the other tunes don't come out quite as well as the title screen music did - but most sound very good, and two of them are too large to fit into memory along with all my LUTs and code. (I'm not using banked memory because using that would've slowed me down a lot last weekend) - once I push the C source code, I'll start working on the ASM version of the driver as it stands. (I haven't tried supporting more OPL features / accuracy yet). The next thing I'm working on driver-wise is shrinking the LUT way way down by doing some light-weight interpolation.

I have to be careful though because the relationship between pitch and frequency is exponential/logarithmic and thus not a cheap calculation to do - but I'm thinking of a smaller LUT to get close to the final value where the error between linear and exponential translation should be small enough not to notice in the results.

But doing this is important, because if I can get the code's entire scratch space to fit into a single HI-RAM bank (not counting music data), then I'd consider that good enough to use as a general-purpose driver. The music data loading / streaming to the driver would be your game's responsibility, @Jeffrey, but I bet you're up to the task.

My work with the PHP file should make that job a lot easier for you, as I've spent a lot of time documenting the parser and exactly how to read the WL1 files to extract the music. The script also can extract AdLib SFX, but since my driver ignores OPL voice 0, and all SFX use that channel exclusively, they don't work. But in theory if the code ignored voice 8 instead, then the SFX _should_ work. If you do flesh out your raycast demo into a working engine, then you'd have to decide how to handle the fact that there aren't enough voices in OPM to do it all at once.

Sounds like a good plan. I first wrote the demo in c as well, since that is much easier to understand and to debug. I will have to experiment a lot with your code to understand how it works and how I can incorporate it into the Wolf3D game/demo. I also plan to completely port the  Wold3D demo into assembly, because the c-code takes up quite a lot of ram and handling both c and asm is quite inconventient.

I am accustomed to php and python so that should be no problem. I think what you are doing will be used by many others in the future. I hope the Wold3D project is just the beginning... 🙂

Hopefully next week I will have some more time to spend on all of this. Currently doing lots of work on our house 😉

Regards,

Jeffrey

Share this post


Link to post
Share on other sites

Okay, I've cleaned up the code to where I'm not totally ashamed of myself, and written a lot of documentation.

https://github.com/ZeroByteOrg/vopl-demo/

There is still plenty to do with this - for instance make the main program load the music directly from the Wolf3d audio files instead of having it linked in as a C array in a big fat .H file. As it stands, you can extract any song from the WL1 data using the chunks tool and recompile the demo.

There is a #define at the top which can be uncommented to use current pre-R39 support. The main differences are the banking register location (used to bank in kernal jiffy counter for waitvsync()), YM base address, and YM clock frequency. R39 will use a corrected clock rate. My program uses different LUT values for the two clock rates, so the output is pitch-correct, regardless of which version you build.

I will probably work on my Flappy Bird game a little bit now, and after that, come back to this project to convert it to ASM and enhance the fidelity and hopefully get it into a state where it's available as a library to use in your own programs w/o the Wolf3d front end.

@Jeffrey - feel free to cram this into your program if you want to try - it probably won't be easy in its current state, as it uses ram very very liberally right now. I'm working on a fast freq. conversion that should only need 128 bytes of RAM instead of 8k. 😉

Edited by ZeroByte
Forgot to post the Github link - lol
  • Like 2
  • Thanks 1

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...

Important Information

Please review our Terms of Use