TonnSDK Best Practices

This document outlines best practices and recommendations for getting optimal results with TonnSDK in your applications.

Table of Contents

Audio Input Quality

The quality of the final mix heavily depends on the input audio quality. Follow these recommendations:

Sample Rate and Bit Depth

  • Use Consistent Sample Rates: Ensure all tracks have the same sample rate
  • Recommended Sample Rates: 44.1 kHz or 48 kHz for standard projects
  • Bit Depth: 32-bit floating point is optimal for internal processing

Gain Staging

  • Avoid Clipping: Input tracks should not be clipped (exceeding ±1.0)

Audio Content Quality

  • Minimize Bleed: Use tracks with minimal bleed from other instruments
  • Clean Recordings: Minimize background noise and unwanted artifacts
  • Phase Coherence: Check multi-miked sources for phase issues before processing

GPU Mode Selection (v1.6)

Version 1.6 introduces GPU-accelerated mixing for faster processing and higher quality mixes. Choose the appropriate mode based on your hardware and requirements.

When to Use Each Mode

Use CPU_STATIC (default) when: - No NVIDIA GPU is available - Running in environments without CUDA support (e.g., CI/CD, lightweight containers) - Broad compatibility is needed across different hardware - Processing small or simple mixes where GPU overhead isn't justified

Use GPU_STATIC when: - An NVIDIA GPU with CUDA 12.2+ is available - You want faster processing times and higher quality mixes - Running in a production environment with GPU hardware (e.g., cloud instances with GPUs) - Processing complex mixes with many tracks

GPU Mode Example

tonn::TonnSDK sdk(44100.0f, tonn::MusicalStyle::ROCK_INDIE);
sdk.initialize(licenseKey);

// Check hardware and select mode
sdk.setMixingModel(tonn::MixingModel::GPU_STATIC);

// Add tracks and process as normal
sdk.addTrack("drums.wav", drumSettings);
sdk.addTrack("bass.wav", bassSettings);

tonn::MixResult result = sdk.process();
// If GPU is unavailable, the SDK falls back to CPU automatically

Docker GPU Setup

For GPU mode in Docker, use the --gpus flag:

docker run --gpus all -e TONNSDK_LICENSE_KEY="your_key" tonnsdk:1.6.1

Creative Compression

Genre-specific stylized compression automatically applies professional compression presets based on musical style and instrument type.

When to Use Creative Compression

Enable Creative Compression (default) when: - You want genre-appropriate compression character (punchy drums for rock, tight bass for electronic, etc.) - Processing complete productions where consistent compression style is desired - Working with specific genres that benefit from stylized compression - Creating final mixes that need professional compression polish

Disable Creative Compression when: - You want a more transparent, natural sound without genre-specific coloring - The source material already has compression applied - You plan to apply your own compression in a DAW after exporting - Working with orchestral or acoustic material where transparency is preferred

Creative Compression Example

tonn::TonnSDK sdk(44100.0f, tonn::MusicalStyle::ROCK_INDIE);
sdk.setLicenseKey(licenseKey);

// Creative compression is ON by default
std::cout << "Creative compression: " 
          << (sdk.isCreativeCompressionEnabled() ? "ON" : "OFF") << std::endl;

// Disable for transparent processing
sdk.setCreativeCompressionEnabled(false);

// Or explicitly enable for stylized compression
sdk.setCreativeCompressionEnabled(true);

// Process tracks - compression will be applied based on setting
tonn::MixResult result = sdk.process();

Instrument-Specific Compression

Creative compression applies different presets based on instrument type:

Instrument Group Compression Character
VOCAL_GROUP Medium ratio, moderate attack for presence and clarity
DRUMS_GROUP, KICK_GROUP, SNARE_GROUP Fast attack, punchy release for impact
BASS_GROUP Tight control, consistent low-end
E_GUITAR_GROUP, ACOUSTIC_GUITAR_GROUP Musical compression for sustain
SYNTH_GROUP, KEYS_GROUP Subtle compression, preserve dynamics
PERCS_GROUP Fast, transparent limiting

