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 +21 -0
- cicadc-0.1.0/PKG-INFO +117 -0
- cicadc-0.1.0/README.md +86 -0
- cicadc-0.1.0/pyproject.toml +55 -0
- cicadc-0.1.0/setup.cfg +4 -0
- cicadc-0.1.0/src/cicadc/__init__.py +19 -0
- cicadc-0.1.0/src/cicadc/app.py +33 -0
- cicadc-0.1.0/src/cicadc/assets/car.png +0 -0
- cicadc-0.1.0/src/cicadc/cli.py +46 -0
- cicadc-0.1.0/src/cicadc/command.py +27 -0
- cicadc-0.1.0/src/cicadc/main_window.py +203 -0
- cicadc-0.1.0/src/cicadc/manim_scene.py +443 -0
- cicadc-0.1.0/src/cicadc/quantizer.py +55 -0
- cicadc-0.1.0/src/cicadc/render_widget.py +86 -0
- cicadc-0.1.0/src/cicadc/signal_source.py +149 -0
- cicadc-0.1.0/src/cicadc.egg-info/PKG-INFO +117 -0
- cicadc-0.1.0/src/cicadc.egg-info/SOURCES.txt +19 -0
- cicadc-0.1.0/src/cicadc.egg-info/dependency_links.txt +1 -0
- cicadc-0.1.0/src/cicadc.egg-info/entry_points.txt +5 -0
- cicadc-0.1.0/src/cicadc.egg-info/requires.txt +5 -0
- cicadc-0.1.0/src/cicadc.egg-info/top_level.txt +1 -0
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,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
|
+
)
|