Real low level sound generation in C#?
Anyone knows of a sensible way to create an ARBITRARY sound wave in C# and play it back from the speakers?
This issue has been coming back to every now and then for years, I always end up giving it up after a lot of failure without finding a solution.
What I want to do is like a reverse-visualizer, that is, I don't want to generate "numbers" from sound, I want to generate sound from numbers.
Like get a function that I provide with sample rate, sample size, and the sound data (an array of integers for example), and it would generate the appropriate wav file from it (real-time sound playback would be ideal, but I'd be more than pleased with this too).
I know the wav file specifications are all over the interweb, and did make several attempts creating the above function, had some success for low frequencies, but once I start messing with bits per sample etc... it becomes a HUGE,开发者_如何学运维 uncontrollable mess.
Is this not already done in any way? I wouldn't mind what it uses, as long as there's a .NET managed wrapper for it (and I can access it from the most recent VS to time). XNA doesn't support low level audio this way. Also found several examples that claim to achieve something similar, but they either don't work at all, or do something entirely different.
Thank you.
This looked interesting so I've knocked up a simple app that:
- Creates the samples for two seconds of a pure tone (440Hz A).
- Converts them into a byte array in WAV file format.
- Plays the sound by passing the byte array to the PlaySound API.
- Also includes code to save the WAV data to a WAV file.
You can easily change the sample rate, tone frequency and sample duration. The code is very ugly and space-inefficient but it works. The following is a complete command-line app:
using System; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; namespace playwav { class Program { [DllImport("winmm.dll", EntryPoint = "PlaySound", SetLastError = true)] private extern static int PlaySound(byte[] wavData, IntPtr hModule, PlaySoundFlags flags); //#define SND_SYNC 0x0000 /* play synchronously (default) */ //#define SND_ASYNC 0x0001 /* play asynchronously */ //#define SND_NODEFAULT 0x0002 /* silence (!default) if sound not found */ //#define SND_MEMORY 0x0004 /* pszSound points to a memory file */ //#define SND_LOOP 0x0008 /* loop the sound until next sndPlaySound */ //#define SND_NOSTOP 0x0010 /* don't stop any currently playing sound */ //#define SND_NOWAIT 0x00002000L /* don't wait if the driver is busy */ //#define SND_ALIAS 0x00010000L /* name is a registry alias */ //#define SND_ALIAS_ID 0x00110000L /* alias is a predefined ID */ //#define SND_FILENAME 0x00020000L /* name is file name */ //#define SND_RESOURCE 0x00040004L /* name is resource name or atom */ enum PlaySoundFlags { SND_SYNC = 0x0000, SND_ASYNC = 0x0001, SND_MEMORY = 0x0004 } // Play a wav file appearing in a byte array static void PlayWav(byte[] wav) { PlaySound(wav, System.IntPtr.Zero, PlaySoundFlags.SND_MEMORY | PlaySoundFlags.SND_SYNC); } static byte[] ConvertSamplesToWavFileFormat(short[] left, short[] right, int sampleRate) { Debug.Assert(left.Length == right.Length); const int channelCount = 2; int sampleSize = sizeof(short) * channelCount * left.Length; int totalSize = 12 + 24 + 8 + sampleSize; byte[] wav = new byte[totalSize]; int b = 0; // RIFF header wav[b++] = (byte)'R'; wav[b++] = (byte)'I'; wav[b++] = (byte)'F'; wav[b++] = (byte)'F'; int chunkSize = totalSize - 8; wav[b++] = (byte)(chunkSize & 0xff); wav[b++] = (byte)((chunkSize >> 8) & 0xff); wav[b++] = (byte)((chunkSize >> 16) & 0xff); wav[b++] = (byte)((chunkSize >> 24) & 0xff); wav[b++] = (byte)'W'; wav[b++] = (byte)'A'; wav[b++] = (byte)'V'; wav[b++] = (byte)'E'; // Format header wav[b++] = (byte)'f'; wav[b++] = (byte)'m'; wav[b++] = (byte)'t'; wav[b++] = (byte)' '; wav[b++] = 16; wav[b++] = 0; wav[b++] = 0; wav[b++] = 0; // Chunk size wav[b++] = 1; wav[b++] = 0; // Compression code wav[b++] = channelCount; wav[b++] = 0; // Number of channels wav[b++] = (byte)(sampleRate & 0xff); wav[b++] = (byte)((sampleRate >> 8) & 0xff); wav[b++] = (byte)((sampleRate >> 16) & 0xff); wav[b++] = (byte)((sampleRate >> 24) & 0xff); int byteRate = sampleRate * channelCount * sizeof(short); // byte rate for all channels wav[b++] = (byte)(byteRate & 0xff); wav[b++] = (byte)((byteRate >> 8) & 0xff); wav[b++] = (byte)((byteRate >> 16) & 0xff); wav[b++] = (byte)((byteRate >> 24) & 0xff); wav[b++] = channelCount * sizeof(short); wav[b++] = 0; // Block align (bytes per sample) wav[b++] = sizeof(short) * 8; wav[b++] = 0; // Bits per sample // Data chunk header wav[b++] = (byte)'d'; wav[b++] = (byte)'a'; wav[b++] = (byte)'t'; wav[b++] = (byte)'a'; wav[b++] = (byte)(sampleSize & 0xff); wav[b++] = (byte)((sampleSize >> 8) & 0xff); wav[b++] = (byte)((sampleSize >> 16) & 0xff); wav[b++] = (byte)((sampleSize >> 24) & 0xff); Debug.Assert(b == 44); for (int s = 0; s != left.Length; ++s) { wav[b++] = (byte)(left[s] & 0xff); wav[b++] = (byte)(((ushort)left[s] >> 8) & 0xff); wav[b++] = (byte)(right[s] & 0xff); wav[b++] = (byte)(((ushort)right[s] >> 8) & 0xff); } Debug.Assert(b == totalSize); return wav; } // Create a simple sine wave static void CreateSamples(out short[] left, out short[] right, int sampleRate) { const double middleC = 261.626; const double standardA = 440; const double frequency = standardA; int count = sampleRate * 2; // Two seconds left = new short[count]; right = new short[count]; for (int i = 0; i != count; ++i) { double t = (double)i / sampleRate; // Time of this sample in seconds short s = (short)Math.Floor(Math.Sin(t * 2 * Math.PI * frequency) * short.MaxValue); left[i] = s; right[i] = s; } } static void Main(string[] args) { short[] left; short[] right; int sampleRate = 44100; CreateSamples(out left, out right, sampleRate); byte[] wav = ConvertSamplesToWavFileFormat(left, right, sampleRate); PlayWav(wav); /* // Write the data to a wav file using (FileStream fs = new FileStream(@"C:\documents and settings\carlos\desktop\a440stereo.wav", FileMode.Create)) { fs.Write(wav, 0, wav.Length); } */ } } }
FMOD can do sample loads from memory and has a C# wrapper.
How to play from an array below
PlayerEx pl = new PlayerEx();
private static void PlayArray(PlayerEx pl)
{
double fs = 8000; // sample freq
double freq = 1000; // desired tone
short[] mySound = new short[4000];
for (int i = 0; i < 4000; i++)
{
double t = (double)i / fs; // current time
mySound[i] = (short)(Math.Cos(t * freq) * (short.MaxValue));
}
IntPtr format = AudioCompressionManager.GetPcmFormat(1, 16, (int)fs);
pl.OpenPlayer(format);
byte[] mySoundByte = new byte[mySound.Length * 2];
Buffer.BlockCopy(mySound, 0, mySoundByte, 0, mySoundByte.Length);
pl.AddData(mySoundByte);
pl.StartPlay();
}
精彩评论