LoginSignup
1
1

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-12-30

この説明は3部作の最後。
JUCEで簡単なソフトシンセを作る(準備編)
JUCEで簡単なソフトシンセを作る(UI実装編)

ソースはここ

超シンプルな三角波シンセの実装

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

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を繋いでなかった!うーん、それは宿題ってことで!
スクリーンショット 2018-12-30 11.08.38.png
スクリーンショット 2018-12-30 11.09.46.png

1
1
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
1
1