C# 中真正的低电平声音生成?

发布于 2024-09-24 04:18:33 字数 462 浏览 10 评论 0原文

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

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

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

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

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

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

谢谢。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

酷到爆炸 2024-10-01 04:18:33

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

  • 创建两秒纯音 (440Hz A) 的样本。
  • 将它们转换为 WAV 文件格式的字节数组。
  • 通过将字节数组传递给 PlaySound API 来播放声音。
  • 还包括将 WAV 数据保存到 WAV 文件的代码。

您可以轻松更改采样率、音调频率和采样持续时间。该代码非常丑陋且空间效率低下,但它可以工作。以下是一个完整的命令行应用程序:

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);
            }
            */
        }
    }
}

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);
            }
            */
        }
    }
}
只为一人 2024-10-01 04:18:33

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

FMOD can do sample loads from memory and has a C# wrapper.

长安忆 2024-10-01 04:18:33

下面如何从数组中播放

    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();
    }

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();
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文