我的Bilibili频道:香芋派Taro 我的个人博客:taropie0224.github.io 我的公众号:香芋派的烘焙坊 我的音频技术交流群:1136403177 我的个人微信:JazzyTaroPie
简介 Amplitude Modulation(调幅)是音频处理中的非常常用的一种方法,它通过将输入信号乘上一个调制信号来获得新的信号。我们通常会使用一个LFO(低频振荡器)作为调制信号,LFO的种类包括正弦波、方波、三角波等等,且它的频率通常不超过20Hz。
Tremolo也是一种非常常见的效果器,它通过上面介绍的Amplitude Modulation来自动化控制输入信号的幅度,实现一种响度忽大忽小的效果。
Github: https://github.com/TaroPie0224/TaroTremolo
原理 我们需要让用户输入两个变量,分别是:
rate,用于控制LFO的频率,通常为0~20
depth,调制的深度,通常为0~100
首先我们需要把depth转化成LFO的幅度,至于为什么是200,之后会提到
得到偏置offset
定义LFO
1 lfo = a * sin(2 * pi * rate * t) + offset
定义output
我们可以发现,当depth取最大值100时,a为0.5,此时offset也为0.5,若此时正好震荡到sin为最小值-1的时候,此时lfo的值为0,为调幅的最低点;同理我们也能得到lfo的最大值为1。
这也就是为什么a和offset是这么定义的,这样才能够保证在LFO震荡到最高点时相当于bypass,在LFO震荡到最低点时相当于mute。
当然这也不是强制要求的,你完全可以通过修改你的a和offset来定制LFO得到你想要的tremelo效果,比如不想让它触碰到0等等。
JUCE Code 1 2 3 4 5 6 7 ... private : int sampleCount = 0 ; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TaroTremeloAudioProcessor) };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 void TaroTremeloAudioProcessor::processBlock (juce::AudioBuffer<float >& buffer, juce::MidiBuffer& midiMessages) { juce::ScopedNoDenormals noDenormals; auto totalNumInputChannels = getTotalNumInputChannels (); auto totalNumOutputChannels = getTotalNumOutputChannels (); auto currentRate = apvts.getRawParameterValue ("Rate" )->load (); auto currentDepth = apvts.getRawParameterValue ("Depth" )->load (); for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) buffer.clear (i, 0 , buffer.getNumSamples ()); juce::dsp::AudioBlock<float > block {buffer}; float periodicity = 1 / currentRate; int sampleRate = getSampleRate (); for (int sample = 0 ; sample < block.getNumSamples (); ++sample) { auto * channelDataLeft = buffer.getWritePointer (0 ); auto * channelDataRight = buffer.getWritePointer (1 ); float a = currentDepth / 200 ; float offset = 1 - a; float currentTime = static_cast <float >(sampleCount) / static_cast <float >(sampleRate); auto lfo = a * std::sin (juce::MathConstants<float >::twoPi * currentRate * currentTime) + offset; channelDataLeft[sample] *= lfo; channelDataRight[sample] *= lfo; if (currentTime < periodicity) { sampleCount++; } else { sampleCount = 0 ; } } }