modusa 0.3.31__py3-none-any.whl → 0.3.42__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/__init__.py CHANGED
@@ -4,6 +4,6 @@ from modusa.utils import excp, config
4
4
  from modusa.tools import plot1d, plot2d, 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
modusa/tools/__init__.py CHANGED
@@ -5,4 +5,5 @@ from .audio_player import play
5
5
  from .audio_converter import convert
6
6
  from .youtube_downloader import download
7
7
  from .audio_loader import load
8
- from .ann_loader import load_ann
8
+ from .ann_loader import load_ann
9
+ from .audio_recorder import record
@@ -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
modusa/tools/plotter.py CHANGED
@@ -25,9 +25,31 @@ def _calculate_extent(x, y):
25
25
  y[0] - dy / 2,
26
26
  y[-1] + dy / 2
27
27
  ]
28
+
29
+ # Helper to load fonts (devnagri)
30
+ def set_default_hindi_font():
31
+ """
32
+ Hindi fonts works for both english and hindi.
33
+ """
34
+ from pathlib import Path
35
+ import matplotlib as mpl
36
+ import matplotlib.font_manager as fm
37
+ # Path to your bundled font
38
+ font_path = Path(__file__).resolve().parents[1] / "fonts" / "NotoSansDevanagari-Regular.ttf"
39
+
40
+ # Register the font with matplotlib
41
+ fm.fontManager.addfont(str(font_path))
42
+
43
+ # Get the font family name from the file
44
+ hindi_font = fm.FontProperties(fname=str(font_path))
45
+
46
+ # Set as default rcParam
47
+ mpl.rcParams['font.family'] = hindi_font.get_name()
48
+
49
+ set_default_hindi_font()
28
50
 
29
51
  #======== 1D ===========
30
- def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylabel=None, title=None, legend=None, show_grid=False, show_stem=False):
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):
31
53
  """
32
54
  Plots a 1D signal using matplotlib.
33
55
 
@@ -72,6 +94,9 @@ def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylab
72
94
  legend : list[str] | None
73
95
  - List of legend labels corresponding to each signal if plotting multiple lines.
74
96
  - Default: None
97
+ fmt: list[str] | None
98
+ - linefmt for different line plots.
99
+ - Default: None
75
100
  show_grid: bool
76
101
  - If you want to show the grid.
77
102
  - Default: False
@@ -84,21 +109,24 @@ def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylab
84
109
  plt.Figure
85
110
  Matplolib figure.
86
111
  """
87
-
88
112
  for arg in args:
89
113
  if len(arg) not in [1, 2]: # 1 if it just provides values, 2 if it provided axis as well
90
114
  raise ValueError(f"1D signal needs to have max 2 arrays (y, x) or simply (y, )")
91
115
 
92
116
  if isinstance(legend, str): legend = (legend, )
93
-
94
117
  if legend is not None:
95
118
  if len(legend) < len(args):
96
- raise ValueError(f"Legend should be provided for each signal.")
119
+ raise ValueError(f"`legend` should be provided for each signal.")
120
+
121
+ if isinstance(fmt, str): fmt = [fmt]
122
+ if fmt is not None:
123
+ if len(fmt) < len(args):
124
+ raise ValueError(f"`fmt` should be provided for each signal.")
125
+
126
+ colors = plt.get_cmap('tab10').colors
97
127
 
98
128
  fig = plt.figure(figsize=(16, 2))
99
129
  gs = gridspec.GridSpec(2, 1, height_ratios=[0.2, 1])
100
-
101
- colors = plt.get_cmap('tab10').colors
102
130
 
103
131
  signal_ax = fig.add_subplot(gs[1, 0])
104
132
  annotation_ax = fig.add_subplot(gs[0, 0], sharex=signal_ax)
@@ -109,7 +137,6 @@ def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylab
109
137
 
110
138
  if ylim is not None:
111
139
  signal_ax.set_ylim(ylim)
112
-
113
140
 
114
141
  # Add signal plot
115
142
  for i, signal in enumerate(args):
@@ -123,7 +150,10 @@ def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylab
123
150
  stemlines.set_color(colors[i])
124
151
  baseline.set_color("k")
125
152
  else:
126
- signal_ax.plot(x, y, color=colors[i], label=legend[i])
153
+ if fmt is not None:
154
+ signal_ax.plot(x, y, fmt[i], markersize=4, label=legend[i])
155
+ else:
156
+ signal_ax.plot(x, y, color=colors[i], label=legend[i])
127
157
  else:
128
158
  if show_stem is True:
129
159
  markerline, stemlines, baseline = signal_ax.stem(x, y)
@@ -131,7 +161,10 @@ def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylab
131
161
  stemlines.set_color(colors[i])
132
162
  baseline.set_color("k")
