首页 文章

将音符写入wav文件

提问于
浏览
49

我感兴趣的是如何拍音符(例如A,B,C#等)或和弦(同时多个音符)并将它们写入wav文件 .

根据我的理解,每个音符都有一个与之相关的特定频率(对于完美的音高) - 例如A4(中间C以上的A)是440 Hz(完整列表的下行方式的2/3 This Page) .

如果我的理解是正确的,这个音调是在频域,因此需要应用它的逆快速傅里叶变换来生成时域等价?

我想知道的是:

  • 和弦如何工作?他们是球场的平均值吗?

  • 当wav文件的内容是波形时,如何指定播放每个音符的时间长度?

  • 如何将多个音符的反FFT转换为字节数组,这组成了wav文件中的数据?

  • 与此有关的任何其他相关信息 .

谢谢你提供的所有帮助 . 如果给出代码示例,我使用的是C#,我目前用来创建wav文件的代码如下:

int channels = 1;
int bitsPerSample = 8;
//WaveFile is custom class to create a wav file.
WaveFile file = new WaveFile(channels, bitsPerSample, 11025);

int seconds = 60;
int samples = 11025 * seconds; //Create x seconds of audio

// Sound Data Size = Number Of Channels * Bits Per Sample * Samples

byte[] data = new byte[channels * bitsPerSample/8 * samples];

//Creates a Constant Sound
for(int i = 0; i < data.Length; i++)
{
    data[i] = (byte)(256 * Math.Sin(i));
}
file.SetData(data, samples);

这会(以某种方式)创建一个恒定的声音 - 但我不完全理解代码如何与结果相关联 .

3 回答

  • 6

    还没有人提到过Karplus Strong弹拨的字符串算法 .

    Karplus–Strong string synthesis这是一种非常简单的方法,用于生成逼真的弹拨弦乐声 . 我用这个写了复音乐器/实时MIDI播放器 .

    你这样做:

    首先,您想要模拟的频率是多少?让我们说音乐会音高A = 440Hz

    假设您的采样率为44.1kHz,即每波长44100/440 = 100.25个样本 .

    让我们将其舍入到最接近的整数:100,并创建一个循环缓冲区长度100 .

    所以它将保持一个频率~440Hz的驻波(注意它不精确,有这种方法) .

    在-1和1之间填充随机静态,并且:

    DECAY = 0.99
    while( n < 99999 )
        outbuf[n++] = buf[k]
    
        newVal = DECAY  *  ( buf[k] + buf_prev ) / 2
    
        buf_prev = buf[k]
        buf[k] = newVal
    
        k = (k+1) % 100
    

    这是一个了不起的算法,因为它非常简单,并产生超级声音 .

    理解正在发生的事情的最好方法是认识到时域中的随机静态是白噪声;频域中的随机静态 . 你可以把它想象成不同(随机)频率的许多波的复合 .

    接近440Hz(或2 * 440Hz,3 * 440Hz等)的频率会对它们自身造成建设性干扰,因为它们会一次又一次地绕过环 . 所以他们将被保留下来 . 其他频率会破坏性地干扰自己 .

    此外,平均作为低通滤波器 - 想象你的序列是1 -1 1 -1 1 -1,如果你是平均对,那么每个平均值出现为0.但如果你有较慢的波,如0 0.2 0.3 0.33 0.3 0.2 ......然后平均值仍会导致波形 . 波越长,其能量保持得越多 - 即平均值导致阻尼减少 .

    因此,平均可以被认为是非常简单的低通滤波器 .

    当然存在复杂性,必须选择整数缓冲器长度迫使量化可能的频率,这对于钢琴的顶部变得明显 . 一切都是可以克服的,但它变得艰难!

    链接:

    Delicious Max/MSP Tutorial 1: Karplus-Strong

    The Karplus-Strong Algorithm

    据我所知,JOS是合成音发生的世界领先权威,所有道路都可以回到他的网站 . 但要注意,它变得非常棘手,需要大学水平的数学 .

  • 2

    你走在正确的轨道上 . :)

    Audio signal

    你不需要进行逆FFT(你可以,但你需要为它找到一个lib或实现它,再加上生成一个信号作为输入) . 直接生成我们期望的IFFT结果要容易得多,IFFT是具有给定频率的正弦信号 .

    正弦的参数取决于您想要生成的音符和您生成的波形文件的sampling frequency(通常等于44100Hz,在您的示例中使用的是11025Hz) .

    对于1 Hz音调,您需要一个正弦信号,其周期等于一秒 . 对于44100 Hz,每秒有44100个样本,这意味着我们需要一个正弦信号,其中一个周期等于44100个样本 . 由于正弦周期等于Tau(2 * Pi),我们得到:

    sin(44100*f) = sin(tau)
    44100*f = tau
    f = tau / 44100 = 2*pi / 44100
    

    对于440 Hz,我们得到:

    sin(44100*f) = sin(440*tau)
    44100*f = 440*tau
    f = 440 * tau / 44100 = 440 * 2 * pi / 44100
    

    在C#中,这将是这样的:

    double toneFreq = 440d;
    double f = toneFreq * 2d * Math.PI / 44100d;
    for (int i = 0; i<data.Length; i++)
        data[i] = (byte)(128 + 127*Math.Sin(f*i));
    

    注意:我没有对此进行测试以验证代码的正确性 . 我会尽力做到并纠正任何错误 . Update: 我已将代码更新为有效的代码 . 抱歉伤害了你的耳朵;-)

    Chords

    和弦是音符的组合(参见例如Minor chord on Wikipedia) . 因此,信号将是具有不同频率的正弦的组合(和) .

    Pure tones

    然而,这些音调和和弦听起来并不自然,因为传统乐器不会播放单频音 . 相反,当您演奏A4时,频率分布很广,浓度约为440 Hz . 参见例如Timbre .

  • 115

    你走在正确的轨道上 .

    我们来看看你的例子:

    for(int i = 0; i < data.Length; i++)
      data[i] = (byte)(256 * Math.Sin(i));
    

    好的,你've got 11025 samples per second. You' ve得到了60秒的样品 . 每个样本是0到255之间的数字,表示在给定时间内空间点处的气压的微小变化 .

    等一下,正弦从-1变为1,因此样本从-256变为256,并且大于一个字节的范围,所以这里有一些傻瓜 . 让我们重新编写代码,使样本处于正确的范围内 .

    for(int i = 0; i < data.Length; i++)
      data[i] = (byte)(128 + 127 * Math.Sin(i));
    

    现在我们可以平滑地改变1到255之间的数据,因此我们处于一个字节的范围内 .

    尝试一下,看看它听起来如何 . 它应该听起来很“平滑” .

    人耳检测到气压变化非常微小 . 如果这些变化形成重复模式,那么模式重复的频率将被耳朵中的耳蜗解释为特定音调 . 压力变化的大小被解释为体积 .

    你的波形长达60秒 . 变化从最小的变化1变为最大的变化,255 . 峰值在哪里?也就是说,样本在何处达到255或接近它?

    那么,正弦在π/2,5π/2,9π/2,13π/ 2处是1,依此类推 . 所以每当我接近其中一个峰值时,峰值就会出现 . 也就是说,在2,8,14,20 ......

    那些时间相隔多远?每个样品是1/11025秒,因此每个峰之间的峰值约为2π/ 11025 =约570微秒 . 每秒有多少个峰值? 11025 /2π= 1755 Hz . (赫兹是频率的度量;每秒多少个峰值) . 1760赫兹是A 440以上的两个八度音阶,所以这是一个略微平坦的A音 .

    和弦如何工作?他们是球场的平均值吗?

    不是 . 一个A440和八度音阶的和弦,A880不等于660赫兹 . 你没有平均音高 . 你总结波形 .

    想想空气压力 . 如果你有一个振动源每秒抽吸压力440次,另一个振动压力每秒上下压力880次,那么网络与每秒660次的振动不同 . 它's equal to the sum of the pressures at any given point in time. Remember, that'所有的WAV文件是: a big list of air pressure changes .

    假设您想在样本下方制作一个八度音程 . 频率是多少?一半多 . 所以,让它经常发生一半:

    for(int i = 0; i < data.Length; i++)
      data[i] = (byte)(128 + 127 * Math.Sin(i/2.0));
    

    注意它必须是2.0,而不是2.我们不希望整数舍入! 2.0告诉编译器你希望结果是浮点数,而不是整数 .

    如果你这样做,你将获得一半的峰值:在i = 4,16,28 ......因此音调将是一个完整的八度音阶 . (每个八度音程都会使频率减半;每个八度音程都会增加一倍 . )

    尝试一下,看看你是如何得到相同的音调,低一个八度 .

    现在将它们加在一起 .

    for(int i = 0; i < data.Length; i++)
      data[i] = (byte)(128 + 127 * Math.Sin(i)) + 
                (byte)(128 + 127 * Math.Sin(i/2.0));
    

    这可能听起来像废话 . 发生了什么? We overflowed again ;在许多点上总和大于256 . 将两个波浪的体积减半:

    for(int i = 0; i < data.Length; i++)
      data[i] = (byte)(128 + (63 * Math.Sin(i/2.0) + 63 * Math.Sin(i)));
    

    更好 . “63 sin x 63 sin y”介于-126和126之间,因此不会溢出一个字节 .

    (所以有一个平均值:我们基本上取每个音调压力的平均值,而不是频率的平均值 . )

    如果你演奏它,你应该同时获得两个音调,一个八度音高于另一个音高 .

    最后一个表达很复杂,难以阅读 . 让我们将其分解为更易于阅读的代码 . 但首先,总结到目前为止的故事:

    • 128介于低压(0)和高压(255)之间 .

    • 音调的音量是波浪达到的最大压力

    • 音调是给定频率的正弦波

    • 以Hz为单位的频率是采样频率(11025)除以2π

    所以我们把它放在一起:

    double sampleFrequency = 11025.0;
    double multiplier = 2.0 * Math.PI / sampleFrequency;
    int volume = 20;
    
    // initialize the data to "flat", no change in pressure, in the middle:
    for(int i = 0; i < data.Length; i++)
      data[i] = 128;
    
    // Add on a change in pressure equal to A440:
    for(int i = 0; i < data.Length; i++)
      data[i] = (byte)(data[i] + volume * Math.Sin(i * multiplier * 440.0))); 
    
    // Add on a change in pressure equal to A880:
    
    for(int i = 0; i < data.Length; i++)
      data[i] = (byte)(data[i] + volume * Math.Sin(i * multiplier * 880.0)));
    

    你去了;现在你可以生成任何频率和音量的音调 . 要制作一个和弦,将它们加在一起,确保你不要太大声并溢出字节 .

    你怎么知道A220,A440,A880等以外音符的频率?每个半音将上一个频率乘以2的第12个根 . 因此,计算2的第12个根,将其乘以440,即A# . 将A#乘以12的2,即B.B乘以12的第12个根是C,然后是C#,依此类推 . 这样做12次,因为它是2的第12根,你将得到880,是你开始的两倍 .

    当wav文件的内容是波形时,播放每个音符的时间长度如何?

    只需填写音调发声的样本空间即可 . 假设你想玩A440 30秒,然后A880玩30秒:

    // initialize the data to "flat", no change in pressure, in the middle:
    for(int i = 0; i < data.Length; i++)
      data[i] = 128;
    
    // Add on a change in pressure equal to A440 for 30 seconds:
    for(int i = 0; i < data.Length / 2; i++)
      data[i] = (data[i] + volume * Math.Sin(i * multiplier * 440.0))); 
    
    // Add on a change in pressure equal to A880 for the other 30 seconds:
    
    for(int i = data.Length / 2; i < data.Length; i++)
      data[i] = (byte)(data[i] + volume * Math.Sin(i * multiplier * 880.0)));
    

    多个音符的结果如何被逆FFT转换为字节数组,这组成了wav文件中的数据?

    反向FFT只是 Build 正弦波并将它们加在一起,就像我们在这里做的那样 . 这就是全部!

    与此相关的任何其他相关信息?

    请参阅我关于此主题的文章 .

    http://blogs.msdn.com/b/ericlippert/archive/tags/music/

    第一至第三部分解释了为什么钢琴每个八度音程有十二个音符 .

    第四部分与您的问题相关;这就是我们从头开始构建WAV文件的地方 .

    请注意,在我的示例中,我每秒使用44100个样本,而不是11025,我使用的是16位样本范围从-16000到16000,而不是8位样本,范围从0到255.但除了这些细节,它与您的基本相同 .

    如果您打算做任何复杂的波形,我建议您使用更高的比特率;对于复杂的波形,每秒11K采样的8位声音听起来很糟糕 . 每个样本16位,每秒44K样本是CD质量 .

    坦率地说,如果你用签名的短裤而不是无符号字节来做正确的数学就好了 .

    第五部分给出了一个有趣的听觉幻觉的例子 .

    另外,尝试使用Windows Media Player中的“范围”可视化来观察波形 . 这将使您了解实际情况 .

    更新:

    我注意到,当将两个音符叠加在一起时,由于两个波形之间的过渡过于尖锐(例如,从一个波形的顶部开始,从下一个波形的底部开始),最终可能会出现砰砰声 . 如何克服这个问题?

    优秀的后续问题 .

    基本上这里发生的是从高压到低压的瞬间过渡,这被称为“流行” . 有几种方法可以解决这个问题 .

    Technique 1: Phase shift

    一种方法是将后续音调“相移”一些小量,使得后续音调的起始值与前一音调的结束值之间的差异 . 您可以添加这样的相移术语:

    data[i] = (data[i] + volume * Math.Sin(phaseshift + i * multiplier * 440.0)));
    

    如果相移为零,显然这是没有变化的 . 2π的相移(或π的任何偶数倍)也没有变化,因为sin具有2π的周期 . 0到2π之间的每个值都会在音调“开始”沿着波形进一步移动的地方移动 .

    确切地确定正确的相移可能有点棘手 . 如果你阅读我关于产生“连续下降”Shepard错觉音调的文章,你会发现我使用了一些简单的微积分来确保一切都在不断变化而没有任何爆炸声 . 你可以使用类似的技巧来弄清楚正确的转变是什么让流行音乐消失 .

    我正在尝试研究如何生成phasehift值 . 是“ArcSin(((新笔记的第一个数据样本) - (前一个笔记的最后一个数据样本))/ noteVolume”“对吗?

    嗯,要意识到的第一件事是可能没有"right value" . 如果结尾音符非常响亮并且在峰值处结束,并且起始音符非常安静,则新音调中可能没有与旧音调的值匹配的点 .

    假设有一个解决方案,它是什么?你有一个结束样本,称之为y,你想要找到相移x

    y = v * sin(x + i * freq)
    

    当我是零 . 所以那是

    x = arcsin(y / v)
    

    However ,这可能不太对!假设你有

    sine wave 1

    而且你想追加

    sine wave 2

    two possible phase shifts

    sine wave 3

    Sine wave 4

    猜测哪个听起来更好听 . :-)

    弄清楚你是处于波浪的“上冲程”还是“下冲程”可能有点棘手 . 如果你不想计算出真正的数学,你可以做一些简单的启发式算法,比如“连续数据点之间的差异符号在转换时是否会发生变化?”

    Technique 2: ADSR envelope

    如果您正在建模听起来像真实乐器的东西,那么您可以通过如下更改音量来获得良好的效果 .

    你想要做的是每个音符有四个不同的部分,称为攻击,衰减,延音和释放 . 在乐器上演奏的音符音量可以这样建模:

    /\
        /  \__________
       /              \
      /                \
       A  D   S       R
    

    音量从零开始 . 然后发生攻击:声音快速上升到其峰值音量 . 然后它略微衰减到其维持水平 . 然后它保持在那个水平,可能在音符播放时缓慢下降,然后它再次回落到零 .

    如果你这样做那么没有弹出,因为每个音符的开头和结尾都是零音量 . 该版本确保了这一点 .

    不同的乐器有不同的“信封” . 例如,管风琴具有令人难以置信的短暂攻击,腐烂和释放;这一切都是持续的,而持续是无限的 . 您现有的代码就像一个管风琴 . 比如钢琴 . 再次,短攻击,短暂衰减,短暂释放,但声音在维持期间逐渐变得更安静 .

    攻击,腐烂和释放部分可能非常短,太短而无法听到,但足够长以防止弹出 . 尝试在音符播放时改变音量,看看会发生什么 .

相关问题