diff --git a/.gitmodules b/.gitmodules index 5f0e538d1..0dcbd0c8c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "extern/Nuked-OPN2"] - path = extern/Nuked-OPN2 - url = https://github.com/nukeykt/Nuked-OPN2 [submodule "extern/SDL"] path = extern/SDL url = https://github.com/libsdl-org/SDL.git diff --git a/CMakeLists.txt b/CMakeLists.txt index fe7c81189..6e6659f96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -352,7 +352,7 @@ extern/adpcm/yma_codec.c extern/adpcm/ymb_codec.c extern/adpcm/ymz_codec.c -extern/Nuked-OPN2/ym3438.c +extern/opn/ym3438.c extern/Nuked-PSG/ympsg.c extern/opm/opm.c extern/Nuked-OPLL/opll.c diff --git a/README.md b/README.md index e5573b4ba..710c2baba 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,17 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a - TI SN76489 used in Sega Master System and BBC Micro - PC Speaker - Philips SAA1099 used in SAM Coupé + - OKI MSM5232 used in some arcade boards - sample chips: + - SNES - Amiga - SegaPCM - all 16 channels - Capcom QSound - Yamaha YMZ280B (PCMD8) - Ricoh RF5C68 used in Sega CD and FM Towns - OKI MSM6258 and MSM6295 + - Konami K007232 + - Irem GA20 - wavetable chips: - HuC6280 used in PC Engine - Konami Bubble System WSG @@ -61,9 +65,12 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a - SID (6581/8580) used in Commodore 64 - Mikey used in Atari Lynx - ZX Spectrum beeper (SFX-like engine) + - Pokémon Mini - Commodore PET - TIA used in Atari 2600 + - POKEY used in Atari 8-bit computers - Game Boy + - Virtual Boy - modern/fantasy: - Commander X16 VERA - tildearrow Sound Unit @@ -71,12 +78,12 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a - over 200 ready to use presets from computers, game consoles and arcade boards... - ...or create your own - up to 32 of them or a total of 128 channels! - DefleMask compatibility - - loads .dmf modules from all versions (beta 1 to 1.1.3) + - loads .dmf modules from all versions (beta 1 to 1.1.5) - saves .dmf modules - both modern and legacy - Furnace doubles as a module downgrader - loads/saves .dmp instruments and .dmw wavetables as well - clean-room design (guesswork and ABX tests only, no decompilation involved) - - bug/quirk implementation for increased playback accuracy through compatibility flags + - some bug/quirk implementation for increased playback accuracy through compatibility flags - VGM export - modular layout that you may adapt to your needs - audio file export - entire song, per chip or per channel @@ -88,11 +95,15 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a - additional features: - FM macros! - negative octaves + - advanced arp macros - arbitrary pitch samples - sample loop points - SSG envelopes and ADPCM-B in Neo Geo + - pitchable OPLL drums - full duty/cutoff range in C64 + - full 16-channel SegaPCM - ability to change tempo mid-song + - decimal tempo/tick rate - multiple sub-songs in a module - per-channel oscilloscope with waveform centering - built-in sample editor @@ -251,6 +262,10 @@ xattr -d com.apple.quarantine /path/to/Furnace.app you may need to log out and/or reboot after doing this. +> .spc export? + +**not yet!** coming in 0.7 though, in a future... + > how do I use C64 absolute filter/duty? on Instrument Editor in the C64 tab there are two options to toggle these. diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 137e4f7ba..000000000 --- a/TODO.md +++ /dev/null @@ -1,3 +0,0 @@ -# to-do for 0.6pre2 - -- bug fixes diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b77cf32e1..082cebc3f 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ diff --git a/demos/misc/Optimistic_SharpX68k.fur b/demos/misc/Optimistic_SharpX68k.fur new file mode 100644 index 000000000..c49e71a8f Binary files /dev/null and b/demos/misc/Optimistic_SharpX68k.fur differ diff --git a/demos/misc/sparkling_aria_pokemini.fur b/demos/misc/sparkling_aria_pokemini.fur new file mode 100644 index 000000000..3dbbfc13c Binary files /dev/null and b/demos/misc/sparkling_aria_pokemini.fur differ diff --git a/demos/multichip/Fight Against the Dark Matter.fur b/demos/multichip/Fight Against the Dark Matter.fur new file mode 100644 index 000000000..156c696d0 Binary files /dev/null and b/demos/multichip/Fight Against the Dark Matter.fur differ diff --git a/demos/snes/segacd.fur b/demos/snes/segacd.fur new file mode 100644 index 000000000..fbf9d55da Binary files /dev/null and b/demos/snes/segacd.fur differ diff --git a/extern/Nuked-OPN2 b/extern/Nuked-OPN2 deleted file mode 160000 index b0e9de0f8..000000000 --- a/extern/Nuked-OPN2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b0e9de0f816943ad3820ddfefa0fff276d659250 diff --git a/extern/imgui_patched/backends/imgui_impl_sdl.cpp b/extern/imgui_patched/backends/imgui_impl_sdl.cpp index 00c55ae80..499676fcd 100644 --- a/extern/imgui_patched/backends/imgui_impl_sdl.cpp +++ b/extern/imgui_patched/backends/imgui_impl_sdl.cpp @@ -682,17 +682,22 @@ void ImGui_ImplSDL2_NewFrame() SDL_GetWindowSize(bd->Window, &w, &h); if (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_MINIMIZED) w = h = 0; - if (bd->Renderer != NULL) - SDL_GetRendererOutputSize(bd->Renderer, &display_w, &display_h); - else + if (bd->Renderer != NULL) { + if (SDL_GetRendererOutputSize(bd->Renderer, &display_w, &display_h)!=0) { + display_w=0; + display_h=0; + } + } else { SDL_GL_GetDrawableSize(bd->Window, &display_w, &display_h); - io.DisplaySize = ImVec2((float)w, (float)h); - if (w > 0 && h > 0) + } + if (w > 0 && h > 0) { + io.DisplaySize = ImVec2((float)w, (float)h); io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h); + } // On Apple and Wayland, The window size is reported in Low DPI, even when running in high DPI mode ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); - if (!platform_io.Monitors.empty() /*&& platform_io.Monitors[0].DpiScale > 1.0f*/ && display_h != h && w != 0) + if (!platform_io.Monitors.empty() /*&& platform_io.Monitors[0].DpiScale > 1.0f*/ && display_h != h && w != 0 && display_w != 0) { io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); io.DisplaySize = ImVec2((float)display_w, (float)display_h); diff --git a/extern/opn/LICENSE b/extern/opn/LICENSE new file mode 100644 index 000000000..8000a6faa --- /dev/null +++ b/extern/opn/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random + Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/extern/opn/MODIFIED.md b/extern/opn/MODIFIED.md new file mode 100644 index 000000000..33b00cb7e --- /dev/null +++ b/extern/opn/MODIFIED.md @@ -0,0 +1,5 @@ +# modification disclaimer + +this is a modified version of Nuked-OPN2 which implements high resolution output (for OPN/OPNA/OPNB). + +accuracy of OPN2 (YM2612/YM3438) should not be altered. diff --git a/extern/opn/README.md b/extern/opn/README.md new file mode 100644 index 000000000..f5fce509c --- /dev/null +++ b/extern/opn/README.md @@ -0,0 +1,34 @@ +# Nuked-OPN2 +High accuracy Yamaha YM3438(OPN2) emulator. + +The YM3438 is a CMOS variant of the YM2612 used in Sega MegaDrive(Genesis) and FM Towns. + +Genesis Plus GX fork with this core integrated is available here: https://github.com/nukeykt/Genesis-Plus-GX + +# Features: +- Based on YM3438 die shot reverse engineering and thus provides very high emulation accuracy. + +- Cycle-accurate. + +- Undocumented registers/features emulation. +- SSG-EG, CSM mode emulation. +- Compatible with the YM2612. + +# API documention +``` +void OPN2_Reset(ym3438_t *chip) - Reset emulated chip +void OPN2_Clock(ym3438_t *chip, Bit32s *buffer) - Advances emulated chip state by 1 internal clock(6 master clocks). Writes signed 9-bit MOL, MOR pin states to buffer. +void OPN2_Write(ym3438_t *chip, Bit32u port, Bit8u data) - Write 8-bit data to port. +void OPN2_SetTestPin(ym3438_t *chip, Bit32u value) - Set TEST pin value. +Bit32u OPN2_ReadTestPin(ym3438_t *chip) - Read TEST pin value. +Bit32u OPN2_ReadIRQPin(ym3438_t *chip) - Read IRQ pin value. +Bit8u OPN2_Read(ym3438_t *chip, Bit32u port) - Read chip status. +``` + +# Samples +Sonic the Hedgehog: +https://youtu.be/ImmKy_-pJ8g + +Sega CD BIOS v1.10: +https://youtu.be/s-8ASMbtojQ + diff --git a/extern/opn/ym3438.c b/extern/opn/ym3438.c new file mode 100644 index 000000000..72b9cb9bf --- /dev/null +++ b/extern/opn/ym3438.c @@ -0,0 +1,1435 @@ +/* + * Copyright (C) 2017-2022 Alexey Khokholov (Nuke.YKT) + * + * This file is part of Nuked OPN2. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Nuked OPN2(Yamaha YM3438) emulator. + * Thanks: + * Silicon Pr0n: + * Yamaha YM3438 decap and die shot(digshadow). + * OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): + * OPL2 ROMs. + * + * version: 1.0.11 + */ + +#include +#include "ym3438.h" + +#define SIGN_EXTEND(bit_index, value) (((value) & ((1u << (bit_index)) - 1u)) - ((value) & (1u << (bit_index)))) + +enum { + eg_num_attack = 0, + eg_num_decay = 1, + eg_num_sustain = 2, + eg_num_release = 3 +}; + +/* logsin table */ +static const Bit16u logsinrom[256] = { + 0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471, + 0x443, 0x41a, 0x3f5, 0x3d3, 0x3b5, 0x398, 0x37e, 0x365, + 0x34e, 0x339, 0x324, 0x311, 0x2ff, 0x2ed, 0x2dc, 0x2cd, + 0x2bd, 0x2af, 0x2a0, 0x293, 0x286, 0x279, 0x26d, 0x261, + 0x256, 0x24b, 0x240, 0x236, 0x22c, 0x222, 0x218, 0x20f, + 0x206, 0x1fd, 0x1f5, 0x1ec, 0x1e4, 0x1dc, 0x1d4, 0x1cd, + 0x1c5, 0x1be, 0x1b7, 0x1b0, 0x1a9, 0x1a2, 0x19b, 0x195, + 0x18f, 0x188, 0x182, 0x17c, 0x177, 0x171, 0x16b, 0x166, + 0x160, 0x15b, 0x155, 0x150, 0x14b, 0x146, 0x141, 0x13c, + 0x137, 0x133, 0x12e, 0x129, 0x125, 0x121, 0x11c, 0x118, + 0x114, 0x10f, 0x10b, 0x107, 0x103, 0x0ff, 0x0fb, 0x0f8, + 0x0f4, 0x0f0, 0x0ec, 0x0e9, 0x0e5, 0x0e2, 0x0de, 0x0db, + 0x0d7, 0x0d4, 0x0d1, 0x0cd, 0x0ca, 0x0c7, 0x0c4, 0x0c1, + 0x0be, 0x0bb, 0x0b8, 0x0b5, 0x0b2, 0x0af, 0x0ac, 0x0a9, + 0x0a7, 0x0a4, 0x0a1, 0x09f, 0x09c, 0x099, 0x097, 0x094, + 0x092, 0x08f, 0x08d, 0x08a, 0x088, 0x086, 0x083, 0x081, + 0x07f, 0x07d, 0x07a, 0x078, 0x076, 0x074, 0x072, 0x070, + 0x06e, 0x06c, 0x06a, 0x068, 0x066, 0x064, 0x062, 0x060, + 0x05e, 0x05c, 0x05b, 0x059, 0x057, 0x055, 0x053, 0x052, + 0x050, 0x04e, 0x04d, 0x04b, 0x04a, 0x048, 0x046, 0x045, + 0x043, 0x042, 0x040, 0x03f, 0x03e, 0x03c, 0x03b, 0x039, + 0x038, 0x037, 0x035, 0x034, 0x033, 0x031, 0x030, 0x02f, + 0x02e, 0x02d, 0x02b, 0x02a, 0x029, 0x028, 0x027, 0x026, + 0x025, 0x024, 0x023, 0x022, 0x021, 0x020, 0x01f, 0x01e, + 0x01d, 0x01c, 0x01b, 0x01a, 0x019, 0x018, 0x017, 0x017, + 0x016, 0x015, 0x014, 0x014, 0x013, 0x012, 0x011, 0x011, + 0x010, 0x00f, 0x00f, 0x00e, 0x00d, 0x00d, 0x00c, 0x00c, + 0x00b, 0x00a, 0x00a, 0x009, 0x009, 0x008, 0x008, 0x007, + 0x007, 0x007, 0x006, 0x006, 0x005, 0x005, 0x005, 0x004, + 0x004, 0x004, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002, + 0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000 +}; + +/* exp table */ +static const Bit16u exprom[256] = { + 0x000, 0x003, 0x006, 0x008, 0x00b, 0x00e, 0x011, 0x014, + 0x016, 0x019, 0x01c, 0x01f, 0x022, 0x025, 0x028, 0x02a, + 0x02d, 0x030, 0x033, 0x036, 0x039, 0x03c, 0x03f, 0x042, + 0x045, 0x048, 0x04b, 0x04e, 0x051, 0x054, 0x057, 0x05a, + 0x05d, 0x060, 0x063, 0x066, 0x069, 0x06c, 0x06f, 0x072, + 0x075, 0x078, 0x07b, 0x07e, 0x082, 0x085, 0x088, 0x08b, + 0x08e, 0x091, 0x094, 0x098, 0x09b, 0x09e, 0x0a1, 0x0a4, + 0x0a8, 0x0ab, 0x0ae, 0x0b1, 0x0b5, 0x0b8, 0x0bb, 0x0be, + 0x0c2, 0x0c5, 0x0c8, 0x0cc, 0x0cf, 0x0d2, 0x0d6, 0x0d9, + 0x0dc, 0x0e0, 0x0e3, 0x0e7, 0x0ea, 0x0ed, 0x0f1, 0x0f4, + 0x0f8, 0x0fb, 0x0ff, 0x102, 0x106, 0x109, 0x10c, 0x110, + 0x114, 0x117, 0x11b, 0x11e, 0x122, 0x125, 0x129, 0x12c, + 0x130, 0x134, 0x137, 0x13b, 0x13e, 0x142, 0x146, 0x149, + 0x14d, 0x151, 0x154, 0x158, 0x15c, 0x160, 0x163, 0x167, + 0x16b, 0x16f, 0x172, 0x176, 0x17a, 0x17e, 0x181, 0x185, + 0x189, 0x18d, 0x191, 0x195, 0x199, 0x19c, 0x1a0, 0x1a4, + 0x1a8, 0x1ac, 0x1b0, 0x1b4, 0x1b8, 0x1bc, 0x1c0, 0x1c4, + 0x1c8, 0x1cc, 0x1d0, 0x1d4, 0x1d8, 0x1dc, 0x1e0, 0x1e4, + 0x1e8, 0x1ec, 0x1f0, 0x1f5, 0x1f9, 0x1fd, 0x201, 0x205, + 0x209, 0x20e, 0x212, 0x216, 0x21a, 0x21e, 0x223, 0x227, + 0x22b, 0x230, 0x234, 0x238, 0x23c, 0x241, 0x245, 0x249, + 0x24e, 0x252, 0x257, 0x25b, 0x25f, 0x264, 0x268, 0x26d, + 0x271, 0x276, 0x27a, 0x27f, 0x283, 0x288, 0x28c, 0x291, + 0x295, 0x29a, 0x29e, 0x2a3, 0x2a8, 0x2ac, 0x2b1, 0x2b5, + 0x2ba, 0x2bf, 0x2c4, 0x2c8, 0x2cd, 0x2d2, 0x2d6, 0x2db, + 0x2e0, 0x2e5, 0x2e9, 0x2ee, 0x2f3, 0x2f8, 0x2fd, 0x302, + 0x306, 0x30b, 0x310, 0x315, 0x31a, 0x31f, 0x324, 0x329, + 0x32e, 0x333, 0x338, 0x33d, 0x342, 0x347, 0x34c, 0x351, + 0x356, 0x35b, 0x360, 0x365, 0x36a, 0x370, 0x375, 0x37a, + 0x37f, 0x384, 0x38a, 0x38f, 0x394, 0x399, 0x39f, 0x3a4, + 0x3a9, 0x3ae, 0x3b4, 0x3b9, 0x3bf, 0x3c4, 0x3c9, 0x3cf, + 0x3d4, 0x3da, 0x3df, 0x3e4, 0x3ea, 0x3ef, 0x3f5, 0x3fa +}; + +/* Note table */ +static const Bit32u fn_note[16] = { + 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3, 3, 3, 3 +}; + +/* Envelope generator */ +static const Bit32u eg_stephi[4][4] = { + { 0, 0, 0, 0 }, + { 1, 0, 0, 0 }, + { 1, 0, 1, 0 }, + { 1, 1, 1, 0 } +}; + +static const Bit8u eg_am_shift[4] = { + 7, 3, 1, 0 +}; + +/* Phase generator */ +static const Bit32u pg_detune[8] = { 16, 17, 19, 20, 22, 24, 27, 29 }; + +static const Bit32u pg_lfo_sh1[8][8] = { + { 7, 7, 7, 7, 7, 7, 7, 7 }, + { 7, 7, 7, 7, 7, 7, 7, 7 }, + { 7, 7, 7, 7, 7, 7, 1, 1 }, + { 7, 7, 7, 7, 1, 1, 1, 1 }, + { 7, 7, 7, 1, 1, 1, 1, 0 }, + { 7, 7, 1, 1, 0, 0, 0, 0 }, + { 7, 7, 1, 1, 0, 0, 0, 0 }, + { 7, 7, 1, 1, 0, 0, 0, 0 } +}; + +static const Bit32u pg_lfo_sh2[8][8] = { + { 7, 7, 7, 7, 7, 7, 7, 7 }, + { 7, 7, 7, 7, 2, 2, 2, 2 }, + { 7, 7, 7, 2, 2, 2, 7, 7 }, + { 7, 7, 2, 2, 7, 7, 2, 2 }, + { 7, 7, 2, 7, 7, 7, 2, 7 }, + { 7, 7, 7, 2, 7, 7, 2, 1 }, + { 7, 7, 7, 2, 7, 7, 2, 1 }, + { 7, 7, 7, 2, 7, 7, 2, 1 } +}; + +/* Address decoder */ +static const Bit32u op_offset[12] = { + 0x000, /* Ch1 OP1/OP2 */ + 0x001, /* Ch2 OP1/OP2 */ + 0x002, /* Ch3 OP1/OP2 */ + 0x100, /* Ch4 OP1/OP2 */ + 0x101, /* Ch5 OP1/OP2 */ + 0x102, /* Ch6 OP1/OP2 */ + 0x004, /* Ch1 OP3/OP4 */ + 0x005, /* Ch2 OP3/OP4 */ + 0x006, /* Ch3 OP3/OP4 */ + 0x104, /* Ch4 OP3/OP4 */ + 0x105, /* Ch5 OP3/OP4 */ + 0x106 /* Ch6 OP3/OP4 */ +}; + +static const Bit32u ch_offset[6] = { + 0x000, /* Ch1 */ + 0x001, /* Ch2 */ + 0x002, /* Ch3 */ + 0x100, /* Ch4 */ + 0x101, /* Ch5 */ + 0x102 /* Ch6 */ +}; + +/* LFO */ +static const Bit32u lfo_cycles[8] = { + 108, 77, 71, 67, 62, 44, 8, 5 +}; + +/* FM algorithm */ +static const Bit32u fm_algorithm[4][6][8] = { + { + { 1, 1, 1, 1, 1, 1, 1, 1 }, /* OP1_0 */ + { 1, 1, 1, 1, 1, 1, 1, 1 }, /* OP1_1 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP2 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ + { 0, 0, 0, 0, 0, 0, 0, 1 } /* Out */ + }, + { + { 0, 1, 0, 0, 0, 1, 0, 0 }, /* OP1_0 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_1 */ + { 1, 1, 1, 0, 0, 0, 0, 0 }, /* OP2 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ + { 0, 0, 0, 0, 0, 1, 1, 1 } /* Out */ + }, + { + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_0 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_1 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP2 */ + { 1, 0, 0, 1, 1, 1, 1, 0 }, /* Last operator */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* Last operator */ + { 0, 0, 0, 0, 1, 1, 1, 1 } /* Out */ + }, + { + { 0, 0, 1, 0, 0, 1, 0, 0 }, /* OP1_0 */ + { 0, 0, 0, 0, 0, 0, 0, 0 }, /* OP1_1 */ + { 0, 0, 0, 1, 0, 0, 0, 0 }, /* OP2 */ + { 1, 1, 0, 1, 1, 0, 0, 0 }, /* Last operator */ + { 0, 0, 1, 0, 0, 0, 0, 0 }, /* Last operator */ + { 1, 1, 1, 1, 1, 1, 1, 1 } /* Out */ + } +}; + +static void OPN2_DoIO(ym3438_t *chip) +{ + /* Write signal check */ + chip->write_a_en = (chip->write_a & 0x03) == 0x01; + chip->write_d_en = (chip->write_d & 0x03) == 0x01; + chip->write_a <<= 1; + chip->write_d <<= 1; + /* Busy counter */ + chip->busy = chip->write_busy; + chip->write_busy_cnt += chip->write_busy; + chip->write_busy = (chip->write_busy && !(chip->write_busy_cnt >> 5)) || chip->write_d_en; + chip->write_busy_cnt &= 0x1f; +} + +static void OPN2_DoRegWrite(ym3438_t *chip) +{ + Bit32u i; + Bit32u slot = chip->cycles % 12; + Bit32u address; + Bit32u channel = chip->channel; + /* Update registers */ + if (chip->write_fm_data) + { + /* Slot */ + if (op_offset[slot] == (chip->address & 0x107)) + { + if (chip->address & 0x08) + { + /* OP2, OP4 */ + slot += 12; + } + address = chip->address & 0xf0; + switch (address) + { + case 0x30: /* DT, MULTI */ + chip->multi[slot] = chip->data & 0x0f; + if (!chip->multi[slot]) + { + chip->multi[slot] = 1; + } + else + { + chip->multi[slot] <<= 1; + } + chip->dt[slot] = (chip->data >> 4) & 0x07; + break; + case 0x40: /* TL */ + chip->tl[slot] = chip->data & 0x7f; + break; + case 0x50: /* KS, AR */ + chip->ar[slot] = chip->data & 0x1f; + chip->ks[slot] = (chip->data >> 6) & 0x03; + break; + case 0x60: /* AM, DR */ + chip->dr[slot] = chip->data & 0x1f; + chip->am[slot] = (chip->data >> 7) & 0x01; + break; + case 0x70: /* SR */ + chip->sr[slot] = chip->data & 0x1f; + break; + case 0x80: /* SL, RR */ + chip->rr[slot] = chip->data & 0x0f; + chip->sl[slot] = (chip->data >> 4) & 0x0f; + chip->sl[slot] |= (chip->sl[slot] + 1) & 0x10; + break; + case 0x90: /* SSG-EG */ + chip->ssg_eg[slot] = chip->data & 0x0f; + break; + default: + break; + } + } + + /* Channel */ + if (ch_offset[channel] == (chip->address & 0x103)) + { + address = chip->address & 0xfc; + switch (address) + { + case 0xa0: + chip->fnum[channel] = (chip->data & 0xff) | ((chip->reg_a4 & 0x07) << 8); + chip->block[channel] = (chip->reg_a4 >> 3) & 0x07; + chip->kcode[channel] = (chip->block[channel] << 2) | fn_note[chip->fnum[channel] >> 7]; + break; + case 0xa4: + chip->reg_a4 = chip->data & 0xff; + break; + case 0xa8: + chip->fnum_3ch[channel] = (chip->data & 0xff) | ((chip->reg_ac & 0x07) << 8); + chip->block_3ch[channel] = (chip->reg_ac >> 3) & 0x07; + chip->kcode_3ch[channel] = (chip->block_3ch[channel] << 2) | fn_note[chip->fnum_3ch[channel] >> 7]; + break; + case 0xac: + chip->reg_ac = chip->data & 0xff; + break; + case 0xb0: + chip->connect[channel] = chip->data & 0x07; + chip->fb[channel] = (chip->data >> 3) & 0x07; + break; + case 0xb4: + chip->pms[channel] = chip->data & 0x07; + chip->ams[channel] = (chip->data >> 4) & 0x03; + chip->pan_l[channel] = (chip->data >> 7) & 0x01; + chip->pan_r[channel] = (chip->data >> 6) & 0x01; + break; + default: + break; + } + } + } + + if (chip->write_a_en || chip->write_d_en) + { + /* Data */ + if (chip->write_a_en) + { + chip->write_fm_data = 0; + } + + if (chip->write_fm_address && chip->write_d_en) + { + chip->write_fm_data = 1; + } + + /* Address */ + if (chip->write_a_en) + { + if ((chip->write_data & 0xf0) != 0x00) + { + /* FM Write */ + chip->address = chip->write_data; + chip->write_fm_address = 1; + } + else + { + /* SSG write */ + chip->write_fm_address = 0; + } + } + + /* FM Mode */ + /* Data */ + if (chip->write_d_en && (chip->write_data & 0x100) == 0) + { + switch (chip->write_fm_mode_a) + { + case 0x21: /* LSI test 1 */ + for (i = 0; i < 8; i++) + { + chip->mode_test_21[i] = (chip->write_data >> i) & 0x01; + } + break; + case 0x22: /* LFO control */ + if ((chip->write_data >> 3) & 0x01) + { + chip->lfo_en = 0x7f; + } + else + { + chip->lfo_en = 0; + } + chip->lfo_freq = chip->write_data & 0x07; + break; + case 0x24: /* Timer A */ + chip->timer_a_reg &= 0x03; + chip->timer_a_reg |= (chip->write_data & 0xff) << 2; + break; + case 0x25: + chip->timer_a_reg &= 0x3fc; + chip->timer_a_reg |= chip->write_data & 0x03; + break; + case 0x26: /* Timer B */ + chip->timer_b_reg = chip->write_data & 0xff; + break; + case 0x27: /* CSM, Timer control */ + chip->mode_ch3 = (chip->write_data & 0xc0) >> 6; + chip->mode_csm = chip->mode_ch3 == 2; + chip->timer_a_load = chip->write_data & 0x01; + chip->timer_a_enable = (chip->write_data >> 2) & 0x01; + chip->timer_a_reset = (chip->write_data >> 4) & 0x01; + chip->timer_b_load = (chip->write_data >> 1) & 0x01; + chip->timer_b_enable = (chip->write_data >> 3) & 0x01; + chip->timer_b_reset = (chip->write_data >> 5) & 0x01; + break; + case 0x28: /* Key on/off */ + for (i = 0; i < 4; i++) + { + chip->mode_kon_operator[i] = (chip->write_data >> (4 + i)) & 0x01; + } + if ((chip->write_data & 0x03) == 0x03) + { + /* Invalid address */ + chip->mode_kon_channel = 0xff; + } + else + { + chip->mode_kon_channel = (chip->write_data & 0x03) + ((chip->write_data >> 2) & 1) * 3; + } + break; + case 0x2a: /* DAC data */ + chip->dacdata &= 0x01; + chip->dacdata |= (chip->write_data ^ 0x80) << 1; + break; + case 0x2b: /* DAC enable */ + chip->dacen = chip->write_data >> 7; + break; + case 0x2c: /* LSI test 2 */ + for (i = 0; i < 8; i++) + { + chip->mode_test_2c[i] = (chip->write_data >> i) & 0x01; + } + chip->dacdata &= 0x1fe; + chip->dacdata |= chip->mode_test_2c[3]; + chip->eg_custom_timer = !chip->mode_test_2c[7] && chip->mode_test_2c[6]; + break; + default: + break; + } + } + + /* Address */ + if (chip->write_a_en) + { + chip->write_fm_mode_a = chip->write_data & 0x1ff; + } + } + + if (chip->write_fm_data) + { + chip->data = chip->write_data & 0xff; + } +} + +static void OPN2_PhaseCalcIncrement(ym3438_t *chip) +{ + Bit32u chan = chip->channel; + Bit32u slot = chip->cycles; + Bit32u fnum = chip->pg_fnum; + Bit32u fnum_h = fnum >> 4; + Bit32u fm; + Bit32u basefreq; + Bit8u lfo = chip->lfo_pm; + Bit8u lfo_l = lfo & 0x0f; + Bit8u pms = chip->pms[chan]; + Bit8u dt = chip->dt[slot]; + Bit8u dt_l = dt & 0x03; + Bit8u detune = 0; + Bit8u block, note; + Bit8u sum, sum_h, sum_l; + Bit8u kcode = chip->pg_kcode; + + fnum <<= 1; + /* Apply LFO */ + if (lfo_l & 0x08) + { + lfo_l ^= 0x0f; + } + fm = (fnum_h >> pg_lfo_sh1[pms][lfo_l]) + (fnum_h >> pg_lfo_sh2[pms][lfo_l]); + if (pms > 5) + { + fm <<= pms - 5; + } + fm >>= 2; + if (lfo & 0x10) + { + fnum -= fm; + } + else + { + fnum += fm; + } + fnum &= 0xfff; + + basefreq = (fnum << chip->pg_block) >> 2; + + /* Apply detune */ + if (dt_l) + { + if (kcode > 0x1c) + { + kcode = 0x1c; + } + block = kcode >> 2; + note = kcode & 0x03; + sum = block + 9 + ((dt_l == 3) | (dt_l & 0x02)); + sum_h = sum >> 1; + sum_l = sum & 0x01; + detune = pg_detune[(sum_l << 2) | note] >> (9 - sum_h); + } + if (dt & 0x04) + { + basefreq -= detune; + } + else + { + basefreq += detune; + } + basefreq &= 0x1ffff; + chip->pg_inc[slot] = (basefreq * chip->multi[slot]) >> 1; + chip->pg_inc[slot] &= 0xfffff; +} + +static void OPN2_PhaseGenerate(ym3438_t *chip) +{ + Bit32u slot; + /* Mask increment */ + slot = (chip->cycles + 20) % 24; + if (chip->pg_reset[slot]) + { + chip->pg_inc[slot] = 0; + } + /* Phase step */ + slot = (chip->cycles + 19) % 24; + if (chip->pg_reset[slot] || chip->mode_test_21[3]) + { + chip->pg_phase[slot] = 0; + } + chip->pg_phase[slot] += chip->pg_inc[slot]; + chip->pg_phase[slot] &= 0xfffff; +} + +static void OPN2_EnvelopeSSGEG(ym3438_t *chip) +{ + Bit32u slot = chip->cycles; + Bit8u direction = 0; + chip->eg_ssg_pgrst_latch[slot] = 0; + chip->eg_ssg_repeat_latch[slot] = 0; + chip->eg_ssg_hold_up_latch[slot] = 0; + chip->eg_ssg_inv[slot] = 0; + if (chip->ssg_eg[slot] & 0x08) + { + direction = chip->eg_ssg_dir[slot]; + if (chip->eg_level[slot] & 0x200) + { + /* Reset */ + if ((chip->ssg_eg[slot] & 0x03) == 0x00) + { + chip->eg_ssg_pgrst_latch[slot] = 1; + } + /* Repeat */ + if ((chip->ssg_eg[slot] & 0x01) == 0x00) + { + chip->eg_ssg_repeat_latch[slot] = 1; + } + /* Inverse */ + if ((chip->ssg_eg[slot] & 0x03) == 0x02) + { + direction ^= 1; + } + if ((chip->ssg_eg[slot] & 0x03) == 0x03) + { + direction = 1; + } + } + /* Hold up */ + if (chip->eg_kon_latch[slot] + && ((chip->ssg_eg[slot] & 0x07) == 0x05 || (chip->ssg_eg[slot] & 0x07) == 0x03)) + { + chip->eg_ssg_hold_up_latch[slot] = 1; + } + direction &= chip->eg_kon[slot]; + chip->eg_ssg_inv[slot] = (chip->eg_ssg_dir[slot] ^ ((chip->ssg_eg[slot] >> 2) & 0x01)) + & chip->eg_kon[slot]; + } + chip->eg_ssg_dir[slot] = direction; + chip->eg_ssg_enable[slot] = (chip->ssg_eg[slot] >> 3) & 0x01; +} + +static void OPN2_EnvelopeADSR(ym3438_t *chip) +{ + Bit32u slot = (chip->cycles + 22) % 24; + + Bit8u nkon = chip->eg_kon_latch[slot]; + Bit8u okon = chip->eg_kon[slot]; + Bit8u kon_event; + Bit8u koff_event; + Bit8u eg_off; + Bit16s level; + Bit16s nextlevel = 0; + Bit16s ssg_level; + Bit8u nextstate = chip->eg_state[slot]; + Bit16s inc = 0; + chip->eg_read[0] = chip->eg_read_inc; + chip->eg_read_inc = chip->eg_inc > 0; + + /* Reset phase generator */ + chip->pg_reset[slot] = (nkon && !okon) || chip->eg_ssg_pgrst_latch[slot]; + + /* KeyOn/Off */ + kon_event = (nkon && !okon) || (okon && chip->eg_ssg_repeat_latch[slot]); + koff_event = okon && !nkon; + + ssg_level = level = (Bit16s)chip->eg_level[slot]; + + if (chip->eg_ssg_inv[slot]) + { + /* Inverse */ + ssg_level = 512 - level; + ssg_level &= 0x3ff; + } + if (koff_event) + { + level = ssg_level; + } + if (chip->eg_ssg_enable[slot]) + { + eg_off = level >> 9; + } + else + { + eg_off = (level & 0x3f0) == 0x3f0; + } + nextlevel = level; + if (kon_event) + { + nextstate = eg_num_attack; + /* Instant attack */ + if (chip->eg_ratemax) + { + nextlevel = 0; + } + else if (chip->eg_state[slot] == eg_num_attack && level != 0 && chip->eg_inc && nkon) + { + inc = (~level << chip->eg_inc) >> 5; + } + } + else + { + switch (chip->eg_state[slot]) + { + case eg_num_attack: + if (level == 0) + { + nextstate = eg_num_decay; + } + else if(chip->eg_inc && !chip->eg_ratemax && nkon) + { + inc = (~level << chip->eg_inc) >> 5; + } + break; + case eg_num_decay: + if ((level >> 4) == (chip->eg_sl[1] << 1)) + { + nextstate = eg_num_sustain; + } + else if (!eg_off && chip->eg_inc) + { + inc = 1 << (chip->eg_inc - 1); + if (chip->eg_ssg_enable[slot]) + { + inc <<= 2; + } + } + break; + case eg_num_sustain: + case eg_num_release: + if (!eg_off && chip->eg_inc) + { + inc = 1 << (chip->eg_inc - 1); + if (chip->eg_ssg_enable[slot]) + { + inc <<= 2; + } + } + break; + default: + break; + } + if (!nkon) + { + nextstate = eg_num_release; + } + } + if (chip->eg_kon_csm[slot]) + { + nextlevel |= chip->eg_tl[1] << 3; + } + + /* Envelope off */ + if (!kon_event && !chip->eg_ssg_hold_up_latch[slot] && chip->eg_state[slot] != eg_num_attack && eg_off) + { + nextstate = eg_num_release; + nextlevel = 0x3ff; + } + + nextlevel += inc; + + chip->eg_kon[slot] = chip->eg_kon_latch[slot]; + chip->eg_level[slot] = (Bit16u)nextlevel & 0x3ff; + chip->eg_state[slot] = nextstate; +} + +static void OPN2_EnvelopePrepare(ym3438_t *chip) +{ + Bit8u rate; + Bit8u sum; + Bit8u inc = 0; + Bit32u slot = chip->cycles; + Bit8u rate_sel; + + /* Prepare increment */ + rate = (chip->eg_rate << 1) + chip->eg_ksv; + + if (rate > 0x3f) + { + rate = 0x3f; + } + + sum = ((rate >> 2) + chip->eg_shift_lock) & 0x0f; + if (chip->eg_rate != 0 && chip->eg_quotient == 2) + { + if (rate < 48) + { + switch (sum) + { + case 12: + inc = 1; + break; + case 13: + inc = (rate >> 1) & 0x01; + break; + case 14: + inc = rate & 0x01; + break; + default: + break; + } + } + else + { + inc = eg_stephi[rate & 0x03][chip->eg_timer_low_lock] + (rate >> 2) - 11; + if (inc > 4) + { + inc = 4; + } + } + } + chip->eg_inc = inc; + chip->eg_ratemax = (rate >> 1) == 0x1f; + + /* Prepare rate & ksv */ + rate_sel = chip->eg_state[slot]; + if ((chip->eg_kon[slot] && chip->eg_ssg_repeat_latch[slot]) + || (!chip->eg_kon[slot] && chip->eg_kon_latch[slot])) + { + rate_sel = eg_num_attack; + } + switch (rate_sel) + { + case eg_num_attack: + chip->eg_rate = chip->ar[slot]; + break; + case eg_num_decay: + chip->eg_rate = chip->dr[slot]; + break; + case eg_num_sustain: + chip->eg_rate = chip->sr[slot]; + break; + case eg_num_release: + chip->eg_rate = (chip->rr[slot] << 1) | 0x01; + break; + default: + break; + } + chip->eg_ksv = chip->pg_kcode >> (chip->ks[slot] ^ 0x03); + if (chip->am[slot]) + { + chip->eg_lfo_am = chip->lfo_am >> eg_am_shift[chip->ams[chip->channel]]; + } + else + { + chip->eg_lfo_am = 0; + } + /* Delay TL & SL value */ + chip->eg_tl[1] = chip->eg_tl[0]; + chip->eg_tl[0] = chip->tl[slot]; + chip->eg_sl[1] = chip->eg_sl[0]; + chip->eg_sl[0] = chip->sl[slot]; +} + +static void OPN2_EnvelopeGenerate(ym3438_t *chip) +{ + Bit32u slot = (chip->cycles + 23) % 24; + Bit16u level; + + level = chip->eg_level[slot]; + + if (chip->eg_ssg_inv[slot]) + { + /* Inverse */ + level = 512 - level; + } + if (chip->mode_test_21[5]) + { + level = 0; + } + level &= 0x3ff; + + /* Apply AM LFO */ + level += chip->eg_lfo_am; + + /* Apply TL */ + if (!(chip->mode_csm && chip->channel == 2 + 1)) + { + level += chip->eg_tl[0] << 3; + } + if (level > 0x3ff) + { + level = 0x3ff; + } + chip->eg_out[slot] = level; +} + +static void OPN2_UpdateLFO(ym3438_t *chip) +{ + if ((chip->lfo_quotient & lfo_cycles[chip->lfo_freq]) == lfo_cycles[chip->lfo_freq]) + { + chip->lfo_quotient = 0; + chip->lfo_cnt++; + } + else + { + chip->lfo_quotient += chip->lfo_inc; + } + chip->lfo_cnt &= chip->lfo_en; +} + +static void OPN2_FMPrepare(ym3438_t *chip) +{ + Bit32u slot = (chip->cycles + 6) % 24; + Bit32u channel = chip->channel; + Bit16s mod, mod1, mod2; + Bit32u op = slot / 6; + Bit8u connect = chip->connect[channel]; + Bit32u prevslot = (chip->cycles + 18) % 24; + + /* Calculate modulation */ + mod1 = mod2 = 0; + + if (fm_algorithm[op][0][connect]) + { + mod2 |= chip->fm_op1[channel][0]; + } + if (fm_algorithm[op][1][connect]) + { + mod1 |= chip->fm_op1[channel][1]; + } + if (fm_algorithm[op][2][connect]) + { + mod1 |= chip->fm_op2[channel]; + } + if (fm_algorithm[op][3][connect]) + { + mod2 |= chip->fm_out[prevslot]; + } + if (fm_algorithm[op][4][connect]) + { + mod1 |= chip->fm_out[prevslot]; + } + mod = mod1 + mod2; + if (op == 0) + { + /* Feedback */ + mod = mod >> (10 - chip->fb[channel]); + if (!chip->fb[channel]) + { + mod = 0; + } + } + else + { + mod >>= 1; + } + chip->fm_mod[slot] = mod; + + slot = (chip->cycles + 18) % 24; + /* OP1 */ + if (slot / 6 == 0) + { + chip->fm_op1[channel][1] = chip->fm_op1[channel][0]; + chip->fm_op1[channel][0] = chip->fm_out[slot]; + } + /* OP2 */ + if (slot / 6 == 2) + { + chip->fm_op2[channel] = chip->fm_out[slot]; + } +} + +static void OPN2_ChGenerate(ym3438_t *chip) +{ + Bit32u slot = (chip->cycles + 18) % 24; + Bit32u channel = chip->channel; + Bit32u op = slot / 6; + Bit32u test_dac = chip->mode_test_2c[5]; + Bit16s acc = chip->ch_acc[channel]; + Bit16s add = test_dac; + Bit16s sum = 0; + if (op == 0 && !test_dac) + { + acc = 0; + } + if (chip->chip_type & ym3438_mode_opn) { + if (fm_algorithm[op][5][chip->connect[channel]] && !test_dac) + { + add += chip->fm_out[slot]; + } + sum = acc + add; + } else { + if (fm_algorithm[op][5][chip->connect[channel]] && !test_dac) + { + add += chip->fm_out[slot] >> 5; + } + sum = acc + add; + /* Clamp */ + if (sum > 255) + { + sum = 255; + } + else if(sum < -256) + { + sum = -256; + } + } + + if (op == 0 || test_dac) + { + chip->ch_out[channel] = chip->ch_acc[channel]; + } + chip->ch_acc[channel] = sum; +} + +static void OPN2_ChOutput(ym3438_t *chip) +{ + Bit32u cycles = chip->cycles; + Bit32u slot = chip->cycles; + Bit32u channel = chip->channel; + Bit32u test_dac = chip->mode_test_2c[5]; + Bit16s out; + Bit16s sign; + Bit32u out_en; + chip->ch_read = chip->ch_lock; + if (slot < 12) + { + /* Ch 4,5,6 */ + channel++; + } + if ((cycles & 3) == 0) + { + if (!test_dac) + { + /* Lock value */ + chip->ch_lock = chip->ch_out[channel]; + } + chip->ch_lock_l = chip->pan_l[channel]; + chip->ch_lock_r = chip->pan_r[channel]; + } + /* Ch 6 */ + if (((cycles >> 2) == 1 && chip->dacen) || test_dac) + { + out = (Bit16s)chip->dacdata; + out = SIGN_EXTEND(8, out); + } + else + { + out = chip->ch_lock; + } + chip->mol = 0; + chip->mor = 0; + + if (chip->chip_type & ym3438_mode_ym2612) + { + out_en = ((cycles & 3) == 3) || test_dac; + /* YM2612 DAC emulation(not verified) */ + sign = out >> 8; + if (out >= 0) + { + out++; + sign++; + } + if (chip->ch_lock_l && out_en) + { + chip->mol = out; + } + else + { + chip->mol = sign; + } + if (chip->ch_lock_r && out_en) + { + chip->mor = out; + } + else + { + chip->mor = sign; + } + /* Amplify signal */ + chip->mol *= 3; + chip->mor *= 3; + } + else + { + out_en = ((cycles & 3) != 0) || test_dac; + if (chip->ch_lock_l && out_en) + { + chip->mol = out; + } + if (chip->ch_lock_r && out_en) + { + chip->mor = out; + } + } +} + +static void OPN2_FMGenerate(ym3438_t *chip) +{ + Bit32u slot = (chip->cycles + 19) % 24; + /* Calculate phase */ + Bit16u phase = (chip->fm_mod[slot] + (chip->pg_phase[slot] >> 10)) & 0x3ff; + Bit16u quarter; + Bit16u level; + Bit16s output; + if (phase & 0x100) + { + quarter = (phase ^ 0xff) & 0xff; + } + else + { + quarter = phase & 0xff; + } + level = logsinrom[quarter]; + /* Apply envelope */ + level += chip->eg_out[slot] << 2; + /* Transform */ + if (level > 0x1fff) + { + level = 0x1fff; + } + output = ((exprom[(level & 0xff) ^ 0xff] | 0x400) << 2) >> (level >> 8); + if (phase & 0x200) + { + output = ((~output) ^ (chip->mode_test_21[4] << 13)) + 1; + } + else + { + output = output ^ (chip->mode_test_21[4] << 13); + } + output = SIGN_EXTEND(13, output); + chip->fm_out[slot] = output; +} + +static void OPN2_DoTimerA(ym3438_t *chip) +{ + Bit16u time; + Bit8u load; + load = chip->timer_a_overflow; + if (chip->cycles == 2) + { + /* Lock load value */ + load |= (!chip->timer_a_load_lock && chip->timer_a_load); + chip->timer_a_load_lock = chip->timer_a_load; + if (chip->mode_csm) + { + /* CSM KeyOn */ + chip->mode_kon_csm = load; + } + else + { + chip->mode_kon_csm = 0; + } + } + /* Load counter */ + if (chip->timer_a_load_latch) + { + time = chip->timer_a_reg; + } + else + { + time = chip->timer_a_cnt; + } + chip->timer_a_load_latch = load; + /* Increase counter */ + if ((chip->cycles == 1 && chip->timer_a_load_lock) || chip->mode_test_21[2]) + { + time++; + } + /* Set overflow flag */ + if (chip->timer_a_reset) + { + chip->timer_a_reset = 0; + chip->timer_a_overflow_flag = 0; + } + else + { + chip->timer_a_overflow_flag |= chip->timer_a_overflow & chip->timer_a_enable; + } + chip->timer_a_overflow = (time >> 10); + chip->timer_a_cnt = time & 0x3ff; +} + +static void OPN2_DoTimerB(ym3438_t *chip) +{ + Bit16u time; + Bit8u load; + load = chip->timer_b_overflow; + if (chip->cycles == 2) + { + /* Lock load value */ + load |= (!chip->timer_b_load_lock && chip->timer_b_load); + chip->timer_b_load_lock = chip->timer_b_load; + } + /* Load counter */ + if (chip->timer_b_load_latch) + { + time = chip->timer_b_reg; + } + else + { + time = chip->timer_b_cnt; + } + chip->timer_b_load_latch = load; + /* Increase counter */ + if (chip->cycles == 1) + { + chip->timer_b_subcnt++; + } + if ((chip->timer_b_subcnt == 0x10 && chip->timer_b_load_lock) || chip->mode_test_21[2]) + { + time++; + } + chip->timer_b_subcnt &= 0x0f; + /* Set overflow flag */ + if (chip->timer_b_reset) + { + chip->timer_b_reset = 0; + chip->timer_b_overflow_flag = 0; + } + else + { + chip->timer_b_overflow_flag |= chip->timer_b_overflow & chip->timer_b_enable; + } + chip->timer_b_overflow = (time >> 8); + chip->timer_b_cnt = time & 0xff; +} + +static void OPN2_KeyOn(ym3438_t*chip) +{ + Bit32u slot = chip->cycles; + Bit32u chan = chip->channel; + /* Key On */ + chip->eg_kon_latch[slot] = chip->mode_kon[slot]; + chip->eg_kon_csm[slot] = 0; + if (chip->channel == 2 && chip->mode_kon_csm) + { + /* CSM Key On */ + chip->eg_kon_latch[slot] = 1; + chip->eg_kon_csm[slot] = 1; + } + if (chip->cycles == chip->mode_kon_channel) + { + /* OP1 */ + chip->mode_kon[chan] = chip->mode_kon_operator[0]; + /* OP2 */ + chip->mode_kon[chan + 12] = chip->mode_kon_operator[1]; + /* OP3 */ + chip->mode_kon[chan + 6] = chip->mode_kon_operator[2]; + /* OP4 */ + chip->mode_kon[chan + 18] = chip->mode_kon_operator[3]; + } +} + +void OPN2_Reset(ym3438_t *chip) +{ + Bit32u i; + memset(chip, 0, sizeof(ym3438_t)); + chip->chip_type = ym3438_mode_readmode; + for (i = 0; i < 24; i++) + { + chip->eg_out[i] = 0x3ff; + chip->eg_level[i] = 0x3ff; + chip->eg_state[i] = eg_num_release; + chip->multi[i] = 1; + } + for (i = 0; i < 6; i++) + { + chip->pan_l[i] = 1; + chip->pan_r[i] = 1; + } +} + +void OPN2_SetChipType(ym3438_t *chip, Bit32u type) +{ + chip->chip_type = type; +} + +void OPN2_Clock(ym3438_t *chip, Bit16s *buffer) +{ + Bit32u slot = chip->cycles; + chip->lfo_inc = chip->mode_test_21[1]; + chip->pg_read >>= 1; + chip->eg_read[1] >>= 1; + chip->eg_cycle++; + /* Lock envelope generator timer value */ + if (chip->cycles == 1 && chip->eg_quotient == 2) + { + if (chip->eg_cycle_stop) + { + chip->eg_shift_lock = 0; + } + else + { + chip->eg_shift_lock = chip->eg_shift + 1; + } + chip->eg_timer_low_lock = chip->eg_timer & 0x03; + } + /* Cycle specific functions */ + switch (chip->cycles) + { + case 0: + chip->lfo_pm = chip->lfo_cnt >> 2; + if (chip->lfo_cnt & 0x40) + { + chip->lfo_am = chip->lfo_cnt & 0x3f; + } + else + { + chip->lfo_am = chip->lfo_cnt ^ 0x3f; + } + chip->lfo_am <<= 1; + break; + case 1: + chip->eg_quotient++; + chip->eg_quotient %= 3; + chip->eg_cycle = 0; + chip->eg_cycle_stop = 1; + chip->eg_shift = 0; + chip->eg_timer_inc |= chip->eg_quotient >> 1; + chip->eg_timer = chip->eg_timer + chip->eg_timer_inc; + chip->eg_timer_inc = chip->eg_timer >> 12; + chip->eg_timer &= 0xfff; + break; + case 2: + chip->pg_read = chip->pg_phase[21] & 0x3ff; + chip->eg_read[1] = chip->eg_out[0]; + break; + case 13: + chip->eg_cycle = 0; + chip->eg_cycle_stop = 1; + chip->eg_shift = 0; + chip->eg_timer = chip->eg_timer + chip->eg_timer_inc; + chip->eg_timer_inc = chip->eg_timer >> 12; + chip->eg_timer &= 0xfff; + break; + case 23: + chip->lfo_inc |= 1; + break; + } + chip->eg_timer &= ~(chip->mode_test_21[5] << chip->eg_cycle); + if (((chip->eg_timer >> chip->eg_cycle) | (chip->pin_test_in & chip->eg_custom_timer)) & chip->eg_cycle_stop) + { + chip->eg_shift = chip->eg_cycle; + chip->eg_cycle_stop = 0; + } + + OPN2_DoIO(chip); + + OPN2_DoTimerA(chip); + OPN2_DoTimerB(chip); + OPN2_KeyOn(chip); + + OPN2_ChOutput(chip); + OPN2_ChGenerate(chip); + + OPN2_FMPrepare(chip); + OPN2_FMGenerate(chip); + + OPN2_PhaseGenerate(chip); + OPN2_PhaseCalcIncrement(chip); + + OPN2_EnvelopeADSR(chip); + OPN2_EnvelopeGenerate(chip); + OPN2_EnvelopeSSGEG(chip); + OPN2_EnvelopePrepare(chip); + + /* Prepare fnum & block */ + if (chip->mode_ch3) + { + /* Channel 3 special mode */ + switch (slot) + { + case 1: /* OP1 */ + chip->pg_fnum = chip->fnum_3ch[1]; + chip->pg_block = chip->block_3ch[1]; + chip->pg_kcode = chip->kcode_3ch[1]; + break; + case 7: /* OP3 */ + chip->pg_fnum = chip->fnum_3ch[0]; + chip->pg_block = chip->block_3ch[0]; + chip->pg_kcode = chip->kcode_3ch[0]; + break; + case 13: /* OP2 */ + chip->pg_fnum = chip->fnum_3ch[2]; + chip->pg_block = chip->block_3ch[2]; + chip->pg_kcode = chip->kcode_3ch[2]; + break; + case 19: /* OP4 */ + default: + chip->pg_fnum = chip->fnum[(chip->channel + 1) % 6]; + chip->pg_block = chip->block[(chip->channel + 1) % 6]; + chip->pg_kcode = chip->kcode[(chip->channel + 1) % 6]; + break; + } + } + else + { + chip->pg_fnum = chip->fnum[(chip->channel + 1) % 6]; + chip->pg_block = chip->block[(chip->channel + 1) % 6]; + chip->pg_kcode = chip->kcode[(chip->channel + 1) % 6]; + } + + OPN2_UpdateLFO(chip); + OPN2_DoRegWrite(chip); + chip->cycles = (chip->cycles + 1) % 24; + chip->channel = chip->cycles % 6; + + buffer[0] = chip->mol; + buffer[1] = chip->mor; + + if (chip->status_time) + chip->status_time--; +} + +void OPN2_Write(ym3438_t *chip, Bit32u port, Bit8u data) +{ + port &= 3; + chip->write_data = ((port << 7) & 0x100) | data; + if (port & 1) + { + /* Data */ + chip->write_d |= 1; + } + else + { + /* Address */ + chip->write_a |= 1; + } +} + +void OPN2_SetTestPin(ym3438_t *chip, Bit32u value) +{ + chip->pin_test_in = value & 1; +} + +Bit32u OPN2_ReadTestPin(ym3438_t *chip) +{ + if (!chip->mode_test_2c[7]) + { + return 0; + } + return chip->cycles == 23; +} + +Bit32u OPN2_ReadIRQPin(ym3438_t *chip) +{ + return chip->timer_a_overflow_flag | chip->timer_b_overflow_flag; +} + +Bit8u OPN2_Read(ym3438_t *chip, Bit32u port) +{ + if ((port & 3) == 0 || (chip->chip_type & ym3438_mode_readmode)) + { + if (chip->mode_test_21[6]) + { + /* Read test data */ + Bit32u slot = (chip->cycles + 18) % 24; + Bit16u testdata = ((chip->pg_read & 0x01) << 15) + | ((chip->eg_read[chip->mode_test_21[0]] & 0x01) << 14); + if (chip->mode_test_2c[4]) + { + testdata |= chip->ch_read & 0x1ff; + } + else + { + testdata |= chip->fm_out[slot] & 0x3fff; + } + if (chip->mode_test_21[7]) + { + chip->status = testdata & 0xff; + } + else + { + chip->status = testdata >> 8; + } + } + else + { + chip->status = (chip->busy << 7) | (chip->timer_b_overflow_flag << 1) + | chip->timer_a_overflow_flag; + } + if (chip->chip_type & ym3438_mode_ym2612) + { + chip->status_time = 300000; + } + else + { + chip->status_time = 40000000; + } + } + if (chip->status_time) + { + return chip->status; + } + return 0; +} diff --git a/extern/opn/ym3438.h b/extern/opn/ym3438.h new file mode 100644 index 000000000..955b73174 --- /dev/null +++ b/extern/opn/ym3438.h @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2017-2021 Alexey Khokholov (Nuke.YKT) + * + * This file is part of Nuked OPN2. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Nuked OPN2(Yamaha YM3438) emulator. + * Thanks: + * Silicon Pr0n: + * Yamaha YM3438 decap and die shot(digshadow). + * OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): + * OPL2 ROMs. + * + * version: 1.0.9 + */ + +#ifndef YM3438_H +#define YM3438_H + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + ym3438_mode_ym2612 = 0x01, /* Enables YM2612 emulation (MD1, MD2 VA2) */ + ym3438_mode_readmode = 0x02, /* Enables status read on any port (TeraDrive, MD1 VA7, MD2, etc) */ + ym3438_mode_opn = 0x04 /* Outputs full, unshifted signal per channel (OPN/OPNA/OPNB) */ +}; + +#include + +typedef uintptr_t Bitu; +typedef intptr_t Bits; +typedef uint64_t Bit64u; +typedef int64_t Bit64s; +typedef uint32_t Bit32u; +typedef int32_t Bit32s; +typedef uint16_t Bit16u; +typedef int16_t Bit16s; +typedef uint8_t Bit8u; +typedef int8_t Bit8s; + +typedef struct +{ + Bit32u cycles; + Bit32u channel; + Bit16s mol, mor; + /* IO */ + Bit16u write_data; + Bit8u write_a; + Bit8u write_d; + Bit8u write_a_en; + Bit8u write_d_en; + Bit8u write_busy; + Bit8u write_busy_cnt; + Bit8u write_fm_address; + Bit8u write_fm_data; + Bit16u write_fm_mode_a; + Bit16u address; + Bit8u data; + Bit8u pin_test_in; + Bit8u pin_irq; + Bit8u busy; + /* LFO */ + Bit8u lfo_en; + Bit8u lfo_freq; + Bit8u lfo_pm; + Bit8u lfo_am; + Bit8u lfo_cnt; + Bit8u lfo_inc; + Bit8u lfo_quotient; + /* Phase generator */ + Bit16u pg_fnum; + Bit8u pg_block; + Bit8u pg_kcode; + Bit32u pg_inc[24]; + Bit32u pg_phase[24]; + Bit8u pg_reset[24]; + Bit32u pg_read; + /* Envelope generator */ + Bit8u eg_cycle; + Bit8u eg_cycle_stop; + Bit8u eg_shift; + Bit8u eg_shift_lock; + Bit8u eg_timer_low_lock; + Bit16u eg_timer; + Bit8u eg_timer_inc; + Bit16u eg_quotient; + Bit8u eg_custom_timer; + Bit8u eg_rate; + Bit8u eg_ksv; + Bit8u eg_inc; + Bit8u eg_ratemax; + Bit8u eg_sl[2]; + Bit8u eg_lfo_am; + Bit8u eg_tl[2]; + Bit8u eg_state[24]; + Bit16u eg_level[24]; + Bit16u eg_out[24]; + Bit8u eg_kon[24]; + Bit8u eg_kon_csm[24]; + Bit8u eg_kon_latch[24]; + Bit8u eg_csm_mode[24]; + Bit8u eg_ssg_enable[24]; + Bit8u eg_ssg_pgrst_latch[24]; + Bit8u eg_ssg_repeat_latch[24]; + Bit8u eg_ssg_hold_up_latch[24]; + Bit8u eg_ssg_dir[24]; + Bit8u eg_ssg_inv[24]; + Bit32u eg_read[2]; + Bit8u eg_read_inc; + /* Settings */ + Bit8u chip_type; + /* FM */ + Bit16s fm_op1[6][2]; + Bit16s fm_op2[6]; + Bit16s fm_out[24]; + Bit16u fm_mod[24]; + /* Channel */ + Bit16s ch_acc[6]; + Bit16s ch_out[6]; + Bit16s ch_lock; + Bit8u ch_lock_l; + Bit8u ch_lock_r; + Bit16s ch_read; + /* Timer */ + Bit16u timer_a_cnt; + Bit16u timer_a_reg; + Bit8u timer_a_load_lock; + Bit8u timer_a_load; + Bit8u timer_a_enable; + Bit8u timer_a_reset; + Bit8u timer_a_load_latch; + Bit8u timer_a_overflow_flag; + Bit8u timer_a_overflow; + + Bit16u timer_b_cnt; + Bit8u timer_b_subcnt; + Bit16u timer_b_reg; + Bit8u timer_b_load_lock; + Bit8u timer_b_load; + Bit8u timer_b_enable; + Bit8u timer_b_reset; + Bit8u timer_b_load_latch; + Bit8u timer_b_overflow_flag; + Bit8u timer_b_overflow; + + /* Register set */ + Bit8u mode_test_21[8]; + Bit8u mode_test_2c[8]; + Bit8u mode_ch3; + Bit8u mode_kon_channel; + Bit8u mode_kon_operator[4]; + Bit8u mode_kon[24]; + Bit8u mode_csm; + Bit8u mode_kon_csm; + Bit8u dacen; + Bit16s dacdata; + + Bit8u ks[24]; + Bit8u ar[24]; + Bit8u sr[24]; + Bit8u dt[24]; + Bit8u multi[24]; + Bit8u sl[24]; + Bit8u rr[24]; + Bit8u dr[24]; + Bit8u am[24]; + Bit8u tl[24]; + Bit8u ssg_eg[24]; + + Bit16u fnum[6]; + Bit8u block[6]; + Bit8u kcode[6]; + Bit16u fnum_3ch[6]; + Bit8u block_3ch[6]; + Bit8u kcode_3ch[6]; + Bit8u reg_a4; + Bit8u reg_ac; + Bit8u connect[6]; + Bit8u fb[6]; + Bit8u pan_l[6], pan_r[6]; + Bit8u ams[6]; + Bit8u pms[6]; + Bit8u status; + Bit32u status_time; +} ym3438_t; + +void OPN2_Reset(ym3438_t *chip); +void OPN2_SetChipType(ym3438_t *chip, Bit32u type); +void OPN2_Clock(ym3438_t *chip, Bit16s *buffer); +void OPN2_Write(ym3438_t *chip, Bit32u port, Bit8u data); +void OPN2_SetTestPin(ym3438_t *chip, Bit32u value); +Bit32u OPN2_ReadTestPin(ym3438_t *chip); +Bit32u OPN2_ReadIRQPin(ym3438_t *chip); +Bit8u OPN2_Read(ym3438_t *chip, Bit32u port); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/extern/opn/ym3438.svg b/extern/opn/ym3438.svg new file mode 100644 index 000000000..d1d86667d --- /dev/null +++ b/extern/opn/ym3438.svg @@ -0,0 +1,16454 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2CLSITEST + daSSSs e + 2ADAC + + 21LSITEST + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Read En + Busy flag + + + + + + + + + + A1 + IC + RD + A0 + CS + WR + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LFO Counter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AMS + + AMS + + + + + + + + + + + + + + AM + + EG out + + TL + + + + + + + + + + + + + + + + + + + + + + + + + + + AM DR + + SL RR + + DT + MULTI + + SR + + RS KS + + AR + + SSG-EG + + AMS + + Data + + + + + + + + + + M + + GND + + D0 + + D1 + + D2 + + D3 + + D4 + + D5 + + D6 + + D7 + + TEST + + IC + + GND + + IRQ + + CS + + WR + + RD + + A0 + + A1 + + AGND + + MOR + + MOL + + AVcc + + VCC + MSB + + + + /D7 + D7 + + + + /D6 + D6 + + + /D5 + D5 + + + /D4 + D4 + /D3 + D3 + + + + + + 30 + + 40 + + + 60 + 50 + + 70 + + 80 + + 90 + + + + + + + + + + + PMS + FMS + R + L + + ALG + + Feedback + + + + + + + + + + D4 + /D4 + D3 + /D3 + D2 + /D2 + + + + + B4 + + B0 + + AC + + A8 + + A4 + + A0 + AC + + + Blck FN Hi + + CH3 Blck FN Hi + + CH3 FN Low + + FN Low + + Blck FN Hi + Data + Address + + + + + + + enable + + CS + WR + bank + IC + RD + A0 + Read Enable + write addr + IC + write data + + + + + + + + + + + + Read Mode + + + + Timer A + Timer B + Timer A + Timer B + + IRQ + Read Status + Timer A + Timer B + + + + + + + + + + + + + + + + + Write Data + Write address + + + + Write Data + + + + + + Address + Data + + + + Bank + + + 2BD EN + + 22LFO + + 24TM AMSB + + 25TM A + + MSB + MSB + MSB + + 22 + 26 + + 27 + + 25 + 0 + 0 + 0 + 0 + 0 + 1 + 1 + 1 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 1 + + 24 + 0 + 0 + 0 + 0 + 0 + 1 + 1 + 1 + + 28 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 1 + + 28KeyOn + + 27CH3 + + 26TM B + + EG Rate select + + EG Rate Select + + Rate + + Rate + + KS + + KS + + + KCODE + + KCODE + + + FNUM + + NT + BLCK + + KC Latch + + MUL Latch + + MULT + + + + Feedback + + Algorithm + + + + Exponent + Sine + DACMODE + DAC EN + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 + 0 + DAC Data + + + + + + + + + + + + DACEN + + DACEn + + + + + PAN + + + + + + + + Note + + PHASE + + EG + + ReadData + Out + + MSB + + + + + + + + + + + AND + + AND + + + + + + + + + + + + + + + MSB + b0 + b2 + b1 + n0 + n1 + DT0 + DT1 + DT2 + + DT0 | DT1 + + DT0 & DT1 + + + + + + + sum0 + + N0N1S0 + + !N0N1S0 + + N0!N1S0 + + !N0!N1S0 + + N0N1!S0 + + !N0N1!S0 + + + N0!N1!S0 + + !N0!N1!S0 + MSB + d0 + d1 + d2 + + + + d3 + + + + + + + + + + + + + Rising + + + 0 + + 1 + + 2 + + 3 + + 4 + + 5 + + 6 + + 7 + + 8 + + 9 + + 10 + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 1 + 2 + 3 + 4 + + + + + + + + + + + + + + + + + + + + + + + + 1 + 2 + 3 + 4 + 1 + 2 + 3 + 4 + + + + 9 + 8 + 7 + + + + + 0 + + 1 + + 2 + + 3 + + 4 + + 5 + + 6 + + 7 + + + + + + + + FMS + MSB + + + + + + + 001 + + 100 + + 011 + + 010 + + 101 + + 111 + + 110 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A or (A << 1) (A << 1) or(A << 2) 1 + 0 + 2 + 1 + 2 + + + + + + + + + + + + + + 11X + + >=5 + + + + <=5 ==7 + ==6 + Enable + + + Repeat + + + + + + XX11 + + XX10 + + XX00 + + + + + + X101X011 + + XXX0 + + + + + + + + + + + + + + + + Enable + Inverse + XX11 + X101 X011 + XXX0 + XX10 + XX00 + + + + + + Timer A + + Timer B + KeyOn + + + 0 + + 1 + + 2 + 3 + 4 + 5 + 6 + 7 + 0 + + 2 + + 3 + + 4 + + 5 + + 6 + + 7 + + 8 + + 9 + + 10 + + 11 + + + + + + + + + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 1011 + 0 + 1 + 2 + 3 + 0 + 1 + 2 + 2 + 0 + 1 + + + + + + + + 0 + + 1 + + 2 + + 3 + + 4 + + 5 + + 6 + + 7 + + 5,6,7 + + 4,5,67 + + 2,5 + + 1,5 + + 0,34,5,6 + + + 0,1,3,4 + 0,1,2 + + OP2 Out + OP3 Out + + OperatorAccumulator + + Switch + + Op 1 + + Op3 + + Op2 + + Op4 + + + + + Op4Select + Op1Select + Op3Select + + Op2Select + + + + + + + + + + + + + 0 + 1 + 1 + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 6-11 + 0-5 + 12-17 + 18-23 + + 18-23 + + 0,4,8,12,16,20 + + 12-17 + + 2,8,14,20 + + 4-7 + + + Rise + + 0 + 1 + 2 + 3 + 0 + 1 + 2 + 3 + + + + + + + + + + + + + + + + + 0.5 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + + 0.5 + + + 1,3,5,7,9,1113 + + Switch + + 2,36,710,11 + + 4,5,6,7 + + 8,9,10,11,12,13 + + + 15 + 14 + + 14,15 + + 12,13 + + + Frequency + Frequency + + + + + 0 + 1 + 2 + + X00 + + X01 + + X10 + + X11 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 14,15 + Reset + + M And + + PG Reset + + TL + + + TL + + AND OR + MSB + n0 + n1 + b0 + b1 + b2 + n0 + n1 + b0 + b1 + b2 + + 11->n010->n101->b000->b1 + + 11->n110->b001->b100->b2 + + 11->b010->b101->b2 + + 11->b110->b2 + + 11->b2 + 0 + + Non Zero + + + + + + + + + + + <12 + + + + + Max Rate + + + + + + + 1111XX + + 1110XX + + 1101XX + + 1100XX + + Non zero + + Clock 0 + + + 0,12or IC + + EG LSI + IC + + + + + + + + + + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + + 0,2,4,6,8,10 + + 1,2,5,6,9,10 + + 3,4,5,6,11 + + 7,8,9,10,11 + + + + + + + + + + + + + + + + + + + LFO En + + + Bank + + + + + Top 4 non zero + + + AND OR + + IC + + AND OR + + + + + + + + + + + + + + + + + Exponent + + 3 + + 2 + + + 1 + 0 + + + + + + + + + + + Linear + + SSG Enable + + + + + + + + + + + + AND OR + + + + PG Reset + + 00 - AR + + 01 - DR + + 10-SR + + 11-RR + + + SL + + + + + + + + + + + + + + + + Sustain reach + 111111xxxx + + 00000xxxxx + + + Zero att + + + + AND OR + + Key on + + Key Off + + Attack!KonEvZeroAtt + Decay!KonEvSL reach + + + Release!KonEv + + + + + + CSM + CH3 mode + CH3 mode + + Load A + + Load B + + + Timer A inc + + Timer A LoadRise + + Timer AOverflow + + Timer Reset + + CSM Key + CSM + + CH select + CSM Key On + CH3 + + Is not CSM + + Key latch + + + Envelope off + + SL + + IC + + + + No Attack + + + + + LSI Test + + EG invert + + + Inverse + + Bit9xx10 + Bit9xx11 + + Non attack + Hold up + + Hold up + bit9xx00 + + Reset + + PG Reset + + bit9xxx0 + PG reset + Hold + + + Bank 1 + + Bank 2 + + + + + 2 + + 1 + + 0 + + 1 + + 2 + + + + FB ac + 3 + + 4 + + 5 + + 6 + + 7 + 7 + 6 + 5 + 4 + 3 + 2 + 1 + + + OP2 + + OP1 + + OP1 + + OP1 + + + + + cur2 + + + + 19-24 + + + OP2 + OP1 old + + OP2Mod + + Clock + + + TimerLow + Rate 11Timer 01 + + Rate 01Timer 00 + + + + Rate 1XTimer X0 + + + + IC + + A0 + + A1 + + A2 + + A8 + + + + + And OR + + + + + + + + A0 + + A1 + + A9 + + + Standard + + OP2 + + OP3 + + OP1 + + Ch3 OP1 + + + + + + + + + + LFOenable + + + + LFO0 + LFO1 + LFO2 + + + LFOfreq + L + + 0 + + 1 + + + 2 + 3 + + 4 + + 5 + + 6 + + 7 + + 0 + + 1 + + 2 + + 3 + + 4 + + 5 + 6 + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + + Delay 5 + PG Freeze + + + !PG reset + + !PG reset& !TEST + + + + + Phaseread,cycle 2 + + Nonzero + Non zr<1213 + + Non zr<1212 + + Non zr<12 + 14 + + >12incr + + Inc 1 + + + Inc 2 + Inc 3 + + AttackKeyOn!ZeroAttn!RateMax + + + Key + + KONSSG en + + SSG inverse + + 1 + + 4 + + 2 + + 3 + + + Kon + + + + + + + + + + + + Cycle 2 + + 1 + + 2 + hold up + + Switchor + + Decay!KonEv!SL reach + Cur1 + + + + + Lock + + + Neg + + Load A + + Load B + + EnableA + + EnableB + + + ResetB + ResetA + Timer Test + Clock 1 + + + !ResetEnableOverflow + + + Load Up + + !ResetOvrfEnable + 0 + 1 + 2 + 3 + 4 + 5 + 6 + + 0 + 1 + 2 + 3 + OP1 Old + + Op2 + + OP1 + cur2 + cur1 + Op2 + diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5504.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5504.hpp index d37ef36da..b30bc3a9c 100644 --- a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5504.hpp +++ b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5504.hpp @@ -60,8 +60,8 @@ class es5504_core : public es550x_shared_core *this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this} , m_adc(0) - , m_out{0} { + m_out.fill(0); } // host interface @@ -111,7 +111,7 @@ class es5504_core : public es550x_shared_core private: std::array m_voice; // 25 voices u16 m_adc = 0; // ADC register - std::array m_out = {0}; // 16 channel outputs + std::array m_out; // 16 channel outputs }; #endif diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5505.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5505.hpp index cfd6f88c4..ebe61b72b 100644 --- a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5505.hpp +++ b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5505.hpp @@ -106,8 +106,8 @@ class es5505_core : public es550x_shared_core , m_rvol(0) , m_ch(output_t()) , m_mute(false) - , m_output{0} { + m_output.fill(0); } // internal state diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5506.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5506.hpp index f5b54d4f1..294efff74 100644 --- a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5506.hpp +++ b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es5506.hpp @@ -147,8 +147,8 @@ class es5506_core : public es550x_shared_core , m_filtcount(0) , m_ch(output_t()) , m_mute(false) - , m_output{0} { + m_output.fill(0); } // internal state diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x.hpp index d99fdfbf4..12e3f1afa 100644 --- a/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x.hpp +++ b/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x.hpp @@ -166,8 +166,8 @@ class es550x_shared_core : public vgsound_emu_core , m_start(0) , m_end(0) , m_accum(0) - , m_sample({0}) { + m_sample.fill(0); } // configurations @@ -367,7 +367,7 @@ class es550x_shared_core : public vgsound_emu_core // 21 integer, 11 fraction for ES5506 u32 m_accum = 0; // Samples - std::array m_sample = {0}; + std::array m_sample; }; // Filter diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.hpp index ef3af56dd..c3e2333e7 100644 --- a/extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.hpp +++ b/extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.hpp @@ -87,8 +87,8 @@ class k007232_core : public vgsound_emu_core : vgsound_emu_core("k007232") , m_voice{*this, *this} , m_intf(intf) - , m_reg{0} { + m_reg.fill(0); } // host accessors @@ -111,7 +111,7 @@ class k007232_core : public vgsound_emu_core k007232_intf &m_intf; // common memory interface - std::array m_reg = {0}; // register pool + std::array m_reg; // register pool }; #endif diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.hpp index a616708ba..a8668a0df 100644 --- a/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.hpp +++ b/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.hpp @@ -57,8 +57,8 @@ class k053260_core : public vgsound_emu_core , m_bitpos(4) , m_data(0) , m_adpcm_buf(0) - , m_out{0} { + m_out.fill(0); } // internal state @@ -112,7 +112,7 @@ class k053260_core : public vgsound_emu_core u8 m_bitpos = 4; // bit position for ADPCM decoding u8 m_data = 0; // current data s8 m_adpcm_buf = 0; // ADPCM buffer - std::array m_out = {0}; // current output + std::array m_out; // current output }; class ctrl_t @@ -156,15 +156,15 @@ class k053260_core : public vgsound_emu_core { public: ym3012_t() - : m_in{0} - , m_out{0} { + m_in.fill(0); + m_out.fill(0); } void reset() { - std::fill(m_in.begin(), m_in.end(), 0); - std::fill(m_out.begin(), m_out.end(), 0); + m_in.fill(0); + m_out.fill(0); } void tick(u8 ch, s32 in) @@ -174,8 +174,8 @@ class k053260_core : public vgsound_emu_core } private: - std::array m_in = {0}; - std::array m_out = {0}; + std::array m_in; + std::array m_out; }; class dac_t @@ -212,14 +212,14 @@ class k053260_core : public vgsound_emu_core : vgsound_emu_core("k053260") , m_voice{*this, *this, *this, *this} , m_intf(intf) - , m_host2snd{0} - , m_snd2host{0} , m_ctrl(ctrl_t()) , m_ym3012(ym3012_t()) , m_dac(dac_t()) - , m_reg{0} - , m_out{0} { + m_host2snd.fill(0); + m_snd2host.fill(0); + m_reg.fill(0); + m_out.fill(0); } // communications @@ -249,16 +249,16 @@ class k053260_core : public vgsound_emu_core std::array m_voice; k053260_intf &m_intf; // common memory interface - std::array m_host2snd = {0}; - std::array m_snd2host = {0}; + std::array m_host2snd; + std::array m_snd2host; ctrl_t m_ctrl; // chip control ym3012_t m_ym3012; // YM3012 output dac_t m_dac; // YM3012 interface - std::array m_reg = {0}; // register pool - std::array m_out = {0}; // stereo output + std::array m_reg; // register pool + std::array m_out; // stereo output }; #endif diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/n163/n163.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/n163/n163.hpp index bbf2720ac..efda50429 100644 --- a/extern/vgsound_emu-modified/vgsound_emu/src/n163/n163.hpp +++ b/extern/vgsound_emu-modified/vgsound_emu/src/n163/n163.hpp @@ -58,14 +58,14 @@ class n163_core : public vgsound_emu_core n163_core() : vgsound_emu_core("namco_163") , m_disable(false) - , m_ram{0} , m_voice_cycle(0x78) , m_addr_latch(addr_latch_t()) , m_out(0) - , m_voice_out{0} , m_multiplex(true) , m_acc(0) { + m_ram.fill(0); + m_voice_out.fill(0); } // accessors, getters, setters @@ -97,12 +97,12 @@ class n163_core : public vgsound_emu_core private: bool m_disable = false; - std::array m_ram = {0}; // internal 128 byte RAM + std::array m_ram; // internal 128 byte RAM u8 m_voice_cycle = 0x78; // Voice cycle for processing addr_latch_t m_addr_latch; // address latch s16 m_out = 0; // output - std::array m_voice_out = {0}; // per-voice output, for preview only + std::array m_voice_out; // per-voice output, for preview only // demultiplex related bool m_multiplex = true; // multiplex flag, but less noisy = inaccurate! s16 m_acc = 0; // accumulated output diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/scc/scc.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/scc/scc.hpp index 12322d84f..10b153e7f 100644 --- a/extern/vgsound_emu-modified/vgsound_emu/src/scc/scc.hpp +++ b/extern/vgsound_emu-modified/vgsound_emu/src/scc/scc.hpp @@ -29,7 +29,6 @@ class scc_core : public vgsound_emu_core voice_t(scc_core &host) : vgsound_emu_core("scc_voice") , m_host(host) - , m_wave{0} , m_enable(false) , m_pitch(0) , m_volume(0) @@ -37,6 +36,7 @@ class scc_core : public vgsound_emu_core , m_counter(0) , m_out(0) { + m_wave.fill(0); } // internal state @@ -69,7 +69,7 @@ class scc_core : public vgsound_emu_core private: // registers scc_core &m_host; - std::array m_wave = {0}; // internal waveform + std::array m_wave; // internal waveform bool m_enable = false; // output enable flag u16 m_pitch : 12; // pitch u16 m_volume : 4; // volume @@ -138,8 +138,8 @@ class scc_core : public vgsound_emu_core , m_voice{*this, *this, *this, *this, *this} , m_test(test_t()) , m_out(0) - , m_reg{0} { + m_reg.fill(0); } // destructor @@ -173,7 +173,7 @@ class scc_core : public vgsound_emu_core test_t m_test; // test register s32 m_out = 0; // output to DA0...10 - std::array m_reg = {0}; // register pool + std::array m_reg; // register pool }; // SCC core @@ -269,9 +269,12 @@ class k052539_core : public k052539_scc_core public: k052539_mapper_t() : vgsound_emu_core("k052539_mapper") - , m_bank{0, 1, 2, 3} - , m_ram_enable{false} { + m_bank[0] = 0; + m_bank[1] = 1; + m_bank[2] = 2; + m_bank[3] = 3; + m_ram_enable.fill(false); } // internal state @@ -292,8 +295,8 @@ class k052539_core : public k052539_scc_core private: // registers - std::array m_bank = {0, 1, 2, 3}; - std::array m_ram_enable = {false}; + std::array m_bank; + std::array m_ram_enable; }; public: diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/template/template.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/template/template.hpp index ffc6f9321..97a16f652 100644 --- a/extern/vgsound_emu-modified/vgsound_emu/src/template/template.hpp +++ b/extern/vgsound_emu-modified/vgsound_emu/src/template/template.hpp @@ -64,9 +64,9 @@ class template_core : public vgsound_emu_core // anywhere, and it works as initializer. , m_voice{*this} , m_intf(intf) - , m_array{0} - , m_vector{0} { + m_array.fill(0); + m_vector.resize(1,0); } // accessors, getters, setters @@ -83,9 +83,8 @@ class template_core : public vgsound_emu_core std::array m_voice; // voice classes vgsound_emu_mem_intf &m_intf; // common memory interface - std::array m_array = { - 0}; // std::array for static size array - std::vector m_vector = {0}; // std::vector for variable size array + std::array m_array; // std::array for static size array + std::vector m_vector; // std::vector for variable size array }; #endif diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/x1_010/x1_010.hpp b/extern/vgsound_emu-modified/vgsound_emu/src/x1_010/x1_010.hpp index 37a1895bd..52843685a 100644 --- a/extern/vgsound_emu-modified/vgsound_emu/src/x1_010/x1_010.hpp +++ b/extern/vgsound_emu-modified/vgsound_emu/src/x1_010/x1_010.hpp @@ -87,9 +87,9 @@ class x1_010_core : public vgsound_emu_core , m_acc(0) , m_env_acc(0) , m_data(0) - , m_vol_out{0} - , m_out{0} { + m_vol_out.fill(0); + m_out.fill(0); } // internal state @@ -117,10 +117,10 @@ class x1_010_core : public vgsound_emu_core u32 m_acc = 0; u32 m_env_acc = 0; s8 m_data = 0; - std::array m_vol_out = {0}; + std::array m_vol_out; // for preview only - std::array m_out = {0}; + std::array m_out; }; public: @@ -144,10 +144,10 @@ class x1_010_core : public vgsound_emu_core *this, *this} , m_intf(intf) - , m_envelope{0} - , m_wave{0} - , m_out{0} { + m_envelope.fill(0); + m_wave.fill(0); + m_out.fill(0); } // register accessor @@ -172,11 +172,11 @@ class x1_010_core : public vgsound_emu_core vgsound_emu_mem_intf &m_intf; // RAM - std::array m_envelope = {0}; - std::array m_wave = {0}; + std::array m_envelope; + std::array m_wave; // output data - std::array m_out = {0}; + std::array m_out; }; #endif diff --git a/instruments/FM/synth/Kirby SNES Synth Pad.fui b/instruments/FM/synth/Kirby SNES Synth Pad.fui new file mode 100644 index 000000000..f37268341 Binary files /dev/null and b/instruments/FM/synth/Kirby SNES Synth Pad.fui differ diff --git a/papers/format.md b/papers/format.md index a36325df3..0b8ebcd20 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,6 +32,8 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: +- 132: Furnace 0.6pre2 +- 131: Furnace dev131 - 130: Furnace dev130 - 129: Furnace dev129 - 128: Furnace dev128 diff --git a/papers/newIns.md b/papers/newIns.md index 548edbdeb..31dff219c 100644 --- a/papers/newIns.md +++ b/papers/newIns.md @@ -440,7 +440,7 @@ size | description | - bit 0-4: release 1 | flags | - bit 4: envelope on - | - bit 3: make sustain effective + | - bit 3: make sustain effective (<131) | - bit 0-2: gain mode | - 0: direct | - 4: dec @@ -448,6 +448,13 @@ size | description | - 6: inc | - 7: bent 1 | gain + 1 | decay 2/sustain mode (>=131) + | - bit 5-6: sustain mode + | - 0: direct + | - 1: sustain (release with dec) + | - 2: sustain (release with exp) + | - 3: sustain (release with rel) + | - bit 0-4: decay 2 ``` # Namco 163 data (N1) diff --git a/res/Info.plist b/res/Info.plist index 7b8d0120c..d7f0f98dd 100644 --- a/res/Info.plist +++ b/res/Info.plist @@ -15,17 +15,17 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString - 0.6pre1 + 0.6pre2 CFBundleName Furnace CFBundlePackageType APPL CFBundleShortVersionString - 0.6pre1 + 0.6pre2 CFBundleSignature ???? CFBundleVersion - 0.6pre1 + 0.6pre2 NSHumanReadableCopyright NSHighResolutionCapable diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 90f7f8841..be8480501 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -249,16 +249,20 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: dispatch=new DivPlatformYM2610; + ((DivPlatformYM2610*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1); break; case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL_EXT: dispatch=new DivPlatformYM2610Ext; + ((DivPlatformYM2610Ext*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1); break; case DIV_SYSTEM_YM2610B: dispatch=new DivPlatformYM2610B; + ((DivPlatformYM2610B*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1); break; case DIV_SYSTEM_YM2610B_EXT: dispatch=new DivPlatformYM2610BExt; + ((DivPlatformYM2610BExt*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1); break; case DIV_SYSTEM_AMIGA: dispatch=new DivPlatformAmiga; @@ -278,15 +282,19 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do break; case DIV_SYSTEM_YM2203: dispatch=new DivPlatformYM2203; + ((DivPlatformYM2203*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1); break; case DIV_SYSTEM_YM2203_EXT: dispatch=new DivPlatformYM2203Ext; + ((DivPlatformYM2203Ext*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1); break; case DIV_SYSTEM_YM2608: dispatch=new DivPlatformYM2608; + ((DivPlatformYM2608*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1); break; case DIV_SYSTEM_YM2608_EXT: dispatch=new DivPlatformYM2608Ext; + ((DivPlatformYM2608Ext*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1); break; case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: diff --git a/src/engine/engine.h b/src/engine/engine.h index f57bd8d0c..faca57d9d 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -47,8 +47,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev130" -#define DIV_ENGINE_VERSION 130 +#define DIV_VERSION "0.6pre2" +#define DIV_ENGINE_VERSION 132 // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 384e0b780..d30e1fb9c 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -506,6 +506,8 @@ void DivInstrument::writeFeatureSN(SafeWriter* w) { w->writeC(snes.gain); + w->writeC(((snes.sus&3)<<5)|(snes.d2&31)); + FEATURE_END; } @@ -1809,7 +1811,7 @@ void DivInstrument::putInsData(SafeWriter* w) { #define READ_FEAT_END \ if (reader.tell()=131) { + next=reader.readC(); + snes.sus=(next>>5)&3; + snes.d2=next&31; + } READ_FEAT_END; } -void DivInstrument::readFeatureN1(SafeReader& reader) { +void DivInstrument::readFeatureN1(SafeReader& reader, short version) { READ_FEAT_BEGIN; n163.wave=reader.readI(); @@ -2260,7 +2267,7 @@ void DivInstrument::readFeatureN1(SafeReader& reader) { READ_FEAT_END; } -void DivInstrument::readFeatureFD(SafeReader& reader) { +void DivInstrument::readFeatureFD(SafeReader& reader, short version) { READ_FEAT_BEGIN; fds.modSpeed=reader.readI(); @@ -2271,7 +2278,7 @@ void DivInstrument::readFeatureFD(SafeReader& reader) { READ_FEAT_END; } -void DivInstrument::readFeatureWS(SafeReader& reader) { +void DivInstrument::readFeatureWS(SafeReader& reader, short version) { READ_FEAT_BEGIN; ws.wave1=reader.readI(); @@ -2406,7 +2413,7 @@ void DivInstrument::readFeatureWL(SafeReader& reader, DivSong* song, short versi READ_FEAT_END; } -void DivInstrument::readFeatureMP(SafeReader& reader) { +void DivInstrument::readFeatureMP(SafeReader& reader, short version) { READ_FEAT_BEGIN; multipcm.ar=reader.readC(); @@ -2422,7 +2429,7 @@ void DivInstrument::readFeatureMP(SafeReader& reader) { READ_FEAT_END; } -void DivInstrument::readFeatureSU(SafeReader& reader) { +void DivInstrument::readFeatureSU(SafeReader& reader, short version) { READ_FEAT_BEGIN; su.switchRoles=reader.readC(); @@ -2430,7 +2437,7 @@ void DivInstrument::readFeatureSU(SafeReader& reader) { READ_FEAT_END; } -void DivInstrument::readFeatureES(SafeReader& reader) { +void DivInstrument::readFeatureES(SafeReader& reader, short version) { READ_FEAT_BEGIN; es5506.filter.mode=(DivInstrumentES5506::Filter::FilterMode)reader.readC(); @@ -2447,7 +2454,7 @@ void DivInstrument::readFeatureES(SafeReader& reader) { READ_FEAT_END; } -void DivInstrument::readFeatureX1(SafeReader& reader) { +void DivInstrument::readFeatureX1(SafeReader& reader, short version) { READ_FEAT_BEGIN; x1_010.bankSlot=reader.readI(); @@ -2479,47 +2486,47 @@ DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, b if (memcmp(featCode,"EN",2)==0) { // end of instrument break; } else if (memcmp(featCode,"NA",2)==0) { // name - readFeatureNA(reader); + readFeatureNA(reader,version); } else if (memcmp(featCode,"FM",2)==0) { // FM - readFeatureFM(reader); + readFeatureFM(reader,version); } else if (memcmp(featCode,"MA",2)==0) { // macros - readFeatureMA(reader); + readFeatureMA(reader,version); } else if (memcmp(featCode,"64",2)==0) { // C64 - readFeature64(reader); + readFeature64(reader,version); } else if (memcmp(featCode,"GB",2)==0) { // Game Boy - readFeatureGB(reader); + readFeatureGB(reader,version); } else if (memcmp(featCode,"SM",2)==0) { // sample - readFeatureSM(reader); + readFeatureSM(reader,version); } else if (memcmp(featCode,"O1",2)==0) { // op1 macros - readFeatureOx(reader,0); + readFeatureOx(reader,0,version); } else if (memcmp(featCode,"O2",2)==0) { // op2 macros - readFeatureOx(reader,1); + readFeatureOx(reader,1,version); } else if (memcmp(featCode,"O3",2)==0) { // op3 macros - readFeatureOx(reader,2); + readFeatureOx(reader,2,version); } else if (memcmp(featCode,"O4",2)==0) { // op4 macros - readFeatureOx(reader,3); + readFeatureOx(reader,3,version); } else if (memcmp(featCode,"LD",2)==0) { // OPL drums - readFeatureLD(reader); + readFeatureLD(reader,version); } else if (memcmp(featCode,"SN",2)==0) { // SNES - readFeatureSN(reader); + readFeatureSN(reader,version); } else if (memcmp(featCode,"N1",2)==0) { // Namco 163 - readFeatureN1(reader); + readFeatureN1(reader,version); } else if (memcmp(featCode,"FD",2)==0) { // FDS/VB - readFeatureFD(reader); + readFeatureFD(reader,version); } else if (memcmp(featCode,"WS",2)==0) { // WaveSynth - readFeatureWS(reader); + readFeatureWS(reader,version); } else if (memcmp(featCode,"SL",2)==0 && fui && song!=NULL) { // sample list readFeatureSL(reader,song,version); } else if (memcmp(featCode,"WL",2)==0 && fui && song!=NULL) { // wave list readFeatureWL(reader,song,version); } else if (memcmp(featCode,"MP",2)==0) { // MultiPCM - readFeatureMP(reader); + readFeatureMP(reader,version); } else if (memcmp(featCode,"SU",2)==0) { // Sound Unit - readFeatureSU(reader); + readFeatureSU(reader,version); } else if (memcmp(featCode,"ES",2)==0) { // ES5506 - readFeatureES(reader); + readFeatureES(reader,version); } else if (memcmp(featCode,"X1",2)==0) { // X1-010 - readFeatureX1(reader); + readFeatureX1(reader,version); } else { if (song==NULL && (memcmp(featCode,"SL",2)==0 || (memcmp(featCode,"WL",2)==0))) { // nothing @@ -3134,7 +3141,7 @@ DivDataErrors DivInstrument::readInsDataOld(SafeReader &reader, short version) { snes.a=reader.readC(); snes.d=reader.readC(); snes.s=reader.readC(); - snes.sus=snes.s&8; + snes.sus=(snes.s&8)?1:0; snes.s&=7; snes.r=reader.readC(); } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 1f7fad19c..de31295e1 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -630,10 +630,15 @@ struct DivInstrumentSNES { GAIN_MODE_INC_LINEAR=6, GAIN_MODE_INC_INVLOG=7 }; - bool useEnv, sus; + bool useEnv; + // 0: no sustain (key off = cut) + // 1: sustain (R = d2; key off = dec linear to r) + // 2: sustain (R = d2; key off = dec exponential to r) + // 3: sustain (R = d2; key off = R to r) + unsigned char sus; GainMode gainMode; unsigned char gain; - unsigned char a, d, s, r; + unsigned char a, d, s, r, d2; bool operator==(const DivInstrumentSNES& other); bool operator!=(const DivInstrumentSNES& other) { @@ -642,13 +647,14 @@ struct DivInstrumentSNES { DivInstrumentSNES(): useEnv(true), - sus(false), + sus(0), gainMode(GAIN_MODE_DIRECT), gain(127), a(15), d(7), s(7), - r(0) {} + r(0), + d2(0) {} }; struct DivInstrument { @@ -691,24 +697,24 @@ struct DivInstrument { void writeFeatureES(SafeWriter* w); void writeFeatureX1(SafeWriter* w); - void readFeatureNA(SafeReader& reader); - void readFeatureFM(SafeReader& reader); - void readFeatureMA(SafeReader& reader); - void readFeature64(SafeReader& reader); - void readFeatureGB(SafeReader& reader); - void readFeatureSM(SafeReader& reader); - void readFeatureOx(SafeReader& reader, int op); - void readFeatureLD(SafeReader& reader); - void readFeatureSN(SafeReader& reader); - void readFeatureN1(SafeReader& reader); - void readFeatureFD(SafeReader& reader); - void readFeatureWS(SafeReader& reader); + void readFeatureNA(SafeReader& reader, short version); + void readFeatureFM(SafeReader& reader, short version); + void readFeatureMA(SafeReader& reader, short version); + void readFeature64(SafeReader& reader, short version); + void readFeatureGB(SafeReader& reader, short version); + void readFeatureSM(SafeReader& reader, short version); + void readFeatureOx(SafeReader& reader, int op, short version); + void readFeatureLD(SafeReader& reader, short version); + void readFeatureSN(SafeReader& reader, short version); + void readFeatureN1(SafeReader& reader, short version); + void readFeatureFD(SafeReader& reader, short version); + void readFeatureWS(SafeReader& reader, short version); void readFeatureSL(SafeReader& reader, DivSong* song, short version); void readFeatureWL(SafeReader& reader, DivSong* song, short version); - void readFeatureMP(SafeReader& reader); - void readFeatureSU(SafeReader& reader); - void readFeatureES(SafeReader& reader); - void readFeatureX1(SafeReader& reader); + void readFeatureMP(SafeReader& reader, short version); + void readFeatureSU(SafeReader& reader, short version); + void readFeatureES(SafeReader& reader, short version); + void readFeatureX1(SafeReader& reader, short version); DivDataErrors readInsDataOld(SafeReader& reader, short version); DivDataErrors readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song); diff --git a/src/engine/platform/fmshared_OPN.h b/src/engine/platform/fmshared_OPN.h index 610e6160d..630a49701 100644 --- a/src/engine/platform/fmshared_OPN.h +++ b/src/engine/platform/fmshared_OPN.h @@ -21,6 +21,7 @@ #define _FMSHARED_OPN_H #include "fmsharedbase.h" +#include "../../../extern/opn/ym3438.h" #define PLEASE_HELP_ME(_targetChan) \ int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); \ @@ -146,26 +147,37 @@ class DivPlatformOPN: public DivPlatformFMBase { pan(3) {} }; + const int extChanOffs, psgChanOffs, adpcmAChanOffs, adpcmBChanOffs, chanNum; + double fmFreqBase; unsigned int fmDivBase; unsigned int ayDiv; unsigned char csmChan; unsigned char lfoValue; - bool extSys; + bool extSys, useCombo; DivConfig ayFlags; friend void putDispatchChip(void*,int); friend void putDispatchChan(void*,int,int); - DivPlatformOPN(double f=9440540.0, unsigned int d=72, unsigned int a=32, bool isExtSys=false, unsigned char cc=255): + DivPlatformOPN(int ext, int psg, int adpcmA, int adpcmB, int chanCount, double f=9440540.0, unsigned int d=72, unsigned int a=32, bool isExtSys=false, unsigned char cc=255): DivPlatformFMBase(), + extChanOffs(ext), + psgChanOffs(psg), + adpcmAChanOffs(adpcmA), + adpcmBChanOffs(adpcmB), + chanNum(chanCount), fmFreqBase(f), fmDivBase(d), ayDiv(a), csmChan(cc), lfoValue(0), - extSys(isExtSys) {} - + extSys(isExtSys), + useCombo(false) {} + public: + void setCombo(bool combo) { + useCombo=combo; + } }; #endif diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 55471ada1..7ebdf9018 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -1155,7 +1155,7 @@ void DivPlatformGenesis::reset() { fm_ymfm->reset(); } OPN2_Reset(&fm); - OPN2_SetChipType(ladder?ym3438_mode_ym2612:0); + OPN2_SetChipType(&fm,ladder?ym3438_mode_ym2612:0); if (dumpWrites) { addWrite(0xffffffff,0); } @@ -1253,7 +1253,7 @@ void DivPlatformGenesis::setFlags(const DivConfig& flags) { } ladder=flags.getBool("ladderEffect",false); noExtMacros=flags.getBool("noExtMacros",false); - OPN2_SetChipType(ladder?ym3438_mode_ym2612:0); + OPN2_SetChipType(&fm,ladder?ym3438_mode_ym2612:0); CHECK_CUSTOM_CLOCK; if (useYMFM) { if (fm_ymfm!=NULL) delete fm_ymfm; diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index a99aac610..9b72649de 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -21,7 +21,6 @@ #define _GENESIS_H #include "fmshared_OPN.h" -#include "../../../extern/Nuked-OPN2/ym3438.h" #include "sound/ymfm/ymfm_opn.h" @@ -131,7 +130,7 @@ class DivPlatformGenesis: public DivPlatformOPN { int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); DivPlatformGenesis(): - DivPlatformOPN(9440540.0, 72, 32, false, 7) {} + DivPlatformOPN(2, 6, 6, 6, 6, 9440540.0, 72, 32, false, 7) {} ~DivPlatformGenesis(); }; #endif diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index d902e2422..0136123e3 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -248,6 +248,7 @@ void DivPlatformOPLL::tick(bool sysTick) { if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>65535) chan[i].freq=65535; int freqt=toFreq(chan[i].freq); + if (freqt>2047) freqt=2047; chan[i].freqL=freqt&0xff; if (i>=6 && properDrums && (i<9 || !noTopHatFreq)) { immWrite(0x10+drumSlot[i],freqt&0xff); @@ -257,7 +258,7 @@ void DivPlatformOPLL::tick(bool sysTick) { immWrite(0x10+i,freqt&0xff); } } - chan[i].freqH=freqt>>8; + chan[i].freqH=(freqt>>8)&15; } if (chan[i].keyOn && i>=6 && properDrums) { if (!isMuted[i]) { diff --git a/src/engine/platform/pokey.cpp b/src/engine/platform/pokey.cpp index a6ddb78f1..a79ec4537 100644 --- a/src/engine/platform/pokey.cpp +++ b/src/engine/platform/pokey.cpp @@ -455,6 +455,7 @@ void DivPlatformPOKEY::setFlags(const DivConfig& flags) { oscBuf[i]->rate=rate/2; } altASAP.init(chipClock,rate); + altASAP.reset(); } else { rate=chipClock; for (int i=0; i<4; i++) { diff --git a/src/engine/platform/snes.cpp b/src/engine/platform/snes.cpp index 20b9a52b4..8752c08b2 100644 --- a/src/engine/platform/snes.cpp +++ b/src/engine/platform/snes.cpp @@ -607,10 +607,23 @@ void DivPlatformSNES::writeEnv(int ch) { if (chan[ch].state.sus) { if (chan[ch].active) { chWrite(ch,5,chan[ch].state.a|(chan[ch].state.d<<4)|0x80); - chWrite(ch,6,chan[ch].state.s<<5); - } else { // dec linear - chWrite(ch,7,0x80|chan[ch].state.r); - chWrite(ch,5,0); + chWrite(ch,6,(chan[ch].state.s<<5)|(chan[ch].state.d2&31)); + } else { + switch (chan[ch].state.sus) { + case 1: // dec linear + chWrite(ch,7,0x80|chan[ch].state.r); + chWrite(ch,5,0); + break; + case 2: // dec exp + chWrite(ch,7,0xa0|chan[ch].state.r); + chWrite(ch,5,0); + break; + case 3: // update r + chWrite(ch,6,(chan[ch].state.s<<5)|(chan[ch].state.r&31)); + break; + default: // what? + break; + } } } else { chWrite(ch,5,chan[ch].state.a|(chan[ch].state.d<<4)|0x80); diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index d31c58f40..a968d49d2 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -157,6 +157,80 @@ const char** DivPlatformYM2203::getRegisterSheet() { } void DivPlatformYM2203::acquire(short* bufL, short* bufR, size_t start, size_t len) { + if (useCombo) { + acquire_combo(bufL,bufR,start,len); + } else { + acquire_ymfm(bufL,bufR,start,len); + } +} + +void DivPlatformYM2203::acquire_combo(short* bufL, short* bufR, size_t start, size_t len) { + static int os; + static short ignored[2]; + + for (size_t h=start; hread(0)&0x80)) { + QueuedWrite& w=writes.front(); + + if (w.addr<=0x1c || w.addr==0x2d || w.addr==0x2e || w.addr==0x2f) { + // ymfm write + fm->write(0x0,w.addr); + fm->write(0x1,w.val); + + regPool[w.addr&0xff]=w.val; + writes.pop_front(); + delay=1; + } else { + // Nuked write + if (w.addrOrVal) { + OPN2_Write(&fm_nuked,0x1,w.val); + regPool[w.addr&0xff]=w.val; + writes.pop_front(); + } else { + lastBusy++; + if (fm_nuked.write_busy==0) { + OPN2_Write(&fm_nuked,0x0,w.addr); + w.addrOrVal=true; + } + } + } + } + } + + OPN2_Clock(&fm_nuked,ignored); + } + os=( + (fm_nuked.ch_out[0])+ + (fm_nuked.ch_out[1])+ + (fm_nuked.ch_out[2]) + ); + + os&=~3; + + // ymfm part + fm->generate(&fmout); + + os+=((fmout.data[1]+fmout.data[2]+fmout.data[3])>>1); + if (os<-32768) os=-32768; + if (os>32767) os=32767; + + bufL[h]=os; + + for (int i=0; i<3; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[i]; + } + + for (int i=3; i<6; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=fmout.data[i-2]; + } + } +} + +void DivPlatformYM2203::acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len) { static int os; ymfm::ym2203::fm_engine* fme=fm->debug_fm_engine(); @@ -857,6 +931,8 @@ void DivPlatformYM2203::reset() { if (dumpWrites) { addWrite(0xffffffff,0); } + OPN2_Reset(&fm_nuked); + OPN2_SetChipType(&fm_nuked,ym3438_mode_opn); fm->reset(); for (int i=0; i<6; i++) { chan[i]=DivPlatformOPN::OPNChannel(); @@ -946,18 +1022,21 @@ void DivPlatformYM2203::setFlags(const DivConfig& flags) { fmFreqBase=4720270.0/2.0, fmDivBase=18, ayDiv=8; + nukedMult=16; break; case 0x02: // /2 prescale=0x2f; fmFreqBase=4720270.0/3.0, fmDivBase=12, ayDiv=4; + nukedMult=24; break; default: // /6 prescale=0x2d; fmFreqBase=4720270.0, fmDivBase=36, ayDiv=16; + nukedMult=8; break; } CHECK_CUSTOM_CLOCK; diff --git a/src/engine/platform/ym2203.h b/src/engine/platform/ym2203.h index 2a3f292f2..3d4670f7b 100644 --- a/src/engine/platform/ym2203.h +++ b/src/engine/platform/ym2203.h @@ -42,6 +42,7 @@ class DivPlatformYM2203: public DivPlatformOPN { OPNChannel chan[6]; DivDispatchOscBuffer* oscBuf[6]; bool isMuted[6]; + ym3438_t fm_nuked; ymfm::ym2203* fm; ymfm::ym2203::output_data fmout; DivYM2203Interface iface; @@ -50,9 +51,13 @@ class DivPlatformYM2203: public DivPlatformOPN { unsigned char sampleBank; bool extMode, noExtMacros; - unsigned char prescale; + unsigned char prescale, nukedMult; friend void putDispatchChip(void*,int); + + void acquire_combo(short* bufL, short* bufR, size_t start, size_t len); + void acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len); + public: void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); @@ -77,7 +82,7 @@ class DivPlatformYM2203: public DivPlatformOPN { int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); DivPlatformYM2203(): - DivPlatformOPN(4720270.0, 36, 16), + DivPlatformOPN(2, 3, 6, 6, 6, 4720270.0, 36, 16), prescale(0x2d) {} ~DivPlatformYM2203(); }; diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 98248472c..924b6dbb9 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -298,6 +298,116 @@ double DivPlatformYM2608::NOTE_ADPCMB(int note) { } void DivPlatformYM2608::acquire(short* bufL, short* bufR, size_t start, size_t len) { + if (useCombo) { + acquire_combo(bufL,bufR,start,len); + } else { + acquire_ymfm(bufL,bufR,start,len); + } +} + +void DivPlatformYM2608::acquire_combo(short* bufL, short* bufR, size_t start, size_t len) { + static int os[2]; + static short ignored[2]; + + ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); + ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); + ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); + + ymfm::ssg_engine::output_data ssgOut; + + ymfm::adpcm_a_channel* adpcmAChan[6]; + for (int i=0; i<6; i++) { + adpcmAChan[i]=aae->debug_channel(i); + } + + for (size_t h=start; hread(0)&0x80)) { + QueuedWrite& w=writes.front(); + + if (w.addr<=0x1c || w.addr==0x2d || w.addr==0x2e || w.addr==0x2f || (w.addr>=0x100 && w.addr<=0x12d)) { + // ymfm write + fm->write(0x0+((w.addr>>8)<<1),w.addr); + fm->write(0x1+((w.addr>>8)<<1),w.val); + + regPool[w.addr&0x1ff]=w.val; + writes.pop_front(); + delay=1; + } else { + // Nuked write + if (w.addrOrVal) { + OPN2_Write(&fm_nuked,0x1+((w.addr>>8)<<1),w.val); + regPool[w.addr&0x1ff]=w.val; + writes.pop_front(); + } else { + lastBusy++; + if (fm_nuked.write_busy==0) { + OPN2_Write(&fm_nuked,0x0+((w.addr>>8)<<1),w.addr); + w.addrOrVal=true; + } + } + } + } + } + + OPN2_Clock(&fm_nuked,ignored); + } + os[0]=( + (fm_nuked.pan_l[0]?fm_nuked.ch_out[0]:0)+ + (fm_nuked.pan_l[1]?fm_nuked.ch_out[1]:0)+ + (fm_nuked.pan_l[2]?fm_nuked.ch_out[2]:0)+ + (fm_nuked.pan_l[3]?fm_nuked.ch_out[3]:0)+ + (fm_nuked.pan_l[4]?fm_nuked.ch_out[4]:0)+ + (fm_nuked.pan_l[5]?fm_nuked.ch_out[5]:0) + ); + os[1]=( + (fm_nuked.pan_r[0]?fm_nuked.ch_out[0]:0)+ + (fm_nuked.pan_r[1]?fm_nuked.ch_out[1]:0)+ + (fm_nuked.pan_r[2]?fm_nuked.ch_out[2]:0)+ + (fm_nuked.pan_r[3]?fm_nuked.ch_out[3]:0)+ + (fm_nuked.pan_r[4]?fm_nuked.ch_out[4]:0)+ + (fm_nuked.pan_r[5]?fm_nuked.ch_out[5]:0) + ); + + os[0]>>=1; + os[1]>>=1; + + // ymfm part + fm->generate(&fmout); + + os[0]+=fmout.data[0]+(fmout.data[2]>>1); + if (os[0]<-32768) os[0]=-32768; + if (os[0]>32767) os[0]=32767; + + os[1]+=fmout.data[1]+(fmout.data[2]>>1); + if (os[1]<-32768) os[1]=-32768; + if (os[1]>32767) os[1]=32767; + + bufL[h]=os[0]; + bufR[h]=os[1]; + + + for (int i=0; idata[oscBuf[i]->needle++]=fm_nuked.ch_out[i]; + } + + ssge->get_last_out(ssgOut); + for (int i=psgChanOffs; idata[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]; + } + + for (int i=adpcmAChanOffs; idata[oscBuf[i]->needle++]=adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1); + } + + oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + } +} + +void DivPlatformYM2608::acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; ymfm::ym2608::fm_engine* fme=fm->debug_fm_engine(); @@ -1256,6 +1366,8 @@ void DivPlatformYM2608::reset() { if (dumpWrites) { addWrite(0xffffffff,0); } + OPN2_Reset(&fm_nuked); + OPN2_SetChipType(&fm_nuked,ym3438_mode_opn); fm->reset(); for (int i=0; i<16; i++) { chan[i]=DivPlatformOPN::OPNChannelStereo(); @@ -1407,18 +1519,21 @@ void DivPlatformYM2608::setFlags(const DivConfig& flags) { fmFreqBase=9440540.0/2.0, fmDivBase=36, ayDiv=16; + nukedMult=16; break; case 0x02: // /2 prescale=0x2f; fmFreqBase=9440540.0/3.0, fmDivBase=24, ayDiv=8; + nukedMult=24; break; default: // /6 prescale=0x2d; fmFreqBase=9440540.0, fmDivBase=72, ayDiv=32; + nukedMult=8; break; } CHECK_CUSTOM_CLOCK; diff --git a/src/engine/platform/ym2608.h b/src/engine/platform/ym2608.h index f6dfc21ab..4e6c59b90 100644 --- a/src/engine/platform/ym2608.h +++ b/src/engine/platform/ym2608.h @@ -47,6 +47,7 @@ class DivPlatformYM2608: public DivPlatformOPN { OPNChannelStereo chan[16]; DivDispatchOscBuffer* oscBuf[16]; bool isMuted[16]; + ym3438_t fm_nuked; ymfm::ym2608* fm; ymfm::ym2608::output_data fmout; @@ -62,12 +63,16 @@ class DivPlatformYM2608: public DivPlatformOPN { int globalRSSVolume; bool extMode, noExtMacros; - unsigned char prescale; + unsigned char prescale, nukedMult; double NOTE_OPNB(int ch, int note); double NOTE_ADPCMB(int note); friend void putDispatchChip(void*,int); + + void acquire_combo(short* bufL, short* bufR, size_t start, size_t len); + void acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len); + public: void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); @@ -97,7 +102,7 @@ class DivPlatformYM2608: public DivPlatformOPN { int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); DivPlatformYM2608(): - DivPlatformOPN(9440540.0, 72, 32), + DivPlatformOPN(2, 6, 9, 15, 16, 9440540.0, 72, 32), prescale(0x2d) {} ~DivPlatformYM2608(); }; diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 7a0a4df73..12ac797da 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -233,6 +233,112 @@ const char** DivPlatformYM2610::getRegisterSheet() { } void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t len) { + if (useCombo) { + acquire_combo(bufL,bufR,start,len); + } else { + acquire_ymfm(bufL,bufR,start,len); + } +} + +void DivPlatformYM2610::acquire_combo(short* bufL, short* bufR, size_t start, size_t len) { + static int os[2]; + static short ignored[2]; + + ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); + ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); + ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); + + ymfm::ssg_engine::output_data ssgOut; + + ymfm::adpcm_a_channel* adpcmAChan[6]; + for (int i=0; i<6; i++) { + adpcmAChan[i]=aae->debug_channel(i); + } + + for (size_t h=start; hread(0)&0x80)) { + QueuedWrite& w=writes.front(); + + if (w.addr<=0x1c || (w.addr>=0x100 && w.addr<=0x12d)) { + // ymfm write + fm->write(0x0+((w.addr>>8)<<1),w.addr); + fm->write(0x1+((w.addr>>8)<<1),w.val); + + regPool[w.addr&0x1ff]=w.val; + writes.pop_front(); + delay=32; + } else { + // Nuked write + if (w.addrOrVal) { + OPN2_Write(&fm_nuked,0x1+((w.addr>>8)<<1),w.val); + regPool[w.addr&0x1ff]=w.val; + writes.pop_front(); + } else { + lastBusy++; + if (fm_nuked.write_busy==0) { + OPN2_Write(&fm_nuked,0x0+((w.addr>>8)<<1),w.addr); + w.addrOrVal=true; + } + } + } + } + } + + OPN2_Clock(&fm_nuked,ignored); + } + os[0]=( + (fm_nuked.pan_l[1]?fm_nuked.ch_out[1]:0)+ + (fm_nuked.pan_l[2]?fm_nuked.ch_out[2]:0)+ + (fm_nuked.pan_l[4]?fm_nuked.ch_out[4]:0)+ + (fm_nuked.pan_l[5]?fm_nuked.ch_out[5]:0) + ); + os[1]=( + (fm_nuked.pan_r[1]?fm_nuked.ch_out[1]:0)+ + (fm_nuked.pan_r[2]?fm_nuked.ch_out[2]:0)+ + (fm_nuked.pan_r[4]?fm_nuked.ch_out[4]:0)+ + (fm_nuked.pan_r[5]?fm_nuked.ch_out[5]:0) + ); + + os[0]>>=1; + os[1]>>=1; + + // ymfm part + fm->generate(&fmout); + + os[0]+=fmout.data[0]+(fmout.data[2]>>1); + if (os[0]<-32768) os[0]=-32768; + if (os[0]>32767) os[0]=32767; + + os[1]+=fmout.data[1]+(fmout.data[2]>>1); + if (os[1]<-32768) os[1]=-32768; + if (os[1]>32767) os[1]=32767; + + bufL[h]=os[0]; + bufR[h]=os[1]; + + + for (int i=0; idata[oscBuf[i]->needle++]=fm_nuked.ch_out[bchOffs[i]]; + } + + ssge->get_last_out(ssgOut); + for (int i=psgChanOffs; idata[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]; + } + + for (int i=adpcmAChanOffs; idata[oscBuf[i]->needle++]=adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1); + } + + oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + } +} + +void DivPlatformYM2610::acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; ymfm::ym2610::fm_engine* fme=fm->debug_fm_engine(); @@ -254,13 +360,13 @@ void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t l for (size_t h=start; hread(0)&0x80)) { QueuedWrite& w=writes.front(); fm->write(0x0+((w.addr>>8)<<1),w.addr); fm->write(0x1+((w.addr>>8)<<1),w.val); regPool[w.addr&0x1ff]=w.val; writes.pop_front(); - delay=4; + delay=1; } } @@ -1275,7 +1381,7 @@ bool DivPlatformYM2610::keyOffAffectsArp(int ch) { } void DivPlatformYM2610::notifyInsChange(int ins) { - for (int i=0; i { +class DivPlatformYM2610: public DivPlatformYM2610Base { protected: const unsigned short chanOffs[4]={ 0x01, 0x02, 0x101, 0x102 @@ -37,6 +37,10 @@ class DivPlatformYM2610: public DivPlatformYM2610Base<14> { }; friend void putDispatchChip(void*,int); + + void acquire_combo(short* bufL, short* bufR, size_t start, size_t len); + void acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len); + public: void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); @@ -60,7 +64,7 @@ class DivPlatformYM2610: public DivPlatformYM2610Base<14> { int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); DivPlatformYM2610(): - DivPlatformYM2610Base<14>(1,4,7,13) {} + DivPlatformYM2610Base(1,4,7,13,14) {} ~DivPlatformYM2610(); }; #endif diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index cb30063a0..0d0d58c21 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -297,6 +297,116 @@ const char** DivPlatformYM2610B::getRegisterSheet() { } void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t len) { + if (useCombo) { + acquire_combo(bufL,bufR,start,len); + } else { + acquire_ymfm(bufL,bufR,start,len); + } +} + +void DivPlatformYM2610B::acquire_combo(short* bufL, short* bufR, size_t start, size_t len) { + static int os[2]; + static short ignored[2]; + + ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); + ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); + ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); + + ymfm::ssg_engine::output_data ssgOut; + + ymfm::adpcm_a_channel* adpcmAChan[6]; + for (int i=0; i<6; i++) { + adpcmAChan[i]=aae->debug_channel(i); + } + + for (size_t h=start; hread(0)&0x80)) { + QueuedWrite& w=writes.front(); + + if (w.addr<=0x1c || (w.addr>=0x100 && w.addr<=0x12d)) { + // ymfm write + fm->write(0x0+((w.addr>>8)<<1),w.addr); + fm->write(0x1+((w.addr>>8)<<1),w.val); + + regPool[w.addr&0x1ff]=w.val; + writes.pop_front(); + delay=32; + } else { + // Nuked write + if (w.addrOrVal) { + OPN2_Write(&fm_nuked,0x1+((w.addr>>8)<<1),w.val); + regPool[w.addr&0x1ff]=w.val; + writes.pop_front(); + } else { + lastBusy++; + if (fm_nuked.write_busy==0) { + OPN2_Write(&fm_nuked,0x0+((w.addr>>8)<<1),w.addr); + w.addrOrVal=true; + } + } + } + } + } + + OPN2_Clock(&fm_nuked,ignored); + } + os[0]=( + (fm_nuked.pan_l[0]?fm_nuked.ch_out[0]:0)+ + (fm_nuked.pan_l[1]?fm_nuked.ch_out[1]:0)+ + (fm_nuked.pan_l[2]?fm_nuked.ch_out[2]:0)+ + (fm_nuked.pan_l[3]?fm_nuked.ch_out[3]:0)+ + (fm_nuked.pan_l[4]?fm_nuked.ch_out[4]:0)+ + (fm_nuked.pan_l[5]?fm_nuked.ch_out[5]:0) + ); + os[1]=( + (fm_nuked.pan_r[0]?fm_nuked.ch_out[0]:0)+ + (fm_nuked.pan_r[1]?fm_nuked.ch_out[1]:0)+ + (fm_nuked.pan_r[2]?fm_nuked.ch_out[2]:0)+ + (fm_nuked.pan_r[3]?fm_nuked.ch_out[3]:0)+ + (fm_nuked.pan_r[4]?fm_nuked.ch_out[4]:0)+ + (fm_nuked.pan_r[5]?fm_nuked.ch_out[5]:0) + ); + + os[0]>>=1; + os[1]>>=1; + + // ymfm part + fm->generate(&fmout); + + os[0]+=fmout.data[0]+(fmout.data[2]>>1); + if (os[0]<-32768) os[0]=-32768; + if (os[0]>32767) os[0]=32767; + + os[1]+=fmout.data[1]+(fmout.data[2]>>1); + if (os[1]<-32768) os[1]=-32768; + if (os[1]>32767) os[1]=32767; + + bufL[h]=os[0]; + bufR[h]=os[1]; + + + for (int i=0; idata[oscBuf[i]->needle++]=fm_nuked.ch_out[i]; + } + + ssge->get_last_out(ssgOut); + for (int i=psgChanOffs; idata[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]; + } + + for (int i=adpcmAChanOffs; idata[oscBuf[i]->needle++]=adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1); + } + + oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + } +} + +void DivPlatformYM2610B::acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; ymfm::ym2610b::fm_engine* fme=fm->debug_fm_engine(); @@ -316,13 +426,13 @@ void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t for (size_t h=start; hread(0)&0x80)) { QueuedWrite& w=writes.front(); fm->write(0x0+((w.addr>>8)<<1),w.addr); fm->write(0x1+((w.addr>>8)<<1),w.val); regPool[w.addr&0x1ff]=w.val; writes.pop_front(); - delay=4; + delay=1; } } @@ -1342,7 +1452,7 @@ bool DivPlatformYM2610B::keyOffAffectsArp(int ch) { } void DivPlatformYM2610B::notifyInsChange(int ins) { - for (int i=0; i { +class DivPlatformYM2610B: public DivPlatformYM2610Base { protected: const unsigned short chanOffs[6]={ 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 @@ -33,6 +33,10 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base<16> { }; friend void putDispatchChip(void*,int); + + void acquire_combo(short* bufL, short* bufR, size_t start, size_t len); + void acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len); + public: void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); @@ -56,7 +60,7 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base<16> { int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); DivPlatformYM2610B(): - DivPlatformYM2610Base<16>(2,6,9,15) {} + DivPlatformYM2610Base(2,6,9,15,16) {} ~DivPlatformYM2610B(); }; #endif diff --git a/src/engine/platform/ym2610shared.h b/src/engine/platform/ym2610shared.h index 487412d54..9671c1571 100644 --- a/src/engine/platform/ym2610shared.h +++ b/src/engine/platform/ym2610shared.h @@ -44,12 +44,13 @@ class DivYM2610Interface: public ymfm::ymfm_interface { sampleBank(0) {} }; -template class DivPlatformYM2610Base: public DivPlatformOPN { +class DivPlatformYM2610Base: public DivPlatformOPN { protected: - OPNChannelStereo chan[ChanNum]; - DivDispatchOscBuffer* oscBuf[ChanNum]; - bool isMuted[ChanNum]; + OPNChannelStereo chan[16]; + DivDispatchOscBuffer* oscBuf[16]; + bool isMuted[16]; + ym3438_t fm_nuked; ymfm::ym2610b* fm; ymfm::ym2610b::output_data fmout; DivPlatformAY8910* ay; @@ -72,9 +73,6 @@ template class DivPlatformYM2610Base: public DivPlatformOPN { unsigned char writeADPCMAOff, writeADPCMAOn; int globalADPCMAVolume; - const int extChanOffs, psgChanOffs, adpcmAChanOffs, adpcmBChanOffs; - const int chanNum=ChanNum; - double NOTE_OPNB(int ch, int note) { if (ch>=adpcmBChanOffs) { // ADPCM return NOTE_ADPCMB(note); @@ -98,6 +96,9 @@ template class DivPlatformYM2610Base: public DivPlatformOPN { writeADPCMAOn=0; globalADPCMAVolume=0x3f; + OPN2_Reset(&fm_nuked); + OPN2_SetChipType(&fm_nuked,ym3438_mode_opn); + ay->reset(); ay->getRegisterWrites().clear(); ay->flushWrites(); @@ -220,8 +221,8 @@ template class DivPlatformYM2610Base: public DivPlatformOPN { } CHECK_CUSTOM_CLOCK; noExtMacros=flags.getBool("noExtMacros",false); - rate=chipClock/16; - for (int i=0; isample_rate(chipClock); + for (int i=0; i<16; i++) { oscBuf[i]->rate=rate; } } @@ -231,7 +232,7 @@ template class DivPlatformYM2610Base: public DivPlatformOPN { ayFlags.set("chipType",1); dumpWrites=false; skipRegisterWrites=false; - for (int i=0; i class DivPlatformYM2610Base: public DivPlatformOPN { iface.adpcmBMem=adpcmBMem; iface.sampleBank=0; fm=new ymfm::ym2610b(iface); - fm->set_fidelity(ymfm::OPN_FIDELITY_MAX); + fm->set_fidelity(ymfm::OPN_FIDELITY_MED); setFlags(flags); // YM2149, 2MHz ay=new DivPlatformAY8910(true,chipClock,32); @@ -253,7 +254,7 @@ template class DivPlatformYM2610Base: public DivPlatformOPN { } void quit() { - for (int i=0; iquit(); @@ -262,12 +263,8 @@ template class DivPlatformYM2610Base: public DivPlatformOPN { delete[] adpcmBMem; } - DivPlatformYM2610Base(int ext, int psg, int adpcmA, int adpcmB): - DivPlatformOPN(9440540.0, 72, 32), - extChanOffs(ext), - psgChanOffs(psg), - adpcmAChanOffs(adpcmA), - adpcmBChanOffs(adpcmB) {} + DivPlatformYM2610Base(int ext, int psg, int adpcmA, int adpcmB, int chanCount): + DivPlatformOPN(ext,psg,adpcmA,adpcmB,chanCount,9440540.0, 72, 32) {} }; #endif diff --git a/src/gui/about.cpp b/src/gui/about.cpp index d73466389..08daa4947 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -141,6 +141,7 @@ const char* aboutLine[]={ "MAME MSM5232 core by Jarek Burczynski and Hiromitsu Shioya", "MAME MSM6258 core by Barry Rodewald", "MAME YMZ280B core by Aaron Giles", + "MAME GA20 core by Acho A. Tang and R. Belmont", "SAASound by Dave Hooper and Simon Owen", "SameBoy by Lior Halphon", "Mednafen PCE, WonderSwan, T6W28 and Virtual Boy audio cores", diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index 13a6cbf41..3c2ed3e7e 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -66,7 +66,11 @@ FM_CHIP_DEBUG; \ ImGui::Text("- fmFreqBase: %.f",ch->fmFreqBase); \ ImGui::Text("- fmDivBase: %d",ch->fmDivBase); \ - ImGui::Text("- ayDiv: %d",ch->ayDiv); + ImGui::Text("- ayDiv: %d",ch->ayDiv); \ + ImGui::Text("- extChanOffs: %d",ch->extChanOffs); \ + ImGui::Text("- psgChanOffs: %d",ch->psgChanOffs); \ + ImGui::Text("- adpcmAChanOffs: %d",ch->adpcmAChanOffs); \ + ImGui::Text("- adpcmBChanOffs: %d",ch->adpcmBChanOffs); \ #define COMMON_CHIP_DEBUG_BOOL \ ImGui::TextColored(ch->skipRegisterWrites?colorOn:colorOff,">> SkipRegisterWrites"); \ @@ -98,11 +102,6 @@ ImGui::Text("- writeADPCMAOff: %d",ch->writeADPCMAOff); \ ImGui::Text("- writeADPCMAOn: %d",ch->writeADPCMAOn); \ ImGui::Text("- globalADPCMAVolume: %d",ch->globalADPCMAVolume); \ - ImGui::Text("- extChanOffs: %d",ch->extChanOffs); \ - ImGui::Text("- psgChanOffs: %d",ch->psgChanOffs); \ - ImGui::Text("- adpcmAChanOffs: %d",ch->adpcmAChanOffs); \ - ImGui::Text("- adpcmBChanOffs: %d",ch->adpcmBChanOffs); \ - ImGui::Text("- chanNum: %d",ch->chanNum); \ FM_OPN_CHIP_DEBUG_BOOL; \ ImGui::TextColored(ch->extMode?colorOn:colorOff,">> ExtMode"); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 6fbc1681d..0149cc65c 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3101,14 +3101,32 @@ bool FurnaceGUI::loop() { scrX=ev.window.data1; scrY=ev.window.data2; updateWindow=true; + logV("window moved to %dx%d",scrX,scrY); + break; + case SDL_WINDOWEVENT_SIZE_CHANGED: + logV("window size changed to %dx%d",ev.window.data1,ev.window.data2); + break; + case SDL_WINDOWEVENT_MINIMIZED: + logV("window minimized"); break; case SDL_WINDOWEVENT_MAXIMIZED: scrMax=true; updateWindow=true; + logV("window maximized"); break; case SDL_WINDOWEVENT_RESTORED: scrMax=false; updateWindow=true; + logV("window restored"); + break; + case SDL_WINDOWEVENT_SHOWN: + logV("window shown"); + break; + case SDL_WINDOWEVENT_HIDDEN: + logV("window hidden"); + break; + case SDL_WINDOWEVENT_EXPOSED: + logV("window exposed"); break; } break; @@ -3196,7 +3214,12 @@ bool FurnaceGUI::loop() { } else { //logV("updateWindow: canvas size %dx%d",canvasW,canvasH); // and therefore window size + int prevScrW=scrW; + int prevScrH=scrH; SDL_GetWindowSize(sdlWin,&scrW,&scrH); + if (prevScrW!=scrW || prevScrH!=scrH) { + logV("size change 2: %dx%d (from %dx%d)",scrW,scrH,prevScrW,prevScrH); + } } wantCaptureKeyboard=ImGui::GetIO().WantTextInput; diff --git a/src/gui/gui.h b/src/gui/gui.h index cd2c32477..88c30651b 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1179,6 +1179,7 @@ class FurnaceGUI { int fdsCore; int c64Core; int pokeyCore; + int opnCore; int pcSpeakerOutMethod; String yrw801Path; String tg100Path; @@ -1312,6 +1313,7 @@ class FurnaceGUI { fdsCore(0), c64Core(1), pokeyCore(1), + opnCore(1), pcSpeakerOutMethod(0), yrw801Path(""), tg100Path(""), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index f1d8bc77b..a85ef1341 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -4329,7 +4329,7 @@ void FurnaceGUI::drawInsEdit() { } // Note map ImGui::BeginDisabled(ins->amiga.useWave); - P(ImGui::Checkbox("Use sample map (does not work yet!)",&ins->amiga.useNoteMap)); + P(ImGui::Checkbox("Use sample map",&ins->amiga.useNoteMap)); if (ins->amiga.useNoteMap) { // TODO: frequency map? if (ImGui::BeginTable("NoteMap",3/*4*/,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { @@ -4625,10 +4625,13 @@ void FurnaceGUI::drawInsEdit() { P(ImGui::Checkbox("Use envelope",&ins->snes.useEnv)); ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale); if (ins->snes.useEnv) { - if (ImGui::BeginTable("SNESEnvParams",5,ImGuiTableFlags_NoHostExtendX)) { + if (ImGui::BeginTable("SNESEnvParams",ins->snes.sus?6:5,ImGuiTableFlags_NoHostExtendX)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + if (ins->snes.sus) { + ImGui::TableSetupColumn("c2x",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); + } ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch); @@ -4642,6 +4645,11 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); CENTER_TEXT("S"); ImGui::TextUnformatted("S"); + if (ins->snes.sus) { + ImGui::TableNextColumn(); + CENTER_TEXT("D2"); + ImGui::TextUnformatted("D2"); + } ImGui::TableNextColumn(); CENTER_TEXT("R"); ImGui::TextUnformatted("R"); @@ -4656,14 +4664,30 @@ void FurnaceGUI::drawInsEdit() { P(CWVSliderScalar("##Decay",sliderSize,ImGuiDataType_U8,&ins->snes.d,&_ZERO,&_SEVEN)); ImGui::TableNextColumn(); P(CWVSliderScalar("##Sustain",sliderSize,ImGuiDataType_U8,&ins->snes.s,&_ZERO,&_SEVEN)); + if (ins->snes.sus) { + ImGui::TableNextColumn(); + P(CWVSliderScalar("##Decay2",sliderSize,ImGuiDataType_U8,&ins->snes.d2,&_ZERO,&_THIRTY_ONE)); + } ImGui::TableNextColumn(); P(CWVSliderScalar("##Release",sliderSize,ImGuiDataType_U8,&ins->snes.r,&_ZERO,&_THIRTY_ONE)); ImGui::TableNextColumn(); - drawFMEnv(0,ins->snes.a+1,1+ins->snes.d*2,ins->snes.r,ins->snes.r,(14-ins->snes.s*2),(ins->snes.r==0 || ins->snes.sus),0,0,7,16,31,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); + drawFMEnv(0,ins->snes.a+1,1+ins->snes.d*2,ins->snes.sus?ins->snes.d2:ins->snes.r,ins->snes.sus?ins->snes.r:31,(14-ins->snes.s*2),(ins->snes.r==0 || (ins->snes.sus && ins->snes.d2==0)),0,0,7,16,31,ImVec2(ImGui::GetContentRegionAvail().x,sliderSize.y),ins->type); ImGui::EndTable(); } - ImGui::Checkbox("Make sustain effective",&ins->snes.sus); + ImGui::Text("Sustain/release mode:"); + if (ImGui::RadioButton("Direct (cut on release)",ins->snes.sus==0)) { + ins->snes.sus=0; + } + if (ImGui::RadioButton("Effective (linear decrease)",ins->snes.sus==1)) { + ins->snes.sus=1; + } + if (ImGui::RadioButton("Effective (exponential decrease)",ins->snes.sus==2)) { + ins->snes.sus=2; + } + if (ImGui::RadioButton("Delayed (write R on release)",ins->snes.sus==3)) { + ins->snes.sus=3; + } } else { if (ImGui::BeginTable("SNESGainParams",3,ImGuiTableFlags_NoHostExtendX)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index d14902eef..3502b8a52 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -619,7 +619,7 @@ void FurnaceGUI::drawPattern() { ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y); if (ImGui::ItemAdd(rect,ImGui::GetID(chanID))) { bool hovered=ImGui::ItemHoverable(rect,ImGui::GetID(chanID)); - ImU32 col=(hovered || (!mobileUI && ImGui::IsMouseDown(ImGuiMouseButton_Left)))?ImGui::GetColorU32(ImGuiCol_HeaderHovered):ImGui::GetColorU32(ImGuiCol_Header); + ImU32 col=(hovered || (mobileUI && ImGui::IsMouseDown(ImGuiMouseButton_Left)))?ImGui::GetColorU32(ImGuiCol_HeaderHovered):ImGui::GetColorU32(ImGuiCol_Header); dl->AddRectFilled(rect.Min,rect.Max,col); dl->AddText(ImVec2(minLabelArea.x,rect.Min.y),ImGui::GetColorU32(channelTextColor(i)),chanID); } diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 3e74c00e3..b6a522976 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -1547,6 +1547,12 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_LYNX, 64, 0, "") } ); + ENTRY( + "POKEY", { + CH(DIV_SYSTEM_POKEY, 64, 0, "clockSel=1") + }, + "tickRate=50" + ); ENTRY( "Atari TIA", { CH(DIV_SYSTEM_TIA, 64, 0, "") diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index a8de4dace..6b17e967e 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -30,6 +30,16 @@ #include "sampleUtil.h" #include "util.h" +const double timeDivisors[10]={ + 1000.0, 500.0, 200.0, 100.0, 50.0, 20.0, 10.0, 5.0, 2.0, 1.0 +}; + +const double timeMultipliers[13]={ + 1.0, 2.0, 5.0, 10.0, 20.0, 30.0, + 60.0, 2*60.0, 5*60.0, 10*60.0, 20*60.0, 30*60.0, + 3600.0 +}; + #define CENTER_TEXT(text) \ ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(text).x)); @@ -1000,7 +1010,48 @@ void FurnaceGUI::drawSampleEdit() { ImGui::ItemSize(size,style.FramePadding.y); if (ImGui::ItemAdd(rect,ImGui::GetID("SETime"))) { - dl->AddText(minArea,0xffffffff,"0"); + int targetRate=sampleCompatRate?sample->rate:sample->centerRate; + int curDivisorSel=0; + int curMultiplierSel=0; + double divisor=1000.0; + double multiplier=1.0; + while ((((double)targetRate/divisor)/sampleZoom)<60.0*dpiScale) { + if (curDivisorSel>=10) break; + divisor=timeDivisors[++curDivisorSel]; + } + if (curDivisorSel>=10) { + while ((((double)targetRate*multiplier)/sampleZoom)<60.0*dpiScale) { + if (curMultiplierSel>=13) { + multiplier+=3600.0; + } else { + multiplier=timeMultipliers[++curMultiplierSel]; + } + } + } + double timeStep=multiplier*((double)targetRate/divisor); + double timeMin=-fmod(samplePos,timeStep); + double timeMax=size.x*sampleZoom; + ImU32 color=ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_TIME_FG]); + + dl->AddRectFilled(minArea,maxArea,ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_TIME_BG])); + + for (double i=timeMin; i=9) { + if (timeMs>=3600000) { + t=fmt::sprintf("%d:%02d:%02d",timeMs/3600000,(timeMs/60000)%60,(timeMs/1000)%60); + } else if (timeMs>=60000) { + t=fmt::sprintf("%d:%02d.%02d",timeMs/60000,(timeMs/1000)%60,(timeMs%1000)/10); + } else { + t=fmt::sprintf("%d.%03d",timeMs/1000,timeMs%1000); + } + } else { + t=fmt::sprintf("%dms",timeMs); + } + dl->AddText(pos,color,t.c_str()); + } } ImVec2 avail=ImGui::GetContentRegionAvail(); // sample view size determined here diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 41e87d199..2abcc67ea 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -107,6 +107,11 @@ const char* pokeyCores[]={ "ASAP (C++ port)" }; +const char* opnCores[]={ + "ymfm only", + "Nuked-OPN2 (FM) + ymfm (SSG/ADPCM)" +}; + const char* pcspkrOutMethods[]={ "evdev SND_TONE", "KIOCSOUND on /dev/tty1", @@ -1078,6 +1083,10 @@ void FurnaceGUI::drawSettings() { ImGui::SameLine(); ImGui::Combo("##POKEYCore",&settings.pokeyCore,pokeyCores,2); + ImGui::Text("OPN/OPNA/OPNB cores"); + ImGui::SameLine(); + ImGui::Combo("##OPNCore",&settings.opnCore,opnCores,2); + ImGui::Separator(); ImGui::Text("PC Speaker strategy"); @@ -2346,6 +2355,7 @@ void FurnaceGUI::syncSettings() { settings.fdsCore=e->getConfInt("fdsCore",0); settings.c64Core=e->getConfInt("c64Core",1); settings.pokeyCore=e->getConfInt("pokeyCore",1); + settings.opnCore=e->getConfInt("opnCore",1); settings.pcSpeakerOutMethod=e->getConfInt("pcSpeakerOutMethod",0); settings.yrw801Path=e->getConfString("yrw801Path",""); settings.tg100Path=e->getConfString("tg100Path",""); @@ -2472,6 +2482,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.fdsCore,0,1); clampSetting(settings.c64Core,0,1); clampSetting(settings.pokeyCore,0,1); + clampSetting(settings.opnCore,0,1); clampSetting(settings.pcSpeakerOutMethod,0,4); clampSetting(settings.mainFont,0,6); clampSetting(settings.patFont,0,6); @@ -2616,7 +2627,8 @@ void FurnaceGUI::commitSettings() { settings.nesCore!=e->getConfInt("nesCore",0) || settings.fdsCore!=e->getConfInt("fdsCore",0) || settings.c64Core!=e->getConfInt("c64Core",1) || - settings.pokeyCore!=e->getConfInt("pokeyCore",1) + settings.pokeyCore!=e->getConfInt("pokeyCore",1) || + settings.opnCore!=e->getConfInt("opnCore",1) ); e->setConf("mainFontSize",settings.mainFontSize); @@ -2637,6 +2649,7 @@ void FurnaceGUI::commitSettings() { e->setConf("fdsCore",settings.fdsCore); e->setConf("c64Core",settings.c64Core); e->setConf("pokeyCore",settings.pokeyCore); + e->setConf("opnCore",settings.opnCore); e->setConf("pcSpeakerOutMethod",settings.pcSpeakerOutMethod); e->setConf("yrw801Path",settings.yrw801Path); e->setConf("tg100Path",settings.tg100Path); diff --git a/src/main.cpp b/src/main.cpp index 6bc7d9601..a16bce1cf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -180,6 +180,7 @@ TAParamResult pVersion(String) { printf("- MAME MSM5232 core by Jarek Burczynski and Hiromitsu Shioya (GPLv2)\n"); printf("- MAME MSM6258 core by Barry Rodewald (BSD 3-clause)\n"); printf("- MAME YMZ280B core by Aaron Giles (BSD 3-clause)\n"); + printf("- MAME GA20 core by Acho A. Tang and R. Belmont (BSD 3-clause)\n"); printf("- QSound core by superctr (BSD 3-clause)\n"); printf("- VICE VIC-20 by Rami Rasanen and viznut (GPLv2)\n"); printf("- VERA core by Frank van den Hoef (BSD 2-clause)\n");