任何人都知道在C#中创建ARBITRARY声波并从扬声器播放的合理方法吗?
这个问题多年来一直在回归,我总是在经历了很多失败之后放弃了它而没有找到解决方案。
我想做的就像反向可视化,也就是说,我不想从声音中生成“数字”,我想从数字中生成声音。
像获取一个我提供的函数,包括采样率,样本大小和声音数据(例如整数数组),它会从中生成相应的wav文件(实时声音播放将是理想的,但我对此也非常满意)。
我知道wav文件规格遍布整个interweb,并且确实做了几次创建上述功能的尝试,对于低频有一些成功,但是一旦我开始弄乱每个样本的比特等等......它就变成了巨大的,无法控制的混乱。
这还没有以任何方式完成吗? 我不介意它使用什么,只要有一个.NET托管包装(我可以从最近的VS访问它到时间)。 XNA不支持低级音频。还发现了一些声称可以实现类似功能的例子,但它们要么根本不起作用,要么完全不同。
谢谢。
答案 0 :(得分:8)
这看起来很有趣,所以我打破了一个简单的应用程序:
您可以轻松更改采样率,音频和采样持续时间。代码非常丑陋且空间效率低但是有效。以下是一个完整的命令行应用程序:
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); } */ } } }
答案 1 :(得分:2)
FMOD可以从内存中进行样本加载并具有C#包装器。
答案 2 :(得分:2)
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();
}