modusa 0.2.23__py3-none-any.whl → 0.3.1__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.
Files changed (80) hide show
  1. modusa/.DS_Store +0 -0
  2. modusa/__init__.py +8 -1
  3. modusa/devtools/{generate_doc_source.py → generate_docs_source.py} +5 -5
  4. modusa/devtools/generate_template.py +5 -5
  5. modusa/devtools/main.py +3 -3
  6. modusa/devtools/templates/generator.py +1 -1
  7. modusa/devtools/templates/io.py +1 -1
  8. modusa/devtools/templates/{signal.py → model.py} +18 -11
  9. modusa/devtools/templates/plugin.py +1 -1
  10. modusa/generators/__init__.py +11 -1
  11. modusa/generators/audio.py +188 -0
  12. modusa/generators/audio_waveforms.py +1 -1
  13. modusa/generators/base.py +1 -1
  14. modusa/generators/ftds.py +298 -0
  15. modusa/generators/s1d.py +270 -0
  16. modusa/generators/s2d.py +300 -0
  17. modusa/generators/s_ax.py +102 -0
  18. modusa/generators/t_ax.py +64 -0
  19. modusa/generators/tds.py +267 -0
  20. modusa/models/__init__.py +14 -0
  21. modusa/models/__pycache__/signal1D.cpython-312.pyc.4443461152 +0 -0
  22. modusa/models/audio.py +90 -0
  23. modusa/models/base.py +70 -0
  24. modusa/models/data.py +457 -0
  25. modusa/models/ftds.py +584 -0
  26. modusa/models/s1d.py +578 -0
  27. modusa/models/s2d.py +619 -0
  28. modusa/models/s_ax.py +448 -0
  29. modusa/models/t_ax.py +335 -0
  30. modusa/models/tds.py +465 -0
  31. modusa/plugins/__init__.py +3 -1
  32. modusa/tmp.py +98 -0
  33. modusa/tools/__init__.py +5 -0
  34. modusa/tools/audio_converter.py +56 -67
  35. modusa/tools/audio_loader.py +90 -0
  36. modusa/tools/audio_player.py +42 -67
  37. modusa/tools/math_ops.py +104 -1
  38. modusa/tools/plotter.py +305 -497
  39. modusa/tools/youtube_downloader.py +31 -98
  40. modusa/utils/excp.py +6 -0
  41. modusa/utils/np_func_cat.py +44 -0
  42. modusa/utils/plot.py +142 -0
  43. {modusa-0.2.23.dist-info → modusa-0.3.1.dist-info}/METADATA +24 -19
  44. modusa-0.3.1.dist-info/RECORD +60 -0
  45. modusa/devtools/docs/source/generators/audio_waveforms.rst +0 -8
  46. modusa/devtools/docs/source/generators/base.rst +0 -8
  47. modusa/devtools/docs/source/generators/index.rst +0 -8
  48. modusa/devtools/docs/source/io/audio_loader.rst +0 -8
  49. modusa/devtools/docs/source/io/base.rst +0 -8
  50. modusa/devtools/docs/source/io/index.rst +0 -8
  51. modusa/devtools/docs/source/plugins/base.rst +0 -8
  52. modusa/devtools/docs/source/plugins/index.rst +0 -7
  53. modusa/devtools/docs/source/signals/audio_signal.rst +0 -8
  54. modusa/devtools/docs/source/signals/base.rst +0 -8
  55. modusa/devtools/docs/source/signals/frequency_domain_signal.rst +0 -8
  56. modusa/devtools/docs/source/signals/index.rst +0 -11
  57. modusa/devtools/docs/source/signals/spectrogram.rst +0 -8
  58. modusa/devtools/docs/source/signals/time_domain_signal.rst +0 -8
  59. modusa/devtools/docs/source/tools/audio_converter.rst +0 -8
  60. modusa/devtools/docs/source/tools/audio_player.rst +0 -8
  61. modusa/devtools/docs/source/tools/base.rst +0 -8
  62. modusa/devtools/docs/source/tools/fourier_tranform.rst +0 -8
  63. modusa/devtools/docs/source/tools/index.rst +0 -13
  64. modusa/devtools/docs/source/tools/math_ops.rst +0 -8
  65. modusa/devtools/docs/source/tools/plotter.rst +0 -8
  66. modusa/devtools/docs/source/tools/youtube_downloader.rst +0 -8
  67. modusa/io/__init__.py +0 -5
  68. modusa/io/audio_loader.py +0 -184
  69. modusa/io/base.py +0 -43
  70. modusa/signals/__init__.py +0 -3
  71. modusa/signals/audio_signal.py +0 -540
  72. modusa/signals/base.py +0 -27
  73. modusa/signals/frequency_domain_signal.py +0 -376
  74. modusa/signals/spectrogram.py +0 -564
  75. modusa/signals/time_domain_signal.py +0 -412
  76. modusa/tools/fourier_tranform.py +0 -24
  77. modusa-0.2.23.dist-info/RECORD +0 -70
  78. {modusa-0.2.23.dist-info → modusa-0.3.1.dist-info}/WHEEL +0 -0
  79. {modusa-0.2.23.dist-info → modusa-0.3.1.dist-info}/entry_points.txt +0 -0
  80. {modusa-0.2.23.dist-info → modusa-0.3.1.dist-info}/licenses/LICENSE.md +0 -0
