Signal Processing

SINAD

1. What is SINAD?

SINAD (an acronym for Signal-to-Noise and Distortion Ratio) is a fundamental measurement in electronics, signal processing, and telecommunications. It quantifies the “cleanliness” or quality of a signal by comparing the power of the desired signal to the combined power of all undesirable components.

These undesirable components include two main elements:

  1. Noise: Random phenomena (thermal noise, shot noise, etc.) that are added to the signal.
  2. Distortion: Unwanted signals created by the system itself (non-linearities), such as harmonics or intermodulation products.

A high SINAD indicates a very clean signal, with little noise and distortion. A low SINAD indicates a poor-quality signal.

2. The Mathematical Formula

SINAD is most often expressed in decibels (dB). Its basic definition is the ratio of the desired signal’s power (the information we want) to the combined power of the noise and distortion.

The formula is as follows:

\[ \text{SINAD} = \frac{P_{\text{Signal}}}{P_{\text{Noise}} + P_{\text{Distortion}}} \]

Where:

  • \(P_{\text{Signal}}\) is the power of the fundamental component (the useful signal).
  • \(P_{\text{Noise}}\) is the power of all noise components.
  • \(P_{\text{Distortion}}\) is the power of all distortion components (harmonics, etc.).

Expression in Decibels (dB)

In practice, SINAD is almost always expressed in decibels (dB) using the logarithmic formula:

\[ \text{SINAD}_{dB} = 10 \log_{10} \left( \frac{P_{\text{Signal}}}{P_{\text{Noise}} + P_{\text{Distortion}}} \right) \]

Note: An alternative definition, sometimes used in RF receiver testing, relates the total power (Signal + Noise + Distortion) to the noise and distortion. However, the formula above (Signal / (Noise + Distortion)) is the most common, especially for converter characterization.

3. SINAD vs. SNR vs. THD

It is crucial not to confuse SINAD with other similar metrics.

  • SNR (Signal-to-Noise Ratio): \[ \text{SNR} = \frac{P_{\text{Signal}}}{P_{\text{Noise}}} \] SNR ignores distortion. It only measures the impact of random noise.

  • THD (Total Harmonic Distortion): \[ \text{THD} = \frac{P_{\text{Distortion}}}{P_{\text{Signal}}} \] THD ignores noise. It only measures the impact of system non-linearities.

SINAD is a more comprehensive measure than SNR or THD taken separately, as it accounts for both sources of signal degradation. By definition, SINAD will always be lower than (or at best equal to) SNR.

4. Main Applications

SINAD is an essential performance metric in several fields.

Analog-to-Digital Converters (ADC)

This is the most common application of SINAD. It is used to determine the effective number of bits of a converter.

ENOB (Effective Number of Bits): An ideal \(N\)-bit ADC has a theoretical SINAD (due only to quantization noise) given by: \(\text{SINAD}_{\text{ideal}} \approx 6.02 \times N + 1.76 dB\)

In practice, thermal noise and non-linearities (distortion) degrade this performance. Therefore, the actual SINAD of the component is measured, and the inverse formula is used to find the “true” number of bits of resolution:

\[ ENOB = \frac{SINAD_{actual, dB} - 1.76}{6.02} \]

For example, a 16-bit ADC with a measured SINAD of 92 dB actually only has an ENOB of \(\frac{92 - 1.76}{6.02} \approx 14.99\) bits.

Radio (RF) Receivers

In radiocommunications, SINAD is used to define the sensitivity of a receiver. A typical specification for an FM radio is “sensitivity for 12 dB SINAD”. This corresponds to the minimum input signal level required for the audio output to have a SINAD of 12 dB, which is considered the threshold of comprehensibility for voice.

Audio Systems

In amplifiers, preamplifiers, or any audio equipment, SINAD (sometimes simply called “Signal + Noise / Noise + Distortion”) is an overall measure of fidelity. A high SINAD means the equipment reproduces the sound without adding perceptible background noise or harmonic distortion.

5. In Summary

  • SINAD = Ratio of the useful signal to all undesirable components (noise + distortion).
  • It is a comprehensive measure of a signal’s quality and fidelity.
  • The higher the SINAD (in dB), the better the signal quality.
  • It is fundamental for calculating the ENOB (Effective Number of Bits) of data converters.

6. Code

import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt  

def sine_model(t, amplitude, frequency, phase, offset):
    """Sine wave model for fitting."""
    return amplitude * np.sin(2 * np.pi * frequency * t + phase) + offset

def fit_sine(y_signal, t_axis=None):
    """
    Fits a sine wave to a signal using FFT for initial parameter estimation.

    Args:
        y_signal (np.array): The input signal (y samples).
        t_axis (np.array, optional): The time axis (x samples). 
                                     If None, indices (0, 1, 2...) are used.

    Returns:
        tuple: (popt, y_fitted)
            - popt (list): The optimized parameters [amplitude, frequency, phase, offset].
            - y_fitted (np.array): The fitted sine wave generated with the optimized parameters.
            Returns (None, None) if the fit fails.
    """
    
    n = len(y_signal)
    if t_axis is None:
        t_axis = np.arange(n)
        
    # === 1. Estimate initial parameters (p0) for the fit ===
    
    guess_offset = np.mean(y_signal)
    guess_amplitude = (np.max(y_signal) - np.min(y_signal)) / 2
    
    # Estimate frequency using FFT
    if len(t_axis) > 1:
        dt = t_axis[1] - t_axis[0]
    else:
        dt = 1.0
        
    fft_y = np.fft.fft(y_signal - guess_offset)
    fft_freq = np.fft.fftfreq(n, d=dt)
    
    positive_freq_indices = np.where(fft_freq > 0)[0]
    try:
        peak_freq_index = positive_freq_indices[np.argmax(np.abs(fft_y)[positive_freq_indices])]
        guess_frequency = fft_freq[peak_freq_index]
    except ValueError:
        guess_frequency = 1 / (n * dt) 
        
    guess_phase = 0
    p0 = [guess_amplitude, guess_frequency, guess_phase, guess_offset]

    # === 2. Fit the sine wave ===
    
    try:
        popt, pcov = curve_fit(sine_model, t_axis, y_signal, p0=p0)
    except RuntimeError as e:
        print(f"Error: The fit did not converge. Initial guesses were: {p0}")
        print(f"Scipy message: {e}")
        return None, None

    # === 3. Generate the fitted curve ===
    y_fitted = sine_model(t_axis, *popt)
    
    return popt, y_fitted

