def mlsExciationSignal(self): """ function to create graphs to illustrate generating mls signals """ self.logger.debug("entering mlsExciationSignal") # get the mls signal mls_db = MlsDb() mls = mls_db.getMls(5) # convert into -1's and 1s mls = -2 * mls + 1 # hold values to produce plots mls_plot = reduce(lambda x, y: x + y, zip(mls, mls)) bins_plot = reduce(lambda x, y: x + y, zip(arange(len(mls)), arange(1, len(mls) + 1))) plot(bins_plot, mls_plot) ylim(-1.1, 1.1) yticks([-1, 0, 1]) xlabel("Bins") ylabel("Amplitude") axhline(y=-1, ls="--", color="gray") axhline(y=0, ls="--", color="gray") axhline(y=1, ls="--", color="gray") subplots_adjust(left=0.10, right=0.97, top=0.97, bottom=0.10) xlim(0, 31) savefig("Analysis/Images/5_tap_mls_signal.eps")
def __init__(self, microphone_signals, generator_signals, measurement_settings, analysis_settings): """ Constructor for AbsorptionCoefficient object. :param microphone_signals: An array of signals recorded from the microphone. :type microphone_signals: array of float :param generator_signals: An array of signals recorded directly from the generator. :type generator_signals: array of float :param measurement_settings: A dictionary containing the settings used to measure the signals. As well as the location of the impulse in the microphone signal and the generator signal. :type measurement_settings: dict :param analysis_settings: A dictionary containing the settings used to analyze the signals. :type analysis_settings: dict """ self.logger = logging.getLogger("Alpha") self.logger.debug("Creating AbsorptionCoefficient Object") self.microphone_signals = microphone_signals self.generator_signals = generator_signals self.measurement_settings = measurement_settings self.analysis_settings = analysis_settings self.mls_db = MlsDb() self.determineAlpha()
def __init__(self, microphone_signals, generator_signals, measurement_settings): """ Default constructor to create the FrequencyResponse object. :param microphone_signals: An array of signals recorded from the microphone. :type microphone_signals: array of float :param generator_signals: An array of signals recorded directly from the generator. :type generator_signals: array of float :param measurement_settings: A dictionary containing the settings used to measure the signals. As well as the location of the impulse in the microphone signal and the generator signal. :type measurement_settings: dict """ self.logger = logging.getLogger("Alpha") self.logger.debug("Creating Frequency Response Object") # Set up object variables self.microphone_signals = microphone_signals self.generator_signals = generator_signals self.measurement_settings = measurement_settings # Create new object to access MLS database self.mls_db = MlsDb() self.determineFreqResp()
def __init__(self, parameters): """Constructor to create signal generator :param parameters: The parameters to use to generate the signal. It is a dictionary containing information, such as sample rate, signal type and other depending on the signal. :type parameters: dict """ self.logger = logging.getLogger("Alpha") self.parameters = parameters self.mls_db = MlsDb() self.generateSignal()
class AbsorptionCoefficient(object): def __init__(self, microphone_signals, generator_signals, measurement_settings, analysis_settings): """ Constructor for AbsorptionCoefficient object. :param microphone_signals: An array of signals recorded from the microphone. :type microphone_signals: array of float :param generator_signals: An array of signals recorded directly from the generator. :type generator_signals: array of float :param measurement_settings: A dictionary containing the settings used to measure the signals. As well as the location of the impulse in the microphone signal and the generator signal. :type measurement_settings: dict :param analysis_settings: A dictionary containing the settings used to analyze the signals. :type analysis_settings: dict """ self.logger = logging.getLogger("Alpha") self.logger.debug("Creating AbsorptionCoefficient Object") self.microphone_signals = microphone_signals self.generator_signals = generator_signals self.measurement_settings = measurement_settings self.analysis_settings = analysis_settings self.mls_db = MlsDb() self.determineAlpha() def determineAlpha(self): """ Determine the absorption coefficient of the material under test. The absorption coefficient is determined directly by lifting the impulse response from the power cepstrum. The power cepstrum is the generator's power cepstrum subtracted from microphone's power cepstrum. The microphone and generator power cepstra are determined from the averaged recorded microphone and generator response. These averaged signals are filtered with an anti-aliasing filter, and decimated. """ self.logger.debug("Entering determineAlpha") # Extract the signals from the recorded responses, and average self._extractSignals() # Determine the system response of the signals self._determineResponse() # LPF the signals and decimate self._downsampleSignals() # Determine the Cepstrum self._determineCepstrum() # Lift the impulse response self._liftImpulseResponse() # Determine the absorption coefficient self._determineAbsorptionCoefficient() def _determineAbsorptionCoefficient(self): """ By taking the Fourier transform of the impulse response, the absorption coefficient can be determined. The absorption coefficient is simply, a = 1 - |F{h}|^2 with: a - the absorption coefficient h - the impulse response F{} - The Fourier transform operator """ self.logger.debug("Entering _determineAbsorptionCoefficient") fft_size = int(self.measurement_settings["fft size"]) self.alpha = 1 - abs(fft(self.impulse_response, fft_size)) ** 2 def _determineResponse(self): """ For Psuedorandom signal, determine the system response using the autocorrellation property of the signal. """ self.logger.debug("Entering _determineResponse") # If MLS signal, then utilize the circular convolution property signal_type = self.measurement_settings["signal type"] sample_rate = float(self.measurement_settings["sample rate"]) if signal_type.lower() == "maximum length sequence": number_taps = int(self.measurement_settings["mls taps"]) self.microphone_response = self.mls_db.getSystemResponse(self.average_microphone_response, number_taps) self.generator_response = self.mls_db.getSystemResponse(self.average_generator_response, number_taps) self.system_response = ifft(fft(self.microphone_response) / fft(self.generator_response)) elif signal_type.lower() == "inverse repeat sequence": number_taps = int(self.measurement_settings["mls taps"]) # TODO: Re-factor this out of absorption coefficient mls = self.mls_db.getMls(number_taps) mls = -2 * mls + 1 irs = array([]) for index, sample in enumerate(r_[mls, mls]): if index % 2 == 0: irs = r_[irs, sample] else: irs = r_[irs, -1 * sample] self.microphone_response = irfft(rfft(self.average_microphone_response) * rfft(irs[-1::-1])) self.generator_response = irfft(rfft(self.average_generator_response) * rfft(irs[-1::-1])) # The output of the auto-correlation of an irs signal is a + impulse at 0 and a # - impulse at N / 2. One is only interested in the positive impulse, so extract # up to N / 2 samples, or the MLS length of 2 ^ (number of taps) - 1. BUT, since # the DFT is circular, samples at the end wrap to be connected to sample's at the # start - if the start of the IRS signal is missed, then part of the - impulse # response will corrupt the positive impulse response. impulse_length = len(mls) window = hanning(0.1 * impulse_length) self.microphone_response = self.microphone_response[:0.9 * impulse_length] self.microphone_response[-len(window) / 2 :] *= window[-len(window) / 2 :] tmp_response = self.microphone_response[-0.1 * impulse_length:] tmp_response[:len(window) / 2] *= window[:len(window) / 2] append(self.microphone_response, tmp_response) self.generator_response = self.generator_response[:0.9 * impulse_length] self.generator_response[-len(window) / 2 :] *= window[-len(window) / 2 :] tmp_response = self.generator_response[-0.1 * impulse_length:] tmp_response[:len(window) / 2] *= window[:len(window) / 2] append(self.generator_response, tmp_response) else: self.microphone_response = self.average_microphone_response self.generator_response = self.average_generator_response def _extractSignals(self): """ Extract the microphone and generator signals from the raw signals. The microphone and generator signal are preceded by an impulse. The location of the impulse is given in the signal settings. The delay from the impulse to the start of the signal is also specified in the signal settings. The microphone and generator can therefore be extracted from the start of the signal. """ self.logger.debug("Entering _extractSignals") sample_rate = float(self.measurement_settings["sample rate"]) signal_type = str(self.measurement_settings["signal type"]) impulse_location = int(self.measurement_settings["microphone impulse location"]) impulse_signal_delay = float(self.measurement_settings["impulse delay"]) impulse_signal_samples = impulse_signal_delay * sample_rate signal_start = impulse_location + impulse_signal_samples self.microphone_responses = [] for signal_index, signal in enumerate(self.microphone_signals): # Ignore first signal, unless there is only one signal if signal_index > 0 or len(self.microphone_signals) == 1: self.microphone_responses.append(signal[signal_start:]) self.average_microphone_response = average(self.microphone_responses, axis=0) impulse_location = int(self.measurement_settings["generator impulse location"]) signal_start = impulse_location + impulse_signal_samples self.generator_responses = [] for signal_index, signal in enumerate(self.generator_signals): # Ignore first signal, unless there is only one signal if signal_index > 0 or len(self.generator_signals) == 1: self.generator_responses.append(array(signal[signal_start:])) self.average_generator_response = average(self.generator_responses,axis=0) if signal_type.lower() == "maximum length sequence": mls_reps = int(self.measurement_settings["mls reps"]) mls_taps = int(self.measurement_settings["mls taps"]) assert(mls_reps > 0) mls_length = 2 ** mls_taps - 1 mls_sig = self.average_microphone_response[mls_length:(mls_length * (mls_reps))] mls_array = reshape(mls_sig, (mls_reps - 1, -1)) self.average_microphone_response = average(mls_array, axis=0) mls_sig = self.average_generator_response[mls_length:(mls_length * (mls_reps))] mls_array = reshape(mls_sig, (mls_reps - 1, -1)) self.average_generator_response = average(mls_array, axis=0) elif signal_type.lower() == "inverse repeat sequence": mls_reps = int(self.measurement_settings["mls reps"]) mls_taps = int(self.measurement_settings["mls taps"]) assert(mls_reps > 1) mls_length = 2 ** mls_taps - 1 irs_length = 2 * mls_length irs_sig = self.average_microphone_response[irs_length:(irs_length * (mls_reps))] irs_array = reshape(irs_sig, (mls_reps - 1, -1)) self.average_microphone_response = average(irs_array, axis=0) irs_sig = self.average_generator_response[irs_length:(irs_length * (mls_reps))] irs_array = reshape(irs_sig, (mls_reps - 1, -1)) self.average_generator_response = average(irs_array, axis=0) def _downsampleSignals(self): """ Low pass filter microphone response and generator response, then down sample by an Integer factor. The response signals is either the raw signal, or if the MLS signal was used to excite the system, the system response determined by the convolution property of MLS signals. """ self.logger.debug("Entering _downsampleSignals") # Get required variables sample_rate = float(self.measurement_settings["sample rate"]) decimation_factor = int(self.analysis_settings["decimation factor"]) filter_order = int(self.analysis_settings["antialiasing filter order"]) # Down sample the responses if decimation_factor > 1: self.microphone_response_ds = decimate(self.microphone_response, decimation_factor, ftype="fir") self.generator_response_ds = decimate(self.generator_response, decimation_factor, ftype="fir") else: self.microphone_response_ds = self.microphone_response self.generator_response_ds = self.generator_response #self.system_response_ds = decimate(self.system_response, decimation_factor) #self.microphone_response_ds = lfilter(b, a, self.microphone_response[::decimation_factor]) #self.generator_response_ds = lfilter(b, a, self.generator_response[::decimation_factor]) def _liftImpulseResponse(self): """ Lift the impulse response off the power cepstrum. Using the specified window settings, create a window to lift the impulse response off the power cepstrum. """ self.logger.debug("Entering _liftImpulseResponse") # Get required variables window_type = str(self.analysis_settings["window type"]) window_start = float(self.analysis_settings["window start"]) window_end = float(self.analysis_settings["window end"]) taper_length = float(self.analysis_settings["taper length"]) sample_rate = float(self.measurement_settings["sample rate"]) decimation_factor = float(self.analysis_settings["decimation factor"]) effective_sample_rate = sample_rate / decimation_factor window_length = window_end - window_start window_samples = window_length * effective_sample_rate taper_samples = taper_length * effective_sample_rate # Create the window tapers = hanning(2 * taper_samples) if window_type == "one sided": self.window = r_[ones(window_samples - taper_samples), tapers[taper_samples:]] elif window_type == "two sided": self.window = r_[tapers[:taper_samples], ones(window_samples - (2 * taper_samples)), tapers[taper_samples:]] self.window[-1] = 0 # Lift impulse response start = window_start * effective_sample_rate end = start + len(self.window) self.impulse_response = self.power_cepstrum[start:end].copy() self.impulse_response *= self.window def _determineCepstrum(self): """ Determines the power cepstra of the averaged microphone and generator signals. The power cepstra is defined as: c(t) = ifft(log(abs(fft(x(t)) ** 2)) "The inverse Fourier Transform of the logarithmic squared magnitude of the Fourier Transform of a signal" The scaled generator cepstrum is subtracted from the microphone cepstrum to determine the overall system cepstrum. The algorithm to determine the scaling factor, beta, is as follows: 1. Determine where the squared modulus of the generator cepstrum reaches 1% of it's peak. 2. Window out the generator cepstrum from that sample to the sample before the start of the impulse response lifter. 3. Window out the same section from the microphone cepstrum. 4. Beta is then equal to the ratio of the windowed generator cepstrum to that of the windowed microphone cepstrum. """ self.logger.debug("Entering _determineCepstrum") # required variables fft_size = int(self.measurement_settings["fft size"]) window_start = float(self.analysis_settings["window start"]) sample_rate = float(self.measurement_settings["sample rate"]) decimation_factor = float(self.analysis_settings["decimation factor"]) effective_sample_rate = sample_rate / decimation_factor cepstrum = lambda x: irfft(log(abs(rfft(x, fft_size)) ** 2)) # Determine Cepstra self.microphone_cepstrum = cepstrum(self.microphone_response_ds) self.generator_cepstrum = cepstrum(self.generator_response_ds) #self.system_cepstrum = cepstrum(self.system_response_ds) #self.power_cepstrum = ifft(log(abs(fft(self.microphone_response_ds, fft_size) / fft(self.generator_response_ds, fft_size)) ** 2)) self.power_cepstrum = self.microphone_cepstrum - self.generator_cepstrum
class AbsorptionCoefficient(object): def __init__(self, microphone_signals, generator_signals, measurement_settings, analysis_settings): """ Constructor for AbsorptionCoefficient object. :param microphone_signals: An array of signals recorded from the microphone. :type microphone_signals: array of float :param generator_signals: An array of signals recorded directly from the generator. :type generator_signals: array of float :param measurement_settings: A dictionary containing the settings used to measure the signals. As well as the location of the impulse in the microphone signal and the generator signal. :type measurement_settings: dict :param analysis_settings: A dictionary containing the settings used to analyze the signals. :type analysis_settings: dict """ self.logger = logging.getLogger("Alpha") self.logger.debug("Creating AbsorptionCoefficient Object") self.microphone_signals = microphone_signals self.generator_signals = generator_signals self.measurement_settings = measurement_settings self.analysis_settings = analysis_settings self.mls_db = MlsDb() self.determineAlpha() def determineAlpha(self): """ Determine the absorption coefficient of the material under test. The absorption coefficient is determined directly by lifting the impulse response from the power cepstrum. The power cepstrum is the generator's power cepstrum subtracted from microphone's power cepstrum. The microphone and generator power cepstra are determined from the averaged recorded microphone and generator response. These averaged signals are filtered with an anti-aliasing filter, and decimated. """ self.logger.debug("Entering determineAlpha") # Extract the signals from the recorded responses, and average self._extractSignals() # Determine the system response of the signals self._determineResponse() # LPF the signals and decimate self._downsampleSignals() # Determine the Cepstrum self._determineCepstrum() # Lift the impulse response self._liftImpulseResponse() # Determine the absorption coefficient self._determineAbsorptionCoefficient() def _determineAbsorptionCoefficient(self): """ By taking the Fourier transform of the impulse response, the absorption coefficient can be determined. The absorption coefficient is simply, a = 1 - |F{h}|^2 with: a - the absorption coefficient h - the impulse response F{} - The Fourier transform operator """ self.logger.debug("Entering _determineAbsorptionCoefficient") fft_size = int(self.measurement_settings["fft size"]) self.alpha = 1 - abs(fft(self.impulse_response, fft_size))**2 def _determineResponse(self): """ For Psuedorandom signal, determine the system response using the autocorrellation property of the signal. """ self.logger.debug("Entering _determineResponse") # If MLS signal, then utilize the circular convolution property signal_type = self.measurement_settings["signal type"] sample_rate = float(self.measurement_settings["sample rate"]) if signal_type.lower() == "maximum length sequence": number_taps = int(self.measurement_settings["mls taps"]) self.microphone_response = self.mls_db.getSystemResponse( self.average_microphone_response, number_taps) self.generator_response = self.mls_db.getSystemResponse( self.average_generator_response, number_taps) self.system_response = ifft( fft(self.microphone_response) / fft(self.generator_response)) elif signal_type.lower() == "inverse repeat sequence": number_taps = int(self.measurement_settings["mls taps"]) # TODO: Re-factor this out of absorption coefficient mls = self.mls_db.getMls(number_taps) mls = -2 * mls + 1 irs = array([]) for index, sample in enumerate(r_[mls, mls]): if index % 2 == 0: irs = r_[irs, sample] else: irs = r_[irs, -1 * sample] self.microphone_response = irfft( rfft(self.average_microphone_response) * rfft(irs[-1::-1])) self.generator_response = irfft( rfft(self.average_generator_response) * rfft(irs[-1::-1])) # The output of the auto-correlation of an irs signal is a + impulse at 0 and a # - impulse at N / 2. One is only interested in the positive impulse, so extract # up to N / 2 samples, or the MLS length of 2 ^ (number of taps) - 1. BUT, since # the DFT is circular, samples at the end wrap to be connected to sample's at the # start - if the start of the IRS signal is missed, then part of the - impulse # response will corrupt the positive impulse response. impulse_length = len(mls) window = hanning(0.1 * impulse_length) self.microphone_response = self.microphone_response[:0.9 * impulse_length] self.microphone_response[-len(window) / 2:] *= window[-len(window) / 2:] tmp_response = self.microphone_response[-0.1 * impulse_length:] tmp_response[:len(window) / 2] *= window[:len(window) / 2] append(self.microphone_response, tmp_response) self.generator_response = self.generator_response[:0.9 * impulse_length] self.generator_response[-len(window) / 2:] *= window[-len(window) / 2:] tmp_response = self.generator_response[-0.1 * impulse_length:] tmp_response[:len(window) / 2] *= window[:len(window) / 2] append(self.generator_response, tmp_response) else: self.microphone_response = self.average_microphone_response self.generator_response = self.average_generator_response def _extractSignals(self): """ Extract the microphone and generator signals from the raw signals. The microphone and generator signal are preceded by an impulse. The location of the impulse is given in the signal settings. The delay from the impulse to the start of the signal is also specified in the signal settings. The microphone and generator can therefore be extracted from the start of the signal. """ self.logger.debug("Entering _extractSignals") sample_rate = float(self.measurement_settings["sample rate"]) signal_type = str(self.measurement_settings["signal type"]) impulse_location = int( self.measurement_settings["microphone impulse location"]) impulse_signal_delay = float( self.measurement_settings["impulse delay"]) impulse_signal_samples = impulse_signal_delay * sample_rate signal_start = impulse_location + impulse_signal_samples self.microphone_responses = [] for signal_index, signal in enumerate(self.microphone_signals): # Ignore first signal, unless there is only one signal if signal_index > 0 or len(self.microphone_signals) == 1: self.microphone_responses.append(signal[signal_start:]) self.average_microphone_response = average(self.microphone_responses, axis=0) impulse_location = int( self.measurement_settings["generator impulse location"]) signal_start = impulse_location + impulse_signal_samples self.generator_responses = [] for signal_index, signal in enumerate(self.generator_signals): # Ignore first signal, unless there is only one signal if signal_index > 0 or len(self.generator_signals) == 1: self.generator_responses.append(array(signal[signal_start:])) self.average_generator_response = average(self.generator_responses, axis=0) if signal_type.lower() == "maximum length sequence": mls_reps = int(self.measurement_settings["mls reps"]) mls_taps = int(self.measurement_settings["mls taps"]) assert (mls_reps > 0) mls_length = 2**mls_taps - 1 mls_sig = self.average_microphone_response[mls_length:(mls_length * (mls_reps))] mls_array = reshape(mls_sig, (mls_reps - 1, -1)) self.average_microphone_response = average(mls_array, axis=0) mls_sig = self.average_generator_response[mls_length:(mls_length * (mls_reps))] mls_array = reshape(mls_sig, (mls_reps - 1, -1)) self.average_generator_response = average(mls_array, axis=0) elif signal_type.lower() == "inverse repeat sequence": mls_reps = int(self.measurement_settings["mls reps"]) mls_taps = int(self.measurement_settings["mls taps"]) assert (mls_reps > 1) mls_length = 2**mls_taps - 1 irs_length = 2 * mls_length irs_sig = self.average_microphone_response[irs_length:(irs_length * (mls_reps))] irs_array = reshape(irs_sig, (mls_reps - 1, -1)) self.average_microphone_response = average(irs_array, axis=0) irs_sig = self.average_generator_response[irs_length:(irs_length * (mls_reps))] irs_array = reshape(irs_sig, (mls_reps - 1, -1)) self.average_generator_response = average(irs_array, axis=0) def _downsampleSignals(self): """ Low pass filter microphone response and generator response, then down sample by an Integer factor. The response signals is either the raw signal, or if the MLS signal was used to excite the system, the system response determined by the convolution property of MLS signals. """ self.logger.debug("Entering _downsampleSignals") # Get required variables sample_rate = float(self.measurement_settings["sample rate"]) decimation_factor = int(self.analysis_settings["decimation factor"]) filter_order = int(self.analysis_settings["antialiasing filter order"]) # Down sample the responses if decimation_factor > 1: self.microphone_response_ds = decimate(self.microphone_response, decimation_factor, ftype="fir") self.generator_response_ds = decimate(self.generator_response, decimation_factor, ftype="fir") else: self.microphone_response_ds = self.microphone_response self.generator_response_ds = self.generator_response #self.system_response_ds = decimate(self.system_response, decimation_factor) #self.microphone_response_ds = lfilter(b, a, self.microphone_response[::decimation_factor]) #self.generator_response_ds = lfilter(b, a, self.generator_response[::decimation_factor]) def _liftImpulseResponse(self): """ Lift the impulse response off the power cepstrum. Using the specified window settings, create a window to lift the impulse response off the power cepstrum. """ self.logger.debug("Entering _liftImpulseResponse") # Get required variables window_type = str(self.analysis_settings["window type"]) window_start = float(self.analysis_settings["window start"]) window_end = float(self.analysis_settings["window end"]) taper_length = float(self.analysis_settings["taper length"]) sample_rate = float(self.measurement_settings["sample rate"]) decimation_factor = float(self.analysis_settings["decimation factor"]) effective_sample_rate = sample_rate / decimation_factor window_length = window_end - window_start window_samples = window_length * effective_sample_rate taper_samples = taper_length * effective_sample_rate # Create the window tapers = hanning(2 * taper_samples) if window_type == "one sided": self.window = r_[ones(window_samples - taper_samples), tapers[taper_samples:]] elif window_type == "two sided": self.window = r_[tapers[:taper_samples], ones(window_samples - (2 * taper_samples)), tapers[taper_samples:]] self.window[-1] = 0 # Lift impulse response start = window_start * effective_sample_rate end = start + len(self.window) self.impulse_response = self.power_cepstrum[start:end].copy() self.impulse_response *= self.window def _determineCepstrum(self): """ Determines the power cepstra of the averaged microphone and generator signals. The power cepstra is defined as: c(t) = ifft(log(abs(fft(x(t)) ** 2)) "The inverse Fourier Transform of the logarithmic squared magnitude of the Fourier Transform of a signal" The scaled generator cepstrum is subtracted from the microphone cepstrum to determine the overall system cepstrum. The algorithm to determine the scaling factor, beta, is as follows: 1. Determine where the squared modulus of the generator cepstrum reaches 1% of it's peak. 2. Window out the generator cepstrum from that sample to the sample before the start of the impulse response lifter. 3. Window out the same section from the microphone cepstrum. 4. Beta is then equal to the ratio of the windowed generator cepstrum to that of the windowed microphone cepstrum. """ self.logger.debug("Entering _determineCepstrum") # required variables fft_size = int(self.measurement_settings["fft size"]) window_start = float(self.analysis_settings["window start"]) sample_rate = float(self.measurement_settings["sample rate"]) decimation_factor = float(self.analysis_settings["decimation factor"]) effective_sample_rate = sample_rate / decimation_factor cepstrum = lambda x: irfft(log(abs(rfft(x, fft_size))**2)) # Determine Cepstra self.microphone_cepstrum = cepstrum(self.microphone_response_ds) self.generator_cepstrum = cepstrum(self.generator_response_ds) #self.system_cepstrum = cepstrum(self.system_response_ds) #self.power_cepstrum = ifft(log(abs(fft(self.microphone_response_ds, fft_size) / fft(self.generator_response_ds, fft_size)) ** 2)) self.power_cepstrum = self.microphone_cepstrum - self.generator_cepstrum
class SignalGenerator(object): def __init__(self, parameters): """Constructor to create signal generator :param parameters: The parameters to use to generate the signal. It is a dictionary containing information, such as sample rate, signal type and other depending on the signal. :type parameters: dict """ self.logger = logging.getLogger("Alpha") self.parameters = parameters self.mls_db = MlsDb() self.generateSignal() def setParameters(self, parameters): """" Update the signal parameters, and update the signal. :param parameters: The signal parameters to use to generate the signal. :type parameters: dict """ self.logger.debug("Entering setParameters") self.parameters = parameters self.generateSignal() def generateSignal(self): """Generate the signal specified by the parameters. Using the parameters specified, a new signal will be generated. The signal will then be accessible by the signal property of the SignalGenerator object. """ self.logger.debug("Entering generateSignal") # Get parameters signal_type = str(self.parameters["signal type"]) lpf_enabled = int(self.parameters["lpf enabled"]) lpf_cutoff = int(self.parameters["lpf cutoff"]) lpf_order = int(self.parameters["lpf order"]) hpf_enabled = int(self.parameters["hpf enabled"]) hpf_cutoff = int(self.parameters["hpf cutoff"]) hpf_order = int(self.parameters["hpf order"]) pad_signal = int(self.parameters["pad signal"]) signal_reps = int(self.parameters["signal reps"]) if "gain" in self.parameters: gain = float(self.parameters["gain"]) if gain <= 0: # Gain is in dB; convert to decimal gain = 10 ** (gain / 20.0) else: gain = 0.562341325190349 # -6 dB # Generate the signal if signal_type.lower() == "swept sine": self.generateSweptSine() if signal_type.lower() == "low pass swept sine": self.generateLowPassSweptSine() if signal_type.lower() == "maximum length sequence": self.generateMls() if signal_type.lower() == "inverse repeat sequence": self.generateIRS() # Filter the signal if lpf_enabled == 1: self.filterSignal(lpf_cutoff, lpf_order, "low") if hpf_enabled == 1: self.filterSignal(hpf_cutoff, hpf_order, "high") #self.inverseFilter() # Adjust gain # TODO: Get gain from database self.signal /= max(abs(self.signal)) self.signal *= gain # Pad the filter with impulse, and delay at the end if pad_signal == 1: self.padSignal() # Repeat the signal to improve SNR tmp_signal = self.signal for i in range(signal_reps): self.signal = r_[self.signal, tmp_signal] def inverseFilter(self): """ Loads the frequency response of the loud speaker, fits a cosine series to the logarithm of squared magnitude of the frequency response. determines the magnitude of the spectrum by |Sf| = |St| / |Sl| with: Sf the frequency repsonse of the consentation filter St is the target spectrum Sl is the loudspeaker response determines the minimum phase of the compensation filter: """ self.logger.debug("Entering inverseFilter") import BaseDelegate # Create new base delegate bd = BaseDelegate.BaseDelegate() # Load the frequency response measurement_file = "../testdata/120802_frequency_response_20.fdb" freq_response = bd.loadFrequencyResponse(measurement_file) sample_rate = float(freq_response.measurement_settings["sample rate"]) N = len(freq_response.frequency_response) # find the bin of 4000 Hz bin = float(floor(4410* N / sample_rate)) freq = freq_response.frequency_response # We are solving Ax = 2 * log10(abs(y)) # Determine A M = 20 k = arange(bin) a = array([]) for m in range(M): a = r_[a, cos(2 * pi * k * m / bin)] A = matrix(reshape(a, (M, bin))) # Determine the weights W = pinv(A).transpose()*asmatrix(2 * log10(abs(freq[:bin]))).transpose() # Create 2 * log10(abs(y)) s = zeros(bin) for m, w in enumerate(W): s += w[0,0] * cos(2 * pi * k * m / bin) # target spectrum is now mix_samples = ceil(bin * 0.1) # create first half of s transistion = linspace(1, 0, mix_samples) * s[-mix_samples:] + linspace(0, 1, mix_samples) * 2 * log10(freq_response.frequency_response[bin - mix_samples: bin]) s = r_[s[:bin - mix_samples], transistion, 2 * log10(freq_response.frequency_response[bin:N / 2])] # mirror it s = r_[s, s[::-1]] plot(s) plot(2*log10(freq_response.frequency_response)) show() S = 10 ** (s / 2.0) #plot(S, "--") #plot(freq_response.frequency_response) #show() # compensation filter X = fft(self.signal, N) Sc = abs(freq_response.frequency_response) / abs(X) #Sc = abs(S) / abs(freq_response.frequency_response) # To ensure that the filter is causal, and the impulse response is as short as possible in the time domain # determine the minimum phase to use with the filter c = ifft(log(abs(Sc) ** -1), N) m = r_[c[0], 2 * c[1:N / 2.0 - 1], c[N/2] ] m = r_[m, zeros(N - len(m))] Scmp = exp(fft(m, N)) Y = Scmp * X x = ifft(Y) x = x[:len(self.signal)] self.signal = x / max(abs(x)) def generateSweptSine(self): """Generate a linear swept sine wave from lower frequency to an upper frequency in a specific length of time. A swept sine signal sweeps in frequency from a lower frequency, f_0, to a upper frequency, f_1, in a given time, T. a = pi * ( f_1 - f_0 ) / T b = 2 * pi * f_0 s = sin( (at + b) * t) The parameters for the swept sine are stored in the parameters dictionary. """ self.logger.debug("Entering generateSweptSine") # Get signal parameters if "lower frequency" in self.parameters: f_0 = int(self.parameters["lower frequency"]) else: f_0 = 0 f_1 = int(self.parameters["upper frequency"]) T = float(self.parameters["signal length"]) sample_rate = float(self.parameters["sample rate"]) signal_length = float(self.parameters["signal length"]) # Generate time t = arange(0, signal_length, 1 / sample_rate) # Generate the signal a = pi * (f_1 - f_0) / T b = 2 * pi * f_0 self.signal = sin((a * t + b) * t) def generateLowPassSweptSine(self): """Generates a swept sine signal, the inverse filters the signal to reduce the ripples in the spectrum. Creates a minimum phases inverse filter to create a flat spectrum for the swept sine. It should be noted that this algorithm creates a flat spectrum up to half of the Nyquist ( Fs / 2 ); where as for cepstral techniques a spectrum up to a specified frequency (in this case, the upper frequency of the swept sine) is ideal. This function therefore assumes a sampling rate of 2 * upper frequency of the swept sine, inverse filters the signal, and re-samples the signal up to the real sampling rate. The algorithm is given as: First a swept sine is generated using s = sin((a * t + b) * t) where: a = pi * ((sample_rate / 2) - f_0) / T b = 2 * pi * freq1 T is the signal length f_1, f_0 are the upper and lower frequencies Then zero pad to 4096 points, and get S = FFT(s) Take the inverse of S. The minimum phase is generated by the following algorithm cp = IFFT(log|S| ^ -1) This function is then windowed as follows: / cp[n] n = 0, N/2 m[n] = | 2 * cp[n] 1 <= n < N/2 \ 0 N/2 < n <= N-1 Then Re[FFT(m)] is equal to log|S| ^ -1 Im[FFT(m)] is the minimum phase function ie <S_mp The minimum phase inverse spectrum is then given by S_mp ^ -1 = e ^ { IFFT(m) } """ self.logger.debug("Entering generateModifiedSweptSine") # Get signal parameters f_0 = int(self.parameters["lower frequency"]) f_1 = int(self.parameters["upper frequency"]) T = float(self.parameters["signal length"]) sample_rate = float(self.parameters["sample rate"]) fft_size = int(self.parameters["fft size"]) signal_length = float(self.parameters["signal length"]) # Generate time vector t = arange(0, signal_length, 1 / sample_rate) # Generate the signal from 0 to Nyquist frequency s = sin(2 * pi * (((sample_rate / 2) - 0) / (2 * T) * t + 0) * t) # Determine the spectrum S = fft(s, fft_size) # Inverse of the magnitude spectrum iaS = abs(S) ** -1 # c, similar to the cepstrum, is the inverse of the logarithmic inverse # magnitude spectrum c = ifft(log(iaS)) # Window c to produce m m = r_[c[0], 2 * c[1:len(S) / 2 - 1], c[len(S) / 2], zeros(len(S) / 2)] # Determine the spectrum of the windowed 'cepstrum' M = fft(m, fft_size) # Determine the minimum phase inverse filter iSmp = exp(M) # Determine the minimum phase spectrum Smp = S * iSmp # Determin the minimum phase signal smp = ifft(Smp) # smp will have fft_size samples, which could be very long # reduce to length of the signal specified smp = smp[:len(t)] # Low pass filter the signal to the upper frequency [b, a] = butter(8, 0.8 * f_1 / (sample_rate / 2), btype="low") #smp = lfilter(b, a, smp) # Normalize so that the maximum value is 1 smp /= max(abs(smp)) self.signal = smp def generateMls(self): """Fetches the MLS signal from the database with the desired number of taps, and maps the values from {0,1} -> {+1, -1} """ self.logger.debug("Entering generateMls") # Get signal parameters taps = int(self.parameters["mls taps"]) reps = int(self.parameters["mls reps"]) mls = self.mls_db.getMls(taps) mls = -2 * mls + 1 repeated_mls = mls for i in range(reps + 1): repeated_mls = r_[repeated_mls, mls] self.signal = repeated_mls def generateIRS(self): """ Creates Inverse Repeat Sequence by fetching the required number of taps from the MLS db, then forming the IRS signal as follows: x[n] = s[n] n is even x[n] = -s[n] n is odd """ self.logger.debug("Entering generateIRS") # Get signal parameters taps = int(self.parameters["mls taps"]) reps = int(self.parameters["mls reps"]) mls = self.mls_db.getMls(taps) mls = -2 * mls + 1 irs = array([]) for index, sample in enumerate(r_[mls, mls]): if index % 2 == 0: irs = r_[irs, sample] else: irs = r_[irs, -1 * sample] repeated_irs = irs for i in range(reps + 1): repeated_irs = r_[repeated_irs, irs] self.signal = repeated_irs def filterSignal(self, cutoff, order, type): """ Filters the current signal with a specified filter. :param cutoff: The cut off frequency of the filter. :type cutoff: float :param order: The order of the filter to use. :type order: int :param type: The type of filter to use, either "low" or "high", for low pass filter, or high pass filter. :type type: str """ self.logger.debug("Entering filterSignal (%s, %s, %s)" % (cutoff, order, type)) # Get signal parameters sample_rate = float(self.parameters["sample rate"]) if type == "high": [b, a] = butter(order, cutoff / (sample_rate / 2), btype=type) self.signal = lfilter(b, a, self.signal) # b = firwin(251, cutoff=[50, 200], nyq=(sample_rate / 2)) # a = 1 # self.signal = lfilter(b, a, self.signal) # [b, a] = iirdesign(wp=cutoff / (sample_rate / 2), ws = 50 / (sample_rate / 2), gpass=1, gstop=12, ftype="butter") else: [b, a] = butter(order, cutoff / (sample_rate / 2), btype=type) self.signal = lfilter(b, a, self.signal) def padSignal(self): """ Pad signal with an impulse at the front of the signal, followed by a delay, also adds a delay to the end of the signal. """ self.logger.debug("Entering padSignal") # Get signal parameters impulse_delay = float(self.parameters["impulse delay"]) signal_padding = float(self.parameters["signal padding"]) sample_rate = float(self.parameters["sample rate"]) impulse_delay_samples = zeros(int(impulse_delay * sample_rate)) signal_padding_samples = zeros(int(signal_padding * sample_rate)) self.signal = r_[0, 1, impulse_delay_samples, self.signal, signal_padding_samples]
class FrequencyResponse(object): def __init__(self, microphone_signals, generator_signals, measurement_settings): """ Default constructor to create the FrequencyResponse object. :param microphone_signals: An array of signals recorded from the microphone. :type microphone_signals: array of float :param generator_signals: An array of signals recorded directly from the generator. :type generator_signals: array of float :param measurement_settings: A dictionary containing the settings used to measure the signals. As well as the location of the impulse in the microphone signal and the generator signal. :type measurement_settings: dict """ self.logger = logging.getLogger("Alpha") self.logger.debug("Creating Frequency Response Object") # Set up object variables self.microphone_signals = microphone_signals self.generator_signals = generator_signals self.measurement_settings = measurement_settings # Create new object to access MLS database self.mls_db = MlsDb() self.determineFreqResp() def determineFreqResp(self): """ Determine the frequency response of device under test, usually a loudspeaker. First, the system response of the microphone response and the generator response are determined. From this, the generator response is deconvolved from the microphone response. The resulting response is then transformed into the frequency domain, giving the frequency response of the device. """ self.logger.debug("Entering determineFreqResp") # First the signals need be extracted and averaged together self._extractSignals() # Then determine the system response self._determineSystemResponse() # deconvolve the generator response from the microphone response self._deconvoleSignals() # Extract the impulse response self._extractImpulseResponse() def _extractImpulseResponse(self): """ Extract the impulse response of the device under investigation, by windowing it out directly from the system response. """ self.logger.debug("Entering _extractImpulseResponse") window_length = int(self.measurement_settings["window length"]) taper_length = int(self.measurement_settings["taper length"]) fft_size = int(self.measurement_settings["fft size"]) taper = hanning(2 * taper_length) self.impulse_response = self.microphone_response[:window_length] self.impulse_response[-taper_length:] *= taper[-taper_length:] self.frequency_response = fft(self.impulse_response, fft_size) def _deconvoleSignals(self): """ Deconvolves the generator signal from the microphone signal, removing the effects of the generator on the signal. """ self.logger.debug("Entering _deconvoleSignals") self.system_response = deconvolve(self.microphone_response,self.generator_response) # deconvolve function returns a tuple (remainder, deconvolved signal) self.system_response = ifft(fft(self.microphone_response) / fft(self.generator_response)) def _determineSystemResponse(self): """ Determines the system response of both the microphone and generator recorded signals. """ self.logger.debug("Entering _determineSystemResponse") # If MLS / IRS signal is used, use the circular convolution property # to determine the system response. signal_type = self.measurement_settings["signal type"] if signal_type.lower() == "maximum length sequence": number_taps = int(self.measurement_settings["mls taps"]) self.microphone_response = self.mls_db.getSystemResponse(self.average_microphone_response, number_taps) self.generator_response = self.mls_db.getSystemResponse(self.average_generator_response, number_taps) elif signal_type.lower() == "inverse repeat sequence": number_taps = int(self.measurement_settings["mls taps"]) # Preform the circular convolution manually! mls = self.mls_db.getMls(number_taps) mls = -2 * mls + 1 irs = array([]) for index, sample in enumerate(r_[mls, mls]): if index % 2 == 0: irs = r_[irs, sample] else: irs = r_[irs, -1 * sample] assert (len(self.average_microphone_response) == len(irs)) assert (len(self.average_generator_response) == len(irs)) self.microphone_response = ifft(fft(self.average_microphone_response) * fft(irs[-1::-1])) self.microphone_response = self.microphone_response[:2 ** number_taps - 1] self.generator_response = ifft(fft(self.average_generator_response) * fft(irs[-1::-1])) self.generator_response = self.generator_response[:2 ** number_taps - 1] else: self.microphone_response = self.average_microphone_response self.generator_response = self.average_microphone_response def _extractSignals(self): """ Extract the microphone and generator signals from the raw signals. The microphone and generator signal are preceded by an impulse. The location of the impulse is given in the signal settings. The delay from the impulse to the start of the signal is also specified in the signal settings. The microphone and generator can therefore be extracted from the start of the signal. """ self.logger.debug("Entering _extractSignals") sample_rate = float(self.measurement_settings["sample rate"]) signal_type = str(self.measurement_settings["signal type"]) impulse_location = int(self.measurement_settings["microphone impulse location"]) impulse_signal_delay = float(self.measurement_settings["impulse delay"]) impulse_signal_samples = impulse_signal_delay * sample_rate # Since the impulse is also a sample, we need to add one before the actual # start of the signal. signal_start = impulse_location + impulse_signal_samples self.microphone_responses = [] for signal in self.microphone_signals: self.microphone_responses.append(signal[signal_start:]) self.average_microphone_response = average(self.microphone_responses[1:], axis=0) impulse_location = int(self.measurement_settings["generator impulse location"]) signal_start = impulse_location + impulse_signal_samples self.generator_responses = [] for signal in self.generator_signals: self.generator_responses.append(array(signal[signal_start:])) self.average_generator_response = average(self.generator_responses[1:], axis=0) if signal_type.lower() == "maximum length sequence": mls_reps = int(self.measurement_settings["mls reps"]) mls_taps = int(self.measurement_settings["mls taps"]) assert(mls_reps > 0) mls_length = 2 ** mls_taps - 1 mls_sig = self.average_microphone_response[mls_length:(mls_length * (mls_reps + 1))] mls_array = reshape(mls_sig, (mls_reps, -1)) self.average_microphone_response = average(mls_array, axis=0) mls_sig = self.average_generator_response[mls_length:(mls_length * (mls_reps + 1))] mls_array = reshape(mls_sig, (mls_reps, -1)) self.average_generator_response = average(mls_array, axis=0) elif signal_type.lower() == "inverse repeat sequence": mls_reps = int(self.measurement_settings["mls reps"]) mls_taps = int(self.measurement_settings["mls taps"]) assert(mls_reps > 0) mls_length = 2 ** mls_taps - 1 irs_length = 2 * mls_length irs_sig = self.average_microphone_response[irs_length:(irs_length * (mls_reps + 1))] irs_array = reshape(irs_sig, (mls_reps, -1)) self.average_microphone_response = average(irs_array, axis=0) irs_sig = self.average_generator_response[irs_length:(irs_length * (mls_reps + 1))] irs_array = reshape(irs_sig, (mls_reps, -1)) self.average_generator_response = average(irs_array, axis=0)