チュートリアルMIDIシンセサイザーを作る
このチュートリアルでは、MIDI 入力に反応するポリフォニックなサイン波シンセサイザーを実装します。これはSynthesiserクラスおよび関連クラス。
レベル:中級
プラットフォーム:Windows, macOS, Linux
クラス: Synthesiser,SynthesiserVoice,SynthesiserSound,AudioSource,MidiMessageCollector
スタート
JUCEでのMIDI入力の扱い方と、サイン波の生成方法に慣れている必要があります。参照Tutorial: Handling MIDI eventsそしてTutorial: Build a sine wave synthesiser.
このチュートリアルのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。
このステップに ヘルプが必要な場合は、以下を参照してください。Tutorial: Projucer Part 1: Getting started with the Projucer.
デモ・プロジェクト
このデモ・プロジェクトでは、シンプルなサイン波シンセサイザーを演奏するのに使えるオンスクリーン・キーボードを紹介している。
コンピュータのキーボードのキーを使って、画面上のキーボードをコントロールすることができます(A、S、D、Fなどのキーを使って、音符C、D、E、Fなどをコントロールします)。これにより、シンセサイザーをポリフォニックに演奏することができます。
シンセシザークラス
このチュートリアルでは、JUCESynthesiserクラスを使用して、ポリフォニック・シンセシスを実装することができます。これは、独自のアプリケーションで独自のサウンド を使ってシンセサイザーをカスタマイズするために必要なすべての基本要素を示しています。これを動作させるために必要なクラスはいろいろあり、標準のMainContentComponent
クラスがある:
SynthAudioSource
を実装している。AudioSourceというクラスがあります。SynthAudioSource
を含む。Synthesiserクラスそのものです。これはシンセサイザーからすべてのオーディオを出力する。SineWaveVoice
これはカスタムSynthesiserVoiceというクラスがあります。SineWaveVoice
.ボイスクラスは、シンセサイザーのボイスの1つを、他のボイスとミックスしてレンダリングします。Synthesiserオブジェクトを生成します。1つのボイスクラスのインスタンスが1つのボイスをレンダリングします。SineWaveSound
カスタムSynthesiserSoundというクラスがあります。SineWaveSound
.サウンドクラスは、ボイスとして作成できるサウンドを記述したものです。例えば、サンプラーボイスのサンプルデータや、ウェーブテーブルシンセサイザーのウェーブテーブルデータが含まれます。
キーボードの設定
私たちのMainContentComponent
クラスには以下のデータ・メンバが含まれる。
juce::MidiKeyboardState keyboardState;
SynthAudioSource synthAudioSource;
juce::MidiKeyboardComponent keyboardComponent;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};
についてsynthAudioSource
そしてkeyboardComponent
メンバはMainContentComponent
ビルダー
MainContentComponent()
: synthAudioSource (keyboardState),
keyboardComponent (keyboardState, juce::MidiKeyboardComponent::horizontalKeyboard)
{
addAndMakeVisible (keyboardComponent);
setAudioChannels (0, 2);
setSize (600, 160);
startTimer (400);
}
参照Tutorial: Handling MIDI eventsの詳細についてはMidiKeyboardComponentクラスである。
コンピュータのキーボードからキーボードの演奏を開始するために、アプリケーションの起動直後にキーボード・フォーカスを取得します。そのために、400ミリ秒後に起動する単純なタイマーを使います:
void timerCallback() override
{
keyboardComponent.grabKeyboardFocus();
stopTimer();
}
AudioAppComponent 関数
アプリケーションはAudioAppComponent簡単なオーディオ・アプリケーションをセットアップする (Tutorial: Build a white noise generator最も基本的なアプリケーションの場合)。必要な3つpure virtual関数は、カスタムAudioSourceクラスである:
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override
{
synthAudioSource.prepareToPlay (samplesPerBlockExpected, sampleRate);
}
void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override
{
synthAudioSource.getNextAudioBlock (bufferToFill);
}
void releaseResources() override
{
synthAudioSource.releaseResources();
}
SynthAudioSource クラス
についてSynthAudioSource
クラスはもう少し仕事をする:
class SynthAudioSource : public juce::AudioSource
{
public:
SynthAudioSource (juce::MidiKeyboardState& keyState)
: keyboardState (keyState)
{
for (auto i = 0; i < 4; ++i) // [1]
synth.addVoice (new SineWaveVoice());
synth.addSound (new SineWaveSound()); // [2]
}
void setUsingSineWaveSound()
{
synth.clearSounds();
}
void prepareToPlay (int /*samplesPerBlockExpected*/, double sampleRate) override
{
synth.setCurrentPlaybackSampleRate (sampleRate); // [3]
}
void releaseResources() override {}
void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override
{
bufferToFill.clearActiveBufferRegion();
juce::MidiBuffer incomingMidi;
keyboardState.processNextMidiBuffer (incomingMidi, bufferToFill.startSample,
bufferToFill.numSamples, true); // [4]
synth.renderNextBlock (*bufferToFill.buffer, incomingMidi,
bufferToFill.startSample, bufferToFill.numSamples); // [5]
}
private:
juce::MidiKeyboardState& keyboardState;
juce::Synthesiser synth;
};
- [1]シンセサイザーに音色を追加します。このボイスの数によって、シンセサイザーのポリフォニーが決まります。
- [2]シンセサイザーがどの音を演奏できるかを知るために、音を加えるんだ。
- [3]シンセサイザーはオーディオ出力のサンプルレートを知る必要がある。
- [4]において
getNextAudioBlock()
関数からMIDIデータのバッファを取り出します。MidiKeyboardStateオブジェクトがある。 - [5]これらのMIDIバッファはシンセサイザーに渡され、ノートオンとノートオフのタイムスタンプ(および他のMIDIチャンネルの音色メッセージ)を使って音色をレンダリングします。
SynthesiserVoiceオブジェクトを追加する必要があります。Synthesiserオブジェクトになる。そのSynthesiserオブジェクトがヴォイスのライフタイムを管理する。
SynthesiserSoundオブジェクトはSynthesiserオブジェクトを使用することができます。そのSynthesiserSoundクラスはReferenceCountedObjectクラスの寿命はSynthesiserSoundオブジェクトは自動的に処理される。
へのポインタを保持する必要がある場合は、次のようにする。SynthesiserSoundオブジェクトに格納する必要があります。YourSoundClass::Ptr
このメモリ管理が機能するように、この変数を使用する。
SineWaveSoundクラス
私たちのサウンドクラスはとてもシンプルで、データを含む必要すらありません。ただ、このサウンドが特定のMIDIチャンネルとそのチャンネルの特定の音符または音域で再生されるべきかどうかを報告する必要があるだけです。この単純なケースではtrue
ともにappliesToNote()
そしてappliesToChannel()
関数を使用します。前述したように、サウンド・クラスには、サウンドを作成するのに必要なデ ータ(ウェーブテーブルなど)を格納することができる。
struct SineWaveSound : public juce::SynthesiserSound
{
SineWaveSound() {}
bool appliesToNote (int) override { return true; }
bool appliesToChannel (int) override { return true; }
};
正弦波の音声状態
についてSineWaveVoice
クラスはもう少し複雑だ。シンセサイザーのボイスの1つの状態を維持する必要がある。私たちのサイン波には、これらのデータ・メンバーが必要だ:
private:
double currentAngle = 0.0, angleDelta = 0.0, level = 0.0, tailOff = 0.0;
};
参照Tutorial: Build a sine wave synthesiser最初の3つに関する情報はこちら。そのtailOff
メンバーを使って、それぞれの声を少しソフトにする。リ リースをその振幅エンベロープに加えます。これにより、各音声は突然止まるのではなく、最後にわずかにフェードアウトする。
再生可能な音の確認
についてSynthesiserVoice::canPlaySound()関数をオーバーライドして、ボイスがサウンドを再生できるかどうかを返さなければならない。単にtrue
を使用する方法を説明する。dynamic_cast
で、渡されたサウンド・クラスのタイプをチェックする。
bool canPlaySound (juce::SynthesiserSound* sound) override
{
return dynamic_cast (sound) != nullptr;
}
声を出す
音声は、シンセサイザーが私たちのSynthesiserVoice::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::pi;
}
繰り返しになるが、この大部分は、あなたが以前から慣れ親しんできたものであるはずだ。Tutorial: Build a sine wave synthesiser.そのtailOff
の値は、各ボイスの開始時にゼロに設定されます。また、MIDIノートオンイベントのベロシティを使って、ボイスのレベルをコントロールします。