pyboy 2.5.3__tar.gz → 2.6.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.
- pyboy-2.6.0/PKG-INFO +228 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/lcd.py +21 -19
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/mb.pxd +3 -1
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/mb.py +31 -9
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/ram.pxd +2 -1
- pyboy-2.6.0/pyboy/core/serial.pxd +29 -0
- pyboy-2.6.0/pyboy/core/serial.py +78 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/sound.pxd +2 -2
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/sound.py +24 -16
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/timer.pxd +1 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/timer.py +3 -1
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/rewind.pxd +2 -2
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/rewind.py +14 -6
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/window_sdl2.py +1 -1
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/utils.pxd +7 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/utils.py +17 -1
- pyboy-2.6.0/pyboy.egg-info/PKG-INFO +228 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy.egg-info/SOURCES.txt +2 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyproject.toml +3 -4
- {pyboy-2.5.3 → pyboy-2.6.0}/setup.py +7 -2
- pyboy-2.5.3/PKG-INFO +0 -392
- pyboy-2.5.3/pyboy.egg-info/PKG-INFO +0 -392
- {pyboy-2.5.3 → pyboy-2.6.0}/LICENSE.md +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/MANIFEST.in +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/README.md +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/__init__.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/__main__.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/api/__init__.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/api/constants.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/api/gameshark.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/api/gameshark.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/api/memory_scanner.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/api/memory_scanner.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/api/screen.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/api/screen.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/api/sound.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/api/sound.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/api/sprite.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/api/sprite.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/api/tile.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/api/tile.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/api/tilemap.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/api/tilemap.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/conftest.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/__init__.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/bootrom.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/bootrom.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/bootrom_cgb.bin +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/bootrom_dmg.bin +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/cartridge/__init__.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/cartridge/base_mbc.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/cartridge/base_mbc.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/cartridge/cartridge.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/cartridge/cartridge.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/cartridge/mbc1.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/cartridge/mbc1.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/cartridge/mbc2.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/cartridge/mbc2.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/cartridge/mbc3.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/cartridge/mbc3.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/cartridge/mbc5.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/cartridge/mbc5.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/cartridge/rtc.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/cartridge/rtc.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/cpu.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/cpu.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/interaction.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/interaction.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/lcd.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/opcodes.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/opcodes.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/opcodes_gen.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/core/ram.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/default_rom.gb +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/logging/__init__.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/logging/_logging.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/logging/logging.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/logging/logging.pyx +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/__init__.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/auto_pause.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/auto_pause.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/base_plugin.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/base_plugin.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/debug.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/debug.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/debug_prompt.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/debug_prompt.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/font.txt +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/game_wrapper_kirby_dream_land.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/game_wrapper_kirby_dream_land.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/game_wrapper_pokemon_gen1.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/game_wrapper_pokemon_gen1.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/game_wrapper_pokemon_pinball.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/game_wrapper_pokemon_pinball.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/game_wrapper_super_mario_land.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/game_wrapper_super_mario_land.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/game_wrapper_tetris.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/game_wrapper_tetris.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/manager.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/manager.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/manager_gen.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/record_replay.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/record_replay.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/screen_recorder.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/screen_recorder.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/screenshot_recorder.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/screenshot_recorder.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/window_null.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/window_null.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/window_open_gl.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/window_open_gl.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/plugins/window_sdl2.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/pyboy.pxd +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy/pyboy.py +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy.egg-info/dependency_links.txt +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy.egg-info/entry_points.txt +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy.egg-info/not-zip-safe +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy.egg-info/requires.txt +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/pyboy.egg-info/top_level.txt +0 -0
- {pyboy-2.5.3 → pyboy-2.6.0}/setup.cfg +0 -0
pyboy-2.6.0/PKG-INFO
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyboy
|
|
3
|
+
Version: 2.6.0
|
|
4
|
+
Summary: Game Boy emulator written in Python
|
|
5
|
+
Author-email: Mads Ynddal <mads@ynddal.dk>
|
|
6
|
+
License-Expression: LGPL-3.0-only
|
|
7
|
+
Project-URL: Homepage, https://github.com/Baekalfen/PyBoy
|
|
8
|
+
Project-URL: Documentation, https://docs.pyboy.dk/index.html
|
|
9
|
+
Project-URL: Repository, https://github.com/Baekalfen/PyBoy.git
|
|
10
|
+
Keywords: gameboy,game boy,emulator,cython,pypy
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Cython
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
15
|
+
Classifier: Topic :: System :: Emulators
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE.md
|
|
19
|
+
Requires-Dist: numpy
|
|
20
|
+
Requires-Dist: pysdl2
|
|
21
|
+
Requires-Dist: pysdl2-dll
|
|
22
|
+
Provides-Extra: all
|
|
23
|
+
Requires-Dist: pyopengl; extra == "all"
|
|
24
|
+
Requires-Dist: markdown; extra == "all"
|
|
25
|
+
Requires-Dist: pdoc3; extra == "all"
|
|
26
|
+
Requires-Dist: gym; extra == "all"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
<p align="center">
|
|
30
|
+
<img src="extras/README/pyboy.svg" width="480">
|
|
31
|
+
</p>
|
|
32
|
+
|
|
33
|
+
__If you have any questions, or just want to chat, [join us on Discord](https://discord.gg/wUbag3KNqQ).__
|
|
34
|
+
|
|
35
|
+
[](https://discord.gg/wUbag3KNqQ)
|
|
36
|
+
|
|
37
|
+
<!---
|
|
38
|
+
Generate GIF with the layout and captions
|
|
39
|
+
-->
|
|
40
|
+
<table>
|
|
41
|
+
<tbody>
|
|
42
|
+
<tr>
|
|
43
|
+
<td colspan=2 align="center"><a href=https://github.com/PWhiddy/PokemonRedExperiments>Train RL agents to play Pokemon Red</a>
|
|
44
|
+
</td>
|
|
45
|
+
<td align="center"><a href=https://github.com/Baekalfen/PyBoy/wiki/Experimental-and-optional-features#rewind-time>Rewind any game</a><br>
|
|
46
|
+
</td>
|
|
47
|
+
</tr>
|
|
48
|
+
<tr>
|
|
49
|
+
<td colspan=2 align="center">
|
|
50
|
+
<div align="center">
|
|
51
|
+
<a href="http://www.youtube.com/watch?feature=player_embedded&v=DcYLT37ImBY" target="_blank">
|
|
52
|
+
<img src="http://img.youtube.com/vi/DcYLT37ImBY/maxresdefault.jpg" alt="Watch the video" width="500" border="10" />
|
|
53
|
+
<!-- <img src="http://img.youtube.com/vi/DcYLT37ImBY/hqdefault.jpg" alt="Watch the video" width="500" border="10" /> -->
|
|
54
|
+
</a>
|
|
55
|
+
<!-- https://www.youtube.com/watch?v=DcYLT37ImBY -->
|
|
56
|
+
<!-- <img src="extras/README/8.gif" width="400"><br> -->
|
|
57
|
+
</div>
|
|
58
|
+
</td>
|
|
59
|
+
<td align="center">
|
|
60
|
+
<img src="extras/README/5.gif" width="250">
|
|
61
|
+
</td>
|
|
62
|
+
</tr>
|
|
63
|
+
<tr>
|
|
64
|
+
<td align="center">Play the classics<br>
|
|
65
|
+
</td>
|
|
66
|
+
<td align="center"><a href=https://github.com/lixado/PyBoy-RL>Create your own AI</a><br>
|
|
67
|
+
</td>
|
|
68
|
+
<td align="center"><a href=https://github.com/uiucanh/tetris>Beat world records with AI</a><br>
|
|
69
|
+
</td>
|
|
70
|
+
</tr>
|
|
71
|
+
<tr>
|
|
72
|
+
<td align="center">
|
|
73
|
+
<img src="extras/README/1.gif" width="250">
|
|
74
|
+
</td>
|
|
75
|
+
<td align="center">
|
|
76
|
+
<img src="extras/README/6.gif" width="250">
|
|
77
|
+
</td>
|
|
78
|
+
<td align="center">
|
|
79
|
+
<img src="extras/README/7.gif" width="250">
|
|
80
|
+
</td>
|
|
81
|
+
</tr>
|
|
82
|
+
</tbody>
|
|
83
|
+
</table>
|
|
84
|
+
|
|
85
|
+
Getting Started
|
|
86
|
+
===============
|
|
87
|
+
The instructions are simple:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
$ pip install pyboy
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
For details, see [installation instructions](https://github.com/Baekalfen/PyBoy/wiki/Installation).
|
|
94
|
+
|
|
95
|
+
Now you're ready! Either use PyBoy directly from the terminal
|
|
96
|
+
```sh
|
|
97
|
+
$ pyboy game_rom.gb
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Or use it in your Python scripts:
|
|
101
|
+
```python
|
|
102
|
+
from pyboy import PyBoy
|
|
103
|
+
pyboy = PyBoy('game_rom.gb')
|
|
104
|
+
while pyboy.tick():
|
|
105
|
+
pass
|
|
106
|
+
pyboy.stop()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
<!-- Or using the context manager:
|
|
110
|
+
```python
|
|
111
|
+
from pyboy import PyBoy
|
|
112
|
+
with PyBoy('game_rom.gb') as pyboy:
|
|
113
|
+
while pyboy.tick():
|
|
114
|
+
pass
|
|
115
|
+
``` -->
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
The API
|
|
119
|
+
=======
|
|
120
|
+
|
|
121
|
+
If you are looking to make a bot or AI, then these resources are a good place to start:
|
|
122
|
+
* [PyBoy API Documentation](https://baekalfen.github.io/PyBoy/index.html)
|
|
123
|
+
* [Wiki Pages](https://github.com/Baekalfen/PyBoy/wiki/)
|
|
124
|
+
* [Using PyBoy with Gym](https://github.com/Baekalfen/PyBoy/wiki/Using-PyBoy-with-Gym)
|
|
125
|
+
* [Example: Kirby](https://github.com/Baekalfen/PyBoy/wiki/Example-Kirby)
|
|
126
|
+
* [Example: Tetris](https://github.com/Baekalfen/PyBoy/wiki/Example-Tetris)
|
|
127
|
+
* [Example: Super Mario Land](https://github.com/Baekalfen/PyBoy/wiki/Example-Super-Mario-Land)
|
|
128
|
+
* [Code Examples](https://github.com/Baekalfen/PyBoy/tree/master/extras/examples)
|
|
129
|
+
* [Discord](https://discord.gg/wUbag3KNqQ)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
When the emulator is running, you can easily access [PyBoy's API](https://baekalfen.github.io/PyBoy/index.html):
|
|
133
|
+
```python
|
|
134
|
+
pyboy.set_emulation_speed(0) # No speed limit
|
|
135
|
+
pyboy.button('down')
|
|
136
|
+
pyboy.button('a')
|
|
137
|
+
pyboy.tick() # Process at least one frame to let the game register the input
|
|
138
|
+
value_of_interest = pyboy.memory[0xC345]
|
|
139
|
+
|
|
140
|
+
pil_image = pyboy.screen.image
|
|
141
|
+
pil_image.save('screenshot.png')
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The [Wiki](https://github.com/Baekalfen/PyBoy/wiki) shows how to interface with PyBoy from your own project.
|
|
145
|
+
|
|
146
|
+
Performance
|
|
147
|
+
===========
|
|
148
|
+
|
|
149
|
+
Performance is a priority for PyBoy, to make your AI training and scripts as fast as possible.
|
|
150
|
+
|
|
151
|
+
The easiest way to improve your performance, is to skip rendering of unnecessary frames. If you know your
|
|
152
|
+
character takes X frames to move, or the game doesn't take input every frame, you can skip those to potentially triple
|
|
153
|
+
your performance. All game logic etc. will still process.
|
|
154
|
+
|
|
155
|
+
Here is a simple comparison of rendering every frame, rendering every 15th frame, and not rendering any frames (higher is better). See [`pyboy.tick`](https://docs.pyboy.dk/#pyboy.PyBoy.tick) for how it works. Your performance will depend on the game.
|
|
156
|
+
|
|
157
|
+
<table>
|
|
158
|
+
<tbody>
|
|
159
|
+
<tr>
|
|
160
|
+
<td align="center">
|
|
161
|
+
Full rendering
|
|
162
|
+
</td>
|
|
163
|
+
<td align="center">
|
|
164
|
+
Frame-skip 15
|
|
165
|
+
</td>
|
|
166
|
+
<td align="center">
|
|
167
|
+
No rendering
|
|
168
|
+
</td>
|
|
169
|
+
</tr>
|
|
170
|
+
<tr>
|
|
171
|
+
<td>
|
|
172
|
+
x124 realtime
|
|
173
|
+
</td>
|
|
174
|
+
<td>
|
|
175
|
+
x344 realtime
|
|
176
|
+
</td>
|
|
177
|
+
<td>
|
|
178
|
+
x395 realtime
|
|
179
|
+
</td>
|
|
180
|
+
</tr>
|
|
181
|
+
<tr> <!-- Markdown/HTML broken after this -->
|
|
182
|
+
<td>
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
for _ in range(target):
|
|
186
|
+
pyboy.tick()
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
</td>
|
|
190
|
+
<td >
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
for _ in range(target//15):
|
|
194
|
+
pyboy.tick(15)
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
</td>
|
|
198
|
+
<td >
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
pyboy.tick(target, False)
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
</td>
|
|
205
|
+
</tr>
|
|
206
|
+
</tbody>
|
|
207
|
+
</table>
|
|
208
|
+
|
|
209
|
+
The Game Boy was originally running at 60 frames per second, so a speed-up of 100x realtime is 6,000 frames per
|
|
210
|
+
second. And trivially from the table above, simulating 395 hours of gameplay can be done in 1 hour with no rendering.
|
|
211
|
+
|
|
212
|
+
It's also recommended to be running multiple instances of PyBoy in parallel. On an 8-core machine, you could potentially
|
|
213
|
+
do 3160 hours of gameplay in 1 hour.
|
|
214
|
+
|
|
215
|
+
Contributing
|
|
216
|
+
============
|
|
217
|
+
Any contribution is appreciated. The currently known problems are tracked in [the Issues tab](https://github.com/Baekalfen/PyBoy/issues). Feel free to take a swing at any one of them. If you have something original in mind, come and [discuss it on on Discord](https://discord.gg/wUbag3KNqQ).
|
|
218
|
+
|
|
219
|
+
[](https://discord.gg/wUbag3KNqQ)
|
|
220
|
+
|
|
221
|
+
For the more major features, there are the following that you can give a try. They are also described in more detail in the [project list in the Wiki](https://github.com/Baekalfen/PyBoy/wiki/Student-Projects):
|
|
222
|
+
* Hacking games
|
|
223
|
+
* Link Cable
|
|
224
|
+
* Debugger (VSCode, GDB, terminal or otherwise)
|
|
225
|
+
* AI - [use the `api`](https://baekalfen.github.io/PyBoy/index.html) or game wrappers to train a neural network
|
|
226
|
+
* Game Wrappers - make wrappers for popular games
|
|
227
|
+
|
|
228
|
+
If you want to implement something which is not on the list, feel free to do so anyway. If you want to merge it into our repo, then just send a pull request and we will have a look at it.
|
|
@@ -42,12 +42,12 @@ class LCD:
|
|
|
42
42
|
|
|
43
43
|
self.speed_shift = 0
|
|
44
44
|
self.clock = 0
|
|
45
|
-
self.clock_target = FRAME_CYCLES
|
|
45
|
+
self.clock_target = FRAME_CYCLES
|
|
46
46
|
self.frame_done = False
|
|
47
47
|
self.first_frame = False
|
|
48
48
|
self.reset = False
|
|
49
49
|
self._cycles_to_interrupt = 0
|
|
50
|
-
self._cycles_to_frame = (FRAME_CYCLES
|
|
50
|
+
self._cycles_to_frame = (FRAME_CYCLES - self.clock) << self.speed_shift
|
|
51
51
|
self.next_stat_mode = 2
|
|
52
52
|
self.LY = 0x00
|
|
53
53
|
self._STAT.set_mode(0)
|
|
@@ -63,7 +63,7 @@ class LCD:
|
|
|
63
63
|
self._scanlineparameters = [[0, 0, 0, 0, 0] for _ in range(ROWS)]
|
|
64
64
|
self.last_cycles = 0
|
|
65
65
|
self._cycles_to_interrupt = 0
|
|
66
|
-
self._cycles_to_frame = (FRAME_CYCLES
|
|
66
|
+
self._cycles_to_frame = (FRAME_CYCLES - self.clock) << self.speed_shift
|
|
67
67
|
|
|
68
68
|
if self.cgb:
|
|
69
69
|
# Setting for both modes, even though CGB is ignoring them. BGP[0] used in scanline_blank.
|
|
@@ -115,9 +115,9 @@ class LCD:
|
|
|
115
115
|
self.reset = True
|
|
116
116
|
|
|
117
117
|
def cycles_to_mode0(self):
|
|
118
|
-
mode2 = 80
|
|
119
|
-
mode3 = 170
|
|
120
|
-
mode1 = 456
|
|
118
|
+
mode2 = 80
|
|
119
|
+
mode3 = 170
|
|
120
|
+
mode1 = 456
|
|
121
121
|
|
|
122
122
|
mode = self._STAT._mode
|
|
123
123
|
# Remaining cycles for this already active mode
|
|
@@ -144,7 +144,7 @@ class LCD:
|
|
|
144
144
|
self.last_cycles = _cycles
|
|
145
145
|
|
|
146
146
|
interrupt_flag = 0
|
|
147
|
-
self.clock += cycles
|
|
147
|
+
self.clock += cycles >> self.speed_shift
|
|
148
148
|
|
|
149
149
|
if self.clock >= self.clock_target:
|
|
150
150
|
if self._LCDC.lcd_enable and (self.LY == 153 or self.reset):
|
|
@@ -159,7 +159,7 @@ class LCD:
|
|
|
159
159
|
|
|
160
160
|
# Reset to new frame and start from mode 2
|
|
161
161
|
self.LY = 0
|
|
162
|
-
self.clock %= FRAME_CYCLES
|
|
162
|
+
self.clock %= FRAME_CYCLES
|
|
163
163
|
self.clock_target = 0
|
|
164
164
|
self.next_stat_mode = 2
|
|
165
165
|
|
|
@@ -167,7 +167,7 @@ class LCD:
|
|
|
167
167
|
interrupt_flag |= self._STAT.set_mode(self.next_stat_mode)
|
|
168
168
|
|
|
169
169
|
# self._STAT._mode == 2: # Searching OAM
|
|
170
|
-
self.clock_target += 80
|
|
170
|
+
self.clock_target += 80
|
|
171
171
|
self.next_stat_mode = 3
|
|
172
172
|
interrupt_flag |= self._STAT.update_LYC(self.LYC, self.LY)
|
|
173
173
|
|
|
@@ -185,14 +185,14 @@ class LCD:
|
|
|
185
185
|
# LCD state machine
|
|
186
186
|
if self._STAT._mode == 2: # Searching OAM
|
|
187
187
|
self.LY += 1
|
|
188
|
-
self.clock_target += 80
|
|
188
|
+
self.clock_target += 80
|
|
189
189
|
self.next_stat_mode = 3
|
|
190
190
|
interrupt_flag |= self._STAT.update_LYC(self.LYC, self.LY)
|
|
191
191
|
elif self._STAT._mode == 3:
|
|
192
|
-
self.clock_target += 170
|
|
192
|
+
self.clock_target += 170
|
|
193
193
|
self.next_stat_mode = 0
|
|
194
194
|
elif self._STAT._mode == 0: # HBLANK
|
|
195
|
-
self.clock_target += 206
|
|
195
|
+
self.clock_target += 206
|
|
196
196
|
|
|
197
197
|
# Recorded for API
|
|
198
198
|
bx, by = self.getviewport()
|
|
@@ -212,7 +212,7 @@ class LCD:
|
|
|
212
212
|
else:
|
|
213
213
|
self.next_stat_mode = 1
|
|
214
214
|
elif self._STAT._mode == 1: # VBLANK
|
|
215
|
-
self.clock_target += 456
|
|
215
|
+
self.clock_target += 456
|
|
216
216
|
self.next_stat_mode = 1
|
|
217
217
|
|
|
218
218
|
self.LY += 1
|
|
@@ -229,15 +229,16 @@ class LCD:
|
|
|
229
229
|
else:
|
|
230
230
|
# See also `self.set_lcdc`
|
|
231
231
|
self.frame_done = True
|
|
232
|
-
self.clock %= FRAME_CYCLES
|
|
233
|
-
self.clock_target = FRAME_CYCLES
|
|
232
|
+
self.clock %= FRAME_CYCLES
|
|
233
|
+
self.clock_target = FRAME_CYCLES
|
|
234
234
|
|
|
235
235
|
# Renderer
|
|
236
236
|
self.renderer.blank_screen(self)
|
|
237
237
|
|
|
238
|
-
|
|
238
|
+
# NOTE: speed_shift because they are using in externally in mb
|
|
239
|
+
self._cycles_to_interrupt = (self.clock_target - self.clock) << self.speed_shift
|
|
239
240
|
# TODO: STAT Cycles to interrupts
|
|
240
|
-
self._cycles_to_frame = (FRAME_CYCLES
|
|
241
|
+
self._cycles_to_frame = (FRAME_CYCLES - self.clock) << self.speed_shift
|
|
241
242
|
return interrupt_flag
|
|
242
243
|
|
|
243
244
|
def save_state(self, f):
|
|
@@ -337,8 +338,9 @@ class LCD:
|
|
|
337
338
|
self.last_cycles = f.read_64bit()
|
|
338
339
|
self.clock = f.read_64bit()
|
|
339
340
|
self.clock_target = f.read_64bit()
|
|
340
|
-
|
|
341
|
-
self.
|
|
341
|
+
# NOTE: speed_shift because they are using in externally in mb
|
|
342
|
+
self._cycles_to_interrupt = (self.clock_target - self.clock) << self.speed_shift
|
|
343
|
+
self._cycles_to_frame = (FRAME_CYCLES - self.clock) << self.speed_shift
|
|
342
344
|
self.next_stat_mode = f.read()
|
|
343
345
|
|
|
344
346
|
if self.cgb:
|
|
@@ -13,6 +13,7 @@ cimport pyboy.core.cpu
|
|
|
13
13
|
cimport pyboy.core.interaction
|
|
14
14
|
cimport pyboy.core.lcd
|
|
15
15
|
cimport pyboy.core.ram
|
|
16
|
+
cimport pyboy.core.serial
|
|
16
17
|
cimport pyboy.core.sound
|
|
17
18
|
cimport pyboy.core.timer
|
|
18
19
|
from pyboy.logging.logging cimport Logger
|
|
@@ -23,7 +24,7 @@ cdef Logger logger
|
|
|
23
24
|
|
|
24
25
|
cdef int64_t MAX_CYCLES
|
|
25
26
|
cdef uint16_t STAT, LY, LYC
|
|
26
|
-
cdef int INTR_TIMER, INTR_HIGHTOLOW
|
|
27
|
+
cdef int INTR_TIMER, INTR_SERIAL, INTR_HIGHTOLOW
|
|
27
28
|
cdef uint16_t OPCODE_BRK
|
|
28
29
|
cdef int STATE_VERSION
|
|
29
30
|
|
|
@@ -35,6 +36,7 @@ cdef class Motherboard:
|
|
|
35
36
|
cdef pyboy.core.lcd.LCD lcd
|
|
36
37
|
cdef pyboy.core.cpu.CPU cpu
|
|
37
38
|
cdef pyboy.core.timer.Timer timer
|
|
39
|
+
cdef pyboy.core.serial.Serial serial
|
|
38
40
|
cdef pyboy.core.sound.Sound sound
|
|
39
41
|
cdef pyboy.core.cartridge.base_mbc.BaseMBC cartridge
|
|
40
42
|
cdef bint bootrom_enabled
|
|
@@ -9,14 +9,15 @@ from pyboy.utils import (
|
|
|
9
9
|
PyBoyException,
|
|
10
10
|
PyBoyOutOfBoundsException,
|
|
11
11
|
INTR_TIMER,
|
|
12
|
+
INTR_SERIAL,
|
|
12
13
|
INTR_HIGHTOLOW,
|
|
13
14
|
OPCODE_BRK,
|
|
15
|
+
MAX_CYCLES,
|
|
14
16
|
)
|
|
15
17
|
|
|
16
|
-
from . import bootrom, cartridge, cpu, interaction, lcd, ram, sound, timer
|
|
18
|
+
from . import bootrom, cartridge, cpu, interaction, lcd, ram, serial, sound, timer
|
|
17
19
|
|
|
18
20
|
logger = pyboy.logging.get_logger(__name__)
|
|
19
|
-
MAX_CYCLES = 1 << 31
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
class Motherboard:
|
|
@@ -48,6 +49,7 @@ class Motherboard:
|
|
|
48
49
|
logger.debug("Cartridge type auto-detected to %s", ("CGB" if self.cartridge.cgb else "DMG"))
|
|
49
50
|
|
|
50
51
|
self.timer = timer.Timer()
|
|
52
|
+
self.serial = serial.Serial()
|
|
51
53
|
self.interaction = interaction.Interaction()
|
|
52
54
|
self.ram = ram.RAM(cgb, randomize=randomize)
|
|
53
55
|
self.cpu = cpu.CPU(self)
|
|
@@ -95,7 +97,9 @@ class Motherboard:
|
|
|
95
97
|
bit0 = self.key1 & 0b1
|
|
96
98
|
if bit0 == 1:
|
|
97
99
|
self.double_speed = not self.double_speed
|
|
100
|
+
self.lcd.tick(self.cpu.cycles)
|
|
98
101
|
self.lcd.speed_shift = 1 if self.double_speed else 0
|
|
102
|
+
self.sound.tick(self.cpu.cycles)
|
|
99
103
|
self.sound.speed_shift = 1 if self.double_speed else 0
|
|
100
104
|
logger.debug("CGB double speed is now: %d", self.double_speed)
|
|
101
105
|
self.key1 ^= 0b10000001
|
|
@@ -244,6 +248,7 @@ class Motherboard:
|
|
|
244
248
|
self.timer.save_state(f)
|
|
245
249
|
self.cartridge.save_state(f)
|
|
246
250
|
self.interaction.save_state(f)
|
|
251
|
+
self.serial.save_state(f)
|
|
247
252
|
f.flush()
|
|
248
253
|
logger.debug("State saved.")
|
|
249
254
|
|
|
@@ -289,6 +294,8 @@ class Motherboard:
|
|
|
289
294
|
self.timer.load_state(f, state_version)
|
|
290
295
|
self.cartridge.load_state(f, state_version)
|
|
291
296
|
self.interaction.load_state(f, state_version)
|
|
297
|
+
if state_version >= 15:
|
|
298
|
+
self.serial.load_state(f, state_version)
|
|
292
299
|
f.flush()
|
|
293
300
|
logger.debug("State loaded.")
|
|
294
301
|
|
|
@@ -321,7 +328,7 @@ class Motherboard:
|
|
|
321
328
|
self.lcd._cycles_to_interrupt, # TODO: Be more agreesive. Only if actual interrupt enabled.
|
|
322
329
|
self.lcd._cycles_to_frame,
|
|
323
330
|
self.sound._cycles_to_interrupt,
|
|
324
|
-
|
|
331
|
+
self.serial._cycles_to_interrupt,
|
|
325
332
|
mode0_cycles,
|
|
326
333
|
),
|
|
327
334
|
)
|
|
@@ -334,6 +341,9 @@ class Motherboard:
|
|
|
334
341
|
|
|
335
342
|
self.sound.tick(self.cpu.cycles)
|
|
336
343
|
|
|
344
|
+
if self.serial.tick(self.cpu.cycles):
|
|
345
|
+
self.cpu.set_interruptflag(INTR_SERIAL)
|
|
346
|
+
|
|
337
347
|
if self.timer.tick(self.cpu.cycles):
|
|
338
348
|
self.cpu.set_interruptflag(INTR_TIMER)
|
|
339
349
|
|
|
@@ -380,7 +390,14 @@ class Motherboard:
|
|
|
380
390
|
elif 0xFEA0 <= i < 0xFF00: # Empty but unusable for I/O
|
|
381
391
|
return self.ram.non_io_internal_ram0[i - 0xFEA0]
|
|
382
392
|
elif 0xFF00 <= i < 0xFF4C: # I/O ports
|
|
383
|
-
if
|
|
393
|
+
if 0xFF01 <= i <= 0xFF02:
|
|
394
|
+
if self.serial.tick(self.cpu.cycles):
|
|
395
|
+
self.cpu.set_interruptflag(INTR_SERIAL)
|
|
396
|
+
if i == 0xFF01:
|
|
397
|
+
return self.serial.SB
|
|
398
|
+
elif i == 0xFF02:
|
|
399
|
+
return self.serial.SC
|
|
400
|
+
elif 0xFF04 <= i <= 0xFF07:
|
|
384
401
|
if self.timer.tick(self.cpu.cycles):
|
|
385
402
|
self.cpu.set_interruptflag(INTR_TIMER)
|
|
386
403
|
|
|
@@ -510,11 +527,16 @@ class Motherboard:
|
|
|
510
527
|
elif 0xFF00 <= i < 0xFF4C: # I/O ports
|
|
511
528
|
if i == 0xFF00:
|
|
512
529
|
self.ram.io_ports[i - 0xFF00] = self.interaction.pull(value)
|
|
513
|
-
elif i
|
|
514
|
-
self.
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
530
|
+
elif 0xFF01 <= i <= 0xFF02:
|
|
531
|
+
if self.serial.tick(self.cpu.cycles):
|
|
532
|
+
self.cpu.set_interruptflag(INTR_SERIAL)
|
|
533
|
+
if i == 0xFF01:
|
|
534
|
+
self.serialbuffer[self.serialbuffer_count] = value
|
|
535
|
+
self.serialbuffer_count += 1
|
|
536
|
+
self.serialbuffer_count &= 0x3FF
|
|
537
|
+
self.serial.set_SB(value)
|
|
538
|
+
elif i == 0xFF02:
|
|
539
|
+
self.serial.set_SC(value)
|
|
518
540
|
elif 0xFF04 <= i <= 0xFF07:
|
|
519
541
|
if self.timer.tick(self.cpu.cycles):
|
|
520
542
|
self.cpu.set_interruptflag(INTR_TIMER)
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
#
|
|
5
5
|
|
|
6
6
|
cimport cython
|
|
7
|
-
from libc.stdint cimport uint8_t
|
|
7
|
+
from libc.stdint cimport uint8_t, uint16_t
|
|
8
8
|
|
|
9
9
|
from pyboy.logging.logging cimport Logger
|
|
10
10
|
from pyboy.utils cimport IntIOInterface
|
|
11
11
|
|
|
12
|
+
cdef uint16_t INTERNAL_RAM0, INTERNAL_RAM0_CGB, NON_IO_INTERNAL_RAM0, IO_PORTS, NON_IO_INTERNAL_RAM1, INTERNAL_RAM1
|
|
12
13
|
|
|
13
14
|
cdef Logger logger
|
|
14
15
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#
|
|
2
|
+
# License: See LICENSE.md file
|
|
3
|
+
# GitHub: https://github.com/Baekalfen/PyBoy
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
from libc.stdint cimport int64_t, uint8_t, uint16_t, uint32_t, uint64_t
|
|
7
|
+
|
|
8
|
+
from pyboy.utils cimport IntIOInterface
|
|
9
|
+
|
|
10
|
+
import cython
|
|
11
|
+
|
|
12
|
+
from pyboy.logging.logging cimport Logger
|
|
13
|
+
|
|
14
|
+
cdef uint64_t MAX_CYCLES, CYCLES_8192HZ
|
|
15
|
+
cdef Logger logger
|
|
16
|
+
|
|
17
|
+
cdef class Serial:
|
|
18
|
+
cdef uint64_t SB, SC
|
|
19
|
+
cdef int64_t _cycles_to_interrupt
|
|
20
|
+
cdef uint64_t last_cycles, clock, clock_target
|
|
21
|
+
cdef bint transfer_enabled, double_speed, internal_clock
|
|
22
|
+
|
|
23
|
+
cdef bint tick(self, uint64_t) noexcept nogil
|
|
24
|
+
|
|
25
|
+
cdef void set_SB(self, uint8_t) noexcept nogil
|
|
26
|
+
cdef void set_SC(self, uint8_t) noexcept nogil
|
|
27
|
+
|
|
28
|
+
cdef int save_state(self, IntIOInterface) except -1
|
|
29
|
+
cdef int load_state(self, IntIOInterface, int) except -1
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#
|
|
2
|
+
# License: See LICENSE.md file
|
|
3
|
+
# GitHub: https://github.com/Baekalfen/PyBoy
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
from pyboy.utils import MAX_CYCLES
|
|
7
|
+
|
|
8
|
+
CYCLES_8192HZ = 128
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Serial:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.SB = 0xFF # Always 0xFF for a disconnected link cable
|
|
14
|
+
self.SC = 0
|
|
15
|
+
self.transfer_enabled = 0
|
|
16
|
+
self.internal_clock = 0
|
|
17
|
+
self._cycles_to_interrupt = 0
|
|
18
|
+
self.last_cycles = 0
|
|
19
|
+
self.clock = 0
|
|
20
|
+
self.clock_target = MAX_CYCLES
|
|
21
|
+
|
|
22
|
+
def set_SB(self, value):
|
|
23
|
+
# Always 0xFF when cable is disconnected. Connecting is not implemented yet.
|
|
24
|
+
self.SB = 0xFF
|
|
25
|
+
|
|
26
|
+
def set_SC(self, value): # cgb, double_speed
|
|
27
|
+
self.SC = value
|
|
28
|
+
self.transfer_enabled = self.SC & 0x80
|
|
29
|
+
# TODO:
|
|
30
|
+
# if cgb and (self.SC & 0b10): # High speed transfer
|
|
31
|
+
# self.double_speed = ...
|
|
32
|
+
self.internal_clock = self.SC & 1 # 0: external, 1: internal
|
|
33
|
+
if self.internal_clock:
|
|
34
|
+
self.clock_target = self.clock + 8 * CYCLES_8192HZ
|
|
35
|
+
else:
|
|
36
|
+
# Will never complete, as there is no connection
|
|
37
|
+
self.transfer_enabled = 0 # Technically it is enabled, but no reason to track it.
|
|
38
|
+
self.clock_target = MAX_CYCLES
|
|
39
|
+
self._cycles_to_interrupt = self.clock_target - self.clock
|
|
40
|
+
|
|
41
|
+
def tick(self, _cycles):
|
|
42
|
+
cycles = _cycles - self.last_cycles
|
|
43
|
+
if cycles == 0:
|
|
44
|
+
return False
|
|
45
|
+
self.last_cycles = _cycles
|
|
46
|
+
|
|
47
|
+
self.clock += cycles
|
|
48
|
+
|
|
49
|
+
interrupt = False
|
|
50
|
+
if self.transfer_enabled and self.clock >= self.clock_target:
|
|
51
|
+
self.SC &= 0x80
|
|
52
|
+
self.transfer_enabled = 0
|
|
53
|
+
# self._cycles_to_interrupt = MAX_CYCLES
|
|
54
|
+
self.clock_target = MAX_CYCLES
|
|
55
|
+
interrupt = True
|
|
56
|
+
|
|
57
|
+
self._cycles_to_interrupt = self.clock_target - self.clock
|
|
58
|
+
return interrupt
|
|
59
|
+
|
|
60
|
+
def save_state(self, f):
|
|
61
|
+
f.write(self.SB)
|
|
62
|
+
f.write(self.SC)
|
|
63
|
+
f.write(self.transfer_enabled)
|
|
64
|
+
f.write(self.internal_clock)
|
|
65
|
+
f.write_64bit(self.last_cycles)
|
|
66
|
+
f.write_64bit(self._cycles_to_interrupt)
|
|
67
|
+
f.write_64bit(self.clock)
|
|
68
|
+
f.write_64bit(self.clock_target)
|
|
69
|
+
|
|
70
|
+
def load_state(self, f, state_version):
|
|
71
|
+
self.SB = f.read()
|
|
72
|
+
self.SC = f.read()
|
|
73
|
+
self.transfer_enabled = f.read()
|
|
74
|
+
self.internal_clock = f.read()
|
|
75
|
+
self.last_cycles = f.read_64bit()
|
|
76
|
+
self._cycles_to_interrupt = f.read_64bit()
|
|
77
|
+
self.clock = f.read_64bit()
|
|
78
|
+
self.clock_target = f.read_64bit()
|
|
@@ -10,7 +10,7 @@ from pyboy.logging.logging cimport Logger
|
|
|
10
10
|
from pyboy.utils cimport IntIOInterface, double_to_uint64_ceil
|
|
11
11
|
|
|
12
12
|
cdef int FRAME_CYCLES
|
|
13
|
-
|
|
13
|
+
cdef uint64_t MAX_CYCLES
|
|
14
14
|
|
|
15
15
|
cdef Logger logger
|
|
16
16
|
|
|
@@ -59,7 +59,7 @@ cdef class Sound:
|
|
|
59
59
|
cdef uint8_t get(self, uint8_t) noexcept nogil
|
|
60
60
|
cdef void set(self, uint8_t, uint8_t) noexcept nogil
|
|
61
61
|
|
|
62
|
-
@cython.locals(cycles=uint64_t)
|
|
62
|
+
@cython.locals(cycles=uint64_t, _cycles=uint64_t)
|
|
63
63
|
cdef void tick(self, uint64_t) noexcept nogil
|
|
64
64
|
cdef void sample(self) noexcept nogil
|
|
65
65
|
cdef uint8_t pcm12(self) noexcept nogil
|