이 글은 2부작으로 제작된 블로그 시리즈의 제 2부입니다. 제 1부에서는 ReaWwise의 사전 제작에 대해 알아보았고, 제 2부에서는 이 확장의 개발에 대해 알아보게 됩니다.
거의 최종에 가까운 사용자 인터페이스 디자인과 WAAPI-Transfer 코드 베이스에 대한 가정을 검증하기 위한 여러 반복 작업을 걸친 후 이제 프로젝트의 기술적인 기반에 대한 몇 가지 결정을 내려야 할 때가 왔습니다.
프로그래밍 언어
REAPER는 스크립트 제작을 위한 여러 프로그래밍 언어를 지원합니다 (Lua, EEL, Python, C++). 다른 언어를 제치고 C++가 선택된 이유는 여러가지가 있습니다.
- Audiokinetic 팀이 제공하는 C++ 전문성
- 기존 코드 재사용
- WAAPI C++ 클라이언트
- REAPER의 Lua처럼 REAPER와 결합된 언어에 의존하지 않음
- Python 스크립트의 인터프리터같이 런타임 종속성에 의존하지 않음
- WAAPI-Transfer와 같은 기타 오픈 소스 프로젝트의 재사용 잠재성
- GUI에 대해 있을 수 있는 기타 ReaScript에 대한 의존성이 없음
프레임워크
사용할 언어를 설정한 후 이제 프로젝트에 어떤 프레임워크가 가장 적절한지를 살펴봐야 했습니다. 이를 결정하기 위해 다음 기준을 사용했습니다.
- 크로스 플랫폼이어야 함 (Windows와 Mac)
- 탄탄한 커뮤니티 지원이 있어야 함
- 테마 설정과 커스터마이징이 완전히 가능해야 함
- 현대 프로그래밍 패턴 및 패러다임을 사용해야 함
- 회사의 코딩 표준과 잘 맞아야 함
WDL/Native
C++ 기반 REAPER 확장에는 이 방법을 가장 많이 사용하는 것 같습니다. OS에서 호스트하는 창의 맥락 내에서 사용자 인터페이스를 제작할 수 있죠. 또한 Mac에서 Win32 API의 부분 집합을 사용할 수 있게 해주는 SWELL도 제공합니다. 이는 크로스 플랫폼 호환성에서 중요합니다.
개발 초기 단계 동안 저희는 WAAPI-Transfer 코드 베이스를 사용하여 특정 아이디어를 검토했습니다. 그리고 SWELL에서 지원하지 않는 Win32 API 사용이 꽤 많다는 것을 깨달았죠. 이는 교차 플랫폼 호환성에서 몇 가지 문제를 일으켰고 특정 그래픽 컴포넌트를 사용할 수 없음을 의미했습니다.
SWELL의 경우 Mac에서 네이티브 컴포넌트를 사용할 시 Mac과 Windows에서의 모습과 경험이 완전히 동일하지 않음을 의미했죠.
Win32 API를 사용할 경우 또 다른 단점은 어렵기로 소문난 API입니다. C 언어로 제작되었고 수년간 존재해온 API이죠. 또한 이전 버전과의 호환성을 유지해야 한다는 요구 사항은 많은 레거시 아티팩트가 여전히 많이 남아있다는 것을 의미합니다.
JUCE
JUCE는 오디오 관련 애플리케이션과 플러그인을 생성하는 데 흔히 사용되는 크로스 플랫폼 C++ 프레임워크입니다. 현대적이며 안정적인 API를 가지고 있으며, 커스터마이징 가능성이 뛰어나고, 커뮤니티 지원도 탄탄합니다. 또한 CMake에 대한 네이티브 지원도 있습니다. 이에 대해서는 글의 후반부에서 더 다루도록 하겠습니다.
저는 오픈 소스의 JUCE 기반 REAPER 확장을 아직 본 적이 없습니다. 하지만 REAPER 확장에서의 JUCE 구현이 충분히 가능하며 꽤나 간단한 일이라는 것을 알려주는 코드 몇 조각을 발견했죠!
WAAPI-Transfer 코드 베이스에서 어느 정도 작업을 한 후 새로운 UX가 꽤나 달랐기 때문에 대부분의 UI를 다시 제작해야 한다는 것이 분명해졌습니다. JUCE 컴포넌트의 도면을 쉽게 접근하고 재정의할 수 있기 때문에 바닐라 JUCE 컴포넌트를 계획한 디자인 그대로 정확하게 보이도록 만드는 것이 쉬웠습니다.
이벤트 처리 시스템도 Win32 이벤트 처리 시스템을 직접 사용하는 것보다 훨씬 더 직관적입니다. 이벤트가 컴포넌트 자체 안에서 관리되죠. 창의 메시지 반복 재생 복잡성이 JUCE에 의해 추상화되기 때문에 따로 관리할 필요가 없습니다.
JUCE의 또 다른 장점은 오브젝트 지향 프로그래밍 자체가 GUI 애플리케이션 개발에 적합하다는 것입니다. 서로 다른 GUI 컴포넌트를 오브젝트로서 쉽게 표시할 수 있고 각 오브젝트가 함수의 형태로서 기능을 보여줄 수 있죠. Win32 API의 경우 이 부분이 자연스럽게 실행되지 않습니다. 컴포넌트는 구조체로 정의됩니다. JUCE를 사용할 경우와 동일한 방식으로 코드를 구성하려면 더 많은 주의가 필요합니다.
프로젝트 구성 및 구조
프로젝트 구조를 살펴보기 전에 먼저 CMake를 소개해드릴게요.
CMake
ReaWwise 프로젝트는 빌드 파일을 담고 있지 않습니다. 대신 CMake를 사용합니다. CMake는 프로젝트가 어떻게 빌드되어야 하는지를 일반적인 방식으로 설명할 수 있게 해줍니다. 그 후 CMake를 사용하여 플랫폼 전용 빌드 파일을 생성할 수 있습니다.
CMake를 사용하면 몇 가지 장점이 있습니다.
- 각 빌드 환경마다 서로 다른 빌드 파일을 관리할 필요가 없음
- 시스템 라이브러리를 찾는 도우미 함수가 있음
- 광범위한 시스템의 빌드 파일을 생성할 수 있음
- 파일 저작 및 쉘 명령 실행 등 추가 업무를 실행할 수 있음.
프로젝트 구조
처음부터 걱정이 될 수 있는 것들을 최대한 분리하는 것이 의도였습니다. 그래서 코드 베이스를 다음 네 개의 메인 프로젝트로 구성했죠.
ReaWwise (src/extension)
이 프로젝트는 모든 REAPER 전용 코드를 담고 있습니다. 이는 확장 엔트리 포인트를 정의하고 확장 API에 필요한 모든 호출을 수행합니다. 또한 다른 확장에 사용할 수 있도록 WAAPI를 Lua에 노출시키는 코드를 담습니다. 이 프로젝트는 공유 라이브러리로서 컴파일되며 REAPER 안으로 가져오는 실제 확장 파일(reaper_reawwise.dll)을 생성합니다.
WwiseTransfer_Standalone (src/standalone)
이 포르젝트는 ReaWwise를 독립형 애플리케이션으로 시작하는 코드를 담고 있습니다. 주로 GUI 개발 및 시험에 사용됩니다. REAPER와는 연결되지 않습니다. REAPER에서 예상되는 데이터는 모형입니다.
WwiseTransfer_Shared (src/shared)
이 프로젝트는 애플리케이션의 모든 코어 컴포넌트와 GUI 요소를 담고 있습니다. 이 코드는 정적 라이브러리로 컴파일되며 확장 및 독립형 코드에 의해 의존성으로서 사용됩니다.
WwiseTransfer_Test (src/test)
이 프로젝트는 시험에 관한 모든 코드를 담고 있습니다.
의존성
서드 파티
- Catch2: 프레임워크 테스트
- JUCE: 애플리케이션 프레임워크
- rapidjson: WAAPI 클라이언트의 의존성
- reaper-sdk: REAPER API 헤더
- trompeloeil: 모형 프레임워크
마지막 의존성은 AkAutobahn(WAAPI 클라이언트의 C++ 구현)과 Wwise SDK에서의 몇몇 헤더입니다. 이 의존성은 코드 베이스의 일부가 아니며 Audiokinetic 런처를 통해 따로 받을 수 있습니다.
다음 도표는 서로 다른 프로젝트와 의존성이 어떻게 연결되어 있는지를 보여줍니다.
애플리케이션 아키텍처
이 섹션에서는 ReaWwise의 주요 구성 요소를 살펴보고 작동하는 방식을 살펴봅시다.
Preview Panel
Preview Panel을 구동하는 메인 구성 요소는 DawWatcher 오브젝트입니다. DawWatcher 는 주기적으로 DawContext 를 쿼리하여 Preview Panel의 콘텐츠를 업데이트해야 하는지의 여부를 알아냅니다. 또한 미리 보기에 영향을 줄 수 있는 무언가가 변경되었을 경우 Wwise에게 알림을 받습니다. Preview Panel이 업데이트되어야 하는 경우 DawWatcher 는 DawContext 에서 렌더링되어야 하는 아이템과 Wwise에서 오브젝트 정보를 가져옵니다. 그런 다음 모든 내용을 함께 컴파일하여 Preview Panel로 전송될 미리 보기를 생성합니다.
DawContext 는 DAW를 표시하는 인터페이스입니다. DawWatcher와 같이 WwiseTransfer_Shared 안에 있는 코드가 파일 경로, 세션 정보, 세션 상태와 같은 정보를 REAPER에서 얻는 방식이죠. WwiseTransfer_Shared에 있는 코드는 REAPER에 대해 알지 못합니다. DawContext 를 구현하여 또 다른 DAW나 애플리케이션과 인터페이스하도록 할 수도 있습니다.
ReaWwise에서 DawContext 의 구체적인 구현은 ReaperContext입니다. 확장 API로의 모든 호출을 여기에서 볼 수 있습니다.
아래는 미리 보기 패널 업데이트 과정을 보여주는 간소화된 연속적 도표입니다.
Wwise로 전송하기
ReaWwise와 Wwise 간의 전송을 구동하는 주요 컴포넌트는 ImportTask 오브젝트입니다. 이 오브젝트는 WaapiClient 오브젝트를 사용하여 Wwise로의 여러 호출을 실행합니다. 또한 전송에 대한 모든 상세 정보를 추적하며, 이 정보는 전송이 끝날 때 상세 보고를 생성하는 데 사용됩니다. ImportTask 는 ReaWwise의 메시지 스레드를 차단하지 않도록 비동기적으로 실행됩니다.
WaapiClient 는 Wwise와 커뮤니케이션하는 데 사용되는 컴포넌트입니다. 이는 모든 WAAPI 요청과 응답 관리를 추상화하여 AkAutobahn 클라이언트에 대한 레이어 역할을 합니다. WaapiClientWatcher 는 WaapiClient 와 함께 사용되는 컴포넌트이며 WAAPI로의 연결을 모니터링합니다. Wwise와의 여전히 연결되어 있는지 주기적으로 확인합니다.
다음은 가져오기 과정을 묘사하는 간소화된 연속적 도표입니다.
애플리케이션 상태
ReaWwise는 JUCE ValueTree를 사용하여 컴포넌트간에 공유되어야 하는 데이터 및 상태 정보를 저장합니다. 이 데이터 구조체는 트리 모습을 하고 있으며 정수, 문자열, 불리언 등의 수많은 기본 데이터 유형을 저장할 수 있습니다. 커스텀 데이터 유형의 경우 특수 변환 템플릿을 사용할 수 있습니다. 컴포넌트는 자체를 리스너로 등록하면 ValueTree의 데이터의 변경 사항에 대한 알림을 받을 수 있습니다.
ReaWwise에서 GUI 컴포넌트는 ValueTree의 변경 사항을 주시할 수 있으며 이에 맞게 자신을 업데이트할 수 있습니다. 이는 컴포넌트를 서로 완전히 분리할 수 있게 해줍니다. 또한 비즈니스 논리와 표시 논리를 따로 유지할 수 있게 해줍니다. 다음 간소화된 연속적 도표는 ReaWwise의 텍스트 입력란이 해당 콘텐츠의 유효성에 따라 오류 상태를 보여주는 방식을 보여줍니다.
시험
개발 단계 동안 저희는 여러 접근 방식으로 테스트를 해봤습니다. 여전히 프로젝트의 일부로 남아있는 첫 번째 접근 방식은 바로 단위 테스트입니다. 완전히 제거된 두 번째 방식은 종단간(end-to-end) 테스트입니다.
종단간(End-to-End) 테스트
종단간 테스트는 ADC21에서 조엘 노엘(Joel Noel)의 발표에서 영감을 받은 아이디어입니다. 조엘은 JUCE End to end라는 새로운 JUCE용 종단간 테스트 프레임워크를 발표했습니다. 이 도구는 어떤 JUCE 애플리케이션이든 javascript로 제어할 수 있게 해줍니다. 또한 어떤 JUCE 컴포넌트이든 쿼리할 수 있게 해줍니다. 예를 들어 애플리케이션에서 버튼이 클릭되도록 트리거한 후 확인 팝업이 표시되는지를 확인할 수 있죠.
결국 프레임워크는 잘 작동했지만 오토메이션된 방식으로 모든 컴포넌트(Reaper, ReaWwise, Wwise)가 함께 매끄럽게 작동하도록 하는 것이 거의 불가능했습니다.
글을 끝내며
2부로 제작된 이 블로그 시리즈에서는 애플리케이션 개발의 여러 다른 방면을 살펴보았습니다. 제품 개념화부터 사용자 경험 고려 사항 및 인터페이스 디자인까지 저희가 ReaWwise 제작에 적용한 기술 결정 및 소프트웨어 아키텍처 선택에 대해 자세히 살펴봤죠.
ReaWwise의 내부 작동 방식에 대해 더 자세히 알아보려면 GitHub에 제공된 audiokinetic/ReaWwise 코드를 확인하세요.
댓글