Skip to content

Instantly share code, notes, and snippets.

@benkitzelman
Created April 25, 2013 10:30
Show Gist options
  • Save benkitzelman/5458854 to your computer and use it in GitHub Desktop.
Save benkitzelman/5458854 to your computer and use it in GitHub Desktop.
Generate WAV formatted dtmf tones in C#
using System;
using System.Collections.Generic;
using System.Text;
namespace ConversionLayer.Wave
{
internal class SineWave
{
#region Fields
private double m_frequency;
private double m_dataSlice;
private double m_leftAmplitude;
private double m_rightAmplitude;
#endregion
#region Properties
public double RightAmplitude
{
get { return m_rightAmplitude; }
set { m_rightAmplitude = value; }
}
public double LeftAmplitude
{
get { return m_leftAmplitude; }
set { m_leftAmplitude = value; }
}
public double DataSlice
{
get { return m_dataSlice; }
set { m_dataSlice = value; }
}
public double Frequency
{
get { return m_frequency; }
set { m_frequency = value; }
}
#endregion
#region Constructors
internal SineWave( double p_frequency, double p_leftAmplitude, double p_rightAmplitude )
{
this.m_frequency = p_frequency;
this.m_leftAmplitude = p_leftAmplitude;
this.m_rightAmplitude = p_rightAmplitude;
}
#endregion
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
namespace ConversionLayer.Wave
{
public enum AudioMode
{
Mono,
Left,
Right,
LeftRight,
}
public enum Resolution
{
EightBit = 8,
SixteenBit = 16,
}
public class Wave
{
#region Fields
#region Private Enumerations
private enum WaveSegments
{
RIFF,
WAVE,
fmt,
}
private enum SoundFlags : int
{
SND_SYNC = 0x0000, // play synchronously (default)
SND_ASYNC = 0x0001, // play asynchronously
SND_NODEFAULT = 0x0002, // silence (!default) if sound not found
SND_MEMORY = 0x0004, // pszSound points to a memory file
SND_LOOP = 0x0008, // loop the sound until next sndPlaySound
SND_NOSTOP = 0x0010, // don't stop any currently playing sound
SND_NOWAIT = 0x00002000, // don't wait if the driver is busy
SND_ALIAS = 0x00010000, // name is a registry alias
SND_ALIAS_ID = 0x00110000, // alias is a predefined id
SND_FILENAME = 0x00020000, // name is file name
SND_RESOURCE = 0x00040004 // name is resource name or atom
}
#endregion
private byte[] m_waveBytes;
private Resolution m_resolution;
private AudioMode m_audioMode;
private double m_leftVolume;
private double m_rightVolume;
public static int HEADER_SIZE = 44;
#endregion
#region Properties
public double LeftVolume
{
get { return m_leftVolume; }
}
public AudioMode AudioMode
{
get
{
return m_audioMode;
}
set
{
switch (value)
{
case AudioMode.Mono:
m_waveBytes[22] = 1;
m_waveBytes[23] = 0;
break;
default:
m_waveBytes[22] = 2;
m_waveBytes[23] = 0;
break;
}
m_audioMode = value;
}
}
public double RightVolume
{
get { return m_rightVolume; }
}
public Resolution Resolution
{
get
{
return m_resolution;
}
set
{
m_waveBytes[34] = (byte) (short) value;
m_waveBytes[35] = 0;
this.m_resolution = value;
}
}
internal byte[] Data
{
get
{
return this.m_waveBytes;
}
}
/// <summary>
/// Bytes 40 - 43 Length of Data Samples * Channels * Bits per Sample / 8
/// </summary>
private int DataSize
{
get
{
byte[] bytes = new byte[4];
bytes[0] = this.m_waveBytes[40];
bytes[1] = this.m_waveBytes[41];
bytes[2] = this.m_waveBytes[42];
bytes[3] = this.m_waveBytes[43];
return ExtractInt(bytes);
}
set
{
this.m_waveBytes[40] = ExtractByte(value, 0);
this.m_waveBytes[41] = ExtractByte(value, 1);
this.m_waveBytes[42] = ExtractByte(value, 2);
this.m_waveBytes[43] = ExtractByte(value, 3);
//
// Set the frame size :
// headerlength - 8 + datasize
// WHERE the 8 represents the header bytes before the FrameSizeMeasurement
//
this.FrameSize = (HEADER_SIZE - 8) + value;
}
}
/// <summary>
/// Bytes 04 - 07 Total Length to Follow (Length of File - 8)
/// </summary>
private int FrameSize
{
get
{
byte[] bytes = new byte[4];
bytes[0] = this.m_waveBytes[4];
bytes[1] = this.m_waveBytes[5];
bytes[2] = this.m_waveBytes[6];
bytes[3] = this.m_waveBytes[7];
return ExtractInt(bytes) ;
}
set
{
this.m_waveBytes[4] = ExtractByte(value, 0);
this.m_waveBytes[5] = ExtractByte(value, 1);
this.m_waveBytes[6] = ExtractByte(value, 2);
this.m_waveBytes[7] = ExtractByte(value, 3);
}
}
private int SampleRate
{
get
{
byte[] bytes = new byte[4];
bytes[0] = this.m_waveBytes[24];
bytes[1] = this.m_waveBytes[25];
bytes[2] = this.m_waveBytes[26];
bytes[3] = this.m_waveBytes[27];
return ExtractInt(bytes);
}
set
{
this.m_waveBytes[24] = ExtractByte(value, 0);
this.m_waveBytes[25] = ExtractByte(value, 1);
this.m_waveBytes[26] = ExtractByte(value, 2);
this.m_waveBytes[27] = ExtractByte(value, 3);
}
}
private int BytesPerSecond
{
get
{
byte[] bytes = new byte[4];
bytes[0] = this.m_waveBytes[28];
bytes[1] = this.m_waveBytes[29];
bytes[2] = this.m_waveBytes[30];
bytes[3] = this.m_waveBytes[31];
return ExtractInt(bytes);
}
set
{
this.m_waveBytes[28] = ExtractByte(value, 0);
this.m_waveBytes[29] = ExtractByte(value, 1);
this.m_waveBytes[30] = ExtractByte(value, 2);
this.m_waveBytes[31] = ExtractByte(value, 3);
}
}
private short BytesPerSample
{
set
{
m_waveBytes[32] = (byte) value;
m_waveBytes[33] = 0;
}
}
#endregion
#region Constructors
private Wave( int p_sampleRate, Resolution p_resolution, AudioMode p_audioMode, double p_leftVolumeMix, double p_rightVolumeMix )
{
if (p_leftVolumeMix > 0.5 || p_rightVolumeMix > 0.5) throw new ArgumentException("The left and right Volume mixes cannot exceed 0.5");
this.m_waveBytes = new byte[Wave.HEADER_SIZE];
this.Resolution = p_resolution;
this.AudioMode = p_audioMode;
this.SampleRate = p_sampleRate;
this.m_leftVolume = p_leftVolumeMix;
this.m_rightVolume = p_rightVolumeMix;
BuildHeader();
}
#endregion
#region Operators
public static Wave operator +( Wave p_waveA, Wave p_waveB )
{
if (p_waveA == null) return p_waveB;
if (p_waveB == null) return p_waveA;
if (p_waveA.AudioMode != p_waveB.AudioMode) throw new ArgumentException("Only Waves with identical settings can be appended to one another : Inconguous AudioMode");
if (p_waveA.Resolution != p_waveB.Resolution) throw new ArgumentException("Only Waves with identical settings can be appended to one another : Inconguous Resolution");
if (p_waveA.SampleRate != p_waveB.SampleRate) throw new ArgumentException("Only Waves with identical settings can be appended to one another : Inconguous SampleRate");
Wave merged = new Wave(p_waveA.SampleRate, p_waveA.Resolution, p_waveA.AudioMode, p_waveA.LeftVolume, p_waveA.RightVolume);
//
// resize the target byte array to accomodate the merged data
//
merged.DataSize = p_waveA.DataSize + p_waveB.DataSize;
System.Array.Resize(ref merged.m_waveBytes, merged.DataSize + Wave.HEADER_SIZE);
//
// Copy A's data
//
//p_waveA.Data.CopyTo(merged.m_waveBytes, Wave.HEADER_SIZE);
System.Array.Copy(p_waveA.Data, Wave.HEADER_SIZE, merged.m_waveBytes, Wave.HEADER_SIZE, p_waveA.DataSize);
//
// Append B's Array
//
System.Array.Copy(p_waveB.Data, Wave.HEADER_SIZE, merged.m_waveBytes, p_waveA.Data.Length, (p_waveB.Data.Length - Wave.HEADER_SIZE));
Console.Out.WriteLine("Merged Total Size :" + merged.Data.Length.ToString());
Console.Out.WriteLine("Merged Data Size :" + merged.DataSize.ToString());
Console.Out.WriteLine("Merged Header Size :" + (merged.Data.Length - merged.DataSize).ToString());
return merged;
}
#endregion
#region Methods
#region Private Methods
private void BuildHeader()
{
//
// Init headers
//
m_waveBytes[0] = 82; // R
m_waveBytes[1] = 73; // I
m_waveBytes[2] = 70; // F
m_waveBytes[3] = 70; // F
m_waveBytes[8] = 87; // W
m_waveBytes[9] = 65; // A
m_waveBytes[10] = 86; // V
m_waveBytes[11] = 69; // E
m_waveBytes[12] = 102; // f
m_waveBytes[13] = 109; // m
m_waveBytes[14] = 116; // t
m_waveBytes[15] = 32; // .
m_waveBytes[16] = 16; // Length of Format Chunk
m_waveBytes[17] = 0; // Length of Format Chunk
m_waveBytes[18] = 0; // Length of Format Chunk
m_waveBytes[19] = 0; // Length of Format Chunk
m_waveBytes[20] = 1; // Audio Format
m_waveBytes[21] = 0; // Audio Format
m_waveBytes[36] = 100; // d
m_waveBytes[37] = 97; // a
m_waveBytes[38] = 116; // t
m_waveBytes[39] = 97; // a
//--------------------------------------------------------------------------------
// Set Bytes Per Second
//
//
//--------------------------------------------------------------------------------
int audioChannels = 0;
if (m_audioMode == AudioMode.Mono)
{
audioChannels = 1;
}
else
{
audioChannels = 2;
}
int sampleBytes = 1;
if(m_resolution == Resolution.SixteenBit)
{
sampleBytes = 2;
}
this.BytesPerSecond = this.SampleRate * audioChannels * sampleBytes;
//-------------------------------------------------------------------------------
// Bytes per Sample Channels * Bits per Sample / 8
// 1 = 8 bit Mono
// 2 = 8 bit Stereo or 16 bit Mono
// 4 = 16 bit Stereo
//-------------------------------------------------------------------------------
BytesPerSample = 2;
if (this.m_audioMode == AudioMode.Mono && this.m_resolution == Resolution.EightBit)
{
BytesPerSample = 1;
}
else if (this.m_audioMode != AudioMode.Mono && this.m_resolution == Resolution.SixteenBit)
{
BytesPerSample = 4;
}
}
private byte ExtractByte( int p_int, int p_position )
{
int temp = p_int;
short retValue = 0;
if (p_position == 1)
{
//
// shift right 8 bits.
//
temp = (temp >> 8);
retValue = (short) temp;
}
else if (p_position == 2)
{
//
// shift right 16 bits.
//
temp = (temp >> 16);
retValue = (short) temp;
}
else if (p_position == 3)
{
//
// shift right 24 bits.
//
temp = (temp >> 24);
retValue = (short) temp;
}
else
{
//
// only grab the first 8 bits (byte)
//
retValue = (short) (temp & 255);
}
return (byte) retValue;
}
private int ExtractInt( byte[] p_segment )
{
if (p_segment.Length != 4) throw new ArgumentException("4 bytes must be provided to convert to an int");
int theValue = 0;
int temp = (short) p_segment[0];
theValue += temp;
temp = ((short) p_segment[1]) << 8;
theValue += temp;
temp = ((short) p_segment[2]) << 16;
theValue += temp;
temp = ((short) p_segment[3]) << 24;
theValue += temp;
return theValue;
}
#endregion
#region Public Methods
public void PlayWave()
{
IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(this.m_waveBytes, 0);
PlaySound(ptr, UIntPtr.Zero, (uint) SoundFlags.SND_MEMORY);
}
public void SaveWave( string p_filePath )
{
FileStream writer = null;
try
{
writer = new FileStream(p_filePath, FileMode.Create, FileAccess.Write, FileShare.Write);
writer.Write(this.m_waveBytes, 0, m_waveBytes.Length);
}
finally
{
if (writer != null) writer.Close();
}
}
#endregion
#region Internal Methods
//
// Used in testing only
//
internal void AppendSimple()
{
int limit = 127;
int dataSize = 8000;
int sampleRate = 8000;
int samples = 8000;
double frequencyA = 1209;
double frequencyB = 697;
double waveTimeA = 1 / frequencyA;
double waveTimeB = 1 / frequencyB;
double sampleTime = 1 / sampleRate;
double frequencyASlice = (2 * Math.PI) / (waveTimeA / sampleTime);
double frequencyBSlice = (2 * Math.PI) / (waveTimeB / sampleTime);
System.Array.Resize(ref this.m_waveBytes, 44 + dataSize);
//-------------------------------------------------------------------------------
// Bytes 40 - 43 Length of Data Samples * Channels * Bits per Sample / 8
//-------------------------------------------------------------------------------
this.m_waveBytes[40] = ExtractByte(dataSize, 0);
this.m_waveBytes[41] = ExtractByte(dataSize, 1);
this.m_waveBytes[42] = ExtractByte(dataSize, 2);
this.m_waveBytes[43] = ExtractByte(dataSize, 3);
//-------------------------------------------------------------------------------
// Bytes 04 - 07 Total Length to Follow (Length of File - 8)
//-------------------------------------------------------------------------------
int fileSize = dataSize + 36;
m_waveBytes[4] = ExtractByte(fileSize, 0);
m_waveBytes[5] = ExtractByte(fileSize, 1);
m_waveBytes[6] = ExtractByte(fileSize, 2);
m_waveBytes[7] = ExtractByte(fileSize, 3);
//-----------------------------------------------------------------------
// 8 Bit Sample Data
// 128 + 63*sin(n*2*pi*f1/8000) + 63*sin(n*2*pi*f2/8000)
//-----------------------------------------------------------------------
for (int i = 0; i < samples; i++)
{
double sample = 128 + 63 * Math.Sin(i * 2 * Math.PI * frequencyA / 8000);
sample = sample + (63 * Math.Sin(i * 2 * Math.PI * frequencyB / 8000));
this.m_waveBytes[44 + i] = ExtractByte(Convert.ToInt32(sample), 0);
}
}
internal void AppendSample(SineWave[] p_sineWaves, TimeSpan p_duration)
{
double sampleTime = 1 / Convert.ToDouble(this.SampleRate);
double seconds = p_duration.TotalMilliseconds / 1000.0;
int samples = Convert.ToInt32(seconds / sampleTime);
foreach (SineWave sine in p_sineWaves)
{
double waveTime = 1 / sine.Frequency;
sine.DataSlice = (2 * Math.PI) / (waveTime / sampleTime);
//
// if inserting silence
//
if (sine.Frequency == 0.0)
{
sine.DataSlice = 0.0;
}
}
//
// Set the dataSize measurement for the wav to be created
// no of samples * sample size bytes
//
this.DataSize = samples * (((int) m_resolution) / 8);
if(this.m_audioMode != AudioMode.Mono)
DataSize = samples * 2 * (((int) m_resolution) / 8);
//
// Resize the array to accomodate the new samples
//
int startIndex = this.m_waveBytes.Length;
System.Array.Resize(ref this.m_waveBytes, startIndex + DataSize);
//-------------------------------------------------------------------------------
// Bytes 44 - End of array Data Samples
//-------------------------------------------------------------------------------
for (int i = 0; i < samples; i++)
{
int limit = 127;
double dataPtLeft = 0;
double dataPtRight = 0;
//
// Set the volume for the left and right channels
//
foreach (SineWave sine in p_sineWaves)
{
dataPtLeft += (Math.Sin(i * sine.DataSlice) * sine.LeftAmplitude);
dataPtRight += (Math.Sin(i * sine.DataSlice) * sine.RightAmplitude);
}
//
// 8 Bit Data
//
if (this.m_resolution == Resolution.EightBit)
{
//
// Normalize the data for left and right channels
//
int dataLeft = Convert.ToInt32(dataPtLeft * m_leftVolume * limit) + limit;
int dataRight = Convert.ToInt32(dataPtRight * m_rightVolume * limit) + limit;
//
// Write sample data to bytes collection
//
if (this.m_audioMode == AudioMode.Mono)
{
this.m_waveBytes[startIndex + i] = ExtractByte(dataLeft, 0);
}
else if (this.m_audioMode == AudioMode.LeftRight)
{
this.m_waveBytes[startIndex + (i * 2)] = ExtractByte(dataLeft, 0); // 44
this.m_waveBytes[(startIndex + 1) + (i * 2)] = ExtractByte(dataRight, 0); // 45
}
else if (this.m_audioMode == AudioMode.Left)
{
this.m_waveBytes[startIndex + (i * 2)] = ExtractByte(dataLeft, 0);
this.m_waveBytes[(startIndex + 1) + (i * 2)] = 0;
}
if (this.m_audioMode == AudioMode.Right)
{
this.m_waveBytes[startIndex + (i * 2)] = 0;
this.m_waveBytes[(startIndex + 1) + (i * 2)] = ExtractByte(dataRight, 0);
}
}
//
// 16 bit data
//
else
{
limit = 32767;
//
// Normalize the data for left and right channels
//
int dataLeft = Convert.ToInt32(dataPtLeft * m_leftVolume * limit);
int dataRight = Convert.ToInt32(dataPtRight * m_rightVolume * limit);
if(AudioMode == AudioMode.Mono)
{
this.m_waveBytes[startIndex + (i*2)] = ExtractByte(dataLeft, 0);
this.m_waveBytes[(startIndex + 1) + (i*2)] = ExtractByte(dataLeft, 1);
}
else if(AudioMode == AudioMode.LeftRight)
{
this.m_waveBytes[startIndex + (i*4)] = ExtractByte(dataLeft, 0);
this.m_waveBytes[(startIndex + 1) + (i*4)] = ExtractByte(dataLeft, 1);
this.m_waveBytes[(startIndex + 2) + (i*4)] = ExtractByte(dataRight, 1);
this.m_waveBytes[(startIndex + 3) + (i*4)] = ExtractByte(dataRight, 1);
}
else if(AudioMode == AudioMode.Left)
{
this.m_waveBytes[startIndex + (i*4)] = ExtractByte(dataLeft, 0);
this.m_waveBytes[(startIndex + 1) + (i*4)] = ExtractByte(dataLeft, 1);
this.m_waveBytes[(startIndex + 2) + (i*4)] = 0;
this.m_waveBytes[(startIndex + 3) + (i*4)] = 0;
}
else if(AudioMode == AudioMode.Right)
{
this.m_waveBytes[startIndex + (i*4)] = 0;
this.m_waveBytes[(startIndex + 1) + (i*4)] = 0;
this.m_waveBytes[(startIndex + 2) + (i*4)] = ExtractByte(dataRight, 0);
this.m_waveBytes[(startIndex + 3) + (i*4)] = ExtractByte(dataRight, 1);
}
}
}
}
#endregion
#endregion
#region Static Methods
internal static Wave ConstructWave( SineWave[] p_waves )
{
Wave wave = new Wave(16000, Resolution.SixteenBit, AudioMode.Mono, 0.5, 0.5);
wave.AppendSample(p_waves, TimeSpan.FromSeconds(0.25));
return wave;
}
internal static Wave ConstructWave( SineWave[] p_waves, int p_sampleRateHZ, Resolution p_resolution, AudioMode p_audioMode, TimeSpan p_duration )
{
Wave wave = new Wave(p_sampleRateHZ, p_resolution, p_audioMode, 0.5, 0.5);
wave.AppendSample(p_waves, p_duration);
return wave;
}
#endregion
#region External Methods
[DllImport("winmm.dll", SetLastError = true)]
static extern bool PlaySound( string pszSound, System.UIntPtr hmod, uint fdwSound );
[DllImport("winmm.dll", SetLastError = true)]
static extern bool PlaySound( IntPtr pszSound, System.UIntPtr hmod, uint fdwSound );
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment