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:
Noise: Random phenomena (thermal noise, shot noise, etc.) that are added to the signal.
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.
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:
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 npfrom scipy.optimize import curve_fitimport 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) + offsetdef 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 isNone: 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 FFTiflen(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]exceptValueError: 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)exceptRuntimeErroras e:print(f"Error: The fit did not converge. Initial guesses were: {p0}")print(f"Scipy message: {e}")returnNone, None# === 3. Generate the fitted curve === y_fitted = sine_model(t_axis, *popt)return popt, y_fitteddef 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 ==0or power_signal ==0:# Perfect case (no noise) or error case (no signal) sinad_db = np.inf if power_noise ==0else-np.infelse:# 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 isnotNone: sinad = calculate_sinad(signal_final, fitted_curve)# 3. Display the resultsprint("--- 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.")