modusa 0.1.0__py3-none-any.whl → 0.2.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.
- modusa/.DS_Store +0 -0
- modusa/decorators.py +5 -5
- modusa/devtools/generate_template.py +80 -15
- modusa/devtools/main.py +6 -4
- modusa/devtools/templates/{engines.py → engine.py} +8 -7
- modusa/devtools/templates/{generators.py → generator.py} +8 -10
- modusa/devtools/templates/io.py +24 -0
- modusa/devtools/templates/{plugins.py → plugin.py} +7 -6
- modusa/devtools/templates/signal.py +40 -0
- modusa/devtools/templates/test.py +11 -0
- modusa/engines/.DS_Store +0 -0
- modusa/engines/__init__.py +1 -2
- modusa/generators/__init__.py +3 -1
- modusa/generators/audio_waveforms.py +227 -0
- modusa/generators/base.py +14 -25
- modusa/io/__init__.py +9 -0
- modusa/io/audio_converter.py +76 -0
- modusa/io/audio_loader.py +212 -0
- modusa/io/audio_player.py +72 -0
- modusa/io/base.py +43 -0
- modusa/io/plotter.py +430 -0
- modusa/io/youtube_downloader.py +139 -0
- modusa/main.py +15 -17
- modusa/plugins/__init__.py +1 -7
- modusa/signals/__init__.py +4 -6
- modusa/signals/audio_signal.py +421 -175
- modusa/signals/base.py +11 -271
- modusa/signals/frequency_domain_signal.py +329 -0
- modusa/signals/signal_ops.py +158 -0
- modusa/signals/spectrogram.py +465 -0
- modusa/signals/time_domain_signal.py +309 -0
- modusa/utils/excp.py +5 -0
- {modusa-0.1.0.dist-info → modusa-0.2.1.dist-info}/METADATA +16 -11
- modusa-0.2.1.dist-info/RECORD +47 -0
- modusa/devtools/templates/signals.py +0 -63
- modusa/engines/plot_1dsignal.py +0 -130
- modusa/engines/plot_2dmatrix.py +0 -159
- modusa/generators/basic_waveform.py +0 -185
- modusa/plugins/plot_1dsignal.py +0 -59
- modusa/plugins/plot_2dmatrix.py +0 -76
- modusa/plugins/plot_time_domain_signal.py +0 -59
- modusa/signals/signal1d.py +0 -311
- modusa/signals/signal2d.py +0 -226
- modusa/signals/uniform_time_domain_signal.py +0 -212
- modusa-0.1.0.dist-info/RECORD +0 -41
- {modusa-0.1.0.dist-info → modusa-0.2.1.dist-info}/WHEEL +0 -0
- {modusa-0.1.0.dist-info → modusa-0.2.1.dist-info}/entry_points.txt +0 -0
- {modusa-0.1.0.dist-info → modusa-0.2.1.dist-info}/licenses/LICENSE.md +0 -0
modusa/.DS_Store
CHANGED
|
Binary file
|
modusa/decorators.py
CHANGED
|
@@ -103,7 +103,7 @@ def validate_arg(arg_name: str, value: Any, expected_type: Any) -> None:
|
|
|
103
103
|
union_args = get_args(expected_type)
|
|
104
104
|
for typ in union_args:
|
|
105
105
|
typ_origin = get_origin(typ) or typ
|
|
106
|
-
if
|
|
106
|
+
if isinstance(value, typ_origin):
|
|
107
107
|
return
|
|
108
108
|
|
|
109
109
|
# ❌ If none match
|
|
@@ -116,17 +116,17 @@ def validate_arg(arg_name: str, value: Any, expected_type: Any) -> None:
|
|
|
116
116
|
|
|
117
117
|
# Handle generic types like list[float], tuple[int, str]
|
|
118
118
|
elif origin is not None:
|
|
119
|
-
if
|
|
119
|
+
if not isinstance(value, origin):
|
|
120
120
|
raise excp.ValidationError(
|
|
121
|
-
f"Argument '{arg_name}' must be
|
|
121
|
+
f"Argument '{arg_name}' must be of type {origin.__name__}, got {type(value).__name__}"
|
|
122
122
|
)
|
|
123
123
|
return
|
|
124
124
|
|
|
125
125
|
# ✅ Handle plain types
|
|
126
126
|
elif isinstance(expected_type, type):
|
|
127
|
-
if
|
|
127
|
+
if not isinstance(value, expected_type):
|
|
128
128
|
raise excp.ValidationError(
|
|
129
|
-
f"Argument '{arg_name}' must be
|
|
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
|
|
@@ -6,26 +6,76 @@ import questionary
|
|
|
6
6
|
import sys
|
|
7
7
|
|
|
8
8
|
ROOT_DIR = Path(__file__).parents[3].resolve()
|
|
9
|
+
SRC_CODE_DIR = ROOT_DIR / "src/modusa"
|
|
10
|
+
TESTS_DIR = ROOT_DIR / "tests"
|
|
9
11
|
TEMPLATES_DIR = ROOT_DIR / "src/modusa/devtools/templates"
|
|
10
12
|
|
|
13
|
+
PLUGIN_INFO = {
|
|
14
|
+
"template_fp": TEMPLATES_DIR / "plugin.py",
|
|
15
|
+
"test_template_fp": TEMPLATES_DIR / "test.py",
|
|
16
|
+
"template_dump_dp": SRC_CODE_DIR / "plugins",
|
|
17
|
+
"test_template_dump_dp": TESTS_DIR / "test_plugins"
|
|
18
|
+
}
|
|
19
|
+
IO_INFO = {
|
|
20
|
+
"template_fp": TEMPLATES_DIR / "io.py",
|
|
21
|
+
"test_template_fp": TEMPLATES_DIR / "test.py",
|
|
22
|
+
"template_dump_dp": SRC_CODE_DIR / "io",
|
|
23
|
+
"test_template_dump_dp": TESTS_DIR / "test_io"
|
|
24
|
+
}
|
|
25
|
+
GENERATOR_INFO = {
|
|
26
|
+
"template_fp": TEMPLATES_DIR / "generator.py",
|
|
27
|
+
"test_template_fp": TEMPLATES_DIR / "test.py",
|
|
28
|
+
"template_dump_dp": SRC_CODE_DIR / "generators",
|
|
29
|
+
"test_template_dump_dp": TESTS_DIR / "test_generators"
|
|
30
|
+
}
|
|
31
|
+
SIGNAL_INFO = {
|
|
32
|
+
"template_fp": TEMPLATES_DIR / "signal.py",
|
|
33
|
+
"test_template_fp": TEMPLATES_DIR / "test.py",
|
|
34
|
+
"template_dump_dp": SRC_CODE_DIR / "signals",
|
|
35
|
+
"test_template_dump_dp": TESTS_DIR / "test_signals"
|
|
36
|
+
}
|
|
37
|
+
ENGINE_INFO = {
|
|
38
|
+
"template_fp": TEMPLATES_DIR / "engine.py",
|
|
39
|
+
"test_template_fp": TEMPLATES_DIR / "test.py",
|
|
40
|
+
"template_dump_dp": SRC_CODE_DIR / "engines",
|
|
41
|
+
"test_template_dump_dp": TESTS_DIR / "test_engines"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
11
45
|
class TemplateGenerator():
|
|
12
46
|
"""
|
|
13
|
-
Generates template for `plugin`, `engine`, `signal`, `generator
|
|
47
|
+
Generates template for `plugin`, `engine`, `signal`, `generator` along with its corresponding `test` file
|
|
48
|
+
in the `tests` directory.
|
|
14
49
|
"""
|
|
15
50
|
|
|
51
|
+
|
|
16
52
|
@staticmethod
|
|
17
|
-
def
|
|
53
|
+
def get_path_info(for_what: str):
|
|
54
|
+
if for_what == "plugin": return PLUGIN_INFO
|
|
55
|
+
if for_what == "io": return IO_INFO
|
|
56
|
+
if for_what == "signal": return SIGNAL_INFO
|
|
57
|
+
if for_what == "engine": return ENGINE_INFO
|
|
58
|
+
if for_what == "generator": return GENERATOR_INFO
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def ask_questions(for_what: str, path_info: dict) -> dict:
|
|
62
|
+
"""Asks question about the template to be generated."""
|
|
18
63
|
print("----------------------")
|
|
19
64
|
print(for_what.upper())
|
|
20
65
|
print("----------------------")
|
|
21
66
|
module_name = questionary.text("Module name (snake_case): ").ask()
|
|
67
|
+
|
|
22
68
|
if module_name is None:
|
|
23
69
|
sys.exit(1)
|
|
24
|
-
|
|
70
|
+
|
|
71
|
+
if not module_name.endswith(".py"): # Adding extension
|
|
72
|
+
module_name = module_name + ".py"
|
|
73
|
+
# Checking if the module name already exists in the dump directory
|
|
74
|
+
if (path_info["template_dump_dp"] / module_name).exists():
|
|
25
75
|
print(f"⚠️ File already exists, choose another name.")
|
|
26
76
|
sys.exit(1)
|
|
27
77
|
|
|
28
|
-
class_name = questionary.text("Class name (CamelCase): ").ask()
|
|
78
|
+
class_name = questionary.text(f"Class name (CamelCase{for_what.capitalize()}): ").ask()
|
|
29
79
|
if class_name is None:
|
|
30
80
|
sys.exit(1)
|
|
31
81
|
|
|
@@ -37,43 +87,58 @@ class TemplateGenerator():
|
|
|
37
87
|
if author_email is None:
|
|
38
88
|
sys.exit(1)
|
|
39
89
|
|
|
40
|
-
answers = {"module_name": module_name, "class_name": class_name, "author_name": author_name, "author_email": author_email, "date_created": date.today()}
|
|
90
|
+
answers = {"for_what": for_what, "module_name": module_name, "class_name": class_name, "author_name": author_name, "author_email": author_email, "date_created": date.today()}
|
|
41
91
|
|
|
42
92
|
return answers
|
|
43
93
|
|
|
44
94
|
@staticmethod
|
|
45
|
-
def load_template_file(
|
|
46
|
-
|
|
47
|
-
if not
|
|
48
|
-
print(f"❌ Template not found: {
|
|
95
|
+
def load_template_file(template_fp: Path) -> str:
|
|
96
|
+
"""Loads template file."""
|
|
97
|
+
if not template_fp.exists():
|
|
98
|
+
print(f"❌ Template not found: {template_fp}")
|
|
49
99
|
sys.exit(1)
|
|
50
100
|
|
|
51
|
-
template_code =
|
|
101
|
+
template_code = template_fp.read_text()
|
|
52
102
|
|
|
53
103
|
return template_code
|
|
54
104
|
|
|
55
105
|
@staticmethod
|
|
56
106
|
def fill_placeholders(template_code: str, placehoders_dict: dict) -> str:
|
|
107
|
+
"""Fills placeholder in the template with the user input from CLI."""
|
|
57
108
|
template_code = template_code.format(**placehoders_dict) # Fill placeholders
|
|
58
109
|
return template_code
|
|
59
110
|
|
|
60
111
|
@staticmethod
|
|
61
112
|
def save_file(content: str, output_path: Path) -> None:
|
|
113
|
+
"""Saves file in the correct directory with the right tempalate content."""
|
|
62
114
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
63
115
|
output_path.write_text(content)
|
|
64
|
-
|
|
116
|
+
|
|
117
|
+
# Generating a corresponding test file too
|
|
118
|
+
what_for = output_path.parent.name # plugins, generators, ...
|
|
119
|
+
module_name = output_path.name # this.py
|
|
120
|
+
|
|
65
121
|
|
|
66
122
|
@staticmethod
|
|
67
123
|
def create_template(for_what: str) -> None:
|
|
68
124
|
|
|
125
|
+
# Load correct path location info for the templates and where to dump the files
|
|
126
|
+
path_info: dict = TemplateGenerator.get_path_info(for_what)
|
|
127
|
+
|
|
69
128
|
# Ask basic questions to create the template for `plugin`, `generator`, ...
|
|
70
|
-
answers: dict = TemplateGenerator.ask_questions(for_what)
|
|
129
|
+
answers: dict = TemplateGenerator.ask_questions(for_what, path_info)
|
|
71
130
|
|
|
72
|
-
# Load the correct template file
|
|
73
|
-
template_code: str = TemplateGenerator.load_template_file(
|
|
131
|
+
# Load the correct template file and test file
|
|
132
|
+
template_code: str = TemplateGenerator.load_template_file(template_fp=path_info['template_fp'])
|
|
133
|
+
test_code: str = TemplateGenerator.load_template_file(template_fp=path_info['test_template_fp'])
|
|
74
134
|
|
|
75
135
|
# Update the dynamic values based on the answers
|
|
76
136
|
template_code: str = TemplateGenerator.fill_placeholders(template_code, answers)
|
|
137
|
+
test_code: str = TemplateGenerator.fill_placeholders(test_code, answers)
|
|
77
138
|
|
|
78
139
|
# Save it to a file and put it in the correct folder
|
|
79
|
-
TemplateGenerator.save_file(content=template_code, output_path=
|
|
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'])
|
|
142
|
+
|
|
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']))
|
modusa/devtools/main.py
CHANGED
|
@@ -20,10 +20,12 @@ def main():
|
|
|
20
20
|
create_parser = subparsers.add_parser("create", help="Create new Modusa components")
|
|
21
21
|
create_subparsers = create_parser.add_subparsers(dest="what", required=True)
|
|
22
22
|
|
|
23
|
-
create_subparsers.add_parser("engine", help="Create a new engine class").set_defaults(func=lambda:TemplateGenerator.create_template("
|
|
24
|
-
create_subparsers.add_parser("plugin", help="Create a new plugin class").set_defaults(func=lambda:TemplateGenerator.create_template("
|
|
25
|
-
create_subparsers.add_parser("signal", help="Create a new signal class").set_defaults(func=lambda:TemplateGenerator.create_template("
|
|
26
|
-
create_subparsers.add_parser("generator", help="Create a new signal generator class").set_defaults(func=lambda:TemplateGenerator.create_template("
|
|
23
|
+
create_subparsers.add_parser("engine", help="Create a new engine class").set_defaults(func=lambda:TemplateGenerator.create_template("engine"))
|
|
24
|
+
create_subparsers.add_parser("plugin", help="Create a new plugin class").set_defaults(func=lambda:TemplateGenerator.create_template("plugin"))
|
|
25
|
+
create_subparsers.add_parser("signal", help="Create a new signal class").set_defaults(func=lambda:TemplateGenerator.create_template("signal"))
|
|
26
|
+
create_subparsers.add_parser("generator", help="Create a new signal generator class").set_defaults(func=lambda:TemplateGenerator.create_template("generator"))
|
|
27
|
+
create_subparsers.add_parser("io", help="Create a new IO class").set_defaults(func=lambda:TemplateGenerator.create_template("io"))
|
|
28
|
+
|
|
27
29
|
|
|
28
30
|
# --- LIST group ---
|
|
29
31
|
list_parser = subparsers.add_parser("list", help="List information about Modusa components")
|
|
@@ -3,20 +3,21 @@
|
|
|
3
3
|
|
|
4
4
|
from modusa import excp
|
|
5
5
|
from modusa.decorators import validate_args_type
|
|
6
|
-
from modusa.engines
|
|
6
|
+
from modusa.engines import ModusaEngine
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
+
|
|
9
10
|
class {class_name}(ModusaEngine):
|
|
10
11
|
"""
|
|
11
|
-
|
|
12
|
+
|
|
12
13
|
"""
|
|
13
14
|
|
|
14
15
|
#--------Meta Information----------
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
_name = ""
|
|
17
|
+
_description = ""
|
|
18
|
+
_author_name = "{author_name}"
|
|
19
|
+
_author_email = "{author_email}"
|
|
20
|
+
_created_at = "{date_created}"
|
|
20
21
|
#----------------------------------
|
|
21
22
|
|
|
22
23
|
def __init__(self):
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
from modusa import excp
|
|
4
5
|
from modusa.decorators import validate_args_type
|
|
5
|
-
from modusa.generators
|
|
6
|
+
from modusa.generators import ModusaGenerator
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class {class_name}(ModusaGenerator):
|
|
@@ -11,16 +12,13 @@ class {class_name}(ModusaGenerator):
|
|
|
11
12
|
"""
|
|
12
13
|
|
|
13
14
|
#--------Meta Information----------
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
_name = ""
|
|
16
|
+
_description = ""
|
|
17
|
+
_author_name = "{author_name}"
|
|
18
|
+
_author_email = "{author_email}"
|
|
19
|
+
_created_at = "{date_created}"
|
|
19
20
|
#----------------------------------
|
|
20
21
|
|
|
21
22
|
def __init__(self):
|
|
22
23
|
super().__init__()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def generate(self) -> Any:
|
|
26
|
-
pass
|
|
24
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
|
|
8
|
+
|
|
9
|
+
class {class_name}(ModusaIO):
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
#--------Meta Information----------
|
|
15
|
+
_name = ""
|
|
16
|
+
_description = ""
|
|
17
|
+
_author_name = "{author_name}"
|
|
18
|
+
_author_email = "{author_email}"
|
|
19
|
+
_created_at = "{date_created}"
|
|
20
|
+
#----------------------------------
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
from modusa
|
|
4
|
+
from modusa import excp
|
|
5
5
|
from modusa.decorators import immutable_property, validate_args_type, plugin_safety_check
|
|
6
|
+
from modusa.plugins import ModusaPlugin
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class {class_name}(ModusaPlugin):
|
|
@@ -11,11 +12,11 @@ class {class_name}(ModusaPlugin):
|
|
|
11
12
|
"""
|
|
12
13
|
|
|
13
14
|
#--------Meta Information----------
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
_name = ""
|
|
16
|
+
_description = ""
|
|
17
|
+
_author_name = "{author_name}"
|
|
18
|
+
_author_email = "{author_email}"
|
|
19
|
+
_created_at = "{date_created}"
|
|
19
20
|
#----------------------------------
|
|
20
21
|
|
|
21
22
|
def __init__(self):
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
|
|
10
|
+
class {class_name}(ModusaSignal):
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
#--------Meta Information----------
|
|
16
|
+
_name = ""
|
|
17
|
+
_description = ""
|
|
18
|
+
_author_name = "{author_name}"
|
|
19
|
+
_author_email = "{author_email}"
|
|
20
|
+
_created_at = "{date_created}"
|
|
21
|
+
#----------------------------------
|
|
22
|
+
|
|
23
|
+
@validate_args_type()
|
|
24
|
+
def __init__(self):
|
|
25
|
+
super().__init__() # Instantiating `ModusaSignal` class
|
|
26
|
+
|
|
27
|
+
self.title = "" # This title will be used as plot title by default
|
|
28
|
+
|
|
29
|
+
#----------------------
|
|
30
|
+
# Properties
|
|
31
|
+
#----------------------
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
#----------------------
|
|
39
|
+
# Tools
|
|
40
|
+
#----------------------
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
#--------Meta Information----------
|
|
5
|
+
_class_name = "{class_name}"
|
|
6
|
+
_author_name = "{author_name}"
|
|
7
|
+
_author_email = "{author_email}"
|
|
8
|
+
_created_at = "{date_created}"
|
|
9
|
+
#----------------------------------
|
|
10
|
+
|
|
11
|
+
from modusa.{for_what}s import {class_name}
|
modusa/engines/.DS_Store
ADDED
|
Binary file
|
modusa/engines/__init__.py
CHANGED
modusa/generators/__init__.py
CHANGED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from modusa import excp
|
|
5
|
+
from modusa.decorators import validate_args_type
|
|
6
|
+
from modusa.generators import ModusaGenerator
|
|
7
|
+
from modusa.signals import AudioSignal
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
class AudioWaveformGenerator(ModusaGenerator):
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
#--------Meta Information----------
|
|
16
|
+
_name = "Audio Waveform Generator"
|
|
17
|
+
_description = ""
|
|
18
|
+
_author_name = "Ankit Anand"
|
|
19
|
+
_author_email = "ankit0.anand0@gmail.com"
|
|
20
|
+
_created_at = "2025-07-07"
|
|
21
|
+
#----------------------------------
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def generate_example() -> "AudioSignal":
|
|
25
|
+
"""
|
|
26
|
+
Generates a simple sine wave audio signal for demonstration purposes.
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
AudioSignal
|
|
31
|
+
A 600 Hz sine wave lasting 2 seconds, sampled at 10,000 Hz.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
sr = 10000 # Hz
|
|
35
|
+
duration = 2 # sec
|
|
36
|
+
freq = 600 # Hz
|
|
37
|
+
|
|
38
|
+
t = np.arange(0, duration, 1 / sr)
|
|
39
|
+
y = np.sin(2 * np.pi * freq * t)
|
|
40
|
+
|
|
41
|
+
signal = AudioSignal(y=y, sr=sr, title="Example") # assuming AudioSignal accepts y and t
|
|
42
|
+
|
|
43
|
+
return signal
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def generate_random(duration: float = 1.0, sr: int = 10000) -> "AudioSignal":
|
|
48
|
+
"""
|
|
49
|
+
Generates a random audio signal of given duration and sample rate.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
duration : float, optional
|
|
54
|
+
Duration of the signal in seconds (default is 1.0).
|
|
55
|
+
sr : int, optional
|
|
56
|
+
Sampling rate in Hz (default is 10,000).
|
|
57
|
+
|
|
58
|
+
Returns
|
|
59
|
+
-------
|
|
60
|
+
AudioSignal
|
|
61
|
+
A randomly generated signal of the specified duration and sample rate.
|
|
62
|
+
"""
|
|
63
|
+
num_samples = int(duration * sr)
|
|
64
|
+
t = np.linspace(0, duration, num=num_samples, endpoint=False)
|
|
65
|
+
y = np.random.uniform(low=-1.0, high=1.0, size=num_samples) # use uniform [-1, 1] for audio-like signal
|
|
66
|
+
|
|
67
|
+
signal = AudioSignal(y=y, t=t, title="Random")
|
|
68
|
+
return signal
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
@validate_args_type()
|
|
72
|
+
def generate_sinusoid(
|
|
73
|
+
A: float | int = 1.0,
|
|
74
|
+
f: float | int = 10.0,
|
|
75
|
+
phi: float | int = 0.0,
|
|
76
|
+
duration: float | int = 1.0,
|
|
77
|
+
sr: int = 1000,
|
|
78
|
+
) -> "AudioSignal":
|
|
79
|
+
"""
|
|
80
|
+
Generates a sinusoidal AudioSignal with specified amplitude, frequency, phase, duration, and sample rate.
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
A : float
|
|
85
|
+
Amplitude of the sinusoid (default: 1.0)
|
|
86
|
+
f : float
|
|
87
|
+
Frequency in Hz (default: 10.0)
|
|
88
|
+
phi : float
|
|
89
|
+
Phase in radians (default: 0.0)
|
|
90
|
+
duration : float
|
|
91
|
+
Duration of the signal in seconds (default: 1.0)
|
|
92
|
+
sr : int
|
|
93
|
+
Sampling rate in Hz (default: 1000)
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
AudioSignal
|
|
98
|
+
A time-domain sinusoidal signal with the given parameters.
|
|
99
|
+
"""
|
|
100
|
+
A, f, phi, duration, sr = float(A), float(f), float(phi), float(duration), int(sr)
|
|
101
|
+
|
|
102
|
+
t = np.arange(0, duration, 1 / sr)
|
|
103
|
+
y = A * np.sin(2 * np.pi * f * t + phi)
|
|
104
|
+
|
|
105
|
+
signal = AudioSignal(y=y, sr=sr, title="Sinusoid")
|
|
106
|
+
return signal
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
@validate_args_type()
|
|
110
|
+
def generate_square(
|
|
111
|
+
A: float | int = 1.0,
|
|
112
|
+
f: float | int = 10.0,
|
|
113
|
+
phi: float | int = 0.0,
|
|
114
|
+
duration: float | int = 1.0,
|
|
115
|
+
sr: int = 1000,
|
|
116
|
+
) -> "AudioSignal":
|
|
117
|
+
"""
|
|
118
|
+
Generates a square wave AudioSignal with specified amplitude, frequency, phase, duration, and sample rate.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
A : float
|
|
123
|
+
Amplitude of the square wave (default: 1.0)
|
|
124
|
+
f : float
|
|
125
|
+
Frequency in Hz (default: 10.0)
|
|
126
|
+
phi : float
|
|
127
|
+
Phase in radians (default: 0.0)
|
|
128
|
+
duration : float
|
|
129
|
+
Duration of the signal in seconds (default: 1.0)
|
|
130
|
+
sr : int
|
|
131
|
+
Sampling rate in Hz (default: 1000)
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
AudioSignal
|
|
136
|
+
A square wave signal of the specified parameters.
|
|
137
|
+
"""
|
|
138
|
+
A, f, phi, duration, sr = float(A), float(f), float(phi), float(duration), int(sr)
|
|
139
|
+
t = np.arange(0, duration, 1 / sr)
|
|
140
|
+
|
|
141
|
+
y = A * np.sign(np.sin(2 * np.pi * f * t + phi))
|
|
142
|
+
|
|
143
|
+
signal = AudioSignal(y=y, sr=sr, title="Square")
|
|
144
|
+
return signal
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
@validate_args_type()
|
|
149
|
+
def generate_sawtooth(
|
|
150
|
+
A: float | int = 1.0,
|
|
151
|
+
f: float | int = 10.0,
|
|
152
|
+
phi: float | int = 0.0,
|
|
153
|
+
duration: float | int = 1.0,
|
|
154
|
+
sr: int = 1000,
|
|
155
|
+
) -> "AudioSignal":
|
|
156
|
+
"""
|
|
157
|
+
Generates a sawtooth wave AudioSignal with specified amplitude, frequency, phase, duration, and sample rate.
|
|
158
|
+
|
|
159
|
+
Parameters
|
|
160
|
+
----------
|
|
161
|
+
A : float
|
|
162
|
+
Amplitude of the sawtooth wave (default: 1.0)
|
|
163
|
+
f : float
|
|
164
|
+
Frequency in Hz (default: 10.0)
|
|
165
|
+
phi : float
|
|
166
|
+
Phase in radians (default: 0.0)
|
|
167
|
+
duration : float
|
|
168
|
+
Duration of the signal in seconds (default: 1.0)
|
|
169
|
+
sr : int
|
|
170
|
+
Sampling rate in Hz (default: 1000)
|
|
171
|
+
|
|
172
|
+
Returns
|
|
173
|
+
-------
|
|
174
|
+
AudioSignal
|
|
175
|
+
A sawtooth wave signal of the specified parameters.
|
|
176
|
+
"""
|
|
177
|
+
A, f, phi, duration, sr = float(A), float(f), float(phi), float(duration), int(sr)
|
|
178
|
+
t = np.arange(0, duration, 1 / sr)
|
|
179
|
+
|
|
180
|
+
# Convert phase from radians to fractional cycle offset
|
|
181
|
+
phase_offset = phi / (2 * np.pi)
|
|
182
|
+
y = A * (2 * ((f * t + phase_offset) % 1) - 1)
|
|
183
|
+
|
|
184
|
+
signal = AudioSignal(y=y, sr=sr, title="Sawtooth")
|
|
185
|
+
return signal
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@staticmethod
|
|
189
|
+
@validate_args_type()
|
|
190
|
+
def generate_triangle(
|
|
191
|
+
A: float | int = 1.0,
|
|
192
|
+
f: float | int = 10.0,
|
|
193
|
+
phi: float | int = 0.0,
|
|
194
|
+
duration: float | int = 1.0,
|
|
195
|
+
sr: int = 1000,
|
|
196
|
+
) -> "AudioSignal":
|
|
197
|
+
"""
|
|
198
|
+
Generates a triangle wave AudioSignal with specified amplitude, frequency, phase, duration, and sample rate.
|
|
199
|
+
|
|
200
|
+
Parameters
|
|
201
|
+
----------
|
|
202
|
+
A : float
|
|
203
|
+
Amplitude of the triangle wave (default: 1.0)
|
|
204
|
+
f : float
|
|
205
|
+
Frequency in Hz (default: 10.0)
|
|
206
|
+
phi : float
|
|
207
|
+
Phase in radians (default: 0.0)
|
|
208
|
+
duration : float
|
|
209
|
+
Duration of the signal in seconds (default: 1.0)
|
|
210
|
+
sr : int
|
|
211
|
+
Sampling rate in Hz (default: 1000)
|
|
212
|
+
|
|
213
|
+
Returns
|
|
214
|
+
-------
|
|
215
|
+
AudioSignal
|
|
216
|
+
A triangle wave signal of the specified parameters.
|
|
217
|
+
"""
|
|
218
|
+
A, f, phi, duration, sr = float(A), float(f), float(phi), float(duration), int(sr)
|
|
219
|
+
t = np.arange(0, duration, 1 / sr)
|
|
220
|
+
phase_offset = phi / (2 * np.pi) # Convert radians to cycle offset
|
|
221
|
+
|
|
222
|
+
# Triangle wave formula: 2 * abs(2 * frac(x) - 1) - 1 scaled to amplitude
|
|
223
|
+
y = A * (2 * np.abs(2 * ((f * t + phase_offset) % 1) - 1) - 1)
|
|
224
|
+
|
|
225
|
+
signal = AudioSignal(y=y, sr=sr, title="Triangle")
|
|
226
|
+
return signal
|
|
227
|
+
|
modusa/generators/base.py
CHANGED
|
@@ -8,33 +8,22 @@ from typing import Any
|
|
|
8
8
|
|
|
9
9
|
class ModusaGenerator(ABC):
|
|
10
10
|
"""
|
|
11
|
-
Base class for any
|
|
11
|
+
Base class for any type of signal generators for modusa framework.
|
|
12
|
+
|
|
13
|
+
Note
|
|
14
|
+
----
|
|
15
|
+
- This class is intended to be subclassed by any Generator related tools built for the modusa framework.
|
|
16
|
+
- In order to create a generator tool, you can use modusa-dev CLI to generate an generator template.
|
|
17
|
+
- It is recommended to treat subclasses of ModusaGenerator as namespaces and define @staticmethods with control parameters, rather than using instance-level __init__ methods.
|
|
18
|
+
|
|
12
19
|
|
|
13
|
-
Generates instance of different `ModusaSignal` subclass.
|
|
14
20
|
"""
|
|
15
21
|
|
|
16
22
|
#--------Meta Information----------
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
_name = ""
|
|
24
|
+
_description = ""
|
|
25
|
+
_author_name = "Ankit Anand"
|
|
26
|
+
_author_email = "ankit0.anand0@gmail.com"
|
|
27
|
+
_created_at = "2025-07-04"
|
|
22
28
|
#----------------------------------
|
|
23
|
-
|
|
24
|
-
def __init__(self, signal_cls: Any):
|
|
25
|
-
|
|
26
|
-
if not issubclass(signal_cls, ModusaSignal):
|
|
27
|
-
raise excp.InputValueError(f"`signal_cls` must be a subclass of ModusaSignal, got {signal_cls.__name__}")
|
|
28
|
-
if signal_cls not in self.allowed_output_signal_types:
|
|
29
|
-
raise excp.InputValueError(f"`signal_cls` must be a one of the allowed types {self.allowed_output_signal_types}, got {signal_cls.__name__}")
|
|
30
|
-
|
|
31
|
-
self._signal_cls = signal_cls
|
|
32
|
-
|
|
33
|
-
@immutable_property("Mutation not allowed.")
|
|
34
|
-
def signal_cls(self) -> Any:
|
|
35
|
-
return self._signal_cls
|
|
36
|
-
|
|
37
|
-
@property
|
|
38
|
-
@abstractmethod
|
|
39
|
-
def allowed_output_signal_types(self) -> tuple[type, ...]:
|
|
40
|
-
return
|
|
29
|
+
|
modusa/io/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from .base import ModusaIO
|
|
4
|
+
|
|
5
|
+
from .audio_loader import AudioLoader
|
|
6
|
+
from .audio_converter import AudioConverter
|
|
7
|
+
from .youtube_downloader import YoutubeDownloader
|
|
8
|
+
from .plotter import Plotter
|
|
9
|
+
from .audio_player import AudioPlayer
|