3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

VSTプラグインを自作してみた

3
Last updated at Posted at 2026-08-04

VSTプラグインって何?

DAW(Digital Audio Workstation:PCで音楽の録音・編集を行うソフト)で使われる拡張プログラム、あるいはその共通規格のこと。

Steinberg 社が策定した規格で、現在は VST3 が最新バージョン。対応 DAW(Cubase、Fender Studio、FL Studio など)に .vst3 ファイルを配置するだけで機能を追加できます。

ChromeやIDEの拡張機能と同じようなイメージで捉えると分かりやすいと思います。

VSTプラグインには、主に下記の2種類があります。

種類 説明
VSTエフェクト 入力された音声に効果をかけて出力する リバーブ、コンプレッサー、イコライザーなど
VSTインストゥルメント MIDI 入力を受けて音を鳴らす シンセサイザー、サンプラーなど

今回作成したのは音源に直接効果をかけるタイプですから、1つ目のVSTエフェクトになります。

また、市販のプラグインは数千円〜数万円するものも多いですが、無償で配布されているものも大量に存在します。

市販品でもJUCEを用いて作られたものは多数存在するらしく、自分で作ってしまえば安上がりでは???と思ったのが今回自作に至ったきっかけです。


JUCEって何?

概要

JUCE(Jules' Utility Class Extensions) は、音楽・音声アプリケーションの開発に特化した C++ フレームワーク です。

VST / AU(Audio Units)/ AAX / LV2 など、主要なプラグイン形式に対応したコードをほぼ同一のソースから生成できます。

主要な構成要素

機能 説明
DSP モジュール フィルター、信号処理のユーティリティ
GUI ライブラリ スライダー、ボタン、コンボボックス等を自給
Audio I/O プラットフォーム別の音声実装を抽象化
パラメータ実装 AudioProcessorValueTreeState (APVTS)クラスでパラメータ管理を実装

メリット

  • マルチフォーマット対応:VST3、AU、AAX、LV2 などを一度の実装でビルド可能
  • クロスプラットフォーム:Windows / macOS / Linux に対応(iOS / Android も可)
  • GUI ライブラリ内蔵:スライダーやボタンなどの UI コンポーネントが揃っている
  • 情報が豊富:公式チュートリアルや YouTube 解説動画が多い

デメリット

  • ビルド設定がやや複雑:独自のプロジェクト管理ツール「Projucer」または CMake を使う必要がある

実際に作ってみた

ファズを作ろう!

私はファズが大好きなので、とにもかくにもまずはこれ。
目指す音はゲート系のファズです。ブチブチした太い音です。

おそらくほとんどのギタープレイヤーは実機(アナログ)のエフェクターを使用していると思います。
モノにもよりますが、なんと実機は数万円したりします。
ヴィンテージの、"名機"と称されるものは平気で10万円超えます。高過ぎ。

私のような慎ましやかな生活を送る庶民にとってこれは大金です。
自分で作っちゃいましょう。

デジタルなら材料費もかからず、PC一つで名機みたいな良い音出せるかもしれない。

ファズとは

ギターやベースで頻繁に使用される、歪みと呼ばれるエフェクターの一種です。
世界で一番最初に作られたエフェクターがファズといわれており、アナログのものは単純な電気回路をしています。
デジタルで実機の音を再現するのはなかなか難しいですが、なるべく近い音が出たら良いな。


1. JUCEのインストール・環境構築

↓こちらのサイトを参考にインストールを行いました。
https://trap.jp/post/2307/

必要なもの

ツール 備考
JUCE(最新版) 公式サイトまたは GitHub からダウンロード
Visual Studio Windows の場合。筆者は2026版を使用。
Projucer JUCE に同梱されているプロジェクト管理ツール

手順

1. JUCE の取得と位置決定

  1. JUCE 公式サイトから最新版をダウンロードして解凍
  2. 例:C:\JUCE などの標準的なディレクトリ位置を決める

2. Projucer を使用したプロジェクト作成

  1. C:\JUCE\Projucer.exe を起動
  2. 上部のメニューバーからFileNew Project を選択
  3. 左側のメニューからNew Project → Plug-In → Basic を選択
  4. 右側のタブで以下を設定:
    • Project NameFirstFuzz(任意のプロジェクト名)
  5. Create Project... をクリック
  6. プロジェクトの保存先を選択

3. Visual Studio でビルド

  1. Projucerに表示されているアイコンをクリックしてVSを起動
  2. ビルドを実行
  3. 出力先:FirstFuzz_artefacts/VST3/FirstFuzz.vst3 が生成されれば成功

ビルドに成功した後、VS上で実行してスタンドアロンで動作確認することもできます。

ちなみに筆者はJUCEのモジュールの参照元設定をいじってしまったりでビルドに手こずりました……

Projucer の役割

.jucer プロジェクトファイルを管理し、各種IDEに合わせてプロジェクトファイル一式を生成。実際のコード編集はIDEで行う。


2. 実装

プラグインの基本構成

今回筆者が作ったプラグインのソースコードは以下より参照できます。
https://github.com/ip0627/FirstFuzz_test.git

JUCE のプラグインテンプレを使うと、主に以下の2つのクラスが生成されます。

クラス 役割
PluginProcessor 音声処理のロジック(DSP)
PluginEditor GUI の描画・操作

実装の流れ:

[1] パラメータ定義(PluginProcessor)
    ↓
[2] 音声処理実装(processBlockメソッド)
    ↓
[3] UI コンポーネント配置(PluginEditor.cpp)
    ↓
[4] Attachment でパラメータと UI を自動同期
    ↓
[5] ビルド → `.vst3` 生成

APVTS(AudioProcessorValueTreeState)とパラメータ管理

JUCE でパラメータを実装する時は、AudioProcessorValueTreeState(APVTS)クラスを使います。

APVTS の利点:

  • UI とパラメータ値を自動同期。UI コードを部分書き換えする必要がない
  • DAW 中でのオートメーション有効。Cubase 等でパラメータ変化を記録できる
  • XML シリアライズでプリセット管理が容易

アルゴリズム

今回はJUCEを用いたプログラミングが主題のため詳細は割愛しますが、大体以下のように音声信号が処理されています。

処理は大きく4ステップで構成されます。

入力信号
  │
  ├─ [1] 歪み(Gain)
  │       入力を増幅し、tanh でソフトクリッピング
  │       → 奇数倍音が乗った、太い歪みが生まれる
  │
  ├─ [2] ノイズゲート(Gate)
  │       クリッピング後の振幅が閾値以下なら完全に無音化
  │       → 「ブチブチ」した断続的なサウンドの正体
  │
  ├─ [3] トーン(Tone)
  │       1次ローパスフィルター。高域をカットして音色を整える
  │
  └─ [4] 出力音量(Volume)

パラメータ定義(PluginProcessor.cpp)

juce::AudioProcessorValueTreeState::ParameterLayout
FirstFuzzAudioProcessor::createParameterLayout()
{
    std::vector<std::unique_ptr<juce::RangedAudioParameter>> params;

    // Gain:歪みの強さ。skew で小さい値の操作幅を広く取る
    params.push_back(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID { "gain", 1 }, "Gain",
        juce::NormalisableRange<float>(1.0f, 40.0f, 0.01f, 0.4f), 8.0f));

    // Gate:ゲート閾値(0〜1 を後で 0.001〜0.2 にリマップ)
    params.push_back(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID { "gate", 1 }, "Gate",
        juce::NormalisableRange<float>(0.0f, 1.0f, 0.0001f), 0.08f));

    // Tone:ローパスフィルターのカットオフ周波数(Hz)
    params.push_back(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID { "tone", 1 }, "Tone",
        juce::NormalisableRange<float>(200.0f, 12000.0f, 1.0f, 0.5f), 4000.0f));

    // Volume:出力ゲイン
    params.push_back(std::make_unique<juce::AudioParameterFloat>(
        juce::ParameterID { "volume", 1 }, "Volume",
        juce::NormalisableRange<float>(0.0f, 1.0f, 0.0001f), 0.8f));

    // Input Channel:処理対象チャンネルの選択
    params.push_back(std::make_unique<juce::AudioParameterChoice>(
        juce::ParameterID { "inputChannel", 1 }, "Input Channel",
        juce::StringArray { "Left", "Right", "Both" }, 2));

    return { params.begin(), params.end() };
}