Best Practice: Check FX Chain JSON

Use the FX Chain JSON to verify what compression was applied:

tonn::MixResult result = sdk.process();

// Parse FX chain to see compression settings
std::cout << result.fxChainsJson << std::endl;
// Look for "type": "COMPRESSOR", "subtype": "CREATIVE"

FX Chain JSON Export

The new fxChainsJson field in MixResult provides a complete ordered export of all effects applied to each track.

When to Use FX Chain Export

Use FX Chain JSON for: - DAW integration: Import exact settings into your DAW - Mix analysis: Understand what processing was applied - Preset generation: Save and reuse effect chains - A/B comparison: Compare settings between different mixes - Documentation: Record exactly how a mix was processed - Future bidirectional import: Foundation for importing settings back

FX Chain JSON Example

tonn::MixResult result = sdk.process();

// Check if FX chain data is available
if (!result.fxChainsJson.empty()) {
    // Parse and display
    json fxChains = json::parse(result.fxChainsJson);
    std::cout << fxChains.dump(2) << std::endl;  // Pretty print

    // Save to file
    std::ofstream fxFile("fx_chain_settings.json");
    fxFile << fxChains.dump(2);
    fxFile.close();
}

Understanding the FX Chain Structure

Each track's FX chain contains 9 ordered slots:

  1. LOW_CUT (order: 0) - High-pass filter applied based on instrument type
  2. GAIN (PRE_GAIN) (order: 1) - Loudness normalization before processing
  3. EQ (order: 2) - 6-band parametric EQ
  4. COMPRESSOR (CORRECTIVE) (order: 3) - Masking minimization compression
  5. COMPRESSOR (CREATIVE) (order: 4) - Genre-specific stylized compression
  6. PAN (order: 5) - Stereo positioning
  7. GAIN (POST_GAIN) (order: 6) - Final level adjustment
  8. REVERB (order: 7) - Reverb send amount
  9. SIDECHAIN_COMPRESSOR (order: 8) - Sidechain ducking

Best Practice: Track Names

Always pass track names for clearer FX chain output:

// Good: Pass the actual track name
sdk.addTrackFromBuffer(audioBuffer, settings, "lead_vocals.wav");

// Result in FX chain JSON: "trackName": "lead_vocals.wav"

// Avoid: Using default (results in "trackName": "buffer_track")
sdk.addTrackFromBuffer(audioBuffer, settings);

Skip Quiet Tracks

The skipQuietTracks parameter provides graceful handling of quiet or silent audio tracks, perfect for batch processing workflows.

When to Enable Skip Quiet Tracks

Enable skipQuietTracks (true) when: - Processing large collections of tracks where some might be silent or extremely quiet - Doing batch processing where you want to skip problematic tracks automatically - Working with user-generated content that might include silent/empty files - Processing exported tracks from DAWs where some tracks might be muted/empty

Keep skipQuietTracks disabled (false) when: - You need to guarantee every track is processed or get an explicit error - Working with curated content where all tracks should be valid - You want to handle quiet tracks with custom logic - Maximum control over error handling is required

Example: Content Processing Pipeline

// For user-uploaded content - enable skip quiet tracks
tonn::TonnSDK sdk(44100.0f, tonn::MusicalStyle::POP, true);

std::vector<std::string> userFiles = getUserUploadedFiles();
int processedCount = 0;
int skippedCount = 0;

for (const auto& file : userFiles) {
    size_t tracksBefore = sdk.getTrackCount();
    try {
        sdk.addTrack(file, getSettingsForFile(file));
        if (sdk.getTrackCount() > tracksBefore) {
            processedCount++;
        } else {
            skippedCount++;  // Track was skipped due to being quiet
        }
    } catch (const std::exception& e) {
        // Handle other errors (file not found, format issues, etc.)
        std::cerr << "Error processing " << file << ": " << e.what() << std::endl;
    }
}

std::cout << "Processed: " << processedCount << " tracks, Skipped: " << skippedCount << " quiet tracks" << std::endl;

Pre-gain Information Access

