├── .gitignore ├── .readthedocs.yml ├── LICENSE.txt ├── README.md ├── apt.txt ├── docs ├── Makefile ├── _static │ └── audio │ │ ├── adsr.ogg │ │ ├── sawtooth.ogg │ │ ├── simple-fm-synth.ogg │ │ ├── tb303.5.ogg │ │ ├── violet.ogg │ │ └── wobbly.ogg ├── _templates │ └── layout.html ├── conf.py ├── images │ ├── Atari-2600-Wood-4Sw-Set-small.png │ ├── Commodore-64-Computer-FL-small.png │ ├── adsr.png │ ├── coordinate_systems_right_handed.png │ ├── fm-sawtooth.png │ ├── gate.png │ ├── hello-world.gif │ ├── lowpass.png │ ├── pong.step0.png │ ├── pong.step5.png │ ├── pong.x3.step0.png │ ├── pong.x3.step5.png │ ├── power-mode.png │ ├── resonance.png │ ├── sawtooth.png │ ├── ship1.png │ ├── skybox-layout-small.png │ ├── spaceship-3d.jpg │ ├── spaceship.gif │ ├── spaceship.jpg │ └── spaceship_3d.gif ├── index.rst ├── make.bat ├── programmers_reference_guide │ ├── api.rst │ ├── appendices.rst │ ├── getting_started.rst │ ├── graphics-3d.rst │ ├── graphics.rst │ ├── introduction.rst │ ├── rl.rst │ ├── sound.rst │ └── synthesis.rst └── requirements.txt ├── examples ├── 01-hello-world.ipynb ├── 02-hello-jupylet.ipynb ├── 11-spaceship.ipynb ├── 12-spaceship-3d.ipynb ├── 13-lego-3d.ipynb ├── 14-piano.ipynb ├── 15-sonic.ipynb ├── 16-shadertoy-demo.ipynb ├── 17-spectrum-analyzer.ipynb ├── 21-pong.ipynb ├── 22-pong-RL.ipynb ├── fonts │ ├── FreeLicense.txt │ └── PetMe64.ttf ├── images │ ├── alien.png │ ├── keyboard.png │ ├── moon.png │ ├── ship1.png │ ├── ship2.png │ ├── stars.png │ └── yellow-circle.png ├── lego_3d.py ├── piano.py ├── pong-start.state ├── pong.py ├── scenes │ ├── lego │ │ ├── lego.bin │ │ ├── lego.blend │ │ └── lego.gltf │ └── moon │ │ ├── TexturesCom_Leather_Plain_1K_albedo_blue.jpg │ │ ├── TexturesCom_Leather_Plain_1K_normal.jpg │ │ ├── TexturesCom_Leather_Plain_1K_roughness.png │ │ ├── alien-moon.bin │ │ ├── alien-moon.blend │ │ ├── alien-moon.gltf │ │ ├── eye.jpg │ │ ├── lroc_color_poles_4k.jpg │ │ ├── moon-normal-map.jpg │ │ └── nebula │ │ ├── nebulaBK.png │ │ ├── nebulaDN.png │ │ ├── nebulaFT.png │ │ ├── nebulaLF.png │ │ ├── nebulaRT.png │ │ └── nebulaUP.png ├── shadertoy_demo.py ├── sounds │ ├── VCSL │ │ ├── README.md │ │ └── Xylophone │ │ │ ├── Xylophone - Medium Mallets.sfz │ │ │ └── Xylophone │ │ │ └── Medium Mallets │ │ │ ├── Xylo_Medium_C4_ff_01_far.ogg │ │ │ ├── Xylo_Medium_C4_pp_01_far.ogg │ │ │ ├── Xylo_Medium_C5_ff_01_far.ogg │ │ │ ├── Xylo_Medium_C5_pp_01_far.ogg │ │ │ ├── Xylo_Medium_C6_ff_01_far.ogg │ │ │ ├── Xylo_Medium_C6_pp_01_far.ogg │ │ │ ├── Xylo_Medium_C7_ff_01_far.ogg │ │ │ ├── Xylo_Medium_C7_pp_01_far.ogg │ │ │ ├── Xylo_Medium_G3_ff_01_far.ogg │ │ │ ├── Xylo_Medium_G3_pp_01_far.ogg │ │ │ ├── Xylo_Medium_G4_ff_01_far.ogg │ │ │ ├── Xylo_Medium_G4_pp_01_far.ogg │ │ │ ├── Xylo_Medium_G5_ff_01_far.ogg │ │ │ ├── Xylo_Medium_G5_pp_01_far.ogg │ │ │ ├── Xylo_Medium_G6_ff_01_far.ogg │ │ │ └── Xylo_Medium_G6_pp_01_far.ogg │ └── pong-blip.wav ├── spaceship.py ├── spaceship_3d.py ├── spectrum_analyzer.py └── spectrum_analyzer.state ├── jupylet ├── __init__.py ├── app.py ├── assets │ ├── fonts │ │ ├── SIL Open Font License.txt │ │ └── SourceSerifPro-Bold.otf │ ├── shaders │ │ ├── default-fragment-shader.glsl │ │ ├── default-vertex-shader.glsl │ │ ├── shadertoy-wrapper.glsl │ │ └── sprite.glsl │ └── sounds │ │ └── impulses │ │ ├── InsidePiano.flac │ │ ├── MaesHowe.flac │ │ ├── README.md │ │ └── StAndrewsChurch.flac ├── audio │ ├── __init__.py │ ├── bundle.py │ ├── device.py │ ├── effects.py │ ├── filters.py │ ├── midi.py │ ├── note.py │ ├── sample.py │ ├── sound.py │ └── synth.py ├── clock.py ├── collision.py ├── color.py ├── env.py ├── event.py ├── label.py ├── loader.py ├── lru.py ├── model.py ├── node.py ├── resource.py ├── rl.py ├── shadertoy.py ├── sprite.py ├── state.py └── utils.py ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | .vscode/settings.json 131 | jpl.code-workspace 132 | tmp/ 133 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/conf.py 11 | 12 | python: 13 | version: 3.8 14 | install: 15 | - requirements: docs/requirements.txt 16 | - method: pip 17 | path: . 18 | system_packages: true -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2022, Nir Aides 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jupylet 2 | 3 | *Jupylet* is a Python library for programming 2D and 3D games, graphics, music 4 | and sound synthesizers, interactively in a Jupyter notebook. It is intended 5 | for three types of audiences: 6 | 7 | * Computer scientists, researchers, and students of deep reinforcement learning. 8 | * Musicians interested in sound synthesis and live music coding. 9 | * Kids and their parents interested in learning to program. 10 | 11 |   12 | 13 |

14 | 15 | 16 |

