初歩的なシンセ作りNG集

JUCEを使って、サイン波形を作って音を鳴らす。

簡単なはず。と試みて、つまづきそうな点を例に上げていきます。

# まずはJUCEをダウンロード

[JUCEのサイト](https://juce.com/)からGet JUCEで、ダウンロード。

自分の環境はMacなので、Mac版をダウンロードしています。

# Projucerを起動

アプリケーションフォルダにJUCEフォルダをドロップして、中にあるProjucerを起動。

ファイルメニューからNew Project>AudioPlug-inを選択。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/af1f0534-11fe-58b3-1b91-d60869ba8857.png)


Project名はSinSynthとでもしておきます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/9690da90-fb89-973a-7412-9bd69f103a71.png)

Createを押して、
右上のXCodeのアイコンをクリックすると

XCodeが起動します。

左上のメニューからSinSynthのアプリを選びます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/e37b6c7c-8aa9-7525-2ab4-2f569c846f84.png)

再生ボタンみたいなビルドボタンを押して実行し、

MIDIキーボードを繋いで、
オプションから選択しておきます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/1f6c2ce3-5b95-19d6-1179-22415abfc44d.png)

まだ、鍵盤を押しても何も音がしません。

Projucerにもどって、
左上の歯車アイコンから、設定を開いて、
Plugin is a Synth
Plugin MIDI Input
をチェックします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/7ad65213-ee07-21cd-1a5f-99821ba8fa2c.png)

右上のXCodeアイコンを押して、XCodeでビルド

Optionを見ると、オーディオインプットが無くなっています。
Instrumentとしてビルドされたようです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/4c83efcb-f546-cb02-30e2-db262c380a56.png)

まだ、MIDI鍵盤押しても音がしません。

# Sin波形を鳴らしたい

Projucerにもどって、
Sin波形を鳴らすコードを書きます。

PluginProcessor.cppのSinSynthAudioProcessor::processBlockというところにコード書きます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/6e62c6fb-7733-519c-7b0f-ac50f93a78df.png)

``` PluginProcessor.cpp
    for (int channel = 0; channel < totalNumInputChannels; ++channel)
    {
        auto* channelData = buffer.getWritePointer (channel);

        // ..do something to the data...
        for(int sample = 0;sample < buffer.getNumSamples();sample++)
        {
            channelData[sample] = std::sin(sample);
        }
        
    }
```
音が出ない？
そう
totalNumInputChannelsは0ですね。
ここをtotalNumOutputChannelsにします。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">何かやばいサイン波形 <a href="https://t.co/jsTW0rZQoi">pic.twitter.com/jsTW0rZQoi</a></p>&mdash; tatmos (@tatmos) <a href="https://twitter.com/tatmos/status/1065967731718053889?ref_src=twsrc%5Etfw">2018年11月23日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 音は一応でました。

しかし、これはサイン波形ではありません。

std::sin()の引数にsampleを直接入れてはいけません。
これは、サイン波形を0~NumSamplesの数までの波形を作っているのですが、
この関数は、外から呼ばれ、どれくらいの周期で呼ばれるかがわかりません。

ためしに、SampleRateやAudio buffer sizeを変えて見ると、なる音の高さや音色が変化します！！

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/aa276578-6f59-8b8b-cecf-2fc7b698f2b3.png)

これはよくない

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/022e9052-612e-05ea-1bd6-3e760effc40a.png)


## 音色をなんとかする

おそらく、buffer sizeごとに波形が細切れになって、それが繋がって鳴っているので、
buffer sizeの音が聞こえています。

buffer sizeに依存しないように

``` phaseを導入.cpp
    static int phase = 0;

    for (int channel = 0; channel < totalNumOutputChannels; ++channel)
    {
        auto* channelData = buffer.getWritePointer (channel);

        // ..do something to the data...
        for(int sample = 0;sample < buffer.getNumSamples();sample++)
        {
            channelData[sample] = std::sin(phase++);
        }
        
    }
```
## 少し滑らかになったけどまだ変ですね。

はい、これステレオですね。

``` phaseを導入.cpp
    static int phase = 0;
    int startPhase = phase;
    for (int channel = 0; channel < totalNumOutputChannels; ++channel)
    {
        phase = startPhase;
        auto* channelData = buffer.getWritePointer (channel);

        // ..do something to the data...
        for(int sample = 0;sample < buffer.getNumSamples();sample++)
        {
            channelData[sample] = std::sin(phase++);
        }        
    }
```
開始phaseを入れて、ステレオで同期しておくようにしておきました。
（片方作って、コピーでも良いね）
![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/d3651cc4-27dc-1866-789e-ff132e41c3a0.png)




