음악, 삶, 개발

DAW 의 파라미터에 대한 모든것 : 파트3 (마지막) 본문

개발 공부/Juce 공부방

DAW 의 파라미터에 대한 모든것 : 파트3 (마지막)

Lee_____ 2020. 10. 13. 00:34

 

이제 남은 문제는 아래와 같다.

 

4. 어떻게 나의 파라미터를 GUI 에 연결할수있는지? 

5. 어떻게 나의 파라미터 값을 이 GUI 로 변경할수있는지?


< 일단 강좌 시청하기 ! >

 

위 문제들을 마지막으로 해결하기전에

아래의 강좌를 무조건 시청한다.

 

Controlling Audio Parameters with ValueTree Attachments

 

위의 강좌는 파트1,  파트2 내용을 모두 다루는데,

우리는 이미 앞선 포스트들로 학습을 했기때문에 이해하는데 무리가 없을것이다.


4. 어떻게 나의 파라미터를 GUI 에 연결할수있는지? 

앞서 우리는 juce::AudioProcessor 의 서브 클래스에 코드를 작성하는 작업을 하였다.

이제부터는 juce::AudioProcessorEditor 의 서브 클래스에 코드를 작성해야한다.


4.1 apvts 를 refernece 로 넘겨받기

class PluginWindow : public juce::AudioProcessorEditor {

public:

    PluginWindow (juce::AudioProcessor& p, juce::AudioProcessorValueTreeState& a)

        : AudioProcessorEditor(p),
          apvts(a)

    {


    }

private:

    juce::AudioProcessorValueTreeState& apvts;

};

내가 만든 파라미터는 AudioProcessor 의 서브클래스안에 apvts 객체에 등록되어있다.

따라서 AudioProcessorEditor 클래스는 이 apvts 객체를 reference 로 가지고있어야한다.

따라서, PluginWindow 생성자는 2번째 인자로 apvts 를 reference 로 넘겨받아야한다.

PluginWindow (juce::AudioProcessor& p, juce::AudioProcessorValueTreeState& a) :  apvts(a)

GUI component 는 굉장히 많은수일수가있는데,

모두가 apvts 를 reference 로 가지는것은 불필요하며,

부모 style 의 component 에만 apvts 를 refernece 로 넘기는것이 현명할것이다.

검은색이 부모 component

위와 같은 GUI 요소들이 있다면, 검은색이 부모 component 이고,

이들만이 apvts 에 대해 알고있도록 디자인하는것이 좋다고 한다. (Peter 말)

 


4.2 GUI 생성하기

당연한 이야기지만, GUI 를 생성해야한다.

이때 주의할점은 juce::Button, juce::Slider, juce::ComboBox 

이 셋중 하나여야한다는것이다. (아닌 gui 도 가능하지만, 다음 파트에서 다루겠다)

class PluginWindow : public juce::AudioProcessorEditor {

public:


private:
    
    juce::Slider testSlider;

};

이때 juce::Slider 객체의 속성들을 정의해줘야하하긴하는데,

juce::Slider 강좌가 아니므로 여기서는 생략한다.


4.3 GUI 를 파라미터에 연결하기.

GUI 와 파라미터를 연결하기위해 Juce 에서는 APVTS 클래스의 내부 클래스로 ---Attachment 라는 클래스를 제공한다.

 

ButtonAttachment

ComboBoxAttachment

SliderAttachment

 

Attachment 앞에 붙은 이름을 보면 알수있듯이,

이 클래스를 사용하기위해서는 GUI 로 juce::Button, juce::ComboBox, juce::Slider 를 사용해야한다.

이 Attachment 를 사용하기위해서, 4.2 에서 이 3가지의 GUI 만을 사용하라고한것이다.


4.4 SliderAttachment 클래스의 객체를 포인터로 생성하기.

class PluginWindow : public juce::AudioProcessorEditor {

public:

    using SliderAttachment = juce::AudioProcessorValueTreeState::SliderAttachment;

private:
    
    juce::Slider testSlider;
    std::unique_ptr<SliderAttachment> testAttachment; // GUI 와 파라미터의 연결고리

};

위의 코드에서 SliderAttachment 로 type 을 줄여쓰기위해 코드의 초입에 using 을 사용하였다. 

type 명이 너무 길때, 그리고 이 type 이 코드내에 자주 등장할것이라면 using 을 사용하는것이 현명하다.

 

SliderAttachment 객체는 반드시 unique_ptr 이여만 한다.

unique_ptr 은 이 PluginWindow 객체가 파괴될때, 즉 플러그인창이 닫힐때

pointer 를 자동적으로 삭제해주기때문이다.

이 SliderAttachment 포인터가 GUI 와 파라미터를 이어주는 연결고리라고 생각하면된다.

(남의 코드를 보았을때 왜 포인터가 있어!! 했는데, 너무 놀라지말자. 무조건 필요하것이니..)

 

