CafeM0ca

[JUCE]튜토리얼10 The Audio Application template 본문

Programming/JUCE

[JUCE]튜토리얼10 The Audio Application template

M0ca 2018. 2. 27. 23:06
반응형

//발번역 https://juce.com/doc/tutorial_simple_synth_noise

tutorial_simple_synth_noise.zip


Projucer에서 Audio Application template. 오디오 관련 응용프로그램 만들때 유용하다.


데모 프로젝트를 실행해보면 삐-소리가 난다.


이번 튜토리얼에서는 오디오 출력만 구현할꺼다. 오디오 입력과 현실시간 오디오 입력 데이터 처리는 다른 튜토리얼에서 알아볼것이다.

오디오 어플리케이션 템플릿은 GUI 어플리케이션 템플릿과 비슷하다. 아래의 경우만 제외하면 말이다.

- MainContentComponent 클래스는 AudioAppComponent클래스를 Component클래스 대신 상속한다.

- juce_audio_utils 모듈이 프로젝트에 추가되었고 다른 오디오 관련 모듈들이 프로젝트에 기본값으로 추가되었다.



 오디오 응용프로그램 lifecycle(생활 주기?)

AudioAppComponent 클래스는 기본 추상클래스다. 이것은 3개의 pure virtual 함수를 갖고있는데 오디오 응용프로그램의 lifecycle을 나타낸다. 

AudioAppComponent를 파생된 클래스에서 구현해야한다.


AudioAppComponent::prepareToPlay(): 오디오 진행 시작전에 호출된다.

AudioAppComponent::releaseResources(): 오디오 진행이 끝난 후 호출된다.

AudioAppComponent::getNextAudioBlock(): 오디오 데이터의 새 블럭이 오디오 하드웨어가 필요할때마다 호출된다.


이 3가지중 가장 중요한것은 아마 AudioAppComponent::getNextAudioBlock() 함수다. JUCE 오디오 응용프로그램에서 오디오를 생성하거나 처리하기때문이다.

작동 방식을 이해하려면 현대 컴퓨터가 오디오를 생성하는 방법을 알아야한다. 오디오 하드웨어는 초마다 채널 당 일정 수의 샘플을 생성해야한다.

CD-품질 샘플은 44.1kHz(초당 44100 샘플)이다.  한번에 하나의 샘플을 오디오 하드웨어에 전달하는 대신 샘플은 특정 수의 샘플을 포함하는 버퍼 또는 블럭으로 전달된다. 예를 들어, 44.1kHz와 블록크기 441에서 AudioAppComponent::getNextAudioBlock() 함수는 초당 100번 호출된다.


Note: 실제로는 441사이즈의 버퍼는 사용되지 않고 256,512,1024 ..2의 배수로 쓰인다.


필수적으로 AudioAppComponent::getNextAudioBlock() 함수는 오디오 하드웨어의 콜백으로 서비스한다. 이것은 주의하는것이 중요하다. 이 함수가 다른 쓰레드로부터 호출된다.


JUCE 오디오 응용프로그램은 알맞게 작동하기위해 2가지 중요한 함수가 있다. 이번에는 구현하지 말고 호출해야한다.

-AudioAppComponent::setAudioChannels() : 반드시 호출하기위해 몇개의 입력을 등록하고 출력 채널이 필요하다. 전현적으로 생성자에서 처리한다.  이 함수는 응용프로그램에서 오디오 프로세싱의 시작을 트리거한다.

-AudioAppComponent::shutdownAudio() : 오디오 시스템을 강제종료할때 호출한다. 전형적으로 소멸자에 추가한다.


오디오 응용프로그램 초기화

예제 프로그램을 상세하게 뜯어보자. 생성자는 Component 객체의 크기 지정이 필요하다. 따라서 다음과 같이 초기화한다.

MainContentComponent()
{
setSize (800, 600);
setAudioChannels (0, 2); // no inputs, two outputs
}

위에 언급한거와 같이 AudioAppComponent::setAudioChannels() 함수는 오디오 시스템을 시작하기전에 트리거한다. 특히 prepareToPlay()함수를 호출할것이다.

void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override
{
String message;
message << "Preparing to play audio..." << newLine;
message << " samplesPerBlockExpected = " << samplesPerBlockExpected << newLine;
message << " sampleRate = " << sampleRate;
}

이 경우 여기서는 아무것도 할 필요가 없지만 순수 가상 함수이므로 적어도 빈 함수를 구현해야한다. 여기서 우리는 이 시점에서 대상 장치의 오디오 시스템에 대해 얻을 수 있는 유용한 정보를 기록한다. samplesPerBlockExpected의 이름에서 알 수 있듯이 getNextAudioBlock() 함수에서 오디오 버퍼가 요청 될 때마다 요청할 수 있는 오디오 버퍼의 크기(샘플)이다. //이래서 함수 이름을 잘 지어야한다.

