This document outlines the general conventions, design patterns, and usage guidelines for the JonssonicDSP library. These guidelines aim at ensuring consistency, maintainability, and ease of use for contributors and users.
-
Header-Only: The library is designed to be header-only for easy integration.
-
Templated Classes: All major DSP components are implemented as C++ templates. This provides:
- Flexibility in data types (e.g., float, double)
- Customization of algorithmic behavior (e.g., different interpolators, smoothing types, filter orders)
- Compile-time optimization and inlining
Only structural or algorithmic parameters (such as data type, filter order, or interpolation method) should be template parameters. Runtime parameters (such as smoothing time, cutoff frequency, or gain) must always be set via member functions, not as template arguments. This keeps the API flexible and avoids code bloat from excessive template instantiations.
- Global Configuration: Build-time options (e.g., versioning, max channels, sample rate limits) are set in CMakeLists.txt and reflected in jonssonic_config.h. Refer to these files to customize library-wide settings.
Modular Structure: The code is organized in a pyramid of main layers:
- core/ — backbone DSP classes (filters, delays, dynamics, etc.)
- models/ — behavioral models built by combining core classes (e.g., virtual-analog models, reverb algorithms).
- effects/ — complete audio effects built from models and core classes.
- utils/ — helper functions and utilities.
Each layer is further divided into modules by DSP domain or behavioural function e.g.,
- core: Core DSP classes are directly in the
jnsc::namespace (e.g.,jnsc::BiquadFilter,jnsc::DelayLine,jnsc::GainComputer) - models:
jnsc::models::namespace (e.g.,jnsc::models::FeedbackDelayNetwork) - effects:
jnsc::effects::namespace (e.g.,jnsc::effects::Reverb,jnsc::effects::Flanger) - utils:
jnsc::utils::namespace for helper functions
The structure is hierarchical:
- Effects depend on models, core, and utils
- Models depend on core and utils
- Core depends only on core and utils
- Utils is standalone and can be used anywhere
This ensures a clear separation of concerns and minimizes circular dependencies.
- Public/Internal API:
- Public headers are exposed in the
include/directory and are intended for library users. - Internal implementation details are placed in
detail/subfolders (and namespace) at the module level (e.g.,core/filters/detail/) to discourage direct use and avoid polluting the public API. - All
detail/folders are excluded from installation and distribution by the build system (e.g., CMake). - Module-specific limits (e.g., buffer sizes, algorithm parameters) are defined in a
module_limits.hfile inside the relevantdetail/subfolder. These are for internal use only and are not exposed to the public API. - For larger modules with many enum classes, it is good practice to place them in a specific types file, such as
filter_types.h, for clarity and maintainability. These types should be part of the public API. - Module-specific limits (e.g., buffer sizes, algorithm parameters) are defined in files named after the module, such as
filter_limits.h, inside the relevantdetail/subfolder. These are for internal use only and are not exposed to the public API.
- Public headers are exposed in the
Processor Classes:
- All processor classes provide internal multichannel support.
- Processor classes are not thread-safe unless otherwise documented.
- All processor classes provide a prepare() method which takes care of all memory allocation. All othere methods should be kept real-time safe.
- Runtime parameters (e.g., smoothing time, cutoff frequency) are set via dedicated setter methods, not in prepare().
- All processor classes provide a reset() method to clear internal state.
- All processor classes provide a processSample() and when appropriate processBlock(), for sample by sample and block processing, respectively.
- No Copy/Move: Most processor classes delete copy and move constructors/assignment to avoid accidental state sharing.
-
Namespaces:
- Public API uses the
jnsc::namespace for core classes, withjnsc::models::,jnsc::effects::, andjnsc::utils::for higher-level components. - Internal helpers and implementation details are always in
detailnamespace (e.g.,jnsc::detail)
- Public API uses the
-
Type Names:
- Use
snake_casefor folder and file names. - Use
camelCasefor variable and function names. - Use
PascalCasefor class and type names.
- Use
- Doxygen: All public classes, methods, and parameters are documented with Doxygen comments.
- General Guidelines: This file (GUIDELINES.md) documents non-class-specific patterns and best practices.
- Assertions: Use
assertfor internal invariants and precondition checks. - Clamping: All parameter setters clamp values to safe, documented ranges.
- Template Specialization: Use template specializations for algorithmic variations (e.g., different smoothing types).
- Test Files:
- Test files may use
using namespacefor brevity. - Tests should ideally cover all public API and edge cases.
- Test files may use