17 | 18 | ## Jupylet for Kids 19 | 20 | A Jupyter notebook is in essence a laboratory for programming. It is the ideal 21 | environment for playing around with code, experimenting, and exploring ideas. 22 | It is used by professional machine learning scientists who come every day to 23 | play at work, so why not by kids? 24 | 25 | *Jupylet* is wonderfully easy to use for creating simple 2D and 3D games and 26 | music interactively and experimentally. Change a variable or a function and 27 | see how the game is affected immediately while running. 28 | 29 | ## Jupylet for Deep Reinforcement Learning 30 | 31 | *Jupylet* makes it is super easy to create and modify environments in which to 32 | experiment with deep reinforcement learning algorithms and it includes the API 33 | to programmatically control multiple simultaneous games and render thousands 34 | of frames per second. 35 | 36 | Consider for example the pong game included in this code base. With a few 37 | lines of code you can modify the colors of the game to experiment with transfer 38 | learning, or turn the game into 4-way pong with agents on all four sides of the 39 | game court to experiment with cooperation between multiple agents. And since you 40 | can modify the game interactively in Jupyter this process is not only easy but 41 | also fun. 42 | 43 | Check out the [*Programming Graphics*](https://jupylet.readthedocs.io/en/latest/programmers_reference_guide/graphics.html) 44 | and the [*Reinforcement Learning*](https://jupylet.readthedocs.io/en/latest/programmers_reference_guide/rl.html) 45 | chapters in the Jupylet Programmer's Reference Guide. 46 | 47 | ## Jupylet for Musicians 48 | 49 | *Jupylet* imports ideas and methods from machine learning into the domain 50 | of sound synthesis to easily let you create sound synthesizers as wild as you 51 | can dream up - it includes impulse response reverb effects, colored noise 52 | generators, resonant filters with cutoff frequency sweeping, oscillators with 53 | LFO modulation, multi sampled instruments, and much more... And all of it in 54 | pure Python for you to modify and experiment with. 55 | 56 | In addition *Jupylet* draws inspiration from the wonderful [*Sonic Pi*](https://sonic-pi.net/) 57 | and brings live loops and live music coding to Jupyter and Python. Hook up 58 | your MIDI keyboard and take off. 59 | 60 | Check out the [*Programming Sound and Music*](https://jupylet.readthedocs.io/en/latest/programmers_reference_guide/sound.html) 61 | and the [*Programming Synthesizers*](https://jupylet.readthedocs.io/en/latest/programmers_reference_guide/synthesis.html) 62 | chapters in the Jupylet Programmer's Reference Guide. 63 | 64 | ## Requirements 65 | 66 | *Jupylet* should run on Python 3.9 and up on Windows, Mac, and Linux. 67 | 68 | ## How to Install and Run Jupylet 69 | 70 | If you are new to Python, I recommend that you install and use the 71 | [Miniconda Python](https://docs.conda.io/en/latest/miniconda.html) 72 | distribution. 73 | 74 | **On Windows** – download and run the 64-bit installer for Python 3.11. Once 75 | Miniconda is installed press the `⊞ Winkey` and then type *Miniconda* and 76 | press the `Enter` key. This should open a small window that programmers call 77 | *console* or *shell* in which you can enter commands and run programs. 78 | 79 | **On macOS with M1 processor** – download and run "Miniconda3 macOS Apple M1 64-bit pkg" 80 | for Python 3.11. Once installed click the Spotlight icon `🔍` and in the search 81 | field type *terminal* and press the `Enter` key to open the console. Then you need 82 | to run the following command: 83 | 84 | pip install --extra-index https://github.com/nir/jupylet/releases/download/v0.9.2/ moderngl glcontext 85 | 86 | **On macOS with Intel processor** – download and run "Miniconda3 macOS Intel x86 64-bit pkg" 87 | for Python 3.11. Once installed click the Spotlight icon `🔍` and in the search 88 | field type *terminal* and press the `Enter` key to open the console. 89 | 90 | **On Linux** – download "Miniconda3 Linux 64-bit". This should download the file 91 | Miniconda3-latest-Linux-x86_64.sh. Install it by running the following command 92 | in a bash shell (once installed start a new bash shell): 93 | 94 | bash Miniconda3-latest-Linux-x86_64.sh 95 | 96 | --- 97 | 98 | Once Miniconda is installed it is time to install *jupylet* by typing the 99 | following command in the console: 100 | 101 | pip install jupylet 102 | 103 | Next, to run the example notebooks, download the *jupylet* source code. 104 | If you have [Git](https://git-scm.com/) installed type the following command: 105 | 106 | git clone https://github.com/nir/jupylet.git 107 | 108 | Alternatively, you can download the source code with the following command: 109 | 110 | python -m jupylet download 111 | 112 | Next, enter the *jupylet/examples/* directory with the change directory 113 | command: 114 | 115 | cd jupylet/examples/ 116 | 117 | And start a jupyter notebook with: 118 | 119 | jupyter notebook 11-spaceship.ipynb 120 | 121 | Run the notebook by following the instructions in the notebook and a game 122 | canvas should appear with the spaceship example: 123 | 124 | 125 | 126 | Alternatively, you can run the same game as a Python script from the console 127 | with: 128 | 129 | python spaceship.py 130 | 131 | ## Documentation 132 | 133 | To get started with Jupylet head over to the *Jupylet Programmer's Reference 134 | Guide* which you can find at 135 | [jupylet.readthedocs.io](https://jupylet.readthedocs.io/). 136 | 137 | To complement the online guide check out the growing collection of 138 | [*example notebooks*](examples/) that you can download and run on your 139 | computer as explained above. 140 | 141 | ## Contact 142 | 143 | For questions and feedback send an email to [Nir Aides](mailto:nir.8bit@gmail.com) or [join the discussion](https://github.com/nir/jupylet/discussions). 144 | 145 | ## Spread the Word 146 | 147 | Jupylet is a new library and you can help it grow with a few clicks - 148 | if you like it let your friends know about it! 149 | 150 | ## Acknowledgements 151 | 152 | * [Einar Forselv](https://github.com/einarf) - The programmer behind ModernGL 153 | for his endless help in the trenches of OpenGL programming. 154 | * [Alban Fichet](https://afichet.github.io/) - For kindly licensing his 155 | sound visualizer Shadertoy as CC BY 4.0 license. 156 | 157 | ## What's New in Version 0.9.1 158 | 159 | * Support for Python 3.10 and Python 3.11 with MIDI functionality. 160 | * Seamlessly track changes to audio devices on macOS. 161 | * Workaround PIL api change - thanks to [@misolietavec](https://github.com/misolietavec). 162 | * Bug fixes. 163 | 164 | ## What's New in Version 0.8.9 165 | 166 | * Support for Python 3.10 and Python 3.11 - except for MIDI functionality. 167 | * Support for macOS M1. 168 | * Spectrum analyzer. 169 | * Bug fixes. 170 | 171 | 172 | 173 | ## What's New in Version 0.8.8 174 | 175 | * Support for Python 3.9. 176 | 177 | ## What's New in Version 0.8.7 178 | 179 | * Workaround auto-completion bug in Jupyter notebooks. 180 | 181 | ## What's New in Version 0.8.6 182 | 183 | * Support for rendering Shadertoy OpenGL shaders. 184 | [Shadertoy](https://www.shadertoy.com/) is an awesome online platform for 185 | programming and sharing OpenGL shaders online, and now you can 186 | [use and render shadertoy shaders in Jupylet!](https://jupylet.readthedocs.io/en/latest/programmers_reference_guide/graphics-3d.html#shadertoys) 187 | 188 | -------------------------------------------------------------------------------- /apt.txt: -------------------------------------------------------------------------------- 1 | xvfb 2 | freeglut3-dev -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/audio/adsr.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/_static/audio/adsr.ogg -------------------------------------------------------------------------------- /docs/_static/audio/sawtooth.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/_static/audio/sawtooth.ogg -------------------------------------------------------------------------------- /docs/_static/audio/simple-fm-synth.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/_static/audio/simple-fm-synth.ogg -------------------------------------------------------------------------------- /docs/_static/audio/tb303.5.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/_static/audio/tb303.5.ogg -------------------------------------------------------------------------------- /docs/_static/audio/violet.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/_static/audio/violet.ogg -------------------------------------------------------------------------------- /docs/_static/audio/wobbly.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/_static/audio/wobbly.ogg -------------------------------------------------------------------------------- /docs/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | 3 | {% block menu %} 4 | {{ super() }} 5 | INDEX 6 | {% endblock %} -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | 16 | sys.path.insert(0, os.path.abspath('..')) 17 | 18 | 19 | # -- Project information ----------------------------------------------------- 20 | 21 | project = 'Jupylet' 22 | copyright = '2022, Nir Aides' 23 | author = 'Nir Aides' 24 | 25 | # The full version, including alpha/beta/rc tags 26 | release = 'v0.9.2' 27 | 28 | 29 | # -- General configuration --------------------------------------------------- 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [ 35 | 'sphinx_rtd_theme', 36 | 'sphinx.ext.napoleon', 37 | 'sphinx.ext.autodoc', 38 | ] 39 | 40 | # Add any paths that contain templates here, relative to this directory. 41 | templates_path = ['_templates'] 42 | 43 | # List of patterns, relative to source directory, that match files and 44 | # directories to ignore when looking for source files. 45 | # This pattern also affects html_static_path and html_extra_path. 46 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 47 | 48 | 49 | # -- Options for HTML output ------------------------------------------------- 50 | 51 | # The theme to use for HTML and HTML Help pages. See the documentation for 52 | # a list of builtin themes. 53 | # 54 | #html_theme = 'alabaster' 55 | html_theme = "sphinx_rtd_theme" 56 | 57 | # Add any paths that contain custom static files (such as style sheets) here, 58 | # relative to this directory. They are copied after the builtin static files, 59 | # so a file named "default.css" will overwrite the builtin "default.css". 60 | html_static_path = ['_static'] 61 | 62 | master_doc = 'index' 63 | 64 | autodoc_mock_imports = [ 65 | 'scikit-image', 66 | 'sounddevice', 67 | 'matplotlib', 68 | 'soundfile', 69 | 'webcolors', 70 | 'gltflib', 71 | 'PyGLM', 72 | 'mido', 73 | ] 74 | 75 | -------------------------------------------------------------------------------- /docs/images/Atari-2600-Wood-4Sw-Set-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/Atari-2600-Wood-4Sw-Set-small.png -------------------------------------------------------------------------------- /docs/images/Commodore-64-Computer-FL-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/Commodore-64-Computer-FL-small.png -------------------------------------------------------------------------------- /docs/images/adsr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/adsr.png -------------------------------------------------------------------------------- /docs/images/coordinate_systems_right_handed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/coordinate_systems_right_handed.png -------------------------------------------------------------------------------- /docs/images/fm-sawtooth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/fm-sawtooth.png -------------------------------------------------------------------------------- /docs/images/gate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/gate.png -------------------------------------------------------------------------------- /docs/images/hello-world.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/hello-world.gif -------------------------------------------------------------------------------- /docs/images/lowpass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/lowpass.png -------------------------------------------------------------------------------- /docs/images/pong.step0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/pong.step0.png -------------------------------------------------------------------------------- /docs/images/pong.step5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/pong.step5.png -------------------------------------------------------------------------------- /docs/images/pong.x3.step0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/pong.x3.step0.png -------------------------------------------------------------------------------- /docs/images/pong.x3.step5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/pong.x3.step5.png -------------------------------------------------------------------------------- /docs/images/power-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/power-mode.png -------------------------------------------------------------------------------- /docs/images/resonance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/resonance.png -------------------------------------------------------------------------------- /docs/images/sawtooth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/sawtooth.png -------------------------------------------------------------------------------- /docs/images/ship1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/ship1.png -------------------------------------------------------------------------------- /docs/images/skybox-layout-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/skybox-layout-small.png -------------------------------------------------------------------------------- /docs/images/spaceship-3d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/spaceship-3d.jpg -------------------------------------------------------------------------------- /docs/images/spaceship.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/spaceship.gif -------------------------------------------------------------------------------- /docs/images/spaceship.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/spaceship.jpg -------------------------------------------------------------------------------- /docs/images/spaceship_3d.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/docs/images/spaceship_3d.gif -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Jupylet documentation master file, created by 2 | sphinx-quickstart on Sat Feb 1 08:19:08 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | JUPYLET PROGRAMMER'S REFERENCE GUIDE 7 | ==================================== 8 | 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | :caption: TABLE OF CONTENTS 13 | 14 | programmers_reference_guide/introduction 15 | programmers_reference_guide/getting_started 16 | programmers_reference_guide/graphics 17 | programmers_reference_guide/graphics-3d 18 | programmers_reference_guide/sound 19 | programmers_reference_guide/synthesis 20 | programmers_reference_guide/rl 21 | programmers_reference_guide/api 22 | programmers_reference_guide/appendices 23 | 24 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/programmers_reference_guide/api.rst: -------------------------------------------------------------------------------- 1 | API REFERENCE 2 | ============= 3 | 4 | 5 | Module jupylet.app 6 | ------------------ 7 | 8 | Class App 9 | ^^^^^^^^^ 10 | 11 | .. py:currentmodule:: jupylet.app 12 | .. autoclass:: App 13 | 14 | 15 | Methods 16 | """"""" 17 | 18 | .. automethod:: App.run 19 | .. automethod:: App.stop 20 | .. automethod:: App.set_midi_sound 21 | .. automethod:: App.observe 22 | .. automethod:: App.scale_window_to 23 | .. automethod:: App.save_state 24 | .. automethod:: App.load_state 25 | .. automethod:: App.get_logging_widget 26 | .. automethod:: App.sonic_live_loop2 27 | .. automethod:: App.sonic_live_loop 28 | .. automethod:: App.run_me_every 29 | .. automethod:: App.run_me 30 | .. automethod:: App.mouse_position_event 31 | .. automethod:: App.mouse_press_event 32 | .. automethod:: App.mouse_release_event 33 | .. automethod:: App.key_event 34 | .. automethod:: App.render 35 | .. automethod:: App.event 36 | .. automethod:: App.close 37 | .. automethod:: App.load_program 38 | 39 | 40 | Properties 41 | """""""""" 42 | 43 | .. autoattribute:: App.width 44 | .. autoattribute:: App.height 45 | 46 | 47 | Module jupylet.sprite 48 | --------------------- 49 | 50 | 51 | Class Sprite 52 | ^^^^^^^^^^^^ 53 | 54 | .. py:currentmodule:: jupylet.sprite 55 | .. autoclass:: Sprite 56 | 57 | Methods 58 | """"""" 59 | 60 | .. automethod:: Sprite.render 61 | .. automethod:: Sprite.draw 62 | .. automethod:: Sprite.set_anchor 63 | .. automethod:: Sprite.collisions_with 64 | .. automethod:: Sprite.distance_to 65 | .. automethod:: Sprite.angle_to 66 | .. automethod:: Sprite.wrap_position 67 | .. automethod:: Sprite.clip_position 68 | .. automethod:: Sprite.get_state 69 | .. automethod:: Sprite.set_state 70 | 71 | 72 | Properties 73 | """""""""" 74 | 75 | .. autoattribute:: Sprite.scale 76 | .. autoattribute:: Sprite.x 77 | .. autoattribute:: Sprite.y 78 | .. autoattribute:: Sprite.angle 79 | .. autoattribute:: Sprite.width 80 | .. autoattribute:: Sprite.height 81 | .. autoattribute:: Sprite.image 82 | .. autoattribute:: Sprite.top 83 | .. autoattribute:: Sprite.right 84 | .. autoattribute:: Sprite.bottom 85 | .. autoattribute:: Sprite.left 86 | .. autoattribute:: Sprite.radius 87 | .. autoattribute:: Sprite.opacity 88 | .. autoattribute:: Sprite.color 89 | 90 | .. py:attribute:: Sprite.flip=True 91 | 92 | Flip the image upside down while rendering. 93 | 94 | :type: bool 95 | 96 | .. py:attribute:: Sprite.mipmap=True 97 | 98 | Compute mipmap textures when first loading the sprite image. 99 | 100 | :type: bool 101 | 102 | .. py:attribute:: Sprite.autocrop=False 103 | 104 | Auto crop the image to its bounding box when first loading the sprite image. 105 | 106 | :type: bool 107 | 108 | .. py:attribute:: Sprite.anisotropy=8.0 109 | 110 | Use anisotropic filtering when rendering the sprite image. 111 | 112 | :type: float 113 | 114 | 115 | Module jupylet.label 116 | -------------------- 117 | 118 | 119 | Class Label 120 | ^^^^^^^^^^^ 121 | 122 | .. py:currentmodule:: jupylet.label 123 | .. autoclass:: Label 124 | 125 | 126 | Properties 127 | """""""""" 128 | 129 | .. py:attribute:: Label.text 130 | 131 | Text to render as label. 132 | 133 | :type: str 134 | 135 | .. py:attribute:: Label.font_path 136 | 137 | Path to a true type or open type font. 138 | 139 | :type: str 140 | 141 | .. py:attribute:: Label.font_size=16 142 | 143 | Font size to use. 144 | 145 | :type: float 146 | 147 | .. py:attribute:: Label.line_height=1.2 148 | 149 | Determines the distance between lines. 150 | 151 | :type: float 152 | 153 | .. py:attribute:: Label.align='left' 154 | 155 | The desired alignment for the text label. May be one of 'left', 'center', 156 | and 'right'. 157 | 158 | :type: str 159 | 160 | 161 | Module jupylet.loader 162 | --------------------- 163 | 164 | .. py:currentmodule:: jupylet.loader 165 | .. py:module:: jupylet.loader 166 | 167 | .. autofunction:: load_blender_gltf 168 | 169 | 170 | Module jupylet.model 171 | -------------------- 172 | 173 | 174 | Class Scene 175 | ^^^^^^^^^^^ 176 | 177 | .. py:currentmodule:: jupylet.model 178 | .. autoclass:: Scene 179 | 180 | 181 | Methods 182 | """"""" 183 | 184 | .. automethod:: Scene.draw 185 | 186 | 187 | Properties 188 | """""""""" 189 | 190 | .. py:attribute:: Scene.meshes 191 | 192 | A list of meshes. 193 | 194 | :type: list 195 | 196 | .. py:attribute:: Scene.lights 197 | 198 | A list of lights. 199 | 200 | :type: list 201 | 202 | .. py:attribute:: Scene.cameras 203 | 204 | A list of cameras. 205 | 206 | :type: list 207 | 208 | .. py:attribute:: Scene.materials 209 | 210 | A list of materials. 211 | 212 | :type: list 213 | 214 | .. py:attribute:: Scene.skybox 215 | 216 | A Skybox object. 217 | 218 | :type: Skybox 219 | 220 | .. py:attribute:: Scene.shadows 221 | 222 | Set to True to enable shadows. 223 | 224 | :type: bool 225 | 226 | .. py:attribute:: Scene.name 227 | 228 | Name of scene. 229 | 230 | :type: str 231 | 232 | 233 | Class Mesh 234 | ^^^^^^^^^^ 235 | 236 | .. py:currentmodule:: jupylet.model 237 | .. autoclass:: Mesh 238 | 239 | 240 | Methods 241 | """"""" 242 | 243 | .. automethod:: Mesh.move_local 244 | .. automethod:: Mesh.move_global 245 | .. automethod:: Mesh.rotate_local 246 | .. automethod:: Mesh.rotate_global 247 | 248 | 249 | Properties 250 | """""""""" 251 | 252 | .. autoattribute:: Mesh.front 253 | .. autoattribute:: Mesh.up 254 | 255 | .. py:attribute:: Mesh.primitives 256 | 257 | List of primitives the mesh consists of. 258 | 259 | :type: list 260 | 261 | .. py:attribute:: Mesh.children 262 | 263 | List of child meshes. 264 | 265 | :type: list 266 | 267 | .. py:attribute:: Mesh.hide 268 | 269 | Set to False to hide mesh from view. 270 | 271 | :type: bool 272 | 273 | .. py:attribute:: Mesh.name 274 | 275 | Name of mesh. 276 | 277 | :type: str 278 | 279 | 280 | Module jupylet.audio 281 | -------------------- 282 | 283 | .. py:currentmodule:: jupylet.audio 284 | .. py:module:: jupylet.audio 285 | 286 | 287 | .. autofunction:: sonic_py 288 | .. autofunction:: set_bpm 289 | .. autofunction:: set_note_value 290 | .. autofunction:: use 291 | .. autofunction:: play 292 | .. autofunction:: sleep 293 | 294 | 295 | Module jupylet.audio.sound 296 | -------------------------- 297 | 298 | 299 | Class Sound 300 | ^^^^^^^^^^^ 301 | 302 | .. py:currentmodule:: jupylet.audio.sound 303 | .. autoclass:: Sound 304 | 305 | 306 | Methods 307 | """"""" 308 | 309 | .. automethod:: Sound.play 310 | .. automethod:: Sound.play_poly 311 | .. automethod:: Sound.play_release 312 | .. automethod:: Sound.set_effects 313 | .. automethod:: Sound.get_effects 314 | 315 | 316 | Properties 317 | """""""""" 318 | 319 | .. autoattribute:: Sound.note 320 | .. autoattribute:: Sound.key 321 | 322 | .. py:attribute:: Sound.freq 323 | 324 | Fundamental requency of sound object. 325 | 326 | :type: float 327 | 328 | .. py:attribute:: Sound.amp 329 | 330 | Output amplitude - a value between 0 and 1. 331 | 332 | :type: float 333 | 334 | .. py:attribute:: Sound.pan 335 | 336 | Balance between left (-1) and right (1) output channels. 337 | 338 | :type: float 339 | 340 | 341 | Class Oscillator 342 | ^^^^^^^^^^^^^^^^ 343 | 344 | .. py:currentmodule:: jupylet.audio.sound 345 | .. autoclass:: Oscillator 346 | 347 | 348 | Methods 349 | """"""" 350 | 351 | .. automethod:: Oscillator.forward 352 | 353 | 354 | Properties 355 | """""""""" 356 | 357 | .. py:attribute:: Oscillator.shape 358 | 359 | Waveform to generate - one of `sine`, `triangle`, `sawtooth`, or `square`. 360 | 361 | :type: str 362 | 363 | .. py:attribute:: Oscillator.sign 364 | 365 | Set to -1 to flip sawtooth waveform upside down. 366 | 367 | :type: float 368 | 369 | .. py:attribute:: Oscillator.duty 370 | 371 | The fraction of the square waveform cycle its value is 1. 372 | 373 | :type: float 374 | 375 | 376 | Class LatencyGate 377 | ^^^^^^^^^^^^^^^^^ 378 | 379 | .. py:currentmodule:: jupylet.audio.sound 380 | .. autoclass:: LatencyGate 381 | 382 | 383 | Methods 384 | """"""" 385 | 386 | .. automethod:: LatencyGate.open 387 | .. automethod:: LatencyGate.close 388 | 389 | 390 | Class GatedSound 391 | ^^^^^^^^^^^^^^^^ 392 | 393 | .. py:currentmodule:: jupylet.audio.sound 394 | .. autoclass:: GatedSound 395 | 396 | 397 | Methods 398 | """"""" 399 | 400 | .. automethod:: GatedSound.play 401 | .. automethod:: GatedSound.play_poly 402 | .. automethod:: GatedSound.play_release 403 | 404 | 405 | Module jupylet.audio.sample 406 | --------------------------- 407 | 408 | 409 | Class Sample 410 | ^^^^^^^^^^^^ 411 | 412 | .. py:currentmodule:: jupylet.audio.sample 413 | .. autoclass:: Sample 414 | 415 | 416 | Methods 417 | """"""" 418 | 419 | .. automethod:: Sample.load 420 | 421 | 422 | Properties 423 | """""""""" 424 | 425 | .. py:attribute:: Sample.path 426 | 427 | Path to audio file. 428 | 429 | :type: str 430 | 431 | -------------------------------------------------------------------------------- /docs/programmers_reference_guide/appendices.rst: -------------------------------------------------------------------------------- 1 | APPENDICES 2 | ========== 3 | 4 | A Few Words About Notebooks 5 | --------------------------- 6 | 7 | A Jupyter notebook lets you mix formatted text with computer code and graphics. 8 | Jupyter notebooks belong to the more general category of `computational 9 | notebooks `_. 10 | 11 | Computational notebooks are the modern version of the traditional scientist's 12 | laboratory notebook. Such notebooks have been used by scientists for hundreds 13 | of years to keep track of their ideas and experiments in physics, chemistry, 14 | biology, etc... 15 | 16 | A very interesting thing about Jupyter notebooks is that when it comes to 17 | computer science and data science they mix the laboratory and the notebook into 18 | one. That is, a Jupyter notebook is also a computing laboratory since you can 19 | actually run code in them! 20 | 21 | Here is a very special example of a traditional laboratory notebook. A page 22 | from `Galileo Galilei's `_ 23 | notebook in which he documented his first observations of the moons of Jupiter 24 | 400 years ago: 25 | 26 | .. image:: https://upload.wikimedia.org/wikipedia/commons/thumb/a/a6/Galileo_manuscript.png/419px-Galileo_manuscript.png 27 | 28 | You can think of it as the first Jupiter notebook 😎 and if you know Italian 29 | let me know what it says. 30 | 31 | 32 | The Atari 2600 33 | -------------- 34 | 35 | I grew up playing an `Atari 2600 `_ 36 | like the one shown in this picture: 37 | 38 | .. image:: ../images/Atari-2600-Wood-4Sw-Set-small.png 39 | 40 | I spent endless hours of fun playing so many of its games. 41 | 42 | In 2013 Deepmind published a `Reinforcement Learning` algorithm that surpassed 43 | human level performance on some of its classic games. The video of their agent 44 | learning to play and then master the game of `Breakout `_ 45 | stunned the world: 46 | 47 | .. raw:: html 48 | 49 | 50 |
51 |
52 | 53 | Since then Atari games are used as a benchmark for `Reinforcement Learning` 54 | algorithms. 55 | 56 | When I started learning `Deep Learning` and `Deep Reinforcement 57 | Learning` I found myself spending too much time trying to customize 58 | `the computer code running the Atari games `_, 59 | and so it made perfect sense to me to spend a whole lot more time, I mean, 60 | way more time, programming a completely new environment, and so Jupylet came 61 | to be. 62 | 63 | 64 | The Commodore 64 65 | ---------------- 66 | 67 | I still remember the day my dad brought home the `Commodore 64 `_ 68 | and we connected it to the TV set for the first time. It is such a beauty - 69 | take a look: 70 | 71 | .. image:: ../images/Commodore-64-Computer-FL-small.png 72 | 73 | Just as with the `Atari 2600` I spent countless hours playing its wonderful 74 | games, which made my parents worry quite a bit 😎 but like many other kids I 75 | soon tried to program it, and then to program basic games with graphics and 76 | sound, and soon I got carried away down the rabbit hole of computation into 77 | programming wonderland. 78 | 79 | It is a wonder land in which you can turn any idea that pops into your head 80 | into a `something` that works and sounds and plays just like you dreamed it. 81 | 82 | The `Commodore 64` was the perfect conductor for that kind of magic. Soon 83 | after I started programming Jupylet I realized that what I really wanted is 84 | to try to recreate this kind of environment for today's kids. 85 | 86 | 87 | The 6581 SID Chip 88 | ----------------- 89 | 90 | The `Commodore 64` had a gem of a chip hiding inside it - the 91 | `6581 SID chip `_ 92 | which was a very sophisticated sound synthesizer for its time: 93 | 94 | .. raw:: html 95 | 96 | 97 |
98 |
99 | 100 | As I was programming the sound synthesis framework of Jupylet I revisited the 101 | epic `Commodore 64 Programmers's Reference Guide `_ 102 | and I was stunned to see how advanced it really was. 103 | 104 | It had three independant waveform generators that could generate `sine`, 105 | `triangle`, `sawtooth`, `pulse` waveform with variable duty, and `white noise`, 106 | and you could use the amplitude of one to modulate the frequency of another! 107 | It had a classic ADSR envelope generator, and it had a filter that you could 108 | use as `lowpass`, `highpass`, or `bandpass` and sweep its `cutoff` frequency 109 | dynamically. 110 | 111 | And its epic guide explained all of it clearly in a language that a child could 112 | understand and in depth that a musician would find useful and professional 113 | programmers appreciate, starting with what sound waves really are, 114 | explaining fundamental frequencies and harmonics, and all the way to describing 115 | frequency sweeping and how to code the computer to control all of the 116 | synthesizer's parameters dynamically. 117 | 118 | It was recently included in `IEEE Spectrum Chip Hall of Fame `_ 119 | and nearly 40 years later it still has a following of fans. 120 | 121 | -------------------------------------------------------------------------------- /docs/programmers_reference_guide/getting_started.rst: -------------------------------------------------------------------------------- 1 | GETTING STARTED 2 | =============== 3 | 4 | How to Install and Run Jupylet 5 | ------------------------------ 6 | 7 | If you are new to Python, I recommend that you install and use the 8 | `Miniconda Python `_ 9 | distribution. 10 | 11 | **On Windows** -- download and run the 64-bit installer for Python 3.11. Once 12 | Miniconda is installed press the :guilabel:`⊞ Winkey` and then type 13 | *Miniconda* and press the :guilabel:`Enter` key. This should open a small 14 | window that programmers call *console* or *shell* in which you can enter 15 | commands and run programs. 16 | 17 | **On macOS with M1 processor** -- download and run "Miniconda3 macOS Apple M1 64-bit pkg" 18 | for Python 3.11. Once installed click the Spotlight icon :guilabel:`🔍` and 19 | in the search field type *terminal* and press the :guilabel:`Enter` key to 20 | open the console. 21 | 22 | **On macOS with Intel processor** -- download and run "Miniconda3 macOS Intel x86 64-bit pkg" 23 | for Python 3.11. Once installed click the Spotlight icon :guilabel:`🔍` and 24 | in the search field type *terminal* and press the :guilabel:`Enter` key to 25 | open the console. 26 | 27 | **On Linux** -- download "Miniconda3 Linux 64-bit". This should download the file 28 | Miniconda3-latest-Linux-x86_64.sh. Install it by running the following command 29 | in a bash shell (once installed start a new bash shell): 30 | 31 | .. code-block:: bash 32 | 33 | bash Miniconda3-latest-Linux-x86_64.sh 34 | 35 | ------------ 36 | 37 | Once Miniconda is installed it is time to install *jupylet* by typing the 38 | following command in the console: 39 | 40 | .. code-block:: bash 41 | 42 | pip install jupylet 43 | 44 | Next, to run the example notebooks download the *jupylet* source code. If 45 | you have `Git `_ installed type the following command: 46 | 47 | .. code-block:: bash 48 | 49 | git clone https://github.com/nir/jupylet.git 50 | 51 | Alternatively, you can download the source code with the following command: 52 | 53 | .. code-block:: bash 54 | 55 | python -m jupylet download 56 | 57 | Next, enter the *jupylet/examples/* directory with the change directory 58 | command: 59 | 60 | .. code-block:: bash 61 | 62 | cd jupylet/examples/ 63 | 64 | And start a jupyter notebook with: 65 | 66 | .. code-block:: bash 67 | 68 | jupyter notebook 11-spaceship.ipynb 69 | 70 | Run the notebook by following the instructions in the notebook and a game 71 | canvas should appear with the spaceship example: 72 | 73 | .. image:: ../images/spaceship.gif 74 | 75 | Alternatively, you can run the same game as a Python script from the console 76 | with: 77 | 78 | .. code-block:: bash 79 | 80 | python spaceship.py 81 | 82 | The Python Programming Language 83 | ------------------------------- 84 | 85 | Python is an awesome programming language. It is both simple for kids to 86 | learn and powerful enough to be `one of the most popular programming languages 87 | `_ among computer scientists and 88 | programmers. 89 | 90 | However, this reference guide is not designed to teach the Python programming 91 | language. If you don't already have a working knowlege of Python and how to 92 | use it to program, I would like to suggest a few resources that may help you 93 | get started: 94 | 95 | - `Microsoft's introduction to Python `_ 96 | \- Microsoft has a long tradition of publishing good guides to programming 97 | languages and this tutorial appears to be in line with this tradition. 98 | However, their Azure Cloud Shell is unfortunately a distraction. You would 99 | be better off trying out their exercises in Python's own `online shell `_. 100 | 101 | - `Python's own tutorial `_ 102 | \- Perhaps not as didactic as Microsoft's guide, but it is a good idea to 103 | get familiar with Python's official documentation. 104 | 105 | - `Mike Dane's Learn Python Yotube tutorial `_ 106 | \- Appears to be a good didactic introduction to Python. 107 | 108 | These guides will instruct you how to start a python interpreter where you 109 | can type and run Python code. You may do that, but once you gain a little bit 110 | of confidence or if you feel adventurous try starting a Jupyter notebook 111 | instead of a simple python interpreter. 112 | 113 | To do that start the Miniconda Prompt 114 | `as explained above <#how-to-install-and-run-jupylet>`_, then change 115 | directory into the *jupylet/examples/* directory and start a new notebook by 116 | typing: 117 | 118 | .. code-block:: bash 119 | 120 | jupyter notebook 01-hello-world.ipynb 121 | 122 | Jupyter Notebooks 123 | ----------------- 124 | 125 | Jupyter notebooks are awesome but they can be a little confusing at 126 | first. Here are a few resources that explain how to use them: 127 | 128 | - `examples/01-hello-world.ipynb `_ 129 | notebook contains a basic introduction to Jupyter notebooks. Check it out. 130 | 131 | - `Running Code `_ 132 | \- This is a Jupyter notebook explaining how to use Jupyter notebooks 🙂. 133 | It is in fact a live notebook running in a web service called mybinder. The 134 | first time you click it may take a moment to start, so give it a moment. 135 | Since it is "live" you can play around with it. It works! 136 | 137 | - `Jupyter's documentation `_ 138 | \- There's a whole lot of text in there. 139 | 140 | -------------------------------------------------------------------------------- /docs/programmers_reference_guide/introduction.rst: -------------------------------------------------------------------------------- 1 | INTRODUCTION 2 | ============ 3 | 4 | *Jupylet* is a Python library for programming 2D and 3D games, graphics, 5 | music and sound synthesizers, interactively in a Jupyter notebook. It is 6 | intended for three types of audiences: 7 | 8 | * Computer scientists, researchers, and students of deep reinforcement learning. 9 | * Musicians interested in sound synthesis and live music coding. 10 | * Kids and their parents interested in learning to program. 11 | 12 | .. image:: ../images/spaceship.gif 13 | :width: 36 % 14 | .. image:: ../images/spaceship_3d.gif 15 | :width: 54 % 16 | 17 | Jupylet for Kids 18 | ---------------- 19 | 20 | A Jupyter notebook is in essence a laboratory for programming. It is the ideal 21 | environment for playing around with code, experimenting, and exploring ideas. 22 | It is used by professional machine learning scientists who come every day to 23 | play at work, so why not by kids? 24 | 25 | *Jupylet* is wonderfully easy to use for creating simple 2D and 3D games and 26 | music interactively and experimentally. Change a variable or a function and 27 | see how the game is affected immediately while it is running. 28 | :any:`Let's get started!` 29 | 30 | 31 | Jupylet for Deep Reinforcement Learning 32 | --------------------------------------- 33 | 34 | *Jupylet* makes it is super easy to create and modify environments in which to 35 | experiment with deep reinforcement learning algorithms and it includes the API 36 | to programmatically control multiple simultaneous games and render thousands 37 | of frames per second. 38 | 39 | Consider for example the pong game included in this repository. With a few 40 | lines of code you can modify the colors of the game to experiment with transfer 41 | learning, or turn the game into 4-way pong with agents on all four sides of the 42 | game court to experiment with cooperation between multiple agents. And since you 43 | can modify the game interactively in Jupyter this process is not only easy but 44 | also fun. 45 | 46 | See the :any:`Programming Graphics` and the 47 | :any:`Reinforcement Learning` chapters for more information. 48 | 49 | 50 | Jupylet for Musicians 51 | --------------------- 52 | 53 | *Jupylet* imports ideas and methods from machine learning into the domain 54 | of sound synthesis to easily let you create sound synthesizers as wild as you 55 | can dream up - it includes impulse response reverb effects, colored noise 56 | generators, resonant filters with cutoff frequency sweeping, oscillators with 57 | LFO modulation, multi sampled instruments, and much more... And all of it in 58 | pure Python for you to modify and experiment with. 59 | 60 | In addition *Jupylet* draws inspiration from the wonderful `Sonic Pi `_ 61 | and brings live loops and live music coding to Jupyter and Python. Hook up 62 | your MIDI keyboard and take off. 63 | 64 | See the :any:`Programming Sound and Music` and the 65 | :any:`Programming Synthesizers` chapters for more information. 66 | 67 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx==5.3.0 2 | sphinx-rtd-theme==1.1.1 3 | Jinja2==3.1.3 -------------------------------------------------------------------------------- /examples/01-hello-world.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Hello, World!\n", 8 | "\n", 9 | "Let's start with a traditional first program called [Hello, World!](https://en.wikipedia.org/wiki/%22Hello,_World!%22_program) \n", 10 | "\n", 11 | "Here is how it looked like 45 years ago when it was written for the first time by Brian Kernighan for the C programming language:\n", 12 | "\n", 13 | "```c\n", 14 | "main( ) {\n", 15 | " printf(\"hello, world\\n\");\n", 16 | "}\n", 17 | "```\n", 18 | "\n", 19 | "Now it's your turn. \n", 20 | "\n", 21 | "Jupyter notebooks are made of cells. The cell in which this text is written is called a markdown cell. Markdown cells can be used to describe and document your code. \n", 22 | "\n", 23 | "The cell below is a called a code cell. Code cells can be used to run code. The cell below contains the python code for *Hello, World!* \n", 24 | "\n", 25 | "To run the cell below press the `Down` arrow key or click the button in the toolbar above to select it. A blue border around the cell will indicate it is now in focus. Then press `Shift-Enter` or click the button in the toolbar above to run it. If you do it correctly the code will print the famous words:" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": 1, 31 | "metadata": {}, 32 | "outputs": [ 33 | { 34 | "name": "stdout", 35 | "output_type": "stream", 36 | "text": [ 37 | "hello, world\n" 38 | ] 39 | } 40 | ], 41 | "source": [ 42 | "print('hello, world')" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "metadata": {}, 55 | "source": [ 56 | "Great work! \n", 57 | "\n", 58 | "Now, click the `Help` menu above and select `User Interface Tour` from the drop down list for a short introduction to the user interface of Jupyter. \n", 59 | "\n", 60 | "To edit the content of a cell select it with the `Up` and `Down` arrow keys and press `Enter`. The color of the cell border should change from blue to green to indicate that the cell is in edit mode. \n", 61 | "\n", 62 | "Edit the content of the cell below. Change it to `2 + 2`. When you are done press `Esc` to switch back to command mode or press `Shift-Enter` to run the cell. " 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 2, 68 | "metadata": {}, 69 | "outputs": [ 70 | { 71 | "data": { 72 | "text/plain": [ 73 | "2" 74 | ] 75 | }, 76 | "execution_count": 2, 77 | "metadata": {}, 78 | "output_type": "execute_result" 79 | } 80 | ], 81 | "source": [ 82 | "1 + 1" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": null, 88 | "metadata": {}, 89 | "outputs": [], 90 | "source": [] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "metadata": {}, 95 | "source": [ 96 | "Great work!\n", 97 | "\n", 98 | "Now, explore the menubar above to see how you can manipulate cells around. There are keyboard shortcuts that you can use for most actions.\n", 99 | "\n", 100 | "I have prepared a few more cells for you to play around with. You can use this notebook to practice your skills. If you are new to Python I recommend [Microsoft's introduction to Python](https://docs.microsoft.com/en-us/learn/modules/intro-to-python/1-introduction)." 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": null, 106 | "metadata": {}, 107 | "outputs": [], 108 | "source": [] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": 3, 113 | "metadata": {}, 114 | "outputs": [], 115 | "source": [ 116 | "a = 5" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": 4, 122 | "metadata": {}, 123 | "outputs": [ 124 | { 125 | "data": { 126 | "text/plain": [ 127 | "6" 128 | ] 129 | }, 130 | "execution_count": 4, 131 | "metadata": {}, 132 | "output_type": "execute_result" 133 | } 134 | ], 135 | "source": [ 136 | "a + 1" 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": 5, 142 | "metadata": {}, 143 | "outputs": [ 144 | { 145 | "name": "stdout", 146 | "output_type": "stream", 147 | "text": [ 148 | "0\n", 149 | "1\n", 150 | "2\n", 151 | "3\n", 152 | "4\n" 153 | ] 154 | } 155 | ], 156 | "source": [ 157 | "for i in range(5):\n", 158 | " print(i)" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": null, 164 | "metadata": {}, 165 | "outputs": [], 166 | "source": [] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": 6, 171 | "metadata": {}, 172 | "outputs": [], 173 | "source": [ 174 | "def greet(name):\n", 175 | " print('Hello, ' + name + '!')" 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": 7, 181 | "metadata": {}, 182 | "outputs": [ 183 | { 184 | "name": "stdout", 185 | "output_type": "stream", 186 | "text": [ 187 | "Hello, Dave!\n" 188 | ] 189 | } 190 | ], 191 | "source": [ 192 | "greet('Dave')" 193 | ] 194 | }, 195 | { 196 | "cell_type": "markdown", 197 | "metadata": {}, 198 | "source": [ 199 | "" 200 | ] 201 | } 202 | ], 203 | "metadata": { 204 | "kernelspec": { 205 | "display_name": "Python 3", 206 | "language": "python", 207 | "name": "python3" 208 | }, 209 | "language_info": { 210 | "codemirror_mode": { 211 | "name": "ipython", 212 | "version": 3 213 | }, 214 | "file_extension": ".py", 215 | "mimetype": "text/x-python", 216 | "name": "python", 217 | "nbconvert_exporter": "python", 218 | "pygments_lexer": "ipython3", 219 | "version": "3.8.5" 220 | } 221 | }, 222 | "nbformat": 4, 223 | "nbformat_minor": 2 224 | } 225 | -------------------------------------------------------------------------------- /examples/02-hello-jupylet.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Hello, Jupylet!\n", 8 | "\n", 9 | "This notebook demonstrates creating a simple canvas with a scrolling banner." 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "### How to use Jupyter notebooks\n", 17 | "\n", 18 | "If you are unfamiliar with Jupyter notebooks open the [*01-hello-world.ipynb*](./01-hello-world.ipynb) notebook which introduces the Jupyter notebook user inteface." 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "### Run the game\n", 26 | "\n", 27 | "Run this notebook and see what happens. Later when you are ready come back here to read the game code.\n", 28 | "\n", 29 | "To run the game click `Cell` in the menubar above and selct `Run All` from the drop down list. The notebook will scroll to its bottom where a game canvas should appear with the scrolling banner." 30 | ] 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "metadata": {}, 35 | "source": [ 36 | "### Prerequisites\n", 37 | "\n", 38 | "To understand this code you need to know about Python imports, functions, and classes." 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": null, 44 | "metadata": {}, 45 | "outputs": [], 46 | "source": [] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": 1, 51 | "metadata": {}, 52 | "outputs": [], 53 | "source": [ 54 | "import sys\n", 55 | "import os" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 2, 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "sys.path.insert(0, os.path.abspath('./..'))" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 3, 70 | "metadata": {}, 71 | "outputs": [], 72 | "source": [ 73 | "from jupylet.label import Label\n", 74 | "from jupylet.app import App" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": null, 80 | "metadata": {}, 81 | "outputs": [], 82 | "source": [] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "metadata": {}, 87 | "source": [ 88 | "Create a game application object:" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": 4, 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "app = App(width=320, height=64)" 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": {}, 103 | "source": [ 104 | "Load a text label with the text *'hello, world'* and position it just outside the right hand side of the canvas:" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": 5, 110 | "metadata": {}, 111 | "outputs": [], 112 | "source": [ 113 | "hello = Label('hello, world', color='cyan', font_size=32, x=app.width, y=22)" 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "metadata": {}, 119 | "source": [ 120 | "Create a function to update the label position. The `@app.run_me_every(1/24)` decorator will make sure it is run 24 times each second:" 121 | ] 122 | }, 123 | { 124 | "cell_type": "code", 125 | "execution_count": 6, 126 | "metadata": {}, 127 | "outputs": [], 128 | "source": [ 129 | "@app.run_me_every(1/24)\n", 130 | "def scroll(ct, dt):\n", 131 | " hello.x -= dt * 48\n", 132 | " if hello.right < 0:\n", 133 | " hello.x = app.width" 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "metadata": {}, 139 | "source": [ 140 | "Create a function to render a game frame to the canvas:" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": 7, 146 | "metadata": {}, 147 | "outputs": [], 148 | "source": [ 149 | "@app.event\n", 150 | "def render(ct, dt):\n", 151 | " app.window.clear()\n", 152 | " hello.draw()" 153 | ] 154 | }, 155 | { 156 | "cell_type": "markdown", 157 | "metadata": {}, 158 | "source": [ 159 | "And finally start the game:" 160 | ] 161 | }, 162 | { 163 | "cell_type": "code", 164 | "execution_count": 8, 165 | "metadata": { 166 | "scrolled": false 167 | }, 168 | "outputs": [ 169 | { 170 | "data": { 171 | "application/vnd.jupyter.widget-view+json": { 172 | "model_id": "d29d131b754b4e83a6140adf39068988", 173 | "version_major": 2, 174 | "version_minor": 0 175 | }, 176 | "text/plain": [ 177 | "Image(value=b'\\xff\\xd8\\xff\\xe0\\x00\\x10JFIF\\x00\\x01\\x01\\x00\\x00\\x01\\x00\\x01\\x00\\x00\\xff\\xdb\\x00C\\x00\\x08\\x06\\x0…" 178 | ] 179 | }, 180 | "metadata": {}, 181 | "output_type": "display_data" 182 | } 183 | ], 184 | "source": [ 185 | "app.run()" 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": null, 191 | "metadata": {}, 192 | "outputs": [], 193 | "source": [] 194 | } 195 | ], 196 | "metadata": { 197 | "kernelspec": { 198 | "display_name": "Python 3", 199 | "language": "python", 200 | "name": "python3" 201 | }, 202 | "language_info": { 203 | "codemirror_mode": { 204 | "name": "ipython", 205 | "version": 3 206 | }, 207 | "file_extension": ".py", 208 | "mimetype": "text/x-python", 209 | "name": "python", 210 | "nbconvert_exporter": "python", 211 | "pygments_lexer": "ipython3", 212 | "version": "3.8.5" 213 | } 214 | }, 215 | "nbformat": 4, 216 | "nbformat_minor": 2 217 | } 218 | -------------------------------------------------------------------------------- /examples/16-shadertoy-demo.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Shadertoys\n", 8 | "\n", 9 | "This notebook demostrates how to display [shadertoys](https://www.shadertoy.com/). Check out the documentation for more information: \n", 10 | "https://jupylet.readthedocs.io/en/latest/programmers_reference_guide/graphics-3d.html#shadertoys" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 1, 23 | "metadata": {}, 24 | "outputs": [], 25 | "source": [ 26 | "import sys\n", 27 | "import os" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": 2, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "sys.path.insert(0, os.path.abspath('./..'))" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 3, 42 | "metadata": {}, 43 | "outputs": [], 44 | "source": [ 45 | "from jupylet.app import App\n", 46 | "from jupylet.label import Label\n", 47 | "from jupylet.shadertoy import Shadertoy" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": 4, 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "from jupylet.audio.bundle import *" 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": null, 62 | "metadata": {}, 63 | "outputs": [], 64 | "source": [] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 5, 69 | "metadata": {}, 70 | "outputs": [], 71 | "source": [ 72 | "app = App(width=533, height=300)" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "metadata": {}, 79 | "outputs": [], 80 | "source": [] 81 | }, 82 | { 83 | "cell_type": "markdown", 84 | "metadata": {}, 85 | "source": [ 86 | "### Star Nest Shadertoy by Pablo Roman Andrioli\n", 87 | "\n", 88 | "The code in the following cell is of a [beautiful shadertoy by Pablo Roman Andrioli](https://www.shadertoy.com/view/XlfGRj). It is available under the MIT License. " 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": 6, 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "st = Shadertoy(\"\"\"\n", 98 | "\n", 99 | " // Star Nest by Pablo Roman Andrioli\n", 100 | "\n", 101 | " // This content is under the MIT License.\n", 102 | "\n", 103 | " #define iterations 17\n", 104 | " #define formuparam 0.53\n", 105 | "\n", 106 | " #define volsteps 20\n", 107 | " #define stepsize 0.1\n", 108 | "\n", 109 | " #define zoom 0.800\n", 110 | " #define tile 0.850\n", 111 | " #define speed 0.010 \n", 112 | "\n", 113 | " #define brightness 0.0015\n", 114 | " #define darkmatter 0.300\n", 115 | " #define distfading 0.730\n", 116 | " #define saturation 0.850\n", 117 | "\n", 118 | "\n", 119 | " void mainImage( out vec4 fragColor, in vec2 fragCoord )\n", 120 | " {\n", 121 | " //get coords and direction\n", 122 | " vec2 uv=fragCoord.xy/iResolution.xy-.5;\n", 123 | " uv.y*=iResolution.y/iResolution.x;\n", 124 | " vec3 dir=vec3(uv*zoom,1.);\n", 125 | " float time=iTime*speed+.25;\n", 126 | "\n", 127 | " //mouse rotation\n", 128 | " float a1=.5+iMouse.x/iResolution.x*2.;\n", 129 | " float a2=.8+iMouse.y/iResolution.y*2.;\n", 130 | " mat2 rot1=mat2(cos(a1),sin(a1),-sin(a1),cos(a1));\n", 131 | " mat2 rot2=mat2(cos(a2),sin(a2),-sin(a2),cos(a2));\n", 132 | " dir.xz*=rot1;\n", 133 | " dir.xy*=rot2;\n", 134 | " vec3 from=vec3(1.,.5,0.5);\n", 135 | " from+=vec3(time*2.,time,-2.);\n", 136 | " from.xz*=rot1;\n", 137 | " from.xy*=rot2;\n", 138 | "\n", 139 | " //volumetric rendering\n", 140 | " float s=0.1,fade=1.;\n", 141 | " vec3 v=vec3(0.);\n", 142 | " for (int r=0; r6) fade*=1.-dm; // dark matter, don't render near\n", 154 | " //v+=vec3(dm,dm*.5,0.);\n", 155 | " v+=fade;\n", 156 | " v+=vec3(s,s*s,s*s*s*s)*a*brightness*fade; // coloring based on distance\n", 157 | " fade*=distfading; // distance fading\n", 158 | " s+=stepsize;\n", 159 | " }\n", 160 | " v=mix(vec3(length(v)),v,saturation); //color adjust\n", 161 | " fragColor = vec4(v*.01,1.);\t\n", 162 | "\n", 163 | " }\n", 164 | " \n", 165 | "\"\"\", 533, 300)" 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": null, 171 | "metadata": {}, 172 | "outputs": [], 173 | "source": [] 174 | }, 175 | { 176 | "cell_type": "code", 177 | "execution_count": 7, 178 | "metadata": {}, 179 | "outputs": [], 180 | "source": [ 181 | "l0 = Label('Star Nest by Pablo Roman Andrioli', x=10, y=10, color='cyan')" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": null, 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [] 190 | }, 191 | { 192 | "cell_type": "code", 193 | "execution_count": 8, 194 | "metadata": {}, 195 | "outputs": [], 196 | "source": [ 197 | "@app.event\n", 198 | "def render(ct, dt):\n", 199 | " \n", 200 | " st.render(ct, dt)\n", 201 | " l0.render()" 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": null, 207 | "metadata": {}, 208 | "outputs": [], 209 | "source": [] 210 | }, 211 | { 212 | "cell_type": "code", 213 | "execution_count": 9, 214 | "metadata": {}, 215 | "outputs": [], 216 | "source": [ 217 | "sample = Sample('../docs/_static/audio/tb303.5.ogg', loop=True, amp=8.)\n", 218 | "sample.play()" 219 | ] 220 | }, 221 | { 222 | "cell_type": "code", 223 | "execution_count": null, 224 | "metadata": {}, 225 | "outputs": [], 226 | "source": [] 227 | }, 228 | { 229 | "cell_type": "code", 230 | "execution_count": 10, 231 | "metadata": {}, 232 | "outputs": [ 233 | { 234 | "data": { 235 | "application/vnd.jupyter.widget-view+json": { 236 | "model_id": "c9775505c8a54cb0b3e9034fe630ad70", 237 | "version_major": 2, 238 | "version_minor": 0 239 | }, 240 | "text/plain": [ 241 | "Image(value=b'\\xff\\xd8\\xff\\xe0\\x00\\x10JFIF\\x00\\x01\\x01\\x00\\x00\\x01\\x00\\x01\\x00\\x00\\xff\\xdb\\x00C\\x00\\x08\\x06\\x0…" 242 | ] 243 | }, 244 | "metadata": {}, 245 | "output_type": "display_data" 246 | } 247 | ], 248 | "source": [ 249 | "app.run()" 250 | ] 251 | }, 252 | { 253 | "cell_type": "code", 254 | "execution_count": null, 255 | "metadata": {}, 256 | "outputs": [], 257 | "source": [] 258 | } 259 | ], 260 | "metadata": { 261 | "kernelspec": { 262 | "display_name": "Python 3", 263 | "language": "python", 264 | "name": "python3" 265 | }, 266 | "language_info": { 267 | "codemirror_mode": { 268 | "name": "ipython", 269 | "version": 3 270 | }, 271 | "file_extension": ".py", 272 | "mimetype": "text/x-python", 273 | "name": "python", 274 | "nbconvert_exporter": "python", 275 | "pygments_lexer": "ipython3", 276 | "version": "3.8.5" 277 | } 278 | }, 279 | "nbformat": 4, 280 | "nbformat_minor": 2 281 | } 282 | -------------------------------------------------------------------------------- /examples/fonts/FreeLicense.txt: -------------------------------------------------------------------------------- 1 | KREATIVE SOFTWARE RELAY FONTS FREE USE LICENSE 2 | version 1.2f 3 | 4 | Permission is hereby granted, free of charge, to any person or entity (the "User") obtaining a copy of the included font files (the "Software") produced by Kreative Software, to utilize, display, embed, or redistribute the Software, subject to the following conditions: 5 | 6 | 1. The User may not sell copies of the Software for a fee. 7 | 8 | 1a. The User may give away copies of the Software free of charge provided this license and any documentation is included verbatim and credit is given to Kreative Korporation or Kreative Software. 9 | 10 | 2. The User may not modify, reverse-engineer, or create any derivative works of the Software. 11 | 12 | 3. Any Software carrying the following font names or variations thereof is not covered by this license and may not be used under the terms of this license: Jewel Hill, Miss Diode n Friends, This is Beckie's font! 13 | 14 | 3a. Any Software carrying a font name ending with the string "Pro CE" is not covered by this license and may not be used under the terms of this license. 15 | 16 | 4. This license becomes null and void if any of the above conditions are not met. 17 | 18 | 5. Kreative Software reserves the right to change this license at any time without notice. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE SOFTWARE OR FROM OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /examples/fonts/PetMe64.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/fonts/PetMe64.ttf -------------------------------------------------------------------------------- /examples/images/alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/images/alien.png -------------------------------------------------------------------------------- /examples/images/keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/images/keyboard.png -------------------------------------------------------------------------------- /examples/images/moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/images/moon.png -------------------------------------------------------------------------------- /examples/images/ship1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/images/ship1.png -------------------------------------------------------------------------------- /examples/images/ship2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/images/ship2.png -------------------------------------------------------------------------------- /examples/images/stars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/images/stars.png -------------------------------------------------------------------------------- /examples/images/yellow-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/images/yellow-circle.png -------------------------------------------------------------------------------- /examples/lego_3d.py: -------------------------------------------------------------------------------- 1 | """ 2 | examples/lego-3d.py 3 | 4 | Copyright (c) 2022, Nir Aides - nir.8bit@gmail.com 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | 28 | import logging 29 | import random 30 | import struct 31 | import time 32 | import glm 33 | import sys 34 | import os 35 | 36 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 37 | 38 | from jupylet.label import Label 39 | from jupylet.app import App 40 | from jupylet.state import State 41 | from jupylet.loader import load_blender_gltf 42 | 43 | 44 | logger = logging.getLogger() 45 | 46 | 47 | app = App(768, 512) 48 | 49 | scene = load_blender_gltf('./scenes/lego/lego.gltf') 50 | scene.shadows = True 51 | 52 | camera = scene.cameras['Camera'] 53 | 54 | brick = scene.meshes['brick.green'] 55 | 56 | 57 | state = State( 58 | 59 | capslock = False, 60 | shift = False, 61 | alt = False, 62 | 63 | up = False, 64 | down = False, 65 | right = False, 66 | left = False, 67 | 68 | key_w = False, 69 | key_s = False, 70 | key_a = False, 71 | key_d = False, 72 | 73 | lv = glm.vec3(0), 74 | av = glm.vec3(0), 75 | ) 76 | 77 | 78 | @app.event 79 | def key_event(key, action, modifiers): 80 | logger.info('Enter key_event(key=%r, action=%r, modifiers=%r).', key, action, modifiers) 81 | 82 | keys = app.window.keys 83 | 84 | value = action != keys.ACTION_RELEASE 85 | 86 | if key == keys.CAPS_LOCK and value: 87 | state.capslock = not state.capslock 88 | 89 | state.alt = modifiers.alt 90 | state.shift = modifiers.shift 91 | 92 | if key == keys.SPACE: 93 | state.lv *= 0. 94 | state.av *= 0. 95 | 96 | if key == keys.UP: 97 | state.up = value 98 | 99 | if key == keys.DOWN: 100 | state.down = value 101 | 102 | if key == keys.LEFT: 103 | state.left = value 104 | 105 | if key == keys.RIGHT: 106 | state.right = value 107 | 108 | if key == keys.W: 109 | state.key_w = value 110 | 111 | if key == keys.S: 112 | state.key_s = value 113 | 114 | if key == keys.A: 115 | state.key_a = value 116 | 117 | if key == keys.D: 118 | state.key_d = value 119 | 120 | 121 | obj = brick if state.capslock else camera 122 | 123 | linear_acceleration = 1 / 2 124 | angular_acceleration = 1 / 24 125 | 126 | 127 | @app.run_me_every(1/48) 128 | def move_object(ct, dt): 129 | 130 | global obj 131 | 132 | obj = brick if state.capslock else camera 133 | sign = -1 if obj is camera else 1 134 | 135 | if state.right and state.shift: 136 | state.av.z += angular_acceleration * sign 137 | 138 | if state.right and not state.shift: 139 | state.av.y -= angular_acceleration 140 | 141 | if state.left and state.shift: 142 | state.av.z -= angular_acceleration * sign 143 | 144 | if state.left and not state.shift: 145 | state.av.y += angular_acceleration 146 | 147 | if state.up: 148 | state.av.x -= angular_acceleration 149 | 150 | if state.down: 151 | state.av.x += angular_acceleration 152 | 153 | if state.key_w and state.alt: 154 | state.lv.y += linear_acceleration 155 | 156 | if state.key_w and not state.alt: 157 | state.lv.z += linear_acceleration * sign 158 | 159 | if state.key_s and state.alt: 160 | state.lv.y -= linear_acceleration 161 | 162 | if state.key_s and not state.alt: 163 | state.lv.z -= linear_acceleration * sign 164 | 165 | if state.key_a: 166 | state.lv.x += linear_acceleration * sign 167 | 168 | if state.key_d: 169 | state.lv.x -= linear_acceleration * sign 170 | 171 | state.lv = glm.clamp(state.lv, -64, 64) 172 | state.av = glm.clamp(state.av, -64, 64) 173 | 174 | obj.move_local(dt * state.lv) 175 | 176 | obj.rotate_local(dt * state.av.x, (1, 0, 0)) 177 | obj.rotate_local(dt * state.av.y, (0, 1, 0)) 178 | obj.rotate_local(dt * state.av.z, (0, 0, 1)) 179 | 180 | state.lv *= 0.67 ** dt 181 | state.av *= 0.67 ** dt 182 | 183 | 184 | label0 = Label('Hello World!', color='white', font_size=12, x=10, y=74) 185 | label1 = Label('Hello World!', color='white', font_size=12, x=10, y=52) 186 | label2 = Label('Hello World!', color='white', font_size=12, x=10, y=30) 187 | label3 = Label('Hello World!', color='white', font_size=12, x=10, y=8) 188 | 189 | hello_world = Label('hello, world 3D!', color='cyan', font_size=24, x=575, y=10) 190 | 191 | 192 | @app.event 193 | def render(ct, dt): 194 | 195 | app.window.clear() 196 | 197 | scene.draw() 198 | 199 | label0.text = 'time to draw - %.2f ms' % (1000 * app._time2draw_rm) 200 | label1.text = 'up - %r' % obj.up 201 | label2.text = 'front - %r' % obj.front 202 | label3.text = 'position - %r' % obj.position 203 | 204 | label0.draw() 205 | label1.draw() 206 | label2.draw() 207 | label3.draw() 208 | 209 | hello_world.draw() 210 | 211 | 212 | if __name__ == '__main__': 213 | app.run() 214 | 215 | -------------------------------------------------------------------------------- /examples/piano.py: -------------------------------------------------------------------------------- 1 | """ 2 | examples/sounds_demo.py 3 | 4 | Copyright (c) 2022, Nir Aides - nir.8bit@gmail.com 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | 28 | import sys 29 | import os 30 | 31 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 32 | 33 | import jupylet.color 34 | 35 | from jupylet.app import App 36 | from jupylet.state import State 37 | from jupylet.label import Label 38 | from jupylet.sprite import Sprite 39 | from jupylet.shadertoy import Shadertoy, get_shadertoy_audio 40 | 41 | from jupylet.audio.bundle import * 42 | 43 | import numpy as np 44 | 45 | 46 | app = App(width=512, height=420, quality=100)#, log_level=logging.INFO) 47 | 48 | 49 | # 50 | # Default oscilloscope shader: 51 | # The code in the following cell is of a simple shadertoy shader that 52 | # displays an audio oscilloscope. Shadertoy (http://shadertoy.com/) are 53 | # an easy way to create graphic effects by programming the GPU directly: 54 | # 55 | st0 = Shadertoy(""" 56 | 57 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 58 | { 59 | // Normalized pixel coordinates (from 0 to 1) 60 | vec2 uv = fragCoord / iResolution.xy; 61 | 62 | // Time varying pixel color 63 | vec3 col = 0.2 + 0.2 * cos(iTime + uv.xyx + vec3(0, 2, 4)); 64 | 65 | float amp = texture(iChannel0, vec2(uv.x, 1.)).r; 66 | 67 | vec3 sig = vec3(0.00033 / max(pow(amp - uv.y, 2.), 1e-6)); 68 | 69 | sig *= vec3(.5, .5, 4.) / 2.; 70 | 71 | col += sig; 72 | 73 | // Output to screen 74 | fragColor = vec4(col,1.0); 75 | } 76 | 77 | """, 512, 256, 0, 420, 0, 'left', 'top') 78 | 79 | 80 | # 81 | # Audio visualization shader by Alban Fichet: 82 | # The code in the following cell is of a beautiful shadertoy shader by 83 | # graphics expert and researcher Alban Fichet (https://afichet.github.io/) 84 | # who kindly allowed its inclusion here under CC BY 4.0 license: 85 | # 86 | ba1 = Shadertoy(""" 87 | 88 | vec3 hue2rgb(in float h) { 89 | vec3 k = mod(vec3(5., 3., 1.) + vec3(h*360./60.), vec3(6.)); 90 | return vec3(1.) - clamp(min(k, vec3(4.) - k), vec3(0.), vec3(1.)); 91 | } 92 | 93 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 94 | { 95 | vec2 uv = fragCoord/iResolution.xy; 96 | 97 | float aspect = iResolution.x / iResolution.y; 98 | float blurr = 0.3; 99 | float sharpen = 1.7; 100 | 101 | vec2 maxWindow = vec2(3., 3./aspect); 102 | uv = mix(-maxWindow, maxWindow, uv); 103 | 104 | float r = dot(uv, uv); 105 | float theta = atan(uv.y, uv.x) + 3.14; 106 | 107 | float t = abs(2.*theta / (2.*3.14) - 1.); 108 | 109 | float signal = 2.0*texture(iChannel0,vec2(t,1.)).x; 110 | float ampl = 2.0*texture(iChannel0,vec2(0.8,.25)).x; 111 | 112 | float v = 1. - pow(smoothstep(0., blurr, abs(r - signal)), 0.02); 113 | float hue = pow(fract(abs(sin(theta/2.) * ampl)), sharpen); 114 | 115 | fragColor = vec4(v * hue2rgb(fract(hue + iTime/10.)), 1.0); 116 | } 117 | 118 | """) 119 | 120 | bb1 = Shadertoy(""" 121 | 122 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 123 | { 124 | vec2 uv = fragCoord/iResolution.xy; 125 | 126 | vec2 center = vec2(.5 + 0.15*sin(iTime)); 127 | float zoom = 1.02; 128 | 129 | vec4 prevParams = texture(iChannel0, (uv - center)/zoom + center); 130 | vec4 bB = texture(iChannel1, uv); 131 | 132 | fragColor = clamp(mix(prevParams, 4 * bB, 0.1), 0., 1.) ; 133 | } 134 | 135 | """) 136 | 137 | bb1.set_channel(0, bb1) 138 | bb1.set_channel(1, ba1) 139 | 140 | st1 = Shadertoy(""" 141 | 142 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 143 | { 144 | vec3 bA = texelFetch(iChannel0, ivec2(fragCoord), 0).rgb; 145 | vec3 bB = texelFetch(iChannel1, ivec2(fragCoord), 0).rgb; 146 | 147 | vec3 col_rgb = bB; 148 | 149 | col_rgb = col_rgb*exp2(3.5); 150 | 151 | fragColor = vec4(pow(col_rgb, vec3(1./2.2)), 1.); 152 | } 153 | 154 | """, 512, 256, 0, 420, 0, 'left', 'top') 155 | 156 | 157 | st1.set_channel(0, ba1) 158 | st1.set_channel(1, bb1) 159 | 160 | keyboard_layout = Sprite('images/keyboard.png', x=256, y=82, scale=0.5) 161 | label0 = Label('amp: %.2f' % get_master_volume(), anchor_x='right', x=app.width - 10, y=174) 162 | label1 = Label('use ↑ ↓ to control volume', x=10, y=194) 163 | label2 = Label('tap SPACE to change shader', x=10, y=174) 164 | 165 | state = State( 166 | 167 | up = False, 168 | down = False, 169 | shader = 0, 170 | ) 171 | 172 | keys = app.window.keys 173 | 174 | keyboard = { 175 | 176 | keys.Z: note.C, 177 | keys.S: note.Cs, 178 | keys.X: note.D, 179 | keys.D: note.Ds, 180 | keys.C: note.E, 181 | keys.V: note.F, 182 | keys.G: note.Fs, 183 | keys.B: note.G, 184 | keys.H: note.Gs, 185 | keys.N: note.A, 186 | keys.J: note.As, 187 | keys.M: note.B, 188 | 189 | keys.Q: note.C5, 190 | 50: note.Cs5, 191 | keys.W: note.D5, 192 | 51: note.Ds5, 193 | keys.E: note.E5, 194 | keys.R: note.F5, 195 | 53: note.Fs5, 196 | keys.T: note.G5, 197 | 54: note.Gs5, 198 | keys.Y: note.A5, 199 | 55: note.As5, 200 | keys.U: note.B5, 201 | 202 | keys.I: note.C6, 203 | 57: note.Cs6, 204 | keys.O: note.D6, 205 | 48: note.Ds6, 206 | keys.P: note.E6, 207 | } 208 | 209 | 210 | _keyd = {} 211 | 212 | @app.event 213 | def key_event(key, action, modifiers): 214 | 215 | keys = app.window.keys 216 | value = action == keys.ACTION_PRESS 217 | 218 | if key == keys.UP: 219 | state.up = value 220 | 221 | if key == keys.DOWN: 222 | state.down = value 223 | 224 | if key == keys.SPACE and action == keys.ACTION_PRESS: 225 | state.shader = 1 - state.shader 226 | 227 | if action == keys.ACTION_PRESS and key in keyboard: 228 | assert key not in _keyd 229 | _keyd[key] = tb303.play_poly(note=keyboard[key]) 230 | 231 | if action == keys.ACTION_RELEASE and key in keyboard: 232 | _keyd.pop(key).play_release() 233 | 234 | 235 | @app.run_me_every(1/24) 236 | def modify_volume(ct, dt): 237 | 238 | s = 2 ** dt 239 | amp = get_master_volume() 240 | 241 | if state.up: 242 | amp *= s 243 | set_master_volume(amp) 244 | label0.text = 'amp: %.2f' % amp 245 | 246 | if state.down: 247 | amp /= s 248 | set_master_volume(amp) 249 | label0.text = 'amp: %.2f' % amp 250 | 251 | 252 | @app.event 253 | def render(ct, dt): 254 | 255 | app.window.clear(color='#555') 256 | 257 | keyboard_layout.draw() 258 | 259 | if state.shader == 0: 260 | st0.set_channel(0, *get_shadertoy_audio(amp=5)) 261 | st0.render(ct, dt) 262 | else: 263 | ba1.set_channel(0, *get_shadertoy_audio(amp=5)) 264 | st1.render(ct, dt) 265 | 266 | label0.draw() 267 | label1.draw() 268 | label2.draw() 269 | 270 | 271 | app.set_midi_sound(tb303) 272 | 273 | set_latency('lowest') 274 | set_effects(ConvolutionReverb('./sounds/impulses/MaesHowe.flac')) 275 | 276 | xylo = Sample('sounds/VCSL/Xylophone/Xylophone - Medium Mallets.sfz') 277 | xylo.amp = 8 278 | 279 | 280 | @app.sonic_live_loop 281 | async def loop0(): 282 | 283 | use(tb303, duration=2, amp=0.15) 284 | 285 | play(note.C2) 286 | await sleep(3) 287 | 288 | play(note.E2) 289 | await sleep(3) 290 | 291 | play(note.C2) 292 | await sleep(6) 293 | 294 | 295 | @app.sonic_live_loop 296 | async def loop1(): 297 | 298 | use(xylo, amp=8) 299 | 300 | play(note.C5) 301 | await sleep(1) 302 | 303 | play(note.E5) 304 | await sleep(1) 305 | 306 | play(note.G5) 307 | await sleep(1) 308 | 309 | 310 | if __name__ == '__main__': 311 | app.run(1/30) 312 | 313 | -------------------------------------------------------------------------------- /examples/pong-start.state: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/pong-start.state -------------------------------------------------------------------------------- /examples/scenes/lego/lego.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/scenes/lego/lego.bin -------------------------------------------------------------------------------- /examples/scenes/lego/lego.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/scenes/lego/lego.blend -------------------------------------------------------------------------------- /examples/scenes/moon/TexturesCom_Leather_Plain_1K_albedo_blue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/scenes/moon/TexturesCom_Leather_Plain_1K_albedo_blue.jpg -------------------------------------------------------------------------------- /examples/scenes/moon/TexturesCom_Leather_Plain_1K_normal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/scenes/moon/TexturesCom_Leather_Plain_1K_normal.jpg -------------------------------------------------------------------------------- /examples/scenes/moon/TexturesCom_Leather_Plain_1K_roughness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/scenes/moon/TexturesCom_Leather_Plain_1K_roughness.png -------------------------------------------------------------------------------- /examples/scenes/moon/alien-moon.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/scenes/moon/alien-moon.bin -------------------------------------------------------------------------------- /examples/scenes/moon/alien-moon.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/scenes/moon/alien-moon.blend -------------------------------------------------------------------------------- /examples/scenes/moon/eye.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/scenes/moon/eye.jpg -------------------------------------------------------------------------------- /examples/scenes/moon/lroc_color_poles_4k.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/scenes/moon/lroc_color_poles_4k.jpg -------------------------------------------------------------------------------- /examples/scenes/moon/moon-normal-map.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/scenes/moon/moon-normal-map.jpg -------------------------------------------------------------------------------- /examples/scenes/moon/nebula/nebulaBK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/scenes/moon/nebula/nebulaBK.png -------------------------------------------------------------------------------- /examples/scenes/moon/nebula/nebulaDN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/scenes/moon/nebula/nebulaDN.png -------------------------------------------------------------------------------- /examples/scenes/moon/nebula/nebulaFT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/scenes/moon/nebula/nebulaFT.png -------------------------------------------------------------------------------- /examples/scenes/moon/nebula/nebulaLF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/scenes/moon/nebula/nebulaLF.png -------------------------------------------------------------------------------- /examples/scenes/moon/nebula/nebulaRT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/scenes/moon/nebula/nebulaRT.png -------------------------------------------------------------------------------- /examples/scenes/moon/nebula/nebulaUP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/scenes/moon/nebula/nebulaUP.png -------------------------------------------------------------------------------- /examples/shadertoy_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | examples/shadertoy_demo.py 3 | 4 | This script demonstrates how to display shadertoys. Check out the 5 | documentation for more information: 6 | https://jupylet.readthedocs.io/en/latest/programmers_reference_guide/graphics-3d.html#shadertoys 7 | 8 | The code displays the beautiful Star Nest Shadertoy by Pablo Roman Andrioli 9 | https://www.shadertoy.com/view/XlfGRj. 10 | """ 11 | 12 | 13 | import sys 14 | import os 15 | 16 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 17 | 18 | from jupylet.app import App 19 | from jupylet.label import Label 20 | from jupylet.shadertoy import Shadertoy 21 | 22 | from jupylet.audio.bundle import * 23 | 24 | 25 | app = App(width=533, height=300) 26 | 27 | 28 | st = Shadertoy(""" 29 | 30 | // Star Nest by Pablo Roman Andrioli 31 | 32 | // This content is under the MIT License. 33 | 34 | #define iterations 17 35 | #define formuparam 0.53 36 | 37 | #define volsteps 20 38 | #define stepsize 0.1 39 | 40 | #define zoom 0.800 41 | #define tile 0.850 42 | #define speed 0.010 43 | 44 | #define brightness 0.0015 45 | #define darkmatter 0.300 46 | #define distfading 0.730 47 | #define saturation 0.850 48 | 49 | 50 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 51 | { 52 | //get coords and direction 53 | vec2 uv=fragCoord.xy/iResolution.xy-.5; 54 | uv.y*=iResolution.y/iResolution.x; 55 | vec3 dir=vec3(uv*zoom,1.); 56 | float time=iTime*speed+.25; 57 | 58 | //mouse rotation 59 | float a1=.5+iMouse.x/iResolution.x*2.; 60 | float a2=.8+iMouse.y/iResolution.y*2.; 61 | mat2 rot1=mat2(cos(a1),sin(a1),-sin(a1),cos(a1)); 62 | mat2 rot2=mat2(cos(a2),sin(a2),-sin(a2),cos(a2)); 63 | dir.xz*=rot1; 64 | dir.xy*=rot2; 65 | vec3 from=vec3(1.,.5,0.5); 66 | from+=vec3(time*2.,time,-2.); 67 | from.xz*=rot1; 68 | from.xy*=rot2; 69 | 70 | //volumetric rendering 71 | float s=0.1,fade=1.; 72 | vec3 v=vec3(0.); 73 | for (int r=0; r6) fade*=1.-dm; // dark matter, don't render near 85 | //v+=vec3(dm,dm*.5,0.); 86 | v+=fade; 87 | v+=vec3(s,s*s,s*s*s*s)*a*brightness*fade; // coloring based on distance 88 | fade*=distfading; // distance fading 89 | s+=stepsize; 90 | } 91 | v=mix(vec3(length(v)),v,saturation); //color adjust 92 | fragColor = vec4(v*.01,1.); 93 | 94 | } 95 | 96 | """, 533, 300)#, -1066, -600)#, 0, 'center', 'center') 97 | 98 | 99 | l0 = Label('Star Nest by Pablo Roman Andrioli', x=10, y=10, color='cyan') 100 | 101 | 102 | @app.event 103 | def render(ct, dt): 104 | 105 | st.render(ct, dt) 106 | l0.render() 107 | 108 | 109 | sample = Sample('../docs/_static/audio/tb303.5.ogg', loop=True, amp=8.) 110 | sample.play() 111 | 112 | 113 | if __name__ == '__main__': 114 | app.run() 115 | 116 | -------------------------------------------------------------------------------- /examples/sounds/VCSL/README.md: -------------------------------------------------------------------------------- 1 | # VCSL 2 | ![logo](https://github.com/sgossner/VCSL/raw/master/Assets/VCSL_Color_128x128.png "VCSL Logo") 3 | 4 | The Versilian Community Sample Library is an open CC0 general-purpose sample library created by Versilian Studios LLC for the purpose of introducing a set of quality, publicly available samples suitable for use in software and media of all kinds. This library is intended to be a broader expansion to the VSCO 2 CE sample set. 5 | 6 | This collection is under a Creative Commons 0 license. Essentially it's Public Domain- you can do whatever you want with these sounds (even make commercial software), no royalties, no credit, no special terms. 7 | 8 | The easiest way to keep up to date is to set up a local copy of the repository with Github Desktop, then just 'Fetch origin' whenever something new comes out. Install Github Desktop, then back on the VCSL main branch page click 'Clone or Download' then 'Open in Desktop'. 9 | 10 | Want to stay updated? Join the mailing list and we'll send you an e-mail every time the project is updated- 11 | http://www.versilstudios.net/sendy/subscription?f=b763a892L4k9FXaEnJ3glhJUq3yyF71hesBFROqKTz08slZriGNWCYyV7OLLLPCTd8Dn 12 | 13 | # Design Philosophy 14 | The selection of samples for VCSL is designed specifically to suit lightweight sample library creation. A greater emphasis is placed on pitch fidelity (with most being wholetone sampled if possible) and consistency, but less on more 'professional' features such as many round robins or velocity layers, except where warranted (e.g. percussion). As such, most articulations only have a single round robin and two to three velocity layers, although like all things in life, there are exceptions to that. On average, this means each instrument weighs about 20-75 MB in raw .wav format, a small but comfortable size for adequate reproduction. 15 | 16 | This goal of this set is to provide a consistently-recorded and convenient starting place for factory libraries, sound designers, and hobbyist developers to work from. 17 | 18 | # Inclusion of Previous Works 19 | This set shall include the bulk of the VSCO 2 CE sample set, including some reworked and re-curated selections to better fit the design philosophy. It also shall include many instruments not featured in the CE sample set, but found in other products or the larger VSCO 2 Pro sample set. Lastly, new recordings will be produced ongoing throughout the next few years to continually add and update content in this sample set. Content added to the set will not be removed; new content will simply be added on in the form of new instrument options. 20 | 21 | # 3rd Party Contributions 22 | 3rd party developers are welcome to make additions to the sample set. The following standards are highly recommended: 23 | - Samples shall be named in a human-readable format. The recommended syntax is- `[Instrument]_[Articulation]_[Mic]_vl[Velocity]_rr[RR].wav.` 24 | - Samples shall be made available at 44.1 or 48 kHz sample rate, and either 16 or 24-bit depth, as uncompressed .wav files. 25 | - Samples shall be recorded in stereo if possible, unless (1) idiomatic (e.g. harmonicas, solo vox) or (2) impractical to do so. Most of the sample set is recorded with spaced pair or NOS, but other arrays are acceptable so long as the stereo image is reasonable. 26 | - Samples shall preferably be recorded in a clean, 'mid-close' position, typically 0.5-3 meters from the performer(s), in an acoustically neutral space with as little background noise as possible. These recordings shall not be processed with the exception of (1) tuning, (2) mild eq'ing to fix microphone problems, and (3) noise reduction (if necessary). 27 | - Samples shall be organized in folders, one per instrument, preferably with sub-folders, one per articulation, if there are many samples. 28 | - Any metadata relating to the samples should be included (brand/model, performer, mics, space) if possible, but not necessary. 29 | 30 | Contributions may be made by creating a fork, and then requesting that fork to be merged back into the main set. In addition, forks that go off and do their 'own thing' are welcome as well (such as a 3rd party SFZ conversion). 31 | -------------------------------------------------------------------------------- /examples/sounds/VCSL/Xylophone/Xylophone - Medium Mallets.sfz: -------------------------------------------------------------------------------- 1 | // Generation Options: --notuning --volume 5 --release 15 2 | 3 | 4 | ampeg_attack=0.004000 5 | ampeg_release=15.000000 6 | 7 | 8 | lovel=0 9 | hivel=83 10 | sample=Xylophone/Medium Mallets/Xylo_Medium_G3_pp_01_far.ogg 11 | pitch_keycenter=55 12 | lokey=55 13 | hikey=57 14 | volume=22.810545 15 | 16 | 17 | lovel=84 18 | hivel=127 19 | sample=Xylophone/Medium Mallets/Xylo_Medium_G3_ff_01_far.ogg 20 | pitch_keycenter=55 21 | lokey=55 22 | hikey=57 23 | volume=1.425835 24 | 25 | 26 | lovel=0 27 | hivel=83 28 | sample=Xylophone/Medium Mallets/Xylo_Medium_C4_pp_01_far.ogg 29 | pitch_keycenter=60 30 | lokey=58 31 | hikey=63 32 | volume=12.381368 33 | 34 | 35 | lovel=84 36 | hivel=127 37 | sample=Xylophone/Medium Mallets/Xylo_Medium_C4_ff_01_far.ogg 38 | pitch_keycenter=60 39 | lokey=58 40 | hikey=63 41 | volume=1.863334 42 | 43 | 44 | lovel=0 45 | hivel=83 46 | sample=Xylophone/Medium Mallets/Xylo_Medium_G4_pp_01_far.ogg 47 | pitch_keycenter=67 48 | lokey=64 49 | hikey=69 50 | volume=10.140772 51 | 52 | 53 | lovel=84 54 | hivel=127 55 | sample=Xylophone/Medium Mallets/Xylo_Medium_G4_ff_01_far.ogg 56 | pitch_keycenter=67 57 | lokey=64 58 | hikey=69 59 | volume=-1.767685 60 | 61 | 62 | lovel=0 63 | hivel=83 64 | sample=Xylophone/Medium Mallets/Xylo_Medium_C5_pp_01_far.ogg 65 | pitch_keycenter=72 66 | lokey=70 67 | hikey=75 68 | volume=13.198534 69 | 70 | 71 | lovel=84 72 | hivel=127 73 | sample=Xylophone/Medium Mallets/Xylo_Medium_C5_ff_01_far.ogg 74 | pitch_keycenter=72 75 | lokey=70 76 | hikey=75 77 | volume=2 78 | 79 | 80 | lovel=0 81 | hivel=83 82 | sample=Xylophone/Medium Mallets/Xylo_Medium_G5_pp_01_far.ogg 83 | pitch_keycenter=79 84 | lokey=76 85 | hikey=81 86 | volume=15.197086 87 | 88 | 89 | lovel=84 90 | hivel=127 91 | sample=Xylophone/Medium Mallets/Xylo_Medium_G5_ff_01_far.ogg 92 | pitch_keycenter=79 93 | lokey=76 94 | hikey=81 95 | volume=1.039045 96 | 97 | 98 | lovel=0 99 | hivel=83 100 | sample=Xylophone/Medium Mallets/Xylo_Medium_C6_pp_01_far.ogg 101 | pitch_keycenter=84 102 | lokey=82 103 | hikey=87 104 | volume=15.817703 105 | 106 | 107 | lovel=84 108 | hivel=127 109 | sample=Xylophone/Medium Mallets/Xylo_Medium_C6_ff_01_far.ogg 110 | pitch_keycenter=84 111 | lokey=82 112 | hikey=87 113 | volume=3.136201 114 | 115 | 116 | lovel=0 117 | hivel=83 118 | sample=Xylophone/Medium Mallets/Xylo_Medium_G6_pp_01_far.ogg 119 | pitch_keycenter=91 120 | lokey=88 121 | hikey=93 122 | volume=21.807102 123 | 124 | 125 | lovel=84 126 | hivel=127 127 | sample=Xylophone/Medium Mallets/Xylo_Medium_G6_ff_01_far.ogg 128 | pitch_keycenter=91 129 | lokey=88 130 | hikey=93 131 | volume=2.679822 132 | 133 | 134 | lovel=0 135 | hivel=83 136 | sample=Xylophone/Medium Mallets/Xylo_Medium_C7_pp_01_far.ogg 137 | pitch_keycenter=96 138 | lokey=94 139 | hikey=97 140 | volume=16.578977 141 | 142 | 143 | lovel=84 144 | hivel=127 145 | sample=Xylophone/Medium Mallets/Xylo_Medium_C7_ff_01_far.ogg 146 | pitch_keycenter=96 147 | lokey=94 148 | hikey=97 149 | volume=4.621578 150 | 151 | 152 | -------------------------------------------------------------------------------- /examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_C4_ff_01_far.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_C4_ff_01_far.ogg -------------------------------------------------------------------------------- /examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_C4_pp_01_far.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_C4_pp_01_far.ogg -------------------------------------------------------------------------------- /examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_C5_ff_01_far.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_C5_ff_01_far.ogg -------------------------------------------------------------------------------- /examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_C5_pp_01_far.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_C5_pp_01_far.ogg -------------------------------------------------------------------------------- /examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_C6_ff_01_far.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_C6_ff_01_far.ogg -------------------------------------------------------------------------------- /examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_C6_pp_01_far.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_C6_pp_01_far.ogg -------------------------------------------------------------------------------- /examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_C7_ff_01_far.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_C7_ff_01_far.ogg -------------------------------------------------------------------------------- /examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_C7_pp_01_far.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_C7_pp_01_far.ogg -------------------------------------------------------------------------------- /examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_G3_ff_01_far.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_G3_ff_01_far.ogg -------------------------------------------------------------------------------- /examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_G3_pp_01_far.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_G3_pp_01_far.ogg -------------------------------------------------------------------------------- /examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_G4_ff_01_far.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_G4_ff_01_far.ogg -------------------------------------------------------------------------------- /examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_G4_pp_01_far.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_G4_pp_01_far.ogg -------------------------------------------------------------------------------- /examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_G5_ff_01_far.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_G5_ff_01_far.ogg -------------------------------------------------------------------------------- /examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_G5_pp_01_far.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_G5_pp_01_far.ogg -------------------------------------------------------------------------------- /examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_G6_ff_01_far.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_G6_ff_01_far.ogg -------------------------------------------------------------------------------- /examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_G6_pp_01_far.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/sounds/VCSL/Xylophone/Xylophone/Medium Mallets/Xylo_Medium_G6_pp_01_far.ogg -------------------------------------------------------------------------------- /examples/sounds/pong-blip.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/sounds/pong-blip.wav -------------------------------------------------------------------------------- /examples/spaceship.py: -------------------------------------------------------------------------------- 1 | """ 2 | examples/spaceship.py 3 | 4 | Copyright (c) 2022, Nir Aides - nir.8bit@gmail.com 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | 28 | import logging 29 | import math 30 | import sys 31 | import os 32 | 33 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 34 | 35 | from jupylet.sprite import Sprite 36 | from jupylet.label import Label 37 | from jupylet.app import App 38 | 39 | 40 | logger = logging.getLogger() 41 | 42 | 43 | app = App() 44 | 45 | 46 | stars = Sprite('images/stars.png', scale=2.5) 47 | alien = Sprite('images/alien.png', scale=0.5) 48 | ship = Sprite('images/ship1.png', x=app.width/2, y=app.height/2, scale=0.5) 49 | moon = Sprite('images/moon.png', x=app.width-70, y=app.height-70, scale=0.5) 50 | 51 | circle = Sprite('images/yellow-circle.png', width=184) 52 | circle.opacity = 0. 53 | 54 | label = Label('hello, world', color='cyan', font_size=32, x=10, y=10) 55 | 56 | 57 | @app.event 58 | def mouse_position_event(x, y, dx, dy): 59 | logger.info('Enter mouse_position_event(%r, %r, %r, %r).', x, y, dx, dy) 60 | 61 | alien.x = x 62 | alien.y = y 63 | 64 | circle.x = x 65 | circle.y = y 66 | 67 | 68 | vx = 0 69 | vy = 0 70 | 71 | up = 0 72 | left = 0 73 | right = 0 74 | 75 | 76 | @app.run_me_every(1/60) 77 | def update_ship(ct, dt): 78 | 79 | global vx, vy 80 | 81 | if left: 82 | ship.angle += 128 * dt 83 | 84 | if right: 85 | ship.angle -= 128 * dt 86 | 87 | if up: 88 | vx += 3 * math.cos(math.radians(90 + ship.angle)) 89 | vy += 3 * math.sin(math.radians(90 + ship.angle)) 90 | 91 | ship.x += vx * dt 92 | ship.y += vy * dt 93 | 94 | ship.wrap_position(app.width, app.height) 95 | 96 | if len(ship.collisions_with(alien)) > 0: 97 | circle.opacity = 0.5 98 | else: 99 | circle.opacity = 0.0 100 | 101 | 102 | @app.run_me_every(1/60) 103 | def rotate(ct, dt): 104 | 105 | alien.angle += 64 * dt 106 | 107 | 108 | @app.event 109 | def key_event(key, action, modifiers): 110 | logger.info('Enter key_event(key=%r, action=%r, modifiers=%r).', key, action, modifiers) 111 | 112 | global up, left, right 113 | 114 | keys = app.window.keys 115 | 116 | if action == keys.ACTION_PRESS: 117 | 118 | if key == keys.UP: 119 | ship.image = 'images/ship2.png' 120 | up = True 121 | 122 | if key == keys.LEFT: 123 | left = True 124 | 125 | if key == keys.RIGHT: 126 | right = True 127 | 128 | if action == keys.ACTION_RELEASE: 129 | 130 | if key == keys.UP: 131 | ship.image = 'images/ship1.png' 132 | up = False 133 | 134 | if key == keys.LEFT: 135 | left = False 136 | 137 | if key == keys.RIGHT: 138 | right = False 139 | 140 | 141 | @app.event 142 | def render(ct, dt): 143 | #logger.debug('Enter render(%r, %r).', ct, dt) 144 | 145 | app.window.clear() 146 | 147 | stars.draw() 148 | moon.draw() 149 | 150 | circle.draw() 151 | alien.draw() 152 | ship.draw() 153 | 154 | label.draw() 155 | 156 | 157 | if __name__ == '__main__': 158 | app.run() 159 | 160 | -------------------------------------------------------------------------------- /examples/spaceship_3d.py: -------------------------------------------------------------------------------- 1 | """ 2 | examples/spaceship_3d.py 3 | 4 | Copyright (c) 2022, Nir Aides - nir.8bit@gmail.com 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | 28 | import logging 29 | import random 30 | import struct 31 | import time 32 | import glm 33 | import sys 34 | import os 35 | 36 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 37 | 38 | from jupylet.label import Label 39 | from jupylet.app import App 40 | from jupylet.state import State 41 | from jupylet.model import Skybox 42 | from jupylet.loader import load_blender_gltf 43 | 44 | 45 | logger = logging.getLogger() 46 | 47 | 48 | if __name__ == '__main__': 49 | mode = 'window' 50 | else: 51 | mode = 'hidden' 52 | 53 | app = App(768, 512, mode=mode)#, log_level=logging.INFO) 54 | 55 | scene = load_blender_gltf('./scenes/moon/alien-moon.gltf') 56 | 57 | scene.skybox = Skybox('./scenes/moon/nebula/nebula*.png', intensity=2., flip_left_right=True) 58 | 59 | scene.shadows = True 60 | 61 | 62 | sun = scene.lights['Light.Sun'] 63 | sun.shadowmaps_depths = [1., 0.12, 0.04, 0.015, 0.0] 64 | 65 | moon = scene.meshes['Moon'] 66 | moon.shadow_bias = 0.2 67 | 68 | camera = scene.cameras['Camera'] 69 | 70 | 71 | state = State( 72 | 73 | capslock = False, 74 | shift = False, 75 | alt = False, 76 | 77 | up = False, 78 | down = False, 79 | right = False, 80 | left = False, 81 | 82 | key_w = False, 83 | key_s = False, 84 | key_a = False, 85 | key_d = False, 86 | 87 | lv = glm.vec3(0), 88 | av = glm.vec3(0), 89 | ) 90 | 91 | 92 | @app.event 93 | def key_event(key, action, modifiers): 94 | logger.info('Enter key_event(key=%r, action=%r, modifiers=%r).', key, action, modifiers) 95 | 96 | keys = app.window.keys 97 | 98 | value = action != keys.ACTION_RELEASE 99 | 100 | if key == keys.CAPS_LOCK and value: 101 | state.capslock = not state.capslock 102 | 103 | state.alt = modifiers.alt 104 | state.shift = modifiers.shift 105 | 106 | if key == keys.SPACE: 107 | state.lv *= 0. 108 | state.av *= 0. 109 | 110 | if key == keys.UP: 111 | state.up = value 112 | 113 | if key == keys.DOWN: 114 | state.down = value 115 | 116 | if key == keys.LEFT: 117 | state.left = value 118 | 119 | if key == keys.RIGHT: 120 | state.right = value 121 | 122 | if key == keys.W: 123 | state.key_w = value 124 | 125 | if key == keys.S: 126 | state.key_s = value 127 | 128 | if key == keys.A: 129 | state.key_a = value 130 | 131 | if key == keys.D: 132 | state.key_d = value 133 | 134 | 135 | obj = moon if state.capslock else camera 136 | 137 | linear_acceleration = 1 / 2 138 | angular_acceleration = 1 / 24 139 | 140 | 141 | @app.run_me_every(1/48) 142 | def move_object(ct, dt): 143 | 144 | global obj 145 | 146 | obj = moon if state.capslock else camera 147 | sign = -1 if obj is camera else 1 148 | 149 | if state.right and state.shift: 150 | state.av.z += angular_acceleration * sign 151 | 152 | if state.right and not state.shift: 153 | state.av.y -= angular_acceleration 154 | 155 | if state.left and state.shift: 156 | state.av.z -= angular_acceleration * sign 157 | 158 | if state.left and not state.shift: 159 | state.av.y += angular_acceleration 160 | 161 | if state.up: 162 | state.av.x -= angular_acceleration 163 | 164 | if state.down: 165 | state.av.x += angular_acceleration 166 | 167 | if state.key_w and state.alt: 168 | state.lv.y += linear_acceleration 169 | 170 | if state.key_w and not state.alt: 171 | state.lv.z += linear_acceleration * sign 172 | 173 | if state.key_s and state.alt: 174 | state.lv.y -= linear_acceleration 175 | 176 | if state.key_s and not state.alt: 177 | state.lv.z -= linear_acceleration * sign 178 | 179 | if state.key_a: 180 | state.lv.x += linear_acceleration * sign 181 | 182 | if state.key_d: 183 | state.lv.x -= linear_acceleration * sign 184 | 185 | state.lv = glm.clamp(state.lv, -64, 64) 186 | state.av = glm.clamp(state.av, -64, 64) 187 | 188 | obj.move_local(dt * state.lv) 189 | 190 | obj.rotate_local(dt * state.av.x, (1, 0, 0)) 191 | obj.rotate_local(dt * state.av.y, (0, 1, 0)) 192 | obj.rotate_local(dt * state.av.z, (0, 0, 1)) 193 | 194 | state.lv *= 0.67 ** dt 195 | state.av *= 0.67 ** dt 196 | 197 | 198 | label0 = Label('Hello World!', color='white', font_size=14, x=10, y=74) 199 | label1 = Label('Hello World!', color='white', font_size=14, x=10, y=52) 200 | label2 = Label('Hello World!', color='white', font_size=14, x=10, y=30) 201 | label3 = Label('Hello World!', color='white', font_size=14, x=10, y=8) 202 | 203 | hello_world = Label('hello, world 3D!', color='cyan', font_size=24, x=575, y=10) 204 | 205 | 206 | @app.event 207 | def render(ct, dt): 208 | 209 | app.window.clear(blue=0.3) 210 | 211 | scene.draw() 212 | 213 | label0.text = 'time to draw - %0.3f ms' % (1000 * app._time2draw_rm) 214 | label1.text = 'up - %0.3f, %0.3f, %0.3f' % tuple(obj.up) 215 | label2.text = 'front - %0.3f, %0.3f, %0.3f' % tuple(obj.front) 216 | label3.text = 'position - %0.3f, %0.3f, %0.3f' % tuple(obj.position) 217 | 218 | label0.draw() 219 | label1.draw() 220 | label2.draw() 221 | label3.draw() 222 | 223 | hello_world.draw() 224 | 225 | 226 | @app.schedule_interval(1/30) 227 | def spin(ct, dt): 228 | scene.meshes['Alien'].rotate_local(-0.5 * dt, (0, 0, 1)) 229 | 230 | 231 | if __name__ == '__main__': 232 | app.run() 233 | 234 | -------------------------------------------------------------------------------- /examples/spectrum_analyzer.state: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/examples/spectrum_analyzer.state -------------------------------------------------------------------------------- /jupylet/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | jupylet/__init__.py 3 | 4 | Copyright (c) 2022, Nir Aides - nir.8bit@gmail.com 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | 28 | import platform 29 | import sys 30 | import os 31 | import re 32 | 33 | from .env import is_remote, has_display, is_numpy_openblas, is_jupyter 34 | 35 | 36 | VERSION = '0.9.2' 37 | 38 | 39 | if platform.system() == 'Linux' and not has_display(): 40 | setattr(sys, 'is_pyglet_doc_run', True) 41 | 42 | 43 | # 44 | # Workaround segmentation fault when calling np.linalg.inv() in 45 | # mutlithreaded app. 46 | # 47 | if platform.system() == 'Darwin': 48 | if 'numpy' in sys.modules and is_numpy_openblas(): 49 | sys.stderr.write( 50 | 'WARNING: numpy was imported before jupylet. ' + 51 | 'On macOS you should import jupylet first to let it work around ' + 52 | 'a bug in the algebra libraries used by numpy that may cause the ' + 53 | 'program to exit.' + '\n' 54 | ) 55 | 56 | os.environ['OPENBLAS_NUM_THREADS'] = '1' 57 | 58 | 59 | if platform.system() == 'Darwin': 60 | try: 61 | import moderngl 62 | moderngl.create_standalone_context().release() 63 | except NameError: 64 | if is_jupyter(): 65 | raise SystemError('ERROR: Library libcxx is missing. You can install it by running: conda install libcxx') 66 | sys.stderr.write('ERROR: Library libcxx is missing. You can install it by running: conda install libcxx\n') 67 | sys.exit(0) 68 | 69 | 70 | # 71 | # Work around problem in pip install jupyter in python 3.8 as described in: 72 | # https://github.com/jupyter/notebook/issues/4980#issuecomment-600992296 73 | # 74 | if platform.system() == 'Windows' and sys.version_info >= (3, 8): 75 | if sys.argv[-2:] == ['-m', 'postinstall']: 76 | os.system(r'python %s\Scripts\pywin32_postinstall.py -install' % os.__file__.rsplit('\\', 2)[0]) 77 | sys.exit(0) 78 | 79 | 80 | def download_url(url, progress=False): 81 | 82 | if not progress: 83 | r = urllib.request.urlopen(GITHUB_MASTER_URL) 84 | return zipfile.ZipFile(io.BytesIO(r.read())) 85 | 86 | import tqdm 87 | 88 | pbar = tqdm.tqdm(unit='B', unit_scale=True, desc=url.split('/')[-1]) 89 | 90 | def update(b=1, bsize=1, tsize=None): 91 | if tsize is not None: 92 | pbar.total = tsize 93 | pbar.update(b * bsize - pbar.n) 94 | 95 | path, headers = urllib.request.urlretrieve(url, reporthook=update) 96 | pbar.close() 97 | 98 | return zipfile.ZipFile(open(path, 'rb')) 99 | 100 | 101 | def extract_master(zf, to='jupylet', noisy=False): 102 | 103 | for p0 in zf.namelist(): 104 | p1 = re.sub(r'jupylet-master', to, p0) 105 | if p1[-1] == '/': 106 | os.makedirs(p1, exist_ok=True) 107 | else: 108 | noisy and print('%s -> %s' % (p0, p1)) 109 | open(p1, 'wb').write(zf.read(p0)) 110 | 111 | 112 | if sys.argv[-2:] == ['-m', 'download']: 113 | 114 | import urllib.request 115 | import zipfile 116 | import io 117 | 118 | while os.path.exists('jupylet'): 119 | r = input('Target directory ./jupylet/ already exists. Would you like to overwrite it (y/n)? ') 120 | if r == 'n': 121 | sys.exit(0) 122 | if r == 'y': 123 | break 124 | 125 | GITHUB_MASTER_URL = 'https://github.com/nir/jupylet/archive/master.zip' 126 | 127 | sys.stderr.write('Downloading jupylet source code ZIP from %s...\n' % GITHUB_MASTER_URL) 128 | zf = download_url(GITHUB_MASTER_URL, progress=True) 129 | 130 | sys.stderr.write('Extracting source code from ZIP file to ./jupylet/.\n') 131 | extract_master(zf) 132 | 133 | sys.stderr.write('Type "cd ./jupyter/examples" to enter the examples directory.\n') 134 | 135 | sys.exit(0) 136 | 137 | -------------------------------------------------------------------------------- /jupylet/assets/fonts/SIL Open Font License.txt: -------------------------------------------------------------------------------- 1 | Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 5 | 6 | ----------------------------------------------------------- 7 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 8 | ----------------------------------------------------------- 9 | 10 | PREAMBLE 11 | The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. 12 | 13 | The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. 14 | 15 | DEFINITIONS 16 | "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. 17 | 18 | "Reserved Font Name" refers to any names specified as such after the copyright statement(s). 19 | 20 | "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). 21 | 22 | "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. 23 | 24 | "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. 25 | 26 | PERMISSION & CONDITIONS 27 | Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 28 | 29 | 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 30 | 31 | 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 32 | 33 | 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 34 | 35 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 36 | 37 | 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. 38 | 39 | TERMINATION 40 | This license becomes null and void if any of the above conditions are not met. 41 | 42 | DISCLAIMER 43 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /jupylet/assets/fonts/SourceSerifPro-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/jupylet/assets/fonts/SourceSerifPro-Bold.otf -------------------------------------------------------------------------------- /jupylet/assets/shaders/default-vertex-shader.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | in vec3 in_position; 4 | in vec3 in_normal; 5 | in vec2 in_texcoord_0; 6 | 7 | uniform mat4 model; 8 | uniform mat4 view; 9 | uniform mat4 projection; 10 | 11 | out vec4 frag_view; 12 | out vec3 vert_position; 13 | out vec3 frag_position; 14 | out vec3 frag_normal; 15 | out vec2 frag_uv; 16 | 17 | struct Skybox { 18 | 19 | int render_skybox; 20 | int texture_exists; 21 | float intensity; 22 | samplerCube texture; 23 | }; 24 | 25 | uniform Skybox skybox; 26 | 27 | struct Camera { 28 | 29 | vec3 position; 30 | float zfar; 31 | }; 32 | 33 | uniform Camera camera; 34 | 35 | #define DIRECTIONAL_LIGHT 0 36 | #define POINT_LIGHT 1 37 | #define SPOT_LIGHT 2 38 | 39 | #define MAX_CASCADES 4 40 | 41 | struct ShadowmapTexture { 42 | int layer; 43 | float depth; 44 | mat4 projection; 45 | }; 46 | 47 | struct Light { 48 | 49 | int type; 50 | 51 | vec3 position; 52 | vec3 direction; 53 | 54 | vec3 color; 55 | float intensity; 56 | float ambient; 57 | 58 | float inner_cone; 59 | float outer_cone; 60 | 61 | float scale; 62 | float snear; 63 | 64 | int shadows; 65 | 66 | ShadowmapTexture shadowmap_textures[MAX_CASCADES]; 67 | 68 | int shadowmap_textures_count; 69 | 70 | int shadowmap_pcf; 71 | float shadowmap_bias; 72 | mat4 shadowmap_projection; 73 | }; 74 | 75 | #define MAX_LIGHTS 12 76 | 77 | uniform Light lights[MAX_LIGHTS]; 78 | uniform int nlights; 79 | 80 | uniform int shadowmap_pass; 81 | uniform int shadowmap_light; 82 | 83 | 84 | void main() 85 | { 86 | vec4 mp4 = model * vec4(in_position, 1.0); 87 | 88 | if (shadowmap_pass == 1) { 89 | 90 | int li = shadowmap_light; 91 | 92 | gl_Position = lights[li].shadowmap_projection * mp4; 93 | 94 | vec3 light_direction; 95 | 96 | if (lights[li].type == DIRECTIONAL_LIGHT) { 97 | light_direction = normalize(lights[li].direction); 98 | } 99 | else { 100 | frag_position = vec3(mp4); 101 | light_direction = normalize(lights[li].position - frag_position); 102 | } 103 | 104 | frag_normal = normalize(mat3(transpose(inverse(model))) * in_normal); 105 | 106 | float nl = dot(frag_normal, light_direction); 107 | if (nl < 0.001) { 108 | return; 109 | } 110 | 111 | float bias = lights[li].shadowmap_bias / nl; 112 | 113 | if (lights[li].type == DIRECTIONAL_LIGHT) { 114 | gl_Position.z += bias * lights[li].scale / 100; 115 | return; 116 | } 117 | 118 | float d0 = length(frag_position - lights[li].position); 119 | 120 | bias *= 2 * lights[li].snear / d0; 121 | 122 | gl_Position.z += bias * gl_Position.w; 123 | 124 | return; 125 | } 126 | 127 | frag_view = view * mp4; 128 | 129 | if (skybox.texture_exists == 1 && skybox.render_skybox == 1) { 130 | 131 | mat4 model = mat4(1.0); 132 | model[3].xyz = camera.position; 133 | 134 | gl_Position = projection * view * model * vec4(in_position, 1.0); 135 | gl_Position = gl_Position.xyww; 136 | 137 | vert_position = in_position; 138 | } 139 | else { 140 | gl_Position = projection * frag_view; 141 | } 142 | 143 | frag_position = vec3(mp4); 144 | frag_normal = mat3(transpose(inverse(model))) * in_normal; 145 | frag_uv = vec2(in_texcoord_0.x, 1.0 - in_texcoord_0.y); 146 | } 147 | 148 | -------------------------------------------------------------------------------- /jupylet/assets/shaders/shadertoy-wrapper.glsl: -------------------------------------------------------------------------------- 1 | 2 | #version 330 3 | 4 | #if defined VERTEX_SHADER 5 | 6 | in vec3 in_position; 7 | in vec2 in_texcoord_0; 8 | 9 | uniform mat4 jpl_model; 10 | uniform mat4 jpl_projection; 11 | 12 | out vec2 jpl_frag_uv; 13 | 14 | void main() { 15 | gl_Position = jpl_projection * jpl_model * vec4(in_position, 1.0); 16 | jpl_frag_uv = in_texcoord_0; 17 | } 18 | 19 | #elif defined FRAGMENT_SHADER 20 | 21 | uniform vec3 iResolution; // viewport resolution (in pixels) 22 | uniform float iTime; // shader playback time (in seconds) 23 | uniform float iTimeDelta; // render time (in seconds) 24 | uniform int iFrame; // shader playback frame 25 | uniform float iChannelTime[4]; // channel playback time (in seconds) 26 | uniform vec3 iChannelResolution[4]; // channel resolution (in pixels) 27 | uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click 28 | uniform sampler2D iChannel0; // input channel. XX = 2D/Cube 29 | uniform sampler2D iChannel1; // input channel. XX = 2D/Cube 30 | uniform sampler2D iChannel2; // input channel. XX = 2D/Cube 31 | uniform sampler2D iChannel3; // input channel. XX = 2D/Cube 32 | uniform vec4 iDate; // (year, month, day, time in seconds) 33 | uniform float iSampleRate; // sound sample rate (i.e., 44100) 34 | 35 | 36 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) {} 37 | 38 | 39 | uniform int jpl_components; 40 | uniform vec4 jpl_color; 41 | 42 | in vec2 jpl_frag_uv; 43 | 44 | out vec4 fragColor; 45 | 46 | 47 | void main() { 48 | 49 | vec2 uv = jpl_frag_uv; 50 | 51 | uv *= iResolution.xy; 52 | 53 | fragColor = jpl_color; 54 | 55 | vec4 color0; 56 | 57 | mainImage(color0, uv); 58 | 59 | if (jpl_components == 4) { 60 | fragColor *= color0; 61 | } 62 | else if (jpl_components == 1) { 63 | fragColor.a *= color0.x; 64 | } 65 | else { 66 | fragColor.rgb *= color0.rgb; 67 | } 68 | } 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /jupylet/assets/shaders/sprite.glsl: -------------------------------------------------------------------------------- 1 | 2 | #version 330 3 | 4 | #if defined VERTEX_SHADER 5 | 6 | in vec3 in_position; 7 | in vec2 in_texcoord_0; 8 | 9 | uniform mat4 model; 10 | uniform mat4 projection; 11 | 12 | out vec2 frag_uv; 13 | 14 | void main() { 15 | gl_Position = projection * model * vec4(in_position, 1.0); 16 | frag_uv = in_texcoord_0; 17 | } 18 | 19 | #elif defined FRAGMENT_SHADER 20 | 21 | out vec4 fragColor; 22 | 23 | in vec2 frag_uv; 24 | 25 | uniform sampler2D texture0; 26 | 27 | uniform vec4 color; 28 | 29 | uniform int components; 30 | uniform int flip; 31 | 32 | void main() { 33 | 34 | vec2 uv = frag_uv; 35 | 36 | if (flip == 1) { 37 | uv.y = 1.0 - frag_uv.y; 38 | } 39 | 40 | fragColor = color; 41 | 42 | if (components == 4) { 43 | fragColor *= texture(texture0, uv); 44 | } 45 | else if (components == 1) { 46 | fragColor.a *= texture(texture0, uv).x; 47 | } 48 | else { 49 | fragColor.rgb *= texture(texture0, uv).rgb; 50 | } 51 | } 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /jupylet/assets/sounds/impulses/InsidePiano.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/jupylet/assets/sounds/impulses/InsidePiano.flac -------------------------------------------------------------------------------- /jupylet/assets/sounds/impulses/MaesHowe.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/jupylet/assets/sounds/impulses/MaesHowe.flac -------------------------------------------------------------------------------- /jupylet/assets/sounds/impulses/README.md: -------------------------------------------------------------------------------- 1 | # Impulse Response Files 2 | 3 | An impulse response is an acoustic signature used by the process of convolution 4 | reverb to simulate the acoustic reverberation characteristic of a particular 5 | physical location like a concert chamber, etc... 6 | 7 | ### The impulse response files included here and their licenses 8 | 9 | ## Maes Howe 10 | 11 | This impulse response adds a sense of space and a touch of realism to the 12 | output audio stream. It does not produce a lot of echo and is not heavy 13 | computationally. 14 | 15 | **Description by author:** Maes-Howe, Orkney, is one of the finest chambered cairns in Europe, and is dated to 3000BC. Prior work in the acoustics of ancient sites explores how the resonances exhibited therein might have affected regular human ritual and interaction with the space. [Read More](https://www.openair.hosted.york.ac.uk/?page_id=602) 16 | 17 | **Author:** Damian T. Murphy, Audiolab, University of York - [www.openairlib.net](https://www.openairlib.net/) 18 | 19 | **License:** Attribution 4.0 International (CC BY 4.0) 20 | 21 | ## St Andrew’s Church 22 | 23 | As its name suggests, this impulse response gives the feeling of playing an 24 | instrument in a big hall with a beautiful moderate echo. 25 | 26 | **Description by author:** St Andrew’s Church, built in the 14th Century, has one of the finest examples of in-situ acoustic jars (vases or pots) in the UK. These jars were common to European church construction in the late Middle Ages and are said to be based on the ideas of Roman architect Vitruvius, who discussed the use of resonant jars in the design of amphitheatres to provide clarity of voice presentation. [Read More](https://www.openair.hosted.york.ac.uk/?page_id=683) 27 | 28 | **Author:** Damian T. Murphy, Audiolab, University of York - [www.openairlib.net](https://www.openairlib.net/) 29 | 30 | **License:** Attribution 4.0 International (CC BY 4.0) 31 | 32 | ## Inside Piano 33 | 34 | A beatutiful echo. This impulse response is computationally heavier than the 35 | other two. If it makes your computer sweat try running it with a little bit 36 | of extra buffering. 37 | 38 | **Description by author:** An impulse response recorded from just outside a Yamaha baby grand piano with the damper pedal down. 39 | 40 | **Author:** William Andrew Burnson (2013) - [reverbjs.org](http://reverbjs.org/) 41 | 42 | **License:** Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0) -------------------------------------------------------------------------------- /jupylet/assets/sounds/impulses/StAndrewsChurch.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nir/jupylet/7e1fca6365380567106140fbade48687a378dc36/jupylet/assets/sounds/impulses/StAndrewsChurch.flac -------------------------------------------------------------------------------- /jupylet/audio/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | jupylet/audio/__init__.py 3 | 4 | Copyright (c) 2022, Nir Aides - nir.8bit@gmail.com 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | 28 | import asyncio 29 | import pathlib 30 | import time 31 | import os 32 | 33 | from ..utils import callerframe, callerpath 34 | 35 | 36 | def sonic_py(resource_dir='.'): 37 | """Start an audio application. 38 | 39 | An audio application is need to run live loops. 40 | 41 | Args: 42 | resource_dir (str): Path to root of resource dir, for samples, etc... 43 | 44 | Returns: 45 | App: A running application object. 46 | """ 47 | from ..app import App 48 | 49 | red = os.path.join(callerpath(), resource_dir) 50 | red = pathlib.Path(red).absolute() 51 | 52 | app = App(32, 32, resource_dir=str(red)) 53 | app.run(0) 54 | 55 | return app 56 | 57 | 58 | DEFAULT_AMP = 0.5 59 | 60 | MIDDLE_C = 261.63 61 | 62 | FPS = 44100 63 | 64 | 65 | def t2frames(t): 66 | """Convert time in seconds to frames at 44100 frames per second. 67 | 68 | Args: 69 | t (float): The time duration in seconds. 70 | 71 | Returns: 72 | int: The number of frames. 73 | """ 74 | return int(FPS * t) 75 | 76 | 77 | def frames2t(frames): 78 | """Convert frames at 44100 frames per second to time in seconds. 79 | 80 | Args: 81 | frames (int): The number of frames. 82 | 83 | Returns: 84 | float: The time duration in seconds. 85 | """ 86 | return frames / FPS 87 | 88 | 89 | def get_time(): 90 | return time.time() 91 | 92 | 93 | _note_value = 4 94 | 95 | 96 | def set_note_value(v=4): 97 | """Set the note value representing one beat. 98 | 99 | Args: 100 | v (float): Note value. 101 | """ 102 | global _note_value 103 | _note_value = v 104 | 105 | 106 | def get_note_value(): 107 | return _note_value 108 | 109 | 110 | _bpm = 240 111 | 112 | 113 | def set_bpm(bpm=240): 114 | """Set the tempo to the given beats per minute. 115 | 116 | Args: 117 | bpm (float): Beats per minute. 118 | """ 119 | global _bpm 120 | _bpm = bpm 121 | 122 | 123 | def get_bpm(): 124 | return _bpm 125 | 126 | 127 | dtd = {} 128 | syd = {} 129 | 130 | 131 | def use(sound, **kwargs): 132 | """Set the instrument to use in subsequent calls to :func:`play`. 133 | 134 | You can supply key/value pairs of properties to modify in the given 135 | instrument. If you do, the instrument will be copied first, and 136 | the modifications will be applied to the new copy. 137 | 138 | Args: 139 | sound (GatedSound): Instrument to use. 140 | **kwargs: Properties of intrument to modify. 141 | """ 142 | if kwargs: 143 | sound = sound.copy().set(**kwargs) 144 | 145 | cf = callerframe() 146 | cn = cf.f_code.co_name 147 | 148 | if cn in ['', 'async-def-wrapper']: 149 | hh = '' 150 | elif cn.startswith('', 'async-def-wrapper']: 179 | hh = '' 180 | elif cn.startswith('', 'async-def-wrapper']: 223 | hh = '' 224 | elif cn.startswith(' 0.5 else n1 91 | 92 | return _n + str(int(octave)) 93 | 94 | -------------------------------------------------------------------------------- /jupylet/audio/synth.py: -------------------------------------------------------------------------------- 1 | """ 2 | jupylet/audio/synth.py 3 | 4 | Copyright (c) 2022, Nir Aides - nir.8bit@gmail.com 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | 28 | import logging 29 | 30 | from .sound import Sound, GatedSound, Envelope, Oscillator, Noise, noise_color 31 | from .sound import PhaseModulator 32 | from .effects import SchroederReverb, Overdrive 33 | from .filters import ResonantFilter 34 | 35 | from ..audio import note, DEFAULT_AMP 36 | 37 | import numpy as np 38 | 39 | 40 | logger = logging.getLogger(__name__) 41 | 42 | 43 | class Synth(GatedSound): 44 | 45 | def __init__(self, amp=DEFAULT_AMP, pan=0., duration=None): 46 | 47 | super().__init__(amp=amp, pan=pan, duration=duration) 48 | 49 | self.env0 = Envelope(0.03, 0.3, 0.7, 1., linear=False) 50 | self.osc0 = Oscillator('sine', 4) 51 | self.osc1 = Oscillator('tri') 52 | 53 | def forward(self): 54 | 55 | self.osc1.freq = self.freq 56 | 57 | g0 = self.gate() 58 | e0 = self.env0(g0) 59 | 60 | o0 = self.osc0() 61 | o1 = self.osc1(key_modulation=o0/2) 62 | 63 | return o1 * e0 64 | 65 | 66 | class Drums(GatedSound): 67 | 68 | def __init__(self, amp=DEFAULT_AMP, pan=0.): 69 | 70 | super().__init__(amp=amp, pan=pan) 71 | 72 | self.env0 = Envelope(0.002, 0.15, 0., 0., linear=False) 73 | self.noise = Noise() 74 | 75 | def forward(self): 76 | 77 | color = (self.key - note.C1) / (note.B7 - note.C1) * 12 - 6 78 | 79 | g0 = self.gate() 80 | e0 = self.env0(g0) 81 | a0 = self.noise(color) 82 | 83 | return a0 * e0 84 | 85 | 86 | drawbars = [16, 5+1/3, 8, 4, 2+2/3, 2, 1+3/5, 1+1/3, 1] 87 | 88 | def d2f(d): 89 | return 440 * 16 / (drawbars[d]) 90 | 91 | 92 | class Chorus(Sound): 93 | 94 | def __init__(self, mix=0.5, depth=1/3, shared=False): 95 | 96 | super().__init__(shared=shared) 97 | 98 | self.mix = mix 99 | self.depth = depth 100 | 101 | self.osc = Oscillator('tri', freq=7) 102 | self.phm = PhaseModulator(beta=0.85*44.1) 103 | 104 | def forward(self, x): 105 | 106 | if self.mix <= 0: 107 | return x 108 | 109 | vo = self.osc() 110 | vb = self.phm(x, vo * self.depth) 111 | 112 | return x * (1 - self.mix) + vb * self.mix 113 | 114 | @property 115 | def vibrato_and_chorus(self): 116 | 117 | mode = max(0, min(2, round(self.mix * 2))) 118 | if mode == 0: 119 | return None 120 | 121 | mode = 'c' if mode == 1 else 'v' 122 | valu = max(1, min(3, round(self.depth * 3))) 123 | 124 | return mode + str(int(valu)) 125 | 126 | @vibrato_and_chorus.setter 127 | def vibrato_and_chorus(self, v): 128 | 129 | assert v in ['c1', 'c2', 'c3', 'v1', 'v2', 'v3', None] 130 | 131 | if v is None: 132 | self.mix = 0 133 | return 134 | 135 | self.mix = 0.5 if v[0] == 'c' else 1. 136 | self.depth = float(v[1]) / 3 137 | 138 | 139 | class Hammond(GatedSound): 140 | 141 | def __init__(self, configuration='888000000', amp=DEFAULT_AMP, pan=0., duration=None): 142 | 143 | super().__init__(amp=amp, pan=pan, duration=duration) 144 | 145 | self.configuration = configuration 146 | 147 | self.reve = SchroederReverb(mix=0.25, rt=0.750, shared=True) 148 | self.over = Overdrive(gain=1/amp, amp=amp, shared=True) 149 | self.chor = Chorus(shared=True) 150 | 151 | self.leak = Noise(noise_color.violet) 152 | self.env0 = Envelope(0.005, 0., 1., 0.01, linear=False) 153 | self.prec = Envelope(0., 0.2, 0., 0.01, linear=False) 154 | 155 | self.bass = Oscillator(freq=440) 156 | self.quin = Oscillator(freq=1320) 157 | self.neut = Oscillator(freq=880) 158 | self.octa = Oscillator(freq=1760) 159 | self.naza = Oscillator(freq=2640) 160 | self.bloc = Oscillator(freq=3520) 161 | self.tier = Oscillator(freq=4400) 162 | self.lari = Oscillator(freq=5280) 163 | self.siff = Oscillator(freq=7040) 164 | 165 | self.chorus = True 166 | self.reverb = True 167 | self.overdrive = True 168 | 169 | self.leak_gain = 0.05 170 | 171 | self.precussion = True 172 | self.precussion_gain = 1.5 173 | self.precussion_decay = 0.2 174 | self.precussion_drawbar = 3 175 | 176 | def get_effects(self): 177 | 178 | el = [] 179 | 180 | if self.chorus: 181 | el.append(self.chor) 182 | 183 | if self.reverb: 184 | el.append(self.reve) 185 | 186 | if self.overdrive: 187 | el.append(self.over) 188 | 189 | return tuple(el) + self._effects 190 | 191 | def get_vibrato_and_chorus(self): 192 | return self.chor.vibrato_and_chorus 193 | 194 | def set_vibrato_and_chorus(self, v): 195 | self.chor.vibrato_and_chorus = v 196 | 197 | def parse_configuration(self, c): 198 | return [(i, float(c) / 8) for i, c in enumerate(c)] 199 | 200 | def get_drawbar(self, i): 201 | dbl = ['bass', 'quin', 'neut', 'octa', 'naza', 'bloc', 'tier', 'lari', 'siff'] 202 | return getattr(self, dbl[i]) 203 | 204 | def forward(self): 205 | 206 | self.prec.decay = self.precussion_decay 207 | 208 | g0 = self.gate() 209 | e0 = self.env0(g0) 210 | ep = self.prec(g0) 211 | 212 | km = self.key - 69 213 | 214 | al = [] 215 | 216 | for i, a in self.parse_configuration(self.configuration): 217 | 218 | if i == self.precussion_drawbar and self.precussion: 219 | 220 | db = self.get_drawbar(i) 221 | a0 = db(key_modulation=km) 222 | al.append(a0 * self.precussion_gain * ep) 223 | 224 | elif a > 0: 225 | 226 | db = self.get_drawbar(i) 227 | a0 = db(key_modulation=km) 228 | al.append(a0 * a) 229 | 230 | a0 = np.stack(al).sum(0) 231 | a1 = a0 + self.leak() * self.leak_gain * ep 232 | a2 = a1 * e0 233 | 234 | return a2 235 | 236 | 237 | class TB303(GatedSound): 238 | 239 | def __init__( 240 | self, 241 | shape='sawtooth', 242 | resonance=1, 243 | cutoff=0, 244 | decay=2, 245 | amp=DEFAULT_AMP, 246 | pan=0., 247 | duration=None 248 | ): 249 | 250 | super().__init__(amp=amp, pan=pan, duration=duration) 251 | 252 | self.shape = shape 253 | self.resonance = resonance 254 | self.cutoff = cutoff 255 | self.decay = decay 256 | 257 | self.env0 = Envelope(0.01, 0., 1., 0.01) 258 | self.env1 = Envelope(0., decay, 0., 0., linear=False) 259 | 260 | self.osc0 = Oscillator(shape) 261 | 262 | self.filter = ResonantFilter(btype='lowpass', resonance=resonance) 263 | 264 | def forward(self): 265 | 266 | g0 = self.gate() 267 | 268 | e0 = self.env0(g0) 269 | e1 = self.env1(g0, decay=self.decay) * 12 * 8 270 | 271 | a0 = self.osc0(shape=self.shape, freq=self.freq) 272 | 273 | a1 = self.filter( 274 | a0, 275 | key_modulation=e1+self.cutoff, 276 | resonance=self.resonance, 277 | freq=self.freq, 278 | ) 279 | 280 | return a1 * e0 281 | 282 | 283 | tb303 = TB303() 284 | 285 | -------------------------------------------------------------------------------- /jupylet/collision.py: -------------------------------------------------------------------------------- 1 | """ 2 | jupylet/collision.py 3 | 4 | Copyright (c) 2022, Nir Aides - nir.8bit@gmail.com 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | 28 | import functools 29 | import math 30 | import glm 31 | 32 | import PIL.Image 33 | import scipy.signal 34 | 35 | import numpy as np 36 | 37 | from .resource import pil_resize_to 38 | 39 | 40 | MAP_SIZE = 128 41 | 42 | 43 | def affine(a=0, s=1, ax=0, ay=0, dx=0, dy=0): 44 | 45 | r = math.radians(a) 46 | a = math.cos(r) * s 47 | b = math.sin(r) * s 48 | 49 | return np.array([ 50 | [a, b, -a*ax - b*ay + dx], 51 | [-b, a, b*ax - a*ay + dy], 52 | [0, 0, 1] 53 | ]) 54 | 55 | 56 | """ 57 | @functools.lru_cache(1024) 58 | def trbl0(width, height, anchor_x=0, anchor_y=0, angle=0, scale=1): 59 | 60 | bb0 = np.array([[width, height, 1], [width, 0, 1], [0, 0, 1], [0, height, 1]]) 61 | bb1 = affine0(angle, scale, anchor_x, anchor_y).dot(bb0.T).T 62 | bb2 = bb1.tolist() 63 | 64 | x = [v[0] for v in bb2] 65 | y = [v[1] for v in bb2] 66 | 67 | t, r, b, l = max(y), max(x), min(y), min(x) 68 | 69 | return t, r, b, l 70 | """ 71 | 72 | def affine0(a=0, s=1, ax=0, ay=0, dx=0, dy=0): 73 | 74 | r = math.radians(a) 75 | a = math.cos(r) * s 76 | b = math.sin(r) * s 77 | 78 | return glm.mat3( 79 | a, b, -a*ax - b*ay + dx, 80 | -b, a, b*ax - a*ay + dy, 81 | 0, 0, 1 82 | ) 83 | 84 | 85 | @functools.lru_cache(1024) 86 | def trbl(width, height, anchor_x=0, anchor_y=0, angle=0, scale=1): 87 | 88 | bb0 = glm.mat4x3( 89 | width, height, 1, 90 | width, 0, 1, 91 | 0, 0, 1, 92 | 0, height, 1 93 | ) 94 | 95 | bb1 = glm.transpose(bb0) * affine0(angle, scale, anchor_x, anchor_y) 96 | 97 | x = bb1[0] 98 | y = bb1[1] 99 | 100 | t, r, b, l = max(y), max(x), min(y), min(x) 101 | 102 | return t, r, b, l 103 | 104 | 105 | def hitmap_and_outline_from_alpha(im): 106 | 107 | if isinstance(im, np.ndarray): 108 | assert max(im.shape[:2]) == MAP_SIZE, 'Maximum size of array expected to be %s.' % MAP_SIZE 109 | else: 110 | im = pil_resize_to(im, MAP_SIZE) 111 | 112 | a0 = np.array(im) 113 | 114 | if len(a0.shape) == 3: 115 | a0 = a0[...,-1] 116 | 117 | k0 = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) 118 | 119 | a2 = (a0 > 128).astype('uint8')[::-1] 120 | a3 = scipy.signal.convolve2d(a2, k0, 'same') != 4 121 | a4 = a2 * a3 122 | a5 = np.stack(a4.nonzero()[::-1], -1) 123 | a6 = np.concatenate([a5, a5[:,:1] * 0 + 1], -1) 124 | 125 | h1, w1 = a2.shape 126 | xx = max(a2.shape) + 2 127 | 128 | a7 = np.pad(a2, ((1, xx - h1 - 1), (1, xx - w1 - 1))) 129 | 130 | return a7, a6 131 | 132 | 133 | def collisions_from_hitmap_and_outline(a0, a1): 134 | a1 = np.core.umath.clip(a1, 0, a0.shape[0]-1) 135 | a2 = a0[a1[:,1], a1[:,0]] 136 | a3 = a1[a2.nonzero()] 137 | return a3 138 | 139 | 140 | def compute_collisions(o0, o1, debug=False): 141 | 142 | s0 = max(o0.height, o0.width) / MAP_SIZE 143 | s1 = max(o1.height, o1.width) / MAP_SIZE 144 | 145 | dr = o1.angle - o0.angle 146 | r0 = o1.angle * math.pi / 180 * -1 147 | 148 | dy0 = o0.y - o1.y 149 | dx0 = o0.x - o1.x 150 | 151 | dx1 = math.cos(r0) * dx0 - math.sin(r0) * dy0 152 | dy1 = math.cos(r0) * dy0 + math.sin(r0) * dx0 153 | 154 | dy2 = (dy1 + 1 + o1.anchor.y * o1.height) / s1 155 | dx2 = (dx1 + 1 + o1.anchor.x * o1.width) / s1 156 | 157 | af = affine( 158 | dr, 159 | s0 / s1, 160 | o0.anchor.x * o0.width / s0, 161 | o0.anchor.y * o0.height / s0, dx2, 162 | dy2 163 | ) 164 | 165 | oo = af.dot(o0.outline.T)[:2].T.astype('int64') 166 | cl = collisions_from_hitmap_and_outline(o1.hitmap, oo) 167 | 168 | if not debug: 169 | return cl - (o1.anchor.x * o1.width / s1, o1.anchor.y * o1.height / s1) 170 | 171 | # 172 | # Use the following code to display debug output in a jupyter notebook: 173 | # 174 | # fig = plt.figure(figsize=(8, 8)) 175 | # ax = fig.add_subplot(111) 176 | # ax.set_aspect(1.) 177 | # _ = ax.plot(hm[:,1], hm[:,0], 'y.', oo[:,0], oo[:,1], 'r.', cl[:,0], cl[:,1], 'bo') 178 | # 179 | 180 | hm = np.argwhere(o1.hitmap > 0) 181 | return cl, oo, hm 182 | 183 | -------------------------------------------------------------------------------- /jupylet/color.py: -------------------------------------------------------------------------------- 1 | """ 2 | jupylet/color.py 3 | 4 | Copyright (c) 2022, Nir Aides - nir.8bit@gmail.com 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | 28 | import functools 29 | import webcolors 30 | import glm 31 | 32 | 33 | @functools.lru_cache(maxsize=1024) 34 | def parse_webcolor(color): 35 | 36 | if color[0] == '#': 37 | return glm.vec3(webcolors.hex_to_rgb(color)) / 255 38 | 39 | return glm.vec3(webcolors.name_to_rgb(color)) / 255 40 | 41 | 42 | def c2v(color, alpha=1.): 43 | 44 | if type(color) is str: 45 | color = parse_webcolor(color) 46 | 47 | if len(color) == 3: 48 | color = glm.vec4(color, alpha) 49 | 50 | return glm.vec4(color) 51 | 52 | -------------------------------------------------------------------------------- /jupylet/env.py: -------------------------------------------------------------------------------- 1 | """ 2 | jupylet/env.py 3 | 4 | Copyright (c) 2022, Nir Aides - nir.8bit@gmail.com 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | 28 | import functools 29 | import argparse 30 | import platform 31 | import inspect 32 | import sys 33 | import os 34 | 35 | import multiprocessing as mp 36 | 37 | 38 | def parse_args(): 39 | return create_parser().parse_args(sys.argv[1:]) 40 | 41 | 42 | def create_parser(): 43 | 44 | parser = argparse.ArgumentParser() 45 | 46 | parser.add_argument( 47 | "--window", 48 | choices=['pyglet', 'glfw'], 49 | help="Windowing library to use.", 50 | ) 51 | 52 | parser.add_argument( 53 | "--log_level", 54 | choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], 55 | help="logging level.", 56 | ) 57 | 58 | return parser 59 | 60 | 61 | _window_size = None 62 | 63 | 64 | def set_window_size(size): 65 | global _window_size 66 | _window_size = size 67 | 68 | 69 | def get_window_size(): 70 | return _window_size 71 | 72 | 73 | _is_rl_worker = False 74 | 75 | 76 | def set_rl_worker(): 77 | global _is_rl_worker 78 | _is_rl_worker = True 79 | 80 | 81 | def is_rl_worker(): 82 | return _is_rl_worker 83 | 84 | 85 | @functools.lru_cache() 86 | def is_remote(): 87 | 88 | if is_binder_env(): 89 | return True 90 | 91 | if is_aws_linux(): 92 | return True 93 | 94 | 95 | def is_aws_linux(): 96 | 97 | if platform.system() == 'Linux': 98 | cmd = 'find /sys/devices/virtual/dmi/id/ -type f | xargs grep "Amazon EC2" 2> /dev/null' 99 | return 'Amazon' in os.popen(cmd).read() 100 | 101 | 102 | def is_binder_env(): 103 | return 'BINDER_REQUEST' in os.environ 104 | 105 | 106 | def is_numpy_openblas(): 107 | import numpy 108 | 109 | if hasattr(numpy.__config__, 'get_info'): 110 | ll = numpy.__config__.get_info('blas_opt_info').get('libraries', []) 111 | for l in ll: 112 | if 'openblas' in l: 113 | return True 114 | return False 115 | 116 | return 'openblas' in repr(numpy.__config__.show('dicts')) 117 | 118 | 119 | def is_osx(): 120 | return platform.system().lower() == 'darwin' 121 | 122 | 123 | _has_display = None 124 | 125 | 126 | def has_display(): 127 | 128 | global _has_display 129 | 130 | if _has_display is not None: 131 | return _has_display 132 | 133 | v = mp.Value('i', 0) 134 | 135 | if 'pyglet' in sys.modules: 136 | _has_display0(v) 137 | 138 | else: 139 | p = mp.Process(target=_has_display0, args=(v,)) 140 | p.start() 141 | p.join() 142 | 143 | _has_display = v.value 144 | 145 | return _has_display 146 | 147 | 148 | def _has_display0(v): 149 | 150 | try: 151 | import pyglet 152 | pyglet.canvas.get_display() 153 | v.value = 1 154 | except: 155 | pass 156 | 157 | 158 | _xvfb = None 159 | 160 | 161 | def start_xvfb(): 162 | 163 | global _xvfb 164 | 165 | if platform.system() == 'Linux' and _xvfb is None: 166 | 167 | import xvfbwrapper 168 | _xvfb = xvfbwrapper.Xvfb() 169 | _xvfb.start() 170 | 171 | 172 | def is_xvfb(): 173 | return _xvfb is not None 174 | 175 | 176 | @functools.lru_cache() 177 | def is_python_script(): 178 | 179 | f0 = inspect.currentframe() 180 | 181 | while f0: 182 | if not f0.f_back and f0.f_globals.get('__name__') == '__main__': 183 | return True 184 | 185 | f0 = f0.f_back 186 | 187 | return False 188 | 189 | 190 | @functools.lru_cache() 191 | def is_jupyter(): 192 | try: 193 | from IPython import get_ipython 194 | if 'IPKernelApp' not in get_ipython().config: # Checks for non-notebook IPython kernels 195 | return False 196 | except (ImportError, AttributeError): 197 | return False 198 | return True 199 | 200 | 201 | def is_sphinx_build(): 202 | return 'SPHINXBUILD' in os.environ 203 | 204 | -------------------------------------------------------------------------------- /jupylet/label.py: -------------------------------------------------------------------------------- 1 | """ 2 | jupylet/label.py 3 | 4 | Copyright (c) 2022, Nir Aides - nir.8bit@gmail.com 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | 28 | import functools 29 | import math 30 | import io 31 | import os 32 | 33 | import PIL.Image 34 | import PIL.ImageDraw 35 | import PIL.ImageFont 36 | 37 | import moderngl_window as mglw 38 | import numpy as np 39 | 40 | from moderngl_window.meta import DataDescription 41 | 42 | from .sprite import Sprite 43 | from .state import State 44 | 45 | 46 | def rtl(s): 47 | return str(s[::-1]) 48 | 49 | 50 | # 51 | # Note: Find free fonts at www.fontsquirrel.com 52 | # 53 | 54 | @functools.lru_cache(maxsize=32) 55 | def load_font(path, size): 56 | 57 | ff = mglw.resources.data.load(DataDescription(path=path, kind='binary')) 58 | return PIL.ImageFont.truetype(io.BytesIO(ff), size) 59 | 60 | 61 | @functools.lru_cache(maxsize=2048) 62 | def draw_chr(c, path, size): 63 | 64 | font = load_font(path, size) 65 | 66 | if hasattr(font, 'getsize'): 67 | w, h = font.getsize(c) 68 | else: 69 | w, h = font.getbbox(c)[2:] 70 | 71 | im = PIL.Image.new('L', (w, h)) 72 | di = PIL.ImageDraw.Draw(im) 73 | di.text((0, 0), c, fill='white', font=font) 74 | 75 | return np.array(im) 76 | 77 | 78 | def draw_str(s, path, size, line_height=1.2, align='left'): 79 | 80 | al = [] 81 | ll = [] 82 | 83 | # Compute line height and baseline height. 84 | lh = math.ceil(size * line_height) 85 | bl = draw_chr('a', path, size).shape[0] 86 | 87 | # Coordinates for top-left position for each char. 88 | hh = 0 89 | ww = 0 90 | 91 | # Maximum accumulated width and height for label. 92 | mh = 0 93 | mw = 0 94 | 95 | for c in s.rstrip(): 96 | if c == '\n': 97 | ll.append(ww) 98 | hh += lh 99 | mw = max(mw, ww) 100 | ww = 0 101 | mh = 0 102 | continue 103 | 104 | ca = draw_chr(c, path, size) 105 | al.append((ca, (hh, ww), len(ll))) 106 | 107 | h, w = ca.shape 108 | 109 | mh = max(mh, h) 110 | ww += w 111 | 112 | ll.append(ww) 113 | 114 | # Compute final baseline, maximum width and height for label. 115 | bl = mh - bl 116 | mh = hh + mh 117 | mw = max(mw, ww) 118 | 119 | a0 = np.zeros((mh, mw), dtype='uint8') 120 | 121 | aw = {'left': 0, 'center': 0.5, 'right': 1}[align] 122 | 123 | for ca, (hh, ww), li in al: 124 | 125 | a = int((mw - ll[li]) * aw) 126 | h, w = ca.shape 127 | a0[hh:hh+h, ww+a:ww+a+w] = ca 128 | 129 | return a0, bl 130 | 131 | 132 | class Label(Sprite): 133 | 134 | """A text label. 135 | 136 | Since a text label is actually implemented as a 2D sprite, it has all the 137 | functionality and methods of a Sprite. 138 | 139 | Args: 140 | text (str): text to render as label. 141 | font (path): path to a true type or open type font. 142 | font_size (float): font size to use. 143 | line_height (float): determines the distance between lines. 144 | align (str): the desired alignment for the text label. May be one 145 | of 'left', 'center', and 'right'. 146 | color (str or 3-tuple): a color name, color hex notation, or a 147 | 3-tuple. specifying the color for the text label. 148 | x (float): the x position for the label. 149 | y (float): the y position for the label. 150 | angle (float): clockwise rotation of the label in degrees. 151 | anchor_x (float or str): either 'left', 'center' or 'right' or a 152 | value between 0.0 (for left) and 1.0 (for right) indicating 153 | the anchor point inside the label along its x axis. 154 | anchor_y (float or str): either 'bottom', 'baseline', 'center' or 155 | 'top' or a value between 0.0 (for bottom) and 1.0 (for top) 156 | indicating the anchor point inside the label along its y axis. 157 | """ 158 | 159 | def __init__( 160 | self, 161 | text='', 162 | font_path='fonts/SourceSerifPro-Bold.otf', 163 | font_size=16, 164 | line_height=1.2, 165 | align='left', 166 | bold=False, 167 | italic=False, 168 | color='white', 169 | x=0, 170 | y=0, 171 | angle=0.0, 172 | width=None, 173 | height=None, 174 | anchor_x='left', 175 | anchor_y='baseline', 176 | ): 177 | """""" 178 | 179 | image, baseline = draw_str(text, font_path, font_size, line_height, align) 180 | 181 | super().__init__( 182 | image, 183 | x, 184 | y, 185 | angle=angle, 186 | anchor_x=anchor_x, 187 | anchor_y=anchor_y, 188 | height=height, 189 | width=width, 190 | collisions=False, 191 | ) 192 | 193 | self._items = dict( 194 | text = text, 195 | font_path = font_path, 196 | font_size = font_size, 197 | line_height = line_height, 198 | align = align, 199 | ) 200 | 201 | self.baseline = baseline / self.texture.height 202 | 203 | self.color = color 204 | 205 | def update(self, shader): 206 | 207 | if self._dirty: 208 | self._dirty.clear() 209 | 210 | self.image, baseline = draw_str( 211 | self.text, 212 | self.font_path, 213 | self.font_size, 214 | self.line_height, 215 | self.align, 216 | ) 217 | 218 | self.baseline = baseline / self.texture.height 219 | 220 | if self._ay == 'baseline': 221 | self.anchor.y = self.baseline 222 | 223 | def get_state(self): 224 | return dict( 225 | sprite = super().get_state(), 226 | text = self.text, 227 | font_path = self.font_path, 228 | font_size = self.font_size, 229 | line_height = self.line_height, 230 | align = self.align, 231 | ) 232 | 233 | def set_state(self, s): 234 | 235 | super().set_state(s['sprite']) 236 | 237 | for k, v in s.items(): 238 | if k != 'sprite': 239 | setattr(self, k, v) 240 | 241 | -------------------------------------------------------------------------------- /jupylet/lru.py: -------------------------------------------------------------------------------- 1 | """ 2 | jupylet/lru.py 3 | 4 | Copyright (c) 2022, Nir Aides - nir.8bit@gmail.com 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | 28 | import logging 29 | 30 | 31 | logger = logging.getLogger(__name__) 32 | 33 | 34 | SPRITE_TEXTURE_UNIT = 0 35 | SKYBOX_TEXTURE_UNIT = 1 36 | SHADOW_TEXTURE_UNIT = 2 37 | TARRAY_TEXTURE_UNIT = 3 38 | 39 | 40 | # TODO: an lru is actually not an ideal policy for allocating texture units. 41 | # fix it. 42 | 43 | class LRU(object): 44 | """Mechanism to allocate least recently used slot in array.""" 45 | 46 | def __init__(self, min_items, max_items): 47 | 48 | self.mini = min_items 49 | self.step = max_items 50 | self.items = {i: [i, i, i, 0] for i in range(min_items, max_items)} 51 | 52 | def reset(self, min_items, max_items): 53 | 54 | self.mini = min_items 55 | self.step = max_items 56 | self.items = {i: [i, i, i, 0] for i in range(min_items, max_items)} 57 | 58 | def allocate(self, lid=None): 59 | """Allocate slot. 60 | 61 | Args: 62 | lid (int): An id that identifies "object" in array. A new id will 63 | be generated if None is given. 64 | 65 | Returns: 66 | tuple: A 4-tuple consisting of (step, lid, slot, new) where 67 | *step* indicates the lru "timestamp" for this object, 68 | *lid* is the allocated object id, 69 | *slot* is the array index allocated for the "object", and 70 | *new* is 1 if "object" was allocated a new slot or 0 if it 71 | remains in the same slot it was before. 72 | """ 73 | self.step += 1 74 | 75 | if lid is None: 76 | lid = self.step 77 | 78 | r = self.items.get(lid) 79 | 80 | if r is None: 81 | 82 | lid0, slot = min(self.items.values())[1:3] 83 | self.items.pop(lid0) 84 | self.items[lid] = [self.step, lid, slot, 0] 85 | 86 | logger.debug('Allocated a new LRU slot with step=%r, lid=%r, slot=%r, 1.', self.step, lid, slot) 87 | 88 | return self.step, lid, slot, 1 89 | 90 | r[0] = self.step 91 | 92 | return r 93 | 94 | 95 | _MAX_MATERIALS = 12 96 | _lru_materials = LRU(0, _MAX_MATERIALS) 97 | 98 | -------------------------------------------------------------------------------- /jupylet/node.py: -------------------------------------------------------------------------------- 1 | """ 2 | jupylet/node.py 3 | 4 | Copyright (c) 2022, Nir Aides - nir.8bit@gmail.com 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | 28 | import copy 29 | import math 30 | import glm 31 | 32 | from .utils import glm_dumps, glm_loads 33 | 34 | 35 | class Object(object): 36 | 37 | """Implement an object that tracks changes to its properties. 38 | 39 | It is used for implementing lazy mechanisms that only engage in costly 40 | computations when necessary. 41 | """ 42 | 43 | def __init__(self, dirty=True): 44 | 45 | self._items = {} 46 | self._dirty = set([True]) if dirty else set() 47 | 48 | def __dir__(self): 49 | return list(self._items.keys()) + super().__dir__() 50 | 51 | def __getattr__(self, k): 52 | 53 | if k not in self._items: 54 | return super().__getattribute__(k) 55 | 56 | return self._items[k] 57 | 58 | def __setattr__(self, k, v): 59 | 60 | if k == '_items' or k not in self._items: 61 | return super().__setattr__(k, v) 62 | 63 | self._items[k] = v 64 | self._dirty.add(k) 65 | 66 | def __repr__(self): 67 | return '%s(%s)' % (type(self).__name__, ', '.join( 68 | '%s=%s' % i for i in list(self.__dict__.items()) + list(self._items.items()) if i[0][0] != '_' 69 | )) 70 | 71 | 72 | def q2aa(rotation, deg=False): 73 | """Transform quaternion to angle+axis.""" 74 | 75 | if not rotation or rotation == (1., 0., 0., 0.): 76 | return 0, glm.vec3(0, 0, 1) 77 | 78 | c, xs, ys, zs = rotation #glm.conjugate(rotation) 79 | 80 | angle = math.acos(c) * 2 81 | s = math.sin(angle / 2) 82 | 83 | if s == 0: 84 | return 0, glm.vec3(0, 0, 1) 85 | 86 | if deg: 87 | angle = round(180 * angle / math.pi, 3) 88 | 89 | return angle, glm.vec3(xs / s, ys / s, zs / s) 90 | 91 | 92 | def aa2q(angle, axis=glm.vec3(0, 0, 1)): 93 | return glm.angleAxis(angle, glm.normalize(axis)) 94 | 95 | 96 | _i4 = glm.mat4(1.) 97 | 98 | 99 | class Node(Object): 100 | 101 | """Handle and represent geometric operations as matrix transformation. 102 | 103 | Handle and represent scaling, rotation, and translation in local and global 104 | 3D coordinates as a single matrix (lazily maintained) transformation. 105 | 106 | Args: 107 | name (str, optional): Name of object. 108 | anchor (glm.vec3, optional): 109 | """ 110 | 111 | def __init__( 112 | self, 113 | name='', 114 | anchor=None, 115 | scale=None, 116 | rotation=None, 117 | position=None, 118 | ): 119 | 120 | super().__init__() 121 | 122 | self.name = name 123 | 124 | self.anchor = glm.vec3(anchor) if anchor else glm.vec3(0.) 125 | self.scale0 = glm.vec3(scale) if scale else glm.vec3(1.) 126 | self.rotation = glm.quat(rotation) if rotation else glm.quat(1., 0., 0., 0.) 127 | self.position = glm.vec3(position) if position else glm.vec3(0.) 128 | 129 | self._itemz = None 130 | 131 | self._matrix = glm.mat4(1.) 132 | 133 | @property 134 | def scale(self): 135 | return self.scale0 136 | 137 | @scale.setter 138 | def scale(self, value): 139 | self.scale0 = value 140 | 141 | @property 142 | def matrix(self): 143 | 144 | if self._itemz != [self.anchor, self.scale0, self.rotation, self.position]: 145 | self._itemz = copy.deepcopy([ 146 | self.anchor, 147 | self.scale0, 148 | self.rotation, 149 | self.position 150 | ]) 151 | 152 | t0 = glm.translate(_i4, self.position) 153 | r0 = t0 * glm.mat4_cast(self.rotation) 154 | s0 = glm.scale(r0, self.scale0) 155 | a0 = glm.translate(s0, -self.anchor) 156 | 157 | self._matrix = a0 158 | 159 | self._dirty.add('_matrix') 160 | 161 | return self._matrix 162 | 163 | def move_local(self, xyz): 164 | """Move by given displacement in local coordinate system. 165 | 166 | Args: 167 | xyz (glm.vec3): Displacement. 168 | """ 169 | rxyz = glm.mat4_cast(self.rotation) * glm.vec4(xyz, 1.) 170 | self.position += rxyz.xyz 171 | 172 | def rotate_local(self, angle, axis=(0., 0., 1.)): 173 | """Rotate counter clockwise by given angle around given axis in local 174 | coordinate system. 175 | 176 | Args: 177 | angle (float): Angle in radians. 178 | axis (glm.vec3): Rotation axis. 179 | """ 180 | self.rotation *= aa2q(angle, glm.vec3(axis)) 181 | 182 | def move_global(self, xyz): 183 | """Move by given displacement in global coordinate system. 184 | 185 | Args: 186 | xyz (glm.vec3): Displacement. 187 | """ 188 | self.position += xyz 189 | 190 | def rotate_global(self, angle, axis=(0., 0., 1.)): 191 | """Rotate counter clockwise by given angle around given axis in global 192 | coordinate system. 193 | 194 | Args: 195 | angle (float): Angle in radians. 196 | axis (glm.vec3): Rotation axis. 197 | """ 198 | self.rotation = aa2q(angle, glm.vec3(axis)) * self.rotation 199 | 200 | @property 201 | def up(self): 202 | """glm.vec3: Return the local up (+y) axis.""" 203 | return (self.matrix * glm.vec4(0, 1, 0, 0)).xyz 204 | 205 | @property 206 | def front(self): 207 | """glm.vec3: Return the local front (+z) axis.""" 208 | return (self.matrix * glm.vec4(0, 0, 1, 0)).xyz 209 | 210 | def get_state(self): 211 | """Get a dictionary of properties defining the object state. 212 | 213 | Returns: 214 | dict: A dictionary of properties. 215 | """ 216 | return dict( 217 | rotation = glm_dumps(glm.quat(self.rotation)), 218 | position = glm_dumps(glm.vec3(self.position)), 219 | anchor = glm_dumps(glm.vec3(self.anchor)), 220 | scale0 = glm_dumps(glm.vec3(self.scale0)), 221 | ) 222 | 223 | def set_state(self, s): 224 | """Set object state from given dictionary of properties. 225 | 226 | Args: 227 | s (dict): A dictionary of properties previously returned by a call 228 | to ``get_state()``. 229 | """ 230 | for k, v in s.items(): 231 | setattr(self, k, glm_loads(v)) 232 | 233 | -------------------------------------------------------------------------------- /jupylet/state.py: -------------------------------------------------------------------------------- 1 | """ 2 | jupylet/state.py 3 | 4 | Copyright (c) 2022, Nir Aides - nir.8bit@gmail.com 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | 28 | class State(object): 29 | 30 | def __init__(self, **kwargs): 31 | 32 | for k, v in kwargs.items(): 33 | setattr(self, k, v) 34 | 35 | def __repr__(self): 36 | return repr(self.__dict__) 37 | 38 | def __setitem__(self, key, item): 39 | self.__dict__[key] = item 40 | 41 | def __getitem__(self, key): 42 | return self.__dict__[key] 43 | 44 | def get_state(self): 45 | return self 46 | 47 | def set_state(self, s): 48 | self.__dict__ = vars(s) 49 | 50 | -------------------------------------------------------------------------------- /jupylet/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | jupylet/utils.py 3 | 4 | Copyright (c) 2022, Nir Aides - nir.8bit@gmail.com 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | 28 | import ipywidgets 29 | import functools 30 | import traceback 31 | import hashlib 32 | import inspect 33 | import logging 34 | import pickle 35 | import types 36 | import glm 37 | import sys 38 | import re 39 | import os 40 | 41 | import numpy as np 42 | 43 | 44 | LOGGING_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' 45 | 46 | 47 | class StreamHandler(logging.StreamHandler): 48 | pass 49 | 50 | 51 | class LoggingWidget(logging.Handler): 52 | """ Custom logging handler sending logs to an output widget """ 53 | 54 | def __init__(self, height='256px', *args, **kwargs): 55 | super(LoggingWidget, self).__init__(*args, **kwargs) 56 | 57 | self.out = ipywidgets.Output() 58 | self.set_layout(height) 59 | 60 | def set_layout(self, height='256px', overflow='scroll', **kwargs): 61 | self.out.layout=ipywidgets.Layout( 62 | height=height, 63 | overflow=overflow, 64 | **kwargs 65 | ) 66 | 67 | def emit(self, record): 68 | with self.out: 69 | print(self.format(record)) 70 | 71 | 72 | def get_logging_widget(height='256px', quiet_default_logger=True): 73 | 74 | if type(height) is int: 75 | height = str(height) + 'px' 76 | 77 | logger = logging.getLogger() 78 | 79 | wl = [h for h in logger.handlers if isinstance(h, LoggingWidget)] 80 | if wl: 81 | w = wl[-1] 82 | w.set_layout(height) 83 | return w.out 84 | 85 | handler = LoggingWidget(height) 86 | handler.setLevel(logging.DEBUG) 87 | handler.setFormatter(logging.Formatter(LOGGING_FORMAT)) 88 | 89 | logger.addHandler(handler) 90 | 91 | if quiet_default_logger: 92 | wl = [h for h in logger.handlers if isinstance(h, StreamHandler)] 93 | if wl: 94 | wl[-1].setLevel(logging.ERROR) 95 | 96 | return handler.out 97 | 98 | 99 | _logging_level = logging.WARNING 100 | 101 | 102 | def get_logging_level(): 103 | return _logging_level 104 | 105 | 106 | def setup_basic_logging(level): 107 | """Set up basic logging 108 | 109 | Args: 110 | level (int): The log level 111 | """ 112 | 113 | global _logging_level 114 | 115 | if type(level) is str: 116 | level = logging._nameToLevel.get(level, None) 117 | 118 | if level is None: 119 | return 120 | 121 | _logging_level = level 122 | 123 | logger = logging.getLogger() 124 | logger.setLevel(level) 125 | 126 | if not logger.handlers: 127 | 128 | handler = StreamHandler() 129 | handler.setLevel(logging.DEBUG) 130 | handler.setFormatter(logging.Formatter(LOGGING_FORMAT)) 131 | 132 | logger.addHandler(handler) 133 | 134 | 135 | def abspath(path): 136 | 137 | dirname = os.path.dirname(os.path.abspath(__file__)) 138 | return os.path.abspath(os.path.join(dirname, path)) 139 | 140 | 141 | def callerpath(levelsup=1): 142 | 143 | ff = inspect.currentframe().f_back 144 | for i in range(levelsup): 145 | ff = ff.f_back 146 | 147 | pp = ff.f_globals.get('__file__', '') 148 | return os.path.dirname(pp) 149 | 150 | 151 | def callerframe(levelsup=1): 152 | 153 | ff = inspect.currentframe().f_back 154 | for i in range(levelsup): 155 | ff = ff.f_back 156 | 157 | return ff 158 | 159 | 160 | def auto_read(s): 161 | return s if '\n' in s else open(s).read() 162 | 163 | 164 | def o2h(o, n=12): 165 | return hashlib.sha256(pickle.dumps(o)).hexdigest()[:n] 166 | 167 | 168 | class Dict(dict): 169 | 170 | def __dir__(self): 171 | return list(self.keys()) + super().__dir__() 172 | 173 | def __getattr__(self, k): 174 | 175 | if k not in self: 176 | raise AttributeError(k) 177 | 178 | return self[k] 179 | 180 | def __setattr__(self, k, v): 181 | self[k] = v 182 | 183 | 184 | def patch_method(obj, key, method): 185 | 186 | foo = getattr(obj, key) 187 | 188 | if isinstance(foo.__func__, functools.partial): 189 | return foo 190 | 191 | par = functools.partial(method, foo=foo) 192 | bar = types.MethodType(par, obj) 193 | bar.__func__.__name__ = foo.__func__.__name__ 194 | 195 | setattr(obj, key, bar) 196 | 197 | return bar 198 | 199 | 200 | def glm_dumps(o): 201 | 202 | if "'glm." not in repr(o.__class__): 203 | return o 204 | 205 | return ('__glm__', o.__class__.__name__, tuple(o)) 206 | 207 | 208 | def glm_loads(o): 209 | 210 | if type(o) is not tuple or not o or o[0] != '__glm__': 211 | return o 212 | 213 | return getattr(glm, o[1])(o[2]) 214 | 215 | 216 | def trimmed_traceback(): 217 | 218 | e = ''.join(traceback.format_exception(*sys.exc_info())) 219 | e = re.sub(r'(?s)^.*?The above exception was the direct cause of the following exception:\s*', '', e) 220 | return e 221 | 222 | 223 | def auto(o): 224 | 225 | t = type(o) 226 | 227 | if t in (tuple, list): 228 | return t(auto(v) for v in o) 229 | 230 | if t is dict: 231 | return {k: auto(v) for k, v in o.items()} 232 | 233 | if t is not str: 234 | return o 235 | 236 | if o.isdecimal(): 237 | return int(o) 238 | 239 | try: 240 | return float(o) 241 | except: 242 | pass 243 | 244 | return o 245 | 246 | 247 | def settable(o, name): 248 | 249 | if name[0] == '_': 250 | return False 251 | 252 | if name in o.__dict__: 253 | return True 254 | 255 | v = getattr(o, name, '__NONE__') 256 | 257 | return v != '__NONE__' and not callable(v) 258 | 259 | 260 | def np_is_zero(a): 261 | return np.abs(a).sum().item() == 0 262 | 263 | 264 | class Enum(object): 265 | 266 | def __init__(self, **kwargs): 267 | 268 | for k, v in kwargs.items(): 269 | setattr(self, k, v) 270 | 271 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | license_files=LICENSE.txt -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import setuptools 4 | 5 | 6 | with open('README.md', 'rb') as f: 7 | long_description = f.read().decode() 8 | 9 | 10 | setuptools.setup( 11 | name = 'jupylet', 12 | packages = ['jupylet', 'jupylet.audio'], 13 | package_data={ 14 | 'jupylet': ['assets/*', 'assets/*/*', 'assets/*/*/*'], 15 | }, 16 | version = '0.9.2', 17 | license='bsd-2-clause', 18 | description = 'Python game programming in Jupyter notebooks.', 19 | long_description=long_description, 20 | long_description_content_type="text/markdown", 21 | author = 'Nir Aides', 22 | author_email = 'nir.8bit@gmail.com', 23 | url = 'https://github.com/nir/jupylet', 24 | download_url = 'https://github.com/nir/jupylet/archive/v0.9.2.tar.gz', 25 | keywords = [ 26 | 'reinforcement learning', 27 | 'deep learning', 28 | 'synthesizers', 29 | 'moderngl', 30 | 'children', 31 | 'jupyter', 32 | 'python', 33 | 'games', 34 | 'midi', 35 | 'kids', 36 | 'RL', 37 | ], 38 | python_requires='>=3.9,<3.13', 39 | install_requires=[ 40 | 'glfw', 41 | 'mido', 42 | 'tqdm', 43 | 'jedi', 44 | 'numpy', 45 | 'PyGLM', 46 | 'scipy', 47 | 'pillow', 48 | 'gltflib', 49 | 'jupyter', 50 | 'notebook', 51 | 'moderngl', 52 | 'soundfile', 53 | 'webcolors', 54 | 'ipyevents', 55 | 'ipywidgets', 56 | 'matplotlib', 57 | 'sounddevice', 58 | 'soundcard; platform_system=="Darwin"', 59 | 'python-rtmidi', 60 | 'moderngl-window', 61 | ], 62 | extras_require = { 63 | 'midi': ['python-rtmidi'] 64 | }, 65 | classifiers=[ 66 | 'Development Status :: 4 - Beta', 67 | 'Intended Audience :: Education', 68 | 'Intended Audience :: Developers', 69 | 'Intended Audience :: Science/Research', 70 | 'Topic :: Education', 71 | 'Topic :: Multimedia :: Graphics', 72 | 'Topic :: Multimedia :: Graphics :: 3D Rendering', 73 | 'Topic :: Multimedia :: Sound/Audio', 74 | 'Topic :: Multimedia :: Sound/Audio :: MIDI', 75 | 'Topic :: Multimedia :: Sound/Audio :: Sound Synthesis', 76 | 'Topic :: Scientific/Engineering :: Artificial Intelligence', 77 | 'License :: OSI Approved :: BSD License', 78 | 'Programming Language :: Python :: 3.9', 79 | 'Programming Language :: Python :: 3.10', 80 | 'Programming Language :: Python :: 3.11', 81 | 'Programming Language :: Python :: 3.12', 82 | ], 83 | ) 84 | 85 | --------------------------------------------------------------------------------