cicadc 0.1.0__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.
cicadc-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Carsten Wulff Software, Norway
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
cicadc-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: cicadc
3
+ Version: 0.1.0
4
+ Summary: Interactive PySide6 + manim demonstration of how an ADC works
5
+ Author-email: Carsten Wulff <carsten@wulff.no>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/wulffern/cicadc
8
+ Project-URL: Bug Tracker, https://github.com/wulffern/cicadc/issues
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Education
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Natural Language :: English
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Education
20
+ Classifier: Topic :: Scientific/Engineering
21
+ Classifier: Topic :: Scientific/Engineering :: Visualization
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: numpy
26
+ Requires-Dist: scipy
27
+ Requires-Dist: manim
28
+ Requires-Dist: PySide6
29
+ Requires-Dist: click
30
+ Dynamic: license-file
31
+
32
+ # cicadc
33
+
34
+ An interactive desktop program that demonstrates how an analog-to-digital
35
+ converter (ADC) works. It shows two side-by-side panels rendered with
36
+ [manim](https://www.manim.community/) inside a [PySide6](https://doc.qt.io/qtforpython-6/)
37
+ window:
38
+
39
+ - **Left panel (analog)** - the analog signal drawn as a path that scrolls past
40
+ "now" (the middle line), with the future at the top and the past at the
41
+ bottom. Optional noise is added. A blue car drives along the signal and turns
42
+ to follow the path; a translucent gray "shadow" car marks the quantized value.
43
+ - **Right panel (digital)** - the digital signal as a sample-and-hold staircase.
44
+ Gray is the unfiltered (raw) quantizer output and green is the optional
45
+ moving-average filtered output (normalised to unity gain at the signal
46
+ frequency). A green car follows the filtered output, trailing by the filter's
47
+ group delay so it lands back on the analog curve (delay-compensated).
48
+
49
+ ## Project layout
50
+
51
+ ```
52
+ src/cicadc/ package (src layout, mirrors cicwave)
53
+ cli.py click console entry point (`cicadc`)
54
+ app.py QApplication bootstrap
55
+ main_window.py PySide6 window + controls
56
+ manim_scene.py offscreen manim renderer
57
+ signal_source.py analog signal model (sinusoid + noise)
58
+ quantizer.py N-bit quantizer
59
+ render_widget.py Qt widget + animation loop
60
+ assets/car.png car sprite
61
+ tests/unittests/ unittest suite
62
+ ```
63
+
64
+ ## Requirements
65
+
66
+ - Python 3.9-3.13 (manim does not yet support 3.14). A `python3.12` venv is used
67
+ by the setup steps below.
68
+ - A working manim install (Cairo backend). On macOS you may need the system
69
+ libraries `pkg-config cairo pango` via Homebrew if the wheels do not cover your
70
+ platform.
71
+
72
+ ## Setup
73
+
74
+ ```bash
75
+ python3.12 -m venv .venv
76
+ source .venv/bin/activate
77
+ pip install -e . # or: pip install -r requirements.txt
78
+ ```
79
+
80
+ `make dev-install` runs the editable install for you.
81
+
82
+ ## Run
83
+
84
+ ```bash
85
+ cicadc # installed console script
86
+ # or, from a checkout without installing:
87
+ python main.py
88
+ ```
89
+
90
+ ## Controls
91
+
92
+ - **Frequency / Amplitude** - the input sinusoid (amplitude is a fraction of full scale).
93
+ - **Speed** - how fast the signal scrolls past "now".
94
+ - **Bits** - resolution of the quantizer.
95
+ - **Sample period** - the ADC sample clock interval.
96
+ - **Noise** - additive analog noise amplitude.
97
+ - **Avg** - moving-average filter length on the digital output (1 = off).
98
+ - **Play / Pause** - start or stop the animation.
99
+
100
+ ## Development
101
+
102
+ ```bash
103
+ make test # run unit tests
104
+ make check # import and print version
105
+ make lint # ruff (if installed)
106
+ make build # build wheel + sdist
107
+ ```
108
+
109
+ ## Acknowledgements
110
+
111
+ Big thanks to **Domen Visnar** for the idea behind this project!
112
+
113
+ ## Status
114
+
115
+ Single/− sinusoid input, adjustable bit depth, noise, and a normalised
116
+ moving-average filter with delay compensation are implemented. Random /
117
+ multi-sinusoid inputs and a configurable bandwidth filter are planned follow-ups.
cicadc-0.1.0/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # cicadc
2
+
3
+ An interactive desktop program that demonstrates how an analog-to-digital
4
+ converter (ADC) works. It shows two side-by-side panels rendered with
5
+ [manim](https://www.manim.community/) inside a [PySide6](https://doc.qt.io/qtforpython-6/)
6
+ window:
7
+
8
+ - **Left panel (analog)** - the analog signal drawn as a path that scrolls past
9
+ "now" (the middle line), with the future at the top and the past at the
10
+ bottom. Optional noise is added. A blue car drives along the signal and turns
11
+ to follow the path; a translucent gray "shadow" car marks the quantized value.
12
+ - **Right panel (digital)** - the digital signal as a sample-and-hold staircase.
13
+ Gray is the unfiltered (raw) quantizer output and green is the optional
14
+ moving-average filtered output (normalised to unity gain at the signal
15
+ frequency). A green car follows the filtered output, trailing by the filter's
16
+ group delay so it lands back on the analog curve (delay-compensated).
17
+
18
+ ## Project layout
19
+
20
+ ```
21
+ src/cicadc/ package (src layout, mirrors cicwave)
22
+ cli.py click console entry point (`cicadc`)
23
+ app.py QApplication bootstrap
24
+ main_window.py PySide6 window + controls
25
+ manim_scene.py offscreen manim renderer
26
+ signal_source.py analog signal model (sinusoid + noise)
27
+ quantizer.py N-bit quantizer
28
+ render_widget.py Qt widget + animation loop
29
+ assets/car.png car sprite
30
+ tests/unittests/ unittest suite
31
+ ```
32
+
33
+ ## Requirements
34
+
35
+ - Python 3.9-3.13 (manim does not yet support 3.14). A `python3.12` venv is used
36
+ by the setup steps below.
37
+ - A working manim install (Cairo backend). On macOS you may need the system
38
+ libraries `pkg-config cairo pango` via Homebrew if the wheels do not cover your
39
+ platform.
40
+
41
+ ## Setup
42
+
43
+ ```bash
44
+ python3.12 -m venv .venv
45
+ source .venv/bin/activate
46
+ pip install -e . # or: pip install -r requirements.txt
47
+ ```
48
+
49
+ `make dev-install` runs the editable install for you.
50
+
51
+ ## Run
52
+
53
+ ```bash
54
+ cicadc # installed console script
55
+ # or, from a checkout without installing:
56
+ python main.py
57
+ ```
58
+
59
+ ## Controls
60
+
61
+ - **Frequency / Amplitude** - the input sinusoid (amplitude is a fraction of full scale).
62
+ - **Speed** - how fast the signal scrolls past "now".
63
+ - **Bits** - resolution of the quantizer.
64
+ - **Sample period** - the ADC sample clock interval.
65
+ - **Noise** - additive analog noise amplitude.
66
+ - **Avg** - moving-average filter length on the digital output (1 = off).
67
+ - **Play / Pause** - start or stop the animation.
68
+
69
+ ## Development
70
+
71
+ ```bash
72
+ make test # run unit tests
73
+ make check # import and print version
74
+ make lint # ruff (if installed)
75
+ make build # build wheel + sdist
76
+ ```
77
+
78
+ ## Acknowledgements
79
+
80
+ Big thanks to **Domen Visnar** for the idea behind this project!
81
+
82
+ ## Status
83
+
84
+ Single/− sinusoid input, adjustable bit depth, noise, and a normalised
85
+ moving-average filter with delay compensation are implemented. Random /
86
+ multi-sinusoid inputs and a configurable bandwidth filter are planned follow-ups.
@@ -0,0 +1,55 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "cicadc"
7
+ version = "0.1.0"
8
+ authors = [
9
+ { name="Carsten Wulff", email="carsten@wulff.no" },
10
+ ]
11
+ dependencies = [
12
+ "numpy", # Core data arrays
13
+ "scipy", # Signal processing (filters)
14
+ "manim", # Animation / rendering engine
15
+ "PySide6", # Qt6 GUI framework
16
+ "click", # CLI parsing
17
+ ]
18
+ description = "Interactive PySide6 + manim demonstration of how an ADC works"
19
+ readme = "README.md"
20
+ license = {text = "MIT"}
21
+ requires-python = ">=3.9"
22
+ classifiers = [
23
+ "Development Status :: 3 - Alpha",
24
+ "Intended Audience :: Education",
25
+ "Intended Audience :: Science/Research",
26
+ "Natural Language :: English",
27
+ "Operating System :: OS Independent",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.9",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Topic :: Education",
34
+ "Topic :: Scientific/Engineering",
35
+ "Topic :: Scientific/Engineering :: Visualization",
36
+ ]
37
+
38
+ [project.scripts]
39
+ cicadc = "cicadc.cli:main"
40
+
41
+ [project.gui-scripts]
42
+ cicadcw = "cicadc.cli:main" # Windows no-console wrapper
43
+
44
+ [project.urls]
45
+ "Homepage" = "https://github.com/wulffern/cicadc"
46
+ "Bug Tracker" = "https://github.com/wulffern/cicadc/issues"
47
+
48
+ [tool.setuptools]
49
+ package-dir = {"" = "src"}
50
+
51
+ [tool.setuptools.packages.find]
52
+ where = ["src"]
53
+
54
+ [tool.setuptools.package-data]
55
+ cicadc = ["assets/*.png"]
cicadc-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,19 @@
1
+ """cicadc - an interactive PySide6 + manim demonstration of how an ADC works.
2
+
3
+ Two side-by-side panels visualise analog-to-digital conversion: a scrolling
4
+ analog signal (with optional noise) on the left and the resulting digital
5
+ signal on the right, with an optional moving-average filter, quantization
6
+ shadow, and little cars that drive along the signal paths.
7
+ """
8
+
9
+ __version__ = "0.1.0"
10
+ __author__ = "Carsten Wulff"
11
+ __email__ = "carsten@wulff.no"
12
+
13
+ __all__ = [
14
+ "SignalSource",
15
+ "Quantizer",
16
+ ]
17
+
18
+ from .signal_source import SignalSource
19
+ from .quantizer import Quantizer
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env python3
2
+ ######################################################################
3
+ ## Copyright (c) 2026 Carsten Wulff Software, Norway
4
+ ## ###################################################################
5
+ ## The MIT License (MIT)
6
+ ##
7
+ ## Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ ## of this software and associated documentation files (the "Software"), to deal
9
+ ## in the Software without restriction, including without limitation the rights
10
+ ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ ## copies of the Software, and to permit persons to whom the Software is
12
+ ## furnished to do so, subject to the following conditions:
13
+ ##
14
+ ## The above copyright notice and this permission notice shall be included in all
15
+ ## copies or substantial portions of the Software.
16
+ ######################################################################
17
+ """Qt application bootstrap for the cicadc GUI."""
18
+
19
+ from __future__ import annotations
20
+
21
+ import sys
22
+
23
+
24
+ def run() -> int:
25
+ """Create the QApplication, show the main window, and run the event loop."""
26
+ from PySide6.QtWidgets import QApplication
27
+
28
+ from .main_window import MainWindow
29
+
30
+ app = QApplication.instance() or QApplication(sys.argv)
31
+ window = MainWindow()
32
+ window.show()
33
+ return app.exec()
Binary file
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env python3
2
+ ######################################################################
3
+ ## Copyright (c) 2026 Carsten Wulff Software, Norway
4
+ ## ###################################################################
5
+ ## The MIT License (MIT)
6
+ ##
7
+ ## Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ ## of this software and associated documentation files (the "Software"), to deal
9
+ ## in the Software without restriction, including without limitation the rights
10
+ ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ ## copies of the Software, and to permit persons to whom the Software is
12
+ ## furnished to do so, subject to the following conditions:
13
+ ##
14
+ ## The above copyright notice and this permission notice shall be included in all
15
+ ## copies or substantial portions of the Software.
16
+ ######################################################################
17
+ """cicadc command-line entry point."""
18
+
19
+ from __future__ import annotations
20
+
21
+ import logging
22
+
23
+ import click
24
+
25
+ from .command import setup_logging
26
+
27
+
28
+ @click.command()
29
+ @click.option("--color/--no-color", default=True, help="Enable/disable color output")
30
+ @click.option("--debug", is_flag=True, default=False, help="Enable debug logging")
31
+ def main(color: bool, debug: bool) -> None:
32
+ """cicadc: an interactive demonstration of how an ADC works.
33
+
34
+ Launches a PySide6 + manim window with two side-by-side panels (analog and
35
+ digital), with controls for frequency, amplitude, bit depth, noise and a
36
+ moving-average filter.
37
+ """
38
+ setup_logging(color=color, level=logging.DEBUG if debug else logging.INFO)
39
+
40
+ from .app import run
41
+
42
+ raise SystemExit(run())
43
+
44
+
45
+ if __name__ == "__main__":
46
+ main()
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env python3
2
+ ######################################################################
3
+ ## Copyright (c) 2026 Carsten Wulff Software, Norway
4
+ ## ###################################################################
5
+ ## The MIT License (MIT)
6
+ ##
7
+ ## Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ ## of this software and associated documentation files (the "Software"), to deal
9
+ ## in the Software without restriction, including without limitation the rights
10
+ ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ ## copies of the Software, and to permit persons to whom the Software is
12
+ ## furnished to do so, subject to the following conditions:
13
+ ##
14
+ ## The above copyright notice and this permission notice shall be included in all
15
+ ## copies or substantial portions of the Software.
16
+ ######################################################################
17
+ """Shared helpers (logging setup) for the cicadc CLI."""
18
+
19
+ from __future__ import annotations
20
+
21
+ import logging
22
+
23
+
24
+ def setup_logging(color: bool = True, level: int = logging.INFO) -> None:
25
+ """Configure root logging for the application."""
26
+ fmt = "%(levelname)s: %(message)s"
27
+ logging.basicConfig(level=level, format=fmt)
@@ -0,0 +1,203 @@
1
+ """Main application window: the render view plus a panel of live controls."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from PySide6.QtCore import Qt
6
+ from PySide6.QtWidgets import (
7
+ QComboBox,
8
+ QDoubleSpinBox,
9
+ QFormLayout,
10
+ QGroupBox,
11
+ QHBoxLayout,
12
+ QLabel,
13
+ QPushButton,
14
+ QSlider,
15
+ QVBoxLayout,
16
+ QWidget,
17
+ )
18
+
19
+ from .manim_scene import AdcScene
20
+ from .quantizer import Quantizer
21
+ from .render_widget import RenderWidget
22
+ from .signal_source import SignalSource
23
+
24
+
25
+ class MainWindow(QWidget):
26
+ def __init__(self) -> None:
27
+ super().__init__()
28
+ self.setWindowTitle("ADC Intro - how an analog-to-digital converter works")
29
+
30
+ self.signal = SignalSource(frequency=0.6, amplitude=0.85, speed=1.0, window=4.0, sample_period=0.4)
31
+ self.quantizer = Quantizer(bits=3)
32
+ self.scene = AdcScene(signal=self.signal, quantizer=self.quantizer)
33
+ self.view = RenderWidget(self.scene, fps=24)
34
+
35
+ controls = self._build_controls()
36
+
37
+ layout = QHBoxLayout(self)
38
+ layout.addWidget(self.view, stretch=1)
39
+ layout.addWidget(controls, stretch=0)
40
+
41
+ self._apply_dark_theme()
42
+ self.resize(1280, 760)
43
+
44
+ # --------------------------------------------------------------- controls
45
+ def _build_controls(self) -> QWidget:
46
+ box = QGroupBox("Controls")
47
+ box.setMaximumWidth(280)
48
+ form = QFormLayout()
49
+
50
+ self.input_combo = QComboBox()
51
+ self.input_combo.addItem("Single sinusoid")
52
+ self.input_combo.setEnabled(False) # only shape available in the MVP
53
+ form.addRow("Input", self.input_combo)
54
+
55
+ self.freq_spin = QDoubleSpinBox()
56
+ self.freq_spin.setRange(0.05, 5.0)
57
+ self.freq_spin.setSingleStep(0.05)
58
+ self.freq_spin.setValue(self.signal.frequency)
59
+ self.freq_spin.setSuffix(" Hz")
60
+ self.freq_spin.valueChanged.connect(self._on_freq)
61
+ form.addRow("Frequency", self.freq_spin)
62
+
63
+ self.amp_spin = QDoubleSpinBox()
64
+ self.amp_spin.setRange(0.0, 1.2)
65
+ self.amp_spin.setSingleStep(0.05)
66
+ self.amp_spin.setValue(self.signal.amplitude)
67
+ self.amp_spin.valueChanged.connect(self._on_amp)
68
+ form.addRow("Amplitude (FS)", self.amp_spin)
69
+
70
+ self.speed_spin = QDoubleSpinBox()
71
+ self.speed_spin.setRange(0.1, 4.0)
72
+ self.speed_spin.setSingleStep(0.1)
73
+ self.speed_spin.setValue(self.signal.speed)
74
+ self.speed_spin.setSuffix(" x")
75
+ self.speed_spin.valueChanged.connect(self._on_speed)
76
+ form.addRow("Scroll speed", self.speed_spin)
77
+
78
+ self.bits_slider = QSlider(Qt.Orientation.Horizontal)
79
+ self.bits_slider.setRange(1, 8)
80
+ self.bits_slider.setValue(self.quantizer.bits)
81
+ self.bits_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
82
+ self.bits_slider.valueChanged.connect(self._on_bits)
83
+ self.bits_label = QLabel(self._bits_text())
84
+ form.addRow(self.bits_label, self.bits_slider)
85
+
86
+ self.sample_spin = QDoubleSpinBox()
87
+ self.sample_spin.setRange(0.05, 1.0)
88
+ self.sample_spin.setSingleStep(0.05)
89
+ self.sample_spin.setValue(self.signal.sample_period)
90
+ self.sample_spin.setSuffix(" s")
91
+ self.sample_spin.valueChanged.connect(self._on_sample)
92
+ form.addRow("Sample period", self.sample_spin)
93
+
94
+ self.noise_spin = QDoubleSpinBox()
95
+ self.noise_spin.setRange(0.0, 0.5)
96
+ self.noise_spin.setSingleStep(0.02)
97
+ self.noise_spin.setValue(self.signal.noise_amp)
98
+ self.noise_spin.valueChanged.connect(self._on_noise)
99
+ form.addRow("Noise (FS)", self.noise_spin)
100
+
101
+ self.filter_slider = QSlider(Qt.Orientation.Horizontal)
102
+ self.filter_slider.setRange(1, 16)
103
+ self.filter_slider.setValue(self.scene.filter_taps)
104
+ self.filter_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
105
+ self.filter_slider.valueChanged.connect(self._on_filter)
106
+ self.filter_label = QLabel(self._filter_text())
107
+ form.addRow(self.filter_label, self.filter_slider)
108
+
109
+ self.play_button = QPushButton("Play")
110
+ self.play_button.setCheckable(True)
111
+ self.play_button.toggled.connect(self._on_play)
112
+
113
+ outer = QVBoxLayout(box)
114
+ outer.addLayout(form)
115
+ outer.addWidget(self.play_button)
116
+
117
+ hint = QLabel(
118
+ "Green = analog signal\nBlue car = analog now\n"
119
+ "Gray = unfiltered digital\nGreen = filtered digital\n"
120
+ "Yellow dots = samples"
121
+ )
122
+ hint.setWordWrap(True)
123
+ hint.setStyleSheet("color:#8aa0c8; font-size:11px;")
124
+ outer.addWidget(hint)
125
+ outer.addStretch(1)
126
+
127
+ thanks = QLabel("Big thanks to Domen Visnar for the idea!")
128
+ thanks.setWordWrap(True)
129
+ thanks.setAlignment(Qt.AlignmentFlag.AlignCenter)
130
+ thanks.setStyleSheet("color:#f5c542; font-size:11px; font-style:italic;")
131
+ outer.addWidget(thanks)
132
+ return box
133
+
134
+ def _bits_text(self) -> str:
135
+ return f"Bits: {self.quantizer.bits}"
136
+
137
+ def _filter_text(self) -> str:
138
+ n = self.scene.filter_taps
139
+ return "Filter: off" if n <= 1 else f"Avg: {n}"
140
+
141
+ # ----------------------------------------------------------------- slots
142
+ def _refresh_if_paused(self) -> None:
143
+ if not self.view.is_running():
144
+ self.view.render_once()
145
+
146
+ def _on_freq(self, v: float) -> None:
147
+ self.signal.frequency = v
148
+ self._refresh_if_paused()
149
+
150
+ def _on_amp(self, v: float) -> None:
151
+ self.signal.amplitude = v
152
+ self._refresh_if_paused()
153
+
154
+ def _on_speed(self, v: float) -> None:
155
+ self.signal.speed = v
156
+
157
+ def _on_sample(self, v: float) -> None:
158
+ self.signal.sample_period = v
159
+ self._refresh_if_paused()
160
+
161
+ def _on_bits(self, v: int) -> None:
162
+ self.quantizer.bits = v
163
+ self.bits_label.setText(self._bits_text())
164
+ self._refresh_if_paused()
165
+
166
+ def _on_noise(self, v: float) -> None:
167
+ self.signal.noise_amp = v
168
+ self._refresh_if_paused()
169
+
170
+ def _on_filter(self, v: int) -> None:
171
+ self.scene.filter_taps = v
172
+ self.filter_label.setText(self._filter_text())
173
+ self._refresh_if_paused()
174
+
175
+ def _on_play(self, checked: bool) -> None:
176
+ if checked:
177
+ self.play_button.setText("Pause")
178
+ self.view.start()
179
+ else:
180
+ self.play_button.setText("Play")
181
+ self.view.stop()
182
+
183
+ # ----------------------------------------------------------------- theme
184
+ def _apply_dark_theme(self) -> None:
185
+ self.setStyleSheet(
186
+ """
187
+ QWidget { background-color: #0b0f1a; color: #e6f0ff; }
188
+ QGroupBox {
189
+ border: 1px solid #2b3a63; border-radius: 6px; margin-top: 14px;
190
+ font-weight: bold;
191
+ }
192
+ QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 4px; }
193
+ QPushButton {
194
+ background-color: #1a2440; border: 1px solid #2b3a63;
195
+ border-radius: 5px; padding: 8px; font-weight: bold;
196
+ }
197
+ QPushButton:checked { background-color: #1f7a3a; }
198
+ QDoubleSpinBox, QSpinBox, QComboBox {
199
+ background-color: #121a2e; border: 1px solid #2b3a63;
200
+ border-radius: 4px; padding: 3px;
201
+ }
202
+ """
203
+ )