Pre-gain preservation functionality makes loudness normalization data accessible in results.

Using Pre-gain Information

tonn::MixResult result = sdk.process();

// Log pre-gain information for each track
for (size_t i = 0; i < result.mixTrackSettings.size(); ++i) {
    float preGainDb = result.mixTrackSettings[i].getPreGain();
    float preGainLinear = std::pow(10.0f, preGainDb / 20.0f);

    std::cout << "Track " << i << ":" << std::endl;
    std::cout << "  Pre-gain: " << preGainDb << " dB (" << preGainLinear << "x)" << std::endl;
    std::cout << "  Loudness normalization applied: " << (preGainDb != 0.0f ? "Yes" : "No") << std::endl;
}

Best Practices for Pre-gain Data

  • Save pre-gain values for reprocessing workflows to maintain consistent loudness
  • Use pre-gain for metering - tracks with large pre-gain adjustments may need source-level attention
  • Apply pre-gain in stem exports to maintain the normalized loudness in individual stems
  • Monitor extreme pre-gain values (> ±12 dB) as they may indicate source material issues

Track Organization

Proper track organization is critical for optimal results:

Group Types

  • Accurate Classification: Always assign the most appropriate GroupType for each track
  • Subdivide Instruments: Use specific group types (e.g., KICK_GROUP, SNARE_GROUP) for more control
  • Group Similar Instruments: When using subgroup mixing, group similar instruments together

Presence Settings

  • Lead Elements: Use PresenceSetting::LEAD for main vocal or solo instruments
  • Background Elements: Use PresenceSetting::BACKGROUND for ambience, pads, or backing elements
  • Most Instruments: Use PresenceSetting::NORMAL for most typical instruments

Example Organization Strategy

// Drums
kickSettings.setGroupType(tonn::GroupType::KICK_GROUP);
snareSettings.setGroupType(tonn::GroupType::SNARE_GROUP);
overheadsSettings.setGroupType(tonn::GroupType::CYMBALS_GROUP);
roomSettings.setGroupType(tonn::GroupType::DRUMS_GROUP);

// Bass
bassSettings.setGroupType(tonn::GroupType::BASS_GROUP);

// Guitars
rhythmGuitarSettings.setGroupType(tonn::GroupType::E_GUITAR_GROUP);
leadGuitarSettings.setGroupType(tonn::GroupType::E_GUITAR_GROUP);
leadGuitarSettings.setPresence(tonn::PresenceSetting::LEAD);

// Vocals
leadVocalSettings.setGroupType(tonn::GroupType::VOCAL_GROUP);
leadVocalSettings.setPresence(tonn::PresenceSetting::LEAD);
backingVocalSettings.setGroupType(tonn::GroupType::BACKING_VOX_GROUP);

Settings-Only Processing

The TonnSDK provides a powerful optimization mode through the settingsOnly parameter in the process() method. This feature can dramatically improve performance for specific workflows.

When to Use Settings-Only Mode

Use settingsOnly=true when you:

  • Need fast parameter calculation: 10-50x faster execution than full processing
  • Want to generate presets: Calculate optimized gain, EQ, compression, and panning settings
  • Are working in memory-constrained environments: Significantly lower memory usage
  • Need parameter inspection: Analyze what settings the SDK would apply
  • Are building two-stage workflows: Calculate settings first, apply manually later

Settings-Only vs Full Processing

// Settings-only mode: Fast parameter calculation
tonn::MixResult settingsResult = tonnSDK.process(true);
// Returns optimized settings but no audio data
// - settingsResult.mixTrackSettings contains all optimized parameters
// - settingsResult.audio_x and stems are empty
// - settingsResult.trackLengthInSecs is still calculated

// Full processing mode: Complete audio processing
tonn::MixResult fullResult = tonnSDK.process(false); // or process()
// Returns complete results including processed audio
// - All audio processing applied
// - Final mix and stems generated
// - Optimized settings included

What's Included in Settings-Only Mode

