by Wojciech Sura

How to write a tone generator?

Let’s continue our adventure with FMOD. Today we’ll write a simple sine tone generator.

FMOD supports live streaming of the sound – it periodically asks program for new block of data. We’ll use it to generate sine wave of a specific frequency.

First of all, we need to instantiate and initialize a System object. Nothing new here.

[csharp]public Form1()
{
InitializeComponent();

FMOD.Factory.System_Create(ref system);
system.init(1, FMOD.INITFLAGS.NORMAL, IntPtr.Zero);
}[/csharp]

Now we have to prepare special version of Sound class – a stream. This process is a little bit more complicated than creating a sound from mp3, so let’s take a closer look on the following piece of source code:

[csharp]private const int sampleRate = 44100;
private const int channels = 1;
private const int lengthInSec = 1;
private const int freq = 440;

private void bPlay_Click(object sender, EventArgs e)
{
var exInfo = new FMOD.CREATESOUNDEXINFO();
exInfo.cbsize = Marshal.SizeOf(exInfo);
exInfo.decodebuffersize = sampleRate; // 1 sec
exInfo.length = sampleRate * sizeof(short) * lengthInSec;
exInfo.numchannels = channels;
exInfo.defaultfrequency = sampleRate;
exInfo.format = FMOD.SOUND_FORMAT.PCM16;
exInfo.pcmreadcallback = new FMOD.SOUND_PCMREADCALLBACK(DoReadData);
exInfo.pcmsetposcallback = new FMOD.SOUND_PCMSETPOSCALLBACK(DoSetPos);

current = 0;

system.createStream(String.Empty, FMOD.MODE.OPENUSER | FMOD.MODE.LOOP_NORMAL, ref exInfo, ref sound);
system.playSound(FMOD.CHANNELINDEX.FREE, sound, false, ref channel);
}[/csharp]

A special structure is used to describe, what kind of stream do we want to create. Inside, we specify:

  • cbsize – size of the structure in bytes. Remember, FMOD is natively a C/C++ library.
  • decodebuffersize – size of the buffer, for which FMOD will be asking us periodically, during the playback. We fill it with sampleRate – 44100 samples, which will represent a second of playback (since there are exactly 44100 per second)
  • length – how long is the sound, which we want to stream. In this case it is irrelevant, since sound will be looped.
  • numchannels – how many channels will we use (in this case only a mono sound)
  • defaultfrequency – frequency of sampling
  • format – format of data, which will be sent to FMOD. PCM16 means, that we will send raw data composed of 16-bit signed integer (short).
  • pcmreadcallback – a delegate to method, which will be called whenever FMOD requires data for playback.
  • pcmsetposcallback – a delegate to method, which will be called if someone tries to change position of playback.

Notice, that we pass FMOD.MODE.LOOP_NORMAL to ensure, that sound will be looped. Now we can implement both callbacks.

[csharp]private FMOD.RESULT DoSetPos(IntPtr soundraw, int subsound, uint position, FMOD.TIMEUNIT postype)
{
return FMOD.RESULT.OK;
}[/csharp]

Since we actually don’t plan allowing changing position of the playback, we may simply ignore this callback, returning FMOD.RESULT.OK.

The data read callback is a little bit more complicated.

[csharp]private FMOD.RESULT DoReadData(IntPtr soundraw, IntPtr data, uint datalen)
{
int dataCount = (int)(datalen / sizeof(short));
short[] rawData = new short[dataCount];

double multiplier = ((double)sampleRate / (double)freq) / (2 * Math.PI);
for (int i = 0; i < rawData.Length; i++)
rawData[i] = (short)(Math.Sin((current + i) / multiplier) * short.MaxValue);

Marshal.Copy(rawData, 0, data, rawData.Length);

current += rawData.Length;

return FMOD.RESULT.OK;
}[/csharp]

FMOD gives us two key information. First is data: pointer to buffer in which we will place the actual wave data. Second is datalen, which is size of the buffer (in bytes, so count of samples is actually datalen / sizeof(short)).

Now some maths. We want to achieve sound of frequency 440 Hz. We know, that one second contains 44100 samples, and we want one second to contain 440 complete sine waves, so single wave will be (44100/440) ~= 100.22 samples long. Since sine loops at 2π, we have to multiply the actual data passed to Math.Sin by 2π (such that it will loop at 100.22 samples instead).

The rest is simple – we copy the data to given buffer using the Marshal.Copy method and return FMOD.RESULT.OK. The current field is used in order to provide fluent playback.

Stopping code is identical as in previous example.

[csharp]private void bStop_Click(object sender, EventArgs e)
{
if (channel != null)
channel.stop();
}[/csharp]

by Wojciech Sura

FMOD – great Sound library

A while ago I presented a way to write an mp3 player in 16 lines of code. Let’s try to do the same thing with a different sound library, FMOD. FMOD has an advantage over other sound libraries, because it has the same API for different platforms: Win32, Windows 8, Windows phone 8, Macintosh, iOS, Linux, Android, Blackberry and several gaming platforms, like PS3/4/PSP/Vita, Xbox 360/One, Wii etc.

FMOD comes as a native DLL library – fmodex.dll – which is required for the application to run properly. For convenience, we may add it to the project and set its Copy to Output Directory property to Copy always – Visual Studio will take care for copying that DLL for us.

fmod-1fmod-2

fmodes.dll is native, but fortunately FMOD team provides an almost-complete set of wrappers for C#, so we won’t have to P/Invoke our way to play a sound.

Let’s start with adding a few fields to our class.

[csharp] private FMOD.System system;
private FMOD.Sound sound;
private FMOD.Channel channel;[/csharp]

System is the core of FMOD – it creates sound objects, allows starting playback etc. Sound is a class representing the actual sound, being played. Finally, Channel is a class, which represents the actual playback process and allows modifying its aspects – for instance, the volume.

The System object can be created with aid of FMOD.Factory. We may create and initialize it in form’s constructor, as there’s usually no point in keeping several instances of System.

[csharp]public Form1()
{
InitializeComponent();

FMOD.Factory.System_Create(ref system);
system.init(1, FMOD.INITFLAGS.NORMAL, IntPtr.Zero);
}[/csharp]

Now we can implement the Open & Play button. Fortunately, System.createSound method accepts filename as its first parameter, so the process will be as easy as in case of NAudio.

[csharp]private void bPlay_Click(object sender, EventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog()
{
Filter = "Sound files (*.mp3;*.wav;*.wma)|*.mp3;*.wav;*.wma"
};

if (dialog.ShowDialog() == DialogResult.OK)
{
system.createSound(dialog.FileName, FMOD.MODE.DEFAULT, ref sound);
system.playSound(FMOD.CHANNELINDEX.FREE, sound, false, ref channel);
}
}[/csharp]

Stopping playback is also quite an easy task – it’s only a matter of asking a Channel object to stop.

[csharp]private void bStop_Click(object sender, EventArgs e)
{
if (channel != null)
channel.stop();
}[/csharp]

And that’s all we need to play a sound. Keep in mind, that this example program does not provide any error checking – all FMOD methods return FMOD.Result, which informs about outcome of the operation.