Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
110 changes: 110 additions & 0 deletions examples/XXX_test_sound_classes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# 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.
from ansys.dpf.core import FieldsContainer

from ansys.sound.core.psychoacoustics.loudness_iso_532_1_stationary import (
LoudnessISO532_1_Stationary,
)
from ansys.sound.core.psychoacoustics.tone_to_noise_ratio import ToneToNoiseRatio
from ansys.sound.core.server_helpers._connect_to_or_start_server import connect_to_or_start_server
from ansys.sound.core.signal_utilities import CropSignal, LoadWav
from ansys.sound.core.sound_composer.sound_composer import SoundComposer
from ansys.sound.core.spectral_processing.power_spectral_density import PowerSpectralDensity
from ansys.sound.core.spectrogram_processing.stft import Stft

server = connect_to_or_start_server(use_license_context=True)

loader = LoadWav(
"C:/ANSYSDev/PyDev/pyansys-sound/tests/data/Acceleration_stereo_nonUnitaryCalib.wav"
)
loader.process()
sound = loader.get_output()
print(sound)
# sound.plot()
channels = sound.split_channels()
print(channels)
loader = LoadWav("C:/ANSYSDev/PyDev/pyansys-sound/tests/data/flute.wav")
loader.process()
sound = loader.get_output()
print(sound)
# sound.plot()
cropper = CropSignal(sound, start_time=0.0, end_time=1.0)
cropper.process()
cropped = cropper.get_output()
# cropped.plot()
loudness = LoudnessISO532_1_Stationary(signal=cropped)
loudness.process()
print(f"Loudness: {loudness.get_loudness_sone()}")

psder = PowerSpectralDensity(input_signal=sound)
psder.process()
psd = psder.get_output()
print(type(psd))
print(psd)

tnrer = ToneToNoiseRatio(psd=psd)
tnrer.process()
# tnrer.plot()

stfter = Stft(signal=sound, fft_size=1024, window_type="HANN", window_overlap=0.5)
stfter.process()
stft = stfter.get_output()
print(stft.time)
print(stft.frequencies)


scer = SoundComposer(
"C:/ANSYSDev/PyDev/pyansys-sound/tests/data/"
"20250130_SoundComposerProjectForDpfSoundTesting_valid.scn"
)
bbn: FieldsContainer = scer.tracks[0].source.source_bbn
bbn_support = bbn.get_support("control_parameter_1")
bbn_support_ppts = bbn_support.available_field_supported_properties()
bbn_control = bbn_support.field_support_by_property("kph")
bbn2 = scer.tracks[3].source.source_bbn_two_parameters
bbn2_field = bbn2.get_field({"control_parameter_1": 0, "control_parameter_2": 0})
bbn2_support1 = bbn2.get_support("control_parameter_1")
bbn2_support1_ppts = bbn2_support1.available_field_supported_properties()
bbn2_control1 = bbn2_support1.field_support_by_property("celsius")
bbn2_support2 = bbn2.get_support("control_parameter_2")
bbn2_support2_ppts = bbn2_support2.available_field_supported_properties()
bbn2_control2 = bbn2_support2.field_support_by_property("%")
h = scer.tracks[4].source.source_harmonics
h_support = h.get_support("control_parameter_1")
h_support_ppts = h_support.available_field_supported_properties()
h_control = h_support.field_support_by_property("RPM")
h2 = scer.tracks[5].source.source_harmonics_two_parameters
h2_support1 = h2.get_support("control_parameter_1")
h2_support1_ppts = h2_support1.available_field_supported_properties()
h2_control1 = h2_support1.field_support_by_property("RPM")
h2_support2 = h2.get_support("control_parameter_2")
h2_support2_ppts = h2_support2.available_field_supported_properties()
h2_control2 = h2_support2.field_support_by_property("%")

h2_2 = scer.tracks[6].source.source_harmonics_two_parameters
h2_2_support1 = h2_2.get_support("control_parameter_1")
h2_2_support1_ppts = h2_2_support1.available_field_supported_properties()
h2_2_control1 = h2_2_support1.field_support_by_property("RPM")
h2_2_support2 = h2_2.get_support("control_parameter_2")
h2_2_support2_ppts = h2_2_support2.available_field_supported_properties()
h2_2_control2 = h2_2_support2.field_support_by_property("%")
print()
39 changes: 39 additions & 0 deletions src/ansys/sound/core/data_management/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 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.

