Desktop App (Windows / MacOS)

Desktop App (Windows / MacOS)

Desktop App (Windows / MacOS)

Sound Search Application

Sound Search Application

Sound Search Application

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

// Example of dynamic library entry point that includes REAPER and JUCE.
REAPER_PLUGIN_HINSTANCE g_hReaperInstance = nullptr;
bool g_isJuceInitialized = false;

extern "C"
{
	REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(REAPER_PLUGIN_HINSTANCE hInstance, reaper_plugin_info_t* rec) 
	{
		if (rec != nullptr)
		{
#ifdef _OS_PC
			Process::setCurrentModuleInstanceHandle(hInstance);
#endif
			if (REAPERAPI_LoadAPI(rec->GetFunc) > 0)
			{
				return 0;
			}

			if (rec->caller_version != REAPER_PLUGIN_VERSION)
			{
				return 0;
			}

			// Init JUCE API
			if (g_isJuceInitialized == false)
			{
				initialiseJuce_GUI();
				g_isJuceInitialized = true;
			}

			// Register function pointers
			ReaperFunctionRegistry::RegisterReaperFunctionPointers(rec);

			// Setting global vars
			g_hReaperInstance = hInstance;
			g_hWindowParent = rec->hwnd_main;

			return 1; // plugin are registered, return success
		}
		else
		{
			// On REAPER shutdown
			AppStateManager::shutdown();
			if (g_isJuceInitialized == true)
			{
				g_isJuceInitialized = false;
				ReaperFunctionRegistry::shutdown();
				shutdownJuce_GUI();
			}
			return 0;
		}
	}
}
// Example of dynamic library entry point that includes REAPER and JUCE.
REAPER_PLUGIN_HINSTANCE g_hReaperInstance = nullptr;
bool g_isJuceInitialized = false;

extern "C"
{
	REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(REAPER_PLUGIN_HINSTANCE hInstance, reaper_plugin_info_t* rec) 
	{
		if (rec != nullptr)
		{
#ifdef _OS_PC
			Process::setCurrentModuleInstanceHandle(hInstance);
#endif
			if (REAPERAPI_LoadAPI(rec->GetFunc) > 0)
			{
				return 0;
			}

			if (rec->caller_version != REAPER_PLUGIN_VERSION)
			{
				return 0;
			}

			// Init JUCE API
			if (g_isJuceInitialized == false)
			{
				initialiseJuce_GUI();
				g_isJuceInitialized = true;
			}

			// Register function pointers
			ReaperFunctionRegistry::RegisterReaperFunctionPointers(rec);

			// Setting global vars
			g_hReaperInstance = hInstance;
			g_hWindowParent = rec->hwnd_main;

			return 1; // plugin are registered, return success
		}
		else
		{
			// On REAPER shutdown
			AppStateManager::shutdown();
			if (g_isJuceInitialized == true)
			{
				g_isJuceInitialized = false;
				ReaperFunctionRegistry::shutdown();
				shutdownJuce_GUI();
			}
			return 0;
		}
	}
}

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.