133
163
  else:
134
- signal_ax.plot(x, y, color=colors[i])
164
+ if fmt is not None:
165
+ signal_ax.plot(x, y, fmt[i], markersize=4)
166
+ else:
167
+ signal_ax.plot(x, y, color=colors[i])
135
168
 
136
169
  elif len(signal) == 2:
137
170
  y, x = signal[0], signal[1]
@@ -142,15 +175,22 @@ def plot1d(*args, ann=None, events=None, xlim=None, ylim=None, xlabel=None, ylab
142
175
  stemlines.set_color(colors[i])
143
176
  baseline.set_color("k")
144
177
  else:
145
- signal_ax.plot(x, y, color=colors[i], label=legend[i])
178
+ if fmt is not None:
179
+ signal_ax.plot(x, y, fmt[i], markersize=4, label=legend[i])
180
+ else:
181
+ signal_ax.plot(x, y, color=colors[i], label=legend[i])
146
182
  else:
147
183
  if show_stem is True:
148
- markerline, stemlines, baseline = signal_ax.stem(x, y, label=legend[i])
184
+ markerline, stemlines, baseline = signal_ax.stem(x, y)
149
185
  markerline.set_color(colors[i])
150
186
  stemlines.set_color(colors[i])
151
187
  baseline.set_color("k")
152
188
  else:
153
- signal_ax.plot(x, y, color=colors[i])
189
+ if fmt is not None:
190
+ signal_ax.plot(x, y, fmt[i], markersize=4)
191
+ else:
192
+ signal_ax.plot(x, y, color=colors[i])
193
+
154
194
 
155
195
  # Add annotations
156
196
  if ann is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modusa
3
- Version: 0.3.31
3
+ Version: 0.3.42
4
4
  Summary: A modular signal analysis python library.
5
5
  Author-Email: Ankit Anand <ankit0.anand0@gmail.com>
6
6
  License: MIT
@@ -10,6 +10,7 @@ Requires-Dist: matplotlib>=3.10.3
10
10
  Requires-Dist: yt-dlp>=2025.6.30
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,9 +1,9 @@
1
- modusa-0.3.31.dist-info/METADATA,sha256=TqaRsl9ajARqimZirKDNdTahba3b9X_d0gJqD4oiSuc,1369
2
- modusa-0.3.31.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
- modusa-0.3.31.dist-info/entry_points.txt,sha256=fmKpleVXj6CdaBVL14WoEy6xx7JQCs85jvzwTi3lePM,73
4
- modusa-0.3.31.dist-info/licenses/LICENSE.md,sha256=JTaXAjx5awk76VArKCx5dUW8vmLEWsL_ZlR7-umaHbA,1078
1
+ modusa-0.3.42.dist-info/METADATA,sha256=T8MRyy7MBHjm6w1boi604AFhBgWq2zSqNYukSFB9gJw,1403
2
+ modusa-0.3.42.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
+ modusa-0.3.42.dist-info/entry_points.txt,sha256=fmKpleVXj6CdaBVL14WoEy6xx7JQCs85jvzwTi3lePM,73
4
+ modusa-0.3.42.dist-info/licenses/LICENSE.md,sha256=JTaXAjx5awk76VArKCx5dUW8vmLEWsL_ZlR7-umaHbA,1078
5
5
  modusa/.DS_Store,sha256=_gm6qJREwfMi8dE7n5S89_RG46u5t3xHyD-smNhtNoM,6148
6
- modusa/__init__.py,sha256=uq6kORFFAODiCMGmOLWO0shE8-dVFWf5gmV8wxekmnk,280
6
+ modusa/__init__.py,sha256=Vny15JTP5lDr_4VxA_zSqL8eARbQzqLj0dk1OBSpJmk,288
7
7
  modusa/config.py,sha256=bTqK4t00FZqERVITrxW_q284aDDJAa9aMSfFknfR-oU,280
8
8
  modusa/decorators.py,sha256=8zeNX_wE37O6Vp0ysR4-WCZaEL8mq8dyCF_I5DHOzks,5905
9
9
  modusa/devtools/generate_docs_source.py,sha256=UDflHsk-Yh9-3YJTVBzKL32y8hcxiRgAlFEBTMiDqwM,3301
@@ -17,6 +17,7 @@ modusa/devtools/templates/model.py,sha256=iwmXadLMA4IAMgOEktdCWKZsceLgaaZkTWcJHq
17
17
  modusa/devtools/templates/plugin.py,sha256=MWqrxBxfRKVw2zjEgfAG0RYUAMjuazhqC5PXIb0gJi4,868
18
18
  modusa/devtools/templates/test.py,sha256=CjZ_oqwJFhltNYzqfK1pcz1ShgeJXZLOnYpmOawfASs,244
19
19
  modusa/devtools/templates/tool.py,sha256=2iQfHbFKtTELPvNoX42rHr9_5lXRrXpwxb9z5OL96Xc,435
20
+ modusa/fonts/NotoSansDevanagari-Regular.ttf,sha256=CEqU2J61Sq-5OgVuFUJcNP2Fn2NCh1Fl0wSDezvPwtI,221084
20
21
  modusa/generators/__init__.py,sha256=xQUsOObRhTEPzdZsBGd4nT_9AWaL8uE_bisFAN_Wp9Q,236
21
22
  modusa/generators/audio.py,sha256=91CnBDPZhagfJOx-cDoBBj3NrE92m70PmFmv7T8aQco,4477
22
23
  modusa/generators/audio_waveforms.py,sha256=x-r4_kDi2xieQNBCo11qY2uk2bFcvzeNOppfJnj2oYs,6105
@@ -40,14 +41,15 @@ modusa/models/t_ax.py,sha256=ZUhvZPUW1TkdZYuUd6Ucm-vsv0JqtZ9yEe3ab67Ma6w,8022
40
41
  modusa/models/tds.py,sha256=FAGfibjyyE_lkEuQp-vSCuqQnopOjmy_IXqUjRlg9kc,11677
41
42
  modusa/plugins/__init__.py,sha256=r1Bf5mnrVKRIwxboutY1iGzDy4EPQhqpk1kSW7iJj_Q,54
42
43
  modusa/plugins/base.py,sha256=Bh_1Bja7fOymFsCgwhXDbV6ys3D8muNrPwrfDrG_G_A,2382
43
- modusa/tools/__init__.py,sha256=ixqujVIJwU5TNX2j64lVajGtZlXTspcxN-30X54GgcM,243
44
+ modusa/tools/__init__.py,sha256=6EO3h7wMRW49ajo27BJprANPQMrOGWZudwYYRNKVISU,278
44
45
  modusa/tools/ann_loader.py,sha256=RePlwD4qG6gGrD4mOJ3RDR9q_gUscCY90_R9lgFU9lM,780
45
46
  modusa/tools/audio_converter.py,sha256=415qBoPm2sBIuBSI7m1XBKm0AbmVmPydIPPr-uO8D3c,1778
46
47
  modusa/tools/audio_loader.py,sha256=DrCzq0pdiQrUDIG-deLJGcu8EaylO5yRtwT4lr8WSf8,2166
47
48
  modusa/tools/audio_player.py,sha256=GP04TWW4jBwQBjANkfR_cJtEy7cIhvbu8RTwnf9hD6E,2817
49
+ modusa/tools/audio_recorder.py,sha256=d2fVt0Sd2tlBdb2WlUs60K4N23zuxM3KUpQqX0ifPi8,2769
48
50
  modusa/tools/base.py,sha256=C0ESJ0mIfjjRlAkRbSetNtMoOfS6IrHBjexRp3l_Mh4,1293
49
51
  modusa/tools/math_ops.py,sha256=ZZ7U4DgqT7cOeE7_Lzi_Qq-48WYfwR9_osbZwTmE9eg,8690
50
- modusa/tools/plotter.py,sha256=Sw_iy8OOZ2A_RQFnznQkRQMqgE_ucg6VlOWwMpi4QIY,17673
52
+ modusa/tools/plotter.py,sha256=e1NKFTFm8tG7yd-9VLmxmI9h9mTcUl1vaK1XDMvyoOw,18917
51
53
  modusa/tools/youtube_downloader.py,sha256=hB_X8-7nOHXOlxg6vv3wyhBLoAsWyomrULP6_uCQL7s,1698
52
54
  modusa/utils/.DS_Store,sha256=nLXMwF7QJNuglLI_Gk74F7vl5Dyus2Wd74Mgowijmdo,6148
53
55
  modusa/utils/__init__.py,sha256=1oLL20yLB1GL9IbFiZD8OReDqiCpFr-yetIR6x1cNkI,23
@@ -56,4 +58,4 @@ modusa/utils/excp.py,sha256=L9vhaGjKpv9viJYdmC9n5ndmk2GVbUBuFyZyhAQZmWY,906
56
58
  modusa/utils/logger.py,sha256=K0rsnObeNKCxlNeSnVnJeRhgfmob6riB2uyU7h3dDmA,571
57
59
  modusa/utils/np_func_cat.py,sha256=TyIFgRc6bARRMDnZxlVURO5Z0I-GWhxRONYyIv-Vwxs,1007
58
60
  modusa/utils/plot.py,sha256=s_vNdxvKfwxEngvJPgrF1PcmxZNnNaaXPViHWjyjJ-c,5335
59
- modusa-0.3.31.dist-info/RECORD,,
61
+ modusa-0.3.42.dist-info/RECORD,,