"""PyAnsys Sound data classes."""
from .psd2 import PSD
from .sonagram import Sonagram
from .sound import Sound, convert_to_sound
from .sources import BroadbandNoiseSource, HarmonicsSource

# from .utilities import convert_type

__all__ = (
"Sound",
"PSD",
"Sonagram",
"BroadbandNoiseSource",
"HarmonicsSource",
# "convert_type",
"convert_to_sound",
)
59 changes: 59 additions & 0 deletions src/ansys/sound/core/data_management/_data_management_parent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# 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.


class NoExtraAttributesMeta(type):
"""Metaclass restricting attribute creation."""

def __new__(mcs, name, bases, namespace):
"""Create a new class with restricted attribute creation."""
base = bases[0] # Base class. We assume each class inherits from one class only.
# Method Resolution Order (MRO) of the base class, that is, base + every other level of
# inheritance.
mro = base.__mro__
# Store the list of the base class attribute names (in a variable called _allowed_attrs)
# It is stored in the namespace so that it can be accessed anywhere in the class to create.
_allowed_attrs = set()
for cls in mro:
_allowed_attrs.update(vars(cls).keys())
namespace["_allowed_attrs"] = _allowed_attrs
# print(f"Allowed attributes for {name}: {namespace['_allowed_attrs']}")

def __setattr__(self, key, value):
# Check if the created attribute is allowed, ie is in the list of attribute defined in
# the base class.
print(key)
if key not in self._allowed_attrs:
raise AttributeError(
f"Cannot create attribute {key}. Class {self.__class__.__name__} does not "
"allow creation of attributes that are not defined in "
f"{self.__class__.__bases__[0].__name__}."
)
else:
super(cls, self).__setattr__(key, value)

# Add the __setattr__ method to the namespace of the class to create.
namespace["__setattr__"] = __setattr__

# Create and return the new class.
cls = super().__new__(mcs, name, bases, namespace)
return cls
101 changes: 101 additions & 0 deletions src/ansys/sound/core/data_management/psd2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# 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.

"""PyAnsys Sound class to store sound data."""
from ansys.dpf.core import Field
import matplotlib.pyplot as plt
import numpy as np

from .._pyansys_sound import PyAnsysSoundException


# class PSD(Field, metaclass=NoExtraAttributesMeta):
class PSD(Field):
"""PyAnsys Sound class to store sound data.

Add Nfft, window type, and overlap as ppts, but then probably redundant with
PowerSpectralDensity.
"""

def __init__(
self,
):
"""TODO."""
super().__init__()

def __str__(self):
"""Return the string representation of the object."""
if len(self.frequencies) > 0:
properties_str = (
f":\n\tFrequency resolution: {self.delta_f:.2f} Hz"
f"\n\tMaximum frequency: {self.f_max:.1f} Hz"
)
else:
properties_str = ""
return f"PSD object{properties_str}"

@property
def frequencies(self) -> np.ndarray:
"""Array of frequencies in Hz where the PSD is defined."""
return np.array(self.time_freq_support.time_frequencies.data)

@property
def delta_f(self) -> float:
"""Frequency resolution in Hz of the PSD."""
if len(self.frequencies) < 2:
raise PyAnsysSoundException(
"Not enough frequency points to determine frequency resolution."
)

return self.frequencies[1] - self.frequencies[0]

@property
def f_max(self) -> float:
"""Maximum frequency in Hz."""
return self.frequencies[-1]

def update(self) -> None:
"""Update the sound data."""
# Nothing to update here for now
# TODO:
# - check support is regularly spaced
pass

def get_as_nparray(self) -> list[np.ndarray]:
"""Get the sound data as a NumPy array."""
return (np.array(self.data), self.frequencies)

@classmethod
def create(cls, object: Field) -> "PSD":
"""TODO."""
object.__class__ = cls
object.update()
return object

def plot(self):
"""Plot the PSD data."""
plt.plot(self.time_freq_support.time_frequencies.data, self.data)

plt.xlabel("Frequency (Hz)")
plt.ylabel("Amplitude (dB/Hz)")
plt.title(self.name)
plt.show()
Loading