184 lines
6.6 KiB
C#
184 lines
6.6 KiB
C#
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.IO;
|
|||
|
using System.Linq;
|
|||
|
using System.Text;
|
|||
|
using System.Threading.Tasks;
|
|||
|
|
|||
|
namespace AudioToneGenerator
|
|||
|
{
|
|||
|
class Program
|
|||
|
{
|
|||
|
static void Main(string[] args)
|
|||
|
{
|
|||
|
FileStream fs = new FileStream("F:\\testFile.wav", FileMode.Create);
|
|||
|
Stream s = GenerateWaveFormStream(50, 4800000);
|
|||
|
s.CopyTo(fs);
|
|||
|
fs.Flush();
|
|||
|
fs.Close();
|
|||
|
Console.WriteLine("Complete");
|
|||
|
|
|||
|
Console.ReadLine();
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// Internal class that adds in simple write buffer support
|
|||
|
/// </summary>
|
|||
|
internal class MemoryStream_Mod : MemoryStream
|
|||
|
{
|
|||
|
public void Write(byte[] buffer)
|
|||
|
{
|
|||
|
Write(buffer, 0, buffer.Length);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static internal byte[] GetBytes(int i)
|
|||
|
{
|
|||
|
byte[] bytes = BitConverter.GetBytes(i);
|
|||
|
if (!BitConverter.IsLittleEndian)
|
|||
|
{
|
|||
|
// We need data in little-endian format; so reverse the array
|
|||
|
Array.Reverse(bytes);
|
|||
|
}
|
|||
|
return bytes;
|
|||
|
}
|
|||
|
|
|||
|
static internal byte[] GetBytes(uint i)
|
|||
|
{
|
|||
|
byte[] bytes = BitConverter.GetBytes(i);
|
|||
|
if (!BitConverter.IsLittleEndian)
|
|||
|
{
|
|||
|
// We need data in little-endian format; so reverse the array
|
|||
|
Array.Reverse(bytes);
|
|||
|
}
|
|||
|
return bytes;
|
|||
|
}
|
|||
|
static internal byte[] GetBytes(short i)
|
|||
|
{
|
|||
|
byte[] bytes = BitConverter.GetBytes(i);
|
|||
|
if (!BitConverter.IsLittleEndian)
|
|||
|
{
|
|||
|
Array.Reverse(bytes);
|
|||
|
}
|
|||
|
return bytes;
|
|||
|
}
|
|||
|
|
|||
|
static internal byte[] GetBytes(Int24 i)
|
|||
|
{
|
|||
|
byte[] bytes = BitConverter.GetBytes(i); // final irrelevant byte is 0; create a new array and ignore
|
|||
|
byte[] tripleBytes;
|
|||
|
if (BitConverter.IsLittleEndian)
|
|||
|
{
|
|||
|
// Little-endian, so use the first three bytes
|
|||
|
tripleBytes = new byte[] { bytes[0], bytes[1], bytes[2] };
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Big-endian, so use the last three, beginning from the end
|
|||
|
tripleBytes = new byte[] { bytes[3], bytes[2], bytes[1] };
|
|||
|
}
|
|||
|
return tripleBytes;
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// Non-standard 24-bit integer. Uses the system's endianness (underlying implementation is Int32)
|
|||
|
/// </summary>
|
|||
|
internal readonly struct Int24
|
|||
|
{
|
|||
|
private readonly int m_value;
|
|||
|
|
|||
|
public const int MaxValue = 0x7FFFFF;
|
|||
|
public const int MinValue = -0x800000;
|
|||
|
|
|||
|
public bool Equals(Int24 obj)
|
|||
|
{
|
|||
|
return m_value == obj.m_value;
|
|||
|
}
|
|||
|
|
|||
|
public Int24(int i)
|
|||
|
{
|
|||
|
if (i > MaxValue || i < MinValue)
|
|||
|
{
|
|||
|
throw new ArgumentOutOfRangeException(nameof(i), i.ToString());
|
|||
|
}
|
|||
|
m_value = i;
|
|||
|
}
|
|||
|
|
|||
|
public static explicit operator Int24(int i) => new Int24(i);
|
|||
|
public static implicit operator int(Int24 i) => i.m_value;
|
|||
|
|
|||
|
public override string ToString() => m_value.ToString();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Non-standard unsigned 24-bit integer. Uses the system's endianness (underlying implementation is Int32)
|
|||
|
/// </summary>
|
|||
|
internal readonly struct UInt24
|
|||
|
{
|
|||
|
private readonly int m_value;
|
|||
|
|
|||
|
public const int MaxValue = 0xFFFFFF;
|
|||
|
public const int MinValue = 0x0;
|
|||
|
|
|||
|
public bool Equals(UInt24 obj)
|
|||
|
{
|
|||
|
return m_value == obj.m_value;
|
|||
|
}
|
|||
|
|
|||
|
public UInt24(int i)
|
|||
|
{
|
|||
|
if (i > MaxValue || i < MinValue)
|
|||
|
{
|
|||
|
throw new ArgumentOutOfRangeException(nameof(i), i.ToString());
|
|||
|
}
|
|||
|
m_value = i;
|
|||
|
}
|
|||
|
|
|||
|
public static explicit operator UInt24(int i) => new UInt24(i);
|
|||
|
public static implicit operator int(UInt24 i) => i.m_value;
|
|||
|
|
|||
|
public override string ToString() => m_value.ToString();
|
|||
|
}
|
|||
|
|
|||
|
public static Stream GenerateWaveFormStream(int frequency, uint numberOfSamples, int sampleRate=48000, short numChannels=1, short bitsPerSample=24)
|
|||
|
{
|
|||
|
// Create a variant of the memory stream which allows shorthand writing
|
|||
|
MemoryStream_Mod ms = new MemoryStream_Mod();
|
|||
|
// Create the file header
|
|||
|
ms.Write(Encoding.ASCII.GetBytes("RIFF"));
|
|||
|
ms.Write(GetBytes(0)); // File size in bytes
|
|||
|
ms.Write(Encoding.ASCII.GetBytes("WAVEfmt ")); // File type and format chunk
|
|||
|
ms.Write(GetBytes((int)ms.Length)); // Length of currently written bytes
|
|||
|
ms.Write(GetBytes((short)1)); // short; little-endian = 1 [= PCM data]
|
|||
|
ms.Write(GetBytes(numChannels)); // Number of channels
|
|||
|
ms.Write(GetBytes(sampleRate)); // Sample Frequency
|
|||
|
ms.Write(GetBytes((sampleRate * numChannels * bitsPerSample) / 8)); // Effective byte rate (num channels * sample rate * bitrate) / 8
|
|||
|
ms.Write(GetBytes((short)((bitsPerSample * numChannels)/8))); // Block align
|
|||
|
ms.Write(GetBytes(bitsPerSample));
|
|||
|
// Data block
|
|||
|
ms.Write(Encoding.ASCII.GetBytes("data"));
|
|||
|
ms.Write(GetBytes((uint)(numberOfSamples * numChannels * bitsPerSample) / 8));
|
|||
|
// Actual sound data
|
|||
|
// Frequency == C->H->C->L->C oscillations per second; so angular velocity is 2 x Pi x frequency
|
|||
|
// Sample rate = number of times/second we sample that value
|
|||
|
// Therefore, if the sample was 1-i per second, 20 Hz would be (20 * 360)i
|
|||
|
UInt24[] channelData = new UInt24[numChannels];
|
|||
|
double angularVelocity = 2 * Math.PI * frequency;
|
|||
|
double timeBetweenSamples = 1 / (double)sampleRate;
|
|||
|
double samplesAngular = angularVelocity / sampleRate;
|
|||
|
|
|||
|
double normalisedFrequency = frequency / timeBetweenSamples;
|
|||
|
for (uint i = 0; i < numberOfSamples; i++)
|
|||
|
{
|
|||
|
for (int n = 0; n < numChannels; n++)
|
|||
|
{
|
|||
|
double curr = Math.Sin(samplesAngular * i);
|
|||
|
ms.Write(GetBytes((Int24)(((Int24.MaxValue-1) / 2) * curr)));
|
|||
|
}
|
|||
|
}
|
|||
|
ms.Position = 4;
|
|||
|
ms.Write(GetBytes((int)ms.Length));
|
|||
|
ms.Position = 0; // Return the stream position to the beginning
|
|||
|
return ms;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|