쿼츠 개요

샘플링 정밀도가 높은 스케줄링 기능을 제공하는 음악 시스템을 살펴봅니다.

Choose your operating system:

Windows

macOS

Linux

오디오 렌더링의 정밀한 타이밍 및 지연시간 문제

오디오 엔진에서는 CPU 성능을 고려하여 오디오 샘플이 버퍼 에서 렌더링되고 디지털 오디오 컨버터(DAC) 같은 출력 하드웨어로 개별적으로 전송됩니다. CPU 캐시 일관성, 하드웨어 API 오버헤드 등의 여러 가지 이유로 인해 이 방법을 통해서만 CPU에서 실시간으로 오디오 렌더링을 구현할 수 있습니다.

이러한 버퍼는 일반적으로 수백 또는 심지어 수천 개의 샘플을 동시에 포함할 수 있습니다. 오디오 엔진 렌더링 명령은 보통 오디오 버퍼 렌더를 시작할 때 사용된다는 것을 이해하는 것이 중요합니다. 예를 들어 이러한 명령은 새로운 사운드를 재생하거나, 오래된 사운드의 재생을 중지하거나, 볼륨 또는 피치 같은 사운드의 파라미터를 변경하는 것일 수 있습니다.

이러한 이유로 인해 렌더링된 오디오 버퍼의 크기는 새로운 명령이 사용되는 속도에 직접적인 영향을 미칩니다. 이 속도는 실행된 모든 명령의 탐지된 가청 지연시간에도 영향을 줍니다. 예를 들어 동일한 게임 스레드 틱에서 엄청난 양의 VFX와 사운드를 트리거한 경우 이러한 VFX를 보고 사운드를 듣는 사이의 지연시간은 이 버퍼 크기에 의해 결정됩니다.

즉, 초당 48k(kHz) 샘플로 렌더링된 2,048개의 샘플을 포함하는 버퍼의 경우 최대 43ms의 가청 지연시간이 발생할 수 있습니다.

렌더 버퍼 크기로 인한 오디오 렌더러의 고유한 지연시간 외에도 게임 스레드에서 실행된 오디오 명령은 게임 스레드 및 오디오 스레드 틱, 그리고 일반 스레드 통신 오버헤드 때문에 오디오 엔진까지 도달하는 데 시간이 걸립니다. 스레딩 또는 게임플레이 지연시간이 13ms(용인 가능한 수치)이고 48kHz에서 2,048개의 샘플에 대해 동일한 버퍼 크기를 사용한 경우, 그리고 명령이 렌더링된 현재 버퍼의 시작 부분을 놓친 경우에는 56ms의 지연시간이 누적되는 최악의 사례가 발생할 수 있습니다.

오디오 렌더러 지연시간

오디오 요청이 버퍼가 렌더링(1) 을 시작한 이후에 들어오는 경우 해당 요청은 다음 버퍼 (3) 가 시작될 때까지 재생(2) 되지 않고 이월됩니다.

게임 스레드 틱은 최소한 오디오 렌더러의 관점에서는 매우 가변적이며 가비지 컬렉션, 에셋 로딩 등을 진행하는 동안에는 언제든지 멈춤 현상이 발생할 수 있어 문제가 더 복잡해집니다.

게임 스레드 틱은 오디오 렌더링 및 오디오 타이밍뿐만 아니라 게임플레이 및 그래픽 렌더링을 위해서도 시간이 지정됩니다. 정밀히 다른 사운드가 종료되거나 시작될 때와 같이 정밀한 순간에 사운드를 재생해야 하는 경우 게임 스레드와 오디오 렌더러 스레드 이벤트 타이밍이 분리되어 있기 때문에 게임 스레드에서 명령을 호출하면 제대로 작동하지 않습니다.

변수 지연시간오디오 이벤트의 게임 스레드 종속 타이밍 에 대한 문제는 대부분의 오디오 애플리케이션에서는 중요하지 않습니다. 게임의 CPU 로드 또는 특정 플랫폼의 제한에 따라 다르긴 하지만 대부분의 타이밍 문제는 인지 가능한 한계치 미만의 크기로 미리 렌더링된 버퍼 크기와 버퍼 개수를 미세 조정할 수 있습니다.

하지만 인터랙션이 필요한 음악, 정밀한 시간에 반복되는 무기 발사 또는 기타 리드미컬한 독립적인 오디오와 같은 일부 오디오 애플리케이션의 경우에는 이러한 문제점들이 매우 중요합니다.