When settingsOnly=true, the SDK calculates and returns:

  • Optimized EQ parameters: Frequency bands, gains, and Q factors
  • Compression settings: Threshold, ratio, attack, and release times
  • Panning angles: Calculated stereo positioning
  • Loudness normalization gains: Level adjustments for each track
  • Track length: Duration information
  • Track names: Corresponding to the input tracks

Example Workflow: Two-Stage Processing

// Stage 1: Fast settings calculation
std::cout << "Calculating optimized parameters..." << std::endl;
tonn::MixResult settingsResult = tonnSDK.process(true);

// Inspect or modify the calculated settings
for (size_t i = 0; i < settingsResult.mixTrackSettings.size(); ++i) {
    auto& settings = settingsResult.mixTrackSettings[i];

    // Log the optimized parameters
    std::cout << "Track " << i << " optimized settings:" << std::endl;
    std::cout << "  Pan angle: " << settings.getPanAngle() << " degrees" << std::endl;
    std::cout << "  Gain: " << settings.getGain() << "x" << std::endl;
    std::cout << "  Compressor threshold: " << settings.getCompressorThreshold() << " dB" << std::endl;

}

// Stage 2: Apply settings manually or use them in another processing pipeline
// ... custom processing using the optimized parameters ...

Command-Line Integration

For applications with command-line interfaces, provide easy access to settings-only mode:

// Example command-line argument parsing
bool settingsOnly = false;
for (int i = 1; i < argc; ++i) {
    std::string arg = argv[i];
    if (arg == "--settings-only" || arg == "-s") {
        settingsOnly = true;
        std::cout << "Settings-only mode: Computing parameters without audio processing" << std::endl;
        break;
    }
}

tonn::MixResult result = tonnSDK.process(settingsOnly);

Performance Benefits

Settings-only mode provides significant advantages:

  • Speed: 10-50x faster than full processing
  • Memory: Much lower peak memory usage (no audio buffers for final processing)
  • CPU: Minimal CPU usage during the settings calculation phase
  • Scalability: Handle larger sessions that might not fit in memory for full processing

Use Cases

Preset Generation Systems:

// Generate mixing presets for different genres
for (const auto& genre : genres) {
    tonnSDK.setMusicalStyle(genre);
    auto presetSettings = tonnSDK.process(true);
    savePreset(genre, presetSettings.mixTrackSettings);
}

Interactive Mixing Applications:

// Quick parameter preview before full processing
auto quickSettings = tonnSDK.process(true);
if (userApprovesSettings(quickSettings)) {
    auto finalMix = tonnSDK.process(false); // Full processing
    saveMix(finalMix);
}

Batch Analysis:

// Analyze multiple projects quickly
for (const auto& project : projects) {
    loadProject(project, tonnSDK);
    auto analysis = tonnSDK.process(true);
    logProjectAnalysis(project, analysis.mixTrackSettings);
}

AudioReprocessor Best Practices

The AudioReprocessor feature provides powerful offline reprocessing capabilities. Follow these best practices to get optimal results:

1. Stem Management

Preserve Original Quality:

// Always load stems at their original quality
// Avoid re-encoding or quality loss before loading
bool loadOriginalStem(const std::string& stemPath) {
    // Load at original sample rate and bit depth
    return reprocessor.loadStem(stemPath, originalSettings);
}

Organize Stem Storage:

// Use organized directory structure for stems
struct StemLibrary {
    std::string projectDir;
    std::string stemsDir;
    std::string settingsDir;

    StemLibrary(const std::string& project) 
        : projectDir(project) {
        stemsDir = projectDir + "/stems/";
        settingsDir = projectDir + "/settings/";

        // Create directories if they don't exist
        createDirectoryIfNotExists(stemsDir);
        createDirectoryIfNotExists(settingsDir);
    }

    void saveStems(const tonn::TonnSDK& sdk) {
        sdk.saveOriginalStems(stemsDir, true);
    }
};

2. Settings Preservation and Versioning

Save Original Settings:

// Always preserve original settings for reference
struct SettingsVersion {
    std::vector<tonn::MixTrackSettings> settings;
    std::string version;
    std::string timestamp;
    std::string description;
};

