Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I'm working on a "Game Maker"-type of application for Windows where the user imports his own assets to be used in the game. I need to be able to load this content at runtime on the engine side.

However I don't want the user to have to install anything more than the XNA runtime, so calling the content pipeline at runtime is out.

For images I'm doing fine using Texture2D.FromStream.

I've also noticed that XNA 4.0 added a FromStream method to the SoundEffect class but it only accepts PCM wave files.

I'd like to support more than wave files though, at least MP3.

Any recommendations? Perhaps some C# library that would do the decoding to PCM wave format.

share|improve this question
    
It's worth noting that the XNA runtime itself has no way to decode MP3s for sound effects. XNA Game Studio handles the conversion to PCM (uncompressed) or a compressed format (not MP3, but I forget exactly what the format is right now) at content build time. (Music, on the other hand, gets handed off to Media Player, which can play MP3s.) –  Andrew Russell Dec 9 '11 at 2:12

3 Answers 3

up vote 2 down vote accepted

I hadn't noticed the FromStream method, that's a good one to remember. I'm now curious about using FromStream vs DynamicSoundEffectInstance, since you could theoretically accomplish a lot of the same work with both methods.

Google turned up this library: http://robburke.net/mle/mp3sharp/ And you could also conceivably call lame.exe from within your app to decode the sound.

I'd be wary of MP3 though, it's a legal can of worms. Adobe pays a hefty fee to Fraunhofer for the right to use it in Flash. Ogg Vorbis is a superior format, easily converted from mp3 using free and open-source software, and there seem to be many more C#/.Net decoder libraries for it.

share|improve this answer
    
Thanks! I was vaguely aware of the legal implications of using MP3 but that cleared it up for me. I'll give those libraries a try to see if they can really decode from MP3 into OGG (which will be done when the user imports the assets) and then from OGG to PCM (at runtime/load time). I'll report back with my results. –  David Gouveia Dec 8 '11 at 18:29
    
The ogg libraries won't be able to, but you could decode with lame then convert to Ogg Vorbis. Just don't include LAME and do as Audacity does, "You need an mp3 decoder, we don't care where you get it, we may know a place to get it, but it's your responsibility to acquire it". And disclaimer: I am not a lawyer. –  michael.bartnett Dec 8 '11 at 18:39
    
I've just noticed that SoundEffect.FromStream expects a PCM wave file (incluiding headers) and a very specific format. I've decoded the OGG file using one of those libraries above but I just can't seem to get FromStream to accept it without throwing an exception. I'm starting to wonder whether I should use an external Sound API such as FMOD instead. –  David Gouveia Dec 8 '11 at 19:15
    
That might be worth it if you don't care about targeting Windows Phone or XBLIG. You could also give DynamicSoundEffectInstance a try, it just throws up an event asking for samples every time it runs low. –  michael.bartnett Dec 8 '11 at 19:46
1  
I've actually used DynamicSoundEffectInstance to make some other stuff before (such as this and this). :) But it also requires you to submit your samples in a very specific format (interleaved 16-bit samples), which I don't know if that's the format of my decoded OGG stream. Since I only need Windows support I went the FMOD route. I'll post my solution using FMOD in a minute. –  David Gouveia Dec 8 '11 at 20:14

I decided to give this problem another try today and finally managed to load an OGG file at runtime into a SoundEffect object. Here's what I did! First download the library below which contains a class capable of decoding OGG files:

Prerequisite - Download library

The library already has an example, but it uses DynamicSoundEffectInstance and streams the audio. But I wanted to load it all at once into a regular SoundEffect object so the process was a bit different.

Step 1 - Decode file

First create an instance of OggDecoder and initialize it with your file:

decoder = new OggDecoder();
decoder.Initialize(TitleContainer.OpenStream(@"sound.ogg"));

Step 2 - Get decoded data

Read all of the data into a buffer. This is the decoded raw PCM data of the file:

byte[] data = decoder.SelectMany(chunk => chunk.Bytes.Take(chunk.Length)).ToArray();

Step 3 - Create SoundEffect from stream containing complete wave file header

However, the stream that SoundEffect requires must contain not only the raw data, but also the complete wave file header. You can use this helper method to write the header plus the data:

private static void WriteWave(BinaryWriter writer, int channels, int rate, byte[] data)
{
    writer.Write(new char[4] { 'R', 'I', 'F', 'F' });
    writer.Write((int)(36 + data.Length));
    writer.Write(new char[4] { 'W', 'A', 'V', 'E' });

    writer.Write(new char[4] { 'f', 'm', 't', ' ' });
    writer.Write((int)16);
    writer.Write((short)1);
    writer.Write((short)channels);
    writer.Write((int)rate);
    writer.Write((int)(rate * ((16 * channels) / 8)));
    writer.Write((short)((16 * channels) / 8));
    writer.Write((short)16);

    writer.Write(new char[4] { 'd', 'a', 't', 'a' });
    writer.Write((int)data.Length);
    writer.Write(data);
}

Use that method to write the data to a stream that can be fed to SoundEffect.FromStream:

using (MemoryStream stream = new MemoryStream())
using(BinaryWriter writer = new BinaryWriter(stream))
{
    WriteWave(writer, decoder.Stereo ? 2 : 1, decoder.SampleRate, data);
    stream.Position = 0;
    soundEffect = SoundEffect.FromStream(stream);
}

Step 4 - Use the SoundEffect as normal

Your OGG file is now loaded and can be used like any other SoundEffect loaded through the content pipeline:

soundEffect.Play();
share|improve this answer

If you're only targeting Windows, I've found that the easiest way is to bypass the XNA Audio API altogether and use something else.

I found the FMOD API to be great for this, and it even comes bundled with a C# wrapper already. I've added my own wrapper around theirs and here's the bare minimum you need to load a sound from a file and play it:

The wrapper:

namespace YourNamespace
{
    using System;
    using FMOD;

    public class SoundSystem
    {
        public SoundSystem()
        {
            RESULT result = Factory.System_Create(ref _system);
            if(result != RESULT.OK) 
                throw new Exception("Create SoundSystem Failed");

            uint version = 0;
            result = System.getVersion(ref version);
            if (result != RESULT.OK || version < VERSION.number)
                throw new Exception("Create SoundSystem Failed");

            result = System.init(32, INITFLAGS.NORMAL, (IntPtr)null);
            if (result != RESULT.OK)
                throw new Exception("Create SoundSystem Failed");
        }

        public System System 
        {
            get { return _system; }
        }

        private readonly System _system;
    }

    public class Sound
    {
        public Sound(SoundSystem system, string path)
        {
            _system = system;
            RESULT result = system.System.createSound(path, MODE.HARDWARE, ref _sound);
            if (result != RESULT.OK)
                throw new Exception("Create Sound Failed");
        }

        public void Play()
        {
            Channel channel = null;
            RESULT result = _system.System.playSound(CHANNELINDEX.FREE, _sound, false, ref channel);
            if (result != RESULT.OK)
                throw new Exception("Play Sound Failed");
        }

        private readonly SoundSystem _system;
        private readonly FMOD.Sound _sound;
    }
}

And how to use it:

SoundSystem system = new SoundSystem();
Sound sound = new Sound(system, "song.mp3");
sound.Play();

Of course that wrapper is just the bare minimum, but should be simple to expand in order to expose any other features you need.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.