10
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JUCEAdvent Calendar 2018

Day 7

初歩的なシンセ作りNG集

Last updated at Posted at 2018-12-06

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

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

まずはJUCEをダウンロード

JUCEのサイトからGet JUCEで、ダウンロード。

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

Projucerを起動

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

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

image.png

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

image.png

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

XCodeが起動します。

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

image.png

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

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

image.png

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

Projucerにもどって、
左上の歯車アイコンから、設定を開いて、
Plugin is a Synth
Plugin MIDI Input
をチェックします。
image.png

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

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

image.png

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

Sin波形を鳴らしたい

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

PluginProcessor.cppのSinSynthAudioProcessor::processBlockというところにコード書きます。
image.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にします。

音は一応でました。

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

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

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

image.png

これはよくない

image.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

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

これ、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で音が鳴る、鳴らないにしてみましょう。

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

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

これ直すとこんな感じ。

音量変化をつける.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時に完全に0になっていないのか音が鳴っていますね。

音がうっすら鳴っているのを直す.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());
        }
    }

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

まずい点、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());
        }
    }

音程をなだらかに変える

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

image.png

実はタイミングが怪しい

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

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

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

JUCEにあるシンセを使う

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

10
4
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
10
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?