class SettingsManager {
private:
    std::vector<SettingsVersion> settingsHistory;

public:
    void saveSettingsVersion(const std::vector<tonn::MixTrackSettings>& settings,
                            const std::string& description) {
        SettingsVersion version;
        version.settings = settings;
        version.version = "v" + std::to_string(settingsHistory.size() + 1);
        version.timestamp = getCurrentTimestamp();
        version.description = description;

        settingsHistory.push_back(version);

        // Save to JSON for persistence
        saveSettingsToFile(version);
    }

    std::vector<tonn::MixTrackSettings> getSettingsVersion(const std::string& version) {
        auto it = std::find_if(settingsHistory.begin(), settingsHistory.end(),
            [&version](const SettingsVersion& sv) { return sv.version == version; });

        if (it != settingsHistory.end()) {
            return it->settings;
        }

        return {}; // Return empty if not found
    }
};

3. Parameter Modification Strategies

Incremental Changes:

// Make incremental changes for better control
class ParameterAdjuster {
public:
    static tonn::MixTrackSettings adjustCompression(const tonn::MixTrackSettings& base,
                                                   float ratioMultiplier,
                                                   float thresholdOffset) {
        tonn::MixTrackSettings adjusted = base;

        // Apply incremental changes
        float currentRatio = base.getCompressorRatio();
        float currentThreshold = base.getCompressorThreshold();

        adjusted.setCompressorRatio(currentRatio * ratioMultiplier);
        adjusted.setCompressorThreshold(currentThreshold + thresholdOffset);

        return adjusted;
    }

    static tonn::MixTrackSettings adjustEQ(const tonn::MixTrackSettings& base,
                                          int bandIndex,
                                          float gainChange) {
        tonn::MixTrackSettings adjusted = base;

        auto currentGains = base.getEQGains();
        if (bandIndex < currentGains.size()) {
            currentGains[bandIndex] += gainChange;
            adjusted.setEQGains(currentGains);
        }

        return adjusted;
    }
};

Batch Parameter Changes:

// Group related parameter changes together
struct ParameterBatch {
    std::vector<std::pair<size_t, tonn::MixTrackSettings>> changes;
    std::string description;

    void addChange(size_t trackIndex, const tonn::MixTrackSettings& newSettings) {
        changes.emplace_back(trackIndex, newSettings);
    }
};

class BatchProcessor {
public:
    tonn::ReprocessResult applyBatch(tonn::AudioReprocessor& reprocessor,
                                    const std::vector<tonn::MixTrackSettings>& baseSettings,
                                    const ParameterBatch& batch) {
        // Start with base settings
        std::vector<tonn::MixTrackSettings> modifiedSettings = baseSettings;

        // Apply all changes in the batch
        for (const auto& [trackIndex, newSettings] : batch.changes) {
            if (trackIndex < modifiedSettings.size()) {
                modifiedSettings[trackIndex] = newSettings;
            }
        }

        return reprocessor.reprocess(modifiedSettings);
    }
};

4. Error Handling and Validation

Comprehensive Validation:

class ReprocessValidator {
public:
    struct ValidationResult {
        bool isValid = true;
        std::vector<std::string> warnings;
        std::vector<std::string> errors;
    };

    static ValidationResult validateReprocessSetup(const tonn::AudioReprocessor& reprocessor,
                                                  const std::vector<tonn::MixTrackSettings>& settings) {
        ValidationResult result;

        // Check stem count matches settings count
        if (reprocessor.getStemCount() != settings.size()) {
            result.isValid = false;
            result.errors.push_back("Stem count doesn't match settings count");
        }

        // Validate each setting
        for (size_t i = 0; i < settings.size(); ++i) {
            validateSingleSetting(settings[i], result, i);
        }

        return result;
    }

private:
    static void validateSingleSetting(const tonn::MixTrackSettings& setting,
                                     ValidationResult& result,
                                     size_t index) {
        // Check for extreme parameter values
        if (setting.getCompressorRatio() > 20.0f) {
            result.warnings.push_back("Track " + std::to_string(index) + 
                                     ": Very high compression ratio");
        }

        if (std::abs(setting.getPanAngle()) > 90.0f) {
            result.errors.push_back("Track " + std::to_string(index) + 
                                   ": Pan angle out of range");
            result.isValid = false;
        }
    }
};

