def test_loudness_roundtrip(self): for lo in (55, 62, 73, 84): for frequency in (58, 120, 223, 489, 4000, 9800): spl = loudness.loudness_to_spl(lo, frequency) lo_roundtrip = loudness.spl_to_loudness(spl, frequency) self.assertLess(abs(lo - lo_roundtrip), 5e-2)
def plot_iso_examples(data: Dict[int, Dict[str, Any]], path: str): """Plot ISO equal loudness curves w/ markers for the data examples.""" _, ax = plt.subplots(1, 1, figsize=(12, 14)) frequencies_on_range = [i for i in range(20, 20000, 10)] # These are the colors that will be used in the plot colors = [ "#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5" ] ax.set_prop_cycle(color=colors) plt.xscale("log") ax.set_xlabel("Frequency (Hz)") ax.set_ylabel("SPL (dB)") ax.set_title("ISO equal loudness curves") phons_levels = [i * 10 for i in range(10)] legend_handles = ["{} Phons".format(phons) for phons in phons_levels] levels_per_phons = [] for phons in phons_levels: levels = [] for frequency in frequencies_on_range: level = loudness.loudness_to_spl(phons, frequency) levels.append(level) levels_per_phons.append(levels) for i, y in enumerate(levels_per_phons): plt.plot(frequencies_on_range, y, label=legend_handles[i]) for i, examples in enumerate(data.values()): level = examples["ref1000_spl"] plt.scatter(1000, level, marker="x", c="b") color = colors[i] for other_tone in examples["other_tones"]: if "error" in other_tone: plt.errorbar(other_tone["frequency"], other_tone["level"], c=color, yerr=other_tone["error"], fmt="o") else: plt.scatter(other_tone["frequency"], other_tone["level"], marker="x", c=color) ax.legend() plt.savefig(os.path.join(path, "iso_repro.png"))
def plot_loudness_conversion(path: str, phons_levels: List[int], frequencies: List[int]): """PLot ISO loudness curves with loudness to decibel conversion.""" _, ax = plt.subplots(1, 1, figsize=(12, 14)) # These are the colors that will be used in the plot ax.set_prop_cycle(color=[ "#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5" ]) plt.xscale("log") levels_per_phons = [] for phons in phons_levels: levels = [] for frequency in frequencies: level = loudness.loudness_to_spl(phons, frequency) levels.append(level) levels_per_phons.append(levels) for y in levels_per_phons: plt.plot(frequencies, y) plt.savefig(path)
def generate_data( num_examples_per_cb: int, min_tones: int, max_tones: int, clip_db: int, desired_skewness: int, desired_mean: int, desired_variance: int, min_frequency: int, max_frequency: int, critical_bands: List[int], min_phons=0, max_phons=80, max_iterations=1000000) -> Dict[int, List[Dict[str, List[int]]]]: """Generates all listening data. Generates examples until each critical band has exactly `num_examples_per_cb` number of examples. TODO(lauraruis): check if n_per_cb cannot be generated (e.g. due to too large beatrange) Args: num_examples_per_cb: how many frequencies to sample per CB min_tones: minimum number of frequencies per example max_tones: maximum number of frequencies per example clip_db: maximum spl desired_skewness: skewness of skewed normal distr. for phons desired_mean: mean of skewed normal distr. for phons desired_variance: variance of skewed normal distr. for phons min_frequency: below this frequency no tones will be generated max_frequency: above this frequency no tones will be generated critical_bands: list of critical bands min_phons: minimum level of phons for examples max_phons: maximum level of phons for examples max_iterations: how long to try combining examples Returns: Dict with data examples. """ # Initialize the structures for keeping the data. data = {i: [] for i in range(min_tones, max_tones + 1)} num_examples = 0 # Calculate the needed shift and scaling for the SN distribution over phons. sn_shift, sn_scale = distributions.calculate_shift_scaling_skewnorm( desired_variance=desired_variance, desired_mean=desired_mean, alpha=desired_skewness) # Sample n examples per critical band. examples_per_cb = sample_n_per_cb(num_examples_per_cb, critical_bands) # Generate examples by combining a subset of tones until they run out. while len(examples_per_cb) >= min_tones: # Sample a number of tones for the example. num_tones = min(len(examples_per_cb), random.randint(min_tones, max_tones)) # If less than max_tones examples left, check how far apart they are. if len(examples_per_cb) <= max_tones: if not check_frequencies( np.array([ex.frequency for ex in examples_per_cb]), min_frequency, max_frequency): break # Sample frequencies from the pre-generated tones until they satisfy the # constraint of being beat_range apart. frequencies = np.array([100] * num_tones) iteration = 0 while not check_frequencies( frequencies, min_frequency, max_frequency) and iteration < max_iterations: sampled_idxs = random.sample(range(len(examples_per_cb)), k=num_tones) for i, sampled_idx in enumerate(sampled_idxs): frequencies[i] = examples_per_cb[sampled_idx].frequency iteration += 1 # If the correct frequencies weren't found, stop generation. if iteration >= max_iterations: print("WARNING: didn't find correct frequencies: ", frequencies) break # Delete the used tones from the pregenerated list. for sampled_idx in sorted(sampled_idxs, reverse=True): del examples_per_cb[sampled_idx] # Sample phons for each tone on a skewed normal distribution. phons = np.array([100] * num_tones) while not check_phons(phons, min_phons, max_phons): phons = distributions.sample_skewed_distribution( sn_shift, sn_scale, num_samples=num_tones, alpha=desired_skewness) phons = np.round(phons) num_examples += 1 # Convert phons to sound pressure level in decibel with the ISO 226. sp_levels = [ np.round( loudness.loudness_to_spl(loudness_phon=loudness_phons, frequency=frequency)) for loudness_phons, frequency in zip(phons, frequencies) ] sp_levels = np.clip(np.array(sp_levels), a_min=0, a_max=clip_db) data[num_tones].append({ "frequencies": list(frequencies), "phons": list(phons), "levels": list(sp_levels) }) return data
def generate_iso_repro_examples(num_examples_per_curve: int, num_curves: int, clip_db=80) -> Dict[int, Dict[str, Any]]: """Generates a particular amount of examples per ISO equal loudness curve. Args: num_examples_per_curve: how many examples per curve to generate num_curves: how many equal loudness curves (phons) to generate examples from clip_db: maximum decibel level Returns: The data in a dict with as key the phons level (curve) and as values the reference tone (1000 Hz) level and a list of examples for that curve. """ available_phons_levels = [20, 30, 40, 50, 60, 70, 80] assert num_curves < len( available_phons_levels), "Too many curves specified." phons_ptr = len(available_phons_levels) // 2 chosen_phons_levels = [] while len(chosen_phons_levels) < num_curves: chosen_phons_levels.append(available_phons_levels[phons_ptr]) del available_phons_levels[phons_ptr] phons_ptr = len(available_phons_levels) // 2 chosen_phons_levels.sort() # Find minimum frequency and phons level before it exceeds 90 dB max_phons = chosen_phons_levels[-1] frequency_ptr = -1 spl = clip_db + 1 while spl > clip_db and frequency_ptr < len(ISO_FREQUENCIES): frequency_ptr += 1 spl = loudness.loudness_to_spl(max_phons, ISO_FREQUENCIES[frequency_ptr]) min_frequency = ISO_FREQUENCIES[frequency_ptr] # Find maximum frequency and phons level before it exceeds 90 dB frequency_ptr = len(ISO_FREQUENCIES) // 2 spl = 0 while spl < clip_db and frequency_ptr < len(ISO_FREQUENCIES): spl = loudness.loudness_to_spl(max_phons, ISO_FREQUENCIES[frequency_ptr]) frequency_ptr += 1 max_frequency = ISO_FREQUENCIES[frequency_ptr - 1] if min_frequency > max_frequency: raise ValueError( "Can't generate examples below {} decibel for {} curves. " "Consider increasing clip_db or decreasing num_curves.".format( clip_db, num_curves)) # Get the right number of frequencies with equal distance between them. frequency_steps = (np.log(max_frequency) - np.log(min_frequency)) / num_examples_per_curve frequencies = [ np.log(min_frequency) + frequency_steps * i for i in range(num_examples_per_curve) ] frequencies = [np.round(np.exp(frequency)) for frequency in frequencies] # Generate the data data = {} for phons_level in chosen_phons_levels: data[phons_level] = { "ref1000_spl": np.round(loudness.loudness_to_spl(phons_level, 1000)), "other_tones": [] } for frequency in frequencies: spl = loudness.loudness_to_spl(phons_level, frequency) data[phons_level]["other_tones"].append({ "frequency": frequency, "level": np.round(spl) }) return data