쿼츠 및 샘플 정밀도

샘플 정밀도(Sample accuracy) 는 사운드가 오디오 렌더러에서 버퍼 바운더리에서가 아니라 버퍼의 중간에서와 같이 임의 샘플에서 시작할 수 있게 해줍니다.

쿼츠(Quartz) 는 모든 사운드 샘플을 정밀하게 재생할 수 있는 방법을 제공함으로써 변수 지연시간 및 게임 스레드 타이밍의 호환성 문제를 해결해주는 시스템입니다. 즉, 샘플 정밀도는 버퍼의 시작 부분이 아닌 오디오 버퍼 내의 임의의 샘플(특정 시점)에서 사운드가 오디오를 렌더링할 수 있게 해줍니다.

쿼츠로 사운드 렌더링

쿼츠는 다음 버퍼가 시작될 때까지 오디오 렌더를 딜레이하지 않고 버퍼 중간에서 오디오 호출을 스케줄링하는 방식을 제공합니다.

쿼츠는 오디오 버퍼를 시작할 때 사운드를 렌더링하는 대신 사운드가 버퍼 크기, 게임 스레드 타이밍 또는 변수 지연시간에 대한 기타 소스와 관계없이 원하는 음악적 값(마디 또는 비트) 또는 시간 값(초)에서 사운드를 재생할 수 있도록 합니다.

여기에는 동적인 음악 시스템부터 기관단총 소리와 같은 리드미컬하고 타이밍에 의존하는 사운드의 재생을 제어하는 것에 이르기까지 많은 애플리케이션이 포함됩니다.

쿼츠 작동 원리

쿼츠를 사용하면 오디오 렌더의 특정 샘플에서 재생 명령을 실행하는 PlayQuantized()로 사운드를 스케줄링하여 오디오 엔진에 미리 알려줄 수 있습니다. 즉, 미리 스케줄링하여 딜레이를 처리함으로써 큐에 등록된 사운드가 딜레이 없이 재생되도록 정밀하게 계산된 샘플에서 사운드를 렌더링할 수 있습니다.

버퍼 크기는 오디오 버퍼 콜백 크기 또는 줄여서 콜백 크기 라고도 합니다.

쿼츠 작동 원리

A 는 게임 프레임 틱을 나타내는 X축 바운더리로서 게임 스레드를 나타내고, B 는 렌더링된 오디오 버퍼를 나타내는 X축 바운더리로서 오디오 렌더 스레드를 나타냅니다. 각각의 X축은 현실 세계의 시간에 수직으로 정렬됩니다. (1) 게임 스레드가 PlayQuantized()를 호출하면 오디오 렌더 스레드에서 가장 가까운 오디오 출력 샘플을 계산한 다음 4분음표와 같은 지정된 양자화 바운더리에서 사운드가 재생되도록 요청합니다. (2) 쿼츠는 일반적인 `양자화된 명령`의 형태로 이 요청을 실행하고 미래의 특정 시점에 오디오 렌더링을 수행할 사운드를 큐에 등록합니다. 이 요청은 계산된 양에 따라 오디오 렌더 스레드(**3**) 의 여러 오디오 버퍼를 통해 유지됩니다. 계산된 버퍼에서 사운드를 렌더링할 시간이 되면 쿼츠는 인티저 딜레이를 통해 오디오를 피딩합니다. 인티저 딜레이는 버퍼(**4**) 의 시작 부분에서 렌더링되는 것을 방지하고 대신 4분음표 (**5**) 에서 정확하게 재생해야 하는 샘플에서 일정 정도를 출력 버퍼로 렌더링합니다.

쿼츠는 블루프린트 를 사용해서도 작업할 수 있습니다. 블루프린트는 이러한 양자화된 명령과 타이밍 이벤트를 적용할 수 있는 방법을 제공하며, 이는 오디오 믹서에서 발생한 양자화된 액션과 동기화하여 게임플레이 로직을 트리거해줍니다.

쿼츠의 주요 컴포넌트

쿼츠 클럭

클럭(clock)은 오디오 렌더링 스레드에서 이벤트에 대한 스케줄링과 시작을 담당하는 오브젝트입니다. 클럭은 쿼츠 서브시스템 을 통해 생성되며, 클럭 핸들 을 사용하여 블루프린트를 통해 수정됩니다. 각각의 클럭은 쿼츠 메트로놈(Quartz Metronome) 을 가집니다.

