modusa 0.2.22__tar.gz → 0.3__tar.gz
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-0.2.22 → modusa-0.3}/PKG-INFO +5 -16
- {modusa-0.2.22 → modusa-0.3}/README.md +2 -14
- {modusa-0.2.22 → modusa-0.3}/pyproject.toml +3 -2
- {modusa-0.2.22 → modusa-0.3}/src/modusa/.DS_Store +0 -0
- modusa-0.3/src/modusa/__init__.py +8 -0
- {modusa-0.2.22 → modusa-0.3}/src/modusa/decorators.py +4 -4
- modusa-0.3/src/modusa/devtools/generate_docs_source.py +96 -0
- {modusa-0.2.22 → modusa-0.3}/src/modusa/devtools/generate_template.py +13 -13
- {modusa-0.2.22 → modusa-0.3}/src/modusa/devtools/main.py +4 -3
- {modusa-0.2.22 → modusa-0.3}/src/modusa/devtools/templates/generator.py +1 -1
- {modusa-0.2.22 → modusa-0.3}/src/modusa/devtools/templates/io.py +1 -1
- modusa-0.2.22/src/modusa/devtools/templates/signal.py → modusa-0.3/src/modusa/devtools/templates/model.py +18 -11
- {modusa-0.2.22 → modusa-0.3}/src/modusa/devtools/templates/plugin.py +1 -1
- {modusa-0.2.22 → modusa-0.3}/src/modusa/devtools/templates/test.py +2 -3
- modusa-0.2.22/src/modusa/devtools/templates/engine.py → modusa-0.3/src/modusa/devtools/templates/tool.py +3 -8
- modusa-0.3/src/modusa/generators/__init__.py +13 -0
- modusa-0.3/src/modusa/generators/audio.py +188 -0
- {modusa-0.2.22 → modusa-0.3}/src/modusa/generators/audio_waveforms.py +22 -13
- {modusa-0.2.22 → modusa-0.3}/src/modusa/generators/base.py +1 -1
- modusa-0.3/src/modusa/generators/ftds.py +298 -0
- modusa-0.3/src/modusa/generators/s1d.py +270 -0
- modusa-0.3/src/modusa/generators/s2d.py +300 -0
- modusa-0.3/src/modusa/generators/s_ax.py +102 -0
- modusa-0.3/src/modusa/generators/t_ax.py +64 -0
- modusa-0.3/src/modusa/generators/tds.py +267 -0
- modusa-0.3/src/modusa/main.py +3 -0
- modusa-0.3/src/modusa/models/__init__.py +14 -0
- modusa-0.3/src/modusa/models/audio.py +90 -0
- modusa-0.3/src/modusa/models/base.py +70 -0
- modusa-0.3/src/modusa/models/data.py +457 -0
- modusa-0.3/src/modusa/models/ftds.py +584 -0
- modusa-0.3/src/modusa/models/s1d.py +578 -0
- modusa-0.3/src/modusa/models/s2d.py +619 -0
- modusa-0.3/src/modusa/models/s_ax.py +448 -0
- modusa-0.3/src/modusa/models/t_ax.py +335 -0
- modusa-0.3/src/modusa/models/tds.py +465 -0
- modusa-0.3/src/modusa/plugins/__init__.py +3 -0
- modusa-0.3/src/modusa/tmp.py +98 -0
- modusa-0.3/src/modusa/tools/__init__.py +7 -0
- modusa-0.3/src/modusa/tools/audio_converter.py +73 -0
- modusa-0.3/src/modusa/tools/audio_loader.py +90 -0
- modusa-0.3/src/modusa/tools/audio_player.py +89 -0
- modusa-0.3/src/modusa/tools/base.py +43 -0
- modusa-0.3/src/modusa/tools/math_ops.py +335 -0
- modusa-0.3/src/modusa/tools/plotter.py +351 -0
- modusa-0.3/src/modusa/tools/youtube_downloader.py +72 -0
- modusa-0.3/src/modusa/utils/excp.py +49 -0
- modusa-0.3/src/modusa/utils/np_func_cat.py +44 -0
- modusa-0.3/src/modusa/utils/plot.py +142 -0
- modusa-0.3/tests/__init__.py +0 -0
- modusa-0.3/tests/data/song1.mp3 +0 -0
- modusa-0.3/tests/data/song1.wav +0 -0
- modusa-0.3/tests/test_generators/test_audio.py +10 -0
- modusa-0.3/tests/test_generators/test_ftds.py +10 -0
- modusa-0.3/tests/test_generators/test_s1d.py +10 -0
- modusa-0.3/tests/test_generators/test_s2d.py +10 -0
- modusa-0.3/tests/test_generators/test_s_ax.py +10 -0
- modusa-0.3/tests/test_generators/test_signal.py +10 -0
- modusa-0.3/tests/test_generators/test_signal_generator.py +10 -0
- modusa-0.3/tests/test_generators/test_t_ax.py +10 -0
- modusa-0.3/tests/test_generators/test_tds.py +10 -0
- modusa-0.3/tests/test_models/test_data.py +10 -0
- modusa-0.3/tests/test_models/test_t_ax.py +10 -0
- modusa-0.3/tests/test_signals/test_axis.py +10 -0
- modusa-0.3/tests/test_signals/test_feature_time_domain_signal.py +10 -0
- modusa-0.3/tests/test_signals/test_frequency_time_domain_signal.py +10 -0
- modusa-0.3/tests/test_signals/test_signal1D.py +10 -0
- modusa-0.3/tests/test_signals/test_signal2D.py +10 -0
- modusa-0.3/tests/test_signals/test_time_domain_signal.py +10 -0
- modusa-0.3/tests/test_signals/test_u_ax.py +10 -0
- modusa-0.3/tests/test_signals/test_window_signal.py +10 -0
- modusa-0.3/tests/test_tools/test_audio_converter.py +48 -0
- modusa-0.3/tests/test_tools/test_fourier_tranform.py +10 -0
- modusa-0.3/tests/test_tools/test_math_ops.py +687 -0
- modusa-0.2.22/src/modusa/__init__.py +0 -1
- modusa-0.2.22/src/modusa/engines/.DS_Store +0 -0
- modusa-0.2.22/src/modusa/engines/__init__.py +0 -3
- modusa-0.2.22/src/modusa/engines/base.py +0 -14
- modusa-0.2.22/src/modusa/generators/__init__.py +0 -5
- modusa-0.2.22/src/modusa/io/__init__.py +0 -9
- modusa-0.2.22/src/modusa/io/audio_converter.py +0 -76
- modusa-0.2.22/src/modusa/io/audio_loader.py +0 -214
- modusa-0.2.22/src/modusa/io/audio_player.py +0 -72
- modusa-0.2.22/src/modusa/io/base.py +0 -43
- modusa-0.2.22/src/modusa/io/plotter.py +0 -430
- modusa-0.2.22/src/modusa/io/youtube_downloader.py +0 -139
- modusa-0.2.22/src/modusa/main.py +0 -33
- modusa-0.2.22/src/modusa/plugins/__init__.py +0 -1
- modusa-0.2.22/src/modusa/signals/__init__.py +0 -7
- modusa-0.2.22/src/modusa/signals/audio_signal.py +0 -483
- modusa-0.2.22/src/modusa/signals/base.py +0 -34
- modusa-0.2.22/src/modusa/signals/frequency_domain_signal.py +0 -329
- modusa-0.2.22/src/modusa/signals/signal_ops.py +0 -158
- modusa-0.2.22/src/modusa/signals/spectrogram.py +0 -465
- modusa-0.2.22/src/modusa/signals/time_domain_signal.py +0 -309
- modusa-0.2.22/src/modusa/utils/excp.py +0 -76
- modusa-0.2.22/tests/test_engines/audio_converter.py +0 -11
- modusa-0.2.22/tests/test_engines/youtube_audio_loader.py +0 -11
- modusa-0.2.22/tests/test_engines/youtube_downloader.py +0 -20
- {modusa-0.2.22 → modusa-0.3}/LICENSE.md +0 -0
- {modusa-0.2.22 → modusa-0.3}/src/modusa/config.py +0 -0
- {modusa-0.2.22 → modusa-0.3}/src/modusa/devtools/list_authors.py +0 -0
- {modusa-0.2.22 → modusa-0.3}/src/modusa/devtools/list_plugins.py +0 -0
- /modusa-0.2.22/tests/__init__.py → /modusa-0.3/src/modusa/models/__pycache__/signal1D.cpython-312.pyc.4443461152 +0 -0
- {modusa-0.2.22 → modusa-0.3}/src/modusa/plugins/base.py +0 -0
- {modusa-0.2.22 → modusa-0.3}/src/modusa/utils/.DS_Store +0 -0
- {modusa-0.2.22 → modusa-0.3}/src/modusa/utils/__init__.py +0 -0
- {modusa-0.2.22 → modusa-0.3}/src/modusa/utils/config.py +0 -0
- {modusa-0.2.22 → modusa-0.3}/src/modusa/utils/logger.py +0 -0
- {modusa-0.2.22 → modusa-0.3}/tests/test_generators/audio_waveform.py +0 -0
- {modusa-0.2.22 → modusa-0.3}/tests/test_io/audio_player.py +0 -0
- {modusa-0.2.22 → modusa-0.3}/tests/test_io/plotter.py +0 -0
- {modusa-0.2.22 → modusa-0.3}/tests/test_plugins/youtube_audio_loader.py +0 -0
- {modusa-0.2.22 → modusa-0.3}/tests/test_signals/frequency_domain_signal.py +0 -0
- {modusa-0.2.22 → modusa-0.3}/tests/test_signals/spectrogram.py +0 -0
- {modusa-0.2.22 → modusa-0.3}/tests/test_signals/time_domain_signal.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: modusa
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3
|
|
4
4
|
Summary: A modular signal analysis python library.
|
|
5
5
|
Author-Email: Ankit Anand <ankit0.anand0@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -8,23 +8,14 @@ Requires-Python: >=3.12
|
|
|
8
8
|
Requires-Dist: numpy>=2.2.6
|
|
9
9
|
Requires-Dist: matplotlib>=3.10.3
|
|
10
10
|
Requires-Dist: yt-dlp>=2025.6.30
|
|
11
|
-
Requires-Dist: librosa==0.
|
|
11
|
+
Requires-Dist: librosa==0.11.0
|
|
12
12
|
Requires-Dist: IPython>=8.0.0
|
|
13
|
+
Requires-Dist: pyloudnorm>=0.1.1
|
|
13
14
|
Description-Content-Type: text/markdown
|
|
14
15
|
|
|
15
16
|
# modusa
|
|
16
17
|
|
|
17
|
-
[**modusa**](https://meluron-toolbox.github.io/modusa/) is a
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Core Components
|
|
22
|
-
|
|
23
|
-
- ⚙️ **modusa Signals**
|
|
24
|
-
- 🧩 **modusa Plugins**
|
|
25
|
-
- 📊 **modusa Genetators**
|
|
26
|
-
- 📥 **modusa I/O**
|
|
27
|
-
- ♻️ **modusa Engines**
|
|
18
|
+
[**modusa**](https://meluron-toolbox.github.io/modusa/) is a library with utility tools especially for audio signals.
|
|
28
19
|
|
|
29
20
|
---
|
|
30
21
|
|
|
@@ -33,9 +24,7 @@ Description-Content-Type: text/markdown
|
|
|
33
24
|
> modusa is under active development. You can install the latest version via:
|
|
34
25
|
|
|
35
26
|
```bash
|
|
36
|
-
|
|
37
|
-
cd modusa
|
|
38
|
-
pdm install
|
|
27
|
+
pip install modusa
|
|
39
28
|
```
|
|
40
29
|
|
|
41
30
|
---
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
# modusa
|
|
2
2
|
|
|
3
|
-
[**modusa**](https://meluron-toolbox.github.io/modusa/) is a
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Core Components
|
|
8
|
-
|
|
9
|
-
- ⚙️ **modusa Signals**
|
|
10
|
-
- 🧩 **modusa Plugins**
|
|
11
|
-
- 📊 **modusa Genetators**
|
|
12
|
-
- 📥 **modusa I/O**
|
|
13
|
-
- ♻️ **modusa Engines**
|
|
3
|
+
[**modusa**](https://meluron-toolbox.github.io/modusa/) is a library with utility tools especially for audio signals.
|
|
14
4
|
|
|
15
5
|
---
|
|
16
6
|
|
|
@@ -19,9 +9,7 @@
|
|
|
19
9
|
> modusa is under active development. You can install the latest version via:
|
|
20
10
|
|
|
21
11
|
```bash
|
|
22
|
-
|
|
23
|
-
cd modusa
|
|
24
|
-
pdm install
|
|
12
|
+
pip install modusa
|
|
25
13
|
```
|
|
26
14
|
|
|
27
15
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "modusa"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3"
|
|
4
4
|
description = "A modular signal analysis python library."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Ankit Anand", email = "ankit0.anand0@gmail.com" },
|
|
@@ -9,8 +9,9 @@ dependencies = [
|
|
|
9
9
|
"numpy>=2.2.6",
|
|
10
10
|
"matplotlib>=3.10.3",
|
|
11
11
|
"yt-dlp>=2025.6.30",
|
|
12
|
-
"librosa==0.
|
|
12
|
+
"librosa==0.11.0",
|
|
13
13
|
"IPython>=8.0.0",
|
|
14
|
+
"pyloudnorm>=0.1.1",
|
|
14
15
|
]
|
|
15
16
|
requires-python = ">=3.12"
|
|
16
17
|
readme = "README.md"
|
|
Binary file
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from modusa.utils import excp, config
|
|
2
|
+
|
|
3
|
+
#=====Giving access to plot functions to plot multiple signals.=====
|
|
4
|
+
from modusa.tools import plot1d, plot2d
|
|
5
|
+
from modusa.tools import play, convert
|
|
6
|
+
from modusa.tools import download
|
|
7
|
+
from modusa.tools import load
|
|
8
|
+
#=====
|
|
@@ -110,14 +110,14 @@ def validate_arg(arg_name: str, value: Any, expected_type: Any) -> None:
|
|
|
110
110
|
expected_names = ", ".join(
|
|
111
111
|
get_origin(t).__name__ if get_origin(t) else t.__name__ for t in union_args
|
|
112
112
|
)
|
|
113
|
-
raise excp.
|
|
113
|
+
raise excp.InputTypeError(
|
|
114
114
|
f"Argument '{arg_name}' must be one of ({expected_names}), got {type(value).__name__}"
|
|
115
115
|
)
|
|
116
116
|
|
|
117
117
|
# Handle generic types like list[float], tuple[int, str]
|
|
118
118
|
elif origin is not None:
|
|
119
119
|
if not isinstance(value, origin):
|
|
120
|
-
raise excp.
|
|
120
|
+
raise excp.InputTypeError(
|
|
121
121
|
f"Argument '{arg_name}' must be of type {origin.__name__}, got {type(value).__name__}"
|
|
122
122
|
)
|
|
123
123
|
return
|
|
@@ -125,13 +125,13 @@ def validate_arg(arg_name: str, value: Any, expected_type: Any) -> None:
|
|
|
125
125
|
# ✅ Handle plain types
|
|
126
126
|
elif isinstance(expected_type, type):
|
|
127
127
|
if not isinstance(value, expected_type):
|
|
128
|
-
raise excp.
|
|
128
|
+
raise excp.InputTypeError(
|
|
129
129
|
f"Argument '{arg_name}' must be of type {expected_type.__name__}, got {type(value).__name__}"
|
|
130
130
|
)
|
|
131
131
|
return
|
|
132
132
|
# ❌ Unsupported type structure
|
|
133
133
|
else:
|
|
134
|
-
raise excp.
|
|
134
|
+
raise excp.InputTypeError(f"Unsupported annotation for '{arg_name}': {expected_type}")
|
|
135
135
|
|
|
136
136
|
def validate_args_type() -> Callable:
|
|
137
137
|
def decorator(func: Callable) -> Callable:
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import importlib
|
|
5
|
+
import pkgutil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from collections import defaultdict
|
|
8
|
+
|
|
9
|
+
# === Configuration ===
|
|
10
|
+
BASE_MODULES = [
|
|
11
|
+
'modusa.tools',
|
|
12
|
+
'modusa.models',
|
|
13
|
+
'modusa.generators',
|
|
14
|
+
'modusa.plugins',
|
|
15
|
+
# 'modusa.io'
|
|
16
|
+
]
|
|
17
|
+
OUTPUT_DIRS = [
|
|
18
|
+
Path('docs/source/tools'),
|
|
19
|
+
Path('docs/source/models'),
|
|
20
|
+
Path('docs/source/generators'),
|
|
21
|
+
Path('docs/source/plugins'),
|
|
22
|
+
# Path('docs/source/io')
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
# Ensure output directories exist
|
|
26
|
+
for output_dir in OUTPUT_DIRS:
|
|
27
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
|
|
29
|
+
# === Utils ===
|
|
30
|
+
def get_classes_grouped_by_module(base_module):
|
|
31
|
+
"""
|
|
32
|
+
Returns a dictionary: { module_path: [class_name, ...] }
|
|
33
|
+
"""
|
|
34
|
+
found = defaultdict(list)
|
|
35
|
+
module = importlib.import_module(base_module)
|
|
36
|
+
|
|
37
|
+
for _, modname, _ in pkgutil.walk_packages(module.__path__, base_module + "."):
|
|
38
|
+
try:
|
|
39
|
+
submodule = importlib.import_module(modname)
|
|
40
|
+
for name, obj in inspect.getmembers(submodule, inspect.isclass):
|
|
41
|
+
if obj.__module__ == modname:
|
|
42
|
+
found[modname].append(name)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
print(f"⚠️ Skipping {modname} due to import error: {e}")
|
|
45
|
+
return found
|
|
46
|
+
|
|
47
|
+
def write_module_rst_file(module_path, class_names, output_dir):
|
|
48
|
+
"""
|
|
49
|
+
Writes a .rst file for a module, documenting all its classes.
|
|
50
|
+
Filename is based on the module, but title is the first class (Modusa* gets priority).
|
|
51
|
+
"""
|
|
52
|
+
filename = output_dir / f"{module_path.split('.')[-1]}.rst"
|
|
53
|
+
|
|
54
|
+
# Prioritize 'Modusa*' classes first, then alphabetical
|
|
55
|
+
sorted_classes = sorted(class_names, key=lambda x: (not x.startswith("Modusa"), x.lower()))
|
|
56
|
+
title = sorted_classes[0] if sorted_classes else module_path.split('.')[-1]
|
|
57
|
+
|
|
58
|
+
with open(filename, 'w') as f:
|
|
59
|
+
f.write(f"{title}\n{'=' * len(title)}\n\n")
|
|
60
|
+
for class_name in sorted_classes:
|
|
61
|
+
f.write(f".. autoclass:: {module_path}.{class_name}\n")
|
|
62
|
+
f.write(" :members:\n")
|
|
63
|
+
f.write(" :undoc-members:\n")
|
|
64
|
+
f.write(" :show-inheritance:\n\n")
|
|
65
|
+
|
|
66
|
+
return filename.name
|
|
67
|
+
|
|
68
|
+
def write_index_rst_file(tools_by_module, output_dir, section_name="Tools"):
|
|
69
|
+
"""
|
|
70
|
+
Writes index.rst in the given output_dir with 'base' on top, then other files alphabetically.
|
|
71
|
+
"""
|
|
72
|
+
index_file = output_dir / "index.rst"
|
|
73
|
+
with open(index_file, "w") as f:
|
|
74
|
+
f.write(f"{section_name}\n{'=' * len(section_name)}\n\n")
|
|
75
|
+
f.write(".. toctree::\n :maxdepth: 1\n\n")
|
|
76
|
+
|
|
77
|
+
filenames = [module_path.split('.')[-1] for module_path in tools_by_module]
|
|
78
|
+
sorted_filenames = sorted(filenames, key=lambda x: (x != "base", x.lower()))
|
|
79
|
+
|
|
80
|
+
for name in sorted_filenames:
|
|
81
|
+
f.write(f" {name}\n")
|
|
82
|
+
|
|
83
|
+
# === Main Script ===
|
|
84
|
+
def generate_docs_source():
|
|
85
|
+
for base_module, output_dir in zip(BASE_MODULES, OUTPUT_DIRS):
|
|
86
|
+
module_class_map = get_classes_grouped_by_module(base_module)
|
|
87
|
+
|
|
88
|
+
for module_path, class_list in module_class_map.items():
|
|
89
|
+
write_module_rst_file(module_path, class_list, output_dir)
|
|
90
|
+
|
|
91
|
+
section_name = base_module.split('.')[-1].capitalize()
|
|
92
|
+
if section_name == "Io":
|
|
93
|
+
section_name = "IO"
|
|
94
|
+
|
|
95
|
+
write_index_rst_file(module_class_map, output_dir, section_name=section_name)
|
|
96
|
+
print(f"✅ Documentation generated for {base_module} in {output_dir}")
|
|
@@ -28,17 +28,17 @@ GENERATOR_INFO = {
|
|
|
28
28
|
"template_dump_dp": SRC_CODE_DIR / "generators",
|
|
29
29
|
"test_template_dump_dp": TESTS_DIR / "test_generators"
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
"template_fp": TEMPLATES_DIR / "
|
|
31
|
+
MODEL_INFO = {
|
|
32
|
+
"template_fp": TEMPLATES_DIR / "model.py",
|
|
33
33
|
"test_template_fp": TEMPLATES_DIR / "test.py",
|
|
34
|
-
"template_dump_dp": SRC_CODE_DIR / "
|
|
35
|
-
"test_template_dump_dp": TESTS_DIR / "
|
|
34
|
+
"template_dump_dp": SRC_CODE_DIR / "models",
|
|
35
|
+
"test_template_dump_dp": TESTS_DIR / "test_models"
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
"template_fp": TEMPLATES_DIR / "
|
|
37
|
+
TOOL_INFO = {
|
|
38
|
+
"template_fp": TEMPLATES_DIR / "tool.py",
|
|
39
39
|
"test_template_fp": TEMPLATES_DIR / "test.py",
|
|
40
|
-
"template_dump_dp": SRC_CODE_DIR / "
|
|
41
|
-
"test_template_dump_dp": TESTS_DIR / "
|
|
40
|
+
"template_dump_dp": SRC_CODE_DIR / "tools",
|
|
41
|
+
"test_template_dump_dp": TESTS_DIR / "test_tools"
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
|
|
@@ -53,8 +53,8 @@ class TemplateGenerator():
|
|
|
53
53
|
def get_path_info(for_what: str):
|
|
54
54
|
if for_what == "plugin": return PLUGIN_INFO
|
|
55
55
|
if for_what == "io": return IO_INFO
|
|
56
|
-
if for_what == "
|
|
57
|
-
if for_what == "
|
|
56
|
+
if for_what == "model": return MODEL_INFO
|
|
57
|
+
if for_what == "tool": return TOOL_INFO
|
|
58
58
|
if for_what == "generator": return GENERATOR_INFO
|
|
59
59
|
|
|
60
60
|
@staticmethod
|
|
@@ -75,7 +75,7 @@ class TemplateGenerator():
|
|
|
75
75
|
print(f"⚠️ File already exists, choose another name.")
|
|
76
76
|
sys.exit(1)
|
|
77
77
|
|
|
78
|
-
class_name = questionary.text(f"Class name (CamelCase
|
|
78
|
+
class_name = questionary.text(f"Class name (CamelCase): ").ask()
|
|
79
79
|
if class_name is None:
|
|
80
80
|
sys.exit(1)
|
|
81
81
|
|
|
@@ -138,7 +138,7 @@ class TemplateGenerator():
|
|
|
138
138
|
|
|
139
139
|
# Save it to a file and put it in the correct folder
|
|
140
140
|
TemplateGenerator.save_file(content=template_code, output_path=path_info['template_dump_dp'] / answers['module_name'])
|
|
141
|
-
TemplateGenerator.save_file(content=test_code, output_path=path_info['test_template_dump_dp'] / answers['module_name'])
|
|
141
|
+
TemplateGenerator.save_file(content=test_code, output_path=path_info['test_template_dump_dp'] / f"test_{answers['module_name']}")
|
|
142
142
|
|
|
143
143
|
print(f"✅ {for_what}:", "open " + str(path_info['template_dump_dp'] / answers['module_name']))
|
|
144
|
-
print(f"✅ test:", "open " + str(path_info['test_template_dump_dp'] / answers['module_name']))
|
|
144
|
+
print(f"✅ test:", "open " + str(path_info['test_template_dump_dp'] / f"test_{answers['module_name']}"))
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
from .generate_template import TemplateGenerator
|
|
5
|
+
from .generate_docs_source import generate_docs_source
|
|
5
6
|
from .list_plugins import list_plugins
|
|
6
7
|
from . import list_authors
|
|
7
8
|
|
|
@@ -20,12 +21,12 @@ def main():
|
|
|
20
21
|
create_parser = subparsers.add_parser("create", help="Create new Modusa components")
|
|
21
22
|
create_subparsers = create_parser.add_subparsers(dest="what", required=True)
|
|
22
23
|
|
|
23
|
-
create_subparsers.add_parser("
|
|
24
|
+
create_subparsers.add_parser("tool", help="Create a new tool class").set_defaults(func=lambda:TemplateGenerator.create_template("tool"))
|
|
24
25
|
create_subparsers.add_parser("plugin", help="Create a new plugin class").set_defaults(func=lambda:TemplateGenerator.create_template("plugin"))
|
|
25
|
-
create_subparsers.add_parser("
|
|
26
|
+
create_subparsers.add_parser("model", help="Create a new model class").set_defaults(func=lambda:TemplateGenerator.create_template("model"))
|
|
26
27
|
create_subparsers.add_parser("generator", help="Create a new signal generator class").set_defaults(func=lambda:TemplateGenerator.create_template("generator"))
|
|
27
28
|
create_subparsers.add_parser("io", help="Create a new IO class").set_defaults(func=lambda:TemplateGenerator.create_template("io"))
|
|
28
|
-
|
|
29
|
+
create_subparsers.add_parser("docs", help="Generate the docs").set_defaults(func=lambda:generate_docs_source())
|
|
29
30
|
|
|
30
31
|
# --- LIST group ---
|
|
31
32
|
list_parser = subparsers.add_parser("list", help="List information about Modusa components")
|
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
|
|
4
4
|
from modusa import excp
|
|
5
5
|
from modusa.decorators import immutable_property, validate_args_type
|
|
6
|
-
from modusa.signals.base import
|
|
6
|
+
from modusa.signals.base import ?
|
|
7
7
|
from typing import Self, Any
|
|
8
8
|
import numpy as np
|
|
9
9
|
|
|
10
|
-
class {class_name}(
|
|
10
|
+
class {class_name}():
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
#--------Meta Information----------
|
|
16
16
|
_name = ""
|
|
17
|
+
_nickname = "" # This is to be used in repr/str methods
|
|
17
18
|
_description = ""
|
|
18
19
|
_author_name = "{author_name}"
|
|
19
20
|
_author_email = "{author_email}"
|
|
@@ -26,15 +27,21 @@ class {class_name}(ModusaSignal):
|
|
|
26
27
|
|
|
27
28
|
self.title = "" # This title will be used as plot title by default
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
#-----------------------------------
|
|
30
31
|
# Properties
|
|
31
|
-
|
|
32
|
+
#-----------------------------------
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
|
|
35
|
+
#===================================
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
#-----------------------------------
|
|
39
43
|
# Tools
|
|
40
|
-
|
|
44
|
+
#-----------------------------------
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
#===================================
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
|
+
from modusa import excp
|
|
3
4
|
|
|
4
5
|
#--------Meta Information----------
|
|
5
6
|
_class_name = "{class_name}"
|
|
6
7
|
_author_name = "{author_name}"
|
|
7
8
|
_author_email = "{author_email}"
|
|
8
9
|
_created_at = "{date_created}"
|
|
9
|
-
#----------------------------------
|
|
10
|
-
|
|
11
|
-
from modusa.{for_what}s import {class_name}
|
|
10
|
+
#----------------------------------
|
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
from modusa import excp
|
|
5
5
|
from modusa.decorators import validate_args_type
|
|
6
|
-
from modusa.
|
|
7
|
-
from typing import Any
|
|
6
|
+
from modusa.tools.base import ModusaTool
|
|
8
7
|
|
|
9
8
|
|
|
10
|
-
class {class_name}(
|
|
9
|
+
class {class_name}(ModusaTool):
|
|
11
10
|
"""
|
|
12
11
|
|
|
13
12
|
"""
|
|
@@ -22,8 +21,4 @@ class {class_name}(ModusaEngine):
|
|
|
22
21
|
|
|
23
22
|
def __init__(self):
|
|
24
23
|
super().__init__()
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@validate_args_type()
|
|
28
|
-
def run(self) -> Any:
|
|
29
|
-
pass
|
|
24
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from .base import ModusaGenerator
|
|
4
|
+
|
|
5
|
+
from .s_ax import SAxGen
|
|
6
|
+
from .t_ax import TAxGen
|
|
7
|
+
|
|
8
|
+
from .s1d import S1DGen
|
|
9
|
+
from .tds import TDSGen
|
|
10
|
+
from .audio import AudioGen
|
|
11
|
+
|
|
12
|
+
from .s2d import S2DGen
|
|
13
|
+
from .ftds import FTDSGen
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from modusa import excp
|
|
5
|
+
from modusa.decorators import validate_args_type
|
|
6
|
+
from .base import ModusaGenerator
|
|
7
|
+
from modusa.models.t_ax import TAx
|
|
8
|
+
from modusa.models.audio import Audio
|
|
9
|
+
from modusa.models.data import Data
|
|
10
|
+
import numpy as np
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
class AudioGen(ModusaGenerator):
|
|
14
|
+
"""
|
|
15
|
+
Provides user friendly APIs to generate instances of different
|
|
16
|
+
`AudioSignal` instances.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
#--------Meta Information----------
|
|
20
|
+
_name = ""
|
|
21
|
+
_description = ""
|
|
22
|
+
_author_name = "Ankit Anand"
|
|
23
|
+
_author_email = "ankit0.anand0@gmail.com"
|
|
24
|
+
_created_at = "2025-07-27"
|
|
25
|
+
#----------------------------------
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def from_array(
|
|
29
|
+
y: np.ndarray,
|
|
30
|
+
sr: float | int = 1.0,
|
|
31
|
+
t0: float | int = 0.0,
|
|
32
|
+
y_label: str = "Amplitude",
|
|
33
|
+
t_label: str = "Time (sec)",
|
|
34
|
+
title: str = "Audio Signal"
|
|
35
|
+
) -> Audio:
|
|
36
|
+
"""
|
|
37
|
+
Create `AudioSignal` instance from basic data structures.
|
|
38
|
+
|
|
39
|
+
.. code-block:: python
|
|
40
|
+
|
|
41
|
+
import modusa as ms
|
|
42
|
+
t = ms.tds.from_array([1, 2, 3])
|
|
43
|
+
print(t)
|
|
44
|
+
t.print_info()
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
y: np.ndarray
|
|
49
|
+
- Audio data array.
|
|
50
|
+
sr: float | int
|
|
51
|
+
- Sampling rate.
|
|
52
|
+
t0: float | int
|
|
53
|
+
- Start timestamp.
|
|
54
|
+
y_label: str
|
|
55
|
+
- Y label for the signal.
|
|
56
|
+
- Default: "Y"
|
|
57
|
+
t_label: str
|
|
58
|
+
- T label for the signal.
|
|
59
|
+
- Default: "Time (sec)"
|
|
60
|
+
title: str
|
|
61
|
+
- Title for the signal.
|
|
62
|
+
- Default: "1D Signal"
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
Audio
|
|
66
|
+
An instance of Audio.
|
|
67
|
+
"""
|
|
68
|
+
assert isinstance(y, np.ndarray)
|
|
69
|
+
assert isinstance(sr, (int, float)) and isinstance(t0, (int, float))
|
|
70
|
+
assert isinstance(y_label, str) and isinstance(t_label, str) and isinstance(title, str)
|
|
71
|
+
|
|
72
|
+
assert y.ndim == 1
|
|
73
|
+
|
|
74
|
+
sr = float(sr)
|
|
75
|
+
t0 = float(t0)
|
|
76
|
+
|
|
77
|
+
t = TAx(n_points=y.shape[0], sr=sr, t0=t0, label=t_label) # Creating a signal axis instance
|
|
78
|
+
y = Data(values=y, label=y_label)
|
|
79
|
+
|
|
80
|
+
return Audio(y=y, t=t, title=title)
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def from_youtube(cls, url: str, sr: int | float = None):
|
|
84
|
+
"""
|
|
85
|
+
Loads audio from youtube at a given sr.
|
|
86
|
+
The audio is deleted from the device
|
|
87
|
+
after loading.
|
|
88
|
+
|
|
89
|
+
.. code-block:: python
|
|
90
|
+
|
|
91
|
+
import modusa as ms
|
|
92
|
+
audio = ms.audio.from_youtube(
|
|
93
|
+
url="https://www.youtube.com/watch?v=lIpw9-Y_N0g",
|
|
94
|
+
sr=None
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
PARAMETERS
|
|
98
|
+
----------
|
|
99
|
+
url: str
|
|
100
|
+
Link to the YouTube video.
|
|
101
|
+
sr: int
|
|
102
|
+
Sampling rate to load the audio in.
|
|
103
|
+
|
|
104
|
+
Returns
|
|
105
|
+
-------
|
|
106
|
+
Audio:
|
|
107
|
+
An `Audio` instance with loaded audio content from YouTube.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
from modusa.tools.youtube_downloader import YoutubeDownloader
|
|
111
|
+
from modusa import convert
|
|
112
|
+
import soundfile as sf
|
|
113
|
+
from scipy.signal import resample
|
|
114
|
+
import tempfile
|
|
115
|
+
|
|
116
|
+
# Download the audio in temp directory using tempfile module
|
|
117
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
118
|
+
audio_fp: Path = YoutubeDownloader.download(url=url, content_type="audio", output_dir=Path(tmpdir))
|
|
119
|
+
|
|
120
|
+
# Convert the audio to ".wav" form for loading
|
|
121
|
+
wav_audio_fp: Path = convert(inp_audio_fp=audio_fp, output_audio_fp=audio_fp.with_suffix(".wav"))
|
|
122
|
+
|
|
123
|
+
# Load the audio in memory
|
|
124
|
+
audio_data, audio_sr = sf.read(wav_audio_fp)
|
|
125
|
+
|
|
126
|
+
# Convert to mono if it's multi-channel
|
|
127
|
+
if audio_data.ndim > 1:
|
|
128
|
+
audio_data = audio_data.mean(axis=1)
|
|
129
|
+
|
|
130
|
+
# Resample if needed
|
|
131
|
+
if sr is not None:
|
|
132
|
+
if audio_sr != sr:
|
|
133
|
+
n_samples = int(len(audio_data) * sr / audio_sr)
|
|
134
|
+
audio_data = resample(audio_data, n_samples)
|
|
135
|
+
audio_sr = sr
|
|
136
|
+
|
|
137
|
+
audio = cls.from_array(y=audio_data, sr=audio_sr, title=audio_fp.stem)
|
|
138
|
+
|
|
139
|
+
return audio
|
|
140
|
+
|
|
141
|
+
@classmethod
|
|
142
|
+
def from_filepath(cls, fp: str | Path, sr: int | float = None):
|
|
143
|
+
"""
|
|
144
|
+
Loads audio from filepath at a given sr.
|
|
145
|
+
|
|
146
|
+
.. code-block:: python
|
|
147
|
+
|
|
148
|
+
import modusa as ms
|
|
149
|
+
audio = ms.audio.from_filepath(
|
|
150
|
+
fp="path/to/audio.wav",
|
|
151
|
+
sr=None
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
PARAMETERS
|
|
155
|
+
----------
|
|
156
|
+
fp: str | Path
|
|
157
|
+
Audio file path.
|
|
158
|
+
sr: int
|
|
159
|
+
Sampling rate to load the audio in.
|
|
160
|
+
|
|
161
|
+
Returns
|
|
162
|
+
-------
|
|
163
|
+
Audio:
|
|
164
|
+
An `Audio` instance with loaded audio content.
|
|
165
|
+
"""
|
|
166
|
+
import soundfile as sf
|
|
167
|
+
from scipy.signal import resample
|
|
168
|
+
from pathlib import Path
|
|
169
|
+
|
|
170
|
+
fp = Path(fp)
|
|
171
|
+
# Load the audio in memory
|
|
172
|
+
audio_data, audio_sr = sf.read(fp)
|
|
173
|
+
|
|
174
|
+
# Convert to mono if it's multi-channel
|
|
175
|
+
if audio_data.ndim > 1:
|
|
176
|
+
audio_data = audio_data.mean(axis=1)
|
|
177
|
+
|
|
178
|
+
# Resample if needed
|
|
179
|
+
if sr is not None:
|
|
180
|
+
if audio_sr != sr:
|
|
181
|
+
n_samples = int(len(audio_data) * sr / audio_sr)
|
|
182
|
+
audio_data = resample(audio_data, n_samples)
|
|
183
|
+
audio_sr = sr
|
|
184
|
+
|
|
185
|
+
audio = cls.from_array(y=audio_data, sr=audio_sr, title=fp.stem)
|
|
186
|
+
|
|
187
|
+
return audio
|
|
188
|
+
|