65.9K
CodeProject 正在变化。 阅读更多。
Home

从频率合成声音并可视化它们

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (48投票s)

2005年12月3日

CPOL

6分钟阅读

viewsIcon

201440

downloadIcon

3485

关于PCM,你永远不想知道的一切。

引言

你是否曾经想过为什么同一个音符在不同乐器上演奏时听起来会不同?你想知道声音如何变得可见吗?欢迎来到初学者的波形探索器!

通过这个小程序,你可以从零开始,通过添加许多频率来合成声音。你可以听到你的声音,并添加正弦波,直到达到所需的结果。除了常见的波形显示,你还可以观看声音在联觉模式下变化。这种替代的可视化将整个波形生成一个位图,你可以缩放和着色,以看到声音的移动和增长。

你可能会想我为什么会写WaveMixer。有两个好理由:乐趣和好奇心。对于另一个声音应用程序,我需要一种包含特定频率的恼人噪声。当我意识到我对PCM波的了解太肤浅,无法生成自然的声音时,我牺牲了一个周末,制作了这个漂亮的工具。它是进入脉冲编码调制波美妙世界的一个舒适的起点。

声音是使用来自用C#使用waveIn/waveOut API编写的全双工音频播放器的代码播放的。

功能及其工作原理

你的第一次波形体验

在开始合成声音之前,我们需要一个空的波形。WaveMixer以五秒钟的干净波形开始。如果你想要更短或更长的声音,请输入秒数并创建它

到目前为止还没有发生任何事情;新波形只被初始化了。这四行代码完成了这个任务

//define sampling rate, sample size and number of channels
WaveFormat format = new WaveFormat(44100, 16, 2);
//initialize the samples
short[] samples = 
  new short[(int)(format.Channels * format.SamplesPerSec * numNewWave.Value)];
//load the empty wave into the display control
WaveSound waveSound = new WaveSound(format, samples);
waveControl.WaveSound = waveSound;

让我们来创建一个A!a' 定义为440Hz,所以这将是第一个要添加的频率。输入a'的频率,正弦波的最大幅度在1到32767之间,以及波形添加的时间

结果看起来很无聊,听起来也一样。左右声道包含相同的哔哔声

此时联觉视图也差不多,但你可以尝试几种不同的图像尺寸,看看结果如何变化

PCM代表脉冲编码调制,意味着没有波形函数或精确的描述(就像你可能从MIDI标准中知道的那样),而是从实际波形中采样。一个.wav文件包含大量的快照,WaveOut API将它们发送到扬声器,以便你能听到与原始波形相似的声音。你刚刚从零开始创建了这样一组波形样本,这里是按钮背后的代码。它计算第一个和最后一个受影响样本的索引,然后将新样本值混合到现有的样本中。

/// <summary>Adds a sine sound of a specific frequency.</summary>
/// <param name="waveSound">The sound that's being edited</param>
/// <param name="frequencyHz">Frequency of the new wave in Hertz.
/// </param>
/// <param name="offsetSeconds">Starting second of the new wave.
/// </param>
/// <param name="lengthSeconds">Length of the new wave in seconds.
/// </param>
/// <param name="amplitude">Maximum amplitude of the new wave.
/// </param>
public void AddWave(WaveSound waveSound,
    float frequencyHz, float offsetSeconds,
    float lengthSeconds, int amplitude)
{
    //get the existing wave samples
    short[] samples = waveSound.Samples;

    //interval for 1 Hz
    double xStep = (2 * Math.PI) / waveSound.Format.SamplesPerSec;

    //interval for the requested frequency = 1Hz * frequencyHz
    xStep = xStep * frequencyHz;

    long lastSample;
    double xValue = 0;
    short yValue;
    short channelSample;

    long offsetSamples = (long)(
        waveSound.Format.Channels
        * waveSound.Format.SamplesPerSec
        * offsetSeconds);

    //if the beginning sample exists
    if (offsetSamples < samples.Length)
    {
        lastSample = (long)(
            waveSound.Format.Channels
            * waveSound.Format.SamplesPerSec
            * (offsetSeconds + lengthSeconds) );

        if (lastSample > samples.Length)
        { //last sample does not exist - shorten the new wave
          lastSample = 
              samples.Length - waveSound.Format.Channels + 1;
        }

        //for all affected samples
        for (long n = offsetSamples; n < lastSample; 
                               n += waveSound.Format.Channels)
        {
            //calculated the next snapshot from the sine wave
            xValue += xStep;
            yValue = (short)(Math.Sin(xValue) * amplitude);

            //mix the value into every channel
            for (int channelIndex = 0; 
               channelIndex < waveSound.Format.Channels; channelIndex++)
            {
                channelSample = samples[n + channelIndex];
                channelSample = (short)((channelSample + yValue) / 2);
                samples[n + channelIndex] = channelSample;
            }
        }
    }
}

