11

任何人都知道在 C# 中创建任意声波并从扬声器播放的明智方法吗?

这个问题多年来一直不时出现,我总是在多次失败后放弃它而没有找到解决方案。

我想做的就像一个反向可视化器,也就是说,我不想从声音中生成“数字”,我想从数字中生成声音。

就像获取我提供的带有采样率、采样大小和声音数据(例如整数数组)的函数,它会从中生成适当的 wav 文件(实时声音播放是理想的,但我我对此也非常满意)。

我知道 wav 文件规范遍布整个互联网,并且确实多次尝试创建上述功能,在低频方面取得了一些成功,但是一旦我开始弄乱每个样本的比特等......它就变成了一个巨大的、无法控制的混乱。

这不是已经以任何方式完成了吗?我不介意它使用什么,只要它有一个 .NET 托管包装器(我可以从最近的 VS 到时间访问它)。XNA 不支持这种方式的低电平音频。还发现了几个声称可以实现类似功能的示例,但它们要么根本不起作用,要么做的事情完全不同。

谢谢你。

4

3 回答 3

8

这看起来很有趣,所以我开发了一个简单的应用程序:

  • 创建两秒纯音 (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 /* 如果没有找到声音,则静音 (!default) */
        //#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 是资源名称或原子 */

        枚举 PlaySoundFlags
        {
            SND_SYNC = 0x0000,
            SND_ASYNC = 0x0001,
            SND_MEMORY = 0x0004
        }

        // 播放出现在字节数组中的 wav 文件
        静态无效 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);

            常量 int channelCount = 2;
            int sampleSize = sizeof(short) * channelCount * left.Length;
            整数总大小 = 12 + 24 + 8 + 样本大小;

            字节[] wav = 新字节[totalSize];
            诠释 b = 0;

            // RIFF 头
            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';

            // 格式化标题
            wav[b++] = (byte)'f';
            wav[b++] = (byte)'m';
            wav[b++] = (byte)'t';
            wav[b++] = (字节)' ';
            wav[b++] = 16;
            wav[b++] = 0;
            wav[b++] = 0;
            wav[b++] = 0; // 块大小
            wav[b++] = 1;
            wav[b++] = 0; // 压缩代码
            wav[b++] = 频道计数;
            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(short);
            wav[b++] = 0; // 块对齐(每个样本的字节数)
            wav[b++] = sizeof(short) * 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)(right[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;
            常量双标准A = 440;

            常数倍频=标准A;

            整数计数 = 采样率 * 2;// 两秒
            左=新短[计数];
            对 = 新的短 [count];

            for (int i = 0; i != count; ++i)
            {
                双 t = (双) i / 采样率;// 这个样本的时间,以秒为单位
                短 s = (short)Math.Floor(Math.Sin(t * 2 * Math.PI * 频率) * short.MaxValue);
                左[i] = s;
                对[i] = s;
            }
        }

        静态无效主要(字符串 [] 参数)
        {
            短 [] 左;
            短 [] 对;
            整数采样率 = 44100;
            CreateSamples(左出,右出,sampleRate);
            byte[] wav = ConvertSamplesToWavFileFormat(left, right, sampleRate);
            播放Wav(wav);

            /*
            // 将数据写入 wav 文件
            使用 (FileStream fs = new FileStream(@"C:\documents and settings\carlos\desktop\a440stereo.wav", FileMode.Create))
            {
                fs.Write(wav, 0, wav.Length);
            }
            */
        }
    }
}
于 2010-09-19T20:54:35.453 回答
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();
    }
于 2014-02-02T21:25:56.740 回答
2

FMOD 可以从内存中加载样本并具有 C# 包装器。

于 2010-09-18T21:34:08.097 回答