### Build libASPL Examples with make Source: https://github.com/gavv/libaspl/blob/main/README.md Command to build the example drivers for the libASPL project. CODESIGN_ID is an optional parameter. ```bash make examples [CODESIGN_ID=...] ``` -------------------------------- ### Install Library Headers and Exported Targets with CMake Source: https://github.com/gavv/libaspl/blob/main/CMakeLists.txt Installs the 'include/aspl' directory to the specified installation prefix and exports targets for CMake package management. This ensures the library and its headers are available for other projects. ```cmake install(DIRECTORY include/aspl DESTINATION ${CMAKE_INSTALL_PREFIX}/include ) install(EXPORT ${PACKAGE_NAME}Targets FILE ${PACKAGE_NAME}Targets.cmake NAMESPACE aspl:: DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/${PACKAGE_NAME} ) ``` -------------------------------- ### Install Package Configuration Files with CMake Source: https://github.com/gavv/libaspl/blob/main/CMakeLists.txt Installs the generated package configuration file and version file to the CMake package installation directory. This makes the package discoverable by CMake's find_package command. ```cmake install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}Config.cmake ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}ConfigVersion.cmake DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/${PACKAGE_NAME} ) ``` -------------------------------- ### Clone and Build libASPL Source: https://github.com/gavv/libaspl/blob/main/README.md These commands clone the libASPL repository from GitHub and build and install the library to the default location (/usr/local). This is a standard procedure for obtaining and setting up a C++ library. ```shell git clone https://github.com/gavv/libASPL.git cd libASPL make sudo make install ``` -------------------------------- ### External Project Configuration for libASPL Source: https://github.com/gavv/libaspl/blob/main/examples/SinewaveDevice/CMakeLists.txt This CMake code uses the `ExternalProject_Add` command to download, configure, build, and install the libASPL library. It specifies source and binary directories, installation prefix, and logging options for each stage. ```cmake # path to libASPL source directory get_filename_component(LIBASPL_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../.. ABSOLUTE) # add rules for building libASPL and installing it into a prefix set(LIBASPL_TARGET SinewaveDevice_libASPL) include(ExternalProject) ExternalProject_Add(${LIBASPL_TARGET} SOURCE_DIR ${LIBASPL_SOURCE_DIR} BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/libASPL-build INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/libASPL-prefix CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= LOG_DOWNLOAD YES LOG_CONFIGURE YES LOG_BUILD YES LOG_INSTALL YES ) ``` -------------------------------- ### Configure and Build libASPL with CMake Source: https://github.com/gavv/libaspl/blob/main/README.md This demonstrates a more customized build process for libASPL using CMake. It allows specifying the build type (Release) and installation path, followed by compilation and installation. This offers greater control over the build and deployment of the library. ```shell mkdir -p build/Release cd build/Release cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/my/install/path ../.. make -j4 make install ``` -------------------------------- ### Initialize Driver with Callback - C++ Source: https://context7.com/gavv/libaspl/llms.txt This C++ snippet shows how to implement and use an initialization callback for a driver. The `InitializationHandler` class inherits from `aspl::DriverRequestHandler` and overrides `OnInitialize` to perform setup tasks like accessing driver storage, loading configurations, and modifying device properties after the driver is fully initialized. It requires the `aspl/DriverRequestHandler.hpp` and `aspl/Driver.hpp` headers. ```cpp #include #include class InitializationHandler : public aspl::DriverRequestHandler { public: InitializationHandler(std::shared_ptr driver) : driver_(driver) { } // Called when HAL completes asynchronous driver initialization OSStatus OnInitialize() override { auto driver = driver_.lock(); if (!driver) { return kAudioHardwareUnspecifiedError; } // Now storage is available and devices are published auto storage = driver->GetStorage(); // Load saved configuration auto [enabled, ok] = storage->ReadBoolean("driver.enabled"); if (ok && !enabled) { // Device was disabled, hide it auto plugin = driver->GetPlugin(); if (plugin->GetDeviceCount() > 0) { auto device = plugin->GetDeviceByIndex(0); device->SetIsHidden(true); } } // Perform other initialization that requires storage auto [lastVersion, versionOk] = storage->ReadString("driver.version"); if (!versionOk) { // First time initialization storage->WriteString("driver.version", "1.0.0"); storage->WriteBoolean("driver.enabled", true); } return kAudioHardwareNoError; } private: std::weak_ptr driver_; }; // Usage in driver creation auto driver = std::make_shared(context, plugin); auto initHandler = std::make_shared(driver); driver->SetDriverHandler(initHandler); ``` -------------------------------- ### Configure Advanced Audio Device with Multiple Streams (C++) Source: https://context7.com/gavv/libaspl/llms.txt This example demonstrates advanced device configuration in C++, setting up separate input and output streams, attaching custom volume and mute controls, and defining available sample rates. It utilizes the ASPL library for device and stream management. ```cpp #include void ConfigureAdvancedDevice(std::shared_ptr device) { // Configure first output stream with custom parameters aspl::StreamParameters outputParams; outputParams.Direction = aspl::Direction::Output; outputParams.StartingChannel = 1; outputParams.Format.mSampleRate = 48000; outputParams.Format.mChannelsPerFrame = 2; outputParams.Latency = 512; auto outputStream = device->AddStreamAsync(outputParams); // Add volume control for output aspl::VolumeControlParameters volumeParams; volumeParams.Scope = kAudioObjectPropertyScopeOutput; volumeParams.MinDecibels = -96.0f; volumeParams.MaxDecibels = 0.0f; auto volumeControl = device->AddVolumeControlAsync(volumeParams); outputStream->AttachVolumeControl(volumeControl); // Add mute control for output auto muteControl = device->AddMuteControlAsync( kAudioObjectPropertyScopeOutput); outputStream->AttachMuteControl(muteControl); // Configure input stream aspl::StreamParameters inputParams; inputParams.Direction = aspl::Direction::Input; inputParams.StartingChannel = 1; inputParams.Format.mSampleRate = 48000; inputParams.Format.mChannelsPerFrame = 1; // Mono input auto inputStream = device->AddStreamAsync(inputParams); // Set device to support multiple sample rates device->SetAvailableSampleRatesAsync({ {44100.0, 44100.0}, // 44.1 kHz {48000.0, 48000.0}, // 48 kHz {96000.0, 96000.0} // 96 kHz }); // Get stream information UInt32 outputStreamCount = device->GetStreamCount(aspl::Direction::Output); auto stream = device->GetStreamByIndex(aspl::Direction::Output, 0); if (stream) { Float64 sampleRate = stream->GetSampleRate(); UInt32 channels = stream->GetChannelCount(); } } ``` -------------------------------- ### Install CMake using Homebrew Source: https://github.com/gavv/libaspl/blob/main/README.md This command installs the CMake build system using the Homebrew package manager. CMake is a prerequisite for building the libASPL library. ```shell brew install cmake ``` -------------------------------- ### CMake: Define and Install Static Library Source: https://github.com/gavv/libaspl/blob/main/CMakeLists.txt This CMake code defines the static library target 'libASPL' using the collected source files. It sets include directories, links against CoreFoundation, specifies output names, and configures installation rules. ```cmake set(LIB_TARGET libASPL) set(LIB_NAME ASPL) set(TEST_NAME aspl-test) add_library(${LIB_TARGET} STATIC ${SOURCE_LIST} ) target_include_directories(${LIB_TARGET} PUBLIC $ $ ) find_library(LIB_CoreFoundation CoreFoundation REQUIRED) target_link_libraries(${LIB_TARGET} PUBLIC ${LIB_CoreFoundation} ) set_target_properties(${LIB_TARGET} PROPERTIES OUTPUT_NAME ${LIB_NAME} ) set_property(TARGET ${LIB_TARGET} PROPERTY VERSION ${PACKAGE_VERSION} ) set_property(TARGET ${LIB_TARGET} APPEND PROPERTY COMPATIBLE_INTERFACE_STRING ${PACKAGE_VERSION} ) install(TARGETS ${LIB_TARGET} EXPORT ${PACKAGE_NAME}Targets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) ``` -------------------------------- ### Install sox Audio Tool using Homebrew Source: https://github.com/gavv/libaspl/blob/main/README.md Command to install the sox audio processing tool using the Homebrew package manager. This tool is used for recording and manipulating audio files. ```bash brew install sox ``` -------------------------------- ### Driver Initialization Callback Handler in C++ Source: https://github.com/gavv/libaspl/blob/main/README.md Provides a handler for driver initialization callbacks using the DriverRequestHandler. The OnInitialize method is invoked by the HAL after asynchronous driver initialization, allowing for setup that requires a functioning driver. Dependencies include the aspl library. ```cpp class MyHandler : public aspl::DriverRequestHandler { public: // Invoked when HAL performs asynchrnous initialization. OSStatus OnInitialize() override { // do initialization stuff that requires functioning driver return kAudioHardwareNoError; } }; auto handler = std::make_shared(); driver->SetDriverHandler(handler); ``` -------------------------------- ### Install/Uninstall libASPL Drivers using install.sh Source: https://github.com/gavv/libaspl/blob/main/README.md Shell script commands to install or uninstall the built libASPL drivers into the system. The -u flag is used for uninstallation. ```bash sudo ./examples/install.sh [-u] ``` -------------------------------- ### Add libASPL to CMake Project using ExternalProject Source: https://github.com/gavv/libaspl/blob/main/README.md CMake code snippet for integrating libASPL into a project using ExternalProject. It defines how to download, build, and install the library. Dependencies include CMAKE_CURRENT_SOURCE_DIR and CMAKE_CURRENT_BINARY_DIR. ```cmake ExternalProject_Add(libASPL URL "https://github.com/gavv/libASPL.git" SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libASPL-src BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/libASPL-build INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/libASPL-prefix CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= ) target_include_directories(YourDriver PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/libASPL-prefix/include ) target_link_libraries(YourDriver PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/libASPL-prefix/lib/libASPL.a ) ``` -------------------------------- ### CMake: Detect Git Version Tag Source: https://github.com/gavv/libaspl/blob/main/CMakeLists.txt This snippet demonstrates how to detect the current Git tag and use it as the package version. It executes a shell command to get the latest tag and then parses it. ```cmake cmake_minimum_required(VERSION 3.12.0) project(aspl CXX) option(BUILD_DOCUMENTATION "Build Doxygen documentation" OFF) execute_process( OUTPUT_VARIABLE GIT_TAG OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND sh -c "cd \"${PROJECT_SOURCE_DIR}\" && git describe --tags --abbrev=0" ) string(REGEX MATCH "v?([0-9.]+)" RESULT ${GIT_TAG}) set(GIT_TAG "${CMAKE_MATCH_1}") if(NOT GIT_TAG STREQUAL "") message(STATUS "Detected version tag from git: ${GIT_TAG}") else() message(WARNING "Unable to detect version tag from git") endif() set(PACKAGE_NAME libASPL) set(PACKAGE_VERSION ${GIT_TAG}) ``` -------------------------------- ### Create Basic Audio Driver in C++ Source: https://context7.com/gavv/libaspl/llms.txt This C++ code demonstrates how to create a basic virtual audio driver using libASPL. It initializes the driver context, configures device parameters, adds an output stream with controls, and creates the main plugin and driver objects. The function `MyDriverEntryPoint` serves as the entry point for macOS CoreAudio. ```cpp #include #include extern "C" void* MyDriverEntryPoint(CFAllocatorRef allocator, CFUUIDRef typeUUID) { // Verify correct plugin type UUID if (!CFEqual(typeUUID, kAudioServerPlugInTypeUUID)) { return nullptr; } // Create shared context for all objects auto context = std::make_shared(); // Configure device parameters aspl::DeviceParameters deviceParams; deviceParams.Name = "My Virtual Device"; deviceParams.Manufacturer = "My Company"; deviceParams.SampleRate = 44100; deviceParams.ChannelCount = 2; deviceParams.EnableMixing = true; // Create device with parameters auto device = std::make_shared(context, deviceParams); // Add output stream with volume and mute controls device->AddStreamWithControlsAsync(aspl::Direction::Output); // Create plugin (root of object hierarchy) auto plugin = std::make_shared(context); plugin->AddDevice(device); // Create driver (top-level entry point) static std::shared_ptr driver = std::make_shared(context, plugin); return driver->GetReference(); } ``` -------------------------------- ### Minimal libASPL Driver Entry Point Source: https://github.com/gavv/libaspl/blob/main/README.md C++ code for a minimal libASPL driver, demonstrating the entry point and basic driver/plugin/device creation. It includes necessary includes and the extern "C" function required for audio drivers. ```cpp #include #include #include "aspl/Context.h" #include "aspl/Device.h" #include "aspl/Driver.h" #include "aspl/Plugin.h" std::shared_ptr CreateDriver() { auto context = std::make_shared(); auto device = std::make_shared(context); device->AddStreamWithControlsAsync(aspl::Direction::Output); auto plugin = std::make_shared(context); plugin->AddDevice(device); return std::make_shared(context, plugin); } extern "C" void* EntryPoint(CFAllocatorRef allocator, CFUUIDRef typeUUID) { if (!CFEqual(typeUUID, kAudioServerPlugInTypeUUID)) { return nullptr; } static std::shared_ptr driver = CreateDriver(); return driver->GetReference(); } ``` -------------------------------- ### Dynamic Sample Rate Device in C++ Source: https://context7.com/gavv/libaspl/llms.txt Illustrates the creation of a C++ device class that supports dynamic changes to its sample rate at runtime. It includes validation of supported rates and updates all associated streams accordingly. ```cpp #include class DynamicRateDevice : public aspl::Device { public: DynamicRateDevice(std::shared_ptr context) : aspl::Device(context, CreateParameters()) { // Define supported sample rates SetAvailableSampleRatesAsync({ {44100.0, 44100.0}, {48000.0, 48000.0}, {88200.0, 88200.0}, {96000.0, 96000.0} }); } protected: // Called when sample rate change is requested OSStatus SetNominalSampleRateImpl(Float64 rate) override { // Validate rate is supported auto rates = GetAvailableSampleRates(); bool supported = false; for (const auto& range : rates) { if (rate >= range.mMinimum && rate <= range.mMaximum) { supported = true; break; } } if (!supported) { return kAudioHardwareUnsupportedOperationError; } // Update hardware if needed if (!ConfigureHardwareRate(rate)) { return kAudioHardwareUnspecifiedError; } // Update all streams for (UInt32 i = 0; i < GetStreamCount(aspl::Direction::Output); i++) { auto stream = GetStreamByIndex(aspl::Direction::Output, i); if (stream) { auto format = stream->GetPhysicalFormat(); format.mSampleRate = rate; stream->SetPhysicalFormatAsync(format); } } // Call base implementation to update stored value return aspl::Device::SetNominalSampleRateImpl(rate); } private: static aspl::DeviceParameters CreateParameters() { aspl::DeviceParameters params; params.Name = "Dynamic Sample Rate Device"; params.SampleRate = 44100; params.ChannelCount = 2; return params; } bool ConfigureHardwareRate(Float64 rate) { // Implement hardware-specific rate configuration return true; } }; ``` -------------------------------- ### Use Persistent Storage for Device Configuration in C++ Source: https://context7.com/gavv/libaspl/llms.txt This C++ class demonstrates saving and loading device configuration that persists across system reboots using `aspl::Storage`. It handles various data types including simple values and binary data. ```cpp #include class ConfigurableDevice { public: ConfigurableDevice(std::shared_ptr driver) : storage_(driver->GetStorage()) { } // Save device configuration bool SaveConfiguration() { // Save simple values if (!storage_->WriteFloat("device.volume", currentVolume_)) { return false; } if (!storage_->WriteBoolean("device.muted", isMuted_)) { return false; } if (!storage_->WriteInt("device.sampleRate", sampleRate_)) { return false; } if (!storage_->WriteString("device.name", deviceName_)) { return false; } // Save binary data std::vector customData = SerializeSettings(); if (!storage_->WriteBytes("device.customData", customData)) { return false; } return true; } // Load device configuration bool LoadConfiguration() { // Read simple values auto [volume, volumeOk] = storage_->ReadFloat("device.volume"); if (volumeOk) { currentVolume_ = volume; } auto [muted, mutedOk] = storage_->ReadBoolean("device.muted"); if (mutedOk) { isMuted_ = muted; } auto [sampleRate, rateOk] = storage_->ReadInt("device.sampleRate"); if (rateOk) { sampleRate_ = static_cast(sampleRate); } auto [name, nameOk] = storage_->ReadString("device.name"); if (nameOk) { deviceName_ = name; } // Read binary data auto [customData, dataOk] = storage_->ReadBytes("device.customData"); if (dataOk) { DeserializeSettings(customData); } return true; } // Delete configuration bool ClearConfiguration() { storage_->Delete("device.volume"); storage_->Delete("device.muted"); storage_->Delete("device.sampleRate"); storage_->Delete("device.name"); storage_->Delete("device.customData"); return true; } private: std::shared_ptr storage_; Float64 currentVolume_ = 1.0; bool isMuted_ = false; UInt32 sampleRate_ = 44100; std::string deviceName_; std::vector SerializeSettings() { return {}; // Implement serialization } void DeserializeSettings(const std::vector&) { // Implement deserialization } }; ``` -------------------------------- ### Custom Tracer Implementation in C++ Source: https://context7.com/gavv/libaspl/llms.txt Demonstrates how to create a custom tracer in C++ that redirects logging output to a specified file. It handles thread-safe writing to the log file and provides options for enabling or disabling tracing. ```cpp #include #include #include #include class FileTracer : public aspl::Tracer { public: FileTracer(const std::string& logPath) : aspl::Tracer(Mode::Enabled) , logFile_(logPath, std::ios::app) { } ~FileTracer() { if (logFile_.is_open()) { logFile_.close(); } } protected: void Print(const char* message) override { std::lock_guard lock(mutex_); if (logFile_.is_open()) { auto now = std::chrono::system_clock::now(); auto time = std::chrono::system_clock::to_time_t(now); logFile_ << std::ctime(&time) << " " << message << std::endl; logFile_.flush(); } } private: std::ofstream logFile_; std::mutex mutex_; }; // Usage auto tracer = std::make_shared("/tmp/mydriver.log"); auto context = std::make_shared(tracer); // Or disable tracing entirely auto noopTracer = std::make_shared(aspl::Tracer::Mode::Noop); auto quietContext = std::make_shared(noopTracer); ``` -------------------------------- ### Precise Stream and Control Configuration in C++ Source: https://github.com/gavv/libaspl/blob/main/README.md Allows for more precise configuration of audio streams and controls by creating streams and controls separately and then attaching them. This provides greater flexibility in managing audio device properties. Dependencies include the aspl library. ```cpp auto stream = device->AddStreamAsync(aspl::Direction::Output); auto volumeControl = device->AddVolumeControlAsync(kAudioObjectPropertyScopeOutput); auto muteControl = device->AddMuteControlAsync(kAudioObjectPropertyScopeOutput); stream->AttachVolumeControl(volumeControl); stream->AttachMuteControl(muteControl); ``` -------------------------------- ### CMake Configuration for SinewaveDevice Project Source: https://github.com/gavv/libaspl/blob/main/examples/SinewaveDevice/CMakeLists.txt This snippet defines the minimum CMake version, project name, C++ standard, and compiler options. It also sets various cache variables for driver metadata such as name, version, copyright, identifier, and entry point. ```cmake cmake_minimum_required(VERSION 3.12.0) project(SinewaveDevice CXX) # set compiler options set(CMAKE_CXX_STANDARD 17) set(CMAKE_POSITION_INDEPENDENT_CODE ON) # set to non-empty string to enable code signing set(CODESIGN_ID "" CACHE STRING "Codesign ID") set(DRIVER_NAME "SinewaveDevice") set(DRIVER_VERSION "1.0.0") set(DRIVER_COPYRIGHT "Copyright (c) libASPL authors") set(DRIVER_IDENTIFIER "sinewavedevice.examples.libaspl") set(DRIVER_UID "30F8E35D-FB97-4A9C-A469-9038A751B4B6") set(DRIVER_ENTRYPOINT "ExampleEntryPoint") # report configuration to console message(STATUS "Driver name - ${DRIVER_NAME}") message(STATUS "Driver version - ${DRIVER_VERSION}") message(STATUS "Driver copyright - ${DRIVER_COPYRIGHT}") message(STATUS "Driver identifier - ${DRIVER_IDENTIFIER}") message(STATUS "Driver codesign ID - ${CODESIGN_ID}") ``` -------------------------------- ### Generate Documentation with Doxygen Source: https://github.com/gavv/libaspl/blob/main/CMakeLists.txt Generates HTML documentation using Doxygen if BUILD_DOCUMENTATION is enabled. It defines a custom command that depends on the library target and executes Doxygen to create the documentation in the 'html' directory. ```cmake if(BUILD_DOCUMENTATION) find_package(Doxygen REQUIRED) if(DOXYGEN_FOUND STREQUAL YES) add_custom_command( COMMENT "Generating HTML documentation" DEPENDS ${LIB_TARGET} OUTPUT ${PROJECT_SOURCE_DIR}/html/index.html COMMAND cd ${PROJECT_SOURCE_DIR} && doxygen && touch html/index.html ) add_custom_target(doxygen ALL DEPENDS ${PROJECT_SOURCE_DIR}/html/index.html ) endif() endif(BUILD_DOCUMENTATION) ``` -------------------------------- ### Implement Custom Input Device with Sine Wave Generation (C++) Source: https://context7.com/gavv/libaspl/llms.txt This snippet shows how to create a custom input device that generates a sine wave. It overrides the OnReadClientInput method to provide audio data to applications. Dependencies include ASPL driver headers and cmath for mathematical functions. ```cpp #include #include #include class InputHandler : public aspl::ControlRequestHandler, public aspl::IORequestHandler { public: // Realtime thread: Provide audio data to client void OnReadClientInput(const std::shared_ptr& client, const std::shared_ptr& stream, Float64 zeroTimestamp, Float64 timestamp, void* bytes, UInt32 bytesCount) override { const UInt32 sampleRate = 44100; const UInt32 channelCount = 2; const UInt32 frequency = 500; // 500 Hz sine wave SInt16* samples = static_cast(bytes); UInt32 numSamples = bytesCount / sizeof(SInt16) / channelCount; for (UInt32 n = 0; n < numSamples; n++) { // Generate sine wave sample double angle = 2.0 * M_PI * frequency * (timestamp + n) / sampleRate; double value = std::sin(angle); // Convert to 16-bit integer constexpr SInt16 maxValue = std::numeric_limits::max(); constexpr SInt16 minValue = std::numeric_limits::min(); value *= (maxValue + 1.0); SInt16 sample = (value < minValue) ? minValue : (value >= maxValue + 1.0) ? maxValue : static_cast(value); // Write to all channels (stereo) for (UInt32 c = 0; c < channelCount; c++) { samples[n * channelCount + c] = sample; } } } }; // Usage in driver creation aspl::DeviceParameters deviceParams; deviceParams.Name = "Sine Wave Generator"; deviceParams.SampleRate = 44100; deviceParams.ChannelCount = 2; auto device = std::make_shared(context, deviceParams); device->AddStreamWithControlsAsync(aspl::Direction::Input); auto handler = std::make_shared(); device->SetControlHandler(handler); device->SetIOHandler(handler); ``` -------------------------------- ### Add Googletest for Testing with CMake Source: https://github.com/gavv/libaspl/blob/main/CMakeLists.txt Downloads and builds the googletest framework using ExternalProject_Add if BUILD_TESTING is enabled. It then adds dependencies and configures test executables to link against googletest and the project's library. ```cmake if(BUILD_TESTING) include(ExternalProject) ExternalProject_Add(googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.12.1 GIT_SHALLOW ON UPDATE_DISCONNECTED 1 SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/googletest-src BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/googletest-build INSTALL_COMMAND "" TEST_COMMAND "" LOG_DOWNLOAD ON LOG_CONFIGURE ON LOG_BUILD ON ) add_dependencies(googletest ${LIB_TARGET} ) add_executable(${TEST_NAME} "test/Main.cpp" "test/TestClients.cpp" "test/TestConstruction.cpp" "test/TestDoubleBuffer.cpp" "test/TestOperations.cpp" "test/TestProperties.cpp" "test/TestRegistration.cpp" "test/TestStorage.cpp" ) add_dependencies(${TEST_NAME} ${LIB_TARGET} googletest ) target_include_directories(${TEST_NAME} SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/googletest-src/googletest/include ) target_link_libraries(${TEST_NAME} ${LIB_TARGET} ${CMAKE_CURRENT_BINARY_DIR}/googletest-build/lib/libgtest.a ) enable_testing() add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME} ) endif(BUILD_TESTING) ``` -------------------------------- ### C++ Per-Client Audio Processing with Custom Effects and Mixing Source: https://context7.com/gavv/libaspl/llms.txt This C++ code defines a `PerClientProcessor` class inheriting from `aspl::IORequestHandler`. It enables per-client audio manipulation within `OnProcessClientOutput`, applying effects like gain boost for music applications and a low-pass filter for browser audio based on client bundle IDs. The `OnWriteClientOutput` method demonstrates how to mix audio from multiple clients before writing to hardware, ensuring proper synchronization and buffer management. Dependencies include ASPL library headers. ```cpp #include #include #include #include #include #include class PerClientProcessor : public aspl::IORequestHandler { public: // Process output from each client individually (EnableMixing = false) void OnProcessClientOutput(const std::shared_ptr& client, const std::shared_ptr& stream, Float64 zeroTimestamp, Float64 timestamp, Float32* frames, UInt32 frameCount, UInt32 channelCount) override { // Apply default stream processing (volume, mute) stream->ApplyProcessing(frames, frameCount, channelCount); // Apply per-client custom processing const auto& clientInfo = client->GetInfo(); if (clientInfo.BundleID.find("Music") != std::string::npos) { // Boost music applications ApplyGain(frames, frameCount * channelCount, 1.5f); } else if (clientInfo.BundleID.find("Safari") != std::string::npos) { // Apply low-pass filter to browser audio ApplyLowPassFilter(frames, frameCount, channelCount); } } // Write individual client output (must perform mixing) void OnWriteClientOutput(const std::shared_ptr& client, const std::shared_ptr& stream, Float64 zeroTimestamp, Float64 timestamp, const Float32* frames, UInt32 frameCount, UInt32 channelCount) override { std::lock_guard lock(mixMutex_); // Initialize mix buffer on first client if (mixBuffer_.empty()) { mixBuffer_.resize(frameCount * channelCount, 0.0f); } // Mix this client's audio into buffer for (UInt32 i = 0; i < frameCount * channelCount; i++) { mixBuffer_[i] += frames[i]; } clientCount_++; // After all clients processed, write to hardware if (clientCount_ >= expectedClientCount_) { WriteToHardware(mixBuffer_.data(), frameCount, channelCount); mixBuffer_.clear(); clientCount_ = 0; } } private: std::mutex mixMutex_; std::vector mixBuffer_; UInt32 clientCount_ = 0; UInt32 expectedClientCount_ = 1; void ApplyGain(Float32* frames, UInt32 sampleCount, Float32 gain) { for (UInt32 i = 0; i < sampleCount; i++) { frames[i] *= gain; } } void ApplyLowPassFilter(Float32* frames, UInt32 frameCount, UInt32 channels) { // Simple one-pole low-pass filter implementation const Float32 alpha = 0.1f; static std::vector prevSample(channels, 0.0f); for (UInt32 f = 0; f < frameCount; f++) { for (UInt32 c = 0; c < channels; c++) { UInt32 idx = f * channels + c; frames[idx] = alpha * frames[idx] + (1.0f - alpha) * prevSample[c]; prevSample[c] = frames[idx]; } } } void WriteToHardware(const Float32* frames, UInt32 frameCount, UInt32 channels) { // Write mixed audio to hardware/network/file } }; // Usage aspl::DeviceParameters params; params.EnableMixing = false; // Disable automatic mixing // Assuming 'context' is available in this scope // auto device = std::make_shared(context, params); // auto handler = std::make_shared(); // device->SetIOHandler(handler); ``` -------------------------------- ### Building and Linking SinewaveDevice Driver Library Source: https://github.com/gavv/libaspl/blob/main/examples/SinewaveDevice/CMakeLists.txt This snippet defines the main driver library as a MODULE (shared object). It adds dependencies on the libASPL external project, includes necessary header directories, and links against the static libASPL library and the CoreFoundation framework. ```cmake # we use MODULE to create .so library instead of .dylib library; .so files are # also known as Mach-O loadable bundles (not to be confused with macOS bundle # directory, which we create below) # see: # - https://cmake.org/cmake/help/latest/command/add_library.html # - https://stackoverflow.com/a/2339910/3169754 add_library(${DRIVER_NAME} MODULE "Driver.cpp" ) # add libASPL dependency add_dependencies(${DRIVER_NAME} ${LIBASPL_TARGET}) target_include_directories(${DRIVER_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/libASPL-prefix/include ) target_link_libraries(${DRIVER_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/libASPL-prefix/lib/libASPL.a ) # add CoreFoundation dependency find_library(LIB_CoreFoundation CoreFoundation REQUIRED) target_link_libraries(${DRIVER_NAME} PRIVATE ${LIB_CoreFoundation} ) ``` -------------------------------- ### Persistent Storage Operations in C++ Source: https://github.com/gavv/libaspl/blob/main/README.md Illustrates the usage of libASPL's persistent storage wrapper for the CoreAudio Storage API. It demonstrates writing and reading string values, providing a mechanism for storing plugin configuration that persists across restarts. Dependencies include the aspl library and a driver object. ```cpp auto ok = driver->GetStorage()->WriteString("key", "value"); // ... auto [str, ok] = driver->GetStorage()->ReadString("key"); if (ok) { // ... } ``` -------------------------------- ### Record NetcatDevice Output with netcat and sox Source: https://github.com/gavv/libaspl/blob/main/README.md Command to capture UDP audio data from NetcatDevice, decode it, and save it as a WAV file using netcat and sox. It captures 1 million samples. ```bash nc -u -l 127.0.0.1 4444 | head -c 1000000 | sox -t raw -r 44100 -e signed -b 16 -c 2 - test.wav ``` -------------------------------- ### Manual Storage Object Creation in C++ Source: https://github.com/gavv/libaspl/blob/main/README.md Demonstrates how to manually create and pass a shared pointer to an aspl::Storage object to the aspl::Driver constructor. This allows for custom storage implementations or explicit control over storage initialization. Dependencies include the aspl library. ```cpp // ... auto storage = std::make_shared(context); // ... auto driver = std::make_shared(context, plugin, storage); ``` -------------------------------- ### Record SinewaveDevice Output with sox Source: https://github.com/gavv/libaspl/blob/main/README.md Command to record 5 seconds of audio from the SinewaveDevice and save it as a WAV file using the sox tool. ```bash sox -t coreaudio "Sinewave Device (libASPL)" test.wav trim 0 5 ``` -------------------------------- ### Implement Custom Client Tracking Handler in C++ Source: https://context7.com/gavv/libaspl/llms.txt This C++ handler tracks individual clients connecting to the device, maintaining per-client state. It utilizes `aspl::ControlRequestHandler` and manages client connections and disconnections. ```cpp #include #include #include #include struct ClientState { std::string processName; pid_t processID; UInt32 ioStartCount; std::chrono::steady_clock::time_point connectedAt; }; class ClientTrackingHandler : public aspl::ControlRequestHandler { public: // Called when a new client connects std::shared_ptr OnAddClient( const aspl::ClientInfo& clientInfo) override { auto client = std::make_shared(clientInfo); std::lock_guard lock(mutex_); ClientState state; state.processName = clientInfo.BundleID; state.processID = clientInfo.ProcessID; state.ioStartCount = 0; state.connectedAt = std::chrono::steady_clock::now(); clientStates_[client->GetID()] = state; return client; } // Called when a client disconnects void OnRemoveClient(std::shared_ptr client) override { std::lock_guard lock(mutex_); auto it = clientStates_.find(client->GetID()); if (it != clientStates_.end()) { auto& state = it->second; auto duration = std::chrono::steady_clock::now() - state.connectedAt; // Log or process client disconnection clientStates_.erase(it); } } OSStatus OnStartIO() override { std::lock_guard lock(mutex_); activeClients_++; return kAudioHardwareNoError; } void OnStopIO() override { std::lock_guard lock(mutex_); activeClients_--; } private: std::mutex mutex_; std::unordered_map clientStates_; UInt32 activeClients_ = 0; }; ``` -------------------------------- ### Custom Tracer Implementation in C++ Source: https://github.com/gavv/libaspl/blob/main/README.md Shows how to implement a custom tracer by inheriting from aspl::Tracer and overriding the Print method. This allows for custom logging or diagnostic behavior. The custom tracer is then used to create a Context. Dependencies include the aspl library. ```cpp class MyTracer : public aspl::Tracer { protected: void Print(const char* message) override { // ... } }; auto tracer = std::make_shared(); auto context = std::make_shared(tracer); // pass context to all objects ``` -------------------------------- ### Configuring macOS Bundle Properties for Driver Source: https://github.com/gavv/libaspl/blob/main/examples/SinewaveDevice/CMakeLists.txt This CMake code configures the properties for the built shared library to package it as a macOS bundle. It sets the output name, bundle extension, and various bundle metadata like version, copyright, and identifier, using a template for the Info.plist file. ```cmake # here we adjust properties of our shared library and ask CMake to pack it into # macOS bundle directory # # we set OUTPUT_NAME to be "${DRIVER_NAME}" # we set PREFIX to "" to remove "lib" prefix # we set SUFFIX to "" to remove ".so" suffix # resulting name of the shared library will be just "${DRIVER_NAME}" # # we set MACOSX_BUNDLE to "ON" and BUNDLE to "true" to instruct CMake # to pack our shared library into macOS bundle directory (Finder may display that # directory as a single opaque item) # # we set MACOSX_BUNDLE_BUNDLE_NAME to "${DRIVER_NAME}" and # BUNDLE_EXTENSION to "driver", so that the resulting directory name # will be "${DRIVER_NAME}.driver" # # we set MACOSX_BUNDLE_INFO_PLIST to the path of Info.plist.in template, which will be # used by CMake to generate resulting Info.plist; CMake will substitute variables in # template with CMake vars like MACOSX_BUNDLE_* # # the bundle directory will contain our shared library, which is a plugin for audio # server, and "Info.plist" file, which is meta-information for the plugin, and # optionally code signature # # $ tree build/Example/${DRIVER_NAME}.driver # `-- Contents # |-- Info.plist # |-- MacOS # | `-- ${DRIVER_NAME} # `-- _CodeSignature # `-- CodeResources # # the user can install the bundle directory into /Library/Audio/Plug-Ins/HAL; on start, # audio server will find it, read Info.plist, load shared library into its address # space, and invoke entry point function (see above) to construct our plugin; it will # then use the plugin to create virtual audio device set_target_properties(${DRIVER_NAME} PROPERTIES OUTPUT_NAME "${DRIVER_NAME}" BUNDLE true BUNDLE_EXTENSION "driver" PREFIX "" SUFFIX "" MACOSX_BUNDLE ON MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in" MACOSX_BUNDLE_BUNDLE_NAME "${DRIVER_NAME}" MACOSX_BUNDLE_BUNDLE_VERSION "${DRIVER_VERSION}" MACOSX_BUNDLE_COPYRIGHT "${DRIVER_COPYRIGHT}" MACOSX_BUNDLE_GUI_IDENTIFIER "${DRIVER_IDENTIFIER}" MACOSX_BUNDLE_SHORT_VERSION_STRING "${DRIVER_VERSION}" ) ``` -------------------------------- ### Add libASPL to CMake Project using FindPackage Source: https://github.com/gavv/libaspl/blob/main/README.md CMake code snippets for finding and linking libASPL in a project using FindPackage. It covers cases where libASPL is pre-installed in the system or a specific directory. ```cmake # if libASPL is pre-installed into the system: find_package(libASPL REQUIRED) # if libASPL is pre-installed into a directory: find_package(libASPL REQUIRED PATHS /your/install/directory NO_DEFAULT_PATH ) target_include_directories(YourDriver PRIVATE aspl::libASPL) target_link_libraries(YourDriver PRIVATE aspl::libASPL) ``` -------------------------------- ### libASPL Library Overview Source: https://github.com/gavv/libaspl/blob/main/README.md libASPL (Audio Server Plug-In Library) is a C++17 library that assists in creating macOS CoreAudio Audio Server Plug-Ins. It acts as a shim between Audio Server and user code, managing property dispatch, CF types, and providing default implementations for common properties. ```APIDOC ## libASPL Library Overview ### Description libASPL is a C++17 library designed to simplify the development of macOS CoreAudio Audio Server Plug-Ins (User-Space CoreAudio Drivers). It abstracts away much of the boilerplate code required for creating virtual audio devices. Key features include: - **Statically Typed Access**: Inherit from library classes and override statically typed getters and setters instead of dealing with dynamic property dispatch. - **C++ Type Convenience**: Work with C++ types like `std::string` and `std::vector` instead of Core Foundation types like `CFString`. - **Default Implementations**: Properties have reasonable default implementations, reducing the need to override all of them. - **Full Control**: Does not hide Audio Server functionality; allows customization of all aspects by overriding virtual methods. - **Minimal Abstraction**: Maps Audio Server properties and callbacks almost one-to-one to C++ methods. - **Verbose Tracing**: Includes detailed tracing of driver activities, with customizable output. ### Synopsis **libASPL** (**A**udio **S**erver **PL**ugin **lib**rary) is a C++17 library helping to create macOS CoreAudio [Audio Server Plug-In](https://developer.apple.com/documentation/coreaudio/building_an_audio_server_plug-in_and_driver_extension) (a.k.a User-Space CoreAudio Driver) with your custom virtual device. ### Installation 1. Install CMake: `brew install cmake` 2. Clone the repository: `git clone https://github.com/gavv/libASPL.git` 3. Navigate to the directory: `cd libASPL` 4. Build and install: `make && sudo make install` Alternatively, use CMake for more control: `mkdir -p build/Release && cd build/Release && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/my/install/path ../.. && make -j4 && make install` ### Status Production-ready and used in commercial and open-source applications. See [Roc Virtual Audio Device for macOS](https://github.com/roc-streaming/roc-vad) for a real-world example. ### Versioning Uses [semantic versioning](https://semver.org/). Source-level compatibility is maintained, but binary compatibility is not guaranteed between releases. Recompile your code with new versions. See [changelog](CHANGES.md). ### API Reference Doxygen-generated documentation is available at [https://gavv.net/libASPL/](https://gavv.net/libASPL/). ```