Source code for mosqito.utils.fm_sine_generator

import numpy as np

[docs] def fm_sine_generator(xmod, fs, fc, k, spl_level, print_info=False): """ Creates a frequency-modulated (FM) signal of level 'spl_level' (in dB SPL) with sinusoidal carrier of frequency 'fc', arbitrary modulating signal 'xm', frequency sensitivity 'k', and sampling frequency 'fs'. The FM signal length is the same as the length of 'xm'. Parameters ---------- xmod: array Modulating signal, dim(N) fs: float Sampling frequency, in [Hz]. fc: float Carrier frequency, in [Hz]. Must be less than 'fs/2'. k: float Frequency sensitivity of the modulator. spl_level: float Sound Pressure Level [dB ref 20 uPa RMS] of the modulated signal. print_info: bool, optional If True, the maximum frequency deviation and modulation index are printed. Default is False Returns ------- y_fm: numpy.array Frequency-modulated signal with sine carrier, dim(N) in [Pa]. inst_freq: numpy.array Instantaneaous frequency, dim(N) max_freq_deviation: float Maximum frequency deviation [Hz] FM_modulation_index: float Modulation index Warning ------- spl_level must be provided in dB, ref=2e-5 Pa. See Also -------- .am_sine_generator : Amplitude modulation with sine wave carrier .am_noise_generator : Amplitude modulation with broadband noise carrier .sine_wave_generator : Sine wave generation Notes ----- The frequency sensitivity 'k' is equal to the frequency deviation in Hz away from 'fc' per unit amplitude of the modulating signal 'xmod'. Examples -------- .. plot:: :include-source: >>> from mosqito.utils import fm_sine_generator >>> import matplotlib.pyplot as plt >>> fs = 48000 # [Hz] >>> duration = 1 >>> t = np.linspace(0, duration, int(fs*duration)) >>> dB = 60 # [dB SPL] >>> fc = 50 # [Hz] >>> k = fc//2 # [Hz per unit amplitude of 'xm'] >>> fm = 10 # [Hz] >>> xmod = np.sin(2*np.pi*t*fm) >>> y_fm, inst_freq, f_delta, m = fm_sine_generator(xmod, fs, fc, k, dB, True) >>> fig, plots = plt.subplots(3, 1) >>> plots[0].set_title('Frequency-modulated sine wave') >>> plots[0].plot(t, xmod, 'C0', label='Modulating signal') >>> plots[0].legend(loc='upper right') >>> plots[0].grid() >>> plots[0].set_ylabel('Amplitude') >>> plots[0].set_xlim([0, duration]) >>> plots[1].plot(t, y_fm, '#69c3c5', label='FM signal') >>> plots[1].legend(loc='upper right') >>> plots[1].grid() >>> plots[1].set_ylabel('Amplitude') >>> plots[1].set_xlim([0, duration]) >>> plots[2].plot(t, inst_freq, '#7894cf', label='Inst Frequency') >>> plots[2].legend(loc='upper right') >>> plots[2].grid() >>> plots[2].hlines(fc, -0.1, 1.2*duration, color='k', linestyle='--') >>> plots[2].text(0.0025, fc*0.7, 'carrier freq $f_c$', fontsize=12) >>> plots[2].set_ylim([0, 1.1*(fc+k)]) >>> plots[2].set_xlim([0, duration]) >>> plots[2].set_ylabel('Frequency') >>> plots[2].set_xlabel('Time [s]') >>> plt.tight_layout() """ assert fc < fs/2, "Carrier frequency 'fc' must be less than 'fs/2'!" # sampling interval in seconds dt = 1/fs # instantaneous frequency of FM signal inst_freq = fc + k*xmod # unit-amplitude FM signal y_fm = np.sin(2*np.pi * np.cumsum(inst_freq)*dt) # max frequency deviation f_delta = k * np.max(np.abs(xmod)) # FM modulation index m = np.max(np.abs(2*np.pi * k * np.cumsum(xmod)*dt)) # Apply amplitude factor to obtain 'spl_level' p_ref = 20e-6 A_rms = p_ref * 10**(spl_level/20) y_fm *= A_rms/np.std(y_fm) if print_info: print(f'\tMax freq deviation: {f_delta} Hz') print(f'\tFM modulation index: {m:.2f}') return y_fm, inst_freq, f_delta, m