音声処理(PluginProcessor.cpp)

void FirstFuzzAudioProcessor::processBlock(
    juce::AudioBuffer<float>& buffer, juce::MidiBuffer&)
{
    juce::ScopedNoDenormals noDenormals;

    const auto gain   = gainParameter->load();
    const auto gate   = gateParameter->load();
    const auto tone   = toneParameter->load();
    const auto volume = volumeParameter->load();

    // gate(0〜1)を実際の振幅閾値(0.001〜0.2)にリマップ
    const auto gateThreshold = juce::jmap(gate, 0.0f, 1.0f, 0.001f, 0.2f);
    const auto sampleRate    = static_cast<float>(currentSampleRate);

    // 1次 LPF の係数。tone が高いほど alpha が小さくなり高域が通りやすくなる
    const auto alpha = std::exp(
        -2.0f * juce::MathConstants<float>::pi * tone / sampleRate);

    auto processChannel = [&](int chIndex)
    {
        auto* data  = buffer.getWritePointer(chIndex);
        auto& state = toneFilterState[chIndex];

        for (int i = 0; i < buffer.getNumSamples(); ++i)
        {
            // [1] 歪み(tanh ソフトクリッピング)
            auto fuzzed = std::tanh(data[i] * gain);

            // [2] ゲート(クリッピング後の振幅で判定)
            if (std::abs(fuzzed) < gateThreshold)
                fuzzed = 0.0f;

            // [3] トーン(1次 LPF: y[n] = (1-α)x[n] + α*y[n-1])
            state = (1.0f - alpha) * fuzzed + alpha * state;

            // [4] 出力音量
            data[i] = state * volume;
        }
    };

    // Input Channel に応じて処理対象チャンネルを切り替え
    const auto inputMode = inputChannelParameter->getIndex();
    if (inputMode == 0)       // Left のみ → 両チャンネルに複製
    {
        processChannel(0);
        buffer.copyFrom(1, 0, buffer, 0, 0, buffer.getNumSamples());
    }
    else if (inputMode == 1)  // Right のみ → 両チャンネルに複製
    {
        processChannel(1);
        buffer.copyFrom(0, 0, buffer, 1, 0, buffer.getNumSamples());
    }
    else                      // Both
    {
        for (int ch = 0; ch < getTotalNumInputChannels(); ++ch)
            processChannel(ch);
    }
}

