0

Given a DLL that's supposed to play SNES SPC files and FMOD, in C#, why does this call to system.createSound fail?

var ret = system.init(32, FMOD.INITFLAGS.NORMAL, (IntPtr)null);
var soundEx = new FMOD.CREATESOUNDEXINFO()
{
    cbsize = Marshal.SizeOf(soundEx),
    fileoffset = 0,
    length = ~0U,
    numchannels = 2,
    defaultfrequency = 32000,
    format = FMOD.SOUND_FORMAT.PCM16,
    pcmreadcallback = pcmreadcallback,
    pcmsetposcallback = pcmsetposcallback,
    dlsname = null,
};
var mode = FMOD.MODE.DEFAULT | FMOD.MODE.OPENUSER
         | FMOD.MODE.LOOP_NORMAL | FMOD.MODE.CREATESTREAM;
ret = system.createSound((string)null, mode, ref soundEx, ref sound);
//^-- ERR_INVALID_PARAM
ret = system.playSound(FMOD.CHANNELINDEX.FREE, sound, false, ref channel);

Compare that with the usercreatedsound sample that comes with FMOD:

FMOD.MODE mode = (FMOD.MODE._2D | FMOD.MODE.DEFAULT
               | FMOD.MODE.OPENUSER | FMOD.MODE.LOOP_NORMAL
               | FMOD.MODE.HARDWARE);
//snip
createsoundexinfo.cbsize            = Marshal.SizeOf(createsoundexinfo);
createsoundexinfo.fileoffset        = 0;
createsoundexinfo.length            = frequency * channels * 2 * 2;
createsoundexinfo.numchannels       = (int)channels;
createsoundexinfo.defaultfrequency  = (int)frequency;
createsoundexinfo.format            = FMOD.SOUND_FORMAT.PCM16;
createsoundexinfo.pcmreadcallback   = pcmreadcallback;
createsoundexinfo.pcmsetposcallback = pcmsetposcallback;
createsoundexinfo.dlsname           = null;
//snop
result = system.createSound(
    (string)null, 
    (mode | FMOD.MODE.CREATESTREAM), 
    ref createsoundexinfo,
    ref sound);

Length, frequency... doesn't matter.

Edit: I've already confirmed that the SPC player works, at least as far as initialization goes, and the sample that came with FMOD builds and runs just fine. The only particularly meaningful change, aside from twiddling the settings in an attempt to get it to run, is writing it in 4.0 style.

Kawa
  • 1,478
  • 1
  • 15
  • 20

1 Answers1

1

Playback of an arbitrary sample rate is not necessarily available on all sound cards. 32Khz is not a 'common' rate such 44.1, 48, 96 etc ...

  • Did you try FMOD.MODE.SOFTWARE ?
  • You might be able to use an arbitrate sample rate using ASIO4ALL but you'll need to switch to ASIO.

Are you tied to FMOD ? If not, then consider BASS.NET, you'll have much more control than in FMOD for doing such stuff.

Example for using BASSMix : see remarks below

using System;
using Un4seen.Bass;
using Un4seen.Bass.AddOn.Mix;

namespace XXX
{
    public class Resampler : IDisposable
    {
        private readonly int _channels;
        private readonly string _filename;
        private readonly int _samplerate;

        public Resampler(string filename, int samplerate, int channels)
        {
            if (filename == null) throw new ArgumentNullException("filename");
            if (samplerate <= 0) throw new ArgumentNullException("samplerate");
            if (channels <= 0) throw new ArgumentNullException("channels");

            #region Initialize BASS stuff

            #endregion

            _filename = filename;
            _samplerate = samplerate;
            _channels = channels;
        }

        #region IDisposable Members

        public void Dispose()
        {
            throw new NotImplementedException();
        }

        #endregion

        public void Resample(string filename)
        {
            if (filename == null) throw new ArgumentNullException("filename");
            Exceptions.ThrowIfPathInvalid(filename);

            #region Create stream and mixer

            int channel = Bass.BASS_StreamCreateFile(filename, 0, 0,
                                                     BASSFlag.BASS_STREAM_PRESCAN | BASSFlag.BASS_STREAM_DECODE |
                                                     BASSFlag.BASS_SAMPLE_FLOAT);
            if (channel == 0)
                throw new BassException("Couldn't create stream.");

            var mixer = BassMix.BASS_Mixer_StreamCreate(22050, 1, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT);
            if (mixer == 0)
                throw new BassException("Couldn't create mixer stream.");

            if (!BassMix.BASS_Mixer_StreamAddChannel(mixer, channel,
                                                     BASSFlag.BASS_MIXER_DOWNMIX | BASSFlag.BASS_MIXER_FILTER |
                                                     BASSFlag.BASS_MIXER_NORAMPIN))
                throw new BassException("Couldn't add stream channel to mixer.");

            #endregion

            int sr = 22050;
            int secs = 20;
            int samples0 = sr * secs;
            var buffer = new float[samples0];
            int length = sizeof(float) * buffer.Length;
            var getData = Bass.BASS_ChannelGetData(mixer, buffer, length | (int)BASSData.BASS_DATA_FLOAT);
            if (getData != length)
            {
                throw new BassException("");
            }
            var bassMixerChannelRemove = BassMix.BASS_Mixer_ChannelRemove(channel);
            var streamFree = Bass.BASS_StreamFree(channel);
            var bassStreamFree = Bass.BASS_StreamFree(mixer);
        }
    }
}

(works in VS2010 with latest bass)

Some extra hints:

  • You must call BASS.Bass_Init first,

  • Do not forget to copy bass.dll and bassmix.dll next to your EXE,

  • I personally would stick to BASS_SAMPLE_FLOAT even though your source is not, the biggest benefit is that you won't get clipping with this format, I'm telling you this because I made an audio wrapper for a NES emu which APU's output was quite 'dynamic', it proven to be helpful. Also that keeps things simpler and of the best quality possible.

  • In your case you'd want to use a push stream and feed it with your SNES output : Bass..::..BASS_StreamCreatePush

  • Create a 'standard' mixer stream (not a decoding one), plug your push stream, play the mixer

  • In the example it is a decoding stream because I needed to process data, not play it back.

  • The BassException is just something I created myself, change it to whatever you want

  • Your best friend will be the CHM help file but also their forum.

  • I suggest you start something with this code then post your new code should you have any problems.

I have to say that BASS is quite hard to grasp at first, but very rewarding in the end. I had the exact same feeling recently when learning OpenGL 3.X; it was incomprehensible at first but now I can do a lot of things. I would say that BASS is on par with OpenGL as it is with 3D.

Sorry but I got quite sick since last time, haven't used the PC mostly, but I seen your message and wanted to respond quickly so you can move on. Should someone complain from SO about the layout of my answer : I will fix it later.

aybe
  • 15,516
  • 9
  • 57
  • 105
  • Software mode and/or 44.1Khz didn't change a thing. Gonna look into BASS.NET for a bit now... – Kawa Jun 02 '12 at 15:48
  • Basically you'll need BassMix addon, you create a 44.1Khz mixer and then you add your 32Khz source to it; finally you play that mixer. If you need help about it, just ask. – aybe Jun 02 '12 at 15:51
  • No I meant as in BASS doesn't work. It breaks on the samples' first call to Bass.BASS_Init(). – Kawa Jun 04 '12 at 17:31
  • For this my 2nd hint, plus you have GetError to know what really happened. Also, if you are on 64-bit : http://stackoverflow.com/questions/10863197/unable-to-capture-unhandled-exceptions-in-winforms/10863328#10863328 – aybe Jun 04 '12 at 21:56