def calculate_sinad(y_original, y_fitted):
    """
    Calculates the SINAD (Signal-to-Noise and Distortion Ratio) given
    an original signal and a fitted (ideal) signal.

    Args:
        y_original (np.array): The original, noisy/distorted signal.
        y_fitted (np.array): The idealized, fitted signal (e.g., from fit_sine).

    Returns:
        float: The SINAD in decibels (dB).
    """
    
    # === 1. Calculate the noise/distortion (residual) ===
    noise_signal = y_original - y_fitted
    
    # === 2. Calculate signal and noise power ===
    
    # The "Signal" for SINAD is the pure fitted sine wave, WITHOUT its DC offset.
    # We can get the DC offset by taking the mean of the fitted (periodic) signal.
    signal_offset = np.mean(y_fitted)
    signal_component_ac = y_fitted - signal_offset
    
    # Calculate the power (Mean Square)
    power_signal = np.mean(signal_component_ac**2)
    power_noise = np.mean(noise_signal**2)
    
    if power_noise == 0 or power_signal == 0:
        # Perfect case (no noise) or error case (no signal)
        sinad_db = np.inf if power_noise == 0 else -np.inf
    else:
        # SINAD (dB) = 10 * log10( Power_Signal / Power_Noise_Distortion )
        sinad_db = 10 * np.log10(power_signal / power_noise)
        
    return sinad_db

# === Example Usage ===
if __name__ == "__main__":
    
    # 1. Create a test signal
    fs = 10000.0  # Sampling frequency (Hz)
    f_signal = 250.0 # Signal frequency (Hz)
    n_samples = 2000 # Number of samples
    
    t = np.arange(n_samples) / fs  # Time axis
    
    # Real parameters
    amp_real = 1.0
    phase_real = np.pi / 4 # 45 degrees
    offset_real = 0.5
    
    # Pure sine signal
    signal_pur = sine_model(t, amp_real, f_signal, phase_real, offset_real)
    
    # Add noise
    noise_amplitude = 0.1
    noise = noise_amplitude * np.random.randn(n_samples)
    
    # Add some distortion (3rd harmonic)
    distortion_amplitude = 0.05
    distortion = distortion_amplitude * np.sin(2 * np.pi * 3 * f_signal * t)
    
    # Final signal = Signal + Noise + Distortion
    signal_final = signal_pur + noise + distortion

    # 2. Run the analysis (in two separate steps)
    params, fitted_curve = fit_sine(signal_final, t)
    
    if params is not None:
        sinad = calculate_sinad(signal_final, fitted_curve)
    
        # 3. Display the results
        print("--- SINAD Analysis ---")
        print(f"Calculated SINAD: {sinad:.2f} dB")
        
        print("\n--- Fitted Parameters ---")
        print(f"  Parameter   |   Real    |   Fitted")
        print(f"--------------------------------------")
        print(f"  Amplitude   |  {amp_real:^9.4f} |  {params[0]:^9.4f}")
        print(f"  Fréquence   |  {f_signal:^9.4f} |  {params[1]:^9.4f}")
        print(f"  Phase (rad) |  {phase_real:^9.4f} |  {params[2]:^9.4f}")
        print(f"  Offset      |  {offset_real:^9.4f} |  {params[3]:^9.4f}")
    
        # 4. Visualize the results (no longer optional)
        plt.figure(figsize=(9, 6))
        
        # Original signal vs Fit
        plt.subplot(2, 1, 1)
        plt.plot(t, signal_final, 'b.', label='Original Signal (noisy)', alpha=0.3)
        plt.plot(t, fitted_curve, 'r-', label=f'Sine Fit (SINAD: {sinad:.2f} dB)')
        plt.title("Sine Wave Fit")
        plt.xlabel("Time (s)")
        plt.ylabel("Amplitude")
        plt.legend()
        plt.grid(True)
        
        # Residual Noise
        plt.subplot(2, 1, 2)
        plt.plot(t, signal_final - fitted_curve, 'g-', label='Residual Noise (Original - Fit)')
        plt.title("Noise and Distortion (Residual)")
        plt.xlabel("Time (s)")
        plt.ylabel("Amplitude")
        plt.legend()
        plt.grid(True)
        
        plt.tight_layout()
        plt.show()

    else:
        print("Sinus fit KO.")
--- SINAD Analysis ---
Calculated SINAD: 16.43 dB

--- Fitted Parameters ---
  Parameter   |   Real    |   Fitted
--------------------------------------
  Amplitude   |   1.0000   |   1.0023  
  Fréquence   |  250.0000  |  250.0058 
  Phase (rad) |   0.7854   |   0.7827  
  Offset      |   0.5000   |   0.5025  

Sinad example