## めっちゃ高い音なんやねん

これ、sin波形に周波数要素が絡んでいません。
音程を鳴らしたい、440Hzを鳴らしたい。

そんな時はこんな感じ

``` 440Hzのサイン波形.cpp
channelData[sample] = std::sin(2.0 * double_Pi * 440.0 * (float)phase++ / 44100.0);
```
これで、落ち着いたサイン波形が得られました。

あ、このままだと再生周波数変えると音が変わってしまうので

``` 440Hzのサイン波形再生周波数対応.cpp
channelData[sample] = std::sin(2.0 * double_Pi * 440.0 * (float)phase++ / this->getSampleRate());
```
これでOK。

# 音がなりっぱなしで発狂しそう

NoteOn,NoteOffで音が鳴る、鳴らないにしてみましょう。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">サイン波形は鳴らせたがプチプチ <a href="https://t.co/sPUE3qApD7">pic.twitter.com/sPUE3qApD7</a></p>&mdash; tatmos (@tatmos) <a href="https://twitter.com/tatmos/status/1065977597664780288?ref_src=twsrc%5Etfw">2018年11月23日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# と、その前にチャンネル処理変更

2つめ以降は1つめのチャンネルのデータを流用するようにちょっと変更しておきます。

``` チャンネルデータを流用.cpp
static int phase = 0;
    for (int channel = 0; channel < totalNumOutputChannels; ++channel)
    {
        auto* channelData = buffer.getWritePointer (channel);
        
        // ..do something to the data...
        for(int sample = 0;sample < buffer.getNumSamples();sample++)
        {
            if(channel > 0)
            {
                channelData[sample] = buffer.getWritePointer(0)[sample];
                continue;
            }
            channelData[sample] = std::sin(2.0 * double_Pi * 440.0 * (float)phase++ / this->getSampleRate());
        }
    }
```

# 音がね。プチプチいってます。なんとかしたい。

波形の始めと終わりにプチプチという音。
これ、波形の0でない時に一番大きく音がなります。

なだらかに0~1.0 1.0~0.0へと変化させることで解決します。

ゲートのカーブを
![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/e35cdf56-8c2d-348d-9915-2a245c22d65d.png)


これ直すとこんな感じ。

``` 音量変化をつける.cpp
    static int note = 0;
    static float amp = 0.0;
    MidiBuffer::Iterator MidiItr(midiMessages);
    MidiMessage MidiMsg;int smpPos;
    while(MidiItr.getNextEvent(MidiMsg,smpPos))
    {
        if(MidiMsg.isNoteOn())
        {
            note = 1;
        }
        if(MidiMsg.isNoteOff())
        {
            note = 0;
        }
    }
    
    static int phase = 0;
    for (int channel = 0; channel < totalNumOutputChannels; ++channel)
    {
        auto* channelData = buffer.getWritePointer (channel);
        
        // ..do something to the data...
        for(int sample = 0;sample < buffer.getNumSamples();sample++)
        {
            if(channel > 0)
            {
                channelData[sample] = buffer.getWritePointer(0)[sample];
                continue;
            }
            
            if(note == 1 && amp < 1.0f) amp += 0.01f;
            if(note == 0 && amp > 0.0f) amp -= 0.01f;
            
            channelData[sample] = amp * std::sin(2.0 * double_Pi * 440.0 * (float)phase++ / this->getSampleRate());
        }
    }
```

## noteOff時に完全に０になっていないのか音が鳴っていますね。

``` 音がうっすら鳴っているのを直す.cpp
    static int phase = 0;
    for (int channel = 0; channel < totalNumOutputChannels; ++channel)
    {
        auto* channelData = buffer.getWritePointer (channel);
        
        // ..do something to the data...
        for(int sample = 0;sample < buffer.getNumSamples();sample++)
        {
            if(channel > 0)
            {
                channelData[sample] = buffer.getWritePointer(0)[sample];
                continue;
            }
            
            if(note == 1 && amp < 1.0f) amp += 0.01f;
            if(note == 0 && amp > 0.0f) amp -= 0.01f;
            if(amp < 0) amp = 0;
            
            channelData[sample] = amp * std::sin(2.0 * double_Pi * 440.0 * (float)phase++ / this->getSampleRate());
        }
    }
```

