aetherdsp-juce-bridge 0.1.7

C FFI bridge for integrating AetherDSP with JUCE
Documentation

AetherDSP JUCE Bridge

C FFI bridge for integrating AetherDSP with JUCE audio plugins.

Add world music tuning systems and real-time safe DSP processing to your JUCE plugins with just a few lines of code.

Features

  • 17 World Music Tuning Systems - Ethiopian (7), Arabic (3), Indian (1), Gamelan (3), Western (3)
  • Lock-Free DSP Graph - Real-time safe audio processing
  • Memory Safe - Rust's safety guarantees protect your audio thread
  • Zero-Cost Abstraction - No performance overhead
  • Drop-in Integration - Works with existing JUCE projects
  • MIT Licensed - Free forever, no royalties

Quick Start

1. Build the Library

cd crates/aether-juce-bridge

cargo build --release

This generates:

  • Static library: target/release/libaetherdsp_juce_bridge.a (macOS/Linux) or aetherdsp_juce_bridge.lib (Windows)
  • Dynamic library: target/release/libaetherdsp_juce_bridge.dylib (macOS), .so (Linux), or .dll (Windows)
  • C header: include/aetherdsp_juce_bridge.h

2. Add to Your JUCE Project

In Projucer:

  1. Add the static library to your project:

    • Go to "Modules" → "Add a module from a specified folder"
    • Or add directly in "Build" tab → "External Libraries to Link"
  2. Add include path:

    • Add crates/aether-juce-bridge/include to header search paths

In CMake:

target_link_libraries(YourPlugin
    PRIVATE
        ${AETHERDSP_BRIDGE_PATH}/target/release/libaetherdsp_juce_bridge.a
)

target_include_directories(YourPlugin
    PRIVATE
        ${AETHERDSP_BRIDGE_PATH}/include
)

3. Use in Your Plugin

#include "aetherdsp_juce_bridge.h"

class EthiopianSynthPlugin : public juce::AudioProcessor
{
public:
    EthiopianSynthPlugin()
    {
        // Create DSP graph
        graph = aether_graph_create(44100.0f, 512);

        // Load Ethiopian Tizita tuning
        tuning = aether_tuning_ethiopian_tizita();

        // Add oscillator with tuning
        oscId = aether_graph_add_oscillator(graph, tuning);

        // Add gain node
        gainId = aether_graph_add_gain(graph);

        // Set initial frequency (MIDI note 60 = Middle C)
        aether_graph_set_param(graph, oscId, 0, 60.0f, 0.0f);
    }

    ~EthiopianSynthPlugin()
    {
        aether_tuning_free(tuning);
        aether_graph_free(graph);
    }

    void processBlock(juce::AudioBuffer<float>& buffer,
                     juce::MidiBuffer& midiMessages) override
    {
        // Process MIDI
        for (const auto metadata : midiMessages)
        {
            const auto msg = metadata.getMessage();
            if (msg.isNoteOn())
            {
                float note = (float)msg.getNoteNumber();
                aether_graph_set_param(graph, oscId, 0, note, 10.0f);
            }
        }

        // Process audio
        aether_graph_process(
            graph,
            nullptr, 0,  // No inputs
            buffer.getArrayOfWritePointers(),
            buffer.getNumChannels(),
            buffer.getNumSamples()
        );
    }

private:
    AetherGraph* graph = nullptr;
    AetherTuningTable* tuning = nullptr;
    AetherNodeId oscId = 0;
    AetherNodeId gainId = 0;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EthiopianSynthPlugin)
};

Available Tuning Systems

Ethiopian Scales (Qenet)

All 7 traditional Ethiopian qenet modes are now implemented:

// Tizita variants (expressing nostalgia and longing)
auto tuning = aether_tuning_ethiopian_tizita();              // Tizita major - pentatonic, characteristic of Ethiopian blues
auto tuning = aether_tuning_ethiopian_tizita_minor();        // Tizita minor - melancholic, introspective

// Bati variants (expressing depth and melancholy)
auto tuning = aether_tuning_ethiopian_bati();                // Bati minor - standard minor pentatonic (most common)
auto tuning = aether_tuning_ethiopian_bati_major();          // Bati major - bright, uplifting variant

// Other main modes
auto tuning = aether_tuning_ethiopian_ambassel();            // Ambassel - pentatonic with flat 2nd
auto tuning = aether_tuning_ethiopian_anchihoye();           // Anchihoye - pentatonic without 3rd degree