GUI と Attachment でのパラメータバインディング(PluginEditor.cpp)

~Attachment クラスを使用すると、UI コンポーネント(Slider、ComboBox など)を APVTS のパラメータに自動連携できます。手動で listener を書いたり、setter/getter を実装したりしなくて済みます。

実装の流れ:

  1. UIコンポーネントを新設定
  2. Attachmentを作成
  3. それ以後、ユーザーがUIを操作するとパラメータ値が自動更新
  4. プラグイン側でオートメーション実行時、UIも自動更新される(UIをsetしなくてもOK)
FirstFuzzAudioProcessorEditor::FirstFuzzAudioProcessorEditor(
    FirstFuzzAudioProcessor& p)
    : AudioProcessorEditor(&p), audioProcessor(p)
{
    // Input Channel コンボボックス
    for (auto& item : { "Left", "Right", "Both" })
        inputChannelCombo.addItem(item, inputChannelCombo.getNumItems() + 1);
    addAndMakeVisible(inputChannelCombo);

    // 4つのノブを共通セットアップ
    for (auto* s : { &gainSlider, &gateSlider, &toneSlider, &volumeSlider })
    {
        s->setSliderStyle(Slider::RotaryHorizontalVerticalDrag);
        s->setTextBoxStyle(Slider::TextBoxBelow, false, 72, 20);
        addAndMakeVisible(s);
    }

    // Attachment でパラメータと UI を自動同期
    auto& apvts = audioProcessor.getValueTreeState();
    inputChannelAttachment = std::make_unique<ComboBoxAttachment>(apvts, "inputChannel", inputChannelCombo);
    gainAttachment   = std::make_unique<SliderAttachment>(apvts, "gain",   gainSlider);
    gateAttachment   = std::make_unique<SliderAttachment>(apvts, "gate",   gateSlider);
    toneAttachment   = std::make_unique<SliderAttachment>(apvts, "tone",   toneSlider);
    volumeAttachment = std::make_unique<SliderAttachment>(apvts, "volume", volumeSlider);

    setSize(480, 290);
}