@@ -1,376 +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 typing import Self, Any
8
- import numpy as np
9
- import matplotlib.pyplot as plt
10
-
11
- class FrequencyDomainSignal(ModusaSignal):
12
- """
13
- Represents a 1D signal in the frequency domain.
14
-
15
- Note
16
- ----
17
- - The class is not intended to be instantiated directly.
18
-
19
- Parameters
20
- ----------
21
- spectrum : np.ndarray
22
- The frequency-domain representation of the signal (real or complex-valued).
23
- f : np.ndarray
24
- The frequency axis corresponding to the spectrum values. Must match the shape of `spectrum`.
25
- t0 : float, optional
26
- The time (in seconds) corresponding to the origin of this spectral slice. Defaults to 0.0.
27
- title : str, optional
28
- An optional title for display or plotting purposes.
29
- """
30
- #--------Meta Information----------
31
- _name = "Frequency Domain Signal"
32
- _description = "Represents Frequency Domain Signal"
33
- _author_name = "Ankit Anand"
34
- _author_email = "ankit0.anand0@gmail.com"
35
- _created_at = "2025-07-09"
36
- #----------------------------------
37
-
38
- @validate_args_type()
39
- def __init__(self, spectrum: np.ndarray, f: np.ndarray, t0: float | int = 0.0, title: str | None = None):
40
- super().__init__() # Instantiating `ModusaSignal` class
41
-
42
- if spectrum.shape != f.shape:
43
- raise excp.InputValueError(f"`spectrum` and `f` shape must match, got {spectrum.shape} and {f.shape}")
44
-
45
- self._spectrum = spectrum
46
- self._f = f
47
- self._t0 = float(t0)
48
-
49
- self.title = title or self._name # This title will be used as plot title by default
50
-
51
-
52
- #----------------------
53
- # Properties
54
- #----------------------
55
-
56
- @immutable_property("Create a new object instead.")
57
- def spectrum(self) -> np.ndarray:
58
- """Complex valued spectrum data."""
59
- return self._spectrum
60
-
61
- @immutable_property("Create a new object instead.")
62
- def f(self) -> np.ndarray:
63
- """frequency array of the spectrum."""
64
- return self._f
65
-
66
- @immutable_property("Create a new object instead.")
67
- def t0(self) -> np.ndarray:
68
- """Time origin (in seconds) of this spectral slice, e.g., from a spectrogram frame."""
69
- return self._t0
70
-
71
- #----------------------
72
- # Derived Properties
73
- #----------------------
74
- @immutable_property("Create a new object instead.")
75
- def __len__(self) -> int:
76
- return len(self.spectrum)
77
-
78
- @immutable_property("Create a new object instead.")
79
- def ndim(self) -> int:
80
- return self.spectrum.ndim
81
-
82
- @immutable_property("Create a new object instead.")
83
- def shape(self) -> tuple:
84
- return self.spectrum.shape
85
-
86
- #----------------------
87
- # Methods
88
- #----------------------
89
-
90
- def print_info(self) -> None:
91
- """Prints info about the audio."""
92
- print("-" * 50)
93
- print(f"{'Title':<20}: {self.title}")
94
- print(f"{'Type':<20}: {self._name}")
95
- print(f"{'Frequency Range':<20}: ({self.f[0]:.2f}, {self.f[-1]:.2f}) Hz")
96
- print("-" * 50)
97
-
98
- def __getitem__(self, key: slice) -> Self:
99
- sliced_spectrum = self._spectrum[key]
100
- sliced_f = self._f[key]
101
- return self.__class__(spectrum=sliced_spectrum, f=sliced_f, t0=self.t0, title=self.title)
102
-
103
- @validate_args_type()
104
- def plot(
105
- self,
106
- ax: plt.Axes | None = None,
107
- fmt: str = "k-",
108
- title: str | None = None,
109
- label: str | None = None,
110
- ylabel: str | None = "Strength",
111
- xlabel: str | None = "Frequency (Hz)",
112
- ylim: tuple[float, float] | None = None,
113
- xlim: tuple[float, float] | None = None,
114
- highlight: list[tuple[float, float]] | None = None,
115
- vlines: list[float] | None = None,
116
- hlines: list[float] | None = None,
117
- show_grid: bool = False,
118
- stem: bool | None = False,
119
- legend_loc: str | None = None,
120
- ) -> plt.Figure | None:
121
- """
122
- Plot the audio waveform using matplotlib.
123
-
124
- .. code-block:: python
125
-
126
- from modusa.generators import AudioSignalGenerator
127
- audio_example = AudioSignalGenerator.generate_example()
128
- audio_example.plot(color="orange", title="Example Audio")
129
-
130
- Parameters
131
- ----------
132
- ax : matplotlib.axes.Axes | None
133
- Pre-existing axes to plot into. If None, a new figure and axes are created.
134
- fmt : str | None
135
- Format of the plot as per matplotlib standards (Eg. "k-" or "blue--o)
136
- title : str | None
137
- Plot title. Defaults to the signal’s title.
138
- label: str | None
139
- Label for the plot, shown as legend.
140
- ylabel : str | None
141
- Label for the y-axis. Defaults to `"Strength"`.
142
- xlabel : str | None
143
- Label for the x-axis. Defaults to `"Frequency (Hz)"`.
144
- ylim : tuple[float, float] | None
145
- Limits for the y-axis.
146
- xlim : tuple[float, float] | None
147
- highlight : list[tuple[float, float]] | None
148
- List of frequency intervals to highlight on the plot, each as (start, end).
149
- vlines: list[float]
150
- List of x values to draw vertical lines. (Eg. [10, 13.5])
151
- hlines: list[float]
152
- List of y values to draw horizontal lines. (Eg. [10, 13.5])
153
- show_grid: bool
154
- If true, shows grid.
155
- stem : bool
156
- If True, use a stem plot instead of a continuous line. Autorejects if signal is too large.
157
- legend_loc : str | None
158
- If provided, adds a legend at the specified location (e.g., "upper right" or "best").
159
- Limits for the x-axis.
160
-
161
- Returns
162
- -------
163
- matplotlib.figure.Figure | None
164
- The figure object containing the plot or None in case an axis is provided.
165
- """
166
-
167
- from modusa.tools.plotter import Plotter
168
-
169
- title = title or self.title
170
-
171
- fig: plt.Figure | None = Plotter.plot_signal(
172
- y=self.spectrum,
173
- x=self.f,
174
- ax=ax,
175
- fmt=fmt,
176
- title=title,
177
- label=label,
178
- ylabel=ylabel,
179
- xlabel=xlabel,
180
- ylim=ylim,
181
- xlim=xlim,
182
- highlight=highlight,
183
- vlines=vlines,
184
- hlines=hlines,
185
- show_grid=show_grid,
186
- stem=stem,
187
- legend_loc=legend_loc,
188
- )
189
-
190
- return fig
191
-
192
- #----------------------------
193
- # Math ops
194
- #----------------------------
195
-
196
- def __array__(self, dtype=None):
197
- return np.asarray(self.spectrum, dtype=dtype)
198
-
199
- def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
200
- if method == "__call__":
201
- input_arrays = [x.spectrum if isinstance(x, self.__class__) else x for x in inputs]
202
- result = ufunc(*input_arrays, **kwargs)
203
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
204
- return NotImplemented
205
-
206
- def __add__(self, other):
207
- other_data = other.spectrum if isinstance(other, self.__class__) else other
208
- result = MathOps.add(self.spectrum, other_data)
209
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
210
-
211
- def __radd__(self, other):
212
- other_data = other.spectrum if isinstance(other, self.__class__) else other
213
- result = MathOps.add(other_data, self.spectrum)
214
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
215
-
216
- def __sub__(self, other):
217
- other_data = other.spectrum if isinstance(other, self.__class__) else other
218
- result = MathOps.subtract(self.spectrum, other_data)
219
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
220
-
221
- def __rsub__(self, other):
222
- other_data = other.spectrum if isinstance(other, self.__class__) else other
223
- result = MathOps.subtract(other_data, self.spectrum)
224
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
225
-
226
- def __mul__(self, other):
227
- other_data = other.spectrum if isinstance(other, self.__class__) else other
228
- result = MathOps.multiply(self.spectrum, other_data)
229
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
230
-
231
- def __rmul__(self, other):
232
- other_data = other.spectrum if isinstance(other, self.__class__) else other
233
- result = MathOps.multiply(other_data, self.spectrum)
234
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
235
-
236
- def __truediv__(self, other):
237
- other_data = other.spectrum if isinstance(other, self.__class__) else other
238
- result = MathOps.divide(self.spectrum, other_data)
239
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
240
-
241
- def __rtruediv__(self, other):
242
- other_data = other.spectrum if isinstance(other, self.__class__) else other
243
- result = MathOps.divide(other_data, self.spectrum)
244
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
245
-
246
- def __floordiv__(self, other):
247
- other_data = other.spectrum if isinstance(other, self.__class__) else other
248
- result = MathOps.floor_divide(self.spectrum, other_data)
249
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
250
-
251
- def __rfloordiv__(self, other):
252
- other_data = other.spectrum if isinstance(other, self.__class__) else other
253
- result = MathOps.floor_divide(other_data, self.spectrum)
254
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
255
-
256
- def __pow__(self, other):
257
- other_data = other.spectrum if isinstance(other, self.__class__) else other
258
- result = MathOps.power(self.spectrum, other_data)
259
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
260
-
261
- def __rpow__(self, other):
262
- other_data = other.spectrum if isinstance(other, self.__class__) else other
263
- result = MathOps.power(other_data, self.spectrum)
264
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
265
-
266
- def __abs__(self):
267
- other_data = other.spectrum if isinstance(other, self.__class__) else other
268
- result = MathOps.abs(self.spectrum)
269
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
270
-
271
- #--------------------------
272
- # Other signal ops
273
- #--------------------------
274
- def abs(self) -> Self:
275
- """Compute the element-wise abs of the signal data."""
276
- result = MathOps.abs(self.y)
277
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
278
-
279
- def sin(self) -> Self:
280
- """Compute the element-wise sine of the signal data."""
281
- result = MathOps.sin(self.y)
282
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
283
-
284
- def cos(self) -> Self:
285
- """Compute the element-wise cosine of the signal data."""
286
- result = MathOps.cos(self.y)
287
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
288
-
289
- def exp(self) -> Self:
290
- """Compute the element-wise exponential of the signal data."""
291
- result = MathOps.exp(self.y)
292
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
293
-
294
- def tanh(self) -> Self:
295
- """Compute the element-wise hyperbolic tangent of the signal data."""
296
- result = MathOps.tanh(self.y)
297
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
298
-
299
- def log(self) -> Self:
300
- """Compute the element-wise natural logarithm of the signal data."""
301
- result = MathOps.log(self.y)
302
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
303
-
304
- def log1p(self) -> Self:
305
- """Compute the element-wise natural logarithm of (1 + signal data)."""
306
- result = MathOps.log1p(self.y)
307
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
308
-
309
- def log10(self) -> Self:
310
- """Compute the element-wise base-10 logarithm of the signal data."""
311
- result = MathOps.log10(self.y)
312
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
313
-
314
- def log2(self) -> Self:
315
- """Compute the element-wise base-2 logarithm of the signal data."""
316
- result = MathOps.log2(self.y)
317
- return self.__class__(spectrum=result, f=self.f, t0=self.t0, title=self.title)
318
-
319
-
320
- #--------------------------
321
- # Aggregation signal ops
322
- #--------------------------
323
- def mean(self) -> "np.generic":
324
- """Compute the mean of the signal data."""
325
- return MathOps.mean(self.spectrum)
326
-
327
- def std(self) -> "np.generic":
328
- """Compute the standard deviation of the signal data."""
329
- return MathOps.std(self.spectrum)
330
-
331
- def min(self) -> "np.generic":
332
- """Compute the minimum value in the signal data."""
333
- return MathOps.min(self.spectrum)
334
-
335
- def max(self) -> "np.generic":
336
- """Compute the maximum value in the signal data."""
337
- return MathOps.max(self.spectrum)
338
-
339
- def sum(self) -> "np.generic":
340
- """Compute the sum of the signal data."""
341
- return MathOps.sum(self.spectrum)
342
-
343
- #-----------------------------------
344
- # Repr
345
- #-----------------------------------
346
-
347
- def __str__(self):
348
- cls = self.__class__.__name__
349
- data = self.spectrum
350
-
351
- arr_str = np.array2string(
352
- data,
353
- separator=", ",
354
- threshold=50, # limit number of elements shown
355
- edgeitems=3, # show first/last 3 rows and columns
356
- max_line_width=120, # avoid wrapping
357
- formatter={'float_kind': lambda x: f"{x:.4g}"}
358
- )
359
-
360
- return f"Signal({arr_str}, shape={data.shape}, type={cls})"
361
-
362
- def __repr__(self):
363
- cls = self.__class__.__name__
364
- data = self.spectrum
365
-
366
- arr_str = np.array2string(
367
- data,
368
- separator=", ",
369
- threshold=50, # limit number of elements shown
370
- edgeitems=3, # show first/last 3 rows and columns
371
- max_line_width=120, # avoid wrapping
372
- formatter={'float_kind': lambda x: f"{x:.4g}"}
373
- )
374
-
375
- return f"Signal({arr_str}, shape={data.shape}, type={cls})"
376
-