JUCEで簡単なソフトシンセを作る（シンセ実装編）

この説明は3部作の最後。
[JUCEで簡単なソフトシンセを作る（準備編）](https://qiita.com/ring2/items/a1a77be0a19fd43e4c3c)
[JUCEで簡単なソフトシンセを作る（UI実装編）](https://qiita.com/ring2/items/c948d0b1f2110a5d25e7)

ソースは[ここ](https://1drv.ms/f/s!Aob6XHqQfC8_gohNJGx42odox7pcqg)。

##超シンプルな三角波シンセの実装
何も考えないごくごくシンプルな三角波シンセのクラスを作る。ちょっと何言ってるかわからない部分が多いかもしれないけどソースは下記（めっちゃ手抜き）。

```SimpleSynthCore.h
/*
  ==============================================================================

    SimpleSynthCore.h
    Created: 30 Dec 2018 9:02:30am
    Author:  ring2

  ==============================================================================
*/

#pragma once

class SimpleSynthCore
{
public:
    SimpleSynthCore();
    ~SimpleSynthCore();

    void reset (double newSampleRate, int bufferSize);
    void process(float** buffers, int numSamples);
    void midi(unsigned char status, unsigned char data1, unsigned char data2, int sampleOffset = 0);
    
private:
    double sampleRate;      //  ホストから渡されたsampleRateを保持しておく
    float phase;            //  saw波/tri波のphase
    float delta;            //  phaseを進めるためのdelta
    float gate;             //  note on/offで開け閉めするゲート
    float deltaData[128];   //  ノートナンバーに対応するdeltaのテーブル
};
```

```SimpleSynthCore.cpp
/*
  ==============================================================================

    SimpleSynthCore.cpp
    Created: 30 Dec 2018 9:02:30am
    Author:  ring2

  ==============================================================================
*/

#include "SimpleSynthCore.h"
#include <stdio.h>
#include <math.h>
#include <cassert>

SimpleSynthCore::SimpleSynthCore()
{
    sampleRate = 0.0;
    phase = 0.0f;
    delta = 0.0f;
    gate = 0.0f;
}

SimpleSynthCore::~SimpleSynthCore()
{
    
}
    
void SimpleSynthCore::reset (double newSampleRate, int /*bufferSize*/)
{
    sampleRate = newSampleRate;
    phase = 0.0f;
    delta = 0.0f;
    gate = 0.0f;

    for (int note = 0; note < 128; note++)
    {
        double freq = 440.0 * pow(2.0, (note - 69) / 12.0);
        deltaData[note] = (float)(freq / sampleRate);
    }
}

void SimpleSynthCore::process(float** buffers, int numSamples)
{
    assert(sampleRate > 0.0);

    float* outputL = buffers[0];
    float* outputR = buffers[1];

    for (int i = 0; i < numSamples; i++)
    {
        float saw, tri;
        phase += delta;
        phase -= (float)((int)phase);
        saw = phase * 2.0f - 1.0f;
        tri = (saw > 0.0f)? saw: -saw;
        tri = tri * 2.0f - 1.0f;

        *outputL++ = *outputR++ = tri * gate * 0.5f;
    }
}

void SimpleSynthCore::midi(unsigned char status, unsigned char data1, unsigned char data2, int /*sampleOffset*/)
{
    unsigned char s = status & 0xf0;

    if ((s == 0x90) && (data2 > 0))
    {
        delta = deltaData[data1 & 0x7f];
        gate = 1.0f;
    }
    else if ((s == 0x80) || ((s == 0x90) && (data2 == 0)))
    {
        gate = 0.0f;
    }
}
```

##シンセのコアとJUCEのAudio処理を繋ぐ
上記のクラスをSimpleSynthAudioProcessorから使う。

```SimpleSynthAudioProcessor.h
#pragma once

#include "../JuceLibraryCode/JuceHeader.h"
#include "SimpleSynthCore.h"  // ヘッダファイルを追加

class SimpleSynthAudioProcessor  : public AudioProcessor
{
public:
    SimpleSynthAudioProcessor();
    ~SimpleSynthAudioProcessor();
（中略）
    // ホスト以外からMIDIメッセージを受けるためのメソッド
    void midi(unsigned char status, unsigned char data1, unsigned char data2);

private:
    ScopedPointer<SimpleSynthCore> core;  // シンセのインスタンスを保持するメンバを追加
    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimpleSynthAudioProcessor)
};
```

```SimpleSynthAudioProcessor.cpp
（前略）
// コンストラクタ
SimpleSynthAudioProcessor::SimpleSynthAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
     : AudioProcessor (BusesProperties()
                     #if ! JucePlugin_IsMidiEffect
                      #if ! JucePlugin_IsSynth
                       .withInput  ("Input",  AudioChannelSet::stereo(), true)
                      #endif
                       .withOutput ("Output", AudioChannelSet::stereo(), true)
                     #endif
                       )
#endif
{
    core = new SimpleSynthCore();
    addParameter(new SimpleSynthParameter);
}

// デストラクタ
SimpleSynthAudioProcessor::~SimpleSynthAudioProcessor()
{
    core = nullptr;
}

// 最初の初期化時を含め、サンプルレートが変更になった場合に呼ばれる
void SimpleSynthAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    // Use this method as the place to do any pre-playback
    // initialisation that you need..
    core->reset(sampleRate, samplesPerBlock);
}

// Audio処理のコールバック
void SimpleSynthAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
{
    // 最初にMIDIメッセージを処理
    if (!midiMessages.isEmpty())
    {
        int i;
        MidiMessage m;
        MidiBuffer::Iterator it(midiMessages);
        while (it.getNextEvent(m, i))
        {
            const unsigned char* rawData = (unsigned char*)m.getRawData();
            core->midi(rawData[0], rawData[1], rawData[2]);
        }
    }
    
    ScopedNoDenormals noDenormals;
    auto totalNumInputChannels  = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();

    for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
        buffer.clear (i, 0, buffer.getNumSamples());

    // Audioのバッファをシンセのコアに渡す
    float* temp[2];
    temp[0] = buffer.getWritePointer (0);
    temp[1] = buffer.getWritePointer (1);
    core->process(temp, buffer.getNumSamples());
}

// ホスト以外からMIDI受けしたものもシンセに渡す
void SimpleSynthAudioProcessor::midi(unsigned char status, unsigned char data1, unsigned char data2)
{
    core->midi(status, data1, data2);
}
```

##UI上の鍵盤とも繋ぐ

```SimpleSynthMainComponent.cpp
void SimpleSynthMainComponent::handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
{
    processor->midi((unsigned char)(0x90 | (midiChannel & 0x0f)), (unsigned char)midiNoteNumber, (unsigned char)(velocity * 127.0f));
    labelNote->setText(String::formatted("%d", midiNoteNumber), dontSendNotification);
}

void SimpleSynthMainComponent::handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
{
    processor->midi((unsigned char)(0x80 | (midiChannel & 0x0f)), (unsigned char)midiNoteNumber, (unsigned char)(velocity * 127.0f));
    labelNote->setText(String("---"), dontSendNotification);
}
```

ホントにインチキというか手抜きな実装なので、モノフォニックだし、折り返しノイズ対策してないし、パツパツノイズ出まくりだし、ノートオン・オフの処理もいい加減だけど、これで一応音は鳴る。

あ！パラメータとして出してたVolumeを繋いでなかった！うーん、それは宿題ってことで！
<img width="928" alt="スクリーンショット 2018-12-30 11.08.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/107818/92f14531-ef2b-fad8-f326-962496ca256e.png">
<img width="927" alt="スクリーンショット 2018-12-30 11.09.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/107818/3b750d39-f1a1-eab1-9ed4-d9810997b60b.png">