0
0

I’m experimentally writing an FMOD codec that uses [url=http://www.slack.net/~ant/libs/audio.html#Game_Music_Emu:2d0rz6c6]Blargg’s Game_Music_Emu library[/url:2d0rz6c6] for reading a range of emulated music formats like NSF, SPC and GYM. This is what I’ve got so far, it’s just been hacked together and is messy and odd (I’ve literally just got it working at this point, so there are probably oddities in it) but seems to work.

The only problem is that that Game_Music_Emu does not know how long in pcm bytes/milliseconds a sound is going to be at load time. It’s possible to determine that a sound has definitely stopped playing, but only once it’s actually got to that point, and some sounds loop forever. FMOD’s codec system does not seem to allow for this, so if I play just a sound effect through the GME codec that stops after a few seconds, the channel it’s using is never freed even though it’s just going to generate silence forever.

So: Is there something I’ve missed that allows you to announce that a codec has reached the end of what it’s playing from the FMOD_CODEC_READCALLBACK?

[code:2d0rz6c6]#include <stdio.h>

include <gme.h>

include <blargg_endian.h>

include <fmod.h>

include <fmod_errors.h>

static FMOD_RESULT F_CALLBACK gmecodec_open(FMOD_CODEC_STATE codec, FMOD_MODE usermode, FMOD_CREATESOUNDEXINFO *userexinfo);
static FMOD_RESULT F_CALLBACK gmecodec_close(FMOD_CODEC_STATE *codec);
static FMOD_RESULT F_CALLBACK gmecodec_read(FMOD_CODEC_STATE *codec, void *buffer, unsigned int size, unsigned int *read);
static FMOD_RESULT F_CALLBACK gmecodec_setposition(FMOD_CODEC_STATE *codec, int subsound, unsigned int position, FMOD_TIMEUNIT postype);
static FMOD_RESULT F_CALLBACK gmecodec_getlength(FMOD_CODEC_STATE
codec, unsigned int* length, FMOD_TIMEUNIT lengthtype);
static FMOD_RESULT F_CALLBACK gmecodec_getwaveformat(FMOD_CODEC_STATE *codec, int index, FMOD_CODEC_WAVEFORMAT *waveformat);

static FMOD_CODEC_DESCRIPTION gmecodec_description = {
"Game_Music_Emu", // Name.
0x00000034, // Version 0xAAAABBBB A = major, B = minor.
1, // Force everything using this codec to be a stream
FMOD_TIMEUNIT_MS | FMOD_TIMEUNIT_MODORDER, // The time format we would like to accept into setposition/getposition.
&gmecodec_open, // Open callback.

&amp;gmecodec_close,                          // Close callback.
&amp;gmecodec_read,                           // Read callback.
&amp;gmecodec_getlength,                                  // Getlength callback.  (If not specified FMOD return the length in FMOD_TIMEUNIT_PCM, FMOD_TIMEUNIT_MS or FMOD_TIMEUNIT_PCMBYTES units based on the lengthpcm member of the FMOD_CODEC structure).
&amp;gmecodec_setposition,                    // Setposition callback.
0,                                  // Getposition callback. (only used for timeunit types that are not FMOD_TIMEUNIT_PCM, FMOD_TIMEUNIT_MS and FMOD_TIMEUNIT_PCMBYTES).
0,                                   // Sound create callback (don't need it)
0 // &amp;gmecodec_getwaveformat

};

static FMOD_CODEC_WAVEFORMAT gmecodec_waveformat;

/*
The actual codec code.

Note that the callbacks uses FMOD's supplied file system callbacks.

This is important as even though you might want to open the file yourself, you would lose the following benefits.
1. Automatic support of memory files, CDDA based files, and HTTP/TCPIP based files.
2. &quot;fileoffset&quot; / &quot;length&quot; support when user calls System::createSound with FMOD_CREATESOUNDEXINFO structure.
3. Buffered file access.
FMOD files are high level abstracts that support all sorts of 'file', they are not just disk file handles.
If you want FMOD to use your own filesystem (and potentially lose the above benefits) use System::setFileSystem.

*/

static gme_err_t gme_fmod_reader(void* your_data, void* out, long count_long) {
FMOD_CODEC_STATE* codec = (FMOD_CODEC_STATE)your_data;
unsigned int count = (unsigned int)count_long;
unsigned int actual_count;
while (count > 0) {
codec->fileread(codec->filehandle, out, count, &actual_count, 0);
count -= actual_count;
((char
)out) += actual_count;
}
return 0;
}

FMOD_RESULT F_CALLBACK gmecodec_open(FMOD_CODEC_STATE codec, FMOD_MODE usermode, FMOD_CREATESOUNDEXINFO *userexinfo)
{
Music_Emu
emu;

codec-&gt;fileseek(codec-&gt;filehandle, 0, 0);
char header[4] = &quot;&quot;;
codec-&gt;fileread(codec-&gt;filehandle, header, 4, 0, 0);

switch ( get_be32( header ) )
{
    case BLARGG_4CHAR('N','E','S','M'):
        emu = gme_new_emu(gme_nsf_type, 44100);
        break;
    case BLARGG_4CHAR('N','S','F','E'):
        emu = gme_new_emu(gme_nsfe_type, 44100);
        break;
    case BLARGG_4CHAR('S','N','E','S'):
        emu = gme_new_emu(gme_spc_type, 44100);
        break;
    case BLARGG_4CHAR('Z','X','A','Y'):
        emu = gme_new_emu(gme_ay_type, 44100);
        break;
    case BLARGG_4CHAR('G','B','S',0x01):
        emu = gme_new_emu(gme_gbs_type, 44100);
        break;
    case BLARGG_4CHAR('G','Y','M','X'):
        emu = gme_new_emu(gme_gym_type, 44100);
        break;
    case BLARGG_4CHAR('H','E','S','M'):
        emu = gme_new_emu(gme_hes_type, 44100);
        break;
    case BLARGG_4CHAR('K','S','C','C'):
    case BLARGG_4CHAR('K','S','S','X'):
        emu = gme_new_emu(gme_kss_type, 44100);
        break;
    case BLARGG_4CHAR('S','A','P',0x0D):
        emu = gme_new_emu(gme_sap_type, 44100);
        break;
    case BLARGG_4CHAR('V','g','m',' '):
        emu = gme_new_emu(gme_vgm_type, 44100);
        break;
    default:
        return FMOD_ERR_FORMAT;
}
if (!emu)
    return FMOD_ERR_MEMORY;

codec-&gt;fileseek(codec-&gt;filehandle, 0, 0);

if (gme_load_custom(emu, &amp;gme_fmod_reader, codec-&gt;filesize, codec) != 0) {
    gme_delete(emu);
    return FMOD_ERR_FILE_BAD;
}

gme_start_track(emu, 0);

gmecodec_waveformat.channels     = 2;
gmecodec_waveformat.format       = FMOD_SOUND_FORMAT_PCM16;
gmecodec_waveformat.frequency    = 44100;
gmecodec_waveformat.blockalign   = gmecodec_waveformat.channels * 2;          /* 2 = 16bit pcm */
gmecodec_waveformat.lengthpcm    = 0xffffffff;   /* no fixed length */;

codec-&gt;numsubsounds = 0; /* number of 'subsounds' in this sound.  For most codecs this is 0, only multi sound codecs such as FSB or CDDA have subsounds. */
codec-&gt;waveformat   = &amp;gmecodec_waveformat;
codec-&gt;plugindata   = emu;                    /* user data value */

return FMOD_OK;

}

static FMOD_RESULT F_CALLBACK gmecodec_close(FMOD_CODEC_STATE codec)
{
gme_delete((Music_Emu
)codec->plugindata);
return FMOD_OK;
}

static FMOD_RESULT F_CALLBACK gmecodec_read(FMOD_CODEC_STATE codec, void *buffer, unsigned int size, unsigned int *read)
{
Music_Emu
emu = (Music_Emu*)(codec->plugindata);
if (gme_track_ended(emu))
{
// How do I tell FMOD this?
}

const char* message = gme_play(emu, size/2, (short*)buffer);
if (message != 0) {
    printf(message);
    return FMOD_ERR_NOTREADY;
}
*read = size;
return FMOD_OK;

// return codec->fileread(codec->filehandle, buffer, size, read, 0);
}

static FMOD_RESULT F_CALLBACK gmecodec_setposition(FMOD_CODEC_STATE codec, int subsound, unsigned int position, FMOD_TIMEUNIT postype)
{
Music_Emu
emu = (Music_Emu*)(codec->plugindata);
gme_start_track(emu, position);
return FMOD_OK;
// return codec->fileseek(codec->filehandle, position, 0);
}

static FMOD_RESULT F_CALLBACK gmecodec_getlength(FMOD_CODEC_STATE* codec, unsigned int* length, FMOD_TIMEUNIT lengthtype)
{
Music_Emu* emu = (Music_Emu*)(codec->plugindata);
*length = gme_track_count(emu);
return FMOD_OK;
}

static FMOD_RESULT F_CALLBACK gmecodec_getwaveformat(FMOD_CODEC_STATE codec, int index, FMOD_CODEC_WAVEFORMAT *waveformat)
{
Music_Emu
emu = (Music_Emu)(codec->plugindata);
track_info_t tinfo;
gme_track_info(emu, &tinfo, 0);
sprintf(waveformat->name, "%s (%s)", tinfo.game, tinfo.system);
waveformat->channels = 2;
waveformat->format = FMOD_SOUND_FORMAT_PCM16;
waveformat->frequency = 44100;
waveformat->blockalign = waveformat->channels * 2; /
2 = 16bit pcm /
waveformat->lengthpcm = 0xffffffff; /
no fixed length */;
return FMOD_OK;
}[/code:2d0rz6c6]

  • You must to post comments
0
0

try returning FMOD_ERR_FILE_EOF, i’m pretty sure that will do it.

  • You must to post comments
0
0

This is reqally funnny, I just started creating the same plugin today and posted a question right after your post. I had issues with playing but thanks to your post I realised that I should use

[code:1dvayswg]
gp->emu->play(size/2,(signed short)buffer);
[/code:1dvayswg]
instead of
[code:1dvayswg]
gp->emu->play(size,(signed short
)buffer);
[/code:1dvayswg]
so do you have any ideas to implement subsongs? I did a plugin for sids using sidplay and I never got subsongs to work.[/quote]

  • You must to post comments
0
0

Thanks Brett – I will try that and get back to you.

blAZER, the way I thought would make the most sense to do it is treat it like a MOD-type format and make the individual units "tracks" rather than subsounds. To be honest I’m not entirely sure about that bit either.

  • You must to post comments
0
0

By the way about the song length, I just realized that a spc/nsf/etc. can contain info about the song length. So this code will give the correct length if that info is present in the songfile.

[code:fg3d4ay5]
int track = 0;
track_info_t info;
gme_track_info( gp->emu, &info, track);

gp->gpwaveformat.lengthpcm = info.length/1000*gp->gpwaveformat.frequency;
[/code:fg3d4ay5]

edit:
If it’s NOT present it seems like it returns 0 or -1 depending on the sound format. So a more complete code would be:

[code:fg3d4ay5]
gp->gpwaveformat.lengthpcm = 0xffffffff;

if(info.length>0)
{
gp->gpwaveformat.lengthpcm = info.length/1000*gp->gpwaveformat.frequency;
}
[/code:fg3d4ay5]

  • You must to post comments
0
0

Just tried your aproach of using setposition to select subsongs and that method works fine.

  • You must to post comments
0
0

Glad to hear it 😀 Thanks for telling me that the length actually is available, too – I’d somehow missed that completely. Maybe one of us should contact Blargg about including source for an FMOD plugin with GME?

  • You must to post comments
0
0

Sure, but I haven’t done all those neat things like returning error codes, checking for first 4 bytes etc. so your code are more complete after you add the song length part.

  • You must to post comments
0
0

I cleaned up my code and made a dll-file along with the code to download at http://andreas.blazer.nu/programs.php

I also mailed Blargg about it.

  • You must to post comments
0
0

as Brett suggeested to use to FMOD_ERR_FILE_EOF to report that a sound has ended doesn’t work though. I tested with the simple playsound example application and isPlaying() still returns true. It works with a bunch of other errors though, I chose FMOD_ERR_INVALID_PARAM.

  • You must to post comments
Showing 9 results
Your Answer

Please first to submit.