Skip to main content

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:

  1. Extension class - Inherits from Extension, provides metadata and initialization
  2. NodeFactory class - Inherits from NodeFactory, creates node instances
  3. 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 processing
  • NODE_CATEGORY_MIXING - Mixing and routing
  • NODE_CATEGORY_MUSIC - Music-related processing
  • NODE_CATEGORY_VOICE - Voice processing
  • NODE_CATEGORY_GENERATION - Audio generation
  • NODE_CATEGORY_ANALYSIS - Analysis and detection
  • NODE_CATEGORY_EFFECTS - Audio effects
  • NODE_CATEGORY_UTILITY - Utility nodes
  • NODE_CATEGORY_AI - AI-powered nodes
  • NODE_CATEGORY_RTC - Real-time communication

Loading the Extension

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 .dylib or .framework
  • Android: Build as a .so shared library
  • Windows: Build as a .dll
  • Linux: Build as a .so shared library

Best Practices

  1. Namespace your extension - Use a unique namespace to avoid conflicts
  2. Provide metadata - Implement getNodeTypeInfo() for each node to enable discoverability
  3. Document your nodes - Include clear descriptions and categories
  4. Initialize resources carefully - Use initialize() for one-time setup
  5. Clean up properly - Release resources in deinitialize()
  6. Handle errors gracefully - Return error Results from initialization if something fails
  7. Version your extension - Include version information in your extension name or metadata
  8. Test thoroughly - Test your extension on all target platforms
  9. Avoid global state - Keep state within your extension or node instances
  10. 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.