Extensions
Extensions are packages that bundle one or more custom nodes into a distributable module that can be loaded into the Switchboard SDK. Once you've created your custom nodes, packaging them as an extension makes them reusable across projects and shareable with others.
Why Create Extensions?
Extensions enable you to:
- Package custom nodes - Bundle related nodes together into a single module
- Distribute functionality - Share your custom nodes with other projects or teams
- Organize code - Keep custom functionality separate from application code
- Namespace nodes - Prefix node types with your extension name (e.g.,
MyExtension.CustomNode) - Initialize resources - Set up shared resources when the extension loads
- Clean up resources - Release resources when the extension unloads
Extension Structure
An extension consists of three main components:
- Extension class - Inherits from
Extension, provides metadata and initialization - NodeFactory class - Inherits from
NodeFactory, creates node instances - Custom node classes - Your actual node implementations (covered in previous sections)
Creating an Extension Class
Your extension class inherits from Extension and provides the extension's name and node factory.
Basic Extension Example
#include <switchboard_core/Extension.hpp>
#include <switchboard_core/ExtensionManager.hpp>
#include "MyNodeFactory.hpp"
namespace myextension {
class MyExtension : public switchboard::Extension {
public:
// Static method to load the extension
static void load() {
auto extension = std::make_shared<MyExtension>();
switchboard::ExtensionManager::getInstance().registerExtension(extension);
}
// Return the extension name
std::string getName() override {
return "MyExtension";
}
// Return the node factory for creating nodes
std::shared_ptr<switchboard::NodeFactory> getNodeFactory() override {
return std::make_shared<MyNodeFactory>();
}
// Optional: Initialize resources when extension loads
switchboard::Result<void> initialize(const switchboard::SBAnyMap& config) override {
// Set up any shared resources, libraries, or state
return switchboard::makeSuccess();
}
// Optional: Clean up resources when extension unloads
switchboard::Result<void> deinitialize() override {
// Release any resources
return switchboard::makeSuccess();
}
};
}
Extension with Initialization
Here's an extension that initializes a third-party library:
#include <switchboard_core/Extension.hpp>
#include <switchboard_core/ExtensionManager.hpp>
#include <switchboard_core/Logger.hpp>
#include "AnalyzerNodeFactory.hpp"
#include <essentia/essentia.h>
namespace switchboard::extensions::analyzer {
class AnalyzerExtension : public Extension {
public:
static void load() {
auto extension = std::make_shared<AnalyzerExtension>();
ExtensionManager::getInstance().registerExtension(extension);
}
std::string getName() override {
return "Analyzer";
}
std::shared_ptr<NodeFactory> getNodeFactory() override {
return std::make_shared<AnalyzerNodeFactory>();
}
Result<void> initialize(const SBAnyMap& config) override {
// Initialize the Essentia library
essentia::init();
Logger::info("[AnalyzerExtension] Essentia library initialized");
return makeSuccess();
}
Result<void> deinitialize() override {
// Clean up Essentia library resources
Logger::info("[AnalyzerExtension] Cleaning up resources");
return makeSuccess();
}
};
}
Creating a NodeFactory Class
Your node factory inherits from NodeFactory and is responsible for creating instances of your custom nodes.
Simple NodeFactory Example
Here's a simple factory that creates nodes using a switch statement:
#include <switchboard_core/NodeFactory.hpp>
#include "NoiseFilterNode.hpp"
namespace myextension {
class MyNodeFactory : public switchboard::NodeFactory {
public:
// Return the namespace prefix for your nodes
std::string getNodeTypePrefix() override {
return "MyExtension";
}
// Create node instances based on type
switchboard::Node* createNode(const std::string& type, const switchboard::SBAnyMap& config) override {
if (type == "NoiseFilter") {
return new NoiseFilterNode();
}
// Add more node types here
return nullptr;
}
};
}
Advanced NodeFactory with Registration
Here's a more sophisticated factory that registers nodes with metadata:
#include <switchboard_core/NodeFactory.hpp>
#include "BeatAnalyzerNode.hpp"
#include "PitchDetectorNode.hpp"
namespace myextension {
class MyNodeFactory : public switchboard::NodeFactory {
public:
MyNodeFactory() {
// Register each node type with metadata
registerNode(
BeatAnalyzerNode::getNodeTypeInfo(),
[](const switchboard::SBAnyMap& config) {
return new BeatAnalyzerNode(config);
}
);
registerNode(
PitchDetectorNode::getNodeTypeInfo(),
[](const switchboard::SBAnyMap& config) {
return new PitchDetectorNode(config);
}
);
}
std::string getNodeTypePrefix() override {
return "MyExtension";
}
};
}
Adding Node Metadata
For nodes to be discoverable and well-documented, add a static getNodeTypeInfo() method to each node class:
#include <switchboard_core/SingleBusAudioProcessorNode.hpp>
#include <switchboard_core/NodeTypeInfo.hpp>
class BeatAnalyzerNode : public switchboard::SingleBusAudioProcessorNode {
public:
static switchboard::NodeTypeInfo getNodeTypeInfo() {
return switchboard::NodeTypeInfo {
"MyExtension", // Namespace
"BeatAnalyzer", // Type name
"Beat Analyzer Node", // Display name
"Analyzes audio for beat detection", // Description
{ // Categories
switchboard::NODE_CATEGORY_ANALYSIS,
switchboard::NODE_CATEGORY_MUSIC
}
};
}
BeatAnalyzerNode(const switchboard::SBAnyMap& config) {
type = "BeatAnalyzer";
// Implementation...
}
};
Available Node Categories
NODE_CATEGORY_AUDIO_PROCESSING- General audio processingNODE_CATEGORY_MIXING- Mixing and routingNODE_CATEGORY_MUSIC- Music-related processingNODE_CATEGORY_VOICE- Voice processingNODE_CATEGORY_GENERATION- Audio generationNODE_CATEGORY_ANALYSIS- Analysis and detectionNODE_CATEGORY_EFFECTS- Audio effectsNODE_CATEGORY_UTILITY- Utility nodesNODE_CATEGORY_AI- AI-powered nodesNODE_CATEGORY_RTC- Real-time communication
Loading the Extension
Dynamic Loading (Recommended)
For extensions that are dynamically loaded at runtime, export a C function:
extern "C" void sb_extension_load() {
myextension::MyExtension::load();
}
This function is automatically called when the extension library is loaded by the Switchboard SDK.
Static Loading
For extensions that are statically linked, call the load method directly in your application:
#include "MyExtension.hpp"
int main() {
// Load the extension
myextension::MyExtension::load();
// Initialize Switchboard and use your nodes
// ...
}
Complete Extension Example
Here's a complete example putting it all together:
MyExtension.hpp
#pragma once
#include <switchboard_core/Extension.hpp>
namespace myextension {
class MyExtension : public switchboard::Extension {
public:
static void load();
std::string getName() override;
std::shared_ptr<switchboard::NodeFactory> getNodeFactory() override;
switchboard::Result<void> initialize(const switchboard::SBAnyMap& config) override;
switchboard::Result<void> deinitialize() override;
};
}
MyExtension.cpp
#include "MyExtension.hpp"
#include "MyNodeFactory.hpp"
#include <switchboard_core/ExtensionManager.hpp>
#include <switchboard_core/Logger.hpp>
// Export C function for dynamic loading
#if !defined(SWITCHBOARD_WEB)
extern "C" void sb_extension_load() {
myextension::MyExtension::load();
}
#endif
namespace myextension {
void MyExtension::load() {
auto extension = std::make_shared<MyExtension>();
switchboard::ExtensionManager::getInstance().registerExtension(extension);
}
std::string MyExtension::getName() {
return "MyExtension";
}
std::shared_ptr<switchboard::NodeFactory> MyExtension::getNodeFactory() {
return std::make_shared<MyNodeFactory>();
}
switchboard::Result<void> MyExtension::initialize(const switchboard::SBAnyMap& config) {
switchboard::Logger::info("[MyExtension] Initializing extension");
// Initialize any shared resources here
return switchboard::makeSuccess();
}
switchboard::Result<void> MyExtension::deinitialize() {
switchboard::Logger::info("[MyExtension] Cleaning up extension");
// Clean up any shared resources here
return switchboard::makeSuccess();
}
}
MyNodeFactory.hpp
#pragma once
#include <switchboard_core/NodeFactory.hpp>
namespace myextension {
class MyNodeFactory : public switchboard::NodeFactory {
public:
MyNodeFactory();
std::string getNodeTypePrefix() override;
};
}
MyNodeFactory.cpp
#include "MyNodeFactory.hpp"
#include "NoiseFilterNode.hpp"
#include "EchoNode.hpp"
namespace myextension {
MyNodeFactory::MyNodeFactory() {
// Register nodes with metadata
registerNode(
NoiseFilterNode::getNodeTypeInfo(),
[](const switchboard::SBAnyMap& config) {
return new NoiseFilterNode(config);
}
);
registerNode(
EchoNode::getNodeTypeInfo(),
[](const switchboard::SBAnyMap& config) {
return new EchoNode(config);
}
);
}
std::string MyNodeFactory::getNodeTypePrefix() {
return "MyExtension";
}
}
Using Your Extension
Once your extension is built and loaded, you can create nodes using the fully-qualified type name:
// Create a node from your extension
auto node = graph->createNode("MyExtension.NoiseFilter");
// Or with configuration
auto node = graph->createNode("MyExtension.EchoNode", {
{ "delayMs", 500 },
{ "feedback", 0.3f }
});
Building and Packaging
CMake Example
Here's a basic CMakeLists.txt for building your extension:
cmake_minimum_required(VERSION 3.15)
project(MyExtension)
# Find Switchboard SDK
find_package(SwitchboardSDK REQUIRED)
# Create extension library
add_library(MyExtension SHARED
src/MyExtension.cpp
src/MyNodeFactory.cpp
src/NoiseFilterNode.cpp
src/EchoNode.cpp
)
target_link_libraries(MyExtension
SwitchboardSDK::SwitchboardSDK
)
target_include_directories(MyExtension
PUBLIC include
PRIVATE src
)
# Set output directory
set_target_properties(MyExtension PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/extensions"
)
Platform-Specific Notes
- iOS/macOS: Build as a
.dylibor.framework - Android: Build as a
.soshared library - Windows: Build as a
.dll - Linux: Build as a
.soshared library
Best Practices
- Namespace your extension - Use a unique namespace to avoid conflicts
- Provide metadata - Implement
getNodeTypeInfo()for each node to enable discoverability - Document your nodes - Include clear descriptions and categories
- Initialize resources carefully - Use
initialize()for one-time setup - Clean up properly - Release resources in
deinitialize() - Handle errors gracefully - Return error Results from initialization if something fails
- Version your extension - Include version information in your extension name or metadata
- Test thoroughly - Test your extension on all target platforms
- Avoid global state - Keep state within your extension or node instances
- Follow naming conventions - Use clear, descriptive names for your nodes
Extension Template
To get started quickly, use our official extension template:
👉 Switchboard SDK C++ Extension Template
This template provides a ready-to-use project structure with example nodes and build configuration.
Next Steps
Now that you've packaged your extension:
- Test your extension on all target platforms
- Document your nodes for users
- Share your extension with the community
- Explore the Extension API Reference for advanced features
If you've built something awesome, we'd love to feature it! Contact us at hello@synervoz.com.