7

我需要能够使用 C# 动态生成波形并播放它,无需任何外部库,也无需将声音文件存储在硬盘上。延迟不是问题;声音会在应用程序需要它们之前生成。

实际上 Console.Beep() 方法可能会满足我的需求,如果不是因为 Microsoft 表示它在 64 位版本的 Windows 中不受支持。

如果我动态地生成自己的声音,我可以获得比简单的哔声更花哨的声音。例如,我可以从频率从 2 KHz 增加到 4 KHz 的三角波制作波形,同时音量衰减。我不需要花哨的 16 位立体声,只需 8 位单声道就可以了。我不需要对音量和音高进行动态控制,只需基本上在内存中生成一个声音文件并播放它而不存储它。

上一次我需要生成声音是在很多年前,在 Apple II、HP 工作站和我的旧 Amiga 电脑上。从那以后就不需要这样做了,似乎我描述的一些简单的事情变得更加复杂了。我很难相信如此简单的事情似乎如此困难。我看到的大多数答案都是指 NAudio 或类似的库,这不是这个项目的选择(除了拉入整个库只是为了播放音调似乎是一种浪费)。

4

4 回答 4

11

根据我收到的答案中的一个链接,以及我发现的有关 .wav 标头格式的其他一些页面,这是我的一个生成 8 位“叮当”的小类的工作代码!具有用户指定的频率和持续时间的声音。它基本上是一种哔声,在指定的持续时间内振幅线性衰减到零。

public class AlertDing {
    private SoundPlayer player = null;
    private BinaryWriter writer = null;

    /// <summary>
    /// Dynamically generate a "ding" sound and save it to a memory stream
    /// </summary>
    /// <param name="freq">Frequency in Hertz, e.g. 880</param>
    /// <param name="tenthseconds">Duration in multiple of 1/10 second</param>
    public AlertDing(double freq, uint tenthseconds) {

        string header_GroupID = "RIFF";  // RIFF
        uint header_FileLength = 0;      // total file length minus 8, which is taken up by RIFF
        string header_RiffType = "WAVE"; // always WAVE

        string fmt_ChunkID = "fmt "; // Four bytes: "fmt "
        uint fmt_ChunkSize = 16;     // Length of header in bytes
        ushort fmt_FormatTag = 1;        // 1 for PCM
        ushort fmt_Channels = 1;         // Number of channels, 2=stereo
        uint fmt_SamplesPerSec = 14000;  // sample rate, e.g. CD=44100
        ushort fmt_BitsPerSample = 8;   // bits per sample
        ushort fmt_BlockAlign =
            (ushort)(fmt_Channels * (fmt_BitsPerSample / 8)); // sample frame size, in bytes
        uint fmt_AvgBytesPerSec =
            fmt_SamplesPerSec * fmt_BlockAlign; // for estimating RAM allocation

        string data_ChunkID = "data";  // "data"
        uint data_ChunkSize;           // Length of header in bytes
        byte [] data_ByteArray;

        // Fill the data array with sample data

        // Number of samples = sample rate * channels * bytes per sample * duration in seconds
        uint numSamples = fmt_SamplesPerSec * fmt_Channels * tenthseconds / 10;
        data_ByteArray = new byte[numSamples];

        //int amplitude = 32760, offset=0; // for 16-bit audio
        int amplitude = 127, offset = 128; // for 8-audio
        double period = (2.0*Math.PI*freq) / (fmt_SamplesPerSec * fmt_Channels);
        double amp;
        for (uint i = 0; i < numSamples - 1; i += fmt_Channels) {
            amp = amplitude * (double)(numSamples - i) / numSamples; // amplitude decay
            // Fill with a waveform on each channel with amplitude decay
            for (int channel = 0; channel < fmt_Channels; channel++) {
                data_ByteArray[i+channel] = Convert.ToByte(amp * Math.Sin(i*period) + offset);
            }
        }

        // Calculate file and data chunk size in bytes
        data_ChunkSize = (uint)(data_ByteArray.Length * (fmt_BitsPerSample / 8));
        header_FileLength = 4 + (8 + fmt_ChunkSize) + (8 + data_ChunkSize);

        // write data to a MemoryStream with BinaryWriter
        MemoryStream audioStream = new MemoryStream();
        BinaryWriter writer = new BinaryWriter(audioStream);

        // Write the header
        writer.Write(header_GroupID.ToCharArray());
        writer.Write(header_FileLength);
        writer.Write(header_RiffType.ToCharArray());

        // Write the format chunk
        writer.Write(fmt_ChunkID.ToCharArray());
        writer.Write(fmt_ChunkSize);
        writer.Write(fmt_FormatTag);
        writer.Write(fmt_Channels);
        writer.Write(fmt_SamplesPerSec);
        writer.Write(fmt_AvgBytesPerSec);
        writer.Write(fmt_BlockAlign);
        writer.Write(fmt_BitsPerSample);

        // Write the data chunk
        writer.Write(data_ChunkID.ToCharArray());
        writer.Write(data_ChunkSize);
        foreach (byte dataPoint in data_ByteArray) {
            writer.Write(dataPoint);
        }
        player = new SoundPlayer(audioStream);
    }

    /// <summary>
    /// Call this to clean up when program is done using this sound
    /// </summary>
    public void Dispose() {
        if (writer != null) writer.Close();
        if (player != null) player.Dispose();
        writer = null;
        player = null;
    }

    /// <summary>
    /// Play "ding" sound
    /// </summary>
    public void Play() {
        if (player != null) {
            player.Stream.Seek(0, SeekOrigin.Begin); // rewind stream
            player.Play();
        }
    }
}

希望这应该可以帮助其他试图动态产生简单警报声音而不需要声音文件的人。

于 2012-08-02T23:00:02.697 回答
2

这可能会有所帮助: http ://channel9.msdn.com/coding4fun/articles/Generating-Sound-Waves-with-C-Wave-Oscillators

于 2012-08-01T23:10:45.467 回答
0

下面的文章解释了如何使用 SoundPlayer 生成和播放 *.wav 文件。请注意,SoundPlayer 可以将流作为参数,因此您可以在 MemoryStream 中生成 wav 文件内容并避免保存到文件中。

http://blogs.msdn.com/b/dawate/archive/2009/06/24/intro-to-audio-programming-part-3-synthesizing-simple-wave-audio-using-c.aspx

于 2012-08-01T23:01:09.187 回答
0

我尝试了从Anachronist (2012-10)中截取的代码——它对我有用。

对我来说最大的障碍:摆脱“AlertDing”wav 结尾系统性的“咔哒声”。

这是由代码片段中的“软错误”引起的:

for (uint i = 0; i < numSamples - 1; i += fmt_Channels)

需要更改为

for (uint i = 0; i < numSamples; i += fmt_Channels)

如果不改变,每次“播放”结束时都会产生一个系统的“零”,从而产生尖锐的咔哒声。(= 幅度跳跃 0->min->0)

原来的问题当然暗示“没有点击噪音”:)

于 2018-10-09T09:27:51.037 回答