def main(): stock = FrequencyResponse.read_from_csv('data/HE400S stock.csv') focus = FrequencyResponse.read_from_csv('data/HE400S focus.csv') base = FrequencyResponse.read_from_csv('innerfidelity/data/HiFiMAN HE400S/HiFiMAN HE400S.csv') stock.interpolate(f_min=20, f_max=20000) stock.center() focus.interpolate(f_min=20, f_max=20000) focus.center() base.interpolate(f=stock.frequency) base.center() diff = FrequencyResponse(name='Diff', frequency=stock.frequency, raw=focus.raw-stock.raw) fr = FrequencyResponse(name='HE400S with Focus Pads', frequency=stock.frequency, raw=base.raw+diff.raw) _fr = FrequencyResponse(name='debug', frequency=stock.frequency, raw=base.raw, smoothed=diff.raw, equalization=fr.raw) _fr.plot_graph() fr.smoothen_fractional_octave(window_size=1 / 5, iterations=10, treble_window_size=1 / 2, treble_iterations=100) #stock.plot_graph() #focus.plot_graph() #diff.plot_graph() #base.plot_graph() #fr.plot_graph() os.makedirs('innerfidelity/data/HiFiMAN HE400S with Focus Pads', exist_ok=True) fr.write_to_csv(file_path='innerfidelity/data/HiFiMAN HE400S with Focus Pads/HiFiMAN HE400S with Focus Pads ORIG.csv') fr.equalize(max_gain=12, smoothen=True, window_size=1 / 5, bass_target=4) fr.write_to_csv(file_path='innerfidelity/data/HiFiMAN HE400S with Focus Pads/HiFiMAN HE400S with Focus Pads.csv') fig, ax = fr.plot_graph(show=False, file_path='innerfidelity/data/HiFiMAN HE400S with Focus Pads/HiFiMAN HE400S with Focus Pads.png') plt.close(fig) fr.write_eqapo_graphic_eq('innerfidelity/data/HiFiMAN HE400S with Focus Pads/HiFiMAN HE400S with Focus Pads EqAPO.txt')
def limited_delta(cls, x, y, limit): peak_finder = cls(name='peak_finder', frequency=x, raw=y) peak_finder.smoothen_fractional_octave(window_size=1 / 12, treble_window_size=1 / 3, treble_f_lower=9000, treble_f_upper=11000) peaks = scipy.signal.find_peaks(-peak_finder.smoothed)[0] forward_start = peaks[0] backward_start = len(x) - peaks[-1] - 1 limited_forward = cls.limited_forward_delta(x, y, limit, start_index=forward_start) limited_backward = cls.limited_forward_delta( np.flip(x), np.flip(y), -limit, start_index=backward_start) limited_backward = np.flip(limited_backward) _fr = FrequencyResponse(name='limiter', frequency=x.copy(), raw=np.min(np.vstack( [limited_forward, limited_backward]), axis=0)) _fr.smoothen_fractional_octave(window_size=1 / 6, treble_window_size=1 / 6) return _fr.smoothed.copy()
def parse_image(im, model): """Parses graph image downloaded from innerfidelity.com""" # Crop by left and right edges box = (69, 31, 550, 290) im = im.crop(box) px_a_max = 0 px_a_min = im.size[1] # im.show() # X axis f_min = 20 f_max = 20000 f_step = (f_max / f_min)**(1 / im.size[0]) f = [f_min] for _ in range(1, im.size[0]): f.append(f[-1] * f_step) # Y axis a_max = 150 a_min = 66 a_res = (a_max - a_min) / (px_a_min - px_a_max) # Try blue curve _im = im.copy() inspection = _im.load() amplitude, _im, _inspection = ReferenceAudioAnalyzerCrawler.find_curve( _im, inspection, 203, 206, 0.8, 1.0, a_max, a_res) if len([x for x in amplitude if x is None]) >= 0.5 * len(amplitude): # More than half of the pixels were discarded, try green curve _im = im.copy() inspection = _im.load() amplitude, _im, _inspection = ReferenceAudioAnalyzerCrawler.find_curve( _im, inspection, 119, 121, 0.8, 1.0, a_max, a_res) # Inspection image draw = ImageDraw.Draw(_im) x0 = np.log(30 / f_min) / np.log(f_step) x1 = np.log(10000 / f_min) / np.log(f_step) y_0 = px_a_max + 12 / a_res y_1 = px_a_min - 12 / a_res draw.rectangle(((x0, y_0), (x1, y_1)), outline='magenta') draw.rectangle(((x0 + 1, y_0 + 1), (x1 - 1, y_1 - 1)), outline='magenta') # Create frequency response fr = FrequencyResponse(model, f, amplitude) fr.interpolate() if len(fr.frequency) < 2: im.show() raise ValueError(f'Failed to parse image for {fr.name}') fr.smoothen_fractional_octave(window_size=1 / 3, treble_window_size=1 / 3) fr.raw = fr.smoothed.copy() fr.smoothed = np.array([]) fr.center() return fr, _im
def main(): fig, ax = plt.subplots() diffs = [] # Calculate differences for all models for file in glob(os.path.join('compensation', 'compensated', '**', '*.csv'), recursive=True): file = os.path.abspath(file) comp = FrequencyResponse.read_from_csv(file) comp.interpolate() comp.center() raw_data_path = file.replace('compensated', 'raw') raw = FrequencyResponse.read_from_csv(raw_data_path) raw.interpolate() raw.center() diff = FrequencyResponse(name=comp.name, frequency=comp.frequency, raw=raw.raw - comp.raw) plt.plot(diff.frequency, diff.raw) diffs.append(diff.raw) # Average and smoothen difference f = FrequencyResponse.generate_frequencies() diffs = np.vstack(diffs) diff = np.mean(diffs, axis=0) diff = FrequencyResponse(name='Headphone.com Compensation', frequency=f, raw=diff) diff.smoothen_fractional_octave(window_size=1 / 9, iterations=10) diff.raw = diff.smoothed diff.smoothed = np.array([]) plt.xlabel('Frequency (Hz)') plt.semilogx() plt.xlim([20, 20000]) plt.ylabel('Amplitude (dBr)') plt.ylim([-15, 15]) plt.grid(which='major') plt.grid(which='minor') plt.title('Headphone.com Compensation Function') ax.xaxis.set_major_formatter(ticker.StrMethodFormatter('{x:.0f}')) plt.show() diff.write_to_csv('headphonecom_compensation.csv') diff.plot_graph(show=True, f_min=10, f_max=20000, file_path='headphonecom_compensation.png')
def main(): harman_onear = FrequencyResponse.read_from_csv( os.path.join(ROOT_DIR, 'compensation', 'harman_over-ear_2018.csv')) harman_onear_wo_bass = FrequencyResponse.read_from_csv( os.path.join(ROOT_DIR, 'compensation', 'harman_over-ear_2018_wo_bass.csv')) harman_inear = FrequencyResponse.read_from_csv( os.path.join(ROOT_DIR, 'compensation', 'harman_in-ear_2019v2.csv')) harman_inear_wo_bass = FrequencyResponse.read_from_csv( os.path.join(ROOT_DIR, 'compensation', 'harman_in-ear_2019v2_wo_bass.csv')) oratory1990_onear = get_measurements( os.path.join(MEASUREMENTS, 'oratory1990', 'data', 'onear')) oratory1990_inear = get_measurements( os.path.join(MEASUREMENTS, 'oratory1990', 'data', 'inear')) crinacle_inear = get_measurements( os.path.join(MEASUREMENTS, 'crinacle', 'data', 'inear')) inear_ref = oratory1990_inear.copy() inear_names = [fr.name for fr in inear_ref] for fr in crinacle_inear: if fr.name not in inear_names: inear_ref.append(fr) dbs = [ ('crinacle_harman_in-ear_2019v2_wo_bass', crinacle_inear, oratory1990_inear, None), ('crinacle_ears-711_harman_over-ear_2018_wo_bass', get_measurements( os.path.join(MEASUREMENTS, 'crinacle', 'data', 'onear', 'Ears-711')), oratory1990_onear, None), ('headphonecom_harman_over-ear_2018_wo_bass', get_measurements( os.path.join(MEASUREMENTS, 'headphonecom', 'data', 'onear')), oratory1990_onear, FrequencyResponse.read_from_csv( os.path.join(MEASUREMENTS, 'headphonecom', 'resources', 'headphonecom_compensation_sbaf-serious.csv'))), ('headphonecom_harman_in-ear_2019v2_wo_bass', get_measurements( os.path.join(MEASUREMENTS, 'headphonecom', 'data', 'inear')), inear_ref, FrequencyResponse.read_from_csv( os.path.join(MEASUREMENTS, 'headphonecom', 'resources', 'headphonecom_compensation_sbaf-serious.csv'))), ('innerfidelity_harman_over-ear_2018_wo_bass', get_measurements( os.path.join(MEASUREMENTS, 'innerfidelity', 'data', 'onear')), oratory1990_onear, FrequencyResponse.read_from_csv( os.path.join(MEASUREMENTS, 'innerfidelity', 'resources', 'innerfidelity_compensation_sbaf-serious.csv'))), ('innerfidelity_harman_in-ear_2019v2_wo_bass', get_measurements( os.path.join(MEASUREMENTS, 'innerfidelity', 'data', 'inear')), inear_ref, FrequencyResponse.read_from_csv( os.path.join(MEASUREMENTS, 'innerfidelity', 'resources', 'innerfidelity_compensation_sbaf-serious.csv'))), ('referenceaudioanalyzer_hdm-x_harman_over-ear_2018_wo_bass', get_measurements( os.path.join(MEASUREMENTS, 'referenceaudioanalyzer', 'data', 'onear', 'HDM-X')), oratory1990_onear, None), ('referenceaudioanalyzer_hdm1_harman_over-ear_2018_wo_bass', get_measurements( os.path.join(MEASUREMENTS, 'referenceaudioanalyzer', 'data', 'onear', 'HDM1')), oratory1990_onear, None), ('referenceaudioanalyzer_siec_harman_in-ear_2019v2_wo_bass', get_measurements( os.path.join(MEASUREMENTS, 'referenceaudioanalyzer', 'data', 'inear', 'SIEC')), inear_ref, None), ('rtings_harman_over-ear_2018_wo_bass', get_measurements(os.path.join(MEASUREMENTS, 'rtings', 'data', 'onear')), oratory1990_onear, FrequencyResponse.read_from_csv( os.path.join(MEASUREMENTS, 'rtings', 'resources', 'rtings_compensation_avg.csv'))), ('rtings_harman_in-ear_2019v2_wo_bass', get_measurements(os.path.join(MEASUREMENTS, 'rtings', 'data', 'inear')), inear_ref, FrequencyResponse.read_from_csv( os.path.join(MEASUREMENTS, 'rtings', 'resources', 'rtings_compensation_avg.csv'))), ('crinacle_gras_43ag-7_harman_over-ear_2018_wo_bass', get_measurements( os.path.join(MEASUREMENTS, 'crinacle', 'data', 'onear', 'GRAS 43AG-7')), oratory1990_onear, None) ] stds = [] for name, measurements, ref, original_target in dbs: print(f'Calibrating {name}...') # Find matching pairs pairs = [] for fr in measurements: for candidate in ref: if fr.name.lower() == candidate.name.lower(): pairs.append((fr, candidate)) fig, axs = plt.subplots(1, 3) fig.set_size_inches(30, 8) fig.suptitle(name) description = 'Calibrated against reference measurements with headphones: ' line_len = len(description) for fr, _ in pairs: if line_len > 240: description += '\n' line_len = 0 description += f'{fr.name}, ' line_len += len(fr.name) + 2 description = description[:-2] fig.text(0.5, -0.05, description, ha='center') # Individual errors errors = [] i = 0 for fr, target in pairs: fr.compensate(target, min_mean_error=True) errors.append(fr.error) fr.raw = fr.error.copy() fr.error = [] fr.target = [] fr.plot_graph(fig=fig, ax=axs[0], show=False, raw_plot_kwargs={ 'color': 'C0', 'alpha': 0.3 }) i += 1 axs[0].set_ylim([-15, 15]) axs[0].set_title('Individual Errors') axs[0].legend(['Error']) # Mean and standard deviation errors = np.vstack(errors) mean = np.mean(errors, axis=0) std = np.std(errors, axis=0) stds.append(FrequencyResponse(name=name, raw=std)) fr = FrequencyResponse(name='Mean and Standard Deviation') fr.raw = mean fr.smoothen_fractional_octave(window_size=1 / 3, treble_window_size=1 / 3) fr.raw = fr.smoothed.copy() fr.smoothed = [] fr.plot_graph(fig=fig, ax=axs[1], color='C0', show=False) axs[1].fill_between(fr.frequency, mean - std, mean + std, facecolor='#c1dff5') axs[1].set_ylim([-15, 15]) axs[1].legend(['Mean', 'STD']) # Target curves ref_target = harman_onear_wo_bass if 'over-ear' in name else harman_inear_wo_bass ref_target.plot_graph(fig=fig, ax=axs[2], show=False, color='C0') target = ref_target.copy() target.name = name target.raw += fr.raw target.plot_graph(fig=fig, ax=axs[2], show=False, color='C1') if original_target is not None: original_target.plot_graph(fig=fig, ax=axs[2], show=False, color='C2') axs[2].legend([ref_target.name, target.name, original_target.name]) else: axs[2].legend([ref_target.name, target.name]) axs[2].set_title(f'{name} target') axs[2].set_ylim([-15, 15]) fig.savefig(os.path.join(DIR_PATH, f'calibration_{name}.png'), bbox_inches='tight') target.plot_graph(show=False, file_path=os.path.join(DIR_PATH, f'{name}.png'), color='C0') target.write_to_csv(file_path=os.path.join(DIR_PATH, f'{name}.csv')) plt.close(fig) fig, axs = plt.subplots(1, 2) fig.set_size_inches(20, 8) onear_labels = [] inear_labels = [] for fr in stds: if 'over-ear' in fr.name: fr.plot_graph(fig=fig, ax=axs[0], color=f'C{len(onear_labels)}', show=False) onear_labels.append(fr.name) else: fr.plot_graph(fig=fig, ax=axs[1], color=f'C{len(inear_labels)}', show=False) inear_labels.append(fr.name) axs[0].legend(onear_labels) axs[1].legend(inear_labels) axs[0].set_title('On-ear') axs[1].set_title('In-ear') axs[0].set_ylim([0, 8]) axs[1].set_ylim([0, 8]) fig.savefig(os.path.join(DIR_PATH, 'STDs.png')) plt.close(fig)
def equalize(self, max_gain=DEFAULT_MAX_GAIN, limit=18, limit_decay=0.0, concha_interference=False, window_size=1 / 12, treble_window_size=2, treble_f_lower=DEFAULT_TREBLE_F_LOWER, treble_f_upper=DEFAULT_TREBLE_F_UPPER): """Creates equalization curve and equalized curve. Args: max_gain: Maximum positive gain in dB limit: Maximum slope in dB per octave limit_decay: Decay coefficient (per octave) for the limit. Value of 0.5 would reduce limit by 50% in an octave when traversing a single limitation zone. concha_interference: Do measurements include concha interference which produced a narrow dip around 9 kHz? window_size: Smoothing window size in octaves. treble_window_size: Smoothing window size in octaves in the treble region. treble_f_lower: Lower boundary of transition frequency region. In the transition region normal filter is \ switched to treble filter with sigmoid weighting function. treble_f_upper: Upper boundary of transition frequency reqion. In the transition region normal filter is \ switched to treble filter with sigmoid weighting function. Returns: """ fr = FrequencyResponse(name='fr', frequency=self.frequency, raw=self.error) # Smoothen data heavily in the treble region to avoid problems caused by peakiness fr.smoothen_fractional_octave(window_size=window_size, treble_window_size=treble_window_size, treble_f_lower=treble_f_lower, treble_f_upper=treble_f_upper) # Copy data x = np.array(fr.frequency) y = np.array(-fr.smoothed) # Inverse of the smoothed error # Find peaks and notches peak_inds, peak_props = find_peaks(y, prominence=1) dip_inds, dip_props = find_peaks(-y, prominence=1) if not len(peak_inds) and not len(dip_inds): self.equalization = y # Equalized self.equalized_raw = self.raw + self.equalization if len(self.smoothed): self.equalized_smoothed = self.smoothed + self.equalization return y, fr.smoothed.copy(), np.array([]), np.array([False] * len(y)), np.array([]),\ np.array([False] * len(y)), np.array([]), np.array([]), len(y) - 1, np.array([False] * len(y)) else: limit_free_mask = self.protection_mask(y, peak_inds, dip_inds) if concha_interference: # 8 kHz - 11.5 kHz should not be limit free zone limit_free_mask[np.logical_and(x >= 8000, x <= 11500)] = False # Find rtl start index rtl_start = self.find_rtl_start(y, peak_inds, dip_inds) # Find ltr and rtl limitations # limited_ltr is y but with slopes limited when traversing left to right # clipped_ltr is boolean mask for limited samples when traversing left to right # limited_rtl is found using ltr algorithm but with flipped data limited_ltr, clipped_ltr, regions_ltr = self.limited_ltr_slope( x, y, limit, limit_decay=limit_decay, start_index=0, peak_inds=peak_inds, limit_free_mask=limit_free_mask, concha_interference=concha_interference) limited_rtl, clipped_rtl, regions_rtl = self.limited_rtl_slope( x, y, limit, limit_decay=limit_decay, start_index=rtl_start, peak_inds=peak_inds, limit_free_mask=limit_free_mask, concha_interference=concha_interference) # ltr and rtl limited curves are combined with min function combined = self.__class__(name='limiter', frequency=x, raw=np.min(np.vstack( [limited_ltr, limited_rtl]), axis=0)) # Gain can be reduced in the treble region # Clip positive gain to max gain combined.raw = np.min(np.vstack( [combined.raw, np.ones(combined.raw.shape) * max_gain]), axis=0) # Smoothen the curve to get rid of hard kinks combined.smoothen_fractional_octave(window_size=1 / 5, treble_window_size=1 / 5) # TODO: Fix trend by comparing super heavy smoothed equalizer frequency responses: limited vs unlimited # Equalization curve self.equalization = combined.smoothed # Equalized self.equalized_raw = self.raw + self.equalization if len(self.smoothed): self.equalized_smoothed = self.smoothed + self.equalization return combined.smoothed.copy(), fr.smoothed.copy(), limited_ltr, clipped_ltr, limited_rtl,\ clipped_rtl, peak_inds, dip_inds, rtl_start, limit_free_mask
def main(): # Filenames if_files = list( glob(os.path.join('innerfidelity', 'data', '**', '*.csv'), recursive=True)) if_file_names = [os.path.split(os.path.abspath(f))[-1] for f in if_files] normalized_if_files = [normalize(s) for s in if_file_names] hp_files = list( glob(os.path.join('rtings', 'data', '**', '*.csv'), recursive=True)) # Find matching files matching_if_files = [] matching_hp_files = [] for hp_file in hp_files: file_name = os.path.split(os.path.abspath(hp_file))[-1] for i in range(len(normalized_if_files)): if normalized_if_files[i] == normalize(file_name): matching_hp_files.append(hp_file) matching_if_files.append(if_files[i]) # Write mathces to file for manual inspection df = pd.DataFrame( np.array([matching_hp_files, matching_if_files]).transpose()) df.to_csv('matches.csv', index=False, header=False) fig, ax = plt.subplots() diffs = [] # Calculate differences for all models if_compensation = FrequencyResponse.read_from_csv( os.path.join('innerfidelity', 'resources', 'innerfidelity_compensation_2017.csv')) if_compensation.interpolate() hp_compensation = FrequencyResponse.read_from_csv( os.path.join('rtings', 'resources', 'rtings_compensation.csv')) hp_compensation.interpolate() for i in range(len(matching_if_files)): if_fr = FrequencyResponse.read_from_csv(matching_if_files[i]) if_fr.interpolate() if_fr.center() #if_fr.compensate(if_compensation) hp_fr = FrequencyResponse.read_from_csv(matching_hp_files[i]) hp_fr.interpolate() hp_fr.center() #hp_fr.compensate(hp_compensation) #diff = FrequencyResponse(name=if_fr.name, frequency=if_fr.frequency, raw=hp_fr.error - if_fr.error) diff = FrequencyResponse(name=if_fr.name, frequency=if_fr.frequency, raw=hp_fr.raw - if_fr.raw) plt.plot(diff.frequency, diff.raw) diffs.append(diff.raw) # Average and smoothen difference f = FrequencyResponse.generate_frequencies() diffs = np.vstack(diffs) diff = np.mean(diffs, axis=0) std = np.std(diffs, axis=0) diff = FrequencyResponse(name='Rtings Raw to Innerfidelity Raw', frequency=f, raw=diff) #diff.smoothen(window_size=1/7, iterations=10) diff.smoothen_fractional_octave(window_size=1 / 5, iterations=100) diff.raw = diff.smoothed diff.smoothed = np.array([]) plt.xlabel('Frequency (Hz)') plt.semilogx() plt.xlim([20, 20000]) plt.ylabel('Amplitude (dBr)') plt.ylim([-15, 15]) plt.grid(which='major') plt.grid(which='minor') plt.title('Rtings Raw to Innerfidelity Raw') ax.xaxis.set_major_formatter(ticker.StrMethodFormatter('{x:.0f}')) plt.show() fig, ax = diff.plot_graph(f_min=20, f_max=20000, show=False, color=None) ax.fill_between(diff.frequency, diff.raw + std, diff.raw - std, facecolor='lightblue') plt.legend(['Rtings Raw to Innerfidelity Raw', 'Standard Deviation']) plt.ylim([-10, 10]) fig.savefig(os.path.join('calibration', 'rtings_to_innerfidelity.png'), dpi=240) plt.show() diff.write_to_csv( os.path.join('calibration', 'rtings_to_innerfidelity.csv')) diff.raw *= -1 diff.name = 'Innerfidelity Raw to Rtings Raw' fig, ax = diff.plot_graph(f_min=20, f_max=20000, show=False, color=None) ax.fill_between(diff.frequency, diff.raw + std, diff.raw - std, facecolor='lightblue') plt.legend(['Innerfidelity Raw to Rtings Raw', 'Standard Deviation']) plt.ylim([-10, 10]) fig.savefig(os.path.join('calibration', 'innerfidelity_to_rtings.png'), dpi=240) plt.show() diff.write_to_csv( os.path.join('calibration', 'innerfidelity_to_rtings.csv'))
def limited_slope(x, y, limit, limit_decay=0.0): """Bi-directional slope limitation for a frequency response curve Args: x: frequencies y: amplitudes limit: Maximum slope in dB per octave limit_decay: Decay coefficient (per octave) for the limit. Value of 0.5 would reduce limit by 50% in an octave when traversing a single limitation zone. Returns: """ fr = FrequencyResponse(name='fr', frequency=x, raw=y) # Smoothen data, heavily on treble to avoid problems in +10 kHz region fr.smoothen_fractional_octave(window_size=1 / 12, treble_window_size=2, treble_f_lower=9000, treble_f_upper=11500) # Copy data x = fr.frequency.copy() y = fr.smoothed.copy() # Find peaks and notches # TODO: these affect which regions are rejected peak_inds, peak_props = scipy.signal.find_peaks(y, prominence=1) dip_inds, dip_props = scipy.signal.find_peaks(-y, prominence=1) limit_free_mask = protection_mask(y, dip_inds) # Find backward start index backward_start = find_backward_start(y, peak_inds, dip_inds) # TODO: backward start # Find forward and backward limitations # limited_forward is y but with slopes limited when traversing left to right # clipped_forward is boolean mask for limited samples when traversing left to right # limited_backward is found using forward algorithm but with flipped data limited_forward, clipped_forward, regions_forward = limited_forward_slope( x, y, limit, limit_decay=limit_decay, start_index=0, peak_inds=peak_inds, limit_free_mask=limit_free_mask) limited_backward, clipped_backward, regions_backward = limited_backward_slope( x, y, limit, limit_decay=limit_decay, start_index=backward_start, peak_inds=peak_inds, limit_free_mask=limit_free_mask) # TODO: Find notches which are lower in level than adjacent notches # TODO: Set detected notches as slope clipping free zones up to levels of adjacent notches # Forward and backward limited curves are combined with min function # Combination function is smoothed to get rid of hard kinks limiter = FrequencyResponse(name='limiter', frequency=x.copy(), raw=np.min(np.vstack( [limited_forward, limited_backward]), axis=0)) limiter.smoothen_fractional_octave(window_size=1 / 5, treble_window_size=1 / 5) #limiter.smoothed = limiter.raw.copy() return limiter.smoothed.copy(), fr.smoothed.copy(), limited_forward, clipped_forward, limited_backward, clipped_backward, \ peak_inds, dip_inds, backward_start, limit_free_mask
def limited_slope_plots(fr, limit): # Smoothen data, heavily on treble to avoid problems in +10 kHz region fr.smoothen_fractional_octave(window_size=1 / 12, treble_window_size=2, treble_f_lower=9000, treble_f_upper=12000) # Copy data x = fr.frequency.copy() y = fr.smoothed.copy() # Find peaks and notches peak_inds, peak_props = scipy.signal.find_peaks( y, prominence=1) # TODO: this affects which regions are rejected notch_inds, notch_props = scipy.signal.find_peaks( -y, prominence=1) # TODO: this affects which regions are protected # Find backward start index backward_start = find_backward_start(y, peak_inds, notch_inds) # TODO: backward start #backward_start = 0 # Find forward and backward limitations # limited_forward is y but with slopes limited when traversing left to right # clipped_forward is boolean mask for limited samples when traversing left to right # limited_backward is found using forward algorithm but with flipped data limited_forward, clipped_forward, regions_forward = limited_forward_slope( x, y, limit, start_index=0, peak_inds=peak_inds) limited_backward, clipped_backward, regions_backward = limited_backward_slope( x, y, limit, start_index=backward_start, peak_inds=peak_inds) # TODO: Find notches which have backward region on left side, forward region on right side and are lower in level than adjacent notches # TODO: Set detected notches as slope clipping free zones up to levels of adjacent notches # Forward and backward limited curves are combined with min function # Combination function is smoothed to get rid of hard kinks limiter = FrequencyResponse(name='limiter', frequency=x.copy(), raw=np.min(np.vstack( [limited_forward, limited_backward]), axis=0)) limiter.smoothen_fractional_octave(window_size=1 / 3, treble_window_size=1 / 3) limited = limiter.smoothed # Plot graphs fig, ax = fr.plot_graph(show=False, raw_plot_kwargs={'color': 'C2'}, smoothed_plot_kwargs={ 'color': 'C2', 'linewidth': 1, 'linestyle': 'dashed' }) fig.set_size_inches(20, 9) ax.plot(x, limited, label='Limited', color='C1') ax.fill_between(x, clipped_forward * -5, clipped_forward * 10, label='Clipped left to right', color='blue', alpha=0.1) ax.fill_between(x, clipped_backward * -10, clipped_backward * 5, label='Clipped right to left', color='red', alpha=0.1) ax.scatter(x[peak_inds], y[peak_inds], color='red') ax.scatter(x[notch_inds], y[notch_inds], color='blue') ax.scatter(x[backward_start], y[backward_start], 300, marker='X', label='Backward start', color='magenta', alpha=0.4) ax.legend() ax.set_xlim([300, 20000]) return fig, ax