The Brief
Construct is a sound search engine designed to play audio directly within REAPER. It features a user-friendly interface with customizable keyboard shortcuts similar to those found in commercial software. Construct's main strengths include automating import and processing of sounds, applying effects, and rendering, all with a single key stroke. Its full-text search functionality is fast, even with over 2 million records. The folder tree eliminates the need for sound designers to create multiple databases in other software, streamlining the process of filtering for specific libraries.
Code Examples
The Process
Design
The UI was initially sketched out on paper, then later developed into a mock-up in Figma. The color scheme was selected to align with PlayStation Studios' internal tooling.
Development
I chose a tech stack that would enable me to successfully complete the project. Since the REAPER API and JUCE were both in C++ and essential, using C++ was an obvious choice. This was especially true for communicating with REAPER, which required creating a dynamic linked library. Regarding the database, other tools in the studio were already using SQLite. Its speed, even with millions of records, and simplicity in implementation made it a fitting choice.
Learnings
The most challenging task was enabling audio playback within REAPER. With no direct API support from REAPER, the alternative was to route sounds through a VST plugin. However, establishing a bidirectional, non-blocking communication between a dynamic link library and a VST plugin posed a significant challenge. This included sending numerous messages encompassing pitch, volume, filename, channels, and more.
The solution, was to use named pipe. The idea of using pipe/socket to send messages to the VST and having the VST handle audio playback felt unorthodox. My primary concern was avoiding any latency during audio file previews. Initially, I attempted to transmit audio buffers over the pipe and have the VST read the buffer and play it back, but this resulted in silence due to the inability to guarantee consistent buffer at regular intervals. I considered implementing shared memory using the Boost library, but it seemed overly complex and possibly not worth the effort. Ultimately, I decided to stick with transmitting control data over pipe/socket, which proved effective. The latency was less significant than I anticipated, making it a successful solution in the end.