├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG ├── CONTRIBUTORS.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── TODO.md ├── __init__.py ├── fluidsynth.py ├── pyproject.toml └── test ├── 1080-c01.mid ├── __init__.py ├── example.sf2 ├── modulatorTest.py ├── sequencerTest.py ├── test1.py ├── test2.py ├── test3.py ├── test4.py ├── test5.py ├── test6.py └── test7.py /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Keep GitHub Actions up to date with GitHub's Dependabot... 2 | # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot 3 | # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem 4 | version: 2 5 | updates: 6 | - package-ecosystem: github-actions 7 | directory: / 8 | groups: 9 | github-actions: 10 | patterns: 11 | - "*" # Group all Actions updates into a single larger pull request 12 | schedule: 13 | interval: weekly 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | release: 8 | types: [created] # Only publish on tagged releases 9 | jobs: 10 | codespell_and_ruff: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: astral-sh/ruff-action@v3 15 | - run: pipx run 'codespell[toml]' **/*.py **/*.txt --skip="venv/lib/python3*" 16 | 17 | ci: 18 | needs: [codespell_and_ruff] 19 | strategy: 20 | fail-fast: false 21 | matrix: # macos-13 in Intel, macos-latest is Apple Silicon ARM 22 | os: [macos-13, macos-latest, ubuntu-latest, ubuntu-24.04-arm, windows-latest, windows-2025] 23 | runs-on: ${{ matrix.os }} 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: astral-sh/setup-uv@v6 27 | with: 28 | activate-environment: true 29 | python-version: 3.13 30 | version: "latest" 31 | - if: runner.os == 'Linux' 32 | run: | 33 | sudo apt-get update 34 | sudo apt-get install fluidsynth libasound-dev portaudio19-dev python3-pyaudio 35 | - if: runner.os == 'macOS' 36 | run: | 37 | brew install fluid-synth 38 | echo "DYLD_LIBRARY_PATH=$(brew --prefix fluid-synth)/lib/" >> $GITHUB_ENV 39 | - if: runner.os == 'Windows' 40 | run: choco install fluidsynth 41 | - run: uv pip install --editable . 42 | - shell: python 43 | run: | 44 | import fluidsynth 45 | print(fluidsynth) 46 | print(dir(fluidsynth)) 47 | # NOTE: The files in test/ are NOT unit tests or pytests. 48 | # On macOS ARM64 the following tests will pass but each takes 8 minutes to run. 49 | # On macOS X64 all tests will pass quickly. 50 | - if: runner.os == 'macOS' && runner.arch == 'X64' 51 | run: | 52 | python test/test1.py 53 | python test/test2.py 54 | python test/test3.py 55 | python test/sequencerTest.py 56 | - if: runner.os == 'Linux' 57 | run: | 58 | python test/test1.py 59 | python test/test3.py 60 | python test/sequencerTest.py 61 | # On Windows all tests will pass quickly. 62 | - if: runner.os == 'Windows' 63 | run: | 64 | python test/test1.py 65 | python test/test2.py 66 | python test/test3.py 67 | python test/sequencerTest.py 68 | 69 | build: 70 | needs: [ci] 71 | runs-on: ubuntu-latest 72 | permissions: 73 | id-token: write 74 | 75 | steps: 76 | - uses: actions/checkout@v4 77 | - uses: astral-sh/setup-uv@v6 78 | with: 79 | python-version: 3.13 80 | version: "latest" 81 | 82 | - name: Clean previous builds 83 | run: rm -rf dist 84 | 85 | - name: Build package 86 | run: | 87 | uv build 88 | uvx twine check --strict dist/* 89 | ls -la dist 90 | 91 | - name: Upload dist directory as artifact 92 | uses: actions/upload-artifact@v4 93 | with: 94 | name: dist 95 | path: dist 96 | 97 | pypi-publish: 98 | needs: [build] 99 | name: upload release to PyPI 100 | if: github.event_name == 'release' && github.event.action == 'created' # Only on release creation 101 | runs-on: ubuntu-latest 102 | # Specifying a GitHub environment is optional, but strongly encouraged 103 | environment: release 104 | permissions: 105 | # IMPORTANT: this permission is mandatory for trusted publishing 106 | id-token: write 107 | steps: 108 | - name: Download dist artifact 109 | uses: actions/download-artifact@v4 110 | with: 111 | name: dist 112 | path: dist 113 | - run: ls -la 114 | - run: ls -la dist || true 115 | - uses: astral-sh/setup-uv@v6 116 | with: 117 | python-version: 3.13 118 | version: "latest" 119 | - run: uv publish 120 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Apr 21, 2024 - 1.3.4 2 | 3 | * Update print, use tobytes instead of tostring -- Christian Clauss 4 | * Support finding MacOS ARM64 fluidsynth library -- Christian Clauss 5 | * GitHub actions support -- Christian Clauss 6 | * Fix Windows search location for Chocolatey installs -- Nathan Whitehead 7 | 8 | ================================================================================ 9 | Feb 18, 2024 - 1.3.3 10 | 11 | * Bump version and changelog -- Christian Romberg 12 | * Add fluid_player_get_status and fix some typing bugs -- Philipp Schmidt 13 | * Fix "Unknown integer parameter 'synth.sample-rate" (#37) -- Philipp Schmidt 14 | 15 | 16 | ================================================================================ 17 | Feb 10, 2023 - 1.3.2 18 | 19 | * Add `all_sounds_off` and `all_notes_off` methods. -- Kevin Zakka 20 | 21 | 22 | ================================================================================ 23 | Nov 11, 2021 - 1.3.1 24 | 25 | * Added methods to support tuning, event analysis, and midi file playback -- Jonathan Allin 26 | * Added support for using an external midi router callback function -- Jonathan Allin 27 | 28 | 29 | ================================================================================ 30 | Nov 6, 2020 - 1.3.0 31 | 32 | * Added lots of bindings and stuff to help with playing live -- Bill Peterson 33 | * Added fluidsynth 2 compatibility -- Bill Peterson 34 | 35 | 36 | ================================================================================ 37 | May 24, 2018 - 1.2.5 38 | 39 | * Added sequencer support -- Christian Romberg 40 | 41 | 42 | ================================================================================ 43 | February 13, 2015 44 | 45 | * Mover repository to git in GitHub 46 | * Tweak README to work with GitHub flavored MarkDown 47 | * Include LGPL license text. 48 | 49 | 50 | ================================================================================ 51 | 52 | March 2, 2009 - 1.2.4 53 | 54 | * Another attempt to find the proper library on Windows systems. Added 55 | short circuited or expression to find either 'fluidsynth' or 56 | 'libfluidsynth'. 57 | * Only import numpy when it's actually needed. 58 | 59 | 60 | ================================================================================ 61 | 62 | January 15, 2009 - 1.2.3 63 | 64 | * Throw ImportError if the fluidsynth library can't be found. 65 | * Range checking in noteon and noteoff 66 | 67 | 68 | ================================================================================ 69 | 70 | January 15, 2009 - 1.2.2 71 | 72 | * Small version increment to update website and maintainer info. 73 | No actual code changes. 74 | 75 | 76 | ================================================================================ 77 | 78 | July 22, 2008 - 1.2.1 79 | 80 | * Added pydoc documentation and this CHANGELOG. 81 | 82 | 83 | ================================================================================ 84 | 85 | July 22, 2008 - 1.2 86 | 87 | * Changed from C extension code to ctypes for portability and simplicity 88 | of code. 89 | 90 | * Updated interface to use objects instead of just functions. 91 | 92 | 93 | ================================================================================ 94 | 95 | July 21, 2008 - 1.1 96 | 97 | * Added direct audio data interface so you don't have to start audio 98 | driver thread. Makes pyFluidSynth work for audio games and other 99 | applications that manage their own sound. 100 | 101 | 102 | ================================================================================ 103 | 104 | July 16, 2008 - 1.0 105 | 106 | * Imported from previous work done earlier in the summer. 107 | 108 | 109 | ================================================================================ 110 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | ## Contributors 2 | 3 | Original code by Nathan Whitehead `nwhitehe@gmail.com`. 4 | 5 | Contributions: 6 | * Christian Clauss 7 | * Bart Spaans 8 | * Christian Romberg 9 | * Bill Peterson 10 | * Christopher Nguyen 11 | * Matthew D. Scholefield 12 | * Mathieu (matael) Gaborit 13 | * Thomas Oster 14 | * Adam Roberts 15 | * Aditya Shankar 16 | * Dude112113 17 | * Kevin Zakka 18 | 19 | You are invited to contribute to this project. Please feel free to open an issue 20 | or create a pull request on GitHub. 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 489 | 490 | Also add information on how to contact you by electronic and paper mail. 491 | 492 | You should also get your employer (if you work as a programmer) or your 493 | school, if any, to sign a "copyright disclaimer" for the library, if 494 | necessary. Here is a sample; alter the names: 495 | 496 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 497 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 498 | 499 | , 1 April 1990 500 | Ty Coon, President of Vice 501 | 502 | That's all there is to it! 503 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include test/*.sf2 2 | include doc/* 3 | include CHANGELOG -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyFluidSynth 2 | 3 | _Python bindings for FluidSynth_ 4 | 5 | This package contains Python bindings for FluidSynth. FluidSynth is a software 6 | synthesizer for generating music. It works like a MIDI synthesizer. You load 7 | patches, set parameters, then send NOTEON and NOTEOFF events to play notes. 8 | Instruments are defined in SoundFonts, generally files with the extension SF2. 9 | FluidSynth can either be used to play audio itself, or you can call a function 10 | that returns chunks of audio data and output the data to the soundcard yourself. 11 | FluidSynth works on all major platforms, so pyFluidSynth should also. 12 | 13 | ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/nwhitehead/pyfluidsynth/ci.yml) | ![PyPI - Version](https://img.shields.io/pypi/v/pyFluidSynth) | ![PyPI - Python Versions](https://img.shields.io/pypi/pyversions/pyFluidSynth) | 14 | 15 | 16 | ## Requirements 17 | 18 | [FluidSynth](http://www.fluidsynth.org/) (2.0.0 or later) 19 | * [Windows/Android FluidSynth Releases](https://github.com/FluidSynth/fluidsynth/releases) 20 | * [MacOS/Linux Distributions](https://github.com/FluidSynth/fluidsynth/wiki/Download#distributions) 21 | * [Building from Source](https://github.com/FluidSynth/fluidsynth/wiki/BuildingWithCMake) 22 | 23 | (optional) [NumPy](http://numpy.org/) 1.0 or later for some features 24 | 25 | NOTE: If you don't need all the features of FluidSynth you may be interested in 26 | [tinysoundfont-pybind](https://github.com/nwhitehead/tinysoundfont-pybind) which 27 | is a self-contained Python package that includes 28 | [TinySoundFont](https://github.com/schellingb/TinySoundFont) for SoundFont 29 | playback and is permissively licensed. 30 | 31 | 32 | ## Installation 33 | 34 | To use the latest official release: 35 | 36 | pip install pyfluidsynth 37 | 38 | 39 | ## Pre-release Versions 40 | 41 | To use pre-release versions of this package, clone this repository, go to the 42 | repository directory, then do: 43 | 44 | pip install --editable . 45 | 46 | 47 | ## Example 48 | 49 | Here is a program that plays a chord for a second. 50 | 51 | ```python 52 | import time 53 | import fluidsynth 54 | 55 | fs = fluidsynth.Synth() 56 | fs.start() 57 | 58 | sfid = fs.sfload("example.sf2") 59 | fs.program_select(0, sfid, 0, 0) 60 | 61 | fs.noteon(0, 60, 30) 62 | fs.noteon(0, 67, 30) 63 | fs.noteon(0, 76, 30) 64 | 65 | time.sleep(1.0) 66 | 67 | fs.noteoff(0, 60) 68 | fs.noteoff(0, 67) 69 | fs.noteoff(0, 76) 70 | 71 | time.sleep(1.0) 72 | 73 | fs.delete() 74 | ``` 75 | 76 | First a Synth object is created to control playback. 77 | The `start()` method starts audio output in a separate thread. 78 | 79 | To get sound, you need to choose an instrument. First load a 80 | SoundFont with `sfload()`, then select a bank and preset with 81 | `program_select()`. 82 | 83 | ```python 84 | program_select(track, soundfontid, banknum, presetnum) 85 | ``` 86 | 87 | To start a note, use the noteon() method. 88 | 89 | ```python 90 | noteon(track, midinum, velocity) 91 | ``` 92 | 93 | To stop a note, use noteoff(). 94 | 95 | ```python 96 | noteoff(track, midinum) 97 | ``` 98 | 99 | 100 | ## Managing Audio 101 | 102 | You can also manage audio IO yourself and just use FluidSynth to 103 | calculate the samples for the music. You might do this, for example, 104 | in a game with WAV sound effects and algorithmically generated music. 105 | To do this, create the Synth object but don't call `start()`. To 106 | generate the next chunk of audio, call `get_samples()`. 107 | 108 | ```python 109 | get_samples(len) 110 | ``` 111 | 112 | The length you pass will be the number of audio samples. Unless 113 | specified otherwise, FluidSynth assumes an output rate of 44100 Hz. 114 | The return value will be a Numpy array of samples. By default 115 | FluidSynth generates stereo sound, so the return array will be 116 | length `2 * len`. 117 | 118 | To join arrays together, use `numpy.append()`. 119 | 120 | To convert an array of samples into a string of bytes suitable for sending 121 | to the soundcard, use `fluidsynth.raw_audio_string(samples)`. 122 | 123 | Here is an example that generates a chord then plays the data using 124 | PyAudio. 125 | 126 | ```python 127 | import time 128 | import numpy 129 | import pyaudio 130 | import fluidsynth 131 | 132 | pa = pyaudio.PyAudio() 133 | strm = pa.open( 134 | format = pyaudio.paInt16, 135 | channels = 2, 136 | rate = 44100, 137 | output = True) 138 | 139 | s = [] 140 | 141 | fl = fluidsynth.Synth() 142 | 143 | # Initial silence is 1 second 144 | s = numpy.append(s, fl.get_samples(44100 * 1)) 145 | 146 | sfid = fl.sfload("example.sf2") 147 | fl.program_select(0, sfid, 0, 0) 148 | 149 | fl.noteon(0, 60, 30) 150 | fl.noteon(0, 67, 30) 151 | fl.noteon(0, 76, 30) 152 | 153 | # Chord is held for 2 seconds 154 | s = numpy.append(s, fl.get_samples(44100 * 2)) 155 | 156 | fl.noteoff(0, 60) 157 | fl.noteoff(0, 67) 158 | fl.noteoff(0, 76) 159 | 160 | # Decay of chord is held for 1 second 161 | s = numpy.append(s, fl.get_samples(44100 * 1)) 162 | 163 | fl.delete() 164 | 165 | samps = fluidsynth.raw_audio_string(s) 166 | 167 | print(len(samps)) 168 | print('Starting playback') 169 | strm.write(samps) 170 | ``` 171 | 172 | ## Using the Sequencer 173 | 174 | You can create a sequencer as follows: 175 | ```python 176 | import fluidsynth 177 | 178 | seq = fluidsynth.Sequencer() 179 | ``` 180 | This will by default create a sequencer that will advance at 181 | a rate of 1000 ticks per second. To change the rate at which 182 | the sequencer advances, you can give it the optional `time_scale` 183 | parameter. As a clock source, it will use your system clock. In 184 | order to manually advance the sequencer, you can give it the 185 | parameter `use_system_timer=False`. You will then have to advance 186 | it using `sequencer.process`. 187 | 188 | In order to make the sequencer aware of your synthesizer, you have to 189 | register it: 190 | ```python 191 | fs = fluidsynth.Synth() 192 | # init and start the synthesizer as described above… 193 | 194 | synth_id = seq.register_fluidsynth(fs) 195 | ``` 196 | You have to keep `synth_id` and use it as a `target` for the midi events 197 | you want to schedule. Now, you can sequence actual notes: 198 | ```python 199 | seq.note_on(time=500, absolute=False, channel=0, key=60, velocity=80, dest=synth_id) 200 | ``` 201 | If you use relative timing like above, the sequencer will 202 | schedule the event the specified time from the current position. 203 | Otherwise, if `absolute` is `True` (the default), you have to use 204 | absolute track positions (in ticks). So the following code snippet 205 | will do the same as the one above: 206 | ```python 207 | current_time = seq.get_tick() 208 | seq.note_on(current_time + 500, 0, 60, 80, dest=synth_id) 209 | ``` 210 | You can also register your own callback functions to be called at 211 | certain ticks: 212 | ```python 213 | def seq_callback(time, event, seq, data): 214 | print('callback called!') 215 | 216 | callback_id = sequencer.register_client("myCallback", seq_callback) 217 | 218 | sequencer.timer(current_time + 2000, dest=callback_id) 219 | ``` 220 | Note that event and seq are low-level objects, not actual Python objects. 221 | 222 | You can find a complete example (inspired by [this one from the fluidsynth library](http://www.fluidsynth.org/api/index.html#Sequencer)) in the test folder. 223 | 224 | 225 | ## Bugs and Limitations 226 | 227 | Not all functions in FluidSynth are bound. 228 | 229 | Not much error checking, FluidSynth will segfault/crash if you call 230 | the functions incorrectly sometimes. 231 | 232 | 233 | ## Authors 234 | 235 | This project was originally created by Nathan Whitehead `nwhitehe@gmail.com` but is the work of many. See [CONTRIBUTORS](./CONTRIBUTORS.md). 236 | 237 | 238 | ## License 239 | 240 | Released under the LGPL v2.1 or any later 241 | version (this is the same as FluidSynth). 242 | 243 | Copyright 2008--2024, Nathan Whitehead and contributors. 244 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | * Some unit tests. 2 | * Update README documentation 3 | * Provide API documentation, either by updating comments in fluidsynth.py or something else 4 | * maybe get rid of this file and use github projects -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwhitehead/pyfluidsynth/dd601166845bc6e2ec0927d78008eab328e65409/__init__.py -------------------------------------------------------------------------------- /fluidsynth.py: -------------------------------------------------------------------------------- 1 | """ 2 | ================================================================================ 3 | 4 | pyFluidSynth 5 | 6 | Python bindings for FluidSynth 7 | 8 | Copyright 2008--2024, Nathan Whitehead and others. 9 | 10 | 11 | Released under the LGPL 12 | 13 | This module contains python bindings for FluidSynth. FluidSynth is a 14 | software synthesizer for generating music. It works like a MIDI 15 | synthesizer. You load patches, set parameters, then send NOTEON and 16 | NOTEOFF events to play notes. Instruments are defined in SoundFonts, 17 | generally files with the extension SF2. FluidSynth can either be used 18 | to play audio itself, or you can call a function that returns chunks 19 | of audio data and output the data to the soundcard yourself. 20 | FluidSynth works on all major platforms, so pyFluidSynth should also. 21 | 22 | ================================================================================ 23 | """ 24 | 25 | import os 26 | from ctypes import ( 27 | CDLL, 28 | CFUNCTYPE, 29 | POINTER, 30 | Structure, 31 | byref, 32 | c_char, 33 | c_char_p, 34 | c_double, 35 | c_float, 36 | c_int, 37 | c_short, 38 | c_uint, 39 | c_void_p, 40 | create_string_buffer, 41 | ) 42 | from ctypes.util import find_library 43 | 44 | # DLL search method changed in Python 3.8 45 | # https://docs.python.org/3/library/os.html#os.add_dll_directory 46 | if hasattr(os, 'add_dll_directory'): # Python 3.8+ on Windows only 47 | os.add_dll_directory(os.getcwd()) 48 | os.add_dll_directory('C:\\tools\\fluidsynth\\bin') 49 | # Workaround bug in find_library, it doesn't recognize add_dll_directory 50 | os.environ['PATH'] += ';C:\\tools\\fluidsynth\\bin' 51 | 52 | # A function to find the FluidSynth library 53 | # (mostly needed for Windows distributions of libfluidsynth supplied with QSynth) 54 | def find_libfluidsynth(debug_print: bool = False) -> str: 55 | r""" 56 | macOS X64: 57 | * 'fluidsynth' was found at /usr/local/opt/fluid-synth/lib/libfluidsynth.dylib. 58 | macOS ARM64: 59 | * 'fluidsynth' was found at /opt/homebrew/opt/fluid-synth/lib/libfluidsynth.dylib. 60 | Ubuntu X86: 61 | * 'fluidsynth' was found at libfluidsynth.so.3. 62 | Windows X86: 63 | * 'libfluidsynth-3' was found at C:\tools\fluidsynth\bin\libfluidsynth-3.dll. --or-- 64 | * 'fluidsynth-3' was found as C:\tools\fluidsynth\bin\fluidsynth-3.dll. >= v2.4.5 65 | * https://github.com/FluidSynth/fluidsynth/issues/1543 66 | """ 67 | libs = "fluidsynth fluidsynth-3 libfluidsynth libfluidsynth-3 libfluidsynth-2 libfluidsynth-1" 68 | for lib_name in libs.split(): 69 | lib = find_library(lib_name) 70 | if lib: 71 | if debug_print: 72 | print(f"'{lib_name}' was found at {lib}.") 73 | return lib 74 | 75 | # On macOS on Apple silicon, non-Homebrew Python distributions fail to locate 76 | # homebrew-installed instances of FluidSynth. This workaround addresses this. 77 | if homebrew_prefix := os.getenv("HOMEBREW_PREFIX"): 78 | lib = os.path.join(homebrew_prefix, "lib", "libfluidsynth.dylib") 79 | if os.path.exists(lib): 80 | return lib 81 | 82 | raise ImportError("Couldn't find the FluidSynth library.") 83 | 84 | lib = find_libfluidsynth() 85 | 86 | # Dynamically link the FluidSynth library 87 | # Architecture (32-/64-bit) must match your Python version 88 | _fl = CDLL(lib) 89 | 90 | # Helper function for declaring function prototypes 91 | def cfunc(name, result, *args): 92 | """Build and apply a ctypes prototype complete with parameter flags""" 93 | if hasattr(_fl, name): 94 | atypes = [] 95 | aflags = [] 96 | for arg in args: 97 | atypes.append(arg[1]) 98 | aflags.append((arg[2], arg[0]) + arg[3:]) 99 | return CFUNCTYPE(result, *atypes)((name, _fl), tuple(aflags)) 100 | else: # Handle Fluidsynth 1.x, 2.x, etc. API differences 101 | return None 102 | 103 | # Bump this up when changing the interface for users 104 | api_version = '1.3.5' 105 | 106 | # Function prototypes for C versions of functions 107 | 108 | FLUID_OK = 0 109 | FLUID_FAILED = -1 110 | 111 | fluid_version = cfunc('fluid_version', c_void_p, 112 | ('major', POINTER(c_int), 1), 113 | ('minor', POINTER(c_int), 1), 114 | ('micro', POINTER(c_int), 1)) 115 | 116 | majver = c_int() 117 | fluid_version(majver, c_int(), c_int()) 118 | FLUIDSETTING_EXISTS = FLUID_OK if majver.value > 1 else 1 119 | 120 | # fluid settings 121 | new_fluid_settings = cfunc('new_fluid_settings', c_void_p) 122 | 123 | fluid_settings_setstr = cfunc('fluid_settings_setstr', c_int, 124 | ('settings', c_void_p, 1), 125 | ('name', c_char_p, 1), 126 | ('str', c_char_p, 1)) 127 | 128 | fluid_settings_setnum = cfunc('fluid_settings_setnum', c_int, 129 | ('settings', c_void_p, 1), 130 | ('name', c_char_p, 1), 131 | ('val', c_double, 1)) 132 | 133 | fluid_settings_setint = cfunc('fluid_settings_setint', c_int, 134 | ('settings', c_void_p, 1), 135 | ('name', c_char_p, 1), 136 | ('val', c_int, 1)) 137 | 138 | fluid_settings_copystr = cfunc('fluid_settings_copystr', c_int, 139 | ('settings', c_void_p, 1), 140 | ('name', c_char_p, 1), 141 | ('str', c_char_p, 1), 142 | ('len', c_int, 1)) 143 | 144 | fluid_settings_getnum = cfunc('fluid_settings_getnum', c_int, 145 | ('settings', c_void_p, 1), 146 | ('name', c_char_p, 1), 147 | ('val', POINTER(c_double), 1)) 148 | 149 | fluid_settings_getint = cfunc('fluid_settings_getint', c_int, 150 | ('settings', c_void_p, 1), 151 | ('name', c_char_p, 1), 152 | ('val', POINTER(c_int), 1)) 153 | 154 | delete_fluid_settings = cfunc('delete_fluid_settings', None, 155 | ('settings', c_void_p, 1)) 156 | 157 | fluid_synth_activate_key_tuning = cfunc('fluid_synth_activate_key_tuning', c_int, 158 | ('synth', c_void_p, 1), 159 | ('bank', c_int, 1), 160 | ('prog', c_int, 1), 161 | ('name', c_char_p, 1), 162 | ('pitch', POINTER(c_double), 1), 163 | ('apply', c_int, 1)) 164 | 165 | fluid_synth_activate_tuning = cfunc('fluid_synth_activate_tuning', c_int, 166 | ('synth', c_void_p, 1), 167 | ('chan', c_int, 1), 168 | ('bank', c_int, 1), 169 | ('prog', c_int, 1), 170 | ('apply', c_int, 1)) 171 | 172 | fluid_synth_deactivate_tuning = cfunc('fluid_synth_deactivate_tuning', c_int, 173 | ('synth', c_void_p, 1), 174 | ('chan', c_int, 1), 175 | ('apply', c_int, 1)) 176 | 177 | fluid_synth_tuning_dump = cfunc('fluid_synth_tuning_dump', c_int, 178 | ('synth', c_void_p, 1), 179 | ('bank', c_int, 1), 180 | ('prog', c_int, 1), 181 | ('name', c_char_p, 1), 182 | ('length', c_int, 1), 183 | ('pitch', POINTER(c_double), 1)) 184 | 185 | # fluid synth 186 | new_fluid_synth = cfunc('new_fluid_synth', c_void_p, 187 | ('settings', c_void_p, 1)) 188 | 189 | delete_fluid_synth = cfunc('delete_fluid_synth', None, 190 | ('synth', c_void_p, 1)) 191 | 192 | fluid_synth_sfload = cfunc('fluid_synth_sfload', c_int, 193 | ('synth', c_void_p, 1), 194 | ('filename', c_char_p, 1), 195 | ('update_midi_presets', c_int, 1)) 196 | 197 | fluid_synth_sfunload = cfunc('fluid_synth_sfunload', c_int, 198 | ('synth', c_void_p, 1), 199 | ('sfid', c_int, 1), 200 | ('update_midi_presets', c_int, 1)) 201 | 202 | fluid_synth_program_select = cfunc('fluid_synth_program_select', c_int, 203 | ('synth', c_void_p, 1), 204 | ('chan', c_int, 1), 205 | ('sfid', c_int, 1), 206 | ('bank', c_int, 1), 207 | ('preset', c_int, 1)) 208 | 209 | fluid_synth_noteon = cfunc('fluid_synth_noteon', c_int, 210 | ('synth', c_void_p, 1), 211 | ('chan', c_int, 1), 212 | ('key', c_int, 1), 213 | ('vel', c_int, 1)) 214 | 215 | fluid_synth_noteoff = cfunc('fluid_synth_noteoff', c_int, 216 | ('synth', c_void_p, 1), 217 | ('chan', c_int, 1), 218 | ('key', c_int, 1)) 219 | 220 | fluid_synth_pitch_bend = cfunc('fluid_synth_pitch_bend', c_int, 221 | ('synth', c_void_p, 1), 222 | ('chan', c_int, 1), 223 | ('val', c_int, 1)) 224 | 225 | fluid_synth_cc = cfunc('fluid_synth_cc', c_int, 226 | ('synth', c_void_p, 1), 227 | ('chan', c_int, 1), 228 | ('ctrl', c_int, 1), 229 | ('val', c_int, 1)) 230 | 231 | fluid_synth_get_cc = cfunc('fluid_synth_get_cc', c_int, 232 | ('synth', c_void_p, 1), 233 | ('chan', c_int, 1), 234 | ('num', c_int, 1), 235 | ('pval', POINTER(c_int), 1)) 236 | 237 | fluid_synth_program_change = cfunc('fluid_synth_program_change', c_int, 238 | ('synth', c_void_p, 1), 239 | ('chan', c_int, 1), 240 | ('prg', c_int, 1)) 241 | 242 | fluid_synth_unset_program = cfunc('fluid_synth_unset_program', c_int, 243 | ('synth', c_void_p, 1), 244 | ('chan', c_int, 1)) 245 | 246 | fluid_synth_get_program = cfunc('fluid_synth_get_program', c_int, 247 | ('synth', c_void_p, 1), 248 | ('chan', c_int, 1), 249 | ('sfont_id', POINTER(c_int), 1), 250 | ('bank_num', POINTER(c_int), 1), 251 | ('preset_num', POINTER(c_int), 1)) 252 | 253 | fluid_synth_bank_select = cfunc('fluid_synth_bank_select', c_int, 254 | ('synth', c_void_p, 1), 255 | ('chan', c_int, 1), 256 | ('bank', c_int, 1)) 257 | 258 | fluid_synth_sfont_select = cfunc('fluid_synth_sfont_select', c_int, 259 | ('synth', c_void_p, 1), 260 | ('chan', c_int, 1), 261 | ('sfid', c_int, 1)) 262 | 263 | fluid_synth_program_reset = cfunc('fluid_synth_program_reset', c_int, 264 | ('synth', c_void_p, 1)) 265 | 266 | fluid_synth_system_reset = cfunc('fluid_synth_system_reset', c_int, 267 | ('synth', c_void_p, 1)) 268 | 269 | fluid_synth_write_s16 = cfunc('fluid_synth_write_s16', c_void_p, 270 | ('synth', c_void_p, 1), 271 | ('len', c_int, 1), 272 | ('lbuf', c_void_p, 1), 273 | ('loff', c_int, 1), 274 | ('lincr', c_int, 1), 275 | ('rbuf', c_void_p, 1), 276 | ('roff', c_int, 1), 277 | ('rincr', c_int, 1)) 278 | 279 | fluid_synth_all_notes_off = cfunc('fluid_synth_all_notes_off', c_int, 280 | ('synth', c_void_p, 1), 281 | ('chan', c_int, 1)) 282 | 283 | fluid_synth_all_sounds_off = cfunc('fluid_synth_all_sounds_off', c_int, 284 | ('synth', c_void_p, 1), 285 | ('chan', c_int, 1)) 286 | 287 | 288 | class fluid_synth_channel_info_t(Structure): 289 | _fields_ = [ 290 | ('assigned', c_int), 291 | ('sfont_id', c_int), 292 | ('bank', c_int), 293 | ('program', c_int), 294 | ('name', c_char*32), 295 | ('reserved', c_char*32)] 296 | 297 | fluid_synth_get_channel_info = cfunc('fluid_synth_get_channel_info', c_int, 298 | ('synth', c_void_p, 1), 299 | ('chan', c_int, 1), 300 | ('info', POINTER(fluid_synth_channel_info_t), 1)) 301 | 302 | fluid_synth_set_reverb_full = cfunc('fluid_synth_set_reverb_full', c_int, 303 | ('synth', c_void_p, 1), 304 | ('set', c_int, 1), 305 | ('roomsize', c_double, 1), 306 | ('damping', c_double, 1), 307 | ('width', c_double, 1), 308 | ('level', c_double, 1)) 309 | 310 | fluid_synth_set_chorus_full = cfunc('fluid_synth_set_chorus_full', c_int, 311 | ('synth', c_void_p, 1), 312 | ('set', c_int, 1), 313 | ('nr', c_int, 1), 314 | ('level', c_double, 1), 315 | ('speed', c_double, 1), 316 | ('depth_ms', c_double, 1), 317 | ('type', c_int, 1)) 318 | 319 | fluid_synth_set_reverb = cfunc('fluid_synth_set_reverb', c_int, 320 | ('synth', c_void_p, 1), 321 | ('roomsize', c_double, 1), 322 | ('damping', c_double, 1), 323 | ('width', c_double, 1), 324 | ('level', c_double, 1)) 325 | 326 | fluid_synth_set_chorus = cfunc('fluid_synth_set_chorus', c_int, 327 | ('synth', c_void_p, 1), 328 | ('nr', c_int, 1), 329 | ('level', c_double, 1), 330 | ('speed', c_double, 1), 331 | ('depth_ms', c_double, 1), 332 | ('type', c_int, 1)) 333 | 334 | fluid_synth_set_reverb_roomsize = cfunc('fluid_synth_set_reverb_roomsize', c_int, 335 | ('synth', c_void_p, 1), 336 | ('roomsize', c_double, 1)) 337 | 338 | fluid_synth_set_reverb_damp = cfunc('fluid_synth_set_reverb_damp', c_int, 339 | ('synth', c_void_p, 1), 340 | ('damping', c_double, 1)) 341 | 342 | fluid_synth_set_reverb_level = cfunc('fluid_synth_set_reverb_level', c_int, 343 | ('synth', c_void_p, 1), 344 | ('level', c_double, 1)) 345 | 346 | fluid_synth_set_reverb_width = cfunc('fluid_synth_set_reverb_width', c_int, 347 | ('synth', c_void_p, 1), 348 | ('width', c_double, 1)) 349 | 350 | fluid_synth_set_chorus_nr = cfunc('fluid_synth_set_chorus_nr', c_int, 351 | ('synth', c_void_p, 1), 352 | ('nr', c_int, 1)) 353 | 354 | fluid_synth_set_chorus_level = cfunc('fluid_synth_set_chorus_level', c_int, 355 | ('synth', c_void_p, 1), 356 | ('level', c_double, 1)) 357 | 358 | fluid_synth_set_chorus_speed = cfunc('fluid_synth_set_chorus_speed', c_int, 359 | ('synth', c_void_p, 1), 360 | ('speed', c_double, 1)) 361 | 362 | fluid_synth_set_chorus_depth = cfunc('fluid_synth_set_chorus_depth', c_int, 363 | ('synth', c_void_p, 1), 364 | ('depth_ms', c_double, 1)) 365 | 366 | fluid_synth_set_chorus_type = cfunc('fluid_synth_set_chorus_type', c_int, 367 | ('synth', c_void_p, 1), 368 | ('type', c_int, 1)) 369 | 370 | fluid_synth_get_reverb_roomsize = cfunc('fluid_synth_get_reverb_roomsize', c_double, 371 | ('synth', c_void_p, 1)) 372 | 373 | fluid_synth_get_reverb_damp = cfunc('fluid_synth_get_reverb_damp', c_double, 374 | ('synth', c_void_p, 1)) 375 | 376 | fluid_synth_get_reverb_level = cfunc('fluid_synth_get_reverb_level', c_double, 377 | ('synth', c_void_p, 1)) 378 | 379 | fluid_synth_get_reverb_width = cfunc('fluid_synth_get_reverb_width', c_double, 380 | ('synth', c_void_p, 1)) 381 | 382 | 383 | fluid_synth_get_chorus_nr = cfunc('fluid_synth_get_chorus_nr', c_int, 384 | ('synth', c_void_p, 1)) 385 | 386 | fluid_synth_get_chorus_level = cfunc('fluid_synth_get_chorus_level', c_double, 387 | ('synth', c_void_p, 1)) 388 | 389 | fluid_synth_get_chorus_speed_Hz = cfunc('fluid_synth_get_chorus_speed_Hz', c_double, 390 | ('synth', c_void_p, 1)) 391 | 392 | fluid_synth_get_chorus_depth_ms = cfunc('fluid_synth_get_chorus_depth_ms', c_double, 393 | ('synth', c_void_p, 1)) 394 | 395 | fluid_synth_get_chorus_type = cfunc('fluid_synth_get_chorus_type', c_int, 396 | ('synth', c_void_p, 1)) 397 | 398 | fluid_synth_set_midi_router = cfunc('fluid_synth_set_midi_router', None, 399 | ('synth', c_void_p, 1), 400 | ('router', c_void_p, 1)) 401 | 402 | fluid_synth_handle_midi_event = cfunc('fluid_synth_handle_midi_event', c_int, 403 | ('data', c_void_p, 1), 404 | ('event', c_void_p, 1)) 405 | 406 | # fluid sequencer 407 | new_fluid_sequencer2 = cfunc('new_fluid_sequencer2', c_void_p, 408 | ('use_system_timer', c_int, 1)) 409 | 410 | fluid_sequencer_process = cfunc('fluid_sequencer_process', None, 411 | ('seq', c_void_p, 1), 412 | ('msec', c_uint, 1)) 413 | 414 | fluid_sequencer_register_fluidsynth = cfunc('fluid_sequencer_register_fluidsynth', c_short, 415 | ('seq', c_void_p, 1), 416 | ('synth', c_void_p, 1)) 417 | 418 | fluid_sequencer_register_client = cfunc('fluid_sequencer_register_client', c_short, 419 | ('seq', c_void_p, 1), 420 | ('name', c_char_p, 1), 421 | ('callback', CFUNCTYPE(None, c_uint, c_void_p, c_void_p, c_void_p), 1), 422 | ('data', c_void_p, 1)) 423 | 424 | fluid_sequencer_get_tick = cfunc('fluid_sequencer_get_tick', c_uint, 425 | ('seq', c_void_p, 1)) 426 | 427 | fluid_sequencer_set_time_scale = cfunc('fluid_sequencer_set_time_scale', None, 428 | ('seq', c_void_p, 1), 429 | ('scale', c_double, 1)) 430 | 431 | fluid_sequencer_get_time_scale = cfunc('fluid_sequencer_get_time_scale', c_double, 432 | ('seq', c_void_p, 1)) 433 | 434 | fluid_sequencer_send_at = cfunc('fluid_sequencer_send_at', c_int, 435 | ('seq', c_void_p, 1), 436 | ('evt', c_void_p, 1), 437 | ('time', c_uint, 1), 438 | ('absolute', c_int, 1)) 439 | 440 | 441 | delete_fluid_sequencer = cfunc('delete_fluid_sequencer', None, 442 | ('seq', c_void_p, 1)) 443 | 444 | # fluid event 445 | new_fluid_event = cfunc('new_fluid_event', c_void_p) 446 | 447 | fluid_event_set_source = cfunc('fluid_event_set_source', None, 448 | ('evt', c_void_p, 1), 449 | ('src', c_void_p, 1)) 450 | 451 | fluid_event_set_dest = cfunc('fluid_event_set_dest', None, 452 | ('evt', c_void_p, 1), 453 | ('dest', c_void_p, 1)) 454 | 455 | fluid_event_timer = cfunc('fluid_event_timer', None, 456 | ('evt', c_void_p, 1), 457 | ('data', c_void_p, 1)) 458 | 459 | fluid_event_note = cfunc('fluid_event_note', None, 460 | ('evt', c_void_p, 1), 461 | ('channel', c_int, 1), 462 | ('key', c_short, 1), 463 | ('vel', c_short, 1), 464 | ('duration', c_uint, 1)) 465 | 466 | fluid_event_noteon = cfunc('fluid_event_noteon', None, 467 | ('evt', c_void_p, 1), 468 | ('channel', c_int, 1), 469 | ('key', c_short, 1), 470 | ('vel', c_short, 1)) 471 | 472 | fluid_event_noteoff = cfunc('fluid_event_noteoff', None, 473 | ('evt', c_void_p, 1), 474 | ('channel', c_int, 1), 475 | ('key', c_short, 1)) 476 | 477 | delete_fluid_event = cfunc('delete_fluid_event', None, 478 | ('evt', c_void_p, 1)) 479 | 480 | fluid_midi_event_get_channel = cfunc('fluid_midi_event_get_channel', c_int, 481 | ('evt', c_void_p, 1)) 482 | 483 | fluid_midi_event_get_control = cfunc('fluid_midi_event_get_control', c_int, 484 | ('evt', c_void_p, 1)) 485 | 486 | fluid_midi_event_get_program = cfunc('fluid_midi_event_get_program', c_int, 487 | ('evt', c_void_p, 1)) 488 | 489 | fluid_midi_event_get_key = cfunc('fluid_midi_event_get_key', c_int, 490 | ('evt', c_void_p, 1)) 491 | 492 | fluid_midi_event_get_type = cfunc('fluid_midi_event_get_type', c_int, 493 | ('evt', c_void_p, 1)) 494 | 495 | fluid_midi_event_get_value = cfunc('fluid_midi_event_get_value', c_int, 496 | ('evt', c_void_p, 1)) 497 | 498 | fluid_midi_event_get_velocity = cfunc('fluid_midi_event_get_velocity', c_int, 499 | ('evt', c_void_p, 1)) 500 | 501 | # fluid modulator 502 | new_fluid_mod = cfunc("new_fluid_mod", c_void_p) 503 | 504 | delete_fluid_mod = cfunc("delete_fluid_mod", c_void_p, ("mod", c_void_p, 1)) 505 | 506 | fluid_mod_clone = cfunc( 507 | "fluid_mod_clone", c_void_p, ("mod", c_void_p, 1), ("src", c_void_p, 1), 508 | ) 509 | 510 | fluid_mod_get_amount = cfunc("fluid_mod_get_amount", c_void_p, ("mod", c_void_p, 1)) 511 | 512 | fluid_mod_get_dest = cfunc("fluid_mod_get_dest", c_void_p, ("mod", c_void_p, 1)) 513 | 514 | fluid_mod_get_flags1 = cfunc("fluid_mod_get_flags1", c_void_p, ("mod", c_void_p, 1)) 515 | 516 | fluid_mod_get_flags2 = cfunc("fluid_mod_get_flags2", c_void_p, ("mod", c_void_p, 1)) 517 | 518 | fluid_mod_get_source1 = cfunc("fluid_mod_get_source1", c_void_p, ("mod", c_void_p, 1)) 519 | 520 | fluid_mod_get_source2 = cfunc("fluid_mod_get_source2", c_void_p, ("mod", c_void_p, 1)) 521 | 522 | fluid_mod_get_transform = cfunc( 523 | "fluid_mod_get_transform", c_void_p, ("mod", c_void_p, 1), 524 | ) 525 | 526 | fluid_mod_has_dest = cfunc( 527 | "fluid_mod_has_dest", c_void_p, ("mod", c_void_p, 1), ("gen", c_uint, 1), 528 | ) 529 | 530 | fluid_mod_has_source = cfunc( 531 | "fluid_mod_has_dest", 532 | c_void_p, 533 | ("mod", c_void_p, 1), 534 | ("cc", c_uint, 1), 535 | ("ctrl", c_uint, 1), 536 | ) 537 | 538 | fluid_mod_set_amount = cfunc( 539 | "fluid_mod_set_amount", c_void_p, ("mod", c_void_p, 1), ("amount", c_double, 1), 540 | ) 541 | 542 | fluid_mod_set_dest = cfunc( 543 | "fluid_mod_set_dest", c_void_p, ("mod", c_void_p, 1), ("dst", c_int, 1), 544 | ) 545 | 546 | fluid_mod_set_source1 = cfunc( 547 | "fluid_mod_set_source1", 548 | c_void_p, 549 | ("mod", c_void_p, 1), 550 | ("src", c_int, 1), 551 | ("flags", c_int, 1), 552 | ) 553 | 554 | fluid_mod_set_source2 = cfunc( 555 | "fluid_mod_set_source2", 556 | c_void_p, 557 | ("mod", c_void_p, 1), 558 | ("src", c_int, 1), 559 | ("flags", c_int, 1), 560 | ) 561 | 562 | fluid_mod_set_transform = cfunc( 563 | "fluid_mod_set_transform", c_void_p, ("mod", c_void_p, 1), ("type", c_int, 1), 564 | ) 565 | 566 | fluid_mod_sizeof = cfunc("fluid_mod_sizeof", c_void_p) 567 | 568 | fluid_mod_test_identity = cfunc( 569 | "fluid_mod_test_identity", c_void_p, ("mod1", c_void_p, 1), ("mod2", c_void_p, 1), 570 | ) 571 | 572 | # fluid_player_status returned by fluid_player_get_status() 573 | FLUID_PLAYER_READY = 0 574 | FLUID_PLAYER_PLAYING = 1 575 | FLUID_PLAYER_STOPPING = 2 576 | FLUID_PLAYER_DONE = 3 577 | 578 | # tempo_type used by fluid_player_set_tempo() 579 | FLUID_PLAYER_TEMPO_INTERNAL = 0 580 | FLUID_PLAYER_TEMPO_EXTERNAL_BPM = 1 581 | FLUID_PLAYER_TEMPO_EXTERNAL_MIDI = 2 582 | 583 | new_fluid_player = cfunc('new_fluid_player', c_void_p, 584 | ('synth', c_void_p, 1)) 585 | 586 | delete_fluid_player = cfunc('delete_fluid_player', None, 587 | ('player', c_void_p, 1)) 588 | 589 | fluid_player_add = cfunc('fluid_player_add', c_int, 590 | ('player', c_void_p, 1), 591 | ('filename', c_char_p, 1)) 592 | 593 | 594 | fluid_player_get_status = cfunc('fluid_player_get_status', c_int, 595 | ('player', c_void_p, 1)) 596 | fluid_player_join = cfunc('fluid_player_join', c_int, 597 | ('player', c_void_p, 1)) 598 | 599 | fluid_player_play = cfunc('fluid_player_play', c_int, 600 | ('player', c_void_p, 1)) 601 | 602 | fluid_player_set_playback_callback = cfunc('fluid_player_set_playback_callback', c_int, 603 | ('player', c_void_p, 1), 604 | ('handler', CFUNCTYPE(c_int, c_void_p, c_void_p), 1), 605 | ('event_handler_data', c_void_p, 1)) 606 | 607 | fluid_player_set_tempo = cfunc('fluid_player_set_tempo', c_int, 608 | ('player', c_void_p, 1), 609 | ('tempo_type', c_int, 1), 610 | ('tempo', c_double, 1)) 611 | 612 | fluid_player_seek = cfunc('fluid_player_seek', c_int, 613 | ('player', c_void_p, 1), 614 | ('ticks', c_int, 1)) 615 | 616 | fluid_player_stop = cfunc('fluid_player_stop', c_int, 617 | ('player', c_void_p, 1)) 618 | 619 | # fluid audio driver 620 | new_fluid_audio_driver = cfunc('new_fluid_audio_driver', c_void_p, 621 | ('settings', c_void_p, 1), 622 | ('synth', c_void_p, 1)) 623 | 624 | delete_fluid_audio_driver = cfunc('delete_fluid_audio_driver', None, 625 | ('driver', c_void_p, 1)) 626 | 627 | # fluid midi driver 628 | new_fluid_midi_driver = cfunc('new_fluid_midi_driver', c_void_p, 629 | ('settings', c_void_p, 1), 630 | ('handler', CFUNCTYPE(c_int, c_void_p, c_void_p), 1), 631 | ('event_handler_data', c_void_p, 1)) 632 | 633 | delete_fluid_midi_driver = cfunc('delete_fluid_midi_driver', None, 634 | ('driver', c_void_p, 1)) 635 | 636 | 637 | # fluid midi router rule 638 | class fluid_midi_router_t(Structure): 639 | _fields_ = [ 640 | ('synth', c_void_p), 641 | ('rules_mutex', c_void_p), 642 | ('rules', c_void_p*6), 643 | ('free_rules', c_void_p), 644 | ('event_handler', c_void_p), 645 | ('event_handler_data', c_void_p), 646 | ('nr_midi_channels', c_int), 647 | ('cmd_rule', c_void_p), 648 | ('cmd_rule_type', POINTER(c_int))] 649 | 650 | delete_fluid_midi_router_rule = cfunc('delete_fluid_midi_router_rule', c_int, 651 | ('rule', c_void_p, 1)) 652 | 653 | new_fluid_midi_router_rule = cfunc('new_fluid_midi_router_rule', c_void_p) 654 | 655 | fluid_midi_router_rule_set_chan = cfunc('fluid_midi_router_rule_set_chan', None, 656 | ('rule', c_void_p, 1), 657 | ('min', c_int, 1), 658 | ('max', c_int, 1), 659 | ('mul', c_float, 1), 660 | ('add', c_int, 1)) 661 | 662 | fluid_midi_router_rule_set_param1 = cfunc('fluid_midi_router_rule_set_param1', None, 663 | ('rule', c_void_p, 1), 664 | ('min', c_int, 1), 665 | ('max', c_int, 1), 666 | ('mul', c_float, 1), 667 | ('add', c_int, 1)) 668 | 669 | fluid_midi_router_rule_set_param2 = cfunc('fluid_midi_router_rule_set_param2', None, 670 | ('rule', c_void_p, 1), 671 | ('min', c_int, 1), 672 | ('max', c_int, 1), 673 | ('mul', c_float, 1), 674 | ('add', c_int, 1)) 675 | 676 | # fluid midi router 677 | new_fluid_midi_router = cfunc('new_fluid_midi_router', POINTER(fluid_midi_router_t), 678 | ('settings', c_void_p, 1), 679 | ('handler', CFUNCTYPE(c_int, c_void_p, c_void_p), 1), 680 | ('event_handler_data', c_void_p, 1)) 681 | 682 | fluid_midi_router_handle_midi_event = cfunc('fluid_midi_router_handle_midi_event', c_int, 683 | ('data', c_void_p, 1), 684 | ('event', c_void_p, 1)) 685 | 686 | fluid_midi_router_clear_rules = cfunc('fluid_midi_router_clear_rules', c_int, 687 | ('router', POINTER(fluid_midi_router_t), 1)) 688 | 689 | fluid_midi_router_set_default_rules = cfunc('fluid_midi_router_set_default_rules', c_int, 690 | ('router', POINTER(fluid_midi_router_t), 1)) 691 | 692 | fluid_midi_router_add_rule = cfunc('fluid_midi_router_add_rule', c_int, 693 | ('router', POINTER(fluid_midi_router_t), 1), 694 | ('rule', c_void_p, 1), 695 | ('type', c_int, 1)) 696 | 697 | # fluid file renderer 698 | new_fluid_file_renderer = cfunc('new_fluid_file_renderer', c_void_p, 699 | ('synth', c_void_p, 1)) 700 | 701 | delete_fluid_file_renderer = cfunc('delete_fluid_file_renderer', None, 702 | ('renderer', c_void_p, 1)) 703 | 704 | fluid_file_renderer_process_block = cfunc('fluid_file_renderer_process_block', c_int, 705 | ('render', c_void_p, 1)) 706 | 707 | # fluidsynth 2.x 708 | new_fluid_cmd_handler=cfunc('new_fluid_cmd_handler', c_void_p, 709 | ('synth', c_void_p, 1), 710 | ('router', c_void_p, 1)) 711 | 712 | fluid_synth_get_sfont_by_id = cfunc('fluid_synth_get_sfont_by_id', c_void_p, 713 | ('synth', c_void_p, 1), 714 | ('id', c_int, 1)) 715 | 716 | fluid_sfont_get_preset = cfunc('fluid_sfont_get_preset', c_void_p, 717 | ('sfont', c_void_p, 1), 718 | ('banknum', c_int, 1), 719 | ('prenum', c_int, 1)) 720 | 721 | fluid_preset_get_name = cfunc('fluid_preset_get_name', c_char_p, 722 | ('preset', c_void_p, 1)) 723 | 724 | fluid_synth_set_reverb = cfunc('fluid_synth_set_reverb', c_int, 725 | ('synth', c_void_p, 1), 726 | ('roomsize', c_double, 1), 727 | ('damping', c_double, 1), 728 | ('width', c_double, 1), 729 | ('level', c_double, 1)) 730 | 731 | fluid_synth_set_chorus = cfunc('fluid_synth_set_chorus', c_int, 732 | ('synth', c_void_p, 1), 733 | ('nr', c_int, 1), 734 | ('level', c_double, 1), 735 | ('speed', c_double, 1), 736 | ('depth_ms', c_double, 1), 737 | ('type', c_int, 1)) 738 | 739 | fluid_synth_get_chorus_speed = cfunc('fluid_synth_get_chorus_speed', c_double, 740 | ('synth', c_void_p, 1)) 741 | 742 | fluid_synth_get_chorus_depth = cfunc('fluid_synth_get_chorus_depth', c_double, 743 | ('synth', c_void_p, 1)) 744 | 745 | 746 | def fluid_synth_write_s16_stereo(synth, len): 747 | """Return generated samples in stereo 16-bit format 748 | 749 | Return value is a Numpy array of samples. 750 | 751 | """ 752 | import numpy 753 | buf = create_string_buffer(len * 4) 754 | fluid_synth_write_s16(synth, len, buf, 0, 2, buf, 1, 2) 755 | return numpy.frombuffer(buf[:], dtype=numpy.int16) 756 | 757 | 758 | # Object-oriented interface, simplifies access to functions 759 | 760 | class Synth: 761 | """Synth represents a FluidSynth synthesizer""" 762 | def __init__(self, gain=0.2, samplerate=44100, channels=256, **kwargs): 763 | """Create new synthesizer object to control sound generation 764 | 765 | Optional keyword arguments: 766 | gain : scale factor for audio output, default is 0.2 767 | lower values are quieter, allow more simultaneous notes 768 | samplerate : output samplerate in Hz, default is 44100 Hz 769 | added capability for passing arbitrary fluid settings using args 770 | """ 771 | self.settings = new_fluid_settings() 772 | self.setting('synth.gain', gain) 773 | self.setting('synth.sample-rate', float(samplerate)) 774 | self.setting('synth.midi-channels', channels) 775 | for opt,val in kwargs.items(): 776 | self.setting(opt, val) 777 | self.synth = new_fluid_synth(self.settings) 778 | self.audio_driver = None 779 | self.midi_driver = None 780 | self.router = None 781 | self.custom_router_callback = None 782 | def setting(self, opt, val): 783 | """change an arbitrary synth setting, type-smart""" 784 | if isinstance(val, (str, bytes)): 785 | fluid_settings_setstr(self.settings, opt.encode(), val.encode()) 786 | elif isinstance(val, int): 787 | fluid_settings_setint(self.settings, opt.encode(), val) 788 | elif isinstance(val, float): 789 | fluid_settings_setnum(self.settings, opt.encode(), c_double(val)) 790 | def get_setting(self, opt): 791 | """get current value of an arbitrary synth setting""" 792 | val = c_int() 793 | if fluid_settings_getint(self.settings, opt.encode(), byref(val)) == FLUIDSETTING_EXISTS: 794 | return val.value 795 | strval = create_string_buffer(32) 796 | if fluid_settings_copystr(self.settings, opt.encode(), strval, 32) == FLUIDSETTING_EXISTS: 797 | return strval.value.decode() 798 | num = c_double() 799 | if fluid_settings_getnum(self.settings, opt.encode(), byref(num)) == FLUIDSETTING_EXISTS: 800 | return round(num.value, 6) 801 | return None 802 | 803 | def start(self, driver=None, device=None, midi_driver=None, midi_router=None): 804 | """Start audio output driver in separate background thread 805 | 806 | Call this function any time after creating the Synth object. 807 | If you don't call this function, use get_samples() to generate 808 | samples. 809 | 810 | Optional keyword argument: 811 | driver : which audio driver to use for output 812 | device : the device to use for audio output 813 | midi_driver : the midi driver to use for communicating with midi devices 814 | see http://www.fluidsynth.org/api/fluidsettings.xml for allowed values and defaults by platform 815 | """ 816 | driver = driver or self.get_setting('audio.driver') 817 | device = device or self.get_setting(f'audio.{driver}.device') 818 | midi_driver = midi_driver or self.get_setting('midi.driver') 819 | 820 | self.setting('audio.driver', driver) 821 | self.setting(f'audio.{driver}.device', device) 822 | self.audio_driver = new_fluid_audio_driver(self.settings, self.synth) 823 | self.setting('midi.driver', midi_driver) 824 | self.router = new_fluid_midi_router(self.settings, fluid_synth_handle_midi_event, self.synth) 825 | if new_fluid_cmd_handler: 826 | new_fluid_cmd_handler(self.synth, self.router) 827 | else: 828 | fluid_synth_set_midi_router(self.synth, self.router) 829 | if midi_router is None: ## Use fluidsynth to create a MIDI event handler 830 | self.midi_driver = new_fluid_midi_driver(self.settings, fluid_midi_router_handle_midi_event, self.router) 831 | self.custom_router_callback = None 832 | else: ## Supply an external MIDI event handler 833 | self.custom_router_callback = CFUNCTYPE(c_int, c_void_p, c_void_p)(midi_router) 834 | self.midi_driver = new_fluid_midi_driver(self.settings, self.custom_router_callback, self.router) 835 | return FLUID_OK 836 | 837 | def delete(self): 838 | if self.audio_driver: 839 | delete_fluid_audio_driver(self.audio_driver) 840 | if self.midi_driver: 841 | delete_fluid_midi_driver(self.midi_driver) 842 | delete_fluid_synth(self.synth) 843 | delete_fluid_settings(self.settings) 844 | def sfload(self, filename, update_midi_preset=0): 845 | """Load SoundFont and return its ID""" 846 | return fluid_synth_sfload(self.synth, filename.encode(), update_midi_preset) 847 | def sfunload(self, sfid, update_midi_preset=0): 848 | """Unload a SoundFont and free memory it used""" 849 | return fluid_synth_sfunload(self.synth, sfid, update_midi_preset) 850 | def program_select(self, chan, sfid, bank, preset): 851 | """Select a program""" 852 | return fluid_synth_program_select(self.synth, chan, sfid, bank, preset) 853 | def program_unset(self, chan): 854 | """Set the preset of a MIDI channel to an unassigned state""" 855 | return fluid_synth_unset_program(self.synth, chan) 856 | def channel_info(self, chan): 857 | """get soundfont, bank, prog, preset name of channel""" 858 | if fluid_synth_get_channel_info is not None: 859 | info=fluid_synth_channel_info_t() 860 | fluid_synth_get_channel_info(self.synth, chan, byref(info)) 861 | return (info.sfont_id, info.bank, info.program, info.name) 862 | else: 863 | (sfontid, banknum, presetnum) = self.program_info(chan) 864 | presetname = self.sfpreset_name(sfontid, banknum, presetnum) 865 | return (sfontid, banknum, presetnum, presetname) 866 | def program_info(self, chan): 867 | """get active soundfont, bank, prog on a channel""" 868 | if fluid_synth_get_program is not None: 869 | sfontid=c_int() 870 | banknum=c_int() 871 | presetnum=c_int() 872 | fluid_synth_get_program(self.synth, chan, byref(sfontid), byref(banknum), byref(presetnum)) 873 | return (sfontid.value, banknum.value, presetnum.value) 874 | else: 875 | (sfontid, banknum, prognum, presetname) = self.channel_info(chan) 876 | return (sfontid, banknum, prognum) 877 | def sfpreset_name(self, sfid, bank, prenum): 878 | """Return name of a soundfont preset""" 879 | if fluid_synth_get_sfont_by_id is not None: 880 | sfont=fluid_synth_get_sfont_by_id(self.synth, sfid) 881 | preset=fluid_sfont_get_preset(sfont, bank, prenum) 882 | if not preset: 883 | return None 884 | return fluid_preset_get_name(preset).decode('ascii') 885 | else: 886 | return None 887 | def router_clear(self): 888 | if self.router is not None: 889 | fluid_midi_router_clear_rules(self.router) 890 | def router_default(self): 891 | if self.router is not None: 892 | fluid_midi_router_set_default_rules(self.router) 893 | def router_begin(self, type): 894 | """types are [note|cc|prog|pbend|cpress|kpress]""" 895 | if self.router is not None: 896 | if type=='note': 897 | self.router.cmd_rule_type=0 898 | elif type=='cc': 899 | self.router.cmd_rule_type=1 900 | elif type=='prog': 901 | self.router.cmd_rule_type=2 902 | elif type=='pbend': 903 | self.router.cmd_rule_type=3 904 | elif type=='cpress': 905 | self.router.cmd_rule_type=4 906 | elif type=='kpress': 907 | self.router.cmd_rule_type=5 908 | if 'self.router.cmd_rule' in globals(): 909 | delete_fluid_midi_router_rule(self.router.cmd_rule) 910 | self.router.cmd_rule = new_fluid_midi_router_rule() 911 | def router_end(self): 912 | if self.router is not None: 913 | if self.router.cmd_rule is None: 914 | return 915 | if fluid_midi_router_add_rule(self.router, self.router.cmd_rule, self.router.cmd_rule_type)<0: 916 | delete_fluid_midi_router_rule(self.router.cmd_rule) 917 | self.router.cmd_rule=None 918 | def router_chan(self, min, max, mul, add): 919 | if self.router is not None: 920 | fluid_midi_router_rule_set_chan(self.router.cmd_rule, min, max, mul, add) 921 | def router_par1(self, min, max, mul, add): 922 | if self.router is not None: 923 | fluid_midi_router_rule_set_param1(self.router.cmd_rule, min, max, mul, add) 924 | def router_par2(self, min, max, mul, add): 925 | if self.router is not None: 926 | fluid_midi_router_rule_set_param2(self.router.cmd_rule, min, max, mul, add) 927 | def set_reverb(self, roomsize=-1.0, damping=-1.0, width=-1.0, level=-1.0): 928 | """ 929 | roomsize Reverb room size value (0.0-1.0) 930 | damping Reverb damping value (0.0-1.0) 931 | width Reverb width value (0.0-100.0) 932 | level Reverb level value (0.0-1.0) 933 | """ 934 | if fluid_synth_set_reverb is not None: 935 | return fluid_synth_set_reverb(self.synth, roomsize, damping, width, level) 936 | else: 937 | flags=0 938 | if roomsize>=0: 939 | flags+=0b0001 940 | if damping>=0: 941 | flags+=0b0010 942 | if width>=0: 943 | flags+=0b0100 944 | if level>=0: 945 | flags+=0b1000 946 | return fluid_synth_set_reverb_full(self.synth, flags, roomsize, damping, width, level) 947 | def set_chorus(self, nr=-1, level=-1.0, speed=-1.0, depth=-1.0, type=-1): 948 | """ 949 | nr Chorus voice count (0-99, CPU time consumption proportional to this value) 950 | level Chorus level (0.0-10.0) 951 | speed Chorus speed in Hz (0.29-5.0) 952 | depth_ms Chorus depth (max value depends on synth sample rate, 0.0-21.0 is safe for sample rate values up to 96KHz) 953 | type Chorus waveform type (0=sine, 1=triangle) 954 | """ 955 | if fluid_synth_set_chorus is not None: 956 | return fluid_synth_set_chorus(self.synth, nr, level, speed, depth, type) 957 | else: 958 | set=0 959 | if nr>=0: 960 | set+=0b00001 961 | if level>=0: 962 | set+=0b00010 963 | if speed>=0: 964 | set+=0b00100 965 | if depth>=0: 966 | set+=0b01000 967 | if type>=0: 968 | set+=0b10000 969 | return fluid_synth_set_chorus_full(self.synth, set, nr, level, speed, depth, type) 970 | def set_reverb_roomsize(self, roomsize): 971 | if fluid_synth_set_reverb_roomsize is not None: 972 | return fluid_synth_set_reverb_roomsize(self.synth, roomsize) 973 | else: 974 | return self.set_reverb(roomsize=roomsize) 975 | def set_reverb_damp(self, damping): 976 | if fluid_synth_set_reverb_damp is not None: 977 | return fluid_synth_set_reverb_damp(self.synth, damping) 978 | else: 979 | return self.set_reverb(damping=damping) 980 | def set_reverb_level(self, level): 981 | if fluid_synth_set_reverb_level is not None: 982 | return fluid_synth_set_reverb_level(self.synth, level) 983 | else: 984 | return self.set_reverb(level=level) 985 | def set_reverb_width(self, width): 986 | if fluid_synth_set_reverb_width is not None: 987 | return fluid_synth_set_reverb_width(self.synth, width) 988 | else: 989 | return self.set_reverb(width=width) 990 | def set_chorus_nr(self, nr): 991 | if fluid_synth_set_chorus_nr is not None: 992 | return fluid_synth_set_chorus_nr(self.synth, nr) 993 | else: 994 | return self.set_chorus(nr=nr) 995 | def set_chorus_level(self, level): 996 | if fluid_synth_set_chorus_level is not None: 997 | return fluid_synth_set_chorus_level(self.synth, level) 998 | else: 999 | return self.set_chorus(level=level) 1000 | def set_chorus_speed(self, speed): 1001 | if fluid_synth_set_chorus_speed is not None: 1002 | return fluid_synth_set_chorus_speed(self.synth, speed) 1003 | else: 1004 | return self.set_chorus(speed=speed) 1005 | def set_chorus_depth(self, depth_ms): 1006 | if fluid_synth_set_chorus_depth is not None: 1007 | return fluid_synth_set_chorus_depth(self.synth, depth_ms) 1008 | else: 1009 | return self.set_chorus(depth=depth_ms) 1010 | def set_chorus_type(self, type): 1011 | if fluid_synth_set_chorus_type is not None: 1012 | return fluid_synth_set_chorus_type(self.synth, type) 1013 | else: 1014 | return self.set_chorus(type=type) 1015 | def get_reverb_roomsize(self): 1016 | return fluid_synth_get_reverb_roomsize(self.synth) 1017 | def get_reverb_damp(self): 1018 | return fluid_synth_get_reverb_damp(self.synth) 1019 | def get_reverb_level(self): 1020 | return fluid_synth_get_reverb_level(self.synth) 1021 | def get_reverb_width(self): 1022 | return fluid_synth_get_reverb_width(self.synth) 1023 | def get_chorus_nr(self): 1024 | return fluid_synth_get_chorus_nr(self.synth) 1025 | def get_chorus_level(self): 1026 | return fluid_synth_get_reverb_level(self.synth) 1027 | def get_chorus_speed(self): 1028 | if fluid_synth_get_chorus_speed is not None: 1029 | return fluid_synth_get_chorus_speed(self.synth) 1030 | else: 1031 | return fluid_synth_get_chorus_speed_Hz(self.synth) 1032 | def get_chorus_depth(self): 1033 | if fluid_synth_get_chorus_depth is not None: 1034 | return fluid_synth_get_chorus_depth(self.synth) 1035 | else: 1036 | return fluid_synth_get_chorus_depth_ms(self.synth) 1037 | def get_chorus_type(self): 1038 | return fluid_synth_get_chorus_type(self.synth) 1039 | def noteon(self, chan, key, vel): 1040 | """Play a note""" 1041 | if key < 0 or key > 127: 1042 | return False 1043 | if chan < 0: 1044 | return False 1045 | if vel < 0 or vel > 127: 1046 | return False 1047 | return fluid_synth_noteon(self.synth, chan, key, vel) 1048 | def noteoff(self, chan, key): 1049 | """Stop a note""" 1050 | if key < 0 or key > 127: 1051 | return False 1052 | if chan < 0: 1053 | return False 1054 | return fluid_synth_noteoff(self.synth, chan, key) 1055 | def pitch_bend(self, chan, val): 1056 | """Adjust pitch of a playing channel by small amounts 1057 | 1058 | A pitch bend value of 0 is no pitch change from default. 1059 | A value of -2048 is 1 semitone down. 1060 | A value of 2048 is 1 semitone up. 1061 | Maximum values are -8192 to +8191 (transposing by 4 semitones). 1062 | 1063 | """ 1064 | return fluid_synth_pitch_bend(self.synth, chan, max(0, min(val + 8192, 16383))) 1065 | def cc(self, chan, ctrl, val): 1066 | """Send control change value 1067 | 1068 | The controls that are recognized are dependent on the 1069 | SoundFont. Values are always 0 to 127. Typical controls 1070 | include: 1071 | 1 : vibrato 1072 | 7 : volume 1073 | 10 : pan (left to right) 1074 | 11 : expression (soft to loud) 1075 | 64 : sustain 1076 | 91 : reverb 1077 | 93 : chorus 1078 | """ 1079 | return fluid_synth_cc(self.synth, chan, ctrl, val) 1080 | def get_cc(self, chan, num): 1081 | i=c_int() 1082 | fluid_synth_get_cc(self.synth, chan, num, byref(i)) 1083 | return i.value 1084 | def program_change(self, chan, prg): 1085 | """Change the program""" 1086 | return fluid_synth_program_change(self.synth, chan, prg) 1087 | def bank_select(self, chan, bank): 1088 | """Choose a bank""" 1089 | return fluid_synth_bank_select(self.synth, chan, bank) 1090 | def all_notes_off(self, chan): 1091 | """Turn off all notes on a channel (release all keys)""" 1092 | return fluid_synth_all_notes_off(self.synth, chan) 1093 | def all_sounds_off(self, chan): 1094 | """Turn off all sounds on a channel (equivalent to mute)""" 1095 | return fluid_synth_all_sounds_off(self.synth, chan) 1096 | def sfont_select(self, chan, sfid): 1097 | """Choose a SoundFont""" 1098 | return fluid_synth_sfont_select(self.synth, chan, sfid) 1099 | def program_reset(self): 1100 | """Reset the programs on all channels""" 1101 | return fluid_synth_program_reset(self.synth) 1102 | def system_reset(self): 1103 | """Stop all notes and reset all programs""" 1104 | return fluid_synth_system_reset(self.synth) 1105 | def get_samples(self, len=1024): 1106 | """Generate audio samples 1107 | 1108 | The return value will be a NumPy array containing the given 1109 | length of audio samples. If the synth is set to stereo output 1110 | (the default) the array will be size 2 * len. 1111 | 1112 | """ 1113 | return fluid_synth_write_s16_stereo(self.synth, len) 1114 | def tuning_dump(self, bank, prog): 1115 | """Get tuning information for given bank and preset 1116 | 1117 | Return value is an array of length 128 with tuning factors for each MIDI note. 1118 | Tuning factor of 0.0 in each position is standard tuning. Measured in cents. 1119 | """ 1120 | pitch = (c_double * 128)() 1121 | fluid_synth_tuning_dump(self.synth, bank, prog, None, 0, pitch) 1122 | return pitch[:] 1123 | 1124 | def midi_event_get_type(self, event): 1125 | return fluid_midi_event_get_type(event) 1126 | def midi_event_get_velocity(self, event): 1127 | return fluid_midi_event_get_velocity(event) 1128 | def midi_event_get_key(self, event): 1129 | return fluid_midi_event_get_key(event) 1130 | def midi_event_get_channel(self, event): 1131 | return fluid_midi_event_get_channel(event) 1132 | def midi_event_get_control(self, event): 1133 | return fluid_midi_event_get_control(event) 1134 | def midi_event_get_program(self, event): 1135 | return fluid_midi_event_get_program(event) 1136 | def midi_event_get_value(self, event): 1137 | return fluid_midi_event_get_value(event) 1138 | 1139 | def play_midi_file(self, filename): 1140 | self.player = new_fluid_player(self.synth) 1141 | if self.player is None: 1142 | return FLUID_FAILED 1143 | if self.custom_router_callback is not None: 1144 | fluid_player_set_playback_callback(self.player, self.custom_router_callback, self.synth) 1145 | status = fluid_player_add(self.player, filename.encode()) 1146 | if status == FLUID_FAILED: 1147 | return status 1148 | status = fluid_player_play(self.player) 1149 | return status 1150 | 1151 | def play_midi_stop(self): 1152 | status = fluid_player_stop(self.player) 1153 | if status == FLUID_FAILED: 1154 | return status 1155 | status = fluid_player_seek(self.player, 0) 1156 | delete_fluid_player(self.player) 1157 | return status 1158 | 1159 | def player_set_tempo(self, tempo_type, tempo): 1160 | return fluid_player_set_tempo(self.player, tempo_type, tempo) 1161 | 1162 | def midi2audio(self, midifile, audiofile = "output.wav"): 1163 | """Convert a midi file to an audio file""" 1164 | self.setting("audio.file.name", audiofile) 1165 | player = new_fluid_player(self.synth) 1166 | fluid_player_add(player, midifile.encode()) 1167 | fluid_player_play(player) 1168 | renderer = new_fluid_file_renderer(self.synth) 1169 | while fluid_player_get_status(player) == FLUID_PLAYER_PLAYING: 1170 | if fluid_file_renderer_process_block(renderer) != FLUID_OK: 1171 | break 1172 | delete_fluid_file_renderer(renderer) 1173 | delete_fluid_player(player) 1174 | 1175 | # flag values 1176 | FLUID_MOD_POSITIVE = 0 1177 | FLUID_MOD_NEGATIVE = 1 1178 | FLUID_MOD_UNIPOLAR = 0 1179 | FLUID_MOD_BIPOLAR = 2 1180 | FLUID_MOD_LINEAR = 0 1181 | FLUID_MOD_CONCAVE = 4 1182 | FLUID_MOD_CONVEX = 8 1183 | FLUID_MOD_SWITCH = 12 1184 | FLUID_MOD_GC = 0 1185 | FLUID_MOD_CC = 16 1186 | FLUID_MOD_SIN = 0x80 1187 | 1188 | # src values 1189 | FLUID_MOD_NONE = 0 1190 | FLUID_MOD_VELOCITY = 2 1191 | FLUID_MOD_KEY = 3 1192 | FLUID_MOD_KEYPRESSURE = 10 1193 | FLUID_MOD_CHANNELPRESSURE = 13 1194 | FLUID_MOD_PITCHWHEEL = 14 1195 | FLUID_MOD_PITCHWHEELSENS = 16 1196 | 1197 | # Transforms 1198 | FLUID_MOD_TRANSFORM_LINEAR = 0 1199 | FLUID_MOD_TRANSFORM_ABS = 2 1200 | 1201 | class Modulator: 1202 | def __init__(self): 1203 | """Create new modulator object""" 1204 | self.mod = new_fluid_mod() 1205 | 1206 | def clone(self, src): 1207 | response = fluid_mod_clone(self.mod, src) 1208 | if response == FLUID_FAILED: 1209 | raise Exception("Modulation clone failed") 1210 | return response 1211 | 1212 | def get_amount(self): 1213 | response = fluid_mod_get_amount(self.mod) 1214 | if response == FLUID_FAILED: 1215 | raise Exception("Modulation amount get failed") 1216 | return response 1217 | 1218 | def get_dest(self): 1219 | response = fluid_mod_get_dest(self.mod) 1220 | if response == FLUID_FAILED: 1221 | raise Exception("Modulation destination get failed") 1222 | return response 1223 | 1224 | def get_flags1(self): 1225 | response = fluid_mod_get_flags1(self.mod) 1226 | if response == FLUID_FAILED: 1227 | raise Exception("Modulation flags1 get failed") 1228 | return response 1229 | 1230 | def get_flags2(self): 1231 | response = fluid_mod_get_flags2(self.mod) 1232 | if response == FLUID_FAILED: 1233 | raise Exception("Modulation flags2 get failed") 1234 | return response 1235 | 1236 | def get_source1(self): 1237 | response = fluid_mod_get_source1(self.mod) 1238 | if response == FLUID_FAILED: 1239 | raise Exception("Modulation source1 get failed") 1240 | return response 1241 | 1242 | def get_source2(self): 1243 | response = fluid_mod_get_source2(self.mod) 1244 | if response == FLUID_FAILED: 1245 | raise Exception("Modulation source2 get failed") 1246 | return response 1247 | 1248 | def get_transform(self): 1249 | response = fluid_mod_get_transform(self.mod) 1250 | if response == FLUID_FAILED: 1251 | raise Exception("Modulation transform get failed") 1252 | return response 1253 | 1254 | def has_dest(self, gen): 1255 | response = fluid_mod_has_dest(self.mod, gen) 1256 | if response == FLUID_FAILED: 1257 | raise Exception("Modulation has destination check failed") 1258 | return response 1259 | 1260 | def has_source(self, cc, ctrl): 1261 | response = fluid_mod_has_source(self.mod, cc, ctrl) 1262 | if response == FLUID_FAILED: 1263 | raise Exception("Modulation has source check failed") 1264 | return response 1265 | 1266 | def set_amount(self, amount): 1267 | response = fluid_mod_set_amount(self.mod, amount) 1268 | if response == FLUID_FAILED: 1269 | raise Exception("Modulation set amount failed") 1270 | return response 1271 | 1272 | def set_dest(self, dest): 1273 | response = fluid_mod_set_dest(self.mod, dest) 1274 | if response == FLUID_FAILED: 1275 | raise Exception("Modulation set dest failed") 1276 | return response 1277 | 1278 | def set_source1(self, src, flags): 1279 | response = fluid_mod_set_source1(self.mod, src, flags) 1280 | if response == FLUID_FAILED: 1281 | raise Exception("Modulation set source 1 failed") 1282 | return response 1283 | 1284 | def set_source2(self, src, flags): 1285 | response = fluid_mod_set_source2(self.mod, src, flags) 1286 | if response == FLUID_FAILED: 1287 | raise Exception("Modulation set source 2 failed") 1288 | return response 1289 | 1290 | def set_transform(self, type): 1291 | response = fluid_mod_set_transform(self.mod, type) 1292 | if response == FLUID_FAILED: 1293 | raise Exception("Modulation set transform failed") 1294 | return response 1295 | 1296 | def sizeof(self): 1297 | response = fluid_mod_sizeof() 1298 | if response == FLUID_FAILED: 1299 | raise Exception("Modulation sizeof failed") 1300 | return response 1301 | 1302 | def test_identity(self, mod2): 1303 | response = fluid_mod_sizeof(self.mod, mod2) 1304 | if response == FLUID_FAILED: 1305 | raise Exception("Modulation identity check failed") 1306 | return response 1307 | 1308 | class Sequencer: 1309 | def __init__(self, time_scale=1000, use_system_timer=True): 1310 | """Create new sequencer object to control and schedule timing of midi events 1311 | 1312 | Optional keyword arguments: 1313 | time_scale: ticks per second, defaults to 1000 1314 | use_system_timer: whether the sequencer should advance by itself 1315 | """ 1316 | self.client_callbacks = [] 1317 | self.sequencer = new_fluid_sequencer2(use_system_timer) 1318 | fluid_sequencer_set_time_scale(self.sequencer, time_scale) 1319 | 1320 | def register_fluidsynth(self, synth): 1321 | response = fluid_sequencer_register_fluidsynth(self.sequencer, synth.synth) 1322 | if response == FLUID_FAILED: 1323 | raise Exception("Registering fluid synth failed") 1324 | return response 1325 | 1326 | def register_client(self, name, callback, data=None): 1327 | c_callback = CFUNCTYPE(None, c_uint, c_void_p, c_void_p, c_void_p)(callback) 1328 | response = fluid_sequencer_register_client(self.sequencer, name.encode(), c_callback, data) 1329 | if response == FLUID_FAILED: 1330 | raise Exception("Registering client failed") 1331 | 1332 | # store in a list to prevent garbage collection 1333 | self.client_callbacks.append(c_callback) 1334 | 1335 | return response 1336 | 1337 | def note(self, time, channel, key, velocity, duration, source=-1, dest=-1, absolute=True): 1338 | evt = self._create_event(source, dest) 1339 | fluid_event_note(evt, channel, key, velocity, duration) 1340 | self._schedule_event(evt, time, absolute) 1341 | delete_fluid_event(evt) 1342 | 1343 | def note_on(self, time, channel, key, velocity=127, source=-1, dest=-1, absolute=True): 1344 | evt = self._create_event(source, dest) 1345 | fluid_event_noteon(evt, channel, key, velocity) 1346 | self._schedule_event(evt, time, absolute) 1347 | delete_fluid_event(evt) 1348 | 1349 | def note_off(self, time, channel, key, source=-1, dest=-1, absolute=True): 1350 | evt = self._create_event(source, dest) 1351 | fluid_event_noteoff(evt, channel, key) 1352 | self._schedule_event(evt, time, absolute) 1353 | delete_fluid_event(evt) 1354 | 1355 | def timer(self, time, data=None, source=-1, dest=-1, absolute=True): 1356 | evt = self._create_event(source, dest) 1357 | fluid_event_timer(evt, data) 1358 | self._schedule_event(evt, time, absolute) 1359 | delete_fluid_event(evt) 1360 | 1361 | def _create_event(self, source=-1, dest=-1): 1362 | evt = new_fluid_event() 1363 | fluid_event_set_source(evt, source) 1364 | fluid_event_set_dest(evt, dest) 1365 | return evt 1366 | 1367 | def _schedule_event(self, evt, time, absolute=True): 1368 | response = fluid_sequencer_send_at(self.sequencer, evt, time, absolute) 1369 | if response == FLUID_FAILED: 1370 | raise Exception("Scheduling event failed") 1371 | 1372 | def get_tick(self): 1373 | return fluid_sequencer_get_tick(self.sequencer) 1374 | 1375 | def process(self, msec): 1376 | fluid_sequencer_process(self.sequencer, msec) 1377 | 1378 | def delete(self): 1379 | delete_fluid_sequencer(self.sequencer) 1380 | 1381 | def raw_audio_string(data): 1382 | """Return a string of bytes to send to soundcard 1383 | 1384 | Input is a numpy array of samples. Default output format 1385 | is 16-bit signed (other formats not currently supported). 1386 | 1387 | """ 1388 | import numpy 1389 | return (data.astype(numpy.int16)).tobytes() 1390 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "setuptools.build_meta" 3 | 4 | requires = [ "setuptools>=61.2" ] 5 | 6 | [project] 7 | name = "pyfluidsynth" 8 | version = "1.3.4" 9 | description = "Python bindings for FluidSynth, a MIDI synthesizer that uses SoundFont instruments" 10 | readme = { file = "README.md", content-type = "text/markdown" } 11 | authors = [ { name = "Nathan Whitehead", email = "nwhitehe@gmail.com" } ] 12 | classifiers = [ 13 | "Programming Language :: Python :: 3 :: Only", 14 | "Programming Language :: Python :: 3.9", 15 | "Programming Language :: Python :: 3.10", 16 | "Programming Language :: Python :: 3.11", 17 | "Programming Language :: Python :: 3.12", 18 | "Programming Language :: Python :: 3.13", 19 | ] 20 | dependencies = [ "numpy", "pyaudio" ] 21 | requires-python = ">= 3.9" 22 | 23 | urls.Homepage = "https://github.com/nwhitehead/pyfluidsynth" 24 | 25 | [tool.setuptools] 26 | py-modules = [ "fluidsynth" ] 27 | include-package-data = false 28 | 29 | [tool.uv] 30 | upgrade = true 31 | 32 | [tool.ruff] 33 | line-length = 123 34 | lint.select = [ 35 | "AIR", # Airflow 36 | "ASYNC", # flake8-async 37 | "B", # flake8-bugbear 38 | "BLE", # flake8-blind-except 39 | "C4", # flake8-comprehensions 40 | "C90", # McCabe cyclomatic complexity 41 | "COM", # flake8-commas 42 | "DJ", # flake8-django 43 | "DTZ", # flake8-datetimez 44 | "E", # pycodestyle 45 | "F", # Pyflakes 46 | "FA", # flake8-future-annotations 47 | "FIX", # flake8-fixme 48 | "FLY", # flynt 49 | "G", # flake8-logging-format 50 | "I", # isort 51 | "INP", # flake8-no-pep420 52 | "INT", # flake8-gettext 53 | "ISC", # flake8-implicit-str-concat 54 | "LOG", # flake8-logging 55 | "NPY", # NumPy-specific rules 56 | "PD", # pandas-vet 57 | "PERF", # Perflint 58 | "PGH", # pygrep-hooks 59 | "PIE", # flake8-pie 60 | "PL", # Pylint 61 | "PT", # flake8-pytest-style 62 | "PYI", # flake8-pyi 63 | "RSE", # flake8-raise 64 | "S", # flake8-bandit 65 | "SIM", # flake8-simplify 66 | "SLF", # flake8-self 67 | "SLOT", # flake8-slots 68 | "T10", # flake8-debugger 69 | "TCH", # flake8-type-checking 70 | "TD", # flake8-todos 71 | "TID", # flake8-tidy-imports 72 | "UP", # pyupgrade 73 | "W", # pycodestyle 74 | "YTT", # flake8-2020 75 | # "A", # flake8-builtins 76 | # "ANN", # flake8-annotations 77 | # "ARG", # flake8-unused-arguments 78 | # "CPY", # flake8-copyright 79 | # "D", # pydocstyle 80 | # "EM", # flake8-errmsg 81 | # "ERA", # eradicate 82 | # "EXE", # flake8-executable 83 | # "FBT", # flake8-boolean-trap 84 | # "FURB", # refurb 85 | # "ICN", # flake8-import-conventions 86 | # "N", # pep8-naming 87 | # "PTH", # flake8-use-pathlib 88 | # "Q", # flake8-quotes 89 | # "RET", # flake8-return 90 | # "RUF", # Ruff-specific rules 91 | # "T20", # flake8-print 92 | # "TRY", # tryceratops 93 | ] 94 | lint.per-file-ignores."__init__.py" = [ "E402" ] 95 | lint.per-file-ignores."test/*" = [ "S101" ] 96 | lint.pylint.allow-magic-value-types = [ "int", "str" ] 97 | lint.pylint.max-args = 8 98 | -------------------------------------------------------------------------------- /test/1080-c01.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwhitehead/pyfluidsynth/dd601166845bc6e2ec0927d78008eab328e65409/test/1080-c01.mid -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwhitehead/pyfluidsynth/dd601166845bc6e2ec0927d78008eab328e65409/test/__init__.py -------------------------------------------------------------------------------- /test/example.sf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwhitehead/pyfluidsynth/dd601166845bc6e2ec0927d78008eab328e65409/test/example.sf2 -------------------------------------------------------------------------------- /test/modulatorTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S uv run --script 2 | 3 | # /// script 4 | # requires-python = ">=3.9" 5 | # dependencies = [ 6 | # "pyfluidsynth", 7 | # ] 8 | # /// 9 | 10 | import unittest 11 | 12 | import fluidsynth as fs 13 | 14 | 15 | def local_file_path(file_name: str) -> str: 16 | """ 17 | Return a file path to a file that is in the same directory as this file. 18 | """ 19 | from os.path import dirname, join 20 | 21 | return join(dirname(__file__), file_name) 22 | 23 | 24 | class TestModulatorMethods(unittest.TestCase): 25 | @classmethod 26 | def setUpClass(cls): 27 | cls.fs = fs.Synth() 28 | cls.modulator = fs.Modulator() 29 | 30 | ## Your installation of FluidSynth may require a different driver. 31 | ## Use something like: 32 | # fs.start(driver="pulseaudio") 33 | cls.fs.start() 34 | cls.sfid = cls.fs.sfload(local_file_path("example.sf2")) 35 | cls.fs.program_select(0, cls.sfid, 0, 0) 36 | 37 | @classmethod 38 | def tearDownClass(cls): 39 | cls.fs.delete() 40 | 41 | def test_dest(self): 42 | assert self.modulator.has_dest(5) is None 43 | self.modulator.set_dest(5) 44 | assert self.modulator.has_dest(5) == 1 45 | assert self.modulator.get_dest() == 5 46 | 47 | def test_flags(self): 48 | assert self.modulator.get_source1() is None 49 | self.modulator.set_source1(fs.FLUID_MOD_KEY, fs.FLUID_MOD_CONVEX) 50 | assert self.modulator.get_source1() == fs.FLUID_MOD_KEY 51 | assert self.modulator.get_flags1() == fs.FLUID_MOD_CONVEX 52 | 53 | assert self.modulator.get_source2() is None 54 | self.modulator.set_source2(fs.FLUID_MOD_VELOCITY, fs.FLUID_MOD_CONCAVE) 55 | assert self.modulator.get_source2() == fs.FLUID_MOD_VELOCITY 56 | assert self.modulator.get_flags2() == fs.FLUID_MOD_CONCAVE 57 | 58 | def test_transforms(self): 59 | assert self.modulator.get_transform() is None 60 | self.modulator.set_transform(fs.FLUID_MOD_TRANSFORM_ABS) 61 | assert self.modulator.get_transform() == fs.FLUID_MOD_TRANSFORM_ABS 62 | 63 | 64 | if __name__ == "__main__": 65 | unittest.main() 66 | -------------------------------------------------------------------------------- /test/sequencerTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S uv run --script 2 | 3 | # /// script 4 | # requires-python = ">=3.9" 5 | # dependencies = [ 6 | # "pyfluidsynth", 7 | # ] 8 | # /// 9 | 10 | # ruff: noqa: PLW0603 11 | 12 | import time 13 | 14 | import fluidsynth 15 | 16 | seqduration = 1000 17 | 18 | def schedule_next_callback(): 19 | # I want to be called back before the end of the next sequence 20 | callbackdate = int(now + seqduration/2) 21 | sequencer.timer(callbackdate, dest=mySeqID) 22 | 23 | def schedule_next_sequence(): 24 | global now # noqa: PLW0603 25 | # the sequence to play 26 | # the beat : 2 beats per sequence 27 | sequencer.note(int(now + seqduration * 1/2), 0, 60, duration=250, velocity=80, dest=synthSeqID) 28 | sequencer.note(int(now + seqduration * 2/2), 0, 60, duration=250, velocity=80, dest=synthSeqID) 29 | # melody 30 | sequencer.note(int(now + seqduration*1/10), 1, 45, duration=250, velocity=int(127*2/3), dest=synthSeqID) 31 | sequencer.note(int(now + seqduration*4/10), 1, 50, duration=250, velocity=int(127*2/3), dest=synthSeqID) 32 | sequencer.note(int(now + seqduration*8/10), 1, 55, duration=250, velocity=int(127*3/3), dest=synthSeqID) 33 | # so that we are called back early enough to schedule the next sequence 34 | schedule_next_callback() 35 | 36 | now = now + seqduration 37 | 38 | def seq_callback(time, event, seq, data): 39 | schedule_next_sequence() 40 | 41 | def local_file_path(file_name: str) -> str: 42 | """ 43 | Return a file path to a file that is in the same directory as this file. 44 | """ 45 | from os.path import dirname, join 46 | 47 | return join(dirname(__file__), file_name) 48 | 49 | if __name__=="__main__": 50 | global sequencer, fs, mySeqID, synthSeqID, now # noqa: PLW0604 51 | fs = fluidsynth.Synth() 52 | fs.start() 53 | # you might have to use other drivers: 54 | # fs.start(driver="alsa", midi_driver="alsa_seq") 55 | 56 | sfid = fs.sfload(local_file_path("example.sf2")) 57 | fs.program_select(0, sfid, 0, 0) 58 | fs.program_select(1, sfid, 0, 0) # use the same program for channel 2 for cheapness 59 | 60 | sequencer = fluidsynth.Sequencer() 61 | synthSeqID = sequencer.register_fluidsynth(fs) 62 | mySeqID = sequencer.register_client("mycallback", seq_callback) 63 | now = sequencer.get_tick() 64 | schedule_next_sequence() 65 | 66 | time.sleep(10) 67 | 68 | sequencer.delete() 69 | fs.delete() 70 | -------------------------------------------------------------------------------- /test/test1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S uv run --script 2 | 3 | # /// script 4 | # requires-python = ">=3.9" 5 | # dependencies = [ 6 | # "pyfluidsynth", 7 | # ] 8 | # /// 9 | 10 | import time 11 | 12 | import fluidsynth 13 | 14 | 15 | def local_file_path(file_name: str) -> str: 16 | """ 17 | Return a file path to a file that is in the same directory as this file. 18 | """ 19 | from os.path import dirname, join 20 | 21 | return join(dirname(__file__), file_name) 22 | 23 | fs = fluidsynth.Synth() 24 | fs.start() 25 | ## Your installation of FluidSynth may require a different driver. 26 | ## Use something like: 27 | # fs.start(driver="pulseaudio") 28 | 29 | sfid = fs.sfload(local_file_path("example.sf2")) 30 | fs.program_select(0, sfid, 0, 0) 31 | 32 | fs.noteon(0, 60, 30) 33 | fs.noteon(0, 67, 30) 34 | fs.noteon(0, 76, 30) 35 | 36 | time.sleep(1.0) 37 | 38 | fs.noteoff(0, 60) 39 | fs.noteoff(0, 67) 40 | fs.noteoff(0, 76) 41 | 42 | time.sleep(1.0) 43 | 44 | fs.delete() 45 | -------------------------------------------------------------------------------- /test/test2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S uv run --script 2 | 3 | # /// script 4 | # requires-python = ">=3.9" 5 | # dependencies = [ 6 | # "numpy", 7 | # "pyaudio", 8 | # "pyfluidsynth", 9 | # ] 10 | # /// 11 | 12 | import numpy 13 | import pyaudio 14 | 15 | import fluidsynth 16 | 17 | 18 | def local_file_path(file_name: str) -> str: 19 | """ 20 | Return a file path to a file that is in the same directory as this file. 21 | """ 22 | from os.path import dirname, join 23 | 24 | return join(dirname(__file__), file_name) 25 | 26 | pa = pyaudio.PyAudio() 27 | strm = pa.open( 28 | format = pyaudio.paInt16, 29 | channels = 2, 30 | rate = 44100, 31 | output = True) 32 | 33 | s = [] 34 | 35 | fs = fluidsynth.Synth() 36 | 37 | # Initial silence is 1 second 38 | s = numpy.append(s, fs.get_samples(44100 * 1)) 39 | 40 | sfid = fs.sfload(local_file_path("example.sf2")) 41 | fs.program_select(0, sfid, 0, 0) 42 | 43 | fs.noteon(0, 60, 30) 44 | fs.noteon(0, 67, 30) 45 | fs.noteon(0, 76, 30) 46 | 47 | # Chord is held for 2 seconds 48 | s = numpy.append(s, fs.get_samples(44100 * 2)) 49 | 50 | fs.noteoff(0, 60) 51 | fs.noteoff(0, 67) 52 | fs.noteoff(0, 76) 53 | 54 | # Decay of chord is held for 1 second 55 | s = numpy.append(s, fs.get_samples(44100 * 1)) 56 | 57 | fs.delete() 58 | 59 | samps = fluidsynth.raw_audio_string(s) 60 | 61 | print(len(samps)) 62 | print('Starting playback') 63 | strm.write(samps) 64 | -------------------------------------------------------------------------------- /test/test3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S uv run --script 2 | 3 | # /// script 4 | # requires-python = ">=3.9" 5 | # dependencies = [ 6 | # "pyfluidsynth", 7 | # ] 8 | # /// 9 | 10 | import time 11 | 12 | import fluidsynth 13 | 14 | 15 | def local_file_path(file_name: str) -> str: 16 | """ 17 | Return a file path to a file that is in the same directory as this file. 18 | """ 19 | from os.path import dirname, join 20 | 21 | return join(dirname(__file__), file_name) 22 | 23 | fs = fluidsynth.Synth() 24 | fs.start() 25 | ## Your installation of FluidSynth may require a different driver. 26 | ## Use something like: 27 | # fs.start(driver="pulseaudio") 28 | 29 | sfid = fs.sfload(local_file_path("example.sf2")) 30 | fs.program_select(0, sfid, 0, 0) 31 | 32 | fs.noteon(0, 60, 30) 33 | time.sleep(0.3) 34 | 35 | for i in range(10): 36 | fs.cc(0, 93, 127) 37 | fs.pitch_bend(0, i * 512) 38 | time.sleep(0.1) 39 | fs.noteoff(0, 60) 40 | 41 | time.sleep(1.0) 42 | 43 | fs.delete() 44 | -------------------------------------------------------------------------------- /test/test4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S uv run --script 2 | 3 | # /// script 4 | # requires-python = ">=3.9" 5 | # dependencies = [ 6 | # "pyfluidsynth", 7 | # ] 8 | # /// 9 | 10 | import time 11 | 12 | import fluidsynth 13 | 14 | 15 | def local_file_path(file_name: str) -> str: 16 | """ 17 | Return a file path to a file that is in the same directory as this file. 18 | """ 19 | from os.path import dirname, join 20 | 21 | return join(dirname(__file__), file_name) 22 | 23 | fs = fluidsynth.Synth() 24 | fs.start() 25 | ## Your installation of FluidSynth may require a different driver. 26 | ## Use something like: 27 | # fs.start(driver="pulseaudio") 28 | 29 | sfid = fs.sfload(local_file_path("example.sf2")) 30 | fs.program_select(0, sfid, 0, 0) 31 | 32 | # Note that reverb send amount from presets is also determined by SF2 file. 33 | # If needed you can edit SF2 files with Polytope. To add reverb: 34 | # * Open SF2 file in Polytope. 35 | # * Choose the desired preset in Polytope in left panel. 36 | # * Scroll down to "Reverb (%)". 37 | # * In "Global" column of "Reverb (%)" row set to 100.0 or appropriate percentage. 38 | # * Save SF2 file. 39 | fs.set_reverb(roomsize=0.8, damping=0.2, width=50.0, level=0.5) 40 | fs.noteon(0, 60, 30) 41 | fs.noteon(0, 67, 30) 42 | fs.noteon(0, 76, 30) 43 | 44 | time.sleep(2.0) 45 | fs.pitch_bend(0, -8192) 46 | time.sleep(2.0) 47 | 48 | fs.noteoff(0, 60) 49 | fs.noteoff(0, 67) 50 | fs.noteoff(0, 76) 51 | 52 | time.sleep(2.0) 53 | 54 | fs.delete() 55 | -------------------------------------------------------------------------------- /test/test5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S uv run --script 2 | 3 | # /// script 4 | # requires-python = ">=3.9" 5 | # dependencies = [ 6 | # "pyfluidsynth", 7 | # ] 8 | # /// 9 | 10 | # Runtime is 2:49.45 11 | 12 | import time 13 | 14 | import fluidsynth 15 | 16 | 17 | def local_file_path(file_name: str) -> str: 18 | """ 19 | Return a file path to a file that is in the same directory as this file. 20 | """ 21 | from os.path import dirname, join 22 | 23 | return join(dirname(__file__), file_name) 24 | 25 | fs = fluidsynth.Synth() 26 | fs.start() 27 | ## Your installation of FluidSynth may require a different driver. 28 | ## Use something like: 29 | # fs.start(driver="pulseaudio") 30 | 31 | sfid = fs.sfload(local_file_path("example.sf2")) 32 | fs.program_select(0, sfid, 0, 0) 33 | 34 | fs.play_midi_file(local_file_path("1080-c01.mid")) 35 | while fluidsynth.fluid_player_get_status(fs.player) == fluidsynth.FLUID_PLAYER_PLAYING: 36 | time.sleep(1) 37 | 38 | fs.delete() 39 | -------------------------------------------------------------------------------- /test/test6.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S uv run --script 2 | 3 | # /// script 4 | # requires-python = ">=3.9" 5 | # dependencies = [ 6 | # "numpy", 7 | # "pyaudio", 8 | # "pyfluidsynth", 9 | # ] 10 | # /// 11 | 12 | import numpy 13 | import pyaudio 14 | 15 | import fluidsynth 16 | 17 | 18 | def local_file_path(file_name: str) -> str: 19 | """ 20 | Return a file path to a file that is in the same directory as this file. 21 | """ 22 | from os.path import dirname, join 23 | 24 | return join(dirname(__file__), file_name) 25 | 26 | pa = pyaudio.PyAudio() 27 | strm = pa.open( 28 | format = pyaudio.paInt16, 29 | channels = 2, 30 | rate = 44100, 31 | output = True) 32 | 33 | fs = fluidsynth.Synth() 34 | fs.custom_router_callback = None 35 | 36 | sfid = fs.sfload(local_file_path("example.sf2")) 37 | fs.program_select(0, sfid, 0, 0) 38 | 39 | fs.play_midi_file(local_file_path("1080-c01.mid")) 40 | 41 | # Generate 10 seconds of audio into s 42 | s = [] 43 | for _ in range(10): 44 | s = numpy.append(s, fs.get_samples(44100)) 45 | if fluidsynth.fluid_player_get_status(fs.player) != fluidsynth.FLUID_PLAYER_PLAYING: 46 | break 47 | 48 | fs.delete() 49 | 50 | samps = fluidsynth.raw_audio_string(s) 51 | print('Starting playback') 52 | strm.write(samps) 53 | -------------------------------------------------------------------------------- /test/test7.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S uv run --script 2 | 3 | # /// script 4 | # requires-python = ">=3.9" 5 | # dependencies = [ 6 | # "pyfluidsynth", 7 | # ] 8 | # /// 9 | 10 | import fluidsynth 11 | 12 | 13 | def local_file_path(file_name: str) -> str: 14 | """ 15 | Return a file path to a file that is in the same directory as this file. 16 | """ 17 | from os.path import dirname, join 18 | 19 | return join(dirname(__file__), file_name) 20 | 21 | fs = fluidsynth.Synth() 22 | sfid = fs.sfload(local_file_path("example.sf2")) 23 | fs.midi2audio(local_file_path("1080-c01.mid"), local_file_path("1080-c01.wav")) 24 | # A Synth object can synthesize multiple files. For example: 25 | # fs.midi2audio(local_file_path("1080-c02.mid"), local_file_path("1080-c02.wav")) 26 | # fs.midi2audio(local_file_path("1080-c03.mid"), local_file_path("1080-c03.wav")) 27 | 28 | fs.delete() 29 | --------------------------------------------------------------------------------