在我解释可视化之前,让我们让波形听起来更自然一些。添加220 Hz (a),880 Hz (a'') 和其他八度音阶到波形;记住八度音阶是频率*2或频率/2

结果看起来更有趣,听起来也稍微好一点。不过,一个音符还不算音乐;我们需要一些像基本旋律的东西。再添加一个440 Hz的波形,但让它在第二秒开始,只持续半秒。看看当你在这里和那里添加短波时声音是如何变化的

一直盯着黑白图很无聊,你也可以观察你的波形的位图

现在你会发现声音相当和谐,但仍然不自然。我们需要什么?灰尘和污垢来处理我们的样本,用变化的频率随机添加波形!不要用精确的频率,尝试添加一组波形。频率应该在一定的限制范围内,以使其接近a'的440 Hz。

在几百个接近440 Hz的随机波之后,我建议你添加一个从0秒到结束的精确波,其高幅度至少为30000。这将使奇怪的噪声再次清晰。结果听起来引人入胜,看起来令人惊叹

在彩色视图中,你可以看到最后一个精确的波是如何主导声音,并只为我们之前添加的干扰留下很小的空间

联觉模式

很长一段时间以来,我一直在思考噪声真正具有什么样的颜色。每个人看到的颜色都不同,大多数人甚至什么也看不到,他们说他们只能听到。将脉冲码直接转换为像素是行不通的,因为有三种颜色(红、绿、蓝),而通常只有两个声道(左耳和右耳的感知)。哪种颜色属于哪个声道?我担心没有一个普遍的答案...

我决定将颜色选择权留给用户。WaveMixer不是将两个声道混合成一个像素(例如,红色代表左声道,绿色代表右声道),而是将它们并排绘制。这意味着每个样本由两个点表示:左像素和右像素。就像样本一个接一个地播放一样,WaveMixer成对地绘制像素。下一行从哪里开始由位图的尺寸决定。我将波形图片放得足够近,以便你可以专注于每一个像素对,并尝试“用眼睛去听”。 ;-)

如果红色、绿色和/或蓝色用于某个声道,可以在用户界面中进行配置。你已经知道那些复选框了。所选颜色分量的实际值来自样本的值。遗憾的是,Int16波形样本不得不被简化为无符号byte

//get the factor that reduces the highest sample to 127
float scale = 127f / maximumSampleValueInTheWholeWave;

//scale one sample
byte scaledSampleValue = (byte)(sampleValue * scale);

//mix the sample's colour
pixelColor = Color.FromArgb(
    channelColors[channelIndex].IsRed ? scaledSampleValue : 0,
    channelColors[channelIndex].IsGreen ? scaledSampleValue : 0,
    channelColors[channelIndex].IsBlue ? scaledSampleValue : 0);

当然,图像不必和声音一样大。如果你选择的尺寸包含的像素对少于要可视化的样本数,则样本会被打包成块,并且每个块中最响亮的样本决定颜色

int samplesPerPixel = 1;
if (countPixels < countSamples)
{
    samplesPerPixel = Math.Ceiling(countSamples / countPixels);
}

在源代码文件中,WaveUtility.PaintWave方法解释了如何应用这个块大小来压缩许多样本到一个像素。但在你深入代码之前,让我给你看一个很棒的例子。

玩转Windows系统声音

大多数Windows版本都包含许多小的波形文件,位于[installDrive]\[installPath]\Media,通常路径是c:\windows\media,其中一个文件是notify.wav。即使在波形视图中,它看起来也很搞笑

现在,切换到联觉视图,并将图像的宽度/高度都设置为250。或者,宽度=100,高度=200也不错。

这难道不令人惊叹吗?对于如此熟悉、平淡的声音,Notify.wav看起来真的很酷

顺便说一下...

在继续玩那些奇怪的波形之前,仔细看看这些音符的频率

c' d e f g a h c' d' e' f' g' a' h'
132 148,5 165 176 198 220 247,5 264 297 330 352 396 440 495

并记住,C大调和弦是C-E-G。你可以通过添加以下波形来混合这个和弦

  1. 264 Hz, 幅度 30000
  2. 330 Hz, 幅度 20000
  3. 396 Hz, 幅度 10000

等待,然后看... 或者听,都一样!

© . All rights reserved.