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
- Track Organization
- GPU Mode Selection
- Creative Compression
- FX Chain JSON Export
- Settings-Only Processing
- AudioReprocessor Best Practices
- Performance Optimization
- Error Handling
- Memory Management
- Cross-Platform Considerations
- Advanced Workflow Strategies
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¶
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:2.0.0
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:
- LOW_CUT (order: 0) - High-pass filter applied based on instrument type
- GAIN (PRE_GAIN) (order: 1) - Loudness normalization before processing
- EQ (order: 2) - 6-band parametric EQ
- COMPRESSOR (CORRECTIVE) (order: 3) - Masking minimization compression
- COMPRESSOR (CREATIVE) (order: 4) - Genre-specific stylized compression
- PAN (order: 5) - Stereo positioning
- GAIN (POST_GAIN) (order: 6) - Final level adjustment
- REVERB (order: 7) - Reverb send amount
- 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
GroupTypefor 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::LEADfor main vocal or solo instruments - Background Elements: Use
PresenceSetting::BACKGROUNDfor ambience, pads, or backing elements - Most Instruments: Use
PresenceSetting::NORMALfor 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.
Post-Production Best Practices¶
Dialogue loudness targets by format¶
Align expectations with the selected PostProdFormat and verify using PostProdResult (integrated program loudness, dialogue-specific metrics where applicable, and compliance flags):
| Format | Dialogue / program targeting (typical) |
|---|---|
FILM |
About -24 LUFS integrated for film-style delivery. |
TV |
About -23 LUFS (EBU R128-style television loudness). |
STREAMING |
About -27 LUFS for post-production streaming delivery. |
PODCAST uses -16 LUFS for podcast-oriented mastering. ADMIX and BROADCAST follow broadcast-style norms (e.g. -23 LUFS for EBU R128-oriented modes). Always confirm against the exact specification from your client, broadcaster, or platform; use getTargetLoudness(), getMaxTruePeak(), and getLoudnessTolerance() on PostProductionSDK where you need numeric targets in code.
EBU R128 compliance tips¶
- Use
TVorBROADCASTwhen the deliverable must align with EBU R128 (integrated loudness and true-peak limits as implemented for that format in the SDK). - After processing, treat
meetsLoudnessSpec,meetsPeakSpec, and any program/dialogue LRA flags inPostProdResultas your first automated gate; follow with an external meter or QC tool for legal compliance sign-off. - Keep true peak under the format maximum reported by the SDK; avoid upstream inter-sample peaks by leaving modest headroom in source stems when possible.
Ducking preset selection¶
| Preset | When to use |
|---|---|
LIGHT |
Gentle reduction (e.g. ambiences, subtle beds) where music should barely yield to dialogue. |
MEDIUM |
General film/TV balance for score and effects under dialogue. |
HEAVY |
Dense mixes or loud beds where dialogue must dominate clearly. |
AD_MIX |
Commercials and promos — strong, fast ducking so voiceover cuts through music and SFX. |
Use NONE or duckingEnabled = false only when the stem should not respond to the sidechain (e.g. isolated M&E exports handled in a separate pass).
Dialogue enhancement modes¶
PRESERVE: Minimal processing; use when the production dialogue is already balanced and you want a natural timbre.ENHANCE: Standard clarity enhancement for typical dialogue and narration in long-form content.AD_ENHANCED: Stronger processing for commercial voiceover and other cases where prominence and intelligibility over dense beds are required.
Sample rate¶
Use 48 kHz for video and broadcast sessions so stems match picture workflows and downstream encoders. Keep all stems at a single sample rate for a given PostProductionSDK instance.
Track organization for ducking¶
- Split dialogue from music and SFX on separate tracks (or stems). Ducking and sidechain detection depend on clean role separation; a single interleaved “mix-minus” file limits automatic ducking quality.
- Mark exactly one primary dialogue or voiceover chain as the ducking sidechain per mix pass unless your workflow explicitly requires multiple sidechains (if supported by your integration).
- Reserve
AMBIENCEandFOLEYon their own tracks with lighter ducking presets where appropriate so room tone does not collapse unnaturally when dialogue appears.