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.
- {modusa-0.3.41 → modusa-0.3.43}/PKG-INFO +3 -2
- {modusa-0.3.41 → modusa-0.3.43}/pyproject.toml +3 -2
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/__init__.py +2 -2
- modusa-0.3.43/src/modusa/tools/__init__.py +12 -0
- modusa-0.3.41/src/modusa/tools/plotter.py → modusa-0.3.43/src/modusa/tools/_plotter_old.py +20 -9
- modusa-0.3.43/src/modusa/tools/audio_recorder.py +115 -0
- modusa-0.3.43/src/modusa/tools/plotter.py +887 -0
- modusa-0.3.41/src/modusa/tools/__init__.py +0 -8
- {modusa-0.3.41 → modusa-0.3.43}/LICENSE.md +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/README.md +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/.DS_Store +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/config.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/decorators.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/generate_docs_source.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/generate_template.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/list_authors.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/list_plugins.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/main.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/templates/generator.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/templates/io.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/templates/model.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/templates/plugin.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/templates/test.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/devtools/templates/tool.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/fonts/NotoSansDevanagari-Regular.ttf +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/__init__.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/audio.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/audio_waveforms.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/base.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/ftds.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/s1d.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/s2d.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/s_ax.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/t_ax.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/generators/tds.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/__init__.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/__pycache__/signal1D.cpython-312.pyc.4443461152 +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/audio.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/base.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/data.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/ftds.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/s1d.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/s2d.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/s_ax.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/t_ax.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/models/tds.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/plugins/__init__.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/plugins/base.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/tools/ann_loader.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/tools/audio_converter.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/tools/audio_loader.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/tools/audio_player.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/tools/base.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/tools/math_ops.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/tools/youtube_downloader.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/utils/.DS_Store +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/utils/__init__.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/utils/config.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/utils/excp.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/utils/logger.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/utils/np_func_cat.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/src/modusa/utils/plot.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/__init__.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/data/song1.mp3 +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/data/song1.wav +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/audio_waveform.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/test_audio.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/test_ftds.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/test_s1d.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/test_s2d.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/test_s_ax.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/test_signal.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/test_signal_generator.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/test_t_ax.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_generators/test_tds.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_io/audio_player.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_io/plotter.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_models/test_data.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_models/test_t_ax.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_plugins/youtube_audio_loader.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/frequency_domain_signal.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/spectrogram.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/test_axis.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/test_feature_time_domain_signal.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/test_frequency_time_domain_signal.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/test_signal1D.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/test_signal2D.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/test_time_domain_signal.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/test_u_ax.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/test_window_signal.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_signals/time_domain_signal.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_tools/test_audio_converter.py +0 -0
- {modusa-0.3.41 → modusa-0.3.43}/tests/test_tools/test_fourier_tranform.py +0 -0
- {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. | 
| 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. | 
| 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. | 
| 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. | 
| 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  | 
| 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( | 
| 129 | 
            +
            		gs = gridspec.GridSpec(3, 1, height_ratios=[0.2, 0.2, 1])
         | 
| 130 130 |  | 
| 131 | 
            -
            		signal_ax = fig.add_subplot(gs[ | 
| 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 | 
            -
            				 | 
| 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= | 
| 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= | 
| 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=' | 
| 236 | 
            +
            						annotation_ax.axvline(x=xpos, color='red', linestyle='-', linewidth=1.5)
         | 
| 231 237 | 
             
            				else:
         | 
| 232 | 
            -
            					annotation_ax.axvline(x=xpos, color=' | 
| 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
         |