C#真正的低级声音生成?
任何人都知道在C#中创建ARBITRARY声波并从扬声器播放的合理方法吗?
这个问题已经不断出现多年,我总是在经历了很多失败之后放弃了它而没有找到解决方案。
我想做的就像一个反向可视化器,也就是说,我不想从声音生成“数字”,我想从数字生成声音。
就像我提供的一个函数,我提供了采样率,样本大小和声音数据(例如一个整数数组),它会从中生成相应的wav文件(实时声音播放将是理想的,但我’对此也非常满意)。
我知道wav文件规格遍布整个interweb,并且确实做了几次创建上述function的尝试,对于低频率有一些成功,但是一旦我开始弄乱每个样本的比特等……它就变成了一个巨大的,无法控制的混乱。
这还没有以任何方式完成吗? 我不介意它使用什么,只要它有一个.NET托管包装(我可以从最近的VS访问它到时间)。 XNA不支持低级音频。 还发现了几个声称可以实现类似function的例子,但它们要么根本不起作用,要么做一些完全不同的事情。
谢谢。
这看起来很有趣,所以我敲了一个简单的应用程序:
- 创建两秒纯音(440Hz A)的样本。
- 将它们转换为WAV文件格式的字节数组。
- 通过将字节数组传递给PlaySound API来播放声音。
- 还包括将WAV数据保存到WAV文件的代码。
您可以轻松更改采样率,音频和采样持续时间。 代码非常丑陋且空间效率低但是有效。 以下是一个完整的命令行应用程序:
使用系统; 使用System.Diagnostics; 使用System.IO; 使用System.Runtime.InteropServices; 命名空间playwav { 课程 { [DllImport(“winmm.dll”,EntryPoint =“PlaySound”,SetLastError = true)] private extern static int PlaySound(byte [] wavData,IntPtr hModule,PlaySoundFlags flags); // #define SND_SYNC 0x0000 / *同步播放(默认)* / // #define SND_ASYNC 0x0001 / *异步播放* / // #define SND_NODEFAULT 0x0002 / *沉默(!默认)如果找不到声音* / // #define SND_MEMORY 0x0004 / * pszSound指向内存文件* / // #define SND_LOOP 0x0008 / *循环声音直到下一个sndPlaySound * / // #define SND_NOSTOP 0x0010 / *不要停止任何当前播放的声音* / // #define SND_NOWAIT 0x00002000L / *如果驱动程序忙,请不要等待* / // #define SND_ALIAS 0x00010000L / * name是注册表别名* / // #define SND_ALIAS_ID 0x00110000L / *别名是预定义的ID * / // #define SND_FILENAME 0x00020000L / * name是文件名* / // #define SND_RESOURCE 0x00040004L / * name是资源名称或primefaces* / 枚举PlaySoundFlags { SND_SYNC = 0x0000, SND_ASYNC = 0x0001, SND_MEMORY = 0x0004 } //播放出现在字节数组中的wav文件 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(短)* channelCount * left.Length; int totalSize = 12 + 24 + 8 + sampleSize; byte [] wav = new byte [totalSize]; int b = 0; // RIFF标题 wav [b ++] =(byte)'R'; wav [b ++] =(byte)'我'; 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'; //格式标题 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; //块大小 wav [b ++] = 1; wav [b ++] = 0; //压缩代码 wav [b ++] = channelCount; wav [b ++] = 0; //频道数量 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); //所有通道的字节速率 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(短); wav [b ++] = 0; //块对齐(每个样本的字节数) wav [b ++] = sizeof(短)* 8; wav [b ++] = 0; //每个样本的比特数 //数据块头 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)(右[s]&0xff); wav [b ++] =(byte)(((ushort)right [s] >> 8)&0xff); } Debug.Assert(b == totalSize); 返回wav; } //创建一个简单的正弦波 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; //两秒钟 left = new short [count]; right = new short [count]; for(int i = 0; i!= count; ++ i) { double t =(double)i / sampleRate; //这个样本的时间,以秒为单位 short s =(short)Math.Floor(Math.Sin(t * 2 * Math.PI * frequency)* short.MaxValue); left [i] = s; 对[i] = s; } } static void Main(string [] args) { 短[]左; 短[]右; int sampleRate = 44100; CreateSamples(左,右,sampleRate); byte [] wav = ConvertSamplesToWavFileFormat(left,right,sampleRate); PlayWav(WAV); / * //将数据写入wav文件 using(FileStream fs = new FileStream(@“C:\ documents and settings \ carlos \ desktop \ a440stereo.wav”,FileMode.Create)) { fs.Write(wav,0,wav.Length); } * / } } }
FMOD可以从内存中进行样本加载,并具有C#包装器。
如何从下面的arrays播放
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(); }