是否好奇不同乐器演奏时声音的波形有何不同?是否想知道如何将声音转化为可视化的图像?欢迎来到初学者的波形探索之旅!
通过这个小程序,可以从零开始,通过添加多种频率来合成声音。可以聆听创作,并添加正弦波,直到达到期望的结果。除了常见的波形显示外,还可以在“联觉模式”下观察声音的变化。这种替代的可视化方式会将整个波形创建为一个位图,可以缩放和着色,以观察声音的移动和成长。
编写WaveMixer有两个很好的理由:乐趣和好奇心。为了另一个声音应用程序,需要一个包含特定频率的干扰噪音。当意识到对PCM波的了解太肤浅,无法生成自然听起来的噪音时,牺牲了一个周末,制作了这个不错的工具。这是进入脉冲编码调制波形世界的一个舒适的起点。
声音是通过C#代码播放的,使用了waveIn/waveOut API。
在开始合成声音之前,需要一个空的波形。WaveMixer从一个五秒钟的干净波形开始。如果想要有更短或更长的声音,请输入长度(以秒为单位)并创建它:
WaveFormat format = new WaveFormat(44100, 16, 2);
short[] samples = new short[(int)(format.Channels * format.SamplesPerSec * numNewWave.Value)];
WaveSound waveSound = new WaveSound(format, samples);
waveControl.WaveSound = waveSound;
到目前为止还没有发生任何事情;新的波形只是被初始化了。上面的四行代码完成了这个工作。
A音定义为440Hz,这将是要添加的第一个频率。输入A音的频率,正弦波的最大振幅在1到32767之间,以及波形应该添加的时间:
结果看起来无聊,听起来也一样。左右声道包含相同的“哔哔声”:
此时的联觉视图并没有好多少,但可能可以尝试不同的图像大小,看看结果如何变化:
PCM代表脉冲编码调制,意味着没有波函数或确切的描述(可能从MIDI标准中知道这一点),相反,是从实际波形中取样。一个.wav文件包含大量的快照,WaveOut API将它们发送到扬声器,以便可以听到类似于原始波形的声音。刚刚从头开始创建了这样一组波形样本,以下是按钮背后的代码。它计算了第一个和最后一个受影响样本的索引,然后将新的样本值混合到现有的样本中。
public void AddWave(WaveSound waveSound, float frequencyHz, float offsetSeconds, float lengthSeconds, int amplitude)
{
short[] samples = waveSound.Samples;
double xStep = (2 * Math.PI) / waveSound.Format.SamplesPerSec;
xStep = xStep * frequencyHz;
long lastSample;
double xValue = 0;
short yValue;
short channelSample;
long offsetSamples = (long)(waveSound.Format.Channels * waveSound.Format.SamplesPerSec * offsetSeconds);
if (offsetSamples < samples.Length)
{
lastSample = (long)(waveSound.Format.Channels * waveSound.Format.SamplesPerSec * (offsetSeconds + lengthSeconds));
if (lastSample > samples.Length)
{
lastSample = samples.Length - waveSound.Format.Channels + 1;
}
for (long n = offsetSamples; n < lastSample; n += waveSound.Format.Channels)
{
xValue += xStep;
yValue = (short)(Math.Sin(xValue) * amplitude);
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的波形,但让它从第二秒开始,只持续半秒钟。看看当在这里和那里添加短波形时,声音是如何变化的:
一直盯着黑白图表看很无聊,也可以观察波形位图:
现在会发现声音相当和谐,但仍然不够自然。还需要什么?为样本添加灰尘和污垢,随机波形在变化的频率中!而不是精确的频率,尝试添加一组波形。频率应该在一定的限制内,以保持接近440 Hz的A音。
在添加了几百个接近440 Hz的随机波形之后,建议从第二秒开始,添加一个精确的波形,持续到最后,振幅至少为30000。这将使奇怪的噪音再次变得清晰。结果听起来很迷人,看起来也很惊人:
在多彩的视图中,可以看到最后一个精确的波形主导了声音,只留下了之前添加的干扰的很小空间:
很长一段时间以来,一直在想噪音真正的颜色是什么。每个人看到的相同声音的颜色都不同,大多数人甚至什么也看不到,他们说他们只能“听到”它。将脉冲代码直接转换为像素并不可行,因为有三个颜色(红、绿和蓝),但通常只有两个通道(左右耳的感知)。哪个颜色属于哪个通道?恐怕没有通用答案...
决定让用户决定颜色。WaveMixer不是将两个通道混合成一个像素(例如,左通道为红色,右通道为绿色),而是将它们并排绘制。这意味着,每个样本由两个点表示:左像素和右像素。就像样本一个接一个地播放一样,WaveMixer成对地绘制像素。下一行将从哪里开始,由位图的尺寸定义。将波形图片放大得足够近,这样就可以专注于每一个像素对,并尝试“用眼睛听”。;-)
如果红色、绿色和/或蓝色用于一个通道,可以在用户界面中配置。已经知道这些复选框了。选定的颜色分量的实际值来自样本的值。遗憾的是,Int16波形样本不得不减少到无符号字节值:
float scale = 127f / maximumSampleValueInTheWholeWave;
byte scaledSampleValue = (byte)(sampleValue * scale);
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版本在[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