Created
April 25, 2013 10:30
-
-
Save benkitzelman/5458854 to your computer and use it in GitHub Desktop.
Generate WAV formatted dtmf tones in C#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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