modusa 0.3.41__tar.gz → 0.3.43__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.
Files changed (94) hide show
  1. {modusa-0.3.41 → modusa-0.3.43}/PKG-INFO +3 -2
  2. {modusa-0.3.41 → modusa-0.3.43}/pyproject.toml +3 -2
  3. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/__init__.py +2 -2
  4. modusa-0.3.43/src/modusa/tools/__init__.py +12 -0
  5. modusa-0.3.41/src/modusa/tools/plotter.py → modusa-0.3.43/src/modusa/tools/_plotter_old.py +20 -9
  6. modusa-0.3.43/src/modusa/tools/audio_recorder.py +115 -0
  7. modusa-0.3.43/src/modusa/tools/plotter.py +887 -0
  8. modusa-0.3.41/src/modusa/tools/__init__.py +0 -8
  9. {modusa-0.3.41 → modusa-0.3.43}/LICENSE.md +0 -0
  10. {modusa-0.3.41 → modusa-0.3.43}/README.md +0 -0
  11. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/.DS_Store +0 -0
  12. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/config.py +0 -0
  13. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/decorators.py +0 -0
  14. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/generate_docs_source.py +0 -0
  15. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/generate_template.py +0 -0
  16. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/list_authors.py +0 -0
  17. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/list_plugins.py +0 -0
  18. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/main.py +0 -0
  19. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/templates/generator.py +0 -0
  20. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/templates/io.py +0 -0
  21. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/templates/model.py +0 -0
  22. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/templates/plugin.py +0 -0
  23. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/templates/test.py +0 -0
  24. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/templates/tool.py +0 -0
  25. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/fonts/NotoSansDevanagari-Regular.ttf +0 -0
  26. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/__init__.py +0 -0
  27. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/audio.py +0 -0
  28. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/audio_waveforms.py +0 -0
  29. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/base.py +0 -0
  30. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/ftds.py +0 -0
  31. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/s1d.py +0 -0
  32. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/s2d.py +0 -0
  33. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/s_ax.py +0 -0
  34. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/t_ax.py +0 -0
  35. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/tds.py +0 -0
  36. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/__init__.py +0 -0
  37. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/__pycache__/signal1D.cpython-312.pyc.4443461152 +0 -0
  38. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/audio.py +0 -0
  39. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/base.py +0 -0
  40. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/data.py +0 -0
  41. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/ftds.py +0 -0
  42. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/s1d.py +0 -0
  43. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/s2d.py +0 -0
  44. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/s_ax.py +0 -0
  45. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/t_ax.py +0 -0
  46. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/tds.py +0 -0
  47. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/plugins/__init__.py +0 -0
  48. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/plugins/base.py +0 -0
  49. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/tools/ann_loader.py +0 -0
  50. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/tools/audio_converter.py +0 -0
  51. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/tools/audio_loader.py +0 -0
  52. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/tools/audio_player.py +0 -0
  53. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/tools/base.py +0 -0
  54. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/tools/math_ops.py +0 -0
  55. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/tools/youtube_downloader.py +0 -0
  56. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/utils/.DS_Store +0 -0
  57. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/utils/__init__.py +0 -0
  58. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/utils/config.py +0 -0
  59. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/utils/excp.py +0 -0
  60. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/utils/logger.py +0 -0
  61. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/utils/np_func_cat.py +0 -0
  62. {modusa-0.3.41 → modusa-0.3.43}/src/modusa/utils/plot.py +0 -0
  63. {modusa-0.3.41 → modusa-0.3.43}/tests/__init__.py +0 -0
  64. {modusa-0.3.41 → modusa-0.3.43}/tests/data/song1.mp3 +0 -0
  65. {modusa-0.3.41 → modusa-0.3.43}/tests/data/song1.wav +0 -0
  66. {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/audio_waveform.py +0 -0
  67. {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/test_audio.py +0 -0
  68. {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/test_ftds.py +0 -0
  69. {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/test_s1d.py +0 -0
  70. {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/test_s2d.py +0 -0
  71. {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/test_s_ax.py +0 -0
  72. {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/test_signal.py +0 -0
  73. {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/test_signal_generator.py +0 -0
  74. {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/test_t_ax.py +0 -0
  75. {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/test_tds.py +0 -0
  76. {modusa-0.3.41 → modusa-0.3.43}/tests/test_io/audio_player.py +0 -0
  77. {modusa-0.3.41 → modusa-0.3.43}/tests/test_io/plotter.py +0 -0
  78. {modusa-0.3.41 → modusa-0.3.43}/tests/test_models/test_data.py +0 -0
  79. {modusa-0.3.41 → modusa-0.3.43}/tests/test_models/test_t_ax.py +0 -0
  80. {modusa-0.3.41 → modusa-0.3.43}/tests/test_plugins/youtube_audio_loader.py +0 -0
  81. {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/frequency_domain_signal.py +0 -0
  82. {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/spectrogram.py +0 -0
  83. {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/test_axis.py +0 -0
  84. {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/test_feature_time_domain_signal.py +0 -0
  85. {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/test_frequency_time_domain_signal.py +0 -0
  86. {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/test_signal1D.py +0 -0
  87. {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/test_signal2D.py +0 -0
  88. {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/test_time_domain_signal.py +0 -0
  89. {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/test_u_ax.py +0 -0
  90. {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/test_window_signal.py +0 -0
  91. {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/time_domain_signal.py +0 -0
  92. {modusa-0.3.41 → modusa-0.3.43}/tests/test_tools/test_audio_converter.py +0 -0
  93. {modusa-0.3.41 → modusa-0.3.43}/tests/test_tools/test_fourier_tranform.py +0 -0
  94. {modusa-0.3.41 → modusa-0.3.43}/tests/test_tools/test_math_ops.py +0 -0
@@ -1,15 +1,16 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modusa
3
- Version: 0.3.41
3
+ Version: 0.3.43
4
4
  Summary: A modular signal analysis python library.
5
5
  Author-Email: Ankit Anand <ankit0.anand0@gmail.com>
6
6
  License: MIT
7
7
  Requires-Python: >=3.10
8
8
  Requires-Dist: numpy>=2.2.6
9
9
  Requires-Dist: matplotlib>=3.10.3
10
- Requires-Dist: yt-dlp>=2025.6.30
10
+ Requires-Dist: yt-dlp>=2025.8.22
11
11
  Requires-Dist: librosa==0.11.0
12
12
  Requires-Dist: IPython>=8.0.0
13
+ Requires-Dist: sounddevice>=0.5.2
13
14
  Description-Content-Type: text/markdown
14
15
 
15
16
  # modusa
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "modusa"
3
- version = "0.3.41"
3
+ version = "0.3.43"
4
4
  description = "A modular signal analysis python library."
5
5
  authors = [
6
6
  { name = "Ankit Anand", email = "ankit0.anand0@gmail.com" },
@@ -8,9 +8,10 @@ authors = [
8
8
  dependencies = [
9
9
  "numpy>=2.2.6",
10
10
  "matplotlib>=3.10.3",
11
- "yt-dlp>=2025.6.30",
11
+ "yt-dlp>=2025.8.22",
12
12
  "librosa==0.11.0",
13
13
  "IPython>=8.0.0",
14
+ "sounddevice>=0.5.2",
14
15
  ]
15
16
  requires-python = ">=3.10"
16
17
  readme = "README.md"
@@ -1,9 +1,9 @@
1
1
  from modusa.utils import excp, config
2
2
 
3
3
  #=====Giving access to plot functions to plot multiple signals.=====
4
- from modusa.tools import plot1d, plot2d, plot_dist
4
+ from modusa.tools import fig1d, fig2d, plot_dist
5
5
  #=====
6
6
 
7
- from modusa.tools import play, convert
7
+ from modusa.tools import play, convert, record
8
8
  from modusa.tools import download
9
9
  from modusa.tools import load, load_ann
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from .audio_player import play
4
+ from .audio_converter import convert
5
+ from .youtube_downloader import download
6
+ from .audio_loader import load
7
+ from .ann_loader import load_ann
8
+ from .audio_recorder import record
9
+
10
+ from .plotter import Figure1D as fig1d
11
+ from .plotter import Figure2D as fig2d
12
+ from .plotter import plot_dist
@@ -46,7 +46,7 @@ def set_default_hindi_font():
46
46
  # Set as default rcParam
47
47
  mpl.rcParams['font.family'] = hindi_font.get_name()
48
48
 
49
- set_default_hindi_font()
49
+ #set_default_hindi_font()
50
50
 
51
51
  #======== 1D ===========
52
52
  def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylabel=None, title=None, legend=None, fmt=None, show_grid=False, show_stem=False):
@@ -126,10 +126,11 @@ def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylab
126
126
  colors = plt.get_cmap('tab10').colors
127
127
 
128
128
  fig = plt.figure(figsize=(16, 2))
129
- gs = gridspec.GridSpec(2, 1, height_ratios=[0.2, 1])
129
+ gs = gridspec.GridSpec(3, 1, height_ratios=[0.2, 0.2, 1])
130
130
 
131
- signal_ax = fig.add_subplot(gs[1, 0])
131
+ signal_ax = fig.add_subplot(gs[2, 0])
132
132
  annotation_ax = fig.add_subplot(gs[0, 0], sharex=signal_ax)
133
+ events_ax = fig.add_subplot(gs[1, 0])
133
134
 
134
135
  # Set lim
135
136
  if xlim is not None:
@@ -207,16 +208,18 @@ def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylab
207
208
  # Clip boundaries to xlim
208
209
  start = max(start, x_view_min)
209
210
  end = min(end, x_view_max)
210
-
211
- color = colors[i % len(colors)]
211
+
212
+ box_colors = ["gray", "lightgray"] # Alternates color between two
213
+ box_color = box_colors[i % 2]
214
+
212
215
  width = end - start
213
- rect = Rectangle((start, 0), width, 1, color=color, alpha=0.7)
216
+ rect = Rectangle((start, 0), width, 1, color=box_color, alpha=0.7)
214
217
  annotation_ax.add_patch(rect)
215
218
 
216
219
  text_obj = annotation_ax.text(
217
220
  (start + end) / 2, 0.5, tag,
218
221
  ha='center', va='center',
219
- fontsize=10, color='white', fontweight='bold', zorder=10, clip_on=True
222
+ fontsize=10, color="black", fontweight='bold', zorder=10, clip_on=True
220
223
  )
221
224
 
222
225
  text_obj.set_clip_path(rect)
@@ -224,12 +227,15 @@ def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylab
224
227
 
225
228
  # Add vlines
226
229
  if events is not None:
230
+ # if not isinstance(events, tuple):
231
+ # raise TypeError(f"`events` should be tuple, got {type(events)}")
232
+
227
233
  for xpos in events:
228
234
  if xlim is not None:
229
235
  if xlim[0] <= xpos <= xlim[1]:
230
- annotation_ax.axvline(x=xpos, color='black', linestyle='--', linewidth=1.5)
236
+ annotation_ax.axvline(x=xpos, color='red', linestyle='-', linewidth=1.5)
231
237
  else:
232
- annotation_ax.axvline(x=xpos, color='black', linestyle='--', linewidth=1.5)
238
+ annotation_ax.axvline(x=xpos, color='red', linestyle='-', linewidth=1.5)
233
239
 
234
240
  # Add legend
235
241
  if legend is not None:
@@ -253,6 +259,11 @@ def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylab
253
259
  annotation_ax.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
254
260
  else:
255
261
  annotation_ax.axis("off")
262
+
263
+ if events is not None:
264
+ events_ax.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
265
+ else:
266
+ events_ax.axis("off")
256
267
 
257
268
 
258
269
  fig.subplots_adjust(hspace=0.01, wspace=0.05)
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env python3
2
+
3
+ #---------------------------------
4
+ # Author: Ankit Anand
5
+ # Date: 22/08/25
6
+ # Email: ankit0.anand0@gmail.com
7
+ #---------------------------------
8
+
9
+
10
+ import sounddevice as sd
11
+ import numpy as np
12
+ import ipywidgets as widgets
13
+ from IPython.display import display, clear_output, Audio
14
+
15
+ def record():
16
+ """
17
+ Create a UI to record audio in jupyter notebook, the
18
+ recorded signal is available as array.
19
+
20
+ .. code-block:: python
21
+
22
+ import modusa as ms
23
+ result = ms.record()
24
+ y, sr, title = result() # Keep it in the next cell
25
+
26
+ Returns
27
+ -------
28
+ Callable
29
+ A lambda function that returns y(audio signal), sr(sampling rate), title(title set in the UI)
30
+ """
31
+ devices = sd.query_devices()
32
+ device_options = [(d['name'][:30], i) for i, d in enumerate(devices) if d['max_input_channels'] > 0]
33
+
34
+ # Controls
35
+ device_dropdown = widgets.Dropdown(
36
+ options=device_options,
37
+ description="Microphone:",
38
+ layout=widgets.Layout(width="300px")
39
+ )
40
+
41
+ sr_dropdown = widgets.Dropdown(
42
+ options=[('16 Khz', 16000), ('22.05 Khz', 22050), ('44.1 Khz', 44100)],
43
+ value=22050,
44
+ description="Sample Rate:",
45
+ layout=widgets.Layout(width="300px")
46
+ )
47
+
48
+ title_box = widgets.Text(
49
+ placeholder="Title",
50
+ description="Title:",
51
+ layout=widgets.Layout(width="300px")
52
+ )
53
+
54
+ toggle_button = widgets.Button(
55
+ description="Record",
56
+ button_style="",
57
+ )
58
+
59
+ status = widgets.HTML(value="")
60
+ out = widgets.Output()
61
+
62
+ # State
63
+ recording = {"data": None, "sr": None, "title": None}
64
+ stream = {"obj": None, "frames": [], "recording": False}
65
+
66
+ def callback(indata, frames, time, status):
67
+ if not status:
68
+ stream["frames"].append(indata.copy())
69
+
70
+ def on_toggle(b):
71
+ if not stream["recording"]:
72
+ stream["frames"].clear()
73
+ sr = sr_dropdown.value
74
+ device_id = device_dropdown.value
75
+
76
+ stream["obj"] = sd.InputStream(callback=callback, channels=1, samplerate=sr, device=device_id)
77
+ stream["obj"].start()
78
+ stream["recording"] = True
79
+
80
+ toggle_button.description = "Stop"
81
+ toggle_button.button_style = "danger"
82
+ status.value = "Recording..."
83
+ else:
84
+ stream["obj"].stop()
85
+ stream["obj"].close()
86
+
87
+ sr = sr_dropdown.value
88
+ y = np.concatenate(stream["frames"], axis=0).flatten()
89
+ title = title_box.value.strip() or "Recording"
90
+
91
+ recording["data"], recording["sr"], recording["title"] = y, sr, title
92
+ record.result = (y, sr, title)
93
+ stream["recording"] = False
94
+
95
+ toggle_button.description = "Record"
96
+ toggle_button.button_style = "success"
97
+
98
+ with out:
99
+ clear_output()
100
+ display(Audio(y, rate=sr))
101
+
102
+ toggle_button.on_click(on_toggle)
103
+
104
+ # Layout
105
+ ui = widgets.VBox([
106
+ device_dropdown,
107
+ sr_dropdown,
108
+ title_box,
109
+ widgets.HBox([toggle_button]),
110
+ out
111
+ ])
112
+
113
+ display(ui)
114
+ record.result = None
115
+ return lambda: record.result