Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

LOOPERを作ってみた。

More than 3 years have passed since last update.

概要

JUCEを使って、LOOPERのVSTプラグインを作成します。
1つのボタンで、録音とループ再生を行うシンプルなVSTを作成します。

スクリーンショット 2017-12-19 4.40.24.png

処理の仕組みは、録音時には入力のバッファを全て保存しておき、ループ再生時には、バッファに保存したデータを書き出力するだけです。
1バッファは512サンプルとしたとき、processeblock()で入力からの1バッファを取り込み、それを加工して出力へと吐き出されます。何もしなければバイパスとなります。

具体的には、録音時には、最初の1バッファは1~512サンプル、次のバッファは513〜1024・・・を順次、float型の値を格納するarrayに入力バッファを取り込みます。(バッファ自体は書き変わらないため、バイパスと同じ。)
ループ再生時には、1~512サンプルをバッファへ書き込む、次のバッファは513〜1024を書き込む・・・を行い、録音開始から終了までのバッファ数まできたら1サンプル目に戻ります。

仕組み.001.png

ファイルの構成

projucerで新規プロジェクトを立ち上げ、Audio Plug-inを選択し、プロジェクト名(ここでは"looper")を入れて、Create...を押してください。なお、今回はXcodeでの作成になります。

Xcodeの画面がでると、左側にファイルの一覧がでます。
プログラムを書く必要があるファイルは、
・PluginProcessor.cpp(主にバッファ毎に音声処理したいプログラムを書きます。)
・PluginProcessor.h
・PluginEditor.cpp(GUIを作成します。)
・PluginEditor.h
の4つに加え、以下2つを新規に作成します。
・loop.cpp(録音のためのメソッドとループ再生を行うメソッドを作成します。)
・loop.h

上記のファイルに必要な記載について、1ファイル毎に説明していきます。

1. PluginProcessor.cpp

録音からループ再生とその逆の切り替え時には、looplengthもしくはlooptimeを初期値に戻す必要があります。
onoffは録音中かループ再生中かを表すフラグで、前者が1,後者が0としています。0→1の場合は録音へ切り替え、1→0の場合はループ再生へ切り替えます。preonoffはonoffの1バッファ前の状態を保存しています。

(前略)
void LooperAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
(中略)
    for (int channel = 0; channel < totalNumInputChannels; ++channel)
    {
        MidiBuffer processedMidi;
        MidiMessage m;

            if (onoff==1 && preonoff==0)//録音へ切り替わったらlooplengthを決め直すため、1に戻す。
            {
                LOOP.looplength = 1;
            }
            else if (onoff==0 && preonoff==1)//ループ再生へ切り替わったらstoredloop[]を頭から再生するため、looptimeを0にする。
            {
                LOOP.looptime = 0;
            }

        if(onoff == 1)
            LOOP.setloop(buffer.getWritePointer(channel),buffer.getNumSamples());//録音の関数を呼び出す
        else if(onoff == 0)
            LOOP.getloop(buffer.getWritePointer(channel),buffer.getNumSamples());//ループ再生の関数を呼び出す。

    }
    preonoff=onoff;//onnoffの一つ前の状態をpreonoffに保存。
}

2. PluginProcessor.h

processBlockの処理過程で必要な関数は、
同LooperAudioProcessorクラスに書きましょう。
また、GUI側からも参照されるので、publicに書きましょう。

#include "../JuceLibraryCode/JuceHeader.h"
#include "loop.h"//録音・ループ再生を処理する関数に関するloopクラスのヘッダーファイル

