Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Example 8 - calculate RMS, dBSPL and dBA levels #254

Merged
merged 19 commits into from
Mar 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,8 @@ doc/source/api/_autosummary/
.vscode/

# Data output
tests/data/output/*.wav
tests/data/output/*.wav

# Example outputs
examples/*.csv
/*.csv
1 change: 1 addition & 0 deletions doc/changelog.d/254.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
feat: Example 8 - calculate RMS, dBSPL and dBA levels
228 changes: 228 additions & 0 deletions examples/008_calculate_levels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""
.. _calculate_levels:

Calculate RMS, dBSPL and dBA levels
-----------------------------------

This example shows how to calculate RMS, dBSPL and dBA levels.
The following levels are included:

- Overall RMS level
- Overall dBSPL level
- Overall dBA level
- RMS level over time
- dBSPL level over time
- dBA level over time

The example shows how to perform these operations:

- Set up the analysis.
- Calculate levels on loaded WAV files.
- Get calculation outputs.
- Plot some corresponding curves.
- Export levels into a .csv files.

"""

# %%
# Set up analysis
# ~~~~~~~~~~~~~~~
# Setting up the analysis consists of loading Ansys libraries, and connecting to the DPF server.

# Load standard libraries.
import csv
import os

import matplotlib.pyplot as plt

# Load Ansys libraries.
from ansys.sound.core.examples_helpers import (
download_aircraft_wav,
download_fan_wav,
)
from ansys.sound.core.server_helpers import connect_to_or_start_server
from ansys.sound.core.signal_utilities import LoadWav
from ansys.sound.core.standard_levels import LevelOverTime, OverallLevel

# Connect to a remote server or start a local server.
my_server = connect_to_or_start_server(use_license_context=True)


# %%
# Calculate overall RMS, dBSPL and dBA levels
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Load a signal from a WAV file using the :class:`.LoadWav` class. It is returned as a
# :class:`FieldsContainer <ansys.dpf.core.fields_container.FieldsContainer>` object.

# Load example data from two wav files.
path_fan_wav = download_fan_wav(server=my_server)
path_aircraft_wav = download_aircraft_wav(server=my_server)


# %%
# Create a first .csv file in which the overall levels will be written, and write the header row.
filepath_overall_results = open("Overall_Results.csv", "w+", newline="")
csv_writer_overall_results = csv.writer(filepath_overall_results)
header_row = ["Signal name", "Overall level [RMS]", "Overall level [dBSPL]", "Overall level [dBA]"]
_ = csv_writer_overall_results.writerow(header_row)


# %%
# For each sound, create an :class:`.OverallLevel` object, set its signals, compute the overall RMS,
# dBSPL and dBA levels, and then write the results into the .csv file.

for file_path in (path_fan_wav, path_aircraft_wav):
# Load wav.
wav_loader = LoadWav(file_path)
wav_loader.process()
signal = wav_loader.get_output()[0]

# Calculate RMS.
level_RMS = OverallLevel(signal=signal, scale="RMS")
level_RMS.process()
rms = level_RMS.get_level()

# Calculate dBSPL.
level_dBSPL = OverallLevel(signal=signal, scale="dB", reference_value=2e-5)
level_dBSPL.process()
dBSPL = level_dBSPL.get_level()

# Calculate dBA.
level_dBA = OverallLevel(
signal=signal, scale="dB", reference_value=2e-5, frequency_weighting="A"
)
level_dBA.process()
dBA = level_dBA.get_level()

# Print the results.
file_name = os.path.basename(file_path)
print(
f"\nThe RMS level of sound file {file_name} is {rms:.1f} Pa, its dBSPL level is"
f" {dBSPL:.1f} dBSPL and its dBA level is {dBA:.1f} dBA."
)

# Write the results in .csv.
csv_writer_overall_results.writerow([file_name[:-4], rms, dBSPL, dBA])

filepath_overall_results.close()


# %%
# Calculate RMS, dBSPL and dBA levels over time
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Initialize empty lists to store the levels in order to plot them.
time = []
rms_levels = []
dBSPL_levels = []
dBA_levels = []

# %%
# For each sound, create a :class:`.LevelOverTime` object, set its signals, compute and plot the
# RMS level over time, the dBSPL level over time and the dBA level over time, then write the
# results into an individual .csv file.

for file_path in (path_fan_wav, path_aircraft_wav):
# Write a .csv file for each sound, and add the header row.
file = os.path.basename(file_path)
filepath_results_vs_time = open(
os.path.join(file[:-4] + "_Levels_vs_time_Results.csv"), "w+", newline=""
)
csv_writer_results_vs_time = csv.writer(filepath_results_vs_time)
csv_writer_results_vs_time.writerow(["time steps [s]", "RMS level", "dBSPL level", "dBA level"])

# Load wav.
wav_loader = LoadWav(file_path)
wav_loader.process()
signal = wav_loader.get_output()[0]

# Calculate RMS over time and get the time steps.
rms_time_varying = LevelOverTime(
signal=signal, scale="RMS", frequency_weighting="", time_weighting="Fast"
)
rms_time_varying.process()
rms = rms_time_varying.get_level_over_time()
time_steps = rms_time_varying.get_time_scale()
time.append(time_steps.tolist())

# Calculate dBSPL over time.
dBSPL_time_varying = LevelOverTime(
signal=signal,
scale="dB",
reference_value=2e-5,
frequency_weighting="",
time_weighting="Fast",
)
dBSPL_time_varying.process()
dBSPL = dBSPL_time_varying.get_level_over_time()

# Calculate dBA over time.
dBA_time_varying = LevelOverTime(
signal=signal,
scale="dB",
reference_value=2e-5,
frequency_weighting="A",
time_weighting="Fast",
)
dBA_time_varying.process()
dBA = dBA_time_varying.get_level_over_time()

# Append all the results to the lists previously created.
rms_levels.append(rms.tolist())
dBSPL_levels.append(dBSPL.tolist())
dBA_levels.append(dBA.tolist())

# Write the results in the .csv files.
for i in range(len(time_steps)):
csv_writer_results_vs_time.writerow([time_steps[i], rms[i], dBSPL[i], dBA[i]])

# %%
# Use the object's ``plot()`` method to plot the level over time (here, level in dBA, for the
# second signal).

dBA = dBA_time_varying.plot()

# %%
# Alternatively, plot the results over time for both signals into three graphs.

fig, axs = plt.subplots(3)
fig.suptitle("Time varying RMS, dBSPL and dBA levels")

axs[0].plot(time[0], rms_levels[0], color="b", label="Fan")
axs[0].plot(time[1], rms_levels[1], color="r", label="Airplane")
axs[0].set_ylabel("RMS (Pa)")
axs[0].legend(loc="upper right")

axs[1].plot(time[0], dBSPL_levels[0], color="b", label="Fan")
axs[1].plot(time[1], dBSPL_levels[1], color="r", label="Airplane")
axs[1].set_ylabel("dBSPL")
axs[1].legend(loc="upper right")

axs[2].plot(time[0], dBA_levels[0], color="b", label="Fan")
axs[2].plot(time[1], dBA_levels[1], color="r", label="Airplane")
axs[2].set_ylabel("dBA")
axs[2].legend(loc="upper right")
axs[2].set_xlabel("Time (s)")

plt.show()
4 changes: 4 additions & 0 deletions src/ansys/sound/core/examples_helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
download_accel_with_rpm_2_wav,
download_accel_with_rpm_3_wav,
download_accel_with_rpm_wav,
download_aircraft_wav,
download_fan_wav,
download_flute_psd,
download_flute_wav,
download_xtract_demo_signal_1_wav,
Expand All @@ -42,4 +44,6 @@
"download_accel_with_rpm_3_wav",
"download_xtract_demo_signal_1_wav",
"download_xtract_demo_signal_2_wav",
"download_fan_wav",
"download_aircraft_wav",
)
22 changes: 22 additions & 0 deletions src/ansys/sound/core/examples_helpers/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,25 @@ def download_xtract_demo_signal_2_wav(server=None):
Path for the ``xtract_demo_signal_2.wav`` file.
"""
return _download_example_file_to_server_tmp_folder("xtract_demo_signal_2.wav", server=server)


def download_fan_wav(server=None):
"""Download the ``Fan.wav`` file.

Returns
-------
str
Path for the ``Fan.wav`` file.
"""
return _download_example_file_to_server_tmp_folder("Fan.wav", server=server)


def download_aircraft_wav(server=None):
"""Download the ``Aircraft.wav`` file.

Returns
-------
str
Path for the ``Aircraft.wav`` file.
"""
return _download_example_file_to_server_tmp_folder("Aircraft.wav", server=server)
20 changes: 19 additions & 1 deletion tests/test_example_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
download_accel_with_rpm_2_wav,
download_accel_with_rpm_3_wav,
download_accel_with_rpm_wav,
download_aircraft_wav,
download_fan_wav,
download_flute_psd,
download_flute_wav,
download_xtract_demo_signal_1_wav,
Expand All @@ -39,7 +41,6 @@ def test_download_flute_psd():
download_flute_psd()[0]
p = str(EXAMPLES_PATH) + "/flute_psd.txt"
assert pathlib.Path(p).exists() == True

assert os.path.getsize(p) == 118119


Expand All @@ -55,10 +56,12 @@ def test_download_accel_with_rpm_wav():
p = str(EXAMPLES_PATH) + "/accel_with_rpm.wav"
assert pathlib.Path(p).exists() == True
assert os.path.getsize(p) == 3639982

p = str(EXAMPLES_PATH) + "/accel_with_rpm_2.wav"
download_accel_with_rpm_2_wav()[0]
assert pathlib.Path(p).exists() == True
assert os.path.getsize(p) == 3639982

p = str(EXAMPLES_PATH) + "/accel_with_rpm_3.wav"
download_accel_with_rpm_3_wav()[0]
assert pathlib.Path(p).exists() == True
Expand All @@ -70,7 +73,22 @@ def test_download_xtract_demo_signal_wav():
p = str(EXAMPLES_PATH) + "/xtract_demo_signal_1.wav"
assert pathlib.Path(p).exists() == True
assert os.path.getsize(p) == 882363

p = str(EXAMPLES_PATH) + "/xtract_demo_signal_2.wav"
download_xtract_demo_signal_2_wav()[0]
assert pathlib.Path(p).exists() == True
assert os.path.getsize(p) == 882363


def test_download_fan_wav():
download_fan_wav()[0]
p = str(EXAMPLES_PATH) + "/Fan.wav"
assert pathlib.Path(p).exists() == True
assert os.path.getsize(p) == 500195


def test_download_aircraft_wav():
download_aircraft_wav()[0]
p = str(EXAMPLES_PATH) + "/Aircraft.wav"
assert pathlib.Path(p).exists() == True
assert os.path.getsize(p) == 2299160
Loading