LoginSignup
4

More than 3 years have passed since last update.

初歩的なシンセ作り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モジュールを使うという手があります。

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
What you can do with signing up
4