또 여기서 정말 중요한것은, juce::Slider 객체보다 SliderAttachment 객체가 무조건 다음에 정의되어야한다는것이다.

Juce 공식문서에도, SliderAttachment 객체가 파괴되기전까지는 juce::Slider 객체가 생존하고있어야한다고 경고한다.

During the lifetime of this SliderAttachment object, it keeps the two things in sync, making it easy to connect a slider to a parameter. When this object is deleted, the connection is broken.
Make sure that your 
AudioProcessorValueTreeState and Slider aren't deleted before this object!
private:
    
    juce::Slider testSlider;
    std::unique_ptr<SliderAttachment> testAttachment;

C++ 의 private 멤버는 정의된 순서대로 객체가 생성되는데,

파괴는 정의의 역순으로 이루어진다.

즉, 위의 코드에서 testSlider 가 먼저 생성되지만,

파괴는 testAttachment 가 먼저 파괴되는것이다.

만약 GUI 가 여러개라면, Attachment 포인터는 무조건 이들보다 하단에 위치하여야한다.

아래처럼 말이다.

private:
    
    juce::Slider s1;
    juce::Slider s2;
    juce::Slider s3;
    juce::Slider s4;

    std::unique_ptr<SliderAttachment> sa1;
    std::unique_ptr<SliderAttachment> sa2;
    std::unique_ptr<SliderAttachment> sa3;
    std::unique_ptr<SliderAttachment> sa4;

다시 원래의 이야기로 돌아가보자


4.5 SliderAttachment 포인터와 Slider 객체 연결하기

드디어, "어떻게 나의 파라미터를 GUI 에 연결할수있는지?" 에 대한 답이다.

PluginWindow 생성자의 block 안에서,

아래의 코드를 작성한다.

gainAttachment = std::make_unique<SliderAttachment> (apvts, "test", testSlider);

이 코드가 마침내, 연결고리를 연결하는것이다.


4.6 완성!!!! ㅠㅠㅠ...

class PluginWindow : public juce::AudioProcessorEditor {

public:

    using SliderAttachment = juce::AudioProcessorValueTreeState::SliderAttachment;

    PluginWindow (juce::AudioProcessor& p, juce::AudioProcessorValueTreeState& a)

        : AudioProcessorEditor(p),
          apvts(a)

    {

        gainAttachment = std::make_unique<SliderAttachment> (apvts, "test", testSlider);

    }

private:

    juce::AudioProcessorValueTreeState& apvts;

    juce::Slider testSlider;
    std::unique_ptr<SliderAttachment> testAttachment;

};

< 결말 >

앞에서 작성한 AudioProcessor 의 서브 클래스 코드와 연결하여 함께 보도록 하자.

class Plugin  : public juce::AudioProcessor {

    public :

        Plugin() : apvts(*this, nullptr, juce::Identifier("APVTS"), createParams()) {}

        using Params = juce::AudioProcessorValueTreeState::ParameterLayout;
        using ParamInt = juce::AudioParameterInt;
		
        Params createParams() {

            Params params;
	
            params.add(std::make_unique<ParamInt>("test", "Test", 0, 10, 0)); // Id, Display Name, Min, Max, Default

            return params;

        }

        void processBlock (juce::AudioSampleBuffer& buffer, juce::MidiBuffer& midiBuffer) override {

            auto value = *(apvts.getRawParameterValue("test")); 
            
        }


    private :

        juce::AudioProcessorValueTreeState apvts; 

        
}


class PluginWindow : public juce::AudioProcessorEditor {

public:

    using SliderAttachment = juce::AudioProcessorValueTreeState::SliderAttachment;

    PluginWindow (juce::AudioProcessor& p, juce::AudioProcessorValueTreeState& a)

        : AudioProcessorEditor(p),
          apvts(a)

    {

        testAttachment = std::make_unique<SliderAttachment> (apvts, "test", testSlider);

    }

private:

    juce::AudioProcessorValueTreeState& apvts;

    juce::Slider testSlider;
    std::unique_ptr<SliderAttachment> testAttachment;

};

 

사실 수많은 boilerPlate 코드들을 생략한 상태이다. (특히 Plugin 클래스에서)

또한 testSlider 도 크기등을 지정하기위해 PluginWindow 안에 많은 함수들이 호출되어야한다.

하지만 플러그인에 파라미터를 등록하는것이 핵심이므로 모두 생략하였다.


< 마지막으로... : 결국은 암기다!>

앞선 파트1,파트2, 파트3 까지를 통해 흐름을 이해하는데 주력하였다.

하지만 이해를 했다면 결국 코드를 암기하는것도 중요한듯하다.

이렇게 파라미터를 생성하고 GUI 와 연결하는것은, 딱히 응용할수있는, 창의력이 필요한 부분이 없기때문이다.

정해진 코드의 흐름을 따라야한다.