C++/Sound

[JUCE] Juce Framework로 사운드 프로그래밍 #2 구조 대충 알아보기

Kareus 2022. 6. 8. 03:06

먼저, JUCE를 이용해 Audio Application을 만들 때 고려할 사항을 정리해봅시다.

 

Audio Application

Plugin과의 가장 큰 차이점이라고 한다면, 아마도 Processor가 없다는 점일 겁니다.

간단히 말해서, MIDI 데이터를 받아오고, 사운드를 내보낼 Component가 필요합니다.

물론 JUCE에서 제공하는 템플릿으로 생성했다면 MainComponent라고 이미 생성이 되어 있을 겁니다.

 

class MainComponent  : public juce::AudioAppComponent
{
public:
    MainComponent();
    ~MainComponent() override;
    
    void prepareToPlay(int samplesPerBlockExpected, double sampleRate) override;
    void getNextAudioBlock(const juce::AudioSourceChannelInfo& bufferToFill) override;
    void releaseResources() override;
    //...
}

사운드를 입출력하는 Component는 juce::AudioAppComponent를 상속받은 클래스여야 합니다.

해당 클래스에서는 prepareToPlay, getNextAudioBlock, releaseResources

총 3개의 가상 함수가 정의되어 있고, 상속받은 쪽에서 사운드를 입출력하도록 override 해줘야 합니다.

 

물론, 굳이 MainComponent가 아니라 다른 AudioAppComponent를 만들고 거기서 처리해줘도 됩니다.

사운드를 입출력해야될 Component에서는 setAudioChannel을 호출해서 입출력 채널을 설정해줘야 합니다.

 

prepareToPlay는, 사운드를 출력하기 전에 준비 단계에서 실행하는 함수입니다.

Sound Generator를 parameter에 맞게 설정해주고, 준비시켜주면 됩니다.

각 parameter는 예상되는 출력해야 될 샘플의 수와 sample rate입니다.

sample rate는 초당 샘플의 수입니다. 44100Hz, 48000Hz 등등의 그 수를 말합니다.

 

getNextAudioBlock에서는 주어지는 출력 채널 정보에서 오디오 버퍼를 가져와서, 이 버퍼에 사운드 데이터를 채워주면 됩니다. 사운드 데이터에 채워넣는 작업은 juce::Synthesiser를 이용하면 됩니다. 자세한 건 다음 포스팅에서 알아봅시다.

juce::MPESynthesiser라는 것도 있긴 한데, 얘는 넘어가겠습니다.

Multidimensional Device에서 입력되는 노트 포맷을 처리할 수 있는 Synthesizer인데, 방식은 Synthesizer와 똑같은 데다, 애초에 저한테 그런 장치가 없습니다.

* 찾아봤는데, 최근 (2022.03)에 나온 Seaboard RISE 2가 1400달러, 170만원 정도 하는군요.

  140달러짜리 키보드도 비싸다고 고민했는데 아 ㅋㅋ

 

아무튼, 다음으로 넘어갑시다.

releaseResources는 사운드 프로세싱에서 사용한 리소스를 해제할 필요가 있다면 여기서 해제하면 됩니다.

키보드 등에서 MIDI 데이터를 받아올 때 MidiKeyboardState라는 클래스를 사용하는데, 여기서 받아온 리소스를 해제하고 싶을 때 releaseResources에 코드를 작성해주면 됩니다. 다음과 같습니다.

/* MainComponent.h */
class MainComponent : public juce::AudioAppComponent
{
    //...
private:
    //...
    juce::MidiKeyboardState keyboardState;
}


/* MainComponent.cpp */
void MainComponent::releaseResources() override
{
    keyboardState.reset();
}

 

Plugin의 경우도 알아봅시다.

Plugin

 

Plugin은 MainComponent 대신 PluginProcessor와 PluginProcessorEditor로 시작합니다.

Processor는 말그대로 사운드 프로세싱이 이루어지는 코어입니다.

ProcessorEditor는 그러한 Processor의 UI를 그리고 상호작용하는 곳입니다.

 

메인이 되는 곳은 Processor이기 때문에, ProcessorEditor 없이도 사운드 처리를 할 수 있도록 프로그래밍을 해야 합니다.

저는 AudioAppComponent를 그대로 갖고 와서 Editor에 넣었다가 사운드가 안 나오거나 중복돼서 나오거나를 경험했습니다. 이러지 마세요.

 

그리고 Processor에서 사운드 출력에 사용하는 함수 중에

prepareToPlay와 releaseResources는 동일하지만, 사운드 프로세싱을 processBlock 이라는 함수에서 해야 합니다.

getNextAudioBlock이라는 함수가 없습니다. 아무래도 사운드 채널에 연결한 Component가 아니라 프로세싱만 담당하는 중개체 같은 거라 그런 것 같네요.

위에서도 자세한 내용은 다음에 설명한다고 했으니, 얘도 그렇게 하겠습니다.

 

그 외에는, 동일합니다. 사운드 생성도 Synthesiser 클래스를 이용하면 됩니다.

Audio Application에서는 AudioAppComponent에서 사운드 처리와 GUI 모두 담당했다면,

Plugin에서는 사운드 처리는 Processor, GUI는 ProcessorEditor에서 이루어진다고 생각하면 됩니다.

물론 Plugin의 특성상 Processor에서 좀 더 처리해줘야 할 게 있는데, 나중에 하겠습니다.

다만 Audio Application에서 하던 그대로 Plugin에서 하면 코드가 꼬일 가능성이 많이 높습니다.

왜냐고요? 저도 알고 싶지 않았습니다. 일단 Plugin은 Standalone이 메인이 아니라는 것만 알아두세요.

 

다음 포스팅에서 사운드 신시사이저를 만들어봅시다.