class LooperAudioProcessor  : public AudioProcessor
{
public:
    int onoff=1;//録音中なら1、ループ再生中なら0
    int preonoff=1;//1つ前のバッファ取り込み時のonoffを保存するためのもの。前後で10か01なら録音からループ再生
またはその逆に切り替わる時点とする。
(中略)
loop LOOP;//loopクラスの実装
private:
(後略)

3. PluginEditor.cpp

GUIはボタンの設計だけを行なっています。
GUIのボタンを押した時に録音(setloop())またはループ再生(getloop())の関数を呼び出します。
ただし、ボタンを押す度に、int型の変数onoffは1→0→1→0→・・・と順番に変化するようにし、これに変化があったとき、録音とループ再生の切り替えが行われるようにします。

LooperAudioProcessorEditor::LooperAudioProcessorEditor (LooperAudioProcessor& p)
    : AudioProcessorEditor (&p), processor (p)
{   
    addAndMakeVisible (button1);//GUIにボタンを表示
    button1.setButtonText("looper");//GUIのボタン内に表示するワードをセット
    button1.addListener (this);//いつ押したかを監視している。 
    setSize (400, 300);
}

(中略)

void LooperAudioProcessorEditor::resized()
{
    button1.setBounds (getWidth() * 0.1, getHeight() * 0.1, getWidth()*0.8, getHeight() * 0.1);//ボタンの大きさを設定
}
void LooperAudioProcessorEditor::buttonClicked(Button* button){//ボタンを押したら1onoffを切り替え
    if (processor.onoff==0)  processor.onoff = 1;
    else  processor.onoff=0;
}

4. PluginEditor.h

buttonClicked(Button* button)には、GUI上のボタンを押した際に行う処理を記載します。
ただし、これを使えるようにするには、juce::Button::Listenerの継承をお忘れなく。
ここでは、ボタン押下時にonoffを切り替えます。

class LooperAudioProcessorEditor  : public AudioProcessorEditor,private juce::Button::Listener//下のTextButtonを使えるように、追加を忘れずに!
{
(中略)
private:    
    void buttonClicked(Button* button) override;    //ボタンを押した時にonoffを切り替える関数
    LooperAudioProcessor& processor; //
    TextButton button1; //
(後略)

5. loop.cpp

録音のためのメソッドとループ再生を行うメソッドを作成します。
概要でも説明の通り、録音の時間がloopの長さ(=バッファ数(=looplength)*512サンプル)で、
ループ再生時には、storedloop[]からバッファへ順次書き込みます。looptimeでstoredloop[]の何バッファ目を書き込むか指定します。

#include "loop.h"
#include <math.h>

loop::loop()
:looplength(1),storedloop{0},looptime(0)
{}

loop::~loop(){}

void loop::setloop(float* bufferPtr, int bufferSize) //録音
{
    for(int i = 0 ; i < bufferSize;i++){
        storedloop[(looplength - 1) * bufferSize + i] = bufferPtr[i];
    }
    if(looplength < 900){//storedloop[]の大きさを超えないように制御
    looplength = looplength + 1 ;
    }
};



void loop::getloop(float* bufferPtr, int bufferSize){//ループ再生
    for(int i = 0 ; i < bufferSize;i++){
        bufferPtr[i] = storedloop[looptime * bufferSize + i];
    }
      looptime = (looptime + 1) % looplength;  //looptimeは、looplengthを超えないで繰り返します。
};

6. loop.h

バッファはfloat型のサンプルを512個持ったarray[]ですので、録音先もfloatで数値を保存できるようにする必要があります。

#ifndef LOOP_H_INCLUDEDED
#define LOOP_H_INCLUDEDED

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

    void setloop(float* bufferPtr, int bufferSize);
    void getloop(float* bufferPtr, int bufferSize);

    int looplength;//録音再生から終了までのバッファ数
    float storedloop[500000]; //ひとまず今回は、10秒ぐらいまで録音できるようにする。
    int looptime;//ループ再生時に何バッファ目かを示す。

};

#endif

まとめ・感想

今回はシンプルなlooperを作成する手順を記載しました。以外と簡単にできる!と思っていてもちょっとしたことがわからないと、調べるのに時間がかかったり、すぐわかるはずのことに気がつかないことも多々ありました。本記事は、過程で気づいたことを1つでも多くと思って書きました。全体的にメモ書きっぽくなり申し訳ないと思う一方で、ちょっとした気づきの一助でも担えれば幸いです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away