def strength_factor(Lpe, Lpe_revCh, V_revCh, T_revCh, Lps_revCh, Lps_inSitu): S0 = 1 # [m2] bands = T_revCh.bands nthOct = T_revCh.nthOct terms = [] for bandData in T_revCh.data: if bandData == 0: terms.append(0) else: term = (V_revCh * 0.16) / (bandData * S0) terms.append(term) terms = [10 * np.log10(term) if term != 0 else 0 for term in terms] revChTerm = Analysis(anType='mixed', nthOct=nthOct, minBand=float(bands[0]), maxBand=float(bands[-1]), data=terms) Lpe.anType = 'mixed' Lpe_revCh.anType = 'mixed' Lps_revCh.anType = 'mixed' Lps_inSitu.anType = 'mixed' G = Lpe - Lpe_revCh - revChTerm + 37 \ + Lps_revCh - Lps_inSitu G.anType = 'G' return G
def strength_factor(Lpe, Lpe_revCh, V_revCh, T_revCh, Lps_revCh, Lps_inSitu): """ Reference: Christensen, C. L.; Rindel, J. H. APPLYING IN-SITU RECALIBRATION FOR SOUND STRENGTH MEASUREMENTS IN AUDITORIA. """ # TO DO: docs S0 = 1 # [m2] bands = T_revCh.bands nthOct = T_revCh.nthOct terms = [] for bandData in T_revCh.data: if bandData == 0: terms.append(0) else: term = (V_revCh * 0.16) / (bandData * S0) terms.append(term) terms = [10 * np.log10(term) if term != 0 else 0 for term in terms] revChTerm = Analysis(anType='mixed', nthOct=nthOct, minBand=float(bands[0]), maxBand=float(bands[-1]), data=terms) Lpe.anType = 'mixed' Lpe_revCh.anType = 'mixed' Lps_revCh.anType = 'mixed' Lps_inSitu.anType = 'mixed' G = Lpe - Lpe_revCh - revChTerm + 37 \ + Lps_revCh - Lps_inSitu G.anType = 'G' return G
def Lp_ST(sigObjList, nthOct, minFreq, maxFreq, IRManualCut=None): """ Calculate from the provided list of one channel SignalObjs the mean one-third-octave band time-averaged sound pressure level in the test room with the noise source under test in operation, Lp(ST), and the standard deviation, Sm, for the preliminary measurements, located at error property of the returned pytta.Analysis. Correction for background noise not implemented because of the UFSM reverberation chamber's good isolation. Parameters (default), (type): ----------------------------- * sigObjList (), (list): A list containing SignalObjs of each measurement position. * nthOct (), (int): The number of fractions per octave; * minFreq (), (int | float): The exact or approximated start band frequency; * maxFreq (), (int | float): The exact or approximated stop band frequency; * IRManualCut (None), (int | float): Optional manual IR cut in seconds. Returns: --------- pytta.Analysis object. """ # Code snippet to guarantee that generated object name is # the declared at global scope # for frame, line in traceback.walk_stack(None): for framenline in traceback.walk_stack(None): # varnames = frame.f_code.co_varnames varnames = framenline[0].f_code.co_varnames if varnames == (): break # creation_file, creation_line, creation_function, \ # creation_text = \ extracted_text = \ traceback.extract_stack(framenline[0], 1)[0] # traceback.extract_stack(frame, 1)[0] # creation_name = creation_text.split("=")[0].strip() creation_name = extracted_text[3].split("=")[0].strip() Leqs = [] for idx, sigObj in enumerate(sigObjList): if not sigObj.channels[firstChNum].calibCheck: raise ValueError("SignalObj {} must be calibrated.".format(idx+1)) # Cutting the IR if IRManualCut is not None: sigObj.crop(0, IRManualCut) # Bands filtering hSignal = SignalObj(sigObj.timeSignal[:,0], sigObj.lengthDomain, sigObj.samplingRate) hSignal = _filter(signal=hSignal, nthOct=nthOct, minFreq=minFreq, maxFreq=maxFreq) bands = FOF(nthOct=nthOct, minFreq=minFreq, maxFreq=maxFreq)[:,1] Leq = [] for chIndex in range(hSignal.numChannels): Leq.append( 10*np.log10(np.mean(hSignal.timeSignal[:,chIndex]**2)/ (2e-5**2))) Leq = Analysis(anType='mixed', nthOct=nthOct, minBand=float(bands[0]), maxBand=float(bands[-1]), data=Leq, comment='Leq') Leqs.append(Leq) Leq = 0 for L in Leqs: Leq = L + Leq Lp_ST = Leq / len(sigObjList) Lp_ST.anType = 'mixed' Lp_ST.unit = 'dB' Lp_ST.creation_name = creation_name # Statistics for Lp_ST data = np.vstack([an.data for an in Leqs]) Sm = [] for bandIdx in range(data.shape[1]): summing = 0 for idx in range(data.shape[0]): summing += \ (data[idx, bandIdx] - Lp_ST.data[bandIdx])**2 / (data.shape[0] - 1) Sm.append(summing**(1/2)) Lp_ST.error = Sm Lp_ST.errorLabel = "Standard deviation" return Lp_ST
def analyse(obj, *params, bypassLundeby=False, plotLundebyResults=False, IREndManualCut=None, **kwargs): """ Receives an one channel SignalObj or ImpulsiveResponse and calculate the room acoustic parameters especified in the positional input arguments. :param obj: one channel impulsive response :type obj: SignalObj or ImpulsiveResponse Input parameters for reverberation time, 'RT': :param RTdecay: decay interval for RT calculation. e.g. 20 :type RTdecay: int Input parameters for clarity, 'C': TODO Input parameters for definition, 'D': TODO Input parameters for strength factor, 'G': TODO :param nthOct: number of fractions per octave :type nthOct: int :param minFreq: analysis inferior frequency limit :type minFreq: float :param maxFreq: analysis superior frequency limit :type maxFreq: float :param bypassLundeby: bypass lundeby correction to False :type bypassLundeby: bool, optional :param plotLundebyResults: plot the Lundeby correction parameters, defaults to False :type plotLundebyResults: bool, optional :return: Analysis object with the calculated parameter :rtype: Analysis """ # Code snippet to guarantee that generated object name is # the declared at global scope # for frame, line in traceback.walk_stack(None): for framenline in traceback.walk_stack(None): # varnames = frame.f_code.co_varnames varnames = framenline[0].f_code.co_varnames if varnames is (): break # creation_file, creation_line, creation_function, \ # creation_text = \ extracted_text = \ traceback.extract_stack(framenline[0], 1)[0] # traceback.extract_stack(frame, 1)[0] # creation_name = creation_text.split("=")[0].strip() creation_name = extracted_text[3].split("=")[0].strip() if not isinstance(obj, SignalObj) and not isinstance( obj, ImpulsiveResponse): raise TypeError("'obj' must be an one channel SignalObj or" + " ImpulsiveResponse.") if isinstance(obj, ImpulsiveResponse): SigObj = obj.systemSignal else: SigObj = obj if SigObj.numChannels > 1: raise TypeError("'obj' can't contain more than one channel.") samplingRate = SigObj.samplingRate SigObj = crop_IR(SigObj, IREndManualCut) listEDC = cumulative_integration(SigObj, bypassLundeby, plotLundebyResults, **kwargs) for _ in params: if 'RT' in params: RTdecay = params[params.index('RT') + 1] nthOct = kwargs['nthOct'] RT = reverberation_time(RTdecay, nthOct, samplingRate, listEDC) result = Analysis(anType='RT', nthOct=nthOct, minBand=kwargs['minFreq'], maxBand=kwargs['maxFreq'], data=RT) # if 'C' in prm: # Ctemp = prm[1] # if 'D' in prm: # Dtemp = prm[1] result.creation_name = creation_name return result
def G_Lps(IR, nthOct, minFreq, maxFreq): """G_Lps Calculates the recalibration level, for both in-situ and reverberation chamber. Lps is applied for G calculation. During the recalibration: source height and mic heigth must be >= 1 [m], while the distance between source and mic must be <= 1 [m]. The distances must be the same for in-situ and reverberation chamber measurements. Reference: Christensen, C. L.; Rindel, J. H. APPLYING IN-SITU RECALIBRATION FOR SOUND STRENGTH MEASUREMENTS IN AUDITORIA. :param IR: one channel impulsive response :type IR: ImpulsiveResponse :param nthOct: number of fractions per octave :type nthOct: int :param minFreq: analysis inferior frequency limit :type minFreq: float :param maxFreq: analysis superior frequency limit :type maxFreq: float :return: Analysis object with the calculated parameter :rtype: Analysis """ # Code snippet to guarantee that generated object name is # the declared at global scope # for frame, line in traceback.walk_stack(None): for framenline in traceback.walk_stack(None): # varnames = frame.f_code.co_varnames varnames = framenline[0].f_code.co_varnames if varnames is (): break # creation_file, creation_line, creation_function, \ # creation_text = \ extracted_text = \ traceback.extract_stack(framenline[0], 1)[0] # traceback.extract_stack(frame, 1)[0] # creation_name = creation_text.split("=")[0].strip() creation_name = extracted_text[3].split("=")[0].strip() # firstChNum = IR.systemSignal.channels.mapping[0] # if not IR.systemSignal.channels[firstChNum].calibCheck: # raise ValueError("'IR' must be a calibrated ImpulsiveResponse") if isinstance(IR, SignalObj): SigObj = IR elif isinstance(IR, ImpulsiveResponse): SigObj = IR.systemSignal else: raise TypeError("'IR' must be an ImpulsiveResponse or SignalObj.") # Windowing the IR # dBtoOnSet = 20 # dBIR = 10*np.log10((SigObj.timeSignal[:,0]**2)/((2e-5)**2)) # windowStart = np.where(dBIR > (max(dBIR) - dBtoOnSet))[0][0] broadBandTimeSignal = cp.copy(SigObj.timeSignal[:, 0]) broadBandTimeSignalNoStart, sampleShift = \ _circular_time_shift(broadBandTimeSignal) windowLength = 0.0032 # [s] windowEnd = int(windowLength * SigObj.samplingRate) hSignal = SignalObj( broadBandTimeSignalNoStart[:windowEnd], # hSignal = SignalObj(timeSignal, SigObj.lengthDomain, SigObj.samplingRate) hSignal = _filter(signal=hSignal, nthOct=nthOct, minFreq=minFreq, maxFreq=maxFreq) bands = FOF(nthOct=nthOct, freqRange=[minFreq, maxFreq])[:, 1] Lps = [] for chIndex in range(hSignal.numChannels): timeSignal = cp.copy(hSignal.timeSignal[:, chIndex]) # timeSignalNoStart, sampleShift = _circular_time_shift(timeSignal) # windowLength = 0.0032 # [s] # windowEnd = int(windowLength*SigObj.samplingRate) Lps.append( # 10*np.log10(np.trapz(y=timeSignalNoStart[:windowEnd]**2/(2e-5**2), 10 * np.log10( np.trapz( y=timeSignal**2 / (2e-5**2), # x=hSignal.timeVector[sampleShift:sampleShift+windowEnd]))) x=hSignal.timeVector))) LpsAnal = Analysis(anType='mixed', nthOct=nthOct, minBand=float(bands[0]), maxBand=float(bands[-1]), data=Lps, comment='Source recalibration method IR') LpsAnal.creation_name = creation_name LpsAnal.windowLimits = ((sampleShift) / SigObj.samplingRate, (sampleShift + windowEnd) / SigObj.samplingRate) # Plot IR cutting # fig = plt.figure(figsize=(10, 5)) # ax = fig.add_axes([0.08, 0.15, 0.75, 0.8], polar=False, # projection='rectilinear', xscale='linear') # ax.plot(SigObj.timeVector, 10*np.log10(SigObj.timeSignal**2/2e-5**2)) # ax.axvline(x=(sampleShift)/SigObj.samplingRate, linewidth=4, color='k') # ax.axvline(x=(sampleShift+windowEnd)/SigObj.samplingRate, linewidth=4, color='k') # ax.set_xlim([(sampleShift-100)/SigObj.samplingRate, (sampleShift+windowEnd+100)/SigObj.samplingRate]) return LpsAnal
def G_Lpe(IR, nthOct, minFreq, maxFreq, IREndManualCut=None): """ Calculate the energy level from the room impulsive response. Reference: Christensen, C. L.; Rindel, J. H. APPLYING IN-SITU RECALIBRATION FOR SOUND STRENGTH MEASUREMENTS IN AUDITORIA. :param IR: one channel impulsive response :type IR: ImpulsiveResponse :param nthOct: number of fractions per octave :type nthOct: int :param minFreq: analysis inferior frequency limit :type minFreq: float :param maxFreq: analysis superior frequency limit :type maxFreq: float :return: Analysis object with the calculated parameter :rtype: Analysis """ # Code snippet to guarantee that generated object name is # the declared at global scope # for frame, line in traceback.walk_stack(None): for framenline in traceback.walk_stack(None): # varnames = frame.f_code.co_varnames varnames = framenline[0].f_code.co_varnames if varnames is (): break # creation_file, creation_line, creation_function, \ # creation_text = \ extracted_text = \ traceback.extract_stack(framenline[0], 1)[0] # traceback.extract_stack(frame, 1)[0] # creation_name = creation_text.split("=")[0].strip() creation_name = extracted_text[3].split("=")[0].strip() # firstChNum = IR.systemSignal.channels.mapping[0] # if not IR.systemSignal.channels[firstChNum].calibCheck: # raise ValueError("'IR' must be a calibrated ImpulsiveResponse") if isinstance(IR, SignalObj): SigObj = cp.copy(IR) elif isinstance(IR, ImpulsiveResponse): SigObj = cp.copy(IR.systemSignal) else: raise TypeError("'IR' must be an ImpulsiveResponse or SignalObj.") # Cutting the IR if IREndManualCut is not None: SigObj.crop(0, IREndManualCut) timeSignal, _ = _circular_time_shift(SigObj.timeSignal[:, 0]) # Bands filtering # hSignal = SignalObj(SigObj.timeSignal[:,0], hSignal = SignalObj(timeSignal, SigObj.lengthDomain, SigObj.samplingRate) hSignal = _filter(signal=hSignal, nthOct=nthOct, minFreq=minFreq, maxFreq=maxFreq) bands = FOF(nthOct=nthOct, freqRange=[minFreq, maxFreq])[:, 1] Lpe = [] for chIndex in range(hSignal.numChannels): Lpe.append(10 * np.log10( np.trapz(y=hSignal.timeSignal[:, chIndex]**2 / (2e-5**2), x=hSignal.timeVector))) LpeAnal = Analysis(anType='mixed', nthOct=nthOct, minBand=float(bands[0]), maxBand=float(bands[-1]), data=Lpe, comment='h**2 energy level') LpeAnal.creation_name = creation_name return LpeAnal
def analyse(obj, *params, bypassLundeby=False, plotLundebyResults=False, suppressWarnings=False, IREndManualCut=None, **kwargs): """ Receives an one channel SignalObj or ImpulsiveResponse and calculate the room acoustic parameters especified in the positional input arguments. Calculates reverberation time, definition and clarity. The method for strength factor calculation implies in many input parameters and specific procedures, as the sound source's power estimation. The pytta.roomir app was designed aiming to support this room parameter measurement. For further information check pytta.roomir's and pytta.rooms.strength_factor's docstrings. Input arguments (default), (type): ----------------------------------- * obj (), (SignalObj | ImpulsiveResponse): one channel impulsive response * non-keyworded argument pairs: Pair for 'RT' (reverberation time): - RTdecay (20), (int): Decay interval for RT calculation. e.g. 20 Pair for 'C' (clarity): # TODO - Cparam (50), (int): ... Pair for 'D' (definition): # TODO - Dparam (50), (int): ... * nthOct (), (int): Number of bands per octave; * minFreq (), (int | float): Analysis' inferior frequency limit; * maxFreq (), (int | float): Analysis' superior frequency limit; * bypassLundeby (false), (bool): Bypass lundeby correction * plotLundebyResults (false), (bool): Plot the Lundeby correction parameters; * suppressWarnings (false), (bool): Suppress the warnings from the Lundeby correction; Return (type): -------------- * Analyses (Analysis | list): Analysis object with the calculated parameter or a list of Analyses for more than one parameter. Usage example: >>> myRT = pytta.rooms.analyse(IR, 'RT', 20', 'C', 50, 'D', 80, nthOct=3, minFreq=100, maxFreq=10000) For more tips check the examples folder. """ # Code snippet to guarantee that generated object name is # the declared at global scope # for frame, line in traceback.walk_stack(None): for framenline in traceback.walk_stack(None): # varnames = frame.f_code.co_varnames varnames = framenline[0].f_code.co_varnames if varnames is (): break # creation_file, creation_line, creation_function, \ # creation_text = \ extracted_text = \ traceback.extract_stack(framenline[0], 1)[0] # traceback.extract_stack(frame, 1)[0] # creation_name = creation_text.split("=")[0].strip() creation_name = extracted_text[3].split("=")[0].strip() if not isinstance(obj, SignalObj) and not isinstance( obj, ImpulsiveResponse): raise TypeError("'obj' must be an one channel SignalObj or" + " ImpulsiveResponse.") if isinstance(obj, ImpulsiveResponse): SigObj = obj.systemSignal else: SigObj = obj if SigObj.numChannels > 1: raise TypeError("'obj' can't contain more than one channel.") samplingRate = SigObj.samplingRate SigObj = crop_IR(SigObj, IREndManualCut) calcEDC = False result = [] for param in params: if param in ['RT']: # 'C', 'D']: calcEDC = True break if calcEDC: listEDC = cumulative_integration(SigObj, bypassLundeby, plotLundebyResults, suppressWarnings=suppressWarnings, **kwargs) if 'RT' in params: RTdecay = params[params.index('RT') + 1] if not isinstance(RTdecay, (int)): RTdecay = 20 nthOct = kwargs['nthOct'] RT = reverberation_time(RTdecay, nthOct, samplingRate, listEDC) RTtemp = Analysis(anType='RT', nthOct=nthOct, minBand=kwargs['minFreq'], maxBand=kwargs['maxFreq'], data=RT) RTtemp.creation_name = creation_name result.append(RTtemp) # if 'C' in params: # Cparam = params[params.index('C')+1] # if not isinstance(Cparam,(int)): # Cparam = 50 # Ctemp = None # result.append(Ctemp) # if 'D' in params: # Dparam = params[params.index('D')+1] # if not isinstance(Dparam,(int)): # Dparam = 50 # Dtemp = None # result.append(Dtemp) if len(result) == 1: result = result[0] return result