OPN2cog - The sound of the SEGA Mega Drive in a cog!
(or SEGA Genesis if you're in yankland...)
This is an emulation of the Yamaha "OPN2" YM2612 FM synth chip that runs in one P2 cog.
Features:
- 6 channels of 4-operator phase-modulation synthesis
- All 24 oscillators updated at the same rate as the real chip (~53 kHz)
- Channels are not mixed, but multiplexed onto the DAC, like in the real chip.
- Emulates DAC distortion
- Emulates output lowpass filter
- Supports SSG-EG
- Mostly supports CH3 special mode (rate scaling doesn't account for it)
- Supports CH6 raw DAC mode
- Bonus: SEGA PSG (SN76489 variant) emulation!
Misfeatures:
Envelope generators are updated at 1/12 the real rate (~1.5kHz instead of ~18 kHz)
This causes slight clicking on certain envelope rates and other artifacts.Timers and CSM mode are not implemented.
- PSG sound is somewhat aliased due to relatively low update rate (~53 kHz)
Requires at least 250 MHz P2 clock to run properly.
(Too low clock speed -> low and/or inconsistent pitch)Some instruments sound a little off, but maybe I'm just going insane.
Still orders of magnitude better than those silly AtGames units, haha
Videos
MEGA JUKE
Spin API example
(Note: the spectrograph in these is not running on the P2, it's just there so it's more than a still image)
What's included
File name | Purpose |
---|---|
OPN2cog.spin2 |
OPN2 emulation core |
OPN2_ROM.DAT |
OPN2 logsin/exponent ROM |
megajuke.c |
MEGA JUKE program. |
MEGAJUKE.DAT |
MEGA JUKE data file. |
megajuke_builder.rb |
Script for building MEGAJUKE.DAT |
megajuke_tracks.yml |
Track list for MEGA JUKE. Add your own favorite tunes! |
ExampleSpinAPI.spin2 |
Example that plays a weird little tune using the Spin API and VGI patch files |
*.vgi |
VGI patch files used by the Spin API example |
ExampleVGMPlay.spin2 |
In-memory VGM player. Copy a VGM file from the tunes directory and load it! |
ExampleSSG-EG.spin2 |
Example/test that plays a note normally and then with all 8 SSG-EG modes |
tunes/ |
Folder of lots of VGM dumps! |
romgen.rb |
Script for building OPN2_ROM.DAT |
eg_analyzer.rb |
Script for computing the EG rate lookup tables |
lfo_analyzer.rb |
Script for computing the LFO FM lookup table |
logo.gal |
GraphicsGale layered file for the logo |
Running MEGA JUKE
The MEGA JUKE program streams register dumps from a file, using the 9P server in loadp2 (in theory, replacing _vfs_open_host
with _vfs_open_sdcard
should make it run from SD card, too, but in practice it hangs after reading the track text, IDK why. SD code is probably not quite thread safe)
- Have the latest flexspin/flexprop (5.4.3) installed
- Open
megajuke.c
and edit the pin constants near the top. - Compile with
flexspin -2 megajuke.c -D_BAUD=2000000
- Load with
loadp2 -p [your port here] -t -9 . -b 2000000 megajuke.binary
Comments
I didn't have this hardware (so no nostalgia) but I like FM synthesis and I had an Soundblaster with Adlib complatible 2/4 op FM synthesizers.
The OPL series chips used in the AdLib/Soundblaster are actually quite similar to the OPN series. Though OPL3 will not fit in 1 cog. (But I think OPL2 extended with some OPL3 features will)
This is awesome @Wuerfel_21
Fantastic. Thanks for posting the YouTube sample. I didn’t listen to the whole track but did listen to samples along the track.
Excellent, we'll have a Megadrive in no time at all
Yeah I was wondering about whether an Adlib OPL2 device could be emulated at some point. Did you attempt the OPL3 and run into some COG performance limits?
No, but I can see them from a mile away. The OPN2 has 24 operators and barely fits at 250 MHz. The OPL2 has 18. The OPL3 has 36.
So it is time to try FM synth using P2 and check how many operators can fit. I have this HDMI driver wihch uses "Amiga based" frequencies (Paula x 80, 90, 100 which gives ~280/320./360 MHz ) so I will try this with Paula related frequencies.
Making a Paula like FM can be interesting but it will need more processing (or maybe not?) than "normal" phase accumulator thing.
Not really. The way the Yamaha chips do it is simple: Just add the output from the modulator(s) together with the phase accumulation and then generate the waveform based on that. I think/hope the OPN2cog code is commented well enough to read, so you can do that to get an idea of how it works.
I've just PR'd this and SNEcog into the OBEX: https://github.com/parallaxinc/propeller/pull/231
@Wuerfel_21,
Fantastic work!! I haven't compared it thoroughly to the real thing yet, but it sounds really close when listening to the tunes I know well. This is a great contribution to the P2 community!
I've been able to patch this code to work with a CS4344 I2S DAC. I used the PMOD I2S board with the P2-EVAL and managed to get something working with it and can hear the music playing in the jukebox demo. Output sample rate is ~159kHz for I2S and double that for analog output.
Here's the patch you need. Just call start_with_i2s(...) instead of start(...) and pass the I2S pin group as an argument. See the file for pin order. Without I2S enabled there is no run time overhead added in the critical loop. Enabling it will add some cycles, but it seemed to still work at 250MHz...YMMV.
Added bonus is the analog audio still happens with I2S enabled as well.
p.s. Listening to more of this with I2S I actually think the 2x faster analog audio output sounds clearer, maybe it's a bad sample rate conversion due to mismatch of digital output sampling and sample generation inside the COG itself or just the fact that the rate is halved.
EDIT: Updated this code to sum every second sample and playback (still at 159kHz). Sounds way better now.
As evident in the source, OPN2cog multiplexes the output between the channels like the real chip (which is where the ridiculous 319.6kHz come from. The synth as a whole updates at 53.26kHz). I don't see any sample summing in your code, so if it was actually outputting at exactly halved rate, half the channels would be missing. And if they aren't, you're missing samples (try 320MHz clock?)
Aha, if that is the case then that would explain why it doesn't sound as good.
Yes I tried it at the higher rate, but this particular I2S DAC doesn't take sample rates over 200kHz according to its data sheet and so it didn't work. I guess we could try storing every second sample and summing them, maybe with a small clock boost for the extra overhead.
I meant to try setting _CLKFREQ to 320Mhz. That should rule out any skipped samples. In that case with 159kHz sampling, you should get half the channels missing.
This was from May? I missed it somehow... Sound great!
How would you say this compares to SimpleSound from @Ahle2 ?
I'm guessing 16 bits makes it sound better. But, there's a huge library of those 8-bit music samples out there...
For a game, guess you want to play some background music and mix in sound effects from the game. SimpleSound made that pretty easy...
Does this support that too?
Well, this is more akin to SIDcog, in that it emulates a vintage synth chip.
It's not doing 16 bit sampling, it's just 5 FM synth channels and one 8 bit sampling channel (and 4 PSG channels). (The logo just says "16 BIT" because it's supposed to look like a Mega Drive console)
There's no built-in music/sfx routine. The sampling channel isn't really usable most applications, because you need to constantly rewrite the DAC register. I could make a version that can go through the samples on it's own though.
I've modified the code in my earlier post to sum every second sample before playback, which is still getting done at 159kHz and 250MHz. The music seems to sound so much better this way over I2S now it includes all samples (as you can imagine). I also tried operating the P2 at 320MHz and it worked too.
Also: not included in the code was this one line change (which I wasn't sure would affect the pitch or not). This change keeps the i2s output rate locked to the generation rate stopping it from drifting due to quantization error in the divisors.
To include it for trying out, change this line
to this:
Potentially (if needed) that tweaked calculation could be made only when i2s is enabled in the start method in order to keep the audio pure when i2s is not used.
Good job!
Here's OPN2Cog V1.1
Because the forum upload limit changed, I can't actually post the ZIP with all the example data anymore, so won't update the OP until that's cleared. In the meantime, have this ZIP without them.
Main change is that there's now an "Ultra" version that requires 300 MHz clock speed, but offers improved quality. There's also an improvement to the DAC distortion simulation and both of the VGM file playing examples now correctly implement write delay (so files exported straight from trackers play correctly now).
Downloaded.
As I don't have any experience with this chip/system I have several questions
- Where to get the music for it from?
As the prop2play works at Paula*100=354 MHz, the Ultra version can fit but I have to finish the SIDCog integration first.
Edit: Found a big zip in the first post.
VGM format is basically a big stream of "write chip register" and "wait" commands. There's a specification for it out there somewhere (I recommend you reference an older version, the most recent versions are more complex and in pratice you'll only find the first couple really simple versions for MD music). In addition, a file may contain a data block to store any samples it needs. I think(?) you can get away with allocating 64k for that. There's two examples for playing VGM (reference the newer versions from the V1.1 ZIP, they both had some issues fixed).
ExampleVGMPlay.spin2
is in Spin2 and plays a file loaded in memory, whereasmegajuke.c
is in C and plays files from filesystem. Since the commands can get pretty fast, especially when sample playing is involved, you ideally want to constantly load the next bit of data in the background.No.