├── .editorconfig ├── .github └── workflows │ └── rust-wasm-pack.yml ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── LICENSE.txt ├── README.md ├── docs ├── .htaccess ├── 8b2e6717384cb79e54e1.wasm ├── bootstrap.js ├── images │ ├── ogp.jpg │ └── ogp.png ├── index.html ├── src_js_index_js.bootstrap.js ├── style.css └── vgm │ ├── sn76489.vgm │ └── ym2612.vgm ├── synth-emulator ├── .gitignore ├── .vscode │ ├── launch.json │ └── settings.json ├── CMakeLists.txt ├── Cargo.lock ├── Cargo.toml ├── cmake │ └── macros.cmake └── src │ ├── c │ ├── mamedef.h │ ├── panning.c │ ├── panning.h │ ├── pwm.c │ ├── pwm.h │ ├── sn76489.c │ ├── sn76489.h │ ├── vgmplay.c │ ├── vgmplay.h │ ├── ym3438.c │ └── ym3438.h │ └── rust │ ├── driver.rs │ ├── driver │ ├── metadata.rs │ └── vgmplay.rs │ ├── lib.rs │ ├── sound.rs │ └── sound │ ├── pwm.rs │ ├── segapcm.rs │ ├── sn76489.rs │ └── ym3438.rs └── wasm-synth-player ├── .cargo └── config.toml.not_yet_support_webpack ├── .eslintrc.js ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── js │ ├── bootstrap.js │ └── index.js ├── rust │ └── lib.rs └── www │ ├── index.html │ └── style.css ├── webpack.config.js └── webpack.dev.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/workflows/rust-wasm-pack.yml: -------------------------------------------------------------------------------- 1 | name: Rust-wasm CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | 11 | - name: Install wasm32-unknown-unknown 12 | uses: actions-rs/toolchain@v1 13 | with: 14 | toolchain: stable 15 | target: wasm32-unknown-unknown 16 | override: true 17 | 18 | - name: Install wasm-pack 19 | run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 20 | 21 | - name: Build 22 | run: | 23 | cd wasm-synth-player 24 | wasm-pack build 25 | npm install 26 | npm run build 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | target/ 3 | pkg/ 4 | dist/ 5 | *.vgm 6 | *.vgz 7 | *.pcm 8 | !sn76489.vgm 9 | !ym2612.vgm 10 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "rust-lang.rust", 6 | "vadimcn.vscode-lldb", 7 | "serayuzgur.crates", 8 | "dbaeumer.vscode-eslint", 9 | "mkaufman.HTMLHint", 10 | "EditorConfig.EditorConfig" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "lldb", 6 | "request": "launch", 7 | "name": "Debug unit tests in library 'synth-emulator'", 8 | "cargo": { 9 | "args": [ 10 | "test", 11 | "--no-run", 12 | "--lib", 13 | "--package=synth-emulator" 14 | ], 15 | "filter": { 16 | "name": "synth-emulator", 17 | "kind": "lib" 18 | } 19 | }, 20 | "args": [], 21 | "cwd": "${workspaceFolder}/synth-emulator" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-client.enableMultiProjectSetup": true 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 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 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-synth-emulation 2 | 3 | YM2612/SN76489 VGM player by Rust 4 | 5 | ## New version here 6 | 7 | > [libymfm.wasm](https://github.com/h1romas4/libymfm.wasm) 8 | > 9 | > This repository is an experimental WebAssembly build of the [ymfm](https://github.com/aaronsgiles/ymfm) Yamaha FM sound cores library. 10 | 11 | ## WebAssembly demo site 12 | 13 | [![](https://raw.githubusercontent.com/h1romas4/rust-synth-emulation/master/docs/images/ogp.png)](https://h1romas4.github.io/rust-synth-emulation/index.html) 14 | 15 | [ym2612.vgm](https://h1romas4.github.io/rust-synth-emulation/index.html) 16 | 17 | ## Build 18 | 19 | ![](https://github.com/h1romas4/rust-synth-emulation/workflows/Rust-wasm%20CI/badge.svg) 20 | 21 | Rust and [wasm-pack](https://rustwasm.github.io/wasm-pack) setup 22 | 23 | ``` 24 | rustup target add wasm32-unknown-unknown 25 | curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 26 | ``` 27 | 28 | **Compile** 29 | 30 | ``` 31 | git clone git@github.com:h1romas4/rust-synth-emulation.git 32 | cd rust-synth-emulation 33 | cd wasm-synth-player 34 | wasm-pack build 35 | npm install 36 | npm run start 37 | ``` 38 | 39 | **Play** 40 | 41 | ``` 42 | http://localhost:9000/ 43 | ``` 44 | 45 | **Create VGM file** 46 | 47 | * [mml2vgm](https://github.com/kuma4649/mml2vgm) by [kumatan](https://github.com/kuma4649) san 48 | * [mucomMD2vgm](https://github.com/kuma4649/mucomMD2vgm) by [kumatan](https://github.com/kuma4649) san 49 | 50 | ## License 51 | 52 | [GNU General Public License v2.0](https://github.com/h1romas4/rust-synth-emulation/blob/master/LICENSE.txt) 53 | 54 | ## Thanks! 55 | 56 | * [Nuked-OPN2](https://github.com/nukeykt/Nuked-OPN2) 57 | * [sn76489.c](https://github.com/vgmrips/vgmplay/blob/master/VGMPlay/chips/sn76489.c) 58 | * [pwm.c](https://github.com/vgmrips/vgmplay/blob/master/VGMPlay/chips/pwm.c) 59 | -------------------------------------------------------------------------------- /docs/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | AddType application/wasm wasm 3 | 4 | -------------------------------------------------------------------------------- /docs/8b2e6717384cb79e54e1.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h1romas4/rust-synth-emulation/c89b88c450e704c96c2e2c8a707eea93f04b46aa/docs/8b2e6717384cb79e54e1.wasm -------------------------------------------------------------------------------- /docs/bootstrap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). 3 | * This devtool is neither made for production nor for readable output files. 4 | * It uses "eval()" calls to create a separate source file in the browser devtools. 5 | * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) 6 | * or disable the default devtool with "devtool: false". 7 | * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). 8 | */ 9 | /******/ (() => { // webpackBootstrap 10 | /******/ var __webpack_modules__ = ({ 11 | 12 | /***/ "./src/js/bootstrap.js": 13 | /*!*****************************!*\ 14 | !*** ./src/js/bootstrap.js ***! 15 | \*****************************/ 16 | /***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { 17 | 18 | eval("// A dependency graph that contains any wasm must all be imported\n// asynchronously. This `bootstrap.js` file does the single async import, so\n// that no one else needs to worry about it again.\n__webpack_require__.e(/*! import() */ \"src_js_index_js\").then(__webpack_require__.bind(__webpack_require__, /*! ./index.js */ \"./src/js/index.js\"))[\"catch\"](function (e) {\n return console.error(\"Error importing `index.js`:\", e);\n});\n\n//# sourceURL=webpack://wasm-synth-player/./src/js/bootstrap.js?"); 19 | 20 | /***/ }) 21 | 22 | /******/ }); 23 | /************************************************************************/ 24 | /******/ // The module cache 25 | /******/ var __webpack_module_cache__ = {}; 26 | /******/ 27 | /******/ // The require function 28 | /******/ function __webpack_require__(moduleId) { 29 | /******/ // Check if module is in cache 30 | /******/ var cachedModule = __webpack_module_cache__[moduleId]; 31 | /******/ if (cachedModule !== undefined) { 32 | /******/ return cachedModule.exports; 33 | /******/ } 34 | /******/ // Create a new module (and put it into the cache) 35 | /******/ var module = __webpack_module_cache__[moduleId] = { 36 | /******/ id: moduleId, 37 | /******/ loaded: false, 38 | /******/ exports: {} 39 | /******/ }; 40 | /******/ 41 | /******/ // Execute the module function 42 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 43 | /******/ 44 | /******/ // Flag the module as loaded 45 | /******/ module.loaded = true; 46 | /******/ 47 | /******/ // Return the exports of the module 48 | /******/ return module.exports; 49 | /******/ } 50 | /******/ 51 | /******/ // expose the modules object (__webpack_modules__) 52 | /******/ __webpack_require__.m = __webpack_modules__; 53 | /******/ 54 | /************************************************************************/ 55 | /******/ /* webpack/runtime/async module */ 56 | /******/ (() => { 57 | /******/ var webpackThen = typeof Symbol === "function" ? Symbol("webpack then") : "__webpack_then__"; 58 | /******/ var webpackExports = typeof Symbol === "function" ? Symbol("webpack exports") : "__webpack_exports__"; 59 | /******/ var completeQueue = (queue) => { 60 | /******/ if(queue) { 61 | /******/ queue.forEach((fn) => (fn.r--)); 62 | /******/ queue.forEach((fn) => (fn.r-- ? fn.r++ : fn())); 63 | /******/ } 64 | /******/ } 65 | /******/ var completeFunction = (fn) => (!--fn.r && fn()); 66 | /******/ var queueFunction = (queue, fn) => (queue ? queue.push(fn) : completeFunction(fn)); 67 | /******/ var wrapDeps = (deps) => (deps.map((dep) => { 68 | /******/ if(dep !== null && typeof dep === "object") { 69 | /******/ if(dep[webpackThen]) return dep; 70 | /******/ if(dep.then) { 71 | /******/ var queue = []; 72 | /******/ dep.then((r) => { 73 | /******/ obj[webpackExports] = r; 74 | /******/ completeQueue(queue); 75 | /******/ queue = 0; 76 | /******/ }); 77 | /******/ var obj = {}; 78 | /******/ obj[webpackThen] = (fn, reject) => (queueFunction(queue, fn), dep.catch(reject)); 79 | /******/ return obj; 80 | /******/ } 81 | /******/ } 82 | /******/ var ret = {}; 83 | /******/ ret[webpackThen] = (fn) => (completeFunction(fn)); 84 | /******/ ret[webpackExports] = dep; 85 | /******/ return ret; 86 | /******/ })); 87 | /******/ __webpack_require__.a = (module, body, hasAwait) => { 88 | /******/ var queue = hasAwait && []; 89 | /******/ var exports = module.exports; 90 | /******/ var currentDeps; 91 | /******/ var outerResolve; 92 | /******/ var reject; 93 | /******/ var isEvaluating = true; 94 | /******/ var nested = false; 95 | /******/ var whenAll = (deps, onResolve, onReject) => { 96 | /******/ if (nested) return; 97 | /******/ nested = true; 98 | /******/ onResolve.r += deps.length; 99 | /******/ deps.map((dep, i) => (dep[webpackThen](onResolve, onReject))); 100 | /******/ nested = false; 101 | /******/ }; 102 | /******/ var promise = new Promise((resolve, rej) => { 103 | /******/ reject = rej; 104 | /******/ outerResolve = () => (resolve(exports), completeQueue(queue), queue = 0); 105 | /******/ }); 106 | /******/ promise[webpackExports] = exports; 107 | /******/ promise[webpackThen] = (fn, rejectFn) => { 108 | /******/ if (isEvaluating) { return completeFunction(fn); } 109 | /******/ if (currentDeps) whenAll(currentDeps, fn, rejectFn); 110 | /******/ queueFunction(queue, fn); 111 | /******/ promise.catch(rejectFn); 112 | /******/ }; 113 | /******/ module.exports = promise; 114 | /******/ body((deps) => { 115 | /******/ if(!deps) return outerResolve(); 116 | /******/ currentDeps = wrapDeps(deps); 117 | /******/ var fn, result; 118 | /******/ var promise = new Promise((resolve, reject) => { 119 | /******/ fn = () => (resolve(result = currentDeps.map((d) => (d[webpackExports])))); 120 | /******/ fn.r = 0; 121 | /******/ whenAll(currentDeps, fn, reject); 122 | /******/ }); 123 | /******/ return fn.r ? promise : result; 124 | /******/ }).then(outerResolve, reject); 125 | /******/ isEvaluating = false; 126 | /******/ }; 127 | /******/ })(); 128 | /******/ 129 | /******/ /* webpack/runtime/define property getters */ 130 | /******/ (() => { 131 | /******/ // define getter functions for harmony exports 132 | /******/ __webpack_require__.d = (exports, definition) => { 133 | /******/ for(var key in definition) { 134 | /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { 135 | /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); 136 | /******/ } 137 | /******/ } 138 | /******/ }; 139 | /******/ })(); 140 | /******/ 141 | /******/ /* webpack/runtime/ensure chunk */ 142 | /******/ (() => { 143 | /******/ __webpack_require__.f = {}; 144 | /******/ // This file contains only the entry chunk. 145 | /******/ // The chunk loading function for additional chunks 146 | /******/ __webpack_require__.e = (chunkId) => { 147 | /******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { 148 | /******/ __webpack_require__.f[key](chunkId, promises); 149 | /******/ return promises; 150 | /******/ }, [])); 151 | /******/ }; 152 | /******/ })(); 153 | /******/ 154 | /******/ /* webpack/runtime/get javascript chunk filename */ 155 | /******/ (() => { 156 | /******/ // This function allow to reference async chunks 157 | /******/ __webpack_require__.u = (chunkId) => { 158 | /******/ // return url for filenames based on template 159 | /******/ return "" + chunkId + ".bootstrap.js"; 160 | /******/ }; 161 | /******/ })(); 162 | /******/ 163 | /******/ /* webpack/runtime/global */ 164 | /******/ (() => { 165 | /******/ __webpack_require__.g = (function() { 166 | /******/ if (typeof globalThis === 'object') return globalThis; 167 | /******/ try { 168 | /******/ return this || new Function('return this')(); 169 | /******/ } catch (e) { 170 | /******/ if (typeof window === 'object') return window; 171 | /******/ } 172 | /******/ })(); 173 | /******/ })(); 174 | /******/ 175 | /******/ /* webpack/runtime/harmony module decorator */ 176 | /******/ (() => { 177 | /******/ __webpack_require__.hmd = (module) => { 178 | /******/ module = Object.create(module); 179 | /******/ if (!module.children) module.children = []; 180 | /******/ Object.defineProperty(module, 'exports', { 181 | /******/ enumerable: true, 182 | /******/ set: () => { 183 | /******/ throw new Error('ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: ' + module.id); 184 | /******/ } 185 | /******/ }); 186 | /******/ return module; 187 | /******/ }; 188 | /******/ })(); 189 | /******/ 190 | /******/ /* webpack/runtime/hasOwnProperty shorthand */ 191 | /******/ (() => { 192 | /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) 193 | /******/ })(); 194 | /******/ 195 | /******/ /* webpack/runtime/load script */ 196 | /******/ (() => { 197 | /******/ var inProgress = {}; 198 | /******/ var dataWebpackPrefix = "wasm-synth-player:"; 199 | /******/ // loadScript function to load a script via script tag 200 | /******/ __webpack_require__.l = (url, done, key, chunkId) => { 201 | /******/ if(inProgress[url]) { inProgress[url].push(done); return; } 202 | /******/ var script, needAttach; 203 | /******/ if(key !== undefined) { 204 | /******/ var scripts = document.getElementsByTagName("script"); 205 | /******/ for(var i = 0; i < scripts.length; i++) { 206 | /******/ var s = scripts[i]; 207 | /******/ if(s.getAttribute("src") == url || s.getAttribute("data-webpack") == dataWebpackPrefix + key) { script = s; break; } 208 | /******/ } 209 | /******/ } 210 | /******/ if(!script) { 211 | /******/ needAttach = true; 212 | /******/ script = document.createElement('script'); 213 | /******/ 214 | /******/ script.charset = 'utf-8'; 215 | /******/ script.timeout = 120; 216 | /******/ if (__webpack_require__.nc) { 217 | /******/ script.setAttribute("nonce", __webpack_require__.nc); 218 | /******/ } 219 | /******/ script.setAttribute("data-webpack", dataWebpackPrefix + key); 220 | /******/ script.src = url; 221 | /******/ } 222 | /******/ inProgress[url] = [done]; 223 | /******/ var onScriptComplete = (prev, event) => { 224 | /******/ // avoid mem leaks in IE. 225 | /******/ script.onerror = script.onload = null; 226 | /******/ clearTimeout(timeout); 227 | /******/ var doneFns = inProgress[url]; 228 | /******/ delete inProgress[url]; 229 | /******/ script.parentNode && script.parentNode.removeChild(script); 230 | /******/ doneFns && doneFns.forEach((fn) => (fn(event))); 231 | /******/ if(prev) return prev(event); 232 | /******/ } 233 | /******/ ; 234 | /******/ var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); 235 | /******/ script.onerror = onScriptComplete.bind(null, script.onerror); 236 | /******/ script.onload = onScriptComplete.bind(null, script.onload); 237 | /******/ needAttach && document.head.appendChild(script); 238 | /******/ }; 239 | /******/ })(); 240 | /******/ 241 | /******/ /* webpack/runtime/make namespace object */ 242 | /******/ (() => { 243 | /******/ // define __esModule on exports 244 | /******/ __webpack_require__.r = (exports) => { 245 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 246 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 247 | /******/ } 248 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 249 | /******/ }; 250 | /******/ })(); 251 | /******/ 252 | /******/ /* webpack/runtime/publicPath */ 253 | /******/ (() => { 254 | /******/ var scriptUrl; 255 | /******/ if (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + ""; 256 | /******/ var document = __webpack_require__.g.document; 257 | /******/ if (!scriptUrl && document) { 258 | /******/ if (document.currentScript) 259 | /******/ scriptUrl = document.currentScript.src 260 | /******/ if (!scriptUrl) { 261 | /******/ var scripts = document.getElementsByTagName("script"); 262 | /******/ if(scripts.length) scriptUrl = scripts[scripts.length - 1].src 263 | /******/ } 264 | /******/ } 265 | /******/ // When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration 266 | /******/ // or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic. 267 | /******/ if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser"); 268 | /******/ scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/"); 269 | /******/ __webpack_require__.p = scriptUrl; 270 | /******/ })(); 271 | /******/ 272 | /******/ /* webpack/runtime/jsonp chunk loading */ 273 | /******/ (() => { 274 | /******/ // no baseURI 275 | /******/ 276 | /******/ // object to store loaded and loading chunks 277 | /******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched 278 | /******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded 279 | /******/ var installedChunks = { 280 | /******/ "main": 0 281 | /******/ }; 282 | /******/ 283 | /******/ __webpack_require__.f.j = (chunkId, promises) => { 284 | /******/ // JSONP chunk loading for javascript 285 | /******/ var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; 286 | /******/ if(installedChunkData !== 0) { // 0 means "already installed". 287 | /******/ 288 | /******/ // a Promise means "currently loading". 289 | /******/ if(installedChunkData) { 290 | /******/ promises.push(installedChunkData[2]); 291 | /******/ } else { 292 | /******/ if(true) { // all chunks have JS 293 | /******/ // setup Promise in chunk cache 294 | /******/ var promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject])); 295 | /******/ promises.push(installedChunkData[2] = promise); 296 | /******/ 297 | /******/ // start chunk loading 298 | /******/ var url = __webpack_require__.p + __webpack_require__.u(chunkId); 299 | /******/ // create error before stack unwound to get useful stacktrace later 300 | /******/ var error = new Error(); 301 | /******/ var loadingEnded = (event) => { 302 | /******/ if(__webpack_require__.o(installedChunks, chunkId)) { 303 | /******/ installedChunkData = installedChunks[chunkId]; 304 | /******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined; 305 | /******/ if(installedChunkData) { 306 | /******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type); 307 | /******/ var realSrc = event && event.target && event.target.src; 308 | /******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; 309 | /******/ error.name = 'ChunkLoadError'; 310 | /******/ error.type = errorType; 311 | /******/ error.request = realSrc; 312 | /******/ installedChunkData[1](error); 313 | /******/ } 314 | /******/ } 315 | /******/ }; 316 | /******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); 317 | /******/ } else installedChunks[chunkId] = 0; 318 | /******/ } 319 | /******/ } 320 | /******/ }; 321 | /******/ 322 | /******/ // no prefetching 323 | /******/ 324 | /******/ // no preloaded 325 | /******/ 326 | /******/ // no HMR 327 | /******/ 328 | /******/ // no HMR manifest 329 | /******/ 330 | /******/ // no on chunks loaded 331 | /******/ 332 | /******/ // install a JSONP callback for chunk loading 333 | /******/ var webpackJsonpCallback = (parentChunkLoadingFunction, data) => { 334 | /******/ var [chunkIds, moreModules, runtime] = data; 335 | /******/ // add "moreModules" to the modules object, 336 | /******/ // then flag all "chunkIds" as loaded and fire callback 337 | /******/ var moduleId, chunkId, i = 0; 338 | /******/ for(moduleId in moreModules) { 339 | /******/ if(__webpack_require__.o(moreModules, moduleId)) { 340 | /******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; 341 | /******/ } 342 | /******/ } 343 | /******/ if(runtime) var result = runtime(__webpack_require__); 344 | /******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); 345 | /******/ for(;i < chunkIds.length; i++) { 346 | /******/ chunkId = chunkIds[i]; 347 | /******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { 348 | /******/ installedChunks[chunkId][0](); 349 | /******/ } 350 | /******/ installedChunks[chunkIds[i]] = 0; 351 | /******/ } 352 | /******/ 353 | /******/ } 354 | /******/ 355 | /******/ var chunkLoadingGlobal = self["webpackChunkwasm_synth_player"] = self["webpackChunkwasm_synth_player"] || []; 356 | /******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); 357 | /******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); 358 | /******/ })(); 359 | /******/ 360 | /******/ /* webpack/runtime/wasm chunk loading */ 361 | /******/ (() => { 362 | /******/ __webpack_require__.v = (exports, wasmModuleId, wasmModuleHash, importsObj) => { 363 | /******/ var req = fetch(__webpack_require__.p + "" + wasmModuleHash + ".wasm"); 364 | /******/ if (typeof WebAssembly.instantiateStreaming === 'function') { 365 | /******/ return WebAssembly.instantiateStreaming(req, importsObj) 366 | /******/ .then((res) => (Object.assign(exports, res.instance.exports))); 367 | /******/ } 368 | /******/ return req 369 | /******/ .then((x) => (x.arrayBuffer())) 370 | /******/ .then((bytes) => (WebAssembly.instantiate(bytes, importsObj))) 371 | /******/ .then((res) => (Object.assign(exports, res.instance.exports))); 372 | /******/ }; 373 | /******/ })(); 374 | /******/ 375 | /************************************************************************/ 376 | /******/ 377 | /******/ // startup 378 | /******/ // Load entry module and return exports 379 | /******/ // This entry module can't be inlined because the eval devtool is used. 380 | /******/ var __webpack_exports__ = __webpack_require__("./src/js/bootstrap.js"); 381 | /******/ 382 | /******/ })() 383 | ; -------------------------------------------------------------------------------- /docs/images/ogp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h1romas4/rust-synth-emulation/c89b88c450e704c96c2e2c8a707eea93f04b46aa/docs/images/ogp.jpg -------------------------------------------------------------------------------- /docs/images/ogp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h1romas4/rust-synth-emulation/c89b88c450e704c96c2e2c8a707eea93f04b46aa/docs/images/ogp.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Synth emurator by Rust/WebAssembly 18 | 19 | 20 | Fork me on GitHub 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | body { 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | overflow: hidden; 11 | margin: 0; 12 | padding: 0; 13 | background-color: black; 14 | } 15 | 16 | canvas { 17 | display: block; 18 | background-color: black; 19 | } 20 | -------------------------------------------------------------------------------- /docs/vgm/sn76489.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h1romas4/rust-synth-emulation/c89b88c450e704c96c2e2c8a707eea93f04b46aa/docs/vgm/sn76489.vgm -------------------------------------------------------------------------------- /docs/vgm/ym2612.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h1romas4/rust-synth-emulation/c89b88c450e704c96c2e2c8a707eea93f04b46aa/docs/vgm/ym2612.vgm -------------------------------------------------------------------------------- /synth-emulator/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | target/ 3 | -------------------------------------------------------------------------------- /synth-emulator/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug original version 'csynth-emulator'", 11 | "program": "${workspaceFolder}/build/bin/vgmplay", 12 | "args": [], 13 | "cwd": "${workspaceFolder}/build" 14 | }, 15 | { 16 | "type": "lldb", 17 | "request": "launch", 18 | "name": "Debug Rust version 'synth-emulator'", 19 | "cargo": { 20 | "args": [ 21 | "test", 22 | "--no-run", 23 | "--lib", 24 | "--package=synth-emulator" 25 | ], 26 | "filter": { 27 | "name": "synth-emulator", 28 | "kind": "lib" 29 | } 30 | }, 31 | "args": [], 32 | "cwd": "${workspaceFolder}" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /synth-emulator/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "C_Cpp.default.configurationProvider": "vector-of-bool.cmake-tools" 3 | } -------------------------------------------------------------------------------- /synth-emulator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | set(CMAKE_C_COMPILER "/usr/bin/clang" CACHE string "clang compiler" FORCE) 3 | set(CMAKE_CXX_COMPILER "/usr/bin/clang++" CACHE string "clang++ compiler" FORCE) 4 | 5 | include(./cmake/macros.cmake) 6 | project(vgmplay C CXX) 7 | 8 | header_directories(./src/c) 9 | 10 | add_source_files( 11 | ./src/c/panning.c 12 | ./src/c/sn76489.c 13 | ./src/c/vgmplay.c 14 | ./src/c/ym3438.c 15 | ./src/c/pwm.c 16 | ) 17 | 18 | add_compile_flags(C 19 | -std=gnu11 20 | -g 21 | ) 22 | 23 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ./bin) 24 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ./bin) 25 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ./bin) 26 | 27 | add_executable(${PROJECT_NAME} ${SOURCE_FILES}) 28 | 29 | target_link_libraries(${PROJECT_NAME} m) 30 | target_link_libraries(${PROJECT_NAME} gcc) 31 | -------------------------------------------------------------------------------- /synth-emulator/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "array-macro" 13 | version = "1.0.5" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "06e97b4e522f9e55523001238ac59d13a8603af57f69980de5d8de4bbbe8ada6" 16 | 17 | [[package]] 18 | name = "arrayvec" 19 | version = "0.5.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 22 | 23 | [[package]] 24 | name = "autocfg" 25 | version = "1.0.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 28 | 29 | [[package]] 30 | name = "bitflags" 31 | version = "1.2.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 34 | 35 | [[package]] 36 | name = "cfg-if" 37 | version = "1.0.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 40 | 41 | [[package]] 42 | name = "crc32fast" 43 | version = "1.2.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" 46 | dependencies = [ 47 | "cfg-if", 48 | ] 49 | 50 | [[package]] 51 | name = "flate2" 52 | version = "1.0.20" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" 55 | dependencies = [ 56 | "cfg-if", 57 | "crc32fast", 58 | "libc", 59 | "miniz_oxide", 60 | ] 61 | 62 | [[package]] 63 | name = "itoa" 64 | version = "0.4.7" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 67 | 68 | [[package]] 69 | name = "lexical-core" 70 | version = "0.7.6" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" 73 | dependencies = [ 74 | "arrayvec", 75 | "bitflags", 76 | "cfg-if", 77 | "ryu", 78 | "static_assertions", 79 | ] 80 | 81 | [[package]] 82 | name = "libc" 83 | version = "0.2.97" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" 86 | 87 | [[package]] 88 | name = "memchr" 89 | version = "2.4.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" 92 | 93 | [[package]] 94 | name = "miniz_oxide" 95 | version = "0.4.4" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 98 | dependencies = [ 99 | "adler", 100 | "autocfg", 101 | ] 102 | 103 | [[package]] 104 | name = "nom" 105 | version = "5.1.2" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" 108 | dependencies = [ 109 | "lexical-core", 110 | "memchr", 111 | "version_check", 112 | ] 113 | 114 | [[package]] 115 | name = "proc-macro2" 116 | version = "1.0.27" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" 119 | dependencies = [ 120 | "unicode-xid", 121 | ] 122 | 123 | [[package]] 124 | name = "quote" 125 | version = "1.0.9" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 128 | dependencies = [ 129 | "proc-macro2", 130 | ] 131 | 132 | [[package]] 133 | name = "ryu" 134 | version = "1.0.5" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 137 | 138 | [[package]] 139 | name = "serde" 140 | version = "1.0.126" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" 143 | 144 | [[package]] 145 | name = "serde_derive" 146 | version = "1.0.126" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" 149 | dependencies = [ 150 | "proc-macro2", 151 | "quote", 152 | "syn", 153 | ] 154 | 155 | [[package]] 156 | name = "serde_json" 157 | version = "1.0.64" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" 160 | dependencies = [ 161 | "itoa", 162 | "ryu", 163 | "serde", 164 | ] 165 | 166 | [[package]] 167 | name = "static_assertions" 168 | version = "1.1.0" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 171 | 172 | [[package]] 173 | name = "syn" 174 | version = "1.0.73" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" 177 | dependencies = [ 178 | "proc-macro2", 179 | "quote", 180 | "unicode-xid", 181 | ] 182 | 183 | [[package]] 184 | name = "synth-emulator" 185 | version = "0.3.0" 186 | dependencies = [ 187 | "array-macro", 188 | "flate2", 189 | "nom", 190 | "serde", 191 | "serde_derive", 192 | "serde_json", 193 | ] 194 | 195 | [[package]] 196 | name = "unicode-xid" 197 | version = "0.2.2" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 200 | 201 | [[package]] 202 | name = "version_check" 203 | version = "0.9.3" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 206 | -------------------------------------------------------------------------------- /synth-emulator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "synth-emulator" 3 | version = "0.3.0" 4 | authors = ["Hiromasa Tanaka "] 5 | edition = "2018" 6 | 7 | [lib] 8 | path = "src/rust/lib.rs" 9 | 10 | [dependencies] 11 | flate2 = "1.0" 12 | nom = "5" 13 | serde = "1" 14 | serde_json = "1" 15 | serde_derive = "1" 16 | array-macro = "1.0" 17 | -------------------------------------------------------------------------------- /synth-emulator/cmake/macros.cmake: -------------------------------------------------------------------------------- 1 | macro(global_set Name Value) 2 | # message("set ${Name} to " ${ARGN}) 3 | set(${Name} "${Value}" CACHE STRING "NoDesc" FORCE) 4 | endmacro() 5 | 6 | macro(condition_set Name Value) 7 | if (NOT ${Name}) 8 | global_set(${Name} ${Value}) 9 | else () 10 | # message("exists ${Name} is " ${ARGN}) 11 | endif () 12 | endmacro() 13 | 14 | 15 | set(SOURCE_FILES "" CACHE STRING "Source Files" FORCE) 16 | macro(add_source_files) 17 | # message(" + add_source_files ${ARGN}") 18 | file(GLOB_RECURSE newlist ${ARGN}) 19 | 20 | foreach (filepath ${newlist}) 21 | string(FIND ${filepath} ${CMAKE_BINARY_DIR} found) 22 | if (NOT found EQUAL 0) 23 | set(SOURCE_FILES ${SOURCE_FILES} ${filepath} CACHE STRING "Source Files" FORCE) 24 | endif () 25 | endforeach () 26 | endmacro() 27 | 28 | function(JOIN VALUES GLUE OUTPUT) 29 | string(REGEX REPLACE "([^\\]|^);" "\\1${GLUE}" _TMP_STR "${VALUES}") 30 | string(REGEX REPLACE "[\\](.)" "\\1" _TMP_STR "${_TMP_STR}") #fixes escaping 31 | set(${OUTPUT} "${_TMP_STR}" PARENT_SCOPE) 32 | endfunction() 33 | 34 | global_set(LDFLAGS "") 35 | global_set(CMAKE_EXE_LINKER_FLAGS "") 36 | global_set(CMAKE_SHARED_LINKER_FLAGS "") 37 | global_set(CMAKE_MODULE_LINKER_FLAGS "") 38 | 39 | function(removeDuplicateSubstring stringIn stringOut) 40 | separate_arguments(stringIn) 41 | list(REMOVE_DUPLICATES stringIn) 42 | string(REPLACE ";" " " stringIn "${stringIn}") 43 | set(${stringOut} "${stringIn}" PARENT_SCOPE) 44 | endfunction() 45 | 46 | macro(add_compile_flags WHERE) 47 | JOIN("${ARGN}" " " STRING_ARGS) 48 | if (${WHERE} STREQUAL C) 49 | global_set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${STRING_ARGS}") 50 | 51 | elseif (${WHERE} STREQUAL CXX) 52 | global_set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${STRING_ARGS}") 53 | 54 | elseif (${WHERE} STREQUAL LD) 55 | global_set(LDFLAGS "${LDFLAGS} ${STRING_ARGS}") 56 | global_set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${STRING_ARGS}") 57 | global_set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${STRING_ARGS}") 58 | global_set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${STRING_ARGS}") 59 | 60 | elseif (${WHERE} STREQUAL BOTH) 61 | add_compile_flags(C ${ARGN}) 62 | add_compile_flags(CXX ${ARGN}) 63 | 64 | elseif (${WHERE} STREQUAL ALL) 65 | add_compile_flags(C ${ARGN}) 66 | add_compile_flags(CXX ${ARGN}) 67 | add_compile_flags(LD ${ARGN}) 68 | 69 | else () 70 | message(FATAL_ERROR "add_compile_flags - only support: C, CXX, BOTH, LD, ALL") 71 | endif () 72 | endmacro() 73 | 74 | # Add lib headers 75 | macro(header_directories parent) 76 | file(GLOB_RECURSE newList ${parent}/*.h) 77 | set(dir_list "") 78 | foreach (file_path ${newList}) 79 | get_filename_component(dir_path ${file_path} DIRECTORY) 80 | set(dir_list ${dir_list} ${dir_path}) 81 | endforeach () 82 | list(REMOVE_DUPLICATES dir_list) 83 | 84 | include_directories(${dir_list}) 85 | endmacro() 86 | -------------------------------------------------------------------------------- /synth-emulator/src/c/mamedef.h: -------------------------------------------------------------------------------- 1 | #ifndef __MAMEDEF_H__ 2 | #define __MAMEDEF_H__ 3 | 4 | // typedefs to use MAME's (U)INTxx types (copied from MAME\src\ods\odscomm.h) 5 | /* 8-bit values */ 6 | typedef unsigned char UINT8; 7 | typedef signed char INT8; 8 | 9 | /* 16-bit values */ 10 | typedef unsigned short UINT16; 11 | typedef signed short INT16; 12 | 13 | /* 32-bit values */ 14 | #ifndef _WINDOWS_H 15 | typedef unsigned int UINT32; 16 | typedef signed int INT32; 17 | #endif 18 | 19 | /* 64-bit values */ 20 | #ifndef _WINDOWS_H 21 | #ifdef _MSC_VER 22 | typedef signed __int64 INT64; 23 | typedef unsigned __int64 UINT64; 24 | #else 25 | __extension__ typedef unsigned long long UINT64; 26 | __extension__ typedef signed long long INT64; 27 | #endif 28 | #endif 29 | 30 | /* offsets and addresses are 32-bit (for now...) */ 31 | typedef UINT32 offs_t; 32 | 33 | /* stream_sample_t is used to represent a single sample in a sound stream */ 34 | typedef INT32 stream_sample_t; 35 | 36 | #if defined(VGM_BIG_ENDIAN) 37 | #define BYTE_XOR_BE(x) (x) 38 | #elif defined(VGM_LITTLE_ENDIAN) 39 | #define BYTE_XOR_BE(x) ((x) ^ 0x01) 40 | #else 41 | // don't define BYTE_XOR_BE so that it throws an error when compiling 42 | #endif 43 | 44 | #if defined(_MSC_VER) 45 | //#define INLINE static __forceinline 46 | #define INLINE static __inline 47 | #elif defined(__GNUC__) 48 | #define INLINE static __inline__ 49 | #else 50 | #define INLINE static inline 51 | #endif 52 | #ifndef M_PI 53 | #define M_PI 3.14159265358979323846 54 | #endif 55 | 56 | #ifdef _DEBUG 57 | #define logerror printf 58 | #else 59 | #define logerror 60 | #endif 61 | 62 | extern stream_sample_t* DUMMYBUF[]; 63 | 64 | typedef void (*SRATE_CALLBACK)(void*, UINT32); 65 | 66 | #endif // __MAMEDEF_H__ 67 | -------------------------------------------------------------------------------- /synth-emulator/src/c/panning.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "panning.h" 4 | 5 | #ifndef PI 6 | #define PI 3.14159265359 7 | #endif 8 | #ifndef SQRT2 9 | #define SQRT2 1.414213562 10 | #endif 11 | #define RANGE 512 12 | 13 | //----------------------------------------------------------------- 14 | // Set the panning values for the two stereo channels (L,R) 15 | // for a position -256..0..256 L..C..R 16 | //----------------------------------------------------------------- 17 | void calc_panning(float channels[2], int position) 18 | { 19 | if ( position > RANGE / 2 ) 20 | position = RANGE / 2; 21 | else if ( position < -RANGE / 2 ) 22 | position = -RANGE / 2; 23 | position += RANGE / 2; // make -256..0..256 -> 0..256..512 24 | 25 | // Equal power law: equation is 26 | // right = sin( position / range * pi / 2) * sqrt( 2 ) 27 | // left is equivalent to right with position = range - position 28 | // position is in the range 0 .. RANGE 29 | // RANGE / 2 = centre, result = 1.0f 30 | channels[1] = (float)( sin( (double)position / RANGE * PI / 2 ) * SQRT2 ); 31 | position = RANGE - position; 32 | channels[0] = (float)( sin( (double)position / RANGE * PI / 2 ) * SQRT2 ); 33 | } 34 | 35 | //----------------------------------------------------------------- 36 | // Reset the panning values to the centre position 37 | //----------------------------------------------------------------- 38 | void centre_panning(float channels[2]) 39 | { 40 | channels[0] = channels[1] = 1.0f; 41 | } 42 | 43 | /*//----------------------------------------------------------------- 44 | // Generate a stereo position in the range 0..RANGE 45 | // with Gaussian distribution, mean RANGE/2, S.D. RANGE/5 46 | //----------------------------------------------------------------- 47 | int random_stereo() 48 | { 49 | int n = (int)(RANGE/2 + gauss_rand() * (RANGE * 0.2) ); 50 | if ( n > RANGE ) n = RANGE; 51 | if ( n < 0 ) n = 0; 52 | return n; 53 | } 54 | 55 | //----------------------------------------------------------------- 56 | // Generate a Gaussian random number with mean 0, variance 1 57 | // Copied from an ancient C newsgroup FAQ 58 | //----------------------------------------------------------------- 59 | double gauss_rand() 60 | { 61 | static double V1, V2, S; 62 | static int phase = 0; 63 | double X; 64 | 65 | if(phase == 0) { 66 | do { 67 | double U1 = (double)rand() / RAND_MAX; 68 | double U2 = (double)rand() / RAND_MAX; 69 | 70 | V1 = 2 * U1 - 1; 71 | V2 = 2 * U2 - 1; 72 | S = V1 * V1 + V2 * V2; 73 | } while(S >= 1 || S == 0); 74 | 75 | X = V1 * sqrt(-2 * log(S) / S); 76 | } else 77 | X = V2 * sqrt(-2 * log(S) / S); 78 | 79 | phase = 1 - phase; 80 | 81 | return X; 82 | }*/ 83 | -------------------------------------------------------------------------------- /synth-emulator/src/c/panning.h: -------------------------------------------------------------------------------- 1 | /* 2 | panning.h by Maxim in 2006 3 | Implements "simple equal power" panning using sine distribution 4 | I am not an expert on this stuff, but this is the best sounding of the methods I've tried 5 | */ 6 | 7 | #ifndef PANNING_H 8 | #define PANNING_H 9 | 10 | void calc_panning(float channels[2], int position); 11 | void centre_panning(float channels[2]); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /synth-emulator/src/c/pwm.c: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Gens: PWM audio emulator. * 3 | * * 4 | * Copyright (c) 1999-2002 by Stéphane Dallongeville * 5 | * Copyright (c) 2003-2004 by Stéphane Akhoun * 6 | * Copyright (c) 2008-2009 by David Korth * 7 | * * 8 | * This program is free software; you can redistribute it and/or modify it * 9 | * under the terms of the GNU General Public License as published by the * 10 | * Free Software Foundation; either version 2 of the License, or (at your * 11 | * option) any later version. * 12 | * * 13 | * This program is distributed in the hope that it will be useful, but * 14 | * WITHOUT ANY WARRANTY; without even the implied warranty of * 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 16 | * GNU General Public License for more details. * 17 | * * 18 | * You should have received a copy of the GNU General Public License along * 19 | * with this program; if not, write to the Free Software Foundation, Inc., * 20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 21 | ***************************************************************************/ 22 | 23 | #include "mamedef.h" 24 | #include "pwm.h" 25 | 26 | #include 27 | 28 | //#include "gens_core/mem/mem_sh2.h" 29 | //#include "gens_core/cpu/sh2/sh2.h" 30 | 31 | #define CHILLY_WILLY_SCALE 1 32 | 33 | #if PWM_BUF_SIZE == 8 34 | unsigned char PWM_FULL_TAB[PWM_BUF_SIZE * PWM_BUF_SIZE] = 35 | { 36 | 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 37 | 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 38 | 0x00, 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 39 | 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 40 | 0x00, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x00, 41 | 0x00, 0x00, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 42 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40, 0x00, 43 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40, 44 | }; 45 | #elif PWM_BUF_SIZE == 4 46 | unsigned char PWM_FULL_TAB[PWM_BUF_SIZE * PWM_BUF_SIZE] = 47 | { 48 | 0x40, 0x00, 0x00, 0x80, 49 | 0x80, 0x40, 0x00, 0x00, 50 | 0x00, 0x80, 0x40, 0x00, 51 | 0x00, 0x00, 0x80, 0x40, 52 | }; 53 | #else 54 | #error PWM_BUF_SIZE must equal 4 or 8. 55 | #endif /* PWM_BUF_SIZE */ 56 | 57 | typedef struct _pwm_chip 58 | { 59 | unsigned short PWM_FIFO_R[8]; 60 | unsigned short PWM_FIFO_L[8]; 61 | unsigned int PWM_RP_R; 62 | unsigned int PWM_WP_R; 63 | unsigned int PWM_RP_L; 64 | unsigned int PWM_WP_L; 65 | unsigned int PWM_Cycles; 66 | unsigned int PWM_Cycle; 67 | unsigned int PWM_Cycle_Cnt; 68 | unsigned int PWM_Int; 69 | unsigned int PWM_Int_Cnt; 70 | unsigned int PWM_Mode; 71 | //unsigned int PWM_Enable; 72 | unsigned int PWM_Out_R; 73 | unsigned int PWM_Out_L; 74 | 75 | unsigned int PWM_Cycle_Tmp; 76 | unsigned int PWM_Cycles_Tmp; 77 | unsigned int PWM_Int_Tmp; 78 | unsigned int PWM_FIFO_L_Tmp; 79 | unsigned int PWM_FIFO_R_Tmp; 80 | 81 | #if CHILLY_WILLY_SCALE 82 | // TODO: Fix Chilly Willy's new scaling algorithm. 83 | /* PWM scaling variables. */ 84 | int PWM_Offset; 85 | int PWM_Scale; 86 | //int PWM_Loudness; 87 | #endif 88 | 89 | int clock; 90 | } pwm_chip; 91 | #if CHILLY_WILLY_SCALE 92 | // TODO: Fix Chilly Willy's new scaling algorithm. 93 | #define PWM_Loudness 0 94 | #endif 95 | 96 | void PWM_Init(pwm_chip* chip); 97 | void PWM_Recalc_Scale(pwm_chip* chip); 98 | 99 | void PWM_Set_Cycle(pwm_chip* chip, unsigned int cycle); 100 | void PWM_Set_Int(pwm_chip* chip, unsigned int int_time); 101 | 102 | void PWM_Update(pwm_chip* chip, int **buf, int length); 103 | 104 | 105 | // extern UINT8 CHIP_SAMPLING_MODE; 106 | // extern INT32 CHIP_SAMPLE_RATE; 107 | UINT8 CHIP_SAMPLING_MODE = 0x00; 108 | INT32 CHIP_SAMPLE_RATE = 44100; 109 | #define MAX_CHIPS 0x02 110 | static pwm_chip PWM_Chip[MAX_CHIPS]; 111 | 112 | /** 113 | * PWM_Init(): Initialize the PWM audio emulator. 114 | */ 115 | void PWM_Init(pwm_chip* chip) 116 | { 117 | chip->PWM_Mode = 0; 118 | chip->PWM_Out_R = 0; 119 | chip->PWM_Out_L = 0; 120 | 121 | memset(chip->PWM_FIFO_R, 0x00, sizeof(chip->PWM_FIFO_R)); 122 | memset(chip->PWM_FIFO_L, 0x00, sizeof(chip->PWM_FIFO_L)); 123 | 124 | chip->PWM_RP_R = 0; 125 | chip->PWM_WP_R = 0; 126 | chip->PWM_RP_L = 0; 127 | chip->PWM_WP_L = 0; 128 | chip->PWM_Cycle_Tmp = 0; 129 | chip->PWM_Int_Tmp = 0; 130 | chip->PWM_FIFO_L_Tmp = 0; 131 | chip->PWM_FIFO_R_Tmp = 0; 132 | 133 | //PWM_Loudness = 0; 134 | PWM_Set_Cycle(chip, 0); 135 | PWM_Set_Int(chip, 0); 136 | } 137 | 138 | 139 | #if CHILLY_WILLY_SCALE 140 | // TODO: Fix Chilly Willy's new scaling algorithm. 141 | void PWM_Recalc_Scale(pwm_chip* chip) 142 | { 143 | chip->PWM_Offset = (chip->PWM_Cycle / 2) + 1; 144 | chip->PWM_Scale = 0x7FFF00 / chip->PWM_Offset; 145 | } 146 | #endif 147 | 148 | 149 | void PWM_Set_Cycle(pwm_chip* chip, unsigned int cycle) 150 | { 151 | cycle--; 152 | chip->PWM_Cycle = (cycle & 0xFFF); 153 | chip->PWM_Cycle_Cnt = chip->PWM_Cycles; 154 | 155 | #if CHILLY_WILLY_SCALE 156 | // TODO: Fix Chilly Willy's new scaling algorithm. 157 | PWM_Recalc_Scale(chip); 158 | #endif 159 | } 160 | 161 | 162 | void PWM_Set_Int(pwm_chip* chip, unsigned int int_time) 163 | { 164 | int_time &= 0x0F; 165 | if (int_time) 166 | chip->PWM_Int = chip->PWM_Int_Cnt = int_time; 167 | else 168 | chip->PWM_Int = chip->PWM_Int_Cnt = 16; 169 | } 170 | 171 | 172 | void PWM_Clear_Timer(pwm_chip* chip) 173 | { 174 | chip->PWM_Cycle_Cnt = 0; 175 | } 176 | 177 | 178 | /** 179 | * PWM_SHIFT(): Shift PWM data. 180 | * @param src: Channel (L or R) with the source data. 181 | * @param dest Channel (L or R) for the destination. 182 | */ 183 | #define PWM_SHIFT(src, dest) \ 184 | { \ 185 | /* Make sure the source FIFO isn't empty. */ \ 186 | if (PWM_RP_##src != PWM_WP_##src) \ 187 | { \ 188 | /* Get destination channel output from the source channel FIFO. */ \ 189 | PWM_Out_##dest = PWM_FIFO_##src[PWM_RP_##src]; \ 190 | \ 191 | /* Increment the source channel read pointer, resetting to 0 if it overflows. */ \ 192 | PWM_RP_##src = (PWM_RP_##src + 1) & (PWM_BUF_SIZE - 1); \ 193 | } \ 194 | } 195 | 196 | 197 | /*static void PWM_Shift_Data(void) 198 | { 199 | switch (PWM_Mode & 0x0F) 200 | { 201 | case 0x01: 202 | case 0x0D: 203 | // Rx_LL: Right -> Ignore, Left -> Left 204 | PWM_SHIFT(L, L); 205 | break; 206 | 207 | case 0x02: 208 | case 0x0E: 209 | // Rx_LR: Right -> Ignore, Left -> Right 210 | PWM_SHIFT(L, R); 211 | break; 212 | 213 | case 0x04: 214 | case 0x07: 215 | // RL_Lx: Right -> Left, Left -> Ignore 216 | PWM_SHIFT(R, L); 217 | break; 218 | 219 | case 0x05: 220 | case 0x09: 221 | // RR_LL: Right -> Right, Left -> Left 222 | PWM_SHIFT(L, L); 223 | PWM_SHIFT(R, R); 224 | break; 225 | 226 | case 0x06: 227 | case 0x0A: 228 | // RL_LR: Right -> Left, Left -> Right 229 | PWM_SHIFT(L, R); 230 | PWM_SHIFT(R, L); 231 | break; 232 | 233 | case 0x08: 234 | case 0x0B: 235 | // RR_Lx: Right -> Right, Left -> Ignore 236 | PWM_SHIFT(R, R); 237 | break; 238 | 239 | case 0x00: 240 | case 0x03: 241 | case 0x0C: 242 | case 0x0F: 243 | default: 244 | // Rx_Lx: Right -> Ignore, Left -> Ignore 245 | break; 246 | } 247 | } 248 | 249 | 250 | void PWM_Update_Timer(unsigned int cycle) 251 | { 252 | // Don't do anything if PWM is disabled in the Sound menu. 253 | 254 | // Don't do anything if PWM isn't active. 255 | if ((PWM_Mode & 0x0F) == 0x00) 256 | return; 257 | 258 | if (PWM_Cycle == 0x00 || (PWM_Cycle_Cnt > cycle)) 259 | return; 260 | 261 | PWM_Shift_Data(); 262 | 263 | PWM_Cycle_Cnt += PWM_Cycle; 264 | 265 | PWM_Int_Cnt--; 266 | if (PWM_Int_Cnt == 0) 267 | { 268 | PWM_Int_Cnt = PWM_Int; 269 | 270 | if (PWM_Mode & 0x0080) 271 | { 272 | // RPT => generate DREQ1 as well as INT 273 | SH2_DMA1_Request(&M_SH2, 1); 274 | SH2_DMA1_Request(&S_SH2, 1); 275 | } 276 | 277 | if (_32X_MINT & 1) 278 | SH2_Interrupt(&M_SH2, 6); 279 | if (_32X_SINT & 1) 280 | SH2_Interrupt(&S_SH2, 6); 281 | } 282 | }*/ 283 | 284 | 285 | INLINE int PWM_Update_Scale(pwm_chip* chip, int PWM_In) 286 | { 287 | if (PWM_In == 0) 288 | return 0; 289 | 290 | // TODO: Chilly Willy's new scaling algorithm breaks drx's Sonic 1 32X (with PWM drums). 291 | #ifdef CHILLY_WILLY_SCALE 292 | //return (((PWM_In & 0xFFF) - chip->PWM_Offset) * chip->PWM_Scale) >> (8 - PWM_Loudness); 293 | // Knuckles' Chaotix: Tachy Touch uses the values 0xF?? for negative values 294 | // This small modification fixes the terrible pops. 295 | PWM_In &= 0xFFF; 296 | if (PWM_In & 0x800) 297 | PWM_In |= ~0xFFF; 298 | return ((PWM_In - chip->PWM_Offset) * chip->PWM_Scale) >> (8 - PWM_Loudness); 299 | #else 300 | const int PWM_adjust = ((chip->PWM_Cycle >> 1) + 1); 301 | int PWM_Ret = ((chip->PWM_In & 0xFFF) - PWM_adjust); 302 | 303 | // Increase PWM volume so it's audible. 304 | PWM_Ret <<= (5+2); 305 | 306 | // Make sure the PWM isn't oversaturated. 307 | if (PWM_Ret > 32767) 308 | PWM_Ret = 32767; 309 | else if (PWM_Ret < -32768) 310 | PWM_Ret = -32768; 311 | 312 | return PWM_Ret; 313 | #endif 314 | } 315 | 316 | 317 | void PWM_Update(pwm_chip* chip, int **buf, int length) 318 | { 319 | int tmpOutL; 320 | int tmpOutR; 321 | int i; 322 | 323 | //if (!PWM_Enable) 324 | // return; 325 | 326 | if (chip->PWM_Out_L == 0 && chip->PWM_Out_R == 0) 327 | { 328 | memset(buf[0], 0x00, length * sizeof(int)); 329 | memset(buf[1], 0x00, length * sizeof(int)); 330 | return; 331 | } 332 | 333 | // New PWM scaling algorithm provided by Chilly Willy on the Sonic Retro forums. 334 | tmpOutL = PWM_Update_Scale(chip, (int)chip->PWM_Out_L); 335 | tmpOutR = PWM_Update_Scale(chip, (int)chip->PWM_Out_R); 336 | 337 | for (i = 0; i < length; i ++) 338 | { 339 | buf[0][i] = tmpOutL; 340 | buf[1][i] = tmpOutR; 341 | } 342 | } 343 | 344 | 345 | // void pwm_update(UINT8 ChipID, stream_sample_t **outputs, int samples) 346 | void pwm_update(UINT8 ChipID, int **outputs, int samples) 347 | { 348 | pwm_chip *chip = &PWM_Chip[ChipID]; 349 | 350 | PWM_Update(chip, outputs, samples); 351 | } 352 | 353 | int device_start_pwm(UINT8 ChipID, int clock) 354 | { 355 | /* allocate memory for the chip */ 356 | //pwm_state *chip = get_safe_token(device); 357 | pwm_chip *chip; 358 | int rate; 359 | 360 | if (ChipID >= MAX_CHIPS) 361 | return 0; 362 | 363 | chip = &PWM_Chip[ChipID]; 364 | rate = 22020; // that's the rate the PWM is mostly used 365 | if (((CHIP_SAMPLING_MODE & 0x01) && rate < CHIP_SAMPLE_RATE) || 366 | CHIP_SAMPLING_MODE == 0x02) 367 | rate = CHIP_SAMPLE_RATE; 368 | chip->clock = clock; 369 | 370 | PWM_Init(chip); 371 | /* allocate the stream */ 372 | //chip->stream = stream_create(device, 0, 2, device->clock / 384, chip, rf5c68_update); 373 | 374 | return rate; 375 | } 376 | 377 | void device_stop_pwm(UINT8 ChipID) 378 | { 379 | //pwm_chip *chip = &PWM_Chip[ChipID]; 380 | //free(chip->ram); 381 | 382 | return; 383 | } 384 | 385 | void device_reset_pwm(UINT8 ChipID) 386 | { 387 | pwm_chip *chip = &PWM_Chip[ChipID]; 388 | PWM_Init(chip); 389 | } 390 | 391 | void pwm_chn_w(UINT8 ChipID, UINT8 Channel, UINT16 data) 392 | { 393 | pwm_chip *chip = &PWM_Chip[ChipID]; 394 | 395 | if (chip->clock == 1) 396 | { // old-style commands 397 | switch(Channel) 398 | { 399 | case 0x00: 400 | chip->PWM_Out_L = data; 401 | break; 402 | case 0x01: 403 | chip->PWM_Out_R = data; 404 | break; 405 | case 0x02: 406 | PWM_Set_Cycle(chip, data); 407 | break; 408 | case 0x03: 409 | chip->PWM_Out_L = data; 410 | chip->PWM_Out_R = data; 411 | break; 412 | } 413 | } 414 | else 415 | { 416 | switch(Channel) 417 | { 418 | case 0x00/2: // control register 419 | PWM_Set_Int(chip, data >> 8); 420 | break; 421 | case 0x02/2: // cycle register 422 | PWM_Set_Cycle(chip, data); 423 | break; 424 | case 0x04/2: // l ch 425 | chip->PWM_Out_L = data; 426 | break; 427 | case 0x06/2: // r ch 428 | chip->PWM_Out_R = data; 429 | if (! chip->PWM_Mode) 430 | { 431 | if (chip->PWM_Out_L == chip->PWM_Out_R) 432 | { 433 | // fixes these terrible pops when 434 | // starting/stopping/pausing the song 435 | chip->PWM_Offset = data; 436 | chip->PWM_Mode = 0x01; 437 | } 438 | } 439 | break; 440 | case 0x08/2: // mono ch 441 | chip->PWM_Out_L = data; 442 | chip->PWM_Out_R = data; 443 | if (! chip->PWM_Mode) 444 | { 445 | chip->PWM_Offset = data; 446 | chip->PWM_Mode = 0x01; 447 | } 448 | break; 449 | } 450 | } 451 | 452 | return; 453 | } 454 | -------------------------------------------------------------------------------- /synth-emulator/src/c/pwm.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Gens: PWM audio emulator. * 3 | * * 4 | * Copyright (c) 1999-2002 by Stéphane Dallongeville * 5 | * Copyright (c) 2003-2004 by Stéphane Akhoun * 6 | * Copyright (c) 2008 by David Korth * 7 | * * 8 | * This program is free software; you can redistribute it and/or modify it * 9 | * under the terms of the GNU General Public License as published by the * 10 | * Free Software Foundation; either version 2 of the License, or (at your * 11 | * option) any later version. * 12 | * * 13 | * This program is distributed in the hope that it will be useful, but * 14 | * WITHOUT ANY WARRANTY; without even the implied warranty of * 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 16 | * GNU General Public License for more details. * 17 | * * 18 | * You should have received a copy of the GNU General Public License along * 19 | * with this program; if not, write to the Free Software Foundation, Inc., * 20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 21 | ***************************************************************************/ 22 | 23 | #define PWM_BUF_SIZE 4 24 | /*extern unsigned char PWM_FULL_TAB[PWM_BUF_SIZE * PWM_BUF_SIZE]; 25 | 26 | extern unsigned short PWM_FIFO_R[8]; 27 | extern unsigned short PWM_FIFO_L[8]; 28 | extern unsigned int PWM_RP_R; 29 | extern unsigned int PWM_WP_R; 30 | extern unsigned int PWM_RP_L; 31 | extern unsigned int PWM_WP_L; 32 | extern unsigned int PWM_Cycles; 33 | extern unsigned int PWM_Cycle; 34 | extern unsigned int PWM_Cycle_Cnt; 35 | extern unsigned int PWM_Int; 36 | extern unsigned int PWM_Int_Cnt; 37 | extern unsigned int PWM_Mode; 38 | extern unsigned int PWM_Enable; 39 | extern unsigned int PWM_Out_R; 40 | extern unsigned int PWM_Out_L;*/ 41 | 42 | //void PWM_Init(void); 43 | //void PWM_Recalc_Scale(void); 44 | 45 | /* Functions called by x86 asm. */ 46 | //void PWM_Set_Cycle(unsigned int cycle); 47 | //void PWM_Set_Int(unsigned int int_time); 48 | 49 | /* Functions called by C/C++ code only. */ 50 | //void PWM_Clear_Timer(void); 51 | //void PWM_Update_Timer(unsigned int cycle); 52 | //void PWM_Update(int **buf, int length); 53 | 54 | 55 | // void pwm_update(UINT8 ChipID, stream_sample_t **outputs, int samples); 56 | void pwm_update(UINT8 ChipID, int **outputs, int samples); 57 | 58 | int device_start_pwm(UINT8 ChipID, int clock); 59 | void device_stop_pwm(UINT8 ChipID); 60 | void device_reset_pwm(UINT8 ChipID); 61 | 62 | void pwm_chn_w(UINT8 ChipID, UINT8 Channel, UINT16 data); 63 | -------------------------------------------------------------------------------- /synth-emulator/src/c/sn76489.c: -------------------------------------------------------------------------------- 1 | /* 2 | SN76489 emulation 3 | by Maxim in 2001 and 2002 4 | converted from my original Delphi implementation 5 | 6 | I'm a C newbie so I'm sure there are loads of stupid things 7 | in here which I'll come back to some day and redo 8 | 9 | Includes: 10 | - Super-high quality tone channel "oversampling" by calculating fractional positions on transitions 11 | - Noise output pattern reverse engineered from actual SMS output 12 | - Volume levels taken from actual SMS output 13 | 14 | 07/08/04 Charles MacDonald 15 | Modified for use with SMS Plus: 16 | - Added support for multiple PSG chips. 17 | - Added reset/config/update routines. 18 | - Added context management routines. 19 | - Removed SN76489_GetValues(). 20 | - Removed some unused variables. 21 | */ 22 | 23 | #include // malloc/free 24 | #include // for FLT_MIN 25 | #include // for memcpy 26 | #include "mamedef.h" 27 | #include "sn76489.h" 28 | #include "panning.h" 29 | 30 | #define NoiseInitialState 0x8000 /* Initial state of shift register */ 31 | #define PSG_CUTOFF 0x6 /* Value below which PSG does not output */ 32 | 33 | static const int PSGVolumeValues[16] = { 34 | /* // These values are taken from a real SMS2's output 35 | {892,892,892,760,623,497,404,323,257,198,159,123,96,75,60,0}, // I can't remember why 892... :P some scaling I did at some point 36 | // these values are true volumes for 2dB drops at each step (multiply previous by 10^-0.1) 37 | 1516,1205,957,760,603,479,381,303,240,191,152,120,96,76,60,0*/ 38 | // The MAME core uses 0x2000 as maximum volume (0x1000 for bipolar output) 39 | 4096, 3254, 2584, 2053, 1631, 1295, 1029, 817, 649, 516, 410, 325, 258, 205, 163, 0 40 | }; 41 | 42 | /*static SN76489_Context SN76489[MAX_SN76489];*/ 43 | static SN76489_Context* LastChipInit = NULL; 44 | //static unsigned short int FNumLimit; 45 | 46 | 47 | SN76489_Context* SN76489_Init( int PSGClockValue, int SamplingRate) 48 | { 49 | int i; 50 | SN76489_Context* chip = (SN76489_Context*)malloc(sizeof(SN76489_Context)); 51 | if(chip) 52 | { 53 | chip->dClock=(float)(PSGClockValue & 0x7FFFFFF)/16/SamplingRate; 54 | 55 | SN76489_SetMute(chip, MUTE_ALLON); 56 | SN76489_Config(chip, /*MUTE_ALLON,*/ FB_SEGAVDP, SRW_SEGAVDP, 1); 57 | 58 | for( i = 0; i <= 3; i++ ) 59 | centre_panning(chip->panning[i]); 60 | //SN76489_Reset(chip); 61 | 62 | if ((PSGClockValue & 0x80000000) && LastChipInit != NULL) 63 | { 64 | // Activate special NeoGeoPocket Mode 65 | LastChipInit->NgpFlags = 0x80 | 0x00; 66 | chip->NgpFlags = 0x80 | 0x01; 67 | chip->NgpChip2 = LastChipInit; 68 | LastChipInit->NgpChip2 = chip; 69 | LastChipInit = NULL; 70 | } 71 | else 72 | { 73 | chip->NgpFlags = 0x00; 74 | chip->NgpChip2 = NULL; 75 | LastChipInit = chip; 76 | } 77 | } 78 | return chip; 79 | } 80 | 81 | void SN76489_Reset(SN76489_Context* chip) 82 | { 83 | int i; 84 | 85 | chip->PSGStereo = 0xFF; 86 | 87 | for( i = 0; i <= 3; i++ ) 88 | { 89 | /* Initialise PSG state */ 90 | chip->Registers[2*i] = 1; /* tone freq=1 */ 91 | chip->Registers[2*i+1] = 0xf; /* vol=off */ 92 | chip->NoiseFreq = 0x10; 93 | 94 | /* Set counters to 0 */ 95 | chip->ToneFreqVals[i] = 0; 96 | 97 | /* Set flip-flops to 1 */ 98 | chip->ToneFreqPos[i] = 1; 99 | 100 | /* Set intermediate positions to do-not-use value */ 101 | chip->IntermediatePos[i] = FLT_MIN; 102 | 103 | /* Set panning to centre */ 104 | //centre_panning( chip->panning[i] ); 105 | } 106 | 107 | chip->LatchedRegister = 0; 108 | 109 | /* Initialise noise generator */ 110 | chip->NoiseShiftRegister = NoiseInitialState; 111 | 112 | /* Zero clock */ 113 | chip->Clock = 0; 114 | } 115 | 116 | void SN76489_Shutdown(SN76489_Context* chip) 117 | { 118 | free(chip); 119 | } 120 | 121 | void SN76489_Config(SN76489_Context* chip, /*int mute,*/ int feedback, int sr_width, int boost_noise) 122 | { 123 | //chip->Mute = mute; 124 | chip->WhiteNoiseFeedback = feedback; 125 | chip->SRWidth = sr_width; 126 | } 127 | 128 | /* 129 | void SN76489_SetContext(int which, uint8 *data) 130 | { 131 | memcpy( &SN76489[which], data, sizeof(SN76489_Context) ); 132 | } 133 | 134 | void SN76489_GetContext(int which, uint8 *data) 135 | { 136 | memcpy( data, &SN76489[which], sizeof(SN76489_Context) ); 137 | } 138 | 139 | uint8 *SN76489_GetContextPtr(int which) 140 | { 141 | return (uint8 *)&SN76489[which]; 142 | } 143 | 144 | int SN76489_GetContextSize(void) 145 | { 146 | return sizeof(SN76489_Context); 147 | } 148 | */ 149 | void SN76489_Write(SN76489_Context* chip, int data) 150 | { 151 | if ( data & 0x80 ) 152 | { 153 | /* Latch/data byte %1 cc t dddd */ 154 | chip->LatchedRegister = ( data >> 4 ) & 0x07; 155 | chip->Registers[chip->LatchedRegister] = 156 | ( chip->Registers[chip->LatchedRegister] & 0x3f0 ) /* zero low 4 bits */ 157 | | ( data & 0xf ); /* and replace with data */ 158 | } else { 159 | /* Data byte %0 - dddddd */ 160 | if ( !( chip->LatchedRegister % 2 ) && ( chip->LatchedRegister < 5 ) ) 161 | /* Tone register */ 162 | chip->Registers[chip->LatchedRegister] = 163 | ( chip->Registers[chip->LatchedRegister] & 0x00f) /* zero high 6 bits */ 164 | | ( ( data & 0x3f ) << 4 ); /* and replace with data */ 165 | else 166 | /* Other register */ 167 | chip->Registers[chip->LatchedRegister]=data&0x0f; /* Replace with data */ 168 | } 169 | switch (chip->LatchedRegister) { 170 | case 0: 171 | case 2: 172 | case 4: /* Tone channels */ 173 | if ( chip->Registers[chip->LatchedRegister] == 0 ) 174 | chip->Registers[chip->LatchedRegister] = 1; /* Zero frequency changed to 1 to avoid div/0 */ 175 | break; 176 | case 6: /* Noise */ 177 | chip->NoiseShiftRegister = NoiseInitialState; /* reset shift register */ 178 | chip->NoiseFreq = 0x10 << ( chip->Registers[6] & 0x3 ); /* set noise signal generator frequency */ 179 | break; 180 | } 181 | } 182 | 183 | void SN76489_GGStereoWrite(SN76489_Context* chip, int data) 184 | { 185 | chip->PSGStereo=data; 186 | } 187 | 188 | //void SN76489_Update(SN76489_Context* chip, INT16 **buffer, int length) 189 | void SN76489_Update(SN76489_Context* chip, int **buffer, int length) 190 | { 191 | int i, j; 192 | int NGPMode; 193 | SN76489_Context* chip2; 194 | SN76489_Context* chip_t; 195 | SN76489_Context* chip_n; 196 | 197 | NGPMode = (chip->NgpFlags >> 7) & 0x01; 198 | if (! NGPMode) 199 | { 200 | chip2 = NULL; 201 | chip_t = chip_n = chip; 202 | } 203 | else 204 | { 205 | chip2 = (SN76489_Context*)chip->NgpChip2; 206 | if (! (chip->NgpFlags & 0x01)) 207 | { 208 | chip_t = chip; 209 | chip_n = chip2; 210 | } 211 | else 212 | { 213 | chip_t = chip2; 214 | chip_n = chip; 215 | } 216 | } 217 | 218 | for( j = 0; j < length; j++ ) 219 | { 220 | /* Tone channels */ 221 | for ( i = 0; i <= 2; ++i ) 222 | if ( (chip_t->Mute >> i) & 1 ) 223 | { 224 | if ( chip_t->IntermediatePos[i] != FLT_MIN ) 225 | /* Intermediate position (antialiasing) */ 226 | chip->Channels[i] = (short)( PSGVolumeValues[chip->Registers[2 * i + 1]] * chip_t->IntermediatePos[i] ); 227 | else 228 | /* Flat (no antialiasing needed) */ 229 | chip->Channels[i]= PSGVolumeValues[chip->Registers[2 * i + 1]] * chip_t->ToneFreqPos[i]; 230 | } 231 | else 232 | /* Muted channel */ 233 | chip->Channels[i] = 0; 234 | 235 | /* Noise channel */ 236 | if ( (chip_t->Mute >> 3) & 1 ) 237 | { 238 | //chip->Channels[3] = PSGVolumeValues[chip->Registers[7]] * ( chip_n->NoiseShiftRegister & 0x1 ) * 2; /* double noise volume */ 239 | // Now the noise is bipolar, too. -Valley Bell 240 | chip->Channels[3] = PSGVolumeValues[chip->Registers[7]] * (( chip_n->NoiseShiftRegister & 0x1 ) * 2 - 1); 241 | // due to the way the white noise works here, it seems twice as loud as it should be 242 | if (chip->Registers[6] & 0x4 ) 243 | chip->Channels[3] >>= 1; 244 | } 245 | else 246 | chip->Channels[i] = 0; 247 | 248 | // Build stereo result into buffer 249 | buffer[0][j] = 0; 250 | buffer[1][j] = 0; 251 | if (! chip->NgpFlags) 252 | { 253 | // For all 4 channels 254 | for ( i = 0; i <= 3; ++i ) 255 | { 256 | if ( ( ( chip->PSGStereo >> i ) & 0x11 ) == 0x11 ) 257 | { 258 | // no GG stereo for this channel 259 | if ( chip->panning[i][0] == 1.0f ) 260 | { 261 | // synth-emuration workaround stereo fix 262 | buffer[0][j] += chip->Channels[i] / 2; // left 263 | buffer[1][j] += chip->Channels[i] / 2; // right 264 | } 265 | else 266 | { 267 | buffer[0][j] += (INT32)( chip->panning[i][0] * chip->Channels[i] ); // left 268 | buffer[1][j] += (INT32)( chip->panning[i][1] * chip->Channels[i] ); // right 269 | } 270 | } 271 | else 272 | { 273 | // GG stereo overrides panning 274 | buffer[0][j] += ( chip->PSGStereo >> (i+4) & 0x1 ) * chip->Channels[i]; // left 275 | buffer[1][j] += ( chip->PSGStereo >> i & 0x1 ) * chip->Channels[i]; // right 276 | } 277 | } 278 | } 279 | else 280 | { 281 | if (! (chip->NgpFlags & 0x01)) 282 | { 283 | // For all 3 tone channels 284 | for (i = 0; i < 3; i ++) 285 | { 286 | buffer[0][j] += (chip->PSGStereo >> (i+4) & 0x1 ) * chip ->Channels[i]; // left 287 | buffer[1][j] += (chip->PSGStereo >> i & 0x1 ) * chip2->Channels[i]; // right 288 | } 289 | } 290 | else 291 | { 292 | // noise channel 293 | i = 3; 294 | buffer[0][j] += (chip->PSGStereo >> (i+4) & 0x1 ) * chip2->Channels[i]; // left 295 | buffer[1][j] += (chip->PSGStereo >> i & 0x1 ) * chip ->Channels[i]; // right 296 | } 297 | } 298 | 299 | /* Increment clock by 1 sample length */ 300 | chip->Clock += chip->dClock; 301 | chip->NumClocksForSample = (int)chip->Clock; /* truncate */ 302 | chip->Clock -= chip->NumClocksForSample; /* remove integer part */ 303 | 304 | /* Decrement tone channel counters */ 305 | for ( i = 0; i <= 2; ++i ) 306 | chip->ToneFreqVals[i] -= chip->NumClocksForSample; 307 | 308 | /* Noise channel: match to tone2 or decrement its counter */ 309 | if ( chip->NoiseFreq == 0x80 ) 310 | chip->ToneFreqVals[3] = chip->ToneFreqVals[2]; 311 | else 312 | chip->ToneFreqVals[3] -= chip->NumClocksForSample; 313 | 314 | /* Tone channels: */ 315 | for ( i = 0; i <= 2; ++i ) { 316 | if ( chip->ToneFreqVals[i] <= 0 ) { /* If the counter gets below 0... */ 317 | if (chip->Registers[i*2]>=PSG_CUTOFF) { 318 | /* For tone-generating values, calculate how much of the sample is + and how much is - */ 319 | /* This is optimised into an even more confusing state than it was in the first place... */ 320 | chip->IntermediatePos[i] = ( chip->NumClocksForSample - chip->Clock + 2 * chip->ToneFreqVals[i] ) * chip->ToneFreqPos[i] / ( chip->NumClocksForSample + chip->Clock ); 321 | /* Flip the flip-flop */ 322 | chip->ToneFreqPos[i] = -chip->ToneFreqPos[i]; 323 | } else { 324 | /* stuck value */ 325 | chip->ToneFreqPos[i] = 1; 326 | chip->IntermediatePos[i] = FLT_MIN; 327 | } 328 | chip->ToneFreqVals[i] += chip->Registers[i*2] * ( chip->NumClocksForSample / chip->Registers[i*2] + 1 ); 329 | } 330 | else 331 | /* signal no antialiasing needed */ 332 | chip->IntermediatePos[i] = FLT_MIN; 333 | } 334 | 335 | /* Noise channel */ 336 | if ( chip->ToneFreqVals[3] <= 0 ) { 337 | /* If the counter gets below 0... */ 338 | /* Flip the flip-flop */ 339 | chip->ToneFreqPos[3] = -chip->ToneFreqPos[3]; 340 | if (chip->NoiseFreq != 0x80) 341 | /* If not matching tone2, decrement counter */ 342 | chip->ToneFreqVals[3] += chip->NoiseFreq * ( chip->NumClocksForSample / chip->NoiseFreq + 1 ); 343 | if (chip->ToneFreqPos[3] == 1) { 344 | /* On the positive edge of the square wave (only once per cycle) */ 345 | int Feedback; 346 | if ( chip->Registers[6] & 0x4 ) { 347 | /* White noise */ 348 | /* Calculate parity of fed-back bits for feedback */ 349 | switch (chip->WhiteNoiseFeedback) { 350 | /* Do some optimised calculations for common (known) feedback values */ 351 | case 0x0003: /* SC-3000, BBC %00000011 */ 352 | case 0x0009: /* SMS, GG, MD %00001001 */ 353 | /* If two bits fed back, I can do Feedback=(nsr & fb) && (nsr & fb ^ fb) */ 354 | /* since that's (one or more bits set) && (not all bits set) */ 355 | Feedback = ( ( chip->NoiseShiftRegister & chip->WhiteNoiseFeedback ) 356 | && ( (chip->NoiseShiftRegister & chip->WhiteNoiseFeedback ) ^ chip->WhiteNoiseFeedback ) ); 357 | break; 358 | default: 359 | /* Default handler for all other feedback values */ 360 | /* XOR fold bits into the final bit */ 361 | Feedback = chip->NoiseShiftRegister & chip->WhiteNoiseFeedback; 362 | Feedback ^= Feedback >> 8; 363 | Feedback ^= Feedback >> 4; 364 | Feedback ^= Feedback >> 2; 365 | Feedback ^= Feedback >> 1; 366 | Feedback &= 1; 367 | break; 368 | } 369 | } else /* Periodic noise */ 370 | Feedback=chip->NoiseShiftRegister&1; 371 | 372 | chip->NoiseShiftRegister=(chip->NoiseShiftRegister>>1) | (Feedback << (chip->SRWidth-1)); 373 | } 374 | } 375 | } 376 | } 377 | 378 | /*void SN76489_UpdateOne(SN76489_Context* chip, int *l, int *r) 379 | { 380 | INT16 tl,tr; 381 | INT16 *buff[2] = { &tl, &tr }; 382 | SN76489_Update( chip, buff, 1 ); 383 | *l = tl; 384 | *r = tr; 385 | }*/ 386 | 387 | 388 | /*int SN76489_GetMute(SN76489_Context* chip) 389 | { 390 | return chip->Mute; 391 | }*/ 392 | 393 | void SN76489_SetMute(SN76489_Context* chip, int val) 394 | { 395 | chip->Mute=val; 396 | } 397 | 398 | void SN76489_SetPanning(SN76489_Context* chip, int ch0, int ch1, int ch2, int ch3) 399 | { 400 | calc_panning( chip->panning[0], ch0 ); 401 | calc_panning( chip->panning[1], ch1 ); 402 | calc_panning( chip->panning[2], ch2 ); 403 | calc_panning( chip->panning[3], ch3 ); 404 | } 405 | -------------------------------------------------------------------------------- /synth-emulator/src/c/sn76489.h: -------------------------------------------------------------------------------- 1 | #ifndef _SN76489_H_ 2 | #define _SN76489_H_ 3 | 4 | // all these defines are defined in mamedef.h, but GCC's #ifdef doesn't seem to know typedefs 5 | #ifndef INT32 6 | #define INT32 signed long 7 | #endif 8 | #ifndef UINT16 9 | #define UINT16 unsigned short 10 | #endif 11 | #ifndef INT16 12 | #define INT16 signed short 13 | #endif 14 | #ifndef INT8 15 | #define INT8 signed char 16 | #endif 17 | #ifndef uint8 18 | #define uint8 signed char 19 | #endif 20 | 21 | 22 | /*#define MAX_SN76489 4*/ 23 | 24 | /* 25 | More testing is needed to find and confirm feedback patterns for 26 | SN76489 variants and compatible chips. 27 | */ 28 | enum feedback_patterns { 29 | FB_BBCMICRO = 0x8005, /* Texas Instruments TMS SN76489N (original) from BBC Micro computer */ 30 | FB_SC3000 = 0x0006, /* Texas Instruments TMS SN76489AN (rev. A) from SC-3000H computer */ 31 | FB_SEGAVDP = 0x0009, /* SN76489 clone in Sega's VDP chips (315-5124, 315-5246, 315-5313, Game Gear) */ 32 | }; 33 | 34 | enum sr_widths { 35 | SRW_SC3000BBCMICRO = 15, 36 | SRW_SEGAVDP = 16 37 | }; 38 | 39 | enum volume_modes { 40 | VOL_TRUNC = 0, /* Volume levels 13-15 are identical */ 41 | VOL_FULL = 1, /* Volume levels 13-15 are unique */ 42 | }; 43 | 44 | enum mute_values { 45 | MUTE_ALLOFF = 0, /* All channels muted */ 46 | MUTE_TONE1 = 1, /* Tone 1 mute control */ 47 | MUTE_TONE2 = 2, /* Tone 2 mute control */ 48 | MUTE_TONE3 = 4, /* Tone 3 mute control */ 49 | MUTE_NOISE = 8, /* Noise mute control */ 50 | MUTE_ALLON = 15, /* All channels enabled */ 51 | }; 52 | 53 | typedef struct 54 | { 55 | int Mute; // per-channel muting 56 | int BoostNoise; // double noise volume when non-zero 57 | 58 | /* Variables */ 59 | float Clock; 60 | float dClock; 61 | int PSGStereo; 62 | int NumClocksForSample; 63 | int WhiteNoiseFeedback; 64 | int SRWidth; 65 | 66 | /* PSG registers: */ 67 | int Registers[8]; /* Tone, vol x4 */ 68 | int LatchedRegister; 69 | int NoiseShiftRegister; 70 | int NoiseFreq; /* Noise channel signal generator frequency */ 71 | 72 | /* Output calculation variables */ 73 | int ToneFreqVals[4]; /* Frequency register values (counters) */ 74 | int ToneFreqPos[4]; /* Frequency channel flip-flops */ 75 | int Channels[4]; /* Value of each channel, before stereo is applied */ 76 | float IntermediatePos[4]; /* intermediate values used at boundaries between + and - (does not need double accuracy)*/ 77 | 78 | float panning[4][2]; /* fake stereo */ 79 | 80 | int NgpFlags; /* bit 7 - NGP Mode on/off, bit 0 - is 2nd NGP chip */ 81 | void* NgpChip2; 82 | } SN76489_Context; 83 | 84 | /* Function prototypes */ 85 | SN76489_Context* SN76489_Init(int PSGClockValue, int SamplingRate); 86 | void SN76489_Reset(SN76489_Context* chip); 87 | void SN76489_Shutdown(SN76489_Context* chip); 88 | void SN76489_Config(SN76489_Context* chip, /*int mute,*/ int feedback, int sw_width, int boost_noise); 89 | /* 90 | void SN76489_SetContext(SN76489_Context* chip, uint8 *data); 91 | void SN76489_GetContext(SN76489_Context* chip, uint8 *data); 92 | uint8 *SN76489_GetContextPtr(int chip); 93 | int SN76489_GetContextSize(void);*/ 94 | void SN76489_Write(SN76489_Context* chip, int data); 95 | void SN76489_GGStereoWrite(SN76489_Context* chip, int data); 96 | //void SN76489_Update(SN76489_Context* chip, INT16 **buffer, int length); 97 | void SN76489_Update(SN76489_Context* chip, int **buffer, int length); 98 | 99 | /* Non-standard getters and setters */ 100 | //int SN76489_GetMute(SN76489_Context* chip); 101 | void SN76489_SetMute(SN76489_Context* chip, int val); 102 | 103 | void SN76489_SetPanning(SN76489_Context* chip, int ch0, int ch1, int ch2, int ch3); 104 | 105 | /* and a non-standard data getter */ 106 | //void SN76489_UpdateOne(SN76489_Context* chip, int *l, int *r); 107 | 108 | #endif /* _SN76489_H_ */ 109 | -------------------------------------------------------------------------------- /synth-emulator/src/c/vgmplay.c: -------------------------------------------------------------------------------- 1 | // for vgm testing 2 | // mkdir build && cd build 3 | // make && bin/vgmplay && ffplay -f u16le -ar 44100 -ac 2 ../vgm/s16le.pcm 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "mamedef.h" 12 | #include "ym3438.h" 13 | #include "sn76489.h" 14 | #include "pwm.h" 15 | #include "vgmplay.h" 16 | 17 | #define SAMPLING_RATE 44100 18 | #define FRAME_SIZE_MAX 4096 19 | 20 | #define STEREO 2 21 | #define MONO 0 22 | 23 | VGM_HEADER *vgmheader; 24 | uint8_t *vgm; 25 | uint32_t vgmpos = 0x40; 26 | uint32_t datpos; 27 | uint32_t pcmpos; 28 | uint32_t pcmoffset; 29 | 30 | bool vgmend = false; 31 | 32 | uint32_t clock_sn76489; 33 | uint32_t clock_ym2612; 34 | uint32_t clock_pwm; 35 | 36 | SN76489_Context *sn76489; 37 | ym3438_t *ym3438; 38 | 39 | void vgm_load(void) { 40 | vgm = (unsigned char *) malloc(30000000); 41 | int fd = open("../../docs/vgm/pwm.vgm", O_RDONLY); 42 | assert(fd != -1); 43 | read(fd, vgm, 30000000); 44 | close(fd); 45 | 46 | vgmheader = (VGM_HEADER *)vgm; 47 | printf("version %x\n", vgmheader->lngVersion); 48 | if(vgmheader->lngVersion >= 0x150) { 49 | vgmheader->lngDataOffset += 0x00000034; 50 | } 51 | 52 | clock_sn76489 = vgmheader->lngHzPSG; 53 | clock_ym2612 = vgmheader->lngHzYM2612; 54 | clock_pwm = 23011360; 55 | if(clock_ym2612 == 0) clock_ym2612 = 7670453; 56 | if(clock_sn76489 == 0) clock_sn76489 = 3579545; 57 | 58 | vgmpos = vgmheader->lngDataOffset; 59 | 60 | printf("vgmpos %x\n", vgmheader->lngDataOffset); 61 | printf("clock_sn76489 %d\n", clock_sn76489); 62 | printf("clock_ym2612 %d\n", clock_ym2612); 63 | } 64 | 65 | uint8_t get_vgm_u8() 66 | { 67 | return vgm[vgmpos++]; 68 | } 69 | 70 | uint16_t get_vgm_u16() 71 | { 72 | return get_vgm_u8() + (get_vgm_u8() << 8); 73 | } 74 | 75 | uint32_t get_vgm_u32() 76 | { 77 | return get_vgm_u8() + (get_vgm_u8() << 8) + (get_vgm_u8() << 16) + (get_vgm_u8() << 24); 78 | } 79 | 80 | uint16_t parse_vgm() 81 | { 82 | uint8_t command; 83 | uint16_t wait = 0; 84 | uint8_t reg; 85 | uint8_t dat; 86 | uint8_t raw1; 87 | uint8_t raw2; 88 | uint8_t channel; 89 | uint16_t data; 90 | 91 | command = get_vgm_u8(); 92 | 93 | switch (command) { 94 | case 0x50: 95 | dat = get_vgm_u8(); 96 | SN76489_Write(sn76489, dat); 97 | break; 98 | case 0x52: 99 | case 0x53: 100 | reg = get_vgm_u8(); 101 | dat = get_vgm_u8(); 102 | OPN2_WriteBuffered(ym3438, 0 + ((command & 1) << 1), reg); 103 | OPN2_WriteBuffered(ym3438, 1 + ((command & 1) << 1), dat); 104 | break; 105 | case 0x61: 106 | wait = get_vgm_u16(); 107 | break; 108 | case 0x62: 109 | wait = 735; 110 | break; 111 | case 0x63: 112 | wait = 882; 113 | break; 114 | case 0x66: 115 | vgmend = true; 116 | break; 117 | case 0x67: 118 | get_vgm_u8(); // 0x66 119 | get_vgm_u8(); // 0x00 data type 120 | datpos = vgmpos + 4; 121 | vgmpos += get_vgm_u32(); // size of data, in bytes 122 | break; 123 | case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: 124 | case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f: 125 | wait = (command & 0x0f) + 1; 126 | break; 127 | case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: 128 | case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f: 129 | wait = (command & 0x0f); 130 | OPN2_WriteBuffered(ym3438, 0, 0x2a); 131 | OPN2_WriteBuffered(ym3438, 1, vgm[datpos + pcmpos + pcmoffset]); 132 | pcmoffset++; 133 | break; 134 | case 0x90: 135 | // Setup Stream Control 136 | get_vgm_u32(); 137 | break; 138 | case 0x91: 139 | // Set Stream Data 140 | get_vgm_u32(); 141 | break; 142 | case 0x92: 143 | // Set Stream Frequency 144 | get_vgm_u8(); 145 | get_vgm_u32(); 146 | case 0x93: 147 | // Start Stream 148 | get_vgm_u8(); 149 | get_vgm_u32(); 150 | get_vgm_u8(); 151 | get_vgm_u32(); 152 | case 0xb2: 153 | raw1 = get_vgm_u8(); 154 | raw2 = get_vgm_u8(); 155 | channel = (raw1 & 0xf0) >> 4; 156 | data = (raw1 & 0x0f) << 8 | raw2; 157 | pwm_chn_w(0, channel, data); 158 | break; 159 | case 0xe0: 160 | pcmpos = get_vgm_u32(); 161 | pcmoffset = 0; 162 | break; 163 | default: 164 | printf("unknown cmd at 0x%x: 0x%x\n", vgmpos, vgm[vgmpos]); 165 | vgmpos++; 166 | break; 167 | } 168 | 169 | return wait; 170 | } 171 | 172 | short audio_write_sound_stereo(int sample32) 173 | { 174 | short sample16; 175 | 176 | if (sample32 < -0x7FFF) { 177 | sample16 = -0x7FFF; 178 | } else if (sample32 > 0x7FFF) { 179 | sample16 = 0x7FFF; 180 | } else { 181 | sample16 = (short)(sample32); 182 | } 183 | 184 | // for I2S_MODE_DAC_BUILT_IN 185 | sample16 = sample16 ^ 0x8000U; 186 | 187 | return sample16; 188 | } 189 | 190 | // The loop routine runs over and over again forever 191 | int main(void) 192 | { 193 | vgm_load(); 194 | 195 | // // Reset for NTSC Genesis/Megadrive 196 | sn76489 = SN76489_Init(clock_sn76489, SAMPLING_RATE); 197 | SN76489_Reset(sn76489); 198 | 199 | ym3438 = (ym3438_t *)malloc(sizeof(ym3438_t)); 200 | OPN2_Reset(ym3438, SAMPLING_RATE, clock_ym2612); 201 | OPN2_SetChipType(ym3438_mode_ym2612); 202 | 203 | device_start_pwm(0, clock_pwm); 204 | 205 | // malloc sound buffer 206 | int **buflr; 207 | 208 | buflr = (int **)malloc(sizeof(int) * STEREO); 209 | assert(buflr != NULL); 210 | buflr[0] = (int *)malloc(sizeof(int) * FRAME_SIZE_MAX); 211 | assert(buflr[0] != NULL); 212 | buflr[1] = (int *)malloc(sizeof(int) * FRAME_SIZE_MAX); 213 | assert(buflr[1] != NULL); 214 | 215 | size_t bytes_written = 0; 216 | uint16_t frame_size = 0; 217 | uint16_t frame_size_count = 0; 218 | uint32_t frame_all = 0; 219 | 220 | int fd = open("../../docs/vgm/s16le.pcm", O_CREAT | O_WRONLY | O_TRUNC, 0666); 221 | assert(fd != -1); 222 | 223 | do { 224 | frame_size = parse_vgm(); 225 | // get sampling 226 | for(uint32_t i = 0; i < frame_size; i++) { 227 | SN76489_Update(sn76489, (int **)buflr, 1); 228 | pwm_update(0, (int **)buflr, 1); 229 | OPN2_GenerateStream(ym3438, (int **)buflr, 1); 230 | short d[STEREO]; 231 | d[0] = audio_write_sound_stereo(buflr[0][0]); 232 | d[1] = audio_write_sound_stereo(buflr[1][0]); 233 | write(fd, d, sizeof(short) * STEREO); 234 | } 235 | frame_all += frame_size; 236 | } while(!vgmend); 237 | 238 | close(fd); 239 | 240 | free(buflr[0]); 241 | free(buflr[1]); 242 | free(buflr); 243 | 244 | free(ym3438); 245 | SN76489_Shutdown(sn76489); 246 | 247 | printf("end!\n"); 248 | 249 | return 0; 250 | } 251 | -------------------------------------------------------------------------------- /synth-emulator/src/c/vgmplay.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Header file for VGM file handling 4 | typedef struct _vgm_file_header 5 | { 6 | uint32_t fccVGM; 7 | uint32_t lngEOFOffset; 8 | uint32_t lngVersion; 9 | uint32_t lngHzPSG; 10 | uint32_t lngHzYM2413; 11 | uint32_t lngGD3Offset; 12 | uint32_t lngTotalSamples; 13 | uint32_t lngLoopOffset; 14 | uint32_t lngLoopSamples; 15 | uint32_t lngRate; 16 | uint16_t shtPSG_Feedback; 17 | uint8_t bytPSG_SRWidth; 18 | uint8_t bytPSG_Flags; 19 | uint32_t lngHzYM2612; 20 | uint32_t lngHzYM2151; 21 | uint32_t lngDataOffset; 22 | } VGM_HEADER; 23 | -------------------------------------------------------------------------------- /synth-emulator/src/c/ym3438.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2018 Alexey Khokholov (Nuke.YKT) 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | * 18 | * 19 | * Nuked OPN2(Yamaha YM3438) emulator. 20 | * Thanks: 21 | * Silicon Pr0n: 22 | * Yamaha YM3438 decap and die shot(digshadow). 23 | * OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): 24 | * OPL2 ROMs. 25 | * 26 | * version: 1.0.9 27 | */ 28 | 29 | #ifndef YM3438_H 30 | #define YM3438_H 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | #define RSM_FRAC 10 37 | #define OPN_WRITEBUF_SIZE 2048 38 | #define OPN_WRITEBUF_DELAY 15 39 | 40 | enum { 41 | ym3438_type_discrete = 0, /* Discrete YM3438 (Teradrive) */ 42 | ym3438_type_asic = 1, /* ASIC YM3438 (MD1 VA7, MD2, MD3, etc) */ 43 | ym3438_type_ym2612 = 2, /* YM2612 (MD1, MD2 VA2) */ 44 | ym3438_type_ym2612_u = 3 /* YM2612 without lowpass filter */ 45 | }; 46 | 47 | enum { 48 | ym3438_mode_ym2612 = 0x01, /* Enables YM2612 emulation (MD1, MD2 VA2) */ 49 | ym3438_mode_readmode = 0x02 /* Enables status read on any port (TeraDrive, MD1 VA7, MD2, etc) */ 50 | }; 51 | 52 | #include 53 | 54 | typedef uintptr_t Bitu; 55 | typedef intptr_t Bits; 56 | typedef uint64_t Bit64u; 57 | typedef int64_t Bit64s; 58 | typedef uint32_t Bit32u; 59 | typedef int32_t Bit32s; 60 | typedef uint16_t Bit16u; 61 | typedef int16_t Bit16s; 62 | typedef uint8_t Bit8u; 63 | typedef int8_t Bit8s; 64 | 65 | typedef struct _opn2_writebuf { 66 | Bit64u time; 67 | Bit8u port; 68 | Bit8u data; 69 | } opn2_writebuf; 70 | 71 | typedef struct 72 | { 73 | Bit32u cycles; 74 | Bit32u channel; 75 | Bit16s mol, mor; 76 | /* IO */ 77 | Bit16u write_data; 78 | Bit8u write_a; 79 | Bit8u write_d; 80 | Bit8u write_a_en; 81 | Bit8u write_d_en; 82 | Bit8u write_busy; 83 | Bit8u write_busy_cnt; 84 | Bit8u write_fm_address; 85 | Bit8u write_fm_data; 86 | Bit8u write_fm_mode_a; 87 | Bit16u address; 88 | Bit8u data; 89 | Bit8u pin_test_in; 90 | Bit8u pin_irq; 91 | Bit8u busy; 92 | /* LFO */ 93 | Bit8u lfo_en; 94 | Bit8u lfo_freq; 95 | Bit8u lfo_pm; 96 | Bit8u lfo_am; 97 | Bit8u lfo_cnt; 98 | Bit8u lfo_inc; 99 | Bit8u lfo_quotient; 100 | /* Phase generator */ 101 | Bit16u pg_fnum; 102 | Bit8u pg_block; 103 | Bit8u pg_kcode; 104 | Bit32u pg_inc[24]; 105 | Bit32u pg_phase[24]; 106 | Bit8u pg_reset[24]; 107 | Bit32u pg_read; 108 | /* Envelope generator */ 109 | Bit8u eg_cycle; 110 | Bit8u eg_cycle_stop; 111 | Bit8u eg_shift; 112 | Bit8u eg_shift_lock; 113 | Bit8u eg_timer_low_lock; 114 | Bit16u eg_timer; 115 | Bit8u eg_timer_inc; 116 | Bit16u eg_quotient; 117 | Bit8u eg_custom_timer; 118 | Bit8u eg_rate; 119 | Bit8u eg_ksv; 120 | Bit8u eg_inc; 121 | Bit8u eg_ratemax; 122 | Bit8u eg_sl[2]; 123 | Bit8u eg_lfo_am; 124 | Bit8u eg_tl[2]; 125 | Bit8u eg_state[24]; 126 | Bit16u eg_level[24]; 127 | Bit16u eg_out[24]; 128 | Bit8u eg_kon[24]; 129 | Bit8u eg_kon_csm[24]; 130 | Bit8u eg_kon_latch[24]; 131 | Bit8u eg_csm_mode[24]; 132 | Bit8u eg_ssg_enable[24]; 133 | Bit8u eg_ssg_pgrst_latch[24]; 134 | Bit8u eg_ssg_repeat_latch[24]; 135 | Bit8u eg_ssg_hold_up_latch[24]; 136 | Bit8u eg_ssg_dir[24]; 137 | Bit8u eg_ssg_inv[24]; 138 | Bit32u eg_read[2]; 139 | Bit8u eg_read_inc; 140 | /* FM */ 141 | Bit16s fm_op1[6][2]; 142 | Bit16s fm_op2[6]; 143 | Bit16s fm_out[24]; 144 | Bit16u fm_mod[24]; 145 | /* Channel */ 146 | Bit16s ch_acc[6]; 147 | Bit16s ch_out[6]; 148 | Bit16s ch_lock; 149 | Bit8u ch_lock_l; 150 | Bit8u ch_lock_r; 151 | Bit16s ch_read; 152 | /* Timer */ 153 | Bit16u timer_a_cnt; 154 | Bit16u timer_a_reg; 155 | Bit8u timer_a_load_lock; 156 | Bit8u timer_a_load; 157 | Bit8u timer_a_enable; 158 | Bit8u timer_a_reset; 159 | Bit8u timer_a_load_latch; 160 | Bit8u timer_a_overflow_flag; 161 | Bit8u timer_a_overflow; 162 | 163 | Bit16u timer_b_cnt; 164 | Bit8u timer_b_subcnt; 165 | Bit16u timer_b_reg; 166 | Bit8u timer_b_load_lock; 167 | Bit8u timer_b_load; 168 | Bit8u timer_b_enable; 169 | Bit8u timer_b_reset; 170 | Bit8u timer_b_load_latch; 171 | Bit8u timer_b_overflow_flag; 172 | Bit8u timer_b_overflow; 173 | 174 | /* Register set */ 175 | Bit8u mode_test_21[8]; 176 | Bit8u mode_test_2c[8]; 177 | Bit8u mode_ch3; 178 | Bit8u mode_kon_channel; 179 | Bit8u mode_kon_operator[4]; 180 | Bit8u mode_kon[24]; 181 | Bit8u mode_csm; 182 | Bit8u mode_kon_csm; 183 | Bit8u dacen; 184 | Bit16s dacdata; 185 | 186 | Bit8u ks[24]; 187 | Bit8u ar[24]; 188 | Bit8u sr[24]; 189 | Bit8u dt[24]; 190 | Bit8u multi[24]; 191 | Bit8u sl[24]; 192 | Bit8u rr[24]; 193 | Bit8u dr[24]; 194 | Bit8u am[24]; 195 | Bit8u tl[24]; 196 | Bit8u ssg_eg[24]; 197 | 198 | Bit16u fnum[6]; 199 | Bit8u block[6]; 200 | Bit8u kcode[6]; 201 | Bit16u fnum_3ch[6]; 202 | Bit8u block_3ch[6]; 203 | Bit8u kcode_3ch[6]; 204 | Bit8u reg_a4; 205 | Bit8u reg_ac; 206 | Bit8u connect[6]; 207 | Bit8u fb[6]; 208 | Bit8u pan_l[6], pan_r[6]; 209 | Bit8u ams[6]; 210 | Bit8u pms[6]; 211 | Bit8u status; 212 | Bit32u status_time; 213 | 214 | Bit32u mute[7]; 215 | Bit32s rateratio; 216 | Bit32s samplecnt; 217 | Bit32s oldsamples[2]; 218 | Bit32s samples[2]; 219 | 220 | Bit64u writebuf_samplecnt; 221 | Bit32u writebuf_cur; 222 | Bit32u writebuf_last; 223 | Bit64u writebuf_lasttime; 224 | opn2_writebuf writebuf[OPN_WRITEBUF_SIZE]; 225 | } ym3438_t; 226 | 227 | void OPN2_Reset(ym3438_t *chip, Bit32u rate, Bit32u clock); 228 | void OPN2_SetChipType(Bit32u type); 229 | void OPN2_Clock(ym3438_t *chip, Bit32s *buffer); 230 | void OPN2_Write(ym3438_t *chip, Bit32u port, Bit8u data); 231 | void OPN2_SetTestPin(ym3438_t *chip, Bit32u value); 232 | Bit32u OPN2_ReadTestPin(ym3438_t *chip); 233 | Bit32u OPN2_ReadIRQPin(ym3438_t *chip); 234 | Bit8u OPN2_Read(ym3438_t *chip, Bit32u port); 235 | 236 | void OPN2_WriteBuffered(ym3438_t *chip, Bit32u port, Bit8u data); 237 | void OPN2_GenerateStream(ym3438_t *chip, Bit32s **sndptr, Bit32u numsamples); 238 | void OPN2_SetOptions(Bit8u flags); 239 | void OPN2_SetMute(ym3438_t *chip, Bit32u mute); 240 | #ifdef __cplusplus 241 | } 242 | #endif 243 | 244 | #endif 245 | -------------------------------------------------------------------------------- /synth-emulator/src/rust/driver.rs: -------------------------------------------------------------------------------- 1 | mod vgmplay; 2 | mod metadata; 3 | 4 | pub use crate::driver::vgmplay::VgmPlay as VgmPlay; 5 | pub use crate::driver::metadata::VgmHeader as VgmHeader; 6 | pub use crate::driver::metadata::Gd3 as Gd3; 7 | -------------------------------------------------------------------------------- /synth-emulator/src/rust/driver/metadata.rs: -------------------------------------------------------------------------------- 1 | use nom::number::complete::{le_u8, le_u16, le_u32}; 2 | use nom::bytes::complete::{tag, take}; 3 | use nom::IResult; 4 | 5 | /// 6 | /// https://vgmrips.net/wiki/VGM_Specification 7 | /// 8 | #[derive(Deserialize, Serialize, Default, Debug)] 9 | pub struct VgmHeader { 10 | pub eof: u32, 11 | pub version: u32, 12 | pub clock_sn76489: u32, 13 | pub clock_ym2413: u32, 14 | pub offset_gd3: u32, 15 | pub total_samples: u32, 16 | pub offset_loop: u32, 17 | pub loop_samples: u32, 18 | pub rate: u32, 19 | pub sn76489_fb: u16, 20 | pub sn76489_w: u8, 21 | pub sn76489_f: u8, 22 | pub clock_ym2612: u32, 23 | pub clock_ym2151: u32, 24 | pub vgm_data_offset: u32, 25 | pub sega_pcm_clock: u32, 26 | pub spcm_interface: u32, 27 | pub clock_rf5c68: u32, 28 | pub clock_ym2203: u32, 29 | pub clock_ym2608: u32, 30 | pub clock_ym2610_b: u32, 31 | pub clock_ym3812: u32, 32 | pub clock_ym3526: u32, 33 | pub clock_y8950: u32, 34 | pub clock_ymf262: u32, 35 | pub clock_ymf278_b: u32, 36 | pub clock_ym271: u32, 37 | pub clock_ymz280b: u32, 38 | pub clock_rf5c164: u32, 39 | pub clock_pwm: u32, 40 | pub clock_ay8910: u32, 41 | pub ay8910_chip_type: u8, 42 | pub ay8910_flag: u16, 43 | pub volume_modifier: u8, 44 | pub reserved01: u8, 45 | pub loop_base: u8, 46 | pub loop_modifier: u8, 47 | pub clock_gb_dmg: u32, 48 | pub clock_nes_apu: u32, 49 | pub clock_multi_pcm: u32, 50 | pub clock_upd7759: u32, 51 | pub clock_okim6258: u32, 52 | pub okmi6258_flag: u8, 53 | pub k054539_flag: u8, 54 | pub c140_chip_type : u8, 55 | pub reserved02: u8, 56 | pub clock_okim6295: u32, 57 | pub clock_k051649: u32, 58 | pub clock_k054539: u32, 59 | pub clock_huc6280: u32, 60 | pub clock_c140: u32, 61 | pub clock_k053260: u32, 62 | pub clock_pokey: u32, 63 | pub clock_qsound: u32, 64 | pub clock_scsp: u32, 65 | pub extra_hdr_ofs: u32, 66 | pub clock_wonder_swan: u32, 67 | pub clock_vsu: u32, 68 | pub clock_saa1099: u32, 69 | pub clock_es5503: u32, 70 | pub clock_es5506: u32, 71 | pub es5503_amount_channel: u8, 72 | pub es5506_amount_channel: u8, 73 | pub c352_clock_divider: u8, 74 | pub reserved03: u8, 75 | pub clock_x1_010: u32, 76 | pub clock_c352: u32, 77 | pub clock_ga20: u32, 78 | pub reserved04: u32, 79 | pub reserved05: u32, 80 | pub reserved06: u32, 81 | pub reserved07: u32, 82 | pub reserved08: u32, 83 | pub reserved09: u32, 84 | pub reserved10: u32 85 | } 86 | 87 | /// 88 | /// https://vgmrips.net/wiki/GD3_Specification 89 | /// 90 | #[derive(Deserialize, Serialize, Default, Debug)] 91 | pub struct Gd3 { 92 | pub track_name: String, 93 | pub track_name_j: String, 94 | pub game_name: String, 95 | pub game_name_j: String, 96 | pub system_name: String, 97 | pub system_name_j: String, 98 | pub track_author: String, 99 | pub track_author_j: String, 100 | pub date: String, 101 | pub converted: String 102 | } 103 | 104 | /// 105 | /// parse_vgm_header 106 | /// 107 | fn parse_vgm_header(i: &[u8]) -> IResult<&[u8], VgmHeader> { 108 | let (i, _) = tag("Vgm ")(i)?; 109 | let (i, eof) = le_u32(i)?; 110 | let (i, version) = take(4usize)(i)?; 111 | let version = version.iter().rev().map(|n| format!("{:02X}", n)).collect::(); 112 | let version = version.parse().unwrap_or(0); 113 | let (i, clock_sn76489) = le_u32(i)?; 114 | let (i, clock_ym2413) = le_u32(i)?; 115 | let (i, offset_gd3) = le_u32(i)?; 116 | let (i, total_samples) = le_u32(i)?; 117 | let (i, offset_loop) = le_u32(i)?; 118 | let (i, loop_samples) = le_u32(i)?; 119 | let (i, rate) = le_u32(i)?; 120 | let (i, sn76489_fb) = le_u16(i)?; 121 | let (i, sn76489_w) = le_u8(i)?; 122 | let (i, sn76489_f) = le_u8(i)?; 123 | let (i, clock_ym2612) = le_u32(i)?; 124 | let (i, clock_ym2151) = le_u32(i)?; 125 | let (i, vgm_data_offset) = le_u32(i)?; 126 | let (i, sega_pcm_clock) = le_u32(i)?; 127 | let (i, spcm_interface) = le_u32(i)?; 128 | let (i, clock_rf5c68) = le_u32(i)?; 129 | let (i, clock_ym2203) = le_u32(i)?; 130 | let (i, clock_ym2608) = le_u32(i)?; 131 | let (i, clock_ym2610_b) = le_u32(i)?; 132 | let (i, clock_ym3812) = le_u32(i)?; 133 | let (i, clock_ym3526) = le_u32(i)?; 134 | let (i, clock_y8950) = le_u32(i)?; 135 | let (i, clock_ymf262) = le_u32(i)?; 136 | let (i, clock_ymf278_b) = le_u32(i)?; 137 | let (i, clock_ym271) = le_u32(i)?; 138 | let (i, clock_ymz280b) = le_u32(i)?; 139 | let (i, clock_rf5c164) = le_u32(i)?; 140 | let (i, clock_pwm) = le_u32(i)?; 141 | let (i, clock_ay8910) = le_u32(i)?; 142 | let (i, ay8910_chip_type) = le_u8(i)?; 143 | let (i, ay8910_flag) = le_u16(i)?; 144 | let (i, volume_modifier) = le_u8(i)?; 145 | let (i, reserved01) = le_u8(i)?; 146 | let (i, loop_base) = le_u8(i)?; 147 | let (i, loop_modifier) = le_u8(i)?; 148 | let (i, clock_gb_dmg) = le_u32(i)?; 149 | let (i, clock_nes_apu) = le_u32(i)?; 150 | let (i, clock_multi_pcm) = le_u32(i)?; 151 | let (i, clock_upd7759) = le_u32(i)?; 152 | let (i, clock_okim6258) = le_u32(i)?; 153 | let (i, okmi6258_flag) = le_u8(i)?; 154 | let (i, k054539_flag) = le_u8(i)?; 155 | let (i, c140_chip_type) = le_u8(i)?; 156 | let (i, reserved02) = le_u8(i)?; 157 | let (i, clock_okim6295) = le_u32(i)?; 158 | let (i, clock_k051649) = le_u32(i)?; 159 | let (i, clock_k054539) = le_u32(i)?; 160 | let (i, clock_huc6280) = le_u32(i)?; 161 | let (i, clock_c140) = le_u32(i)?; 162 | let (i, clock_k053260) = le_u32(i)?; 163 | let (i, clock_pokey) = le_u32(i)?; 164 | let (i, clock_qsound) = le_u32(i)?; 165 | let (i, clock_scsp) = le_u32(i)?; 166 | let (i, extra_hdr_ofs) = le_u32(i)?; 167 | let (i, clock_wonder_swan) = le_u32(i)?; 168 | let (i, clock_vsu) = le_u32(i)?; 169 | let (i, clock_saa1099) = le_u32(i)?; 170 | let (i, clock_es5503) = le_u32(i)?; 171 | let (i, clock_es5506) = le_u32(i)?; 172 | let (i, es5503_amount_channel) = le_u8(i)?; 173 | let (i, es5506_amount_channel) = le_u8(i)?; 174 | let (i, c352_clock_divider) = le_u8(i)?; 175 | let (i, reserved03) = le_u8(i)?; 176 | let (i, clock_x1_010) = le_u32(i)?; 177 | let (i, clock_c352) = le_u32(i)?; 178 | let (i, clock_ga20) = le_u32(i)?; 179 | let (i, reserved04) = le_u32(i)?; 180 | let (i, reserved05) = le_u32(i)?; 181 | let (i, reserved06) = le_u32(i)?; 182 | let (i, reserved07) = le_u32(i)?; 183 | let (i, reserved08) = le_u32(i)?; 184 | let (i, reserved09) = le_u32(i)?; 185 | let (i, reserved10) = le_u32(i)?; 186 | 187 | let mut header = VgmHeader::default(); 188 | 189 | if version >= 100 { 190 | header = VgmHeader { 191 | eof, 192 | version, 193 | clock_sn76489, 194 | clock_ym2413, 195 | offset_gd3, 196 | total_samples, 197 | offset_loop, 198 | loop_samples, 199 | ..header 200 | }; 201 | } 202 | if version >= 101 { 203 | header = VgmHeader { 204 | rate, 205 | ..header 206 | }; 207 | } 208 | if version >= 110 { 209 | header = VgmHeader { 210 | sn76489_fb, 211 | sn76489_w, 212 | clock_ym2612, 213 | clock_ym2151, 214 | ..header 215 | }; 216 | } 217 | if version >= 150 { 218 | header = VgmHeader { 219 | vgm_data_offset, 220 | ..header 221 | }; 222 | } 223 | if version >= 151 { 224 | header = VgmHeader { 225 | sn76489_f, 226 | sega_pcm_clock, 227 | spcm_interface, 228 | clock_rf5c68, 229 | clock_ym2203, 230 | clock_ym2608, 231 | clock_ym2610_b, 232 | clock_ym3812, 233 | clock_ym3526, 234 | clock_y8950, 235 | clock_ymf262, 236 | clock_ymf278_b, 237 | clock_ym271, 238 | clock_ymz280b, 239 | clock_rf5c164, 240 | clock_pwm, 241 | clock_ay8910, 242 | ay8910_chip_type, 243 | ay8910_flag, 244 | loop_modifier, 245 | ..header 246 | }; 247 | } 248 | if version >= 160 { 249 | header = VgmHeader { 250 | volume_modifier, 251 | reserved01, 252 | loop_base, 253 | ..header 254 | }; 255 | } 256 | if version >= 161 { 257 | header = VgmHeader { 258 | clock_gb_dmg, 259 | clock_nes_apu, 260 | clock_multi_pcm, 261 | clock_upd7759, 262 | clock_okim6258, 263 | okmi6258_flag, 264 | k054539_flag, 265 | c140_chip_type, 266 | reserved02, 267 | clock_okim6295, 268 | clock_k051649, 269 | clock_k054539, 270 | clock_huc6280, 271 | clock_c140, 272 | clock_k053260, 273 | clock_pokey, 274 | clock_qsound, 275 | ..header 276 | }; 277 | } 278 | if version >= 170 { 279 | header = VgmHeader { 280 | extra_hdr_ofs, 281 | ..header 282 | }; 283 | } 284 | if version >= 171 { 285 | header = VgmHeader { 286 | clock_scsp, 287 | clock_wonder_swan, 288 | clock_vsu, 289 | clock_saa1099, 290 | clock_es5503, 291 | clock_es5506, 292 | es5503_amount_channel, 293 | es5506_amount_channel, 294 | c352_clock_divider, 295 | reserved03, 296 | clock_x1_010, 297 | clock_c352, 298 | clock_ga20, 299 | reserved04, 300 | reserved05, 301 | reserved06, 302 | reserved07, 303 | reserved08, 304 | reserved09, 305 | reserved10, 306 | ..header 307 | }; 308 | } 309 | 310 | Ok((i, header)) 311 | } 312 | 313 | /// 314 | /// parse_utf16_until_null 315 | /// 316 | fn parse_utf16_until_null(i: &[u8]) -> IResult<&[u8], String> { 317 | let mut string: Vec = Vec::new(); 318 | let (mut i, mut bytes) = take(2usize)(i)?; 319 | while bytes != b"\0\0" { 320 | string.push((bytes[1] as u16) << 8 | (bytes[0] as u16)); 321 | let take = take(2usize)(i)?; 322 | i = take.0; 323 | bytes = take.1; 324 | } 325 | let string = match String::from_utf16(&string) { 326 | Ok(string) => string, 327 | Err(_) => String::from("") 328 | }; 329 | 330 | Ok((i, string)) 331 | } 332 | 333 | /// 334 | /// parse_vgm_gd3 335 | /// 336 | fn parse_vgm_gd3(i: &[u8]) -> IResult<&[u8], Gd3> { 337 | let (i, _) = tag("Gd3 ")(i)?; 338 | let (i, _) = take(4usize)(i)?; // version 339 | let (i, _) = take(4usize)(i)?; // length 340 | 341 | let (i, track_name) = parse_utf16_until_null(i)?; 342 | let (i, track_name_j) = parse_utf16_until_null(i)?; 343 | let (i, game_name) = parse_utf16_until_null(i)?; 344 | let (i, game_name_j) = parse_utf16_until_null(i)?; 345 | let (i, system_name) = parse_utf16_until_null(i)?; 346 | let (i, system_name_j) = parse_utf16_until_null(i)?; 347 | let (i, track_author) = parse_utf16_until_null(i)?; 348 | let (i, track_author_j) = parse_utf16_until_null(i)?; 349 | let (i, date) = parse_utf16_until_null(i)?; 350 | let (i, converted) = parse_utf16_until_null(i)?; 351 | 352 | Ok((i, Gd3 { 353 | track_name, 354 | track_name_j, 355 | game_name, 356 | game_name_j, 357 | system_name, 358 | system_name_j, 359 | track_author, 360 | track_author_j, 361 | date, 362 | converted, 363 | })) 364 | } 365 | 366 | /// 367 | /// parse_vgm_meta 368 | /// 369 | pub(crate) fn parse_vgm_meta(vgmdata: &[u8]) -> Result<(VgmHeader, Gd3), &'static str> { 370 | let header = match parse_vgm_header(&vgmdata[..255]) { 371 | Ok((_, header)) => header, 372 | Err(_) => { 373 | return Err("vgm header parse error.") 374 | } 375 | }; 376 | let gd3 = match parse_vgm_gd3(&vgmdata[(0x14 + header.offset_gd3 as usize)..]) { 377 | Ok((_, gd3)) => gd3, 378 | Err(_) => Gd3::default() // blank values 379 | }; 380 | 381 | Ok((header, gd3)) 382 | } 383 | 384 | /// 385 | /// Jsonlize 386 | /// 387 | pub(crate) trait Jsonlize : serde::Serialize { 388 | fn get_json(&self) -> String { 389 | match serde_json::to_string(&self) { 390 | Ok(json) => json, 391 | Err(_) => String::from("") 392 | } 393 | } 394 | } 395 | 396 | impl Jsonlize for VgmHeader { } 397 | impl Jsonlize for Gd3 { } 398 | 399 | #[cfg(test)] 400 | mod tests { 401 | use super::parse_vgm_meta; 402 | use super::Jsonlize; 403 | use std::fs::File; 404 | use std::io::Read; 405 | 406 | #[test] 407 | fn test_1() { 408 | parse("../docs/vgm/ym2612.vgm") 409 | } 410 | 411 | fn parse(filepath: &str) { 412 | // load sn76489 vgm file 413 | let mut file = File::open(filepath).unwrap(); 414 | let mut buffer = Vec::new(); 415 | let _ = file.read_to_end(&mut buffer).unwrap(); 416 | let (header, gd3) = parse_vgm_meta(&buffer).unwrap(); 417 | 418 | println!("{:#?}", header); 419 | println!("{:#?}", gd3); 420 | println!("{:#?}", header.get_json()); 421 | println!("{:#?}", gd3.get_json()); 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /synth-emulator/src/rust/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate serde_derive; 3 | extern crate serde; 4 | extern crate serde_json; 5 | 6 | pub mod sound; 7 | pub mod driver; 8 | -------------------------------------------------------------------------------- /synth-emulator/src/rust/sound.rs: -------------------------------------------------------------------------------- 1 | mod pwm; 2 | mod segapcm; 3 | mod sn76489; 4 | mod ym3438; 5 | 6 | use std::cell::RefCell; 7 | use std::rc::Rc; 8 | 9 | pub use crate::sound::pwm::PWM; 10 | pub use crate::sound::segapcm::SEGAPCM; 11 | pub use crate::sound::sn76489::SN76489; 12 | pub use crate::sound::ym3438::YM3438; 13 | 14 | /// 15 | /// Device Name 16 | /// 17 | pub enum SoundDeviceName { 18 | YM3438, 19 | YM2612, 20 | SN76489, 21 | PWM, 22 | SEGAPCM, 23 | } 24 | 25 | /// 26 | /// Device common interface 27 | /// 28 | pub trait SoundDevice { 29 | fn new() -> Self; 30 | fn get_name(&self) -> SoundDeviceName; 31 | fn init(&mut self, sample_rate: u32, clock: u32); 32 | fn reset(&mut self); 33 | fn write(&mut self, port: u32, data: T); 34 | fn update( 35 | &mut self, 36 | buffer_l: &mut [f32], 37 | buffer_r: &mut [f32], 38 | numsamples: usize, 39 | buffer_pos: usize, 40 | ); 41 | } 42 | 43 | pub type RomBank = Option>>; 44 | 45 | pub trait RomDevice { 46 | fn set_rom(&mut self, rombank: RomBank); 47 | 48 | fn read_rom(rombank: &RomBank, address: usize) -> u8 { 49 | rombank.as_ref().unwrap().borrow_mut().read(address) 50 | } 51 | } 52 | 53 | /// 54 | /// Sound rom 55 | /// 56 | pub struct Rom { 57 | start_address: usize, 58 | end_address: usize, 59 | memory: Vec, 60 | } 61 | 62 | /// 63 | /// Sound rom sets 64 | /// 65 | #[derive(Default)] 66 | pub struct RomSet { 67 | rom: Vec, 68 | } 69 | 70 | impl RomSet { 71 | pub fn new() -> RomSet { 72 | RomSet { rom: Vec::new() } 73 | } 74 | 75 | pub fn add_rom(&mut self, memory: &[u8], start_address: usize, end_address: usize) { 76 | // println!("rom: {:<08x} - {:<08x}, {:<08x}, {:<02x}", start_address, end_address, memory.len(), memory[0]); 77 | // to_vec(clone) is external SPI memory simulation. 78 | self.rom.push(Rom { 79 | start_address, 80 | end_address, 81 | memory: memory.to_vec(), 82 | }); 83 | } 84 | 85 | pub fn read(&self, address: usize) -> u8 { 86 | for r in self.rom.iter() { 87 | if r.start_address <= address && r.end_address >= address { 88 | return r.memory[address - r.start_address]; 89 | } 90 | } 91 | 0 92 | } 93 | } 94 | 95 | /// 96 | /// convert_sample_i2f 97 | /// 98 | fn convert_sample_i2f(i32_sample: i32) -> f32 { 99 | let mut f32_sample: f32; 100 | if i32_sample < 0 { 101 | f32_sample = i32_sample as f32 / 32768_f32; 102 | } else { 103 | f32_sample = i32_sample as f32 / 32767_f32; 104 | } 105 | if f32_sample > 1_f32 { 106 | f32_sample = 1_f32; 107 | } 108 | if f32_sample < -1_f32 { 109 | f32_sample = -1_f32; 110 | } 111 | f32_sample 112 | } 113 | -------------------------------------------------------------------------------- /synth-emulator/src/rust/sound/pwm.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Rust PWM emulation 3 | * Hiromasa Tanaka 4 | * https://github.com/h1romas4/rust-synth-emulation 5 | * 6 | * Converted from: 7 | * Gens: PWM audio emulator 8 | * https://github.com/vgmrips/vgmplay/blob/master/VGMPlay/chips/pwm.c 9 | * rev. abbe5526a11bb0b159018646ab1cec9e44c3831e 10 | */ 11 | 12 | /** 13 | * Original Gens: PWM audio emulator 14 | */ 15 | /*************************************************************************** 16 | * Gens: PWM audio emulator. * 17 | * * 18 | * Copyright (c) 1999-2002 by Stéphane Dallongeville * 19 | * Copyright (c) 2003-2004 by Stéphane Akhoun * 20 | * Copyright (c) 2008-2009 by David Korth * 21 | * * 22 | * This program is free software; you can redistribute it and/or modify it * 23 | * under the terms of the GNU General Public License as published by the * 24 | * Free Software Foundation; either version 2 of the License, or (at your * 25 | * option) any later version. * 26 | * * 27 | * This program is distributed in the hope that it will be useful, but * 28 | * WITHOUT ANY WARRANTY; without even the implied warranty of * 29 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 30 | * GNU General Public License for more details. * 31 | * * 32 | * You should have received a copy of the GNU General Public License along * 33 | * with this program; if not, write to the Free Software Foundation, Inc., * 34 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 35 | ***************************************************************************/ 36 | 37 | // const CHILLY_WILLY_SCALE: u8 = 1; 38 | // const PWM_BUF_SIZE: usize = 4; 39 | 40 | // const PWM_FULL_TAB: [u8; PWM_BUF_SIZE * PWM_BUF_SIZE] = [ 41 | // 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 42 | // 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 43 | // 0x00, 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 44 | // 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 45 | // 0x00, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x00, 46 | // 0x00, 0x00, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 47 | // 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40, 0x00, 48 | // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40 49 | // ]; 50 | 51 | // const PWM_FULL_TAB: [u8; PWM_BUF_SIZE * PWM_BUF_SIZE] = [ 52 | // 0x40, 0x00, 0x00, 0x80, 53 | // 0x80, 0x40, 0x00, 0x00, 54 | // 0x00, 0x80, 0x40, 0x00, 55 | // 0x00, 0x00, 0x80, 0x40 56 | // ]; 57 | 58 | use crate::sound::{SoundDevice, SoundDeviceName, convert_sample_i2f}; 59 | 60 | const CHIP_SAMPLING_MODE: u8 = 0x00; 61 | const CHIP_SAMPLE_RATE: i32 = 44100; 62 | const MAX_CHIPS: usize = 0x02; 63 | const PWM_LOUDNESS: u8 = 0; 64 | 65 | pub struct PWM { 66 | pwm_chip: [PWMChip; MAX_CHIPS] 67 | } 68 | 69 | #[derive(Default)] 70 | struct PWMChip { 71 | // unsigned short PWM_FIFO_R[8]; 72 | pwm_fifo_r: [u16; 8], 73 | // unsigned short PWM_FIFO_L[8]; 74 | pwm_fifo_l: [u16; 8], 75 | // unsigned int PWM_RP_R; 76 | pwm_rp_r: u32, 77 | // unsigned int PWM_WP_R; 78 | pwm_wp_r: u32, 79 | // unsigned int PWM_RP_L; 80 | pwm_rp_l: u32, 81 | // unsigned int PWM_WP_L; 82 | pwm_wp_l: u32, 83 | // unsigned int PWM_Cycles; 84 | pwm_cycles: u32, 85 | // unsigned int PWM_Cycle; 86 | pwm_cycle: u32, 87 | // unsigned int PWM_Cycle_Cnt; 88 | pwm_cycle_cnt: u32, 89 | // unsigned int PWM_Int; 90 | pwm_int: u32, 91 | // unsigned int PWM_Int_Cnt; 92 | pwm_int_cnt: u32, 93 | // unsigned int PWM_Mode; 94 | pwm_mode: u32, 95 | // //unsigned int PWM_Enable; 96 | // unsigned int PWM_Out_R; 97 | pwm_out_r: u32, 98 | // unsigned int PWM_Out_L; 99 | pwm_out_l: u32, 100 | 101 | // unsigned int PWM_Cycle_Tmp; 102 | pwm_cycle_tmp: u32, 103 | // unsigned int PWM_Cycles_Tmp; 104 | // pwm_cycles_tmp: u32, 105 | // unsigned int PWM_Int_Tmp; 106 | pwm_int_tmp: u32, 107 | // unsigned int PWM_FIFO_L_Tmp; 108 | pwm_fifo_l_tmp: u32, 109 | // unsigned int PWM_FIFO_R_Tmp; 110 | pwm_fifo_r_tmp: u32, 111 | 112 | // #if CHILLY_WILLY_SCALE 113 | // // TODO: Fix Chilly Willy's new scaling algorithm. 114 | // /* PWM scaling variables. */ 115 | // int PWM_Offset; 116 | pwm_offset: i32, 117 | // int PWM_Scale; 118 | pwm_scale: i32, 119 | // //int PWM_Loudness; 120 | // #endif 121 | 122 | // int clock; 123 | clock: i32 124 | } 125 | 126 | #[allow(dead_code)] 127 | impl PWM { 128 | fn pwm_init(chip: &mut PWMChip) { 129 | chip.pwm_mode = 0; 130 | chip.pwm_out_r = 0; 131 | chip.pwm_out_l = 0; 132 | 133 | chip.pwm_fifo_r = [0x00; 8]; 134 | chip.pwm_fifo_l = [0x00; 8]; 135 | 136 | chip.pwm_rp_r = 0; 137 | chip.pwm_wp_r = 0; 138 | chip.pwm_rp_l = 0; 139 | chip.pwm_wp_l = 0; 140 | chip.pwm_cycle_tmp = 0; 141 | chip.pwm_int_tmp = 0; 142 | chip.pwm_fifo_l_tmp = 0; 143 | chip.pwm_fifo_r_tmp = 0; 144 | 145 | PWM::pwm_set_cycle(chip, 0); 146 | PWM::pwm_set_int(chip, 0); 147 | } 148 | 149 | fn pwm_recalc_scale(chip: &mut PWMChip) { 150 | chip.pwm_offset = (chip.pwm_cycle as i32 / 2) + 1; 151 | chip.pwm_scale = 0x7fff00 / chip.pwm_offset; 152 | } 153 | 154 | fn pwm_set_cycle(chip: &mut PWMChip, cycle: u32) { 155 | let cycle: i32 = cycle as i32 - 1; 156 | chip.pwm_cycle = cycle as u32 & 0xfff; 157 | chip.pwm_cycle_cnt = chip.pwm_cycles; 158 | 159 | PWM::pwm_recalc_scale(chip); 160 | } 161 | 162 | fn pwm_set_int(chip: &mut PWMChip, int_time: u32) { 163 | let int_time = int_time & 0x0f; 164 | if int_time != 0 { 165 | chip.pwm_int = int_time; 166 | chip.pwm_int_cnt = int_time; 167 | } else { 168 | chip.pwm_int = 16; 169 | chip.pwm_int_cnt = 16; 170 | } 171 | } 172 | 173 | fn pwm_clear_timer(&self, chip: &mut PWMChip) { 174 | chip.pwm_cycle_cnt = 0; 175 | } 176 | 177 | #[inline(always)] 178 | fn pwm_update_scale(&self, chip: &PWMChip, pwm_in: i32) -> i32 { 179 | if pwm_in == 0 { 180 | return 0; 181 | } 182 | let mut pwm_in = pwm_in & 0xfff; 183 | if pwm_in & 0x800 != 0 { 184 | pwm_in |= !0xfff; 185 | } 186 | ((pwm_in - chip.pwm_offset) * chip.pwm_scale) >> (8 - PWM_LOUDNESS) 187 | } 188 | 189 | fn pwm_update(&self, chip: &PWMChip, buffer_l: &mut [f32], buffer_r: &mut [f32], length: usize) { 190 | let tmp_out_l: i32; 191 | let tmp_out_r: i32; 192 | 193 | if chip.pwm_out_l == 0 && chip.pwm_out_r == 0 { 194 | return; 195 | } 196 | 197 | let pwm_out_l = chip.pwm_out_l as i32; 198 | let pwm_out_r = chip.pwm_out_r as i32; 199 | 200 | tmp_out_l = self.pwm_update_scale(&chip, pwm_out_l); 201 | tmp_out_r = self.pwm_update_scale(&chip, pwm_out_r); 202 | 203 | for i in 0..length { 204 | buffer_l[i] += convert_sample_i2f(tmp_out_l); 205 | buffer_r[i] += convert_sample_i2f(tmp_out_r); 206 | } 207 | } 208 | 209 | pub fn pwm_update_chip(&self, chipid: usize, buffer_l: &mut [f32], buffer_r: &mut [f32], numsamples: usize, buffer_pos: usize) { 210 | self.pwm_update(&self.pwm_chip[chipid], &mut buffer_l[buffer_pos..], &mut buffer_r[buffer_pos..], numsamples); 211 | } 212 | 213 | pub fn device_start_pwm(&mut self, chipid: usize, clock: i32) -> i32 { 214 | if chipid >= MAX_CHIPS { 215 | return 0; 216 | } 217 | 218 | let mut chip = &mut self.pwm_chip[chipid]; 219 | let mut rate: i32 = 22020; 220 | if (CHIP_SAMPLING_MODE & 0x01 != 0 && rate < CHIP_SAMPLE_RATE) || CHIP_SAMPLING_MODE == 0x20 { 221 | rate = CHIP_SAMPLE_RATE; 222 | } 223 | chip.clock = clock; 224 | 225 | PWM::pwm_init(&mut chip); 226 | 227 | rate 228 | } 229 | 230 | pub fn device_stop_pwm(&self, _chipid: usize) { 231 | } 232 | 233 | pub fn device_reset_pwm(&mut self, chipid: usize) { 234 | let mut chip = &mut self.pwm_chip[chipid]; 235 | PWM::pwm_init(&mut chip); 236 | } 237 | 238 | pub fn pwm_chn_w(&mut self, chipid: usize, channel: u8, data: u16) { 239 | let mut chip = &mut self.pwm_chip[chipid]; 240 | let data = data as u32; 241 | 242 | if chip.clock == 1 { 243 | match channel { 244 | 0x00 => { 245 | chip.pwm_out_l = data; 246 | } 247 | 0x01 => { 248 | chip.pwm_out_r = data; 249 | } 250 | 0x02 => { 251 | PWM::pwm_set_cycle(chip, data); 252 | } 253 | 0x03 => { 254 | chip.pwm_out_l = data; 255 | chip.pwm_out_r = data; 256 | } 257 | _ => {} 258 | } 259 | } else { 260 | match channel { 261 | 0x00 => { 262 | // control register 263 | PWM::pwm_set_int(chip, data >> 8); 264 | } 265 | 0x01 => { 266 | // cycle register 267 | PWM::pwm_set_cycle(chip, data); 268 | } 269 | 0x02 => { 270 | // l ch 271 | chip.pwm_out_l = data; 272 | } 273 | 0x03 => { 274 | // r ch 275 | chip.pwm_out_r = data; 276 | if chip.pwm_mode == 0 && chip.pwm_out_l == chip.pwm_out_r { 277 | // fixes these terrible pops when 278 | // starting/stopping/pausing the song 279 | chip.pwm_offset = data as i32; 280 | chip.pwm_mode = 0x01; 281 | } 282 | } 283 | 0x04 => { 284 | // mono ch 285 | chip.pwm_out_l = data; 286 | chip.pwm_out_r = data; 287 | if chip.pwm_mode == 0 { 288 | chip.pwm_offset = data as i32; 289 | chip.pwm_mode = 0x01; 290 | } 291 | } 292 | _ => {} 293 | } 294 | } 295 | } 296 | } 297 | 298 | impl SoundDevice for PWM { 299 | fn new() -> Self { 300 | PWM { 301 | pwm_chip: [PWMChip::default(), PWMChip::default()] 302 | } 303 | } 304 | 305 | fn init(&mut self, _: u32, clock: u32) { 306 | self.device_start_pwm(0, clock as i32); 307 | } 308 | 309 | fn get_name(&self) -> SoundDeviceName { 310 | SoundDeviceName::PWM 311 | } 312 | 313 | fn reset(&mut self) { 314 | self.device_reset_pwm(0); 315 | } 316 | 317 | fn write(&mut self, port: u32, data: u16) { 318 | self.pwm_chn_w(0, port as u8, data); 319 | } 320 | 321 | fn update(&mut self, buffer_l: &mut [f32], buffer_r: &mut [f32], numsamples: usize, buffer_pos: usize) { 322 | self.pwm_update_chip(0, buffer_l, buffer_r, numsamples, buffer_pos); 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /synth-emulator/src/rust/sound/segapcm.rs: -------------------------------------------------------------------------------- 1 | // license:BSD-3-Clause 2 | use crate::sound::{convert_sample_i2f, RomDevice, RomSet, RomBank, SoundDevice, SoundDeviceName}; 3 | /** 4 | * Rust SEGAPCM emulation 5 | * Hiromasa Tanaka 6 | * https://github.com/h1romas4/rust-synth-emulation 7 | * 8 | * Converted from: 9 | * MAME 10 | * copyright-holders:Hiromitsu Shioya, Olivier Galibert 11 | * https://github.com/mamedev/mame/blob/master/src/devices/sound/segapcm.cpp 12 | * rev. 70743c6fb2602a5c2666c679b618706eabfca2ad 13 | */ 14 | use std::{cell::RefCell, rc::Rc}; 15 | 16 | #[allow(clippy::upper_case_acronyms)] 17 | pub struct SEGAPCM { 18 | clock: u32, 19 | ram: [u8; 0x800], 20 | romset: RomBank, 21 | bankshift: u8, 22 | bankmask: u8, 23 | low: [u8; 16], 24 | } 25 | 26 | impl SEGAPCM { 27 | fn new() -> Self { 28 | Self { 29 | clock: 0, 30 | bankshift: 12, 31 | bankmask: 0x70, 32 | ram: [0xff; 0x800], 33 | romset: None, 34 | low: [0; 16], 35 | } 36 | } 37 | 38 | fn init(&mut self, clock: u32) { 39 | self.clock = clock; 40 | } 41 | 42 | fn reset(&mut self) { 43 | self.ram = [0xff; 0x800]; 44 | self.low = [0; 16]; 45 | } 46 | 47 | fn update( 48 | &mut self, 49 | buffer_l: &mut [f32], 50 | buffer_r: &mut [f32], 51 | numsamples: usize, 52 | buffer_pos: usize, 53 | ) { 54 | // reg function 55 | // ------------------------------------------------ 56 | // 0x00 ? 57 | // 0x01 ? 58 | // 0x02 volume left 59 | // 0x03 volume right 60 | // 0x04 loop address (08-15) 61 | // 0x05 loop address (16-23) 62 | // 0x06 end address 63 | // 0x07 address delta 64 | // 0x80 ? 65 | // 0x81 ? 66 | // 0x82 ? 67 | // 0x83 ? 68 | // 0x84 current address (08-15), 00-07 is internal? 69 | // 0x85 current address (16-23) 70 | // 0x86 bit 0: channel disable? 71 | // bit 1: loop disable 72 | // other bits: bank 73 | // 0x87 ? 74 | for ch in 0..16 { 75 | let regs = &mut self.ram[ch * 8..]; 76 | 77 | /* only process active channels */ 78 | if regs[0x86] & 1 == 0 { 79 | let offset: i32 = i32::from(regs[0x86] & self.bankmask) << self.bankshift; 80 | let mut addr: u32 = u32::from(regs[0x85]) << 16 81 | | u32::from(regs[0x84]) << 8 82 | | u32::from(self.low[ch]); 83 | let loops: u32 = u32::from(regs[0x05]) << 16 | u32::from(regs[0x04]) << 8; 84 | let end: u32 = u32::from(regs[6]) + 1; 85 | 86 | for i in 0..numsamples { 87 | /* handle looping if we've hit the end */ 88 | if (addr >> 16) == end { 89 | if regs[0x86] & 2 != 0 { 90 | regs[0x86] |= 1; 91 | break; 92 | } else { 93 | addr = loops; 94 | } 95 | } 96 | /* fetch the sample */ 97 | let v = SEGAPCM::read_rom(&self.romset, offset as usize + (addr >> 8) as usize); 98 | let v: i32 = i32::from(v) - 0x80; 99 | /* apply panning and advance */ 100 | buffer_l[buffer_pos + i] += 101 | convert_sample_i2f(v * (regs[2] & 0x7f) as i32); 102 | buffer_r[buffer_pos + i] += 103 | convert_sample_i2f(v * (regs[3] & 0x7f) as i32); 104 | addr = (addr + regs[7] as u32) & 0xffffff; 105 | } 106 | /* store back the updated address */ 107 | regs[0x84] = (addr >> 8) as u8; 108 | regs[0x85] = (addr >> 16) as u8; 109 | self.low[ch] = if regs[0x86] & 1 != 0 { 0 } else { addr as u8 }; 110 | } 111 | } 112 | } 113 | 114 | fn write(&mut self, offset: u32, data: u8) { 115 | self.ram[offset as usize & 0x07ff] = data; 116 | } 117 | } 118 | 119 | impl SoundDevice for SEGAPCM { 120 | fn new() -> Self { 121 | SEGAPCM::new() 122 | } 123 | 124 | fn init(&mut self, _: u32, clock: u32) { 125 | self.init(clock); 126 | } 127 | 128 | fn get_name(&self) -> SoundDeviceName { 129 | SoundDeviceName::SEGAPCM 130 | } 131 | 132 | fn reset(&mut self) { 133 | self.reset(); 134 | } 135 | 136 | fn write(&mut self, offset: u32, data: u8) { 137 | self.write(offset, data); 138 | } 139 | 140 | fn update( 141 | &mut self, 142 | buffer_l: &mut [f32], 143 | buffer_r: &mut [f32], 144 | numsamples: usize, 145 | buffer_pos: usize, 146 | ) { 147 | self.update(buffer_l, buffer_r, numsamples, buffer_pos); 148 | } 149 | } 150 | 151 | impl RomDevice for SEGAPCM { 152 | fn set_rom(&mut self, romset: Option>>) { 153 | self.romset = romset; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /synth-emulator/src/rust/sound/sn76489.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Rust SN76489 emulation 3 | * Hiromasa Tanaka 4 | * https://github.com/h1romas4/rust-synth-emulation 5 | * 6 | * Converted from: 7 | * SN76489 emulation by Maxim 8 | * https://github.com/vgmrips/vgmplay/blob/master/VGMPlay/chips/sn76489.c 9 | */ 10 | 11 | /** 12 | * Original SN76489 emulation Copyright 13 | */ 14 | /* 15 | SN76489 emulation 16 | by Maxim in 2001 and 2002 17 | converted from my original Delphi implementation 18 | I'm a C newbie so I'm sure there are loads of stupid things 19 | in here which I'll come back to some day and redo 20 | Includes: 21 | - Super-high quality tone channel "oversampling" by calculating fractional positions on transitions 22 | - Noise output pattern reverse engineered from actual SMS output 23 | - Volume levels taken from actual SMS output 24 | 07/08/04 Charles MacDonald 25 | Modified for use with SMS Plus: 26 | - Added support for multiple PSG chips. 27 | - Added reset/config/update routines. 28 | - Added context management routines. 29 | - Removed SN76489_GetValues(). 30 | - Removed some unused variables. 31 | */ 32 | 33 | use std::f32; 34 | use std::i32; 35 | 36 | use crate::sound::{SoundDevice, SoundDeviceName, convert_sample_i2f}; 37 | 38 | // More testing is needed to find and confirm feedback patterns for 39 | // SN76489 variants and compatible chips. 40 | #[allow(dead_code)] 41 | #[allow(clippy::enum_variant_names)] 42 | enum FeedbackPatterns { 43 | // Texas Instruments TMS SN76489N (original) from BBC Micro computer 44 | FbBbcmicro = 0x8005, 45 | // Texas Instruments TMS SN76489AN (rev. A) from SC-3000H computer 46 | FbSc3000 = 0x0006, 47 | // SN76489 clone in Sega's VDP chips (315-5124, 315-5246, 315-5313, Game Gear) 48 | FbSegavdp = 0x0009 49 | } 50 | 51 | #[allow(dead_code)] 52 | enum SrWidths { 53 | SrwSc3000bbcmicro = 15, 54 | SrwSegavdp = 16 55 | } 56 | 57 | #[allow(dead_code)] 58 | enum VolumeModes { 59 | // Volume levels 13-15 are identical 60 | VolTrunc = 0, 61 | // Volume levels 13-15 are unique 62 | VolFull = 1 63 | } 64 | 65 | #[allow(dead_code)] 66 | enum MuteValues { 67 | // All channels muted 68 | AllOff = 0, 69 | // Tone 1 mute control 70 | Tone1On = 1, 71 | // Tone 2 mute control 72 | Tone2On = 2, 73 | // Tone 3 mute control 74 | Tone3On = 4, 75 | // Noise mute control 76 | NoiseOn = 8, 77 | // All channels enabled 78 | AllOn = 15 79 | } 80 | 81 | // Initial state of shift register 82 | const NOISE_INITIAL_STATE: i32 = 0x8000; 83 | const PSG_CUTOFF: i32 = 0x6; 84 | 85 | const PSG_VOLUME_VALUES: [i32; 16] = [ 86 | // These values are taken from a real SMS2's output 87 | // {892,892,892,760,623,497,404,323,257,198,159,123,96,75,60,0}, // I can't remember why 892...:P some scaling I did at some point 88 | // these values are true volumes for 2dB drops at each step (multiply previous by 10^-0.1) 89 | // 1516,1205,957,760,603,479,381,303,240,191,152,120,96,76,60,0 90 | // The MAME core uses 0x2000 as maximum volume (0x1000 for bipolar output) 91 | 4096, 3254, 2584, 2053, 1631, 1295, 1029, 817, 649, 516, 410, 325, 258, 205, 163, 0 92 | ]; 93 | 94 | #[derive(Default)] 95 | pub struct SN76489 { 96 | // per-channel muting 97 | mute: i32, 98 | 99 | // Variables 100 | clock: f32, 101 | d_clock: f32, 102 | psg_stereo: i32, 103 | num_clocks_for_sample: i32, 104 | white_noise_feedback: i32, 105 | sr_width: i32, 106 | 107 | // PSG registers: 108 | // Tone, vol x4 109 | registers: [i32; 8], 110 | latched_register: i32, 111 | noise_shift_register: i32, 112 | // Noise channel signal generator frequency 113 | noise_freq: i32, 114 | 115 | // Output calculation variables 116 | // Frequency register values (counters) 117 | tone_freq_vals: [i32; 4], 118 | // Frequency channel flip-flops 119 | tone_freq_pos: [i32; 4], 120 | // Value of each channel, before stereo is applied 121 | channels: [i32; 4], 122 | // intermediate values used at boundaries between + and - (does not need double accuracy) 123 | intermediate_pos: [f32; 4], 124 | } 125 | 126 | impl SN76489 { 127 | pub fn init(&mut self, psg_clock_value: i32, sampling_rate: i32) { 128 | self.d_clock = (psg_clock_value & 0x07ff_ffff) as f32 / 16_f32 / sampling_rate as f32; 129 | 130 | self.mute(MuteValues::AllOn); 131 | self.config(FeedbackPatterns::FbSegavdp, SrWidths::SrwSegavdp); 132 | } 133 | 134 | pub fn reset(&mut self) { 135 | self.psg_stereo = 0xff; 136 | 137 | for i in 0..4 { 138 | // Initialise PSG state 139 | self.registers[2 * i] = 1; 140 | self.registers[2 * i + 1] = 0xf; 141 | self.noise_freq = 0x10; 142 | 143 | // Set counters to 0 144 | self.tone_freq_vals[i] = 0; 145 | 146 | // Set flip-flops to 1 147 | self.tone_freq_pos[i] = 1; 148 | 149 | // Set intermediate positions to do-not-use value 150 | self.intermediate_pos[i] = f32::MIN; 151 | } 152 | 153 | self.latched_register = 0; 154 | 155 | // Initialise noise generator 156 | self.noise_shift_register = NOISE_INITIAL_STATE; 157 | 158 | // Zero clock 159 | self.clock = 0_f32; 160 | } 161 | 162 | pub fn write(&mut self, data: u8) { 163 | let data : u16 = u16::from(data); 164 | if data & 0x80 != 0 { 165 | self.latched_register = i32::from(data >> 4) & 0x07; 166 | self.registers[self.latched_register as usize] = 167 | // zero low 4 bits 168 | (self.registers[self.latched_register as usize] & 0x3f0) 169 | // and replace with data 170 | | i32::from(data) & 0xf; 171 | } else { 172 | // Data byte %0 - dddddd 173 | if (self.latched_register % 2) == 0 && (self.latched_register < 5) { 174 | // Tone register 175 | self.registers[self.latched_register as usize] = 176 | // zero high 6 bits 177 | (self.registers[self.latched_register as usize] & 0x00f) 178 | // and replace with data 179 | | i32::from(data & 0x3f) << 4; 180 | } else { 181 | // Other register 182 | // Replace with data 183 | self.registers[self.latched_register as usize] = i32::from(data) & 0x0f; 184 | } 185 | } 186 | 187 | match self.latched_register { 188 | 0 | 2 | 4 => { 189 | // Tone channels 190 | if self.registers[self.latched_register as usize] == 0 { 191 | // Zero frequency changed to 1 to avoid div/0 192 | self.registers[self.latched_register as usize] = 1; 193 | } 194 | } 195 | 6 => { 196 | // Noise 197 | // reset shift register 198 | self.noise_shift_register = NOISE_INITIAL_STATE; 199 | // set noise signal generator frequency 200 | self.noise_freq = 0x10 << (self.registers[6] & 0x3); 201 | } 202 | _ => { 203 | // println!("through latched_register value {:x}", self.latched_register); 204 | } 205 | } 206 | } 207 | 208 | pub fn update(&mut self, buffer_l: &mut [f32], buffer_r: &mut [f32], length: usize, buffer_pos: usize) { 209 | for j in 0..length { 210 | // Tone channels 211 | for i in 0..3 { 212 | if (self.mute >> i) & 1 != 0 { 213 | if self.intermediate_pos[i] != f32::MIN { 214 | // Intermediate position (antialiasing) 215 | self.channels[i] = 216 | (PSG_VOLUME_VALUES[self.registers[2 * i + 1] as usize] as f32 217 | * self.intermediate_pos[i]) as i32; 218 | } else { 219 | // Flat (no antialiasing needed) 220 | self.channels[i] = 221 | (PSG_VOLUME_VALUES[self.registers[2 * i + 1] as usize] 222 | * self.tone_freq_pos[i]) as i32; 223 | } 224 | } else { 225 | // Muted channel 226 | self.channels[i] = 0; 227 | } 228 | } 229 | 230 | // Noise channel 231 | if (self.mute >> 3) & 1 != 0 { 232 | // Now the noise is bipolar, too. -Valley Bell 233 | self.channels[3] = PSG_VOLUME_VALUES[self.registers[7] as usize] 234 | * ((self.noise_shift_register & 0x01) * 2 - 1); 235 | // due to the way the white noise works here, it seems twice as loud as it should be 236 | if (self.registers[6] & 0x4) != 0 { 237 | self.channels[3] >>= 1; 238 | } 239 | } else { 240 | self.channels[3] = 0; 241 | } 242 | 243 | // Build stereo result into buffer (clear buffer) 244 | let mut buffer_li: i32 = 0; 245 | let mut buffer_ri: i32 = 0; 246 | for i in 0..4 { 247 | buffer_li += self.channels[i]; 248 | buffer_ri += self.channels[i]; 249 | } 250 | buffer_l[j + buffer_pos] += convert_sample_i2f(buffer_li / 2); 251 | buffer_r[j + buffer_pos] += convert_sample_i2f(buffer_ri / 2); 252 | 253 | // Increment clock by 1 sample length 254 | self.clock += self.d_clock; 255 | self.num_clocks_for_sample = self.clock as i32; 256 | self.clock -= self.num_clocks_for_sample as f32; 257 | 258 | // Decrement tone channel counters 259 | for i in 0..3 { 260 | self.tone_freq_vals[i] -= self.num_clocks_for_sample; 261 | } 262 | 263 | // Noise channel: match to tone2 or decrement its counter 264 | if self.noise_freq == 0x80 { 265 | self.tone_freq_vals[3] = self.tone_freq_vals[2]; 266 | } else { 267 | self.tone_freq_vals[3] -= self.num_clocks_for_sample; 268 | } 269 | 270 | // Tone channels: 271 | for i in 0..3 { 272 | // If the counter gets below 0... 273 | if self.tone_freq_vals[i] <= 0 { 274 | if self.registers[i * 2] >= PSG_CUTOFF { 275 | // For tone-generating values, calculate how much of the sample is + and how much is - 276 | // This is optimised into an even more confusing state than it was in the first place... 277 | self.intermediate_pos[i] = 278 | (self.num_clocks_for_sample as f32 - self.clock + 2_f32 * self.tone_freq_vals[i] as f32) 279 | * self.tone_freq_pos[i] as f32 280 | / (self.num_clocks_for_sample as f32 + self.clock); 281 | // Flip the flip-flop 282 | self.tone_freq_pos[i] = -self.tone_freq_pos[i]; 283 | } else { 284 | // stuck value 285 | self.tone_freq_pos[i] = 1; 286 | self.intermediate_pos[i] = f32::MIN; 287 | } 288 | self.tone_freq_vals[i] += self.registers[i * 2] * 289 | (self.num_clocks_for_sample / self.registers[i * 2] + 1); 290 | } else { 291 | // signal no antialiasing needed 292 | self.intermediate_pos[i] = f32::MIN; 293 | } 294 | } 295 | 296 | // Noise channel 297 | if self.tone_freq_vals[3] <= 0 { 298 | // If the counter gets below 0... 299 | // Flip the flip-flop 300 | self.tone_freq_pos[3] = -self.tone_freq_pos[3]; 301 | if self.noise_freq != 0x80 { 302 | // If not matching tone2, decrement counter 303 | self.tone_freq_vals[3] += self.noise_freq * (self.num_clocks_for_sample / self.noise_freq + 1); 304 | } 305 | if self.tone_freq_pos[3] == 1 { 306 | // On the positive edge of the square wave (only once per cycle) 307 | let mut feedback: i32; 308 | if self.registers[6] & 0x4 != 0 { 309 | // White noise 310 | // Calculate parity of fed-back bits for feedback 311 | match self.white_noise_feedback { 312 | // Do some optimised calculations for common (known) feedback values 313 | // SC-3000, BBC %00000011 314 | // SMS, GG, MD %00001001 315 | 0x0003 | 0x0009 => { 316 | // If two bits fed back, I can do Feedback=(nsr & fb) && (nsr & fb ^ fb) 317 | // since that's (one or more bits set) && (not all bits set) 318 | let f1 = self.noise_shift_register & self.white_noise_feedback; 319 | let f2 = (self.noise_shift_register & self.white_noise_feedback) ^ self.white_noise_feedback; 320 | if f1 != 0 && f2 != 0 { 321 | feedback = 1; 322 | } else { 323 | feedback = 0; 324 | } 325 | } 326 | _ => { 327 | // Default handler for all other feedback values 328 | // XOR fold bits into the final bit 329 | feedback = self.noise_shift_register & self.white_noise_feedback; 330 | feedback ^= feedback >> 8; 331 | feedback ^= feedback >> 4; 332 | feedback ^= feedback >> 2; 333 | feedback ^= feedback >> 1; 334 | feedback &= 1; 335 | } 336 | } 337 | } else { 338 | feedback = self.noise_shift_register & 1; 339 | } 340 | self.noise_shift_register = (self.noise_shift_register >> 1) 341 | | (feedback << (self.sr_width - 1)); 342 | } 343 | } 344 | } 345 | } 346 | 347 | fn config(&mut self, feedback: FeedbackPatterns, sr_width: SrWidths) { 348 | self.white_noise_feedback = feedback as i32; 349 | self.sr_width = sr_width as i32; 350 | } 351 | 352 | fn mute(&mut self, mask: MuteValues) { 353 | self.mute = mask as i32; 354 | } 355 | } 356 | 357 | impl SoundDevice for SN76489 { 358 | fn new() -> Self { 359 | SN76489::default() 360 | } 361 | 362 | fn init(&mut self, sample_rate: u32, clock: u32) { 363 | self.init(clock as i32, sample_rate as i32); 364 | self.reset(); 365 | } 366 | 367 | fn get_name(&self) -> SoundDeviceName { 368 | SoundDeviceName::SN76489 369 | } 370 | 371 | fn reset(&mut self) { 372 | self.reset(); 373 | } 374 | 375 | fn write(&mut self, _: u32, data: u8) { 376 | self.write(data); 377 | } 378 | 379 | fn update(&mut self, buffer_l: &mut [f32], buffer_r: &mut [f32], numsamples: usize, buffer_pos: usize) { 380 | self.update(buffer_l, buffer_r, numsamples, buffer_pos); 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /wasm-synth-player/.cargo/config.toml.not_yet_support_webpack: -------------------------------------------------------------------------------- 1 | # webpack5(webassebmlyjs) is not yet able to bundle WebAssembly with bulk memory feature. 2 | 3 | # @see also WebAssembly with Reference Types cannot be parsed #13559 4 | # https://github.com/webpack/webpack/issues/13559 5 | 6 | [build] 7 | rustflags = '-Ctarget-feature=+bulk-memory' 8 | -------------------------------------------------------------------------------- /wasm-synth-player/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true 6 | }, 7 | "parser": "babel-eslint", 8 | "extends": "eslint:recommended", 9 | "parserOptions": { 10 | "sourceType": "module" 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /wasm-synth-player/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "array-macro" 13 | version = "1.0.5" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "06e97b4e522f9e55523001238ac59d13a8603af57f69980de5d8de4bbbe8ada6" 16 | 17 | [[package]] 18 | name = "arrayvec" 19 | version = "0.5.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 22 | 23 | [[package]] 24 | name = "autocfg" 25 | version = "1.0.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 28 | 29 | [[package]] 30 | name = "bitflags" 31 | version = "1.2.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 34 | 35 | [[package]] 36 | name = "bumpalo" 37 | version = "3.7.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" 40 | 41 | [[package]] 42 | name = "cfg-if" 43 | version = "0.1.10" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 46 | 47 | [[package]] 48 | name = "cfg-if" 49 | version = "1.0.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 52 | 53 | [[package]] 54 | name = "chiptune" 55 | version = "0.4.0" 56 | dependencies = [ 57 | "console_error_panic_hook", 58 | "synth-emulator", 59 | "wasm-bindgen", 60 | "wasm-bindgen-test", 61 | "wee_alloc", 62 | ] 63 | 64 | [[package]] 65 | name = "console_error_panic_hook" 66 | version = "0.1.6" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211" 69 | dependencies = [ 70 | "cfg-if 0.1.10", 71 | "wasm-bindgen", 72 | ] 73 | 74 | [[package]] 75 | name = "crc32fast" 76 | version = "1.2.1" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" 79 | dependencies = [ 80 | "cfg-if 1.0.0", 81 | ] 82 | 83 | [[package]] 84 | name = "flate2" 85 | version = "1.0.20" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" 88 | dependencies = [ 89 | "cfg-if 1.0.0", 90 | "crc32fast", 91 | "libc", 92 | "miniz_oxide", 93 | ] 94 | 95 | [[package]] 96 | name = "itoa" 97 | version = "0.4.7" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 100 | 101 | [[package]] 102 | name = "js-sys" 103 | version = "0.3.51" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062" 106 | dependencies = [ 107 | "wasm-bindgen", 108 | ] 109 | 110 | [[package]] 111 | name = "lazy_static" 112 | version = "1.4.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 115 | 116 | [[package]] 117 | name = "lexical-core" 118 | version = "0.7.6" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" 121 | dependencies = [ 122 | "arrayvec", 123 | "bitflags", 124 | "cfg-if 1.0.0", 125 | "ryu", 126 | "static_assertions", 127 | ] 128 | 129 | [[package]] 130 | name = "libc" 131 | version = "0.2.97" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" 134 | 135 | [[package]] 136 | name = "log" 137 | version = "0.4.14" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 140 | dependencies = [ 141 | "cfg-if 1.0.0", 142 | ] 143 | 144 | [[package]] 145 | name = "memchr" 146 | version = "2.4.0" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" 149 | 150 | [[package]] 151 | name = "memory_units" 152 | version = "0.4.0" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" 155 | 156 | [[package]] 157 | name = "miniz_oxide" 158 | version = "0.4.4" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 161 | dependencies = [ 162 | "adler", 163 | "autocfg", 164 | ] 165 | 166 | [[package]] 167 | name = "nom" 168 | version = "5.1.2" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" 171 | dependencies = [ 172 | "lexical-core", 173 | "memchr", 174 | "version_check", 175 | ] 176 | 177 | [[package]] 178 | name = "proc-macro2" 179 | version = "1.0.27" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" 182 | dependencies = [ 183 | "unicode-xid", 184 | ] 185 | 186 | [[package]] 187 | name = "quote" 188 | version = "1.0.9" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 191 | dependencies = [ 192 | "proc-macro2", 193 | ] 194 | 195 | [[package]] 196 | name = "ryu" 197 | version = "1.0.5" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 200 | 201 | [[package]] 202 | name = "scoped-tls" 203 | version = "1.0.0" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" 206 | 207 | [[package]] 208 | name = "serde" 209 | version = "1.0.126" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" 212 | 213 | [[package]] 214 | name = "serde_derive" 215 | version = "1.0.126" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" 218 | dependencies = [ 219 | "proc-macro2", 220 | "quote", 221 | "syn", 222 | ] 223 | 224 | [[package]] 225 | name = "serde_json" 226 | version = "1.0.64" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" 229 | dependencies = [ 230 | "itoa", 231 | "ryu", 232 | "serde", 233 | ] 234 | 235 | [[package]] 236 | name = "static_assertions" 237 | version = "1.1.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 240 | 241 | [[package]] 242 | name = "syn" 243 | version = "1.0.73" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" 246 | dependencies = [ 247 | "proc-macro2", 248 | "quote", 249 | "unicode-xid", 250 | ] 251 | 252 | [[package]] 253 | name = "synth-emulator" 254 | version = "0.3.0" 255 | dependencies = [ 256 | "array-macro", 257 | "flate2", 258 | "nom", 259 | "serde", 260 | "serde_derive", 261 | "serde_json", 262 | ] 263 | 264 | [[package]] 265 | name = "unicode-xid" 266 | version = "0.2.2" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 269 | 270 | [[package]] 271 | name = "version_check" 272 | version = "0.9.3" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 275 | 276 | [[package]] 277 | name = "wasm-bindgen" 278 | version = "0.2.74" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" 281 | dependencies = [ 282 | "cfg-if 1.0.0", 283 | "wasm-bindgen-macro", 284 | ] 285 | 286 | [[package]] 287 | name = "wasm-bindgen-backend" 288 | version = "0.2.74" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" 291 | dependencies = [ 292 | "bumpalo", 293 | "lazy_static", 294 | "log", 295 | "proc-macro2", 296 | "quote", 297 | "syn", 298 | "wasm-bindgen-shared", 299 | ] 300 | 301 | [[package]] 302 | name = "wasm-bindgen-futures" 303 | version = "0.4.24" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "5fba7978c679d53ce2d0ac80c8c175840feb849a161664365d1287b41f2e67f1" 306 | dependencies = [ 307 | "cfg-if 1.0.0", 308 | "js-sys", 309 | "wasm-bindgen", 310 | "web-sys", 311 | ] 312 | 313 | [[package]] 314 | name = "wasm-bindgen-macro" 315 | version = "0.2.74" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" 318 | dependencies = [ 319 | "quote", 320 | "wasm-bindgen-macro-support", 321 | ] 322 | 323 | [[package]] 324 | name = "wasm-bindgen-macro-support" 325 | version = "0.2.74" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" 328 | dependencies = [ 329 | "proc-macro2", 330 | "quote", 331 | "syn", 332 | "wasm-bindgen-backend", 333 | "wasm-bindgen-shared", 334 | ] 335 | 336 | [[package]] 337 | name = "wasm-bindgen-shared" 338 | version = "0.2.74" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" 341 | 342 | [[package]] 343 | name = "wasm-bindgen-test" 344 | version = "0.3.24" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "8cab416a9b970464c2882ed92d55b0c33046b08e0bdc9d59b3b718acd4e1bae8" 347 | dependencies = [ 348 | "console_error_panic_hook", 349 | "js-sys", 350 | "scoped-tls", 351 | "wasm-bindgen", 352 | "wasm-bindgen-futures", 353 | "wasm-bindgen-test-macro", 354 | ] 355 | 356 | [[package]] 357 | name = "wasm-bindgen-test-macro" 358 | version = "0.3.24" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "dd4543fc6cf3541ef0d98bf720104cc6bd856d7eba449fd2aa365ef4fed0e782" 361 | dependencies = [ 362 | "proc-macro2", 363 | "quote", 364 | ] 365 | 366 | [[package]] 367 | name = "web-sys" 368 | version = "0.3.51" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582" 371 | dependencies = [ 372 | "js-sys", 373 | "wasm-bindgen", 374 | ] 375 | 376 | [[package]] 377 | name = "wee_alloc" 378 | version = "0.4.5" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" 381 | dependencies = [ 382 | "cfg-if 0.1.10", 383 | "libc", 384 | "memory_units", 385 | "winapi", 386 | ] 387 | 388 | [[package]] 389 | name = "winapi" 390 | version = "0.3.9" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 393 | dependencies = [ 394 | "winapi-i686-pc-windows-gnu", 395 | "winapi-x86_64-pc-windows-gnu", 396 | ] 397 | 398 | [[package]] 399 | name = "winapi-i686-pc-windows-gnu" 400 | version = "0.4.0" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 403 | 404 | [[package]] 405 | name = "winapi-x86_64-pc-windows-gnu" 406 | version = "0.4.0" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 409 | -------------------------------------------------------------------------------- /wasm-synth-player/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chiptune" 3 | version = "0.4.0" 4 | authors = ["Hiromasa Tanaka "] 5 | edition = "2018" 6 | description = "Chiptune Synthesizer and Player" 7 | license = "GPL-2.0-or-later" 8 | repository = "https://github.com/h1romas4/rust-synth-emulation" 9 | 10 | [lib] 11 | crate-type = ["cdylib", "rlib"] 12 | path = "src/rust/lib.rs" 13 | 14 | [features] 15 | default = ["console_error_panic_hook"] 16 | 17 | [dependencies] 18 | synth-emulator = { path = "../synth-emulator" } 19 | wasm-bindgen = "0.2" 20 | console_error_panic_hook = { version = "0.1", optional = true } 21 | wee_alloc = { version = "0.4", optional = true } 22 | 23 | [dev-dependencies] 24 | wasm-bindgen-test = "0.3" 25 | 26 | [profile.release] 27 | opt-level = "s" 28 | -------------------------------------------------------------------------------- /wasm-synth-player/LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 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 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /wasm-synth-player/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h1romas4/rust-synth-emulation/c89b88c450e704c96c2e2c8a707eea93f04b46aa/wasm-synth-player/README.md -------------------------------------------------------------------------------- /wasm-synth-player/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wasm-synth-player", 3 | "version": "0.1.0", 4 | "description": "wasm-synth-player", 5 | "license": "GPL-2.0-or-later", 6 | "dependencies": { 7 | "chiptune": "file:./pkg" 8 | }, 9 | "devDependencies": { 10 | "@babel/core": "^7.14.6", 11 | "@babel/preset-env": "^7.14.7", 12 | "babel-eslint": "^10.1.0", 13 | "babel-loader": "^8.2.2", 14 | "copy-webpack-plugin": "^9.0.1", 15 | "eslint": "^7.29.0", 16 | "htmlhint": "^0.9.9", 17 | "webpack": "^5.42.0", 18 | "webpack-cli": "^4.7.2", 19 | "webpack-dev-server": "^3.11.2", 20 | "webpack-merge": "^5.8.0", 21 | "websocket-extensions": ">=0.1.4" 22 | }, 23 | "scripts": { 24 | "build": "webpack --config webpack.config.js", 25 | "start": "webpack serve --config webpack.dev.js" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /wasm-synth-player/src/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | // A dependency graph that contains any wasm must all be imported 2 | // asynchronously. This `bootstrap.js` file does the single async import, so 3 | // that no one else needs to worry about it again. 4 | import("./index.js") 5 | .catch(e => console.error("Error importing `index.js`:", e)); 6 | -------------------------------------------------------------------------------- /wasm-synth-player/src/js/index.js: -------------------------------------------------------------------------------- 1 | import { WgmPlay } from "chiptune"; 2 | import { memory } from "chiptune/chiptune_bg.wasm"; 3 | 4 | // vgm setting 5 | const MAX_SAMPLING_BUFFER = 4096; 6 | const SAMPLING_RATE = 44100; 7 | const LOOP_MAX_COUNT = 2; 8 | const FEED_OUT_SECOND = 2; 9 | const FEED_OUT_REMAIN = (SAMPLING_RATE * FEED_OUT_SECOND) / MAX_SAMPLING_BUFFER; 10 | 11 | // canvas settings 12 | const CANVAS_WIDTH = 768; 13 | const CANVAS_HEIGHT = 576; 14 | const COLOR_MD_GREEN = '#00a040'; 15 | const COLOR_MD_RED = '#e60012'; 16 | const FONT_MAIN_STYLE = '16px sans-serif'; 17 | 18 | // vgm member 19 | let wgmplay = null; 20 | let seqdata; 21 | let samplingBufferL; 22 | let samplingBufferR; 23 | let feedOutCount = 0; 24 | let playlist = []; 25 | let totalPlaylistCount; 26 | let music_meta; 27 | 28 | /** 29 | * audio context 30 | */ 31 | let audioContext = null; 32 | let audioNode = null; 33 | let audioGain; 34 | let audioAnalyser; 35 | let audioAnalyserBuffer; 36 | let audioAnalyserBufferLength; 37 | 38 | // canvas member 39 | let canvas; 40 | let canvasContext; 41 | let animId = null; 42 | 43 | // canvas setting 44 | canvas = document.getElementById('screen'); 45 | canvas.setAttribute('width', CANVAS_WIDTH); 46 | canvas.setAttribute('height', CANVAS_HEIGHT); 47 | let pixelRatio = window.devicePixelRatio ? window.devicePixelRatio : 1; 48 | if(pixelRatio > 1 && window.screen.width < CANVAS_WIDTH) { 49 | canvas.style.width = 320 + "px"; 50 | canvas.style.heigth = 240 + "px"; 51 | } 52 | canvasContext = canvas.getContext('2d'); 53 | 54 | /** 55 | * load sample vgm data 56 | */ 57 | fetch('./vgm/ym2612.vgm') 58 | .then(response => response.arrayBuffer()) 59 | .then(bytes => { init(bytes); }) 60 | .then(() => { 61 | canvas.addEventListener('click', play, false); 62 | canvas.addEventListener('dragover', function(e) { 63 | prevent(e); 64 | canvas.style.border = '4px dotted #333333'; 65 | return false; 66 | }, false); 67 | canvas.addEventListener('dragleave', function(e) { 68 | prevent(e); 69 | canvas.style.border = 'none'; 70 | return false; 71 | }); 72 | canvas.addEventListener('drop', onDrop, false); 73 | // for sample vgm 74 | totalPlaylistCount = 1; 75 | music_meta = createGd3meta({ 76 | track_name: "MEGADRIVE/GENESIS VGM(vgm/vgz) Player", 77 | track_name_j: "", 78 | game_name: "", 79 | game_name_j: "YM2612 sample VGM", 80 | track_author: "@h1romas4", 81 | track_author_j: "" 82 | }); 83 | // ready to go 84 | startScreen(); 85 | }); 86 | 87 | /** 88 | * fillTextCenterd 89 | */ 90 | let fillTextCenterd = function(str, height) { 91 | let left = (CANVAS_WIDTH - canvasContext.measureText(str).width) / 2; 92 | canvasContext.fillText(str, left, height); 93 | } 94 | 95 | /** 96 | * startScreen 97 | */ 98 | let startScreen = function() { 99 | canvasContext.fillStyle = 'rgb(0, 0, 0)'; 100 | canvasContext.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); 101 | canvasContext.font = '20px sans-serif'; 102 | canvasContext.fillStyle = COLOR_MD_GREEN; 103 | fillTextCenterd("DRAG AND DROP MEGADRIVE/GENESIS VGM(vgm/vgz) HEAR!", CANVAS_HEIGHT / 2 - 64); 104 | fillTextCenterd("OR CLICK TO PLAY SAMPLE VGM.", CANVAS_HEIGHT / 2 - 32); 105 | }; 106 | 107 | /** 108 | * event prevent 109 | */ 110 | let prevent = function(e) { 111 | e.preventDefault(); 112 | e.stopPropagation(); 113 | }; 114 | 115 | /** 116 | * Drag and Drop 117 | */ 118 | let onDrop = function(ev) { 119 | prevent(ev); 120 | canvas.removeEventListener('drop', onDrop, false); 121 | canvas.style.border = 'none'; 122 | let filelist = {}; 123 | let files = ev.dataTransfer.files; 124 | [].forEach.call(files, function(file) { 125 | let reader = new FileReader(); 126 | reader.onload = function() { 127 | filelist[file.name] = reader.result; 128 | if(Object.keys(filelist).length >= files.length) { 129 | canvas.addEventListener('drop', onDrop, false); 130 | playlist = []; 131 | Object.keys(filelist).sort().forEach(function(key) { 132 | playlist.push(filelist[key]); 133 | }); 134 | totalPlaylistCount = playlist.length; 135 | next(); 136 | } 137 | }; 138 | reader.readAsArrayBuffer(file); 139 | }); 140 | return false; 141 | }; 142 | 143 | /** 144 | * play next playlist 145 | */ 146 | let next = function() { 147 | if(playlist.length <= 0) return; 148 | if(init(playlist.shift())) { 149 | play(); 150 | } else { 151 | next(); 152 | } 153 | } 154 | 155 | let createGd3meta = function(meta) { 156 | meta.game_track_name = [meta.game_name, meta.track_name].filter(str => str != "").join(" | "); 157 | meta.game_track_name_j = [meta.game_name_j, meta.track_name_j].filter(str => str != "").join(" / "); 158 | meta.track_author_full = [meta.track_author, meta.track_author_j].filter(str => str != "").join(" - "); 159 | canvasContext.font = FONT_MAIN_STYLE; 160 | meta.game_track_name_left = (CANVAS_WIDTH - canvasContext.measureText(meta.game_track_name).width) / 2; 161 | meta.game_track_name_j_left = (CANVAS_WIDTH - canvasContext.measureText(meta.game_track_name_j).width) / 2; 162 | meta.track_author_full_left = (CANVAS_WIDTH - canvasContext.measureText(meta.track_author_full).width) / 2; 163 | return meta; 164 | }; 165 | 166 | /** 167 | * init 168 | * @param ArrayBuffer bytes 169 | */ 170 | let init = function(bytes) { 171 | if(wgmplay != null) wgmplay.free(); 172 | // create wasm instanse 173 | wgmplay = new WgmPlay(SAMPLING_RATE, MAX_SAMPLING_BUFFER, bytes.byteLength); 174 | // set vgmdata 175 | seqdata = new Uint8Array(memory.buffer, wgmplay.get_seq_data_ref(), bytes.byteLength); 176 | seqdata.set(new Uint8Array(bytes)); 177 | // init player 178 | if(!wgmplay.init()) return false; 179 | samplingBufferL = new Float32Array(memory.buffer, wgmplay.get_sampling_l_ref(), MAX_SAMPLING_BUFFER); 180 | samplingBufferR = new Float32Array(memory.buffer, wgmplay.get_sampling_r_ref(), MAX_SAMPLING_BUFFER); 181 | 182 | music_meta = createGd3meta(JSON.parse(wgmplay.get_seq_gd3())); 183 | 184 | return true; 185 | } 186 | 187 | /** 188 | * disconnect 189 | */ 190 | let disconnect = function() { 191 | if(audioAnalyser != null) audioAnalyser.disconnect(); 192 | if(audioGain != null) audioGain.disconnect(); 193 | if(audioNode != null) audioNode.disconnect(); 194 | if(audioContext != null) audioContext.close(); 195 | // force GC 196 | audioAnalyser = null; 197 | audioNode = null; 198 | audioGain = null; 199 | audioContext = null; 200 | } 201 | 202 | /** 203 | * play 204 | */ 205 | let play = function() { 206 | canvas.removeEventListener('click', play, false); 207 | // recreate audio context for prevent memory leak. 208 | disconnect(); 209 | audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: SAMPLING_RATE }); 210 | audioNode = audioContext.createScriptProcessor(MAX_SAMPLING_BUFFER, 2, 2); 211 | feedOutCount = 0; 212 | let stop = false; 213 | audioNode.onaudioprocess = function(ev) { 214 | // flash last sampling 215 | if(stop) { 216 | disconnect(); 217 | next(); 218 | return; 219 | } 220 | let loop = wgmplay.play(); 221 | ev.outputBuffer.getChannelData(0).set(samplingBufferL); 222 | ev.outputBuffer.getChannelData(1).set(samplingBufferR); 223 | if(loop >= LOOP_MAX_COUNT) { 224 | if(feedOutCount == 0 && loop > LOOP_MAX_COUNT) { 225 | // no loop track 226 | stop = true; 227 | } else { 228 | // feedout loop track 229 | if(feedOutCount == 0 ) { 230 | audioGain.gain.setValueAtTime(1, audioContext.currentTime); 231 | audioGain.gain.linearRampToValueAtTime(0, audioContext.currentTime + FEED_OUT_SECOND); 232 | } 233 | feedOutCount++; 234 | if(feedOutCount > FEED_OUT_REMAIN) { 235 | stop = true; 236 | } 237 | } 238 | } 239 | }; 240 | // connect gain 241 | audioGain = audioContext.createGain(); 242 | audioNode.connect(audioGain); 243 | audioGain.connect(audioContext.destination); 244 | audioGain.gain.setValueAtTime(1, audioContext.currentTime); 245 | // connect fft 246 | audioAnalyser = audioContext.createAnalyser(); 247 | audioAnalyserBufferLength = audioAnalyser.frequencyBinCount; 248 | audioAnalyserBuffer = new Uint8Array(audioAnalyserBufferLength); 249 | audioAnalyser.getByteTimeDomainData(audioAnalyserBuffer); 250 | audioGain.connect(audioAnalyser); 251 | if(animId != null) { 252 | window.cancelAnimationFrame(animId); 253 | animId = null; 254 | } 255 | draw(); 256 | }; 257 | 258 | let draw = function() { 259 | animId = window.requestAnimationFrame(draw); 260 | canvasContext.fillStyle = 'rgb(0, 0, 0)'; 261 | canvasContext.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); 262 | if(audioAnalyser != null) { 263 | audioAnalyser.getByteFrequencyData(audioAnalyserBuffer); 264 | 265 | canvasContext.lineWidth = 1; 266 | canvasContext.beginPath(); 267 | canvasContext.strokeStyle = COLOR_MD_RED; 268 | 269 | let width = 4; 270 | let step = Math.round(audioAnalyserBufferLength / (CANVAS_WIDTH / width)); 271 | canvasContext.setLineDash([2, 1]); 272 | canvasContext.lineWidth = width ; 273 | for(var i = 0; i < audioAnalyserBufferLength; i += step) { 274 | canvasContext.beginPath(); 275 | canvasContext.moveTo(i + 2, CANVAS_HEIGHT); 276 | canvasContext.lineTo(i + 2, CANVAS_HEIGHT - (audioAnalyserBuffer[i] * 1.5)); 277 | canvasContext.stroke(); 278 | } 279 | canvasContext.stroke(); 280 | } 281 | 282 | canvasContext.font = "12px monospace"; 283 | canvasContext.fillStyle = COLOR_MD_GREEN; 284 | if(totalPlaylistCount >= 1) { 285 | fillTextCenterd("Track " + (totalPlaylistCount - playlist.length) + " / " + totalPlaylistCount, CANVAS_HEIGHT / 2 - 96); 286 | } 287 | canvasContext.font = FONT_MAIN_STYLE; 288 | canvasContext.fillText(music_meta.game_track_name, music_meta.game_track_name_left, CANVAS_HEIGHT / 2 - 64); 289 | canvasContext.fillText(music_meta.game_track_name_j, music_meta.game_track_name_j_left, CANVAS_HEIGHT / 2 - 32); 290 | canvasContext.fillText(music_meta.track_author_full, music_meta.track_author_full_left, CANVAS_HEIGHT / 2); 291 | } 292 | -------------------------------------------------------------------------------- /wasm-synth-player/src/rust/lib.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | use synth_emulator::driver::VgmPlay; 3 | 4 | #[wasm_bindgen] 5 | extern "C" { 6 | #[wasm_bindgen(js_namespace = console)] 7 | fn log(s: &str); 8 | } 9 | 10 | #[allow(unused_macros)] 11 | macro_rules! console_log { 12 | ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) 13 | } 14 | 15 | #[wasm_bindgen] 16 | pub struct WgmPlay { 17 | vgmplay: VgmPlay 18 | } 19 | 20 | #[wasm_bindgen] 21 | impl WgmPlay { 22 | /// 23 | /// constructor 24 | /// 25 | #[wasm_bindgen(constructor)] 26 | pub fn from(sample_rate: u32, max_sampling_size: usize, data_length: usize) -> Self { 27 | set_panic_hook(); 28 | WgmPlay { 29 | vgmplay: VgmPlay::new(sample_rate, max_sampling_size, data_length) 30 | } 31 | } 32 | 33 | /// 34 | /// Return vgmdata buffer referance. 35 | /// 36 | pub fn get_seq_data_ref(&mut self) -> *mut u8 { 37 | self.vgmplay.get_vgmfile_ref() 38 | } 39 | 40 | /// 41 | /// Return sampling_l buffer referance. 42 | /// 43 | pub fn get_sampling_l_ref(&mut self) -> *mut f32 { 44 | self.vgmplay.get_sampling_l_ref() 45 | } 46 | 47 | /// 48 | /// Return sampling_r buffer referance. 49 | /// 50 | pub fn get_sampling_r_ref(&mut self) -> *mut f32 { 51 | self.vgmplay.get_sampling_r_ref() 52 | } 53 | 54 | /// 55 | /// get_header 56 | /// 57 | pub fn get_seq_header(&self) -> String { 58 | self.vgmplay.get_vgm_header_json() 59 | } 60 | 61 | /// 62 | /// get_gd3 63 | /// 64 | pub fn get_seq_gd3(&self) -> String { 65 | self.vgmplay.get_vgm_gd3_json() 66 | } 67 | 68 | /// 69 | /// Initialize sound driver. 70 | /// 71 | /// # Arguments 72 | /// sample_rate - WebAudio sampling rate 73 | /// 74 | pub fn init(&mut self) -> bool { 75 | self.vgmplay.init().is_ok() 76 | } 77 | 78 | /// 79 | /// play 80 | /// 81 | pub fn play(&mut self) -> usize { 82 | self.vgmplay.play(true) 83 | } 84 | } 85 | 86 | pub fn set_panic_hook() { 87 | #[cfg(feature = "console_error_panic_hook")] 88 | console_error_panic_hook::set_once(); 89 | } 90 | -------------------------------------------------------------------------------- /wasm-synth-player/src/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Synth emurator by Rust/WebAssembly 18 | 19 | 20 | Fork me on GitHub 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /wasm-synth-player/src/www/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | body { 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | overflow: hidden; 11 | margin: 0; 12 | padding: 0; 13 | background-color: black; 14 | } 15 | 16 | canvas { 17 | display: block; 18 | background-color: black; 19 | } 20 | -------------------------------------------------------------------------------- /wasm-synth-player/webpack.config.js: -------------------------------------------------------------------------------- 1 | const CopyPlugin = require('copy-webpack-plugin'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | entry: "./src/js/bootstrap.js", 6 | output: { 7 | path: path.resolve(__dirname, "../dist"), // eslint-disable-line 8 | filename: "bootstrap.js", 9 | webassemblyModuleFilename: "[hash].wasm", 10 | }, 11 | mode: "development", 12 | plugins: [ 13 | new CopyPlugin({ patterns: ['./src/www/index.html', './src/www/style.css'] }) 14 | ], 15 | module: { 16 | rules: [ 17 | { 18 | test: /.js$/, 19 | exclude: /node_modules/, 20 | use: [{ 21 | loader: 'babel-loader', 22 | options: { 23 | presets: ['@babel/preset-env'] 24 | } 25 | }] 26 | } 27 | ] 28 | }, 29 | experiments: { 30 | asyncWebAssembly: true, 31 | }, 32 | resolve: { 33 | extensions: ['.js', '.wasm'], 34 | modules: [ 35 | "node_modules" 36 | ] 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /wasm-synth-player/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const path = require('path'); 3 | const common = require('./webpack.config.js'); 4 | 5 | module.exports = merge(common, { 6 | devtool: 'source-map', 7 | devServer: { 8 | inline: true, 9 | contentBase: [ 10 | path.join(__dirname, '../docs/'), // eslint-disable-line 11 | ], 12 | watchContentBase: false, 13 | port: 9000, 14 | open: true, 15 | // host: '0.0.0.0', 16 | // disableHostCheck: true 17 | } 18 | }); 19 | --------------------------------------------------------------------------------