쿼츠 메트로놈

메트로놈 은 시간 경과를 추적하는 오디오 렌더 스레드 오브젝트로, 분당 비트 수인 BPM 및 타임 시그니처와 같은 사용자 제공 정보에서 앞으로 명령을 실행해야 하는 시점을 결정합니다.

게임플레이 로직은 음악적 지속 시간이 발생할 때 알림을 받기 위해 메트로놈에서 이벤트를 구독할 수 있습니다.

쿼츠 클럭 핸들

클럭 핸들(Clock Handle)활성 클럭 의 게임플레이 측 프록시입니다. 이는 쿼츠 서브시스템 을 통해 얻을 수 있으며, 오디오 믹서에서 실행되는 클럭을 제어하는 데 사용됩니다.

Quartz 서브시스템

클럭 핸들이 특정 클럭의 기능에 대한 액세스를 제공하는 반면에 쿼츠 서브시스템은 특정 클럭에 관련되지 않은 기능에 대한 액세스를 제공합니다. 여기에는 클럭 생성 및 구하기, 클럭의 존재 여부 확인, 지연시간 정보 쿼리가 포함됩니다.

오디오 컴포넌트: PlayQuantized()

이는 오디오 컴포넌트에 추가된 새로운 기능으로, 특정 양자화 바운더리(Quantization Boundary) 를 사용하여 특정 클럭에서 사운드를 재생할 수 있는 방법을 제공합니다.

쿼츠 클럭 생성 및 초기화

다음은 샘플 워크플로입니다.

  1. 클럭을 생성하려면 쿼츠 서브시스템 에서 새 클럭 생성(Create New Clock) 을 호출합니다.

  2. 클럭 이름(Clock Name)타임 시그니처(Time Signature) 를 제공합니다.

  3. 블루프린트 변수로 반환 값을 저장합니다. (동일한 이름의 클럭이 이미 있는 경우 기존 클럭으로 핸들이 반환됩니다.)

  4. 클럭 핸들(Clock Handle) 에서 다음 함수 중 하나를 호출하여 클럭에 대한 틱 속도(Tick Rate) 를 설정합니다.

    • 분당 비트(Beats per Minute) (아래 참고)

    • 틱당 밀리초(Milliseconds per Tick)

    • 초당 틱(Ticks per Second)

    • 분당 32분음표(Thirty-second Notes per Minute)

4단계의 호출은 기술적으로 교체 가능하며, 베리에이션은 편의를 위한 것이기는 하지만 각각에 해당하는 getter 는 주목할만한 가치가 있습니다.

양자화 바운더리에서의 사운드 재생

양자화 바운더리에서 사운드를 재생하려면 오디오 컴포넌트 에서 양자화 재생(Play Quantized)를 호출하고 클럭 핸들 및 양자화 바운더리를 제공해야 합니다.

쿼츠 양자화 바운더리

쿼츠 양자화 바운더리(Quartz Quantization Boundary) 를 사용하면 사운드를 시작하려는 시점을 오디오 믹서에 정확하게 알려줄 수 있습니다. 이는 몇 가지 기타 스케줄링 관련 쿼츠 함수에서 사용됩니다. 이 구조체에는 몇 가지 옵션이 있습니다.

양자화(Quantization) 는 스케줄링에 사용되는 시간 값입니다. 사용할 수 있는 지속 시간은 다음과 같습니다.

지속 시간

설명

마디(Bar)

타임 시그니처로 자동으로 결정합니다.

비트(Beat)

타임 시그니처의 분모가 디폴트이지만 오버라이드할 수 있습니다.

1/32

32분음표는 사용할 수 있는 가장 작은 지속 시간입니다.

1/16, (점음표) 1/16, 1/16(셋잇단음표)

16분음표, 점 16분음표 및 16분음표 3연음

1/8, (점음표) 1/8, 1/8(셋잇단음표)

8분음표, 점 8분음표 및 8분음표 3연음

1/4, (점음표) 1/4, 1/4(셋잇단음표)

4분음표, 점 4분음표 및 4분음표 3연음

2분음표, (점음표) 2분음표, 2분음표(3연음)

2분음표, 점 2분음표 및 2분음표 3연음

온음표, (점음표) 온음표, 온음표(3연음)

