Source code for diffFx_pytorch.processors.eq.tonestack

import torch 
from typing import Dict, Union
from enum import Enum
from ..base_utils import check_params
from ..base import ProcessorsBase, EffectParam
from ..core.iir import IIRFilter

class TonestackPreset(Enum):
    """Collection of component values modeling various guitar amplifier tonestacks.
    From: https://github.com/mod-audio/guitarix/blob/master/trunk/src/faust/tonestack.dsp?fbclid=IwAR02TRPhiyVm5d_K0Df9KR8gxzbYcZX80NPvzT3ciCMn4r0V-iUJg8yH0YU
    Each preset defines the resistor (R1-R4) and capacitor (C1-C3) values that
    characterize the frequency response of a specific amplifier model. Values are
    provided in standard units (ohms for resistors, farads for capacitors).
    
    Available Models:
        Fender Family:
            - BASSMAN: '59 Bassman 5F6-A
            - MESA: Mesa Boogie Mark
            - TWIN: '69 Twin Reverb AA270
            - PRINCETON: '64 Princeton AA1164
            
        Marshall Family:
            - JCM800: '59/81 JCM-800 Lead 100 2203
            - JCM2000: '81 2000 Lead
            - JTM45: JTM 45
            - MLEAD: '67 Major Lead 200
            
        Vox Family:
            - AC30: '59/86 AC-30
            - AC15: VOX AC-15
            
        Other Manufacturers:
            - SOLDANO: Soldano SLO 100
            - SOVTEK: MIG 100 H
            - PEAVEY: Peavey C20
            - IBANEZ: Ibanez GX20
            - ROLAND: Roland Cube 60
            
    Component Value Format:
        Each preset is a dictionary with the following keys:
            - R1, R2, R3, R4: Resistor values in ohms
            - C1, C2, C3: Capacitor values in farads
            
    Note:
        Component values are based on measurements and modeling of actual
        amplifier circuits. The interaction between these components and
        the control settings creates the characteristic tonal response
        of each amplifier model.
    """
    # Fender Models
    BASSMAN = {  # 59 Bassman 5F6-A
        'R1': 250e3, 'R2': 1e6, 'R3': 25e3, 'R4': 56e3,
        'C1': 250e-12, 'C2': 20e-9, 'C3': 20e-9
    }
    MESA = {  # Mesa Boogie Mark
        'R1': 250e3, 'R2': 250e3, 'R3': 25e3, 'R4': 100e3,
        'C1': 250e-12, 'C2': 100e-9, 'C3': 47e-9
    }
    TWIN = {  # 69 Twin Reverb AA270
        'R1': 250e3, 'R2': 250e3, 'R3': 10e3, 'R4': 100e3,
        'C1': 120e-12, 'C2': 100e-9, 'C3': 47e-9
    }
    PRINCETON = {  # 64 Princeton AA1164
        'R1': 250e3, 'R2': 250e3, 'R3': 4.8e3, 'R4': 100e3,
        'C1': 250e-12, 'C2': 100e-9, 'C3': 47e-9
    }
    
    # Marshall Models
    JCM800 = {  # 59/81 JCM-800 Lead 100 2203
        'R1': 220e3, 'R2': 1e6, 'R3': 22e3, 'R4': 33e3,
        'C1': 470e-12, 'C2': 22e-9, 'C3': 22e-9
    }
    JCM2000 = {  # 81 2000 Lead
        'R1': 250e3, 'R2': 1e6, 'R3': 25e3, 'R4': 56e3,
        'C1': 500e-12, 'C2': 22e-9, 'C3': 22e-9
    }
    JTM45 = {  # JTM 45
        'R1': 250e3, 'R2': 1e6, 'R3': 25e3, 'R4': 33e3,
        'C1': 270e-12, 'C2': 22e-9, 'C3': 22e-9
    }
    MLEAD = {  # 67 Major Lead 200
        'R1': 250e3, 'R2': 1e6, 'R3': 25e3, 'R4': 33e3,
        'C1': 500e-12, 'C2': 22e-9, 'C3': 22e-9
    }
    M2199 = {  # M2199 30W solid state
        'R1': 250e3, 'R2': 250e3, 'R3': 25e3, 'R4': 56e3,
        'C1': 250e-12, 'C2': 47e-9, 'C3': 47e-9
    }
    
    # Vox Models
    AC30 = {  # 59/86 AC-30
        'R1': 1e6, 'R2': 1e6, 'R3': 10e3, 'R4': 100e3,
        'C1': 50e-12, 'C2': 22e-9, 'C3': 22e-9
    }
    AC15 = {  # VOX AC-15
        'R1': 220e3, 'R2': 220e3, 'R3': 220e3, 'R4': 100e3,
        'C1': 470e-12, 'C2': 100e-9, 'C3': 47e-9
    }
    
    # Other Manufacturers
    SOLDANO = {  # Soldano SLO 100
        'R1': 250e3, 'R2': 1e6, 'R3': 25e3, 'R4': 47e3,
        'C1': 470e-12, 'C2': 20e-9, 'C3': 20e-9
    }
    SOVTEK = {  # MIG 100 H
        'R1': 500e3, 'R2': 1e6, 'R3': 10e3, 'R4': 47e3,
        'C1': 470e-12, 'C2': 22e-9, 'C3': 22e-9
    }
    PEAVEY = {  # c20
        'R1': 250e3, 'R2': 250e3, 'R3': 20e3, 'R4': 68e3,
        'C1': 270e-12, 'C2': 22e-9, 'C3': 22e-9
    }
    IBANEZ = {  # gx20
        'R1': 250e3, 'R2': 250e3, 'R3': 10e3, 'R4': 100e3,
        'C1': 270e-12, 'C2': 100e-9, 'C3': 40e-9
    }
    ROLAND = {  # Cube 60
        'R1': 250e3, 'R2': 250e3, 'R3': 10e3, 'R4': 41e3,
        'C1': 240e-12, 'C2': 33e-9, 'C3': 82e-9
    }
    AMPEG = {  # VL 501
        'R1': 250e3, 'R2': 1e6, 'R3': 25e3, 'R4': 32e3,
        'C1': 470e-12, 'C2': 22e-9, 'C3': 22e-9
    }
    AMPEG_REV = {  # reverbrocket
        'R1': 250e3, 'R2': 250e3, 'R3': 10e3, 'R4': 100e3,
        'C1': 100e-12, 'C2': 100e-9, 'C3': 47e-9
    }
    BOGNER = {  # Triple Giant Preamp
        'R1': 250e3, 'R2': 1e6, 'R3': 33e3, 'R4': 51e3,
        'C1': 220e-12, 'C2': 15e-9, 'C3': 47e-9
    }
    GROOVE = {  # Trio Preamp
        'R1': 220e3, 'R2': 1e6, 'R3': 22e3, 'R4': 68e3,
        'C1': 470e-12, 'C2': 22e-9, 'C3': 22e-9
    }
    CRUNCH = {  # Hughes&Kettner
        'R1': 220e3, 'R2': 220e3, 'R3': 10e3, 'R4': 100e3,
        'C1': 220e-12, 'C2': 47e-9, 'C3': 47e-9
    }
    FENDER_BLUES = {  # Fender Blues Junior
        'R1': 250e3, 'R2': 250e3, 'R3': 25e3, 'R4': 100e3,
        'C1': 250e-12, 'C2': 22e-9, 'C3': 22e-9
    }
    FENDER_DEFAULT = {  # Fender Default
        'R1': 250e3, 'R2': 250e3, 'R3': 10e3, 'R4': 100e3,
        'C1': 250e-12, 'C2': 100e-9, 'C3': 47e-9
    }
    FENDER_DEVILLE = {  # Fender Hot Rod Deville
        'R1': 250e3, 'R2': 250e3, 'R3': 25e3, 'R4': 130e3,
        'C1': 250e-12, 'C2': 100e-9, 'C3': 22e-9
    }
    GIBSON = {  # gs12 reverbrocket
        'R1': 1e6, 'R2': 1e6, 'R3': 94e3, 'R4': 270e3,
        'C1': 25e-12, 'C2': 60e-9, 'C3': 20e-9
    }
    ENGL = {  # engl
        'R1': 250e3, 'R2': 1e6, 'R3': 20e3, 'R4': 100e3,
        'C1': 600e-12, 'C2': 47e-9, 'C3': 47e-9
    }
    
