Style Guide#
This document defines the coding standards, naming conventions, and best practices for TorchFX development.
Naming Conventions#
Classes#
Filter Classes#
Pattern: <Type><FilterName>
Use
Hiprefix for high-pass filters:HiButterworth,HiEllipticUse
Loprefix for low-pass filters:LoButterworth,LoEllipticBase classes have no prefix:
Butterworth,Elliptic
# Good
class HiButterworth(Butterworth):
"""High-pass Butterworth filter."""
pass
class LoButterworth(Butterworth):
"""Low-pass Butterworth filter."""
pass
# Bad
class HighPassButterworth(Butterworth): # Don't use full words
pass
class ButterworthHP(Butterworth): # Don't use suffix
pass
Effect Classes#
Pattern: <EffectName>
Use descriptive noun names:
Reverb,Delay,ChorusNo prefix or suffix needed
# Good
class Reverb(FX):
"""Reverb effect."""
pass
# Bad
class ReverbEffect(FX): # Don't add "Effect" suffix
pass
Parameters#
Frequency Parameters#
cutoff: For lowpass/highpass/shelving filters (cutoff frequency)frequency: For parametric EQ and peaking filters (center frequency)
# Good - Lowpass filter
class LoButterworth:
def __init__(self, cutoff: float, order: int = 5, fs: int | None = None):
self.cutoff = cutoff
# Good - Parametric EQ
class ParametricEQ:
def __init__(self, frequency: float, q: float, gain: float, fs: int | None = None):
self.frequency = frequency
# Bad - Mixed usage
class LoButterworth:
def __init__(self, frequency: float, ...): # Use cutoff for filters
pass
Gain Parameters#
gain: Always usegainas the parameter namegain_scale: Specify units with"linear"or"db"
# Good
class HiShelving:
def __init__(
self,
cutoff: float,
q: float,
gain: float,
gain_scale: FilterOrderScale = "linear",
fs: int | None = None,
):
self.gain = gain if gain_scale == "linear" else 10 ** (gain / 20)
# Bad - Ambiguous units
class HiShelving:
def __init__(self, cutoff: float, q: float, gain_db: float, ...): # Don't encode units in name
pass
Order Parameters#
order: Filter order (always an integer)order_scale: Use"linear"(default) or"octave"(order per octave)
# Good
class Butterworth:
def __init__(
self,
btype: str,
cutoff: float,
order: int = 4,
order_scale: FilterOrderScale = "linear",
fs: int | None = None,
):
self.order = order if order_scale == "linear" else order // 6
Sample Rate Parameter#
fs: Always usefsfor sampling frequencyAlways type as
int | Noneto support lazy initialization
# Good
def __init__(self, cutoff: float, fs: int | None = None):
self.fs = fs
# Bad
def __init__(self, cutoff: float, sample_rate: int = 44100): # Use fs, make optional
pass
Methods#
Use descriptive verb names:
compute_coefficients(),reset_state()Don’t abbreviate unless the abbreviation is standard:
fft()OK,comp_coef()NOT OK
Unit Conventions#
Frequency Units#
Hz: All frequency parameters are in Hz (not normalized)
Nyquist normalization: Handled internally by filters
# Good - User specifies in Hz
filter = LoButterworth(cutoff=1000, fs=44100)
# Bad - User must normalize
filter = LoButterworth(cutoff=1000/22050, fs=44100) # Don't do this
Gain Units#
Two units are supported with explicit specification:
Linear gain: Amplitude multiplier (1.0 = unity, 2.0 = 6dB)
dB: Decibel scale (0dB = unity, 6dB = 2x amplitude)
# Good - Explicit unit specification
filter = HiShelving(cutoff=1000, q=0.707, gain=6, gain_scale="db", fs=44100)
filter = HiShelving(cutoff=1000, q=0.707, gain=2.0, gain_scale="linear", fs=44100)
# Bad - Ambiguous units
filter = HiShelving(cutoff=1000, q=0.707, gain=6, fs=44100) # Is this dB or linear?
Amplitude/Level Units#
Range [0, 1] for normalized audio:
wave.ysis typically in [-1, 1]dB: For levels and gains
Linear: For simple multipliers
Code Organization Patterns#
Filter Implementation Pattern#
All IIR filters should follow this structure:
class NewFilter(IIR):
"""One-line description.
Detailed description of what the filter does, when to use it,
and any special characteristics.
Parameters
----------
cutoff : float
The cutoff frequency in Hz.
order : int
The filter order. Default is 4.
fs : int | None
The sampling frequency in Hz.
Examples
--------
>>> filter = NewFilter(cutoff=1000, order=4, fs=44100)
>>> filtered = filter.forward(signal)
Notes
-----
Additional technical details about the filter implementation,
references to papers, or usage warnings.
"""
def __init__(
self,
cutoff: float,
order: int = 4,
fs: int | None = None,
) -> None:
super().__init__(fs)
self.cutoff = cutoff
self.order = order
self.a = None
self.b = None
@override
def compute_coefficients(self) -> None:
"""Compute the filter coefficients."""
assert self.fs is not None
# Use scipy or implement custom coefficients
b, a = some_filter_design(self.order, self.cutoff / (0.5 * self.fs))
self.b = b
self.a = a
Effect Implementation Pattern#
All effects should follow this structure:
class NewEffect(FX):
"""One-line description.
Detailed description.
Parameters
----------
param1 : float
Description with units.
param2 : int
Description with units.
fs : int | None
The sampling frequency in Hz.
Examples
--------
>>> effect = NewEffect(param1=0.5, fs=44100)
>>> processed = effect.forward(signal)
"""
def __init__(
self,
param1: float,
param2: int = 100,
fs: int | None = None,
) -> None:
super().__init__(fs)
self.param1 = param1
self.param2 = param2
@override
def forward(self, x: Tensor) -> Tensor:
"""Apply the effect to the input tensor."""
# Implementation
return processed_signal
Module Organization#
torchfx/
├── __init__.py # Public API exports
├── wave.py # Wave class
├── effect.py # Base FX class and effects
├── typing.py # Type aliases
├── _deprecation.py # Internal utilities (prefix with _)
└── filter/
├── __init__.py # Filter exports
├── __base.py # Base filter classes
├── iir.py # IIR filters
└── fir.py # FIR filters
Documentation Standards#
Docstring Format#
Use NumPy-style docstrings:
def function_name(param1: int, param2: float = 1.0) -> float:
"""One-line summary (imperative mood).
Longer description if needed. Can span multiple paragraphs.
Parameters
----------
param1 : int
Description of param1 with units if applicable.
param2 : float, optional
Description of param2. Default is 1.0.
Returns
-------
float
Description of return value with units.
Raises
------
ValueError
If param1 is negative.
Examples
--------
>>> result = function_name(5, 2.0)
>>> print(result)
10.0
Notes
-----
Additional technical details, references, or warnings.
See Also
--------
related_function : Brief description.
"""
pass
Examples Requirements#
Every public class and method must have:
At least one basic usage example
Example should be runnable (or clearly marked if not)
Use realistic parameter values
# Good example
"""
Examples
--------
Create a lowpass filter and apply it:
>>> filter = LoButterworth(cutoff=1000, order=5, fs=44100)
>>> wave = Wave.from_file("audio.wav")
>>> filtered = wave | filter
"""
# Bad example - too abstract
"""
Examples
--------
>>> filter = LoButterworth(x, y, z)
>>> result = filter(data)
"""
Type Hints#
Always use type hints for:
Function parameters
Return values
Class attributes (when clear)
# Good
def process_audio(
signal: Tensor,
fs: int,
gain: float = 1.0,
) -> Tensor:
pass
# Bad - no type hints
def process_audio(signal, fs, gain=1.0):
pass
Testing Standards#
Test Organization#
class TestFilterName:
"""Test suite for FilterName."""
@pytest.fixture
def sample_signal(self):
"""Create a test signal."""
return torch.randn(1, 1000)
def test_coefficient_computation(self):
"""Test that coefficients are computed correctly."""
filter = FilterName(cutoff=1000, fs=44100)
filter.compute_coefficients()
assert filter.a is not None
assert filter.b is not None
def test_forward_pass(self, sample_signal):
"""Test that forward pass works."""
filter = FilterName(cutoff=1000, fs=44100)
output = filter.forward(sample_signal)
assert output.shape == sample_signal.shape
Test Coverage Requirements#
Every public method must have at least one test
Edge cases must be tested
Error handling must be tested
Test Naming#
Use descriptive test names:
test_lowpass_filters_high_frequenciesStart with
test_Use underscores, not camelCase
Code Quality Tools#
Linters#
ruff: Python linter (configured in
pyproject.toml)black: Code formatter
Pre-commit Hooks#
Configure git hooks for automatic formatting:
# Run before committing
ruff check src/ tests/
black src/ tests/
pytest tests/
Deprecation Guidelines#
When deprecating an API:
Use the
@deprecateddecoratorProvide clear alternative
Set removal version (at least +1 minor version)
Update documentation
Add migration guide entry
from torchfx._deprecation import deprecated
@deprecated(
version="0.3.0",
reason="Renamed for consistency",
alternative="new_method()",
removal_version="1.0.0"
)
def old_method(self):
return self.new_method()
Questions?#
If you have questions about the style guide:
Check existing code for examples
Open a GitHub discussion
Ask in code review