I'm following this tutorial on Microsoft Developer blog: https://blogs.msdn.microsoft.com/dawate/2009/06/24/intro-to-audio-programming-part-3-synthesizing-simple-wave-audio-using-c/
The tutorial is called "Intro to Audio Programming" and I followed all steps, but probably I missed something.
This is a configuration piece extracted from WaveFormatChunk
class in his homonym method:
sChunkID = "fmt ";
dwChunkSize = 16;
wFormatTag = 1;
wChannels = 2;
dwSamplesPerSec = 44100;
wBitsPerSample = 16;
wBlockAlign = (ushort)(wChannels * (wBitsPerSample / 8));
dwAvgBytesPerSec = dwSamplesPerSec * wBlockAlign;
The wBitsPerSample
set the bit depth of the generated function, pretty simple till now.
Then, running the program, all is working with this settings. Generating 1Hz frequency with amplitude 32760 at 16bit / 44.1ksample this is the result:
And that is obviously a correct output.
Now I quote it:
we use an array of shorts because we have 16-bit samples as specified in the format block. If you want to change to 8-bit audio, use an array of bytes. If you want to use 32-bit audio, use an array of floats.
Talking about shortArray
in WaveDataChunk
class
public class WaveDataChunk
{
public string sChunkID; // "data"
public uint dwChunkSize; // Length of header in bytes
public short[] shortArray;
Then, for 32bit audio, changing the shortArray
to float:
public float[] shortArray;
and wBitsPerSample
to 32:
wBitsPerSample = 32;
This is the result: 1Hz 32bit 44.1k
Practically, the frequency is doubled and only half of the time is written. What did I do wrong?? What I've to do?
When you use IEEE floating points, the range should be -1..1 (instead of the min..max integer number). Also, the format is then no longer MS PCM (1) but IEEE FLOAT (3).
The problem with the article is that its chunk definitions are initialized without IEEE FLOAT format in mind. Here's a suggestion of mine to make the code more generic (literally); the chunks will initialize the fields (which I made read-only) in the constructor depending on the type chosen:
public class WaveFormatChunk<T> where T: struct, IConvertible
{
public readonly string sChunkID; // Four bytes: "fmt "
public readonly uint dwChunkSize; // Length of chunk in bytes
public readonly ushort wFormatTag; // 1 (MS PCM)
public readonly ushort wChannels; // Number of channels
public readonly uint dwSamplesPerSec; // Frequency of the audio in Hz... 44100
public readonly uint dwAvgBytesPerSec; // for estimating RAM allocation
public readonly ushort wBlockAlign; // sample frame size, in bytes
public readonly ushort wBitsPerSample; // bits per sample
/// <summary>
/// Initializes a format chunk. Supported data types: byte, short, float
/// </summary>
public WaveFormatChunk(short channels, uint samplesPerSec)
{
sChunkID = "fmt ";
dwChunkSize = 16;
wFormatTag = typeof(T) == typeof(float) || typeof(T) == typeof(double) ? 3 : 1;
wChannels = channels;
dwSamplesPerSec = samplesPerSec;
wBitsPerSample = (ushort)(Marshal.SizeOf<T>() * 8);
wBlockAlign = (ushort)(wChannels * ((wBitsPerSample + 7) / 8));
dwAvgBytesPerSec = dwSamplesPerSec * wBlockAlign;
}
}
public class WaveDataChunk<T> where T: struct, IConvertible
{
public readonly string sChunkID; // "data"
public readonly uint dwChunkSize; // Length of data chunk in bytes
public readonly T[] dataArray; // 8-bit audio
/// <summary>
/// Initializes a new data chunk with a specified capacity.
/// </summary>
public WaveDataChunk(uint capacity)
{
dataArray = new T[capacity];
dwChunkSize = (uint)(Marshal.SizeOf<T>() * capacity);
sChunkID = "data";
}
}
public void FloatWaveGenerator(WaveExampleType type)
{
// Init chunks
header = new WaveHeader();
format = new WaveFormatChunk<float>(2, 44100);
data = new WaveDataChunk<float>(format.dwSamplesPerSec * format.wChannels);
// Fill the data array with sample data
switch (type)
{
case WaveExampleType.ExampleSineWave:
double freq = 440.0f; // Concert A: 440Hz
// The "angle" used in the function, adjusted for the number of channels and sample rate.
// This value is like the period of the wave.
double t = (Math.PI * 2 * freq) / format.dwSamplesPerSec;
for (uint i = 0; i < format.dwSamplesPerSec - 1; i++)
{
// Fill with a simple sine wave at max amplitude
for (int channel = 0; channel < format.wChannels; channel++)
{
data.dataArray[i * format.wChannels + channel] = (float)Math.Sin(t * i);
}
}
break;
}
}
Note that you'll need to adjust the variables used by the FloatWaveGenerator, and the foreach loop saving the data must write the correct data type as well. I leave this as an exercise to you. :)