## 音程が欲しい

440Hzだけだと寂しいのでMIDI NoteNoから音程をとってきて周波数に変換します。

``` 音量の変化もなだらかに音程も.cpp
static int note = 0;
    static float amp = 0.0;
    MidiBuffer::Iterator MidiItr(midiMessages);
    MidiMessage MidiMsg;int smpPos;
    while(MidiItr.getNextEvent(MidiMsg,smpPos))
    {
        if(MidiMsg.isNoteOn())
        {
            note = MidiMsg.getNoteNumber();
        }
        if(MidiMsg.isNoteOff())
        {
            note = 0;
        }
    }
    
    static int phase = 0;
    for (int channel = 0; channel < totalNumOutputChannels; ++channel)
    {
        auto* channelData = buffer.getWritePointer (channel);
        
        // ..do something to the data...
        for(int sample = 0;sample < buffer.getNumSamples();sample++)
        {
            if(channel > 0)
            {
                channelData[sample] = buffer.getWritePointer(0)[sample];
                continue;
            }
            
            if(note != 0 && amp < 1.0f) amp += 0.0001f;
            if(note == 0 && amp >= 0.0f) amp -= 0.0001f;
            
            channelData[sample] = amp * std::sin(2.0 * double_Pi * ((440.0f * std::exp(0.057762265f * (note - 69.0f)))) * (float)phase++ / this->getSampleRate());
        }
    }
```

んー、まだぷちぷちいっていますね・・・。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">音程もつけたけど、プチプチは改善しない・・・ <a href="https://t.co/5LOoBx6Ndj">pic.twitter.com/5LOoBx6Ndj</a></p>&mdash; tatmos (@tatmos) <a href="https://twitter.com/tatmos/status/1065990536316641281?ref_src=twsrc%5Etfw">2018年11月23日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


まずい点、note==0の音がsin関数にわたっているので、即時無音になっている。（ゲートゆるやかにした意味ないし・・・）

``` noteとgateを別にして.cpp
    static float note = 0;
    static bool gate = false;
    static float amp = 0.0;
    MidiBuffer::Iterator MidiItr(midiMessages);
    MidiMessage MidiMsg;int smpPos;
    while(MidiItr.getNextEvent(MidiMsg,smpPos))
    {
        if(MidiMsg.isNoteOn())
        {
            gate = true;
            note = MidiMsg.getNoteNumber();
        }
        if(MidiMsg.isNoteOff())
        {
            gate = false;
        }
    }
    
    static int phase = 0;
    for (int channel = 0; channel < totalNumOutputChannels; ++channel)
    {
        auto* channelData = buffer.getWritePointer (channel);
        
        // ..do something to the data...
        for(int sample = 0;sample < buffer.getNumSamples();sample++)
        {
            if(channel > 0)
            {
                channelData[sample] = buffer.getWritePointer(0)[sample];
                continue;
            }
            
            if(gate == true && amp < 1.0f) amp += 0.01f;
            if(gate == false && amp >= 0.0f) amp -= 0.01f;
            
            channelData[sample] = amp * std::sin(2.0 * double_Pi * ((440.0f * std::exp(0.057762265f * (note - 69.0f)))) * (float)phase++ / this->getSampleRate());
        }
    }
```

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">だいぶプチプチは減ったが <a href="https://t.co/LAhnv1z5gI">pic.twitter.com/LAhnv1z5gI</a></p>&mdash; tatmos (@tatmos) <a href="https://twitter.com/tatmos/status/1067774784736395265?ref_src=twsrc%5Etfw">2018年11月28日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


## 音程をなだらかに変える

さらに
音程変更時に断絶しているのが原因とみて、
少し補間処理を入れるようにします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/bb65bfd0-3439-85a3-a3cd-7c47e73291aa.png)


## 実はタイミングが怪しい

で、どのタイミングでMIDIがトリガーされたかは、MIDIのsmpPosという値があって、これを使わないと、
audio buffer sizeに依存したnoteOn,noteOff になってしまっていました。

あと、ポリフォニックとか考えると・・・
音を変化させるのではなく交互にトリガーして、フェードするような仕組みにしたり・・・

あぁ、もう面倒です。どうしたらサイン波形が綺麗に鳴らせるのか？

## JUCEにあるシンセを使う

JUCEのSynthモジュールを使うという手があります。