5. Performance Optimization

Stem Caching:

class StemCache {
private:
    std::map<std::string, std::pair<std::vector<std::vector<float>>, tonn::MixTrackSettings>> cache;
    size_t maxCacheSize = 10; // Limit cache size

public:
    bool loadStemFromCache(tonn::AudioReprocessor& reprocessor, const std::string& stemPath) {
        auto it = cache.find(stemPath);
        if (it != cache.end()) {
            return reprocessor.loadStem(it->second.first, it->second.second, 
                                       getTrackNameFromPath(stemPath));
        }
        return false;
    }

    void cacheStem(const std::string& stemPath,
                   const std::vector<std::vector<float>>& audioBuffer,
                   const tonn::MixTrackSettings& settings) {
        // Implement LRU cache logic
        if (cache.size() >= maxCacheSize) {
            cache.erase(cache.begin()); // Simple eviction
        }

        cache[stemPath] = {audioBuffer, settings};
    }
};

Efficient Workflow:

// Reuse AudioReprocessor instances when possible
class EfficientReprocessor {
private:
    std::unique_ptr<tonn::AudioReprocessor> reprocessor;
    std::vector<std::string> loadedStems;

public:
    bool setupForProject(const std::vector<std::string>& stemPaths,
                        const std::vector<tonn::MixTrackSettings>& settings) {
        // Only recreate if necessary
        if (!reprocessor || stemPaths != loadedStems) {
            reprocessor = std::make_unique<tonn::AudioReprocessor>(44100.0f);

            // Clear and reload stems
            reprocessor->clearStems();
            for (size_t i = 0; i < stemPaths.size() && i < settings.size(); ++i) {
                if (!reprocessor->loadStem(stemPaths[i], settings[i])) {
                    return false;
                }
            }

            loadedStems = stemPaths;
        }

        return true;
    }

    tonn::ReprocessResult quickReprocess(const std::vector<tonn::MixTrackSettings>& newSettings) {
        return reprocessor->reprocess(newSettings);
    }
};

6. Quality Control

A/B Comparison Framework:

class QualityController {
public:
    struct ComparisonResult {
        std::pair<std::vector<float>, std::vector<float>> originalMix;
        std::pair<std::vector<float>, std::vector<float>> reprocessedMix;
        float lufsOriginal;
        float lufsReprocessed;
        std::string analysisReport;
    };

    ComparisonResult compareVersions(const tonn::ReprocessResult& original,
                                   const tonn::ReprocessResult& reprocessed) {
        ComparisonResult result;
        result.originalMix = original.mixedAudio;
        result.reprocessedMix = reprocessed.mixedAudio;

        // Analyze loudness (simplified)
        result.lufsOriginal = calculateLUFS(original.mixedAudio);
        result.lufsReprocessed = calculateLUFS(reprocessed.mixedAudio);

        // Generate analysis report
        result.analysisReport = generateAnalysisReport(result);

        return result;
    }

private:
    float calculateLUFS(const std::pair<std::vector<float>, std::vector<float>>& audio) {
        // Implement LUFS calculation
        // This is a simplified placeholder
        return -23.0f; // LUFS
    }

    std::string generateAnalysisReport(const ComparisonResult& result) {
        std::stringstream report;
        report << "LUFS Comparison:\n";
        report << "Original: " << result.lufsOriginal << " LUFS\n";
        report << "Reprocessed: " << result.lufsReprocessed << " LUFS\n";
        report << "Difference: " << (result.lufsReprocessed - result.lufsOriginal) << " LUFS\n";
        return report.str();
    }
};

7. Workflow Integration

Template System:

class MixTemplate {
public:
    struct Template {
        std::string name;
        std::string genre;
        std::map<tonn::GroupType, tonn::MixTrackSettings> defaultSettings;
        std::string description;
    };

