├── .editorconfig ├── .flake8 ├── .github └── workflows │ ├── pre-commit.yml │ ├── publish-to-test-pypi.yml │ ├── publish.yml │ └── python-tests.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── AUTHORS.rst ├── CHANGELOG.rst ├── CONTRIBUTING.rst ├── LICENSE ├── README.rst ├── docs ├── .gitignore ├── Makefile ├── authors.rst ├── changelog.rst ├── conf.py ├── contributing.rst ├── index.rst ├── license.rst ├── make.bat ├── readme.rst └── updating.rst ├── pyproject.toml ├── sunvox ├── __init__.py ├── api.py ├── buffered │ ├── __init__.py │ ├── process.py │ └── processor.py ├── dll.py ├── lib │ ├── copy-libs.sh │ ├── linux │ │ ├── lib_arm │ │ │ └── sunvox.so │ │ ├── lib_arm64 │ │ │ └── sunvox.so │ │ ├── lib_x86 │ │ │ └── sunvox.so │ │ └── lib_x86_64 │ │ │ └── sunvox.so │ ├── macos │ │ ├── lib_arm64 │ │ │ └── sunvox.dylib │ │ └── lib_x86_64 │ │ │ └── sunvox.dylib │ └── windows │ │ ├── lib_x86 │ │ └── sunvox.dll │ │ └── lib_x86_64 │ │ └── sunvox.dll ├── macros.py ├── process.py ├── processor.py ├── slot.py ├── tools │ ├── __init__.py │ └── export.py └── types.py ├── tests ├── __init__.py └── test_init_deinit.py └── uv.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 4 10 | charset = utf-8 11 | 12 | [*.{bat,cmd,ps1}] 13 | end_of_line = crlf 14 | 15 | [*.rst] 16 | indent_size = 2 17 | 18 | [*.yaml] 19 | indent_size = 2 20 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | # Recommend matching the black default line length of 88, 3 | # rather than the flake8 default of 79: 4 | max-line-length = 88 5 | extend-ignore = 6 | # See https://github.com/PyCQA/pycodestyle/issues/373 7 | E203, 8 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit-all 2 | on: 3 | - push 4 | jobs: 5 | pre-commit: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - uses: actions/setup-python@v2 10 | - uses: pre-commit/action@v2.0.0 11 | with: 12 | extra_args: --all-files 13 | -------------------------------------------------------------------------------- /.github/workflows/publish-to-test-pypi.yml: -------------------------------------------------------------------------------- 1 | name: Release to Test PyPI 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | pypi-publish: 8 | name: Publish release to Test PyPI 9 | runs-on: ubuntu-latest 10 | environment: 11 | name: testpypi 12 | url: https://test.pypi.org/p/sunvox-dll-python 13 | permissions: 14 | id-token: write 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Install uv 19 | uses: astral-sh/setup-uv@v5 20 | with: 21 | enable-cache: true 22 | cache-dependency-glob: "uv.lock" 23 | python-version: ${{ matrix.python-version }} 24 | 25 | - name: Build package 26 | run: uv build 27 | 28 | - name: Publish package distributions to PyPI 29 | uses: pypa/gh-action-pypi-publish@release/v1 30 | with: 31 | repository-url: https://test.pypi.org/legacy/ 32 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Release to PyPI 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | pypi-publish: 9 | name: Publish release to PyPI 10 | runs-on: ubuntu-latest 11 | environment: 12 | name: pypi 13 | url: https://pypi.org/p/sunvox-dll-python 14 | permissions: 15 | id-token: write 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Install uv 20 | uses: astral-sh/setup-uv@v5 21 | with: 22 | enable-cache: true 23 | cache-dependency-glob: "uv.lock" 24 | python-version: ${{ matrix.python-version }} 25 | 26 | - name: Build package 27 | run: uv build 28 | 29 | - name: Publish package distributions to PyPI 30 | uses: pypa/gh-action-pypi-publish@release/v1 31 | -------------------------------------------------------------------------------- /.github/workflows/python-tests.yml: -------------------------------------------------------------------------------- 1 | name: python-tests 2 | on: 3 | - push 4 | jobs: 5 | test: 6 | runs-on: ubuntu-latest 7 | strategy: 8 | matrix: 9 | python-version: 10 | - "3.9" 11 | - "3.10" 12 | - "3.11" 13 | - "3.12" 14 | - "3.13" 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Install uv 20 | uses: astral-sh/setup-uv@v5 21 | with: 22 | enable-cache: true 23 | cache-dependency-glob: "uv.lock" 24 | python-version: ${{ matrix.python-version }} 25 | 26 | - name: Install the project 27 | run: uv sync --all-extras --dev 28 | 29 | - name: Run tests 30 | run: uv run pytest 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | eggs 9 | .eggs 10 | parts 11 | bin 12 | var 13 | sdist 14 | wheelhouse 15 | develop-eggs 16 | .installed.cfg 17 | venv*/ 18 | pyvenv*/ 19 | 20 | # Installer logs 21 | pip-log.txt 22 | 23 | # Unit test / coverage reports 24 | .coverage 25 | .tox 26 | .coverage.* 27 | nosetests.xml 28 | coverage.xml 29 | htmlcov 30 | 31 | # Translations 32 | *.mo 33 | 34 | # Mr Developer 35 | .mr.developer.cfg 36 | .project 37 | .pydevproject 38 | .idea 39 | *.iml 40 | *.komodoproject 41 | 42 | # Complexity 43 | output/*.html 44 | output/*/index.html 45 | 46 | # Sphinx 47 | docs/_build 48 | 49 | .DS_Store 50 | *~ 51 | .*.sw[po] 52 | .build 53 | .ve 54 | .env 55 | .cache 56 | .pytest 57 | .bootstrap 58 | .appveyor.token 59 | *.bak 60 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | fail_fast: true 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v4.4.0 5 | hooks: 6 | - id: check-ast 7 | - id: check-builtin-literals 8 | - id: check-byte-order-marker 9 | - id: check-case-conflict 10 | - id: check-docstring-first 11 | - id: check-merge-conflict 12 | - id: debug-statements 13 | - id: end-of-file-fixer 14 | - id: mixed-line-ending 15 | - id: trailing-whitespace 16 | - repo: https://github.com/astral-sh/ruff-pre-commit 17 | # Ruff version. 18 | rev: v0.9.7 19 | hooks: 20 | # Run the linter. 21 | - id: ruff 22 | args: [ --fix ] 23 | # Run the formatter. 24 | - id: ruff-format 25 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | sphinx: 3 | configuration: docs/conf.py 4 | formats: [] 5 | python: 6 | install: 7 | - method: pip 8 | path: . 9 | extra_requirements: 10 | - docs 11 | build: 12 | os: ubuntu-lts-latest 13 | tools: 14 | python: "3.13" 15 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Authors 2 | ======= 3 | 4 | * `Matthew Scott `__ 5 | * `Phoel Ostrum `__ 6 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 5 | 0.3.6.2.1.2.1 6 | ------------- 7 | 8 | - Updates SunVox DLL to 2.1.2b (2.1.2.1 internally). 9 | 10 | - Adds new library functions and slot methods ``sv_sampler_par`` and 11 | ``sv_save_to_memory``. 12 | 13 | - Allows auto-cleanup of a SunVox process by using a context manager. 14 | 15 | - Fixes missing ``save`` from ``sunvox.dll.__all__``, 16 | which prevented a ``Slot`` of a ``BufferedProcess`` 17 | from being able to save project files to disk. 18 | 19 | - Fixes ``pat_num`` not being correctly passed through 20 | when calling ``sunvox.slot.Slot.set_pattern_event``. 21 | (Thanks to `Phoel Ostrum `__.) 22 | 23 | - Moves from ``poetry`` and ``poetry-core`` to ``uv`` and ``hatchling``. 24 | 25 | - Switches from using black_ to ruff_ for formatting code. 26 | 27 | .. _ruff: 28 | https://docs.astral.sh/ruff/ 29 | 30 | 31 | 0.3.5.2.1.0.3 32 | ------------- 33 | 34 | - Updates SunVox DLL to 2.1c (2.1.0.0 internally). 35 | 36 | - Adds ``sunvox.types.NOTECMD.PREV_TRACK`` command. 37 | 38 | - Exports ``sunvox.types.TIME_MAP`` enum from ``sunvox.api`` module. 39 | 40 | 41 | 0.3.4.2.1.0.2 42 | ------------- 43 | 44 | - Updates SunVox DLL to 2.1b (2.1.0.0 internally). 45 | 46 | - Adds new library functions and slot methods ``set_song_name``, 47 | ``metamodule_load``, ``metamodule_load_from_memory``, ``vplayer_load``, 48 | ``vplayer_load_from_memory``, ``get_module_type``, ``set_module_name``, 49 | ``set_module_xy``, ``set_module_color``, ``set_module_finetune``, 50 | ``set_module_relnote``, ``set_module_ctl_value``, ``get_module_ctl_min``, 51 | ``get_module_ctl_max``, ``get_module_ctl_offset``, ``get_module_ctl_type``, 52 | ``get_module_ctl_group``, ``new_pattern``, ``remove_pattern``, 53 | ``set_pattern_xy``, ``set_pattern_size``, and ``set_pattern_name``. 54 | 55 | - Adds documentation for ``get_module_ctl_value``. 56 | 57 | - Updates documentation for ``module_curve``, ``get_pattern_x``, 58 | ``get_pattern_y``, ``get_pattern_tracks``, ``get_pattern_lines``, 59 | and ``get_pattern_name``. 60 | 61 | - Updates constants and docs in ``sunvox.types`` 62 | based on SunVox DLL 2.1b headers. 63 | 64 | - Fixes mapping of ``sunvox.dll.get_sample_rate`` to point to 65 | the correct C function. 66 | 67 | 68 | 0.3.3.2.0.0.0 69 | ------------- 70 | 71 | - Updates SunVox DLL to 2.0e (2.0.0.0 internally). 72 | 73 | - Adds support for M1-based Mac systems. 74 | 75 | 76 | 0.3.2.2.0.0.0 77 | ------------- 78 | 79 | - Updates SunVox DLL to 2.0c (2.0.0.0 internally). 80 | 81 | - Adds new library functions and slot methods ``save`` 82 | (``save_filename`` when using within a slot), 83 | ``sync_resume``, ``set_pattern_event``, and ``get_pattern_event``. 84 | 85 | - Adds new documentation to ``pause``, ``resume``, ``get_number_of_modules``, 86 | ``get_module_inputs``, ``get_module_outputs``, and ``get_number_of_patterns``. 87 | 88 | 89 | 0.3.1.1.9.6.1 90 | ------------- 91 | 92 | - Updates SunVox DLL to 1.9.6c (still 1.9.6.1 internally). 93 | 94 | - Increases maximum number of slots to 16. 95 | 96 | - Adds new library functions and slot methods ``pause`` and ``resume``. 97 | 98 | - Allows passing extra initialization flags in to a ``BufferedProcess``. 99 | 100 | - Allows passing an input buffer to ``fill_buffer``. 101 | 102 | - Fixes loading DLL using Python 3.9 on Windows. 103 | 104 | 105 | 0.3.0.1.9.6.1 106 | ------------- 107 | 108 | - Update SunVox DLL to 1.9.6b (1.9.6.1). 109 | 110 | - Update minimum version of Python supported to 3.7. 111 | 112 | - Update docstrings to match new information in upstream ``sunvox.h``. 113 | 114 | - Update ``sunvox.slot.Slot.load_module`` method to always use 115 | ``load_module_from_memory`` function internally. 116 | 117 | - Remove ``SV_`` prefix from enums in ``sunvox.types``. 118 | 119 | - Remove functions that were deprecated in DLL 1.9.5c, and remove wrapper methods in 120 | ``sunvox.slot.Slot`` when relevant: 121 | 122 | - ``sunvox.dll.get_sample_type()`` 123 | 124 | - ``sunvox.dll.get_module_scope()`` 125 | 126 | - Add wrappers for functions added in DLL 1.9.4c, with wrapper methods in 127 | ``sunvox.slot.Slot`` when relevant: 128 | 129 | - ``sunvox.dll.audio_callback2()`` 130 | 131 | - ``sunvox.dll.update_input()`` 132 | 133 | - ``sunvox.dll.load_module_from_memory()`` 134 | 135 | - ``sunvox.dll.sampler_load_from_memory()`` 136 | 137 | - ``sunvox.dll.get_log()`` 138 | 139 | - Add wrappers for functions added in DLL 1.9.5c, with wrapper methods in 140 | ``sunvox.slot.Slot`` when relevant: 141 | 142 | - ``sunvox.dll.get_time_map()`` 143 | 144 | - ``sunvox.dll.get_autostop()`` 145 | 146 | - ``sunvox.dll.get_sample_rate()`` 147 | 148 | - ``sunvox.dll.find_module()`` 149 | 150 | - ``sunvox.dll.find_pattern()`` 151 | 152 | - ``sunvox.dll.get_pattern_name()`` 153 | 154 | - ``sunvox.dll.get_module_finetune()`` 155 | 156 | - ``sunvox.dll.module_curve()`` 157 | 158 | - ``sunvox.dll.set_event_t()`` 159 | 160 | - Add new constants: 161 | 162 | - ``sunvox.types.MODULE.FLAG_MUTE`` 163 | 164 | - ``sunvox.types.MODULE.FLAG_SOLO`` 165 | 166 | - ``sunvox.types.MODULE.FLAG_BYPASS`` 167 | 168 | - Add type annotations to all ``sunvox.dll`` function wrappers. 169 | 170 | - Add type annotations and docstrings to ``sunvox.slot.Slot`` methods. 171 | 172 | - Add ``sunvox.macros`` module containing ports of C macros introduced in 1.9.4c 173 | and 1.9.5c: 174 | 175 | - ``sunvox.macros.GET_MODULE_XY`` 176 | 177 | - ``sunvox.macros.GET_MODULE_FINETUNE`` 178 | 179 | - ``sunvox.macros.PITCH_TO_FREQUENCY`` 180 | 181 | - ``sunvox.macros.FREQUENCY_TO_PITCH`` 182 | 183 | - Format code using black_. 184 | 185 | .. _black: 186 | https://black.readthedocs.io/en/stable/ 187 | 188 | 189 | 0.2.1.1.9.3.2 (2018-02-28) 190 | -------------------------- 191 | 192 | - Add Raspberry Pi version of SunVox DLL. 193 | 194 | - Add ``sunvox.SUNVOX_COPYRIGHT_NOTICE`` constant, for use in apps 195 | that make use of this package. 196 | 197 | 198 | 0.2.0.1.9.3.1 (2017-11-25) 199 | -------------------------- 200 | 201 | - Update SunVox DLL to 1.9.3b (1.9.3.1). 202 | 203 | 204 | 0.2.0.1.9.3.0 (2017-11-25) 205 | -------------------------- 206 | 207 | - Update SunVox DLL to 1.9.3.0. 208 | 209 | - Add support for Windows 64-bit. 210 | 211 | - Correct notation of sharps/flats to match SunVox. 212 | 213 | - Improvements to buffered processes. 214 | 215 | 216 | 0.1.0.1.9.2.0 (2016-11-08) 217 | -------------------------- 218 | 219 | - Initial release. 220 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Contributions are welcome, and they are greatly appreciated! Every 6 | little bit helps, and credit will always be given. 7 | 8 | Bug reports 9 | =========== 10 | 11 | When `reporting a bug `_ please include: 12 | 13 | * Your operating system name and version. 14 | * Any details about your local setup that might be helpful in troubleshooting. 15 | * Detailed steps to reproduce the bug. 16 | 17 | Documentation improvements 18 | ========================== 19 | 20 | sunvox-dll-python could always use more documentation, whether as part of the 21 | official sunvox-dll-python docs, in docstrings, or even on the web in blog posts, 22 | articles, and such. 23 | 24 | Feature requests and feedback 25 | ============================= 26 | 27 | The best way to send feedback is to file an issue at https://github.com/metrasynth/sunvox-dll-python/issues. 28 | 29 | If you are proposing a feature: 30 | 31 | * Explain in detail how it would work. 32 | * Keep the scope as narrow as possible, to make it easier to implement. 33 | * Remember that this is a volunteer-driven project, and that code contributions are welcome :) 34 | 35 | Development 36 | =========== 37 | 38 | To set up `sunvox-dll-python` for local development: 39 | 40 | 1. Fork `sunvox-dll-python `_ 41 | (look for the "Fork" button). 42 | 2. Clone your fork locally:: 43 | 44 | git clone git@github.com:your_name_here/sunvox-dll-python.git 45 | 46 | 3. Create a branch for local development:: 47 | 48 | git checkout -b name-of-your-bugfix-or-feature 49 | 50 | Now you can make your changes locally. 51 | 52 | 4. When you're done making changes, run all the checks, doc builder and spell checker with `tox `_ one command:: 53 | 54 | tox 55 | 56 | 5. Commit your changes and push your branch to GitHub:: 57 | 58 | git add . 59 | git commit -m "Your detailed description of your changes." 60 | git push origin name-of-your-bugfix-or-feature 61 | 62 | 6. Submit a pull request through the GitHub website. 63 | 64 | Pull Request Guidelines 65 | ----------------------- 66 | 67 | If you need some code review or feedback while you're developing the code just make the pull request. 68 | 69 | For merging, you should: 70 | 71 | 1. Include passing tests (run ``tox``) [1]_. 72 | 2. Update documentation when there's new API, functionality etc. 73 | 3. Add a note to ``CHANGELOG.rst`` about the changes. 74 | 4. Add yourself to ``AUTHORS.rst``. 75 | 76 | .. [1] If you don't have all the necessary python versions available locally you can rely on Travis - it will 77 | `run the tests `_ for each change you add in the pull request. 78 | 79 | It will be slower though ... 80 | 81 | Tips 82 | ---- 83 | 84 | To run a subset of tests:: 85 | 86 | tox -e envname -- py.test -k test_myfeature 87 | 88 | To run all the test environments in *parallel* (you need to ``pip install detox``):: 89 | 90 | detox 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The files in this project are provided under the following licenses. 2 | 3 | "All other TXT files", required for inclusion by the "Sunvox library license", 4 | are presented inline in this file. 5 | 6 | sunvox-dll-python license 7 | ========================= 8 | 9 | MIT License 10 | 11 | Copyright (c) 2016-2025 Matthew Scott and contributors 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | 31 | 32 | SunVox library license 33 | ====================== 34 | 35 | You can freely use the SunVox library in your own products (even commercial ones). 36 | 37 | REQUIREMENT 1: 38 | The following text must be included in the documentation and/or other materials provided with your products: 39 | Powered by SunVox (modular synth & tracker) 40 | Copyright (c) 2008 - 2024, Alexander Zolotov , WarmPlace.ru 41 | 42 | REQUIREMENT 2: 43 | All other TXT files (from this folder) must be included in the documentation too. 44 | 45 | 46 | libflac.txt 47 | =========== 48 | 49 | Copyright (C) 2000-2009 Josh Coalson 50 | Copyright (C) 2011-2023 Xiph.Org Foundation 51 | 52 | Redistribution and use in source and binary forms, with or without 53 | modification, are permitted provided that the following conditions 54 | are met: 55 | 56 | - Redistributions of source code must retain the above copyright 57 | notice, this list of conditions and the following disclaimer. 58 | 59 | - Redistributions in binary form must reproduce the above copyright 60 | notice, this list of conditions and the following disclaimer in the 61 | documentation and/or other materials provided with the distribution. 62 | 63 | - Neither the name of the Xiph.Org Foundation nor the names of its 64 | contributors may be used to endorse or promote products derived from 65 | this software without specific prior written permission. 66 | 67 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 68 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 69 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 70 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR 71 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 72 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 73 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 74 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 75 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 76 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 77 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 78 | 79 | 80 | sundog.txt 81 | ========== 82 | 83 | SunDog engine is distributed under the MIT license: 84 | 85 | Copyright (c) 2004 - 2024, Alexander Zolotov 86 | WarmPlace.ru 87 | 88 | Permission is hereby granted, free of charge, to any person obtaining a copy 89 | of this software and associated documentation files (the "Software"), to 90 | deal in the Software without restriction, including without limitation the 91 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 92 | sell copies of the Software, and to permit persons to whom the Software is 93 | furnished to do so, subject to the following conditions: 94 | 95 | The above copyright notice and this permission notice shall be included in 96 | all copies or substantial portions of the Software. 97 | 98 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 99 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 100 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 101 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 102 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 103 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 104 | IN THE SOFTWARE. 105 | 106 | 107 | tremor.txt 108 | ========== 109 | 110 | Copyright (c) 2002, Xiph.org Foundation 111 | 112 | Redistribution and use in source and binary forms, with or without 113 | modification, are permitted provided that the following conditions 114 | are met: 115 | 116 | - Redistributions of source code must retain the above copyright 117 | notice, this list of conditions and the following disclaimer. 118 | 119 | - Redistributions in binary form must reproduce the above copyright 120 | notice, this list of conditions and the following disclaimer in the 121 | documentation and/or other materials provided with the distribution. 122 | 123 | - Neither the name of the Xiph.org Foundation nor the names of its 124 | contributors may be used to endorse or promote products derived from 125 | this software without specific prior written permission. 126 | 127 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 128 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 129 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 130 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION 131 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 132 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 133 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 134 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 135 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 136 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 137 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 138 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Overview of sunvox-dll-python 2 | ============================= 3 | 4 | Part of the Metrasynth_ project. 5 | 6 | .. _Metrasynth: https://metrasynth.github.io/ 7 | 8 | 9 | Purpose 10 | ------- 11 | 12 | Provides access to all of the SunVox library functions 13 | as described in the ``sunvox.h`` header file. 14 | 15 | Includes a copy of the most recent SunVox library for supported platforms. 16 | 17 | 18 | Requirements 19 | ------------ 20 | 21 | - Python 3.9+ 22 | 23 | - One of these supported operating systems: 24 | 25 | - macOS (64-bit Intel or M-series) 26 | 27 | - Linux (32-bit, 64-bit) 28 | 29 | - Windows (32-bit, 64-bit) 30 | 31 | 32 | About SunVox 33 | ------------ 34 | 35 | From the `SunVox home page`_: 36 | 37 | SunVox is a small, fast and powerful modular synthesizer with pattern-based sequencer (tracker). 38 | It is a tool for those people who like to compose music wherever they are, whenever they wish. 39 | On any device. SunVox is available for Windows, OS X, Linux, Maemo, Meego, Raspberry Pi, 40 | Windows Mobile (WindowsCE), PalmOS, iOS and Android. 41 | 42 | .. _SunVox home page: http://www.warmplace.ru/soft/sunvox/ 43 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | html 2 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " epub3 to make an epub3" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | @echo " dummy to check syntax errors of document sources" 51 | 52 | .PHONY: clean 53 | clean: 54 | rm -rf $(BUILDDIR)/* 55 | 56 | .PHONY: html 57 | html: 58 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 61 | 62 | .PHONY: dirhtml 63 | dirhtml: 64 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 65 | @echo 66 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 67 | 68 | .PHONY: singlehtml 69 | singlehtml: 70 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 71 | @echo 72 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 73 | 74 | .PHONY: pickle 75 | pickle: 76 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 77 | @echo 78 | @echo "Build finished; now you can process the pickle files." 79 | 80 | .PHONY: json 81 | json: 82 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 83 | @echo 84 | @echo "Build finished; now you can process the JSON files." 85 | 86 | .PHONY: htmlhelp 87 | htmlhelp: 88 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 89 | @echo 90 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 91 | ".hhp project file in $(BUILDDIR)/htmlhelp." 92 | 93 | .PHONY: qthelp 94 | qthelp: 95 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 96 | @echo 97 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 98 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 99 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/RadiantVoices.qhcp" 100 | @echo "To view the help file:" 101 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/RadiantVoices.qhc" 102 | 103 | .PHONY: applehelp 104 | applehelp: 105 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 106 | @echo 107 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 108 | @echo "N.B. You won't be able to view it unless you put it in" \ 109 | "~/Library/Documentation/Help or install it in your application" \ 110 | "bundle." 111 | 112 | .PHONY: devhelp 113 | devhelp: 114 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 115 | @echo 116 | @echo "Build finished." 117 | @echo "To view the help file:" 118 | @echo "# mkdir -p $$HOME/.local/share/devhelp/RadiantVoices" 119 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/RadiantVoices" 120 | @echo "# devhelp" 121 | 122 | .PHONY: epub 123 | epub: 124 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 125 | @echo 126 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 127 | 128 | .PHONY: epub3 129 | epub3: 130 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 131 | @echo 132 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 133 | 134 | .PHONY: latex 135 | latex: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo 138 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 139 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 140 | "(use \`make latexpdf' here to do that automatically)." 141 | 142 | .PHONY: latexpdf 143 | latexpdf: 144 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 145 | @echo "Running LaTeX files through pdflatex..." 146 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 147 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 148 | 149 | .PHONY: latexpdfja 150 | latexpdfja: 151 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 152 | @echo "Running LaTeX files through platex and dvipdfmx..." 153 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 154 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 155 | 156 | .PHONY: text 157 | text: 158 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 159 | @echo 160 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 161 | 162 | .PHONY: man 163 | man: 164 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 165 | @echo 166 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 167 | 168 | .PHONY: texinfo 169 | texinfo: 170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 171 | @echo 172 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 173 | @echo "Run \`make' in that directory to run these through makeinfo" \ 174 | "(use \`make info' here to do that automatically)." 175 | 176 | .PHONY: info 177 | info: 178 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 179 | @echo "Running Texinfo files through makeinfo..." 180 | make -C $(BUILDDIR)/texinfo info 181 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 182 | 183 | .PHONY: gettext 184 | gettext: 185 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 186 | @echo 187 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 188 | 189 | .PHONY: changes 190 | changes: 191 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 192 | @echo 193 | @echo "The overview file is in $(BUILDDIR)/changes." 194 | 195 | .PHONY: linkcheck 196 | linkcheck: 197 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 198 | @echo 199 | @echo "Link check complete; look for any errors in the above output " \ 200 | "or in $(BUILDDIR)/linkcheck/output.txt." 201 | 202 | .PHONY: doctest 203 | doctest: 204 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 205 | @echo "Testing of doctests in the sources finished, look at the " \ 206 | "results in $(BUILDDIR)/doctest/output.txt." 207 | 208 | .PHONY: coverage 209 | coverage: 210 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 211 | @echo "Testing of coverage in the sources finished, look at the " \ 212 | "results in $(BUILDDIR)/coverage/python.txt." 213 | 214 | .PHONY: xml 215 | xml: 216 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 217 | @echo 218 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 219 | 220 | .PHONY: pseudoxml 221 | pseudoxml: 222 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 223 | @echo 224 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 225 | 226 | .PHONY: dummy 227 | dummy: 228 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 229 | @echo 230 | @echo "Build finished. Dummy builder generates no files." 231 | -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGELOG.rst 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # sunvox-dll-python documentation build configuration file, created by 5 | # sphinx-quickstart on Fri Sep 2 09:36:30 2016. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | 17 | import sunvox 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | # sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | # needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | "sphinx.ext.autodoc", 34 | "sphinx.ext.intersphinx", 35 | "sphinx.ext.viewcode", 36 | "plantweb.directive", 37 | ] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ["_templates"] 41 | 42 | # The suffix(es) of source filenames. 43 | # You can specify multiple suffix as a list of string: 44 | # source_suffix = ['.rst', '.md'] 45 | source_suffix = ".rst" 46 | 47 | # The encoding of source files. 48 | # source_encoding = 'utf-8-sig' 49 | 50 | # The master toctree document. 51 | master_doc = "index" 52 | 53 | # General information about the project. 54 | project = "sunvox-dll-python" 55 | copyright = "2016, Matthew Scott" 56 | author = "Matthew Scott" 57 | 58 | # The version info for the project you're documenting, acts as replacement for 59 | # |version| and |release|, also used in various other places throughout the 60 | # built documents. 61 | # 62 | # The short X.Y version. 63 | version = ".".join(sunvox.__version__.split(".")[:2]) 64 | # The full version, including alpha/beta/rc tags. 65 | release = sunvox.__version__ 66 | 67 | # The language for content autogenerated by Sphinx. Refer to documentation 68 | # for a list of supported languages. 69 | # 70 | # This is also used if you do content translation via gettext catalogs. 71 | # Usually you set "language" from the command line for these cases. 72 | language = None 73 | 74 | # There are two options for replacing |today|: either, you set today to some 75 | # non-false value, then it is used: 76 | # today = '' 77 | # Else, today_fmt is used as the format for a strftime call. 78 | # today_fmt = '%B %d, %Y' 79 | 80 | # List of patterns, relative to source directory, that match files and 81 | # directories to ignore when looking for source files. 82 | # This patterns also effect to html_static_path and html_extra_path 83 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 84 | 85 | # The reST default role (used for this markup: `text`) to use for all 86 | # documents. 87 | # default_role = None 88 | 89 | # If true, '()' will be appended to :func: etc. cross-reference text. 90 | # add_function_parentheses = True 91 | 92 | # If true, the current module name will be prepended to all description 93 | # unit titles (such as .. function::). 94 | # add_module_names = True 95 | 96 | # If true, sectionauthor and moduleauthor directives will be shown in the 97 | # output. They are ignored by default. 98 | # show_authors = False 99 | 100 | # The name of the Pygments (syntax highlighting) style to use. 101 | pygments_style = "sphinx" 102 | 103 | # A list of ignored prefixes for module index sorting. 104 | # modindex_common_prefix = [] 105 | 106 | # If true, keep warnings as "system message" paragraphs in the built documents. 107 | # keep_warnings = False 108 | 109 | # If true, `todo` and `todoList` produce output, else they produce nothing. 110 | todo_include_todos = False 111 | 112 | 113 | # -- Options for HTML output ---------------------------------------------- 114 | 115 | # The theme to use for HTML and HTML Help pages. See the documentation for 116 | # a list of builtin themes. 117 | html_theme = "sphinx_rtd_theme" 118 | 119 | # Theme options are theme-specific and customize the look and feel of a theme 120 | # further. For a list of options available for each theme, see the 121 | # documentation. 122 | # html_theme_options = {} 123 | 124 | # Add any paths that contain custom themes here, relative to this directory. 125 | # html_theme_path = [] 126 | 127 | # The name for this set of Sphinx documents. 128 | # " v documentation" by default. 129 | # html_title = 'sunvox-dll-python v0.1.0' 130 | 131 | # A shorter title for the navigation bar. Default is the same as html_title. 132 | # html_short_title = None 133 | 134 | # The name of an image file (relative to this directory) to place at the top 135 | # of the sidebar. 136 | # html_logo = None 137 | 138 | # The name of an image file (relative to this directory) to use as a favicon of 139 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 140 | # pixels large. 141 | # html_favicon = None 142 | 143 | # Add any paths that contain custom static files (such as style sheets) here, 144 | # relative to this directory. They are copied after the builtin static files, 145 | # so a file named "default.css" will overwrite the builtin "default.css". 146 | html_static_path = ["_static"] 147 | 148 | # Add any extra paths that contain custom files (such as robots.txt or 149 | # .htaccess) here, relative to this directory. These files are copied 150 | # directly to the root of the documentation. 151 | # html_extra_path = [] 152 | 153 | # If not None, a 'Last updated on:' timestamp is inserted at every page 154 | # bottom, using the given strftime format. 155 | # The empty string is equivalent to '%b %d, %Y'. 156 | # html_last_updated_fmt = None 157 | 158 | # If true, SmartyPants will be used to convert quotes and dashes to 159 | # typographically correct entities. 160 | # html_use_smartypants = True 161 | 162 | # Custom sidebar templates, maps document names to template names. 163 | # html_sidebars = {} 164 | 165 | # Additional templates that should be rendered to pages, maps page names to 166 | # template names. 167 | # html_additional_pages = {} 168 | 169 | # If false, no module index is generated. 170 | # html_domain_indices = True 171 | 172 | # If false, no index is generated. 173 | # html_use_index = True 174 | 175 | # If true, the index is split into individual pages for each letter. 176 | # html_split_index = False 177 | 178 | # If true, links to the reST sources are added to the pages. 179 | # html_show_sourcelink = True 180 | 181 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 182 | # html_show_sphinx = True 183 | 184 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 185 | # html_show_copyright = True 186 | 187 | # If true, an OpenSearch description file will be output, and all pages will 188 | # contain a tag referring to it. The value of this option must be the 189 | # base URL from which the finished HTML is served. 190 | # html_use_opensearch = '' 191 | 192 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 193 | # html_file_suffix = None 194 | 195 | # Language to be used for generating the HTML full-text search index. 196 | # Sphinx supports the following languages: 197 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 198 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' 199 | # html_search_language = 'en' 200 | 201 | # A dictionary with options for the search language support, empty by default. 202 | # 'ja' uses this config value. 203 | # 'zh' user can custom change `jieba` dictionary path. 204 | # html_search_options = {'type': 'default'} 205 | 206 | # The name of a javascript file (relative to the configuration directory) that 207 | # implements a search results scorer. If empty, the default will be used. 208 | # html_search_scorer = 'scorer.js' 209 | 210 | # Output file base name for HTML help builder. 211 | htmlhelp_basename = "sunvoxdllpythondoc" 212 | 213 | # -- Options for LaTeX output --------------------------------------------- 214 | 215 | latex_elements = { 216 | # The paper size ('letterpaper' or 'a4paper'). 217 | #'papersize': 'letterpaper', 218 | # The font size ('10pt', '11pt' or '12pt'). 219 | #'pointsize': '10pt', 220 | # Additional stuff for the LaTeX preamble. 221 | #'preamble': '', 222 | # Latex figure (float) alignment 223 | #'figure_align': 'htbp', 224 | } 225 | 226 | # Grouping the document tree into LaTeX files. List of tuples 227 | # (source start file, target name, title, 228 | # author, documentclass [howto, manual, or own class]). 229 | latex_documents = [ 230 | ( 231 | master_doc, 232 | "sunvoxdllpython.tex", 233 | "sunvox-dll-python Documentation", 234 | "Matthew Scott", 235 | "manual", 236 | ) 237 | ] 238 | 239 | # The name of an image file (relative to this directory) to place at the top of 240 | # the title page. 241 | # latex_logo = None 242 | 243 | # For "manual" documents, if this is true, then toplevel headings are parts, 244 | # not chapters. 245 | # latex_use_parts = False 246 | 247 | # If true, show page references after internal links. 248 | # latex_show_pagerefs = False 249 | 250 | # If true, show URL addresses after external links. 251 | # latex_show_urls = False 252 | 253 | # Documents to append as an appendix to all manuals. 254 | # latex_appendices = [] 255 | 256 | # If false, no module index is generated. 257 | # latex_domain_indices = True 258 | 259 | 260 | # -- Options for manual page output --------------------------------------- 261 | 262 | # One entry per manual page. List of tuples 263 | # (source start file, name, description, authors, manual section). 264 | man_pages = [ 265 | (master_doc, "sunvoxdllpython", "sunvox-dll-python Documentation", [author], 1) 266 | ] 267 | 268 | # If true, show URL addresses after external links. 269 | # man_show_urls = False 270 | 271 | 272 | # -- Options for Texinfo output ------------------------------------------- 273 | 274 | # Grouping the document tree into Texinfo files. List of tuples 275 | # (source start file, target name, title, author, 276 | # dir menu entry, description, category) 277 | texinfo_documents = [ 278 | ( 279 | master_doc, 280 | "sunvoxdllpython", 281 | "sunvox-dll-python Documentation", 282 | author, 283 | "sunvoxdllpython", 284 | "One line description of project.", 285 | "Miscellaneous", 286 | ) 287 | ] 288 | 289 | # Documents to append as an appendix to all manuals. 290 | # texinfo_appendices = [] 291 | 292 | # If false, no module index is generated. 293 | # texinfo_domain_indices = True 294 | 295 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 296 | # texinfo_show_urls = 'footnote' 297 | 298 | # If true, do not generate a @detailmenu in the "Top" node's menu. 299 | # texinfo_no_detailmenu = False 300 | 301 | 302 | # Example configuration for intersphinx: refer to the Python standard library. 303 | # intersphinx_mapping = {"https://docs.python.org/": None} 304 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Contents 2 | ======== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | readme 8 | contributing 9 | updating 10 | changelog 11 | authors 12 | license 13 | 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../LICENSE 2 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. epub3 to make an epub3 31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 32 | echo. text to make text files 33 | echo. man to make manual pages 34 | echo. texinfo to make Texinfo files 35 | echo. gettext to make PO message catalogs 36 | echo. changes to make an overview over all changed/added/deprecated items 37 | echo. xml to make Docutils-native XML files 38 | echo. pseudoxml to make pseudoxml-XML files for display purposes 39 | echo. linkcheck to check all external links for integrity 40 | echo. doctest to run all doctests embedded in the documentation if enabled 41 | echo. coverage to run coverage check of the documentation if enabled 42 | echo. dummy to check syntax errors of document sources 43 | goto end 44 | ) 45 | 46 | if "%1" == "clean" ( 47 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 48 | del /q /s %BUILDDIR%\* 49 | goto end 50 | ) 51 | 52 | 53 | REM Check if sphinx-build is available and fallback to Python version if any 54 | %SPHINXBUILD% 1>NUL 2>NUL 55 | if errorlevel 9009 goto sphinx_python 56 | goto sphinx_ok 57 | 58 | :sphinx_python 59 | 60 | set SPHINXBUILD=python -m sphinx.__init__ 61 | %SPHINXBUILD% 2> nul 62 | if errorlevel 9009 ( 63 | echo. 64 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 65 | echo.installed, then set the SPHINXBUILD environment variable to point 66 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 67 | echo.may add the Sphinx directory to PATH. 68 | echo. 69 | echo.If you don't have Sphinx installed, grab it from 70 | echo.http://sphinx-doc.org/ 71 | exit /b 1 72 | ) 73 | 74 | :sphinx_ok 75 | 76 | 77 | if "%1" == "html" ( 78 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 79 | if errorlevel 1 exit /b 1 80 | echo. 81 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 82 | goto end 83 | ) 84 | 85 | if "%1" == "dirhtml" ( 86 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 87 | if errorlevel 1 exit /b 1 88 | echo. 89 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 90 | goto end 91 | ) 92 | 93 | if "%1" == "singlehtml" ( 94 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 95 | if errorlevel 1 exit /b 1 96 | echo. 97 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 98 | goto end 99 | ) 100 | 101 | if "%1" == "pickle" ( 102 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 103 | if errorlevel 1 exit /b 1 104 | echo. 105 | echo.Build finished; now you can process the pickle files. 106 | goto end 107 | ) 108 | 109 | if "%1" == "json" ( 110 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 111 | if errorlevel 1 exit /b 1 112 | echo. 113 | echo.Build finished; now you can process the JSON files. 114 | goto end 115 | ) 116 | 117 | if "%1" == "htmlhelp" ( 118 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 119 | if errorlevel 1 exit /b 1 120 | echo. 121 | echo.Build finished; now you can run HTML Help Workshop with the ^ 122 | .hhp project file in %BUILDDIR%/htmlhelp. 123 | goto end 124 | ) 125 | 126 | if "%1" == "qthelp" ( 127 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 128 | if errorlevel 1 exit /b 1 129 | echo. 130 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 131 | .qhcp project file in %BUILDDIR%/qthelp, like this: 132 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\RadiantVoices.qhcp 133 | echo.To view the help file: 134 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\RadiantVoices.ghc 135 | goto end 136 | ) 137 | 138 | if "%1" == "devhelp" ( 139 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 140 | if errorlevel 1 exit /b 1 141 | echo. 142 | echo.Build finished. 143 | goto end 144 | ) 145 | 146 | if "%1" == "epub" ( 147 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 148 | if errorlevel 1 exit /b 1 149 | echo. 150 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 151 | goto end 152 | ) 153 | 154 | if "%1" == "epub3" ( 155 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 156 | if errorlevel 1 exit /b 1 157 | echo. 158 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. 159 | goto end 160 | ) 161 | 162 | if "%1" == "latex" ( 163 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 164 | if errorlevel 1 exit /b 1 165 | echo. 166 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdf" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "latexpdfja" ( 181 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 182 | cd %BUILDDIR%/latex 183 | make all-pdf-ja 184 | cd %~dp0 185 | echo. 186 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 187 | goto end 188 | ) 189 | 190 | if "%1" == "text" ( 191 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 192 | if errorlevel 1 exit /b 1 193 | echo. 194 | echo.Build finished. The text files are in %BUILDDIR%/text. 195 | goto end 196 | ) 197 | 198 | if "%1" == "man" ( 199 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 200 | if errorlevel 1 exit /b 1 201 | echo. 202 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 203 | goto end 204 | ) 205 | 206 | if "%1" == "texinfo" ( 207 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 208 | if errorlevel 1 exit /b 1 209 | echo. 210 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 211 | goto end 212 | ) 213 | 214 | if "%1" == "gettext" ( 215 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 216 | if errorlevel 1 exit /b 1 217 | echo. 218 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 219 | goto end 220 | ) 221 | 222 | if "%1" == "changes" ( 223 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 224 | if errorlevel 1 exit /b 1 225 | echo. 226 | echo.The overview file is in %BUILDDIR%/changes. 227 | goto end 228 | ) 229 | 230 | if "%1" == "linkcheck" ( 231 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 232 | if errorlevel 1 exit /b 1 233 | echo. 234 | echo.Link check complete; look for any errors in the above output ^ 235 | or in %BUILDDIR%/linkcheck/output.txt. 236 | goto end 237 | ) 238 | 239 | if "%1" == "doctest" ( 240 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 241 | if errorlevel 1 exit /b 1 242 | echo. 243 | echo.Testing of doctests in the sources finished, look at the ^ 244 | results in %BUILDDIR%/doctest/output.txt. 245 | goto end 246 | ) 247 | 248 | if "%1" == "coverage" ( 249 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 250 | if errorlevel 1 exit /b 1 251 | echo. 252 | echo.Testing of coverage in the sources finished, look at the ^ 253 | results in %BUILDDIR%/coverage/python.txt. 254 | goto end 255 | ) 256 | 257 | if "%1" == "xml" ( 258 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 259 | if errorlevel 1 exit /b 1 260 | echo. 261 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 262 | goto end 263 | ) 264 | 265 | if "%1" == "pseudoxml" ( 266 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 267 | if errorlevel 1 exit /b 1 268 | echo. 269 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 270 | goto end 271 | ) 272 | 273 | if "%1" == "dummy" ( 274 | %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy 275 | if errorlevel 1 exit /b 1 276 | echo. 277 | echo.Build finished. Dummy builder generates no files. 278 | goto end 279 | ) 280 | 281 | :end 282 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /docs/updating.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Updating sunvox-dll-python 3 | ========================== 4 | 5 | This is a loose collection of notes for maintaining releases of sunvox-dll-python 6 | and uploading them to the Python Package Index (PyPI). 7 | 8 | 9 | After new releases of SunVox library 10 | ==================================== 11 | 12 | 1. Copy libraries from the SunVox library distribution, e.g.: 13 | ``cd sunvox/lib; ./copy-libs.sh ../../../sunvox_lib`` 14 | 15 | 2. Change ``sunvox/dll.py`` to add, change, or remove any functions, 16 | based on changes in ``sunvox.h`` in the SunVox library itself. 17 | 18 | 3. Change ``sunvox/slot.py`` to mirror any changes made to ``sunvox/dll.py``. 19 | 20 | 4. Update ``sunvox/__init__.py:__version__`` to match the new SunVox version. 21 | 22 | The format of the version is ``.``, 23 | where ```` is ``..``, and 24 | ```` is the four-segment representation of the SunVox version. 25 | (For example, 1.9.6b is 1.9.6.1) 26 | 27 | If changes were made to how the Python wrapper works, 28 | bump ```` accordingly. 29 | If changes only reflect SunVox library changes, 30 | bump only the patch number. 31 | 32 | 5. Update ``CHANGELOG.rst``. 33 | 34 | 35 | Prep for PyPI and test 36 | ====================== 37 | 38 | :: 39 | 40 | $ devpi login --password= 41 | $ rm -v dist/* 42 | $ poetry build 43 | $ devpi upload dist/* 44 | $ virtualenv testenv 45 | $ cd testenv 46 | $ . bin/activate 47 | $ devpi install sunvox-dll-python 48 | $ python 49 | >>> import sunvox 50 | >>> sunvox.__version__ 51 | '0.3.6.2.1.2.1' 52 | >>> from sunvox.api import init 53 | >>> hex(init(None, 44100, 2, 0)) 54 | '0x20102' 55 | >>> ^D 56 | $ deactivate 57 | $ cd .. 58 | $ rm -rf testenv 59 | 60 | 61 | Upload to PyPI 62 | ============== 63 | 64 | :: 65 | 66 | $ git tag 67 | $ git push --tags 68 | $ twine upload dist/* 69 | $ rm -v dist/* 70 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "sunvox-dll-python" 3 | version = "0.3.6.2.1.2.1" 4 | description = "Python ctypes-based wrapper for the SunVox library" 5 | readme = "README.rst" 6 | authors = [ 7 | { name = "Matthew Scott", email = "matt@11craft.com" } 8 | ] 9 | requires-python = ">=3.9" 10 | dependencies = [] 11 | documentation = "https://sunvox-dll-python.readthedocs.io/en/latest/" 12 | repository = "https://github.com/metrasynth/sunvox-dll-python" 13 | 14 | [project.optional-dependencies] 15 | buffered = [ 16 | "numpy>=2.0.2", 17 | ] 18 | docs = [ 19 | "plantweb>=1.3.0", 20 | "radiant-voices>=1.0.3", 21 | "sphinx>=7.4.7", 22 | "sphinx-rtd-theme>=3.0.2", 23 | ] 24 | tools = [ 25 | "numpy>=2.0.2", 26 | "scipy>=1.13.1", 27 | "tqdm>=4.67.1", 28 | ] 29 | 30 | [build-system] 31 | requires = ["hatchling"] 32 | build-backend = "hatchling.build" 33 | 34 | [tool.hatch.build.targets.wheel] 35 | packages = ["sunvox"] 36 | 37 | [dependency-groups] 38 | dev = [ 39 | "pytest>=8.3.4", 40 | "pytest-watch>=4.2.0", 41 | ] 42 | 43 | [tool.pytest.ini_options] 44 | addopts = "--doctest-glob='*.rst' --doctest-modules" 45 | testpaths = "sunvox tests" 46 | 47 | [tool.ruff] 48 | target-version = "py39" 49 | exclude = [ 50 | ".git", 51 | ".venv", 52 | ".idea", 53 | "build", 54 | "dist", 55 | ] 56 | 57 | [tool.ruff.lint.isort] 58 | case-sensitive = true 59 | lines-after-imports = 2 60 | -------------------------------------------------------------------------------- /sunvox/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.3.6.2.1.2.1" 2 | 3 | SUNVOX_COPYRIGHT_NOTICE = """\ 4 | SunVox modular synthesizer 5 | Copyright (c) 2008-2024, Alexander Zolotov , WarmPlace.ru 6 | 7 | Ogg Vorbis 'Tremor' integer playback codec 8 | Copyright (c) 2002, Xiph.org Foundation 9 | """ 10 | -------------------------------------------------------------------------------- /sunvox/api.py: -------------------------------------------------------------------------------- 1 | import sunvox.dll 2 | import sunvox.process 3 | import sunvox.slot 4 | import sunvox.types 5 | 6 | from sunvox.dll import * # NOQA 7 | from sunvox.macros import * # NOQA 8 | from sunvox.process import * # NOQA 9 | from sunvox.slot import * # NOQA 10 | from sunvox.types import * # NOQA 11 | 12 | __all__ = ( 13 | sunvox.dll.__all__ 14 | + sunvox.macros.__all__ 15 | + sunvox.slot.__all__ 16 | + sunvox.process.__all__ 17 | + sunvox.types.__all__ 18 | ) 19 | -------------------------------------------------------------------------------- /sunvox/buffered/__init__.py: -------------------------------------------------------------------------------- 1 | from .process import * # NOQA 2 | -------------------------------------------------------------------------------- /sunvox/buffered/process.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | 3 | import numpy 4 | from numpy import float32, int16, zeros 5 | from sunvox.api import INIT_FLAG, Process 6 | 7 | from .processor import BufferedProcessor 8 | 9 | 10 | DATA_TYPE_FLAGS = { 11 | int16: INIT_FLAG.AUDIO_INT16, 12 | float32: INIT_FLAG.AUDIO_FLOAT32, 13 | } 14 | 15 | 16 | class BufferedProcess(Process): 17 | freq = 44100 18 | channels = 2 19 | data_type = float32 20 | size = freq // 60 21 | 22 | processor_class = BufferedProcessor 23 | 24 | def __init__( 25 | self, 26 | freq=freq, 27 | channels=channels, 28 | data_type=data_type, 29 | size=size, 30 | extra_flags=0, 31 | ): 32 | super(BufferedProcess, self).__init__() 33 | self.freq = freq 34 | self.channels = channels 35 | self.data_type = data_type 36 | self.size = size 37 | flags = ( 38 | INIT_FLAG.USER_AUDIO_CALLBACK 39 | | INIT_FLAG.ONE_THREAD 40 | | DATA_TYPE_FLAGS[self.data_type] 41 | | extra_flags 42 | ) 43 | self.init(None, self.freq, self.channels, flags) 44 | self.init_buffer() 45 | 46 | def _send(self, name, *args, **kwargs): 47 | self._lock.acquire() 48 | try: 49 | return self._conn.send((name, args, kwargs)) 50 | finally: 51 | self._lock.release() 52 | 53 | def _recv(self): 54 | self._lock.acquire() 55 | try: 56 | return self._conn.recv() 57 | finally: 58 | self._lock.release() 59 | 60 | @property 61 | def samples(self): 62 | return self.size * self.channels 63 | 64 | @property 65 | def shape(self): 66 | return self.size, self.channels 67 | 68 | @property 69 | def type_code(self): 70 | return {int16: " 2**32 61 | key = (platform, is64bit) 62 | rel_path = PLATFORM_RELATIVE_PATHS.get(key) 63 | if platform == "win32": 64 | machine_path = "lib_x86_64" if is64bit else "lib_x86" 65 | lib_path = os.path.join(DEFAULT_DLL_BASE, "windows", machine_path) 66 | os.environ["PATH"] = f"{lib_path};{os.environ['PATH']}" 67 | return f"{lib_path}\\{rel_path}.dll" 68 | if rel_path is not None: 69 | return os.path.join(DLL_BASE, rel_path) 70 | raise NotImplementedError("SunVox library not available for your platform.") 71 | 72 | 73 | def _platform_with_machine(): 74 | platform = sys.platform 75 | machine = os.uname().machine if platform != "win32" else None 76 | if platform == "darwin" and machine == "arm64": 77 | return "darwin-arm" 78 | if platform == "linux" and machine in {"armv7l", "aarch64"}: 79 | return "linux-arm" 80 | return platform 81 | 82 | 83 | _s = _load_library() 84 | 85 | 86 | GenericFunction = Callable[..., Any] 87 | 88 | 89 | def sunvox_fn( 90 | c_fn, 91 | arg_ctypes=None, 92 | return_ctype=None, 93 | needs_lock=False, 94 | ): 95 | """ 96 | Decorate a ctypes function based on a function declaration's type annotations. 97 | 98 | :param c_fn: The function in the loaded SunVox library (`_s` global) 99 | :return: The decorated function. 100 | """ 101 | 102 | def decorator(fn: GenericFunction) -> GenericFunction: 103 | spec = inspect.getfullargspec(fn) 104 | annotations = spec.annotations 105 | ctypes = arg_ctypes or [annotations[arg] for arg in spec.args] 106 | arg_sig = ", ".join( 107 | f"{arg}: {ctype}" for (arg, ctype) in zip(spec.args, ctypes) 108 | ) 109 | signature = f"{fn.__name__}({arg_sig})" 110 | doc = dedent(fn.__doc__ or "").strip() 111 | c_fn.argtypes = arg_ctypes 112 | c_fn.restype = return_ctype or annotations["return"] 113 | c_fn.needs_lock = needs_lock 114 | c_fn.sunvox_dll_fn = True 115 | c_fn.__doc__ = f"{signature}\n\n{doc}" 116 | return c_fn 117 | 118 | return decorator 119 | 120 | 121 | @sunvox_fn( 122 | _s.sv_init, 123 | [ 124 | c_char_p, 125 | c_int, 126 | c_int, 127 | c_uint32, 128 | ], 129 | c_int, 130 | ) 131 | def init( 132 | config: Optional[bytes], 133 | freq: int, 134 | channels: int, 135 | flags: int, 136 | ) -> int: 137 | """ 138 | global sound system init 139 | 140 | Parameters: 141 | config - 142 | string with additional configuration in the following format: 143 | "option_name=value|option_name=value"; 144 | example: "buffer=1024|audiodriver=alsa|audiodevice=hw:0,0"; 145 | use null if you agree to the automatic configuration; 146 | freq - 147 | desired sample rate (Hz); min - 44100; 148 | the actual rate may be different, if INIT_FLAG.USER_AUDIO_CALLBACK is not set; 149 | channels - only 2 supported now; 150 | flags - mix of the INIT_FLAG.xxx flags. 151 | """ 152 | 153 | 154 | @sunvox_fn( 155 | _s.sv_deinit, 156 | [], 157 | c_int, 158 | ) 159 | def deinit() -> int: 160 | """ 161 | global sound system deinit 162 | """ 163 | 164 | 165 | @sunvox_fn( 166 | _s.sv_get_sample_rate, 167 | [], 168 | c_int, 169 | ) 170 | def get_sample_rate() -> int: 171 | """ 172 | Get current sampling rate (it may differ from the frequency specified in sv_init()) 173 | """ 174 | 175 | 176 | @sunvox_fn( 177 | _s.sv_update_input, 178 | [], 179 | c_int, 180 | ) 181 | def update_input() -> int: 182 | """ 183 | handle input ON/OFF requests to enable/disable input ports of the sound card 184 | (for example, after the Input module creation). 185 | 186 | Call it from the main thread only, where the SunVox sound stream is not locked. 187 | """ 188 | 189 | 190 | @sunvox_fn( 191 | _s.sv_audio_callback, 192 | [ 193 | c_void_p, 194 | c_int, 195 | c_int, 196 | c_uint32, 197 | ], 198 | c_int, 199 | ) 200 | def audio_callback( 201 | buf: bytes, 202 | frames: int, 203 | latency: int, 204 | out_time: int, 205 | ) -> int: 206 | """ 207 | get the next piece of SunVox audio from the Output module. 208 | 209 | With audio_callback() you can ignore the built-in SunVox sound output mechanism 210 | and use some other sound system. 211 | 212 | INIT_FLAG.USER_AUDIO_CALLBACK flag in sv_init() mus be set. 213 | 214 | Parameters: 215 | buf - 216 | destination buffer of type int16_t (if INIT_FLAG.AUDIO_INT16 used in init()) 217 | or float (if INIT_FLAG.AUDIO_FLOAT32 used in init()); 218 | stereo data will be interleaved in this buffer: LRLR... ; 219 | where the LR is the one frame (Left+Right channels); 220 | frames - number of frames in destination buffer; 221 | latency - audio latency (in frames); 222 | out_time - buffer output time (in system ticks, SunVox time space); 223 | 224 | Return values: 0 - silence (buffer filled with zeroes); 1 - some signal. 225 | 226 | Example 1 (simplified, without accurate time sync) - suitable for most cases: 227 | sv_audio_callback( buf, frames, 0, sv_get_ticks() ); 228 | 229 | Example 2 (accurate time sync) - when you need to maintain exact time intervals 230 | between incoming events (notes, commands, etc.): 231 | user_out_time = ... ; //output time in user time space 232 | //(depends on your own implementation) 233 | user_cur_time = ... ; //current time in user time space 234 | user_ticks_per_second = ... ; //ticks per second in user time space 235 | user_latency = user_out_time - user_cur_time; //latency in user time space 236 | uint32_t sunvox_latency = 237 | ( user_latency * sv_get_ticks_per_second() ) / user_ticks_per_second; 238 | //latency in SunVox time space 239 | uint32_t latency_frames = 240 | ( user_latency * sample_rate_Hz ) / user_ticks_per_second; 241 | //latency in frames 242 | sv_audio_callback( buf, frames, latency_frames, sv_get_ticks() + sunvox_latency ); 243 | """ 244 | 245 | 246 | @sunvox_fn( 247 | _s.sv_audio_callback2, 248 | [ 249 | c_void_p, 250 | c_int, 251 | c_int, 252 | c_uint32, 253 | c_int, 254 | c_int, 255 | c_void_p, 256 | ], 257 | c_int, 258 | ) 259 | def audio_callback2( 260 | buf: bytes, 261 | frames: int, 262 | latency: int, 263 | out_time: int, 264 | in_type: int, 265 | in_channels: int, 266 | in_buf: bytes, 267 | ) -> int: 268 | """ 269 | send some data to the Input module and receive the filtered data from the Output 270 | module. 271 | 272 | It's the same as sv_audio_callback() but you also can specify the input buffer. 273 | 274 | Parameters: 275 | ... 276 | in_type - input buffer type: 277 | 0 - int16_t (16bit integer); 278 | 1 - float (32bit floating point); 279 | in_channels - number of input channels; 280 | in_buf - 281 | input buffer; 282 | stereo data must be interleaved in this buffer: LRLR... ; 283 | where the LR is the one frame (Left+Right channels); 284 | """ 285 | 286 | 287 | @sunvox_fn( 288 | _s.sv_open_slot, 289 | [ 290 | c_int, 291 | ], 292 | c_int, 293 | ) 294 | def open_slot(slot: int) -> int: 295 | """ 296 | open sound slot for SunVox. 297 | 298 | You can use several slots simultaneously (each slot with its own SunVox engine). 299 | 300 | Use lock/unlock when you simultaneously read and modify SunVox data from different 301 | threads (for the same slot); 302 | 303 | example: 304 | thread 1: sv_lock_slot(0); sv_get_module_flags(0,mod1); sv_unlock_slot(0); 305 | thread 2: sv_lock_slot(0); sv_remove_module(0,mod2); sv_unlock_slot(0); 306 | 307 | Some functions (marked as "USE LOCK/UNLOCK") can't work without lock/unlock at all. 308 | """ 309 | 310 | 311 | @sunvox_fn( 312 | _s.sv_close_slot, 313 | [ 314 | c_int, 315 | ], 316 | c_int, 317 | ) 318 | def close_slot( 319 | slot: int, 320 | ) -> int: 321 | """ 322 | close sound slot for SunVox. 323 | 324 | You can use several slots simultaneously (each slot with its own SunVox engine). 325 | 326 | Use lock/unlock when you simultaneously read and modify SunVox data from different 327 | threads (for the same slot); 328 | 329 | example: 330 | thread 1: sv_lock_slot(0); sv_get_module_flags(0,mod1); sv_unlock_slot(0); 331 | thread 2: sv_lock_slot(0); sv_remove_module(0,mod2); sv_unlock_slot(0); 332 | 333 | Some functions (marked as "USE LOCK/UNLOCK") can't work without lock/unlock at all. 334 | """ 335 | 336 | 337 | @sunvox_fn( 338 | _s.sv_lock_slot, 339 | [ 340 | c_int, 341 | ], 342 | c_int, 343 | ) 344 | def lock_slot( 345 | slot: int, 346 | ) -> int: 347 | """ 348 | lock sound slot for SunVox. 349 | 350 | You can use several slots simultaneously (each slot with its own SunVox engine). 351 | 352 | Use lock/unlock when you simultaneously read and modify SunVox data from different 353 | threads (for the same slot); 354 | 355 | example: 356 | thread 1: sv_lock_slot(0); sv_get_module_flags(0,mod1); sv_unlock_slot(0); 357 | thread 2: sv_lock_slot(0); sv_remove_module(0,mod2); sv_unlock_slot(0); 358 | 359 | Some functions (marked as "USE LOCK/UNLOCK") can't work without lock/unlock at all. 360 | """ 361 | 362 | 363 | @sunvox_fn( 364 | _s.sv_unlock_slot, 365 | [ 366 | c_int, 367 | ], 368 | c_int, 369 | ) 370 | def unlock_slot( 371 | slot: int, 372 | ) -> int: 373 | """ 374 | unlock sound slot for SunVox. 375 | 376 | You can use several slots simultaneously (each slot with its own SunVox engine). 377 | 378 | Use lock/unlock when you simultaneously read and modify SunVox data from different 379 | threads (for the same slot); 380 | 381 | example: 382 | thread 1: sv_lock_slot(0); sv_get_module_flags(0,mod1); sv_unlock_slot(0); 383 | thread 2: sv_lock_slot(0); sv_remove_module(0,mod2); sv_unlock_slot(0); 384 | 385 | Some functions (marked as "USE LOCK/UNLOCK") can't work without lock/unlock at all. 386 | """ 387 | 388 | 389 | @sunvox_fn( 390 | _s.sv_load, 391 | [ 392 | c_int, 393 | c_char_p, 394 | ], 395 | c_int, 396 | ) 397 | def load( 398 | slot: int, 399 | name: bytes, 400 | ) -> int: 401 | """ 402 | load SunVox project from the file. 403 | """ 404 | 405 | 406 | @sunvox_fn( 407 | _s.sv_load_from_memory, 408 | [ 409 | c_int, 410 | c_void_p, 411 | c_uint32, 412 | ], 413 | c_int, 414 | ) 415 | def load_from_memory( 416 | slot: int, 417 | data: bytes, 418 | data_size: int, 419 | ) -> int: 420 | """ 421 | load SunVox project from the memory block. 422 | """ 423 | 424 | 425 | @sunvox_fn( 426 | _s.sv_save, 427 | [ 428 | c_int, 429 | c_char_p, 430 | ], 431 | c_int, 432 | ) 433 | def save( 434 | slot: int, 435 | name: bytes, 436 | ) -> int: 437 | """ 438 | save project to the file. 439 | """ 440 | 441 | 442 | @sunvox_fn( 443 | _s.sv_play, 444 | [ 445 | c_int, 446 | ], 447 | c_int, 448 | ) 449 | def play( 450 | slot: int, 451 | ) -> int: 452 | """ 453 | play from the current position 454 | """ 455 | 456 | 457 | @sunvox_fn( 458 | _s.sv_play_from_beginning, 459 | [ 460 | c_int, 461 | ], 462 | c_int, 463 | ) 464 | def play_from_beginning( 465 | slot: int, 466 | ) -> int: 467 | """ 468 | play from the beginning (line 0) 469 | """ 470 | 471 | 472 | @sunvox_fn( 473 | _s.sv_stop, 474 | [ 475 | c_int, 476 | ], 477 | c_int, 478 | ) 479 | def stop( 480 | slot: int, 481 | ) -> int: 482 | """ 483 | first call - stop playing; 484 | second call - reset all SunVox activity and switch the engine to standby mode. 485 | """ 486 | 487 | 488 | @sunvox_fn( 489 | _s.sv_pause, 490 | [ 491 | c_int, 492 | ], 493 | c_int, 494 | ) 495 | def pause( 496 | slot: int, 497 | ) -> int: 498 | """ 499 | pause the audio stream on the specified slot 500 | """ 501 | 502 | 503 | @sunvox_fn( 504 | _s.sv_resume, 505 | [ 506 | c_int, 507 | ], 508 | c_int, 509 | ) 510 | def resume( 511 | slot: int, 512 | ) -> int: 513 | """ 514 | resume the audio stream on the specified slot 515 | """ 516 | 517 | 518 | @sunvox_fn( 519 | _s.sv_sync_resume, 520 | [ 521 | c_int, 522 | ], 523 | c_int, 524 | ) 525 | def sync_resume( 526 | slot: int, 527 | ) -> int: 528 | """ 529 | wait for sync (pattern effect 0x33 on any slot) 530 | and resume the audio stream on the specified slot 531 | """ 532 | 533 | 534 | @sunvox_fn( 535 | _s.sv_set_autostop, 536 | [ 537 | c_int, 538 | c_int, 539 | ], 540 | c_int, 541 | ) 542 | def set_autostop( 543 | slot: int, 544 | autostop: int, 545 | ) -> int: 546 | """ 547 | autostop values: 548 | 0 - disable autostop; 549 | 1 - enable autostop. 550 | 551 | When disabled, song is playing infinitely in the loop. 552 | """ 553 | 554 | 555 | @sunvox_fn( 556 | _s.sv_get_autostop, 557 | [ 558 | c_int, 559 | ], 560 | c_int, 561 | ) 562 | def get_autostop( 563 | slot: int, 564 | ) -> int: 565 | """ 566 | autostop values: 567 | 0 - disable autostop; 568 | 1 - enable autostop. 569 | 570 | When disabled, song is playing infinitely in the loop. 571 | """ 572 | 573 | 574 | @sunvox_fn( 575 | _s.sv_end_of_song, 576 | [ 577 | c_int, 578 | ], 579 | c_int, 580 | ) 581 | def end_of_song( 582 | slot: int, 583 | ) -> int: 584 | """ 585 | return values: 586 | 0 - song is playing now; 587 | 1 - stopped. 588 | """ 589 | 590 | 591 | @sunvox_fn( 592 | _s.sv_rewind, 593 | [ 594 | c_int, 595 | c_int, 596 | ], 597 | c_int, 598 | ) 599 | def rewind( 600 | slot: int, 601 | line_num: int, 602 | ) -> int: 603 | pass 604 | 605 | 606 | @sunvox_fn( 607 | _s.sv_volume, 608 | [ 609 | c_int, 610 | c_int, 611 | ], 612 | c_int, 613 | ) 614 | def volume( 615 | slot: int, 616 | vol: int, 617 | ) -> int: 618 | """ 619 | set volume from 0 (min) to 256 (max 100%); 620 | 621 | negative values are ignored; 622 | 623 | return value: previous volume; 624 | """ 625 | 626 | 627 | @sunvox_fn( 628 | _s.sv_set_event_t, 629 | [ 630 | c_int, 631 | c_int, 632 | c_int, 633 | ], 634 | c_int, 635 | ) 636 | def set_event_t( 637 | slot: int, 638 | set: int, 639 | t: int, 640 | ) -> int: 641 | """ 642 | set the time of events to be sent by sv_send_event() 643 | 644 | Parameters: 645 | slot; 646 | set: 647 | 1 - set; 648 | 0 - reset (use automatic time setting - the default mode); 649 | t: the time when the events occurred (in system ticks, SunVox time space). 650 | 651 | Examples: 652 | sv_set_event_t( slot, 1, 0 ) 653 | //not specified - further events will be processed as quickly as possible 654 | sv_set_event_t( slot, 1, sv_get_ticks() ) 655 | //time when the events will be processed = NOW + sound latancy * 2 656 | """ 657 | 658 | 659 | @sunvox_fn( 660 | _s.sv_send_event, 661 | [ 662 | c_int, 663 | c_int, 664 | c_int, 665 | c_int, 666 | c_int, 667 | c_int, 668 | c_int, 669 | ], 670 | c_int, 671 | ) 672 | def send_event( 673 | slot: int, 674 | track_num: int, 675 | note: int, 676 | vel: int, 677 | module: int, 678 | ctl: int, 679 | ctl_val: int, 680 | ) -> int: 681 | """ 682 | send an event (note ON, note OFF, controller change, etc.) 683 | 684 | Parameters: 685 | slot; 686 | track_num - track number within the pattern; 687 | note: 688 | 0 - nothing; 689 | 1..127 - note num; 690 | 128 - note off; 691 | 129, 130... - see NOTECMD.xxx enums; 692 | vel: velocity 1..129; 0 - default; 693 | module: 0 (empty) or module number + 1 (1..65535); 694 | ctl: 0xCCEE. CC - number of a controller (1..255). EE - effect; 695 | ctl_val: value of controller or effect. 696 | """ 697 | 698 | 699 | @sunvox_fn( 700 | _s.sv_get_current_line, 701 | [ 702 | c_int, 703 | ], 704 | c_int, 705 | ) 706 | def get_current_line(slot: int) -> int: 707 | """ 708 | Get current line number 709 | """ 710 | 711 | 712 | @sunvox_fn( 713 | _s.sv_get_current_line2, 714 | [ 715 | c_int, 716 | ], 717 | c_int, 718 | ) 719 | def get_current_line2(slot: int) -> int: 720 | """ 721 | Get current line number in fixed point format 27.5 722 | """ 723 | 724 | 725 | @sunvox_fn(_s.sv_get_current_signal_level, [c_int, c_int], c_int) 726 | def get_current_signal_level(slot: int, channel: int) -> int: 727 | """ 728 | From 0 to 255 729 | """ 730 | 731 | 732 | @sunvox_fn( 733 | _s.sv_get_song_name, 734 | [c_int], 735 | c_char_p, 736 | ) 737 | def get_song_name(slot: int) -> bytes: 738 | pass 739 | 740 | 741 | @sunvox_fn( 742 | _s.sv_set_song_name, 743 | [ 744 | c_int, 745 | c_char_p, 746 | ], 747 | c_int, 748 | ) 749 | def set_song_name(slot: int, name: bytes) -> int: 750 | pass 751 | 752 | 753 | @sunvox_fn( 754 | _s.sv_get_song_bpm, 755 | [ 756 | c_int, 757 | ], 758 | c_int, 759 | ) 760 | def get_song_bpm(slot: int) -> int: 761 | pass 762 | 763 | 764 | @sunvox_fn( 765 | _s.sv_get_song_tpl, 766 | [ 767 | c_int, 768 | ], 769 | c_int, 770 | ) 771 | def get_song_tpl(slot: int) -> int: 772 | pass 773 | 774 | 775 | @sunvox_fn( 776 | _s.sv_get_song_length_frames, 777 | [c_int], 778 | c_uint32, 779 | ) 780 | def get_song_length_frames(slot: int) -> int: 781 | """ 782 | Get the project length in frames. 783 | 784 | Frame is one discrete of the sound. Sample rate 44100 Hz means, that you hear 44100 785 | frames per second. 786 | """ 787 | 788 | 789 | @sunvox_fn( 790 | _s.sv_get_song_length_lines, 791 | [c_int], 792 | c_uint32, 793 | ) 794 | def get_song_length_lines(slot: int) -> int: 795 | """ 796 | Get the project length in lines. 797 | """ 798 | 799 | 800 | @sunvox_fn( 801 | _s.sv_get_time_map, 802 | [ 803 | c_int, 804 | c_int, 805 | c_int, 806 | c_uint32_p, 807 | c_int, 808 | ], 809 | c_int, 810 | ) 811 | def get_time_map( 812 | slot: int, 813 | start_line: int, 814 | len: int, 815 | dest: c_uint32_p, 816 | flags: int, 817 | ) -> int: 818 | """ 819 | Parameters: 820 | slot; 821 | start_line - first line to read (usually 0); 822 | len - number of lines to read; 823 | dest - 824 | pointer to the buffer 825 | (size = len*sizeof(uint32_t)) for storing the map values; 826 | flags: 827 | TIME_MAP.SPEED: dest[X] = BPM | ( TPL << 16 ) 828 | (speed at the beginning of line X); 829 | TIME_MAP.FRAMECNT: dest[X] = frame counter at the beginning of line X; 830 | 831 | Return value: 0 if successful, or negative value in case of some error. 832 | """ 833 | 834 | 835 | @sunvox_fn( 836 | _s.sv_new_module, 837 | [ 838 | c_int, 839 | c_char_p, 840 | c_char_p, 841 | c_int, 842 | c_int, 843 | c_int, 844 | ], 845 | c_int, 846 | needs_lock=True, 847 | ) 848 | def new_module( 849 | slot: int, 850 | type: bytes, 851 | name: bytes, 852 | x: int, 853 | y: int, 854 | z: int, 855 | ) -> int: 856 | """ 857 | Create a new module. 858 | """ 859 | 860 | 861 | @sunvox_fn( 862 | _s.sv_remove_module, 863 | [ 864 | c_int, 865 | c_int, 866 | ], 867 | c_int, 868 | needs_lock=True, 869 | ) 870 | def remove_module( 871 | slot: int, 872 | mod_num: int, 873 | ) -> int: 874 | """ 875 | Remove selected module. 876 | """ 877 | 878 | 879 | @sunvox_fn( 880 | _s.sv_connect_module, 881 | [ 882 | c_int, 883 | c_int, 884 | c_int, 885 | ], 886 | c_int, 887 | needs_lock=True, 888 | ) 889 | def connect_module( 890 | slot: int, 891 | source: int, 892 | destination: int, 893 | ) -> int: 894 | """ 895 | Connect the source to the destination. 896 | """ 897 | 898 | 899 | @sunvox_fn( 900 | _s.sv_disconnect_module, 901 | [ 902 | c_int, 903 | c_int, 904 | c_int, 905 | ], 906 | c_int, 907 | needs_lock=True, 908 | ) 909 | def disconnect_module( 910 | slot: int, 911 | source: int, 912 | destination: int, 913 | ) -> int: 914 | """ 915 | Disconnect the source from the destination. 916 | """ 917 | 918 | 919 | @sunvox_fn( 920 | _s.sv_load_module, 921 | [ 922 | c_int, 923 | c_char_p, 924 | c_int, 925 | c_int, 926 | c_int, 927 | ], 928 | c_int, 929 | ) 930 | def load_module( 931 | slot: int, 932 | file_name: bytes, 933 | x: int, 934 | y: int, 935 | z: int, 936 | ) -> int: 937 | """ 938 | load a module or sample; 939 | 940 | supported file formats: sunsynth, xi, wav, aiff; 941 | 942 | return value: new module number or negative value in case of some error; 943 | """ 944 | 945 | 946 | @sunvox_fn( 947 | _s.sv_load_module_from_memory, 948 | [ 949 | c_int, 950 | c_void_p, 951 | c_uint32_p, 952 | c_int, 953 | c_int, 954 | c_int, 955 | ], 956 | c_int, 957 | ) 958 | def load_module_from_memory( 959 | slot: int, 960 | data: bytes, 961 | data_size: int, 962 | x: int, 963 | y: int, 964 | z: int, 965 | ) -> int: 966 | """ 967 | load a module or sample from the memory block 968 | """ 969 | 970 | 971 | @sunvox_fn( 972 | _s.sv_sampler_load, 973 | [ 974 | c_int, 975 | c_int, 976 | c_char_p, 977 | c_int, 978 | ], 979 | c_int, 980 | ) 981 | def sampler_load( 982 | slot: int, 983 | mod_num: int, 984 | file_name: bytes, 985 | sample_slot: int, 986 | ) -> int: 987 | """ 988 | load a sample to already created Sampler; 989 | to replace the whole sampler - set sample_slot to -1; 990 | """ 991 | 992 | 993 | @sunvox_fn( 994 | _s.sv_sampler_load_from_memory, 995 | [ 996 | c_int, 997 | c_int, 998 | c_void_p, 999 | c_uint32, 1000 | c_int, 1001 | ], 1002 | c_int, 1003 | ) 1004 | def sampler_load_from_memory( 1005 | slot: int, 1006 | mod_num: int, 1007 | data: bytes, 1008 | data_size: int, 1009 | sample_slot: int, 1010 | ) -> int: 1011 | """ 1012 | load a sample to already created Sampler; 1013 | to replace the whole sampler - set sample_slot to -1; 1014 | """ 1015 | 1016 | 1017 | @sunvox_fn( 1018 | _s.sv_sampler_par, 1019 | [ 1020 | c_int, 1021 | c_int, 1022 | c_int, 1023 | c_int, 1024 | c_int, 1025 | c_int, 1026 | ], 1027 | c_int, 1028 | ) 1029 | def sampler_par( 1030 | slot: int, 1031 | mod_num: int, 1032 | sample_slot: int, 1033 | par: int, 1034 | par_val: int, 1035 | set: int, 1036 | ) -> int: 1037 | """ 1038 | set/get sample parameter: 1039 | 0 - Loop begin: 0 ... (sample_length - 1); 1040 | 1 - Loop length: 0 ... (sample_length - loop_begin); 1041 | 2 - Loop type: 0 - none; 1 - fwd; 2 - bidirectional; 1042 | 3 - Loop release flag: 0 - none; 1 - loop will be finished after the note release; 1043 | 4 - Volume: 0 ... 64; 1044 | 5 - Panning: 0 (left) ... 128 (center) ... 255 (right); 1045 | 6 - Finetune: -128 ... 0 ... +127 (higher value = higher pitch); 1046 | 7 - Relative note: -128 ... 0 ... +127 (higher value = higher pitch); 1047 | 8 - Start position: 0 ... (sample_length - 1); 1048 | """ 1049 | 1050 | 1051 | @sunvox_fn( 1052 | _s.sv_metamodule_load, 1053 | [ 1054 | c_int, 1055 | c_int, 1056 | c_char_p, 1057 | ], 1058 | c_int, 1059 | ) 1060 | def metamodule_load( 1061 | slot: int, 1062 | mod_num: int, 1063 | file_name: bytes, 1064 | ) -> int: 1065 | """ 1066 | load a file into the MetaModule; 1067 | supported file formats: sunvox, mod, xm, midi; 1068 | """ 1069 | 1070 | 1071 | @sunvox_fn( 1072 | _s.sv_metamodule_load_from_memory, 1073 | [ 1074 | c_int, 1075 | c_int, 1076 | c_void_p, 1077 | c_int, 1078 | ], 1079 | c_int, 1080 | ) 1081 | def metamodule_load_from_memory( 1082 | slot: int, 1083 | mod_num: int, 1084 | data: bytes, 1085 | data_size: int, 1086 | ) -> int: 1087 | """ 1088 | load a file into the MetaModule; 1089 | supported file formats: sunvox, mod, xm, midi; 1090 | """ 1091 | 1092 | 1093 | @sunvox_fn( 1094 | _s.sv_vplayer_load, 1095 | [ 1096 | c_int, 1097 | c_int, 1098 | c_char_p, 1099 | ], 1100 | c_int, 1101 | ) 1102 | def vplayer_load( 1103 | slot: int, 1104 | mod_num: int, 1105 | file_name: bytes, 1106 | ) -> int: 1107 | """ 1108 | load a file into the Vorbis Player; 1109 | supported file formats: ogg; 1110 | """ 1111 | 1112 | 1113 | @sunvox_fn( 1114 | _s.sv_vplayer_load_from_memory, 1115 | [ 1116 | c_int, 1117 | c_int, 1118 | c_void_p, 1119 | c_int, 1120 | ], 1121 | c_int, 1122 | ) 1123 | def vplayer_load_from_memory( 1124 | slot: int, 1125 | mod_num: int, 1126 | data: bytes, 1127 | data_size: int, 1128 | ) -> int: 1129 | """ 1130 | load a file into the Vorbis Player; 1131 | supported file formats: ogg; 1132 | """ 1133 | 1134 | 1135 | @sunvox_fn( 1136 | _s.sv_get_number_of_modules, 1137 | [ 1138 | c_int, 1139 | ], 1140 | c_int, 1141 | ) 1142 | def get_number_of_modules(slot: int) -> int: 1143 | """ 1144 | get the number of module slots (not the actual number of modules). 1145 | The slot can be empty or it can contain a module. 1146 | Here is the code to determine that the module slot X is not empty: 1147 | ( sv_get_module_flags( slot, X ) & SV_MODULE_FLAG_EXISTS ) != 0; 1148 | """ 1149 | 1150 | 1151 | @sunvox_fn( 1152 | _s.sv_find_module, 1153 | [ 1154 | c_int, 1155 | c_char_p, 1156 | ], 1157 | c_int, 1158 | ) 1159 | def find_module( 1160 | slot: int, 1161 | name: bytes, 1162 | ) -> int: 1163 | """ 1164 | find a module by name; 1165 | 1166 | return value: module number or -1 (if not found); 1167 | """ 1168 | 1169 | 1170 | @sunvox_fn( 1171 | _s.sv_get_module_flags, 1172 | [ 1173 | c_int, 1174 | c_int, 1175 | ], 1176 | c_uint32, 1177 | ) 1178 | def get_module_flags( 1179 | slot: int, 1180 | mod_num: int, 1181 | ) -> int: 1182 | """ 1183 | sunvox.types.MODULE.FLAG_xxx 1184 | """ 1185 | 1186 | 1187 | @sunvox_fn( 1188 | _s.sv_get_module_inputs, 1189 | [ 1190 | c_int, 1191 | c_int, 1192 | ], 1193 | c_int, 1194 | ) 1195 | def get_module_inputs( 1196 | slot: int, 1197 | mod_num: int, 1198 | ) -> int: 1199 | """ 1200 | get pointers to the int[] arrays with the input links. 1201 | Number of input links = ( module_flags & MODULE.INPUTS_MASK ) >> MODULE.INPUTS_OFF 1202 | (this is not the actual number of connections: some links may be empty (value = -1)) 1203 | """ 1204 | 1205 | 1206 | @sunvox_fn( 1207 | _s.sv_get_module_outputs, 1208 | [ 1209 | c_int, 1210 | c_int, 1211 | ], 1212 | c_int, 1213 | ) 1214 | def get_module_outputs( 1215 | slot: int, 1216 | mod_num: int, 1217 | ) -> int: 1218 | """ 1219 | get pointers to the int[] arrays with the output links. 1220 | Number of output links = 1221 | ( module_flags & MODULE.OUTPUTS_MASK ) >> MODULE.OUTPUTS_OFF 1222 | (this is not the actual number of connections: some links may be empty (value = -1)) 1223 | """ 1224 | 1225 | 1226 | @sunvox_fn( 1227 | _s.sv_get_module_type, 1228 | [ 1229 | c_int, 1230 | c_int, 1231 | ], 1232 | c_char_p, 1233 | ) 1234 | def get_module_type( 1235 | slot: int, 1236 | mod_num: int, 1237 | ) -> bytes: 1238 | pass 1239 | 1240 | 1241 | @sunvox_fn( 1242 | _s.sv_get_module_name, 1243 | [ 1244 | c_int, 1245 | c_int, 1246 | ], 1247 | c_char_p, 1248 | ) 1249 | def get_module_name( 1250 | slot: int, 1251 | mod_num: int, 1252 | ) -> bytes: 1253 | pass 1254 | 1255 | 1256 | @sunvox_fn( 1257 | _s.sv_set_module_name, 1258 | [ 1259 | c_int, 1260 | c_int, 1261 | c_char_p, 1262 | ], 1263 | c_int, 1264 | ) 1265 | def set_module_name(slot: int, mod_num: int, name: bytes) -> int: 1266 | pass 1267 | 1268 | 1269 | @sunvox_fn( 1270 | _s.sv_get_module_xy, 1271 | [ 1272 | c_int, 1273 | c_int, 1274 | ], 1275 | c_uint32, 1276 | ) 1277 | def get_module_xy( 1278 | slot: int, 1279 | mod_num: int, 1280 | ) -> int: 1281 | """ 1282 | get module XY coordinates packed in a single uint32 value: 1283 | 1284 | ( x & 0xFFFF ) | ( ( y & 0xFFFF ) << 16 ) 1285 | 1286 | Normal working area: 0x0 ... 1024x1024 1287 | Center: 512x512 1288 | 1289 | Use GET_MODULE_XY() macro to unpack X and Y. 1290 | """ 1291 | 1292 | 1293 | @sunvox_fn( 1294 | _s.sv_set_module_xy, 1295 | [ 1296 | c_int, 1297 | c_int, 1298 | c_int, 1299 | c_int, 1300 | ], 1301 | c_int, 1302 | ) 1303 | def set_module_xy( 1304 | slot: int, 1305 | mod_num: int, 1306 | x: int, 1307 | y: int, 1308 | ) -> int: 1309 | pass 1310 | 1311 | 1312 | @sunvox_fn( 1313 | _s.sv_get_module_color, 1314 | [ 1315 | c_int, 1316 | c_int, 1317 | ], 1318 | c_int, 1319 | ) 1320 | def get_module_color( 1321 | slot: int, 1322 | mod_num: int, 1323 | ) -> int: 1324 | """ 1325 | get module color in the following format: 0xBBGGRR 1326 | """ 1327 | 1328 | 1329 | @sunvox_fn( 1330 | _s.sv_set_module_color, 1331 | [ 1332 | c_int, 1333 | c_int, 1334 | c_int, 1335 | ], 1336 | c_int, 1337 | ) 1338 | def set_module_color( 1339 | slot: int, 1340 | mod_num: int, 1341 | color: int, 1342 | ) -> int: 1343 | """ 1344 | set module color in the following format: 0xBBGGRR 1345 | """ 1346 | 1347 | 1348 | @sunvox_fn( 1349 | _s.sv_get_module_finetune, 1350 | [ 1351 | c_int, 1352 | c_int, 1353 | ], 1354 | c_uint32, 1355 | ) 1356 | def get_module_finetune( 1357 | slot: int, 1358 | mod_num: int, 1359 | ) -> int: 1360 | """ 1361 | get the relative note and finetune of the module; 1362 | 1363 | return value: ( finetune & 0xFFFF ) | ( ( relative_note & 0xFFFF ) << 16 ). 1364 | 1365 | Use GET_MODULE_FINETUNE() macro to unpack finetune and relative_note. 1366 | """ 1367 | 1368 | 1369 | @sunvox_fn( 1370 | _s.sv_set_module_finetune, 1371 | [ 1372 | c_int, 1373 | c_int, 1374 | c_int, 1375 | ], 1376 | c_int, 1377 | ) 1378 | def set_module_finetune( 1379 | slot: int, 1380 | mod_num: int, 1381 | finetune: int, 1382 | ) -> int: 1383 | """ 1384 | change the finetune immediately 1385 | """ 1386 | 1387 | 1388 | @sunvox_fn( 1389 | _s.sv_set_module_relnote, 1390 | [ 1391 | c_int, 1392 | c_int, 1393 | c_int, 1394 | ], 1395 | c_int, 1396 | ) 1397 | def set_module_relnote( 1398 | slot: int, 1399 | mod_num: int, 1400 | relative_note: int, 1401 | ) -> int: 1402 | """ 1403 | change the relative note immediately 1404 | """ 1405 | 1406 | 1407 | @sunvox_fn( 1408 | _s.sv_get_module_scope2, 1409 | [ 1410 | c_int, 1411 | c_int, 1412 | c_int, 1413 | c_int16_p, 1414 | c_uint32, 1415 | ], 1416 | c_uint32, 1417 | ) 1418 | def get_module_scope2( 1419 | slot: int, 1420 | mod_num: int, 1421 | channel: int, 1422 | dest_buf: c_int16_p, 1423 | samples_to_read: int, 1424 | ) -> int: 1425 | """ 1426 | return value = received number of samples (may be less or equal to samples_to_read). 1427 | 1428 | Example: 1429 | int16_t buf[ 1024 ]; 1430 | int received = sv_get_module_scope2( slot, mod_num, 0, buf, 1024 ); 1431 | //buf[ 0 ] = value of the first sample (-32768...32767); 1432 | //buf[ 1 ] = value of the second sample; 1433 | //... 1434 | //buf[ received - 1 ] = value of the last received sample; 1435 | """ 1436 | 1437 | 1438 | @sunvox_fn( 1439 | _s.sv_module_curve, 1440 | [ 1441 | c_int, 1442 | c_int, 1443 | c_int, 1444 | c_float_p, 1445 | c_int, 1446 | c_int, 1447 | ], 1448 | c_int, 1449 | ) 1450 | def module_curve( 1451 | slot: int, 1452 | mod_num: int, 1453 | curve_num: int, 1454 | data: c_float_p, 1455 | len: int, 1456 | w: int, 1457 | ) -> int: 1458 | """ 1459 | access to the curve values of the specified module 1460 | 1461 | Parameters: 1462 | slot; 1463 | mod_num - module number; 1464 | curve_num - curve number; 1465 | data - destination or source buffer; 1466 | len - number of items to read/write; 1467 | w - read (0) or write (1). 1468 | 1469 | return value: number of items processed successfully. 1470 | 1471 | Available curves (Y=CURVE[X]): 1472 | MultiSynth: 1473 | 0 - X = note (0..127); Y = velocity (0..1); 128 items; 1474 | 1 - X = velocity (0..256); Y = velocity (0..1); 257 items; 1475 | 2 - X = note (0..127); Y = pitch (0..1); 128 items; 1476 | pitch range: 0 ... 16384/65535 (note0) ... 49152/65535 (note128) ... 1; semitone = 256/65535; 1477 | WaveShaper: 1478 | 0 - X = input (0..255); Y = output (0..1); 256 items; 1479 | MultiCtl: 1480 | 0 - X = input (0..256); Y = output (0..1); 257 items; 1481 | Analog Generator, Generator: 1482 | 0 - X = drawn waveform sample number (0..31); Y = volume (-1..1); 32 items; 1483 | FMX: 1484 | 0 - X = custom waveform sample number (0..255); Y = volume (-1..1); 256 items; 1485 | """ 1486 | 1487 | 1488 | @sunvox_fn( 1489 | _s.sv_get_number_of_module_ctls, 1490 | [ 1491 | c_int, 1492 | c_int, 1493 | ], 1494 | c_int, 1495 | ) 1496 | def get_number_of_module_ctls( 1497 | slot: int, 1498 | mod_num: int, 1499 | ) -> int: 1500 | pass 1501 | 1502 | 1503 | @sunvox_fn( 1504 | _s.sv_get_module_ctl_name, 1505 | [ 1506 | c_int, 1507 | c_int, 1508 | c_int, 1509 | ], 1510 | c_char_p, 1511 | ) 1512 | def get_module_ctl_name( 1513 | slot: int, 1514 | mod_num: int, 1515 | ctl_num: int, 1516 | ) -> bytes: 1517 | pass 1518 | 1519 | 1520 | @sunvox_fn( 1521 | _s.sv_get_module_ctl_value, 1522 | [ 1523 | c_int, 1524 | c_int, 1525 | c_int, 1526 | c_int, 1527 | ], 1528 | c_int, 1529 | ) 1530 | def get_module_ctl_value( 1531 | slot: int, 1532 | mod_num: int, 1533 | ctl_num: int, 1534 | scaled: int, 1535 | ) -> int: 1536 | """ 1537 | get the value of the specified module controller 1538 | 1539 | Parameters: 1540 | slot; 1541 | mod_num - module number; 1542 | ctl_num - controller number (from 0); 1543 | scaled - describes the type of the returned value: 1544 | 0 - real value (0,1,2...) as it is stored inside the controller; 1545 | but the value displayed in the program interface may be different - you can use scaled=2 to get the displayed value; 1546 | 1 - scaled (0x0000...0x8000) if the controller type = 0, or the real value if the controller type = 1 (enum); 1547 | this value can be used in the pattern column XXYY; 1548 | 2 - final value displayed in the program interface - 1549 | in most cases it is identical to the real value (scaled=0), and sometimes it has an additional offset; 1550 | 1551 | return value: value of the specified module controller. 1552 | """ 1553 | 1554 | 1555 | @sunvox_fn( 1556 | _s.sv_set_module_ctl_value, 1557 | [ 1558 | c_int, 1559 | c_int, 1560 | c_int, 1561 | c_int, 1562 | c_int, 1563 | ], 1564 | c_int, 1565 | ) 1566 | def set_module_ctl_value( 1567 | slot: int, 1568 | mod_num: int, 1569 | ctl_num: int, 1570 | val: int, 1571 | scaled: int, 1572 | ) -> int: 1573 | """ 1574 | send the value to the specified module controller; 1575 | (sv_send_event() will be used internally) 1576 | """ 1577 | 1578 | 1579 | @sunvox_fn( 1580 | _s.sv_get_module_ctl_min, 1581 | [ 1582 | c_int, 1583 | c_int, 1584 | c_int, 1585 | c_int, 1586 | ], 1587 | c_int, 1588 | ) 1589 | def get_module_ctl_min( 1590 | slot: int, 1591 | mod_num: int, 1592 | ctl_num: int, 1593 | scaled: int, 1594 | ) -> int: 1595 | pass 1596 | 1597 | 1598 | @sunvox_fn( 1599 | _s.sv_get_module_ctl_max, 1600 | [ 1601 | c_int, 1602 | c_int, 1603 | c_int, 1604 | c_int, 1605 | ], 1606 | c_int, 1607 | ) 1608 | def get_module_ctl_max( 1609 | slot: int, 1610 | mod_num: int, 1611 | ctl_num: int, 1612 | scaled: int, 1613 | ) -> int: 1614 | pass 1615 | 1616 | 1617 | @sunvox_fn( 1618 | _s.sv_get_module_ctl_offset, 1619 | [ 1620 | c_int, 1621 | c_int, 1622 | c_int, 1623 | ], 1624 | c_int, 1625 | ) 1626 | def get_module_ctl_offset( 1627 | slot: int, 1628 | mod_num: int, 1629 | ctl_num: int, 1630 | ) -> int: 1631 | """Get display value offset""" 1632 | 1633 | 1634 | @sunvox_fn( 1635 | _s.sv_get_module_ctl_type, 1636 | [ 1637 | c_int, 1638 | c_int, 1639 | c_int, 1640 | ], 1641 | c_int, 1642 | ) 1643 | def get_module_ctl_type( 1644 | slot: int, 1645 | mod_num: int, 1646 | ctl_num: int, 1647 | ) -> int: 1648 | """ 1649 | 0 - normal (scaled); 1650 | 1 - selector (enum); 1651 | """ 1652 | 1653 | 1654 | @sunvox_fn( 1655 | _s.sv_get_module_ctl_group, 1656 | [ 1657 | c_int, 1658 | c_int, 1659 | c_int, 1660 | ], 1661 | c_int, 1662 | ) 1663 | def get_module_ctl_group( 1664 | slot: int, 1665 | mod_num: int, 1666 | ctl_num: int, 1667 | ) -> int: 1668 | pass 1669 | 1670 | 1671 | @sunvox_fn( 1672 | _s.sv_new_pattern, 1673 | [ 1674 | c_int, 1675 | c_int, 1676 | c_int, 1677 | c_int, 1678 | c_int, 1679 | c_int, 1680 | c_int, 1681 | c_char_p, 1682 | ], 1683 | c_int, 1684 | needs_lock=True, 1685 | ) 1686 | def new_pattern( 1687 | slot: int, 1688 | clone: int, 1689 | x: int, 1690 | y: int, 1691 | tracks: int, 1692 | lines: int, 1693 | icon_seed: int, 1694 | name: bytes, 1695 | ) -> int: 1696 | """create a new pattern""" 1697 | 1698 | 1699 | @sunvox_fn( 1700 | _s.sv_remove_pattern, 1701 | [ 1702 | c_int, 1703 | c_int, 1704 | ], 1705 | c_int, 1706 | ) 1707 | def remove_pattern(slot: int, pat_num: int) -> int: 1708 | """remove selected pattern""" 1709 | 1710 | 1711 | @sunvox_fn( 1712 | _s.sv_get_number_of_patterns, 1713 | [ 1714 | c_int, 1715 | ], 1716 | c_int, 1717 | ) 1718 | def get_number_of_patterns( 1719 | slot: int, 1720 | ) -> int: 1721 | """ 1722 | get the number of pattern slots (not the actual number of patterns). 1723 | The slot can be empty or it can contain a pattern. 1724 | Here is the code to determine that the pattern slot X is not empty: 1725 | sv_get_pattern_lines( slot, X ) > 0; 1726 | """ 1727 | 1728 | 1729 | @sunvox_fn( 1730 | _s.sv_find_pattern, 1731 | [ 1732 | c_int, 1733 | c_char_p, 1734 | ], 1735 | c_int, 1736 | ) 1737 | def find_pattern( 1738 | slot: int, 1739 | name: bytes, 1740 | ) -> int: 1741 | """ 1742 | find a pattern by name; 1743 | 1744 | return value: pattern number or -1 (if not found); 1745 | """ 1746 | 1747 | 1748 | @sunvox_fn( 1749 | _s.sv_get_pattern_x, 1750 | [ 1751 | c_int, 1752 | c_int, 1753 | ], 1754 | c_int, 1755 | ) 1756 | def get_pattern_x( 1757 | slot: int, 1758 | pat_num: int, 1759 | ) -> int: 1760 | """ 1761 | get pattern position; 1762 | 1763 | x - line number (horizontal position on the timeline) 1764 | """ 1765 | 1766 | 1767 | @sunvox_fn( 1768 | _s.sv_get_pattern_y, 1769 | [ 1770 | c_int, 1771 | c_int, 1772 | ], 1773 | c_int, 1774 | ) 1775 | def get_pattern_y( 1776 | slot: int, 1777 | pat_num: int, 1778 | ) -> int: 1779 | """ 1780 | get pattern position; 1781 | 1782 | y - vertical position on the timeline; 1783 | """ 1784 | 1785 | 1786 | @sunvox_fn( 1787 | _s.sv_set_pattern_xy, 1788 | [ 1789 | c_int, 1790 | c_int, 1791 | c_int, 1792 | c_int, 1793 | ], 1794 | c_int, 1795 | needs_lock=True, 1796 | ) 1797 | def set_pattern_xy( 1798 | slot: int, 1799 | pat_num: int, 1800 | x: int, 1801 | y: int, 1802 | ) -> int: 1803 | """ 1804 | set pattern position; 1805 | 1806 | Parameters: 1807 | x - line number (horizontal position on the timeline); 1808 | y - vertical position on the timeline; 1809 | """ 1810 | 1811 | 1812 | @sunvox_fn( 1813 | _s.sv_get_pattern_tracks, 1814 | [ 1815 | c_int, 1816 | c_int, 1817 | ], 1818 | c_int, 1819 | ) 1820 | def get_pattern_tracks( 1821 | slot: int, 1822 | pat_num: int, 1823 | ) -> int: 1824 | """ 1825 | get pattern size; 1826 | 1827 | return value: number of pattern tracks; 1828 | """ 1829 | 1830 | 1831 | @sunvox_fn( 1832 | _s.sv_get_pattern_lines, 1833 | [ 1834 | c_int, 1835 | c_int, 1836 | ], 1837 | c_int, 1838 | ) 1839 | def get_pattern_lines( 1840 | slot: int, 1841 | pat_num: int, 1842 | ) -> int: 1843 | """ 1844 | get pattern size; 1845 | 1846 | return value: number of pattern lines; 1847 | """ 1848 | 1849 | 1850 | @sunvox_fn( 1851 | _s.sv_set_pattern_size, 1852 | [ 1853 | c_int, 1854 | c_int, 1855 | c_int, 1856 | c_int, 1857 | ], 1858 | c_int, 1859 | needs_lock=True, 1860 | ) 1861 | def set_pattern_size(slot: int, pat_num: int, tracks: int, lines: int) -> int: 1862 | pass 1863 | 1864 | 1865 | @sunvox_fn( 1866 | _s.sv_get_pattern_name, 1867 | [ 1868 | c_int, 1869 | c_int, 1870 | ], 1871 | c_char_p, 1872 | ) 1873 | def get_pattern_name( 1874 | slot: int, 1875 | pat_num: int, 1876 | ) -> bytes: 1877 | pass 1878 | 1879 | 1880 | @sunvox_fn( 1881 | _s.sv_set_pattern_name, 1882 | [ 1883 | c_int, 1884 | c_int, 1885 | c_char_p, 1886 | ], 1887 | c_int, 1888 | needs_lock=True, 1889 | ) 1890 | def set_pattern_name( 1891 | slot: int, 1892 | pat_num: int, 1893 | name: bytes, 1894 | ) -> int: 1895 | pass 1896 | 1897 | 1898 | @sunvox_fn( 1899 | _s.sv_get_pattern_data, 1900 | [ 1901 | c_int, 1902 | c_int, 1903 | ], 1904 | sunvox_note_p, 1905 | ) 1906 | def get_pattern_data( 1907 | slot: int, 1908 | pat_num: int, 1909 | ) -> sunvox_note_p: 1910 | """ 1911 | get the pattern buffer (for reading and writing) 1912 | 1913 | containing notes (events) in the following order: 1914 | line 0: note for track 0, note for track 1, ... note for track X; 1915 | line 1: note for track 0, note for track 1, ... note for track X; 1916 | ... 1917 | line X: ... 1918 | 1919 | Example: 1920 | int pat_tracks = sv_get_pattern_tracks( slot, pat_num ); //number of tracks 1921 | sunvox_note* data = sv_get_pattern_data( slot, pat_num ); 1922 | //get the buffer with all the pattern events (notes) 1923 | sunvox_note* n = &data[ line_number * pat_tracks + track_number ]; 1924 | ... and then do someting with note n ... 1925 | """ 1926 | 1927 | 1928 | @sunvox_fn( 1929 | _s.sv_set_pattern_event, 1930 | [ 1931 | c_int, 1932 | c_int, 1933 | c_int, 1934 | c_int, 1935 | c_int, 1936 | c_int, 1937 | c_int, 1938 | c_int, 1939 | c_int, 1940 | ], 1941 | c_int, 1942 | ) 1943 | def set_pattern_event( 1944 | slot: int, 1945 | pat_num: int, 1946 | track: int, 1947 | line: int, 1948 | nn: int, 1949 | vv: int, 1950 | mm: int, 1951 | ccee: int, 1952 | xxyy: int, 1953 | ) -> int: 1954 | """ 1955 | write the pattern event to the cell at the specified line and track 1956 | nn,vv,mm,ccee,xxyy are the same as the fields of sunvox_note structure. 1957 | Only non-negative values will be written to the pattern. 1958 | Return value: 0 (sucess) or negative error code. 1959 | """ 1960 | 1961 | 1962 | @sunvox_fn( 1963 | _s.sv_get_pattern_event, 1964 | [ 1965 | c_int, 1966 | c_int, 1967 | c_int, 1968 | c_int, 1969 | c_int, 1970 | ], 1971 | c_int, 1972 | ) 1973 | def get_pattern_event( 1974 | slot: int, 1975 | pat_num: int, 1976 | track: int, 1977 | line: int, 1978 | column: int, 1979 | ) -> int: 1980 | """ 1981 | read a pattern event at the specified line and track 1982 | column (field number): 1983 | 0 - note (NN); 1984 | 1 - velocity (VV); 1985 | 2 - module (MM); 1986 | 3 - controller number or effect (CCEE); 1987 | 4 - controller value or effect parameter (XXYY); 1988 | Return value: value of the specified field or negative error code. 1989 | """ 1990 | 1991 | 1992 | @sunvox_fn( 1993 | _s.sv_pattern_mute, 1994 | [ 1995 | c_int, 1996 | c_int, 1997 | c_int, 1998 | ], 1999 | c_int, 2000 | ) 2001 | def pattern_mute( 2002 | slot: int, 2003 | pat_num: int, 2004 | mute: int, 2005 | ) -> int: 2006 | """ 2007 | mute (1) / unmute (0) specified pattern; 2008 | 2009 | negative values are ignored; 2010 | 2011 | return value: previous state (1 - muted; 0 - unmuted) or -1 (error); 2012 | """ 2013 | 2014 | 2015 | @sunvox_fn( 2016 | _s.sv_get_ticks, 2017 | [], 2018 | c_uint32, 2019 | ) 2020 | def get_ticks() -> int: 2021 | """ 2022 | SunVox engine uses its own time space, measured in system ticks (don't confuse it 2023 | with the project ticks); 2024 | 2025 | required when calculating the out_time parameter in the sv_audio_callback(). 2026 | 2027 | Use sv_get_ticks() to get current tick counter (from 0 to 0xFFFFFFFF). 2028 | """ 2029 | 2030 | 2031 | @sunvox_fn( 2032 | _s.sv_get_ticks_per_second, 2033 | [], 2034 | c_uint32, 2035 | ) 2036 | def get_ticks_per_second() -> int: 2037 | """ 2038 | SunVox engine uses its own time space, measured in system ticks (don't confuse it 2039 | with the project ticks); 2040 | 2041 | required when calculating the out_time parameter in the sv_audio_callback(). 2042 | 2043 | Use sv_get_ticks_per_second() to get the number of SunVox ticks per second. 2044 | """ 2045 | 2046 | 2047 | @sunvox_fn( 2048 | _s.sv_get_log, 2049 | [ 2050 | c_int, 2051 | ], 2052 | c_char_p, 2053 | ) 2054 | def get_log( 2055 | size: int, 2056 | ) -> bytes: 2057 | """ 2058 | get the latest messages from the log 2059 | 2060 | Parameters: 2061 | size - max number of bytes to read. 2062 | 2063 | Return value: pointer to the null-terminated string with the latest log messages. 2064 | """ 2065 | 2066 | 2067 | __all__ = [ 2068 | "DEFAULT_DLL_BASE", 2069 | "DLL_BASE", 2070 | "DLL_PATH", 2071 | "audio_callback", 2072 | "audio_callback2", 2073 | "open_slot", 2074 | "close_slot", 2075 | "lock_slot", 2076 | "unlock_slot", 2077 | "init", 2078 | "deinit", 2079 | "get_sample_rate", 2080 | "update_input", 2081 | "load", 2082 | "load_from_memory", 2083 | "save", 2084 | "play", 2085 | "play_from_beginning", 2086 | "stop", 2087 | "pause", 2088 | "resume", 2089 | "set_autostop", 2090 | "get_autostop", 2091 | "end_of_song", 2092 | "rewind", 2093 | "volume", 2094 | "set_event_t", 2095 | "send_event", 2096 | "get_current_line", 2097 | "get_current_line2", 2098 | "get_current_signal_level", 2099 | "get_song_name", 2100 | "get_song_bpm", 2101 | "get_song_tpl", 2102 | "get_song_length_frames", 2103 | "get_song_length_lines", 2104 | "get_time_map", 2105 | "new_module", 2106 | "remove_module", 2107 | "connect_module", 2108 | "disconnect_module", 2109 | "load_module", 2110 | "load_module_from_memory", 2111 | "sampler_load", 2112 | "sampler_load_from_memory", 2113 | "sampler_par", 2114 | "get_number_of_modules", 2115 | "get_module_flags", 2116 | "get_module_inputs", 2117 | "get_module_outputs", 2118 | "get_module_name", 2119 | "get_module_xy", 2120 | "get_module_color", 2121 | "get_module_finetune", 2122 | "get_module_scope2", 2123 | "module_curve", 2124 | "get_number_of_module_ctls", 2125 | "get_module_ctl_name", 2126 | "get_module_ctl_value", 2127 | "get_number_of_patterns", 2128 | "find_pattern", 2129 | "get_pattern_x", 2130 | "get_pattern_y", 2131 | "get_pattern_tracks", 2132 | "get_pattern_lines", 2133 | "get_pattern_name", 2134 | "get_pattern_data", 2135 | "pattern_mute", 2136 | "get_ticks", 2137 | "get_ticks_per_second", 2138 | "get_log", 2139 | ] 2140 | -------------------------------------------------------------------------------- /sunvox/lib/copy-libs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Copies libs from the SunVox library distribution. 4 | 5 | set -euo pipefail 6 | 7 | usage() { 8 | echo "Usage: $0 " 9 | exit 1 10 | } 11 | 12 | [[ "$*" != "" ]] || usage 13 | 14 | SUNVOX_LIB_DIR="$1" 15 | 16 | [[ -d "${SUNVOX_LIB_DIR}" ]] || usage 17 | 18 | SUNVOX_LIB_SUBPATHS=" 19 | linux/lib_arm/ 20 | linux/lib_arm64/ 21 | linux/lib_x86/ 22 | linux/lib_x86_64/ 23 | macos/lib_arm64/ 24 | macos/lib_x86_64/ 25 | windows/lib_x86/ 26 | windows/lib_x86_64/ 27 | " 28 | 29 | for REL_SUBPATH in ${SUNVOX_LIB_SUBPATHS} 30 | do 31 | SRC_SUBPATH="${SUNVOX_LIB_DIR}/${REL_SUBPATH}" 32 | mkdir -p "${REL_SUBPATH}" 33 | cp -v "${SRC_SUBPATH}"sunvox.* "${REL_SUBPATH}" 34 | done 35 | -------------------------------------------------------------------------------- /sunvox/lib/linux/lib_arm/sunvox.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metrasynth/sunvox-dll-python/cce86a7fc7dd7a81c60760d909e4eabbe7503c80/sunvox/lib/linux/lib_arm/sunvox.so -------------------------------------------------------------------------------- /sunvox/lib/linux/lib_arm64/sunvox.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metrasynth/sunvox-dll-python/cce86a7fc7dd7a81c60760d909e4eabbe7503c80/sunvox/lib/linux/lib_arm64/sunvox.so -------------------------------------------------------------------------------- /sunvox/lib/linux/lib_x86/sunvox.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metrasynth/sunvox-dll-python/cce86a7fc7dd7a81c60760d909e4eabbe7503c80/sunvox/lib/linux/lib_x86/sunvox.so -------------------------------------------------------------------------------- /sunvox/lib/linux/lib_x86_64/sunvox.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metrasynth/sunvox-dll-python/cce86a7fc7dd7a81c60760d909e4eabbe7503c80/sunvox/lib/linux/lib_x86_64/sunvox.so -------------------------------------------------------------------------------- /sunvox/lib/macos/lib_arm64/sunvox.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metrasynth/sunvox-dll-python/cce86a7fc7dd7a81c60760d909e4eabbe7503c80/sunvox/lib/macos/lib_arm64/sunvox.dylib -------------------------------------------------------------------------------- /sunvox/lib/macos/lib_x86_64/sunvox.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metrasynth/sunvox-dll-python/cce86a7fc7dd7a81c60760d909e4eabbe7503c80/sunvox/lib/macos/lib_x86_64/sunvox.dylib -------------------------------------------------------------------------------- /sunvox/lib/windows/lib_x86/sunvox.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metrasynth/sunvox-dll-python/cce86a7fc7dd7a81c60760d909e4eabbe7503c80/sunvox/lib/windows/lib_x86/sunvox.dll -------------------------------------------------------------------------------- /sunvox/lib/windows/lib_x86_64/sunvox.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metrasynth/sunvox-dll-python/cce86a7fc7dd7a81c60760d909e4eabbe7503c80/sunvox/lib/windows/lib_x86_64/sunvox.dll -------------------------------------------------------------------------------- /sunvox/macros.py: -------------------------------------------------------------------------------- 1 | from math import log2 2 | from typing import Tuple 3 | 4 | 5 | def GET_MODULE_XY(in_xy: int) -> Tuple[int, int]: 6 | out_x = in_xy & 0xFFFF 7 | if out_x & 0x8000: 8 | out_x -= 0x10000 9 | out_y = (in_xy >> 16) & 0xFFFF 10 | if out_y & 0x8000: 11 | out_y -= 0x10000 12 | return out_x, out_y 13 | 14 | 15 | def GET_MODULE_FINETUNE(in_finetune: int) -> Tuple[int, int]: 16 | out_finetune = in_finetune & 0xFFFF 17 | if out_finetune & 0x8000: 18 | out_finetune -= 0x10000 19 | out_relative_note = (in_finetune >> 16) & 0xFFFF 20 | if out_relative_note & 0x8000: 21 | out_relative_note -= 0x10000 22 | return out_finetune, out_relative_note 23 | 24 | 25 | def PITCH_TO_FREQUENCY(in_pitch: int) -> float: 26 | return 2 ** ((30720.0 - in_pitch) / 3072.0) * 16.3339 27 | 28 | 29 | def FREQUENCY_TO_PITCH(in_freq: float) -> int: 30 | return int(30720 - log2(in_freq / 16.3339) * 3072) 31 | 32 | 33 | __all__ = [ 34 | "GET_MODULE_XY", 35 | "GET_MODULE_FINETUNE", 36 | "PITCH_TO_FREQUENCY", 37 | "FREQUENCY_TO_PITCH", 38 | ] 39 | -------------------------------------------------------------------------------- /sunvox/process.py: -------------------------------------------------------------------------------- 1 | import multiprocessing as mp 2 | from threading import Lock 3 | 4 | import sunvox.dll 5 | from .processor import Processor 6 | 7 | 8 | def passthrough(name): 9 | def fn(self, *args, **kw): 10 | self._lock.acquire() 11 | try: 12 | self._conn.send((name, args, kw)) 13 | return self._conn.recv() 14 | finally: 15 | self._lock.release() 16 | 17 | fn.__name__ = name 18 | return fn 19 | 20 | 21 | class Process(object): 22 | """Starts SunVox DLL in a separate process, with an API bridge.""" 23 | 24 | processor_class = Processor 25 | 26 | def __init__(self, *args, **kwargs): 27 | self._ctx = mp.get_context("spawn") 28 | self._conn, child_conn = mp.Pipe() 29 | self._lock = Lock() 30 | args = (child_conn,) + args 31 | self._process = self._ctx.Process( 32 | target=self.processor_class, args=args, kwargs=kwargs 33 | ) 34 | self._process.start() 35 | 36 | def __enter__(self): 37 | return self 38 | 39 | def __exit__(self, exc_type, exc_value, traceback): 40 | self.kill() 41 | 42 | def kill(self): 43 | self._conn.send(("kill", (), {})) 44 | 45 | _k, _v = None, None 46 | for _k in sunvox.dll.__all__: 47 | _v = getattr(sunvox.dll, _k) 48 | if hasattr(_v, "sunvox_dll_fn"): 49 | locals()[_k] = passthrough(_k) 50 | del _k, _v 51 | 52 | 53 | __all__ = ["Process"] 54 | -------------------------------------------------------------------------------- /sunvox/processor.py: -------------------------------------------------------------------------------- 1 | import sunvox.dll 2 | 3 | 4 | def passthrough(name): 5 | def fn(self, *args, **kw): 6 | return getattr(sunvox.dll, name)(*args, **kw) 7 | 8 | fn.__name__ = name 9 | return fn 10 | 11 | 12 | class Processor(object): 13 | def __init__(self, conn): 14 | self.conn = conn 15 | self.alive = True 16 | self._process_commands() 17 | 18 | def _process_commands(self): 19 | while self.alive: 20 | name, args, kwargs = self.conn.recv() 21 | fn = getattr(self, name) 22 | self.conn.send(fn(*args, **kwargs)) 23 | 24 | _k, _v = None, None 25 | for _k in sunvox.dll.__all__: 26 | _v = getattr(sunvox.dll, _k) 27 | if hasattr(_v, "sunvox_dll_fn"): 28 | locals()[_k] = passthrough(_k) 29 | del _k, _v 30 | 31 | def kill(self): 32 | self.alive = False 33 | -------------------------------------------------------------------------------- /sunvox/slot.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from collections import defaultdict 3 | from contextlib import contextmanager 4 | from io import BytesIO 5 | from pathlib import Path 6 | from typing import Union, BinaryIO, Optional 7 | 8 | from . import dll 9 | from .types import c_uint32_p, c_int16_p, c_float_p, sunvox_note_p 10 | 11 | FILENAME_ENCODING = sys.getfilesystemencoding() 12 | MAX_SLOTS = 16 13 | DEFAULT_ALLOCATION_MAP = [False] * MAX_SLOTS 14 | 15 | FileOrName = Union[str, Path, bytes, BinaryIO] 16 | 17 | 18 | class NoSlotsAvailable(Exception): 19 | """The maximum number of SunVox playback slots are in use.""" 20 | 21 | 22 | class Slot(object): 23 | """A context manager wrapping slot-specific API calls.""" 24 | 25 | allocation_map = defaultdict(DEFAULT_ALLOCATION_MAP.copy) 26 | 27 | def __init__(self, file: FileOrName = None, process=dll): 28 | self.process = process 29 | self.number = self.next_available_slot(process) 30 | if self.number is None: 31 | raise NoSlotsAvailable() 32 | self.allocation_map[process][self.number] = True 33 | self.locks = 0 34 | process.open_slot(self.number) 35 | if file is not None: 36 | self.load(file) 37 | 38 | def __enter__(self): 39 | return self 40 | 41 | def __exit__(self, type, value, traceback): 42 | self.close() 43 | 44 | def __repr__(self): 45 | return "".format(self.number, self.process) 46 | 47 | @classmethod 48 | def next_available_slot(cls, process): 49 | i = cls.allocation_map[process].index(False) 50 | return i if i >= 0 else None 51 | 52 | @property 53 | def closed(self): 54 | return self.number is not None 55 | 56 | @contextmanager 57 | def locked(self): 58 | self.lock() 59 | try: 60 | yield self 61 | finally: 62 | self.unlock() 63 | 64 | def close(self): 65 | i = self.number 66 | self.number = None 67 | self.allocation_map[self.process][i] = False 68 | return self.process.close_slot(i) 69 | 70 | close.__doc__ = dll.close_slot.__doc__ 71 | 72 | def lock(self): 73 | self.locks += 1 74 | if self.locks == 1: 75 | return self.process.lock_slot(self.number) 76 | 77 | lock.__doc__ = dll.lock_slot.__doc__ 78 | 79 | def unlock(self): 80 | if self.locks > 0: 81 | self.locks -= 1 82 | if self.locks == 0: 83 | return self.process.unlock_slot(self.number) 84 | 85 | unlock.__doc__ = dll.unlock_slot.__doc__ 86 | 87 | def load(self, file_or_name: FileOrName) -> int: 88 | """Load SunVox project using a filename or file-like object.""" 89 | if isinstance(file_or_name, (str, bytes, Path)): 90 | return self.load_filename(file_or_name) 91 | elif callable(getattr(file_or_name, "read", None)): 92 | return self.load_file(file_or_name) 93 | 94 | def load_file(self, file: BinaryIO) -> int: 95 | """Load SunVox project from a file-like object.""" 96 | value = file.getvalue() if isinstance(file, BytesIO) else file.read() 97 | return self.process.load_from_memory(self.number, value, len(value)) 98 | 99 | def load_filename(self, filename: Union[str, bytes, Path]) -> int: 100 | """Load SunVox project using a filename.""" 101 | if isinstance(filename, (str, Path)): 102 | filename = str(filename).encode("utf8") 103 | return self.process.load(self.number, filename) 104 | 105 | def save_filename(self, filename: Union[str, bytes, Path]) -> int: 106 | """Save SunVox project using a filename.""" 107 | if isinstance(filename, (str, Path)): 108 | filename = str(filename).encode("utf8") 109 | return self.process.save(self.number, filename) 110 | 111 | def play(self) -> int: 112 | return self.process.play(self.number) 113 | 114 | play.__doc__ = dll.play.__doc__ 115 | 116 | def play_from_beginning(self) -> int: 117 | return self.process.play_from_beginning(self.number) 118 | 119 | play_from_beginning.__doc__ = dll.play_from_beginning.__doc__ 120 | 121 | def stop(self) -> int: 122 | return self.process.stop(self.number) 123 | 124 | stop.__doc__ = dll.stop.__doc__ 125 | 126 | def pause(self) -> int: 127 | return self.process.pause(self.number) 128 | 129 | pause.__doc__ = dll.pause.__doc__ 130 | 131 | def resume(self) -> int: 132 | return self.process.resume(self.number) 133 | 134 | resume.__doc__ = dll.resume.__doc__ 135 | 136 | def sync_resume(self) -> int: 137 | return self.process.sync_resume(self.number) 138 | 139 | sync_resume.__doc__ = dll.resume.__doc__ 140 | 141 | def set_autostop(self, autostop: int) -> int: 142 | return self.process.set_autostop(self.number, autostop) 143 | 144 | set_autostop.__doc__ = dll.set_autostop.__doc__ 145 | 146 | def get_autostop(self) -> int: 147 | return self.process.get_autostop(self.number) 148 | 149 | get_autostop.__doc__ = dll.get_autostop.__doc__ 150 | 151 | def end_of_song(self) -> int: 152 | return self.process.end_of_song(self.number) 153 | 154 | end_of_song.__doc__ = dll.end_of_song.__doc__ 155 | 156 | def rewind(self, line_num: int) -> int: 157 | return self.process.rewind(self.number, line_num) 158 | 159 | rewind.__doc__ = dll.rewind.__doc__ 160 | 161 | def volume(self, vol: int) -> int: 162 | return self.process.volume(self.number, vol) 163 | 164 | volume.__doc__ = dll.volume.__doc__ 165 | 166 | def set_event_t(self, set: int, t: int) -> int: 167 | return self.process.set_event_t(self.number, set, t) 168 | 169 | set_event_t.__doc__ = dll.set_event_t.__doc__ 170 | 171 | def send_event( 172 | self, 173 | track_num: int, 174 | note: int, 175 | vel: int, 176 | module: int, 177 | ctl: int, 178 | ctl_val: int, 179 | ) -> int: 180 | module_index = getattr(module, "index", None) 181 | if module_index is not None: 182 | module = module_index + 1 183 | return self.process.send_event( 184 | self.number, track_num, note, vel, module, ctl, ctl_val 185 | ) 186 | 187 | send_event.__doc__ = dll.send_event.__doc__ 188 | 189 | def get_current_line(self) -> int: 190 | return self.process.get_current_line(self.number) 191 | 192 | get_current_line.__doc__ = dll.get_current_line.__doc__ 193 | 194 | def get_current_line2(self) -> int: 195 | return self.process.get_current_line2(self.number) 196 | 197 | get_current_line2.__doc__ = dll.get_current_line2.__doc__ 198 | 199 | def get_current_signal_level(self, channel) -> int: 200 | return self.process.get_current_signal_level(self.number, channel) 201 | 202 | get_current_signal_level.__doc__ = dll.get_current_signal_level.__doc__ 203 | 204 | def get_song_name(self) -> Optional[str]: 205 | song_name = self.process.get_song_name(self.number) 206 | return song_name.decode("utf8") if song_name is not None else None 207 | 208 | get_song_name.__doc__ = dll.get_song_name.__doc__ 209 | 210 | def set_song_name(self, name: str) -> int: 211 | song_name = name.encode("utf8") 212 | return self.process.set_song_name(self.number, song_name) 213 | 214 | set_song_name.__doc__ = dll.set_song_name.__doc__ 215 | 216 | def get_song_bpm(self) -> int: 217 | return self.process.get_song_bpm(self.number) 218 | 219 | get_song_bpm.__doc__ = dll.get_song_bpm.__doc__ 220 | 221 | def get_song_tpl(self) -> int: 222 | return self.process.get_song_tpl(self.number) 223 | 224 | get_song_tpl.__doc__ = dll.get_song_tpl.__doc__ 225 | 226 | def get_song_length_frames(self) -> int: 227 | return self.process.get_song_length_frames(self.number) 228 | 229 | get_song_length_frames.__doc__ = dll.get_song_length_frames.__doc__ 230 | 231 | def get_song_length_lines(self) -> int: 232 | return self.process.get_song_length_lines(self.number) 233 | 234 | get_song_length_lines.__doc__ = dll.get_song_length_lines.__doc__ 235 | 236 | def get_time_map( 237 | self, 238 | start_line: int, 239 | len: int, 240 | dest: c_uint32_p, 241 | flags: int, 242 | ) -> int: 243 | return self.process.get_time_map(self.number, start_line, len, dest, flags) 244 | 245 | get_time_map.__doc__ = dll.get_time_map.__doc__ 246 | 247 | def new_module( 248 | self, 249 | module_type: str, 250 | name: str, 251 | x: int, 252 | y: int, 253 | z: int, 254 | ) -> int: 255 | with self.locked(): 256 | return self.process.new_module( 257 | self.number, module_type.encode("utf8"), name.encode("utf8"), x, y, z 258 | ) 259 | 260 | new_module.__doc__ = dll.new_module.__doc__ 261 | 262 | def remove_module(self, mod_num: int) -> int: 263 | with self.locked(): 264 | return self.process.remove_module(self.number, mod_num) 265 | 266 | remove_module.__doc__ = dll.remove_module.__doc__ 267 | 268 | def connect_module(self, source: int, destination: int) -> int: 269 | with self.locked(): 270 | return self.process.connect_module(self.number, source, destination) 271 | 272 | connect_module.__doc__ = dll.connect_module.__doc__ 273 | 274 | def disconnect_module(self, source: int, destination: int) -> int: 275 | with self.locked(): 276 | return self.process.disconnect_module(self.number, source, destination) 277 | 278 | disconnect_module.__doc__ = dll.disconnect_module.__doc__ 279 | 280 | def load_module( 281 | self, 282 | file_or_name: FileOrName, 283 | x: int = 512, 284 | y: int = 512, 285 | z: int = 512, 286 | ) -> int: 287 | value = None 288 | if isinstance(file_or_name, BytesIO): 289 | value = file_or_name.getvalue() 290 | elif hasattr(file_or_name, "mtype"): 291 | # Radiant Voices object: serialize into bytes and load from memory. 292 | import rv.api 293 | 294 | value = rv.api.Synth(file_or_name).read() 295 | elif hasattr(file_or_name, "read"): 296 | value = file_or_name.read() 297 | if value is not None: 298 | return self.process.load_module_from_memory( 299 | slot=self.number, 300 | data=value, 301 | data_size=len(value), 302 | x=x, 303 | y=y, 304 | z=z, 305 | ) 306 | 307 | load_module.__doc__ = dll.load_module.__doc__ 308 | 309 | def sampler_load( 310 | self, 311 | mod_num: int, 312 | file_name: str, 313 | sample_slot: int, 314 | ) -> int: 315 | return self.process.sampler_load( 316 | self.number, 317 | mod_num, 318 | file_name.encode(FILENAME_ENCODING), 319 | sample_slot, 320 | ) 321 | 322 | sampler_load.__doc__ = dll.sampler_load.__doc__ 323 | 324 | def sampler_load_from_memory( 325 | self, 326 | mod_num: int, 327 | data: bytes, 328 | sample_slot: int, 329 | ) -> int: 330 | return self.process.sampler_load_from_memory( 331 | self.number, 332 | mod_num, 333 | data, 334 | len(data), 335 | sample_slot, 336 | ) 337 | 338 | sampler_load_from_memory.__doc__ = dll.sampler_load_from_memory.__doc__ 339 | 340 | def sampler_par( 341 | self, 342 | mod_num: int, 343 | sample_slot: int, 344 | par: int, 345 | par_val: int, 346 | set: int, 347 | ) -> int: 348 | return self.process.sampler_par( 349 | self.number, 350 | mod_num, 351 | sample_slot, 352 | par, 353 | par_val, 354 | set, 355 | ) 356 | 357 | sampler_par.__doc__ = dll.sampler_par.__doc__ 358 | 359 | def metamodule_load( 360 | self, 361 | mod_num: int, 362 | file_name: str, 363 | ) -> int: 364 | return self.process.metamodule_load( 365 | self.number, 366 | mod_num, 367 | file_name.encode(FILENAME_ENCODING), 368 | ) 369 | 370 | metamodule_load.__doc__ = dll.metamodule_load.__doc__ 371 | 372 | def metamodule_load_from_memory( 373 | self, 374 | mod_num: int, 375 | data: bytes, 376 | ) -> int: 377 | return self.process.metamodule_load_from_memory( 378 | self.number, 379 | mod_num, 380 | data, 381 | len(data), 382 | ) 383 | 384 | metamodule_load_from_memory.__doc__ = dll.metamodule_load_from_memory.__doc__ 385 | 386 | def vplayer_load( 387 | self, 388 | mod_num: int, 389 | file_name: str, 390 | ) -> int: 391 | return self.process.vplayer_load( 392 | self.number, 393 | mod_num, 394 | file_name.encode(FILENAME_ENCODING), 395 | ) 396 | 397 | vplayer_load.__doc__ = dll.vplayer_load.__doc__ 398 | 399 | def vplayer_load_from_memory( 400 | self, 401 | mod_num: int, 402 | data: bytes, 403 | ) -> int: 404 | return self.process.vplayer_load_from_memory( 405 | self.number, 406 | mod_num, 407 | data, 408 | len(data), 409 | ) 410 | 411 | vplayer_load_from_memory.__doc__ = dll.vplayer_load_from_memory.__doc__ 412 | 413 | def get_number_of_modules(self) -> int: 414 | return self.process.get_number_of_modules(self.number) 415 | 416 | get_number_of_modules.__doc__ = dll.get_number_of_modules.__doc__ 417 | 418 | def find_module(self, name: str) -> int: 419 | return self.process.find_module(self.number, name.encode("utf8")) 420 | 421 | find_module.__doc__ = dll.find_module.__doc__ 422 | 423 | def get_module_flags(self, mod_num: int) -> int: 424 | return self.process.get_module_flags(self.number, mod_num) 425 | 426 | get_module_flags.__doc__ = dll.get_module_flags.__doc__ 427 | 428 | def get_module_inputs(self, mod_num: int) -> int: 429 | return self.process.get_module_inputs(self.number, mod_num) 430 | 431 | get_module_inputs.__doc__ = dll.get_module_inputs.__doc__ 432 | 433 | def get_module_outputs(self, mod_num: int) -> int: 434 | return self.process.get_module_outputs(self.number, mod_num) 435 | 436 | get_module_outputs.__doc__ = dll.get_module_outputs.__doc__ 437 | 438 | def get_module_type(self, mod_num: int) -> str: 439 | module_name = self.process.get_module_type(self.number, mod_num) 440 | return module_name.decode("utf8") 441 | 442 | get_module_type.__doc__ = dll.get_module_type.__doc__ 443 | 444 | def get_module_name(self, mod_num: int) -> Optional[str]: 445 | module_name = self.process.get_module_name(self.number, mod_num) 446 | return module_name.decode("utf8") if module_name is not None else None 447 | 448 | get_module_name.__doc__ = dll.get_module_name.__doc__ 449 | 450 | def set_module_name(self, mod_num: int, name: str) -> int: 451 | module_name = name.encode("utf8") 452 | return self.process.set_module_name(self.number, mod_num, module_name) 453 | 454 | set_module_name.__doc__ = dll.set_module_name.__doc__ 455 | 456 | def get_module_xy(self, mod_num: int) -> int: 457 | return self.process.get_module_xy(self.number, mod_num) 458 | 459 | get_module_xy.__doc__ = dll.get_module_xy.__doc__ 460 | 461 | def set_module_xy(self, mod_num: int, x: int, y: int) -> int: 462 | return self.process.set_module_xy(self.number, mod_num, x, y) 463 | 464 | set_module_xy.__doc__ = dll.set_module_xy.__doc__ 465 | 466 | def get_module_color(self, mod_num: int) -> int: 467 | return self.process.get_module_color(self.number, mod_num) 468 | 469 | get_module_color.__doc__ = dll.get_module_color.__doc__ 470 | 471 | def set_module_color(self, mod_num: int, color: int) -> int: 472 | return self.process.set_module_color(self.number, mod_num, color) 473 | 474 | set_module_color.__doc__ = dll.set_module_color.__doc__ 475 | 476 | def get_module_finetune(self, mod_num: int) -> int: 477 | return self.process.get_module_finetune(self.number, mod_num) 478 | 479 | get_module_finetune.__doc__ = dll.get_module_finetune.__doc__ 480 | 481 | def set_module_finetune(self, mod_num: int, finetune: int) -> int: 482 | return self.process.set_module_finetune(self.number, mod_num, finetune) 483 | 484 | set_module_finetune.__doc__ = dll.set_module_finetune.__doc__ 485 | 486 | def set_module_relnote(self, mod_num: int, relative_note: int) -> int: 487 | return self.process.set_module_relnote(self.number, mod_num, relative_note) 488 | 489 | set_module_relnote.__doc__ = dll.set_module_relnote.__doc__ 490 | 491 | def get_module_scope2( 492 | self, 493 | mod_num: int, 494 | channel: int, 495 | dest_buf: c_int16_p, 496 | samples_to_read: int, 497 | ) -> int: 498 | return self.process.get_module_scope2( 499 | self.number, mod_num, channel, dest_buf, samples_to_read 500 | ) 501 | 502 | get_module_scope2.__doc__ = dll.get_module_scope2.__doc__ 503 | 504 | def module_curve( 505 | self, 506 | mod_num: int, 507 | curve_num: int, 508 | data: c_float_p, 509 | len: int, 510 | w: int, 511 | ) -> int: 512 | return self.module_curve(self.number, mod_num, curve_num, data, len, w) 513 | 514 | module_curve.__doc__ = dll.module_curve.__doc__ 515 | 516 | def get_number_of_module_ctls(self, mod_num: int) -> int: 517 | return self.process.get_number_of_module_ctls(self.number, mod_num) 518 | 519 | get_number_of_module_ctls.__doc__ = dll.get_number_of_module_ctls.__doc__ 520 | 521 | def get_module_ctl_name(self, mod_num: int, ctl_num: int) -> str: 522 | ctl_name = self.process.get_module_ctl_name(self.number, mod_num, ctl_num) 523 | return ctl_name.decode("utf8") if ctl_name is not None else None 524 | 525 | get_module_ctl_name.__doc__ = dll.get_module_ctl_name.__doc__ 526 | 527 | def get_module_ctl_value(self, mod_num: int, ctl_num: int, scaled: int) -> int: 528 | return self.process.get_module_ctl_value(self.number, mod_num, ctl_num, scaled) 529 | 530 | get_module_ctl_value.__doc__ = dll.get_module_ctl_value.__doc__ 531 | 532 | def set_module_ctl_value(self, mod_num: int, ctl_num: int, val: int, scaled: int): 533 | return self.process.set_module_ctl_value( 534 | self.number, mod_num, ctl_num, val, scaled 535 | ) 536 | 537 | set_module_ctl_value.__doc__ = dll.set_module_ctl_value.__doc__ 538 | 539 | def get_module_ctl_min(self, mod_num: int, ctl_num: int, scaled: int) -> int: 540 | return self.process.get_module_ctl_min(self.number, mod_num, ctl_num, scaled) 541 | 542 | get_module_ctl_min.__doc__ = dll.get_module_ctl_min.__doc__ 543 | 544 | def get_module_ctl_max(self, mod_num: int, ctl_num: int, scaled: int) -> int: 545 | return self.process.get_module_ctl_max(self.number, mod_num, ctl_num, scaled) 546 | 547 | get_module_ctl_max.__doc__ = dll.get_module_ctl_max.__doc__ 548 | 549 | def get_module_ctl_offset(self, mod_num: int, ctl_num: int) -> int: 550 | return self.process.get_module_ctl_offset(self.number, mod_num, ctl_num) 551 | 552 | get_module_ctl_offset.__doc__ = dll.get_module_ctl_offset.__doc__ 553 | 554 | def get_module_ctl_type(self, mod_num: int, ctl_num: int) -> int: 555 | return self.process.get_module_ctl_type(self.number, mod_num, ctl_num) 556 | 557 | get_module_ctl_type.__doc__ = dll.get_module_ctl_type.__doc__ 558 | 559 | def get_module_ctl_group(self, mod_num: int, ctl_num: int) -> int: 560 | return self.process.get_module_ctl_group(self.number, mod_num, ctl_num) 561 | 562 | get_module_ctl_group.__doc__ = dll.get_module_ctl_group.__doc__ 563 | 564 | def new_pattern( 565 | self, 566 | clone: int, 567 | x: int, 568 | y: int, 569 | tracks: int, 570 | lines: int, 571 | icon_seed: int, 572 | name: str, 573 | ) -> int: 574 | with self.locked(): 575 | return self.process.new_pattern( 576 | self.number, clone, x, y, tracks, lines, icon_seed, name.encode("utf8") 577 | ) 578 | 579 | new_pattern.__doc__ = dll.new_pattern.__doc__ 580 | 581 | def remove_pattern(self, pat_num: int) -> int: 582 | with self.locked(): 583 | return self.process.remove_pattern(self.number, pat_num) 584 | 585 | remove_pattern.__doc__ = dll.remove_pattern.__doc__ 586 | 587 | def get_number_of_patterns(self) -> int: 588 | return self.process.get_number_of_patterns(self.number) 589 | 590 | get_number_of_patterns.__doc__ = dll.get_number_of_patterns.__doc__ 591 | 592 | def find_pattern(self, name: str) -> int: 593 | return self.process.find_pattern(self.number, name.encode("utf8")) 594 | 595 | find_pattern.__doc__ = dll.find_pattern.__doc__ 596 | 597 | def get_pattern_x(self, pat_num: int) -> int: 598 | return self.process.get_pattern_x(self.number, pat_num) 599 | 600 | get_pattern_x.__doc__ = dll.get_pattern_x.__doc__ 601 | 602 | def get_pattern_y(self, pat_num: int) -> int: 603 | return self.process.get_pattern_y(self.number, pat_num) 604 | 605 | get_pattern_y.__doc__ = dll.get_pattern_y.__doc__ 606 | 607 | def set_pattern_xy(self, pat_num: int, x: int, y: int) -> int: 608 | with self.locked(): 609 | return self.process.set_pattern_xy(self.number, pat_num, x, y) 610 | 611 | set_pattern_xy.__doc__ = dll.set_pattern_xy.__doc__ 612 | 613 | def get_pattern_tracks(self, pat_num: int) -> int: 614 | return self.process.get_pattern_tracks(self.number, pat_num) 615 | 616 | get_pattern_tracks.__doc__ = dll.get_pattern_tracks.__doc__ 617 | 618 | def get_pattern_lines(self, pat_num: int) -> int: 619 | return self.process.get_pattern_lines(self.number, pat_num) 620 | 621 | get_pattern_lines.__doc__ = dll.get_pattern_lines.__doc__ 622 | 623 | def set_pattern_size(self, pat_num: int, tracks: int, lines: int) -> int: 624 | with self.locked(): 625 | return self.process.set_pattern_size(self.number, pat_num, tracks, lines) 626 | 627 | set_pattern_size.__doc__ = dll.set_pattern_size.__doc__ 628 | 629 | def get_pattern_name(self, pat_num: int) -> str: 630 | pattern_name = self.process.get_pattern_name(self.number, pat_num) 631 | return pattern_name.decode("utf8") if pattern_name is not None else None 632 | 633 | get_pattern_name.__doc__ = dll.get_pattern_name.__doc__ 634 | 635 | def set_pattern_name(self, pat_num: int, name: str) -> int: 636 | pattern_name = name.encode("utf8") 637 | return self.process.set_pattern_name(self.number, pattern_name) 638 | 639 | set_pattern_name.__doc__ = dll.set_pattern_name.__doc__ 640 | 641 | def get_pattern_data(self, pat_num: int) -> sunvox_note_p: 642 | return self.process.get_pattern_data(self.number, pat_num) 643 | 644 | get_pattern_data.__doc__ = dll.get_pattern_data.__doc__ 645 | 646 | def set_pattern_event( 647 | self, 648 | pat_num: int, 649 | track: int, 650 | line: int, 651 | nn: int, 652 | vv: int, 653 | mm: int, 654 | ccee: int, 655 | xxyy: int, 656 | ) -> int: 657 | return self.process.set_pattern_event( 658 | self.number, pat_num, track, line, nn, vv, mm, ccee, xxyy 659 | ) 660 | 661 | set_pattern_event.__doc__ = dll.set_pattern_event.__doc__ 662 | 663 | def get_pattern_event( 664 | self, pat_num: int, track: int, line: int, column: int 665 | ) -> int: 666 | return self.process.get_pattern_event(self.number, pat_num, track, line, column) 667 | 668 | get_pattern_event.__doc__ = dll.get_pattern_event.__doc__ 669 | 670 | def pattern_mute(self, pat_num: int, mute: int) -> int: 671 | with self.locked(): 672 | return self.process.pattern_mute(self.number, pat_num, mute) 673 | 674 | pattern_mute.__doc__ = dll.pattern_mute.__doc__ 675 | 676 | 677 | __all__ = ["FILENAME_ENCODING", "MAX_SLOTS", "NoSlotsAvailable", "Slot"] 678 | -------------------------------------------------------------------------------- /sunvox/tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metrasynth/sunvox-dll-python/cce86a7fc7dd7a81c60760d909e4eabbe7503c80/sunvox/tools/__init__.py -------------------------------------------------------------------------------- /sunvox/tools/export.py: -------------------------------------------------------------------------------- 1 | """A SunVox to WAV file exporter. 2 | 3 | Usage: python -m sunvox.tools.export FILE 4 | python -m sunvox.tools.export --help 5 | 6 | Note: Before running, install the necessary packages:: 7 | 8 | $ pip install -r requirements/tools.txt 9 | """ 10 | 11 | import argparse 12 | import logging 13 | import os 14 | import sys 15 | 16 | from sunvox.api import Slot 17 | from sunvox.buffered import BufferedProcess, float32, int16 18 | 19 | 20 | log = logging.getLogger(__name__) 21 | 22 | 23 | parser = argparse.ArgumentParser(description="SunVox to WAV file exporter") 24 | parser.add_argument( 25 | "filename", metavar="FILE", type=str, nargs=1, help="SunVox file to export" 26 | ) 27 | parser.add_argument( 28 | "--int16", 29 | dest="data_type", 30 | action="store_const", 31 | const=int16, 32 | default=float32, 33 | help="Output 16-bit signed integer values", 34 | ) 35 | parser.add_argument( 36 | "--float32", 37 | dest="data_type", 38 | action="store_const", 39 | const=float32, 40 | default=float32, 41 | help="Output 32-bit floating point values", 42 | ) 43 | parser.add_argument( 44 | "--freq", 45 | metavar="RATE", 46 | action="store", 47 | dest="freq", 48 | type=int, 49 | nargs=1, 50 | default=[44100], 51 | help="Output frequency (44100 or 48000)", 52 | ) 53 | parser.add_argument( 54 | "--channels", 55 | metavar="CHANNELS", 56 | action="store", 57 | dest="channels", 58 | type=int, 59 | nargs=1, 60 | default=[2], 61 | help="Channels (1 or 2)", 62 | ) 63 | parser.add_argument( 64 | "--out", 65 | metavar="FILE", 66 | action="store", 67 | dest="out_filename", 68 | type=str, 69 | nargs=1, 70 | default=None, 71 | help='Output file to write (defaults to "inputname.wav")', 72 | ) 73 | 74 | 75 | def main(): 76 | logging.basicConfig(level=logging.DEBUG) 77 | try: 78 | import numpy as np 79 | from scipy.io import wavfile 80 | from tqdm import tqdm 81 | except ImportError: 82 | log.error( 83 | 'Please "pip install -r requirements/tools.txt" to use sunvox.tools.export' 84 | ) 85 | return 1 86 | args = parser.parse_args() 87 | in_filename = args.filename[0] 88 | out_filename = args.out_filename and args.out_filename[0] 89 | if not out_filename: 90 | root, ext = os.path.splitext(in_filename) 91 | out_filename = "{}.wav".format(root) 92 | data_type = args.data_type 93 | freq = args.freq[0] 94 | channels = args.channels[0] 95 | log.debug("%r", channels) 96 | log.debug("Start SunVox process") 97 | p = BufferedProcess(freq=freq, size=freq, channels=channels, data_type=data_type) 98 | slot = Slot(in_filename, process=p) 99 | length = slot.get_song_length_frames() 100 | output = np.zeros((length, 2), data_type) 101 | position = 0 102 | log.info( 103 | "Rendering at %s frames/sec, %s channels, %s resolution", 104 | freq, 105 | channels, 106 | data_type.__name__, 107 | ) 108 | slot.play_from_beginning() 109 | pbar = tqdm(total=length, unit_scale=True, unit="frame", dynamic_ncols=True) 110 | with pbar as pbar: 111 | while position < length: 112 | buffer = p.fill_buffer() 113 | end_pos = min(position + freq, length) 114 | copy_size = end_pos - position 115 | output[position:end_pos] = buffer[:copy_size] 116 | position = end_pos 117 | pbar.update(copy_size) 118 | log.info("Saving to %r", out_filename) 119 | wavfile.write(out_filename, freq, output) 120 | log.debug("Stop SunVox process") 121 | p.deinit() 122 | p.kill() 123 | log.info("Finished") 124 | 125 | 126 | if __name__ == "__main__": 127 | sys.exit(main()) 128 | -------------------------------------------------------------------------------- /sunvox/types.py: -------------------------------------------------------------------------------- 1 | from ctypes import Structure, c_ubyte, c_ushort, POINTER, c_uint32, c_int16, c_float 2 | from enum import IntEnum 3 | 4 | 5 | c_float_p = POINTER(c_float) 6 | c_int16_p = POINTER(c_int16) 7 | c_uint32_p = POINTER(c_uint32) 8 | 9 | 10 | class NOTECMD(IntEnum): 11 | ( 12 | C0, 13 | c0, 14 | D0, 15 | d0, 16 | E0, 17 | F0, 18 | f0, 19 | G0, 20 | g0, 21 | A0, 22 | a0, 23 | B0, 24 | C1, 25 | c1, 26 | D1, 27 | d1, 28 | E1, 29 | F1, 30 | f1, 31 | G1, 32 | g1, 33 | A1, 34 | a1, 35 | B1, 36 | C2, 37 | c2, 38 | D2, 39 | d2, 40 | E2, 41 | F2, 42 | f2, 43 | G2, 44 | g2, 45 | A2, 46 | a2, 47 | B2, 48 | C3, 49 | c3, 50 | D3, 51 | d3, 52 | E3, 53 | F3, 54 | f3, 55 | G3, 56 | g3, 57 | A3, 58 | a3, 59 | B3, 60 | C4, 61 | c4, 62 | D4, 63 | d4, 64 | E4, 65 | F4, 66 | f4, 67 | G4, 68 | g4, 69 | A4, 70 | a4, 71 | B4, 72 | C5, 73 | c5, 74 | D5, 75 | d5, 76 | E5, 77 | F5, 78 | f5, 79 | G5, 80 | g5, 81 | A5, 82 | a5, 83 | B5, 84 | C6, 85 | c6, 86 | D6, 87 | d6, 88 | E6, 89 | F6, 90 | f6, 91 | G6, 92 | g6, 93 | A6, 94 | a6, 95 | B6, 96 | C7, 97 | c7, 98 | D7, 99 | d7, 100 | E7, 101 | F7, 102 | f7, 103 | G7, 104 | g7, 105 | A7, 106 | a7, 107 | B7, 108 | C8, 109 | c8, 110 | D8, 111 | d8, 112 | E8, 113 | F8, 114 | f8, 115 | G8, 116 | g8, 117 | A8, 118 | a8, 119 | B8, 120 | C9, 121 | c9, 122 | D9, 123 | d9, 124 | E9, 125 | F9, 126 | f9, 127 | G9, 128 | g9, 129 | A9, 130 | a9, 131 | B9, 132 | ) = range(1, 121) 133 | 134 | EMPTY = 0 135 | 136 | NOTE_OFF = 128 137 | 138 | ALL_NOTES_OFF = 129 139 | 'send "note off" to all modules' 140 | 141 | CLEAN_SYNTHS = 130 142 | "stop all modules - clear their internal buffers and put them into standby mode" 143 | 144 | STOP = 131 145 | 146 | PLAY = 132 147 | 148 | SET_PITCH = 133 149 | """ 150 | set the pitch specified in column XXYY, where 0x0000 151 | - highest possible pitch, 0x7800 152 | - lowest pitch (note C0); 153 | one semitone = 0x100 154 | """ 155 | 156 | PREV_TRACK = 134 157 | 158 | CLEAN_MODULE = 140 159 | "stop the module - clear its internal buffers and put it into standby mode" 160 | 161 | 162 | class INIT_FLAG(IntEnum): 163 | """Flags for init()""" 164 | 165 | NO_DEBUG_OUTPUT = 1 << 0 166 | 167 | USER_AUDIO_CALLBACK = 1 << 1 168 | """ 169 | Offline mode: 170 | system-dependent audio stream will not be created; 171 | user calls audio_callback() to get the next piece of sound stream 172 | """ 173 | 174 | OFFLINE = 1 << 1 175 | "Same as INIT_FLAG.USER_AUDIO_CALLBACK" 176 | 177 | AUDIO_INT16 = 1 << 2 178 | "Desired sample type of the output sound stream : int16_t" 179 | 180 | AUDIO_FLOAT32 = 1 << 3 181 | """ 182 | Desired sample type of the output sound stream : float 183 | The actual sample type may be different, if INIT_FLAG.USER_AUDIO_CALLBACK is not set 184 | """ 185 | 186 | ONE_THREAD = 1 << 4 187 | """ 188 | Audio callback and song modification functions are in single thread 189 | Use it with INIT_FLAG.USER_AUDIO_CALLBACK only 190 | """ 191 | 192 | 193 | class TIME_MAP(IntEnum): 194 | """Flags for get_time_map()""" 195 | 196 | SPEED = 0 197 | FRAMECNT = 1 198 | TYPE_MASK = 3 199 | 200 | 201 | class MODULE(IntEnum): 202 | """Flags for get_module_flags()""" 203 | 204 | FLAG_EXISTS = 1 << 0 205 | 206 | FLAG_GENERATOR = 1 << 1 207 | "Note input + Sound output" 208 | 209 | FLAG_EFFECT = 1 << 2 210 | "Sound input + Sound output" 211 | 212 | FLAG_MUTE = 1 << 3 213 | 214 | FLAG_SOLO = 1 << 4 215 | 216 | FLAG_BYPASS = 1 << 5 217 | 218 | INPUTS_OFF = 16 219 | 220 | INPUTS_MASK = 255 << INPUTS_OFF 221 | 222 | OUTPUTS_OFF = 16 + 8 223 | 224 | OUTPUTS_MASK = 255 << OUTPUTS_OFF 225 | 226 | 227 | class sunvox_note(Structure): 228 | _fields_ = [ 229 | # NN: 0 - nothing; 1..127 - note num; 128 - note off; 129, 130... 230 | # - see NOTECMD enum 231 | ("note", c_ubyte), 232 | # VV: Velocity 1..129; 0 - default 233 | ("vel", c_ubyte), 234 | # MM: 0 - nothing; 1..65535 - module number + 1 235 | ("module", c_ushort), 236 | # 0xCCEE: CC: 1..127 - controller number + 1; EE - effect 237 | ("ctl", c_ushort), 238 | # 0xXXYY: value of controller or effect 239 | ("ctl_val", c_ushort), 240 | ] 241 | 242 | 243 | sunvox_note_p = POINTER(sunvox_note) 244 | 245 | 246 | __all__ = [ 247 | "NOTECMD", 248 | "INIT_FLAG", 249 | "MODULE", 250 | "TIME_MAP", 251 | "sunvox_note", 252 | "sunvox_note_p", 253 | "c_float_p", 254 | "c_int16_p", 255 | "c_uint32_p", 256 | ] 257 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metrasynth/sunvox-dll-python/cce86a7fc7dd7a81c60760d909e4eabbe7503c80/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_init_deinit.py: -------------------------------------------------------------------------------- 1 | import sunvox.api 2 | 3 | 4 | def test_init_deinit(): 5 | # given: the user wants to initialize the SunVox library 6 | 7 | # and: the user doesn't want to load a custom configuration 8 | config = None 9 | 10 | # and: the user wants a sample rate of 44100 11 | freq = 44100 12 | 13 | # and: the user wants 2 audio channels 14 | channels = 2 15 | 16 | # and: they want it to run in one thread 17 | flags = 0 18 | flags |= sunvox.api.INIT_FLAG.ONE_THREAD 19 | 20 | # and: they want to use a custom audio callback 21 | flags |= sunvox.api.INIT_FLAG.USER_AUDIO_CALLBACK 22 | 23 | # when: the library is initialized 24 | version = sunvox.api.init(config, freq, channels, flags) 25 | 26 | # and: the library is deinitialized 27 | deinit_response = sunvox.api.deinit() 28 | 29 | # then: the version reported is 2.1.2 30 | assert version == 0x020102 31 | 32 | # and: the deinitialization was successful 33 | assert deinit_response == 0 34 | --------------------------------------------------------------------------------