Arabic Maqamat

auto tuning = aether_tuning_arabic_rast();    // Quarter-tone flats on 3rd and 7th
auto tuning = aether_tuning_arabic_bayati();  // Half-flat on 2nd degree
auto tuning = aether_tuning_arabic_hijaz();   // Augmented 2nd (dramatic sound)

Indian Ragas

auto tuning = aether_tuning_indian_yaman();   // Raised 4th (Kalyan thaat)

Gamelan (Javanese)

auto tuning = aether_tuning_gamelan_slendro();           // 5-tone scale
auto tuning = aether_tuning_gamelan_slendro_stretched(); // 1210-cent octaves!
auto tuning = aether_tuning_gamelan_pelog();             // 7-tone with unequal intervals

Western Tunings

auto tuning = aether_tuning_just_intonation();         // 5-limit (pure thirds)
auto tuning = aether_tuning_just_intonation_7_limit(); // 7-limit (blues, barbershop)
auto tuning = aether_tuning_equal_temperament();       // Standard 12-TET

API Reference

Graph Management

// Create a DSP graph
AetherGraph* aether_graph_create(float sample_rate, size_t block_size);

// Free a DSP graph
void aether_graph_free(AetherGraph* graph);

// Process audio
AetherResult aether_graph_process(
    AetherGraph* graph,
    const float** inputs, size_t num_inputs,
    float** outputs, size_t num_outputs,
    size_t num_samples
);

Node Creation

// Add nodes to the graph
AetherNodeId aether_graph_add_oscillator(AetherGraph* graph, const AetherTuningTable* tuning);
AetherNodeId aether_graph_add_gain(AetherGraph* graph);
AetherNodeId aether_graph_add_filter(AetherGraph* graph);

Parameter Control

// Set a parameter with optional smoothing
AetherResult aether_graph_set_param(
    AetherGraph* graph,
    AetherNodeId node_id,
    uint32_t param_index,
    float value,
    float ramp_ms  // 0 for instant, >0 for smooth
);

Tuning Tables

// Get frequency for a MIDI note
AetherResult aether_tuning_get_frequency(
    const AetherTuningTable* tuning,
    uint8_t midi_note,
    float* out_frequency
);

// Free a tuning table
void aether_tuning_free(AetherTuningTable* tuning);

Example Plugins

See the examples/ directory for complete JUCE plugin examples:

  • EthiopianSynth - Simple synthesizer with Ethiopian Tizita scale
  • ArabicMaqamSynth - Polyphonic synth with switchable Arabic maqamat
  • WorldMusicSampler - Sampler with tuning system selector

Building the Examples

cd examples/EthiopianSynth

mkdir build && cd build

cmake ..

cmake --build . --config Release

Performance

The bridge has zero runtime overhead compared to pure Rust:

  • No heap allocations in audio thread
  • Lock-free graph updates
  • SIMD-optimized DSP (where applicable)
  • Typical DSP node latency: <100ns per sample

Benchmark (MacBook Pro M1, 48kHz, 64 samples):

  • Parameter update: 51.7ns
  • Oscillator processing: ~5µs per block
  • Full graph (10 nodes): ~50µs per block

Thread Safety

Real-Time Safe Operations

These functions are safe to call from the audio thread:

  • aether_graph_process()
  • aether_graph_set_param()
  • aether_tuning_get_frequency()

Non-Real-Time Operations

These functions must be called from the main/message thread:

  • aether_graph_create() / aether_graph_free()
  • aether_graph_add_*() (node creation)
  • aether_tuning_*() creation and aether_tuning_free()

Troubleshooting

Linking Errors

macOS: Make sure to link against the C++ standard library:

target_link_libraries(YourPlugin PRIVATE "-lc++")

Windows MSVC: Use .lib files, not .dll.lib:

target_link_libraries(YourPlugin PRIVATE aetherdsp_juce_bridge.lib)

Linux: Link against pthread and dl:

target_link_libraries(YourPlugin PRIVATE pthread dl)

Runtime Errors

"Symbol not found": Make sure the dynamic library is in the search path or use static linking.

Crashes on process(): Ensure sample_rate and block_size match your AudioProcessor settings.

License

MIT License - See LICENSE for details.

Contributing

Contributions welcome! Please see CONTRIBUTING.md.

Support


Made with ❤️ in Addis Ababa, Ethiopia