Posted on 18-12-01, 17:20 in NSF/SPC to MIDI automated converter project
Post: #1 of 7
Since: 12-01-18

Last post: 1958 days
Last view: 1950 days
Just thought I'd announce that I'm starting to work on a standalone NSF/SPC player based on higan source code.

For those curious to watch my progress, I'm hosting the code on https://github.com/JamesDunne/vgm2midi

Obviously, no warantee nor guarantee of functionality here. It's just a very early work in progress.

Once compiled, it produces a console application that emits a WAV file given an input NSF file passed as a commandline argument, if it works at all. It's woefully incomplete and doesn't play most NSFs at the moment. I'm currently working on creating a custom cartridge Board (aka mapper) implementation of the custom iNES mapper 031 to better support NSFs since most seem to require/expect it.

This project is actually more of a stepping stone to my ultimate goal of writing an NSF (and SPC) to MIDI converter. The first milestone is to get a working NSF player, or more simply an NSF to WAV converter. After that I'll augment the APU code to do the MIDI conversion work.

I've actually already prototyped out my NSF/SPC to MIDI converter project based on libgme sources. However, its lack of emulation accuracy was causing issues that couldn't easily be resolved without an impossibly large refactoring or rewrite due to the way it emulates the systems. You can find that effort here: https://github.com/JamesDunne/libgme

NSF is fairly trivial to convert to MIDI because the melodic channels (pulse and triangle) all provide absolute pitch information, so simply taking the log2(pitch / 55.0) * 12 + 36 yields a standard MIDI note number representing the pitch, largely solving the problem. After that you can do more nuanced heuristics to support pitch wheel changes, channel changes, patch changes, etc. It's a fun problem to solve iteratively and listen to your results as the process evolves to produce better and better sounding results.

SPC poses a little bit more of a challenge to convert to MIDI than does NSF. The primary reason being that the SPC is just 8 channels of digital samplers, and the instruments sampled are basically PCM waveforms (after decoding from BRR). The reason this poses a challenge is because the SPC's pitch registers only provide a relative-pitch resampling of the original sample (-2 oct to +2 oct, and lots of semitones in between); there is no absolute pitch information readily available to the SPC. One would have to somehow "know" the absolute pitch of the recorded sample. In practice, there is no "standard" pitch at which to sample an instrument so no real assumption can be made that would cover all samples.

I've had very good success with identifying the fundamental pitch of a PCM sampled instrument by running an FFT (Fast Fourier Transform) over its looped section and using some heuristics to identify the lowest-pitched highest peak which I presume to be the fundamental pitch of the instrument. If one naively grabs simply the highest peak you will get some false-positives due to some instruments having stronger overtones present over the fundamental pitch (like trumpets).

This works great for melodic instruments like strings, piano, guitar, horns, etc. but does not work at all for percussive instruments like drums: kick, snare, hi-hat, drum loops, etc.

To solve this general problem, I've opted to allow the end user to supply a custom mapping file that specifies what exact MIDI representation to use for each sample, including which MIDI program numbers to use, a transposition amount in semitones to adjust for having the wrong octave or erroneously detecting an overtone instead of the fundamental pitch, and even an optional mapping to the General MIDI percussion channel 10 specific notes for samples like kick, snare, hi-hat, crash, bongos, toms, etc. No support for drum loops (think Jurassic Park soundtrack) yet but it does sound feasible to emit a MIDI drum loop every time a sample of a drum loop is seen. It'd just be very difficult to encode such a MIDI drum loop in a mapping file.

All of that MIDI conversion effort is present in my libgme fork but I'm stopping work there so I can get a much more stable/accurate emulation base with higan sources. Once I get a basic NSF and SPC player going, I can port over my MIDI conversion work to higan and pick up where I left off.
Posted on 18-12-03, 00:33 in NSF/SPC to MIDI automated converter project
Post: #2 of 7
Since: 12-01-18

Last post: 1958 days
Last view: 1950 days
I now have a working NSF player that supports both bank-switched and non-bank-switched NSFs. NSF file and track may be selected by command line argument.

https://github.com/JamesDunne/vgm2midi

Next milestone is an SPC player which looks not too difficult to implement.
Posted on 18-12-04, 16:19 in NSF/SPC to MIDI automated converter project
Post: #3 of 7
Since: 12-01-18

Last post: 1958 days
Last view: 1950 days
I've hit a snag in my SPC player. Can anyone help me out here?

I've set up the emulator to disable the SNES CPU and PPU, leaving only the SMP and DSP running. I load up the SPC RAM and registers and the DSP registers from the SPC file and start the emulator but I only get the first note/chord of music and then the instruments just ring out and no further music is played. I think the SPC700 is waiting on something to continue but I'm not sure what.
Posted on 18-12-05, 23:54 in NSF/SPC to MIDI automated converter project
Post: #4 of 7
Since: 12-01-18

Last post: 1958 days
Last view: 1950 days
Any idea what in particular it's waiting for from the cpu? I was fairly certain I wouldn't have to emulate the cpu for a SPC player.

Maybe I just need to emulate the cpu responses or write-back? SPC player documentation is nearly non-existent. The file spec is documented but the emulation process and semantics are not.
Posted on 18-12-06, 00:03 in NSF/SPC to MIDI automated converter project
Post: #5 of 7
Since: 12-01-18

Last post: 1958 days
Last view: 1950 days
Can you elaborate on "two timing crystals"? I realize the SPC and cpu run at different rates and are largely independent except for the communication registers. The SPC emulation treats them as ram when written to and read from. The cpu can of course write to them as well. Is there an implied tiny cpu support program I need to emulate that responds to certain SPC requests?
Posted on 18-12-06, 13:04 in NSF/SPC to MIDI automated converter project
Post: #6 of 7
Since: 12-01-18

Last post: 1958 days
Last view: 1950 days
Exactly. The SPC file format only includes the state of the SPC and DSP. I've loaded in the SPC registers PC, A, X, Y, P, and S. Also loaded in the 128 DSP registers for voice control and others.

My test SPC tracks include f-zero, jurassic park, zelda 3. Every SPC I've tried exhibits this behavior of just stopping after the first chord, so it must be something I'm missing in my player.

The DSP sounds right, since the instruments sound correct and ring out, so the problem must be in the SPC side. I've got the IPL ROM loaded in, and I've tried both both the IPL from higan/bsnes and the 64 bytes at the end of the SPC file. Neither affect the outcome differently.
Posted on 18-12-07, 21:27 in NSF/SPC to MIDI automated converter project
Post: #7 of 7
Since: 12-01-18

Last post: 1958 days
Last view: 1950 days
Finally, a working SPC player!

I found some code from bsnes-plus which is able to plays SPCs and was able to get my code working. I think the key was initializing the SMP and DSP internal registers.
    Main » jsd1982 » List of posts
    Yes, it's an ad.