サイン波の生成に関する音声処理の部分を進めていきます。
サイン波を算出する仕組みは以前と同じだね
Synthの「Build a MIDI synthesiser」のチュートリアルを進めていきます。SineWaveVoiceクラスに実装されている、音声サンプルを作成する機能の関数をチュートリアルに沿ってみていきます。
チュートリアルの項目としては「Starting a voice」の項目からです。
こんな人の役に立つかも
・JUCEプログラミングを勉強している人
・JUCEの「Build a MIDI synthesiser」チュートリアルを進めている人
・JUCEでシンセを作成したい人
音声処理関連の関数
startNote関数
オーバーライドしなければいけない関数の一つです。
startNote関数は、ノートオンが発生して実行されるようです。サイン波を作るためのパラメータを初期化します。
void startNote(int midiNoteNumber, float velocity,
juce::SynthesiserSound*, int /*currentPitchWheelPosition*/) override
{
currentAngle = 0.0;
level = velocity * 0.15;
tailOff = 0.0;
auto cyclesPerSecond = juce::MidiMessage::getMidiNoteInHertz(midiNoteNumber);
auto cyclesPerSample = cyclesPerSecond / getSampleRate();
angleDelta = cyclesPerSample * 2.0 * juce::MathConstants<double>::pi;
}
angleDeltaの値を求めるために、周波数とサンプリングレートから計算を行います。サイン波などの発音の仕組み、パラメータについては、過去記事で勉強しましたので、そちらを参考ください。
renderNextBlock関数
実際に、音声データを作成する関数です。startNote関数などで変更されるangleDeltaの値によって、音声処理の場合分けをしていますので、他の関数も理解してみていく必要があります。
angleDeltaが0のときは、音声処理を行わず、angleDeltaが0以外のときに処理を行います。ノートオンの時は、angleDeltaが計算されて何かしらの値が入るので、
void renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int startSample, int numSamples) override
{
if (angleDelta != 0.0)//[1]angleDeltaの値が0出ない時です。
{
//[2]tailOffが0より大きい時です。(ノートオフの時)
if (tailOff > 0.0)
{
while (--numSamples >= 0)//[2-1]音声バッファ1ブロック分ループです。
{
auto currentSample = (float)(std::sin(currentAngle) * level * tailOff);
for (auto i = outputBuffer.getNumChannels(); --i >= 0;)
outputBuffer.addSample(i, startSample, currentSample);
currentAngle += angleDelta;
++startSample;
tailOff *= 0.99;
if (tailOff <= 0.005)
{
clearCurrentNote();
angleDelta = 0.0;
break;
}
}
}
else//[3]tailOffが0以下の時です(ノートオンの時)
{
while (--numSamples >= 0)
{
auto currentSample = (float)(std::sin(currentAngle) * level);
for (auto i = outputBuffer.getNumChannels(); --i >= 0;)
outputBuffer.addSample(i, startSample, currentSample);
currentAngle += angleDelta;
++startSample;
}
}
}
}
renderNextBlockは、親コンポーネントのgetNextAudioBlockで呼び出されますので、アプリ起動から終了までひたすら繰り返されます。
[1]では、angleDeltaが0ではない時に処理を行います。angleDeltaが0の時は、何もせず処理を抜けることになります。angleDeltaはnoteOn関数で変更されますので、ノートオンメッセージをトリガーとして音声処理されることになります。
次に、[2]の条件を判定します。この条件は、tailOffの数値をみています。tailOffは、後述するstopNoteで0から1になりますので、この条件は、ノートオフの信号がトリガーとなって処理され始めることになります。ノートオンの時は、[3]の条件の処理を行うことになります。[2]の処理内容としては、whileループで、音声ブロック全サンプル数分のループをしています。そして、サンプル毎にtailOffを0.99倍していきますので、tailOffがサンプル毎に0に減衰していきます。これをゲインとして掛け合わせることで、ノートオフ後に、少しづつ音量が減衰するようになります。アンプエンベロープでいうリリースの効果になります。最後に、tailOffは0.005以下で強制的に0とすることでノートオフのリリースの処理を完了させます。
[3]では、ノートオンの信号の後の音声処理になります。whileループで、音声ブロック全サンプル数分のループをしています。currentSample変数に、サイン関数から取得した現在のラジアンのデータを取得して入れ、outputBufferに設定することで、音声データ(サイン波の音)を作成しています。最後に、angleDeltaを更新して次のサンプルのデータ作成に利用します。
stopNote関数
stopNote関数は、ノートオフの時にまず実行されるようです。allowTailOffは、フラグで、音量を滑らかに減衰させる処理のオンオフができるようになっています。
void stopNote(float /*velocity*/, bool allowTailOff) override
{
if (allowTailOff)
{
//[1]リリースの処理を行う時は、tailOffを1.0にします。
if (tailOff == 0.0)
tailOff = 1.0;
}
else//[2]リリースの処理を行わない場合です。
{
clearCurrentNote();
angleDelta = 0.0;
}
}
[1]では、リリースの処理を行う設定にしている場合(通常時)はtailOffの値を1にします。その後のnextRenderBlockで利用されます。allowTailOffのフラグがfalseの時は、そのまま、clearCurrentNote関数で、発音をリセットします。そして、angleDeltaをここで0にしてしまします。
stopNote関数は、Synthesiserクラスから呼び出されますので、allowTailOffをどこでどのように設定するのかは調査しきれていません。とりあえず、stopNote関数内で直接falseにしてしまえばリリースの処理を行わないようにはできますが、そうするとこのフラグの意味がなくなってしまうような気がしています。
文字ばかりですみません。次回、少し図にして考察してみたいと思います。