이 버퍼 크기는 콜백마다 다를 수 있지만 이는 좋은 의미다. SampleRate 인수는 하드웨어의 현재 샘플 속도를 알려준다. 톤을 합성하거나(튜토리얼: Sine synthesis참조) 또는 이퀄라이제이션을 사용하는 것과 같이 주파수 의존적인 작업을 수행하는 경우 이 작업이 필요할 것이다. 지연 효과를 사용하는 경우 샘플 속도도 알아야한다. 잡음을 생성하기 위해 이 정보가 필요하지 않다. //이 튜토리얼에서는 잡음 출력만 하는 간단한 예제인점.



오디오 데이터 생성

prepreaToPlay() 함수를 호출한 후 오디오 스레드는 오디오 블럭들을 AudioAppComponent::getNextAudioBlock() 함수를 통해 요청하기 시작할것이다.

이 함수는 bufferTofill 인수를 하나 넘겨받는데 bufferTofill은 AudioSourceChannelInfo 구조체다. (channelinfo) 

AudioSourceChannelInfo 구조체는 여러채널 오디오 샘플의 버퍼를 갖고있다. 이것은 또한 호출에서 처리할 버퍼의 영역을 지정하는 두 개의 정수값을 포함한다.

AudioSourceChannelInfo는 다음 멤버를 갖고있다.

- AudioSampleBuffer* buffer: AudioSampleBuffer 객체는 여러채널 오디오의 버퍼 데이터인데 다차원 float 값 배열이다. getNextAudioBlock() 함수가 호출되면 이 버퍼는 아무 오디오 데이터를 지정한 디바이스의 오디오 입력으로부터 포함한다.(오디오 입력을 요청했었을때) GetNextAudioBlock()함수가 반환할때, 반드시 관련된 버퍼 영역을 실행할 오디오와 함께 가득 채워야한다. 

int startSample: 샘플 인덱스인데 getNextAudioBlock() 함수가 읽기/쓰기를 시작해야하는 버퍼다.

int numSamples: 읽어지거나 써져야하는 버퍼안의 샘플 수다.


오디오 데이터는 부동소수점처럼 저장되는데 매우 알맞다. 각각의 오디오 단일 샘플은 값이 보통 오차범위 +-1로 저장된다.



AudioSampleBuffer 클래스

AudioSampleBuffer 클래스가 기본적인 수준에서 실수값의 다중 채널 배열이지만, 오디오 데이터를 다루기위한 유용한 기능들을 제공한다. 

-AudioSampleBuffer::getNumChannels(): 버퍼내 오디오 채널의 수를 반환한다. 이 경우 값은 AudioAppCOmponent::setAudioChannels() 함수를 호출할때 요청한 출력 채널의 개수와 일치해야한다.  (이 값은 항상 입출력 채널 수의 최대값이다.)

-AudioSampleBuffer::getWritePointer(): 지정한 샘플 오프셋의 부동소수점의 버퍼 포인터를 반환한다.


데모 프로젝트는 간단한 백색 소음을 생성한다. 버퍼의 요구된 섹션을 랜덤값으로 채울 필요가 있다. 이렇게 하려면 버퍼의 채널을 반복하고 해당 채널의 버퍼에서 시작 샘플을 찾고 원하는 샘플 수를 버퍼에 쓴다.

void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
{
for (int channel = 0; channel < bufferToFill.buffer->getNumChannels(); ++channel)
{
// Get a pointer to the start sample in the buffer for this audio output channel
float* const buffer = bufferToFill.buffer->getWritePointer (channel, bufferToFill.startSample);
// Fill the required number of samples with noise betweem -0.125 and +0.125
for (int sample = 0; sample < bufferToFill.numSamples; ++sample)
buffer[sample] = random.nextFloat() * 0.25f - 0.125f;
}
}

//Random 클래스 튜토리얼을 한번 보는걸 추천한다.


오디오 응용프로그램 끄기

소멸자에 AudioAppComponent::shutdownAudio()함수를 추가해주자

~MainContentComponent()
{
shutdownAudio();
}

위 함수가 호출된후 AudioAppComponent::releaseResources() 함수는 호출된다. 이것은 리소스를 처분하기위해 좋다. 만약 오디오 프로세스가 실행되는동안 할당되었으면. 이 경우 다른 리소스를 추가할 필요가 없고 그저 메시지를 남기기위해 함수를 호출해주자.

void releaseResources() override
{
Logger::getCurrentLogger()->writeToLog ("Releasing audio resources");
}

연습하기: 오디오 출력의 수를 바꿔보시오. 주의할점은 mono 잡음 소리는 stereo 소음과 다르다. 만약 여러 채널의 사운드 카드를 갖고있으면 소음 채널을 두개 이상 만들 수 있다. 또한 생성된 소음 레벨을 바꿀 수 있다. 

반응형

'Programming > JUCE' 카테고리의 다른 글

JUCE 노트  (0) 2018.07.09
[JUCE]JUCE의 라이선스  (0) 2018.04.18
[JUCE]키 관련 함수  (0) 2018.02.25
[JUCE]튜토리얼9 The Lable class  (0) 2018.02.19
[JUCE]튜토리얼8 Slider values  (0) 2018.02.18
Comments