    static Template createRockTemplate() {
        Template rockTemplate;
        rockTemplate.name = "Modern Rock";
        rockTemplate.genre = "Rock";
        rockTemplate.description = "Settings optimized for modern rock productions";

        // Define default settings for each instrument group
        tonn::MixTrackSettings drumSettings(tonn::GroupType::DRUMS_GROUP,
                                          tonn::PresenceSetting::NORMAL,
                                          tonn::MusicalStyle::ROCK_INDIE);
        drumSettings.setCompressorRatio(4.0f);
        drumSettings.setCompressorThreshold(-12.0f);

        rockTemplate.defaultSettings[tonn::GroupType::DRUMS_GROUP] = drumSettings;

        // Add other instrument groups...

        return rockTemplate;
    }

    std::vector<tonn::MixTrackSettings> applyTemplate(const Template& tmpl,
                                                     const std::vector<tonn::GroupType>& trackGroups) {
        std::vector<tonn::MixTrackSettings> settings;

        for (const auto& groupType : trackGroups) {
            auto it = tmpl.defaultSettings.find(groupType);
            if (it != tmpl.defaultSettings.end()) {
                settings.push_back(it->second);
            } else {
                // Use default settings for unknown groups
                settings.emplace_back(groupType, tonn::PresenceSetting::NORMAL,
                                    tonn::MusicalStyle::ROCK_INDIE);
            }
        }

        return settings;
    }
};

8. Best Practice Summary

  • Always preserve original stems and settings for reference and rollback capability
  • Use incremental parameter changes rather than extreme adjustments
  • Implement comprehensive error handling and validation
  • Cache stems when possible to improve performance for iterative workflows
  • Group related parameter changes into batches for efficiency
  • Validate parameter ranges before reprocessing to avoid processing failures
  • Implement A/B comparison tools for quality control
  • Use template systems for consistent results across projects
  • Monitor resource usage when processing large numbers of stems
  • Document all parameter changes for collaborative workflows

Performance Optimization

TonnSDK is designed for non-realtime processing, but performance is still important:

Primary Performance Strategy: Settings-Only Mode

Start with Settings-Only Processing: For maximum performance, use the settingsOnly=true parameter described in the Settings-Only Processing section. This provides 10-50x faster execution and is ideal for:

  • Parameter calculation and inspection
  • Preset generation workflows
  • Memory-constrained environments
  • Two-stage processing pipelines

Processing Strategy

  • Background Processing: Run TonnSDK in a background thread to avoid blocking UI
  • Progress Reporting: Implement progress reporting for long processing operations
  • Caching Strategy: Cache processed results for previously processed content

Memory Efficiency

  • Buffer Management: Release audio buffers after adding them to the SDK
  • Batch Processing: For large projects, process tracks in batches if memory is constrained
  • Release Resources: Call destructors properly to release SDK resources when done

Processing Large Sessions

  • Subgroup Approach: Use the advanced subgroup mixing approach for large sessions
  • Incremental Processing: Process subgroups one at a time and save stems
  • Parallel Processing: Consider multi-threading for processing multiple subgroups in parallel
// Example of multi-threaded subgroup processing
std::vector<std::future<SubmixResult>> futures;
for (const auto& [groupType, tracks] : trackGroups) {
    futures.push_back(std::async(std::launch::async, [&]() {
        // Process group in separate thread
        tonn::TonnSDK groupProcessor(static_cast<float>(sampleRate), style);
        // ... add tracks and process ...
        return SubmixResult{...};
    }));
}

// Collect results when complete
std::vector<SubmixResult> submixes;
for (auto& future : futures) {
    submixes.push_back(future.get());
}

Error Handling

Robust error handling is essential for professional applications:

Error Detection

  • Check Return Values: Always check return values from SDK methods
  • License Validation: Verify license validity before processing
  • Input Validation: Validate audio input parameters before processing

Graceful Recovery

  • Detailed Error Messages: Use getLastErrorMessage() to get detailed error information
  • Fallback Strategies: Implement fallback processing if SDK operations fail
  • User Feedback: Provide clear error messages to users with suggestion for resolution

