modusa 0.1.0__py3-none-any.whl → 0.2.0__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 (48) hide show
  1. modusa/.DS_Store +0 -0
  2. modusa/decorators.py +5 -5
  3. modusa/devtools/generate_template.py +80 -15
  4. modusa/devtools/main.py +6 -4
  5. modusa/devtools/templates/{engines.py → engine.py} +8 -7
  6. modusa/devtools/templates/{generators.py → generator.py} +8 -10
  7. modusa/devtools/templates/io.py +24 -0
  8. modusa/devtools/templates/{plugins.py → plugin.py} +7 -6
  9. modusa/devtools/templates/signal.py +40 -0
  10. modusa/devtools/templates/test.py +11 -0
  11. modusa/engines/.DS_Store +0 -0
  12. modusa/engines/__init__.py +1 -2
  13. modusa/generators/__init__.py +3 -1
  14. modusa/generators/audio_waveforms.py +227 -0
  15. modusa/generators/base.py +14 -25
  16. modusa/io/__init__.py +9 -0
  17. modusa/io/audio_converter.py +76 -0
  18. modusa/io/audio_loader.py +212 -0
  19. modusa/io/audio_player.py +72 -0
  20. modusa/io/base.py +43 -0
  21. modusa/io/plotter.py +430 -0
  22. modusa/io/youtube_downloader.py +139 -0
  23. modusa/main.py +15 -17
  24. modusa/plugins/__init__.py +1 -7
  25. modusa/signals/__init__.py +4 -6
  26. modusa/signals/audio_signal.py +421 -175
  27. modusa/signals/base.py +11 -271
  28. modusa/signals/frequency_domain_signal.py +329 -0
  29. modusa/signals/signal_ops.py +158 -0
  30. modusa/signals/spectrogram.py +465 -0
  31. modusa/signals/time_domain_signal.py +309 -0
  32. modusa/utils/excp.py +5 -0
  33. {modusa-0.1.0.dist-info → modusa-0.2.0.dist-info}/METADATA +15 -10
  34. modusa-0.2.0.dist-info/RECORD +47 -0
  35. modusa/devtools/templates/signals.py +0 -63
  36. modusa/engines/plot_1dsignal.py +0 -130
  37. modusa/engines/plot_2dmatrix.py +0 -159
  38. modusa/generators/basic_waveform.py +0 -185
  39. modusa/plugins/plot_1dsignal.py +0 -59
  40. modusa/plugins/plot_2dmatrix.py +0 -76
  41. modusa/plugins/plot_time_domain_signal.py +0 -59
  42. modusa/signals/signal1d.py +0 -311
  43. modusa/signals/signal2d.py +0 -226
  44. modusa/signals/uniform_time_domain_signal.py +0 -212
  45. modusa-0.1.0.dist-info/RECORD +0 -41
  46. {modusa-0.1.0.dist-info → modusa-0.2.0.dist-info}/WHEEL +0 -0
  47. {modusa-0.1.0.dist-info → modusa-0.2.0.dist-info}/entry_points.txt +0 -0
  48. {modusa-0.1.0.dist-info → modusa-0.2.0.dist-info}/licenses/LICENSE.md +0 -0
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ from modusa import excp
5
+ from modusa.decorators import validate_args_type
6
+ from modusa.io.base import ModusaIO
7
+ from typing import Any
8
+ import subprocess
9
+ from pathlib import Path
10
+
11
+
12
+ class AudioConverter(ModusaIO):
13
+ """
14
+ Converts audio using FFmpeg.
15
+
16
+ Note
17
+ ----
18
+ - Use `convert()` to perform the actual format conversion.
19
+ - Requires FFMPEG to be installed on the system.
20
+ """
21
+
22
+ #--------Meta Information----------
23
+ _name = "Audio Converter"
24
+ _description = "Convert audio files using ffmpeg"
25
+ _author_name = "Ankit Anand"
26
+ _author_email = "ankit0.anand0@gmail.com"
27
+ _created_at = "2025-07-05"
28
+ #----------------------------------
29
+
30
+ @staticmethod
31
+ @validate_args_type()
32
+ def convert(inp_audio_fp: str | Path, output_audio_fp: str | Path) -> Path:
33
+ """
34
+ Converts an audio file from one format to another using FFmpeg.
35
+
36
+ .. code-block:: python
37
+
38
+ from modusa.engines import AudioConverter
39
+ converted_audio_fp = AudioConverter.convert(
40
+ inp_audio_fp="path/to/input/audio.webm",
41
+ output_audio_fp="path/to/output/audio.wav"
42
+ )
43
+
44
+ Parameters
45
+ ----------
46
+ inp_audio_fp: str | Path
47
+ Filepath of audio to be converted.
48
+ output_audio_fp: str | Path
49
+ Filepath of the converted audio. (e.g. name.mp3)
50
+
51
+ Returns
52
+ -------
53
+ Path:
54
+ Filepath of the converted audio.
55
+
56
+ Note
57
+ ----
58
+ - The conversion takes place based on the extensions of the input and output audio filepath.
59
+ """
60
+ inp_audio_fp = Path(inp_audio_fp)
61
+ output_audio_fp = Path(output_audio_fp)
62
+
63
+ cmd = [
64
+ "ffmpeg",
65
+ "-y", # Overwrite output
66
+ "-i", str(inp_audio_fp),
67
+ "-vn", # No video
68
+ str(output_audio_fp)
69
+ ]
70
+
71
+ try:
72
+ subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
73
+ except subprocess.CalledProcessError:
74
+ raise RuntimeError(f"FFmpeg failed to convert {inp_audio_fp} to {output_audio_fp}")
75
+
76
+ return output_audio_fp
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from modusa.io import ModusaIO
4
+ from modusa.signals import AudioSignal
5
+ from modusa.decorators import validate_args_type
6
+ from pathlib import Path
7
+ import tempfile
8
+ import numpy as np
9
+
10
+ class AudioLoader(ModusaIO):
11
+ """
12
+ Loads audio from various sources like filepath, YouTube, etc.
13
+
14
+ Note
15
+ ----
16
+ - All `from_` methods return :class:`~modusa.signals.AudioSignal` instance.
17
+
18
+ """
19
+
20
+ #--------Meta Information----------
21
+ _name = "Audio Loader"
22
+ _description = "Loads audio from various sources."
23
+ _author_name = "Ankit Anand"
24
+ _author_email = "ankit0.anand0@gmail.com"
25
+ _created_at = "2025-07-05"
26
+ #----------------------------------
27
+
28
+ def __init__(self):
29
+ super().__init__()
30
+
31
+ @staticmethod
32
+ @validate_args_type()
33
+ def from_youtube(url: str, sr: int | None = None) -> "AudioSignal":
34
+ """
35
+ Loads audio from youtube url using :class:`~modusa.io.YoutubeDownloader`,
36
+ :class:`~modusa.io.AudioConverter` and `librosa`.
37
+
38
+ .. code-block:: python
39
+
40
+ from modusa.io import AudioSignalLoader
41
+
42
+ # From youtube
43
+ audio_signal = AudioSignalLoader.from_youtube(
44
+ url="https://www.youtube.com/watch?v=lIpw9-Y_N0g",
45
+ sr=None
46
+ )
47
+
48
+ PARAMETERS
49
+ ----------
50
+ url: str
51
+ Link to the YouTube video.
52
+ sr: int
53
+ Sampling rate to load the audio in.
54
+
55
+ Returns
56
+ -------
57
+
58
+ AudioSignal:
59
+ `Audio signal` instance with loaded audio content from YouTube.
60
+ """
61
+
62
+ from modusa.io import YoutubeDownloader, AudioConverter
63
+ import librosa
64
+
65
+ # Download the audio in temp directory using tempfile module
66
+ with tempfile.TemporaryDirectory() as tmpdir:
67
+ audio_fp: Path = YoutubeDownloader.download(url=url, content_type="audio", output_dir=Path(tmpdir))
68
+
69
+ # Convert the audio to ".wav" form for loading
70
+ wav_audio_fp: Path = AudioConverter.convert(inp_audio_fp=audio_fp, output_audio_fp=audio_fp.with_suffix(".wav"))
71
+
72
+ # Load the audio in memory and return that
73
+ audio_data, audio_sr = librosa.load(wav_audio_fp, sr=sr)
74
+
75
+ audio = AudioSignal(y=audio_data, sr=sr, title=audio_fp.stem)
76
+
77
+ return audio
78
+
79
+ @staticmethod
80
+ @validate_args_type()
81
+ def from_fp(fp: str | Path, sr: int | None = None) -> AudioSignal:
82
+ """
83
+ Loads audio from a filepath using `librosa`.
84
+
85
+ .. code-block:: python
86
+
87
+ from modusa.io import AudioSignalLoader
88
+
89
+ # From file
90
+ audio_signal = AudioSignalLoader.from_fp(
91
+ fp="path/to/audio.wav",
92
+ sr=None
93
+ )
94
+
95
+ Parameters
96
+ ----------
97
+ fp: str | Path
98
+ Local filepath of the audio.
99
+ sr: int | None
100
+ Sampling rate to load the audio in.
101
+
102
+ Returns
103
+ -------
104
+ AudioSignal
105
+ `Audio signal` instance with loaded audio content from filepath.
106
+
107
+ """
108
+ import librosa
109
+
110
+ fp = Path(fp)
111
+ y, sr = librosa.load(fp, sr=sr)
112
+
113
+ audio_signal = AudioSignal(y=y, sr=sr, title=fp.name)
114
+
115
+ return audio_signal
116
+
117
+
118
+ @staticmethod
119
+ def from_array(y: np.ndarray, t: np.ndarray | None = None) -> AudioSignal:
120
+ """
121
+ Loads audio from numpy arrays.
122
+
123
+ .. code-block:: python
124
+
125
+ from modusa.io import AudioSignalLoader
126
+ import numpy as np
127
+
128
+ # From numpy array
129
+ audio_signal = AudioSignalLoader.from_array(
130
+ x=np.random.random((100, )),
131
+ t = None # Automatically creates time index (integer)
132
+ )
133
+
134
+ Parameters
135
+ ----------
136
+ y: np.ndarray
137
+ Data of the audio signal.
138
+ t: np.ndarray | None
139
+ Corresponding time stamps of the audio signal.
140
+
141
+ Returns
142
+ -------
143
+ AudioSignal
144
+ `Audio signal` instance with loaded audio content from arrays.
145
+ """
146
+
147
+ return AudioSignal(y=y, t=t)
148
+
149
+ @staticmethod
150
+ def from_array_with_sr(y: np.ndarray, sr: int) -> AudioSignal:
151
+ """
152
+ Loads audio with a given sampling rate.
153
+
154
+ .. code-block:: python
155
+
156
+ from modusa.io import AudioSignalLoader
157
+ import numpy as np
158
+
159
+ # From numpy array
160
+ audio_signal = AudioSignalLoader.from_array_with_sr(
161
+ x=np.random.random((100, )),
162
+ sr = 100 # Automatically generates time index
163
+ )
164
+
165
+ Parameters
166
+ ----------
167
+ y: np.ndarray
168
+ Data of the audio signal.
169
+ sr: int
170
+ Sampling rate of the audio signal.
171
+
172
+ Returns
173
+ -------
174
+ AudioSignal
175
+ `Audio signal` instance with loaded audio content from sampling rate.
176
+ """
177
+
178
+ return AudioSignal(y=y, sr=sr)
179
+
180
+ @staticmethod
181
+ def from_list(y: list, t: list) -> AudioSignal:
182
+ """
183
+ Loads `AudioSignal` instance from python list.
184
+
185
+ .. code-block:: python
186
+
187
+ from modusa.io import AudioSignalLoader
188
+
189
+ # From list
190
+ audio_signal = AudioSignalLoader.from_list(
191
+ y=[1, 2, 3, 2, 3],
192
+ t = [0.1, 0.2, 0.3, 0.4, 0.5]
193
+ )
194
+
195
+ Parameters
196
+ ----------
197
+ y: list
198
+ Data of the audio signal.
199
+ t: np.ndarray | None
200
+ Corresponding time stamps of the audio signal.
201
+
202
+ Returns
203
+ -------
204
+ AudioSignal
205
+ `Audio signal` instance with loaded audio content from python list.
206
+ """
207
+ y = np.array(y)
208
+ t = np.array(t)
209
+
210
+ return AudioSignal(y=y, t=t)
211
+
212
+
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ from modusa import excp
5
+ from modusa.decorators import validate_args_type
6
+ from modusa.io import ModusaIO
7
+ from IPython.display import display, HTML, Audio
8
+ import numpy as np
9
+
10
+ class AudioPlayer(ModusaIO):
11
+ """
12
+
13
+ """
14
+
15
+ #--------Meta Information----------
16
+ _name = "Audio Player"
17
+ _description = ""
18
+ _author_name = "Ankit Anand"
19
+ _author_email = "ankit0.anand0@gmail.com"
20
+ _created_at = "2025-07-08"
21
+ #----------------------------------
22
+
23
+ @staticmethod
24
+ def play(
25
+ y: np.ndarray,
26
+ sr: int,
27
+ regions: list[tuple[float, float]] | None = None,
28
+ title: str | None = None
29
+ ) -> None:
30
+ """
31
+ Plays audio clips for given regions in Jupyter Notebooks.
32
+
33
+ Parameters
34
+ ----------
35
+ y : np.ndarray
36
+ Audio time series.
37
+ sr : int
38
+ Sampling rate.
39
+ regions : list of (float, float), optional
40
+ Regions to extract and play (in seconds).
41
+ title : str, optional
42
+ Title to display above audio players.
43
+
44
+ Returns
45
+ -------
46
+ None
47
+ """
48
+ if not AudioPlayer._in_notebook():
49
+ return
50
+
51
+ if title:
52
+ display(HTML(f"<h4>{title}</h4>"))
53
+
54
+ if regions:
55
+ for i, (start_sec, end_sec) in enumerate(regions):
56
+ start_sample = int(start_sec * sr)
57
+ end_sample = int(end_sec * sr)
58
+ clip = y[start_sample:end_sample]
59
+
60
+ display(HTML(f"<b>Clip {i+1}</b>: {start_sec:.2f}s → {end_sec:.2f}s"))
61
+ display(Audio(data=clip, rate=sr))
62
+ else:
63
+ display(Audio(data=y, rate=sr))
64
+
65
+ @staticmethod
66
+ def _in_notebook() -> bool:
67
+ try:
68
+ from IPython import get_ipython
69
+ shell = get_ipython()
70
+ return shell and shell.__class__.__name__ == "ZMQInteractiveShell"
71
+ except ImportError:
72
+ return False
modusa/io/base.py ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from abc import ABC, abstractmethod
4
+
5
+ class ModusaIO(ABC):
6
+ """
7
+ Base class for all I/O components: loaders, savers, recorders, etc.
8
+
9
+ >>> modusa-dev create io
10
+
11
+ .. code-block:: python
12
+
13
+ # General template of a subclass of ModusaIO
14
+ from modusa.io import ModusaIO
15
+
16
+ class MyCustomIOClass(ModusaIO):
17
+ #--------Meta Information----------
18
+ _name = "My Custom I/O"
19
+ _description = "My custom class for I/O."
20
+ _author_name = "Ankit Anand"
21
+ _author_email = "ankit0.anand0@gmail.com"
22
+ _created_at = "2025-07-06"
23
+ #----------------------------------
24
+
25
+ @staticmethod
26
+ def do_something():
27
+ pass
28
+
29
+
30
+ Note
31
+ ----
32
+ - This class is intended to be subclassed by any IO related tools built for the modusa framework.
33
+ - In order to create an IO tool, you can use modusa-dev CLI to generate an IO template.
34
+ - It is recommended to treat subclasses of ModusaIO as namespaces and define @staticmethods with control parameters, rather than using instance-level __init__ methods.
35
+ """
36
+
37
+ #--------Meta Information----------
38
+ _name: str = "Modusa I/O"
39
+ _description: str = "Base class for any I/O in the Modusa framework."
40
+ _author_name = "Ankit Anand"
41
+ _author_email = "ankit0.anand0@gmail.com"
42
+ _created_at = "2025-07-06"
43
+ #----------------------------------