gmewav: convert NSF, GBS, SPC, IT, XM, S3M, MOD to WAV

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
gmewav: convert NSF, GBS, SPC, IT, XM, S3M, MOD to WAV
by on (#200347)
One day I felt like streaming my music compositions and arrangements made in ModPlug, FamiTracker, and Pently on someone's Discord server. But Discord music bots tend to support only more common audio formats based on compressed samples, such as MP3 and occasionally Ogg Vorbis. So before I can use LAME and oggenc, I first need to turn them into RIFF WAVE files.

So one night, I felt like writing a wrapper around a couple player libraries that I could put in a shell script to render NSF and S3M to RIFF WAVE to MP3/Ogg. Currently it wraps these:

  • Game_Music_Emu (GME), originally by blargg, now maintained by kode54
  • Dynamic Universal Music Bibliotheque (DUMB), originally by Ben Davis, now also maintained by kode54

These two libraries cover most formats in which I have composed over the past 15 years. In the late 1990s, when I developed games using old Allegro, I composed music in Standard MIDI File format, but I dropped that around 2001 in favor of tracker formats where I could more portably control mixing among instruments and between the music and sound effects. And my potatobook's Atom N450 CPU renders NSF at roughly 60x speed.

C source code is included, but not a binary for a popular pane-ful PC operating system. I haven't tested it in anything but GNU/Linux on x86-64. Dependencies are GME, DUMB, GCC, and GNU Make, as described in the build instructions. The license of both this wrapper and DUMB is the zlib License; GME is LGPLv2.1, which requires letting end users relink an updated, ABI-compatible library with an existing application.

Code:
$ ./gmewav --help
usage: gmewav [options] vgmfile wavfile

options:
  -h, -?, --help        show this usage info
  -t LENGTH             render LENGTH seconds of audio (default: 150.0)
  -f LENGTH             fade out the last LENGTH seconds (default: 0.0)
  -m MOVEMENT           render movement MOVEMENT (default: 1)

Supported game music formats (Game_Music_Emu):
vgm, gym, spc, sap, nsf, nsfe, ay, gbs, hes, kss
Supported module formats (Dynamic Universal Music Bibliotheque):
it, xm, s3m, mod
Re: gmewav: convert NSF, GBS, SPC, IT, XM, S3M, MOD to WAV
by on (#200359)
Can you add support for exporting each channel to a seperate wav file? Would be great for things like SidWiz.
Re: gmewav: convert NSF, GBS, SPC, IT, XM, S3M, MOD to WAV
by on (#200375)
While looking through the GME and DUMB documentation, I made some notes for this and future feature requests:
Code:
// Issue "Mute": --mute and --solo to extract individual tracks
// Voice numbers: 0 <= i < count
// Mute state: 1 for silent, 0 for playing
// Muting masks: (1 << i) OR'd together, where 0x04 mutes voice 2
// Because
int gme_voice_count(const Music_Emu *self);
const char *gme_voice_name(const Music_Emu *self, int i);
const char *gme_mute_voice(Music_Emu *self, int i, int mute);
const char *gme_mute_voices(Music_Emu *self, int muting_mask);

// DUMB appears to lack an API to query a DUH for voice count.
// The duh_sigrenderer_get_n_channels() call returns how many
// output channels it is generating (2 for stereo), not how many
// voices are used.  Solo should treat all DUHs as having
// DUMB_IT_N_CHANNELS channels.
void dumb_it_sr_set_channel_muted(
  DUMB_IT_SIGRENDERER *self, int channel, int muted
);

// gme_identify_file() places non-NULL in out iff the file is
// identifiable.
// For listing GME formats, I must file an upstream issue to add a
// C API call to get the canonical extension (not the console name)
// of a gme_type_t.
// The version of DUMB in Ubuntu 16.04 LTS provides no anaogous
// extensibility means; dumb_load_any() isn't in LTS yet.
gme_type_t const* gme_type_list();
int gme_type_multitrack(gme_type_t self);  // nonzero if multiple movements
const char *gme_identify_file(const char *path, gme_type_t *out);
const char* gme_type_system(gme_type_t self);

// TODO: Figure out which RIFF chunks to use as tags
struct gme_info_t {
  // Contains these members and others:
  const char *system, *game, *song, *author, *copyright, *comment, *dumper;
};

// Allocates a gme_info_t containing tags as C strings
const char *gme_track_info(const Music_Emu *self, gme_info_t** out, int track );
void gme_free_info(gme_info_t *self);

// Set to DUMB_RQ_ALIASING, DUMB_RQ_LINEAR, DUMB_RQ_CUBIC
// I plan to have DUMB's "aliasing" mode render at double sample rate
// (that is, 88200 Hz), convolve audio with the low-pass kernel
// [-1 0 9 16 9 0 -1]/32, and decimate by 2.  This way a chiptune still
// sounds crisp, but without folding quite as many overtones over fs/2.
int dumb_resampling_quality = DUMB_RQ_LINEAR;
Re: gmewav: convert NSF, GBS, SPC, IT, XM, S3M, MOD to WAV
by on (#200377)
I'll assign your mute and solo feature request to myself once I confirm two things from you:

First, if an NSF has eight voices, such as one with 2A03 and VRC6, they are numbered 1 through 8, not 0 through 7, correct? Screenshots of ModPlug Tracker, Schism Tracker, Game Boy Tracker by Paragon 5, and SPC players number them 1-based.

Second, muting a voice might not have quite the desired effect in the following cases:

  1. Voices mix nonlinearly. This is true of the NES, where the current position of the DAC associated with the DMC controls the volume of the triangle and noise channel.
  2. A tune treats several voices as a single pool and aggressively moves individual notes on the same instrument to different voices. This is true of the SPC rip of Viacom's Zoop.
Re: gmewav: convert NSF, GBS, SPC, IT, XM, S3M, MOD to WAV
by on (#200383)
tepples wrote:
I'll assign your mute and solo feature request to myself once I confirm two things from you:

First, if an NSF has eight voices, such as one with 2A03 and VRC6, they are numbered 1 through 8, not 0 through 7, correct? Screenshots of ModPlug Tracker, Schism Tracker, Game Boy Tracker by Paragon 5, and SPC players number them 1-based.

Second, muting a voice might not have quite the desired effect in the following cases:

  1. Voices mix nonlinearly. This is true of the NES, where the current position of the DAC associated with the DMC controls the volume of the triangle and noise channel.
  2. A tune treats several voices as a single pool and aggressively moves individual notes on the same instrument to different voices. This is true of the SPC rip of Viacom's Zoop.

I'd use the 1-8 scheme, as that's what sidplay already uses.

I am not looking to separate different instruments, as that would be an unreasonable request. I am looking to just separate the voices. Can you give an example on the NES where separating the voices and then recombining them would result in a different sound?
Re: gmewav: convert NSF, GBS, SPC, IT, XM, S3M, MOD to WAV
by on (#200393)
Something like Sunsoft bass or loud DMC percussion will cause the triangle and noise to be louder when soloed than for the whole mix.

I had to work around a couple quirks of Game_Music_Emu to get them to work:

  • Silence detection is disabled when at least one channel is muted because GME uses only unmuted voices to determine when to stop playing.
  • GME pre-renders the first few milliseconds of audio immediately after starting a track. If I mute voices between starting a track and the first render call, render has to restart the track to make the muting take effect without popping all voices back on momentarily.

Here's an example of how to use the new solo feature to split out individual tracks:
Code:
./gmewav -m 2 -S 1 -t 150 something.nsf movement2-pulse1.wav
./gmewav -m 2 -S 2 -t 150 something.nsf movement2-pulse2.wav
./gmewav -m 2 -S 3 -t 150 something.nsf movement2-triangle.wav
./gmewav -m 2 -S 4 -t 150 something.nsf movement2-noise.wav
./gmewav -m 2 -S 5 -t 150 something.nsf movement2-dmc.wav
Re: gmewav: convert NSF, GBS, SPC, IT, XM, S3M, MOD to WAV
by on (#202789)
A couple small changes so that I could easily spin up an NSF/SPC/whatever player as a make run target, so that I don't have to build an entire ROM and start an entire emulator just to hear changes to the output of an NSF or SPC that I built:

  • make install PREFIX=~/.local works
  • Option to write raw PCM to standard output instead of writing a RIFF WAVE file
  • scripts/gmeplay.sh to play through PulseAudio; can be adapted to other PCM sinks
Re: gmewav: convert NSF, GBS, SPC, IT, XM, S3M, MOD to WAV
by on (#208138)
Let me explain why there hasn't been much further development on this:

The intended use case was to export WAV, encode, upload to your own web space, and play from there. But for "security reasons", the major Discord music bots have since disabled playback of arbitrary URLs in favor of whitelisting YouTube, SoundCloud, and a couple other major sites. Because music publishers patrol YouTube and SoundCloud in order to make Content Verification Program claims and counterparts on other services, uploading cover versions to YouTube or SoundCloud exposes a cover artist to a far greater chance of getting caught than uploading cover versions to one's own web space.
Re: gmewav: convert NSF, GBS, SPC, IT, XM, S3M, MOD to WAV
by on (#208294)
Can you add support for exporting each channel to a seperate wav file? Would be great for things like SidWiz.
Re: gmewav: convert NSF, GBS, SPC, IT, XM, S3M, MOD to WAV
by on (#208315)
Tiffansy wrote:
Can you add support for exporting each channel to a seperate wav file?

See above under "how to use the new solo feature to split out individual tracks".