modusa 0.2.23__py3-none-any.whl → 0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- modusa/.DS_Store +0 -0
- modusa/__init__.py +8 -1
- modusa/devtools/{generate_doc_source.py → generate_docs_source.py} +5 -5
- modusa/devtools/generate_template.py +5 -5
- modusa/devtools/main.py +3 -3
- modusa/devtools/templates/generator.py +1 -1
- modusa/devtools/templates/io.py +1 -1
- modusa/devtools/templates/{signal.py → model.py} +18 -11
- modusa/devtools/templates/plugin.py +1 -1
- modusa/generators/__init__.py +11 -1
- modusa/generators/audio.py +188 -0
- modusa/generators/audio_waveforms.py +1 -1
- modusa/generators/base.py +1 -1
- modusa/generators/ftds.py +298 -0
- modusa/generators/s1d.py +270 -0
- modusa/generators/s2d.py +300 -0
- modusa/generators/s_ax.py +102 -0
- modusa/generators/t_ax.py +64 -0
- modusa/generators/tds.py +267 -0
- modusa/models/__init__.py +14 -0
- modusa/models/__pycache__/signal1D.cpython-312.pyc.4443461152 +0 -0
- modusa/models/audio.py +90 -0
- modusa/models/base.py +70 -0
- modusa/models/data.py +457 -0
- modusa/models/ftds.py +584 -0
- modusa/models/s1d.py +578 -0
- modusa/models/s2d.py +619 -0
- modusa/models/s_ax.py +448 -0
- modusa/models/t_ax.py +335 -0
- modusa/models/tds.py +465 -0
- modusa/plugins/__init__.py +3 -1
- modusa/tmp.py +98 -0
- modusa/tools/__init__.py +5 -0
- modusa/tools/audio_converter.py +56 -67
- modusa/tools/audio_loader.py +90 -0
- modusa/tools/audio_player.py +42 -67
- modusa/tools/math_ops.py +104 -1
- modusa/tools/plotter.py +305 -497
- modusa/tools/youtube_downloader.py +31 -98
- modusa/utils/excp.py +6 -0
- modusa/utils/np_func_cat.py +44 -0
- modusa/utils/plot.py +142 -0
- {modusa-0.2.23.dist-info → modusa-0.3.dist-info}/METADATA +5 -16
- modusa-0.3.dist-info/RECORD +60 -0
- modusa/devtools/docs/source/generators/audio_waveforms.rst +0 -8
- modusa/devtools/docs/source/generators/base.rst +0 -8
- modusa/devtools/docs/source/generators/index.rst +0 -8
- modusa/devtools/docs/source/io/audio_loader.rst +0 -8
- modusa/devtools/docs/source/io/base.rst +0 -8
- modusa/devtools/docs/source/io/index.rst +0 -8
- modusa/devtools/docs/source/plugins/base.rst +0 -8
- modusa/devtools/docs/source/plugins/index.rst +0 -7
- modusa/devtools/docs/source/signals/audio_signal.rst +0 -8
- modusa/devtools/docs/source/signals/base.rst +0 -8
- modusa/devtools/docs/source/signals/frequency_domain_signal.rst +0 -8
- modusa/devtools/docs/source/signals/index.rst +0 -11
- modusa/devtools/docs/source/signals/spectrogram.rst +0 -8
- modusa/devtools/docs/source/signals/time_domain_signal.rst +0 -8
- modusa/devtools/docs/source/tools/audio_converter.rst +0 -8
- modusa/devtools/docs/source/tools/audio_player.rst +0 -8
- modusa/devtools/docs/source/tools/base.rst +0 -8
- modusa/devtools/docs/source/tools/fourier_tranform.rst +0 -8
- modusa/devtools/docs/source/tools/index.rst +0 -13
- modusa/devtools/docs/source/tools/math_ops.rst +0 -8
- modusa/devtools/docs/source/tools/plotter.rst +0 -8
- modusa/devtools/docs/source/tools/youtube_downloader.rst +0 -8
- modusa/io/__init__.py +0 -5
- modusa/io/audio_loader.py +0 -184
- modusa/io/base.py +0 -43
- modusa/signals/__init__.py +0 -3
- modusa/signals/audio_signal.py +0 -540
- modusa/signals/base.py +0 -27
- modusa/signals/frequency_domain_signal.py +0 -376
- modusa/signals/spectrogram.py +0 -564
- modusa/signals/time_domain_signal.py +0 -412
- modusa/tools/fourier_tranform.py +0 -24
- modusa-0.2.23.dist-info/RECORD +0 -70
- {modusa-0.2.23.dist-info → modusa-0.3.dist-info}/WHEEL +0 -0
- {modusa-0.2.23.dist-info → modusa-0.3.dist-info}/entry_points.txt +0 -0
- {modusa-0.2.23.dist-info → modusa-0.3.dist-info}/licenses/LICENSE.md +0 -0
modusa/signals/audio_signal.py
DELETED
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
from modusa import excp
|
|
5
|
-
from modusa.decorators import immutable_property, validate_args_type
|
|
6
|
-
from modusa.signals.base import ModusaSignal
|
|
7
|
-
from modusa.tools.math_ops import MathOps
|
|
8
|
-
from typing import Self, Any
|
|
9
|
-
import numpy as np
|
|
10
|
-
import matplotlib.pyplot as plt
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
|
|
13
|
-
class AudioSignal(ModusaSignal):
|
|
14
|
-
"""
|
|
15
|
-
Represents a 1D audio signal within modusa framework.
|
|
16
|
-
|
|
17
|
-
Note
|
|
18
|
-
----
|
|
19
|
-
- It is highly recommended to use :class:`~modusa.io.AudioLoader` to instantiate an object of this class.
|
|
20
|
-
- This class assumes audio is mono (1D numpy array).
|
|
21
|
-
|
|
22
|
-
Parameters
|
|
23
|
-
----------
|
|
24
|
-
y : np.ndarray
|
|
25
|
-
1D numpy array representing the audio signal.
|
|
26
|
-
sr : int | None
|
|
27
|
-
Sampling rate in Hz. Required if `t` is not provided.
|
|
28
|
-
t0 : float, optional
|
|
29
|
-
Starting time in seconds. Defaults to 0.0.
|
|
30
|
-
title : str | None, optional
|
|
31
|
-
Optional title for the signal. Defaults to `"Audio Signal"`.
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
#--------Meta Information----------
|
|
35
|
-
_name = "Audio Signal"
|
|
36
|
-
_description = ""
|
|
37
|
-
_author_name = "Ankit Anand"
|
|
38
|
-
_author_email = "ankit0.anand0@gmail.com"
|
|
39
|
-
_created_at = "2025-07-04"
|
|
40
|
-
#----------------------------------
|
|
41
|
-
|
|
42
|
-
@validate_args_type()
|
|
43
|
-
def __init__(self, y: np.ndarray, sr: int, t0: float = 0.0, title: str | None = None):
|
|
44
|
-
"""
|
|
45
|
-
Loads the audio signal.
|
|
46
|
-
"""
|
|
47
|
-
if y.ndim != 1: # Mono signal only
|
|
48
|
-
raise excp.InputValueError(f"`y` must have 1 dimension, not {y.ndim}.")
|
|
49
|
-
|
|
50
|
-
self._y = y
|
|
51
|
-
self._sr = sr
|
|
52
|
-
self._t0 = t0
|
|
53
|
-
self.title = title or self._name
|
|
54
|
-
|
|
55
|
-
#----------------------
|
|
56
|
-
# Properties
|
|
57
|
-
#----------------------
|
|
58
|
-
@immutable_property("Create a new object instead.")
|
|
59
|
-
def y(self) -> np.ndarray:
|
|
60
|
-
"""Returns audio data."""
|
|
61
|
-
return self._y
|
|
62
|
-
|
|
63
|
-
@immutable_property("Create a new object instead.")
|
|
64
|
-
def sr(self) -> np.ndarray:
|
|
65
|
-
"""Returns sampling rate of the audio."""
|
|
66
|
-
return self._sr
|
|
67
|
-
|
|
68
|
-
@immutable_property("Create a new object instead.")
|
|
69
|
-
def t0(self) -> np.ndarray:
|
|
70
|
-
"""Returns start timestamp of the audio."""
|
|
71
|
-
return self._t0
|
|
72
|
-
|
|
73
|
-
#----------------------
|
|
74
|
-
# Derived Properties
|
|
75
|
-
#----------------------
|
|
76
|
-
@immutable_property("Create a new object instead.")
|
|
77
|
-
def t(self) -> np.ndarray:
|
|
78
|
-
"""Timestamp array of the audio."""
|
|
79
|
-
return self.t0 + np.arange(len(self.y)) / self.sr
|
|
80
|
-
|
|
81
|
-
@immutable_property("Mutation not allowed.")
|
|
82
|
-
def Ts(self) -> float:
|
|
83
|
-
"""Sampling Period of the audio."""
|
|
84
|
-
return 1. / self.sr
|
|
85
|
-
|
|
86
|
-
@immutable_property("Mutation not allowed.")
|
|
87
|
-
def duration(self) -> float:
|
|
88
|
-
"""Duration of the audio."""
|
|
89
|
-
return len(self.y) / self.sr
|
|
90
|
-
|
|
91
|
-
@immutable_property("Mutation not allowed.")
|
|
92
|
-
def shape(self) -> tuple:
|
|
93
|
-
"""Shape of the audio signal."""
|
|
94
|
-
return self.y.shape
|
|
95
|
-
|
|
96
|
-
@immutable_property("Mutation not allowed.")
|
|
97
|
-
def ndim(self) -> int:
|
|
98
|
-
"""Dimension of the audio."""
|
|
99
|
-
return self.y.ndim
|
|
100
|
-
|
|
101
|
-
@immutable_property("Mutation not allowed.")
|
|
102
|
-
def __len__(self) -> int:
|
|
103
|
-
"""Dimension of the audio."""
|
|
104
|
-
return len(self.y)
|
|
105
|
-
|
|
106
|
-
#----------------------
|
|
107
|
-
# Methods
|
|
108
|
-
#----------------------
|
|
109
|
-
|
|
110
|
-
def print_info(self) -> None:
|
|
111
|
-
"""Prints info about the audio."""
|
|
112
|
-
print("-" * 50)
|
|
113
|
-
print(f"{'Title':<20}: {self.title}")
|
|
114
|
-
print(f"{'Type':<20}: {self._name}")
|
|
115
|
-
print(f"{'Duration':<20}: {self.duration:.2f} sec")
|
|
116
|
-
print(f"{'Sampling Rate':<20}: {self.sr} Hz")
|
|
117
|
-
print(f"{'Sampling Period':<20}: {(self.Ts*1000) :.4f} ms")
|
|
118
|
-
print("-" * 50)
|
|
119
|
-
|
|
120
|
-
def __getitem__(self, key):
|
|
121
|
-
sliced_y = self.y[key]
|
|
122
|
-
|
|
123
|
-
# If key is a single integer, return just the sample value
|
|
124
|
-
if isinstance(key, int):
|
|
125
|
-
return sliced_y
|
|
126
|
-
|
|
127
|
-
# Otherwise, slicing: use self.t[key][0] as new t0
|
|
128
|
-
new_t0 = self.t[key][0]
|
|
129
|
-
|
|
130
|
-
return self.__class__(
|
|
131
|
-
y=sliced_y,
|
|
132
|
-
sr=self.sr,
|
|
133
|
-
t0=new_t0,
|
|
134
|
-
title=f"{self.title}[{key}]"
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
@validate_args_type()
|
|
138
|
-
def crop(self, t_min: int | float | None = None, t_max: int | float | None = None) -> "AudioSignal":
|
|
139
|
-
"""
|
|
140
|
-
Crop the audio signal to a time range [t_min, t_max].
|
|
141
|
-
|
|
142
|
-
.. code-block:: python
|
|
143
|
-
|
|
144
|
-
from modusa.generators import AudioSignalGenerator
|
|
145
|
-
audio_example = AudioSignalGenerator.generate_example()
|
|
146
|
-
cropped_audio = audio_example.crop(1.5, 2)
|
|
147
|
-
|
|
148
|
-
Parameters
|
|
149
|
-
----------
|
|
150
|
-
t_min : float or None
|
|
151
|
-
Inclusive lower time bound. If None, no lower bound.
|
|
152
|
-
t_max : float or None
|
|
153
|
-
Exclusive upper time bound. If None, no upper bound.
|
|
154
|
-
|
|
155
|
-
Returns
|
|
156
|
-
-------
|
|
157
|
-
AudioSignal
|
|
158
|
-
Cropped audio signal.
|
|
159
|
-
"""
|
|
160
|
-
y = self.y
|
|
161
|
-
t = self.t
|
|
162
|
-
|
|
163
|
-
mask = np.ones_like(t, dtype=bool)
|
|
164
|
-
if t_min is not None:
|
|
165
|
-
mask &= (t >= t_min)
|
|
166
|
-
if t_max is not None:
|
|
167
|
-
mask &= (t < t_max)
|
|
168
|
-
|
|
169
|
-
cropped_y = y[mask]
|
|
170
|
-
new_t0 = t[mask][0] if np.any(mask) else self.t0 # fallback to original t0 if mask is empty
|
|
171
|
-
|
|
172
|
-
return self.__class__(y=cropped_y, sr=self.sr, t0=new_t0, title=self.title)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
@validate_args_type()
|
|
176
|
-
def plot(
|
|
177
|
-
self,
|
|
178
|
-
ax: plt.Axes | None = None,
|
|
179
|
-
fmt: str = "k-",
|
|
180
|
-
title: str | None = None,
|
|
181
|
-
label: str | None = None,
|
|
182
|
-
ylabel: str | None = "Amplitude",
|
|
183
|
-
xlabel: str | None = "Time (sec)",
|
|
184
|
-
ylim: tuple[float, float] | None = None,
|
|
185
|
-
xlim: tuple[float, float] | None = None,
|
|
186
|
-
highlight: list[tuple[float, float]] | None = None,
|
|
187
|
-
vlines: list[float] | None = None,
|
|
188
|
-
hlines: list[float] | None = None,
|
|
189
|
-
show_grid: bool = False,
|
|
190
|
-
stem: bool = False,
|
|
191
|
-
legend_loc: str | None = None,
|
|
192
|
-
) -> plt.Figure | None:
|
|
193
|
-
"""
|
|
194
|
-
Plot the audio waveform using matplotlib.
|
|
195
|
-
|
|
196
|
-
.. code-block:: python
|
|
197
|
-
|
|
198
|
-
from modusa.generators import AudioSignalGenerator
|
|
199
|
-
audio_example = AudioSignalGenerator.generate_example()
|
|
200
|
-
audio_example.plot(color="orange", title="Example Audio")
|
|
201
|
-
|
|
202
|
-
Parameters
|
|
203
|
-
----------
|
|
204
|
-
ax : matplotlib.axes.Axes | None
|
|
205
|
-
Pre-existing axes to plot into. If None, a new figure and axes are created.
|
|
206
|
-
fmt : str | None
|
|
207
|
-
Format of the plot as per matplotlib standards (Eg. "k-" or "blue--o)
|
|
208
|
-
title : str | None
|
|
209
|
-
Plot title. Defaults to the signal’s title.
|
|
210
|
-
label: str | None
|
|
211
|
-
Label for the plot, shown as legend.
|
|
212
|
-
ylabel : str | None
|
|
213
|
-
Label for the y-axis. Defaults to `"Amplitude"`.
|
|
214
|
-
xlabel : str | None
|
|
215
|
-
Label for the x-axis. Defaults to `"Time (sec)"`.
|
|
216
|
-
ylim : tuple[float, float] | None
|
|
217
|
-
Limits for the y-axis.
|
|
218
|
-
xlim : tuple[float, float] | None
|
|
219
|
-
highlight : list[tuple[float, float]] | None
|
|
220
|
-
List of time intervals to highlight on the plot, each as (start, end).
|
|
221
|
-
vlines: list[float]
|
|
222
|
-
List of x values to draw vertical lines. (Eg. [10, 13.5])
|
|
223
|
-
hlines: list[float]
|
|
224
|
-
List of y values to draw horizontal lines. (Eg. [10, 13.5])
|
|
225
|
-
show_grid: bool
|
|
226
|
-
If true, shows grid.
|
|
227
|
-
stem : bool
|
|
228
|
-
If True, use a stem plot instead of a continuous line. Autorejects if signal is too large.
|
|
229
|
-
legend_loc : str | None
|
|
230
|
-
If provided, adds a legend at the specified location (e.g., "upper right" or "best").
|
|
231
|
-
Limits for the x-axis.
|
|
232
|
-
|
|
233
|
-
Returns
|
|
234
|
-
-------
|
|
235
|
-
matplotlib.figure.Figure | None
|
|
236
|
-
The figure object containing the plot or None in case an axis is provided.
|
|
237
|
-
"""
|
|
238
|
-
|
|
239
|
-
from modusa.tools.plotter import Plotter
|
|
240
|
-
|
|
241
|
-
if title is None:
|
|
242
|
-
title = self.title
|
|
243
|
-
|
|
244
|
-
fig: plt.Figure | None = Plotter.plot_signal(
|
|
245
|
-
y=self.y,
|
|
246
|
-
x=self.t,
|
|
247
|
-
ax=ax,
|
|
248
|
-
fmt=fmt,
|
|
249
|
-
title=title,
|
|
250
|
-
label=label,
|
|
251
|
-
ylabel=ylabel,
|
|
252
|
-
xlabel=xlabel,
|
|
253
|
-
ylim=ylim,
|
|
254
|
-
xlim=xlim,
|
|
255
|
-
highlight=highlight,
|
|
256
|
-
vlines=vlines,
|
|
257
|
-
hlines=hlines,
|
|
258
|
-
show_grid=show_grid,
|
|
259
|
-
stem=stem,
|
|
260
|
-
legend_loc=legend_loc,
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
return fig
|
|
264
|
-
|
|
265
|
-
def play(self, regions: list[tuple[float, float], ...] | None = None, title: str | None = None):
|
|
266
|
-
"""
|
|
267
|
-
Play the audio signal inside a Jupyter Notebook.
|
|
268
|
-
|
|
269
|
-
.. code-block:: python
|
|
270
|
-
|
|
271
|
-
from modusa.generators import AudioSignalGenerator
|
|
272
|
-
audio = AudioSignalGenerator.generate_example()
|
|
273
|
-
audio.play(regions=[(0.0, 1.0), (2.0, 3.0)])
|
|
274
|
-
|
|
275
|
-
Parameters
|
|
276
|
-
----------
|
|
277
|
-
regions : list of tuple of float, optional
|
|
278
|
-
List of (start_time, end_time) pairs in seconds specifying the regions to play.
|
|
279
|
-
If None, the entire signal is played.
|
|
280
|
-
title : str or None, optional
|
|
281
|
-
Optional title for the player interface. Defaults to the signal’s internal title.
|
|
282
|
-
|
|
283
|
-
Returns
|
|
284
|
-
-------
|
|
285
|
-
IPython.display.Audio
|
|
286
|
-
An interactive audio player widget for Jupyter environments.
|
|
287
|
-
|
|
288
|
-
See Also
|
|
289
|
-
--------
|
|
290
|
-
:class:`~modusa.tools.audio_player.AudioPlayer`
|
|
291
|
-
"""
|
|
292
|
-
|
|
293
|
-
from modusa.tools.audio_player import AudioPlayer
|
|
294
|
-
title = title or self.title
|
|
295
|
-
audio_player = AudioPlayer.play(y=self.y, sr=self.sr, regions=regions, title=title)
|
|
296
|
-
|
|
297
|
-
return audio_player
|
|
298
|
-
|
|
299
|
-
def to_spectrogram(
|
|
300
|
-
self,
|
|
301
|
-
n_fft: int = 2048,
|
|
302
|
-
hop_length: int = 512,
|
|
303
|
-
win_length: int | None = None,
|
|
304
|
-
window: str = "hann"
|
|
305
|
-
) -> "Spectrogram":
|
|
306
|
-
"""
|
|
307
|
-
Compute the Short-Time Fourier Transform (STFT) and return a Spectrogram object.
|
|
308
|
-
|
|
309
|
-
Parameters
|
|
310
|
-
----------
|
|
311
|
-
n_fft : int
|
|
312
|
-
FFT size.
|
|
313
|
-
win_length : int or None
|
|
314
|
-
Window length. Defaults to `n_fft` if None.
|
|
315
|
-
hop_length : int
|
|
316
|
-
Hop length between frames.
|
|
317
|
-
window : str
|
|
318
|
-
Type of window function to use (e.g., 'hann', 'hamming').
|
|
319
|
-
|
|
320
|
-
Returns
|
|
321
|
-
-------
|
|
322
|
-
Spectrogram
|
|
323
|
-
Spectrogram object containing S (complex STFT), t (time bins), and f (frequency bins).
|
|
324
|
-
"""
|
|
325
|
-
import warnings
|
|
326
|
-
warnings.filterwarnings("ignore", category=UserWarning, module="librosa.core.intervals")
|
|
327
|
-
|
|
328
|
-
from modusa.signals.spectrogram import Spectrogram
|
|
329
|
-
import librosa
|
|
330
|
-
|
|
331
|
-
S = librosa.stft(self.y, n_fft=n_fft, win_length=win_length, hop_length=hop_length, window=window)
|
|
332
|
-
f = librosa.fft_frequencies(sr=self.sr, n_fft=n_fft)
|
|
333
|
-
t = librosa.frames_to_time(np.arange(S.shape[1]), sr=self.sr, hop_length=hop_length)
|
|
334
|
-
frame_rate = self.sr / hop_length
|
|
335
|
-
spec = Spectrogram(S=S, f=f, frame_rate=frame_rate, t0=self.t0)
|
|
336
|
-
if self.title != self._name: # Means title of the audio was reset so we pass that info to spec
|
|
337
|
-
spec.title = self.title
|
|
338
|
-
|
|
339
|
-
return spec
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
#----------------------------
|
|
343
|
-
# Math ops
|
|
344
|
-
#----------------------------
|
|
345
|
-
|
|
346
|
-
def __array__(self, dtype=None):
|
|
347
|
-
return np.asarray(self.y, dtype=dtype)
|
|
348
|
-
|
|
349
|
-
def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
|
|
350
|
-
if method == "__call__":
|
|
351
|
-
input_arrays = [x.y if isinstance(x, self.__class__) else x for x in inputs]
|
|
352
|
-
result = ufunc(*input_arrays, **kwargs)
|
|
353
|
-
return self.__class__(y=result, sr=self.sr, title=f"{self.title}")
|
|
354
|
-
return NotImplemented
|
|
355
|
-
|
|
356
|
-
def __add__(self, other):
|
|
357
|
-
other_data = other.y if isinstance(other, self.__class__) else other
|
|
358
|
-
result = MathOps.add(self.y, other_data)
|
|
359
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
360
|
-
|
|
361
|
-
def __radd__(self, other):
|
|
362
|
-
other_data = other.y if isinstance(other, self.__class__) else other
|
|
363
|
-
result = MathOps.add(other_data, self.y)
|
|
364
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
365
|
-
|
|
366
|
-
def __sub__(self, other):
|
|
367
|
-
other_data = other.y if isinstance(other, self.__class__) else other
|
|
368
|
-
result = MathOps.subtract(self.y, other_data)
|
|
369
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
370
|
-
|
|
371
|
-
def __rsub__(self, other):
|
|
372
|
-
other_data = other.y if isinstance(other, self.__class__) else other
|
|
373
|
-
result = MathOps.subtract(other_data, self.y)
|
|
374
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
375
|
-
|
|
376
|
-
def __mul__(self, other):
|
|
377
|
-
other_data = other.y if isinstance(other, self.__class__) else other
|
|
378
|
-
result = MathOps.multiply(self.y, other_data)
|
|
379
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
380
|
-
|
|
381
|
-
def __rmul__(self, other):
|
|
382
|
-
other_data = other.y if isinstance(other, self.__class__) else other
|
|
383
|
-
result = MathOps.multiply(other_data, self.y)
|
|
384
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
385
|
-
|
|
386
|
-
def __truediv__(self, other):
|
|
387
|
-
other_data = other.y if isinstance(other, self.__class__) else other
|
|
388
|
-
result = MathOps.divide(self.y, other_data)
|
|
389
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
390
|
-
|
|
391
|
-
def __rtruediv__(self, other):
|
|
392
|
-
other_data = other.y if isinstance(other, self.__class__) else other
|
|
393
|
-
result = MathOps.divide(other_data, self.y)
|
|
394
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
395
|
-
|
|
396
|
-
def __floordiv__(self, other):
|
|
397
|
-
other_data = other.y if isinstance(other, self.__class__) else other
|
|
398
|
-
result = MathOps.floor_divide(self.y, other_data)
|
|
399
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
400
|
-
|
|
401
|
-
def __rfloordiv__(self, other):
|
|
402
|
-
other_data = other.y if isinstance(other, self.__class__) else other
|
|
403
|
-
result = MathOps.floor_divide(other_data, self.y)
|
|
404
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
405
|
-
|
|
406
|
-
def __pow__(self, other):
|
|
407
|
-
other_data = other.y if isinstance(other, self.__class__) else other
|
|
408
|
-
result = MathOps.power(self.y, other_data)
|
|
409
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
410
|
-
|
|
411
|
-
def __rpow__(self, other):
|
|
412
|
-
other_data = other.y if isinstance(other, self.__class__) else other
|
|
413
|
-
result = MathOps.power(other_data, self.y)
|
|
414
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
415
|
-
|
|
416
|
-
def __abs__(self):
|
|
417
|
-
other_data = other.y if isinstance(other, self.__class__) else other
|
|
418
|
-
result = MathOps.abs(self.y)
|
|
419
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
420
|
-
|
|
421
|
-
def __or__(self, other):
|
|
422
|
-
if not isinstance(other, self.__class__):
|
|
423
|
-
raise excp.InputTypeError(f"Can only concatenate with another {self.__class__.__name__}")
|
|
424
|
-
|
|
425
|
-
if self.sr != other.sr:
|
|
426
|
-
raise excp.InputValueError(f"Cannot concatenate: Sampling rates differ ({self.sr} vs {other.sr})")
|
|
427
|
-
|
|
428
|
-
# Concatenate raw audio data
|
|
429
|
-
y_cat = np.concatenate([self.y, other.y])
|
|
430
|
-
|
|
431
|
-
# Preserve t0 of the first signal
|
|
432
|
-
new_title = f"{self.title} | {other.title}"
|
|
433
|
-
return self.__class__(y=y_cat, sr=self.sr, t0=self.t0, title=new_title)
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
#--------------------------
|
|
437
|
-
# Other signal ops
|
|
438
|
-
#--------------------------
|
|
439
|
-
def abs(self) -> Self:
|
|
440
|
-
"""Compute the element-wise abs of the signal data."""
|
|
441
|
-
result = MathOps.abs(self.y)
|
|
442
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
443
|
-
|
|
444
|
-
def sin(self) -> Self:
|
|
445
|
-
"""Compute the element-wise sine of the signal data."""
|
|
446
|
-
result = MathOps.sin(self.y)
|
|
447
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
448
|
-
|
|
449
|
-
def cos(self) -> Self:
|
|
450
|
-
"""Compute the element-wise cosine of the signal data."""
|
|
451
|
-
result = MathOps.cos(self.y)
|
|
452
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
453
|
-
|
|
454
|
-
def exp(self) -> Self:
|
|
455
|
-
"""Compute the element-wise exponential of the signal data."""
|
|
456
|
-
result = MathOps.exp(self.y)
|
|
457
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
458
|
-
|
|
459
|
-
def tanh(self) -> Self:
|
|
460
|
-
"""Compute the element-wise hyperbolic tangent of the signal data."""
|
|
461
|
-
result = MathOps.tanh(self.y)
|
|
462
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
463
|
-
|
|
464
|
-
def log(self) -> Self:
|
|
465
|
-
"""Compute the element-wise natural logarithm of the signal data."""
|
|
466
|
-
result = MathOps.log(self.y)
|
|
467
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
468
|
-
|
|
469
|
-
def log1p(self) -> Self:
|
|
470
|
-
"""Compute the element-wise natural logarithm of (1 + signal data)."""
|
|
471
|
-
result = MathOps.log1p(self.y)
|
|
472
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
473
|
-
|
|
474
|
-
def log10(self) -> Self:
|
|
475
|
-
"""Compute the element-wise base-10 logarithm of the signal data."""
|
|
476
|
-
result = MathOps.log10(self.y)
|
|
477
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
478
|
-
|
|
479
|
-
def log2(self) -> Self:
|
|
480
|
-
"""Compute the element-wise base-2 logarithm of the signal data."""
|
|
481
|
-
result = MathOps.log2(self.y)
|
|
482
|
-
return self.__class__(y=result, sr=self.sr, t0=self.t0, title=self.title)
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
#--------------------------
|
|
486
|
-
# Aggregation signal ops
|
|
487
|
-
#--------------------------
|
|
488
|
-
def mean(self) -> "np.generic":
|
|
489
|
-
"""Compute the mean of the signal data."""
|
|
490
|
-
return MathOps.mean(self.y)
|
|
491
|
-
|
|
492
|
-
def std(self) -> "np.generic":
|
|
493
|
-
"""Compute the standard deviation of the signal data."""
|
|
494
|
-
return MathOps.std(self.y)
|
|
495
|
-
|
|
496
|
-
def min(self) -> "np.generic":
|
|
497
|
-
"""Compute the minimum value in the signal data."""
|
|
498
|
-
return MathOps.min(self.y)
|
|
499
|
-
|
|
500
|
-
def max(self) -> "np.generic":
|
|
501
|
-
"""Compute the maximum value in the signal data."""
|
|
502
|
-
return MathOps.max(self.y)
|
|
503
|
-
|
|
504
|
-
def sum(self) -> "np.generic":
|
|
505
|
-
"""Compute the sum of the signal data."""
|
|
506
|
-
return MathOps.sum(self.y)
|
|
507
|
-
|
|
508
|
-
#-----------------------------------
|
|
509
|
-
# Repr
|
|
510
|
-
#-----------------------------------
|
|
511
|
-
|
|
512
|
-
def __str__(self):
|
|
513
|
-
cls = self.__class__.__name__
|
|
514
|
-
data = self.y
|
|
515
|
-
|
|
516
|
-
arr_str = np.array2string(
|
|
517
|
-
data,
|
|
518
|
-
separator=", ",
|
|
519
|
-
threshold=50, # limit number of elements shown
|
|
520
|
-
edgeitems=3, # show first/last 3 rows and columns
|
|
521
|
-
max_line_width=120, # avoid wrapping
|
|
522
|
-
formatter={'float_kind': lambda x: f"{x:.4g}"}
|
|
523
|
-
)
|
|
524
|
-
|
|
525
|
-
return f"Signal({arr_str}, shape={data.shape}, type={cls})"
|
|
526
|
-
|
|
527
|
-
def __repr__(self):
|
|
528
|
-
cls = self.__class__.__name__
|
|
529
|
-
data = self.y
|
|
530
|
-
|
|
531
|
-
arr_str = np.array2string(
|
|
532
|
-
data,
|
|
533
|
-
separator=", ",
|
|
534
|
-
threshold=50, # limit number of elements shown
|
|
535
|
-
edgeitems=3, # show first/last 3 rows and columns
|
|
536
|
-
max_line_width=120, # avoid wrapping
|
|
537
|
-
formatter={'float_kind': lambda x: f"{x:.4g}"}
|
|
538
|
-
)
|
|
539
|
-
|
|
540
|
-
return f"Signal({arr_str}, shape={data.shape}, type={cls})"
|
modusa/signals/base.py
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
from abc import ABC, abstractmethod
|
|
5
|
-
|
|
6
|
-
class ModusaSignal(ABC):
|
|
7
|
-
"""
|
|
8
|
-
Base class for any signal in the modusa framework.
|
|
9
|
-
|
|
10
|
-
Note
|
|
11
|
-
----
|
|
12
|
-
- Intended to be subclassed.
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
#--------Meta Information----------
|
|
16
|
-
_name = "Modusa Signal"
|
|
17
|
-
_description = "Base class for any signal types in the Modusa framework."
|
|
18
|
-
_author_name = "Ankit Anand"
|
|
19
|
-
_author_email = "ankit0.anand0@gmail.com"
|
|
20
|
-
_created_at = "2025-06-23"
|
|
21
|
-
#----------------------------------
|
|
22
|
-
|
|
23
|
-
def __init__(self):
|
|
24
|
-
self._plugin_chain = []
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|