Example Error Handling Approach

try {
    // Attempt to process the mix
    tonn::MixResult result = tonnSDK.process();

    // Check if processing succeeded
    if (result.audio_x.first.empty() || result.audio_x.second.empty()) {
        std::cerr << "Processing failed: " << tonnSDK.getLastErrorMessage() << std::endl;
        // Try fallback strategy or inform user
    }
} catch (const std::exception& e) {
    // Handle unexpected exceptions
    std::cerr << "Critical error during processing: " << e.what() << std::endl;

    // Log detailed information
    logError("TonnSDK processing failed", e.what());

    // Inform user and suggest recovery actions
    notifyUser("Mix processing failed", "Please try with fewer tracks or contact support");
}

Memory Management

Efficient memory management is critical when working with audio data:

Resource Lifecycle

  • Clean Initialization: Properly initialize all SDK objects
  • Proper Cleanup: Destroy SDK instances when no longer needed
  • Scope Management: Use smart pointers and RAII principles where applicable

Large Buffer Handling

  • Buffer Reuse: Reuse buffers when possible to avoid repeated allocations
  • Buffer Format: Use the most efficient buffer format for your application
// Example of reusing buffers
std::vector<std::vector<float>> reusableBuffer;

for (const auto& track : tracks) {
    // Clear the buffer but retain capacity
    for (auto& channel : reusableBuffer) {
        channel.clear();
    }

    // Load new audio data into existing buffer
    loadAudioIntoBuffer(track.filePath, reusableBuffer);

    // Use the buffer
    tonnSDK.addTrackFromBuffer(reusableBuffer, track.settings);
}

Cross-Platform Considerations

TonnSDK is cross-platform, but some platform-specific optimizations may be needed:

macOS

  • Universal Binary Support: TonnSDK supports both Intel and Apple Silicon
  • Architecture Selection: Request the appropriate build for your target architecture
  • Dependency Management: Install x86_64 dependencies specifically when targeting Intel
# For macOS with both architectures
set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64")

Windows

  • Runtime Libraries: Match the runtime library setting with the SDK (typically /MD)
  • Path Handling: Use proper path handling for Windows filesystem
  • DLL Distribution: Ensure TonnSDK DLLs are properly distributed with your application

Linux

  • Distribution Compatibility: Test with multiple Linux distributions
  • Dependency Management: Use standard package managers or bundle dependencies
  • Audio System Integration: Consider JACK, ALSA, or PulseAudio integration needs

Advanced Workflow Strategies

For professional applications, consider these advanced strategies:

DAW Integration Patterns

  • Processing Module: Implement TonnSDK as a processing module or plugin
  • Offline Rendering: Provide an offline rendering option for time-consuming mixes
  • Preset Management: Allow saving and loading of processing presets

Track Analysis

  • Pre-analysis: Analyze tracks before mixing to determine optimal settings
  • Intelligent Grouping: Group tracks automatically based on audio content
  • Metadata Use: Leverage track metadata to inform automatic classification

Mixing Template Approaches

Create mixing templates for common scenarios:

// Example mixing template for a rock song
void applyRockMixTemplate(tonn::TonnSDK& sdk) {
    // Load template settings from configuration
    json template = loadJsonConfig("templates/rock_mix.json");

    // Apply template settings to current tracks
    for (size_t i = 0; i < trackCount; ++i) {
        if (trackGroups[i] == tonn::GroupType::DRUMS_GROUP) {
            // Apply drum-specific template settings
            applyTemplateToTrack(sdk, i, template["drums"]);
        }
        // Handle other instrument groups...
    }
}

Real-world Production Workflows

  • Stem Printing: Generate processed stems for further manual tweaking
  • Two-stage Approach: Use TonnSDK for initial mix, then fine-tune manually
  • A/B Comparison: Provide tools to compare TonnSDK output with reference mixes

By following these best practices, you can achieve optimal results with TonnSDK in your applications, ensuring reliable, high-quality audio processing across a wide range of scenarios.