온음표, 점 온음표 및 온음표 3연음 온음표는 사용할 수 있는 가장 긴 지속 시간입니다.

틱(Tick)

틱은 32분음표와 동일한 가장 작은 값입니다.

배수(Multiplier) 는 사운드를 플레이해야 할 때까지의 양자화 시간 값의 수입니다. 선택된 양자화 시간 값에 대한 배수입니다.

카운팅 레퍼런스 포인트(Counting Reference Point) 는 이벤트 스케줄링에 사용되는 레퍼런스 포인트입니다. 다양한 사용 사례에서는 다양한 레퍼런스 포인트를 사용합니다.

  • 마디 상대(Bar Relative) - 현재 마디의 시작 부분 기준. 드럼 머신을 예로 들면 다음과 같습니다. *양자화를 **비트** 로, 배수를 **2.0** 으로 설정하면 발생하는 **비트 2** (측정에서 두 번째 비트)에 대한 이벤트를 스케줄링합니다.

    • 클럭이 해당 마디에 대해 4비트비트 1 에 설정되면 해당 이벤트는 다음 비트에서 트리거됩니다.

    • 클럭이 비트 3/4 로 설정되면 해당 이벤트는 다음 마디의 비트 2에서 트리거됩니다.

  • 전송 상대(Transport Relative) - 클럭이 시작된 시간 또는 전송이 마지막으로 리셋된 시간 기준. 4마디 바운더리에 음악 스탬을 트리거하는 경우를 예를 들면 다음과 같습니다.

    • 전송(송 카운터)와 관련된 트리거 클립/스탬에 사용됩니다.

    • 클럭이 마디 6에 있는 경우 해당 이벤트는 마디 9(전송 카운팅이 마디 1부터 시작)에서 트리거됩니다.

  • 현재 시간 상대(Current Time Relative) - 현재 시간 기준. 예를 들어 음악적 조화를 이루고 바로 스팅어를 트리거하기 위해 다음 작업이 수행됩니다.

    • 비트 및 배수가 2.0으로 설정된 양자화는 다음 마디 이후 다운비트에 대해 이벤트를 스케줄링합니다.

    • 클럭이 비트 1/4인 경우 해당 이벤트는 비트 3에서 트리거됩니다.

    • 클럭이 비트 3/4인 경우 해당 이벤트는 다음 마디의 비트 1에서 트리거됩니다.

쿼츠에서 마디는 항상 온음표가 아니며 비트는 항상 4분음표가 아닙니다. 자세한 내용은 아래의 타임 시그니처 및 펄스 오버라이드를 참고하세요.

명령 이벤트 구독

게임플레이 로직을 양자화된 이벤트(재생하도록 스케줄링한 사운드 시작 등)와 동기화하려는 경우 특정 명령의 수명 주기에 포함된 다양한 단계에서 알림을 받을 수 있습니다.

이렇게 하려면 다음과 같은 유형의 함수에 대한 In Delegate 인수의 핀을 드래그하여 커스텀 이벤트 를 생성합니다.

명령 이벤트 구독

커스텀 이벤트는 다양한 이유로 여러 번 호출될 수 있습니다. 예를 들어 위의 그림에서 이벤트 유형 `EQuantizationCommandDelegateSubType`을 열거하여 다음과 같은 이벤트에 응답할 수 있습니다.

이벤트 유형

설명

큐 등록 실패(Failed To Queue)

명령이 오디오 렌더 스레드에 실패했습니다(양자화 재생이 동시에 실패했을 가능성이 있음).

큐 등록됨(Queued)

오디오 렌더 스레드에서 명령을 수신하여 실행을 대기하고 있습니다(동시성 통과).

취소됨(Canceled)

명령이 실행되기 전에 오디오 렌더 스레드에서 취소되었습니다(재생을 시작하기 전에 오디오 컴포넌트에서 중지가 호출되었을 수 있음).

시작 예정(About to Start)

명령이 실행되려고 합니다(현재 시작한 것과 동일하게 게임플레이 프레임에서 호출이 되지만, 향후에는 스레드 지연시간을 상쇄할 수 있을 만큼 충분히 일찍 호출되어야 함).

시작됨(Started)

오디오 렌더 스레드에서 명령이 실행되었습니다.

메트로놈 이벤트 구독

게임플레이를 오디오 렌더링과 동기화하기 위해 양자화된 명령(Quantized Command) 수명 주기의 다양한 단계를 구독하는 것뿐 아니라 양자화 지속 시간(Quantization Duration) 이 발생할 때마다 알림을 받을 수도 있습니다.