3. 動作確認

ビルドと配置

Visual Studio でビルドすると、FirstFuzz_artefacts/VST3/.vst3 ファイルが生成されます。

あらかじめDAWで指定されているVSTプラグインの格納場所にビルド出力先を設定しておくと、ファイルのコピペ等の手間が省けます。

DAW での読み込み

  1. DAW を起動し、プラグインのスキャンを実行
  2. エフェクトトラックに "FirstFuzz" を挿入
  3. 各種パラメーターを操作して音が変化することを確認

4. サウンドチェック

実際にDAWから起動し、ギターを録音してエフェクトをかけてみました。
今回使用した機材・ソフトはこちらです

種類 説明
DAW Fender Studio Pro
ギター Fender Japan ストラトキャスター(10万円くらいで買った中古)
オーディオインターフェース FOCUSRITE Scarlett Solo (Gen. 3)

繋ぎ順は、以下の通りです。

Dry(生音) → ファズ → アンプシミュレーター(DAW付属、Marshall JCM800モデリング) → Main Out

アンシミュのセッティングはこんな感じ↓
Amp.png

今回作成したプラグインのGUIはこんな感じです。特にいじったりしてないのでかなり見にくいですがご了承ください。
サウンドテストもこのパラメータ設定で行いました。
Fuzz.png

それでは音を聞いてみましょう。

まずは原音(Dry)から。アンシミュのみの音です。
https://on.soundcloud.com/DQ4ILkGeykLuXsnLYK

続いてエフェクトをかけた音です(Wet)。上の原音にそのままエフェクトをかけています。
https://on.soundcloud.com/rMo3J9Cui8LDeCV4rA

お分かりいただけたでしょうか。
なんとなく、音が太くなり、ゲート感も出ているのではないでしょうか。
普通にアンシミュの音が良いだけでは???


5. 改善点・今後の課題

実際に音を出すまではできました。

あとはGUIの改善や、音をより実機に近づけていくことを目指していきたいですね。

いじるとしたら下記のクラスやメソッドが使えそうです。

クラス / メソッド できること
juce::dsp::IIR::Filter DC カットや、より本格的なトーンスタック(Big Muff 型)の実装
juce::dsp::ProcessorChain 複数の DSP 処理をパイプライン的に接続して見通しを改善
juce::LookAndFeel ノブや背景などの見た目をカスタムし、エフェクターらしい GUI に

さいごに

お疲れ様でした。
JUCEにはライブラリやモジュールが豊富にあるので、それらを組み合わせるだけで結構いろいろできそうです。
今はAIもありますから、実装のハードルは低いと思います。

この記事を読んでくれたDTMerの皆さんも、試しにプラグインを自作してみてはいかがでしょうか。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?