[docs]class Tonestack(ProcessorsBase): """Differentiable implementation of classic guitar amplifier tonestack circuits. This processor implements a digital model of analog tonestack circuits found in guitar amplifiers, providing control over bass, middle, and treble frequencies. The implementation is based on modeling the analog circuit components and their interactions using a third-order IIR filter. The transfer function is based on the following analog circuit topology: .. math:: H(s) = \\frac{b_0 + b_1s + b_2s^2 + b_3s^3}{1 + a_1s + a_2s^2 + a_3s^3} where coefficients b0-b3 and a1-a3 are functions of: - Component values (R1-R4, C1-C3) from the chosen preset - Control positions (bass, mid, treble) - The coefficients are calculated using circuit analysis equations - The bilinear transform is then applied to convert to digital domain Args: sample_rate (int): Audio sample rate in Hz. Defaults to 44100. preset (str): Amplifier model preset to use. Must be one of the following: Fender Family: - BASSMAN: '59 Bassman 5F6-A - MESA: Mesa Boogie Mark - TWIN: '69 Twin Reverb AA270 - PRINCETON: '64 Princeton AA1164 - FENDER_BLUES: Fender Blues Junior - FENDER_DEFAULT: Fender Default - FENDER_DEVILLE: Fender Hot Rod Deville Marshall Family: - JCM800: '59/81 JCM-800 Lead 100 2203 - JCM2000: '81 2000 Lead - JTM45: JTM 45 - MLEAD: '67 Major Lead 200 - M2199: M2199 30W solid state Vox Family: - AC30: '59/86 AC-30 - AC15: VOX AC-15 Other Manufacturers: - SOLDANO: Soldano SLO 100 - SOVTEK: MIG 100 H - PEAVEY: Peavey C20 - IBANEZ: Ibanez GX20 - ROLAND: Roland Cube 60 - AMPEG: VL 501 - AMPEG_REV: Ampeg Reverbrocket - BOGNER: Triple Giant Preamp - GROOVE: Trio Preamp - CRUNCH: Hughes&Kettner - GIBSON: GS12 Reverbrocket - ENGL: Engl Parameters Details: bass: Low frequency control - Range: 0.0 to 1.0 - Higher values boost low frequencies mid: Middle frequency control - Range: 0.0 to 1.0 - Controls presence of middle frequencies treble: High frequency control - Range: 0.0 to 1.0 - Higher values boost high frequencies Note: The processor supports a collection of presets modeling famous guitar amplifier circuits including Fender, Marshall, Vox, and other manufacturers. Each preset defines specific component values that determine the characteristic sound of that amplifier model. Warning: When using with neural networks: - norm_params must be in range [0, 1] - Parameters will be automatically mapped to their ranges - Ensure your network output is properly normalized (e.g., using sigmoid) - Parameter order must match _register_default_parameters() Examples: Basic DSP Usage: >>> # Create a tonestack with Fender Bassman preset >>> tonestack = Tonestack( ... sample_rate=44100, ... preset="bassman" ... ) >>> # Process audio with dsp parameters >>> output = tonestack(input_audio, dsp_params={ ... 'bass': 0.7, ... 'mid': 0.5, ... 'treble': 0.6 ... }) Neural Network Control: >>> # 1. Simple parameter prediction >>> class TonestackController(nn.Module): ... def __init__(self, input_size, num_params): ... super().__init__() ... self.net = nn.Sequential( ... nn.Linear(input_size, 32), ... nn.ReLU(), ... nn.Linear(32, num_params), ... nn.Sigmoid() # Ensures output is in [0,1] range ... ) ... ... def forward(self, x): ... return self.net(x) >>> >>> # Initialize controller >>> num_params = tonestack.count_num_parameters() # 3 parameters >>> controller = TonestackController(input_size=16, num_params=num_params) >>> >>> # Process with features >>> features = torch.randn(batch_size, 16) # Audio features >>> norm_params = controller(features) >>> output = tonestack(input_audio, norm_params=norm_params) """
[docs] def __init__(self, sample_rate=44100, param_range=None, preset='bassman'): super().__init__(sample_rate, param_range) self.filter = IIRFilter(order=3, backend='fsm') # Third order filter self.preset = TonestackPreset[preset.upper()].value
[docs] def _register_default_parameters(self): """Register default parameter ranges for the tonestack. Sets up the following parameters with their ranges: - bass: Low frequency control (0 to 1) - mid: Middle frequency control (0 to 1) - treble: High frequency control (0 to 1) Each control maps to a normalized range that interacts with the circuit component values defined by the chosen preset. """ self.params = { 'bass': EffectParam(min_val=0.0, max_val=1.0), 'mid': EffectParam(min_val=0.0, max_val=1.0), 'treble': EffectParam(min_val=0.0, max_val=1.0) }
[docs] def _calculate_coefficients(self, t: torch.Tensor, m: torch.Tensor, l: torch.Tensor) -> tuple: """Calculate filter coefficients based on control values and component values. Implements the circuit analysis equations for the tonestack, converting control positions and component values into analog filter coefficients, then applying the bilinear transform to obtain digital coefficients. Args: t (torch.Tensor): Treble control value (0-1). Shape: (batch,) m (torch.Tensor): Middle control value (0-1). Shape: (batch,) l (torch.Tensor): Bass control value (0-1). Shape: (batch,) Returns: tuple: (Bs, As) where: - Bs: Numerator coefficients for IIR filter. Shape: (batch, 4) - As: Denominator coefficients for IIR filter. Shape: (batch, 4) Note: The coefficients are computed in these stages: 1. Get component values from current preset (R1-R4, C1-C3) 2. Apply bass control scaling for improved response 3. Calculate analog domain coefficients (b1-b3, a0-a3) 4. Convert to digital domain using bilinear transform 5. Stack coefficients for IIR filtering """ # Get component values R1, R2, R3, R4 = [self.preset[k] for k in ['R1', 'R2', 'R3', 'R4']] C1, C2, C3 = [self.preset[k] for k in ['C1', 'C2', 'C3']] # Convert bass control for better response l = torch.exp((l - 1) * 3.4) # Calculate analog coefficients b1 = (t*C1*R1 + m*C3*R3 + l*(C1*R2 + C2*R2) + (C1*R3 + C2*R3)) b2 = (t*(C1*C2*R1*R4 + C1*C3*R1*R4) - m*m*(C1*C3*R3*R3 + C2*C3*R3*R3) + m*(C1*C3*R1*R3 + C1*C3*R3*R3 + C2*C3*R3*R3) + l*(C1*C2*R1*R2 + C1*C2*R2*R4 + C1*C3*R2*R4) + l*m*(C1*C3*R2*R3 + C2*C3*R2*R3) + (C1*C2*R1*R3 + C1*C2*R3*R4 + C1*C3*R3*R4)) b3 = (l*m*(C1*C2*C3*R1*R2*R3 + C1*C2*C3*R2*R3*R4) - m*m*(C1*C2*C3*R1*R3*R3 + C1*C2*C3*R3*R3*R4) + m*(C1*C2*C3*R1*R3*R3 + C1*C2*C3*R3*R3*R4) + t*C1*C2*C3*R1*R3*R4 - t*m*C1*C2*C3*R1*R3*R4 + t*l*C1*C2*C3*R1*R2*R4) a0 = torch.ones_like(t) a1 = ((C1*R1 + C1*R3 + C2*R3 + C2*R4 + C3*R4) + m*C3*R3 + l*(C1*R2 + C2*R2)) a2 = (m*(C1*C3*R1*R3 - C2*C3*R3*R4 + C1*C3*R3*R3 + C2*C3*R3*R3) + l*m*(C1*C3*R2*R3 + C2*C3*R2*R3) - m*m*(C1*C3*R3*R3 + C2*C3*R3*R3) + l*(C1*C2*R2*R4 + C1*C2*R1*R2 + C1*C3*R2*R4 + C2*C3*R2*R4) + (C1*C2*R1*R4 + C1*C3*R1*R4 + C1*C2*R3*R4 + C1*C2*R1*R3 + C1*C3*R3*R4 + C2*C3*R3*R4)) a3 = (l*m*(C1*C2*C3*R1*R2*R3 + C1*C2*C3*R2*R3*R4) - m*m*(C1*C2*C3*R1*R3*R3 + C1*C2*C3*R3*R3*R4) + m*(C1*C2*C3*R3*R3*R4 + C1*C2*C3*R1*R3*R3 - C1*C2*C3*R1*R3*R4) + l*C1*C2*C3*R1*R2*R4 + C1*C2*C3*R1*R3*R4) # Convert to digital domain using bilinear transform c = 2 * self.sample_rate B0 = -b1*c - b2*c**2 - b3*c**3 B1 = -b1*c + b2*c**2 + 3*b3*c**3 B2 = b1*c + b2*c**2 - 3*b3*c**3 B3 = b1*c - b2*c**2 + b3*c**3 A0 = -a0 - a1*c - a2*c**2 - a3*c**3 A1 = -3*a0 - a1*c + a2*c**2 + 3*a3*c**3 A2 = -3*a0 + a1*c + a2*c**2 - 3*a3*c**3 A3 = -a0 + a1*c - a2*c**2 + a3*c**3 # Stack coefficients for IIR filter Bs = torch.stack([B0, B1, B2, B3], dim=-1) As = torch.stack([A0, A1, A2, A3], dim=-1) return Bs, As
[docs] def process( self, x: torch.Tensor, norm_params: Union[Dict[str, torch.Tensor], None] = None, dsp_params: Union[Dict[str, Union[float, torch.Tensor]], None] = None ) -> torch.Tensor: """Process input signal through the tonestack. Args: x (torch.Tensor): Input audio tensor. Shape: (batch, channels, samples) norm_params (Dict[str, torch.Tensor]): Normalized parameters (0 to 1) Must contain the following keys: - 'bass': Low frequency control (0 to 1) - 'mid': Mid frequency control (0 to 1) - 'treble': High frequency control (0 to 1) Each value should be a tensor of shape (batch_size,) dsp_params (Dict[str, Union[float, torch.Tensor]], optional): Direct DSP parameters. Can specify tone controls as: - float/int: Single value applied to entire batch - 0D tensor: Single value applied to entire batch - 1D tensor: Batch of values matching input batch size Parameters will be automatically expanded to match batch size and moved to input device if necessary. If provided, norm_params must be None. Returns: torch.Tensor: Processed audio tensor of same shape as input """ check_params(norm_params, dsp_params) if norm_params is not None: params = self.map_parameters(norm_params) else: params = dsp_params # Get control values t = params['treble'] m = params['mid'] l = params['bass'] # Calculate filter coefficients Bs, As = self._calculate_coefficients(t, m, l) # Apply IIR filter y = self.filter(x, Bs, As) return y