아래 예시에서는 마디 알림을 구독합니다. 이벤트가 발생할 때마다 사운드를 스케줄링하여 다음 마디에서 사운드를 재생합니다.

양자화 이벤트 구독

단일 이벤트에서 모든 양자화 지속 시간에 액세스하려는 경우 한 번에 모든 양자화 이벤트(Quantization Events) 를 구독하고 양자화 유형(Quantization Type) 열거형에서 전환할 수 있습니다.

모든 양자화 이벤트 구독

타임 시그니처 및 펄스 오버라이드

쿼츠는 타임 시그니처와 박자를 세밀하게 제어할 수 있습니다. 위의 쿼츠 클럭 생성 및 초기화CreateandInitializeQuartzClocks 섹션에 나와 있듯이 클럭을 생성하는 경우 타임 시그니처 를 제공해야 합니다.

타임 시그니처 제공

타임 시그니처에는 세 가지 필드가 있습니다. 처음 두 필드는 일반적인 타임 시그니처를 나타냅니다.

  • 비트 수: '비트 유형(Beat Type)'에 지정된 유형의 측정당 비트 수 또는 타임 시그니처의 분자 를 나타냅니다.

  • 비트 유형: 비트가 발생하는 지속 시간을 나타내는 열거형 또는 타임 시그니처의 분모 를 나타냅니다. 사용 가능한 값은 다음과 같습니다.

    열거형

    /2

    2분음표

    /4

    4분음표

    /8

    8분음표

    /16

    16분음표

    /32

    32분음표

  • 선택적 펄스 오버라이드(Optional Pulse Override) 는 세 번째 필드로, 마디 전체에 걸쳐 비트가 발생하는 지속 시간을 지정하는 방법을 제공합니다. 선택적 펄스 오버라이드가 제공되지 않은 경우 타임 시그니처의 비트 유형 이 디폴트입니다.

마디 양자화 바운더리(Bar Quantization Boundary) 는 타임 시그니처에 의해 직관적으로 제어됩니다. 4/4 박자에서는 한 마디에 4분음표가, 12/8 박자에서는 한 마디에 12/8음표가 유지됩니다.

펄스 오버라이드 인수는 사실상 펄스 오버라이드의 배열입니다. 각 항목을 통해 배열에서 다음 항목으로 이동하기 전에 지정된 지속 시간(Duration) 에서 원하는 펄스 수(Number of Pulses) 를 지정할 수 있습니다. 자세한 내용은 아래의 예시를 참고하세요.

타임 시그니처는 7/8입니다. 선택적 펄스 오버라이드를 제공하지 않은 경우 비트 양자화 바운더리(Beat Quantization Boundary)는 8분음표 양자화 바운더리와 동일합니다.

하지만 쿼츠를 사용하면 비트 또는 펄스를 정확하게 지정할 수 있습니다. 이 예시에서는 '디테일(Detail)' 패널의 '디폴트 값(Default Value)' 아래에 표시된 배열에 두 개의 항목이 있습니다.

선택적 펄스 오버라이드 값

0 값 의 경우:

  • 펄스 수: 2

  • 펄스 지속 시간: 1/32

이것은 처음 2비트가 32노트의 시간을 갖는다는 것을 의미합니다.

1 값 의 경우:

  • 펄스 수: 1

  • 펄스 지속 시간: (점음표) 1/4

이것은 마지막 비트가 점음표 4분음표의 지속 시간을 갖는다는 것을 의미합니다.

굵은 글꼴의 각 1 이 8분음표인 (1, 2) (1, 2) (1, 2, 3)의 카운트로 진행합니다. 블루프린트가 비트 양자화 바운더리를 구독하는 경우 이는 해당 이벤트가 발동되는 시점을 나타냅니다.

주변 항목을 전환하는 경우 카운트는 (1, 2, 3) (1, 2) (1, 2)가 됩니다.

해당 값이 마디보다 짧은 지속 시간을 제공하는 경우 적절한 길이를 맞추기 위해 마지막 항목이 복제됩니다.

제공된 값이 마디보다 긴 지속 시간을 나타내는 경우 해당 목록은 짧게 줄여지고 카운팅이 다음 마디의 다운비트상의 목록 첫 부분에서부터 다시 시작됩니다.