From d9ae898b76e7a8342b0f00ac3dc1239a30500277 Mon Sep 17 00:00:00 2001 From: Sergio Martin Date: Mon, 8 Jan 2024 20:11:49 +0100 Subject: [PATCH] Adding initial version --- LICENSE | 339 ++++++++++ README.md | 22 + core/Blip_Buffer.cpp | 423 ++++++++++++ core/Blip_Buffer.h | 358 ++++++++++ core/Data_Reader.cpp | 115 ++++ core/Data_Reader.h | 110 ++++ core/Effects_Buffer.cpp | 515 +++++++++++++++ core/Effects_Buffer.h | 93 +++ core/Multi_Buffer.cpp | 282 ++++++++ core/Multi_Buffer.h | 190 ++++++ core/Nes_Apu.cpp | 368 +++++++++++ core/Nes_Apu.h | 175 +++++ core/Nes_Buffer.cpp | 240 +++++++ core/Nes_Buffer.h | 70 ++ core/Nes_Cart.cpp | 120 ++++ core/Nes_Cart.h | 85 +++ core/Nes_Core.cpp | 537 +++++++++++++++ core/Nes_Core.h | 116 ++++ core/Nes_Cpu.cpp | 1230 +++++++++++++++++++++++++++++++++++ core/Nes_Cpu.h | 126 ++++ core/Nes_Effects_Buffer.cpp | 91 +++ core/Nes_Effects_Buffer.h | 40 ++ core/Nes_Emu.cpp | 519 +++++++++++++++ core/Nes_Emu.h | 278 ++++++++ core/Nes_File.cpp | 214 ++++++ core/Nes_File.h | 127 ++++ core/Nes_Fme7_Apu.cpp | 110 ++++ core/Nes_Fme7_Apu.h | 136 ++++ core/Nes_Mapper.cpp | 302 +++++++++ core/Nes_Mapper.h | 213 ++++++ core/Nes_Namco_Apu.cpp | 180 +++++ core/Nes_Namco_Apu.h | 104 +++ core/Nes_Oscs.cpp | 539 +++++++++++++++ core/Nes_Oscs.h | 149 +++++ core/Nes_Ppu.cpp | 657 +++++++++++++++++++ core/Nes_Ppu.h | 140 ++++ core/Nes_Ppu_Bg.h | 68 ++ core/Nes_Ppu_Impl.cpp | 492 ++++++++++++++ core/Nes_Ppu_Impl.h | 218 +++++++ core/Nes_Ppu_Rendering.cpp | 464 +++++++++++++ core/Nes_Ppu_Rendering.h | 61 ++ core/Nes_Ppu_Sprites.h | 131 ++++ core/Nes_State.cpp | 284 ++++++++ core/Nes_State.h | 131 ++++ core/Nes_Vrc6_Apu.cpp | 214 ++++++ core/Nes_Vrc6_Apu.h | 99 +++ core/Nes_Vrc7.cpp | 205 ++++++ core/Nes_Vrc7.h | 71 ++ core/abstract_file.cpp | 107 +++ core/abstract_file.h | 107 +++ core/apu_state.cpp | 139 ++++ core/apu_state.h | 77 +++ core/blargg_common.h | 63 ++ core/blargg_config.h | 14 + core/blargg_endian.h | 108 +++ core/blargg_source.h | 40 ++ core/emu2413.cpp | 1165 +++++++++++++++++++++++++++++++++ core/emu2413.h | 218 +++++++ core/emu2413_state.cpp | 107 +++ core/emu2413_state.h | 37 ++ core/mappers/mapper000.hpp | 34 + core/mappers/mapper001.hpp | 121 ++++ core/mappers/mapper002.hpp | 44 ++ core/mappers/mapper003.hpp | 43 ++ core/mappers/mapper004.hpp | 241 +++++++ core/mappers/mapper005.hpp | 152 +++++ core/mappers/mapper007.hpp | 52 ++ core/mappers/mapper009.hpp | 61 ++ core/mappers/mapper010.hpp | 58 ++ core/mappers/mapper011.hpp | 50 ++ core/mappers/mapper015.hpp | 92 +++ core/mappers/mapper019.hpp | 220 +++++++ core/mappers/mapper021.hpp | 250 +++++++ core/mappers/mapper022.hpp | 31 + core/mappers/mapper023.hpp | 30 + core/mappers/mapper024.hpp | 244 +++++++ core/mappers/mapper025.hpp | 30 + core/mappers/mapper026.hpp | 9 + core/mappers/mapper030.hpp | 51 ++ core/mappers/mapper032.hpp | 119 ++++ core/mappers/mapper033.hpp | 93 +++ core/mappers/mapper034.hpp | 43 ++ core/mappers/mapper060.hpp | 50 ++ core/mappers/mapper066.hpp | 51 ++ core/mappers/mapper069.hpp | 206 ++++++ core/mappers/mapper070.hpp | 81 +++ core/mappers/mapper071.hpp | 56 ++ core/mappers/mapper073.hpp | 137 ++++ core/mappers/mapper075.hpp | 110 ++++ core/mappers/mapper078.hpp | 74 +++ core/mappers/mapper079.hpp | 90 +++ core/mappers/mapper085.hpp | 224 +++++++ core/mappers/mapper086.hpp | 24 + core/mappers/mapper087.hpp | 51 ++ core/mappers/mapper088.hpp | 112 ++++ core/mappers/mapper089.hpp | 55 ++ core/mappers/mapper093.hpp | 54 ++ core/mappers/mapper094.hpp | 53 ++ core/mappers/mapper097.hpp | 63 ++ core/mappers/mapper113.hpp | 28 + core/mappers/mapper140.hpp | 63 ++ core/mappers/mapper152.hpp | 24 + core/mappers/mapper154.hpp | 28 + core/mappers/mapper156.hpp | 59 ++ core/mappers/mapper180.hpp | 53 ++ core/mappers/mapper184.hpp | 63 ++ core/mappers/mapper190.hpp | 55 ++ core/mappers/mapper193.hpp | 74 +++ core/mappers/mapper206.hpp | 93 +++ core/mappers/mapper207.hpp | 91 +++ core/mappers/mapper232.hpp | 55 ++ core/mappers/mapper240.hpp | 62 ++ core/mappers/mapper241.hpp | 51 ++ core/mappers/mapper244.hpp | 69 ++ core/mappers/mapper246.hpp | 74 +++ core/nes_cpu_io.h | 143 ++++ core/nes_data.cpp | 66 ++ core/nes_data.h | 155 +++++ core/nes_ntsc.cpp | 289 ++++++++ core/nes_ntsc.h | 192 ++++++ core/nes_ntsc_config.h | 27 + core/nes_ntsc_impl.h | 439 +++++++++++++ core/nes_util.cpp | 153 +++++ core/nes_util.h | 64 ++ 124 files changed, 19962 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 core/Blip_Buffer.cpp create mode 100644 core/Blip_Buffer.h create mode 100644 core/Data_Reader.cpp create mode 100644 core/Data_Reader.h create mode 100644 core/Effects_Buffer.cpp create mode 100644 core/Effects_Buffer.h create mode 100644 core/Multi_Buffer.cpp create mode 100644 core/Multi_Buffer.h create mode 100644 core/Nes_Apu.cpp create mode 100644 core/Nes_Apu.h create mode 100644 core/Nes_Buffer.cpp create mode 100644 core/Nes_Buffer.h create mode 100644 core/Nes_Cart.cpp create mode 100644 core/Nes_Cart.h create mode 100644 core/Nes_Core.cpp create mode 100644 core/Nes_Core.h create mode 100644 core/Nes_Cpu.cpp create mode 100644 core/Nes_Cpu.h create mode 100644 core/Nes_Effects_Buffer.cpp create mode 100644 core/Nes_Effects_Buffer.h create mode 100644 core/Nes_Emu.cpp create mode 100644 core/Nes_Emu.h create mode 100644 core/Nes_File.cpp create mode 100644 core/Nes_File.h create mode 100644 core/Nes_Fme7_Apu.cpp create mode 100644 core/Nes_Fme7_Apu.h create mode 100644 core/Nes_Mapper.cpp create mode 100644 core/Nes_Mapper.h create mode 100644 core/Nes_Namco_Apu.cpp create mode 100644 core/Nes_Namco_Apu.h create mode 100644 core/Nes_Oscs.cpp create mode 100644 core/Nes_Oscs.h create mode 100644 core/Nes_Ppu.cpp create mode 100644 core/Nes_Ppu.h create mode 100644 core/Nes_Ppu_Bg.h create mode 100644 core/Nes_Ppu_Impl.cpp create mode 100644 core/Nes_Ppu_Impl.h create mode 100644 core/Nes_Ppu_Rendering.cpp create mode 100644 core/Nes_Ppu_Rendering.h create mode 100644 core/Nes_Ppu_Sprites.h create mode 100644 core/Nes_State.cpp create mode 100644 core/Nes_State.h create mode 100644 core/Nes_Vrc6_Apu.cpp create mode 100644 core/Nes_Vrc6_Apu.h create mode 100644 core/Nes_Vrc7.cpp create mode 100644 core/Nes_Vrc7.h create mode 100644 core/abstract_file.cpp create mode 100644 core/abstract_file.h create mode 100644 core/apu_state.cpp create mode 100644 core/apu_state.h create mode 100644 core/blargg_common.h create mode 100644 core/blargg_config.h create mode 100644 core/blargg_endian.h create mode 100644 core/blargg_source.h create mode 100644 core/emu2413.cpp create mode 100644 core/emu2413.h create mode 100644 core/emu2413_state.cpp create mode 100644 core/emu2413_state.h create mode 100644 core/mappers/mapper000.hpp create mode 100644 core/mappers/mapper001.hpp create mode 100644 core/mappers/mapper002.hpp create mode 100644 core/mappers/mapper003.hpp create mode 100644 core/mappers/mapper004.hpp create mode 100644 core/mappers/mapper005.hpp create mode 100644 core/mappers/mapper007.hpp create mode 100644 core/mappers/mapper009.hpp create mode 100644 core/mappers/mapper010.hpp create mode 100644 core/mappers/mapper011.hpp create mode 100644 core/mappers/mapper015.hpp create mode 100644 core/mappers/mapper019.hpp create mode 100644 core/mappers/mapper021.hpp create mode 100644 core/mappers/mapper022.hpp create mode 100644 core/mappers/mapper023.hpp create mode 100644 core/mappers/mapper024.hpp create mode 100644 core/mappers/mapper025.hpp create mode 100644 core/mappers/mapper026.hpp create mode 100644 core/mappers/mapper030.hpp create mode 100644 core/mappers/mapper032.hpp create mode 100644 core/mappers/mapper033.hpp create mode 100644 core/mappers/mapper034.hpp create mode 100644 core/mappers/mapper060.hpp create mode 100644 core/mappers/mapper066.hpp create mode 100644 core/mappers/mapper069.hpp create mode 100644 core/mappers/mapper070.hpp create mode 100644 core/mappers/mapper071.hpp create mode 100644 core/mappers/mapper073.hpp create mode 100644 core/mappers/mapper075.hpp create mode 100644 core/mappers/mapper078.hpp create mode 100644 core/mappers/mapper079.hpp create mode 100644 core/mappers/mapper085.hpp create mode 100644 core/mappers/mapper086.hpp create mode 100644 core/mappers/mapper087.hpp create mode 100644 core/mappers/mapper088.hpp create mode 100644 core/mappers/mapper089.hpp create mode 100644 core/mappers/mapper093.hpp create mode 100644 core/mappers/mapper094.hpp create mode 100644 core/mappers/mapper097.hpp create mode 100644 core/mappers/mapper113.hpp create mode 100644 core/mappers/mapper140.hpp create mode 100644 core/mappers/mapper152.hpp create mode 100644 core/mappers/mapper154.hpp create mode 100644 core/mappers/mapper156.hpp create mode 100644 core/mappers/mapper180.hpp create mode 100644 core/mappers/mapper184.hpp create mode 100644 core/mappers/mapper190.hpp create mode 100644 core/mappers/mapper193.hpp create mode 100644 core/mappers/mapper206.hpp create mode 100644 core/mappers/mapper207.hpp create mode 100644 core/mappers/mapper232.hpp create mode 100644 core/mappers/mapper240.hpp create mode 100644 core/mappers/mapper241.hpp create mode 100644 core/mappers/mapper244.hpp create mode 100644 core/mappers/mapper246.hpp create mode 100644 core/nes_cpu_io.h create mode 100644 core/nes_data.cpp create mode 100644 core/nes_data.h create mode 100644 core/nes_ntsc.cpp create mode 100644 core/nes_ntsc.h create mode 100644 core/nes_ntsc_config.h create mode 100644 core/nes_ntsc_impl.h create mode 100644 core/nes_util.cpp create mode 100644 core/nes_util.h diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 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. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, 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 or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +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 give any other recipients of the Program a copy of this License +along with the Program. + +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 Program or any portion +of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +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 Program, 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 Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) 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; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, 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 executable. However, as a +special exception, the source code 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. + +If distribution of executable or 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 counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program 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. + + 5. 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 Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program 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 to +this License. + + 7. 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 Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program 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 Program. + +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. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program 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. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 Program +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 Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, 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 + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), 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 Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. 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 program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; 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. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4d6c96f --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +quickerNES +============ + +quickerNES is an attempt to modernizing and improving the performance of quickNES, the fastest NES emulator in the interwebs (as far as I know). The goals for this project are, in order of importance: + +- Improve overall emulation performance even more +- Modernize the code base with best programming practices, including CI tests, benchmarks, and coverage analysis +- Add support for more mappers, controllers, and features supported by other emulators +- Improve accuracy, if possible + +The main aim is to improve the exploration performance of my TASing bot, [JaffarPlus](https://github.com/SergioMartin86/jaffarPlus). However, if this work might help with homebrew emulation and other people having more fun, then much better! + +Credits +========= + +- quickNES was originally by Shay Green (a.k.a. [Blaarg](http://www.slack.net/~ant/)) under the GNU GPLv2 license. The source code is still located at [](https://github.com/kode54/QuickNES) +- The code was later improved and maintained by Christopher Snowhill (a.k.a. [kode54](https://kode54.net/) +- I could trace further contributions (e.g., new mappers) by retrowertz, CaH4e3, some adaptations from the (FCEUX emulator)[https://github.com/TASEmulators/fceux] (see mapper021) +- The latest version of the code is maintained by Libretro's community at [](https://github.com/libretro/QuickNES_Core) + +All base code for this project was found under the GNU GPLv2 license, which I am preserving. Any non-credited work is unintentional and shall be immediately rectfied. + diff --git a/core/Blip_Buffer.cpp b/core/Blip_Buffer.cpp new file mode 100644 index 0000000..9288921 --- /dev/null +++ b/core/Blip_Buffer.cpp @@ -0,0 +1,423 @@ + +// Blip_Buffer 0.4.0. http://www.slack.net/~ant/ + +#include "Blip_Buffer.h" + +#include +#include +#include +#include + +/* Copyright (C) 2003-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +int const buffer_extra = blip_widest_impulse_ + 2; + +Blip_Buffer::Blip_Buffer() +{ + factor_ = LONG_MAX; + offset_ = 0; + buffer_ = 0; + buffer_size_ = 0; + sample_rate_ = 0; + reader_accum = 0; + bass_shift = 0; + clock_rate_ = 0; + bass_freq_ = 16; + length_ = 0; + + extra_length = length_; + extra_offset = offset_; + extra_reader_accum = reader_accum; + memset(extra_buffer, 0, sizeof(extra_buffer)); +} + +Blip_Buffer::~Blip_Buffer() +{ + if ( buffer_ ) + free( buffer_ ); +} + +void Blip_Buffer::clear( int entire_buffer ) +{ + offset_ = 0; + reader_accum = 0; + if ( buffer_ ) + { + long count = (entire_buffer ? buffer_size_ : samples_avail()); + memset( buffer_, 0, (count + buffer_extra) * sizeof (buf_t_) ); + } +} + +const char *Blip_Buffer::set_sample_rate( long new_rate, int msec ) +{ + // start with maximum length that resampled time can represent + long new_size = (ULONG_MAX >> BLIP_BUFFER_ACCURACY) - buffer_extra - 64; + if ( msec != blip_max_length ) + { + long s = (new_rate * (msec + 1) + 999) / 1000; + if ( s < new_size ) + new_size = s; + } + + if ( buffer_size_ != new_size ) + { + void* p = realloc( buffer_, (new_size + buffer_extra) * sizeof *buffer_ ); + if ( !p ) + return "Out of memory"; + buffer_ = (buf_t_*) p; + } + + buffer_size_ = new_size; + // update things based on the sample rate + sample_rate_ = new_rate; + length_ = new_size * 1000 / new_rate - 1; + if ( clock_rate_ ) + clock_rate( clock_rate_ ); + bass_freq( bass_freq_ ); + + clear(); + + return 0; // success +} + +blip_resampled_time_t Blip_Buffer::clock_rate_factor( long clock_rate ) const +{ + double ratio = (double) sample_rate_ / clock_rate; + long factor = (long) floor( ratio * (1L << BLIP_BUFFER_ACCURACY) + 0.5 ); + return (blip_resampled_time_t) factor; +} + +void Blip_Buffer::bass_freq( int freq ) +{ + bass_freq_ = freq; + int shift = 31; + if ( freq > 0 ) + { + shift = 13; + long f = (freq << 16) / sample_rate_; + while ( (f >>= 1) && --shift ) { } + } + bass_shift = shift; +} + +void Blip_Buffer::end_frame( blip_time_t t ) +{ + offset_ += t * factor_; +} + +void Blip_Buffer::remove_silence( long count ) +{ + offset_ -= (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; +} + +long Blip_Buffer::count_samples( blip_time_t t ) const +{ + unsigned long last_sample = resampled_time( t ) >> BLIP_BUFFER_ACCURACY; + unsigned long first_sample = offset_ >> BLIP_BUFFER_ACCURACY; + return (long) (last_sample - first_sample); +} + +blip_time_t Blip_Buffer::count_clocks( long count ) const +{ + if ( count > buffer_size_ ) + count = buffer_size_; + blip_resampled_time_t time = (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; + return (blip_time_t) ((time - offset_ + factor_ - 1) / factor_); +} + +void Blip_Buffer::remove_samples( long count ) +{ + if ( count ) + { + remove_silence( count ); + + // copy remaining samples to beginning and clear old samples + long remain = samples_avail() + buffer_extra; + memmove( buffer_, buffer_ + count, remain * sizeof *buffer_ ); + memset( buffer_ + remain, 0, count * sizeof *buffer_ ); + } +} + +// Blip_Synth_ + +Blip_Synth_::Blip_Synth_( short* p, int w ) : + impulses( p ), + width( w ) +{ + volume_unit_ = 0.0; + kernel_unit = 0; + buf = 0; + last_amp = 0; + delta_factor = 0; +} + +// TODO: apparently this is defined elsewhere too +#define pi my_pi +static double const pi = 3.1415926535897932384626433832795029; + +static void gen_sinc( float* out, int count, double oversample, double treble, double cutoff ) +{ + if ( cutoff >= 0.999 ) + cutoff = 0.999; + + if ( treble < -300.0 ) + treble = -300.0; + if ( treble > 5.0 ) + treble = 5.0; + + double const maxh = 4096.0; + double const rolloff = pow( 10.0, 1.0 / (maxh * 20.0) * treble / (1.0 - cutoff) ); + double const pow_a_n = pow( rolloff, maxh - maxh * cutoff ); + double const to_angle = pi / 2 / maxh / oversample; + for ( int i = 0; i < count; i++ ) + { + double angle = ((i - count) * 2 + 1) * to_angle; + double c = rolloff * cos( (maxh - 1.0) * angle ) - cos( maxh * angle ); + double cos_nc_angle = cos( maxh * cutoff * angle ); + double cos_nc1_angle = cos( (maxh * cutoff - 1.0) * angle ); + double cos_angle = cos( angle ); + + c = c * pow_a_n - rolloff * cos_nc1_angle + cos_nc_angle; + double d = 1.0 + rolloff * (rolloff - cos_angle - cos_angle); + double b = 2.0 - cos_angle - cos_angle; + double a = 1.0 - cos_angle - cos_nc_angle + cos_nc1_angle; + + out [i] = (float) ((a * d + c * b) / (b * d)); // a / b + c / d + } +} + +void blip_eq_t::generate( float* out, int count ) const +{ + // lower cutoff freq for narrow kernels with their wider transition band + // (8 points->1.49, 16 points->1.15) + double oversample = blip_res * 2.25 / count + 0.85; + double half_rate = sample_rate * 0.5; + if ( cutoff_freq ) + oversample = half_rate / cutoff_freq; + double cutoff = rolloff_freq * oversample / half_rate; + + gen_sinc( out, count, blip_res * oversample, treble, cutoff ); + + // apply (half of) hamming window + double to_fraction = pi / (count - 1); + for ( int i = count; i--; ) + out [i] *= 0.54 - 0.46 * cos( i * to_fraction ); +} + +void Blip_Synth_::adjust_impulse() +{ + // sum pairs for each phase and add error correction to end of first half + int const size = impulses_size(); + for ( int p = blip_res; p-- >= blip_res / 2; ) + { + int p2 = blip_res - 2 - p; + long error = kernel_unit; + for ( int i = 1; i < size; i += blip_res ) + { + error -= impulses [i + p ]; + error -= impulses [i + p2]; + } + if ( p == p2 ) + error /= 2; // phase = 0.5 impulse uses same half for both sides + impulses [size - blip_res + p] += error; + //printf( "error: %ld\n", error ); + } + + //for ( int i = blip_res; i--; printf( "\n" ) ) + // for ( int j = 0; j < width / 2; j++ ) + // printf( "%5ld,", impulses [j * blip_res + i + 1] ); +} + +void Blip_Synth_::treble_eq( blip_eq_t const& eq ) +{ + float fimpulse [blip_res / 2 * (blip_widest_impulse_ - 1) + blip_res * 2]; + + int const half_size = blip_res / 2 * (width - 1); + eq.generate( &fimpulse [blip_res], half_size ); + + int i; + + // need mirror slightly past center for calculation + for ( i = blip_res; i--; ) + fimpulse [blip_res + half_size + i] = fimpulse [blip_res + half_size - 1 - i]; + + // starts at 0 + for ( i = 0; i < blip_res; i++ ) + fimpulse [i] = 0.0f; + + // find rescale factor + double total = 0.0; + for ( i = 0; i < half_size; i++ ) + total += fimpulse [blip_res + i]; + + //double const base_unit = 44800.0 - 128 * 18; // allows treble up to +0 dB + //double const base_unit = 37888.0; // allows treble to +5 dB + double const base_unit = 32768.0; // necessary for blip_unscaled to work + double rescale = base_unit / 2 / total; + kernel_unit = (long) base_unit; + + // integrate, first difference, rescale, convert to int + double sum = 0.0; + double next = 0.0; + int const impulses_size = this->impulses_size(); + for ( i = 0; i < impulses_size; i++ ) + { + impulses [i] = (short) floor( (next - sum) * rescale + 0.5 ); + sum += fimpulse [i]; + next += fimpulse [i + blip_res]; + } + adjust_impulse(); + + // volume might require rescaling + double vol = volume_unit_; + if ( vol ) + { + volume_unit_ = 0.0; + volume_unit( vol ); + } +} + +void Blip_Synth_::volume_unit( double new_unit ) +{ + if ( new_unit != volume_unit_ ) + { + // use default eq if it hasn't been set yet + if ( !kernel_unit ) + treble_eq( -8.0 ); + + volume_unit_ = new_unit; + double factor = new_unit * (1L << blip_sample_bits) / kernel_unit; + + if ( factor > 0.0 ) + { + int shift = 0; + + // if unit is really small, might need to attenuate kernel + while ( factor < 2.0 ) + { + shift++; + factor *= 2.0; + } + + if ( shift ) + { + kernel_unit >>= shift; + + // keep values positive to avoid round-towards-zero of sign-preserving + // right shift for negative values + long offset = 0x8000 + (1 << (shift - 1)); + long offset2 = 0x8000 >> shift; + for ( int i = impulses_size(); i--; ) + impulses [i] = (short) (((impulses [i] + offset) >> shift) - offset2); + adjust_impulse(); + } + } + delta_factor = (int) floor( factor + 0.5 ); + //printf( "delta_factor: %d, kernel_unit: %d\n", delta_factor, kernel_unit ); + } +} + +long Blip_Buffer::read_samples( blip_sample_t* out, long max_samples, int stereo ) +{ + long count = samples_avail(); + if ( count > max_samples ) + count = max_samples; + + if ( count ) + { + int const sample_shift = blip_sample_bits - 16; + int const bass_shift = this->bass_shift; + long accum = reader_accum; + buf_t_* in = buffer_; + + if (out != NULL) + { + if ( !stereo ) + { + for ( long n = count; n--; ) + { + long s = accum >> sample_shift; + accum -= accum >> bass_shift; + accum += *in++; + *out++ = (blip_sample_t) s; + + // clamp sample + if ( (blip_sample_t) s != s ) + out [-1] = (blip_sample_t) (0x7FFF - (s >> 24)); + } + } + else + { + for ( long n = count; n--; ) + { + long s = accum >> sample_shift; + accum -= accum >> bass_shift; + accum += *in++; + *out = (blip_sample_t) s; + out += 2; + + // clamp sample + if ( (blip_sample_t) s != s ) + out [-2] = (blip_sample_t) (0x7FFF - (s >> 24)); + } + } + } + else + { + //only run accumulator, do not output anything + for (long n = count; n--; ) + { + accum -= accum >> bass_shift; + accum += *in++; + } + } + + reader_accum = accum; + remove_samples( count ); + } + return count; +} + +void Blip_Buffer::mix_samples( blip_sample_t const* in, long count ) +{ + buf_t_* out = buffer_ + (offset_ >> BLIP_BUFFER_ACCURACY) + blip_widest_impulse_ / 2; + + int const sample_shift = blip_sample_bits - 16; + int prev = 0; + while ( count-- ) + { + long s = (long) *in++ << sample_shift; + *out += s - prev; + prev = s; + ++out; + } + *out -= prev; +} + +void Blip_Buffer::SaveAudioBufferState() +{ + extra_length = length_; + extra_offset = offset_; + extra_reader_accum = reader_accum; + memcpy(extra_buffer, buffer_, sizeof(extra_buffer)); +} +void Blip_Buffer::RestoreAudioBufferState() +{ + length_ = extra_length; + offset_ = extra_offset; + reader_accum = extra_reader_accum; + memcpy(buffer_, extra_buffer, sizeof(extra_buffer)); +} diff --git a/core/Blip_Buffer.h b/core/Blip_Buffer.h new file mode 100644 index 0000000..1c4c1cf --- /dev/null +++ b/core/Blip_Buffer.h @@ -0,0 +1,358 @@ + +// Band-limited sound synthesis and buffering + +// Blip_Buffer 0.4.0 + +#ifndef BLIP_BUFFER_H +#define BLIP_BUFFER_H + +// Time unit at source clock rate +typedef long blip_time_t; + +// Output samples are 16-bit signed, with a range of -32768 to 32767 +typedef short blip_sample_t; +enum { blip_sample_max = 32767 }; + +class Blip_Buffer { +public: + // Set output sample rate and buffer length in milliseconds (1/1000 sec, defaults + // to 1/4 second), then clear buffer. Returns NULL on success, otherwise if there + // isn't enough memory, returns error without affecting current buffer setup. + const char *set_sample_rate( long samples_per_sec, int msec_length = 1000 / 4 ); + + // Set number of source time units per second + void clock_rate( long ); + + // End current time frame of specified duration and make its samples available + // (along with any still-unread samples) for reading with read_samples(). Begins + // a new time frame at the end of the current frame. + void end_frame( blip_time_t time ); + + // Read at most 'max_samples' out of buffer into 'dest', removing them from from + // the buffer. Returns number of samples actually read and removed. If stereo is + // true, increments 'dest' one extra time after writing each sample, to allow + // easy interleving of two channels into a stereo output buffer. + long read_samples( blip_sample_t* dest, long max_samples, int stereo = 0 ); + +// Additional optional features + + // Current output sample rate + long sample_rate() const; + + // Length of buffer, in milliseconds + int length() const; + + // Number of source time units per second + long clock_rate() const; + + // Set frequency high-pass filter frequency, where higher values reduce bass more + void bass_freq( int frequency ); + + // Number of samples delay from synthesis to samples read out + int output_latency() const; + + // Remove all available samples and clear buffer to silence. If 'entire_buffer' is + // false, just clears out any samples waiting rather than the entire buffer. + void clear( int entire_buffer = 1 ); + + // Number of samples available for reading with read_samples() + long samples_avail() const; + + // Remove 'count' samples from those waiting to be read + void remove_samples( long count ); + +// Experimental features + + // Number of raw samples that can be mixed within frame of specified duration. + long count_samples( blip_time_t duration ) const; + + // Mix 'count' samples from 'buf' into buffer. + void mix_samples( blip_sample_t const* buf, long count ); + + // Count number of clocks needed until 'count' samples will be available. + // If buffer can't even hold 'count' samples, returns number of clocks until + // buffer becomes full. + blip_time_t count_clocks( long count ) const; + + // not documented yet + typedef unsigned long blip_resampled_time_t; + void remove_silence( long count ); + blip_resampled_time_t resampled_duration( int t ) const { return t * factor_; } + blip_resampled_time_t resampled_time( blip_time_t t ) const { return t * factor_ + offset_; } + blip_resampled_time_t clock_rate_factor( long clock_rate ) const; +public: + Blip_Buffer(); + ~Blip_Buffer(); + + // Deprecated + typedef blip_resampled_time_t resampled_time_t; + const char *sample_rate( long r ) { return set_sample_rate( r ); } + const char *sample_rate( long r, int msec ) { return set_sample_rate( r, msec ); } +private: + // noncopyable + Blip_Buffer( const Blip_Buffer& ); + Blip_Buffer& operator = ( const Blip_Buffer& ); +public: + typedef long buf_t_; + unsigned long factor_; + blip_resampled_time_t offset_; + buf_t_* buffer_; + long buffer_size_; +private: + long reader_accum; + int bass_shift; + long sample_rate_; + long clock_rate_; + int bass_freq_; + int length_; + friend class Blip_Reader; + +private: + //extra information necessary to load state to an exact sample + buf_t_ extra_buffer[32]; + int extra_length; + long extra_reader_accum; + blip_resampled_time_t extra_offset; +public: + void SaveAudioBufferState(); + void RestoreAudioBufferState(); +}; + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +// Number of bits in resample ratio fraction. Higher values give a more accurate ratio +// but reduce maximum buffer size. +#ifndef BLIP_BUFFER_ACCURACY + #define BLIP_BUFFER_ACCURACY 16 +#endif + +// Number bits in phase offset. Fewer than 6 bits (64 phase offsets) results in +// noticeable broadband noise when synthesizing high frequency square waves. +// Affects size of Blip_Synth objects since they store the waveform directly. +#ifndef BLIP_PHASE_BITS + #define BLIP_PHASE_BITS 6 +#endif + + // Internal + typedef unsigned long blip_resampled_time_t; + int const blip_widest_impulse_ = 16; + int const blip_res = 1 << BLIP_PHASE_BITS; + class blip_eq_t; + + class Blip_Synth_ { + double volume_unit_; + short* const impulses; + int const width; + long kernel_unit; + int impulses_size() const { return blip_res / 2 * width + 1; } + void adjust_impulse(); + public: + Blip_Buffer* buf; + int last_amp; + int delta_factor; + + Blip_Synth_( short* impulses, int width ); + void treble_eq( blip_eq_t const& ); + void volume_unit( double ); + }; + +// Quality level. Start with blip_good_quality. +const int blip_med_quality = 8; +const int blip_good_quality = 12; +const int blip_high_quality = 16; + +// Range specifies the greatest expected change in amplitude. Calculate it +// by finding the difference between the maximum and minimum expected +// amplitudes (max - min). +template +class Blip_Synth { +public: + // Set overall volume of waveform + void volume( double v ) { impl.volume_unit( v * (1.0 / (range < 0 ? -range : range)) ); } + + // Configure low-pass filter (see notes.txt) + void treble_eq( blip_eq_t const& eq ) { impl.treble_eq( eq ); } + + // Get/set Blip_Buffer used for output + Blip_Buffer* output() const { return impl.buf; } + void output( Blip_Buffer* b ) { impl.buf = b; impl.last_amp = 0; } + + // Update amplitude of waveform at given time. Using this requires a separate + // Blip_Synth for each waveform. + void update( blip_time_t time, int amplitude ); + +// Low-level interface + + // Add an amplitude transition of specified delta, optionally into specified buffer + // rather than the one set with output(). Delta can be positive or negative. + // The actual change in amplitude is delta * (volume / range) + void offset( blip_time_t, int delta, Blip_Buffer* ) const; + void offset( blip_time_t t, int delta ) const { offset( t, delta, impl.buf ); } + + // Works directly in terms of fractional output samples. Contact author for more. + void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const; + + // Same as offset(), except code is inlined for higher performance + void offset_inline( blip_time_t t, int delta, Blip_Buffer* buf ) const { + offset_resampled( t * buf->factor_ + buf->offset_, delta, buf ); + } + void offset_inline( blip_time_t t, int delta ) const { + offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf ); + } + +public: + Blip_Synth() : impl( impulses, quality ) { } +private: + typedef short imp_t; + imp_t impulses [blip_res * (quality / 2) + 1]; + Blip_Synth_ impl; +}; + +// Low-pass equalization parameters +class blip_eq_t { +public: + // Logarithmic rolloff to treble dB at half sampling rate. Negative values reduce + // treble, small positive values (0 to 5.0) increase treble. + blip_eq_t( double treble_db = 0 ); + + // See notes.txt + blip_eq_t( double treble, long rolloff_freq, long sample_rate, long cutoff_freq = 0 ); + +private: + double treble; + long rolloff_freq; + long sample_rate; + long cutoff_freq; + void generate( float* out, int count ) const; + friend class Blip_Synth_; +}; + +int const blip_sample_bits = 30; + +// Optimized inline sample reader for custom sample formats and mixing of Blip_Buffer samples +class Blip_Reader { +public: + // Begin reading samples from buffer. Returns value to pass to next() (can + // be ignored if default bass_freq is acceptable). + int begin( Blip_Buffer& ); + + // Current sample + long read() const { return accum >> (blip_sample_bits - 16); } + + // Current raw sample in full internal resolution + long read_raw() const { return accum; } + + // Advance to next sample + void next( int bass_shift = 9 ) { accum += *buf++ - (accum >> bass_shift); } + + // End reading samples from buffer. The number of samples read must now be removed + // using Blip_Buffer::remove_samples(). + void end( Blip_Buffer& b ) { b.reader_accum = accum; } + +private: + const Blip_Buffer::buf_t_* buf; + long accum; +}; + + +// End of public interface + + +// Compatibility with older version +const long blip_unscaled = 65535; +const int blip_low_quality = blip_med_quality; +const int blip_best_quality = blip_high_quality; + +#define BLIP_FWD( i ) { \ + long t0 = i0 * delta + buf [fwd + i]; \ + long t1 = imp [blip_res * (i + 1)] * delta + buf [fwd + 1 + i]; \ + i0 = imp [blip_res * (i + 2)]; \ + buf [fwd + i] = t0; \ + buf [fwd + 1 + i] = t1; } + +#define BLIP_REV( r ) { \ + long t0 = i0 * delta + buf [rev - r]; \ + long t1 = imp [blip_res * r] * delta + buf [rev + 1 - r]; \ + i0 = imp [blip_res * (r - 1)]; \ + buf [rev - r] = t0; \ + buf [rev + 1 - r] = t1; } + +template +inline void Blip_Synth::offset_resampled( blip_resampled_time_t time, + int delta, Blip_Buffer* blip_buf ) const +{ + // Fails if time is beyond end of Blip_Buffer, due to a bug in caller code or the + // need for a longer buffer as set by set_sample_rate(). + delta *= impl.delta_factor; + int phase = (int) (time >> (BLIP_BUFFER_ACCURACY - BLIP_PHASE_BITS) & (blip_res - 1)); + imp_t const* imp = impulses + blip_res - phase; + long* buf = blip_buf->buffer_ + (time >> BLIP_BUFFER_ACCURACY); + long i0 = *imp; + + int const fwd = (blip_widest_impulse_ - quality) / 2; + int const rev = fwd + quality - 2; + + BLIP_FWD( 0 ) + if ( quality > 8 ) BLIP_FWD( 2 ) + if ( quality > 12 ) BLIP_FWD( 4 ) + { + int const mid = quality / 2 - 1; + long t0 = i0 * delta + buf [fwd + mid - 1]; + long t1 = imp [blip_res * mid] * delta + buf [fwd + mid]; + imp = impulses + phase; + i0 = imp [blip_res * mid]; + buf [fwd + mid - 1] = t0; + buf [fwd + mid] = t1; + } + if ( quality > 12 ) BLIP_REV( 6 ) + if ( quality > 8 ) BLIP_REV( 4 ) + BLIP_REV( 2 ) + + long t0 = i0 * delta + buf [rev]; + long t1 = *imp * delta + buf [rev + 1]; + buf [rev] = t0; + buf [rev + 1] = t1; +} + +#undef BLIP_FWD +#undef BLIP_REV + +template +void Blip_Synth::offset( blip_time_t t, int delta, Blip_Buffer* buf ) const +{ + offset_resampled( t * buf->factor_ + buf->offset_, delta, buf ); +} + +template +void Blip_Synth::update( blip_time_t t, int amp ) +{ + int delta = amp - impl.last_amp; + impl.last_amp = amp; + offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf ); +} + +inline blip_eq_t::blip_eq_t( double t ) : + treble( t ), rolloff_freq( 0 ), sample_rate( 44100 ), cutoff_freq( 0 ) { } +inline blip_eq_t::blip_eq_t( double t, long rf, long sr, long cf ) : + treble( t ), rolloff_freq( rf ), sample_rate( sr ), cutoff_freq( cf ) { } + +inline int Blip_Buffer::length() const { return length_; } +inline long Blip_Buffer::samples_avail() const { return (long) (offset_ >> BLIP_BUFFER_ACCURACY); } +inline long Blip_Buffer::sample_rate() const { return sample_rate_; } +inline int Blip_Buffer::output_latency() const { return blip_widest_impulse_ / 2; } +inline long Blip_Buffer::clock_rate() const { return clock_rate_; } +inline void Blip_Buffer::clock_rate( long cps ) { factor_ = clock_rate_factor( clock_rate_ = cps ); } + +inline int Blip_Reader::begin( Blip_Buffer& blip_buf ) +{ + buf = blip_buf.buffer_; + accum = blip_buf.reader_accum; + return blip_buf.bass_shift; +} + +int const blip_max_length = 0; +int const blip_default_length = 250; + +#endif diff --git a/core/Data_Reader.cpp b/core/Data_Reader.cpp new file mode 100644 index 0000000..cb366a3 --- /dev/null +++ b/core/Data_Reader.cpp @@ -0,0 +1,115 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "Data_Reader.h" + +#include "blargg_endian.h" +#include + +/* Copyright (C) 2005-2009 Shay Green. This module 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 +module 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 module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// Data_Reader + +const char * Data_Reader::read( void* p, int n ) +{ + if ( n < 0 ) + return "Internal usage bug"; + + if ( n <= 0 ) + return 0; + + if ( n > remain() ) + return "Truncated file"; + + const char * err = read_v( p, n ); + if ( !err ) + remain_ -= n; + + return err; +} + +const char * Data_Reader::read_avail( void* p, int* n_ ) +{ + int n = min( (uint64_t)(*n_), remain() ); + *n_ = 0; + + if ( n < 0 ) + return "Internal usage bug"; + + if ( n <= 0 ) + return 0; + + const char * err = read_v( p, n ); + if ( !err ) + { + remain_ -= n; + *n_ = n; + } + + return err; +} + +const char * Data_Reader::read_avail( void* p, long* n ) +{ + int i = STATIC_CAST(int, *n); + const char * err = read_avail( p, &i ); + *n = i; + return err; +} + +const char * Data_Reader::skip_v( int count ) +{ + char buf [512]; + while ( count ) + { + int n = min( count, (int) sizeof buf ); + count -= n; + RETURN_ERR( read_v( buf, n ) ); + } + return 0; +} + +const char * Data_Reader::skip( int n ) +{ + if ( n < 0 ) + return "Internal usage bug"; + + if ( n <= 0 ) + return 0; + + if ( n > remain() ) + return "Truncated file"; + + const char * err = skip_v( n ); + if ( !err ) + remain_ -= n; + + return err; +} + + +// File_Reader + +// Mem_File_Reader + +Mem_File_Reader::Mem_File_Reader( const void* p, long s ) : + begin( STATIC_CAST(const char*, p) ) +{ + set_size( s ); +} + +const char * Mem_File_Reader::read_v( void* p, int s ) +{ + memcpy( p, begin + tell(), s ); + return 0; +} diff --git a/core/Data_Reader.h b/core/Data_Reader.h new file mode 100644 index 0000000..110108c --- /dev/null +++ b/core/Data_Reader.h @@ -0,0 +1,110 @@ +// Lightweight interface for reading data from byte stream + +// File_Extractor 1.0.0 +#ifndef DATA_READER_H +#define DATA_READER_H + +#include +#include "blargg_common.h" + +/* Some functions accept a long instead of int for convenience where caller has +a long due to some other interface, and would otherwise have to get a warning, +or cast it (and verify that it wasn't outside the range of an int). + +To really support huge (>2GB) files, long isn't a solution, since there's no +guarantee it's more than 32 bits. We'd need to use long long (if available), or +something compiler-specific, and change all places file sizes or offsets are +used. */ + +// Supports reading and finding out how many bytes are remaining +class Data_Reader { +public: + + // Reads min(*n,remain()) bytes and sets *n to this number, thus trying to read more + // tham remain() bytes doesn't result in error, just *n being set to remain(). + const char * read_avail( void* p, int* n ); + const char * read_avail( void* p, long* n ); + + // Reads exactly n bytes, or returns error if they couldn't ALL be read. + // Reading past end of file results in blargg_err_file_eof. + const char * read( void* p, int n ); + + // Number of bytes remaining until end of file + uint64_t remain() const { return remain_; } + + // Reads and discards n bytes. Skipping past end of file results in blargg_err_file_eof. + const char * skip( int n ); + + virtual ~Data_Reader() { } + +private: + // noncopyable + Data_Reader( const Data_Reader& ); + Data_Reader& operator = ( const Data_Reader& ); + +// Derived interface +protected: + Data_Reader() : remain_( 0 ) { } + + // Sets remain + void set_remain( uint64_t n ) { remain_ = n; } + + // Do same as read(). Guaranteed that 0 < n <= remain(). Value of remain() is updated + // AFTER this call succeeds, not before. set_remain() should NOT be called from this. + virtual const char * read_v( void*, int n ) BLARGG_PURE( { (void)n; return 0; } ) + + // Do same as skip(). Guaranteed that 0 < n <= remain(). Default just reads data + // and discards it. Value of remain() is updated AFTER this call succeeds, not + // before. set_remain() should NOT be called from this. + virtual const char * skip_v( int n ); + +private: + uint64_t remain_; +}; + + +// Supports seeking in addition to Data_Reader operations +class File_Reader : public Data_Reader { +public: + + // Size of file + uint64_t size() const { return size_; } + + // Current position in file + uint64_t tell() const { return size_ - remain(); } + +// Derived interface +protected: + // Sets size and resets position + void set_size( uint64_t n ) { size_ = n; Data_Reader::set_remain( n ); } + void set_size( int n ) { set_size( STATIC_CAST(uint64_t, n) ); } + void set_size( long n ) { set_size( STATIC_CAST(uint64_t, n) ); } + + // Sets reported position + void set_tell( uint64_t i ) { Data_Reader::set_remain( size_ - i ); } +// Implementation +protected: + File_Reader() : size_( 0 ) { } + +private: + uint64_t size_; + + void set_remain(); // avoid accidental use of set_remain +}; + + +// Treats range of memory as a file +class Mem_File_Reader : public File_Reader { +public: + + Mem_File_Reader( const void* begin, long size ); + +// Implementation +protected: + virtual const char * read_v( void*, int ); + +private: + const char* const begin; +}; + +#endif diff --git a/core/Effects_Buffer.cpp b/core/Effects_Buffer.cpp new file mode 100644 index 0000000..057c7fe --- /dev/null +++ b/core/Effects_Buffer.cpp @@ -0,0 +1,515 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Effects_Buffer.h" + +#include + +/* Copyright (C) 2003-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +typedef long fixed_t; + +#define TO_FIXED( f ) fixed_t ((f) * (1L << 15) + 0.5) +#define FMUL( x, y ) (((x) * (y)) >> 15) + +const unsigned echo_size = 4096; +const unsigned echo_mask = echo_size - 1; +BOOST_STATIC_ASSERT( (echo_size & echo_mask) == 0 ); // must be power of 2 + +const unsigned reverb_size = 8192 * 2; +const unsigned reverb_mask = reverb_size - 1; +BOOST_STATIC_ASSERT( (reverb_size & reverb_mask) == 0 ); // must be power of 2 + +Effects_Buffer::config_t::config_t() +{ + pan_1 = -0.15f; + pan_2 = 0.15f; + reverb_delay = 88.0f; + reverb_level = 0.12f; + echo_delay = 61.0f; + echo_level = 0.10f; + delay_variance = 18.0f; + effects_enabled = false; +} + +void Effects_Buffer::set_depth( double d ) +{ + float f = (float) d; + config_t c; + c.pan_1 = -0.6f * f; + c.pan_2 = 0.6f * f; + c.reverb_delay = 880 * 0.1f; + c.echo_delay = 610 * 0.1f; + if ( f > 0.5 ) + f = 0.5; // TODO: more linear reduction of extreme reverb/echo + c.reverb_level = 0.5f * f; + c.echo_level = 0.30f * f; + c.delay_variance = 180 * 0.1f; + c.effects_enabled = (d > 0.0f); + config( c ); +} + +Effects_Buffer::Effects_Buffer( bool center_only ) : Multi_Buffer( 2 ) +{ + buf_count = center_only ? max_buf_count - 4 : max_buf_count; + + echo_buf = NULL; + echo_pos = 0; + + reverb_buf = NULL; + reverb_pos = 0; + + stereo_remain = 0; + effect_remain = 0; + effects_enabled = false; + set_depth( 0 ); +} + +Effects_Buffer::~Effects_Buffer() +{ + delete [] echo_buf; + delete [] reverb_buf; +} + +const char *Effects_Buffer::set_sample_rate( long rate, int msec ) +{ + if ( !echo_buf ) + { + echo_buf = new blip_sample_t [echo_size]; + CHECK_ALLOC( echo_buf ); + } + + if ( !reverb_buf ) + { + reverb_buf = new blip_sample_t [reverb_size]; + CHECK_ALLOC( reverb_buf ); + } + + for ( int i = 0; i < buf_count; i++ ) + RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) ); + + config( config_ ); + clear(); + + return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() ); +} + +void Effects_Buffer::clock_rate( long rate ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clock_rate( rate ); +} + +void Effects_Buffer::bass_freq( int freq ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].bass_freq( freq ); +} + +void Effects_Buffer::clear() +{ + stereo_remain = 0; + effect_remain = 0; + if ( echo_buf ) + memset( echo_buf, 0, echo_size * sizeof *echo_buf ); + if ( reverb_buf ) + memset( reverb_buf, 0, reverb_size * sizeof *reverb_buf ); + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clear(); +} + +inline int pin_range( int n, int max, int min = 0 ) +{ + if ( n < min ) + return min; + if ( n > max ) + return max; + return n; +} + +void Effects_Buffer::config( const config_t& cfg ) +{ + channels_changed(); + + // clear echo and reverb buffers + if ( !config_.effects_enabled && cfg.effects_enabled && echo_buf ) + { + memset( echo_buf, 0, echo_size * sizeof (blip_sample_t) ); + memset( reverb_buf, 0, reverb_size * sizeof (blip_sample_t) ); + } + + config_ = cfg; + + if ( config_.effects_enabled ) + { + // convert to internal format + + chans.pan_1_levels [0] = TO_FIXED( 1 ) - TO_FIXED( config_.pan_1 ); + chans.pan_1_levels [1] = TO_FIXED( 2 ) - chans.pan_1_levels [0]; + + chans.pan_2_levels [0] = TO_FIXED( 1 ) - TO_FIXED( config_.pan_2 ); + chans.pan_2_levels [1] = TO_FIXED( 2 ) - chans.pan_2_levels [0]; + + chans.reverb_level = TO_FIXED( config_.reverb_level ); + chans.echo_level = TO_FIXED( config_.echo_level ); + + int delay_offset = int (1.0 / 2000 * config_.delay_variance * sample_rate()); + + int reverb_sample_delay = int (1.0 / 1000 * config_.reverb_delay * sample_rate()); + chans.reverb_delay_l = pin_range( reverb_size - + (reverb_sample_delay - delay_offset) * 2, reverb_size - 2, 0 ); + chans.reverb_delay_r = pin_range( reverb_size + 1 - + (reverb_sample_delay + delay_offset) * 2, reverb_size - 1, 1 ); + + int echo_sample_delay = int (1.0 / 1000 * config_.echo_delay * sample_rate()); + chans.echo_delay_l = pin_range( echo_size - 1 - (echo_sample_delay - delay_offset), + echo_size - 1 ); + chans.echo_delay_r = pin_range( echo_size - 1 - (echo_sample_delay + delay_offset), + echo_size - 1 ); + + // set up outputs + for ( unsigned i = 0; i < chan_count; i++ ) + { + channel_t& o = channels [i]; + if ( i < 2 ) + { + o.center = &bufs [i]; + o.left = &bufs [3]; + o.right = &bufs [4]; + } + else + { + o.center = &bufs [2]; + o.left = &bufs [5]; + o.right = &bufs [6]; + } + } + + } + else + { + // set up outputs + for ( unsigned i = 0; i < chan_count; i++ ) + { + channel_t& o = channels [i]; + o.center = &bufs [0]; + o.left = &bufs [1]; + o.right = &bufs [2]; + } + } + + if ( buf_count < max_buf_count ) + { + for ( unsigned i = 0; i < chan_count; i++ ) + { + channel_t& o = channels [i]; + o.left = o.center; + o.right = o.center; + } + } +} + +void Effects_Buffer::end_frame( blip_time_t clock_count, bool stereo ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].end_frame( clock_count ); + + if ( stereo && buf_count == max_buf_count ) + stereo_remain = bufs [0].samples_avail() + bufs [0].output_latency(); + + if ( effects_enabled || config_.effects_enabled ) + effect_remain = bufs [0].samples_avail() + bufs [0].output_latency(); + + effects_enabled = config_.effects_enabled; +} + +long Effects_Buffer::samples_avail() const +{ + return bufs [0].samples_avail() * 2; +} + +long Effects_Buffer::read_samples( blip_sample_t* out, long total_samples ) +{ + long remain = bufs [0].samples_avail(); + if ( remain > (total_samples >> 1) ) + remain = (total_samples >> 1); + total_samples = remain; + while ( remain ) + { + int active_bufs = buf_count; + long count = remain; + + // optimizing mixing to skip any channels which had nothing added + if ( effect_remain ) + { + if ( count > effect_remain ) + count = effect_remain; + + if ( stereo_remain ) + { + mix_enhanced( out, count ); + } + else + { + mix_mono_enhanced( out, count ); + active_bufs = 3; + } + } + else if ( stereo_remain ) + { + mix_stereo( out, count ); + active_bufs = 3; + } + else + { + mix_mono( out, count ); + active_bufs = 1; + } + + out += count * 2; + remain -= count; + + stereo_remain -= count; + if ( stereo_remain < 0 ) + stereo_remain = 0; + + effect_remain -= count; + if ( effect_remain < 0 ) + effect_remain = 0; + + for ( int i = 0; i < buf_count; i++ ) + { + if ( i < active_bufs ) + bufs [i].remove_samples( count ); + else + bufs [i].remove_silence( count ); // keep time synchronized + } + } + + return total_samples * 2; +} + +void Effects_Buffer::mix_mono( blip_sample_t* out, long count ) +{ + Blip_Reader c; + int shift = c.begin( bufs [0] ); + + // unrolled loop + for ( long n = count >> 1; n--; ) + { + long cs0 = c.read(); + c.next( shift ); + + long cs1 = c.read(); + c.next( shift ); + + if ( (int16_t) cs0 != cs0 ) + cs0 = 0x7FFF - (cs0 >> 24); + ((uint32_t*) out) [0] = ((uint16_t) cs0) | (cs0 << 16); + + if ( (int16_t) cs1 != cs1 ) + cs1 = 0x7FFF - (cs1 >> 24); + ((uint32_t*) out) [1] = ((uint16_t) cs1) | (cs1 << 16); + out += 4; + } + + if ( count & 1 ) + { + int s = c.read(); + c.next( shift ); + out [0] = s; + out [1] = s; + if ( (int16_t) s != s ) + { + s = 0x7FFF - (s >> 24); + out [0] = s; + out [1] = s; + } + } + + c.end( bufs [0] ); +} + +void Effects_Buffer::mix_stereo( blip_sample_t* out, long count ) +{ + Blip_Reader l; l.begin( bufs [1] ); + Blip_Reader r; r.begin( bufs [2] ); + Blip_Reader c; + int shift = c.begin( bufs [0] ); + + while ( count-- ) + { + int cs = c.read(); + c.next( shift ); + int left = cs + l.read(); + int right = cs + r.read(); + l.next( shift ); + r.next( shift ); + + if ( (int16_t) left != left ) + left = 0x7FFF - (left >> 24); + + out [0] = left; + out [1] = right; + + out += 2; + + if ( (int16_t) right != right ) + out [-1] = 0x7FFF - (right >> 24); + } + + c.end( bufs [0] ); + r.end( bufs [2] ); + l.end( bufs [1] ); +} + +void Effects_Buffer::mix_mono_enhanced( blip_sample_t* out, long count ) +{ + Blip_Reader sq1; sq1.begin( bufs [0] ); + Blip_Reader sq2; sq2.begin( bufs [1] ); + Blip_Reader center; + int shift = center.begin( bufs [2] ); + + int echo_pos = this->echo_pos; + int reverb_pos = this->reverb_pos; + + while ( count-- ) + { + int sum1_s = sq1.read(); + int sum2_s = sq2.read(); + + sq1.next( shift ); + sq2.next( shift ); + + int new_reverb_l = FMUL( sum1_s, chans.pan_1_levels [0] ) + + FMUL( sum2_s, chans.pan_2_levels [0] ) + + reverb_buf [(reverb_pos + chans.reverb_delay_l) & reverb_mask]; + + int new_reverb_r = FMUL( sum1_s, chans.pan_1_levels [1] ) + + FMUL( sum2_s, chans.pan_2_levels [1] ) + + reverb_buf [(reverb_pos + chans.reverb_delay_r) & reverb_mask]; + + fixed_t reverb_level = chans.reverb_level; + reverb_buf [reverb_pos] = FMUL( new_reverb_l, reverb_level ); + reverb_buf [reverb_pos + 1] = FMUL( new_reverb_r, reverb_level ); + reverb_pos = (reverb_pos + 2) & reverb_mask; + + int sum3_s = center.read(); + center.next( shift ); + + int left = new_reverb_l + sum3_s + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_l) & echo_mask] ); + int right = new_reverb_r + sum3_s + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_r) & echo_mask] ); + + echo_buf [echo_pos] = sum3_s; + echo_pos = (echo_pos + 1) & echo_mask; + + if ( (int16_t) left != left ) + left = 0x7FFF - (left >> 24); + + out [0] = left; + out [1] = right; + + out += 2; + + if ( (int16_t) right != right ) + out [-1] = 0x7FFF - (right >> 24); + } + this->reverb_pos = reverb_pos; + this->echo_pos = echo_pos; + + sq1.end( bufs [0] ); + sq2.end( bufs [1] ); + center.end( bufs [2] ); +} + +void Effects_Buffer::mix_enhanced( blip_sample_t* out, long count ) +{ + Blip_Reader l1; l1.begin( bufs [3] ); + Blip_Reader r1; r1.begin( bufs [4] ); + Blip_Reader l2; l2.begin( bufs [5] ); + Blip_Reader r2; r2.begin( bufs [6] ); + Blip_Reader sq1; sq1.begin( bufs [0] ); + Blip_Reader sq2; sq2.begin( bufs [1] ); + Blip_Reader center; + int shift = center.begin( bufs [2] ); + + int echo_pos = this->echo_pos; + int reverb_pos = this->reverb_pos; + + while ( count-- ) + { + int sum1_s = sq1.read(); + int sum2_s = sq2.read(); + + sq1.next( shift ); + sq2.next( shift ); + + int new_reverb_l = FMUL( sum1_s, chans.pan_1_levels [0] ) + + FMUL( sum2_s, chans.pan_2_levels [0] ) + l1.read() + + reverb_buf [(reverb_pos + chans.reverb_delay_l) & reverb_mask]; + + int new_reverb_r = FMUL( sum1_s, chans.pan_1_levels [1] ) + + FMUL( sum2_s, chans.pan_2_levels [1] ) + r1.read() + + reverb_buf [(reverb_pos + chans.reverb_delay_r) & reverb_mask]; + + l1.next( shift ); + r1.next( shift ); + + fixed_t reverb_level = chans.reverb_level; + reverb_buf [reverb_pos] = FMUL( new_reverb_l, reverb_level ); + reverb_buf [reverb_pos + 1] = FMUL( new_reverb_r, reverb_level ); + reverb_pos = (reverb_pos + 2) & reverb_mask; + + int sum3_s = center.read(); + center.next( shift ); + + int left = new_reverb_l + sum3_s + l2.read() + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_l) & echo_mask] ); + int right = new_reverb_r + sum3_s + r2.read() + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_r) & echo_mask] ); + + l2.next( shift ); + r2.next( shift ); + + echo_buf [echo_pos] = sum3_s; + echo_pos = (echo_pos + 1) & echo_mask; + + if ( (int16_t) left != left ) + left = 0x7FFF - (left >> 24); + + out [0] = left; + out [1] = right; + + out += 2; + + if ( (int16_t) right != right ) + out [-1] = 0x7FFF - (right >> 24); + } + this->reverb_pos = reverb_pos; + this->echo_pos = echo_pos; + + sq1.end( bufs [0] ); + sq2.end( bufs [1] ); + center.end( bufs [2] ); + l1.end( bufs [3] ); + r1.end( bufs [4] ); + l2.end( bufs [5] ); + r2.end( bufs [6] ); +} diff --git a/core/Effects_Buffer.h b/core/Effects_Buffer.h new file mode 100644 index 0000000..7ad1ff7 --- /dev/null +++ b/core/Effects_Buffer.h @@ -0,0 +1,93 @@ + +// Multi-channel effects buffer with panning, echo and reverb + +// Game_Music_Emu 0.3.0 + +#ifndef EFFECTS_BUFFER_H +#define EFFECTS_BUFFER_H + +#include +#include "Multi_Buffer.h" + +// Effects_Buffer uses several buffers and outputs stereo sample pairs. +class Effects_Buffer : public Multi_Buffer { +public: + // If center_only is true, only center buffers are created and + // less memory is used. + Effects_Buffer( bool center_only = false ); + + // Channel Effect Center Pan + // --------------------------------- + // 0,5 reverb pan_1 + // 1,6 reverb pan_2 + // 2,7 echo - + // 3 echo - + // 4 echo - + + // Channel configuration + struct config_t { + double pan_1; // -1.0 = left, 0.0 = center, 1.0 = right + double pan_2; + double echo_delay; // msec + double echo_level; // 0.0 to 1.0 + double reverb_delay; // msec + double delay_variance; // difference between left/right delays (msec) + double reverb_level; // 0.0 to 1.0 + bool effects_enabled; // if false, use optimized simple mixer + config_t(); + }; + + // Set configuration of buffer + virtual void config( const config_t& ); + void set_depth( double ); + +public: + ~Effects_Buffer(); + const char *set_sample_rate( long samples_per_sec, int msec = blip_default_length ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int ); + void end_frame( blip_time_t, bool was_stereo = true ); + long read_samples( blip_sample_t*, long ); + long samples_avail() const; +private: + typedef long fixed_t; + + enum { max_buf_count = 7 }; + Blip_Buffer bufs [max_buf_count]; + enum { chan_count = 5 }; + channel_t channels [chan_count]; + config_t config_; + long stereo_remain; + long effect_remain; + int buf_count; + bool effects_enabled; + + blip_sample_t* reverb_buf; + blip_sample_t* echo_buf; + int reverb_pos; + int echo_pos; + + struct { + fixed_t pan_1_levels [2]; + fixed_t pan_2_levels [2]; + int echo_delay_l; + int echo_delay_r; + fixed_t echo_level; + int reverb_delay_l; + int reverb_delay_r; + fixed_t reverb_level; + } chans; + + void mix_mono( blip_sample_t*, long ); + void mix_stereo( blip_sample_t*, long ); + void mix_enhanced( blip_sample_t*, long ); + void mix_mono_enhanced( blip_sample_t*, long ); +}; + + inline Effects_Buffer::channel_t Effects_Buffer::channel( int i ) { + return channels [i % chan_count]; + } + +#endif diff --git a/core/Multi_Buffer.cpp b/core/Multi_Buffer.cpp new file mode 100644 index 0000000..66cc8b7 --- /dev/null +++ b/core/Multi_Buffer.cpp @@ -0,0 +1,282 @@ + +// Blip_Buffer 0.4.0. http://www.slack.net/~ant/ + +#include "Multi_Buffer.h" + +/* Copyright (C) 2003-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +Multi_Buffer::Multi_Buffer( int spf ) : samples_per_frame_( spf ) +{ + length_ = 0; + sample_rate_ = 0; + channels_changed_count_ = 1; + channels_changed_count_save_ = 1; +} + +const char * Multi_Buffer::set_channel_count( int ) +{ + return 0; +} + +void Multi_Buffer::SaveAudioBufferStatePrivate() +{ + channels_changed_count_save_ = channels_changed_count_; +} + +void Multi_Buffer::RestoreAudioBufferStatePrivate() +{ + channels_changed_count_ = channels_changed_count_save_; +} + +Mono_Buffer::Mono_Buffer() : Multi_Buffer( 1 ) +{ +} + +Mono_Buffer::~Mono_Buffer() +{ +} + +const char * Mono_Buffer::set_sample_rate( long rate, int msec ) +{ + RETURN_ERR( buf.set_sample_rate( rate, msec ) ); + return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() ); +} + +void Mono_Buffer::SaveAudioBufferState() +{ + SaveAudioBufferStatePrivate(); + center()->SaveAudioBufferState(); +} + +void Mono_Buffer::RestoreAudioBufferState() +{ + RestoreAudioBufferStatePrivate(); + center()->RestoreAudioBufferState(); +} + +// Silent_Buffer + +Silent_Buffer::Silent_Buffer() : Multi_Buffer( 1 ) // 0 channels would probably confuse +{ + chan.left = NULL; + chan.center = NULL; + chan.right = NULL; +} + +void Silent_Buffer::SaveAudioBufferState() +{ + SaveAudioBufferStatePrivate(); +} + +void Silent_Buffer::RestoreAudioBufferState() +{ + RestoreAudioBufferStatePrivate(); +} + +// Mono_Buffer + +Mono_Buffer::channel_t Mono_Buffer::channel( int ) +{ + channel_t ch; + ch.center = &buf; + ch.left = &buf; + ch.right = &buf; + return ch; +} + +void Mono_Buffer::end_frame( blip_time_t t, bool ) +{ + buf.end_frame( t ); +} + +// Stereo_Buffer + +Stereo_Buffer::Stereo_Buffer() : Multi_Buffer( 2 ) +{ + chan.center = &bufs [0]; + chan.left = &bufs [1]; + chan.right = &bufs [2]; +} + +Stereo_Buffer::~Stereo_Buffer() +{ +} + +const char * Stereo_Buffer::set_sample_rate( long rate, int msec ) +{ + for ( int i = 0; i < buf_count; i++ ) + RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) ); + return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() ); +} + +void Stereo_Buffer::clock_rate( long rate ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clock_rate( rate ); +} + +void Stereo_Buffer::bass_freq( int bass ) +{ + for ( unsigned i = 0; i < buf_count; i++ ) + bufs [i].bass_freq( bass ); +} + +void Stereo_Buffer::clear() +{ + stereo_added = false; + was_stereo = false; + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clear(); +} + +void Stereo_Buffer::end_frame( blip_time_t clock_count, bool stereo ) +{ + for ( unsigned i = 0; i < buf_count; i++ ) + bufs [i].end_frame( clock_count ); + + stereo_added |= stereo; +} + +long Stereo_Buffer::read_samples( blip_sample_t* out, long count ) +{ + count = (unsigned) count / 2; + + long avail = bufs [0].samples_avail(); + if ( count > avail ) + count = avail; + if ( count ) + { + if ( stereo_added || was_stereo ) + { + mix_stereo( out, count ); + + bufs [0].remove_samples( count ); + bufs [1].remove_samples( count ); + bufs [2].remove_samples( count ); + } + else + { + mix_mono( out, count ); + + bufs [0].remove_samples( count ); + + bufs [1].remove_silence( count ); + bufs [2].remove_silence( count ); + } + + // to do: this might miss opportunities for optimization + if ( !bufs [0].samples_avail() ) { + was_stereo = stereo_added; + stereo_added = false; + } + } + + return count * 2; +} + +void Stereo_Buffer::mix_stereo( blip_sample_t* out, long count ) +{ + Blip_Reader left; + Blip_Reader right; + Blip_Reader center; + + left.begin( bufs [1] ); + right.begin( bufs [2] ); + int bass = center.begin( bufs [0] ); + + if (out != NULL) + { + while ( count-- ) + { + int c = center.read(); + long l = c + left.read(); + long r = c + right.read(); + center.next( bass ); + out [0] = l; + out [1] = r; + out += 2; + + if ( (int16_t) l != l ) + out [-2] = 0x7FFF - (l >> 24); + + left.next( bass ); + right.next( bass ); + + if ( (int16_t) r != r ) + out [-1] = 0x7FFF - (r >> 24); + } + } + else + { + //only run accumulators, do not output any audio + while (count--) + { + center.next(bass); + left.next(bass); + right.next(bass); + } + } + + center.end( bufs [0] ); + right.end( bufs [2] ); + left.end( bufs [1] ); +} + +void Stereo_Buffer::mix_mono( blip_sample_t* out, long count ) +{ + Blip_Reader in; + int bass = in.begin( bufs [0] ); + + if (out != NULL) + { + while ( count-- ) + { + long s = in.read(); + in.next( bass ); + out [0] = s; + out [1] = s; + out += 2; + + if ( (int16_t) s != s ) { + s = 0x7FFF - (s >> 24); + out [-2] = s; + out [-1] = s; + } + } + } + else + { + while (count--) + { + in.next(bass); + } + } + + in.end( bufs [0] ); +} + +void Stereo_Buffer::SaveAudioBufferState() +{ + SaveAudioBufferStatePrivate(); + left()->SaveAudioBufferState(); + center()->SaveAudioBufferState(); + right()->SaveAudioBufferState(); +} +void Stereo_Buffer::RestoreAudioBufferState() +{ + RestoreAudioBufferStatePrivate(); + left()->RestoreAudioBufferState(); + center()->RestoreAudioBufferState(); + right()->RestoreAudioBufferState(); +} diff --git a/core/Multi_Buffer.h b/core/Multi_Buffer.h new file mode 100644 index 0000000..ce4bd5a --- /dev/null +++ b/core/Multi_Buffer.h @@ -0,0 +1,190 @@ + +// Multi-channel sound buffer interface, and basic mono and stereo buffers + +// Blip_Buffer 0.4.0 + +#ifndef MULTI_BUFFER_H +#define MULTI_BUFFER_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +// Interface to one or more Blip_Buffers mapped to one or more channels +// consisting of left, center, and right buffers. +class Multi_Buffer { +public: + Multi_Buffer( int samples_per_frame ); + virtual ~Multi_Buffer() { } + + // Set the number of channels available + virtual const char* set_channel_count( int ); + + // Get indexed channel, from 0 to channel count - 1 + struct channel_t { + Blip_Buffer* center; + Blip_Buffer* left; + Blip_Buffer* right; + }; + virtual channel_t channel( int index ) = 0; + + // See Blip_Buffer.h + virtual const char* set_sample_rate( long rate, int msec = blip_default_length ) = 0; + virtual void clock_rate( long ) = 0; + virtual void bass_freq( int ) = 0; + virtual void clear() = 0; + long sample_rate() const; + + // Length of buffer, in milliseconds + int length() const; + + // See Blip_Buffer.h. For optimal operation, pass false for 'added_stereo' + // if nothing was added to the left and right buffers of any channel for + // this time frame. + virtual void end_frame( blip_time_t, bool added_stereo = true ) = 0; + + // Number of samples per output frame (1 = mono, 2 = stereo) + int samples_per_frame() const; + + // Count of changes to channel configuration. Incremented whenever + // a change is made to any of the Blip_Buffers for any channel. + unsigned channels_changed_count() { return channels_changed_count_; } + + // See Blip_Buffer.h + virtual long read_samples( blip_sample_t*, long ) = 0; + virtual long samples_avail() const = 0; + +protected: + void channels_changed() { channels_changed_count_++; } +private: + // noncopyable + Multi_Buffer( const Multi_Buffer& ); + Multi_Buffer& operator = ( const Multi_Buffer& ); + + unsigned channels_changed_count_; + long sample_rate_; + int length_; + int const samples_per_frame_; + unsigned channels_changed_count_save_; +protected: + void SaveAudioBufferStatePrivate(); + void RestoreAudioBufferStatePrivate(); +public: + virtual void SaveAudioBufferState() = 0; + virtual void RestoreAudioBufferState() = 0; +}; + +// Uses a single buffer and outputs mono samples. +class Mono_Buffer : public Multi_Buffer { + Blip_Buffer buf; +public: + Mono_Buffer(); + ~Mono_Buffer(); + + // Buffer used for all channels + Blip_Buffer* center() { return &buf; } + + // See Multi_Buffer + const char* set_sample_rate( long rate, int msec = blip_default_length ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int ); + void end_frame( blip_time_t, bool unused = true ); + long samples_avail() const; + long read_samples( blip_sample_t*, long ); + + virtual void SaveAudioBufferState(); + virtual void RestoreAudioBufferState(); +}; + +// Uses three buffers (one for center) and outputs stereo sample pairs. +class Stereo_Buffer : public Multi_Buffer { +public: + Stereo_Buffer(); + ~Stereo_Buffer(); + + // Buffers used for all channels + Blip_Buffer* center() { return &bufs [0]; } + Blip_Buffer* left() { return &bufs [1]; } + Blip_Buffer* right() { return &bufs [2]; } + + // See Multi_Buffer + const char* set_sample_rate( long, int msec = blip_default_length ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int index ); + void end_frame( blip_time_t, bool added_stereo = true ); + + long samples_avail() const; + long read_samples( blip_sample_t*, long ); + +private: + enum { buf_count = 3 }; + Blip_Buffer bufs [buf_count]; + channel_t chan; + bool stereo_added; + bool was_stereo; + + void mix_stereo( blip_sample_t*, long ); + void mix_mono( blip_sample_t*, long ); + + virtual void SaveAudioBufferState(); + virtual void RestoreAudioBufferState(); +}; + +// Silent_Buffer generates no samples, useful where no sound is wanted +class Silent_Buffer : public Multi_Buffer { + channel_t chan; +public: + Silent_Buffer(); + + const char* set_sample_rate( long rate, int msec = blip_default_length ); + void clock_rate( long ) { } + void bass_freq( int ) { } + void clear() { } + channel_t channel( int ) { return chan; } + void end_frame( blip_time_t, bool unused = true ) { } + long samples_avail() const { return 0; } + long read_samples( blip_sample_t*, long ) { return 0; } + + virtual void SaveAudioBufferState(); + virtual void RestoreAudioBufferState(); +}; + + +// End of public interface + +inline const char* Multi_Buffer::set_sample_rate( long rate, int msec ) +{ + sample_rate_ = rate; + length_ = msec; + return 0; +} + +inline const char* Silent_Buffer::set_sample_rate( long rate, int msec ) +{ + return Multi_Buffer::set_sample_rate( rate, msec ); +} + +inline int Multi_Buffer::samples_per_frame() const { return samples_per_frame_; } + +inline long Stereo_Buffer::samples_avail() const { return bufs [0].samples_avail() * 2; } + +inline Stereo_Buffer::channel_t Stereo_Buffer::channel( int ) { return chan; } + +inline long Multi_Buffer::sample_rate() const { return sample_rate_; } + +inline int Multi_Buffer::length() const { return length_; } + +inline void Mono_Buffer::clock_rate( long rate ) { buf.clock_rate( rate ); } + +inline void Mono_Buffer::clear() { buf.clear(); } + +inline void Mono_Buffer::bass_freq( int freq ) { buf.bass_freq( freq ); } + +inline long Mono_Buffer::read_samples( blip_sample_t* p, long s ) { return buf.read_samples( p, s ); } + +inline long Mono_Buffer::samples_avail() const { return buf.samples_avail(); } + +#endif diff --git a/core/Nes_Apu.cpp b/core/Nes_Apu.cpp new file mode 100644 index 0000000..ef42a93 --- /dev/null +++ b/core/Nes_Apu.cpp @@ -0,0 +1,368 @@ + +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ + +#include "Nes_Apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +int const amp_range = 15; + +Nes_Apu::Nes_Apu() : + square1( &square_synth ), + square2( &square_synth ) +{ + dmc.apu = this; + dmc.prg_reader = NULL; + irq_notifier_ = NULL; + + oscs [0] = &square1; + oscs [1] = &square2; + oscs [2] = ▵ + oscs [3] = &noise; + oscs [4] = &dmc; + + output( NULL ); + volume( 1.0 ); + reset( false ); +} + +Nes_Apu::~Nes_Apu() +{ +} + +void Nes_Apu::treble_eq( const blip_eq_t& eq ) +{ + square_synth.treble_eq( eq ); + triangle.synth.treble_eq( eq ); + noise.synth.treble_eq( eq ); + dmc.synth.treble_eq( eq ); +} + +void Nes_Apu::enable_nonlinear( double v ) +{ + dmc.nonlinear = true; + square_synth.volume( 1.3 * 0.25751258 / 0.742467605 * 0.25 / amp_range * v ); + + const double tnd = 0.48 / 202 * nonlinear_tnd_gain(); + triangle.synth.volume( 3.0 * tnd ); + noise.synth.volume( 2.0 * tnd ); + dmc.synth.volume( tnd ); + + square1 .last_amp = 0; + square2 .last_amp = 0; + triangle.last_amp = 0; + noise .last_amp = 0; + dmc .last_amp = 0; +} + +void Nes_Apu::volume( double v ) +{ + dmc.nonlinear = false; + square_synth.volume( 0.1128 / amp_range * v ); + triangle.synth.volume( 0.12765 / amp_range * v ); + noise.synth.volume( 0.0741 / amp_range * v ); + dmc.synth.volume( 0.42545 / 127 * v ); +} + +void Nes_Apu::output( Blip_Buffer* buffer ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buffer ); +} + +void Nes_Apu::reset( bool pal_mode, int initial_dmc_dac ) +{ + // to do: time pal frame periods exactly + frame_period = pal_mode ? 8314 : 7458; + dmc.pal_mode = pal_mode; + + square1.reset(); + square2.reset(); + triangle.reset(); + noise.reset(); + dmc.reset(); + + last_time = 0; + last_dmc_time = 0; + osc_enables = 0; + irq_flag = false; + earliest_irq_ = no_irq; + frame_delay = 1; + write_register( 0, 0x4017, 0x00 ); + write_register( 0, 0x4015, 0x00 ); + + for ( nes_addr_t addr = start_addr; addr <= 0x4013; addr++ ) + write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 ); + + dmc.dac = initial_dmc_dac; + if ( !dmc.nonlinear ) + triangle.last_amp = 15; + //if ( !dmc.nonlinear ) // to do: remove? + // dmc.last_amp = initial_dmc_dac; // prevent output transition +} + +void Nes_Apu::irq_changed() +{ + nes_time_t new_irq = dmc.next_irq; + if ( dmc.irq_flag | irq_flag ) { + new_irq = 0; + } + else if ( new_irq > next_irq ) { + new_irq = next_irq; + } + + if ( new_irq != earliest_irq_ ) { + earliest_irq_ = new_irq; + if ( irq_notifier_ ) + irq_notifier_( irq_data ); + } +} + +// frames + +void Nes_Apu::run_until( nes_time_t end_time ) +{ + if ( end_time > next_dmc_read_time() ) + { + nes_time_t start = last_dmc_time; + last_dmc_time = end_time; + dmc.run( start, end_time ); + } +} + +void Nes_Apu::run_until_( nes_time_t end_time ) +{ + if ( end_time == last_time ) + return; + + if ( last_dmc_time < end_time ) + { + nes_time_t start = last_dmc_time; + last_dmc_time = end_time; + dmc.run( start, end_time ); + } + + while ( true ) + { + // earlier of next frame time or end time + nes_time_t time = last_time + frame_delay; + if ( time > end_time ) + time = end_time; + frame_delay -= time - last_time; + + // run oscs to present + square1.run( last_time, time ); + square2.run( last_time, time ); + triangle.run( last_time, time ); + noise.run( last_time, time ); + last_time = time; + + if ( time == end_time ) + break; // no more frames to run + + // take frame-specific actions + frame_delay = frame_period; + switch ( frame++ ) + { + case 0: + if ( !(frame_mode & 0xc0) ) { + next_irq = time + frame_period * 4 + 1; + irq_flag = true; + } + // fall through + case 2: + // clock length and sweep on frames 0 and 2 + square1.clock_length( 0x20 ); + square2.clock_length( 0x20 ); + noise.clock_length( 0x20 ); + triangle.clock_length( 0x80 ); // different bit for halt flag on triangle + + square1.clock_sweep( -1 ); + square2.clock_sweep( 0 ); + break; + + case 1: + // frame 1 is slightly shorter + frame_delay -= 2; + break; + + case 3: + frame = 0; + + // frame 3 is almost twice as long in mode 1 + if ( frame_mode & 0x80 ) + frame_delay += frame_period - 6; + break; + } + + // clock envelopes and linear counter every frame + triangle.clock_linear_counter(); + square1.clock_envelope(); + square2.clock_envelope(); + noise.clock_envelope(); + } +} + +template +inline void zero_apu_osc( T* osc, nes_time_t time ) +{ + Blip_Buffer* output = osc->output; + int last_amp = osc->last_amp; + osc->last_amp = 0; + if ( output && last_amp ) + osc->synth.offset( time, -last_amp, output ); +} + +void Nes_Apu::end_frame( nes_time_t end_time ) +{ + if ( end_time > last_time ) + run_until_( end_time ); + + if ( dmc.nonlinear ) + { + zero_apu_osc( &square1, last_time ); + zero_apu_osc( &square2, last_time ); + zero_apu_osc( &triangle, last_time ); + zero_apu_osc( &noise, last_time ); + zero_apu_osc( &dmc, last_time ); + } + + // make times relative to new frame + last_time -= end_time; + last_dmc_time -= end_time; + + if ( next_irq != no_irq ) { + next_irq -= end_time; + } + if ( dmc.next_irq != no_irq ) { + dmc.next_irq -= end_time; + } + if ( earliest_irq_ != no_irq ) { + earliest_irq_ -= end_time; + if ( earliest_irq_ < 0 ) + earliest_irq_ = 0; + } +} + +// registers + +static const unsigned char length_table [0x20] = { + 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, + 0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E, + 0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, + 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E +}; + +void Nes_Apu::write_register( nes_time_t time, nes_addr_t addr, int data ) +{ + // Ignore addresses outside range + if ( addr < start_addr || end_addr < addr ) + return; + + run_until_( time ); + + if ( addr < 0x4014 ) + { + // Write to channel + int osc_index = (addr - start_addr) >> 2; + Nes_Osc* osc = oscs [osc_index]; + + int reg = addr & 3; + osc->regs [reg] = data; + osc->reg_written [reg] = true; + + if ( osc_index == 4 ) + { + // handle DMC specially + dmc.write_register( reg, data ); + } + else if ( reg == 3 ) + { + // load length counter + if ( (osc_enables >> osc_index) & 1 ) + osc->length_counter = length_table [(data >> 3) & 0x1f]; + + // reset square phase + if ( osc_index < 2 ) + ((Nes_Square*) osc)->phase = Nes_Square::phase_range - 1; + } + } + else if ( addr == 0x4015 ) + { + // Channel enables + for ( int i = osc_count; i--; ) + if ( !((data >> i) & 1) ) + oscs [i]->length_counter = 0; + + bool recalc_irq = dmc.irq_flag; + dmc.irq_flag = false; + + int old_enables = osc_enables; + osc_enables = data; + if ( !(data & 0x10) ) { + dmc.next_irq = no_irq; + recalc_irq = true; + } + else if ( !(old_enables & 0x10) ) { + dmc.start(); // dmc just enabled + } + + if ( recalc_irq ) + irq_changed(); + } + else if ( addr == 0x4017 ) + { + // Frame mode + frame_mode = data; + + bool irq_enabled = !(data & 0x40); + irq_flag &= irq_enabled; + next_irq = no_irq; + + // mode 1 + frame_delay = (frame_delay & 1); + frame = 0; + + if ( !(data & 0x80) ) + { + // mode 0 + frame = 1; + frame_delay += frame_period; + if ( irq_enabled ) + next_irq = time + frame_delay + frame_period * 3; + } + + irq_changed(); + } +} + +int Nes_Apu::read_status( nes_time_t time ) +{ + run_until_( time - 1 ); + + int result = (dmc.irq_flag << 7) | (irq_flag << 6); + + for ( int i = 0; i < osc_count; i++ ) + if ( oscs [i]->length_counter ) + result |= 1 << i; + + run_until_( time ); + + if ( irq_flag ) { + irq_flag = false; + irq_changed(); + } + + return result; +} diff --git a/core/Nes_Apu.h b/core/Nes_Apu.h new file mode 100644 index 0000000..6a7ea67 --- /dev/null +++ b/core/Nes_Apu.h @@ -0,0 +1,175 @@ + +// NES 2A03 APU sound chip emulator + +// Nes_Snd_Emu 0.1.7 + +#ifndef NES_APU_H +#define NES_APU_H + +typedef long nes_time_t; // CPU clock cycle count +typedef unsigned nes_addr_t; // 16-bit memory address + +#include "Nes_Oscs.h" + +struct apu_state_t; +class Nes_Buffer; + +class Nes_Apu { +public: + Nes_Apu(); + ~Nes_Apu(); + + // Set buffer to generate all sound into, or disable sound if NULL + void output( Blip_Buffer* ); + + // Set memory reader callback used by DMC oscillator to fetch samples. + // When callback is invoked, 'user_data' is passed unchanged as the + // first parameter. + void dmc_reader( int (*callback)( void* user_data, nes_addr_t ), void* user_data = NULL ); + + // All time values are the number of CPU clock cycles relative to the + // beginning of the current time frame. Before resetting the CPU clock + // count, call end_frame( last_cpu_time ). + + // Write to register (0x4000-0x4017, except 0x4014 and 0x4016) + enum { start_addr = 0x4000 }; + enum { end_addr = 0x4017 }; + void write_register( nes_time_t, nes_addr_t, int data ); + + // Read from status register at 0x4015 + enum { status_addr = 0x4015 }; + int read_status( nes_time_t ); + + // Run all oscillators up to specified time, end current time frame, then + // start a new time frame at time 0. Time frames have no effect on emulation + // and each can be whatever length is convenient. + void end_frame( nes_time_t ); + +// Additional optional features (can be ignored without any problem) + + // Reset internal frame counter, registers, and all oscillators. + // Use PAL timing if pal_timing is true, otherwise use NTSC timing. + // Set the DMC oscillator's initial DAC value to initial_dmc_dac without + // any audible click. + void reset( bool pal_timing = false, int initial_dmc_dac = 0 ); + + // Save/load exact emulation state + void save_state( apu_state_t* out ) const; + void load_state( apu_state_t const& ); + + // Set overall volume (default is 1.0) + void volume( double ); + + // Set treble equalization (see notes.txt) + void treble_eq( const blip_eq_t& ); + + // Set sound output of specific oscillator to buffer. If buffer is NULL, + // the specified oscillator is muted and emulation accuracy is reduced. + // The oscillators are indexed as follows: 0) Square 1, 1) Square 2, + // 2) Triangle, 3) Noise, 4) DMC. + enum { osc_count = 5 }; + void osc_output( int index, Blip_Buffer* buffer ); + + // Set IRQ time callback that is invoked when the time of earliest IRQ + // may have changed, or NULL to disable. When callback is invoked, + // 'user_data' is passed unchanged as the first parameter. + void irq_notifier( void (*callback)( void* user_data ), void* user_data = NULL ); + + // Get time that APU-generated IRQ will occur if no further register reads + // or writes occur. If IRQ is already pending, returns irq_waiting. If no + // IRQ will occur, returns no_irq. + enum { no_irq = LONG_MAX / 2 + 1 }; + enum { irq_waiting = 0 }; + nes_time_t earliest_irq( nes_time_t ) const; + + // Count number of DMC reads that would occur if 'run_until( t )' were executed. + // If last_read is not NULL, set *last_read to the earliest time that + // 'count_dmc_reads( time )' would result in the same result. + int count_dmc_reads( nes_time_t t, nes_time_t* last_read = NULL ) const; + + // Time when next DMC memory read will occur + nes_time_t next_dmc_read_time() const; + + // Run DMC until specified time, so that any DMC memory reads can be + // accounted for (i.e. inserting CPU wait states). + void run_until( nes_time_t ); + +// End of public interface. +private: + friend class Nes_Nonlinearizer; + void enable_nonlinear( double volume ); + static double nonlinear_tnd_gain() { return 0.75; } +private: + friend struct Nes_Dmc; + + // noncopyable + Nes_Apu( const Nes_Apu& ); + Nes_Apu& operator = ( const Nes_Apu& ); + + Nes_Osc* oscs [osc_count]; + Nes_Square square1; + Nes_Square square2; + Nes_Noise noise; + Nes_Triangle triangle; + Nes_Dmc dmc; + + nes_time_t last_time; // has been run until this time in current frame + nes_time_t last_dmc_time; + nes_time_t earliest_irq_; + nes_time_t next_irq; + int frame_period; + int frame_delay; // cycles until frame counter runs next + int frame; // current frame (0-3) + int osc_enables; + int frame_mode; + bool irq_flag; + void (*irq_notifier_)( void* user_data ); + void* irq_data; + Nes_Square::Synth square_synth; // shared by squares + + void irq_changed(); + void state_restored(); + void run_until_( nes_time_t ); + + // TODO: remove + friend class Nes_Core; +}; + +inline void Nes_Apu::osc_output( int osc, Blip_Buffer* buf ) +{ + oscs [osc]->output = buf; +} + +inline nes_time_t Nes_Apu::earliest_irq( nes_time_t ) const +{ + return earliest_irq_; +} + +inline void Nes_Apu::dmc_reader( int (*func)( void*, nes_addr_t ), void* user_data ) +{ + dmc.prg_reader_data = user_data; + dmc.prg_reader = func; +} + +inline void Nes_Apu::irq_notifier( void (*func)( void* user_data ), void* user_data ) +{ + irq_notifier_ = func; + irq_data = user_data; +} + +inline int Nes_Apu::count_dmc_reads( nes_time_t time, nes_time_t* last_read ) const +{ + return dmc.count_reads( time, last_read ); +} + +inline nes_time_t Nes_Dmc::next_read_time() const +{ + if ( length_counter == 0 ) + return Nes_Apu::no_irq; // not reading + + return apu->last_dmc_time + delay + long (bits_remain - 1) * period; +} + +inline nes_time_t Nes_Apu::next_dmc_read_time() const { return dmc.next_read_time(); } + +#endif diff --git a/core/Nes_Buffer.cpp b/core/Nes_Buffer.cpp new file mode 100644 index 0000000..731e05a --- /dev/null +++ b/core/Nes_Buffer.cpp @@ -0,0 +1,240 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/libs/ + +#include "Nes_Buffer.h" + +#include "Nes_Apu.h" + +/* Library Copyright (C) 2003-2006 Shay Green. 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 +module 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +// Nes_Buffer + +Nes_Buffer::Nes_Buffer() : Multi_Buffer( 1 ) { } + +Nes_Buffer::~Nes_Buffer() { } + +Multi_Buffer* set_apu( Nes_Buffer* buf, Nes_Apu* apu ) +{ + buf->set_apu( apu ); + return buf; +} + +void Nes_Buffer::enable_nonlinearity( bool b ) +{ + if ( b ) + clear(); + + Nes_Apu* apu = nonlin.enable( b, &tnd ); + apu->osc_output( 0, &buf ); + apu->osc_output( 1, &buf ); +} + +const char * Nes_Buffer::set_sample_rate( long rate, int msec ) +{ + enable_nonlinearity( nonlin.enabled ); // reapply + RETURN_ERR( buf.set_sample_rate( rate, msec ) ); + RETURN_ERR( tnd.set_sample_rate( rate, msec ) ); + return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() ); +} + +void Nes_Buffer::clock_rate( long rate ) +{ + buf.clock_rate( rate ); + tnd.clock_rate( rate ); +} + +void Nes_Buffer::bass_freq( int freq ) +{ + buf.bass_freq( freq ); + tnd.bass_freq( freq ); +} + +void Nes_Buffer::clear() +{ + nonlin.clear(); + buf.clear(); + tnd.clear(); +} + +Nes_Buffer::channel_t Nes_Buffer::channel( int i ) +{ + channel_t c; + c.center = &buf; + if ( 2 <= i && i <= 4 ) + c.center = &tnd; // only use for triangle, noise, and dmc + c.left = c.center; + c.right = c.center; + return c; +} + +void Nes_Buffer::end_frame( blip_time_t length, bool ) +{ + buf.end_frame( length ); + tnd.end_frame( length ); +} + +long Nes_Buffer::samples_avail() const +{ + return buf.samples_avail(); +} + +long Nes_Buffer::read_samples( blip_sample_t* out, long count ) +{ + count = nonlin.make_nonlinear( tnd, count ); + if ( count ) + { + Blip_Reader lin; + Blip_Reader nonlin; + + int lin_bass = lin.begin( buf ); + int nonlin_bass = nonlin.begin( tnd ); + + if (out != NULL) + { + for ( int n = count; n--; ) + { + int s = lin.read() + nonlin.read(); + lin.next( lin_bass ); + nonlin.next( nonlin_bass ); + *out++ = s; + + if ( (int16_t) s != s ) + out [-1] = 0x7FFF - (s >> 24); + } + } + else + { + //only run accumulators, do not output audio + for (int n = count; n--; ) + { + lin.next(lin_bass); + nonlin.next(nonlin_bass); + } + } + + lin.end( buf ); + nonlin.end( tnd ); + + buf.remove_samples( count ); + tnd.remove_samples( count ); + } + + return count; +} + +void Nes_Buffer::SaveAudioBufferState() +{ + SaveAudioBufferStatePrivate(); + nonlin.SaveAudioBufferState(); + buf.SaveAudioBufferState(); + tnd.SaveAudioBufferState(); +} + +void Nes_Buffer::RestoreAudioBufferState() +{ + RestoreAudioBufferStatePrivate(); + nonlin.RestoreAudioBufferState(); + buf.RestoreAudioBufferState(); + tnd.RestoreAudioBufferState(); +} + +// Nes_Nonlinearizer + +Nes_Nonlinearizer::Nes_Nonlinearizer() +{ + apu = NULL; + enabled = true; + + float const gain = 0x7fff * 1.3f; + // don't use entire range, so any overflow will stay within table + int const range = (int) (table_size * Nes_Apu::nonlinear_tnd_gain()); + for ( int i = 0; i < table_size; i++ ) + { + int const offset = table_size - range; + int j = i - offset; + float n = 202.0f / (range - 1) * j; + float d = 0; + // Prevent division by zero + if ( n ) + d = gain * 163.67f / (24329.0f / n + 100.0f); + int out = (int) d; + table [j & (table_size - 1)] = out; + } + extra_accum = 0; + extra_prev = 0; +} + +Nes_Apu* Nes_Nonlinearizer::enable( bool b, Blip_Buffer* buf ) +{ + apu->osc_output( 2, buf ); + apu->osc_output( 3, buf ); + apu->osc_output( 4, buf ); + enabled = b; + if ( b ) + apu->enable_nonlinear( 1.0 ); + else + apu->volume( 1.0 ); + return apu; +} + +#define ENTRY( s ) table [(s) >> (blip_sample_bits - table_bits - 1) & (table_size - 1)] + +long Nes_Nonlinearizer::make_nonlinear( Blip_Buffer& buf, long count ) +{ + long avail = buf.samples_avail(); + if ( count > avail ) + count = avail; + if ( count && enabled ) + { + + Blip_Buffer::buf_t_* p = buf.buffer_; + long accum = this->accum; + long prev = this->prev; + for ( unsigned n = count; n; --n ) + { + long entry = ENTRY( accum ); + accum += *p; + *p++ = (entry - prev) << (blip_sample_bits - 16); + prev = entry; + } + + this->prev = prev; + this->accum = accum; + } + + return count; +} + +void Nes_Nonlinearizer::clear() +{ + accum = 0; + prev = ENTRY( 86016000 ); // avoid thump due to APU's triangle dc bias + // TODO: still results in slight clicks and thumps +} + +void Nes_Nonlinearizer::SaveAudioBufferState() +{ + extra_accum = accum; + extra_prev = prev; +} + +void Nes_Nonlinearizer::RestoreAudioBufferState() +{ + accum = extra_accum; + prev = extra_prev; +} diff --git a/core/Nes_Buffer.h b/core/Nes_Buffer.h new file mode 100644 index 0000000..6d70fe4 --- /dev/null +++ b/core/Nes_Buffer.h @@ -0,0 +1,70 @@ + +// NES non-linear audio buffer + +// Nes_Emu 0.7.0 + +#ifndef NES_BUFFER_H +#define NES_BUFFER_H + +#include "Multi_Buffer.h" +class Nes_Apu; + +class Nes_Nonlinearizer { +private: + enum { table_bits = 11 }; + enum { table_size = 1 << table_bits }; + int16_t table [table_size]; + Nes_Apu* apu; + long accum; + long prev; + + long extra_accum; + long extra_prev; +public: + Nes_Nonlinearizer(); + bool enabled; + void clear(); + void set_apu( Nes_Apu* a ) { apu = a; } + Nes_Apu* enable( bool, Blip_Buffer* tnd ); + long make_nonlinear( Blip_Buffer& buf, long count ); + void SaveAudioBufferState(); + void RestoreAudioBufferState(); +}; + +class Nes_Buffer : public Multi_Buffer { +public: + Nes_Buffer(); + ~Nes_Buffer(); + + // Setup APU for use with buffer, including setting its output to this buffer. + // If you're using Nes_Emu, this is automatically called for you. + void set_apu( Nes_Apu* apu ) { nonlin.set_apu( apu ); } + + // Enable/disable non-linear output + void enable_nonlinearity( bool = true ); + + // Blip_Buffer to output other sound chips to + Blip_Buffer* buffer() { return &buf; } + + // See Multi_Buffer.h + const char *set_sample_rate( long rate, int msec = blip_default_length ); + + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int ); + void end_frame( blip_time_t, bool unused = true ); + long samples_avail() const; + long read_samples( blip_sample_t*, long ); + +private: + Blip_Buffer buf; + Blip_Buffer tnd; + Nes_Nonlinearizer nonlin; + friend Multi_Buffer* set_apu( Nes_Buffer*, Nes_Apu* ); +public: + virtual void SaveAudioBufferState(); + virtual void RestoreAudioBufferState(); +}; + +#endif diff --git a/core/Nes_Cart.cpp b/core/Nes_Cart.cpp new file mode 100644 index 0000000..5a1ccde --- /dev/null +++ b/core/Nes_Cart.cpp @@ -0,0 +1,120 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Cart.h" + +#include +#include + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +char const Nes_Cart::not_ines_file [] = "Not an iNES file"; + +Nes_Cart::Nes_Cart() +{ + prg_ = NULL; + chr_ = NULL; + clear(); +} + +Nes_Cart::~Nes_Cart() +{ + clear(); +} + +void Nes_Cart::clear() +{ + if ( prg_ ) + free( prg_ ); + prg_ = NULL; + + if ( chr_ ) + free( chr_ ); + chr_ = NULL; + + prg_size_ = 0; + chr_size_ = 0; + mapper = 0; +} + +long Nes_Cart::round_to_bank_size( long n ) +{ + n += bank_size - 1; + return n - n % bank_size; +} + +const char * Nes_Cart::resize_prg( long size ) +{ + if ( size != prg_size_ ) + { + // padding allows CPU to always read operands of instruction, which + // might go past end of data + void* p = realloc( prg_, round_to_bank_size( size ) + 2 ); + CHECK_ALLOC( p || !size ); + prg_ = (uint8_t*) p; + prg_size_ = size; + } + return 0; +} + +const char * Nes_Cart::resize_chr( long size ) +{ + if ( size != chr_size_ ) + { + void* p = realloc( chr_, round_to_bank_size( size ) ); + CHECK_ALLOC( p || !size ); + chr_ = (uint8_t*) p; + chr_size_ = size; + } + return 0; +} + +// iNES reading + +struct ines_header_t { + uint8_t signature [4]; + uint8_t prg_count; // number of 16K PRG banks + uint8_t chr_count; // number of 8K CHR banks + uint8_t flags; // MMMM FTBV Mapper low, Four-screen, Trainer, Battery, V mirror + uint8_t flags2; // MMMM --XX Mapper high 4 bits + uint8_t zero [8]; // if zero [7] is non-zero, treat flags2 as zero +}; +BOOST_STATIC_ASSERT( sizeof (ines_header_t) == 16 ); + +const char * Nes_Cart::load_ines( Auto_File_Reader in ) +{ + RETURN_ERR( in.open() ); + + ines_header_t h; + RETURN_ERR( in->read( &h, sizeof h ) ); + + if ( 0 != memcmp( h.signature, "NES\x1A", 4 ) ) + return not_ines_file; + + if ( h.zero [7] ) // handle header defaced by a fucking idiot's handle + h.flags2 = 0; + + set_mapper( h.flags, h.flags2 ); + + if ( h.flags & 0x04 ) // skip trainer + RETURN_ERR( in->skip( 512 ) ); + + RETURN_ERR( resize_prg( h.prg_count * 16 * 1024L ) ); + RETURN_ERR( resize_chr( h.chr_count * 8 * 1024L ) ); + + RETURN_ERR( in->read( prg(), prg_size() ) ); + RETURN_ERR( in->read( chr(), chr_size() ) ); + + return 0; +} diff --git a/core/Nes_Cart.h b/core/Nes_Cart.h new file mode 100644 index 0000000..18417ca --- /dev/null +++ b/core/Nes_Cart.h @@ -0,0 +1,85 @@ + +// NES cartridge data (PRG, CHR, mapper) + +// Nes_Emu 0.7.0 + +#ifndef NES_CART_H +#define NES_CART_H + +#include +#include "blargg_common.h" +#include "abstract_file.h" + +class Nes_Cart { +public: + Nes_Cart(); + ~Nes_Cart(); + + // Load iNES file + const char * load_ines( Auto_File_Reader ); + static const char not_ines_file []; + + // to do: support UNIF? + + // True if data is currently loaded + bool loaded() const { return prg_ != NULL; } + + // Free data + void clear(); + + // True if cartridge claims to have battery-backed memory + bool has_battery_ram() const; + + // Size of PRG data + long prg_size() const { return prg_size_; } + + // Size of CHR data + long chr_size() const { return chr_size_; } + + // Change size of PRG (code) data + const char * resize_prg( long ); + + // Change size of CHR (graphics) data + const char * resize_chr( long ); + + // Set mapper and information bytes. LSB and MSB are the standard iNES header + // bytes at offsets 6 and 7. + void set_mapper( int mapper_lsb, int mapper_msb ); + + unsigned mapper_data() const { return mapper; } + + // Initial mirroring setup + int mirroring() const { return mapper & 0x09; } + + // iNES mapper code + int mapper_code() const; + + // Pointer to beginning of PRG data + uint8_t * prg() { return prg_; } + uint8_t const* prg() const { return prg_; } + + // Pointer to beginning of CHR data + uint8_t * chr() { return chr_; } + uint8_t const* chr() const { return chr_; } + + // End of public interface +private: + enum { bank_size = 8 * 1024L }; // bank sizes must be a multiple of this + uint8_t *prg_; + uint8_t *chr_; + long prg_size_; + long chr_size_; + unsigned mapper; + long round_to_bank_size( long n ); +}; + +inline bool Nes_Cart::has_battery_ram() const { return mapper & 0x02; } + +inline void Nes_Cart::set_mapper( int mapper_lsb, int mapper_msb ) +{ + mapper = mapper_msb * 0x100 + mapper_lsb; +} + +inline int Nes_Cart::mapper_code() const { return ((mapper >> 8) & 0xf0) | ((mapper >> 4) & 0x0f); } + +#endif diff --git a/core/Nes_Core.cpp b/core/Nes_Core.cpp new file mode 100644 index 0000000..7273118 --- /dev/null +++ b/core/Nes_Core.cpp @@ -0,0 +1,537 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Core.h" + +#include +#include "Nes_Mapper.h" +#include "Nes_State.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +extern const char unsupported_mapper [] = "Unsupported mapper"; + +bool const wait_states_enabled = true; +bool const single_instruction_mode = false; // for debugging irq/nmi timing issues + +const int unmapped_fill = Nes_Cpu::page_wrap_opcode; + +unsigned const low_ram_size = 0x800; +unsigned const low_ram_end = 0x2000; +unsigned const sram_end = 0x8000; + +Nes_Core::Nes_Core() : ppu( this ) +{ + cart = NULL; + impl = NULL; + mapper = NULL; + memset( &nes, 0, sizeof nes ); + memset( &joypad, 0, sizeof joypad ); +} + +const char * Nes_Core::init() +{ + if ( !impl ) + { + CHECK_ALLOC( impl = new impl_t ); + impl->apu.dmc_reader( read_dmc, this ); + impl->apu.irq_notifier( apu_irq_changed, this ); + } + + return 0; +} + +void Nes_Core::close() +{ + cart = NULL; + delete mapper; + mapper = NULL; + + ppu.close_chr(); + + disable_rendering(); +} + +const char * Nes_Core::open( Nes_Cart const* new_cart ) +{ + close(); + + RETURN_ERR( init() ); + + mapper = Nes_Mapper::create( new_cart, this ); + if ( !mapper ) + return unsupported_mapper; + + RETURN_ERR( ppu.open_chr( new_cart->chr(), new_cart->chr_size() ) ); + + cart = new_cart; + memset( impl->unmapped_page, unmapped_fill, sizeof impl->unmapped_page ); + reset( true, true ); + + return 0; +} + +Nes_Core::~Nes_Core() +{ + close(); + delete impl; +} + +void Nes_Core::save_state( Nes_State_* out ) const +{ + out->clear(); + + out->nes = nes; + out->nes_valid = true; + + *out->cpu = cpu::r; + out->cpu_valid = true; + + *out->joypad = joypad; + out->joypad_valid = true; + + impl->apu.save_state( out->apu ); + out->apu_valid = true; + + ppu.save_state( out ); + + memcpy( out->ram, cpu::low_mem, out->ram_size ); + out->ram_valid = true; + + out->sram_size = 0; + if ( sram_present ) + { + out->sram_size = sizeof impl->sram; + memcpy( out->sram, impl->sram, out->sram_size ); + } + + out->mapper->size = 0; + mapper->save_state( *out->mapper ); + out->mapper_valid = true; +} + +void Nes_Core::save_state( Nes_State* out ) const +{ + save_state( reinterpret_cast(out) ); +} + +void Nes_Core::load_state( Nes_State_ const& in ) +{ + disable_rendering(); + error_count = 0; + + if ( in.nes_valid ) + nes = in.nes; + + // always use frame count + ppu.burst_phase = 0; // avoids shimmer when seeking to same time over and over + nes.frame_count = in.nes.frame_count; + if ( (frame_count_t) nes.frame_count == invalid_frame_count ) + nes.frame_count = 0; + + if ( in.cpu_valid ) + cpu::r = *in.cpu; + + if ( in.joypad_valid ) + joypad = *in.joypad; + + if ( in.apu_valid ) + { + impl->apu.load_state( *in.apu ); + // prevent apu from running extra at beginning of frame + impl->apu.end_frame( -(int) nes.timestamp / ppu_overclock ); + } + else + { + impl->apu.reset(); + } + + ppu.load_state( in ); + + if ( in.ram_valid ) + memcpy( cpu::low_mem, in.ram, in.ram_size ); + + sram_present = false; + if ( in.sram_size ) + { + sram_present = true; + memcpy( impl->sram, in.sram, min( (int) in.sram_size, (int) sizeof impl->sram ) ); + enable_sram( true ); // mapper can override (read-only, unmapped, etc.) + } + + if ( in.mapper_valid ) // restore last since it might reconfigure things + mapper->load_state( *in.mapper ); +} + +void Nes_Core::enable_prg_6000() +{ + sram_writable = 0; + sram_readable = 0; + lrom_readable = 0x8000; +} + +void Nes_Core::enable_sram( bool b, bool read_only ) +{ + sram_writable = 0; + if ( b ) + { + if ( !sram_present ) + { + sram_present = true; + memset( impl->sram, 0xFF, impl->sram_size ); + } + sram_readable = sram_end; + if ( !read_only ) + sram_writable = sram_end; + cpu::map_code( 0x6000, impl->sram_size, impl->sram ); + } + else + { + sram_readable = 0; + for ( int i = 0; i < impl->sram_size; i += cpu::page_size ) + cpu::map_code( 0x6000 + i, cpu::page_size, impl->unmapped_page ); + } +} + +// Unmapped memory + +#ifndef NDEBUG +static nes_addr_t last_unmapped_addr; +#endif + +void Nes_Core::log_unmapped( nes_addr_t addr, int data ) +{ +} + +inline void Nes_Core::cpu_adjust_time( int n ) +{ + ppu_2002_time -= n; + cpu_time_offset += n; + cpu::reduce_limit( n ); +} + +// I/O and sound + +int Nes_Core::read_dmc( void* data, nes_addr_t addr ) +{ + Nes_Core* emu = (Nes_Core*) data; + int result = *emu->cpu::get_code( addr ); + if ( wait_states_enabled ) + emu->cpu_adjust_time( 4 ); + return result; +} + +void Nes_Core::apu_irq_changed( void* emu ) +{ + ((Nes_Core*) emu)->irq_changed(); +} + +void Nes_Core::write_io( nes_addr_t addr, int data ) +{ + // sprite dma + if ( addr == 0x4014 ) + { + ppu.dma_sprites( clock(), cpu::get_code( data * 0x100 ) ); + cpu_adjust_time( 513 ); + return; + } + + // joypad strobe + if ( addr == 0x4016 ) + { + // if strobe goes low, latch data + if ( joypad.w4016 & 1 & ~data ) + { + joypad_read_count++; + joypad.joypad_latches [0] = current_joypad [0]; + joypad.joypad_latches [1] = current_joypad [1]; + } + joypad.w4016 = data; + return; + } + + // apu + if ( unsigned (addr - impl->apu.start_addr) <= impl->apu.end_addr - impl->apu.start_addr ) + { + impl->apu.write_register( clock(), addr, data ); + if ( wait_states_enabled ) + { + if ( addr == 0x4010 || (addr == 0x4015 && (data & 0x10)) ) + { + impl->apu.run_until( clock() + 1 ); + event_changed(); + } + } + return; + } + + #ifndef NDEBUG + log_unmapped( addr, data ); + #endif +} + +int Nes_Core::read_io( nes_addr_t addr ) +{ + if ( (addr & 0xFFFE) == 0x4016 ) + { + // to do: to aid with recording, doesn't emulate transparent latch, + // so a game that held strobe at 1 and read $4016 or $4017 would not get + // the current A status as occurs on a NES + unsigned long result = joypad.joypad_latches [addr & 1]; + if ( !(joypad.w4016 & 1) ) + joypad.joypad_latches [addr & 1] = (result >> 1) | 0x80000000; + return result & 1; + } + + if ( addr == Nes_Apu::status_addr ) + return impl->apu.read_status( clock() ); + + #ifndef NDEBUG + log_unmapped( addr ); + #endif + + return addr >> 8; // simulate open bus +} + +// CPU + +const int irq_inhibit_mask = 0x04; + +nes_addr_t Nes_Core::read_vector( nes_addr_t addr ) +{ + uint8_t const* p = cpu::get_code( addr ); + return p [1] * 0x100 + p [0]; +} + +void Nes_Core::reset( bool full_reset, bool erase_battery_ram ) +{ + if ( full_reset ) + { + cpu::reset( impl->unmapped_page ); + cpu_time_offset = -1; + clock_ = 0; + + // Low RAM + memset( cpu::low_mem, 0xFF, low_ram_size ); + cpu::low_mem [8] = 0xf7; + cpu::low_mem [9] = 0xef; + cpu::low_mem [10] = 0xdf; + cpu::low_mem [15] = 0xbf; + + // SRAM + lrom_readable = 0; + sram_present = true; + enable_sram( false ); + if ( !cart->has_battery_ram() || erase_battery_ram ) + memset( impl->sram, 0xFF, impl->sram_size ); + + joypad.joypad_latches [0] = 0; + joypad.joypad_latches [1] = 0; + + nes.frame_count = 0; + } + + // to do: emulate partial reset + + ppu.reset( full_reset ); + impl->apu.reset(); + + mapper->reset(); + + cpu::r.pc = read_vector( 0xFFFC ); + cpu::r.sp = 0xfd; + cpu::r.a = 0; + cpu::r.x = 0; + cpu::r.y = 0; + cpu::r.status = irq_inhibit_mask; + nes.timestamp = 0; + error_count = 0; +} + +void Nes_Core::vector_interrupt( nes_addr_t vector ) +{ + cpu::push_byte( cpu::r.pc >> 8 ); + cpu::push_byte( cpu::r.pc & 0xFF ); + cpu::push_byte( cpu::r.status | 0x20 ); // reserved bit is set + + cpu_adjust_time( 7 ); + cpu::r.status |= irq_inhibit_mask; + cpu::r.pc = read_vector( vector ); +} + +inline nes_time_t Nes_Core::earliest_irq( nes_time_t present ) +{ + return min( impl->apu.earliest_irq( present ), mapper->next_irq( present ) ); +} + +void Nes_Core::irq_changed() +{ + cpu_set_irq_time( earliest_irq( cpu_time() ) ); +} + +inline nes_time_t Nes_Core::ppu_frame_length( nes_time_t present ) +{ + nes_time_t t = ppu.frame_length(); + if ( t > present ) + return t; + + ppu.render_bg_until( clock() ); // to do: why this call to clock() rather than using present? + return ppu.frame_length(); +} + +inline nes_time_t Nes_Core::earliest_event( nes_time_t present ) +{ + // PPU frame + nes_time_t t = ppu_frame_length( present ); + + // DMC + if ( wait_states_enabled ) + t = min( t, impl->apu.next_dmc_read_time() + 1 ); + + // NMI + t = min( t, ppu.nmi_time() ); + + if ( single_instruction_mode ) + t = min( t, present + 1 ); + + return t; +} + +void Nes_Core::event_changed() +{ + cpu_set_end_time( earliest_event( cpu_time() ) ); +} + +#undef NES_EMU_CPU_HOOK +#ifndef NES_EMU_CPU_HOOK + #define NES_EMU_CPU_HOOK( cpu, end_time ) cpu::run( end_time ) +#endif + +nes_time_t Nes_Core::emulate_frame_() +{ + Nes_Cpu::result_t last_result = cpu::result_cycles; + int extra_instructions = 0; + while ( true ) + { + // Add DMC wait-states to CPU time + if ( wait_states_enabled ) + { + impl->apu.run_until( cpu_time() ); + clock_ = cpu_time_offset; + } + + nes_time_t present = cpu_time(); + if ( present >= ppu_frame_length( present ) ) + { + if ( ppu.nmi_time() <= present ) + { + // NMI will occur next, so delayed CLI and SEI don't need to be handled. + // If NMI will occur normally ($2000.7 and $2002.7 set), let it occur + // next frame, otherwise vector it now. + + if ( !(ppu.w2000 & 0x80 & ppu.r2002) ) + { + /* vectored NMI at end of frame */ + vector_interrupt( 0xFFFA ); + present += 7; + } + return present; + } + + if ( extra_instructions > 2 ) + { + return present; + } + + if ( last_result != cpu::result_cli && last_result != cpu::result_sei && + (ppu.nmi_time() >= 0x10000 || (ppu.w2000 & 0x80 & ppu.r2002)) ) + return present; + + /* Executing extra instructions for frame */ + extra_instructions++; // execute one more instruction + } + + // NMI + if ( present >= ppu.nmi_time() ) + { + ppu.acknowledge_nmi(); + vector_interrupt( 0xFFFA ); + last_result = cpu::result_cycles; // most recent sei/cli won't be delayed now + } + + // IRQ + nes_time_t irq_time = earliest_irq( present ); + cpu_set_irq_time( irq_time ); + if ( present >= irq_time && (!(cpu::r.status & irq_inhibit_mask) || + last_result == cpu::result_sei) ) + { + if ( last_result != cpu::result_cli ) + { + /* IRQ vectored */ + mapper->run_until( present ); + vector_interrupt( 0xFFFE ); + } + else + { + // CLI delays IRQ + cpu_set_irq_time( present + 1 ); + } + } + + // CPU + nes_time_t end_time = earliest_event( present ); + if ( extra_instructions ) + end_time = present + 1; + unsigned long cpu_error_count = cpu::error_count(); + last_result = NES_EMU_CPU_HOOK( cpu, end_time - cpu_time_offset - 1 ); + cpu_adjust_time( cpu::time() ); + clock_ = cpu_time_offset; + error_count += cpu::error_count() - cpu_error_count; + } +} + +nes_time_t Nes_Core::emulate_frame() +{ + joypad_read_count = 0; + + cpu_time_offset = ppu.begin_frame( nes.timestamp ) - 1; + ppu_2002_time = 0; + clock_ = cpu_time_offset; + + // TODO: clean this fucking mess up + impl->apu.run_until_( emulate_frame_() ); + clock_ = cpu_time_offset; + impl->apu.run_until_( cpu_time() ); + + nes_time_t ppu_frame_length = ppu.frame_length(); + nes_time_t length = cpu_time(); + nes.timestamp = ppu.end_frame( length ); + mapper->end_frame( length ); + impl->apu.end_frame( ppu_frame_length ); + + disable_rendering(); + nes.frame_count++; + + return ppu_frame_length; +} + +void Nes_Core::add_mapper_intercept( nes_addr_t addr, unsigned size, bool read, bool write ) +{ + int end = (addr + size + (page_size - 1)) >> page_bits; + for ( int page = addr >> page_bits; page < end; page++ ) + { + data_reader_mapped [page] |= read; + data_writer_mapped [page] |= write; + } +} diff --git a/core/Nes_Core.h b/core/Nes_Core.h new file mode 100644 index 0000000..72771fa --- /dev/null +++ b/core/Nes_Core.h @@ -0,0 +1,116 @@ + +// Internal NES emulator + +// Nes_Emu 0.7.0 + +#ifndef NES_CORE_H +#define NES_CORE_H + +#include "blargg_common.h" +#include "Nes_Apu.h" +#include "Nes_Cpu.h" +#include "Nes_Ppu.h" +class Nes_Mapper; +class Nes_Cart; +class Nes_State; + +class Nes_Core : private Nes_Cpu { + typedef Nes_Cpu cpu; +public: + Nes_Core(); + ~Nes_Core(); + + const char * init(); + const char * open( Nes_Cart const* ); + void reset( bool full_reset = true, bool erase_battery_ram = false ); + blip_time_t emulate_frame(); + void close(); + + void save_state( Nes_State* ) const; + void save_state( Nes_State_* ) const; + void load_state( Nes_State_ const& ); + + void irq_changed(); + void event_changed(); + +public: private: friend class Nes_Emu; + + struct impl_t + { + enum { sram_size = 0x2000 }; + uint8_t sram [sram_size]; + Nes_Apu apu; + + // extra byte allows CPU to always read operand of instruction, which + // might go past end of data + uint8_t unmapped_page [::Nes_Cpu::page_size + 1]; + }; + impl_t* impl; // keep large arrays separate + unsigned long error_count; + bool sram_present; + +public: + unsigned long current_joypad [2]; + int joypad_read_count; + Nes_Cart const* cart; + Nes_Mapper* mapper; + nes_state_t nes; + Nes_Ppu ppu; + +private: + // noncopyable + Nes_Core( const Nes_Core& ); + Nes_Core& operator = ( const Nes_Core& ); + + // Timing + nes_time_t ppu_2002_time; + void disable_rendering() { clock_ = 0; } + nes_time_t earliest_irq( nes_time_t present ); + nes_time_t ppu_frame_length( nes_time_t present ); + nes_time_t earliest_event( nes_time_t present ); + + // APU and Joypad + joypad_state_t joypad; + int read_io( nes_addr_t ); + void write_io( nes_addr_t, int data ); + static int read_dmc( void* emu, nes_addr_t ); + static void apu_irq_changed( void* emu ); + + // CPU + unsigned sram_readable; + unsigned sram_writable; + unsigned lrom_readable; + nes_time_t clock_; + nes_time_t cpu_time_offset; + nes_time_t emulate_frame_(); + nes_addr_t read_vector( nes_addr_t ); + void vector_interrupt( nes_addr_t ); + static void log_unmapped( nes_addr_t addr, int data = -1 ); + void cpu_set_irq_time( nes_time_t t ) { cpu::set_irq_time_( t - 1 - cpu_time_offset ); } + void cpu_set_end_time( nes_time_t t ) { cpu::set_end_time_( t - 1 - cpu_time_offset ); } + nes_time_t cpu_time() const { return clock_ + 1; } + void cpu_adjust_time( int offset ); + +public: private: friend class Nes_Ppu; + void set_ppu_2002_time( nes_time_t t ) { ppu_2002_time = t - 1 - cpu_time_offset; } + +public: private: friend class Nes_Mapper; + void enable_prg_6000(); + void enable_sram( bool enabled, bool read_only = false ); + nes_time_t clock() const { return clock_; } + void add_mapper_intercept( nes_addr_t start, unsigned size, bool read, bool write ); + +public: private: friend class Nes_Cpu; + int cpu_read_ppu( nes_addr_t, nes_time_t ); + int cpu_read( nes_addr_t, nes_time_t ); + void cpu_write( nes_addr_t, int data, nes_time_t ); + void cpu_write_2007( int data ); + +private: + unsigned char data_reader_mapped [page_count + 1]; // extra entry for overflow + unsigned char data_writer_mapped [page_count + 1]; +}; + +int mem_differs( void const* p, int cmp, unsigned long s ); + +#endif diff --git a/core/Nes_Cpu.cpp b/core/Nes_Cpu.cpp new file mode 100644 index 0000000..99d4aa6 --- /dev/null +++ b/core/Nes_Cpu.cpp @@ -0,0 +1,1230 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/nes-emu/ + +// TODO: remove +#if !defined (NDEBUG) && 0 + #pragma peephole on + #pragma global_optimizer on + #pragma optimization_level 4 + #pragma scheduling 604 + #undef BLARGG_ENABLE_OPTIMIZER +#endif + +#include "Nes_Cpu.h" + +#include +#include +#include "blargg_endian.h" + +#include "nes_cpu_io.h" + +/* Copyright (C) 2003-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +inline void Nes_Cpu::set_code_page( int i, uint8_t const* p ) +{ + code_map [i] = p - (unsigned) i * page_size; +} + +void Nes_Cpu::reset( void const* unmapped_page ) +{ + r.status = 0; + r.sp = 0; + r.pc = 0; + r.a = 0; + r.x = 0; + r.y = 0; + + error_count_ = 0; + clock_count = 0; + clock_limit = 0; + irq_time_ = LONG_MAX / 2 + 1; + end_time_ = LONG_MAX / 2 + 1; + + set_code_page( 0, low_mem ); + set_code_page( 1, low_mem ); + set_code_page( 2, low_mem ); + set_code_page( 3, low_mem ); + for ( int i = 4; i < page_count + 1; i++ ) + set_code_page( i, (uint8_t*) unmapped_page ); +} + +void Nes_Cpu::map_code( nes_addr_t start, unsigned size, const void* data ) +{ + unsigned first_page = start / page_size; + for ( unsigned i = size / page_size; i--; ) + set_code_page( first_page + i, (uint8_t*) data + i * page_size ); +} + +// Note: 'addr' is evaulated more than once in the following macros, so it +// must not contain side-effects. + +//static void log_read( int opcode ) { LOG_FREQ( "read", 256, opcode ); } + +#define READ_LIKELY_PPU( addr ) (NES_CPU_READ_PPU( this, (addr), (clock_count) )) +#define READ( addr ) (NES_CPU_READ( this, (addr), (clock_count) )) +#define WRITE( addr, data ) {NES_CPU_WRITE( this, (addr), (data), (clock_count) );} + +#define READ_LOW( addr ) (low_mem [int (addr)]) +#define WRITE_LOW( addr, data ) (void) (READ_LOW( addr ) = (data)) + +#define READ_PROG( addr ) (code_map [(addr) >> page_bits] [addr]) +#define READ_PROG16( addr ) GET_LE16( &READ_PROG( addr ) ) + +#define SET_SP( v ) (sp = ((v) + 1) | 0x100) +#define GET_SP() ((sp - 1) & 0xFF) +#define PUSH( v ) ((sp = (sp - 1) | 0x100), WRITE_LOW( sp, v )) + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +int Nes_Cpu::read( nes_addr_t addr ) +{ + return READ( addr ); +} + +void Nes_Cpu::write( nes_addr_t addr, int value ) +{ + WRITE( addr, value ); +} + +#ifndef NES_CPU_GLUE_ONLY + +static const unsigned char clock_table [256] = { +// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 7,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6,// 0 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 1 + 6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6,// 2 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 3 + 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6,// 4 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 5 + 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6,// 6 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 7 + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// 8 + 3,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5,// 9 + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// A + 3,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4,// B + 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// C + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// D + 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// E + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7 // F +}; + +Nes_Cpu::result_t Nes_Cpu::run( nes_time_t end ) +{ + set_end_time_( end ); + clock_count = 0; + + volatile result_t result = result_cycles; + +#if !BLARGG_CPU_CISC + long clock_count = this->clock_count; + uint8_t* const low_mem = this->low_mem; +#endif + + // registers + unsigned pc = r.pc; + int sp; + SET_SP( r.sp ); + int a = r.a; + int x = r.x; + int y = r.y; + + // status flags + + int const st_n = 0x80; + int const st_v = 0x40; + int const st_r = 0x20; + int const st_b = 0x10; + int const st_d = 0x08; + int const st_i = 0x04; + int const st_z = 0x02; + int const st_c = 0x01; + + #define IS_NEG (nz & 0x880) + + #define CALC_STATUS( out ) do { \ + out = status & (st_v | st_d | st_i); \ + out |= (c >> 8) & st_c; \ + if ( IS_NEG ) out |= st_n; \ + if ( !(nz & 0xFF) ) out |= st_z; \ + } while ( 0 ) + + #define SET_STATUS( in ) do { \ + status = in & (st_v | st_d | st_i); \ + c = in << 8; \ + nz = (in << 4) & 0x800; \ + nz |= ~in & st_z; \ + } while ( 0 ) + + int status; + int c; // carry set if (c & 0x100) != 0 + int nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x880) != 0 + { + int temp = r.status; + SET_STATUS( temp ); + } + + goto loop; +dec_clock_loop: + clock_count--; +loop: + + uint8_t const* page = code_map [pc >> page_bits]; + unsigned opcode = page [pc]; + pc++; + + if ( clock_count >= clock_limit ) + goto stop; + + clock_count += clock_table [opcode]; + unsigned data; + data = page [pc]; + + switch ( opcode ) + { + +// Macros + +#define GET_OPERAND( addr ) page [addr] +#define GET_OPERAND16( addr ) GET_LE16( &page [addr] ) + +//#define GET_OPERAND( addr ) READ_PROG( addr ) +//#define GET_OPERAND16( addr ) READ_PROG16( addr ) + +#define ADD_PAGE (pc++, data += 0x100 * GET_OPERAND( pc )); +#define GET_ADDR() GET_OPERAND16( pc ) + +#define HANDLE_PAGE_CROSSING( lsb ) clock_count += (lsb) >> 8; + +#define INC_DEC_XY( reg, n ) reg = uint8_t (nz = reg + n); goto loop; + +#define IND_Y(r,c) { \ + int temp = READ_LOW( data ) + y; \ + data = temp + 0x100 * READ_LOW( uint8_t (data + 1) ); \ + if (c) HANDLE_PAGE_CROSSING( temp ); \ + if (!(r) || (temp & 0x100)) \ + READ( data - ( temp & 0x100 ) ); \ + } + +#define IND_X { \ + int temp = data + x; \ + data = 0x100 * READ_LOW( uint8_t (temp + 1) ) + READ_LOW( uint8_t (temp) ); \ + } + +#define ARITH_ADDR_MODES( op ) \ +case op - 0x04: /* (ind,x) */ \ + IND_X \ + goto ptr##op; \ +case op + 0x0C: /* (ind),y */ \ + IND_Y(true,true) \ + goto ptr##op; \ +case op + 0x10: /* zp,X */ \ + data = uint8_t (data + x); \ +case op + 0x00: /* zp */ \ + data = READ_LOW( data ); \ + goto imm##op; \ +case op + 0x14: /* abs,Y */ \ + data += y; \ + goto ind##op; \ +case op + 0x18: /* abs,X */ \ + data += x; \ +ind##op: { \ + HANDLE_PAGE_CROSSING( data ); \ + int temp = data; \ + ADD_PAGE \ + if ( temp & 0x100 ) \ + READ( data - 0x100 ); \ + goto ptr##op; \ +} \ +case op + 0x08: /* abs */ \ + ADD_PAGE \ +ptr##op: \ + data = READ( data ); \ +case op + 0x04: /* imm */ \ +imm##op: \ + +#define ARITH_ADDR_MODES_PTR( op ) \ +case op - 0x04: /* (ind,x) */ \ + IND_X \ + goto imm##op; \ +case op + 0x0C: \ + IND_Y(false,false) \ + goto imm##op; \ +case op + 0x10: /* zp,X */ \ + data = uint8_t (data + x); \ + goto imm##op; \ +case op + 0x14: /* abs,Y */ \ + data += y; \ + goto ind##op; \ +case op + 0x18: /* abs,X */ \ + data += x; \ +ind##op: { \ + int temp = data; \ + ADD_PAGE \ + READ( data - ( temp & 0x100 ) ); \ + goto imm##op; \ +} \ +case op + 0x08: /* abs */ \ + ADD_PAGE \ +case op + 0x00: /* zp */ \ +imm##op: \ + +#define BRANCH( cond ) \ +{ \ + pc++; \ + int offset = (int8_t) data; \ + int extra_clock = (pc & 0xFF) + offset; \ + if ( !(cond) ) goto dec_clock_loop; \ + pc += offset; \ + pc = uint16_t( pc ); \ + clock_count += (extra_clock >> 8) & 1; \ + goto loop; \ +} + +// Often-Used + + case 0xB5: // LDA zp,x + data = uint8_t (data + x); + case 0xA5: // LDA zp + a = nz = READ_LOW( data ); + pc++; + goto loop; + + case 0xD0: // BNE + BRANCH( (uint8_t) nz ); + + case 0x20: { // JSR + int temp = pc + 1; + pc = GET_OPERAND16( pc ); + WRITE_LOW( 0x100 | (sp - 1), temp >> 8 ); + sp = (sp - 2) | 0x100; + WRITE_LOW( sp, temp ); + goto loop; + } + + case 0x4C: // JMP abs + pc = GET_OPERAND16( pc ); + goto loop; + + case 0xE8: INC_DEC_XY( x, 1 ) // INX + + case 0x10: // BPL + BRANCH( !IS_NEG ) + + ARITH_ADDR_MODES( 0xC5 ) // CMP + nz = a - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0x30: // BMI + BRANCH( IS_NEG ) + + case 0xF0: // BEQ + BRANCH( !(uint8_t) nz ); + + case 0x95: // STA zp,x + data = uint8_t (data + x); + case 0x85: // STA zp + pc++; + WRITE_LOW( data, a ); + goto loop; + + case 0xC8: INC_DEC_XY( y, 1 ) // INY + + case 0xA8: // TAY + y = a; + case 0x98: // TYA + a = nz = y; + goto loop; + + case 0xAD:{// LDA abs + unsigned addr = GET_ADDR(); + pc += 2; + a = nz = READ_LIKELY_PPU( addr ); + goto loop; + } + + case 0x60: // RTS + pc = 1 + READ_LOW( sp ); + pc += READ_LOW( 0x100 | (sp - 0xFF) ) * 0x100; + sp = (sp - 0xFE) | 0x100; + goto loop; + + case 0x99: // STA abs,Y + data += y; + goto sta_ind_common; + + case 0x9D: // STA abs,X + data += x; + sta_ind_common: { + int temp = data; + ADD_PAGE + READ( data - ( temp & 0x100 ) ); + goto sta_ptr; + } + case 0x8D: // STA abs + ADD_PAGE + sta_ptr: + pc++; + WRITE( data, a ); + goto loop; + + case 0xA9: // LDA #imm + pc++; + a = data; + nz = data; + goto loop; + +#if 0 + case 0xA1: // LDA (ind,X) + IND_X + goto lda_ptr; + + case 0xB1: // LDA (ind),Y + IND_Y(true,true) + goto lda_ptr; + + case 0xB9: // LDA abs,Y + data += y; + goto lda_ind_common; + + case 0xBD: // LDA abs,X + data += x; + lda_ind_common: { + HANDLE_PAGE_CROSSING( data ); + int temp = data; + ADD_PAGE + if ( temp & 0x100 ) + READ( data - 0x100 ); + } + lda_ptr: + a = nz = READ( data ); + pc++; + goto loop; +#else + // optimization of most commonly used memory read instructions + + case 0xB9:// LDA abs,Y + data += y; + data -= x; + case 0xBD:{// LDA abs,X + pc++; + unsigned msb = GET_OPERAND( pc ); + data += x; + // indexed common + pc++; + HANDLE_PAGE_CROSSING( data ); + int temp = data; + data += msb * 0x100; + a = nz = READ_PROG( uint16_t( data ) ); + if ( (unsigned) (data - 0x2000) >= 0x6000 ) + goto loop; + if ( temp & 0x100 ) + READ( data - 0x100 ); + a = nz = READ( data ); + goto loop; + } + + case 0xB1:{// LDA (ind),Y + unsigned msb = READ_LOW( (uint8_t) (data + 1) ); + data = READ_LOW( data ) + y; + // indexed common + pc++; + HANDLE_PAGE_CROSSING( data ); + int temp = data; + data += msb * 0x100; + a = nz = READ_PROG( uint16_t( data ) ); + if ( (unsigned) (data - 0x2000) >= 0x6000 ) + goto loop; + if ( temp & 0x100 ) + READ( data - 0x100 ); + a = nz = READ( data ); + goto loop; + } + + case 0xA1: // LDA (ind,X) + IND_X + a = nz = READ( data ); + pc++; + goto loop; + +#endif + +// Branch + + case 0x50: // BVC + BRANCH( !(status & st_v) ) + + case 0x70: // BVS + BRANCH( status & st_v ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + +// Load/store + + case 0x94: // STY zp,x + data = uint8_t (data + x); + case 0x84: // STY zp + pc++; + WRITE_LOW( data, y ); + goto loop; + + case 0x96: // STX zp,y + data = uint8_t (data + y); + case 0x86: // STX zp + pc++; + WRITE_LOW( data, x ); + goto loop; + + case 0xB6: // LDX zp,y + data = uint8_t (data + y); + case 0xA6: // LDX zp + data = READ_LOW( data ); + case 0xA2: // LDX #imm + pc++; + x = data; + nz = data; + goto loop; + + case 0xB4: // LDY zp,x + data = uint8_t (data + x); + case 0xA4: // LDY zp + data = READ_LOW( data ); + case 0xA0: // LDY #imm + pc++; + y = data; + nz = data; + goto loop; + + case 0x91: // STA (ind),Y + IND_Y(false,false) + goto sta_ptr; + + case 0x81: // STA (ind,X) + IND_X + goto sta_ptr; + + case 0xBC: // LDY abs,X + data += x; + HANDLE_PAGE_CROSSING( data ); + case 0xAC:{// LDY abs + pc++; + unsigned addr = data + 0x100 * GET_OPERAND( pc ); + if ( data & 0x100 ) + READ( addr - 0x100 ); + pc++; + y = nz = READ( addr ); + goto loop; + } + + case 0xBE: // LDX abs,y + data += y; + HANDLE_PAGE_CROSSING( data ); + case 0xAE:{// LDX abs + pc++; + unsigned addr = data + 0x100 * GET_OPERAND( pc ); + pc++; + if ( data & 0x100 ) + READ( addr - 0x100 ); + x = nz = READ( addr ); + goto loop; + } + + { + int temp; + case 0x8C: // STY abs + temp = y; + goto store_abs; + + case 0x8E: // STX abs + temp = x; + store_abs: + unsigned addr = GET_ADDR(); + WRITE( addr, temp ); + pc += 2; + goto loop; + } + +// Compare + + case 0xEC:{// CPX abs + unsigned addr = GET_ADDR(); + pc++; + data = READ( addr ); + goto cpx_data; + } + + case 0xE4: // CPX zp + data = READ_LOW( data ); + case 0xE0: // CPX #imm + cpx_data: + nz = x - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0xCC:{// CPY abs + unsigned addr = GET_ADDR(); + pc++; + data = READ( addr ); + goto cpy_data; + } + + case 0xC4: // CPY zp + data = READ_LOW( data ); + case 0xC0: // CPY #imm + cpy_data: + nz = y - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + +// Logical + + ARITH_ADDR_MODES( 0x25 ) // AND + nz = (a &= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x45 ) // EOR + nz = (a ^= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x05 ) // ORA + nz = (a |= data); + pc++; + goto loop; + + case 0x2C:{// BIT abs + unsigned addr = GET_ADDR(); + pc += 2; + status &= ~st_v; + nz = READ_LIKELY_PPU( addr ); + status |= nz & st_v; + if ( a & nz ) + goto loop; + // result must be zero, even if N bit is set + nz = nz << 4 & 0x800; + goto loop; + } + + case 0x24: // BIT zp + nz = READ_LOW( data ); + pc++; + status &= ~st_v; + status |= nz & st_v; + if ( a & nz ) + goto loop; + // result must be zero, even if N bit is set + nz = nz << 4 & 0x800; + goto loop; + +// Add/subtract + + ARITH_ADDR_MODES( 0xE5 ) // SBC + case 0xEB: // unofficial equivalent + data ^= 0xFF; + goto adc_imm; + + ARITH_ADDR_MODES( 0x65 ) // ADC + adc_imm: { + int carry = (c >> 8) & 1; + int ov = (a ^ 0x80) + carry + (int8_t) data; // sign-extend + status &= ~st_v; + status |= (ov >> 2) & 0x40; + c = nz = a + data + carry; + pc++; + a = (uint8_t) nz; + goto loop; + } + +// Shift/rotate + + case 0x4A: // LSR A + lsr_a: + c = 0; + case 0x6A: // ROR A + nz = (c >> 1) & 0x80; // could use bit insert macro here + c = a << 8; + nz |= a >> 1; + a = nz; + goto loop; + + case 0x0A: // ASL A + nz = a << 1; + c = nz; + a = (uint8_t) nz; + goto loop; + + case 0x2A: { // ROL A + nz = a << 1; + int temp = (c >> 8) & 1; + c = nz; + nz |= temp; + a = (uint8_t) nz; + goto loop; + } + + case 0x3E: // ROL abs,X + data += x; + goto rol_abs; + + case 0x1E: // ASL abs,X + data += x; + case 0x0E: // ASL abs + c = 0; + case 0x2E: // ROL abs + rol_abs: { + int temp = data; + ADD_PAGE + if ( opcode == 0x1E || opcode == 0x3E ) READ( data - ( temp & 0x100 ) ); + WRITE( data, temp = READ( data ) ); + nz = (c >> 8) & 1; + nz |= (c = temp << 1); + } + rotate_common: + pc++; + WRITE( data, (uint8_t) nz ); + goto loop; + + case 0x7E: // ROR abs,X + data += x; + goto ror_abs; + + case 0x5E: // LSR abs,X + data += x; + case 0x4E: // LSR abs + c = 0; + case 0x6E: // ROR abs + ror_abs: { + int temp = data; + ADD_PAGE + if ( opcode == 0x5E || opcode == 0x7E ) READ( data - ( temp & 0x100 ) ); + WRITE( data, temp = READ( data ) ); + nz = ((c >> 1) & 0x80) | (temp >> 1); + c = temp << 8; + goto rotate_common; + } + + case 0x76: // ROR zp,x + data = uint8_t (data + x); + goto ror_zp; + + case 0x56: // LSR zp,x + data = uint8_t (data + x); + case 0x46: // LSR zp + c = 0; + case 0x66: // ROR zp + ror_zp: { + int temp = READ_LOW( data ); + nz = ((c >> 1) & 0x80) | (temp >> 1); + c = temp << 8; + goto write_nz_zp; + } + + case 0x36: // ROL zp,x + data = uint8_t (data + x); + goto rol_zp; + + case 0x16: // ASL zp,x + data = uint8_t (data + x); + case 0x06: // ASL zp + c = 0; + case 0x26: // ROL zp + rol_zp: + nz = (c >> 8) & 1; + nz |= (c = READ_LOW( data ) << 1); + goto write_nz_zp; + +// Increment/decrement + + case 0xCA: INC_DEC_XY( x, -1 ) // DEX + + case 0x88: INC_DEC_XY( y, -1 ) // DEY + + case 0xF6: // INC zp,x + data = uint8_t (data + x); + case 0xE6: // INC zp + nz = 1; + goto add_nz_zp; + + case 0xD6: // DEC zp,x + data = uint8_t (data + x); + case 0xC6: // DEC zp + nz = -1; + add_nz_zp: + nz += READ_LOW( data ); + write_nz_zp: + pc++; + WRITE_LOW( data, nz ); + goto loop; + + case 0xFE: { // INC abs,x + int temp = data + x; + data = x + GET_ADDR(); + READ( data - ( temp & 0x100 ) ); + goto inc_ptr; + } + + case 0xEE: // INC abs + data = GET_ADDR(); + inc_ptr: + nz = 1; + goto inc_common; + + case 0xDE: { // DEC abs,x + int temp = data + x; + data = x + GET_ADDR(); + READ( data - ( temp & 0x100 ) ); + goto dec_ptr; + } + + case 0xCE: // DEC abs + data = GET_ADDR(); + dec_ptr: + nz = -1; + inc_common: { + int temp; + WRITE( data, temp = READ( data ) ); + nz += temp; + pc += 2; + WRITE( data, (uint8_t) nz ); + goto loop; + } + +// Transfer + + case 0xAA: // TAX + x = a; + case 0x8A: // TXA + a = nz = x; + goto loop; + + case 0x9A: // TXS + SET_SP( x ); // verified (no flag change) + goto loop; + + case 0xBA: // TSX + x = nz = GET_SP(); + goto loop; + +// Stack + + case 0x48: // PHA + PUSH( a ); // verified + goto loop; + + case 0x68: // PLA + a = nz = READ_LOW( sp ); + sp = (sp - 0xFF) | 0x100; + goto loop; + + case 0x40: // RTI + { + int temp = READ_LOW( sp ); + pc = READ_LOW( 0x100 | (sp - 0xFF) ); + pc |= READ_LOW( 0x100 | (sp - 0xFE) ) * 0x100; + sp = (sp - 0xFD) | 0x100; + data = status; + SET_STATUS( temp ); + } + if ( !((data ^ status) & st_i) ) + goto loop; // I flag didn't change + i_flag_changed: + //dprintf( "%6d %s\n", time(), (status & st_i ? "SEI" : "CLI") ); + this->r.status = status; // update externally-visible I flag + // update clock_limit based on modified I flag + clock_limit = end_time_; + if ( end_time_ <= irq_time_ ) + goto loop; + if ( status & st_i ) + goto loop; + clock_limit = irq_time_; + goto loop; + + case 0x28:{// PLP + int temp = READ_LOW( sp ); + sp = (sp - 0xFF) | 0x100; + data = status; + SET_STATUS( temp ); + if ( !((data ^ status) & st_i) ) + goto loop; // I flag didn't change + if ( !(status & st_i) ) + goto handle_cli; + goto handle_sei; + } + + case 0x08: { // PHP + int temp; + CALC_STATUS( temp ); + PUSH( temp | st_b | st_r ); + goto loop; + } + + case 0x6C: // JMP (ind) + data = GET_ADDR(); + pc = READ( data ); + pc |= READ( (data & 0xFF00) | ((data + 1) & 0xFF) ) << 8; + goto loop; + + case 0x00: { // BRK + pc++; + WRITE_LOW( 0x100 | (sp - 1), pc >> 8 ); + WRITE_LOW( 0x100 | (sp - 2), pc ); + int temp; + CALC_STATUS( temp ); + sp = (sp - 3) | 0x100; + WRITE_LOW( sp, temp | st_b | st_r ); + pc = GET_LE16( &code_map [0xFFFE >> page_bits] [0xFFFE] ); + status |= st_i; + goto i_flag_changed; + } + +// Flags + + case 0x38: // SEC + c = ~0; + goto loop; + + case 0x18: // CLC + c = 0; + goto loop; + + case 0xB8: // CLV + status &= ~st_v; + goto loop; + + case 0xD8: // CLD + status &= ~st_d; + goto loop; + + case 0xF8: // SED + status |= st_d; + goto loop; + + case 0x58: // CLI + if ( !(status & st_i) ) + goto loop; + status &= ~st_i; + handle_cli: + //dprintf( "%6d CLI\n", time() ); + this->r.status = status; // update externally-visible I flag + if ( clock_count < end_time_ ) + { + if ( end_time_ <= irq_time_ ) + goto loop; // irq is later + if ( clock_count >= irq_time_ ) + irq_time_ = clock_count + 1; // delay IRQ until after next instruction + clock_limit = irq_time_; + goto loop; + } + // execution is stopping now, so delayed CLI must be handled by caller + result = result_cli; + goto end; + + case 0x78: // SEI + if ( status & st_i ) + goto loop; + status |= st_i; + handle_sei: + //dprintf( "%6d SEI\n", time() ); + this->r.status = status; // update externally-visible I flag + clock_limit = end_time_; + if ( clock_count < irq_time_ ) + goto loop; + result = result_sei; // IRQ will occur now, even though I flag is set + goto end; + +// Unofficial + case 0x1C: case 0x3C: case 0x5C: case 0x7C: case 0xDC: case 0xFC: { // SKW + data += x; + HANDLE_PAGE_CROSSING( data ); + int addr = GET_ADDR() + x; + if ( data & 0x100 ) + READ( addr - 0x100 ); + READ( addr ); + } + case 0x0C: // SKW + pc++; + case 0x74: case 0x04: case 0x14: case 0x34: case 0x44: case 0x54: case 0x64: // SKB + case 0x80: case 0x82: case 0x89: case 0xC2: case 0xD4: case 0xE2: case 0xF4: + pc++; + case 0xEA: case 0x1A: case 0x3A: case 0x5A: case 0x7A: case 0xDA: case 0xFA: // NOP + goto loop; + + ARITH_ADDR_MODES_PTR( 0xC7 ) // DCP + WRITE( data, nz = READ( data ) ); + nz = uint8_t( nz - 1 ); + WRITE( data, nz ); + pc++; + nz = a - nz; + c = ~nz; + nz &= 0xFF; + goto loop; + + ARITH_ADDR_MODES_PTR( 0xE7 ) // ISC + WRITE( data, nz = READ( data ) ); + nz = uint8_t( nz + 1 ); + WRITE( data, nz ); + data = nz ^ 0xFF; + goto adc_imm; + + ARITH_ADDR_MODES_PTR( 0x27 ) { // RLA + WRITE( data, nz = READ( data ) ); + int temp = c; + c = nz << 1; + nz = uint8_t( c ) | ( ( temp >> 8 ) & 0x01 ); + WRITE( data, nz ); + pc++; + nz = a &= nz; + goto loop; + } + + ARITH_ADDR_MODES_PTR( 0x67 ) { // RRA + int temp; + WRITE( data, temp = READ( data ) ); + nz = ((c >> 1) & 0x80) | (temp >> 1); + WRITE( data, nz ); + data = nz; + c = temp << 8; + goto adc_imm; + } + + ARITH_ADDR_MODES_PTR( 0x07 ) // SLO + WRITE( data, nz = READ( data ) ); + c = nz << 1; + nz = uint8_t( c ); + WRITE( data, nz ); + nz = (a |= nz); + pc++; + goto loop; + + ARITH_ADDR_MODES_PTR( 0x47 ) // SRE + WRITE( data, nz = READ( data ) ); + c = nz << 8; + nz >>= 1; + WRITE( data, nz ); + nz = a ^= nz; + pc++; + goto loop; + + case 0x4B: // ALR + nz = (a &= data); + pc++; + goto lsr_a; + + case 0x0B: // ANC + case 0x2B: + nz = a &= data; + c = a << 1; + pc++; + goto loop; + + case 0x6B: // ARR + nz = a = uint8_t( ( ( data & a ) >> 1 ) | ( ( c >> 1 ) & 0x80 ) ); + c = a << 2; + status = ( status & ~st_v ) | ( ( a ^ a << 1 ) & st_v ); + pc++; + goto loop; + + case 0xAB: // LXA + a = data; + x = data; + nz = data; + pc++; + goto loop; + + case 0xA3: // LAX + IND_X + goto lax_ptr; + + case 0xB3: + IND_Y(true,true) + goto lax_ptr; + + case 0xB7: + data = uint8_t (data + y); + + case 0xA7: + data = READ_LOW( data ); + goto lax_imm; + + case 0xBF: { + data += y; + HANDLE_PAGE_CROSSING( data ); + int temp = data; + ADD_PAGE; + if ( temp & 0x100 ) + READ( data - 0x100 ); + goto lax_ptr; + } + + case 0xAF: + ADD_PAGE + + lax_ptr: + data = READ( data ); + lax_imm: + nz = x = a = data; + pc++; + goto loop; + + case 0x83: // SAX + IND_X + goto sax_imm; + + case 0x97: + data = uint8_t (data + y); + goto sax_imm; + + case 0x8F: + ADD_PAGE + + case 0x87: + sax_imm: + WRITE( data, a & x ); + pc++; + goto loop; + + case 0xCB: // SBX + data = ( a & x ) - data; + c = ( data <= 0xFF ) ? 0x100 : 0; + nz = x = uint8_t( data ); + pc++; + goto loop; + + case 0x93: // SHA (ind),Y + IND_Y(false,false) + pc++; + WRITE( data, uint8_t( a & x & ( ( data >> 8 ) + 1 ) ) ); + goto loop; + + case 0x9F: { // SHA abs,Y + data += y; + int temp = data; + ADD_PAGE + READ( data - ( temp & 0x100 ) ); + pc++; + WRITE( data, uint8_t( a & x & ( ( data >> 8 ) + 1 ) ) ); + goto loop; + } + + case 0x9E: { // SHX abs,Y + data += y; + int temp = data; + ADD_PAGE + READ( data - ( temp & 0x100 ) ); + pc++; + if ( !( temp & 0x100 ) ) + WRITE( data, uint8_t( x & ( ( data >> 8 ) + 1 ) ) ); + goto loop; + } + + case 0x9C: { // SHY abs,X + data += x; + int temp = data; + ADD_PAGE + READ( data - ( temp & 0x100 ) ); + pc++; + if ( !( temp & 0x100) ) + WRITE( data, uint8_t( y & ( ( data >> 8 ) + 1 ) ) ); + goto loop; + } + + case 0x9B: { // SHS abs,Y + data += y; + int temp = data; + ADD_PAGE + READ( data - ( temp & 0x100 ) ); + pc++; + SET_SP( a & x ); + WRITE( data, uint8_t( a & x & ( ( data >> 8 ) + 1 ) ) ); + goto loop; + } + + case 0xBB: { // LAS abs,Y + data += y; + HANDLE_PAGE_CROSSING( data ); + int temp = data; + ADD_PAGE + if ( temp & 0x100 ) + READ( data - 0x100 ); + pc++; + a = GET_SP(); + x = a &= READ( data ); + SET_SP( a ); + goto loop; + } + +// Unimplemented + + case page_wrap_opcode: // HLT + if ( pc > 0x10000 ) + { + // handle wrap-around (assumes caller has put page of HLT at 0x10000) + pc = (pc - 1) & 0xFFFF; + clock_count -= 2; + goto loop; + } + // fall through + default: + // skip over proper number of bytes + static unsigned char const row [8] = { 0x95, 0x95, 0x95, 0xd5, 0x95, 0x95, 0xd5, 0xf5 }; + int len = row [opcode >> 2 & 7] >> (opcode << 1 & 6) & 3; + if ( opcode == 0x9C ) + len = 3; + pc += len - 1; + error_count_++; + goto loop; + + //result = result_badop; // TODO: re-enable + goto stop; + } + +stop: + pc--; +end: + + { + int temp; + CALC_STATUS( temp ); + r.status = temp; + } + + this->clock_count = clock_count; + r.pc = pc; + r.sp = GET_SP(); + r.a = a; + r.x = x; + r.y = y; + irq_time_ = LONG_MAX / 2 + 1; + + return result; +} + +#endif diff --git a/core/Nes_Cpu.h b/core/Nes_Cpu.h new file mode 100644 index 0000000..f7783b0 --- /dev/null +++ b/core/Nes_Cpu.h @@ -0,0 +1,126 @@ + +// NES 6502 CPU emulator + +// Nes_Emu 0.7.0 + +#ifndef NES_CPU_H +#define NES_CPU_H + +#include +#include "blargg_common.h" + +typedef long nes_time_t; // clock cycle count +typedef unsigned nes_addr_t; // 16-bit address + +class Nes_Cpu { +public: + // Clear registers, unmap memory, and map code pages to unmapped_page. + void reset( void const* unmapped_page = 0 ); + + // Map code memory (memory accessed via the program counter). Start and size + // must be multiple of page_size. + enum { page_bits = 11 }; + enum { page_count = 0x10000 >> page_bits }; + enum { page_size = 1L << page_bits }; + void map_code( nes_addr_t start, unsigned size, void const* code ); + + // Access memory as the emulated CPU does. + int read( nes_addr_t ); + void write( nes_addr_t, int data ); + uint8_t* get_code( nes_addr_t ); // non-const to allow debugger to modify code + + // Push a byte on the stack + void push_byte( int ); + + // NES 6502 registers. *Not* kept updated during a call to run(). + struct registers_t { + long pc; // more than 16 bits to allow overflow detection + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t status; + uint8_t sp; + }; + //registers_t r; + + // Reasons that run() returns + enum result_t { + result_cycles, // Requested number of cycles (or more) were executed + result_sei, // I flag just set and IRQ time would generate IRQ now + result_cli, // I flag just cleared but IRQ should occur *after* next instr + result_badop // unimplemented/illegal instruction + }; + + result_t run( nes_time_t end_time ); + + nes_time_t time() const { return clock_count; } + void reduce_limit( int offset ); + void set_end_time_( nes_time_t t ); + void set_irq_time_( nes_time_t t ); + unsigned long error_count() const { return error_count_; } + + // If PC exceeds 0xFFFF and encounters page_wrap_opcode, it will be silently wrapped. + enum { page_wrap_opcode = 0xF2 }; + + // One of the many opcodes that are undefined and stop CPU emulation. + enum { bad_opcode = 0xD2 }; + +private: + uint8_t const* code_map [page_count + 1]; + nes_time_t clock_limit; + nes_time_t clock_count; + nes_time_t irq_time_; + nes_time_t end_time_; + unsigned long error_count_; + + enum { irq_inhibit = 0x04 }; + void set_code_page( int, uint8_t const* ); + void update_clock_limit(); + +public: + registers_t r; + + // low_mem is a full page size so it can be mapped with code_map + uint8_t low_mem [page_size > 0x800 ? page_size : 0x800]; +}; + +inline uint8_t* Nes_Cpu::get_code( nes_addr_t addr ) +{ + return (uint8_t*) code_map [addr >> page_bits] + addr; +} + +inline void Nes_Cpu::update_clock_limit() +{ + nes_time_t t = end_time_; + if ( t > irq_time_ && !(r.status & irq_inhibit) ) + t = irq_time_; + clock_limit = t; +} + +inline void Nes_Cpu::set_end_time_( nes_time_t t ) +{ + end_time_ = t; + update_clock_limit(); +} + +inline void Nes_Cpu::set_irq_time_( nes_time_t t ) +{ + irq_time_ = t; + update_clock_limit(); +} + +inline void Nes_Cpu::reduce_limit( int offset ) +{ + clock_limit -= offset; + end_time_ -= offset; + irq_time_ -= offset; +} + +inline void Nes_Cpu::push_byte( int data ) +{ + int sp = r.sp; + r.sp = (sp - 1) & 0xFF; + low_mem [0x100 + sp] = data; +} + +#endif diff --git a/core/Nes_Effects_Buffer.cpp b/core/Nes_Effects_Buffer.cpp new file mode 100644 index 0000000..169eb3e --- /dev/null +++ b/core/Nes_Effects_Buffer.cpp @@ -0,0 +1,91 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/libs/ + +#include "Nes_Effects_Buffer.h" + +#include "Nes_Apu.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +Nes_Effects_Buffer::Nes_Effects_Buffer() : + Effects_Buffer( true ) // nes never uses stereo channels +{ + config_t c; + c.effects_enabled = false; + config( c ); +} + +Nes_Effects_Buffer::~Nes_Effects_Buffer() { } + +Multi_Buffer* set_apu( Nes_Effects_Buffer* buf, Nes_Apu* apu ) +{ + buf->set_apu( apu ); + return buf; +} + +void Nes_Effects_Buffer::enable_nonlinearity( bool b ) +{ + if ( b ) + clear(); + Nes_Apu* apu = nonlin.enable( b, channel( 2 ).center ); + apu->osc_output( 0, channel( 0 ).center ); + apu->osc_output( 1, channel( 1 ).center ); +} + +void Nes_Effects_Buffer::config( const config_t& in ) +{ + config_t c = in; + if ( !c.effects_enabled ) + { + // effects must always be enabled to keep separate buffers, so + // set parameters to be equivalent to disabled + c.pan_1 = 0; + c.pan_2 = 0; + c.echo_level = 0; + c.reverb_level = 0; + c.effects_enabled = true; + } + Effects_Buffer::config( c ); +} + +const char * Nes_Effects_Buffer::set_sample_rate( long rate, int msec ) +{ + enable_nonlinearity( nonlin.enabled ); // reapply + return Effects_Buffer::set_sample_rate( rate, msec ); +} + +void Nes_Effects_Buffer::clear() +{ + nonlin.clear(); + Effects_Buffer::clear(); +} + +Nes_Effects_Buffer::channel_t Nes_Effects_Buffer::channel( int i ) +{ + return Effects_Buffer::channel( (2 <= i && i <= 4) ? 2 : i & 1 ); +} + +long Nes_Effects_Buffer::read_samples( blip_sample_t* out, long count ) +{ + count = 2 * nonlin.make_nonlinear( *channel( 2 ).center, count / 2 ); + return Effects_Buffer::read_samples( out, count ); +} + +void Nes_Effects_Buffer::SaveAudioBufferState() +{ +} + +void Nes_Effects_Buffer::RestoreAudioBufferState() +{ +} diff --git a/core/Nes_Effects_Buffer.h b/core/Nes_Effects_Buffer.h new file mode 100644 index 0000000..2f751c2 --- /dev/null +++ b/core/Nes_Effects_Buffer.h @@ -0,0 +1,40 @@ + +// Effects_Buffer with non-linear sound + +// Nes_Emu 0.7.0 + +#ifndef NES_EFFECTS_BUFFER_H +#define NES_EFFECTS_BUFFER_H + +#include "Nes_Buffer.h" +#include "Effects_Buffer.h" + +// Effects_Buffer uses several buffers and outputs stereo sample pairs. +class Nes_Effects_Buffer : public Effects_Buffer { +public: + Nes_Effects_Buffer(); + ~Nes_Effects_Buffer(); + + // Setup APU for use with buffer, including setting its output to this buffer. + // If you're using Nes_Emu, this is automatically called for you. + void set_apu( Nes_Apu* apu ) { nonlin.set_apu( apu ); } + + // Enable/disable non-linear output + void enable_nonlinearity( bool = true ); + + // See Effects_Buffer.h for reference + const char *set_sample_rate( long rate, int msec = blip_default_length ); + void config( const config_t& ); + void clear(); + channel_t channel( int ); + long read_samples( blip_sample_t*, long ); + + void SaveAudioBufferState(); + void RestoreAudioBufferState(); + +private: + Nes_Nonlinearizer nonlin; + friend Multi_Buffer* set_apu( Nes_Effects_Buffer*, Nes_Apu* ); +}; + +#endif diff --git a/core/Nes_Emu.cpp b/core/Nes_Emu.cpp new file mode 100644 index 0000000..9bfdf95 --- /dev/null +++ b/core/Nes_Emu.cpp @@ -0,0 +1,519 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Emu.h" + +#include +#include "Nes_State.h" +#include "Nes_Mapper.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +int const sound_fade_size = 384; + +// Constants are manually duplicated in Nes_Emu so their value can be seen +// directly, rather than having to look in Nes_Ppu.h. "0 +" converts to int. +BOOST_STATIC_ASSERT( Nes_Emu::image_width == 0 + Nes_Ppu::image_width ); +BOOST_STATIC_ASSERT( Nes_Emu::image_height == 0 + Nes_Ppu::image_height ); + +Nes_Emu::equalizer_t const Nes_Emu::nes_eq = { -1.0, 80 }; +Nes_Emu::equalizer_t const Nes_Emu::famicom_eq = { -15.0, 80 }; +Nes_Emu::equalizer_t const Nes_Emu::tv_eq = { -12.0, 180 }; +Nes_Emu::equalizer_t const Nes_Emu::flat_eq = { 0.0, 1 }; +Nes_Emu::equalizer_t const Nes_Emu::crisp_eq = { 5.0, 1 }; +Nes_Emu::equalizer_t const Nes_Emu::tinny_eq = { -47.0, 2000 }; + +Nes_Emu::Nes_Emu() +{ + frame_ = &single_frame; + buffer_height_ = Nes_Ppu::buffer_height + 2; + default_sound_buf = NULL; + sound_buf = &silent_buffer; + sound_buf_changed_count = 0; + equalizer_ = nes_eq; + channel_count_ = 0; + sound_enabled = false; + host_pixels = NULL; + single_frame.pixels = 0; + single_frame.top = 0; + init_called = false; + set_palette_range( 0 ); + memset( single_frame.palette, 0, sizeof single_frame.palette ); + + extra_fade_sound_in = false; + extra_fade_sound_out = false; + extra_sound_buf_changed_count = 0; +} + +Nes_Emu::~Nes_Emu() +{ + delete default_sound_buf; +} + +const char * Nes_Emu::init_() +{ + return emu.init(); +} + +inline const char * Nes_Emu::auto_init() +{ + if ( !init_called ) + { + RETURN_ERR( init_() ); + init_called = true; + } + return 0; +} + +inline void Nes_Emu::clear_sound_buf() +{ + fade_sound_out = false; + fade_sound_in = true; + sound_buf->clear(); +} + +// Emulation + +void Nes_Emu::close() +{ + if ( cart() ) + { + emu.close(); + private_cart.clear(); + } +} + +const char * Nes_Emu::set_cart( Nes_Cart const* new_cart ) +{ + close(); + RETURN_ERR( auto_init() ); + RETURN_ERR( emu.open( new_cart ) ); + + channel_count_ = Nes_Apu::osc_count + emu.mapper->channel_count(); + RETURN_ERR( sound_buf->set_channel_count( channel_count() ) ); + set_equalizer( equalizer_ ); + enable_sound( true ); + + reset(); + + return 0; +} + +void Nes_Emu::reset( bool full_reset, bool erase_battery_ram ) +{ + clear_sound_buf(); + set_timestamp( 0 ); + emu.reset( full_reset, erase_battery_ram ); +} + +void Nes_Emu::set_palette_range( int begin, int end ) +{ + // round up to alignment + emu.ppu.palette_begin = (begin + palette_alignment - 1) & ~(palette_alignment - 1); + host_palette_size = end - emu.ppu.palette_begin; +} + +const char * Nes_Emu::emulate_skip_frame( int joypad1, int joypad2 ) +{ + char *old_host_pixels = host_pixels; + host_pixels = NULL; + const char *result = emulate_frame(joypad1, joypad2); + host_pixels = old_host_pixels; + return result; +} + +const char * Nes_Emu::emulate_frame( int joypad1, int joypad2 ) +{ + emu.current_joypad [0] = (joypad1 |= ~0xFF); + emu.current_joypad [1] = (joypad2 |= ~0xFF); + + emu.ppu.host_pixels = NULL; + + unsigned changed_count = sound_buf->channels_changed_count(); + bool new_enabled = (frame_ != NULL); + if ( sound_buf_changed_count != changed_count || sound_enabled != new_enabled ) + { + sound_buf_changed_count = changed_count; + sound_enabled = new_enabled; + enable_sound( sound_enabled ); + } + + frame_t* f = frame_; + if ( f ) + { + emu.ppu.max_palette_size = host_palette_size; + emu.ppu.host_palette = f->palette + emu.ppu.palette_begin; + // add black and white for emulator to use (unless emulator uses entire + // palette for frame) + f->palette [252] = 0x0F; + f->palette [254] = 0x30; + f->palette [255] = 0x0F; + if ( host_pixels ) + emu.ppu.host_pixels = (uint8_t*) host_pixels + + emu.ppu.host_row_bytes * f->top; + + if ( sound_buf->samples_avail() ) + clear_sound_buf(); + + nes_time_t frame_len = emu.emulate_frame(); + sound_buf->end_frame( frame_len, false ); + + f = frame_; + f->sample_count = sound_buf->samples_avail(); + f->chan_count = sound_buf->samples_per_frame(); + f->palette_begin = emu.ppu.palette_begin; + f->palette_size = emu.ppu.palette_size; + f->joypad_read_count = emu.joypad_read_count; + f->burst_phase = emu.ppu.burst_phase; + f->pitch = emu.ppu.host_row_bytes; + f->pixels = emu.ppu.host_pixels + f->left; + } + else + { + emu.ppu.max_palette_size = 0; + emu.emulate_frame(); + } + + return 0; +} + +// Extras + +const char * Nes_Emu::load_ines( Auto_File_Reader in ) +{ + close(); + RETURN_ERR( private_cart.load_ines( in ) ); + return set_cart( &private_cart ); +} + +const char * Nes_Emu::save_battery_ram( Auto_File_Writer out ) +{ + RETURN_ERR( out.open() ); + return out->write( emu.impl->sram, emu.impl->sram_size ); +} + +const char * Nes_Emu::load_battery_ram( Auto_File_Reader in ) +{ + RETURN_ERR( in.open() ); + emu.sram_present = true; + return in->read( emu.impl->sram, emu.impl->sram_size ); +} + +void Nes_Emu::load_state( Nes_State_ const& in ) +{ + clear_sound_buf(); + emu.load_state( in ); +} + +void Nes_Emu::load_state( Nes_State const& in ) +{ + loading_state( in ); + load_state( STATIC_CAST(Nes_State_ const&,in) ); +} + +const char * Nes_Emu::load_state( Auto_File_Reader in ) +{ + Nes_State* state = new Nes_State; + state->clear(); //initialize it + CHECK_ALLOC( state ); + const char * err = state->read( in ); + if ( !err ) + load_state( *state ); + delete state; + return err; +} + +const char * Nes_Emu::save_state( Auto_File_Writer out ) const +{ + Nes_State* state = new Nes_State; + CHECK_ALLOC( state ); + save_state( state ); + const char * err = state->write( out ); + delete state; + return err; +} + +void Nes_Emu::write_chr( void const* p, long count, long offset ) +{ + long end = offset + count; + memcpy( (uint8_t*) chr_mem() + offset, p, count ); + emu.ppu.rebuild_chr( offset, end ); +} + +const char * Nes_Emu::set_sample_rate( long rate, class Nes_Buffer* buf ) +{ + extern Multi_Buffer* set_apu( class Nes_Buffer*, Nes_Apu* ); + RETURN_ERR( auto_init() ); + return set_sample_rate( rate, set_apu( buf, &emu.impl->apu ) ); +} + +const char * Nes_Emu::set_sample_rate( long rate, class Nes_Effects_Buffer* buf ) +{ + extern Multi_Buffer* set_apu( class Nes_Effects_Buffer*, Nes_Apu* ); + RETURN_ERR( auto_init() ); + return set_sample_rate( rate, set_apu( buf, &emu.impl->apu ) ); +} + +// Sound + +void Nes_Emu::set_frame_rate( double rate ) +{ + sound_buf->clock_rate( (long) (1789773 / 60.0 * rate) ); +} + +const char * Nes_Emu::set_sample_rate( long rate, Multi_Buffer* new_buf ) +{ + RETURN_ERR( auto_init() ); + emu.impl->apu.volume( 1.0 ); // cancel any previous non-linearity + RETURN_ERR( new_buf->set_sample_rate( rate, 1200 / frame_rate ) ); + sound_buf = new_buf; + sound_buf_changed_count = 0; + if ( new_buf != default_sound_buf ) + { + delete default_sound_buf; + default_sound_buf = NULL; + } + set_frame_rate( frame_rate ); + return 0; +} + +const char * Nes_Emu::set_sample_rate( long rate ) +{ + if ( !default_sound_buf ) + CHECK_ALLOC( default_sound_buf = new Mono_Buffer ); + return set_sample_rate( rate, default_sound_buf ); +} + +void Nes_Emu::set_equalizer( equalizer_t const& eq ) +{ + equalizer_ = eq; + if ( cart() ) + { + blip_eq_t blip_eq( eq.treble, 0, sound_buf->sample_rate() ); + emu.impl->apu.treble_eq( blip_eq ); + emu.mapper->set_treble( blip_eq ); + sound_buf->bass_freq( equalizer_.bass ); + } +} + +void Nes_Emu::enable_sound( bool enabled ) +{ + if ( enabled ) + { + for ( int i = channel_count(); i-- > 0; ) + { + Blip_Buffer* buf = sound_buf->channel( i ).center; + int mapper_index = i - Nes_Apu::osc_count; + if ( mapper_index < 0 ) + emu.impl->apu.osc_output( i, buf ); + else + emu.mapper->set_channel_buf( mapper_index, buf ); + } + } + else + { + emu.impl->apu.output( NULL ); + for ( int i = channel_count() - Nes_Apu::osc_count; i-- > 0; ) + emu.mapper->set_channel_buf( i, NULL ); + } +} + +void Nes_Emu::fade_samples( blip_sample_t* p, int size, int step ) +{ + if ( size >= sound_fade_size ) + { + if ( step < 0 ) + p += size - sound_fade_size; + + int const shift = 15; + int mul = (1 - step) << (shift - 1); + step *= (1 << shift) / sound_fade_size; + + for ( int n = sound_fade_size; n--; ) + { + *p = (*p * mul) >> 15; + ++p; + mul += step; + } + } +} + +long Nes_Emu::read_samples( short* out, long out_size ) +{ + long count = sound_buf->read_samples( out, out_size ); + if ( fade_sound_in ) + { + fade_sound_in = false; + if (out != NULL) + fade_samples( out, count, 1 ); + } + + if ( fade_sound_out ) + { + fade_sound_out = false; + fade_sound_in = true; // next buffer should be faded in + if (out != NULL) + fade_samples( out, count, -1 ); + } + return count; +} + +Nes_Emu::rgb_t const Nes_Emu::nes_colors [color_table_size] = +{ + // generated with nes_ntsc default settings + {102,102,102},{ 0, 42,136},{ 20, 18,168},{ 59, 0,164}, + { 92, 0,126},{110, 0, 64},{108, 7, 0},{ 87, 29, 0}, + { 52, 53, 0},{ 12, 73, 0},{ 0, 82, 0},{ 0, 79, 8}, + { 0, 64, 78},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {174,174,174},{ 21, 95,218},{ 66, 64,254},{118, 39,255}, + {161, 27,205},{184, 30,124},{181, 50, 32},{153, 79, 0}, + {108,110, 0},{ 56,135, 0},{ 13,148, 0},{ 0,144, 50}, + { 0,124,142},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {254,254,254},{100,176,254},{147,144,254},{199,119,254}, + {243,106,254},{254,110,205},{254,130,112},{235,159, 35}, + {189,191, 0},{137,217, 0},{ 93,229, 48},{ 69,225,130}, + { 72,206,223},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0}, + {254,254,254},{193,224,254},{212,211,254},{233,200,254}, + {251,195,254},{254,197,235},{254,205,198},{247,217,166}, + {229,230,149},{208,240,151},{190,245,171},{180,243,205}, + {181,236,243},{184,184,184},{ 0, 0, 0},{ 0, 0, 0}, + + {114, 83, 79},{ 0, 23,113},{ 32, 0,145},{ 71, 0,141}, + {104, 0,103},{122, 0, 41},{120, 0, 0},{ 99, 10, 0}, + { 64, 34, 0},{ 24, 54, 0},{ 0, 63, 0},{ 0, 60, 0}, + { 0, 45, 54},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {190,148,143},{ 37, 69,187},{ 83, 38,228},{134, 13,224}, + {177, 1,174},{200, 4, 92},{198, 24, 1},{170, 53, 0}, + {124, 84, 0},{ 73,109, 0},{ 30,122, 0},{ 6,118, 19}, + { 9, 98,110},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {254,222,215},{122,142,254},{168,110,254},{220, 85,254}, + {254, 72,247},{254, 76,164},{254, 96, 71},{254,125, 0}, + {210,157, 0},{158,183, 0},{114,195, 7},{ 90,191, 89}, + { 93,172,182},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0}, + {254,222,215},{214,190,233},{233,177,250},{254,166,248}, + {254,161,228},{254,163,194},{254,171,157},{254,183,125}, + {250,196,108},{229,206,110},{211,211,130},{201,210,164}, + {203,202,202},{184,184,184},{ 0, 0, 0},{ 0, 0, 0}, + { 75,106, 64},{ 0, 46, 98},{ 0, 22,130},{ 32, 3,126}, + { 65, 0, 88},{ 82, 0, 26},{ 80, 11, 0},{ 59, 34, 0}, + { 24, 58, 0},{ 0, 77, 0},{ 0, 86, 0},{ 0, 83, 0}, + { 0, 68, 39},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {136,180,122},{ 0,101,166},{ 29, 69,208},{ 80, 44,203}, + {123, 32,153},{146, 36, 72},{144, 55, 0},{116, 84, 0}, + { 70,116, 0},{ 19,141, 0},{ 0,153, 0},{ 0,149, 0}, + { 0,130, 90},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {207,254,188},{ 51,183,233},{ 98,151,254},{150,126,254}, + {193,113,220},{217,117,137},{214,137, 45},{186,166, 0}, + {140,198, 0},{ 88,224, 0},{ 44,236, 0},{ 20,232, 63}, + { 23,213,155},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0}, + {207,254,188},{144,231,207},{163,218,224},{184,207,222}, + {201,202,201},{211,204,168},{210,212,130},{198,224, 99}, + {180,237, 81},{159,247, 83},{141,252,104},{131,251,137}, + {132,243,175},{184,184,184},{ 0, 0, 0},{ 0, 0, 0}, + { 83, 83, 55},{ 0, 23, 89},{ 0, 0,121},{ 40, 0,117}, + { 73, 0, 79},{ 90, 0, 17},{ 88, 0, 0},{ 67, 10, 0}, + { 32, 34, 0},{ 0, 53, 0},{ 0, 63, 0},{ 0, 60, 0}, + { 0, 45, 30},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {147,148,110},{ 0, 69,154},{ 40, 38,196},{ 91, 12,191}, + {134, 0,141},{157, 4, 60},{155, 23, 0},{127, 52, 0}, + { 81, 84, 0},{ 30,109, 0},{ 0,121, 0},{ 0,117, 0}, + { 0, 98, 78},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {221,222,173},{ 65,142,217},{112,110,254},{164, 84,255}, + {208, 72,204},{231, 76,122},{229, 95, 29},{200,125, 0}, + {154,157, 0},{102,182, 0},{ 58,195, 0},{ 34,191, 47}, + { 37,171,140},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0}, + {221,222,173},{158,189,191},{177,176,208},{198,166,206}, + {216,161,185},{225,163,152},{224,171,114},{213,183, 83}, + {194,195, 66},{173,206, 68},{155,211, 88},{145,209,122}, + {146,201,159},{184,184,184},{ 0, 0, 0},{ 0, 0, 0}, + { 87, 87,133},{ 0, 26,167},{ 5, 2,198},{ 44, 0,195}, + { 77, 0,157},{ 95, 0, 94},{ 93, 0, 25},{ 71, 14, 0}, + { 36, 38, 0},{ 0, 57, 0},{ 0, 66, 0},{ 0, 63, 38}, + { 0, 49,108},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {153,153,216},{ 0, 74,254},{ 46, 43,254},{ 97, 17,254}, + {140, 5,247},{164, 9,165},{161, 28, 74},{133, 57, 0}, + { 87, 89, 0},{ 36,114, 0},{ 0,126, 10},{ 0,122, 92}, + { 0,103,183},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {229,228,254},{ 74,148,254},{120,116,254},{172, 91,254}, + {216, 78,254},{239, 82,254},{237,102,166},{208,131, 89}, + {162,163, 46},{110,189, 51},{ 66,201,102},{ 42,197,184}, + { 45,178,254},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0}, + {229,228,254},{166,196,254},{185,183,254},{206,172,254}, + {224,167,254},{233,169,254},{232,177,252},{221,189,220}, + {202,202,203},{181,212,205},{163,217,226},{153,216,254}, + {154,208,254},{184,184,184},{ 0, 0, 0},{ 0, 0, 0}, + { 90, 71, 97},{ 0, 11,130},{ 8, 0,162},{ 47, 0,158}, + { 80, 0,120},{ 98, 0, 58},{ 96, 0, 0},{ 74, 0, 0}, + { 39, 22, 0},{ 0, 42, 0},{ 0, 51, 0},{ 0, 48, 2}, + { 0, 33, 72},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {158,132,166},{ 4, 53,210},{ 50, 22,252},{101, 0,247}, + {144, 0,197},{168, 0,116},{165, 7, 25},{137, 36, 0}, + { 91, 68, 0},{ 40, 93, 0},{ 0,105, 0},{ 0,101, 42}, + { 0, 82,134},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {234,201,246},{ 79,121,254},{125, 89,254},{177, 63,254}, + {221, 51,254},{245, 55,195},{242, 74,102},{214,104, 24}, + {167,136, 0},{115,161, 0},{ 71,174, 37},{ 48,170,120}, + { 50,150,213},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0}, + {234,201,246},{171,168,254},{190,155,254},{211,145,254}, + {229,140,254},{239,142,225},{237,150,187},{226,162,156}, + {207,174,139},{186,185,141},{168,190,161},{159,188,195}, + {160,180,232},{184,184,184},{ 0, 0, 0},{ 0, 0, 0}, + { 66, 85, 88},{ 0, 25,121},{ 0, 1,153},{ 23, 0,149}, + { 56, 0,111},{ 74, 0, 49},{ 72, 0, 0},{ 51, 12, 0}, + { 16, 36, 0},{ 0, 55, 0},{ 0, 65, 0},{ 0, 62, 0}, + { 0, 47, 63},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {125,151,154},{ 0, 72,198},{ 17, 40,240},{ 69, 15,235}, + {112, 3,185},{135, 7,104},{132, 26, 12},{104, 55, 0}, + { 59, 87, 0},{ 7,112, 0},{ 0,124, 0},{ 0,120, 30}, + { 0,101,121},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {192,225,230},{ 37,145,254},{ 83,114,254},{135, 88,254}, + {179, 76,254},{202, 80,179},{200, 99, 86},{171,129, 8}, + {125,160, 0},{ 73,186, 0},{ 29,198, 21},{ 5,194,104}, + { 8,175,197},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0}, + {192,225,230},{129,193,248},{148,180,254},{169,170,254}, + {187,165,242},{196,166,209},{195,174,171},{184,186,140}, + {165,199,123},{144,209,125},{126,214,145},{116,213,179}, + {118,205,216},{184,184,184},{ 0, 0, 0},{ 0, 0, 0}, + { 69, 69, 69},{ 0, 16,110},{ 0, 0,142},{ 33, 0,138}, + { 66, 0,100},{ 84, 0, 38},{ 82, 0, 0},{ 60, 3, 0}, + { 25, 27, 0},{ 0, 46, 0},{ 0, 56, 0},{ 0, 53, 0}, + { 0, 38, 51},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {134,134,134},{ 0, 64,187},{ 35, 32,228},{ 86, 7,223}, + {129, 0,174},{153, 0, 92},{150, 18, 1},{122, 47, 0}, + { 76, 79, 0},{ 25,104, 0},{ 0,116, 0},{ 0,112, 19}, + { 0, 93,110},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {207,207,207},{ 60,136,254},{107,104,254},{159, 79,254}, + {203, 66,248},{226, 70,165},{224, 90, 72},{195,119, 0}, + {149,151, 0},{ 97,177, 0},{ 53,189, 8},{ 29,185, 91}, + { 32,166,183},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0}, + {207,207,207},{148,178,229},{166,165,246},{188,155,244}, + {205,150,224},{215,152,190},{214,159,152},{202,171,121}, + {183,184,104},{162,195,106},{145,200,126},{135,198,160}, + {136,190,197},{184,184,184},{ 0, 0, 0},{ 0, 0, 0} +}; + +void Nes_Emu::SaveAudioBufferState() +{ + extra_fade_sound_in = fade_sound_in; + extra_fade_sound_out = fade_sound_out; + extra_sound_buf_changed_count = sound_buf_changed_count; + sound_buf->SaveAudioBufferState(); +} +void Nes_Emu::RestoreAudioBufferState() +{ + fade_sound_in = extra_fade_sound_in; + fade_sound_out = extra_fade_sound_out; + sound_buf_changed_count = extra_sound_buf_changed_count; + sound_buf->RestoreAudioBufferState(); +} diff --git a/core/Nes_Emu.h b/core/Nes_Emu.h new file mode 100644 index 0000000..1631abd --- /dev/null +++ b/core/Nes_Emu.h @@ -0,0 +1,278 @@ + +// NES video game console emulator with snapshot support + +// Nes_Emu 0.7.0 + +#ifndef NES_EMU_H +#define NES_EMU_H + +#include "blargg_common.h" +#include "Multi_Buffer.h" +#include "Nes_Cart.h" +#include "Nes_Core.h" +class Nes_State; + +// Register optional mappers included with Nes_Emu +void register_optional_mappers(); +void register_extra_mappers(); + +extern const char unsupported_mapper []; // returned when cartridge uses unsupported mapper + +class Nes_Emu { +public: + Nes_Emu(); + virtual ~Nes_Emu(); + +// Basic setup + + // Load iNES file into emulator and clear recording + const char * load_ines( Auto_File_Reader ); + + // Set sample rate for sound generation + const char * set_sample_rate( long ); + + // Size and depth of graphics buffer required for rendering. Note that this + // is larger than the actual image, with a temporary area around the edge + // that gets filled with junk. + enum { buffer_width = Nes_Ppu::buffer_width }; + int buffer_height() const { return buffer_height_; } + enum { bits_per_pixel = 8 }; + + // Set graphics buffer to render pixels to. Pixels points to top-left pixel and + // row_bytes is the number of bytes to get to the next line (positive or negative). + void set_pixels( void* pixels, long row_bytes ); + + // Size of image generated in graphics buffer + enum { image_width = 256 }; + enum { image_height = 240 }; + +// Basic emulation + + // Emulate one video frame using joypad1 and joypad2 as input. Afterwards, image + // and sound are available for output using the accessors below. + virtual const char * emulate_frame( int joypad1, int joypad2 = 0 ); + + // Emulate one video frame using joypad1 and joypad2 as input, but skips drawing. + // Afterwards, audio is available for output using the accessors below. + virtual const char * emulate_skip_frame( int joypad1, int joypad2 = 0 ); + + // Maximum size of palette that can be generated + enum { max_palette_size = 256 }; + + // Result of current frame + struct frame_t + { + int joypad_read_count; // number of times joypads were strobed (read) + int burst_phase; // NTSC burst phase for frame (0, 1, or 2) + + int sample_count; // number of samples (always a multiple of chan_count) + int chan_count; // 1: mono, 2: stereo + + int top; // top-left position of image in graphics buffer + enum { left = 8 }; + unsigned char* pixels; // pointer to top-left pixel of image + long pitch; // number of bytes to get to next row of image + + int palette_begin; // first host palette entry, as set by set_palette_range() + int palette_size; // number of entries used for current frame + short palette [max_palette_size]; // [palette_begin to palette_begin+palette_size-1] + }; + frame_t const& frame() const { return *frame_; } + + // Read samples for the current frame. Returns number of samples read into buffer. + // Currently all samples must be read in one call. + virtual long read_samples( short* out, long max_samples ); + +// Additional features + + // Use already-loaded cartridge. Retains pointer, so it must be kept around until + // closed. A cartridge can be shared among multiple emulators. After opening, + // cartridge's CHR data shouldn't be modified since a copy is cached internally. + const char * set_cart( Nes_Cart const* ); + + // Pointer to current cartridge, or NULL if none is loaded + Nes_Cart const* cart() const { return emu.cart; } + + // Free any memory and close cartridge, if one was currently open. A new cartridge + // must be opened before further emulation can take place. + void close(); + + // Emulate powering NES off and then back on. If full_reset is false, emulates + // pressing the reset button only, which doesn't affect memory, otherwise + // emulates powering system off then on. + virtual void reset( bool full_reset = true, bool erase_battery_ram = false ); + + // Number of undefined CPU instructions encountered. Cleared after reset() and + // load_state(). A non-zero value indicates that cartridge is probably + // incompatible. + unsigned long error_count() const { return emu.error_count; } + +// Sound + + // Set sample rate and use a custom sound buffer instead of the default + // mono buffer, i.e. Nes_Buffer, Effects_Buffer, etc.. + const char * set_sample_rate( long rate, Multi_Buffer* ); + + // Adjust effective frame rate by changing how many samples are generated each frame. + // Allows fine tuning of frame rate to improve synchronization. + void set_frame_rate( double rate ); + + // Number of sound channels for current cartridge + int channel_count() const { return channel_count_; } + + // Frequency equalizer parameters + struct equalizer_t { + double treble; // 5.0 = extra-crisp, -200.0 = muffled + long bass; // 0 = deep, 20000 = tinny + }; + + // Current frequency equalization + equalizer_t const& equalizer() const { return equalizer_; } + + // Change frequency equalization + void set_equalizer( equalizer_t const& ); + + // Equalizer presets + static equalizer_t const nes_eq; // NES + static equalizer_t const famicom_eq; // Famicom + static equalizer_t const tv_eq; // TV speaker + static equalizer_t const flat_eq; // Flat EQ + static equalizer_t const crisp_eq; // Crisp EQ (Treble boost) + static equalizer_t const tinny_eq; // Tinny EQ (Like a handheld speaker) + +// File save/load + + // Save emulator state + void save_state( Nes_State* s ) const { emu.save_state( s ); } + const char * save_state( Auto_File_Writer ) const; + + // Load state into emulator + void load_state( Nes_State const& ); + const char * load_state( Auto_File_Reader ); + + // True if current cartridge claims it uses battery-backed memory + bool has_battery_ram() const { return cart()->has_battery_ram(); } + + // Save current battery RAM + const char * save_battery_ram( Auto_File_Writer ); + + // Load battery RAM from file. Best called just after reset() or loading cartridge. + const char * load_battery_ram( Auto_File_Reader ); + +// Graphics + + // Number of frames generated per second + enum { frame_rate = 60 }; + + // Size of fixed NES color table (including the 8 color emphasis modes) + enum { color_table_size = 8 * 64 }; + + // NES color lookup table based on standard NTSC TV decoder. Use nes_ntsc.h to + // generate a palette with custom parameters. + struct rgb_t { unsigned char red, green, blue; }; + static rgb_t const nes_colors [color_table_size]; + + // Hide/show/enhance sprites. Sprite mode does not affect emulation accuracy. + enum sprite_mode_t { + sprites_hidden = 0, + sprites_visible = 8, // limit of 8 sprites per scanline as on NES (default) + sprites_enhanced = 64 // unlimited sprites per scanline (no flickering) + }; + void set_sprite_mode( sprite_mode_t n ) { emu.ppu.sprite_limit = n; } + + // Set range of host palette entries to use in graphics buffer; default uses + // all of them. Begin will be rounded up to next multiple of palette_alignment. + // Use frame().palette_begin to find the adjusted beginning entry used. + enum { palette_alignment = 64 }; + void set_palette_range( int begin, int end = 256 ); + +// Access to emulated memory, for viewer/cheater/debugger + + // CHR + uint8_t const* chr_mem(); + long chr_size() const; + void write_chr( void const*, long count, long offset ); + + // Nametable + uint8_t* nametable_mem() { return emu.ppu.impl->nt_ram; } + long nametable_size() const { return 0x1000; } + + // Built-in 2K memory + enum { low_mem_size = 0x800 }; + uint8_t* low_mem() { return emu.low_mem; } + + // Optional 8K memory + enum { high_mem_size = 0x2000 }; + uint8_t* high_mem() { return emu.impl->sram; } + + // End of public interface +public: + const char * set_sample_rate( long rate, class Nes_Buffer* ); + const char * set_sample_rate( long rate, class Nes_Effects_Buffer* ); + void irq_changed() { emu.irq_changed(); } +private: + + frame_t* frame_; + int buffer_height_; + bool fade_sound_in; + bool fade_sound_out; + virtual const char * init_(); + + virtual void loading_state( Nes_State const& ) { } + void load_state( Nes_State_ const& ); + void save_state( Nes_State_* s ) const { emu.save_state( s ); } + int joypad_read_count() const { return emu.joypad_read_count; } + long timestamp() const { return emu.nes.frame_count; } + void set_timestamp( long t ) { emu.nes.frame_count = t; } + +private: + // noncopyable + Nes_Emu( const Nes_Emu& ); + Nes_Emu& operator = ( const Nes_Emu& ); + + // sound + Multi_Buffer* default_sound_buf; + Multi_Buffer* sound_buf; + unsigned sound_buf_changed_count; + Silent_Buffer silent_buffer; + equalizer_t equalizer_; + int channel_count_; + bool sound_enabled; + void enable_sound( bool ); + void clear_sound_buf(); + void fade_samples( blip_sample_t*, int size, int step ); + + char* host_pixels; + int host_palette_size; + frame_t single_frame; + Nes_Cart private_cart; + Nes_Core emu; // large; keep at end + + bool init_called; + const char * auto_init(); + + bool extra_fade_sound_in; + bool extra_fade_sound_out; + unsigned extra_sound_buf_changed_count; +public: + void SaveAudioBufferState(); + void RestoreAudioBufferState(); +}; + +inline void Nes_Emu::set_pixels( void* p, long n ) +{ + host_pixels = (char*) p + n; + emu.ppu.host_row_bytes = n; +} + +inline uint8_t const* Nes_Emu::chr_mem() +{ + return cart()->chr_size() ? (uint8_t*) cart()->chr() : emu.ppu.impl->chr_ram; +} + +inline long Nes_Emu::chr_size() const +{ + return cart()->chr_size() ? cart()->chr_size() : emu.ppu.chr_addr_size; +} + +#endif diff --git a/core/Nes_File.cpp b/core/Nes_File.cpp new file mode 100644 index 0000000..3846d1b --- /dev/null +++ b/core/Nes_File.cpp @@ -0,0 +1,214 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_File.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// Nes_File_Writer + +Nes_File_Writer::Nes_File_Writer() +{ + write_remain = 0; + depth_ = 0; +} + +Nes_File_Writer::~Nes_File_Writer() +{ +} + +const char * Nes_File_Writer::begin( Auto_File_Writer dw, nes_tag_t tag ) +{ + out = dw; + RETURN_ERR( out.open_comp() ); + return begin_group( tag ); +} + +const char * Nes_File_Writer::begin_group( nes_tag_t tag ) +{ + depth_++; + return write_header( tag, group_begin_size ); +} + +const char * Nes_File_Writer::write_header( nes_tag_t tag, long size ) +{ + nes_block_t h; + h.tag = tag; + h.size = size; + h.swap(); + return out->write( &h, sizeof h ); +} + +const char * Nes_File_Writer::write_block( nes_tag_t tag, void const* data, long size ) +{ + RETURN_ERR( write_block_header( tag, size ) ); + return write( data, size ); +} + +const char * Nes_File_Writer::write_block_header( nes_tag_t tag, long size ) +{ + write_remain = size; + return write_header( tag, size ); +} + +const char *Nes_File_Writer::write( void const* p, long s ) +{ + write_remain -= s; + return out->write( p, s ); +} + +const char * Nes_File_Writer::end() +{ + return end_group(); +} + +const char * Nes_File_Writer::end_group() +{ + depth_--; + return write_header( group_end_tag, 0 ); +} + +// Nes_File_Reader + +Nes_File_Reader::Nes_File_Reader() +{ + h.tag = 0; + h.size = 0; + block_type_ = invalid; + depth_ = -1; +} + +Nes_File_Reader::~Nes_File_Reader() +{ +} + +const char * Nes_File_Reader::read_block_data( void* p, long s ) +{ + long extra = remain(); + if ( s > extra ) + s = extra; + extra -= s; + RETURN_ERR( read( p, s ) ); + if ( extra ) + RETURN_ERR( skip( extra ) ); + return 0; +} + +const char * Nes_File_Reader::begin( Auto_File_Reader dr ) +{ + RETURN_ERR( dr.open() ); + in = dr; + RETURN_ERR( read_header() ); + if ( block_type() != group_begin ) + return "File is wrong type"; + return enter_group(); +} + +const char * Nes_File_Reader::read_header() +{ + RETURN_ERR( in->read( &h, sizeof h ) ); + h.swap(); + block_type_ = data_block; + if ( h.size == group_begin_size ) + { + block_type_ = group_begin; + h.size = 0; + } + if ( (long) h.tag == group_end_tag ) + { + block_type_ = group_end; + h.tag = 0; + } + set_remain( h.size ); + return 0; +} + +const char * Nes_File_Reader::next_block() +{ + switch ( block_type() ) + { + case group_end: + return "Tried to go past end of blocks"; + + case group_begin: { + int d = 1; + do + { + RETURN_ERR( skip( h.size ) ); + RETURN_ERR( read_header() ); + if ( block_type() == group_begin ) + d++; + if ( block_type() == group_end ) + d--; + } + while ( d > 0); + break; + } + + case data_block: + RETURN_ERR( skip( h.size ) ); + break; + + case invalid: + break; + } + return read_header(); +} + +const char * Nes_File_Reader::enter_group() +{ + block_type_ = invalid; // cause next_block() not to skip group + depth_++; + return 0; +} + +const char * Nes_File_Reader::exit_group() +{ + int d = 1; + while ( true ) + { + if ( block_type() == group_end ) + d--; + if ( block_type() == group_begin ) + d++; + if ( d == 0 ) + break; + RETURN_ERR( skip( h.size ) ); + RETURN_ERR( read_header() ); + } + + block_type_ = invalid; // cause next_block() to read past end block + depth_--; + return 0; +} + +const char * Nes_File_Reader::skip_v( int s ) +{ + if ( (unsigned long) s > h.size ) + return "Tried to skip past end of data"; + h.size -= s; + set_remain( h.size ); + return in->skip( s ); +} + +const char * Nes_File_Reader::read_v( void* p, int n ) +{ + if ( (unsigned long) n > h.size ) + n = h.size; + h.size -= n; + set_remain( h.size ); + return in->read( p, n ); +} diff --git a/core/Nes_File.h b/core/Nes_File.h new file mode 100644 index 0000000..ad34b13 --- /dev/null +++ b/core/Nes_File.h @@ -0,0 +1,127 @@ + +// NES block-oriented file access + +// Nes_Emu 0.7.0 + +#ifndef NES_FILE_H +#define NES_FILE_H + +#include "blargg_common.h" +#include "abstract_file.h" +#include "nes_data.h" + +// Writes a structured file +class Nes_File_Writer : public Data_Writer { +public: + Nes_File_Writer(); + ~Nes_File_Writer(); + + // Begin writing file with specified signature tag + const char * begin( Auto_File_Writer, nes_tag_t ); + + // Begin tagged group + const char * begin_group( nes_tag_t ); + + // Write tagged block + const char * write_block( nes_tag_t, void const*, long size ); + + // Write tagged block header. 'Size' bytes must be written before next block. + const char * write_block_header( nes_tag_t, long size ); + + // Write data to current block + const char *write( void const*, long ); + + // End tagged group + const char * end_group(); + + // End file + const char * end(); + +private: + Auto_File_Writer out; + long write_remain; + int depth_; + const char * write_header( nes_tag_t tag, long size ); +}; + +// Reads a structured file +class Nes_File_Reader : public Data_Reader { +public: + Nes_File_Reader(); + ~Nes_File_Reader(); + + // If true, verify checksums of any blocks that have them + void enable_checksums( bool = true ); + + // Begin reading file. Until next_block() is called, block_tag() yields tag for file. + const char * begin( Auto_File_Reader ); + + // Read header of next block in current group + const char * next_block(); + + // Type of current block + enum block_type_t { + data_block, + group_begin, + group_end, + invalid + }; + block_type_t block_type() const { return block_type_; } + + // Tag of current block + nes_tag_t block_tag() const { return h.tag; } + + // Read at most s bytes from block and skip any remaining bytes + const char * read_block_data( void*, long s ); + + // Read at most 's' bytes from current block and return number of bytes actually read + virtual const char * read_v( void*, int n ); + + // Skip 's' bytes in current block + virtual const char * skip_v( int s ); + + // Read first sub-block of current group block + const char * enter_group(); + + // Skip past current group + const char * exit_group(); + + // Current depth, where 0 is top-level in file and higher is deeper + int depth() const { return depth_; } + + // True if all data has been read + bool done() const { return depth() == 0 && block_type() == group_end; } +private: + Auto_File_Reader in; + nes_block_t h; + block_type_t block_type_; + int depth_; + const char * read_header(); +}; + +template +inline const char * read_nes_state( Nes_File_Reader& in, T* out ) +{ + const char * err = in.read_block_data( out, sizeof *out ); + out->swap(); + return err; +} + +template +inline const char * write_nes_state( Nes_File_Writer& out, T& in ) +{ + in.swap(); + const char * err = out.write_block( in.tag, &in, sizeof in ); + in.swap(); + return err; +} + +template +inline const char * write_nes_state( Nes_File_Writer& out, const T& in ) +{ + T copy = in; + copy.swap(); + return out.write_block( copy.tag, ©, sizeof copy ); +} + +#endif diff --git a/core/Nes_Fme7_Apu.cpp b/core/Nes_Fme7_Apu.cpp new file mode 100644 index 0000000..eb885f1 --- /dev/null +++ b/core/Nes_Fme7_Apu.cpp @@ -0,0 +1,110 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Fme7_Apu.h" + +#include + +/* Copyright (C) 2003-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +void Nes_Fme7_Apu::reset() +{ + last_time = 0; + + for ( int i = 0; i < osc_count; i++ ) + oscs [i].last_amp = 0; + + fme7_apu_state_t* state = this; + memset( state, 0, sizeof *state ); +} + +unsigned char Nes_Fme7_Apu::amp_table [16] = +{ + #define ENTRY( n ) (unsigned char) (n * amp_range + 0.5) + ENTRY(0.0000), ENTRY(0.0078), ENTRY(0.0110), ENTRY(0.0156), + ENTRY(0.0221), ENTRY(0.0312), ENTRY(0.0441), ENTRY(0.0624), + ENTRY(0.0883), ENTRY(0.1249), ENTRY(0.1766), ENTRY(0.2498), + ENTRY(0.3534), ENTRY(0.4998), ENTRY(0.7070), ENTRY(1.0000) + #undef ENTRY +}; + +void Nes_Fme7_Apu::run_until( blip_time_t end_time ) +{ + for ( int index = 0; index < osc_count; index++ ) + { + int mode = regs [7] >> index; + int vol_mode = regs [010 + index]; + int volume = amp_table [vol_mode & 0x0f]; + + if ( !oscs [index].output ) + continue; + + if ( (mode & 001) | (vol_mode & 0x10) ) + volume = 0; // noise and envelope aren't supported + + // period + int const period_factor = 16; + unsigned period = (regs [index * 2 + 1] & 0x0f) * 0x100 * period_factor + + regs [index * 2] * period_factor; + if ( period < 50 ) // around 22 kHz + { + volume = 0; + if ( !period ) // on my AY-3-8910A, period doesn't have extra one added + period = period_factor; + } + + // current amplitude + int amp = volume; + if ( !phases [index] ) + amp = 0; + int delta = amp - oscs [index].last_amp; + if ( delta ) + { + oscs [index].last_amp = amp; + synth.offset( last_time, delta, oscs [index].output ); + } + + blip_time_t time = last_time + delays [index]; + if ( time < end_time ) + { + Blip_Buffer* const osc_output = oscs [index].output; + int delta = amp * 2 - volume; + + if ( volume ) + { + do + { + delta = -delta; + synth.offset_inline( time, delta, osc_output ); + time += period; + } + while ( time < end_time ); + + oscs [index].last_amp = (delta + volume) >> 1; + phases [index] = (delta > 0); + } + else + { + // maintain phase when silent + int count = (end_time - time + period - 1) / period; + phases [index] ^= count & 1; + time += (long) count * period; + } + } + + delays [index] = time - end_time; + } + + last_time = end_time; +} diff --git a/core/Nes_Fme7_Apu.h b/core/Nes_Fme7_Apu.h new file mode 100644 index 0000000..79d714b --- /dev/null +++ b/core/Nes_Fme7_Apu.h @@ -0,0 +1,136 @@ + +// Sunsoft FME-7 sound emulator + +// Nes_Emu 0.7.0 + +#ifndef NES_FME7_APU_H +#define NES_FME7_APU_H + +#include +#include "blargg_common.h" +#include "Blip_Buffer.h" + +struct fme7_apu_state_t +{ + enum { reg_count = 14 }; + uint8_t regs [reg_count]; + uint8_t phases [3]; // 0 or 1 + uint8_t latch; + uint16_t delays [3]; // a, b, c +}; +BOOST_STATIC_ASSERT( sizeof (fme7_apu_state_t) == 24 ); + +class Nes_Fme7_Apu : private fme7_apu_state_t { +public: + Nes_Fme7_Apu(); + + // See Nes_Apu.h for reference + void reset(); + void volume( double ); + void treble_eq( blip_eq_t const& ); + void output( Blip_Buffer* ); + enum { osc_count = 3 }; + void osc_output( int index, Blip_Buffer* ); + void end_frame( blip_time_t ); + void save_state( fme7_apu_state_t* ) const; + void load_state( fme7_apu_state_t const& ); + + // Mask and addresses of registers + enum { addr_mask = 0xe000 }; + enum { data_addr = 0xe000 }; + enum { latch_addr = 0xc000 }; + + // (addr & addr_mask) == latch_addr + void write_latch( int ); + + // (addr & addr_mask) == data_addr + void write_data( blip_time_t, int data ); + + // End of public interface +private: + // noncopyable + Nes_Fme7_Apu( const Nes_Fme7_Apu& ); + Nes_Fme7_Apu& operator = ( const Nes_Fme7_Apu& ); + + static unsigned char amp_table [16]; + + struct { + Blip_Buffer* output; + int last_amp; + } oscs [osc_count]; + blip_time_t last_time; + + enum { amp_range = 192 }; // can be any value; this gives best error/quality tradeoff + Blip_Synth synth; + + void run_until( blip_time_t ); +}; + +inline void Nes_Fme7_Apu::volume( double v ) +{ + synth.volume( 0.38 / amp_range * v ); // to do: fine-tune +} + +inline void Nes_Fme7_Apu::treble_eq( blip_eq_t const& eq ) +{ + synth.treble_eq( eq ); +} + +inline void Nes_Fme7_Apu::osc_output( int i, Blip_Buffer* buf ) +{ + oscs [i].output = buf; +} + +inline void Nes_Fme7_Apu::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buf ); +} + +inline Nes_Fme7_Apu::Nes_Fme7_Apu() +{ + output( NULL ); + volume( 1.0 ); + reset(); +} + +inline void Nes_Fme7_Apu::write_latch( int data ) { latch = data; } + +inline void Nes_Fme7_Apu::write_data( blip_time_t time, int data ) +{ + if ( (unsigned) latch >= reg_count ) + { + #ifdef dprintf + dprintf( "FME7 write to %02X (past end of sound registers)\n", (int) latch ); + #endif + return; + } + + run_until( time ); + regs [latch] = data; +} + +inline void Nes_Fme7_Apu::end_frame( blip_time_t time ) +{ + if ( time > last_time ) + run_until( time ); + + last_time -= time; +} + +inline void Nes_Fme7_Apu::save_state( fme7_apu_state_t* out ) const +{ + *out = *this; +} + +inline void Nes_Fme7_Apu::load_state( fme7_apu_state_t const& in ) +{ + reset(); + fme7_apu_state_t* state = this; + *state = in; + + //Run sound channels for 0 cycles for clean audio after loading state + run_until(last_time); +} + +#endif diff --git a/core/Nes_Mapper.cpp b/core/Nes_Mapper.cpp new file mode 100644 index 0000000..051535a --- /dev/null +++ b/core/Nes_Mapper.cpp @@ -0,0 +1,302 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +#include +#include +#include "Nes_Core.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +/* + New mapping distribution by Sergio Martin (eien86) + https://github.com/SergioMartin86/jaffarPlus +*/ +#include "mappers/mapper000.hpp" +#include "mappers/mapper001.hpp" +#include "mappers/mapper002.hpp" +#include "mappers/mapper003.hpp" +#include "mappers/mapper004.hpp" +#include "mappers/mapper005.hpp" +#include "mappers/mapper007.hpp" +#include "mappers/mapper009.hpp" +#include "mappers/mapper010.hpp" +#include "mappers/mapper011.hpp" +#include "mappers/mapper015.hpp" +#include "mappers/mapper019.hpp" +#include "mappers/mapper021.hpp" +#include "mappers/mapper022.hpp" +#include "mappers/mapper023.hpp" +#include "mappers/mapper024.hpp" +#include "mappers/mapper025.hpp" +#include "mappers/mapper026.hpp" +#include "mappers/mapper030.hpp" +#include "mappers/mapper032.hpp" +#include "mappers/mapper033.hpp" +#include "mappers/mapper034.hpp" +#include "mappers/mapper060.hpp" +#include "mappers/mapper066.hpp" +#include "mappers/mapper069.hpp" +#include "mappers/mapper070.hpp" +#include "mappers/mapper071.hpp" +#include "mappers/mapper073.hpp" +#include "mappers/mapper075.hpp" +#include "mappers/mapper078.hpp" +#include "mappers/mapper079.hpp" +#include "mappers/mapper085.hpp" +#include "mappers/mapper086.hpp" +#include "mappers/mapper087.hpp" +#include "mappers/mapper088.hpp" +#include "mappers/mapper089.hpp" +#include "mappers/mapper093.hpp" +#include "mappers/mapper094.hpp" +#include "mappers/mapper097.hpp" +#include "mappers/mapper113.hpp" +#include "mappers/mapper140.hpp" +#include "mappers/mapper152.hpp" +#include "mappers/mapper154.hpp" +#include "mappers/mapper156.hpp" +#include "mappers/mapper180.hpp" +#include "mappers/mapper184.hpp" +#include "mappers/mapper190.hpp" +#include "mappers/mapper193.hpp" +#include "mappers/mapper206.hpp" +#include "mappers/mapper207.hpp" +#include "mappers/mapper232.hpp" +#include "mappers/mapper240.hpp" +#include "mappers/mapper241.hpp" +#include "mappers/mapper244.hpp" +#include "mappers/mapper246.hpp" + +Nes_Mapper::Nes_Mapper() +{ + emu_ = NULL; + static char c; + state = &c; // TODO: state must not be null? + state_size = 0; +} + +Nes_Mapper::~Nes_Mapper() +{ +} + +// Sets mirroring, maps first 8K CHR in, first and last 16K of PRG, +// intercepts writes to upper half of memory, and clears registered state. +void Nes_Mapper::default_reset_state() +{ + int mirroring = cart_->mirroring(); + if ( mirroring & 8 ) + mirror_full(); + else if ( mirroring & 1 ) + mirror_vert(); + else + mirror_horiz(); + + set_chr_bank( 0, bank_8k, 0 ); + + set_prg_bank( 0x8000, bank_16k, 0 ); + set_prg_bank( 0xC000, bank_16k, last_bank ); + + intercept_writes( 0x8000, 0x8000 ); + + memset( state, 0, state_size ); +} + +void Nes_Mapper::reset() +{ + default_reset_state(); + reset_state(); + apply_mapping(); +} + +void mapper_state_t::write( const void* p, unsigned long s ) +{ + size = s; + memcpy( data, p, s ); +} + +int mapper_state_t::read( void* p, unsigned long s ) const +{ + if ( (long) s > size ) + s = size; + memcpy( p, data, s ); + return s; +} + +void Nes_Mapper::save_state( mapper_state_t& out ) +{ + out.write( state, state_size ); +} + +void Nes_Mapper::load_state( mapper_state_t const& in ) +{ + default_reset_state(); + read_state( in ); + apply_mapping(); +} + +void Nes_Mapper::read_state( mapper_state_t const& in ) +{ + memset( state, 0, state_size ); + in.read( state, state_size ); + apply_mapping(); +} + +// Timing + +void Nes_Mapper::irq_changed() { emu_->irq_changed(); } + +nes_time_t Nes_Mapper::next_irq( nes_time_t ) { return no_irq; } + +void Nes_Mapper::a12_clocked() { } + +void Nes_Mapper::run_until( nes_time_t ) { } + +void Nes_Mapper::end_frame( nes_time_t ) { } + +bool Nes_Mapper::ppu_enabled() const { return emu().ppu.w2001 & 0x08; } + +// Sound + +int Nes_Mapper::channel_count() const { return 0; } + +void Nes_Mapper::set_channel_buf( int, Blip_Buffer* ) { } + +void Nes_Mapper::set_treble( blip_eq_t const& ) { } + +// Memory mapping + +void Nes_Mapper::set_prg_bank( nes_addr_t addr, bank_size_t bs, int bank ) +{ + int bank_size = 1 << bs; + + int bank_count = cart_->prg_size() >> bs; + if ( bank < 0 ) + bank += bank_count; + + if ( bank >= bank_count ) + bank %= bank_count; + + emu().map_code( addr, bank_size, cart_->prg() + (bank << bs) ); + + if ( unsigned (addr - 0x6000) < 0x2000 ) + emu().enable_prg_6000(); +} + +void Nes_Mapper::set_chr_bank( nes_addr_t addr, bank_size_t bs, int bank ) +{ + emu().ppu.render_until( emu().clock() ); + emu().ppu.set_chr_bank( addr, 1 << bs, bank << bs ); +} + +void Nes_Mapper::set_chr_bank_ex( nes_addr_t addr, bank_size_t bs, int bank ) +{ + emu().ppu.render_until( emu().clock() ); + emu().ppu.set_chr_bank_ex( addr, 1 << bs, bank << bs ); +} + +void Nes_Mapper::mirror_manual( int page0, int page1, int page2, int page3 ) +{ + emu().ppu.render_bg_until( emu().clock() ); + emu().ppu.set_nt_banks( page0, page1, page2, page3 ); +} + +#ifndef NDEBUG +int Nes_Mapper::handle_bus_conflict( nes_addr_t addr, int data ) +{ + return data; +} +#endif + + +Nes_Mapper* Nes_Mapper::create( Nes_Cart const* cart, Nes_Core* emu ) +{ + // Getting cartdrige mapper code + auto mapperCode = cart->mapper_code(); + + // Storage for the mapper, NULL by default + Nes_Mapper* mapper = NULL; + + // Now checking if the detected mapper code is supported + if (mapperCode == 0) mapper = new Mapper000(); + if (mapperCode == 1) mapper = new Mapper001(); + if (mapperCode == 2) mapper = new Mapper002(); + if (mapperCode == 3) mapper = new Mapper003(); + if (mapperCode == 4) mapper = new Mapper004(); + if (mapperCode == 5) mapper = new Mapper005(); + if (mapperCode == 7) mapper = new Mapper007(); + if (mapperCode == 9) mapper = new Mapper009(); + if (mapperCode == 10) mapper = new Mapper010(); + if (mapperCode == 11) mapper = new Mapper011(); + if (mapperCode == 15) mapper = new Mapper015(); + if (mapperCode == 19) mapper = new Mapper019(); + if (mapperCode == 21) mapper = new Mapper021(); + if (mapperCode == 22) mapper = new Mapper022(); + if (mapperCode == 23) mapper = new Mapper023(); + if (mapperCode == 24) mapper = new Mapper024(); + if (mapperCode == 25) mapper = new Mapper025(); + if (mapperCode == 26) mapper = new Mapper026(); + if (mapperCode == 30) mapper = new Mapper030(); + if (mapperCode == 32) mapper = new Mapper032(); + if (mapperCode == 33) mapper = new Mapper033(); + if (mapperCode == 34) mapper = new Mapper034(); + if (mapperCode == 60) mapper = new Mapper060(); + if (mapperCode == 66) mapper = new Mapper066(); + if (mapperCode == 69) mapper = new Mapper069(); + if (mapperCode == 70) mapper = new Mapper070(); + if (mapperCode == 71) mapper = new Mapper071(); + if (mapperCode == 73) mapper = new Mapper073(); + if (mapperCode == 75) mapper = new Mapper075(); + if (mapperCode == 78) mapper = new Mapper078(); + if (mapperCode == 79) mapper = new Mapper079(); + if (mapperCode == 85) mapper = new Mapper085(); + if (mapperCode == 86) mapper = new Mapper086(); + if (mapperCode == 87) mapper = new Mapper087(); + if (mapperCode == 88) mapper = new Mapper088(); + if (mapperCode == 89) mapper = new Mapper089(); + if (mapperCode == 93) mapper = new Mapper093(); + if (mapperCode == 94) mapper = new Mapper094(); + if (mapperCode == 97) mapper = new Mapper097(); + if (mapperCode == 113) mapper = new Mapper113(); + if (mapperCode == 140) mapper = new Mapper140(); + if (mapperCode == 152) mapper = new Mapper152(); + if (mapperCode == 154) mapper = new Mapper154(); + if (mapperCode == 156) mapper = new Mapper156(); + if (mapperCode == 180) mapper = new Mapper180(); + if (mapperCode == 184) mapper = new Mapper184(); + if (mapperCode == 190) mapper = new Mapper190(); + if (mapperCode == 193) mapper = new Mapper193(); + if (mapperCode == 206) mapper = new Mapper206(); + if (mapperCode == 207) mapper = new Mapper207(); + if (mapperCode == 232) mapper = new Mapper232(); + if (mapperCode == 240) mapper = new Mapper240(); + if (mapperCode == 241) mapper = new Mapper241(); + if (mapperCode == 244) mapper = new Mapper244(); + if (mapperCode == 246) mapper = new Mapper246(); + + // If no mapper was found, return null (error) now + if (mapper == NULL) + { + fprintf(stderr, "Could not find mapper for code: %u\n", mapperCode); + return NULL; + } + + // Assigning backwards pointers to cartdrige and emulator now + mapper->cart_ = cart; + mapper->emu_ = emu; + + // Returning successfully created mapper + return mapper; +} diff --git a/core/Nes_Mapper.h b/core/Nes_Mapper.h new file mode 100644 index 0000000..45e949c --- /dev/null +++ b/core/Nes_Mapper.h @@ -0,0 +1,213 @@ + +// NES mapper interface + +// Nes_Emu 0.7.0 + +#ifndef NES_MAPPER +#define NES_MAPPER + +#include "Nes_Cart.h" +#include "Nes_Cpu.h" +#include "nes_data.h" +#include "Nes_Core.h" +class Blip_Buffer; +class blip_eq_t; +class Nes_Core; + +class Nes_Mapper { +public: + // Register function that creates mapper for given code. + typedef Nes_Mapper* (*creator_func_t)(); + static void register_mapper( int code, creator_func_t ); + + // Register optional mappers included with Nes_Emu + void register_optional_mappers(); + + // Create mapper appropriate for cartridge. Returns NULL if it uses unsupported mapper. + static Nes_Mapper* create( Nes_Cart const*, Nes_Core* ); + + virtual ~Nes_Mapper(); + + // Reset mapper to power-up state. + virtual void reset(); + + // Save snapshot of mapper state. Default saves registered state. + virtual void save_state( mapper_state_t& ); + + // Resets mapper, loads state, then applies it + virtual void load_state( mapper_state_t const& ); + +// I/O + + // Read from memory + virtual int read( nes_time_t, nes_addr_t ); + + // Write to memory + virtual void write( nes_time_t, nes_addr_t, int data ) = 0; + + // Write to memory below 0x8000 (returns false if mapper didn't handle write) + virtual bool write_intercepted( nes_time_t, nes_addr_t, int data ); + +// Timing + + // Time returned when current mapper state won't ever cause an IRQ + enum { no_irq = LONG_MAX / 2 }; + + // Time next IRQ will occur at + virtual nes_time_t next_irq( nes_time_t present ); + + // Run mapper until given time + virtual void run_until( nes_time_t ); + + // End video frame of given length + virtual void end_frame( nes_time_t length ); + +// Sound + + // Number of sound channels + virtual int channel_count() const; + + // Set sound buffer for channel to output to, or NULL to silence channel. + virtual void set_channel_buf( int index, Blip_Buffer* ); + + // Set treble equalization + virtual void set_treble( blip_eq_t const& ); + +// Misc + + // Called when bit 12 of PPU's VRAM address changes from 0 to 1 due to + // $2006 and $2007 accesses (but not due to PPU scanline rendering). + virtual void a12_clocked(); + +protected: + // Services provided for derived mapper classes + Nes_Mapper(); + + // Register state data to automatically save and load. Be sure the binary + // layout is suitable for use in a file, including any byte-order issues. + // Automatically cleared to zero by default reset(). + void register_state( void*, unsigned ); + + // Enable 8K of RAM at 0x6000-0x7FFF, optionally read-only. + void enable_sram( bool enabled = true, bool read_only = false ); + + // Cause CPU writes within given address range to call mapper's write() function. + // Might map a larger address range, which the mapper can ignore and pass to + // Nes_Mapper::write(). The range 0x8000-0xffff is always intercepted by the mapper. + void intercept_writes( nes_addr_t addr, unsigned size ); + + // Cause CPU reads within given address range to call mapper's read() function. + // Might map a larger address range, which the mapper can ignore and pass to + // Nes_Mapper::read(). CPU opcode/operand reads and low-memory reads always + // go directly to memory and cannot be intercepted. + void intercept_reads( nes_addr_t addr, unsigned size ); + + // Bank sizes for mapping + enum bank_size_t { // 1 << bank_Xk = X * 1024 + bank_1k = 10, + bank_2k = 11, + bank_4k = 12, + bank_8k = 13, + bank_16k = 14, + bank_32k = 15 + }; + + // Index of last PRG/CHR bank. Last_bank selects last bank, last_bank - 1 + // selects next-to-last bank, etc. + enum { last_bank = -1 }; + + // Map 'size' bytes from 'PRG + bank * size' to CPU address space starting at 'addr' + void set_prg_bank( nes_addr_t addr, bank_size_t size, int bank ); + + // Map 'size' bytes from 'CHR + bank * size' to PPU address space starting at 'addr' + void set_chr_bank( nes_addr_t addr, bank_size_t size, int bank ); + void set_chr_bank_ex( nes_addr_t addr, bank_size_t size, int bank ); + + // Set PPU mirroring. All mappings implemented using mirror_manual(). + void mirror_manual( int page0, int page1, int page2, int page3 ); + void mirror_single( int page ); + void mirror_horiz( int page = 0 ); + void mirror_vert( int page = 0 ); + void mirror_full(); + + // True if PPU rendering is enabled. Some mappers watch PPU memory accesses to determine + // when scanlines occur, and can only do this when rendering is enabled. + bool ppu_enabled() const; + + // Cartridge being emulated + Nes_Cart const& cart() const { return *cart_; } + + // Must be called when next_irq()'s return value is earlier than previous, + // current CPU run can be stopped earlier. Best to call whenever time may + // have changed (no performance impact if called even when time didn't change). + void irq_changed(); + + // Handle data written to mapper that doesn't handle bus conflict arising due to + // PRG also reading data. Returns data that mapper should act as if were + // written. Currently always returns 'data' and just checks that data written is + // the same as byte in PRG at same address and writes debug message if it doesn't. + int handle_bus_conflict( nes_addr_t addr, int data ); + + // Reference to emulator that uses this mapper. + Nes_Core& emu() const { return *emu_; } + +protected: + // Services derived classes provide + + // Read state from snapshot. Default reads data into registered state, then calls + // apply_mapping(). + virtual void read_state( mapper_state_t const& ); + + // Apply current mapping state to hardware. Called after reading mapper state + // from a snapshot. + virtual void apply_mapping() = 0; + + // Called by default reset() before apply_mapping() is called. + virtual void reset_state() { } + + // End of general interface +private: + Nes_Core* emu_; + void* state; + unsigned state_size; + Nes_Cart const* cart_; + + void default_reset_state(); +}; + + +#ifdef NDEBUG +inline int Nes_Mapper::handle_bus_conflict( nes_addr_t addr, int data ) { return data; } +#endif + +inline void Nes_Mapper::mirror_horiz( int p ) { mirror_manual( p, p, p ^ 1, p ^ 1 ); } +inline void Nes_Mapper::mirror_vert( int p ) { mirror_manual( p, p ^ 1, p, p ^ 1 ); } +inline void Nes_Mapper::mirror_single( int p ) { mirror_manual( p, p, p, p ); } +inline void Nes_Mapper::mirror_full() { mirror_manual( 0, 1, 2, 3 ); } + +inline void Nes_Mapper::register_state( void* p, unsigned s ) +{ + state = p; + state_size = s; +} + +inline bool Nes_Mapper::write_intercepted( nes_time_t, nes_addr_t, int ) { return false; } + +inline int Nes_Mapper::read( nes_time_t, nes_addr_t ) { return -1; } // signal to caller + +inline void Nes_Mapper::intercept_reads( nes_addr_t addr, unsigned size ) +{ + emu().add_mapper_intercept( addr, size, true, false ); +} + +inline void Nes_Mapper::intercept_writes( nes_addr_t addr, unsigned size ) +{ + emu().add_mapper_intercept( addr, size, false, true ); +} + +inline void Nes_Mapper::enable_sram( bool enabled, bool read_only ) +{ + emu_->enable_sram( enabled, read_only ); +} + +#endif diff --git a/core/Nes_Namco_Apu.cpp b/core/Nes_Namco_Apu.cpp new file mode 100644 index 0000000..9839f1e --- /dev/null +++ b/core/Nes_Namco_Apu.cpp @@ -0,0 +1,180 @@ + +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ + +#include "Nes_Namco_Apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +Nes_Namco_Apu::Nes_Namco_Apu() +{ + output( NULL ); + volume( 1.0 ); + reset(); +} + +Nes_Namco_Apu::~Nes_Namco_Apu() +{ +} + +void Nes_Namco_Apu::reset() +{ + last_time = 0; + addr_reg = 0; + + int i; + for ( i = 0; i < reg_count; i++ ) + reg [i] = 0; + + for ( i = 0; i < osc_count; i++ ) + { + Namco_Osc& osc = oscs [i]; + osc.delay = 0; + osc.last_amp = 0; + osc.wave_pos = 0; + } +} + +void Nes_Namco_Apu::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buf ); +} + +/* +void Nes_Namco_Apu::reflect_state( Tagged_Data& data ) +{ + reflect_int16( data, 'ADDR', &addr_reg ); + + static const char hex [17] = "0123456789ABCDEF"; + int i; + for ( i = 0; i < reg_count; i++ ) + reflect_int16( data, 'RG\0\0' + hex [i >> 4] * 0x100 + hex [i & 15], ® [i] ); + + for ( i = 0; i < osc_count; i++ ) + { + reflect_int32( data, 'DLY0' + i, &oscs [i].delay ); + reflect_int16( data, 'POS0' + i, &oscs [i].wave_pos ); + } +} +*/ + +void Nes_Namco_Apu::end_frame( nes_time_t time ) +{ + if ( time > last_time ) + run_until( time ); + + last_time -= time; +} + +void Nes_Namco_Apu::run_until( nes_time_t nes_end_time ) +{ + int active_oscs = (reg [0x7F] >> 4 & 7) + 1; + for ( int i = osc_count - active_oscs; i < osc_count; i++ ) + { + Namco_Osc& osc = oscs [i]; + Blip_Buffer* output = osc.output; + if ( !output ) + continue; + + blip_resampled_time_t time = + output->resampled_time( last_time ) + osc.delay; + blip_resampled_time_t end_time = output->resampled_time( nes_end_time ); + osc.delay = 0; + if ( time < end_time ) + { + const uint8_t* osc_reg = ® [i * 8 + 0x40]; + if ( !(osc_reg [4] & 0xE0) ) + continue; + + int volume = osc_reg [7] & 15; + if ( !volume ) + continue; + + long freq = (osc_reg [4] & 3) * 0x10000 + osc_reg [2] * 0x100L + osc_reg [0]; + if ( freq < 64 * active_oscs ) + continue; // prevent low frequencies from excessively delaying freq changes + blip_resampled_time_t period = + output->resampled_duration( 983040 ) / freq * active_oscs; + + int wave_size = 32 - (osc_reg [4] >> 2 & 7) * 4; + if ( !wave_size ) + continue; + + int last_amp = osc.last_amp; + int wave_pos = osc.wave_pos; + + do + { + // read wave sample + int addr = wave_pos + osc_reg [6]; + int sample = reg [addr >> 1] >> (addr << 2 & 4); + wave_pos++; + sample = (sample & 15) * volume; + + // output impulse if amplitude changed + int delta = sample - last_amp; + if ( delta ) + { + last_amp = sample; + synth.offset_resampled( time, delta, output ); + } + + // next sample + time += period; + if ( wave_pos >= wave_size ) + wave_pos = 0; + } + while ( time < end_time ); + + osc.wave_pos = wave_pos; + osc.last_amp = last_amp; + } + osc.delay = time - end_time; + } + + last_time = nes_end_time; +} + +void Nes_Namco_Apu::save_state( namco_state_t* out ) const +{ + out->addr = addr_reg; + for ( int r = 0; r < reg_count; r++ ) + out->regs [ r ] = reg [ r ]; + + for ( int i = 0; i < osc_count; i++ ) + { + Namco_Osc const& osc = oscs [ i ]; + + out->delays [ i ] = osc.delay; + out->positions [ i ] = osc.wave_pos; + } +} + +void Nes_Namco_Apu::load_state( namco_state_t const& in ) +{ + reset(); + addr_reg = in.addr; + for ( int r = 0; r < reg_count; r++ ) + reg [ r ] = in.regs [ r ]; + + for ( int i = 0; i < osc_count; i++ ) + { + Namco_Osc& osc = oscs [ i ]; + + osc.delay = in.delays [ i ]; + osc.wave_pos = in.positions [ i ]; + } + + run_until( last_time ); +} diff --git a/core/Nes_Namco_Apu.h b/core/Nes_Namco_Apu.h new file mode 100644 index 0000000..6f793f0 --- /dev/null +++ b/core/Nes_Namco_Apu.h @@ -0,0 +1,104 @@ + +// Namco 106 sound chip emulator + +// Nes_Snd_Emu 0.1.7 + +#ifndef NES_NAMCO_APU_H +#define NES_NAMCO_APU_H + +#include +#include "Nes_Apu.h" + +struct namco_state_t; + +class Nes_Namco_Apu { +public: + Nes_Namco_Apu(); + ~Nes_Namco_Apu(); + + // See Nes_Apu.h for reference. + void volume( double ); + void treble_eq( const blip_eq_t& ); + void output( Blip_Buffer* ); + enum { osc_count = 8 }; + void osc_output( int index, Blip_Buffer* ); + void reset(); + void end_frame( nes_time_t ); + + // Read/write data register is at 0x4800 + enum { data_reg_addr = 0x4800 }; + void write_data( nes_time_t, int ); + int read_data(); + + // Write-only address register is at 0xF800 + enum { addr_reg_addr = 0xF800 }; + void write_addr( int ); + + // to do: implement save/restore + void save_state( namco_state_t* out ) const; + void load_state( namco_state_t const& ); + +private: + // noncopyable + Nes_Namco_Apu( const Nes_Namco_Apu& ); + Nes_Namco_Apu& operator = ( const Nes_Namco_Apu& ); + + struct Namco_Osc { + long delay; + Blip_Buffer* output; + short last_amp; + short wave_pos; + }; + + Namco_Osc oscs [osc_count]; + + nes_time_t last_time; + int addr_reg; + + enum { reg_count = 0x80 }; + uint8_t reg [reg_count]; + Blip_Synth synth; + + uint8_t& access(); + void run_until( nes_time_t ); +}; + +struct namco_state_t +{ + uint8_t regs [0x80]; + uint8_t addr; + uint8_t unused; + uint8_t positions [8]; + uint32_t delays [8]; +}; + +BOOST_STATIC_ASSERT( sizeof (namco_state_t) == 172 ); + +inline uint8_t& Nes_Namco_Apu::access() +{ + int addr = addr_reg & 0x7f; + if ( addr_reg & 0x80 ) + addr_reg = (addr + 1) | 0x80; + return reg [addr]; +} + +inline void Nes_Namco_Apu::volume( double v ) { synth.volume( 0.10 / osc_count * v ); } + +inline void Nes_Namco_Apu::treble_eq( const blip_eq_t& eq ) { synth.treble_eq( eq ); } + +inline void Nes_Namco_Apu::write_addr( int v ) { addr_reg = v; } + +inline int Nes_Namco_Apu::read_data() { return access(); } + +inline void Nes_Namco_Apu::osc_output( int i, Blip_Buffer* buf ) +{ + oscs [i].output = buf; +} + +inline void Nes_Namco_Apu::write_data( nes_time_t time, int data ) +{ + run_until( time ); + access() = data; +} + +#endif diff --git a/core/Nes_Oscs.cpp b/core/Nes_Oscs.cpp new file mode 100644 index 0000000..1d2629f --- /dev/null +++ b/core/Nes_Oscs.cpp @@ -0,0 +1,539 @@ + +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ + +#include "Nes_Apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +// Nes_Osc + +void Nes_Osc::clock_length( int halt_mask ) +{ + if ( length_counter && !(regs [0] & halt_mask) ) + length_counter--; +} + +void Nes_Envelope::clock_envelope() +{ + int period = regs [0] & 15; + if ( reg_written [3] ) { + reg_written [3] = false; + env_delay = period; + envelope = 15; + } + else if ( --env_delay < 0 ) { + env_delay = period; + if ( envelope | (regs [0] & 0x20) ) + envelope = (envelope - 1) & 15; + } +} + +int Nes_Envelope::volume() const +{ + return length_counter == 0 ? 0 : (regs [0] & 0x10) ? (regs [0] & 15) : envelope; +} + +// Nes_Square + +void Nes_Square::clock_sweep( int negative_adjust ) +{ + int sweep = regs [1]; + + if ( --sweep_delay < 0 ) + { + reg_written [1] = true; + + int period = this->period(); + int shift = sweep & shift_mask; + if ( shift && (sweep & 0x80) && period >= 8 ) + { + int offset = period >> shift; + + if ( sweep & negate_flag ) + offset = negative_adjust - offset; + + if ( period + offset < 0x800 ) + { + period += offset; + // rewrite period + regs [2] = period & 0xff; + regs [3] = (regs [3] & ~7) | ((period >> 8) & 7); + } + } + } + + if ( reg_written [1] ) { + reg_written [1] = false; + sweep_delay = (sweep >> 4) & 7; + } +} + +// TODO: clean up +inline nes_time_t Nes_Square::maintain_phase( nes_time_t time, nes_time_t end_time, + nes_time_t timer_period ) +{ + long remain = end_time - time; + if ( remain > 0 ) + { + int count = (remain + timer_period - 1) / timer_period; + phase = (phase + count) & (phase_range - 1); + time += (long) count * timer_period; + } + return time; +} + +void Nes_Square::run( nes_time_t time, nes_time_t end_time ) +{ + const int period = this->period(); + const int timer_period = (period + 1) * 2; + + if ( !output ) + { + delay = maintain_phase( time + delay, end_time, timer_period ) - end_time; + return; + } + + int offset = period >> (regs [1] & shift_mask); + if ( regs [1] & negate_flag ) + offset = 0; + + const int volume = this->volume(); + if ( volume == 0 || period < 8 || (period + offset) >= 0x800 ) + { + if ( last_amp ) { + synth.offset( time, -last_amp, output ); + last_amp = 0; + } + + time += delay; + time = maintain_phase( time, end_time, timer_period ); + } + else + { + // handle duty select + int duty_select = (regs [0] >> 6) & 3; + int duty = 1 << duty_select; // 1, 2, 4, 2 + int amp = 0; + if ( duty_select == 3 ) { + duty = 2; // negated 25% + amp = volume; + } + if ( phase < duty ) + amp ^= volume; + + int delta = update_amp( amp ); + if ( delta ) + synth.offset( time, delta, output ); + + time += delay; + if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + const Synth& synth = this->synth; + int delta = amp * 2 - volume; + int phase = this->phase; + + do { + phase = (phase + 1) & (phase_range - 1); + if ( phase == 0 || phase == duty ) { + delta = -delta; + synth.offset_inline( time, delta, output ); + } + time += timer_period; + } + while ( time < end_time ); + + last_amp = (delta + volume) >> 1; + this->phase = phase; + } + } + + delay = time - end_time; +} + +// Nes_Triangle + +void Nes_Triangle::clock_linear_counter() +{ + if ( reg_written [3] ) + linear_counter = regs [0] & 0x7f; + else if ( linear_counter ) + linear_counter--; + + if ( !(regs [0] & 0x80) ) + reg_written [3] = false; +} + +inline int Nes_Triangle::calc_amp() const +{ + int amp = phase_range - phase; + if ( amp < 0 ) + amp = phase - (phase_range + 1); + return amp; +} + +// TODO: clean up +inline nes_time_t Nes_Triangle::maintain_phase( nes_time_t time, nes_time_t end_time, + nes_time_t timer_period ) +{ + long remain = end_time - time; + if ( remain > 0 ) + { + int count = (remain + timer_period - 1) / timer_period; + phase = ((unsigned) phase + 1 - count) & (phase_range * 2 - 1); + phase++; + time += (long) count * timer_period; + } + return time; +} + +void Nes_Triangle::run( nes_time_t time, nes_time_t end_time ) +{ + const int timer_period = period() + 1; + if ( !output ) + { + time += delay; + delay = 0; + if ( length_counter && linear_counter && timer_period >= 3 ) + delay = maintain_phase( time, end_time, timer_period ) - end_time; + return; + } + + // to do: track phase when period < 3 + // to do: Output 7.5 on dac when period < 2? More accurate, but results in more clicks. + + int delta = update_amp( calc_amp() ); + if ( delta ) + synth.offset( time, delta, output ); + + time += delay; + if ( length_counter == 0 || linear_counter == 0 || timer_period < 3 ) + { + time = end_time; + } + else if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + + int phase = this->phase; + int volume = 1; + if ( phase > phase_range ) { + phase -= phase_range; + volume = -volume; + } + + do { + if ( --phase == 0 ) { + phase = phase_range; + volume = -volume; + } + else { + synth.offset_inline( time, volume, output ); + } + + time += timer_period; + } + while ( time < end_time ); + + if ( volume < 0 ) + phase += phase_range; + this->phase = phase; + last_amp = calc_amp(); + } + delay = time - end_time; +} + +// Nes_Dmc + +void Nes_Dmc::reset() +{ + address = 0; + dac = 0; + buf = 0; + bits_remain = 1; + bits = 0; + buf_full = false; + silence = true; + next_irq = Nes_Apu::no_irq; + irq_flag = false; + irq_enabled = false; + + Nes_Osc::reset(); + period = 0x1ac; +} + +void Nes_Dmc::recalc_irq() +{ + nes_time_t irq = Nes_Apu::no_irq; + if ( irq_enabled && length_counter ) + irq = apu->last_dmc_time + delay + + ((length_counter - 1) * 8 + bits_remain - 1) * nes_time_t (period) + 1; + if ( irq != next_irq ) { + next_irq = irq; + apu->irq_changed(); + } +} + +int Nes_Dmc::count_reads( nes_time_t time, nes_time_t* last_read ) const +{ + if ( last_read ) + *last_read = time; + + if ( length_counter == 0 ) + return 0; // not reading + + long first_read = next_read_time(); + long avail = time - first_read; + if ( avail <= 0 ) + return 0; + + int count = (avail - 1) / (period * 8) + 1; + if ( !(regs [0] & loop_flag) && count > length_counter ) + count = length_counter; + + if ( last_read ) + *last_read = first_read + (count - 1) * (period * 8) + 1; + + return count; +} + +static const short dmc_period_table [2] [16] = { + {0x1ac, 0x17c, 0x154, 0x140, 0x11e, 0x0fe, 0x0e2, 0x0d6, // NTSC + 0x0be, 0x0a0, 0x08e, 0x080, 0x06a, 0x054, 0x048, 0x036}, + + {0x18e, 0x161, 0x13c, 0x129, 0x10a, 0x0ec, 0x0d2, 0x0c7, // PAL (totally untested) + 0x0b1, 0x095, 0x084, 0x077, 0x062, 0x04e, 0x043, 0x032} // to do: verify PAL periods +}; + +inline void Nes_Dmc::reload_sample() +{ + address = 0x4000 + regs [2] * 0x40; + length_counter = regs [3] * 0x10 + 1; +} + +static const unsigned char dac_table [128] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 9,10,11,12,13,14, + 15,15,16,17,18,19,20,20,21,22,23,24,24,25,26,27, + 27,28,29,30,31,31,32,33,33,34,35,36,36,37,38,38, + 39,40,41,41,42,43,43,44,45,45,46,47,47,48,48,49, + 50,50,51,52,52,53,53,54,55,55,56,56,57,58,58,59, + 59,60,60,61,61,62,63,63,64,64,65,65,66,66,67,67, + 68,68,69,70,70,71,71,72,72,73,73,74,74,75,75,75, + 76,76,77,77,78,78,79,79,80,80,81,81,82,82,82,83, +}; + +void Nes_Dmc::write_register( int addr, int data ) +{ + if ( addr == 0 ) + { + period = dmc_period_table [pal_mode] [data & 15]; + irq_enabled = (data & 0xc0) == 0x80; // enabled only if loop disabled + irq_flag &= irq_enabled; + recalc_irq(); + } + else if ( addr == 1 ) + { + int old_dac = dac; + dac = data & 0x7F; + + // adjust last_amp so that "pop" amplitude will be properly non-linear + // with respect to change in dac + int faked_nonlinear = dac - (dac_table [dac] - dac_table [old_dac]); + if ( !nonlinear ) + last_amp = faked_nonlinear; + } +} + +void Nes_Dmc::start() +{ + reload_sample(); + fill_buffer(); + recalc_irq(); +} + +void Nes_Dmc::fill_buffer() +{ + if ( !buf_full && length_counter ) + { + buf = prg_reader( prg_reader_data, 0x8000u + address ); + address = (address + 1) & 0x7FFF; + buf_full = true; + if ( --length_counter == 0 ) + { + if ( regs [0] & loop_flag ) { + reload_sample(); + } + else { + apu->osc_enables &= ~0x10; + irq_flag = irq_enabled; + next_irq = Nes_Apu::no_irq; + apu->irq_changed(); + } + } + } +} + +void Nes_Dmc::run( nes_time_t time, nes_time_t end_time ) +{ + int delta = update_amp( dac ); + if ( !output ) + silence = true; + else if ( delta ) + synth.offset( time, delta, output ); + + time += delay; + if ( time < end_time ) + { + int bits_remain = this->bits_remain; + if ( silence && !buf_full ) + { + int count = (end_time - time + period - 1) / period; + bits_remain = (bits_remain - 1 + 8 - (count % 8)) % 8 + 1; + time += count * period; + } + else + { + Blip_Buffer* const output = this->output; + const int period = this->period; + int bits = this->bits; + int dac = this->dac; + + do + { + if ( !silence ) + { + int step = (bits & 1) * 4 - 2; + bits >>= 1; + if ( unsigned (dac + step) <= 0x7F ) { + dac += step; + synth.offset_inline( time, step, output ); + } + } + + time += period; + + if ( --bits_remain == 0 ) + { + bits_remain = 8; + if ( !buf_full ) { + silence = true; + } + else { + silence = false; + bits = buf; + buf_full = false; + if ( !output ) + silence = true; + fill_buffer(); + } + } + } + while ( time < end_time ); + + this->dac = dac; + this->last_amp = dac; + this->bits = bits; + } + this->bits_remain = bits_remain; + } + delay = time - end_time; +} + +// Nes_Noise + +static const short noise_period_table [16] = { + 0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0, + 0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4 +}; + +void Nes_Noise::run( nes_time_t time, nes_time_t end_time ) +{ + int period = noise_period_table [regs [2] & 15]; + #if NES_APU_NOISE_LOW_CPU + if ( period < 8 ) + { + period = 8; + } + #endif + + if ( !output ) + { + // TODO: clean up + time += delay; + delay = time + (end_time - time + period - 1) / period * period - end_time; + return; + } + + const int volume = this->volume(); + int amp = (noise & 1) ? volume : 0; + int delta = update_amp( amp ); + if ( delta ) + synth.offset( time, delta, output ); + + time += delay; + if ( time < end_time ) + { + const int mode_flag = 0x80; + + if ( !volume ) + { + // round to next multiple of period + time += (end_time - time + period - 1) / period * period; + + // approximate noise cycling while muted, by shuffling up noise register + // to do: precise muted noise cycling? + if ( !(regs [2] & mode_flag) ) { + int feedback = (noise << 13) ^ (noise << 14); + noise = (feedback & 0x4000) | (noise >> 1); + } + } + else + { + Blip_Buffer* const output = this->output; + + // using resampled time avoids conversion in synth.offset() + blip_resampled_time_t rperiod = output->resampled_duration( period ); + blip_resampled_time_t rtime = output->resampled_time( time ); + + int noise = this->noise; + int delta = amp * 2 - volume; + const int tap = (regs [2] & mode_flag ? 8 : 13); + + do { + int feedback = (noise << tap) ^ (noise << 14); + time += period; + + if ( (noise + 1) & 2 ) { + // bits 0 and 1 of noise differ + delta = -delta; + synth.offset_resampled( rtime, delta, output ); + } + + rtime += rperiod; + noise = (feedback & 0x4000) | (noise >> 1); + } + while ( time < end_time ); + + last_amp = (delta + volume) >> 1; + this->noise = noise; + } + } + + delay = time - end_time; +} diff --git a/core/Nes_Oscs.h b/core/Nes_Oscs.h new file mode 100644 index 0000000..a57ef71 --- /dev/null +++ b/core/Nes_Oscs.h @@ -0,0 +1,149 @@ + +// Private oscillators used by Nes_Apu + +// Nes_Snd_Emu 0.1.7 + +#ifndef NES_OSCS_H +#define NES_OSCS_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +class Nes_Apu; + +struct Nes_Osc +{ + unsigned char regs [4]; + bool reg_written [4]; + Blip_Buffer* output; + int length_counter;// length counter (0 if unused by oscillator) + int delay; // delay until next (potential) transition + int last_amp; // last amplitude oscillator was outputting + + void clock_length( int halt_mask ); + int period() const { + return (regs [3] & 7) * 0x100 + (regs [2] & 0xff); + } + void reset() { + delay = 0; + last_amp = 0; + } + int update_amp( int amp ) { + int delta = amp - last_amp; + last_amp = amp; + return delta; + } +}; + +struct Nes_Envelope : Nes_Osc +{ + int envelope; + int env_delay; + + void clock_envelope(); + int volume() const; + void reset() { + envelope = 0; + env_delay = 0; + Nes_Osc::reset(); + } +}; + +// Nes_Square +struct Nes_Square : Nes_Envelope +{ + enum { negate_flag = 0x08 }; + enum { shift_mask = 0x07 }; + enum { phase_range = 8 }; + int phase; + int sweep_delay; + + typedef Blip_Synth Synth; + Synth const& synth; // shared between squares + + Nes_Square( Synth const* s ) : synth( *s ) { } + + void clock_sweep( int adjust ); + void run( nes_time_t, nes_time_t ); + void reset() { + sweep_delay = 0; + Nes_Envelope::reset(); + } + nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time, + nes_time_t timer_period ); +}; + +// Nes_Triangle +struct Nes_Triangle : Nes_Osc +{ + enum { phase_range = 16 }; + int phase; + int linear_counter; + Blip_Synth synth; + + int calc_amp() const; + void run( nes_time_t, nes_time_t ); + void clock_linear_counter(); + void reset() { + linear_counter = 0; + phase = 1; + Nes_Osc::reset(); + } + nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time, + nes_time_t timer_period ); +}; + +// Nes_Noise +struct Nes_Noise : Nes_Envelope +{ + int noise; + Blip_Synth synth; + + void run( nes_time_t, nes_time_t ); + void reset() { + noise = 1 << 14; + Nes_Envelope::reset(); + } +}; + +// Nes_Dmc +struct Nes_Dmc : Nes_Osc +{ + int address; // address of next byte to read + int period; + //int length_counter; // bytes remaining to play (already defined in Nes_Osc) + int buf; + int bits_remain; + int bits; + bool buf_full; + bool silence; + + enum { loop_flag = 0x40 }; + + int dac; + + nes_time_t next_irq; + bool irq_enabled; + bool irq_flag; + bool pal_mode; + bool nonlinear; + + int (*prg_reader)( void*, nes_addr_t ); // needs to be initialized to prg read function + void* prg_reader_data; + + Nes_Apu* apu; + + Blip_Synth synth; + + void start(); + void write_register( int, int ); + void run( nes_time_t, nes_time_t ); + void recalc_irq(); + void fill_buffer(); + void reload_sample(); + void reset(); + int count_reads( nes_time_t, nes_time_t* ) const; + nes_time_t next_read_time() const; +}; + +#endif diff --git a/core/Nes_Ppu.cpp b/core/Nes_Ppu.cpp new file mode 100644 index 0000000..d2fcf8e --- /dev/null +++ b/core/Nes_Ppu.cpp @@ -0,0 +1,657 @@ + +// Timing and behavior of PPU + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Ppu.h" + +#include +#include "Nes_State.h" +#include "Nes_Mapper.h" +#include "Nes_Core.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +// to do: remove unnecessary run_until() calls + +#include "blargg_source.h" + +// Timing + +ppu_time_t const scanline_len = Nes_Ppu::scanline_len; + +// if non-zero, report sprite max at fixed time rather than calculating it +nes_time_t const fixed_sprite_max_time = 0; // 1 * ((21 + 164) * scanline_len + 100) / ppu_overclock; +int const sprite_max_cpu_offset = 2420 + 3; + +ppu_time_t const t_to_v_time = 20 * scanline_len + 302; +ppu_time_t const even_odd_time = 20 * scanline_len + 328; + +ppu_time_t const first_scanline_time = 21 * scanline_len + 60; // this can be varied +ppu_time_t const first_hblank_time = 21 * scanline_len + 252; + +ppu_time_t const earliest_sprite_max = sprite_max_cpu_offset * ppu_overclock; +ppu_time_t const earliest_sprite_hit = 21 * scanline_len + 339; // needs to be 22 * scanline_len when fixed_sprite_max_time is set + +nes_time_t const vbl_end_time = 2272; +ppu_time_t const max_frame_length = 262 * scanline_len; +//ppu_time_t const max_frame_length = 320 * scanline_len; // longer frame for testing movie resync +nes_time_t const earliest_vbl_end_time = max_frame_length / ppu_overclock - 10; + +// Scanline rendering + +void Nes_Ppu::render_bg_until_( nes_time_t cpu_time ) +{ + ppu_time_t time = ppu_time( cpu_time ); + ppu_time_t const frame_duration = scanline_len * 261; + if ( time > frame_duration ) + time = frame_duration; + + // one-time events + if ( frame_phase <= 1 ) + { + if ( frame_phase < 1 ) + { + // vtemp->vaddr + frame_phase = 1; + if ( w2001 & 0x08 ) + vram_addr = vram_temp; + } + + // variable-length scanline + if ( time <= even_odd_time ) + { + next_bg_time = nes_time( even_odd_time ); + return; + } + frame_phase = 2; + if ( !(w2001 & 0x08) || emu.nes.frame_count & 1 ) + { + if ( --frame_length_extra < 0 ) + { + frame_length_extra = 2; + frame_length_++; + } + burst_phase--; + } + burst_phase = (burst_phase + 2) % 3; + } + + // scanlines + if ( scanline_time < time ) + { + int count = (time - scanline_time + scanline_len) / scanline_len; + + // hblank before next scanline + if ( hblank_time < scanline_time ) + { + hblank_time += scanline_len; + run_hblank( 1 ); + } + + scanline_time += count * scanline_len; + + hblank_time += scanline_len * (count - 1); + int saved_vaddr = vram_addr; + + int start = scanline_count; + scanline_count += count; + draw_background( start, count ); + + vram_addr = saved_vaddr; // to do: this is cheap + run_hblank( count - 1 ); + } + + // hblank after current scanline + ppu_time_t next_ppu_time = hblank_time; + if ( hblank_time < time ) + { + hblank_time += scanline_len; + run_hblank( 1 ); + next_ppu_time = scanline_time; // scanline will run next + } + // either hblank or scanline comes next + next_bg_time = nes_time( next_ppu_time ); +} + +void Nes_Ppu::render_until_( nes_time_t time ) +{ + // render bg scanlines then render sprite scanlines up to wherever bg was rendered to + + render_bg_until( time ); + next_sprites_time = nes_time( scanline_time ); + if ( host_pixels ) + { + int start = next_sprites_scanline; + int count = scanline_count - start; + if ( count > 0 ) + { + next_sprites_scanline += count; + draw_sprites( start, count ); + } + } +} + +// Frame events + +inline void Nes_Ppu::end_vblank() +{ + // clear VBL, sprite hit, and max sprites flags first time after 20 scanlines + r2002 &= end_vbl_mask; + end_vbl_mask = ~0; +} + +inline void Nes_Ppu::run_end_frame( nes_time_t time ) +{ + if ( !frame_ended ) + { + // update frame_length + render_bg_until( time ); + + // set VBL when end of frame is reached + nes_time_t len = frame_length(); + if ( time >= len ) + { + r2002 |= 0x80; + frame_ended = true; + if ( w2000 & 0x80 ) + nmi_time_ = len + 2 - (frame_length_extra >> 1); + } + } +} + +// Sprite max + +inline void Nes_Ppu::invalidate_sprite_max_() +{ + next_sprite_max_run = earliest_sprite_max / ppu_overclock; + sprite_max_set_time = 0; +} + +void Nes_Ppu::run_sprite_max_( nes_time_t cpu_time ) +{ + end_vblank(); // might get run outside $2002 handler + + // 577.0 / 0x10000 ~= 1.0 / 113.581, close enough to accurately calculate which scanline it is + int start_scanline = next_sprite_max_scanline; + next_sprite_max_scanline = unsigned ((cpu_time - sprite_max_cpu_offset) * 577) / 0x10000u; + + if ( !sprite_max_set_time ) + { + if ( !(w2001 & 0x18) ) + return; + + long t = recalc_sprite_max( start_scanline ); + sprite_max_set_time = indefinite_time; + if ( t > 0 ) + sprite_max_set_time = t / 3 + sprite_max_cpu_offset; + next_sprite_max_run = sprite_max_set_time; + //dprintf( "sprite_max_set_time: %d\n", sprite_max_set_time ); + } + + if ( cpu_time > sprite_max_set_time ) + { + r2002 |= 0x20; + //dprintf( "Sprite max flag set: %d\n", sprite_max_set_time ); + next_sprite_max_run = indefinite_time; + } +} + +inline void Nes_Ppu::run_sprite_max( nes_time_t t ) +{ + if ( !fixed_sprite_max_time && t > next_sprite_max_run ) + run_sprite_max_( t ); +} + +inline void Nes_Ppu::invalidate_sprite_max( nes_time_t t ) +{ + if ( !fixed_sprite_max_time && !(r2002 & 0x20) ) + { + run_sprite_max( t ); + invalidate_sprite_max_(); + } +} + +// Sprite 0 hit + +inline int Nes_Ppu_Impl::first_opaque_sprite_line() +{ + // advance earliest time if sprite has blank lines at beginning + uint8_t const* p = map_chr( sprite_tile_index( spr_ram ) * 16 ); + int twice = w2000 >> 5 & 1; // loop twice if double height is set + int line = 0; + do + { + for ( int n = 8; n--; p++ ) + { + if ( p [0] | p [8] ) + return line; + line++; + } + + p += 8; + } + while ( !--twice ); + return line; +} + +void Nes_Ppu::update_sprite_hit( nes_time_t cpu_time ) +{ + ppu_time_t earliest = earliest_sprite_hit + spr_ram [0] * scanline_len + spr_ram [3]; + //ppu_time_t latest = earliest + sprite_height() * scanline_len; + + earliest += first_opaque_sprite_line() * scanline_len; + + ppu_time_t time = ppu_time( cpu_time ); + next_sprite_hit_check = indefinite_time; + + if ( false ) + if ( earliest < time ) + { + r2002 |= 0x40; + return; + } + + if ( time < earliest ) + { + next_sprite_hit_check = nes_time( earliest ); + return; + } + + // within possible range; render scanline and compare pixels + int count_needed = 2 + (time - earliest_sprite_hit - spr_ram [3]) / scanline_len; + if ( count_needed > 240 ) + count_needed = 240; + while ( scanline_count < count_needed ) + render_bg_until( max( cpu_time, next_bg_time + 1 ) ); + + if ( sprite_hit_found < 0 ) + return; // sprite won't hit + + if ( !sprite_hit_found ) + { + // check again next scanline + next_sprite_hit_check = nes_time( earliest_sprite_hit + spr_ram [3] + + (scanline_count - 1) * scanline_len ); + } + else + { + // hit found + ppu_time_t hit_time = earliest_sprite_hit + sprite_hit_found - scanline_len; + + if ( time < hit_time ) + { + next_sprite_hit_check = nes_time( hit_time ); + return; + } + + //dprintf( "Sprite hit x: %d, y: %d, scanline_count: %d\n", + // sprite_hit_found % 341, sprite_hit_found / 341, scanline_count ); + + r2002 |= 0x40; + } +} + +// $2002 + +inline void Nes_Ppu::query_until( nes_time_t time ) +{ + end_vblank(); + + // sprite hit + if ( time > next_sprite_hit_check ) + update_sprite_hit( time ); + + // sprite max + if ( !fixed_sprite_max_time ) + run_sprite_max( time ); + else if ( time >= fixed_sprite_max_time ) + r2002 |= (w2001 << 1 & 0x20) | (w2001 << 2 & 0x20); +} + +int Nes_Ppu::read_2002( nes_time_t time ) +{ + nes_time_t next = next_status_event; + next_status_event = vbl_end_time; + int extra_clock = extra_clocks ? (extra_clocks - 1) >> 2 & 1 : 0; + if ( time > next && time > vbl_end_time + extra_clock ) + { + query_until( time ); + + next_status_event = next_sprite_hit_check; + nes_time_t const next_max = fixed_sprite_max_time ? + fixed_sprite_max_time : next_sprite_max_run; + if ( next_status_event > next_max ) + next_status_event = next_max; + + if ( time > earliest_open_bus_decay() ) + { + next_status_event = earliest_open_bus_decay(); + update_open_bus( time ); + } + + if ( time > earliest_vbl_end_time ) + { + if ( next_status_event > earliest_vbl_end_time ) + next_status_event = earliest_vbl_end_time; + run_end_frame( time ); + + // special vbl behavior when read is just before or at clock when it's set + if ( extra_clocks != 1 ) + { + if ( time == frame_length() ) + { + nmi_time_ = indefinite_time; + //dprintf( "Suppressed NMI\n" ); + } + } + else if ( time == frame_length() - 1 ) + { + r2002 &= ~0x80; + frame_ended = true; + nmi_time_ = indefinite_time; + //dprintf( "Suppressed NMI\n" ); + } + } + } + emu.set_ppu_2002_time( next_status_event ); + + int result = r2002; + second_write = false; + r2002 = result & ~0x80; + poke_open_bus( time, result, 0xE0 ); + update_open_bus( time ); + return ( result & 0xE0 ) | ( open_bus & 0x1F ); +} + +void Nes_Ppu::dma_sprites( nes_time_t time, void const* in ) +{ + //dprintf( "%d sprites written\n", time ); + render_until( time ); + + invalidate_sprite_max( time ); + + memcpy( spr_ram + w2003, in, 0x100 - w2003 ); + memcpy( spr_ram, (char*) in + 0x100 - w2003, w2003 ); +} + +// Read + +inline int Nes_Ppu_Impl::read_2007( int addr ) +{ + int result = r2007; + if ( addr < 0x2000 ) + { + r2007 = *map_chr( addr ); + } + else + { + r2007 = get_nametable( addr ) [addr & 0x3ff]; + if ( addr >= 0x3f00 ) + { + return palette [map_palette( addr )] | ( open_bus & 0xC0 ); + } + } + return result; +} + +int Nes_Ppu::read( unsigned addr, nes_time_t time ) +{ + switch ( addr & 7 ) + { + // status + case 2: // handled inline + return read_2002( time ); + + // sprite ram + case 4: { + int result = spr_ram [w2003]; + if ( (w2003 & 3) == 2 ) + result &= 0xe3; + poke_open_bus( time, result, ~0 ); + return result; + } + + // video ram + case 7: { + render_bg_until( time ); + int addr = vram_addr; + int new_addr = addr + addr_inc; + vram_addr = new_addr; + if ( ~addr & new_addr & vaddr_clock_mask ) + { + emu.mapper->a12_clocked(); + addr = vram_addr - addr_inc; // avoid having to save across func call + } + int result = read_2007( addr & 0x3fff ); + poke_open_bus( time, result, ( ( addr & 0x3fff ) >= 0x3f00 ) ? 0x3F : ~0 ); + return result; + } + + default: + /* Read from unimplemented PPU register */ + break; + } + + update_open_bus( time ); + + return open_bus; +} + +// Write + +void Nes_Ppu::write( nes_time_t time, unsigned addr, int data ) +{ + switch ( addr & 7 ) + { + case 0:{// control + int changed = w2000 ^ data; + + if ( changed & 0x28 ) + render_until( time ); // obj height or pattern addr changed + else if ( changed & 0x10 ) + render_bg_until( time ); // bg pattern addr changed + else if ( ((data << 10) ^ vram_temp) & 0x0C00 ) + render_bg_until( time ); // nametable changed + + if ( changed & 0x80 ) + { + if ( time > vbl_end_time + ((extra_clocks - 1) >> 2 & 1) ) + end_vblank(); // to do: clean this up + + if ( data & 0x80 & r2002 ) + { + nmi_time_ = time + 2; + emu.event_changed(); + } + if ( time >= earliest_vbl_end_time ) + run_end_frame( time - 1 + (extra_clocks & 1) ); + } + + // nametable select + vram_temp = (vram_temp & ~0x0C00) | ((data & 3) * 0x400); + + if ( changed & 0x20 ) // sprite height changed + invalidate_sprite_max( time ); + w2000 = data; + addr_inc = data & 4 ? 32 : 1; + + break; + } + + case 1:{// sprites, bg enable + int changed = w2001 ^ data; + + if ( changed & 0xE1 ) + { + render_until( time + 1 ); // emphasis/monochrome bits changed + palette_changed = 0x18; + } + + if ( changed & 0x14 ) + render_until( time + 1 ); // sprite enable/clipping changed + else if ( changed & 0x0A ) + render_bg_until( time + 1 ); // bg enable/clipping changed + + if ( changed & 0x08 ) // bg enabled changed + emu.mapper->run_until( time ); + + if ( !(w2001 & 0x18) != !(data & 0x18) ) + invalidate_sprite_max( time ); // all rendering just turned on or off + + w2001 = data; + + if ( changed & 0x08 ) + emu.irq_changed(); + + break; + } + + case 3: // spr addr + w2003 = data; + poke_open_bus( time, w2003, ~0 ); + break; + + case 4: + //dprintf( "%d sprites written\n", time ); + if ( time > first_scanline_time / ppu_overclock ) + { + render_until( time ); + invalidate_sprite_max( time ); + } + spr_ram [w2003] = data; + w2003 = (w2003 + 1) & 0xff; + break; + + case 5: + render_bg_until( time ); + if ( (second_write ^= 1) ) + { + pixel_x = data & 7; + vram_temp = (vram_temp & ~0x1f) | (data >> 3); + } + else + { + vram_temp = (vram_temp & ~0x73e0) | + (data << 12 & 0x7000) | (data << 2 & 0x03e0); + } + break; + + case 6: + render_bg_until( time ); + if ( (second_write ^= 1) ) + { + vram_temp = (vram_temp & 0xff) | (data << 8 & 0x3f00); + } + else + { + int changed = ~vram_addr & vram_temp; + vram_addr = vram_temp = (vram_temp & 0xff00) | data; + if ( changed & vaddr_clock_mask ) + emu.mapper->a12_clocked(); + } + break; + + default: + /* Wrote to unimplemented PPU register */ + break; + } + + poke_open_bus( time, data, ~0 ); +} + +// Frame begin/end + +nes_time_t Nes_Ppu::begin_frame( ppu_time_t timestamp ) +{ + // current time + int cpu_timestamp = timestamp / ppu_overclock; + extra_clocks = timestamp - cpu_timestamp * ppu_overclock; + + // frame end + ppu_time_t const frame_end = max_frame_length - 1 - extra_clocks; + frame_length_ = (frame_end + (ppu_overclock - 1)) / ppu_overclock; + frame_length_extra = frame_length_ * ppu_overclock - frame_end; + + // nmi + nmi_time_ = indefinite_time; + if ( w2000 & 0x80 & r2002 ) + nmi_time_ = 2 - (extra_clocks >> 1); + + // bg rendering + frame_phase = 0; + scanline_count = 0; + hblank_time = first_hblank_time; + scanline_time = first_scanline_time; + next_bg_time = nes_time( t_to_v_time ); + + // sprite rendering + next_sprites_scanline = 0; + next_sprites_time = 0; + + // status register + frame_ended = false; + end_vbl_mask = ~0xE0; + next_status_event = 0; + sprite_hit_found = 0; + next_sprite_hit_check = 0; + next_sprite_max_scanline = 0; + invalidate_sprite_max_(); + + decay_low += cpu_timestamp; + decay_high += cpu_timestamp; + + base::begin_frame(); + + //dprintf( "cpu_timestamp: %d\n", cpu_timestamp ); + return cpu_timestamp; +} + +ppu_time_t Nes_Ppu::end_frame( nes_time_t end_time ) +{ + render_bg_until( end_time ); + render_until( end_time ); + query_until( end_time ); + run_end_frame( end_time ); + + update_open_bus( end_time ); + decay_low -= end_time; + decay_high -= end_time; + + // to do: do more PPU RE to get exact behavior + if ( w2001 & 0x08 ) + { + unsigned a = vram_addr + 2; + if ( (vram_addr & 0xff) >= 0xfe ) + a = (vram_addr ^ 0x400) - 0x1e; + vram_addr = a; + } + + if ( w2001 & 0x10 ) + w2003 = 0; + + suspend_rendering(); + + return (end_time - frame_length_) * ppu_overclock + frame_length_extra; +} + +void Nes_Ppu::poke_open_bus( nes_time_t time, int data, int mask ) +{ + open_bus = ( open_bus & ~mask ) | ( data & mask ); + if ( mask & 0x1F ) decay_low = time + scanline_len * 100 / ppu_overclock; + if ( mask & 0xE0 ) decay_high = time + scanline_len * 100 / ppu_overclock; +} + +nes_time_t Nes_Ppu::earliest_open_bus_decay() +{ + return ( decay_low < decay_high ) ? decay_low : decay_high; +} diff --git a/core/Nes_Ppu.h b/core/Nes_Ppu.h new file mode 100644 index 0000000..499f35d --- /dev/null +++ b/core/Nes_Ppu.h @@ -0,0 +1,140 @@ + +// NES PPU emulator + +// Nes_Emu 0.7.0 + +#ifndef NES_PPU_H +#define NES_PPU_H + +#include "Nes_Ppu_Rendering.h" +class Nes_Mapper; +class Nes_Core; + +typedef long nes_time_t; +typedef long ppu_time_t; // ppu_time_t = nes_time_t * ppu_overclock + +ppu_time_t const ppu_overclock = 3; // PPU clocks for each CPU clock + +class Nes_Ppu : public Nes_Ppu_Rendering { + typedef Nes_Ppu_Rendering base; +public: + Nes_Ppu( Nes_Core* ); + + // Begin PPU frame and return beginning CPU timestamp + nes_time_t begin_frame( ppu_time_t ); + + nes_time_t nmi_time() { return nmi_time_; } + void acknowledge_nmi() { nmi_time_ = LONG_MAX / 2 + 1; } + + int read_2002( nes_time_t ); + int read( unsigned addr, nes_time_t ); + void write( nes_time_t, unsigned addr, int ); + + void render_bg_until( nes_time_t ); + void render_until( nes_time_t ); + + // CPU time that frame will have ended by + int frame_length() const { return frame_length_; } + + // End frame rendering and return PPU timestamp for next frame + ppu_time_t end_frame( nes_time_t ); + + // Do direct memory copy to sprite RAM + void dma_sprites( nes_time_t, void const* in ); + + int burst_phase; + +private: + + Nes_Core& emu; + + enum { indefinite_time = LONG_MAX / 2 + 1 }; + + void suspend_rendering(); + int read_( unsigned addr, nes_time_t ); // note swapped arguments! + + // NES<->PPU time conversion + int extra_clocks; + ppu_time_t ppu_time( nes_time_t t ) const { return t * ppu_overclock + extra_clocks; } + nes_time_t nes_time( ppu_time_t t ) const { return (t - extra_clocks) / ppu_overclock; } + + // frame + nes_time_t nmi_time_; + int end_vbl_mask; + int frame_length_; + int frame_length_extra; + bool frame_ended; + void end_vblank(); + void run_end_frame( nes_time_t ); + + // bg rendering + nes_time_t next_bg_time; + ppu_time_t scanline_time; + ppu_time_t hblank_time; + int scanline_count; + int frame_phase; + void render_bg_until_( nes_time_t ); + void run_scanlines( int count ); + + // sprite rendering + ppu_time_t next_sprites_time; + int next_sprites_scanline; + void render_until_( nes_time_t ); + + // $2002 status register + nes_time_t next_status_event; + void query_until( nes_time_t ); + + // sprite hit + nes_time_t next_sprite_hit_check; + void update_sprite_hit( nes_time_t ); + + // open bus decay + void update_open_bus( nes_time_t ); + void poke_open_bus( nes_time_t, int, int mask ); + nes_time_t earliest_open_bus_decay(); + + // sprite max + nes_time_t next_sprite_max_run; // doesn't need to run until this time + nes_time_t sprite_max_set_time; // if 0, needs to be recalculated + int next_sprite_max_scanline; + void run_sprite_max_( nes_time_t ); + void run_sprite_max( nes_time_t ); + void invalidate_sprite_max_(); + void invalidate_sprite_max( nes_time_t ); + + friend int nes_cpu_read_likely_ppu( class Nes_Core*, unsigned, nes_time_t ); +}; + +inline void Nes_Ppu::suspend_rendering() +{ + next_bg_time = indefinite_time; + next_sprites_time = indefinite_time; + extra_clocks = 0; +} + +inline Nes_Ppu::Nes_Ppu( Nes_Core* e ) : emu( *e ) +{ + burst_phase = 0; + suspend_rendering(); +} + +inline void Nes_Ppu::render_until( nes_time_t t ) +{ + if ( t > next_sprites_time ) + render_until_( t ); +} + +inline void Nes_Ppu::render_bg_until( nes_time_t t ) +{ + if ( t > next_bg_time ) + render_bg_until_( t ); +} + +inline void Nes_Ppu::update_open_bus( nes_time_t time ) +{ + if ( time >= decay_low ) open_bus &= ~0x1F; + if ( time >= decay_high ) open_bus &= ~0xE0; +} + +#endif diff --git a/core/Nes_Ppu_Bg.h b/core/Nes_Ppu_Bg.h new file mode 100644 index 0000000..81e1eb3 --- /dev/null +++ b/core/Nes_Ppu_Bg.h @@ -0,0 +1,68 @@ +while ( true ) +{ + while ( count-- ) + { + int attrib = attr_table [addr >> 2 & 0x07]; + attrib >>= (addr >> 4 & 4) | (addr & 2); + unsigned long offset = (attrib & 3) * attrib_factor + this->palette_offset; + + // draw one tile + cache_t const* lines = this->get_bg_tile( nametable [addr] + bg_bank ); + uint8_t* p = pixels; + addr++; + pixels += 8; // next tile + + if ( !clipped ) + { + // optimal case: no clipping + for ( int n = 4; n--; ) + { + unsigned long line = *lines++; + ((unaligned_uint32_t*) p) [0].val = (line >> 4 & mask) + offset; + ((unaligned_uint32_t*) p) [1].val = (line & mask) + offset; + p += row_bytes; + ((unaligned_uint32_t*) p) [0].val = (line >> 6 & mask) + offset; + ((unaligned_uint32_t*) p) [1].val = (line >> 2 & mask) + offset; + p += row_bytes; + } + } + else + { + lines += fine_y >> 1; + + if ( fine_y & 1 ) + { + unsigned long line = *lines++; + ((unaligned_uint32_t*) p) [0].val = (line >> 6 & mask) + offset; + ((unaligned_uint32_t*) p) [1].val = (line >> 2 & mask) + offset; + p += row_bytes; + } + + for ( int n = height >> 1; n--; ) + { + unsigned long line = *lines++; + ((unaligned_uint32_t*) p) [0].val = (line >> 4 & mask) + offset; + ((unaligned_uint32_t*) p) [1].val = (line & mask) + offset; + p += row_bytes; + ((unaligned_uint32_t*) p) [0].val = (line >> 6 & mask) + offset; + ((unaligned_uint32_t*) p) [1].val = (line >> 2 & mask) + offset; + p += row_bytes; + } + + if ( height & 1 ) + { + unsigned long line = *lines; + ((unaligned_uint32_t*) p) [0].val = (line >> 4 & mask) + offset; + ((unaligned_uint32_t*) p) [1].val = (line & mask) + offset; + } + } + } + + count = count2; + count2 = 0; + addr -= 32; + attr_table = attr_table - nametable + nametable2; + nametable = nametable2; + if ( !count ) + break; +} diff --git a/core/Nes_Ppu_Impl.cpp b/core/Nes_Ppu_Impl.cpp new file mode 100644 index 0000000..90dd383 --- /dev/null +++ b/core/Nes_Ppu_Impl.cpp @@ -0,0 +1,492 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Ppu_Impl.h" + +#include +#include "blargg_endian.h" +#include "Nes_State.h" +#include + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +int const cache_line_size = 128; // tile cache is kept aligned to this boundary + +Nes_Ppu_Impl::Nes_Ppu_Impl() +{ + impl = NULL; + chr_data = NULL; + chr_size = 0; + tile_cache = NULL; + host_palette = NULL; + max_palette_size = 0; + tile_cache_mem = NULL; + ppu_state_t::unused = 0; + + mmc24_enabled = false; + mmc24_latched[0] = 0; + mmc24_latched[1] = 0; + + #if !defined(NDEBUG) && !defined(PSP) && !defined(PS2) + // verify that unaligned accesses work + static unsigned char b [19] = { 0 }; + static unsigned char b2 [19] = { 1,2,3,4,0,5,6,7,8,0,9,0,1,2,0,3,4,5,6 }; + for ( int i = 0; i < 19; i += 5 ) + *(volatile uint32_t*) &b [i] = *(volatile uint32_t*) &b2 [i]; + #endif +} + +Nes_Ppu_Impl::~Nes_Ppu_Impl() +{ + close_chr(); + delete impl; +} + +void Nes_Ppu_Impl::all_tiles_modified() +{ + any_tiles_modified = true; + memset( modified_tiles, ~0, sizeof modified_tiles ); +} + +const char *Nes_Ppu_Impl::open_chr( uint8_t const* new_chr, long chr_data_size ) +{ + close_chr(); + + if ( !impl ) + { + impl = new impl_t; + CHECK_ALLOC( impl ); + chr_ram = impl->chr_ram; + } + + chr_data = new_chr; + chr_size = chr_data_size; + chr_is_writable = false; + + if ( chr_data_size == 0 ) + { + // CHR RAM + chr_data = impl->chr_ram; + chr_size = sizeof impl->chr_ram; + chr_is_writable = true; + } + + // allocate aligned memory for cache + long tile_count = chr_size / bytes_per_tile; + tile_cache_mem = new uint8_t [tile_count * sizeof (cached_tile_t) * 2 + cache_line_size]; + CHECK_ALLOC( tile_cache_mem ); + tile_cache = (cached_tile_t*) (tile_cache_mem + cache_line_size - + (uintptr_t) tile_cache_mem % cache_line_size); + flipped_tiles = tile_cache + tile_count; + + // rebuild cache + all_tiles_modified(); + if ( !chr_is_writable ) + { + any_tiles_modified = false; + rebuild_chr( 0, chr_size ); + } + + return 0; +} + +void Nes_Ppu_Impl::close_chr() +{ + delete [] tile_cache_mem; + tile_cache_mem = NULL; +} + +void Nes_Ppu_Impl::set_chr_bank( int addr, int size, long data ) +{ + if ( data + size > chr_size ) + data %= chr_size; + + int count = (unsigned) size / chr_page_size; + + int page = (unsigned) addr / chr_page_size; + while ( count-- ) + { + chr_pages [page] = data - page * chr_page_size; + page++; + data += chr_page_size; + } +} + +void Nes_Ppu_Impl::set_chr_bank_ex( int addr, int size, long data ) +{ + mmc24_enabled = true; + + //check( !chr_is_writable || addr == data ); // to do: is CHR RAM ever bank-switched? + //dprintf( "Tried to set CHR RAM bank at %04X to CHR+%04X\n", addr, data ); + + if ( data + size > chr_size ) + data %= chr_size; + + int count = (unsigned) size / chr_page_size; + //assert( chr_page_size * count == size ); + //assert( addr + size <= chr_addr_size ); + + int page = (unsigned) addr / chr_page_size; + while ( count-- ) + { + chr_pages_ex [page] = data - page * chr_page_size; + page++; + data += chr_page_size; + } +} + +void Nes_Ppu_Impl::save_state( Nes_State_* out ) const +{ + *out->ppu = *this; + out->ppu_valid = true; + + memcpy( out->spr_ram, spr_ram, out->spr_ram_size ); + out->spr_ram_valid = true; + + out->nametable_size = 0x800; + memcpy( out->nametable, impl->nt_ram, 0x800 ); + if ( nt_banks [3] >= &impl->nt_ram [0xC00] ) + { + // save extra nametable data in chr + out->nametable_size = 0x1000; + memcpy( out->chr, &impl->nt_ram [0x800], 0x800 ); + } + + out->chr_size = 0; + if ( chr_is_writable ) + { + out->chr_size = chr_size; + memcpy( out->chr, impl->chr_ram, out->chr_size ); + } +} + +void Nes_Ppu_Impl::load_state( Nes_State_ const& in ) +{ + set_nt_banks( 0, 0, 0, 0 ); + set_chr_bank( 0, 0x2000, 0 ); + + if ( in.ppu_valid ) + STATIC_CAST(ppu_state_t&,*this) = *in.ppu; + + if ( in.spr_ram_valid ) + memcpy( spr_ram, in.spr_ram, sizeof spr_ram ); + + if ( in.nametable_size >= 0x800 ) + { + if ( in.nametable_size > 0x800 ) + memcpy( &impl->nt_ram [0x800], in.chr, 0x800 ); + memcpy( impl->nt_ram, in.nametable, 0x800 ); + } + + if ( chr_is_writable && in.chr_size ) + { + memcpy( impl->chr_ram, in.chr, in.chr_size ); + all_tiles_modified(); + } +} + +static uint8_t const initial_palette [0x20] = +{ + 0x0f,0x01,0x00,0x01,0x00,0x02,0x02,0x0D,0x08,0x10,0x08,0x24,0x00,0x00,0x04,0x2C, + 0x00,0x01,0x34,0x03,0x00,0x04,0x00,0x14,0x00,0x3A,0x00,0x02,0x00,0x20,0x2C,0x08 +}; + +void Nes_Ppu_Impl::reset( bool full_reset ) +{ + w2000 = 0; + w2001 = 0; + r2002 = 0x80; + r2007 = 0; + open_bus = 0; + decay_low = 0; + decay_high = 0; + second_write = false; + vram_temp = 0; + pixel_x = 0; + + if ( full_reset ) + { + vram_addr = 0; + w2003 = 0; + memset( impl->chr_ram, 0xff, sizeof impl->chr_ram ); + memset( impl->nt_ram, 0xff, sizeof impl->nt_ram ); + memcpy( palette, initial_palette, sizeof palette ); + } + + set_nt_banks( 0, 0, 0, 0 ); + set_chr_bank( 0, chr_addr_size, 0 ); + memset( spr_ram, 0xff, sizeof spr_ram ); + all_tiles_modified(); + if ( max_palette_size > 0 ) + memset( host_palette, 0, max_palette_size * sizeof *host_palette ); +} + +void Nes_Ppu_Impl::capture_palette() +{ + if ( palette_size + palette_increment <= max_palette_size ) + { + palette_offset = (palette_begin + palette_size) * 0x01010101; + + short* out = host_palette + palette_size; + palette_size += palette_increment; + + int i; + + int emph = w2001 << 1 & 0x1C0; + int mono = (w2001 & 1 ? 0x30 : 0x3F); + + for ( i = 0; i < 32; i++ ) + out [i] = (palette [i] & mono) | emph; + + int bg = out [0]; + for ( i = 4; i < 32; i += 4 ) + out [i] = bg; + + memcpy( out + 32, out, 32 * sizeof *out ); + } +} + +void Nes_Ppu_Impl::run_hblank( int count ) +{ + long addr = (vram_addr & 0x7be0) + (vram_temp & 0x41f) + (count * 0x1000); + if ( w2001 & 0x08 ) + { + while ( addr >= 0x8000 ) + { + int y = (addr + 0x20) & 0x3e0; + addr = (addr - 0x8000) & ~0x3e0; + if ( y == 30 * 0x20 ) + y = 0x800; + addr ^= y; + } + vram_addr = addr; + } +} + +#ifdef __MWERKS__ + #pragma ppc_unroll_factor_limit 1 // messes up calc_sprite_max_scanlines loop + static int zero = 0; +#else + const int zero = 0; +#endif + +// Tile cache + +inline unsigned long reorder( unsigned long n ) +{ + n |= n << 7; + return ((n << 14) | n); +} + +inline void Nes_Ppu_Impl::update_tile( int index ) +{ + const uint8_t* in = chr_data + (index) * bytes_per_tile; + uint8_t* out = (uint8_t*) tile_cache [index]; + uint8_t* flipped_out = (uint8_t*) flipped_tiles [index]; + + unsigned long bit_mask = 0x11111111 + zero; + + for ( int n = 4; n--; ) + { + // Reorder two lines of two-bit pixels. No bits are wasted, so + // reordered version is also four bytes. + // + // 12345678 to A0E4B1F5C2G6D3H7 + // ABCDEFGH + unsigned long c = + ((reorder( in [0] ) & bit_mask) << 0) | + ((reorder( in [8] ) & bit_mask) << 1) | + ((reorder( in [1] ) & bit_mask) << 2) | + ((reorder( in [9] ) & bit_mask) << 3); + in += 2; + + SET_BE32( out, c ); + out += 4; + + // make horizontally-flipped version + c = ((c >> 28) & 0x000f) | + ((c >> 20) & 0x00f0) | + ((c >> 12) & 0x0f00) | + ((c >> 4) & 0xf000) | + ((c & 0xf000) << 4) | + ((c & 0x0f00) << 12) | + ((c & 0x00f0) << 20) | + ((c & 0x000f) << 28); + SET_BE32( flipped_out, c ); + flipped_out += 4; + } +} + +void Nes_Ppu_Impl::rebuild_chr( unsigned long begin, unsigned long end ) +{ + unsigned end_index = (end + bytes_per_tile - 1) / bytes_per_tile; + for ( unsigned index = begin / bytes_per_tile; index < end_index; index++ ) + update_tile( index ); +} + +void Nes_Ppu_Impl::update_tiles( int first_tile ) +{ + int chunk = 0; + do + { + if ( !(uint32_t&) modified_tiles [chunk] ) + { + chunk += 4; + } + else + { + do + { + int modified = modified_tiles [chunk]; + if ( modified ) + { + modified_tiles [chunk] = 0; + + int index = first_tile + chunk * 8; + do + { + if ( modified & 1 ) + update_tile( index ); + index++; + } + while ( (modified >>= 1) != 0 ); + } + } + while ( ++chunk & 3 ); + } + } + while ( chunk < chr_tile_count / 8 ); +} + +// Sprite max + +template +struct calc_sprite_max_scanlines +{ + static unsigned long func( uint8_t const* sprites, uint8_t* scanlines, int begin ) + { + unsigned long any_hits = 0; + unsigned long const offset = 0x01010101 + zero; + unsigned limit = 239 + height - begin; + for ( int n = 64; n; --n ) + { + int top = *sprites; + sprites += 4; + uint8_t* p = scanlines + top; + if ( (unsigned) (239 - top) < limit ) + { + unsigned long p0 = ((unaligned_uint32_t*)p) [0].val + offset; + unsigned long p4 = ((unaligned_uint32_t*)p) [1].val + offset; + ((unaligned_uint32_t*)p) [0].val = p0; + any_hits |= p0; + ((unaligned_uint32_t*)p) [1].val = p4; + any_hits |= p4; + if ( height > 8 ) + { + unsigned long p0 = ((unaligned_uint32_t*)p) [2].val + offset; + unsigned long p4 = ((unaligned_uint32_t*)p) [3].val + offset; + ((unaligned_uint32_t*)p) [2].val = p0; + any_hits |= p0; + ((unaligned_uint32_t*)p) [3].val = p4; + any_hits |= p4; + } + } + } + + return any_hits; + } +}; + +long Nes_Ppu_Impl::recalc_sprite_max( int scanline ) +{ + int const max_scanline_count = image_height; + + uint8_t sprite_max_scanlines [256 + 16]; + + // recalculate sprites per scanline + memset( sprite_max_scanlines + scanline, 0x78, last_sprite_max_scanline - scanline ); + unsigned long any_hits; + if ( w2000 & 0x20 ) + any_hits = calc_sprite_max_scanlines<16>::func( spr_ram, sprite_max_scanlines, scanline ); + else + any_hits = calc_sprite_max_scanlines<8 >::func( spr_ram, sprite_max_scanlines, scanline ); + + // cause search to terminate past max_scanline_count if none have 8 or more sprites + (uint32_t&) sprite_max_scanlines [max_scanline_count] = 0; + sprite_max_scanlines [max_scanline_count + 3] = 0x80; + + // avoid scan if no possible hits + if ( !(any_hits & 0x80808080) ) + return 0; + + // find soonest scanline with 8 or more sprites + while ( true ) + { + unsigned long const mask = 0x80808080 + zero; + + // check four at a time + uint8_t* pos = &sprite_max_scanlines [scanline]; + unsigned long n = ((unaligned_uint32_t*)pos)->val; + while ( 1 ) + { + unsigned long x = n & mask; + pos += 4; + n = ((unaligned_uint32_t*)pos)->val; + if ( x ) + break; + } + + int height = sprite_height(); + int remain = 8; + int i = 0; + + // find which of the four + pos -= 3 + (pos [-4] >> 7 & 1); + pos += 1 - (*pos >> 7 & 1); + pos += 1 - (*pos >> 7 & 1); + + scanline = pos - sprite_max_scanlines; + if ( scanline >= max_scanline_count ) + break; + + // find time that max sprites flag is set (or that it won't be set) + do + { + int relative = scanline - spr_ram [i]; + i += 4; + if ( (unsigned) relative < (unsigned) height && !--remain ) + { + // now use screwey search for 9th sprite + int offset = 0; + while ( i < 0x100 ) + { + int relative = scanline - spr_ram [i + offset]; + //dprintf( "Checking sprite %d [%d]\n", i / 4, offset ); + i += 4; + offset = (offset + 1) & 3; + if ( (unsigned) relative < (unsigned) height ) + { + //dprintf( "sprite max on scanline %d\n", scanline ); + return scanline * scanline_len + (unsigned) i / 2; + } + } + break; + } + } + while ( i < 0x100 ); + scanline++; + } + + return 0; +} diff --git a/core/Nes_Ppu_Impl.h b/core/Nes_Ppu_Impl.h new file mode 100644 index 0000000..1a27218 --- /dev/null +++ b/core/Nes_Ppu_Impl.h @@ -0,0 +1,218 @@ + +// NES PPU misc functions and setup + +// Nes_Emu 0.7.0 + +#ifndef NES_PPU_IMPL_H +#define NES_PPU_IMPL_H + +#include "nes_data.h" +class Nes_State_; + +class Nes_Ppu_Impl : public ppu_state_t { +public: + Nes_Ppu_Impl(); + ~Nes_Ppu_Impl(); + + void reset( bool full_reset ); + + // Setup + const char * open_chr( const uint8_t*, long size ); + void rebuild_chr( unsigned long begin, unsigned long end ); + void close_chr(); + void save_state( Nes_State_* out ) const; + void load_state( Nes_State_ const& ); + + enum { image_width = 256 }; + enum { image_height = 240 }; + enum { image_left = 8 }; + enum { buffer_width = image_width + 16 }; + enum { buffer_height = image_height }; + + int write_2007( int ); + + // Host palette + enum { palette_increment = 64 }; + short* host_palette; + int palette_begin; + int max_palette_size; + int palette_size; // set after frame is rendered + + // Mapping + enum { vaddr_clock_mask = 0x1000 }; + void set_nt_banks( int bank0, int bank1, int bank2, int bank3 ); + void set_chr_bank( int addr, int size, long data ); + void set_chr_bank_ex( int addr, int size, long data ); + + // Nametable and CHR RAM + enum { nt_ram_size = 0x1000 }; + enum { chr_addr_size = 0x2000 }; + enum { bytes_per_tile = 16 }; + enum { chr_tile_count = chr_addr_size / bytes_per_tile }; + enum { mini_offscreen_height = 16 }; // double-height sprite + struct impl_t + { + uint8_t nt_ram [nt_ram_size]; + uint8_t chr_ram [chr_addr_size]; + union { + uint32_t clip_buf [256 * 2]; + uint8_t mini_offscreen [buffer_width * mini_offscreen_height]; + }; + }; + impl_t* impl; + enum { scanline_len = 341 }; + +protected: + uint8_t spr_ram [0x100]; + void begin_frame(); + void run_hblank( int ); + int sprite_height() const { return (w2000 >> 2 & 8) + 8; } + +protected: //friend class Nes_Ppu; private: + + int addr_inc; // pre-calculated $2007 increment (based on w2001 & 0x04) + int read_2007( int addr ); + + enum { last_sprite_max_scanline = 240 }; + long recalc_sprite_max( int scanline ); + int first_opaque_sprite_line(); + +protected: //friend class Nes_Ppu_Rendering; private: + + unsigned long palette_offset; + int palette_changed; + void capture_palette(); + + bool any_tiles_modified; + bool chr_is_writable; + void update_tiles( int first_tile ); + + typedef uint32_t cache_t; + typedef cache_t cached_tile_t [4]; + cached_tile_t const& get_bg_tile( int index ); + cached_tile_t const& get_sprite_tile( uint8_t const* sprite ); + uint8_t* get_nametable( int addr ) { return nt_banks [addr >> 10 & 3]; }; + +private: + + static int map_palette( int addr ); + int sprite_tile_index( uint8_t const* sprite ) const; + + // Mapping + enum { chr_page_size = 0x400 }; + long chr_pages [chr_addr_size / chr_page_size]; + long chr_pages_ex [chr_addr_size / chr_page_size]; + long map_chr_addr( unsigned a ) /*const*/ + { + if (!mmc24_enabled) + return chr_pages [a / chr_page_size] + a; + + int page = a >> 12 & 1; + int newval0 = (a & 0xff0) != 0xfd0; + int newval1 = (a & 0xff0) == 0xfe0; + + long ret; + if (mmc24_latched[page]) + ret = chr_pages_ex [a / chr_page_size] + a; + else + ret = chr_pages [a / chr_page_size] + a; + + mmc24_latched[page] &= newval0; + mmc24_latched[page] |= newval1; + + return ret; + } + uint8_t* nt_banks [4]; + + bool mmc24_enabled; + uint8_t mmc24_latched [2]; + + // CHR data + uint8_t const* chr_data; // points to chr ram when there is no read-only data + uint8_t* chr_ram; // always points to impl->chr_ram; makes write_2007() faster + long chr_size; + uint8_t const* map_chr( int addr ) { return &chr_data [map_chr_addr( addr )]; } + + // CHR cache + cached_tile_t* tile_cache; + cached_tile_t* flipped_tiles; + uint8_t* tile_cache_mem; + union { + uint8_t modified_tiles [chr_tile_count / 8]; + uint32_t align_; + }; + void all_tiles_modified(); + void update_tile( int index ); +}; + +inline void Nes_Ppu_Impl::set_nt_banks( int bank0, int bank1, int bank2, int bank3 ) +{ + uint8_t* nt_ram = impl->nt_ram; + nt_banks [0] = &nt_ram [bank0 * 0x400]; + nt_banks [1] = &nt_ram [bank1 * 0x400]; + nt_banks [2] = &nt_ram [bank2 * 0x400]; + nt_banks [3] = &nt_ram [bank3 * 0x400]; +} + +inline int Nes_Ppu_Impl::map_palette( int addr ) +{ + if ( (addr & 3) == 0 ) + addr &= 0x0f; // 0x10, 0x14, 0x18, 0x1c map to 0x00, 0x04, 0x08, 0x0c + return addr & 0x1f; +} + +inline int Nes_Ppu_Impl::sprite_tile_index( uint8_t const* sprite ) const +{ + int tile = sprite [1] + (w2000 << 5 & 0x100); + if ( w2000 & 0x20 ) + tile = (tile & 1) * 0x100 + (tile & 0xfe); + return tile; +} + +inline int Nes_Ppu_Impl::write_2007( int data ) +{ + int addr = vram_addr; + uint8_t * chr_ram = this->chr_ram; // pre-read + int changed = addr + addr_inc; + unsigned const divisor = bytes_per_tile * 8; + int mod_index = (unsigned) addr / divisor % (0x4000 / divisor); + vram_addr = changed; + changed ^= addr; + addr &= 0x3fff; + + // use index into modified_tiles [] since it's calculated sooner than addr is masked + if ( (unsigned) mod_index < 0x2000 / divisor ) + { + // Avoid overhead of checking for read-only CHR; if that is the case, + // this modification will be ignored. + int mod = modified_tiles [mod_index]; + chr_ram [addr] = data; + any_tiles_modified = true; + modified_tiles [mod_index] = mod | (1 << ((unsigned) addr / bytes_per_tile % 8)); + } + else if ( addr < 0x3f00 ) + { + get_nametable( addr ) [addr & 0x3ff] = data; + } + else + { + data &= 0x3f; + uint8_t& entry = palette [map_palette( addr )]; + int changed = entry ^ data; + entry = data; + if ( changed ) + palette_changed = 0x18; + } + + return changed; +} + +inline void Nes_Ppu_Impl::begin_frame() +{ + palette_changed = 0x18; + palette_size = 0; + palette_offset = palette_begin * 0x01010101; + addr_inc = w2000 & 4 ? 32 : 1; +} + +#endif diff --git a/core/Nes_Ppu_Rendering.cpp b/core/Nes_Ppu_Rendering.cpp new file mode 100644 index 0000000..2abef34 --- /dev/null +++ b/core/Nes_Ppu_Rendering.cpp @@ -0,0 +1,464 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Ppu_Rendering.h" + +#include +#include + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +#ifdef __MWERKS__ + static unsigned zero = 0; // helps CodeWarrior optimizer when added to constants +#else + const unsigned zero = 0; // compile-time constant on other compilers +#endif + +// Nes_Ppu_Impl + +inline Nes_Ppu_Impl::cached_tile_t const& + Nes_Ppu_Impl::get_sprite_tile( uint8_t const* sprite ) +{ + cached_tile_t* tiles = tile_cache; + if ( sprite [2] & 0x40 ) + tiles = flipped_tiles; + int index = sprite_tile_index( sprite ); + + // use index directly, since cached tile is same size as native tile + BOOST_STATIC_ASSERT( sizeof (cached_tile_t) == bytes_per_tile ); + return *(Nes_Ppu_Impl::cached_tile_t*) + ((uint8_t*) tiles + map_chr_addr( index * bytes_per_tile )); +} + +inline Nes_Ppu_Impl::cached_tile_t const& Nes_Ppu_Impl::get_bg_tile( int index ) +{ + // use index directly, since cached tile is same size as native tile + BOOST_STATIC_ASSERT( sizeof (cached_tile_t) == bytes_per_tile ); + return *(Nes_Ppu_Impl::cached_tile_t*) + ((uint8_t*) tile_cache + map_chr_addr( index * bytes_per_tile )); +} + +// Fill + +void Nes_Ppu_Rendering::fill_background( int count ) +{ + ptrdiff_t const next_line = scanline_row_bytes - image_width; + uint32_t* pixels = (uint32_t*) scanline_pixels; + + unsigned long fill = palette_offset; + if ( (vram_addr & 0x3f00) == 0x3f00 ) + { + // PPU uses current palette entry if addr is within palette ram + int color = vram_addr & 0x1f; + if ( !(color & 3) ) + color &= 0x0f; + fill += color * 0x01010101; + } + + for ( int n = count; n--; ) + { + for ( int n = image_width / 16; n--; ) + { + pixels [0] = fill; + pixels [1] = fill; + pixels [2] = fill; + pixels [3] = fill; + pixels += 4; + } + pixels = (uint32_t*) ((uint8_t*) pixels + next_line); + } +} + +void Nes_Ppu_Rendering::clip_left( int count ) +{ + ptrdiff_t next_line = scanline_row_bytes; + uint8_t* p = scanline_pixels; + unsigned long fill = palette_offset; + + for ( int n = count; n--; ) + { + ((uint32_t*) p) [0] = fill; + ((uint32_t*) p) [1] = fill; + p += next_line; + } +} + +void Nes_Ppu_Rendering::save_left( int count ) +{ + ptrdiff_t next_line = scanline_row_bytes; + uint8_t* in = scanline_pixels; + uint32_t* out = impl->clip_buf; + + for ( int n = count; n--; ) + { + unsigned long in0 = ((uint32_t*) in) [0]; + unsigned long in1 = ((uint32_t*) in) [1]; + in += next_line; + out [0] = in0; + out [1] = in1; + out += 2; + } +} + +void Nes_Ppu_Rendering::restore_left( int count ) +{ + ptrdiff_t next_line = scanline_row_bytes; + uint8_t* out = scanline_pixels; + uint32_t* in = impl->clip_buf; + + for ( int n = count; n--; ) + { + unsigned long in0 = in [0]; + unsigned long in1 = in [1]; + in += 2; + ((uint32_t*) out) [0] = in0; + ((uint32_t*) out) [1] = in1; + out += next_line; + } +} + +// Background + +void Nes_Ppu_Rendering::draw_background_( int remain ) +{ + // Draws 'remain' background scanlines. Does not modify vram_addr. + + int vram_addr = this->vram_addr & 0x7fff; + uint8_t* row_pixels = scanline_pixels - pixel_x; + int left_clip = (w2001 >> 1 & 1) ^ 1; + row_pixels += left_clip * 8; + do + { + // scanlines until next row + int height = 8 - (vram_addr >> 12); + if ( height > remain ) + height = remain; + + // handle hscroll change before next scanline + int hscroll_changed = (vram_addr ^ vram_temp) & 0x41f; + int addr = vram_addr; + if ( hscroll_changed ) + { + vram_addr ^= hscroll_changed; + height = 1; // hscroll will change after first line + } + remain -= height; + + // increment address for next row + vram_addr += height << 12; + if ( vram_addr & 0x8000 ) + { + int y = (vram_addr + 0x20) & 0x3e0; + vram_addr &= 0x7fff & ~0x3e0; + if ( y == 30 * 0x20 ) + y = 0x800; // toggle vertical nametable + vram_addr ^= y; + } + + // nametable change usually occurs in middle of row + uint8_t const* nametable = get_nametable( addr ); + uint8_t const* nametable2 = get_nametable( addr ^ 0x400 ); + int count2 = addr & 31; + int count = 32 - count2 - left_clip; + //if ( pixel_x ) + count2++; + + uint8_t const* attr_table = &nametable [0x3c0 | (addr >> 4 & 0x38)]; + int bg_bank = (w2000 << 4) & 0x100; + addr += left_clip; + + // output pixels + ptrdiff_t const row_bytes = scanline_row_bytes; + uint8_t* pixels = row_pixels; + row_pixels += height * row_bytes; + + unsigned long const mask = 0x03030303 + zero; + unsigned long const attrib_factor = 0x04040404 + zero; + + if ( height == 8 ) + { + // unclipped + addr &= 0x03ff; + int const fine_y = 0; + int const clipped = false; + #include "Nes_Ppu_Bg.h" + } + else + { + // clipped + int const fine_y = addr >> 12; + addr &= 0x03ff; + height -= fine_y & 1; + int const clipped = true; + #include "Nes_Ppu_Bg.h" + } + } + while ( remain ); +} + +// Sprites + +void Nes_Ppu_Rendering::draw_sprites_( int begin, int end ) +{ + // Draws sprites on scanlines begin through end - 1. Handles clipping. + + int const sprite_height = this->sprite_height(); + int end_minus_one = end - 1; + int begin_minus_one = begin - 1; + int index = 0; + do + { + uint8_t const* sprite = &spr_ram [index]; + index += 4; + + // find if sprite is visible + int top_minus_one = sprite [0]; + int visible = end_minus_one - top_minus_one; + if ( visible <= 0 ) + continue; // off bottom + + // quickly determine whether sprite is unclipped + int neg_vis = visible - sprite_height; + int neg_skip = top_minus_one - begin_minus_one; + if ( (neg_skip | neg_vis) >= 0 ) // neg_skip >= 0 && neg_vis >= 0 + { + int const skip = 0; + int visible = sprite_height; + + #define CLIPPED 0 + #include "Nes_Ppu_Sprites.h" + } + else + { + // clipped + if ( neg_vis > 0 ) + visible -= neg_vis; + + if ( neg_skip > 0 ) + neg_skip = 0; + visible += neg_skip; + + if ( visible <= 0 ) + continue; // off top + + int skip = -neg_skip; + + //dprintf( "begin: %d, end: %d, top: %d, skip: %d, visible: %d\n", + // begin, end, top_minus_one + 1, skip, visible ); + + #define CLIPPED 1 + #include "Nes_Ppu_Sprites.h" + } + } + while ( index < 0x100 ); +} + +void Nes_Ppu_Rendering::check_sprite_hit( int begin, int end ) +{ + // Checks for sprite 0 hit on scanlines begin through end - 1. + // Updates sprite_hit_found. Background (but not sprites) must have + // already been rendered for the scanlines. + + // clip + int top = spr_ram [0] + 1; + int skip = begin - top; + if ( skip < 0 ) + skip = 0; + + top += skip; + int visible = end - top; + if ( visible <= 0 ) + return; // not visible + + int height = sprite_height(); + if ( visible >= height ) + { + visible = height; + sprite_hit_found = -1; // signal that no more hit checking will take place + } + + // pixels + ptrdiff_t next_row = this->scanline_row_bytes; + uint8_t const* bg = this->scanline_pixels + spr_ram [3] + (top - begin) * next_row; + cache_t const* lines = get_sprite_tile( spr_ram ); + + // left edge clipping + int start_x = 0; + if ( spr_ram [3] < 8 && (w2001 & 0x01e) != 0x1e ) + { + if ( spr_ram [3] == 0 ) + return; // won't hit + start_x = 8 - spr_ram [3]; + } + + // vertical flip + int final = skip + visible; + if ( spr_ram [2] & 0x80 ) + { + skip += height - 1; + final = skip - visible; + } + + // check each line + unsigned long const mask = 0x01010101 + zero; + do + { + // get pixels for line + unsigned long line = lines [skip >> 1]; + unsigned long hit0 = ((unaligned_uint32_t*) bg) [0].val; + unsigned long hit1 = ((unaligned_uint32_t*) bg) [1].val; + bg += next_row; + line >>= skip << 1 & 2; + line |= line >> 1; + + // check for hits + hit0 = ((hit0 >> 1) | hit0) & (line >> 4); + hit1 = ((hit1 >> 1) | hit1) & line; + if ( (hit0 | hit1) & mask ) + { + // write to memory to avoid endian issues + uint32_t quads [3]; + quads [0] = hit0; + quads [1] = hit1; + + // find which pixel hit + int x = start_x; + do + { + if ( ((uint8_t*) quads) [x] & 1 ) + { + x += spr_ram [3]; + if ( x >= 255 ) + break; // ignore right edge + + if ( spr_ram [2] & 0x80 ) + skip = height - 1 - skip; // vertical flip + int y = spr_ram [0] + 1 + skip; + sprite_hit_found = y * scanline_len + x; + + return; + } + } + while ( x++ < 7 ); + } + if ( skip > final ) + skip -= 2; + skip++; + } + while ( skip != final ); +} + +// Draw scanlines + +inline bool Nes_Ppu_Rendering::sprite_hit_possible( int scanline ) const +{ + return !sprite_hit_found && spr_ram [0] <= scanline && (w2001 & 0x18) == 0x18; +} + +void Nes_Ppu_Rendering::draw_scanlines( int start, int count, + uint8_t* pixels, long pitch, int mode ) +{ + scanline_pixels = pixels + image_left; + scanline_row_bytes = pitch; + + int const obj_mask = 2; + int const bg_mask = 1; + int draw_mode = (w2001 >> 3) & 3; + int clip_mode = (~w2001 >> 1) & draw_mode; + + if ( !(draw_mode & bg_mask) ) + { + // no background + clip_mode |= bg_mask; // avoid unnecessary save/restore + if ( mode & bg_mask ) + fill_background( count ); + } + + if ( start == 0 && mode & 1 ) + memset( sprite_scanlines, max_sprites - sprite_limit, 240 ); + + if ( (draw_mode &= mode) ) + { + // sprites and/or background are being rendered + + if ( any_tiles_modified && chr_is_writable ) + { + any_tiles_modified = false; + update_tiles( 0 ); + } + + if ( draw_mode & bg_mask ) + { + //dprintf( "bg %3d-%3d\n", start, start + count - 1 ); + draw_background_( count ); + + if ( clip_mode == bg_mask ) + clip_left( count ); + + if ( sprite_hit_possible( start + count ) ) + check_sprite_hit( start, start + count ); + } + + if ( draw_mode & obj_mask ) + { + // when clipping just sprites, save left strip then restore after drawing them + if ( clip_mode == obj_mask ) + save_left( count ); + + //dprintf( "obj %3d-%3d\n", start, start + count - 1 ); + + draw_sprites_( start, start + count ); + + if ( clip_mode == obj_mask ) + restore_left( count ); + + if ( clip_mode == (obj_mask | bg_mask) ) + clip_left( count ); + } + } + + scanline_pixels = NULL; +} + +void Nes_Ppu_Rendering::draw_background( int start, int count ) +{ + // always capture palette at least once per frame + if ( (start + count >= 240 && !palette_size) || (w2001 & palette_changed) ) + { + palette_changed = false; + capture_palette(); + } + + if ( host_pixels ) + { + draw_scanlines( start, count, host_pixels + host_row_bytes * start, host_row_bytes, 1 ); + } + else if ( sprite_hit_possible( start + count ) ) + { + // not rendering, but still handle sprite hit using mini graphics buffer + int y = spr_ram [0] + 1; + int skip = min( count, max( y - start, 0 ) ); + int visible = min( count - skip, sprite_height() ); + + if ( visible > 0 ) + { + run_hblank( skip ); + draw_scanlines( start + skip, visible, impl->mini_offscreen, buffer_width, 3 ); + } + } +} diff --git a/core/Nes_Ppu_Rendering.h b/core/Nes_Ppu_Rendering.h new file mode 100644 index 0000000..38ac8ed --- /dev/null +++ b/core/Nes_Ppu_Rendering.h @@ -0,0 +1,61 @@ + +// NES PPU emulator graphics rendering + +// Nes_Emu 0.7.0 + +#ifndef NES_PPU_RENDERING_H +#define NES_PPU_RENDERING_H + +#include "Nes_Ppu_Impl.h" + +class Nes_Ppu_Rendering : public Nes_Ppu_Impl { + typedef Nes_Ppu_Impl base; +public: + Nes_Ppu_Rendering(); + + int sprite_limit; + + uint8_t* host_pixels; + long host_row_bytes; + +protected: + + long sprite_hit_found; // -1: sprite 0 didn't hit, 0: no hit so far, > 0: y * 341 + x + void draw_background( int start, int count ); + void draw_sprites( int start, int count ); + +private: + + void draw_scanlines( int start, int count, uint8_t* pixels, long pitch, int mode ); + void draw_background_( int count ); + + // destination for draw functions; avoids extra parameters + uint8_t* scanline_pixels; + long scanline_row_bytes; + + // fill/copy + void fill_background( int count ); + void clip_left( int count ); + void save_left( int count ); + void restore_left( int count ); + + // sprites + enum { max_sprites = 64 }; + uint8_t sprite_scanlines [image_height]; // number of sprites on each scanline + void draw_sprites_( int start, int count ); + bool sprite_hit_possible( int scanline ) const; + void check_sprite_hit( int begin, int end ); +}; + +inline Nes_Ppu_Rendering::Nes_Ppu_Rendering() +{ + sprite_limit = 8; + host_pixels = NULL; +} + +inline void Nes_Ppu_Rendering::draw_sprites( int start, int count ) +{ + draw_scanlines( start, count, host_pixels + host_row_bytes * start, host_row_bytes, 2 ); +} + +#endif diff --git a/core/Nes_Ppu_Sprites.h b/core/Nes_Ppu_Sprites.h new file mode 100644 index 0000000..8d936f3 --- /dev/null +++ b/core/Nes_Ppu_Sprites.h @@ -0,0 +1,131 @@ + +int sprite_2 = sprite [2]; + +// pixels +ptrdiff_t next_row = this->scanline_row_bytes; +uint8_t* out = this->scanline_pixels + sprite [3] + + (top_minus_one + skip - begin_minus_one) * next_row; +cache_t const* lines = get_sprite_tile( sprite ); + +int dir = 1; +uint8_t* scanlines = this->sprite_scanlines + 1 + top_minus_one + skip; + +if ( sprite_2 & 0x80 ) +{ + // vertical flip + out -= next_row; + out += visible * next_row; + next_row = -next_row; + dir = -1; + scanlines += visible - 1; + #if CLIPPED + int height = this->sprite_height(); + skip = height - skip - visible; + #endif +} + +// attributes +unsigned long offset = (sprite_2 & 3) * 0x04040404 + (this->palette_offset + 0x10101010); + +unsigned long const mask = 0x03030303 + zero; +unsigned long const maskgen = 0x80808080 + zero; + +#define DRAW_PAIR( shift ) { \ + int sprite_count = *scanlines; \ + CALC_FOUR( ((unaligned_uint32_t*) out) [0].val, (line >> (shift + 4)), out0 ) \ + CALC_FOUR( ((unaligned_uint32_t*) out) [1].val, (line >> shift), out1 ) \ + if ( sprite_count < this->max_sprites ) { \ + ((unaligned_uint32_t*) out) [0].val = out0; \ + ((unaligned_uint32_t*) out) [1].val = out1; \ + } \ + if ( CLIPPED ) visible--; \ + out += next_row; \ + *scanlines = sprite_count + 1; \ + scanlines += dir; \ + if ( CLIPPED && !visible ) break; \ +} + +if ( !(sprite_2 & 0x20) ) +{ + // front + unsigned long const maskgen2 = 0x7f7f7f7f + zero; + + #define CALC_FOUR( in, line, out ) \ + unsigned long out; \ + { \ + unsigned long bg = in; \ + unsigned long sp = line & mask; \ + unsigned long bgm = maskgen2 + ((bg >> 4) & mask); \ + unsigned long spm = (maskgen - sp) & maskgen2; \ + unsigned long m = (bgm & spm) >> 2; \ + out = (bg & ~m) | ((sp + offset) & m); \ + } + + #if CLIPPED + lines += skip >> 1; + unsigned long line = *lines++; + if ( skip & 1 ) + goto front_skip; + + while ( true ) + { + DRAW_PAIR( 0 ) + front_skip: + DRAW_PAIR( 2 ) + line = *lines++; + } + #else + for ( int n = visible >> 1; n--; ) + { + unsigned long line = *lines++; + DRAW_PAIR( 0 ) + DRAW_PAIR( 2 ) + } + #endif + + #undef CALC_FOUR +} +else +{ + // behind + unsigned long const omask = 0x20202020 + zero; + unsigned long const bg_or = 0xc3c3c3c3 + zero; + + #define CALC_FOUR( in, line, out ) \ + unsigned long out; \ + { \ + unsigned long bg = in; \ + unsigned long sp = line & mask; \ + unsigned long bgm = maskgen - (bg & mask); \ + unsigned long spm = maskgen - sp; \ + out = (bg & (bgm | bg_or)) | (spm & omask) | \ + (((offset & spm) + sp) & ~(bgm >> 2)); \ + } + + #if CLIPPED + lines += skip >> 1; + unsigned long line = *lines++; + if ( skip & 1 ) + goto back_skip; + + while ( true ) + { + DRAW_PAIR( 0 ) + back_skip: + DRAW_PAIR( 2 ) + line = *lines++; + } + #else + for ( int n = visible >> 1; n--; ) + { + unsigned long line = *lines++; + DRAW_PAIR( 0 ) + DRAW_PAIR( 2 ) + } + #endif + + #undef CALC_FOUR +} + +#undef CLIPPED +#undef DRAW_PAIR diff --git a/core/Nes_State.cpp b/core/Nes_State.cpp new file mode 100644 index 0000000..7711acb --- /dev/null +++ b/core/Nes_State.cpp @@ -0,0 +1,284 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_State.h" + +#include +#include + +#include "blargg_endian.h" +#include "Nes_Emu.h" +#include "Nes_Mapper.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +int mem_differs( void const* p, int cmp, unsigned long s ) +{ + unsigned char const* cp = (unsigned char*) p; + while ( s-- ) + { + if ( *cp++ != cmp ) + return 1; + } + return 0; +} + +Nes_State::Nes_State() +{ + Nes_State_::cpu = &this->cpu; + Nes_State_::joypad = &this->joypad; + Nes_State_::apu = &this->apu; + Nes_State_::ppu = &this->ppu; + Nes_State_::mapper = &this->mapper; + Nes_State_::ram = this->ram; + Nes_State_::sram = this->sram; + Nes_State_::spr_ram = this->spr_ram; + Nes_State_::nametable = this->nametable; + Nes_State_::chr = this->chr; +} + +void Nes_State_::clear() +{ + memset( &nes, 0, sizeof nes ); + nes.frame_count = static_cast(invalid_frame_count); + + nes_valid = false; + cpu_valid = false; + joypad_valid = false; + apu_valid = false; + ppu_valid = false; + mapper_valid = false; + ram_valid = false; + sram_size = 0; + spr_ram_valid = false; + nametable_size = 0; + chr_size = 0; +} + +// write + +const char * Nes_State_Writer::end( Nes_Emu const& emu ) +{ + Nes_State* state = new Nes_State; + CHECK_ALLOC( state ); + emu.save_state( state ); + const char * err = end( *state ); + delete state; + return err; +} + +const char * Nes_State_Writer::end( Nes_State const& ss ) +{ + RETURN_ERR( ss.write_blocks( *this ) ); + return Nes_File_Writer::end(); +} + +const char * Nes_State::write( Auto_File_Writer out ) const +{ + Nes_State_Writer writer; + RETURN_ERR( writer.begin( out ) ); + return writer.end( *this ); +} + +const char * Nes_State_::write_blocks( Nes_File_Writer& out ) const +{ + if ( nes_valid ) + { + nes_state_t s = nes; + s.timestamp *= 5; + RETURN_ERR( write_nes_state( out, s ) ); + } + + if ( cpu_valid ) + { + cpu_state_t s; + memset( &s, 0, sizeof s ); + s.pc = cpu->pc; + s.s = cpu->sp; + s.a = cpu->a; + s.x = cpu->x; + s.y = cpu->y; + s.p = cpu->status; + RETURN_ERR( write_nes_state( out, s ) ); + } + + if ( ppu_valid ) + { + ppu_state_t s = *ppu; + RETURN_ERR( write_nes_state( out, s ) ); + } + + if ( apu_valid ) + { + apu_state_t s = *apu; + RETURN_ERR( write_nes_state( out, s ) ); + } + + if ( joypad_valid ) + { + joypad_state_t s = *joypad; + RETURN_ERR( write_nes_state( out, s ) ); + } + + if ( mapper_valid ) + RETURN_ERR( out.write_block( FOUR_CHAR('MAPR'), mapper->data, mapper->size ) ); + + if ( ram_valid ) + RETURN_ERR( out.write_block( FOUR_CHAR('LRAM'), ram, ram_size ) ); + + if ( spr_ram_valid ) + RETURN_ERR( out.write_block( FOUR_CHAR('SPRT'), spr_ram, spr_ram_size ) ); + + if ( nametable_size ) + { + RETURN_ERR( out.write_block_header( FOUR_CHAR('NTAB'), nametable_size ) ); + RETURN_ERR( out.write( nametable, 0x800 ) ); + if ( nametable_size > 0x800 ) + RETURN_ERR( out.write( chr, 0x800 ) ); + } + + if ( chr_size ) + RETURN_ERR( out.write_block( FOUR_CHAR('CHRR'), chr, chr_size ) ); + + if ( sram_size ) + RETURN_ERR( out.write_block( FOUR_CHAR('SRAM'), sram, sram_size ) ); + + return 0; +} + +// read + +Nes_State_Reader::Nes_State_Reader() { state_ = 0; owned = 0; } + +Nes_State_Reader::~Nes_State_Reader() { delete owned; } + +const char * Nes_State_Reader::begin( Auto_File_Reader dr, Nes_State* out ) +{ + state_ = out; + if ( !out ) + CHECK_ALLOC( state_ = owned = new Nes_State ); + + RETURN_ERR( Nes_File_Reader::begin( dr ) ); + if ( block_tag() != state_file_tag ) + return "Not a state snapshot file"; + return 0; +} + +const char * Nes_State::read( Auto_File_Reader in ) +{ + Nes_State_Reader reader; + RETURN_ERR( reader.begin( in, this ) ); + while ( !reader.done() ) + RETURN_ERR( reader.next_block() ); + + return 0; +} + +const char * Nes_State_Reader::next_block() +{ + if ( depth() != 0 ) + return Nes_File_Reader::next_block(); + return state_->read_blocks( *this ); +} + +void Nes_State_::set_nes_state( nes_state_t const& s ) +{ + nes = s; + nes.timestamp /= 5; + nes_valid = true; +} + +const char * Nes_State_::read_blocks( Nes_File_Reader& in ) +{ + while ( true ) + { + RETURN_ERR( in.next_block() ); + switch ( in.block_tag() ) + { + case nes_state_t::tag: + memset( &nes, 0, sizeof nes ); + RETURN_ERR( read_nes_state( in, &nes ) ); + set_nes_state( nes ); + break; + + case cpu_state_t::tag: { + cpu_state_t s; + memset( &s, 0, sizeof s ); + RETURN_ERR( read_nes_state( in, &s ) ); + cpu->pc = s.pc; + cpu->sp = s.s; + cpu->a = s.a; + cpu->x = s.x; + cpu->y = s.y; + cpu->status = s.p; + cpu_valid = true; + break; + } + + case ppu_state_t::tag: + memset( ppu, 0, sizeof *ppu ); + RETURN_ERR( read_nes_state( in, ppu ) ); + ppu_valid = true; + break; + + case apu_state_t::tag: + memset( apu, 0, sizeof *apu ); + RETURN_ERR( read_nes_state( in, apu ) ); + apu_valid = true; + break; + + case joypad_state_t::tag: + memset( joypad, 0, sizeof *joypad ); + RETURN_ERR( read_nes_state( in, joypad ) ); + joypad_valid = true; + break; + + case FOUR_CHAR('MAPR'): + mapper->size = in.remain(); + RETURN_ERR( in.read_block_data( mapper->data, sizeof mapper->data ) ); + mapper_valid = true; + break; + + case FOUR_CHAR('SPRT'): + spr_ram_valid = true; + RETURN_ERR( in.read_block_data( spr_ram, spr_ram_size ) ); + break; + + case FOUR_CHAR('NTAB'): + nametable_size = in.remain(); + RETURN_ERR( in.read( nametable, 0x800 ) ); + if ( nametable_size > 0x800 ) + RETURN_ERR( in.read( chr, 0x800 ) ); + break; + + case FOUR_CHAR('LRAM'): + ram_valid = true; + RETURN_ERR( in.read_block_data( ram, ram_size ) ); + break; + + case FOUR_CHAR('CHRR'): + chr_size = in.remain(); + RETURN_ERR( in.read_block_data( chr, chr_max ) ); + break; + + case FOUR_CHAR('SRAM'): + sram_size = in.remain(); + RETURN_ERR( in.read_block_data( sram, sram_max ) ); + break; + + default: + return 0; + } + } +} diff --git a/core/Nes_State.h b/core/Nes_State.h new file mode 100644 index 0000000..c7d1c9e --- /dev/null +++ b/core/Nes_State.h @@ -0,0 +1,131 @@ + +// NES state snapshot for saving and restoring emulator state + +// Nes_Emu 0.7.0 + +#ifndef NES_STATE_H +#define NES_STATE_H + +#include "Nes_File.h" +#include "Nes_Cpu.h" +class Nes_Emu; +class Nes_State; + +typedef long frame_count_t; + +// Writes state to a file +class Nes_State_Writer : public Nes_File_Writer { +public: + // Begin writing file + const char * begin( Auto_File_Writer ); + + // Write emulator's current state to file and end + const char * end( Nes_Emu const& ); + + // Write state to file and end + const char * end( Nes_State const& ); +}; + +// Reads state from a file +class Nes_State_Reader : public Nes_File_Reader { +public: + + // Begin reading state snapshot from file + const char * begin( Auto_File_Reader, Nes_State* = 0 ); + + // Go to next unrecognized block in file + const char * next_block(); + + // State as read from file. Only valid after all blocks have been read. + Nes_State const& state() const; + +public: + Nes_State_Reader(); + ~Nes_State_Reader(); +private: + Nes_State* owned; + Nes_State* state_; +}; + +class Nes_State_ { +public: + + const char * write_blocks( Nes_File_Writer& ) const; + void set_nes_state( nes_state_t const& ); + const char * read_blocks( Nes_File_Reader& ); + + enum { ram_size = 0x800 }; + enum { sram_max = 0x2000 }; + enum { spr_ram_size = 0x100 }; + enum { nametable_max = 0x800 }; + enum { chr_max = 0x2000 }; + uint8_t *ram, *sram, *spr_ram, *nametable, *chr; + nes_state_t nes; + Nes_Cpu::registers_t* cpu; + joypad_state_t* joypad; + apu_state_t* apu; + ppu_state_t* ppu; + mapper_state_t* mapper; + + bool nes_valid, cpu_valid, joypad_valid, apu_valid, ppu_valid; + bool mapper_valid, ram_valid, spr_ram_valid; + short sram_size, nametable_size, chr_size; + + // Invalidate all state + void clear(); + + // Change timestamp + void set_timestamp( frame_count_t ); + + // Timestamp snapshot was taken at + frame_count_t timestamp() const; +}; + +// Snapshot of emulator state +class Nes_State : private Nes_State_ { +public: + + Nes_State(); + + // Write snapshot to file + const char * write( Auto_File_Writer ) const; + + // Read snapshot from file + const char * read( Auto_File_Reader ); + +private: + Nes_Cpu::registers_t cpu; + joypad_state_t joypad; + apu_state_t apu; + ppu_state_t ppu; + mapper_state_t mapper; + uint8_t ram [ram_size]; + uint8_t sram [sram_max]; + uint8_t spr_ram [spr_ram_size]; + uint8_t nametable [nametable_max]; + uint8_t chr [chr_max]; + + friend class Nes_Emu; + friend class Nes_State_Writer; + friend class Nes_State_Reader; +}; + +frame_count_t const invalid_frame_count = LONG_MAX / 2 + 1; // a large positive value + +int mem_differs( void const* in, int compare, unsigned long count ); + +inline Nes_State const& Nes_State_Reader::state() const +{ + return *state_; +} + +inline const char * Nes_State_Writer::begin( Auto_File_Writer dw ) +{ + return Nes_File_Writer::begin( dw, state_file_tag ); +} + +inline void Nes_State_::set_timestamp( frame_count_t t ) { nes.frame_count = t; } + +inline frame_count_t Nes_State_::timestamp() const { return nes.frame_count; } + +#endif diff --git a/core/Nes_Vrc6_Apu.cpp b/core/Nes_Vrc6_Apu.cpp new file mode 100644 index 0000000..a25038e --- /dev/null +++ b/core/Nes_Vrc6_Apu.cpp @@ -0,0 +1,214 @@ + +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ + +#include "Nes_Vrc6_Apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +Nes_Vrc6_Apu::Nes_Vrc6_Apu() +{ + output( NULL ); + volume( 1.0 ); + reset(); +} + +Nes_Vrc6_Apu::~Nes_Vrc6_Apu() +{ +} + +void Nes_Vrc6_Apu::reset() +{ + last_time = 0; + for ( int i = 0; i < osc_count; i++ ) + { + Vrc6_Osc& osc = oscs [i]; + for ( int j = 0; j < reg_count; j++ ) + osc.regs [j] = 0; + osc.delay = 0; + osc.last_amp = 0; + osc.phase = 1; + osc.amp = 0; + } +} + +void Nes_Vrc6_Apu::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buf ); +} + +void Nes_Vrc6_Apu::run_until( nes_time_t time ) +{ + run_square( oscs [0], time ); + run_square( oscs [1], time ); + run_saw( time ); + last_time = time; +} + +void Nes_Vrc6_Apu::write_osc( nes_time_t time, int osc_index, int reg, int data ) +{ + run_until( time ); + oscs [osc_index].regs [reg] = data; +} + +void Nes_Vrc6_Apu::end_frame( nes_time_t time ) +{ + if ( time > last_time ) + run_until( time ); + + last_time -= time; +} + +void Nes_Vrc6_Apu::save_state( vrc6_apu_state_t* out ) const +{ + out->saw_amp = oscs [2].amp; + for ( int i = 0; i < osc_count; i++ ) + { + Vrc6_Osc const& osc = oscs [i]; + for ( int r = 0; r < reg_count; r++ ) + out->regs [i] [r] = osc.regs [r]; + + out->delays [i] = osc.delay; + out->phases [i] = osc.phase; + } +} + +void Nes_Vrc6_Apu::load_state( vrc6_apu_state_t const& in ) +{ + reset(); + oscs [2].amp = in.saw_amp; + for ( int i = 0; i < osc_count; i++ ) + { + Vrc6_Osc& osc = oscs [i]; + for ( int r = 0; r < reg_count; r++ ) + osc.regs [r] = in.regs [i] [r]; + + osc.delay = in.delays [i]; + osc.phase = in.phases [i]; + } + if ( !oscs [2].phase ) + oscs [2].phase = 1; + + //Run sound channels for 0 cycles for clean audio after loading state + this->run_until(this->last_time); +} + +void Nes_Vrc6_Apu::run_square( Vrc6_Osc& osc, nes_time_t end_time ) +{ + Blip_Buffer* output = osc.output; + if ( !output ) + return; + + int volume = osc.regs [0] & 15; + if ( !(osc.regs [2] & 0x80) ) + volume = 0; + + int gate = osc.regs [0] & 0x80; + int duty = ((osc.regs [0] >> 4) & 7) + 1; + int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp; + nes_time_t time = last_time; + if ( delta ) + { + osc.last_amp += delta; + square_synth.offset( time, delta, output ); + } + + time += osc.delay; + osc.delay = 0; + int period = osc.period(); + if ( volume && !gate && period > 4 ) + { + if ( time < end_time ) + { + int phase = osc.phase; + + do + { + phase++; + if ( phase == 16 ) + { + phase = 0; + osc.last_amp = volume; + square_synth.offset( time, volume, output ); + } + if ( phase == duty ) + { + osc.last_amp = 0; + square_synth.offset( time, -volume, output ); + } + time += period; + } + while ( time < end_time ); + + osc.phase = phase; + } + osc.delay = time - end_time; + } +} + +void Nes_Vrc6_Apu::run_saw( nes_time_t end_time ) +{ + Vrc6_Osc& osc = oscs [2]; + Blip_Buffer* output = osc.output; + if ( !output ) + return; + + int amp = osc.amp; + int amp_step = osc.regs [0] & 0x3F; + nes_time_t time = last_time; + int last_amp = osc.last_amp; + if ( !(osc.regs [2] & 0x80) || !(amp_step | amp) ) + { + osc.delay = 0; + int delta = (amp >> 3) - last_amp; + last_amp = amp >> 3; + saw_synth.offset( time, delta, output ); + } + else + { + time += osc.delay; + if ( time < end_time ) + { + int period = osc.period() * 2; + int phase = osc.phase; + + do + { + if ( --phase == 0 ) + { + phase = 7; + amp = 0; + } + + int delta = (amp >> 3) - last_amp; + if ( delta ) + { + last_amp = amp >> 3; + saw_synth.offset( time, delta, output ); + } + + time += period; + amp = (amp + amp_step) & 0xFF; + } + while ( time < end_time ); + + osc.phase = phase; + osc.amp = amp; + } + + osc.delay = time - end_time; + } + + osc.last_amp = last_amp; +} diff --git a/core/Nes_Vrc6_Apu.h b/core/Nes_Vrc6_Apu.h new file mode 100644 index 0000000..f70f2d5 --- /dev/null +++ b/core/Nes_Vrc6_Apu.h @@ -0,0 +1,99 @@ + +// Konami VRC6 sound chip emulator + +// Nes_Snd_Emu 0.1.7 + +#ifndef NES_VRC6_APU_H +#define NES_VRC6_APU_H + +#include + +#include "Nes_Apu.h" +#include "Blip_Buffer.h" + +struct vrc6_apu_state_t; + +class Nes_Vrc6_Apu { +public: + Nes_Vrc6_Apu(); + ~Nes_Vrc6_Apu(); + + // See Nes_Apu.h for reference + void reset(); + void volume( double ); + void treble_eq( blip_eq_t const& ); + void output( Blip_Buffer* ); + enum { osc_count = 3 }; + void osc_output( int index, Blip_Buffer* ); + void end_frame( nes_time_t ); + void save_state( vrc6_apu_state_t* ) const; + void load_state( vrc6_apu_state_t const& ); + + // Oscillator 0 write-only registers are at $9000-$9002 + // Oscillator 1 write-only registers are at $A000-$A002 + // Oscillator 2 write-only registers are at $B000-$B002 + enum { reg_count = 3 }; + enum { base_addr = 0x9000 }; + enum { addr_step = 0x1000 }; + void write_osc( nes_time_t, int osc, int reg, int data ); + +private: + // noncopyable + Nes_Vrc6_Apu( const Nes_Vrc6_Apu& ); + Nes_Vrc6_Apu& operator = ( const Nes_Vrc6_Apu& ); + + struct Vrc6_Osc + { + uint8_t regs [3]; + Blip_Buffer* output; + int delay; + int last_amp; + int phase; + int amp; // only used by saw + + int period() const + { + return (regs [2] & 0x0f) * 0x100L + regs [1] + 1; + } + }; + + Vrc6_Osc oscs [osc_count]; + nes_time_t last_time; + + Blip_Synth saw_synth; + Blip_Synth square_synth; + + void run_until( nes_time_t ); + void run_square( Vrc6_Osc& osc, nes_time_t ); + void run_saw( nes_time_t ); +}; + +struct vrc6_apu_state_t +{ + uint8_t regs [3] [3]; + uint8_t saw_amp; + uint16_t delays [3]; + uint8_t phases [3]; + uint8_t unused; +}; +BOOST_STATIC_ASSERT( sizeof (vrc6_apu_state_t) == 20 ); + +inline void Nes_Vrc6_Apu::osc_output( int i, Blip_Buffer* buf ) +{ + oscs [i].output = buf; +} + +inline void Nes_Vrc6_Apu::volume( double v ) +{ + double const factor = 0.0967 * 2; + saw_synth.volume( factor / 31 * v ); + square_synth.volume( factor * 0.5 / 15 * v ); +} + +inline void Nes_Vrc6_Apu::treble_eq( blip_eq_t const& eq ) +{ + saw_synth.treble_eq( eq ); + square_synth.treble_eq( eq ); +} + +#endif diff --git a/core/Nes_Vrc7.cpp b/core/Nes_Vrc7.cpp new file mode 100644 index 0000000..759310f --- /dev/null +++ b/core/Nes_Vrc7.cpp @@ -0,0 +1,205 @@ +#include "Nes_Mapper.h" +#include "Nes_Vrc7.h" +#include "emu2413.h" +#include + +#define BYTESWAP(xxxx) {uint32_t _temp = (uint32_t)(xxxx);\ +((uint8_t*)&(xxxx))[0] = (uint8_t)((_temp) >> 24);\ +((uint8_t*)&(xxxx))[1] = (uint8_t)((_temp) >> 16);\ +((uint8_t*)&(xxxx))[2] = (uint8_t)((_temp) >> 8);\ +((uint8_t*)&(xxxx))[3] = (uint8_t)((_temp) >> 0);\ +} + +static bool IsLittleEndian() +{ + int i = 42; + if (((char*)&i)[0] == 42) + { + return true; + } + return false; +} + +Nes_Vrc7::Nes_Vrc7() +{ + opll = OPLL_new( 3579545 ); + output( NULL ); + volume( 1.0 ); + reset(); +} + +Nes_Vrc7::~Nes_Vrc7() +{ + OPLL_delete( ( OPLL * ) opll ); +} + +void Nes_Vrc7::reset() +{ + last_time = 0; + count = 0; + + for ( int i = 0; i < osc_count; ++i ) + { + Vrc7_Osc& osc = oscs [i]; + for ( int j = 0; j < 3; ++j ) + osc.regs [j] = 0; + osc.last_amp = 0; + } + + OPLL_reset( ( OPLL * ) opll ); +} + +void Nes_Vrc7::volume( double v ) +{ + synth.volume( v * 1. / 3. ); +} + +void Nes_Vrc7::treble_eq( blip_eq_t const& eq ) +{ + synth.treble_eq( eq ); +} + +void Nes_Vrc7::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buf ); +} + +void Nes_Vrc7::run_until( nes_time_t end_time ) +{ + nes_time_t time = last_time; + + while ( time < end_time ) + { + if ( ++count == 36 ) + { + count = 0; + bool run = false; + for ( unsigned i = 0; i < osc_count; ++i ) + { + Vrc7_Osc & osc = oscs [i]; + if ( osc.output ) + { + if ( ! run ) + { + run = true; + OPLL_run( ( OPLL * ) opll ); + } + int amp = OPLL_calcCh( ( OPLL * ) opll, i ); + int delta = amp - osc.last_amp; + if ( delta ) + { + osc.last_amp = amp; + synth.offset( time, delta, osc.output ); + } + } + } + } + ++time; + } + + last_time = end_time; +} + +void Nes_Vrc7::write_reg( int data ) +{ + OPLL_writeIO( ( OPLL * ) opll, 0, data ); +} + +void Nes_Vrc7::write_data( nes_time_t time, int data ) +{ + if ( ( unsigned ) ( ( ( OPLL * ) opll )->adr - 0x10 ) < 0x36 ) + { + int type = ( ( OPLL * ) opll )->adr >> 4; + int chan = ( ( OPLL * ) opll )->adr & 15; + + if ( chan < 6 ) oscs [chan].regs [type-1] = data; + } + + run_until( time ); + OPLL_writeIO( ( OPLL * ) opll, 1, data ); +} + +void Nes_Vrc7::end_frame( nes_time_t time ) +{ + if ( time > last_time ) + run_until( time ); + last_time -= time; +} + +void Nes_Vrc7::save_snapshot( vrc7_snapshot_t* out ) +{ + out->latch = ( ( OPLL * ) opll )->adr; + memcpy( out->inst, ( ( OPLL * ) opll )->CustInst, 8 ); + for ( int i = 0; i < osc_count; ++i ) + { + for ( int j = 0; j < 3; ++j ) + { + out->regs [i] [j] = oscs [i].regs [j]; + } + } + out->count = count; + out->internal_opl_state_size = sizeof(OPLL_STATE); + if (!IsLittleEndian()) + { + BYTESWAP(out->internal_opl_state_size); + } + OPLL_serialize((OPLL*)opll, &(out->internal_opl_state)); + OPLL_state_byteswap(&(out->internal_opl_state)); +} + +void Nes_Vrc7::load_snapshot( vrc7_snapshot_t & in, int dataSize ) +{ + reset(); + write_reg( in.latch ); + int i; + for ( i = 0; i < osc_count; ++i ) + { + for ( int j = 0; j < 3; ++j ) + { + oscs [i].regs [j] = in.regs [i] [j]; + } + } + count = in.count; + + for ( i = 0; i < 8; ++i ) + { + OPLL_writeReg( ( OPLL * ) opll, i, in.inst [i] ); + } + + for ( i = 0; i < 3; ++i ) + { + for ( int j = 0; j < 6; ++j ) + { + OPLL_writeReg( ( OPLL * ) opll, 0x10 + i * 0x10 + j, oscs [j].regs [i] ); + } + } + if (!IsLittleEndian()) + { + BYTESWAP(in.internal_opl_state_size); + } + if (in.internal_opl_state_size == sizeof(OPLL_STATE)) + { + OPLL_state_byteswap(&(in.internal_opl_state)); + OPLL_deserialize((OPLL*)opll, &(in.internal_opl_state)); + } + update_last_amp(); +} + +void Nes_Vrc7::update_last_amp() +{ + for (unsigned i = 0; i < osc_count; ++i) + { + Vrc7_Osc & osc = oscs[i]; + if (osc.output) + { + int amp = OPLL_calcCh((OPLL *)opll, i); + int delta = amp - osc.last_amp; + if (delta) + { + osc.last_amp = amp; + synth.offset(last_time, delta, osc.output); + } + } + } +} diff --git a/core/Nes_Vrc7.h b/core/Nes_Vrc7.h new file mode 100644 index 0000000..ee10c06 --- /dev/null +++ b/core/Nes_Vrc7.h @@ -0,0 +1,71 @@ + +// Konami VRC7 sound chip emulator + +// Nes_Snd_Emu 0.1.7. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef NES_VRC7_H +#define NES_VRC7_H + +#include "emu2413_state.h" + +struct vrc7_snapshot_t; + +class Nes_Vrc7 { +public: + Nes_Vrc7(); + ~Nes_Vrc7(); + + // See Nes_Apu.h for reference + void reset(); + void volume( double ); + void treble_eq( blip_eq_t const& ); + void output( Blip_Buffer* ); + enum { osc_count = 6 }; + void osc_output( int index, Blip_Buffer* ); + void end_frame( nes_time_t ); + void save_snapshot(vrc7_snapshot_t*); + void load_snapshot(vrc7_snapshot_t &, int dataSize); + void update_last_amp(); + + void write_reg( int reg ); + void write_data( nes_time_t, int data ); + +private: + // noncopyable + Nes_Vrc7( const Nes_Vrc7& ); + Nes_Vrc7& operator = ( const Nes_Vrc7& ); + + struct Vrc7_Osc + { + uint8_t regs [3]; + Blip_Buffer* output; + int last_amp; + }; + + void * opll; + nes_time_t last_time; + + Blip_Synth synth; // DB2LIN_AMP_BITS == 11, * 2 + int count; + Vrc7_Osc oscs [osc_count]; + + void run_until( nes_time_t ); +}; + +struct vrc7_snapshot_t +{ + uint8_t latch; + uint8_t inst [8]; + uint8_t regs [6] [3]; + uint8_t count; + int internal_opl_state_size; + OPLL_STATE internal_opl_state; +}; +BOOST_STATIC_ASSERT( sizeof (vrc7_snapshot_t) == 28 + 440 + 4 ); + +inline void Nes_Vrc7::osc_output( int i, Blip_Buffer* buf ) +{ + oscs [i].output = buf; +} + +#endif diff --git a/core/abstract_file.cpp b/core/abstract_file.cpp new file mode 100644 index 0000000..fc42202 --- /dev/null +++ b/core/abstract_file.cpp @@ -0,0 +1,107 @@ + +#include "abstract_file.h" + +#include "blargg_config.h" + +#include +#include + +/* Copyright (C) 2005-2006 Shay Green. Permission is hereby granted, free of +charge, to any person obtaining a copy of this software module and associated +documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and +to permit persons to whom the Software is furnished to do so, subject to the +following conditions: The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. THE +SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +const char *Data_Writer::write( const void*, long ) { return 0; } + +// Mem_Writer + +Mem_Writer::Mem_Writer( void* p, long s, int b ) +{ + data_ = (char*) p; + size_ = 0; + allocated = s; + mode = b ? ignore_excess : fixed; +} + +Mem_Writer::Mem_Writer() +{ + data_ = 0; + size_ = 0; + allocated = 0; + mode = expanding; +} + +Mem_Writer::~Mem_Writer() +{ + if ( ( mode == expanding ) && data_ ) + free( data_ ); +} + +const char *Mem_Writer::write( const void* p, long s ) +{ + long remain = allocated - size_; + if ( s > remain ) + { + if ( mode == fixed ) + return "Tried to write more data than expected"; + + if ( mode == ignore_excess ) + { + s = remain; + } + else // expanding + { + long new_allocated = size_ + s; + new_allocated += (new_allocated >> 1) + 2048; + void* p = realloc( data_, new_allocated ); + if ( !p ) + return "Out of memory"; + data_ = (char*) p; + allocated = new_allocated; + } + } + + memcpy( data_ + size_, p, s ); + size_ += s; + + return 0; +} + +// Auto_File_Reader + +const char* Auto_File_Reader::open() +{ + return 0; +} + +Auto_File_Reader::~Auto_File_Reader() +{ + if ( path ) + delete data; +} + +// Auto_File_Writer + +const char* Auto_File_Writer::open() +{ + return 0; +} + +const char* Auto_File_Writer::open_comp( int level ) +{ + return 0; +} + +Auto_File_Writer::~Auto_File_Writer() +{ +} diff --git a/core/abstract_file.h b/core/abstract_file.h new file mode 100644 index 0000000..3806db6 --- /dev/null +++ b/core/abstract_file.h @@ -0,0 +1,107 @@ + +// Abstract file access interfaces + +#ifndef ABSTRACT_FILE_H +#define ABSTRACT_FILE_H + +#undef BLARGG_CONFIG_H + +#include "Data_Reader.h" + +// Supports writing +class Data_Writer { +public: + Data_Writer() { } + virtual ~Data_Writer() { } + + // Write 'n' bytes. NULL on success, otherwise error string. + virtual const char *write( const void*, long n ) = 0; + +private: + // noncopyable + Data_Writer( const Data_Writer& ); + Data_Writer& operator = ( const Data_Writer& ); +}; + +// Write data to memory +class Mem_Writer : public Data_Writer { + char* data_; + long size_; + long allocated; + enum { expanding, fixed, ignore_excess } mode; +public: + // Keep all written data in expanding block of memory + Mem_Writer(); + + // Write to fixed-size block of memory. If ignore_excess is false, returns + // error if more than 'size' data is written, otherwise ignores any excess. + Mem_Writer( void*, long size, int ignore_excess = 0 ); + + const char *write( const void*, long ); + + // Pointer to beginning of written data + char* data() { return data_; } + + // Number of bytes written + long size() const { return size_; } + + ~Mem_Writer(); +}; + +// Auto_File to use in place of Data_Reader&/Data_Writer&, allowing a normal +// file path to be used in addition to a Data_Reader/Data_Writer. + +class Auto_File_Reader { +public: + Auto_File_Reader() : data( 0 ), path( 0 ) { } + Auto_File_Reader( Data_Reader& r ) : data( &r ), path( 0 ) { } + Auto_File_Reader( Auto_File_Reader const& ); + Auto_File_Reader& operator = ( Auto_File_Reader const& ); + ~Auto_File_Reader(); + const char* open(); + + int operator ! () const { return !data; } + Data_Reader* operator -> () const { return data; } + Data_Reader& operator * () const { return *data; } +private: + /* mutable */ Data_Reader* data; + const char* path; +}; + +class Auto_File_Writer { +public: + Auto_File_Writer() : data( 0 ), path( 0 ) { } + Auto_File_Writer( Data_Writer& r ) : data( &r ), path( 0 ) { } + Auto_File_Writer( Auto_File_Writer const& ); + Auto_File_Writer& operator = ( Auto_File_Writer const& ); + ~Auto_File_Writer(); + const char* open(); + const char* open_comp( int level = -1 ); // compress output if possible + + int operator ! () const { return !data; } + Data_Writer* operator -> () const { return data; } + Data_Writer& operator * () const { return *data; } +private: + /* mutable */ Data_Writer* data; + const char* path; +}; + +inline Auto_File_Reader& Auto_File_Reader::operator = ( Auto_File_Reader const& r ) +{ + data = r.data; + path = r.path; + ((Auto_File_Reader*) &r)->data = 0; + return *this; +} +inline Auto_File_Reader::Auto_File_Reader( Auto_File_Reader const& r ) { *this = r; } + +inline Auto_File_Writer& Auto_File_Writer::operator = ( Auto_File_Writer const& r ) +{ + data = r.data; + path = r.path; + ((Auto_File_Writer*) &r)->data = 0; + return *this; +} +inline Auto_File_Writer::Auto_File_Writer( Auto_File_Writer const& r ) { *this = r; } + +#endif diff --git a/core/apu_state.cpp b/core/apu_state.cpp new file mode 100644 index 0000000..745e06f --- /dev/null +++ b/core/apu_state.cpp @@ -0,0 +1,139 @@ + +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/libs/ + +#include "apu_state.h" +#include "Nes_Apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +template +struct apu_reflection +{ + #define REFLECT( apu, state ) (mode ? void (apu = state) : void (state = apu)) + + static void reflect_env( apu_state_t::env_t* state, Nes_Envelope& osc ) + { + REFLECT( (*state) [0], osc.env_delay ); + REFLECT( (*state) [1], osc.envelope ); + REFLECT( (*state) [2], osc.reg_written [3] ); + } + + static void reflect_square( apu_state_t::square_t& state, Nes_Square& osc ) + { + reflect_env( &state.env, osc ); + REFLECT( state.delay, osc.delay ); + REFLECT( state.length_counter, osc.length_counter ); + REFLECT( state.phase, osc.phase ); + REFLECT( state.swp_delay, osc.sweep_delay ); + REFLECT( state.swp_reset, osc.reg_written [1] ); + } + + static void reflect_triangle( apu_state_t::triangle_t& state, Nes_Triangle& osc ) + { + REFLECT( state.delay, osc.delay ); + REFLECT( state.length_counter, osc.length_counter ); + REFLECT( state.linear_counter, osc.linear_counter ); + REFLECT( state.phase, osc.phase ); + REFLECT( state.linear_mode, osc.reg_written [3] ); + } + + static void reflect_noise( apu_state_t::noise_t& state, Nes_Noise& osc ) + { + reflect_env( &state.env, osc ); + REFLECT( state.delay, osc.delay ); + REFLECT( state.length_counter, osc.length_counter ); + REFLECT( state.shift_reg, osc.noise ); + } + + static void reflect_dmc( apu_state_t::dmc_t& state, Nes_Dmc& osc ) + { + REFLECT( state.delay, osc.delay ); + REFLECT( state.remain, osc.length_counter ); + REFLECT( state.buf, osc.buf ); + REFLECT( state.bits_remain, osc.bits_remain ); + REFLECT( state.bits, osc.bits ); + REFLECT( state.buf_full, osc.buf_full ); + REFLECT( state.silence, osc.silence ); + REFLECT( state.irq_flag, osc.irq_flag ); + if ( mode ) + state.addr = osc.address | 0x8000; + else + osc.address = state.addr & 0x7fff; + } +}; + +void Nes_Apu::save_state( apu_state_t* state ) const +{ + for ( int i = 0; i < osc_count * 4; i++ ) + { + int index = i >> 2; + state->apu.w40xx [i] = oscs [index]->regs [i & 3]; + //if ( index < 4 ) + // state->length_counters [index] = oscs [index]->length_counter; + } + state->apu.w40xx [0x11] = dmc.dac; + + state->apu.w4015 = osc_enables; + state->apu.w4017 = frame_mode; + state->apu.frame_delay = frame_delay; + state->apu.frame_step = frame; + state->apu.irq_flag = irq_flag; + + typedef apu_reflection<1> refl; + Nes_Apu& apu = *(Nes_Apu*) this; // const_cast + refl::reflect_square ( state->square1, apu.square1 ); + refl::reflect_square ( state->square2, apu.square2 ); + refl::reflect_triangle( state->triangle, apu.triangle ); + refl::reflect_noise ( state->noise, apu.noise ); + refl::reflect_dmc ( state->dmc, apu.dmc ); +} + +void Nes_Apu::load_state( apu_state_t const& state ) +{ + reset(); + + write_register( 0, 0x4017, state.apu.w4017 ); + write_register( 0, 0x4015, state.apu.w4015 ); + osc_enables = state.apu.w4015; // DMC clears bit 4 + + for ( int i = 0; i < osc_count * 4; i++ ) + { + int n = state.apu.w40xx [i]; + int index = i >> 2; + oscs [index]->regs [i & 3] = n; + write_register( 0, 0x4000 + i, n ); + //if ( index < 4 ) + // oscs [index]->length_counter = state.length_counters [index]; + } + + frame_delay = state.apu.frame_delay; + frame = state.apu.frame_step; + irq_flag = state.apu.irq_flag; + + typedef apu_reflection<0> refl; + apu_state_t& st = (apu_state_t&) state; // const_cast + refl::reflect_square ( st.square1, square1 ); + refl::reflect_square ( st.square2, square2 ); + refl::reflect_triangle( st.triangle, triangle ); + refl::reflect_noise ( st.noise, noise ); + refl::reflect_dmc ( st.dmc, dmc ); + dmc.recalc_irq(); + + //force channels to have correct last_amp levels after load state + square1.run(last_time, last_time); + square2.run(last_time, last_time); + triangle.run(last_time, last_time); + noise.run(last_time, last_time); + dmc.run(last_time, last_time); +} diff --git a/core/apu_state.h b/core/apu_state.h new file mode 100644 index 0000000..5cfed75 --- /dev/null +++ b/core/apu_state.h @@ -0,0 +1,77 @@ + +// NES APU state snapshot support + +// Nes_Snd_Emu 0.1.7 + +#ifndef APU_STATE_H +#define APU_STATE_H + +#include +#include "blargg_common.h" + +struct apu_state_t +{ + typedef uint8_t env_t [3]; + /*struct env_t { + uint8_t delay; + uint8_t env; + uint8_t written; + };*/ + + struct apu_t { + uint8_t w40xx [0x14]; // $4000-$4013 + uint8_t w4015; // enables + uint8_t w4017; // mode + uint16_t frame_delay; + uint8_t frame_step; + uint8_t irq_flag; + } apu; + + struct square_t { + uint16_t delay; + env_t env; + uint8_t length_counter; + uint8_t phase; + uint8_t swp_delay; + uint8_t swp_reset; + uint8_t unused2 [1]; + }; + + square_t square1; + square_t square2; + + struct triangle_t { + uint16_t delay; + uint8_t length_counter; + uint8_t phase; + uint8_t linear_counter; + uint8_t linear_mode; + } triangle; + + struct noise_t { + uint16_t delay; + env_t env; + uint8_t length_counter; + uint16_t shift_reg; + } noise; + + struct dmc_t { + uint16_t delay; + uint16_t remain; + uint16_t addr; + uint8_t buf; + uint8_t bits_remain; + uint8_t bits; + uint8_t buf_full; + uint8_t silence; + uint8_t irq_flag; + } dmc; + + //uint8_t length_counters [4]; + + enum { tag = 0x41505552 }; // 'APUR' + void swap(); +}; +BOOST_STATIC_ASSERT( sizeof (apu_state_t) == 72 ); + +#endif diff --git a/core/blargg_common.h b/core/blargg_common.h new file mode 100644 index 0000000..413fd6b --- /dev/null +++ b/core/blargg_common.h @@ -0,0 +1,63 @@ +// Sets up common environment for Shay Green's libraries. +// To change configuration options, modify blargg_config.h, not this file. + +// File_Extractor 1.0.0 +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +#include +#include +#include + +/* Pure virtual functions cause a vtable entry to a "called pure virtual" +error handler, requiring linkage to the C++ runtime library. This macro is +used in place of the "= 0", and simply expands to its argument. During +development, it expands to "= 0", allowing detection of missing overrides. */ +#define BLARGG_PURE( def ) def + +/* My code depends on ASCII anywhere a character or string constant is +compared with data read from a file, and anywhere file data is read and +treated as a string. */ +#if '\n'!=0x0A || ' '!=0x20 || '0'!=0x30 || 'A'!=0x41 || 'a'!=0x61 + #error "ASCII character set required" +#endif + +/* My code depends on int being at least 32 bits. Almost everything these days +uses at least 32-bit ints, so it's hard to even find a system with 16-bit ints +to test with. The issue can't be gotten around by using a suitable blargg_int +everywhere either, because int is often converted to implicitly when doing +arithmetic on smaller types. */ +#if UINT_MAX < 0xFFFFFFFF + #error "int must be at least 32 bits" +#endif + +// In case compiler doesn't support these properly. Used rarely. +#define STATIC_CAST(T,expr) static_cast (expr) + +// User configuration can override the above macros if necessary +#include "blargg_config.h" + +// BOOST_STATIC_ASSERT( expr ): Generates compile error if expr is 0. +#ifndef BOOST_STATIC_ASSERT +#ifdef _MSC_VER +// MSVC6 (_MSC_VER < 1300) fails for use of __LINE__ when /Zl is specified +#define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / (int)!!(expr) - 1] ) +#else +// Some other compilers fail when declaring same function multiple times in class, +// so differentiate them by line +#define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / !!(expr) - 1] [__LINE__] ) +#endif +#endif + +typedef struct +#ifdef NO_UNALIGNED_ACCESS +__attribute ((packed)) +#endif +{ + uint32_t val; +}unaligned_uint32_t; + + +#endif diff --git a/core/blargg_config.h b/core/blargg_config.h new file mode 100644 index 0000000..e4333a2 --- /dev/null +++ b/core/blargg_config.h @@ -0,0 +1,14 @@ +// Library configuration. Modify this file as necessary. + +// File_Extractor 1.0.0 +#ifndef BLARGG_CONFIG_H +#define BLARGG_CONFIG_H + +#define HAVE_STDINT_H + +// Use standard config.h if present +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#endif diff --git a/core/blargg_endian.h b/core/blargg_endian.h new file mode 100644 index 0000000..f5f1567 --- /dev/null +++ b/core/blargg_endian.h @@ -0,0 +1,108 @@ + +// CPU Byte Order Utilities + +// Nes_Emu 0.7.0 + +#ifndef BLARGG_ENDIAN +#define BLARGG_ENDIAN + +#include "blargg_common.h" + +// BLARGG_CPU_CISC: Defined if CPU has very few general-purpose registers (< 16) +#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \ + defined (__x86_64__) || defined (__ia64__) + #define BLARGG_CPU_X86 1 + #define BLARGG_CPU_CISC 1 +#endif + +#if defined (__powerpc__) || defined (__ppc__) || defined (__POWERPC__) || defined (__powerc) + #define BLARGG_CPU_POWERPC 1 +#endif + +inline unsigned get_le16( void const* p ) { + return ((unsigned char*) p) [1] * 0x100u + + ((unsigned char*) p) [0]; +} +inline unsigned get_be16( void const* p ) { + return ((unsigned char*) p) [0] * 0x100u + + ((unsigned char*) p) [1]; +} +inline unsigned long get_le32( void const* p ) { + return ((unsigned char*) p) [3] * 0x01000000ul + + ((unsigned char*) p) [2] * 0x00010000ul + + ((unsigned char*) p) [1] * 0x00000100ul + + ((unsigned char*) p) [0]; +} +inline unsigned long get_be32( void const* p ) { + return ((unsigned char*) p) [0] * 0x01000000ul + + ((unsigned char*) p) [1] * 0x00010000ul + + ((unsigned char*) p) [2] * 0x00000100ul + + ((unsigned char*) p) [3]; +} +inline void set_le16( void* p, unsigned n ) { + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [0] = (unsigned char) n; +} +inline void set_be16( void* p, unsigned n ) { + ((unsigned char*) p) [0] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) n; +} +inline void set_le32( void* p, unsigned long n ) { + ((unsigned char*) p) [3] = (unsigned char) (n >> 24); + ((unsigned char*) p) [2] = (unsigned char) (n >> 16); + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [0] = (unsigned char) n; +} +inline void set_be32( void* p, unsigned long n ) { + ((unsigned char*) p) [0] = (unsigned char) (n >> 24); + ((unsigned char*) p) [1] = (unsigned char) (n >> 16); + ((unsigned char*) p) [2] = (unsigned char) (n >> 8); + ((unsigned char*) p) [3] = (unsigned char) n; +} + +#if BLARGG_NONPORTABLE + // Optimized implementation if byte order is known +#ifdef MSB_FIRST + #define GET_BE16( addr ) (*(uint16_t*) (addr)) + #define GET_BE32( addr ) (*(uint32_t*) (addr)) + #define SET_BE16( addr, data ) (void) (*(uint16_t*) (addr) = (data)) + #define SET_BE32( addr, data ) (void) (*(uint32_t*) (addr) = (data)) +#else + #define GET_LE16( addr ) (*(uint16_t*) (addr)) + #define SET_LE16( addr, data ) (void) (*(uint16_t*) (addr) = (data)) + #define SET_LE32( addr, data ) (void) (*(uint32_t*) (addr) = (data)) +#endif + + #if BLARGG_CPU_POWERPC && defined (__MWERKS__) + // PowerPC has special byte-reversed instructions + // to do: assumes that PowerPC is running in big-endian mode + // to do: implement for other compilers which don't support these macros + #define GET_LE16( addr ) (__lhbrx( (addr), 0 )) + #define SET_LE16( addr, data ) (__sthbrx( (data), (addr), 0 )) + #define SET_LE32( addr, data ) (__stwbrx( (data), (addr), 0 )) + #endif +#endif + +#ifndef GET_LE16 + #define GET_LE16( addr ) get_le16( addr ) + #define SET_LE16( addr, data ) set_le16( addr, data ) + #define SET_LE32( addr, data ) set_le32( addr, data ) +#endif + +#ifndef GET_BE16 + #define GET_BE16( addr ) get_be16( addr ) + #define GET_BE32( addr ) get_be32( addr ) + #define SET_BE16( addr, data ) set_be16( addr, data ) + #define SET_BE32( addr, data ) set_be32( addr, data ) +#endif + +// auto-selecting versions + +inline void set_le( uint16_t* p, unsigned n ) { SET_LE16( p, n ); } +inline void set_le( uint32_t* p, unsigned long n ) { SET_LE32( p, n ); } +inline void set_be( uint16_t* p, unsigned n ) { SET_BE16( p, n ); } +inline void set_be( uint32_t* p, unsigned long n ) { SET_BE32( p, n ); } +inline unsigned get_be( uint16_t* p ) { return GET_BE16( p ); } +inline unsigned long get_be( uint32_t* p ) { return GET_BE32( p ); } + +#endif diff --git a/core/blargg_source.h b/core/blargg_source.h new file mode 100644 index 0000000..68942c4 --- /dev/null +++ b/core/blargg_source.h @@ -0,0 +1,40 @@ +/* Included at the beginning of library source files, AFTER all other #include +lines. Sets up helpful macros and services used in my source code. Since this +is only "active" in my source code, I don't have to worry about polluting the +global namespace with unprefixed names. */ + +// File_Extractor 1.0.0 +#ifndef BLARGG_SOURCE_H +#define BLARGG_SOURCE_H + +#ifndef BLARGG_COMMON_H // optimization only + #include "blargg_common.h" +#endif + +#include /* memcpy(), memset(), memmove() */ +#include /* offsetof() */ + +/* If expr yields non-NULL error string, returns it from current function, +otherwise continues normally. */ +#undef RETURN_ERR +#define RETURN_ERR( expr ) \ + do {\ + const char *blargg_return_err_ = (expr);\ + if ( blargg_return_err_ )\ + return blargg_return_err_;\ + } while ( 0 ) + +/* If ptr is NULL, returns out-of-memory error, otherwise continues normally. */ +#undef CHECK_ALLOC +#define CHECK_ALLOC( ptr ) \ + do {\ + if ( !(ptr) )\ + return "Out of memory";\ + } while ( 0 ) + +/* The usual min/max functions for built-in types. */ + +template T min( T x, T y ) { return x < y ? x : y; } +template T max( T x, T y ) { return x > y ? x : y; } + +#endif diff --git a/core/emu2413.cpp b/core/emu2413.cpp new file mode 100644 index 0000000..5faeb17 --- /dev/null +++ b/core/emu2413.cpp @@ -0,0 +1,1165 @@ +/* +Permission is granted to anyone to use this software for any purpose, +including commercial applications. To alter this software and redistribute it freely, +if the origin of this software is not misrepresented. +*/ + +/* This software has been heavily modified for VRC7. To get a stock YM2413 emulator, download + MSXplug. +*/ + +/*********************************************************************************** + + emu2413.c -- YM2413 emulator written by Mitsutaka Okazaki 2001 + + 2001 01-08 : Version 0.10 -- 1st version. + 2001 01-15 : Version 0.20 -- semi-public version. + 2001 01-16 : Version 0.30 -- 1st public version. + 2001 01-17 : Version 0.31 -- Fixed bassdrum problem. + : Version 0.32 -- LPF implemented. + 2001 01-18 : Version 0.33 -- Fixed the drum problem, refine the mix-down method. + -- Fixed the LFO bug. + 2001 01-24 : Version 0.35 -- Fixed the drum problem, + support undocumented EG behavior. + 2001 02-02 : Version 0.38 -- Improved the performance. + Fixed the hi-hat and cymbal model. + Fixed the default percussive datas. + Noise reduction. + Fixed the feedback problem. + 2001 03-03 : Version 0.39 -- Fixed some drum bugs. + Improved the performance. + 2001 03-04 : Version 0.40 -- Improved the feedback. + Change the default table size. + Clock and Rate can be changed during play. + 2001 06-24 : Version 0.50 -- Improved the hi-hat and the cymbal tone. + Added VRC7 patch (OPLL_reset_patch is changed). + Fixed OPLL_reset() bug. + Added OPLL_setMask, OPLL_getMask and OPLL_toggleMask. + Added OPLL_writeIO. + 2001 09-28 : Version 0.51 -- Removed the noise table. + 2002 01-28 : Version 0.52 -- Added Stereo mode. + 2002 02-07 : Version 0.53 -- Fixed some drum bugs. + 2002 02-20 : Version 0.54 -- Added the best quality mode. + 2002 03-02 : Version 0.55 -- Removed OPLL_init & OPLL_close. + 2002 05-30 : Version 0.60 -- Fixed HH&CYM generator and all voice datas. + + 2004 01-24 : Modified by xodnizel to remove code not needed for the VRC7, among other things. + + References: + fmopl.c -- 1999,2000 written by Tatsuyuki Satoh (MAME development). + fmopl.c(fixed) -- (C) 2002 Jarek Burczynski. + s_opl.c -- 2001 written by Mamiya (NEZplug development). + fmgen.cpp -- 1999,2000 written by cisc. + fmpac.ill -- 2000 created by NARUTO. + MSX-Datapack + YMU757 data sheet + YM2143 data sheet + +**************************************************************************************/ +#include +#include +#include +#include "emu2413.h" + +static const unsigned char default_inst[15][8] = { + /* 2019-03-19 VRC7 instrument patchset dumped by Nuke.YKT */ + /* https://wiki.nesdev.com/w/index.php/VRC7_audio */ + { 0x03, 0x21, 0x05, 0x06, 0xE8, 0x81, 0x42, 0x27 }, + { 0x13, 0x41, 0x14, 0x0D, 0xD8, 0xF6, 0x23, 0x12 }, + { 0x11, 0x11, 0x08, 0x08, 0xFA, 0xB2, 0x20, 0x12 }, + { 0x31, 0x61, 0x0C, 0x07, 0xA8, 0x64, 0x61, 0x27 }, + { 0x32, 0x21, 0x1E, 0x06, 0xE1, 0x76, 0x01, 0x28 }, + { 0x02, 0x01, 0x06, 0x00, 0xA3, 0xE2, 0xF4, 0xF4 }, + { 0x21, 0x61, 0x1D, 0x07, 0x82, 0x81, 0x11, 0x07 }, + { 0x23, 0x21, 0x22, 0x17, 0xA2, 0x72, 0x01, 0x17 }, + { 0x35, 0x11, 0x25, 0x00, 0x40, 0x73, 0x72, 0x01 }, + { 0xB5, 0x01, 0x0F, 0x0F, 0xA8, 0xA5, 0x51, 0x02 }, + { 0x17, 0xC1, 0x24, 0x07, 0xF8, 0xF8, 0x22, 0x12 }, + { 0x71, 0x23, 0x11, 0x06, 0x65, 0x74, 0x18, 0x16 }, + { 0x01, 0x02, 0xD3, 0x05, 0xC9, 0x95, 0x03, 0x02 }, + { 0x61, 0x63, 0x0C, 0x00, 0x94, 0xC0, 0x33, 0xF6 }, + { 0x21, 0x72, 0x0D, 0x00, 0xC1, 0xD5, 0x56, 0x06 } +}; + +#define EG2DB(d) ((d)*(e_int32)(EG_STEP/DB_STEP)) +#define TL2EG(d) ((d)*(e_int32)(TL_STEP/EG_STEP)) +#define SL2EG(d) ((d)*(e_int32)(SL_STEP/EG_STEP)) + +#define DB_POS(x) (e_uint32)((x)/DB_STEP) +#define DB_NEG(x) (e_uint32)(DB_MUTE+DB_MUTE+(x)/DB_STEP) + +/* Bits for liner value */ +#define DB2LIN_AMP_BITS 11 +#define SLOT_AMP_BITS (DB2LIN_AMP_BITS) + +/* Bits for envelope phase incremental counter */ +#define EG_DP_BITS 22 +#define EG_DP_WIDTH (1<>(b)) + +/* Leave the lower b bit(s). */ +#define LOWBITS(c,b) ((c)&((1<<(b))-1)) + +/* Expand x which is s bits to d bits. */ +#define EXPAND_BITS(x,s,d) ((x)<<((d)-(s))) + +/* Expand x which is s bits to d bits and fill expanded bits '1' */ +#define EXPAND_BITS_X(x,s,d) (((x)<<((d)-(s)))|((1<<((d)-(s)))-1)) + +#define MOD(o,x) (&(o)->slot[(x)<<1]) +#define CAR(o,x) (&(o)->slot[((x)<<1)|1]) + +#define BIT(s,b) (((s)>>(b))&1) + +/* Definition of envelope mode */ +enum +{ SETTLE, ATTACK, DECAY, SUSHOLD, SUSTINE, RELEASE, FINISH }; + +/*************************************************** + + Create tables + +****************************************************/ +INLINE static e_int32 Min (e_int32 i, e_int32 j) +{ + if (i < j) + return i; + else + return j; +} + +/* Table for AR to LogCurve. */ +static void makeAdjustTable (OPLL * opll) +{ + e_int32 i; + + opll->AR_ADJUST_TABLE[0] = (1 << EG_BITS); + for (i = 1; i < 128; i++) + opll->AR_ADJUST_TABLE[i] = (e_uint16) ((double) (1 << EG_BITS) - 1 - (1 << EG_BITS) * log ((double)i) / log (128.)); +} + + +/* Table for dB(0 -- (1<DB2LIN_TABLE[i] = (e_int16) ((double) ((1 << DB2LIN_AMP_BITS) - 1) * powf(10, -(double) i * DB_STEP / 20)); + if (i >= DB_MUTE) opll->DB2LIN_TABLE[i] = 0; + opll->DB2LIN_TABLE[i + DB_MUTE + DB_MUTE] = (e_int16) (-opll->DB2LIN_TABLE[i]); + } +} + +/* Liner(+0.0 - +1.0) to dB((1<fullsintable[i] = (e_uint32) lin2db (sin (2.0 * PI * i / PG_WIDTH) ); + } + + for (i = 0; i < PG_WIDTH / 4; i++) + { + opll->fullsintable[PG_WIDTH / 2 - 1 - i] = opll->fullsintable[i]; + } + + for (i = 0; i < PG_WIDTH / 2; i++) + { + opll->fullsintable[PG_WIDTH / 2 + i] = (e_uint32) (DB_MUTE + DB_MUTE + opll->fullsintable[i]); + } + + for (i = 0; i < PG_WIDTH / 2; i++) + opll->halfsintable[i] = opll->fullsintable[i]; + for (i = PG_WIDTH / 2; i < PG_WIDTH; i++) + opll->halfsintable[i] = opll->fullsintable[0]; +} + +/* Table for Pitch Modulator */ +static void makePmTable (OPLL * opll) +{ + e_int32 i; + + for (i = 0; i < PM_PG_WIDTH; i++) + opll->pmtable[i] = (e_int32) ((double) PM_AMP * powf(2, (double) PM_DEPTH * sin (2.0 * PI * i / PM_PG_WIDTH) / 1200)); +} + +/* Table for Amp Modulator */ +static void makeAmTable (OPLL * opll) +{ + e_int32 i; + + for (i = 0; i < AM_PG_WIDTH; i++) + opll->amtable[i] = (e_int32) ((double) AM_DEPTH / 2 / DB_STEP * (1.0 + sin (2.0 * PI * i / PM_PG_WIDTH))); +} + +/* Phase increment counter table */ +static void makeDphaseTable (OPLL * opll) +{ + e_uint32 fnum, block, ML; + e_uint32 mltable[16] = + { 1, 1 * 2, 2 * 2, 3 * 2, 4 * 2, 5 * 2, 6 * 2, 7 * 2, 8 * 2, 9 * 2, 10 * 2, 10 * 2, 12 * 2, 12 * 2, 15 * 2, 15 * 2 }; + + for (fnum = 0; fnum < 512; fnum++) + for (block = 0; block < 8; block++) + for (ML = 0; ML < 16; ML++) + opll->dphaseTable[fnum][block][ML] = (((fnum * mltable[ML]) << block) >> (20 - DP_BITS)); +} + +static void makeTllTable (OPLL *opll) +{ +#define dB2(x) ((x)*2) + + static const double kltable[16] = { + dB2 (0.000), dB2 (9.000), dB2 (12.000), dB2 (13.875), dB2 (15.000), dB2 (16.125), dB2 (16.875), dB2 (17.625), + dB2 (18.000), dB2 (18.750), dB2 (19.125), dB2 (19.500), dB2 (19.875), dB2 (20.250), dB2 (20.625), dB2 (21.000) + }; + + e_int32 tmp; + e_int32 fnum, block, TL, KL; + + for (fnum = 0; fnum < 16; fnum++) + for (block = 0; block < 8; block++) + for (TL = 0; TL < 64; TL++) + for (KL = 0; KL < 4; KL++) + { + if (KL == 0) + { + opll->tllTable[fnum][block][TL][KL] = TL2EG (TL); + } + else + { + tmp = (e_int32) (kltable[fnum] - dB2 (3.000) * (7 - block)); + if (tmp <= 0) + opll->tllTable[fnum][block][TL][KL] = TL2EG (TL); + else + opll->tllTable[fnum][block][TL][KL] = (e_uint32) ((tmp >> (3 - KL)) / EG_STEP) + TL2EG (TL); + } + } +} + +#ifdef USE_SPEC_ENV_SPEED +static const double attacktime[16][4] = { + {0, 0, 0, 0}, + {1730.15, 1400.60, 1153.43, 988.66}, + {865.08, 700.30, 576.72, 494.33}, + {432.54, 350.15, 288.36, 247.16}, + {216.27, 175.07, 144.18, 123.58}, + {108.13, 87.54, 72.09, 61.79}, + {54.07, 43.77, 36.04, 30.90}, + {27.03, 21.88, 18.02, 15.45}, + {13.52, 10.94, 9.01, 7.72}, + {6.76, 5.47, 4.51, 3.86}, + {3.38, 2.74, 2.25, 1.93}, + {1.69, 1.37, 1.13, 0.97}, + {0.84, 0.70, 0.60, 0.54}, + {0.50, 0.42, 0.34, 0.30}, + {0.28, 0.22, 0.18, 0.14}, + {0.00, 0.00, 0.00, 0.00} +}; + +static const double decaytime[16][4] = { + {0, 0, 0, 0}, + {20926.60, 16807.20, 14006.00, 12028.60}, + {10463.30, 8403.58, 7002.98, 6014.32}, + {5231.64, 4201.79, 3501.49, 3007.16}, + {2615.82, 2100.89, 1750.75, 1503.58}, + {1307.91, 1050.45, 875.37, 751.79}, + {653.95, 525.22, 437.69, 375.90}, + {326.98, 262.61, 218.84, 187.95}, + {163.49, 131.31, 109.42, 93.97}, + {81.74, 65.65, 54.71, 46.99}, + {40.87, 32.83, 27.36, 23.49}, + {20.44, 16.41, 13.68, 11.75}, + {10.22, 8.21, 6.84, 5.87}, + {5.11, 4.10, 3.42, 2.94}, + {2.55, 2.05, 1.71, 1.47}, + {1.27, 1.27, 1.27, 1.27} +}; +#endif + +/* Rate Table for Attack */ +static void makeDphaseARTable(OPLL * opll) +{ + e_int32 AR, Rks, RM, RL; +#ifdef USE_SPEC_ENV_SPEED + e_uint32 attacktable[16][4]; + + for (RM = 0; RM < 16; RM++) + for (RL = 0; RL < 4; RL++) + { + if (RM == 0) + attacktable[RM][RL] = 0; + else if (RM == 15) + attacktable[RM][RL] = EG_DP_WIDTH; + else + attacktable[RM][RL] = (e_uint32) ((double) (1 << EG_DP_BITS) / (attacktime[RM][RL] * 3579545 / 72000)); + + } +#endif + + for (AR = 0; AR < 16; AR++) + for (Rks = 0; Rks < 16; Rks++) + { + RM = AR + (Rks >> 2); + RL = Rks & 3; + if (RM > 15) + RM = 15; + switch (AR) + { + case 0: + opll->dphaseARTable[AR][Rks] = 0; + break; + case 15: + opll->dphaseARTable[AR][Rks] = 0;/*EG_DP_WIDTH;*/ + break; + default: +#ifdef USE_SPEC_ENV_SPEED + opll->dphaseARTable[AR][Rks] = (attacktable[RM][RL]); +#else + opll->dphaseARTable[AR][Rks] = ((3 * (RL + 4) << (RM + 1))); +#endif + break; + } + } +} + +/* Rate Table for Decay and Release */ +static void makeDphaseDRTable (OPLL * opll) +{ + e_int32 DR, Rks, RM, RL; + +#ifdef USE_SPEC_ENV_SPEED + e_uint32 decaytable[16][4]; + + for (RM = 0; RM < 16; RM++) + for (RL = 0; RL < 4; RL++) + if (RM == 0) + decaytable[RM][RL] = 0; + else + decaytable[RM][RL] = (e_uint32) ((double) (1 << EG_DP_BITS) / (decaytime[RM][RL] * 3579545 / 72000)); +#endif + + for (DR = 0; DR < 16; DR++) + for (Rks = 0; Rks < 16; Rks++) + { + RM = DR + (Rks >> 2); + RL = Rks & 3; + if (RM > 15) + RM = 15; + switch (DR) + { + case 0: + opll->dphaseDRTable[DR][Rks] = 0; + break; + default: +#ifdef USE_SPEC_ENV_SPEED + opll->dphaseDRTable[DR][Rks] = (decaytable[RM][RL]); +#else + opll->dphaseDRTable[DR][Rks] = ((RL + 4) << (RM - 1)); +#endif + break; + } + } +} + +static void makeRksTable (OPLL *opll) +{ + + e_int32 fnum8, block, KR; + + for (fnum8 = 0; fnum8 < 2; fnum8++) + for (block = 0; block < 8; block++) + for (KR = 0; KR < 2; KR++) + { + if (KR != 0) + opll->rksTable[fnum8][block][KR] = (block << 1) + fnum8; + else + opll->rksTable[fnum8][block][KR] = block >> 1; + } +} + +/************************************************************ + + Calc Parameters + +************************************************************/ + +INLINE static e_uint32 calc_eg_dphase (OPLL *opll, OPLL_SLOT * slot) +{ + + switch (slot->eg_mode) + { + case ATTACK: + return opll->dphaseARTable[slot->patch.AR][slot->rks]; + + case DECAY: + return opll->dphaseDRTable[slot->patch.DR][slot->rks]; + + case SUSHOLD: + return 0; + + case SUSTINE: + return opll->dphaseDRTable[slot->patch.RR][slot->rks]; + + case RELEASE: + if (slot->sustine) + return opll->dphaseDRTable[5][slot->rks]; + else if (slot->patch.EG) + return opll->dphaseDRTable[slot->patch.RR][slot->rks]; + else + return opll->dphaseDRTable[7][slot->rks]; + + case FINISH: + return 0; + + default: + return 0; + } +} + +/************************************************************* + + OPLL internal interfaces + +*************************************************************/ + +#define UPDATE_PG(S) (S)->dphase = opll->dphaseTable[(S)->fnum][(S)->block][(S)->patch.ML] +#define UPDATE_TLL(S)\ +(((S)->type==0)?\ +((S)->tll = opll->tllTable[((S)->fnum)>>5][(S)->block][(S)->patch.TL][(S)->patch.KL]):\ +((S)->tll = opll->tllTable[((S)->fnum)>>5][(S)->block][(S)->volume][(S)->patch.KL])) +#define UPDATE_RKS(S) (S)->rks = opll->rksTable[((S)->fnum)>>8][(S)->block][(S)->patch.KR] +#define UPDATE_WF(S) (S)->sintbl = opll->waveform[(S)->patch.WF] +#define UPDATE_EG(S) (S)->eg_dphase = calc_eg_dphase(opll,S) +#define UPDATE_ALL(S)\ + UPDATE_PG(S);\ + UPDATE_TLL(S);\ + UPDATE_RKS(S);\ + UPDATE_WF(S); \ + UPDATE_EG(S) /* EG should be updated last. */ + + +/* Slot key on */ +INLINE static void slotOn (OPLL_SLOT * slot) +{ + slot->eg_mode = ATTACK; + slot->eg_phase = 0; + slot->phase = 0; +} + +/* Slot key off */ +INLINE static void slotOff (OPLL *opll, OPLL_SLOT * slot) +{ + if (slot->eg_mode == ATTACK) + slot->eg_phase = EXPAND_BITS (opll->AR_ADJUST_TABLE[HIGHBITS (slot->eg_phase, EG_DP_BITS - EG_BITS)], EG_BITS, EG_DP_BITS); + slot->eg_mode = RELEASE; +} + +/* Channel key on */ +INLINE static void keyOn (OPLL * opll, e_int32 i) +{ + if (!opll->slot_on_flag[i * 2]) + slotOn (MOD(opll,i)); + if (!opll->slot_on_flag[i * 2 + 1]) + slotOn (CAR(opll,i)); + opll->key_status[i] = 1; +} + +/* Channel key off */ +INLINE static void keyOff (OPLL * opll, e_int32 i) +{ + if (opll->slot_on_flag[i * 2 + 1]) + slotOff (opll, CAR(opll,i)); + opll->key_status[i] = 0; +} + +/* Set sustine parameter */ +INLINE static void setSustine (OPLL * opll, e_int32 c, e_int32 sustine) +{ + CAR(opll,c)->sustine = sustine; + if (MOD(opll,c)->type) + MOD(opll,c)->sustine = sustine; +} + +/* Volume : 6bit ( Volume register << 2 ) */ +INLINE static void setVolume (OPLL * opll, e_int32 c, e_int32 volume) +{ + CAR(opll,c)->volume = volume; +} + +/* Set F-Number ( fnum : 9bit ) */ +INLINE static void setFnumber (OPLL * opll, e_int32 c, e_int32 fnum) +{ + CAR(opll,c)->fnum = fnum; + MOD(opll,c)->fnum = fnum; +} + +/* Set Block data (block : 3bit ) */ +INLINE static void setBlock (OPLL * opll, e_int32 c, e_int32 block) +{ + CAR(opll,c)->block = block; + MOD(opll,c)->block = block; +} + +INLINE static void update_key_status (OPLL * opll) +{ + int ch; + + for (ch = 0; ch < 6; ch++) + opll->slot_on_flag[ch * 2] = opll->slot_on_flag[ch * 2 + 1] = (opll->HiFreq[ch]) & 0x10; +} + +/*********************************************************** + + Initializing + +***********************************************************/ + +static void OPLL_SLOT_reset (OPLL *opll, OPLL_SLOT * slot, int type) +{ + slot->type = type; + slot->sintbl = opll->waveform[0]; + slot->phase = 0; + slot->dphase = 0; + slot->output[0] = 0; + slot->output[1] = 0; + slot->feedback = 0; + slot->eg_mode = SETTLE; + slot->eg_phase = EG_DP_WIDTH; + slot->eg_dphase = 0; + slot->rks = 0; + slot->tll = 0; + slot->sustine = 0; + slot->fnum = 0; + slot->block = 0; + slot->volume = 0; + slot->pgout = 0; + slot->egout = 0; +} + +static void internal_refresh (OPLL *opll) +{ + makeDphaseTable (opll); + makeDphaseARTable (opll); + makeDphaseDRTable (opll); + opll->pm_dphase = (e_uint32) (PM_SPEED * PM_DP_WIDTH / (opll->clk / 72)); + opll->am_dphase = (e_uint32) (AM_SPEED * AM_DP_WIDTH / (opll->clk / 72)); +} + +static void maketables (OPLL *opll, e_uint32 c) +{ + opll->clk = c; + makePmTable (opll); + makeAmTable (opll); + makeDB2LinTable (opll); + makeAdjustTable (opll); + makeTllTable (opll); + makeRksTable (opll); + makeSinTable (opll); + //makeDefaultPatch (); + internal_refresh (opll); +} + +OPLL *OPLL_new (e_uint32 clk) +{ + OPLL *opll; + + opll = (OPLL *) calloc (sizeof (OPLL), 1); + if (opll == NULL) + return NULL; + maketables(opll,clk); + + opll->waveform[0]=opll->fullsintable; + opll->waveform[1]=opll->halfsintable; + + opll->mask = 0; + + OPLL_reset (opll); + + return opll; +} + + +void OPLL_delete (OPLL * opll) +{ + free (opll); +} + +/* Reset whole of OPLL except patch datas. */ +void OPLL_reset (OPLL * opll) +{ + e_int32 i; + + if (!opll) + return; + + opll->adr = 0; + opll->out = 0; + + opll->pm_phase = 0; + opll->am_phase = 0; + + opll->mask = 0; + + for (i = 0; i < 12; i++) + OPLL_SLOT_reset(opll, &opll->slot[i], i%2); + + for (i = 0; i < 6; i++) + { + opll->key_status[i] = 0; + //setPatch (opll, i, 0); + } + + for (i = 0; i < 0x40; i++) + OPLL_writeReg (opll, i, 0); +} + +/* Force Refresh (When external program changes some parameters). */ +void OPLL_forceRefresh (OPLL * opll) +{ + e_int32 i; + + if (opll == NULL) + return; + + for (i = 0; i < 12; i++) + { + UPDATE_PG (&opll->slot[i]); + UPDATE_RKS (&opll->slot[i]); + UPDATE_TLL (&opll->slot[i]); + UPDATE_WF (&opll->slot[i]); + UPDATE_EG (&opll->slot[i]); + } +} + +/********************************************************* + + Generate wave data + +*********************************************************/ +/* Convert Amp(0 to EG_HEIGHT) to Phase(0 to 2PI). */ +#if ( SLOT_AMP_BITS - PG_BITS ) > 0 +#define wave2_2pi(e) ( (e) >> ( SLOT_AMP_BITS - PG_BITS )) +#else +#define wave2_2pi(e) ( (e) << ( PG_BITS - SLOT_AMP_BITS )) +#endif + +/* Convert Amp(0 to EG_HEIGHT) to Phase(0 to 4PI). */ +#if ( SLOT_AMP_BITS - PG_BITS - 1 ) == 0 +#define wave2_4pi(e) (e) +#elif ( SLOT_AMP_BITS - PG_BITS - 1 ) > 0 +#define wave2_4pi(e) ( (e) >> ( SLOT_AMP_BITS - PG_BITS - 1 )) +#else +#define wave2_4pi(e) ( (e) << ( 1 + PG_BITS - SLOT_AMP_BITS )) +#endif + +/* Convert Amp(0 to EG_HEIGHT) to Phase(0 to 8PI). */ +#if ( SLOT_AMP_BITS - PG_BITS - 2 ) == 0 +#define wave2_8pi(e) (e) +#elif ( SLOT_AMP_BITS - PG_BITS - 2 ) > 0 +#define wave2_8pi(e) ( (e) >> ( SLOT_AMP_BITS - PG_BITS - 2 )) +#else +#define wave2_8pi(e) ( (e) << ( 2 + PG_BITS - SLOT_AMP_BITS )) +#endif + + + +/* Update AM, PM unit */ +static void update_ampm (OPLL * opll) +{ + opll->pm_phase = (opll->pm_phase + opll->pm_dphase) & (PM_DP_WIDTH - 1); + opll->am_phase = (opll->am_phase + opll->am_dphase) & (AM_DP_WIDTH - 1); + opll->lfo_am = opll->amtable[HIGHBITS (opll->am_phase, AM_DP_BITS - AM_PG_BITS)]; + opll->lfo_pm = opll->pmtable[HIGHBITS (opll->pm_phase, PM_DP_BITS - PM_PG_BITS)]; +} + +/* PG */ +INLINE static void +calc_phase (OPLL_SLOT * slot, e_int32 lfo) +{ + if (slot->patch.PM) + slot->phase += (slot->dphase * lfo) >> PM_AMP_BITS; + else + slot->phase += slot->dphase; + + slot->phase &= (DP_WIDTH - 1); + + slot->pgout = HIGHBITS (slot->phase, DP_BASE_BITS); +} + +/* EG */ +static void calc_envelope(OPLL *opll, OPLL_SLOT * slot, e_int32 lfo) +{ +#define S2E(x) (SL2EG((e_int32)(x/SL_STEP))<<(EG_DP_BITS-EG_BITS)) + + static const e_uint32 SL[16] = { + S2E (0.0), S2E (3.0), S2E (6.0), S2E (9.0), S2E (12.0), S2E (15.0), S2E (18.0), S2E (21.0), + S2E (24.0), S2E (27.0), S2E (30.0), S2E (33.0), S2E (36.0), S2E (39.0), S2E (42.0), S2E (48.0) + }; + + e_uint32 egout; + + switch (slot->eg_mode) + { + + case ATTACK: + egout = opll->AR_ADJUST_TABLE[HIGHBITS (slot->eg_phase, EG_DP_BITS - EG_BITS)]; + slot->eg_phase += slot->eg_dphase; + if((EG_DP_WIDTH & slot->eg_phase)||(slot->patch.AR==15)) + { + egout = 0; + slot->eg_phase = 0; + slot->eg_mode = DECAY; + UPDATE_EG (slot); + } + break; + + case DECAY: + egout = HIGHBITS (slot->eg_phase, EG_DP_BITS - EG_BITS); + slot->eg_phase += slot->eg_dphase; + if (slot->eg_phase >= SL[slot->patch.SL]) + { + if (slot->patch.EG) + { + slot->eg_phase = SL[slot->patch.SL]; + slot->eg_mode = SUSHOLD; + UPDATE_EG (slot); + } + else + { + slot->eg_phase = SL[slot->patch.SL]; + slot->eg_mode = SUSTINE; + UPDATE_EG (slot); + } + } + break; + + case SUSHOLD: + egout = HIGHBITS (slot->eg_phase, EG_DP_BITS - EG_BITS); + if (slot->patch.EG == 0) + { + slot->eg_mode = SUSTINE; + UPDATE_EG (slot); + } + break; + + case SUSTINE: + case RELEASE: + egout = HIGHBITS (slot->eg_phase, EG_DP_BITS - EG_BITS); + slot->eg_phase += slot->eg_dphase; + if (egout >= (1 << EG_BITS)) + { + slot->eg_mode = FINISH; + egout = (1 << EG_BITS) - 1; + } + break; + + case FINISH: + egout = (1 << EG_BITS) - 1; + break; + + default: + egout = (1 << EG_BITS) - 1; + break; + } + + if (slot->patch.AM) + egout = EG2DB (egout + slot->tll) + lfo; + else + egout = EG2DB (egout + slot->tll); + + if (egout >= DB_MUTE) + egout = DB_MUTE - 1; + + slot->egout = egout; +} + +/* CARRIOR */ +INLINE static e_int32 calc_slot_car(OPLL *opll, OPLL_SLOT * slot, e_int32 fm) +{ + slot->output[1] = slot->output[0]; + + if (slot->egout >= (DB_MUTE - 1)) + { + slot->output[0] = 0; + } + else + { + slot->output[0] = opll->DB2LIN_TABLE[slot->sintbl[(slot->pgout+wave2_8pi(fm))&(PG_WIDTH-1)] + slot->egout]; + } + + return (slot->output[1] + slot->output[0]) >> 1; +} + +/* MODULATOR */ +INLINE static e_int32 calc_slot_mod(OPLL *opll, OPLL_SLOT * slot) +{ + e_int32 fm; + + slot->output[1] = slot->output[0]; + + if (slot->egout >= (DB_MUTE - 1)) + { + slot->output[0] = 0; + } + else if (slot->patch.FB != 0) + { + fm = wave2_4pi (slot->feedback) >> (7 - slot->patch.FB); + slot->output[0] = opll->DB2LIN_TABLE[slot->sintbl[(slot->pgout + fm)&(PG_WIDTH-1)] + slot->egout]; + } + else + { + slot->output[0] = opll->DB2LIN_TABLE[slot->sintbl[slot->pgout] + slot->egout]; + } + + slot->feedback = (slot->output[1] + slot->output[0]) >> 1; + + return slot->feedback; + +} + +static INLINE e_int16 calc (OPLL * opll) +{ + e_int32 inst = 0, out = 0; + e_int32 i; + + update_ampm (opll); + + for (i = 0; i < 12; i++) + { + calc_phase(&opll->slot[i],opll->lfo_pm); + calc_envelope(opll, &opll->slot[i],opll->lfo_am); + } + + for (i = 0; i < 6; i++) + if (!(opll->mask & OPLL_MASK_CH (i)) && (CAR(opll,i)->eg_mode != FINISH)) + inst += calc_slot_car (opll, CAR(opll,i), calc_slot_mod(opll,MOD(opll,i))); + + out = inst; + return (e_int16) out; +} + +static INLINE void run (OPLL * opll) +{ + e_int32 i; + + update_ampm (opll); + + for (i = 0; i < 12; i++) + { + calc_phase(&opll->slot[i],opll->lfo_pm); + calc_envelope(opll, &opll->slot[i],opll->lfo_am); + } +} + +static INLINE e_int16 calc_ch (OPLL * opll, int ch) +{ + if ( /*!(opll->mask & OPLL_MASK_CH (i)) &&*/ (CAR(opll,ch)->eg_mode != FINISH)) + return calc_slot_car (opll, CAR(opll,ch), calc_slot_mod(opll,MOD(opll,ch))); + else + return 0; +} + +e_int16 OPLL_calc (OPLL * opll) +{ + return calc (opll); +} + +void OPLL_run (OPLL * opll) +{ + run (opll); +} + +e_uint32 OPLL_calcCh (OPLL * opll, e_uint32 ch) +{ + return calc_ch (opll, ch); +} + +e_uint32 +OPLL_setMask (OPLL * opll, e_uint32 mask) +{ + e_uint32 ret; + + if (opll) + { + ret = opll->mask; + opll->mask = mask; + return ret; + } + else + return 0; +} + +e_uint32 +OPLL_toggleMask (OPLL * opll, e_uint32 mask) +{ + e_uint32 ret; + + if (opll) + { + ret = opll->mask; + opll->mask ^= mask; + return ret; + } + else + return 0; +} + +/**************************************************** + + I/O Ctrl + +*****************************************************/ + +static void setInstrument(OPLL * opll, e_uint i, e_uint inst) +{ + const e_uint8 *src; + OPLL_PATCH *modp, *carp; + + opll->patch_number[i]=inst; + + if(inst) + src=default_inst[inst-1]; + else + src=opll->CustInst; + + modp=&MOD(opll,i)->patch; + carp=&CAR(opll,i)->patch; + + modp->AM=(src[0]>>7)&1; + modp->PM=(src[0]>>6)&1; + modp->EG=(src[0]>>5)&1; + modp->KR=(src[0]>>4)&1; + modp->ML=(src[0]&0xF); + + carp->AM=(src[1]>>7)&1; + carp->PM=(src[1]>>6)&1; + carp->EG=(src[1]>>5)&1; + carp->KR=(src[1]>>4)&1; + carp->ML=(src[1]&0xF); + + modp->KL=(src[2]>>6)&3; + modp->TL=(src[2]&0x3F); + + carp->KL = (src[3] >> 6) & 3; + carp->WF = (src[3] >> 4) & 1; + + modp->WF = (src[3] >> 3) & 1; + + modp->FB = (src[3]) & 7; + + modp->AR = (src[4]>>4)&0xF; + modp->DR = (src[4]&0xF); + + carp->AR = (src[5]>>4)&0xF; + carp->DR = (src[5]&0xF); + + modp->SL = (src[6]>>4)&0xF; + modp->RR = (src[6]&0xF); + + carp->SL = (src[7]>>4)&0xF; + carp->RR = (src[7]&0xF); +} + + +void OPLL_writeReg (OPLL * opll, e_uint32 reg, e_uint32 data) +{ + + e_int32 i, v, ch; + + data = data & 0xff; + reg = reg & 0x3f; + + switch (reg) + { + case 0x00: + opll->CustInst[0]=data; + for (i = 0; i < 6; i++) + { + if (opll->patch_number[i] == 0) + { + setInstrument(opll, i, 0); + UPDATE_PG (MOD(opll,i)); + UPDATE_RKS (MOD(opll,i)); + UPDATE_EG (MOD(opll,i)); + } + } + break; + + case 0x01: + opll->CustInst[1]=data; + for (i = 0; i < 6; i++) + { + if (opll->patch_number[i] == 0) + { + setInstrument(opll, i, 0); + UPDATE_PG (CAR(opll,i)); + UPDATE_RKS (CAR(opll,i)); + UPDATE_EG (CAR(opll,i)); + } + } + break; + + case 0x02: + opll->CustInst[2]=data; + for (i = 0; i < 6; i++) + { + if (opll->patch_number[i] == 0) + { + setInstrument(opll, i, 0); + UPDATE_TLL(MOD(opll,i)); + } + } + break; + + case 0x03: + opll->CustInst[3]=data; + for (i = 0; i < 6; i++) + { + if (opll->patch_number[i] == 0) + { + setInstrument(opll, i, 0); + UPDATE_WF(MOD(opll,i)); + UPDATE_WF(CAR(opll,i)); + } + } + break; + + case 0x04: + opll->CustInst[4]=data; + for (i = 0; i < 6; i++) + { + if (opll->patch_number[i] == 0) + { + setInstrument(opll, i, 0); + UPDATE_EG (MOD(opll,i)); + } + } + break; + + case 0x05: + opll->CustInst[5]=data; + for (i = 0; i < 6; i++) + { + if (opll->patch_number[i] == 0) + { + setInstrument(opll, i, 0); + UPDATE_EG(CAR(opll,i)); + } + } + break; + + case 0x06: + opll->CustInst[6]=data; + for (i = 0; i < 6; i++) + { + if (opll->patch_number[i] == 0) + { + setInstrument(opll, i, 0); + UPDATE_EG (MOD(opll,i)); + } + } + break; + + case 0x07: + opll->CustInst[7]=data; + for (i = 0; i < 6; i++) + { + if (opll->patch_number[i] == 0) + { + setInstrument(opll, i, 0); + UPDATE_EG (CAR(opll,i)); + } + } + break; + + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + ch = reg - 0x10; + opll->LowFreq[ch]=data; + setFnumber (opll, ch, data + ((opll->HiFreq[ch] & 1) << 8)); + UPDATE_ALL (MOD(opll,ch)); + UPDATE_ALL (CAR(opll,ch)); + break; + + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + ch = reg - 0x20; + opll->HiFreq[ch]=data; + + setFnumber (opll, ch, ((data & 1) << 8) + opll->LowFreq[ch]); + setBlock (opll, ch, (data >> 1) & 7); + setSustine (opll, ch, (data >> 5) & 1); + if (data & 0x10) + keyOn (opll, ch); + else + keyOff (opll, ch); + UPDATE_ALL (MOD(opll,ch)); + UPDATE_ALL (CAR(opll,ch)); + update_key_status (opll); + break; + + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + opll->InstVol[reg-0x30]=data; + i = (data >> 4) & 15; + v = data & 15; + setInstrument(opll, reg-0x30, i); + setVolume (opll, reg - 0x30, v << 2); + UPDATE_ALL (MOD(opll,reg - 0x30)); + UPDATE_ALL (CAR(opll,reg - 0x30)); + break; + + default: + break; + + } +} + +void OPLL_writeIO (OPLL * opll, e_uint32 adr, e_uint32 val) +{ + if (adr & 1) + OPLL_writeReg (opll, opll->adr, val); + else + opll->adr = val; +} diff --git a/core/emu2413.h b/core/emu2413.h new file mode 100644 index 0000000..9ce9f88 --- /dev/null +++ b/core/emu2413.h @@ -0,0 +1,218 @@ +#ifndef EMU2413_H +#define EMU2413_H + +typedef signed int e_int; +typedef unsigned int e_uint; +typedef signed char e_int8; +typedef unsigned char e_uint8; +typedef signed short e_int16; +typedef unsigned short e_uint16; +typedef signed int e_int32; +typedef unsigned int e_uint32; + +#define INLINE inline + +/* Size of Sintable ( 8 -- 18 can be used. 9 recommended.)*/ +#define PG_BITS 9 +#define PG_WIDTH (1< + +#ifdef __cplusplus +extern "C" +{ +#endif + +int OPLL_serialize_size() +{ + return sizeof(OPLL_STATE); +} + +void OPLL_serialize(const OPLL * opll, OPLL_STATE* state) +{ + int i; + + state->pm_phase = opll->pm_phase; + state->am_phase = opll->am_phase; + + for (i = 0; i < 12; i++) + { + OPLL_SLOT_STATE *slotState = &(state->slot[i]); + const OPLL_SLOT *slot= &(opll->slot[i]); + slotState->feedback = slot->feedback; + slotState->output[0] = slot->output[0]; + slotState->output[1] = slot->output[1]; + slotState->phase = slot->phase; + slotState->pgout = slot->pgout; + slotState->eg_mode = slot->eg_mode; + slotState->eg_phase = slot->eg_phase; + slotState->eg_dphase = slot->eg_dphase; + slotState->egout = slot->egout; + } +} + +#define BYTESWAP(xxxx) {uint32_t _temp = (uint32_t)(xxxx);\ +((uint8_t*)&(xxxx))[0] = (uint8_t)((_temp) >> 24);\ +((uint8_t*)&(xxxx))[1] = (uint8_t)((_temp) >> 16);\ +((uint8_t*)&(xxxx))[2] = (uint8_t)((_temp) >> 8);\ +((uint8_t*)&(xxxx))[3] = (uint8_t)((_temp) >> 0);\ +} + + +#define SET(xxxx,yyyy) { if ((xxxx) != (yyyy)) {\ +(xxxx) = (yyyy);\ +} + +void OPLL_deserialize(OPLL * opll, const OPLL_STATE* state) +{ + int i; + + opll->pm_phase = state->pm_phase; + opll->am_phase = state->am_phase; + + for (i = 0; i < 12; i++) + { + const OPLL_SLOT_STATE *slotState = &(state->slot[i]); + OPLL_SLOT *slot = &(opll->slot[i]); + slot->feedback = slotState->feedback; + slot->output[0] = slotState->output[0]; + slot->output[1] = slotState->output[1]; + slot->phase = slotState->phase; + slot->pgout = slotState->pgout; + slot->eg_mode = slotState->eg_mode; + slot->eg_phase = slotState->eg_phase; + slot->eg_dphase = slotState->eg_dphase; + slot->egout = slotState->egout; + } +} + +static bool IsLittleEndian() +{ + int i = 42; + if (((char*)&i)[0] == 42) + { + return true; + } + return false; +} + +void OPLL_state_byteswap(OPLL_STATE *state) +{ + int i; + if (IsLittleEndian()) return; + + BYTESWAP(state->pm_phase); + BYTESWAP(state->am_phase); + + for (i = 0; i < 12; i++) + { + OPLL_SLOT_STATE *slotState = &(state->slot[i]); + BYTESWAP(slotState->feedback); + BYTESWAP(slotState->output[0]); + BYTESWAP(slotState->output[1]); + BYTESWAP(slotState->phase); + BYTESWAP(slotState->pgout); + BYTESWAP(slotState->eg_mode); + BYTESWAP(slotState->eg_phase); + BYTESWAP(slotState->eg_dphase); + BYTESWAP(slotState->egout); + } +} + +#ifdef __cplusplus +} +#endif diff --git a/core/emu2413_state.h b/core/emu2413_state.h new file mode 100644 index 0000000..bcafa79 --- /dev/null +++ b/core/emu2413_state.h @@ -0,0 +1,37 @@ +#ifndef EMU2413_STATE_H +#define EMU2413_STATE_H + +#include "emu2413.h" + +typedef struct { + e_int32 feedback; + e_int32 output[2]; + e_uint32 phase; + e_uint32 pgout; + e_int32 eg_mode; + e_uint32 eg_phase; + e_uint32 eg_dphase; + e_uint32 egout; +} OPLL_SLOT_STATE; + +typedef struct { + e_uint32 pm_phase; + e_int32 am_phase; + OPLL_SLOT_STATE slot[6 * 2]; +} OPLL_STATE; + +#ifdef __cplusplus +extern "C" +{ +#endif + +int OPLL_serialize_size(); +void OPLL_serialize(const OPLL * opll, OPLL_STATE* state); +void OPLL_deserialize(OPLL * opll, const OPLL_STATE* state); +void OPLL_state_byteswap(OPLL_STATE *state); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/core/mappers/mapper000.hpp b/core/mappers/mapper000.hpp new file mode 100644 index 0000000..a7aa280 --- /dev/null +++ b/core/mappers/mapper000.hpp @@ -0,0 +1,34 @@ +#pragma once + +// Common simple mappers + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// NROM + +class Mapper000 : public Nes_Mapper { +public: + Mapper000() { } + + virtual void apply_mapping() { } + + virtual void write( nes_time_t, nes_addr_t, int ) + { + // empty + } +}; diff --git a/core/mappers/mapper001.hpp b/core/mappers/mapper001.hpp new file mode 100644 index 0000000..71b756e --- /dev/null +++ b/core/mappers/mapper001.hpp @@ -0,0 +1,121 @@ +#pragma once + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +#include + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// MMC1 + +class Mapper001 : public Nes_Mapper, mmc1_state_t { +public: + Mapper001() + { + mmc1_state_t* state = this; + register_state( state, sizeof *state ); + } + + virtual void reset_state() + { + regs [0] = 0x0f; + regs [1] = 0x00; + regs [2] = 0x01; + regs [3] = 0x00; + } + + virtual void apply_mapping() + { + enable_sram(); // early MMC1 always had SRAM enabled + register_changed( 0 ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + if ( !(data & 0x80) ) + { + buf |= (data & 1) << bit; + bit++; + + if ( bit >= 5 ) + { + int reg = addr >> 13 & 3; + regs [reg] = buf & 0x1f; + + bit = 0; + buf = 0; + + register_changed( reg ); + } + } + else + { + bit = 0; + buf = 0; + regs [0] |= 0x0c; + register_changed( 0 ); + } + } + + + void register_changed( int reg ) + { + // Mirroring + if ( reg == 0 ) + { + int mode = regs [0] & 3; + if ( mode < 2 ) + mirror_single( mode & 1 ); + else if ( mode == 2 ) + mirror_vert(); + else + mirror_horiz(); + } + + // CHR + if ( reg < 3 && cart().chr_size() > 0 ) + { + if ( regs [0] & 0x10 ) + { + set_chr_bank( 0x0000, bank_4k, regs [1] ); + set_chr_bank( 0x1000, bank_4k, regs [2] ); + } + else + { + set_chr_bank( 0, bank_8k, regs [1] >> 1 ); + } + } + + // PRG + int bank = (regs [1] & 0x10) | (regs [3] & 0x0f); + if ( !(regs [0] & 0x08) ) + { + set_prg_bank( 0x8000, bank_32k, bank >> 1 ); + } + else if ( regs [0] & 0x04 ) + { + set_prg_bank( 0x8000, bank_16k, bank ); + set_prg_bank( 0xC000, bank_16k, bank | 0x0f ); + } + else + { + set_prg_bank( 0x8000, bank_16k, bank & ~0x0f ); + set_prg_bank( 0xC000, bank_16k, bank ); + } + } +}; + + diff --git a/core/mappers/mapper002.hpp b/core/mappers/mapper002.hpp new file mode 100644 index 0000000..5748828 --- /dev/null +++ b/core/mappers/mapper002.hpp @@ -0,0 +1,44 @@ +#pragma once + +// Common simple mappers + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// UNROM + +class Mapper002 : public Nes_Mapper { + uint8_t bank; +public: + Mapper002() + { + register_state( &bank, 1 ); + } + + virtual void apply_mapping() + { + enable_sram(); // at least one UNROM game needs sram (Bomberman 2) + set_prg_bank( 0x8000, bank_16k, bank ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + bank = handle_bus_conflict( addr, data ); + set_prg_bank( 0x8000, bank_16k, bank ); + } +}; + diff --git a/core/mappers/mapper003.hpp b/core/mappers/mapper003.hpp new file mode 100644 index 0000000..7a77c8f --- /dev/null +++ b/core/mappers/mapper003.hpp @@ -0,0 +1,43 @@ +#pragma once + +// Common simple mappers + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// CNROM + +class Mapper003 : public Nes_Mapper { + uint8_t bank; +public: + Mapper003() + { + register_state( &bank, 1 ); + } + + virtual void apply_mapping() + { + set_chr_bank( 0, bank_8k, bank & 7 ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + bank = handle_bus_conflict( addr, data ); + set_chr_bank( 0, bank_8k, bank & 7 ); + } +}; + diff --git a/core/mappers/mapper004.hpp b/core/mappers/mapper004.hpp new file mode 100644 index 0000000..f730fa3 --- /dev/null +++ b/core/mappers/mapper004.hpp @@ -0,0 +1,241 @@ +#pragma once + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +#include +#include "Nes_Core.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// 264 or less breaks Gargoyle's Quest II +// 267 or less breaks Magician +int const irq_fine_tune = 268; +nes_time_t const first_scanline = 20 * Nes_Ppu::scanline_len + irq_fine_tune; +nes_time_t const last_scanline = first_scanline + 240 * Nes_Ppu::scanline_len; + +// MMC3 + +class Mapper004 : public Nes_Mapper, mmc3_state_t { +public: + Mapper004() + { + mmc3_state_t* state = this; + register_state( state, sizeof *state ); + } + + virtual void reset_state() + { + memcpy( banks, "\0\2\4\5\6\7\0\1", sizeof banks ); + + counter_just_clocked = 0; + next_time = 0; + mirror = 1; + + /* Cart specified vertical mirroring */ + if ( cart().mirroring() & 1 ) + mirror = 0; + } + + void start_frame() { next_time = first_scanline; } + + virtual void apply_mapping() + { + write( 0, 0xA000, mirror ); + write( 0, 0xA001, sram_mode ); + update_chr_banks(); + update_prg_banks(); + start_frame(); + } + + void clock_counter() + { + if ( counter_just_clocked ) + counter_just_clocked--; + + if ( !irq_ctr-- ) + { + irq_ctr = irq_latch; + //if ( !irq_latch ) + //dprintf( "MMC3 IRQ counter reloaded with 0\n" ); + } + + //dprintf( "%6d MMC3 IRQ clocked\n", time / ppu_overclock ); + if ( irq_ctr == 0 ) + { + //if ( irq_enabled && !irq_flag ) + //dprintf( "%6d MMC3 IRQ triggered: %f\n", time / ppu_overclock, time / scanline_len.0 - 20 ); + irq_flag = irq_enabled; + } + } + + + virtual void a12_clocked() + { + clock_counter(); + if ( irq_enabled ) + irq_changed(); + } + + virtual void end_frame( nes_time_t end_time ) + { + run_until( end_time ); + start_frame(); + } + + virtual nes_time_t next_irq( nes_time_t present ) + { + run_until( present ); + + if ( !irq_enabled ) + return no_irq; + + if ( irq_flag ) + return 0; + + if ( !ppu_enabled() ) + return no_irq; + + int remain = irq_ctr - 1; + if ( remain < 0 ) + remain = irq_latch; + + long time = remain * 341L + next_time; + if ( time > last_scanline ) + return no_irq; + + return time / ppu_overclock + 1; + } + + void run_until( nes_time_t end_time ) + { + bool bg_enabled = ppu_enabled(); + + if (next_time < 0) next_time = 0; + + end_time *= ppu_overclock; + while ( next_time < end_time && next_time <= last_scanline ) + { + if ( bg_enabled ) + clock_counter(); + next_time += Nes_Ppu::scanline_len; + } + } + + void update_chr_banks() + { + int chr_xor = (mode >> 7 & 1) * 0x1000; + set_chr_bank( 0x0000 ^ chr_xor, bank_2k, banks [0] >> 1 ); + set_chr_bank( 0x0800 ^ chr_xor, bank_2k, banks [1] >> 1 ); + set_chr_bank( 0x1000 ^ chr_xor, bank_1k, banks [2] ); + set_chr_bank( 0x1400 ^ chr_xor, bank_1k, banks [3] ); + set_chr_bank( 0x1800 ^ chr_xor, bank_1k, banks [4] ); + set_chr_bank( 0x1c00 ^ chr_xor, bank_1k, banks [5] ); + } + + void update_prg_banks() + { + set_prg_bank( 0xA000, bank_8k, banks [7] ); + nes_addr_t addr = 0x8000 + 0x4000 * (mode >> 6 & 1); + set_prg_bank( addr, bank_8k, banks [6] ); + set_prg_bank( addr ^ 0x4000, bank_8k, last_bank - 1 ); + } + + void write_irq( nes_addr_t addr, int data ) + { + switch ( addr & 0xE001 ) + { + case 0xC000: + irq_latch = data; + break; + + case 0xC001: + /* MMC3 IRQ counter pathological behavior triggered if + * counter_just_clocked is 1 */ + counter_just_clocked = 2; + irq_ctr = 0; + break; + + case 0xE000: + irq_flag = false; + irq_enabled = false; + break; + + case 0xE001: + irq_enabled = true; + break; + } + if ( irq_enabled ) + irq_changed(); + } + + void write( nes_time_t time, nes_addr_t addr, int data ) + { + switch ( addr & 0xE001 ) + { + case 0x8000: { + int changed = mode ^ data; + mode = data; + // avoid unnecessary bank updates + if ( changed & 0x80 ) + update_chr_banks(); + if ( changed & 0x40 ) + update_prg_banks(); + break; + } + + case 0x8001: { + int bank = mode & 7; + banks [bank] = data; + if ( bank < 6 ) + update_chr_banks(); + else + update_prg_banks(); + break; + } + + case 0xA000: + mirror = data; + if ( !(cart().mirroring() & 0x08) ) + { + if ( mirror & 1 ) + mirror_horiz(); + else + mirror_vert(); + } + break; + + case 0xA001: + sram_mode = data; + //dprintf( "%02X->%04X\n", data, addr ); + + // Startropics 1 & 2 use MMC6 and always enable low 512 bytes of SRAM + if ( (data & 0x3F) == 0x30 ) + enable_sram( true ); + else + enable_sram( data & 0x80, data & 0x40 ); + break; + + default: + run_until( time ); + write_irq( addr, data ); + break; + } + } + + nes_time_t next_time; + int counter_just_clocked; // used only for debugging +}; + diff --git a/core/mappers/mapper005.hpp b/core/mappers/mapper005.hpp new file mode 100644 index 0000000..0724361 --- /dev/null +++ b/core/mappers/mapper005.hpp @@ -0,0 +1,152 @@ +#pragma once + +// NES MMC5 mapper, currently only tailored for Castlevania 3 (U) + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +#include "Nes_Core.h" +#include + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + + +struct mmc5_state_t +{ + enum { reg_count = 0x30 }; + uint8_t regs [0x30]; + uint8_t irq_enabled; +}; +// to do: finalize state format +BOOST_STATIC_ASSERT( sizeof (mmc5_state_t) == 0x31 ); + +// MMC5 + +class Mapper005 : public Nes_Mapper, mmc5_state_t { +public: + Mapper005() + { + mmc5_state_t* state = this; + register_state( state, sizeof *state ); + } + + virtual void reset_state() + { + irq_time = no_irq; + regs [0x00] = 2; + regs [0x01] = 3; + regs [0x14] = 0x7f; + regs [0x15] = 0x7f; + regs [0x16] = 0x7f; + regs [0x17] = 0x7f; + } + + virtual void read_state( mapper_state_t const& in ) + { + Nes_Mapper::read_state( in ); + irq_time = no_irq; + } + + enum { regs_addr = 0x5100 }; + + virtual nes_time_t next_irq( nes_time_t ) + { + if ( irq_enabled & 0x80 ) + return irq_time; + + return no_irq; + } + + virtual bool write_intercepted( nes_time_t time, nes_addr_t addr, int data ) + { + int reg = addr - regs_addr; + if ( (unsigned) reg < reg_count ) + { + regs [reg] = data; + switch ( reg ) + { + case 0x05: + mirror_manual( data & 3, data >> 2 & 3, + data >> 4 & 3, data >> 6 & 3 ); + break; + + case 0x15: + set_prg_bank( 0x8000, bank_16k, data >> 1 & 0x3f ); + break; + + case 0x16: + set_prg_bank( 0xC000, bank_8k, data & 0x7f ); + break; + + case 0x17: + set_prg_bank( 0xE000, bank_8k, data & 0x7f ); + break; + + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x28: + case 0x29: + case 0x2a: + case 0x2b: + set_chr_bank( ((reg >> 1 & 4) + (reg & 3)) * 0x400, bank_1k, data ); + break; + } + } + else if ( addr == 0x5203 ) + { + irq_time = no_irq; + if ( data && data < 240 ) + { + irq_time = (341 * 21 + 128 + (data * 341)) / 3; + if ( irq_time < time ) + irq_time = no_irq; + } + irq_changed(); + } + else if ( addr == 0x5204 ) + { + irq_enabled = data; + irq_changed(); + } + else + { + return false; + } + + return true; + } + + void apply_mapping() + { + static unsigned char list [] = { + 0x05, 0x15, 0x16, 0x17, + 0x20, 0x21, 0x22, 0x23, + 0x28, 0x29, 0x2a, 0x2b + }; + + for ( int i = 0; i < (int) sizeof list; i++ ) + write_intercepted( 0, regs_addr + list [i], regs [list [i]] ); + intercept_writes( 0x5100, 0x200 ); + } + + virtual void write( nes_time_t, nes_addr_t, int ) { } + + nes_time_t irq_time; +}; + + + diff --git a/core/mappers/mapper007.hpp b/core/mappers/mapper007.hpp new file mode 100644 index 0000000..f598235 --- /dev/null +++ b/core/mappers/mapper007.hpp @@ -0,0 +1,52 @@ +#pragma once + +// Common simple mappers + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// AOROM + +class Mapper007 : public Nes_Mapper { + uint8_t bank; +public: + Mapper007() + { + register_state( &bank, 1 ); + } + + virtual void apply_mapping() + { + int b = bank; + bank = ~b; // force update + write( 0, 0, b ); + } + + virtual void write( nes_time_t, nes_addr_t, int data ) + { + int changed = bank ^ data; + bank = data; + + if ( changed & 0x10 ) + mirror_single( bank >> 4 & 1 ); + + if ( changed & 0x0f ) + set_prg_bank( 0x8000, bank_32k, bank & 7 ); + } +}; + + diff --git a/core/mappers/mapper009.hpp b/core/mappers/mapper009.hpp new file mode 100644 index 0000000..84ccf51 --- /dev/null +++ b/core/mappers/mapper009.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include "Nes_Mapper.h" +#include "blargg_source.h" + +// MMC2 + +class Mapper009: public Nes_Mapper +{ + uint8_t regs[6]; // A,B,C,D,E,F + + void mirror(uint8_t val) + { + if (val & 1) + mirror_horiz(); + else + mirror_vert(); + } + +public: + Mapper009() + { + register_state(regs, sizeof(regs)); + } + + virtual void reset_state() + { + std::memset(regs, 0, sizeof(regs)); + } + + virtual void apply_mapping() + { + mirror(regs[5]); + set_prg_bank(0x8000, bank_8k, regs[0]); + set_prg_bank(0xa000, bank_8k, 13); + set_prg_bank(0xc000, bank_8k, 14); + set_prg_bank(0xe000, bank_8k, 15); + + set_chr_bank(0x0000, bank_4k, regs[1]); + set_chr_bank(0x1000, bank_4k, regs[3]); + + set_chr_bank_ex(0x0000, bank_4k, regs[2]); + set_chr_bank_ex(0x1000, bank_4k, regs[4]); + } + + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + switch (addr >> 12) + { + case 0xa: regs[0] = data; set_prg_bank(0x8000, bank_8k, data); break; + case 0xb: regs[1] = data; set_chr_bank(0x0000, bank_4k, data); break; + case 0xc: regs[2] = data; set_chr_bank_ex(0x0000, bank_4k, data); break; + case 0xd: regs[3] = data; set_chr_bank(0x1000, bank_4k, data); break; + case 0xe: regs[4] = data; set_chr_bank_ex(0x1000, bank_4k, data); break; + case 0xf: regs[5] = data; mirror(data); break; + } + } +}; + diff --git a/core/mappers/mapper010.hpp b/core/mappers/mapper010.hpp new file mode 100644 index 0000000..186cc5c --- /dev/null +++ b/core/mappers/mapper010.hpp @@ -0,0 +1,58 @@ +#pragma once +#include + +#include "Nes_Mapper.h" +#include "blargg_source.h" + +// MMC4 + +class Mapper010: public Nes_Mapper +{ + uint8_t regs[6]; // A,B,C,D,E,F + + void mirror(uint8_t val) + { + if (val & 1) + mirror_horiz(); + else + mirror_vert(); + } + +public: + Mapper010() + { + register_state(regs, sizeof(regs)); + } + + virtual void reset_state() + { + std::memset(regs, 0, sizeof(regs)); + } + + virtual void apply_mapping() + { + enable_sram(); + + mirror(regs[5]); + set_prg_bank(0x8000, bank_16k, regs[0]); + + set_chr_bank(0x0000, bank_4k, regs[1]); + set_chr_bank(0x1000, bank_4k, regs[3]); + + set_chr_bank_ex(0x0000, bank_4k, regs[2]); + set_chr_bank_ex(0x1000, bank_4k, regs[4]); + } + + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + switch (addr >> 12) + { + case 0xa: regs[0] = data; set_prg_bank(0x8000, bank_16k, data); break; + case 0xb: regs[1] = data; set_chr_bank(0x0000, bank_4k, data); break; + case 0xc: regs[2] = data; set_chr_bank_ex(0x0000, bank_4k, data); break; + case 0xd: regs[3] = data; set_chr_bank(0x1000, bank_4k, data); break; + case 0xe: regs[4] = data; set_chr_bank_ex(0x1000, bank_4k, data); break; + case 0xf: regs[5] = data; mirror(data); break; + } + } +}; diff --git a/core/mappers/mapper011.hpp b/core/mappers/mapper011.hpp new file mode 100644 index 0000000..fd03ccf --- /dev/null +++ b/core/mappers/mapper011.hpp @@ -0,0 +1,50 @@ +#pragma once + +// Optional less-common simple mappers + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// Color Dreams + +class Mapper011 : public Nes_Mapper { + uint8_t bank; +public: + Mapper011() + { + register_state( &bank, 1 ); + } + + virtual void apply_mapping() + { + int b = bank; + bank = ~b; + write( 0, 0, b ); + } + + virtual void write( nes_time_t, nes_addr_t, int data ) + { + int changed = bank ^ data; + bank = data; + + if ( changed & 0x0f ) + set_prg_bank( 0x8000, bank_32k, bank & 0x0f ); + + if ( changed & 0xf0 ) + set_chr_bank( 0, bank_8k, bank >> 4 ); + } +}; diff --git a/core/mappers/mapper015.hpp b/core/mappers/mapper015.hpp new file mode 100644 index 0000000..df994f1 --- /dev/null +++ b/core/mappers/mapper015.hpp @@ -0,0 +1,92 @@ +/* Copyright (C) 2018. + * This module 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 + * module 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 module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * 100-in-1 Contra Function 16 + */ + +#pragma once + +#include "Nes_Mapper.h" + +struct Mapper015_state_t +{ + uint8_t prg_bank [ 4 ]; + uint8_t mirroring; +}; + +BOOST_STATIC_ASSERT( sizeof (Mapper015_state_t) == 5 ); + +// K-1029, K-1030P + +class Mapper015 : public Nes_Mapper, Mapper015_state_t { +public: + Mapper015() + { + i = 0; + Mapper015_state_t* state = this; + register_state( state, sizeof *state ); + } + + virtual void reset_state() + { + write( 0, 0x8000, 0 ); + } + + virtual void apply_mapping() + { + enable_sram(); + set_chr_bank ( 0, bank_8k, 0 ); + for ( i = 0; i < sizeof prg_bank; i++ ) + set_prg_bank ( 0x8000 + ( i * 0x2000 ), bank_8k, prg_bank [i] ); + switch ( mirroring ) + { + case 0: mirror_vert(); break; + case 1: mirror_horiz(); break; + } + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + uint8_t bank = ( data & 0x3F ) << 1; + uint8_t sbank = ( data >> 7 ) & 1; + + mirroring = ( data >> 6 ) & 1; + switch ( addr & 3 ) + { + case 0: + for ( i = 0; i < sizeof prg_bank; i++ ) + prg_bank [ i ] = bank + i; + apply_mapping(); + break; + case 2: + for ( i = 0; i < sizeof prg_bank; i++ ) + prg_bank [ i ] = bank | sbank; + apply_mapping(); + break; + case 1: + case 3: + for ( i = 0; i < sizeof prg_bank; i++ ) + { + if ( i >= 2 && !( addr & 2 ) ) + bank = 0x7E; + prg_bank [ i ] = bank + ( i & 1 ); + } + apply_mapping(); + break; + } + } + + unsigned long int i; +}; + diff --git a/core/mappers/mapper019.hpp b/core/mappers/mapper019.hpp new file mode 100644 index 0000000..44d4106 --- /dev/null +++ b/core/mappers/mapper019.hpp @@ -0,0 +1,220 @@ +#pragma once + +// Namco 106 mapper + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +#include "blargg_endian.h" +#include "Nes_Namco_Apu.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// to do: CHR mapping and nametable handling needs work + +struct namco106_state_t +{ + uint8_t regs [16]; + uint16_t irq_ctr; + uint8_t irq_pending; + uint8_t unused1 [1]; + namco_state_t sound_state; + + void swap() + { + set_le16( &irq_ctr, irq_ctr ); + for ( unsigned i = 0; i < sizeof sound_state.delays / sizeof sound_state.delays [0]; i++ ) + set_le16( &sound_state.delays [i], sound_state.delays [i] ); + } +}; + +BOOST_STATIC_ASSERT( sizeof (namco106_state_t) == 20 + sizeof (namco_state_t) ); + +// Namco106 + +class Mapper019 : public Nes_Mapper, namco106_state_t { +public: + Mapper019() + { + namco106_state_t* state = this; + register_state( state, sizeof *state ); + } + + virtual int channel_count() const { return sound.osc_count; } + + virtual void set_channel_buf( int i, Blip_Buffer* b ) { sound.osc_output( i, b ); } + + virtual void set_treble( blip_eq_t const& eq ) { sound.treble_eq( eq ); } + + void reset_state() + { + regs [12] = 0; + regs [13] = 1; + regs [14] = last_bank - 1; + sound.reset(); + } + + virtual void apply_mapping() + { + last_time = 0; + enable_sram(); + intercept_writes( 0x4800, 1 ); + intercept_reads ( 0x4800, 1 ); + + intercept_writes( 0x5000, 0x1000 ); + intercept_reads ( 0x5000, 0x1000 ); + + for ( int i = 0; i < (int) sizeof regs; i++ ) + write( 0, 0x8000 + i * 0x800, regs [i] ); + } + + virtual nes_time_t next_irq( nes_time_t time ) + { + if ( irq_pending ) + return time; + + if ( !(irq_ctr & 0x8000) ) + return no_irq; + + return 0x10000 - irq_ctr + last_time; + } + + virtual void run_until( nes_time_t end_time ) + { + long count = irq_ctr + (end_time - last_time); + if ( irq_ctr & 0x8000 ) + { + if ( count > 0xffff ) + { + count = 0xffff; + irq_pending = true; + } + } + else if ( count > 0x7fff ) + { + count = 0x7fff; + } + + irq_ctr = count; + last_time = end_time; + } + + virtual void end_frame( nes_time_t end_time ) + { + if ( end_time > last_time ) + run_until( end_time ); + last_time -= end_time; + sound.end_frame( end_time ); + } + + virtual int read( nes_time_t time, nes_addr_t addr ) + { + if ( addr == 0x4800 ) + return sound.read_data(); + + if ( addr == 0x5000 ) + { + irq_pending = false; + return irq_ctr & 0xff; + } + + if ( addr == 0x5800 ) + { + irq_pending = false; + return irq_ctr >> 8; + } + + return Nes_Mapper::read( time, addr ); + } + + virtual bool write_intercepted( nes_time_t time, nes_addr_t addr, int data ) + { + if ( addr == 0x4800 ) + { + sound.write_data( time, data ); + } + else if ( addr == 0x5000 ) + { + irq_ctr = (irq_ctr & 0xff00) | data; + irq_pending = false; + irq_changed(); + } + else if ( addr == 0x5800 ) + { + irq_ctr = (data << 8) | (irq_ctr & 0xff); + irq_pending = false; + irq_changed(); + } + else + { + return false; + } + + return true; + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + int reg = addr >> 11 & 0x0F; + regs [reg] = data; + + int prg_bank = reg - 0x0c; + if ( (unsigned) prg_bank < 3 ) + { + if ( prg_bank == 0 && (data & 0x40) ) + mirror_vert(); + set_prg_bank( 0x8000 | (prg_bank << bank_8k), bank_8k, data & 0x3F ); + } + else if ( reg < 8 ) + { + set_chr_bank( reg * 0x400, bank_1k, data ); + } + else if ( reg < 0x0c ) + { + mirror_manual( regs [8] & 1, regs [9] & 1, regs [10] & 1, regs [11] & 1 ); + } + else + { + sound.write_addr( data ); + } + } + + void swap() + { + set_le16( &irq_ctr, irq_ctr ); + for ( unsigned i = 0; i < sizeof sound_state.delays / sizeof sound_state.delays [0]; i++ ) + set_le16( &sound_state.delays [i], sound_state.delays [i] ); + } + + void save_state( mapper_state_t& out ) + { + sound.save_state( &sound_state ); + namco106_state_t::swap(); + Nes_Mapper::save_state( out ); + namco106_state_t::swap(); + } + + void read_state( mapper_state_t const& in ) + { + Nes_Mapper::read_state( in ); + namco106_state_t::swap(); + sound.load_state( sound_state ); + } + + Nes_Namco_Apu sound; + nes_time_t last_time; +}; + + diff --git a/core/mappers/mapper021.hpp b/core/mappers/mapper021.hpp new file mode 100644 index 0000000..e6ec509 --- /dev/null +++ b/core/mappers/mapper021.hpp @@ -0,0 +1,250 @@ +/* Copyright notice for this file: + * Copyright (C) 2004-2006 Shay Green + * Copyright (C) 2007 CaH4e3 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * General code is from FCEUX https://sourceforge.net/p/fceultra/code/HEAD/tree/fceu/trunk/src/boards/vrc2and4.cpp + * IRQ portion is from existing VRC6/VRC7 by Shay Green + * This mapper was ported by retrowertz for Libretro port of QuickNES. + * 3-19-2018 + * + * VRC-2/VRC-4 Konami + */ + +#pragma once +#include "Nes_Mapper.h" + +struct vrc2_state_t +{ + uint8_t prg_banks [ 2 ]; + uint8_t chr_banks [ 8 ]; + uint8_t mirroring; + uint8_t prg_swap; + uint8_t irq_latch; + uint8_t irq_control; + + // internal state + uint16_t next_time; + uint8_t irq_pending; +}; + +BOOST_STATIC_ASSERT( sizeof ( vrc2_state_t ) == 18 ); + +template +class Mapper_VRC2_4 : public Nes_Mapper, vrc2_state_t { +public: + Mapper_VRC2_4() + { + if (type_a && type_b) // mapper 21 + { + is22 = 0; + reg1mask = 0x42; + reg2mask = 0x84; + } + else if (!type_a && type_b) // mapper 22 + { + is22 = 1; + reg1mask = 2; + reg2mask = 1; + } + else if (!type_a && !type_b) // mapper 23 + { + is22 = 0; + reg1mask = 0x15; + reg2mask = 0x2a; + } + else if (type_a && !type_b) // mapper 25 + { + is22 = 0; + reg1mask = 0xa; + reg2mask = 0x5; + } + vrc2_state_t * state = this; + register_state( state, sizeof * state ); + } + + void reset_state() + { + } + + void apply_mapping() + { + if ( !is22 ) enable_sram(); + update_prg(); + update_chr(); + set_mirroring(); + } + + void reset_timer( nes_time_t present ) + { + next_time = present + unsigned ( ( 0x100 - irq_latch ) * timer_period ) / 4; + } + + virtual void run_until( nes_time_t end_time ) + { + if ( irq_control & 2 ) + { + while ( next_time < end_time ) + { + // printf( "%d timer expired\n", next_time ); + irq_pending = true; + reset_timer( next_time ); + } + } + } + + virtual void end_frame( nes_time_t end_time ) + { + run_until( end_time ); + + // to do: next_time might go negative if IRQ is disabled + next_time -= end_time; + } + + virtual nes_time_t next_irq( nes_time_t present ) + { + if ( irq_pending ) + return present; + + if ( irq_control & 2 ) + return next_time + 1; + + return no_irq; + } + + void write_irq( nes_time_t time, nes_addr_t addr, int data ); + + void write( nes_time_t time, nes_addr_t addr, int data ) + { + addr = ( addr & 0xF000 ) | !!( addr & reg2mask ) << 1 | !!( addr & reg1mask ); + + if( addr >= 0xB000 && addr <= 0xE003) + { + unsigned banknumber = ( ( addr >> 1 ) & 1 ) | ( ( addr - 0xB000 ) >> 11 ); + unsigned offset = ( addr & 1 ) << 2; + + chr_banks [ banknumber ] &= ( 0xF0 ) >> offset; + chr_banks [ banknumber ] |= ( data & 0xF ) << offset; + chr_banks [ banknumber ] |= ( offset ? ( ( data & 0x10 ) << 4 ) : 0 ); + update_chr(); + } + else + { + switch ( addr & 0xF003 ) + { + case 0x8000: + case 0x8001: + case 0x8002: + case 0x8003: + prg_banks [ 0 ] = data & 0x1F; + update_prg(); + break; + case 0xA000: + case 0xA001: + case 0xA002: + case 0xA003: + prg_banks [ 1 ] = data & 0x1F; + update_prg(); + break; + case 0x9000: + case 0x9001: + mirroring = data; + set_mirroring(); + break; + case 0x9002: + case 0x9003: + prg_swap = data; + update_prg(); + break; + case 0xF000: + case 0xF001: + case 0xF002: + case 0xF003: + write_irq( time, addr, data ); + break; + } + } + } + + unsigned is22, reg1mask, reg2mask; + enum { timer_period = 113 * 4 + 3 }; + +private: + void set_mirroring() + { + switch ( mirroring & 3 ) + { + case 0: mirror_vert(); break; + case 1: mirror_horiz(); break; + case 2: + case 3: mirror_single( mirroring & 1 ); break; + } + } + + void update_prg() + { + if ( prg_swap & 2 ) + { + set_prg_bank( 0x8000, bank_8k, ( 0xFE ) ); + set_prg_bank( 0xC000, bank_8k, prg_banks [ 0 ] ); + } + else + { + set_prg_bank( 0x8000, bank_8k, prg_banks [ 0 ] ); + set_prg_bank( 0xC000, bank_8k, ( 0xFE ) ); + } + + set_prg_bank( 0xA000, bank_8k, prg_banks [ 1 ] ); + set_prg_bank( 0xE000, bank_8k, ( 0xFF ) ); + } + + void update_chr() + { + for ( int i = 0; i < (int) sizeof chr_banks; i++ ) + set_chr_bank( i * 0x400, bank_1k, chr_banks [ i ] >> is22 ); + } +}; + +template +void Mapper_VRC2_4::write_irq( nes_time_t time, + nes_addr_t addr, int data ) +{ + // IRQ + run_until( time ); + //printf("%6d VRC2_4 [%d] A:%04x V:%02x\n", time, addr & 3, addr, data); + switch ( addr & 3 ) + { + case 0: + irq_latch = ( irq_latch & 0xF0 ) | ( data & 0xF ); + break; + case 1: + irq_latch = ( irq_latch & 0x0F ) | ( ( data & 0xF ) << 4 ); + break; + case 2: + irq_pending = false; + irq_control = data & 3; + if ( data & 2 ) reset_timer( time ); + break; + case 3: + irq_pending = false; + irq_control = ( irq_control & ~2 ) | ( ( irq_control << 1 ) & 2 ); + break; + } + irq_changed(); +} + + +typedef Mapper_VRC2_4 Mapper021; diff --git a/core/mappers/mapper022.hpp b/core/mappers/mapper022.hpp new file mode 100644 index 0000000..7200aa1 --- /dev/null +++ b/core/mappers/mapper022.hpp @@ -0,0 +1,31 @@ +/* Copyright notice for this file: + * Copyright (C) 2004-2006 Shay Green + * Copyright (C) 2007 CaH4e3 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * General code is from FCEUX https://sourceforge.net/p/fceultra/code/HEAD/tree/fceu/trunk/src/boards/vrc2and4.cpp + * IRQ portion is from existing VRC6/VRC7 by Shay Green + * This mapper was ported by retrowertz for Libretro port of QuickNES. + * 3-19-2018 + * + * VRC-2/VRC-4 Konami + */ + +#pragma once +#include "Nes_Mapper.h" +#include "mappers/mapper021.hpp" + +typedef Mapper_VRC2_4 Mapper022; \ No newline at end of file diff --git a/core/mappers/mapper023.hpp b/core/mappers/mapper023.hpp new file mode 100644 index 0000000..dda8c03 --- /dev/null +++ b/core/mappers/mapper023.hpp @@ -0,0 +1,30 @@ +/* Copyright notice for this file: + * Copyright (C) 2004-2006 Shay Green + * Copyright (C) 2007 CaH4e3 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * General code is from FCEUX https://sourceforge.net/p/fceultra/code/HEAD/tree/fceu/trunk/src/boards/vrc2and4.cpp + * IRQ portion is from existing VRC6/VRC7 by Shay Green + * This mapper was ported by retrowertz for Libretro port of QuickNES. + * 3-19-2018 + * + * VRC-2/VRC-4 Konami + */ + +#pragma once +#include "Nes_Mapper.h" + +typedef Mapper_VRC2_4 Mapper023; \ No newline at end of file diff --git a/core/mappers/mapper024.hpp b/core/mappers/mapper024.hpp new file mode 100644 index 0000000..f91bcde --- /dev/null +++ b/core/mappers/mapper024.hpp @@ -0,0 +1,244 @@ + +// Konami VRC6 mapper + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +#include +#include "Nes_Vrc6_Apu.h" +#include "blargg_endian.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +struct vrc6_state_t +{ + // written registers + uint8_t prg_16k_bank; + // could move sound regs int and out of vrc6_apu_state_t for state saving, + // allowing them to be stored here + uint8_t old_sound_regs [3] [3]; // to do: eliminate this duplicate + uint8_t mirroring; + uint8_t prg_8k_bank; + uint8_t chr_banks [8]; + uint8_t irq_reload; + uint8_t irq_mode; + + // internal state + uint16_t next_time; + uint8_t irq_pending; + uint8_t unused; + + vrc6_apu_state_t sound_state; + + void swap() + { + set_le16( &next_time, next_time ); + for ( unsigned i = 0; i < sizeof sound_state.delays / sizeof sound_state.delays [0]; i++ ) + set_le16( &sound_state.delays [i], sound_state.delays [i] ); + } +}; +BOOST_STATIC_ASSERT( sizeof (vrc6_state_t) == 26 + sizeof (vrc6_apu_state_t) ); + + + +template +class Mapper_Vrc6 : public Nes_Mapper, vrc6_state_t { +public: + Mapper_Vrc6( ) + { + swap_mask = swapMask; + vrc6_state_t* state = this; + register_state( state, sizeof *state ); + } + + virtual int channel_count() const { return sound.osc_count; } + + virtual void set_channel_buf( int i, Blip_Buffer* b ) { sound.osc_output( i, b ); } + + virtual void set_treble( blip_eq_t const& eq ) { sound.treble_eq( eq ); } + + virtual void reset_state() + { + prg_8k_bank = last_bank - 1; + sound.reset(); + } + + virtual void save_state( mapper_state_t& out ) + { + sound.save_state( &sound_state ); + vrc6_state_t::swap(); + Nes_Mapper::save_state( out ); + vrc6_state_t::swap(); // to do: kind of hacky to swap in place + } + + virtual void apply_mapping() + { + enable_sram(); + set_prg_bank( 0x8000, bank_16k, prg_16k_bank ); + set_prg_bank( 0xC000, bank_8k, prg_8k_bank ); + + for ( int i = 0; i < (int) sizeof chr_banks; i++ ) + set_chr_bank( i * 0x400, bank_1k, chr_banks [i] ); + + write_bank( 0xb003, mirroring ); + } + + void reset_timer( nes_time_t present ) + { + next_time = present + unsigned ((0x100 - irq_reload) * timer_period) / 4; + } + + virtual void run_until( nes_time_t end_time ) + { + if ( irq_mode & 2 ) + { + while ( next_time < end_time ) + { + //dprintf( "%d timer expired\n", next_time ); + irq_pending = true; + reset_timer( next_time ); + } + } + } + + virtual void end_frame( nes_time_t end_time ) + { + run_until( end_time ); + + // to do: next_time might go negative if IRQ is disabled + next_time -= end_time; + + sound.end_frame( end_time ); + } + + virtual nes_time_t next_irq( nes_time_t present ) + { + if ( irq_pending ) + return present; + + if ( irq_mode & 2 ) + return next_time + 1; + + return no_irq; + } + + virtual void write( nes_time_t time, nes_addr_t addr, int data ) + { + int osc = unsigned (addr - sound.base_addr) / sound.addr_step; + + if ( (addr + 1) & 2 ) // optionally swap 1 and 2 + addr ^= swap_mask; + + int reg = addr & 3; + if ( (unsigned) osc < sound.osc_count && reg < sound.reg_count ) + sound.write_osc( time, osc, reg, data ); + else if ( addr < 0xf000 ) + write_bank( addr, data ); + else + write_irq( time, addr, data ); + } + int swap_mask; + Nes_Vrc6_Apu sound; + enum { timer_period = 113 * 4 + 3 }; + + +void read_state( mapper_state_t const& in ) +{ + Nes_Mapper::read_state( in ); + vrc6_state_t::swap(); + + // to do: eliminate when format is updated + // old-style registers + static char zero [sizeof old_sound_regs] = { 0 }; + if ( 0 != memcmp( old_sound_regs, zero, sizeof zero ) ) + { + /* Using old VRC6 sound register format */ + memcpy( sound_state.regs, old_sound_regs, sizeof sound_state.regs ); + memset( old_sound_regs, 0, sizeof old_sound_regs ); + } + + sound.load_state( sound_state ); +} + +void write_irq( nes_time_t time, nes_addr_t addr, int data ) +{ + // IRQ + run_until( time ); + //dprintf( "%d VRC6 IRQ [%d] = %02X\n", time, addr & 3, data ); + switch ( addr & 3 ) + { + case 0: + irq_reload = data; + break; + + case 1: + irq_pending = false; + irq_mode = data; + if ( data & 2 ) + reset_timer( time ); + break; + + case 2: + irq_pending = false; + irq_mode = (irq_mode & ~2) | ((irq_mode << 1) & 2); + break; + } + irq_changed(); +} + +void write_bank( nes_addr_t addr, int data ) +{ + switch ( addr & 0xf003 ) + { + case 0x8000: + prg_16k_bank = data; + set_prg_bank( 0x8000, bank_16k, data ); + break; + + case 0xb003: { + mirroring = data; + + //dprintf( "Change mirroring %d\n", data ); +// emu()->enable_sram( data & 0x80 ); // to do: needed? + int page = data >> 5 & 1; + if ( data & 8 ) + mirror_single( ((data >> 2) ^ page) & 1 ); + else if ( data & 4 ) + mirror_horiz( page ); + else + mirror_vert( page ); + break; + } + + case 0xc000: + prg_8k_bank = data; + set_prg_bank( 0xC000, bank_8k, data ); + break; + + default: + int bank = (addr >> 11 & 4) | (addr & 3); + if ( addr >= 0xd000 ) + { + //dprintf( "change chr bank %d\n", bank ); + chr_banks [bank] = data; + set_chr_bank( bank * 0x400, bank_1k, data ); + } + break; + } + } +}; + + +typedef Mapper_Vrc6<0> Mapper024; \ No newline at end of file diff --git a/core/mappers/mapper025.hpp b/core/mappers/mapper025.hpp new file mode 100644 index 0000000..4669585 --- /dev/null +++ b/core/mappers/mapper025.hpp @@ -0,0 +1,30 @@ +/* Copyright notice for this file: + * Copyright (C) 2004-2006 Shay Green + * Copyright (C) 2007 CaH4e3 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * General code is from FCEUX https://sourceforge.net/p/fceultra/code/HEAD/tree/fceu/trunk/src/boards/vrc2and4.cpp + * IRQ portion is from existing VRC6/VRC7 by Shay Green + * This mapper was ported by retrowertz for Libretro port of QuickNES. + * 3-19-2018 + * + * VRC-2/VRC-4 Konami + */ + +#pragma once +#include "Nes_Mapper.h" + +typedef Mapper_VRC2_4 Mapper025; \ No newline at end of file diff --git a/core/mappers/mapper026.hpp b/core/mappers/mapper026.hpp new file mode 100644 index 0000000..1b64f10 --- /dev/null +++ b/core/mappers/mapper026.hpp @@ -0,0 +1,9 @@ +#pragma once + +// Konami VRC6 mapper + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +typedef Mapper_Vrc6<3> Mapper026; \ No newline at end of file diff --git a/core/mappers/mapper030.hpp b/core/mappers/mapper030.hpp new file mode 100644 index 0000000..c3e817e --- /dev/null +++ b/core/mappers/mapper030.hpp @@ -0,0 +1,51 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * 3/24/18 + * + * Unrom-512 + * + * NOTE: + * No flash and one screen mirroring support. + * Tested only on Troll Burner and Mystic Origins demo. + */ + +#pragma once + +#include "Nes_Mapper.h" + +// Unrom512 + +class Mapper030 : public Nes_Mapper { +public: + Mapper030() { } + + void reset_state() { } + + void apply_mapping() { } + + void write( nes_time_t, nes_addr_t addr, int data ) + { + if ( ( addr & 0xF000 ) >= 0x8000 ) + { + set_prg_bank(0x8000, bank_16k, data & 0x1F); + set_chr_bank(0x0000, bank_8k, (data >> 5) & 0x3); + } + } +}; + diff --git a/core/mappers/mapper032.hpp b/core/mappers/mapper032.hpp new file mode 100644 index 0000000..c534fd8 --- /dev/null +++ b/core/mappers/mapper032.hpp @@ -0,0 +1,119 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * Mapper 32 - Irem's G-101 + * + */ + +#pragma once + +#include "Nes_Mapper.h" + +struct mapper32_state_t +{ + uint8_t chr_bank [ 8 ]; + uint8_t prg_bank [ 2 ]; + uint8_t prg_mode; + uint8_t mirr; +}; + +BOOST_STATIC_ASSERT( sizeof ( mapper32_state_t ) == 12 ); + +// Irem_G101 + +class Mapper032 : public Nes_Mapper, mapper32_state_t { +public: + Mapper032() + { + mapper32_state_t * state = this; + register_state( state, sizeof * state ); + } + + virtual void reset_state() + { + prg_bank [ 0 ] = ~1; + prg_bank [ 1 ] = ~0; + enable_sram(); + } + + virtual void apply_mapping() + { + if ( prg_mode == 0 ) + { + set_prg_bank ( 0x8000, bank_8k, prg_bank [ 0 ] ); + set_prg_bank ( 0xA000, bank_8k, prg_bank [ 1 ] ); + set_prg_bank ( 0xC000, bank_8k, ~1 ); + set_prg_bank ( 0xE000, bank_8k, ~0 ); + } + else + { + set_prg_bank ( 0xC000, bank_8k, prg_bank [ 0 ] ); + set_prg_bank ( 0xA000, bank_8k, prg_bank [ 1 ] ); + set_prg_bank ( 0x8000, bank_8k, ~1 ); + set_prg_bank ( 0xE000, bank_8k, ~0 ); + } + + for ( unsigned long int i = 0; i < sizeof chr_bank; i++) + set_chr_bank( ( i << 10 ), bank_1k, chr_bank [ i ] ); + + switch ( mirr ) + { + case 0: mirror_vert(); break; + case 1: mirror_horiz(); break; + } + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + switch ( addr & 0xF000 ) + { + case 0x8000: + prg_bank [ 0 ] = data; + switch ( prg_mode ) + { + case 0: set_prg_bank ( 0x8000, bank_8k, data ); break; + case 1: set_prg_bank ( 0xC000, bank_8k, data ); break; + } + break; + case 0x9000: + mirr = data & 1; + prg_mode = ( data >> 1 ) & 1; + switch ( data & 1 ) + { + case 0: mirror_vert(); break; + case 1: mirror_horiz(); break; + } + break; + case 0xA000: + prg_bank [ 1 ] = data; + set_prg_bank ( 0xA000, bank_8k, data ); + break; + } + + switch ( addr & 0xF007 ) + { + case 0xB000: case 0xB001: case 0xB002: case 0xB003: + case 0xB004: case 0xB005: case 0xB006: case 0xB007: + chr_bank [ addr & 0x07 ] = data; + set_chr_bank( ( addr & 0x07 ) << 10, bank_1k, data ); + break; + } + } +}; + diff --git a/core/mappers/mapper033.hpp b/core/mappers/mapper033.hpp new file mode 100644 index 0000000..1a5cb52 --- /dev/null +++ b/core/mappers/mapper033.hpp @@ -0,0 +1,93 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * Mapper 33 - Taito TC0190 + * + */ + +#pragma once + +#include "Nes_Mapper.h" + +struct tc0190_state_t +{ + uint8_t preg [ 2 ]; + uint8_t creg [ 6 ]; + uint8_t mirr; +}; + +BOOST_STATIC_ASSERT( sizeof ( tc0190_state_t ) == 9 ); + +// TaitoTC0190 + +class Mapper033 : public Nes_Mapper, tc0190_state_t { +public: + Mapper033() + { + tc0190_state_t *state = this; + register_state( state, sizeof *state ); + } + + virtual void reset_state() + { } + + virtual void apply_mapping() + { + for ( int i = 0; i < 2; i++ ) + { + set_prg_bank ( 0x8000 + ( i << 13 ), bank_8k, preg [ i ] ); + set_chr_bank ( 0x0000 + ( i << 11 ), bank_2k, creg [ i ] ); + } + + for ( int i = 0; i < 4; i++ ) + set_chr_bank ( 0x1000 + ( i << 10 ), bank_1k, creg [ 2 + i ] ); + + if ( mirr ) mirror_horiz(); + else mirror_vert(); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + switch ( addr & 0xA003 ) + { + case 0x8000: + preg [ 0 ] = data & 0x3F; + mirr = data >> 6; + set_prg_bank ( 0x8000, bank_8k, preg [ 0 ] ); + if ( mirr ) mirror_horiz(); + else mirror_vert(); + break; + case 0x8001: + preg [ 1 ] = data & 0x3F; + set_prg_bank ( 0xA000, bank_8k, preg [ 1 ] ); + break; + case 0x8002: case 0x8003: + addr &= 0x01; + creg [ addr ] = data; + set_chr_bank ( addr << 11, bank_2k, creg [ addr ] ); + break; + case 0xA000: case 0xA001: + case 0xA002: case 0xA003: + addr &= 0x03; + creg [ 2 + addr ] = data; + set_chr_bank ( 0x1000 | ( addr << 10 ), bank_1k, creg [ 2 + addr ] ); + break; + } + } +}; diff --git a/core/mappers/mapper034.hpp b/core/mappers/mapper034.hpp new file mode 100644 index 0000000..7cc6d81 --- /dev/null +++ b/core/mappers/mapper034.hpp @@ -0,0 +1,43 @@ +#pragma once + +// Optional less-common simple mappers + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// Nina-1 (Deadly Towers only) + +class Mapper034 : public Nes_Mapper { + uint8_t bank; +public: + Mapper034() + { + register_state( &bank, 1 ); + } + + virtual void apply_mapping() + { + write( 0, 0, bank ); + } + + virtual void write( nes_time_t, nes_addr_t, int data ) + { + bank = data; + set_prg_bank( 0x8000, bank_32k, bank ); + } +}; + diff --git a/core/mappers/mapper060.hpp b/core/mappers/mapper060.hpp new file mode 100644 index 0000000..1c88206 --- /dev/null +++ b/core/mappers/mapper060.hpp @@ -0,0 +1,50 @@ +/* Copyright (C) 2018. + * This module 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 + * module 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 module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * 4-in-1 Multicart ( Reset-based ) + */ + +#pragma once + +#include "Nes_Mapper.h" + +// NROM-128 4-in-1 multicart + +class Mapper060 : public Nes_Mapper { +public: + Mapper060() + { + last_game = 2; + register_state( &game_sel, 1 ); + } + + virtual void reset_state() + { + game_sel = last_game; + game_sel++; + game_sel &= 3; + } + + virtual void apply_mapping() + { + set_prg_bank ( 0x8000, bank_16k, game_sel ); + set_prg_bank ( 0xC000, bank_16k, game_sel ); + set_chr_bank ( 0, bank_8k, game_sel ); + last_game = game_sel; + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) { } + + uint8_t game_sel, last_game; +}; diff --git a/core/mappers/mapper066.hpp b/core/mappers/mapper066.hpp new file mode 100644 index 0000000..75ca737 --- /dev/null +++ b/core/mappers/mapper066.hpp @@ -0,0 +1,51 @@ +#pragma once + +// Optional less-common simple mappers + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// GNROM + +class Mapper066 : public Nes_Mapper { + uint8_t bank; +public: + Mapper066() + { + register_state( &bank, 1 ); + } + + virtual void apply_mapping() + { + int b = bank; + bank = ~b; + write( 0, 0, b ); + } + + virtual void write( nes_time_t, nes_addr_t, int data ) + { + int changed = bank ^ data; + bank = data; + + if ( changed & 0x30 ) + set_prg_bank( 0x8000, bank_32k, bank >> 4 & 3 ); + + if ( changed & 0x03 ) + set_chr_bank( 0, bank_8k, bank & 3 ); + } +}; + diff --git a/core/mappers/mapper069.hpp b/core/mappers/mapper069.hpp new file mode 100644 index 0000000..df8304f --- /dev/null +++ b/core/mappers/mapper069.hpp @@ -0,0 +1,206 @@ +#pragma once + +// Sunsoft FME-7 mapper + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/libs/ + +#include "Nes_Mapper.h" + +#include "blargg_endian.h" +#include "Nes_Fme7_Apu.h" + +/* Copyright (C) 2005 Chris Moeller */ +/* Copyright (C) 2005-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +struct fme7_state_t +{ + // first 16 bytes in register order + uint8_t regs [13]; + uint8_t irq_mode; + uint16_t irq_count; + + uint8_t command; + uint8_t irq_pending; + fme7_apu_state_t sound_state; // only used when saving/restoring state + + void swap() + { + set_le16( &irq_count, irq_count ); + for ( unsigned i = 0; i < sizeof sound_state.delays / sizeof sound_state.delays [0]; i++ ) + set_le16( &sound_state.delays [i], sound_state.delays [i] ); + } +}; +BOOST_STATIC_ASSERT( sizeof (fme7_state_t) == 18 + sizeof (fme7_apu_state_t) ); + + + +// Fme7 + +class Mapper069 : public Nes_Mapper, fme7_state_t { +public: + Mapper069() + { + fme7_state_t* state = this; + register_state( state, sizeof *state ); + } + + virtual int channel_count() const { return sound.osc_count; } + + virtual void set_channel_buf( int i, Blip_Buffer* b ) { sound.osc_output( i, b ); } + + virtual void set_treble( blip_eq_t const& eq ) { sound.treble_eq( eq ); } + + virtual void reset_state() + { + regs [8] = 0x40; // wram disabled + irq_count = 0xFFFF; + sound.reset(); + } + + virtual void save_state( mapper_state_t& out ) + { + sound.save_state( &sound_state ); + fme7_state_t::swap(); + Nes_Mapper::save_state( out ); + fme7_state_t::swap(); // to do: kind of hacky to swap in place + } + + virtual void read_state( mapper_state_t const& in ) + { + Nes_Mapper::read_state( in ); + fme7_state_t::swap(); + sound.load_state( sound_state ); + } + + virtual void apply_mapping() + { + last_time = 0; + for ( int i = 0; i < (int) sizeof regs; i++ ) + write_register( i, regs [i] ); + } + + virtual void run_until( nes_time_t end_time ) + { + int new_count = irq_count - (end_time - last_time); + last_time = end_time; + + if ( new_count <= 0 && (irq_mode & 0x81) == 0x81 ) + irq_pending = true; + + if ( irq_mode & 0x01 ) + irq_count = new_count & 0xFFFF; + } + + virtual nes_time_t next_irq( nes_time_t ) + { + if ( irq_pending ) + return 0; + + if ( (irq_mode & 0x81) == 0x81 ) + return last_time + irq_count + 1; + + return no_irq; + } + + virtual void end_frame( nes_time_t end_time ) + { + if ( end_time > last_time ) + run_until( end_time ); + + last_time -= end_time; + + sound.end_frame( end_time ); + } + + virtual void write( nes_time_t time, nes_addr_t addr, int data ) + { + switch ( addr & 0xE000 ) + { + case 0x8000: + command = data & 0x0F; + break; + + case 0xA000: + if ( command < 0x0D ) + write_register( command, data ); + else + write_irq( time, command, data ); + break; + + case 0xC000: + sound.write_latch( data ); + break; + + case 0xE000: + sound.write_data( time, data ); + break; + } + } + + + void write_irq( nes_time_t time, int index, int data ) + { + run_until( time ); + switch ( index ) + { + case 0x0D: + irq_mode = data; + irq_pending = false; + irq_changed(); + break; + + case 0x0E: + irq_count = (irq_count & 0xFF00) | data; + break; + + case 0x0F: + irq_count = data << 8 | (irq_count & 0xFF); + break; + } + } + + void write_register( int index, int data ) + { + regs [index] = data; + int prg_bank = index - 0x09; + if ( (unsigned) prg_bank < 3 ) // most common + { + set_prg_bank( 0x8000 | (prg_bank << bank_8k), bank_8k, data ); + } + else if ( index == 0x08 ) + { + enable_sram( (data & 0xC0) == 0xC0 ); + if ( !(data & 0xC0) ) + set_prg_bank( 0x6000, bank_8k, data & 0x3F ); + } + else if ( index < 0x08 ) + { + set_chr_bank( index * 0x400, bank_1k, data ); + } + else + { + if ( data & 2 ) + mirror_single( data & 1 ); + else if ( data & 1 ) + mirror_horiz(); + else + mirror_vert(); + } + } + + nes_time_t last_time; + Nes_Fme7_Apu sound; +}; + + diff --git a/core/mappers/mapper070.hpp b/core/mappers/mapper070.hpp new file mode 100644 index 0000000..65f5eba --- /dev/null +++ b/core/mappers/mapper070.hpp @@ -0,0 +1,81 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * Mapper 180 Crazy Climber + * + */ + +#pragma once + +#include "Nes_Mapper.h" + +// Mapper_74x161x162x32 + +template < int mapperId > +class Mapper_74x161x162x32 : public Nes_Mapper { +public: + Mapper_74x161x162x32() + { + register_state( &bank, 1 ); + } + + virtual void reset_state() + { + if ( mapperId == 86 ) + bank = ~0; + } + + virtual void apply_mapping() + { + if ( mapperId == 152 ) write( 0, 0, bank ); + if ( mapperId == 70 ) write( 0, 0, bank ); + if ( mapperId == 86 ) + { + intercept_writes( 0x6000, 1 ); + write_intercepted( 0, 0x6000, bank ); + } + } + + virtual bool write_intercepted( nes_time_t, nes_addr_t addr, int data ) + { + if ( addr != 0x6000 ) return false; + if ( mapperId == 152 ) return false; + if ( mapperId == 70 ) return false; + + bank = data; + set_prg_bank( 0x8000, bank_32k, ( bank >> 4 ) & 0x03 ); + set_chr_bank( 0x0000, bank_8k, ( ( bank >> 4 ) & 0x04 ) | ( bank & 0x03 ) ); + + return true; + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + if ( mapperId == 86) return; + + bank = handle_bus_conflict (addr, data ); + set_prg_bank( 0x8000, bank_16k, ( bank >> 4 ) & 0x07 ); + set_chr_bank( 0x0000, bank_8k, bank & 0x0F ); + mirror_single( ( bank >> 7) & 0x01 ); + } + + uint8_t bank; +}; + +typedef Mapper_74x161x162x32<70> Mapper070; \ No newline at end of file diff --git a/core/mappers/mapper071.hpp b/core/mappers/mapper071.hpp new file mode 100644 index 0000000..17b09de --- /dev/null +++ b/core/mappers/mapper071.hpp @@ -0,0 +1,56 @@ +// Optional less-common simple mappers + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#pragma once + +#include "Nes_Mapper.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// Camerica + +class Mapper071 : public Nes_Mapper { + uint8_t regs [3]; +public: + Mapper071() + { + register_state( regs, sizeof regs ); + } + + virtual void apply_mapping() + { + write( 0, 0xc000, regs [0] ); + if ( regs [1] & 0x80 ) + write( 0, 0x9000, regs [1] ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + if ( addr >= 0xc000 ) + { + regs [0] = data; + set_prg_bank( 0x8000, bank_16k, data ); + } + else if ( (addr & 0xf000) == 0x9000 ) + { + regs [1] = 0x80 | data; + mirror_single( (data >> 4) & 1 ); + } + } +}; + + + + diff --git a/core/mappers/mapper073.hpp b/core/mappers/mapper073.hpp new file mode 100644 index 0000000..9a7ec0a --- /dev/null +++ b/core/mappers/mapper073.hpp @@ -0,0 +1,137 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * 3/24/18 + * + * VRC-3 Konami, Salamander + */ + +#pragma once + +#include "Nes_Mapper.h" + +struct vrc3_state_t +{ + bool irq_enable; + bool irq_awk; + uint16_t irq_latch; + uint16_t irq_counter; + + uint8_t irq_pending; + uint16_t next_time; +}; + +// VRC3 + +class Mapper073 : public Nes_Mapper, vrc3_state_t { +public: + Mapper073() + { + vrc3_state_t * state = this; + register_state( state, sizeof * state ); + } + + void reset_state() + { + } + + void apply_mapping() + { + enable_sram(); + mirror_vert(); + } + + virtual void run_until( nes_time_t end_time ) + { + if ( irq_enable ) + { + long counter = irq_counter + ( end_time - next_time ); + + if ( counter > 0xFFFF ) + { + irq_pending = true; + irq_enable = irq_awk; + irq_counter = irq_latch; + } + else + irq_counter = counter; + } + + next_time = end_time; + } + + virtual void end_frame( nes_time_t end_time ) + { + if ( end_time > next_time ) + run_until( end_time ); + + next_time -= end_time; + } + + virtual nes_time_t next_irq( nes_time_t present ) + { + if ( irq_pending ) + return present; + + if ( !irq_enable ) + return no_irq; + + return 0x10000 - irq_counter + next_time; + } + + void write_irq_counter( int shift, int data ) + { + irq_latch &= ~( 0xF << shift ); + irq_latch |= data << shift; + } + + void write( nes_time_t time, nes_addr_t addr, int data ) + { + data &= 0xF; + + switch ( addr >> 12 ) + { + case 0xF: set_prg_bank( 0x8000, bank_16k, data ); break; + case 0x8: write_irq_counter( 0, data ); break; + case 0x9: write_irq_counter( 4, data ); break; + case 0xA: write_irq_counter( 8, data ); break; + case 0xB: write_irq_counter( 12, data ); break; + case 0xC: + irq_pending = false; + irq_awk = data & 1; + irq_enable = data & 2; + + if ( irq_enable ) + irq_counter = irq_latch; + + break; + case 0xD: + irq_pending = false; + irq_enable = irq_awk; + break; + } + + irq_changed(); + } +}; + +// void register_vrc3_mapper(); +// void register_vrc3_mapper() +// { +// register_mapper< Mapper073> ( 73 ); +// } diff --git a/core/mappers/mapper075.hpp b/core/mappers/mapper075.hpp new file mode 100644 index 0000000..05db0a1 --- /dev/null +++ b/core/mappers/mapper075.hpp @@ -0,0 +1,110 @@ +/* Copyright (C) 2018. + * This module 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 + * module 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 module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * VRC-1 Konami + */ + +#pragma once + +#include "Nes_Mapper.h" + +struct vrc1_state_t +{ + uint8_t prg_banks [ 3 ]; + uint8_t chr_banks [ 2 ]; + uint8_t chr_banks_hi [ 2 ]; + uint8_t mirroring; +}; + +BOOST_STATIC_ASSERT( sizeof ( vrc1_state_t ) == 8 ); + +// VRC1 + +class Mapper075 : public Nes_Mapper, vrc1_state_t { +public: + Mapper075() + { + vrc1_state_t * state = this; + register_state( state, sizeof * state ); + } + + void reset_state() + { + } + + void apply_mapping() + { + update_prg_banks(); + update_chr_banks(); + update_mirroring(); + } + + void write( nes_time_t, nes_addr_t addr, int data ) + { + switch ( addr & 0xF000 ) + { + case 0x8000: + prg_banks [ 0 ] = data & 0xF; + update_prg_banks(); + break; + case 0x9000: + mirroring = data & 1; + chr_banks_hi [ 0 ] = ( data & 2 ) << 3; + chr_banks_hi [ 1 ] = ( data & 4 ) << 2; + update_chr_banks(); + update_mirroring(); + break; + case 0xa000: + prg_banks [ 1 ] = data & 0xF; + update_prg_banks(); + break; + case 0xc000: + prg_banks [ 2 ] = data & 0xF; + update_prg_banks(); + break; + case 0xe000: + chr_banks [ 0 ] = data & 0xF; + update_chr_banks(); + break; + case 0xf000: + chr_banks [ 1 ] = data & 0xF; + update_chr_banks(); + break; + } + } + + void update_prg_banks() + { + set_prg_bank( 0x8000, bank_8k, prg_banks [ 0 ] ); + set_prg_bank( 0xa000, bank_8k, prg_banks [ 1 ] ); + set_prg_bank( 0xc000, bank_8k, prg_banks [ 2 ] ); + } + + void update_chr_banks() + { + set_chr_bank( 0x0000, bank_4k, chr_banks [ 0 ] | chr_banks_hi [ 0 ] ); + set_chr_bank( 0x1000, bank_4k, chr_banks [ 1 ] | chr_banks_hi [ 1 ] ); + } + + void update_mirroring() + { + switch ( mirroring & 1 ) + { + case 1: mirror_horiz(); break; + case 0: mirror_vert(); break; + } + } +}; + + diff --git a/core/mappers/mapper078.hpp b/core/mappers/mapper078.hpp new file mode 100644 index 0000000..9ba71dd --- /dev/null +++ b/core/mappers/mapper078.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include "Nes_Mapper.h" + +// Holy Diver and Uchuusen - Cosmo Carrier. + +class Mapper078 : public Nes_Mapper { + // lower 8 bits are the reg at 8000:ffff + // next two bits are autodetecting type + // 0 = unknown 1 = cosmo carrier 2 = holy diver + int reg; + void writeinternal(int data, int changed) + { + reg &= 0x300; + reg |= data; + + if (changed & 0x07) + set_prg_bank(0x8000, bank_16k, reg & 0x07); + if (changed & 0xf0) + set_chr_bank(0x0000, bank_8k, (reg >> 4) & 0x0f); + if (changed & 0x08) + { + // set mirroring based on memorized board type + if (reg & 0x100) + { + mirror_single((reg >> 3) & 1); + } + else if (reg & 0x200) + { + if (reg & 0x08) + mirror_vert(); + else + mirror_horiz(); + } + else + { + // if you don't set something here, holy diver dumps with 4sc set will + // savestate as 4k NTRAM. then when you later set H\V mapping, state size mismatch. + mirror_single(1); + } + } + } + +public: + Mapper078() + { + register_state(®, 4); + } + + virtual void reset_state() + { + reg = 0; + } + + virtual void apply_mapping() + { + writeinternal(reg, 0xff); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data) + { + // heuristic: if the first write ever to the register is 0, + // we're on holy diver, otherwise, carrier. it works for these two games... + if (!(reg & 0x300)) + { + reg |= data ? 0x100 : 0x200; + writeinternal(data, 0xff); + } + else + { + writeinternal(data, reg ^ data); + } + } +}; diff --git a/core/mappers/mapper079.hpp b/core/mappers/mapper079.hpp new file mode 100644 index 0000000..1996f44 --- /dev/null +++ b/core/mappers/mapper079.hpp @@ -0,0 +1,90 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * Mapper 079 + * Mapper 113 + * Nina-03 / Nina-06 + */ + +#include "Nes_Mapper.h" + +#pragma once + +template < bool multicart > +class Mapper_AveNina : public Nes_Mapper { +public: + Mapper_AveNina() + { + register_state( ®s, 1 ); + } + + void write_regs(); + + virtual void reset_state() + { + intercept_writes( 0x4000, 0x1000 ); + intercept_writes( 0x5000, 0x1000 ); + } + + virtual void apply_mapping() + { + write_intercepted( 0, 0x4100, regs ); + } + + virtual bool write_intercepted( nes_time_t, nes_addr_t addr , int data ) + { + if ( addr < 0x4100 || addr > 0x5FFF ) + return false; + + if ( addr & 0x100 ) + regs = data; + + write_regs(); + return true; + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + if ( multicart == 0 && + ( ( addr == 0x8000 ) || ( addr & 0xFCB0 ) == 0xFCB0 ) ) + set_chr_bank( 0, bank_8k, data & 0x07 ); + } + + uint8_t regs; +}; + +template < bool multicart > +void Mapper_AveNina< multicart >::write_regs() +{ + if ( multicart == 0 ) + { + set_prg_bank ( 0x8000, bank_32k, ( regs >> 3 ) & 0x01 ); + set_chr_bank ( 0, bank_8k, regs & 0x07 ); + } + else + { + set_prg_bank ( 0x8000, bank_32k, ( regs >> 3 ) & 0x07 ); + set_chr_bank ( 0x0000, bank_8k, ( ( regs >> 3 ) & 0x08 ) | ( regs & 0x07 ) ); + if ( regs & 0x80 ) mirror_vert(); + else mirror_horiz(); + } +} + +typedef Mapper_AveNina Mapper079; diff --git a/core/mappers/mapper085.hpp b/core/mappers/mapper085.hpp new file mode 100644 index 0000000..392fb55 --- /dev/null +++ b/core/mappers/mapper085.hpp @@ -0,0 +1,224 @@ +// Nes_Emu 0.5.4. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" +#include "Nes_Vrc7.h" +#include "blargg_endian.h" +#include + +#pragma once + +/* Copyright (C) 2004-2005 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +struct vrc7_state_t +{ + // written registers + uint8_t mirroring; + uint8_t prg_banks [3]; + uint8_t chr_banks [8]; + uint8_t irq_reload; + uint8_t irq_mode; + + // internal state + uint16_t next_time; + uint8_t irq_pending; + uint8_t unused; + + vrc7_snapshot_t sound_state; +}; +BOOST_STATIC_ASSERT( sizeof (vrc7_state_t) == 20 + sizeof (vrc7_snapshot_t) ); + +// Vrc7 + +class Mapper085 : public Nes_Mapper, vrc7_state_t { +public: + Mapper085() + { + vrc7_state_t* state = this; + register_state( state, sizeof *state ); + } + + virtual int channel_count() const { return sound.osc_count; } + + virtual void set_channel_buf( int i, Blip_Buffer* b ) { sound.osc_output( i, b ); } + + virtual void set_treble( blip_eq_t const& eq ) { sound.treble_eq( eq ); } + + virtual void save_state( mapper_state_t & out ) + { + sound.save_snapshot( &sound_state ); + + set_le16( &next_time, next_time ); + + Nes_Mapper::save_state( out ); + } + + virtual void load_state( mapper_state_t const& in ) + { + Nes_Mapper::load_state( in ); + + next_time = get_le16( &next_time ); + + sound.load_snapshot( sound_state, in.size ); + } + + virtual void reset_state() + { + mirroring = 0; + + memset( prg_banks, 0, sizeof prg_banks ); + memset( chr_banks, 0, sizeof chr_banks ); + memset( &sound_state, 0, sizeof sound_state ); + + irq_reload = 0; + irq_mode = 0; + irq_pending = false; + + next_time = 0; + sound.reset(); + + set_prg_bank( 0xE000, bank_8k, last_bank ); + apply_mapping(); + } + + void write_prg_bank( int bank, int data ) + { + prg_banks [bank] = data; + set_prg_bank( 0x8000 | ( bank << bank_8k ), bank_8k, data ); + } + + void write_chr_bank( int bank, int data ) + { + //dprintf( "change chr bank %d\n", bank ); + chr_banks [bank] = data; + set_chr_bank( bank * 0x400, bank_1k, data ); + } + + void write_mirroring( int data ) + { + mirroring = data; + + //dprintf( "Change mirroring %d\n", data ); + enable_sram( data & 128, data & 64 ); + + if ( data & 2 ) + mirror_single( data & 1 ); + else if ( data & 1 ) + mirror_horiz(); + else + mirror_vert(); + } + + void apply_mapping() + { + size_t i; + + for ( i = 0; i < sizeof prg_banks; i++ ) + write_prg_bank( i, prg_banks [i] ); + + for ( i = 0; i < sizeof chr_banks; i++ ) + write_chr_bank( i, chr_banks [i] ); + + write_mirroring( mirroring ); + } + + void reset_timer( nes_time_t present ) + { + next_time = present + unsigned ((0x100 - irq_reload) * timer_period) / 4; + } + + virtual void run_until( nes_time_t end_time ) + { + if ( irq_mode & 2 ) + { + while ( next_time < end_time ) + { + //dprintf( "%d timer expired\n", next_time ); + irq_pending = true; + reset_timer( next_time ); + } + } + } + + virtual void end_frame( nes_time_t end_time ) + { + run_until( end_time ); + + next_time -= end_time; + + sound.end_frame( end_time ); + } + + virtual nes_time_t next_irq( nes_time_t present ) + { + if ( irq_pending ) + return present; + + if ( irq_mode & 2 ) + return next_time + 1; + + return no_irq; + } + + virtual void write( nes_time_t time, nes_addr_t addr, int data ) + { + addr |= ( addr & 8 ) << 1; + + if ( addr >= 0xe010 ) + { + // IRQ + run_until( time ); + //dprintf( "%d VRC6 IRQ [%d] = %02X\n", time, addr & 3, data ); + switch ( addr & 0xf010 ) + { + case 0xe010: + irq_reload = data; + break; + + case 0xf000: + irq_pending = false; + irq_mode = data; + if ( data & 2 ) + reset_timer( time ); + break; + + case 0xf010: + irq_pending = false; + irq_mode = (irq_mode & ~2) | ((irq_mode << 1) & 2); + break; + } + irq_changed(); + } + else if ( ( unsigned ) ( addr - 0xa000 ) < 0x4000 ) + { + write_chr_bank( ((addr >> 4) & 1) | (((addr - 0xa000) >> 11)&~1), data ); + } + else switch ( addr & 0xf010 ) + { + case 0x8000: write_prg_bank( 0, data ); break; + case 0x8010: write_prg_bank( 1, data ); break; + case 0x9000: write_prg_bank( 2, data ); break; + + case 0xe000: + write_mirroring( data ); + break; + + case 0x9010: + if ( addr & 0x20 ) sound.write_data( time, data ); + else sound.write_reg( data ); + break; + } + } + + Nes_Vrc7 sound; + enum { timer_period = 113 * 4 + 3 }; +}; + diff --git a/core/mappers/mapper086.hpp b/core/mappers/mapper086.hpp new file mode 100644 index 0000000..f9e14ed --- /dev/null +++ b/core/mappers/mapper086.hpp @@ -0,0 +1,24 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + */ + +#pragma once + +typedef Mapper_74x161x162x32<86> Mapper086; \ No newline at end of file diff --git a/core/mappers/mapper087.hpp b/core/mappers/mapper087.hpp new file mode 100644 index 0000000..314580e --- /dev/null +++ b/core/mappers/mapper087.hpp @@ -0,0 +1,51 @@ + +// Optional less-common simple mappers + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#pragma once + +#include "Nes_Mapper.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// Jaleco/Konami/Taito + +class Mapper087 : public Nes_Mapper { + uint8_t bank; +public: + Mapper087() + { + register_state( &bank, 1 ); + } + + void apply_mapping() + { + intercept_writes( 0x6000, 1 ); + write( 0, 0x6000, bank ); + } + + bool write_intercepted( nes_time_t, nes_addr_t addr, int data ) + { + if ( addr != 0x6000 ) + return false; + + bank = data; + set_chr_bank( 0, bank_8k, data >> 1 ); + return true; + } + + void write( nes_time_t, nes_addr_t, int ) { } +}; + diff --git a/core/mappers/mapper088.hpp b/core/mappers/mapper088.hpp new file mode 100644 index 0000000..cfdefcb --- /dev/null +++ b/core/mappers/mapper088.hpp @@ -0,0 +1,112 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * 3/24/18 + * + * Mapper 88 + * Mapper 154 + * Mapper 206 + */ + +#pragma once + +#include "Nes_Mapper.h" + +struct namco_34x3_state_t +{ + uint8_t bank [ 8 ]; + uint8_t mirr; + uint8_t mode; +}; + +BOOST_STATIC_ASSERT( sizeof (namco_34x3_state_t) == 10 ); + +template < bool _is154 > +class Mapper_Namco_34x3 : public Nes_Mapper, namco_34x3_state_t { +public: + Mapper_Namco_34x3() + { + namco_34x3_state_t *state = this; + register_state( state, sizeof *state ); + } + + virtual void reset_state() + { } + + virtual void apply_mapping() + { + set_chr_bank( 0x0000, bank_2k, bank [ 0 ] ); + set_chr_bank( 0x0800, bank_2k, bank [ 1 ] ); + for ( int i = 0; i < 4; i++ ) + set_chr_bank( 0x1000 + ( i << 10 ), bank_1k, bank [ i + 2 ] ); + + set_prg_bank( 0x8000, bank_8k, bank [ 6 ] ); + set_prg_bank( 0xA000, bank_8k, bank [ 7 ] ); + set_prg_bank( 0xC000, bank_8k, ~1 ); + set_prg_bank( 0xE000, bank_8k, ~0 ); + + if ( _is154 ) + mirror_single( mirr ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + switch ( addr & 0xE001 ) + { + case 0x8000: + mode = data; + mirr = ( data >> 6 ) & 0x01; + if ( _is154 ) + mirror_single( mirr ); + break; + case 0x8001: + mode &= 0x07; + switch ( mode ) + { + case 0: case 1: + bank [ mode ] = data >> 1; + set_chr_bank( 0x0000 + ( mode << 11 ), bank_2k, bank [ mode ] ); + break; + case 2: case 3: case 4: case 5: + bank [ mode ] = data | 0x40; + set_chr_bank( 0x1000 + ( ( mode - 2 ) << 10 ), bank_1k, bank [ mode ] ); + break; + case 6: case 7: + bank [ mode ] = data; + set_prg_bank( 0x8000 + ( ( mode - 6 ) << 13 ), bank_8k, bank [ mode ] ); + break; + } + break; + case 0xC000: + mirr = ( data >> 6 ) & 0x01; + if ( _is154 ) + mirror_single( mirr ); + } + } +}; + +typedef Mapper_Namco_34x3 Mapper088; + + +// void register_mapper_namco_34xx(); +// void register_mapper_namco_34xx() +// { +// register_mapper< Mapper_Namco_34x3 > ( 88 ); +// register_mapper< Mapper_Namco_34x3 > ( 154 ); +// register_mapper< Mapper_Namco_34xx > ( 206 ); +// } diff --git a/core/mappers/mapper089.hpp b/core/mappers/mapper089.hpp new file mode 100644 index 0000000..fb24c07 --- /dev/null +++ b/core/mappers/mapper089.hpp @@ -0,0 +1,55 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * Mapper 93 - Sunsoft-2 + */ + +#pragma once + +#include "Nes_Mapper.h" + +// Sunsoft2b + +class Mapper089 : public Nes_Mapper { +public: + Mapper089() + { + register_state( ®s, 1 ); + } + + virtual void reset_state() + {} + + virtual void apply_mapping() + { + set_prg_bank( 0xC000, bank_16k, last_bank ); + write( 0, 0x8000, regs ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + regs = handle_bus_conflict( addr, data ); + + set_chr_bank( 0x0000, bank_8k, ( ( data >> 4 ) & 0x08 ) | ( data & 0x07 ) ); + set_prg_bank( 0x8000, bank_16k, ( data >> 4 ) & 0x07 ); + mirror_single( ( data >> 3 ) & 1 ); + } + + uint8_t regs; +}; diff --git a/core/mappers/mapper093.hpp b/core/mappers/mapper093.hpp new file mode 100644 index 0000000..7c5efbc --- /dev/null +++ b/core/mappers/mapper093.hpp @@ -0,0 +1,54 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * Mapper 93 - Sunsoft-2 + */ + +#pragma once + +#include "Nes_Mapper.h" + +// Sunsoft2a + +class Mapper093 : public Nes_Mapper { +public: + Mapper093() + { + register_state( ®s, 1 ); + } + + virtual void reset_state() + {} + + virtual void apply_mapping() + { + set_prg_bank( 0xC000, bank_16k, last_bank ); + write( 0, 0x8000, regs ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + regs = handle_bus_conflict( addr, data ); + + set_chr_bank( 0x0000, bank_8k, data & 0x0F ); + set_prg_bank( 0x8000, bank_16k, ( data >> 4 ) & 0x07 ); + } + + uint8_t regs; +}; diff --git a/core/mappers/mapper094.hpp b/core/mappers/mapper094.hpp new file mode 100644 index 0000000..f68d328 --- /dev/null +++ b/core/mappers/mapper094.hpp @@ -0,0 +1,53 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * Mapper 94 - HVC-UN1ROM + * Senjou no Ookami (Japanese version of Commando) + * + */ + +#pragma once + +#include "Nes_Mapper.h" + +// Un1rom + +class Mapper094 : public Nes_Mapper { +public: + Mapper094() + { + register_state( &bank, 1 ); + } + + virtual void reset_state() + { } + + virtual void apply_mapping() + { + write( 0, 0, bank ); + } + + virtual void write( nes_time_t, nes_addr_t, int data ) + { + bank = data; + set_prg_bank( 0x8000, bank_16k, bank >> 2 ); + } + + uint8_t bank; +}; diff --git a/core/mappers/mapper097.hpp b/core/mappers/mapper097.hpp new file mode 100644 index 0000000..aa7afa1 --- /dev/null +++ b/core/mappers/mapper097.hpp @@ -0,0 +1,63 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * Mapper 97 - Kaiketsu Yanchamaru + * + */ + +#pragma once + +#include "Nes_Mapper.h" + +// Irem_Tam_S1 + +class Mapper097 : public Nes_Mapper { +public: + Mapper097() + { + register_state( &bank, 1 ); + } + + virtual void reset_state() + { + bank = ~0; + } + + virtual void apply_mapping() + { + write( 0, 0, bank ); + } + + virtual void write( nes_time_t, nes_addr_t, int data ) + { + bank = data; + set_prg_bank( 0x8000, bank_16k, ~0 ); + set_prg_bank( 0xC000, bank_16k, bank & 0x0F ); + + switch ( ( bank >> 6 ) & 0x03 ) + { + case 1: mirror_horiz(); break; + case 2: mirror_vert(); break; + case 0: + case 3: mirror_single( bank & 0x01 ); break; + } + } + + uint8_t bank; +}; diff --git a/core/mappers/mapper113.hpp b/core/mappers/mapper113.hpp new file mode 100644 index 0000000..efd7c85 --- /dev/null +++ b/core/mappers/mapper113.hpp @@ -0,0 +1,28 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * Mapper 079 + * Mapper 113 + * Nina-03 / Nina-06 + */ + +#pragma once + +typedef Mapper_AveNina Mapper113; \ No newline at end of file diff --git a/core/mappers/mapper140.hpp b/core/mappers/mapper140.hpp new file mode 100644 index 0000000..8460695 --- /dev/null +++ b/core/mappers/mapper140.hpp @@ -0,0 +1,63 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * Mapper 140 - Jaleco's JF-11 and JF-14 + * + */ + +#pragma once + +#include "Nes_Mapper.h" + +// Jaleco_JF11 + +class Mapper140 : public Nes_Mapper { +public: + Mapper140() + { + register_state( ®s, 1 ); + } + + virtual void reset_state() + { + intercept_writes( 0x6000, 1 ); + } + + virtual void apply_mapping() + { + write_intercepted(0, 0x6000, regs ); + } + + bool write_intercepted( nes_time_t time, nes_addr_t addr, int data ) + { + if ( addr < 0x6000 || addr > 0x7FFF ) + return false; + + regs = data; + set_prg_bank( 0x8000, bank_32k, data >> 4); + set_chr_bank( 0, bank_8k, data ); + + return true; + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) { } + + uint8_t regs; +}; diff --git a/core/mappers/mapper152.hpp b/core/mappers/mapper152.hpp new file mode 100644 index 0000000..f13b155 --- /dev/null +++ b/core/mappers/mapper152.hpp @@ -0,0 +1,24 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + */ + +#pragma once + +typedef Mapper_74x161x162x32<152> Mapper152; \ No newline at end of file diff --git a/core/mappers/mapper154.hpp b/core/mappers/mapper154.hpp new file mode 100644 index 0000000..9b1f0f6 --- /dev/null +++ b/core/mappers/mapper154.hpp @@ -0,0 +1,28 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * 3/24/18 + * + * Mapper 88 + * Mapper 154 + * Mapper 206 + */ + +#pragma once + +typedef Mapper_Namco_34x3 Mapper154; \ No newline at end of file diff --git a/core/mappers/mapper156.hpp b/core/mappers/mapper156.hpp new file mode 100644 index 0000000..f9cd005 --- /dev/null +++ b/core/mappers/mapper156.hpp @@ -0,0 +1,59 @@ +#include "Nes_Mapper.h" + +#pragma once + +// DIS23C01 DAOU ROM CONTROLLER + +struct m156_state_t +{ + uint8_t prg_bank; + uint8_t chr_banks [8]; +}; +BOOST_STATIC_ASSERT( sizeof (m156_state_t) == 9 ); + +class Mapper156 : public Nes_Mapper, m156_state_t { +public: + Mapper156() + { + m156_state_t * state = this; + register_state( state, sizeof * state ); + } + + void reset_state() + { + prg_bank = 0; + for ( unsigned i = 0; i < 8; i++ ) chr_banks [i] = i; + enable_sram(); + apply_mapping(); + } + + void apply_mapping() + { + mirror_single( 0 ); + set_prg_bank( 0x8000, bank_16k, prg_bank ); + + for ( int i = 0; i < (int) sizeof chr_banks; i++ ) + set_chr_bank( i * 0x400, bank_1k, chr_banks [i] ); + } + + void write( nes_time_t, nes_addr_t addr, int data ) + { + unsigned int reg = addr - 0xC000; + if ( addr == 0xC010 ) + { + prg_bank = data; + set_prg_bank( 0x8000, bank_16k, data ); + } + else if ( reg < 4 ) + { + chr_banks [reg] = data; + set_chr_bank( reg * 0x400, bank_1k, data ); + } + else if ( ( reg - 8 ) < 4 ) + { + reg -= 4; + chr_banks [reg] = data; + set_chr_bank( reg * 0x400, bank_1k, data ); + } + } +}; diff --git a/core/mappers/mapper180.hpp b/core/mappers/mapper180.hpp new file mode 100644 index 0000000..e5ef616 --- /dev/null +++ b/core/mappers/mapper180.hpp @@ -0,0 +1,53 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * Mapper 180 Crazy Climber + * + */ + +#pragma once + +#include "Nes_Mapper.h" + +// UxROM (inverted) + +class Mapper180 : public Nes_Mapper { +public: + Mapper180() + { + register_state( &bank, 1 ); + } + + virtual void reset_state() + { } + + virtual void apply_mapping() + { + set_prg_bank( 0x8000, bank_16k, 0 ); + write( 0, 0, bank ); + } + + virtual void write( nes_time_t, nes_addr_t, int data ) + { + bank = data; + set_prg_bank( 0xC000, bank_16k, data ); + } + + uint8_t bank; +}; diff --git a/core/mappers/mapper184.hpp b/core/mappers/mapper184.hpp new file mode 100644 index 0000000..eae52ad --- /dev/null +++ b/core/mappers/mapper184.hpp @@ -0,0 +1,63 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * Mapper 184 - Sunsoft-1 + */ + +#pragma once + +#include "Nes_Mapper.h" + +// Sunsoft1 + +class Mapper184 : public Nes_Mapper { +public: + Mapper184() + { + register_state( ®s, 1 ); + } + + virtual void reset_state() + {} + + virtual void apply_mapping() + { + set_prg_bank( 0x8000, bank_32k, 0 ); + intercept_writes( 0x6000, 1 ); + write_intercepted( 0, 0x6000, regs ); + } + + virtual bool write_intercepted( nes_time_t, nes_addr_t addr, int data ) + { + if ( addr != 0x6000 ) + return false; + + regs = data; + set_chr_bank( 0x0000, bank_4k, data & 0x07 ); + set_chr_bank( 0x1000, bank_4k, ( data >> 4 ) & 0x07 ); + + return true; + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + {} + + uint8_t regs; +}; + diff --git a/core/mappers/mapper190.hpp b/core/mappers/mapper190.hpp new file mode 100644 index 0000000..9d9a58a --- /dev/null +++ b/core/mappers/mapper190.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "Nes_Mapper.h" + +// Magic Kid Googoo + +class Mapper190: public Nes_Mapper { +public: + Mapper190() + { + } + + virtual void reset_state() + { + } + + virtual void apply_mapping() + { + mirror_vert(); + enable_sram(); + set_prg_bank( 0xc000, bank_16k, 0); + } + + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + switch ( addr >> 12 ) + { + case 0x8: + case 0x9: + case 0xc: + case 0xd: + set_prg_bank( 0x8000, bank_16k, ( ( ( addr >> 11 ) & 8 ) | ( data & 7 ) ) ); + break; + case 0xa: + case 0xb: + switch ( addr & 3 ) + { + case 0: + set_chr_bank( 0x0000, bank_2k, data ); + break; + case 1: + set_chr_bank( 0x0800, bank_2k, data ); + break; + case 2: + set_chr_bank( 0x1000, bank_2k, data ); + break; + case 3: + set_chr_bank( 0x1800, bank_2k, data ); + break; + } + break; + } + } +}; + diff --git a/core/mappers/mapper193.hpp b/core/mappers/mapper193.hpp new file mode 100644 index 0000000..1a173f6 --- /dev/null +++ b/core/mappers/mapper193.hpp @@ -0,0 +1,74 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * Mapper 193 - NTDEC TC-112 + * Fighting Hero (Unl) + * War in the Gulf & (its Brazilian localization) + * + */ + +#pragma once + +#include "Nes_Mapper.h" + +// NTDEC's TC-112 mapper IC. + +class Mapper193 : public Nes_Mapper { +public: + Mapper193() + { + register_state( regs, sizeof regs ); + } + + virtual void reset_state() + { } + + virtual void apply_mapping() + { + for ( size_t i = 0; i < sizeof regs; i++ ) + write_intercepted( 0, 0x6000 + i, regs [ i ] ); + set_prg_bank( 0xA000, bank_8k, ~2 ); + set_prg_bank( 0xC000, bank_8k, ~1 ); + set_prg_bank( 0xE000, bank_8k, ~0 ); + intercept_writes( 0x6000, 0x03 ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { } + + virtual bool write_intercepted( nes_time_t, nes_addr_t addr, int data ) + { + if ( addr < 0x6000 || addr > 0x6003 ) + return false; + + regs [ addr & 0x03 ] = data; + switch ( addr & 0x03 ) + { + case 0: set_chr_bank( 0x0000, bank_4k, regs [ 0 ] >> 2 ); break; + case 1: set_chr_bank( 0x1000, bank_2k, regs [ 1 ] >> 1 ); break; + case 2: set_chr_bank( 0x1800, bank_2k, regs [ 2 ] >> 1 ); break; + case 3: set_prg_bank( 0x8000, bank_8k, regs [ 3 ] ); break; + } + + return true; + } + + uint8_t regs [ 4 ]; +}; + diff --git a/core/mappers/mapper206.hpp b/core/mappers/mapper206.hpp new file mode 100644 index 0000000..205985e --- /dev/null +++ b/core/mappers/mapper206.hpp @@ -0,0 +1,93 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * 3/24/18 + * + * Mapper 88 + * Mapper 154 + * Mapper 206 + */ + +#pragma once + +#include "Nes_Mapper.h" + +struct namco_34xx_state_t +{ + uint8_t bank [ 8 ]; + uint8_t mirr; + uint8_t mode; +}; + +BOOST_STATIC_ASSERT( sizeof (namco_34xx_state_t) == 10 ); + +// Namco_34xx + +class Mapper206 : public Nes_Mapper, namco_34xx_state_t { +public: + Mapper206() + { + namco_34xx_state_t *state = this; + register_state( state, sizeof *state ); + } + + virtual void reset_state() + { } + + virtual void apply_mapping() + { + set_chr_bank( 0x0000, bank_2k, bank [ 0 ] ); + set_chr_bank( 0x0800, bank_2k, bank [ 1 ] ); + for ( int i = 0; i < 4; i++ ) + set_chr_bank( 0x1000 + ( i << 10 ), bank_1k, bank [ i + 2 ] ); + + set_prg_bank( 0x8000, bank_8k, bank [ 6 ] ); + set_prg_bank( 0xA000, bank_8k, bank [ 7 ] ); + set_prg_bank( 0xC000, bank_8k, ~1 ); + set_prg_bank( 0xE000, bank_8k, ~0 ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + switch ( addr & 0xE001 ) + { + case 0x8000: + mode = data; + break; + case 0x8001: + mode &= 0x07; + switch ( mode ) + { + case 0: case 1: + bank [ mode ] = data >> 1; + set_chr_bank( 0x0000 + ( mode << 11 ), bank_2k, bank [ mode ] ); + break; + case 2: case 3: case 4: case 5: + bank [ mode ] = data; + set_chr_bank( 0x1000 + ( ( mode - 2 ) << 10 ), bank_1k, bank [ mode ] ); + break; + case 6: case 7: + bank [ mode ] = data; + set_prg_bank( 0x8000 + ( ( mode - 6 ) << 13 ), bank_8k, bank [ mode ] ); + break; + } + break; + } + } +}; + diff --git a/core/mappers/mapper207.hpp b/core/mappers/mapper207.hpp new file mode 100644 index 0000000..2536938 --- /dev/null +++ b/core/mappers/mapper207.hpp @@ -0,0 +1,91 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * Taito's X1-005 ( Rev. B ) Fudou Myouou Den + * + */ + +#pragma once + +#include "Nes_Mapper.h" + +struct taito_x1005_state_t +{ + uint8_t preg [ 3 ]; + uint8_t creg [ 6 ]; + uint8_t nametable [ 2 ]; +}; + +BOOST_STATIC_ASSERT( sizeof (taito_x1005_state_t) == 11 ); + +// TaitoX1005 + +class Mapper207 : public Nes_Mapper, taito_x1005_state_t { +public: + Mapper207() + { + taito_x1005_state_t *state = this; + register_state( state, sizeof *state ); + } + + virtual void reset_state() + { } + + virtual void apply_mapping() + { + int i; + intercept_writes( 0x7EF0, 1 ); + for ( i = 0; i < 3; i++ ) + set_prg_bank( 0x8000 + ( i << 13 ), bank_8k, preg [ i ] ); + for ( i = 0; i < 2; i++ ) + set_chr_bank( 0x0000 + ( i << 11 ), bank_2k, creg [ i ] >> 1); + for ( i = 0; i < 4; i++ ) + set_chr_bank( 0x1000 + ( i << 10 ), bank_1k, creg [ 2 + i ] ); + mirror_manual( nametable [ 0 ], nametable [ 0 ], nametable [ 1 ], nametable [ 1 ] ); + } + + virtual bool write_intercepted( nes_time_t, nes_addr_t addr, int data ) + { + if ( addr < 0x7EF0 || addr > 0x7EFF ) + return false; + + if ( ( addr & 0x0F ) < 6 ) + { + creg [ addr & 0x07 ] = data; + if ( ( addr & 0x0F ) < 2 ) + { + nametable [ addr & 0x01 ] = data >> 7; + mirror_manual( nametable [ 0 ], nametable [ 0 ], nametable [ 1 ], nametable [ 1 ] ); + set_chr_bank( ( addr << 11 ) & 0x800, bank_2k, creg [ addr & 0x01 ] >> 1 ); + return true; + } + + set_chr_bank( 0x1000 | ( ( addr - 0x7EF2 ) << 10 ), bank_1k, creg [ addr & 0x07 ] ); + return true; + } + + addr = ( addr - 0x7EFA ) >> 1; + preg [ addr ] = data; + set_prg_bank( 0x8000 | ( addr << 13 ), bank_8k, preg [ addr ] ); + return true; + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) { } +}; + diff --git a/core/mappers/mapper232.hpp b/core/mappers/mapper232.hpp new file mode 100644 index 0000000..d8e52e2 --- /dev/null +++ b/core/mappers/mapper232.hpp @@ -0,0 +1,55 @@ + +#pragma once + +// Optional less-common simple mappers + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// Quattro + +class Mapper232 : public Nes_Mapper { + uint8_t regs [2]; +public: + Mapper232() + { + register_state( regs, sizeof regs ); + } + + virtual void reset_state() + { + regs [0] = 0; + regs [1] = 3; + } + + virtual void apply_mapping() + { + int bank = regs [0] >> 1 & 0x0c; + set_prg_bank( 0x8000, bank_16k, bank + (regs [1] & 3) ); + set_prg_bank( 0xC000, bank_16k, bank + 3 ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + if ( addr < 0xc000 ) + regs [0] = data; + else + regs [1] = data; + Mapper232::apply_mapping(); + } +}; + diff --git a/core/mappers/mapper240.hpp b/core/mappers/mapper240.hpp new file mode 100644 index 0000000..b324a37 --- /dev/null +++ b/core/mappers/mapper240.hpp @@ -0,0 +1,62 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + */ + +#pragma once + +#include "Nes_Mapper.h" + +// https://www.nesdev.org/wiki/INES_Mapper240 + +class Mapper240 : public Nes_Mapper { +public: + Mapper240() + { + register_state( ®s, 1 ); + } + + virtual void reset_state() + { + } + + virtual void apply_mapping() + { + enable_sram(); + intercept_writes( 0x4020, 1 ); + write_intercepted( 0, 0x4120, regs ); + } + + virtual void write( nes_time_t, nes_addr_t, int data ) + { } + + virtual bool write_intercepted( nes_time_t, nes_addr_t addr, int data ) + { + if ( addr < 0x4020 || addr > 0x5FFF ) + return false; + + regs = data; + set_chr_bank( 0x0000, bank_8k, data & 0x0F ); + set_prg_bank( 0x8000, bank_32k, data >> 4 ); + + return true; + } + + uint8_t regs; +}; diff --git a/core/mappers/mapper241.hpp b/core/mappers/mapper241.hpp new file mode 100644 index 0000000..07859f4 --- /dev/null +++ b/core/mappers/mapper241.hpp @@ -0,0 +1,51 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + */ + +#pragma once + +#include "Nes_Mapper.h" + +// https://www.nesdev.org/wiki/INES_Mapper241 + +class Mapper241 : public Nes_Mapper { +public: + Mapper241() + { + register_state( &bank, 1 ); + } + + virtual void reset_state() + { } + + virtual void apply_mapping() + { + enable_sram(); + write( 0, 0, bank ); + } + + virtual void write( nes_time_t, nes_addr_t, int data ) + { + bank = data; + set_prg_bank( 0x8000, bank_32k, bank ); + } + + uint8_t bank; +}; diff --git a/core/mappers/mapper244.hpp b/core/mappers/mapper244.hpp new file mode 100644 index 0000000..2847208 --- /dev/null +++ b/core/mappers/mapper244.hpp @@ -0,0 +1,69 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * Mapper 244 - Decathlon + * + */ + +#pragma once + +#include "Nes_Mapper.h" + +// https://www.nesdev.org/wiki/INES_Mapper244 + +struct mapper244_state_t +{ + uint8_t preg; + uint8_t creg; +}; + +BOOST_STATIC_ASSERT( sizeof (mapper244_state_t) == 2 ); + +class Mapper244 : public Nes_Mapper, mapper244_state_t { +public: + Mapper244() + { + mapper244_state_t *state = this; + register_state( state, sizeof *state ); + } + + virtual void reset_state() + { } + + virtual void apply_mapping() + { + set_prg_bank( 0x8000, bank_32k, preg ); + set_chr_bank( 0x0000, bank_8k, creg ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + if ( addr >= 0x8065 && addr <= 0x80A4 ) + { + preg = ( addr - 0x8065 ) & 0x03; + set_prg_bank( 0x8000, bank_32k, preg ); + } + + if ( addr >= 0x80A5 && addr <= 0x80E4 ) + { + creg = (addr - 0x80A5 ) & 0x07; + set_chr_bank( 0x0000, bank_8k, creg ); + } + } +}; diff --git a/core/mappers/mapper246.hpp b/core/mappers/mapper246.hpp new file mode 100644 index 0000000..8db8557 --- /dev/null +++ b/core/mappers/mapper246.hpp @@ -0,0 +1,74 @@ +/* Copyright notice for this file: + * Copyright (C) 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This mapper was added by retrowertz for Libretro port of QuickNES. + * + * Mapper 246 - Feng Shen Bang (Asia) (Ja) (Unl) + * + */ + +#pragma once + +#include "Nes_Mapper.h" + +// https://www.nesdev.org/wiki/INES_Mapper246 + +class Mapper246 : public Nes_Mapper { +public: + Mapper246() + { + register_state( regs, sizeof regs ); + } + + virtual void reset_state() + { + regs [ 3 ] = ~0; + } + + virtual void apply_mapping() + { + enable_sram(); + intercept_writes( 0x6000, 0x07 ); + for ( size_t i = 0; i < sizeof regs; i++ ) + write_intercepted( 0, 0x6000 + i, regs [ i ] ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { } + + virtual bool write_intercepted( nes_time_t, nes_addr_t addr, int data ) + { + int bank = addr & 0x07; + + if ( addr < 0x6000 || addr > 0x67FF ) + return false; + + regs [ bank ] = data; + if ( bank < 4 ) + { + set_prg_bank( 0x8000 + ( bank << 13 ), bank_8k, data ); + return true; + } + + set_chr_bank( 0x0000 + ( ( bank & 0x03 ) << 11 ) , bank_2k, data ); + + return true; + } + + uint8_t regs [ 8 ]; +}; + diff --git a/core/nes_cpu_io.h b/core/nes_cpu_io.h new file mode 100644 index 0000000..bf02c9e --- /dev/null +++ b/core/nes_cpu_io.h @@ -0,0 +1,143 @@ + +#include "Nes_Core.h" +#include "Nes_Mapper.h" + +#include "blargg_source.h" + +int Nes_Core::cpu_read( nes_addr_t addr, nes_time_t time ) +{ + //LOG_FREQ( "cpu_read", 16, addr >> 12 ); + + { + int result = cpu::low_mem [addr & 0x7FF]; + if ( !(addr & 0xE000) ) + return result; + } + + { + int result = *cpu::get_code( addr ); + if ( addr > 0x7FFF ) + return result; + } + + time += cpu_time_offset; + if ( addr < 0x4000 ) + return ppu.read( addr, time ); + + clock_ = time; + if ( data_reader_mapped [addr >> page_bits] ) + { + int result = mapper->read( time, addr ); + if ( result >= 0 ) + return result; + } + + if ( addr < 0x6000 ) + return read_io( addr ); + + if ( addr < sram_readable ) + return impl->sram [addr & (impl_t::sram_size - 1)]; + + if ( addr < lrom_readable ) + return *cpu::get_code( addr ); + + #ifndef NDEBUG + log_unmapped( addr ); + #endif + + return addr >> 8; // simulate open bus +} + +inline int Nes_Core::cpu_read_ppu( nes_addr_t addr, nes_time_t time ) +{ + //LOG_FREQ( "cpu_read_ppu", 16, addr >> 12 ); + + // Read of status register (0x2002) is heavily optimized since many games + // poll it hundreds of times per frame. + nes_time_t next = ppu_2002_time; + int result = ppu.r2002; + if ( addr == 0x2002 ) + { + ppu.second_write = false; + if ( time >= next ) + result = ppu.read_2002( time + cpu_time_offset ); + } + else + { + result = cpu::low_mem [addr & 0x7FF]; + if ( addr >= 0x2000 ) + result = cpu_read( addr, time ); + } + + return result; +} + +void Nes_Core::cpu_write_2007( int data ) +{ + // ppu.write_2007() is inlined + if ( ppu.write_2007( data ) & Nes_Ppu::vaddr_clock_mask ) + mapper->a12_clocked(); +} + +void Nes_Core::cpu_write( nes_addr_t addr, int data, nes_time_t time ) +{ + //LOG_FREQ( "cpu_write", 16, addr >> 12 ); + + if ( !(addr & 0xE000) ) + { + cpu::low_mem [addr & 0x7FF] = data; + return; + } + + time += cpu_time_offset; + if ( addr < 0x4000 ) + { + if ( (addr & 7) == 7 ) + cpu_write_2007( data ); + else + ppu.write( time, addr, data ); + return; + } + + clock_ = time; + if ( data_writer_mapped [addr >> page_bits] && mapper->write_intercepted( time, addr, data ) ) + return; + + if ( addr < 0x6000 ) + { + write_io( addr, data ); + return; + } + + if ( addr < sram_writable ) + { + impl->sram [addr & (impl_t::sram_size - 1)] = data; + return; + } + + if ( addr > 0x7FFF ) + { + mapper->write( clock_, addr, data ); + return; + } + + #ifndef NDEBUG + log_unmapped( addr, data ); + #endif +} + +#define NES_CPU_READ_PPU( cpu, addr, time ) \ + STATIC_CAST(Nes_Core&,*cpu).cpu_read_ppu( addr, time ) + +#define NES_CPU_READ( cpu, addr, time ) \ + STATIC_CAST(Nes_Core&,*cpu).cpu_read( addr, time ) + +#define NES_CPU_WRITEX( cpu, addr, data, time ){\ + STATIC_CAST(Nes_Core&,*cpu).cpu_write( addr, data, time );\ +} + +#define NES_CPU_WRITE( cpu, addr, data, time ){\ + if ( addr < 0x800 ) cpu->low_mem [addr] = data;\ + else if ( addr == 0x2007 ) STATIC_CAST(Nes_Core&,*cpu).cpu_write_2007( data );\ + else STATIC_CAST(Nes_Core&,*cpu).cpu_write( addr, data, time );\ +} diff --git a/core/nes_data.cpp b/core/nes_data.cpp new file mode 100644 index 0000000..041b0b5 --- /dev/null +++ b/core/nes_data.cpp @@ -0,0 +1,66 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "nes_data.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +#define SWAP_BE( n ) (void) (set_be( &(n), (n) )) +#define SWAP_LE( n ) (void) (set_le( &(n), (n) )) + +void nes_block_t::swap() +{ + SWAP_BE( tag ); + SWAP_LE( size ); +} + +void nes_state_t::swap() +{ + SWAP_LE( timestamp ); + SWAP_LE( frame_count ); +} + +void cpu_state_t::swap() +{ + SWAP_LE( pc ); +} + +void ppu_state_t::swap() +{ + SWAP_LE( vram_addr ); + SWAP_LE( vram_temp ); + SWAP_LE( decay_low ); + SWAP_LE( decay_high ); +} + +void apu_state_t::swap() +{ + SWAP_LE( apu.frame_delay ); + SWAP_LE( square1.delay ); + SWAP_LE( square2.delay ); + SWAP_LE( triangle.delay ); + SWAP_LE( noise.delay ); + SWAP_LE( noise.shift_reg ); + SWAP_LE( dmc.delay ); + SWAP_LE( dmc.remain ); + SWAP_LE( dmc.addr ); +} + +void joypad_state_t::swap() +{ + SWAP_LE( joypad_latches [0] ); + SWAP_LE( joypad_latches [1] ); +} diff --git a/core/nes_data.h b/core/nes_data.h new file mode 100644 index 0000000..97059ba --- /dev/null +++ b/core/nes_data.h @@ -0,0 +1,155 @@ + +// NES data file block formats + +// Nes_Emu 0.7.0 + +#ifndef NES_DATA_H +#define NES_DATA_H + +#include "blargg_common.h" +#include "apu_state.h" + +typedef long nes_tag_t; + +#if 'ABCD' == '\101\102\103\104' +#define FOUR_CHAR( c ) (\ + ((c) / '\1\0\0\0' % 0x100 * 0x01000000L) +\ + ((c) / '\0\1\0\0' % 0x100 * 0x00010000L) +\ + ((c) / '\0\0\1\0' % 0x100 * 0x00000100L) +\ + ((c) / '\0\0\0\1' % 0x100 * 0x00000001L)\ +) +#else +#if 'ABCD' == 0x41424344 +#define FOUR_CHAR( c ) c +#else +#define FOUR_CHAR( c ) (\ + ((c) / 0x01000000 % 0x100 * 0x00000001) +\ + ((c) / 0x00010000 % 0x100 * 0x00000100) +\ + ((c) / 0x00000100 % 0x100 * 0x00010000) +\ + ((c) / 0x00000001 % 0x100 * 0x01000000)\ +) +#endif +#endif + +// Binary format of save state blocks. All multi-uint8_t values are stored in little-endian. + +nes_tag_t const state_file_tag = FOUR_CHAR('NESS'); + + +// Name of cartridge file in 8-bit characters (UTF-8 preferred) with ".nes" etc *removed*, +// no NUL termination. Yes: "Castlevania (U)". No: "Strider (U).nes". +nes_tag_t const cart_name_tag = FOUR_CHAR('romn'); + +// CRC-32 of cartridge's PRG and CHR data combined +nes_tag_t const cart_checksum_tag = FOUR_CHAR('csum'); + +struct nes_block_t +{ + uint32_t tag; // ** stored in big-endian + uint32_t size; + + void swap(); +}; +BOOST_STATIC_ASSERT( sizeof (nes_block_t) == 8 ); + +unsigned long const group_begin_size = 0xffffffff; // group block has this size +nes_tag_t const group_end_tag = FOUR_CHAR('gend'); // group end block has this tag + +struct nes_state_t +{ + uint16_t timestamp; // CPU clocks * 15 (for NTSC) + uint8_t pal; + uint8_t unused [1]; + uint32_t frame_count; // number of frames emulated since power-up + + enum { tag = FOUR_CHAR('TIME') }; + void swap(); +}; +BOOST_STATIC_ASSERT( sizeof (nes_state_t) == 8 ); + +struct joypad_state_t +{ + uint32_t joypad_latches [2]; // joypad 1 & 2 shift registers + uint8_t w4016; // strobe + uint8_t unused [3]; + + enum { tag = FOUR_CHAR('CTRL') }; + void swap(); +}; +BOOST_STATIC_ASSERT( sizeof (joypad_state_t) == 12 ); + +// Increase this (and let me know) if your mapper requires more state. This only +// sets the size of the in-memory buffer; it doesn't affect the file format at all. +unsigned const max_mapper_state_size = 512; //was 256, needed more for VRC7 audio state +struct mapper_state_t +{ + int size; + union { + double align; + uint8_t data [max_mapper_state_size]; + }; + + void write( const void* p, unsigned long s ); + int read( void* p, unsigned long s ) const; +}; + +struct cpu_state_t +{ + uint16_t pc; + uint8_t s; + uint8_t p; + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t unused [1]; + + enum { tag = FOUR_CHAR('CPUR') }; + void swap(); +}; +BOOST_STATIC_ASSERT( sizeof (cpu_state_t) == 8 ); + +struct ppu_state_t +{ + uint8_t w2000; // control + uint8_t w2001; // control + uint8_t r2002; // status + uint8_t w2003; // sprite ram addr + uint8_t r2007; // vram read buffer + uint8_t second_write; // next write to $2005/$2006 is second since last $2002 read + uint16_t vram_addr; // loopy_v + uint16_t vram_temp; // loopy_t + uint8_t pixel_x; // fine-scroll (0-7) + uint8_t unused; + uint8_t palette [0x20]; // entries $10, $14, $18, $1c should be ignored + uint16_t decay_low; + uint16_t decay_high; + uint8_t open_bus; + uint8_t unused2[3]; + + enum { tag = FOUR_CHAR('PPUR') }; + void swap(); +}; +BOOST_STATIC_ASSERT( sizeof (ppu_state_t) == 20 + 0x20 ); + +struct mmc1_state_t +{ + uint8_t regs [4]; // current registers (5 bits each) + uint8_t bit; // number of bits in buffer (0 to 4) + uint8_t buf; // currently buffered bits (new bits added to bottom) +}; +BOOST_STATIC_ASSERT( sizeof (mmc1_state_t) == 6 ); + +struct mmc3_state_t +{ + uint8_t banks [8]; // last writes to $8001 indexed by (mode & 7) + uint8_t mode; // $8000 + uint8_t mirror; // $a000 + uint8_t sram_mode; // $a001 + uint8_t irq_ctr; // internal counter + uint8_t irq_latch; // $c000 + uint8_t irq_enabled;// last write was to 0) $e000, 1) $e001 + uint8_t irq_flag; +}; +BOOST_STATIC_ASSERT( sizeof (mmc3_state_t) == 15 ); + +#endif diff --git a/core/nes_ntsc.cpp b/core/nes_ntsc.cpp new file mode 100644 index 0000000..417ec5d --- /dev/null +++ b/core/nes_ntsc.cpp @@ -0,0 +1,289 @@ +/* nes_ntsc 0.2.2. http://www.slack.net/~ant/ */ + +#include "nes_ntsc.h" + +/* Copyright (C) 2006-2007 Shay Green. This module 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 +module 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 module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +nes_ntsc_setup_t const nes_ntsc_monochrome = { 0,-1, 0, 0,.2, 0,.2,-.2,-.2,-1, 1, 0, 0, 0, 0 }; +nes_ntsc_setup_t const nes_ntsc_composite = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 }; +nes_ntsc_setup_t const nes_ntsc_svideo = { 0, 0, 0, 0,.2, 0,.2, -1, -1, 0, 1, 0, 0, 0, 0 }; +nes_ntsc_setup_t const nes_ntsc_rgb = { 0, 0, 0, 0,.2, 0,.7, -1, -1,-1, 1, 0, 0, 0, 0 }; + +#define alignment_count 3 +#define burst_count 3 +#define rescale_in 8 +#define rescale_out 7 + +#define artifacts_mid 1.0f +#define fringing_mid 1.0f +#define std_decoder_hue -15 + +#define STD_HUE_CONDITION( setup ) !(setup->base_palette || setup->palette) + +#include "nes_ntsc_impl.h" + +/* 3 input pixels -> 8 composite samples */ +pixel_info_t const nes_ntsc_pixels [alignment_count] = { + { PIXEL_OFFSET( -4, -9 ), { 1, 1, .6667f, 0 } }, + { PIXEL_OFFSET( -2, -7 ), { .3333f, 1, 1, .3333f } }, + { PIXEL_OFFSET( 0, -5 ), { 0, .6667f, 1, 1 } }, +}; + +static void merge_kernel_fields( nes_ntsc_rgb_t* io ) +{ + int n; + for ( n = burst_size; n; --n ) + { + nes_ntsc_rgb_t p0 = io [burst_size * 0] + rgb_bias; + nes_ntsc_rgb_t p1 = io [burst_size * 1] + rgb_bias; + nes_ntsc_rgb_t p2 = io [burst_size * 2] + rgb_bias; + /* merge colors without losing precision */ + io [burst_size * 0] = + ((p0 + p1 - ((p0 ^ p1) & nes_ntsc_rgb_builder)) >> 1) - rgb_bias; + io [burst_size * 1] = + ((p1 + p2 - ((p1 ^ p2) & nes_ntsc_rgb_builder)) >> 1) - rgb_bias; + io [burst_size * 2] = + ((p2 + p0 - ((p2 ^ p0) & nes_ntsc_rgb_builder)) >> 1) - rgb_bias; + ++io; + } +} + +static void correct_errors( nes_ntsc_rgb_t color, nes_ntsc_rgb_t* out ) +{ + int n; + for ( n = burst_count; n; --n ) + { + unsigned i; + for ( i = 0; i < rgb_kernel_size / 2; i++ ) + { + nes_ntsc_rgb_t error = color - + out [i ] - out [(i+12)%14+14] - out [(i+10)%14+28] - + out [i + 7] - out [i + 5 +14] - out [i + 3 +28]; + DISTRIBUTE_ERROR( i+3+28, i+5+14, i+7 ); + } + out += alignment_count * rgb_kernel_size; + } +} + +void nes_ntsc_init( nes_ntsc_t* ntsc, nes_ntsc_setup_t const* setup ) +{ + int merge_fields; + int entry; + init_t impl; + float gamma_factor; + + if ( !setup ) + setup = &nes_ntsc_composite; + init( &impl, setup ); + + /* setup fast gamma */ + { + float gamma = (float) setup->gamma * -0.5f; + if ( STD_HUE_CONDITION( setup ) ) + gamma += 0.1333f; + + gamma_factor = (float) pow( (float) fabs( gamma ), 0.73f ); + if ( gamma < 0 ) + gamma_factor = -gamma_factor; + } + + merge_fields = setup->merge_fields; + if ( setup->artifacts <= -1 && setup->fringing <= -1 ) + merge_fields = 1; + + for ( entry = 0; entry < nes_ntsc_palette_size; entry++ ) + { + /* Base 64-color generation */ + static float const lo_levels [4] = { -0.12f, 0.00f, 0.31f, 0.72f }; + static float const hi_levels [4] = { 0.40f, 0.68f, 1.00f, 1.00f }; + int level = entry >> 4 & 0x03; + float lo = lo_levels [level]; + float hi = hi_levels [level]; + + int color = entry & 0x0F; + if ( color == 0 ) + lo = hi; + if ( color == 0x0D ) + hi = lo; + if ( color > 0x0D ) + hi = lo = 0.0f; + + { + /* phases [i] = cos( i * PI / 6 ) */ + static float const phases [0x10 + 3] = { + -1.0f, -0.866025f, -0.5f, 0.0f, 0.5f, 0.866025f, + 1.0f, 0.866025f, 0.5f, 0.0f, -0.5f, -0.866025f, + -1.0f, -0.866025f, -0.5f, 0.0f, 0.5f, 0.866025f, + 1.0f + }; + #define TO_ANGLE_SIN( color ) phases [color] + #define TO_ANGLE_COS( color ) phases [(color) + 3] + + /* Convert raw waveform to YIQ */ + float sat = (hi - lo) * 0.5f; + float i = TO_ANGLE_SIN( color ) * sat; + float q = TO_ANGLE_COS( color ) * sat; + float y = (hi + lo) * 0.5f; + + /* Optionally use base palette instead */ + if ( setup->base_palette ) + { + unsigned char const* in = &setup->base_palette [(entry & 0x3F) * 3]; + static float const to_float = 1.0f / 0xFF; + float r = to_float * in [0]; + float g = to_float * in [1]; + float b = to_float * in [2]; + q = RGB_TO_YIQ( r, g, b, y, i ); + } + + /* Apply color emphasis */ + #ifdef NES_NTSC_EMPHASIS + { + int tint = entry >> 6 & 7; + if ( tint && color <= 0x0D ) + { + static float const atten_mul = 0.79399f; + static float const atten_sub = 0.0782838f; + + if ( tint == 7 ) + { + y = y * (atten_mul * 1.13f) - (atten_sub * 1.13f); + } + else + { + static unsigned char const tints [8] = { 0, 6, 10, 8, 2, 4, 0, 0 }; + int const tint_color = tints [tint]; + float sat = hi * (0.5f - atten_mul * 0.5f) + atten_sub * 0.5f; + y -= sat * 0.5f; + if ( tint >= 3 && tint != 4 ) + { + /* combined tint bits */ + sat *= 0.6f; + y -= sat; + } + i += TO_ANGLE_SIN( tint_color ) * sat; + q += TO_ANGLE_COS( tint_color ) * sat; + } + } + } + #endif + + /* Optionally use palette instead */ + if ( setup->palette ) + { + unsigned char const* in = &setup->palette [entry * 3]; + static float const to_float = 1.0f / 0xFF; + float r = to_float * in [0]; + float g = to_float * in [1]; + float b = to_float * in [2]; + q = RGB_TO_YIQ( r, g, b, y, i ); + } + + /* Apply brightness, contrast, and gamma */ + y *= (float) setup->contrast * 0.5f + 1; + /* adjustment reduces error when using input palette */ + y += (float) setup->brightness * 0.5f - 0.5f / 256; + + { + float r, g, b = YIQ_TO_RGB( y, i, q, default_decoder, float, r, g ); + + /* fast approximation of n = pow( n, gamma ) */ + r = (r * gamma_factor - gamma_factor) * r + r; + g = (g * gamma_factor - gamma_factor) * g + g; + b = (b * gamma_factor - gamma_factor) * b + b; + + q = RGB_TO_YIQ( r, g, b, y, i ); + } + + i *= rgb_unit; + q *= rgb_unit; + y *= rgb_unit; + y += rgb_offset; + + /* Generate kernel */ + { + int r, g, b = YIQ_TO_RGB( y, i, q, impl.to_rgb, int, r, g ); + /* blue tends to overflow, so clamp it */ + nes_ntsc_rgb_t rgb = PACK_RGB( r, g, (b < 0x3E0 ? b: 0x3E0) ); + + if ( setup->palette_out ) + RGB_PALETTE_OUT( rgb, &setup->palette_out [entry * 3] ); + + if ( ntsc ) + { + nes_ntsc_rgb_t* kernel = ntsc->table [entry]; + gen_kernel( &impl, y, i, q, kernel ); + if ( merge_fields ) + merge_kernel_fields( kernel ); + correct_errors( rgb, kernel ); + } + } + } + } +} + +#ifndef NES_NTSC_NO_BLITTERS + +void nes_ntsc_blit( nes_ntsc_t const* ntsc, NES_NTSC_IN_T const* input, long in_row_width, + int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch ) +{ + int chunk_count = (in_width - 1) / nes_ntsc_in_chunk; + for ( ; in_height; --in_height ) + { + NES_NTSC_IN_T const* line_in = input; + NES_NTSC_BEGIN_ROW( ntsc, burst_phase, + nes_ntsc_black, nes_ntsc_black, NES_NTSC_ADJ_IN( *line_in ) ); + nes_ntsc_out_t* line_out = (nes_ntsc_out_t*) rgb_out; + int n; + ++line_in; + + for ( n = chunk_count; n; --n ) + { + /* order of input and output pixels must not be altered */ + NES_NTSC_COLOR_IN( 0, NES_NTSC_ADJ_IN( line_in [0] ) ); + NES_NTSC_RGB_OUT( 0, line_out [0], NES_NTSC_OUT_DEPTH ); + NES_NTSC_RGB_OUT( 1, line_out [1], NES_NTSC_OUT_DEPTH ); + + NES_NTSC_COLOR_IN( 1, NES_NTSC_ADJ_IN( line_in [1] ) ); + NES_NTSC_RGB_OUT( 2, line_out [2], NES_NTSC_OUT_DEPTH ); + NES_NTSC_RGB_OUT( 3, line_out [3], NES_NTSC_OUT_DEPTH ); + + NES_NTSC_COLOR_IN( 2, NES_NTSC_ADJ_IN( line_in [2] ) ); + NES_NTSC_RGB_OUT( 4, line_out [4], NES_NTSC_OUT_DEPTH ); + NES_NTSC_RGB_OUT( 5, line_out [5], NES_NTSC_OUT_DEPTH ); + NES_NTSC_RGB_OUT( 6, line_out [6], NES_NTSC_OUT_DEPTH ); + + line_in += 3; + line_out += 7; + } + + /* finish final pixels */ + NES_NTSC_COLOR_IN( 0, nes_ntsc_black ); + NES_NTSC_RGB_OUT( 0, line_out [0], NES_NTSC_OUT_DEPTH ); + NES_NTSC_RGB_OUT( 1, line_out [1], NES_NTSC_OUT_DEPTH ); + + NES_NTSC_COLOR_IN( 1, nes_ntsc_black ); + NES_NTSC_RGB_OUT( 2, line_out [2], NES_NTSC_OUT_DEPTH ); + NES_NTSC_RGB_OUT( 3, line_out [3], NES_NTSC_OUT_DEPTH ); + + NES_NTSC_COLOR_IN( 2, nes_ntsc_black ); + NES_NTSC_RGB_OUT( 4, line_out [4], NES_NTSC_OUT_DEPTH ); + NES_NTSC_RGB_OUT( 5, line_out [5], NES_NTSC_OUT_DEPTH ); + NES_NTSC_RGB_OUT( 6, line_out [6], NES_NTSC_OUT_DEPTH ); + + burst_phase = (burst_phase + 1) % nes_ntsc_burst_count; + input += in_row_width; + rgb_out = (char*) rgb_out + out_pitch; + } +} + +#endif diff --git a/core/nes_ntsc.h b/core/nes_ntsc.h new file mode 100644 index 0000000..1f53187 --- /dev/null +++ b/core/nes_ntsc.h @@ -0,0 +1,192 @@ +/* NES NTSC video filter */ + +/* nes_ntsc 0.2.2 */ +#ifndef NES_NTSC_H +#define NES_NTSC_H + +#include "nes_ntsc_config.h" + +#ifdef __cplusplus + extern "C" { +#endif + +/* Image parameters, ranging from -1.0 to 1.0. Actual internal values shown +in parenthesis and should remain fairly stable in future versions. */ +typedef struct nes_ntsc_setup_t +{ + /* Basic parameters */ + double hue; /* -1 = -180 degrees +1 = +180 degrees */ + double saturation; /* -1 = grayscale (0.0) +1 = oversaturated colors (2.0) */ + double contrast; /* -1 = dark (0.5) +1 = light (1.5) */ + double brightness; /* -1 = dark (0.5) +1 = light (1.5) */ + double sharpness; /* edge contrast enhancement/blurring */ + + /* Advanced parameters */ + double gamma; /* -1 = dark (1.5) +1 = light (0.5) */ + double resolution; /* image resolution */ + double artifacts; /* artifacts caused by color changes */ + double fringing; /* color artifacts caused by brightness changes */ + double bleed; /* color bleed (color resolution reduction) */ + int merge_fields; /* if 1, merges even and odd fields together to reduce flicker */ + float const* decoder_matrix; /* optional RGB decoder matrix, 6 elements */ + + unsigned char* palette_out; /* optional RGB palette out, 3 bytes per color */ + + /* You can replace the standard NES color generation with an RGB palette. The + first replaces all color generation, while the second replaces only the core + 64-color generation and does standard color emphasis calculations on it. */ + unsigned char const* palette;/* optional 512-entry RGB palette in, 3 bytes per color */ + unsigned char const* base_palette;/* optional 64-entry RGB palette in, 3 bytes per color */ +} nes_ntsc_setup_t; + +/* Video format presets */ +extern nes_ntsc_setup_t const nes_ntsc_composite; /* color bleeding + artifacts */ +extern nes_ntsc_setup_t const nes_ntsc_svideo; /* color bleeding only */ +extern nes_ntsc_setup_t const nes_ntsc_rgb; /* crisp image */ +extern nes_ntsc_setup_t const nes_ntsc_monochrome;/* desaturated + artifacts */ + +#ifdef NES_NTSC_EMPHASIS + enum { nes_ntsc_palette_size = 64 * 8 }; +#else + enum { nes_ntsc_palette_size = 64 }; +#endif + +/* Initializes and adjusts parameters. Can be called multiple times on the same +nes_ntsc_t object. Can pass NULL for either parameter. */ +typedef struct nes_ntsc_t nes_ntsc_t; +void nes_ntsc_init( nes_ntsc_t* ntsc, nes_ntsc_setup_t const* setup ); + +/* Filters one or more rows of pixels. Input pixels are 6/9-bit palette indicies. +In_row_width is the number of pixels to get to the next input row. Out_pitch +is the number of *bytes* to get to the next output row. Output pixel format +is set by NES_NTSC_OUT_DEPTH (defaults to 16-bit RGB). */ +void nes_ntsc_blit( nes_ntsc_t const* ntsc, NES_NTSC_IN_T const* nes_in, + long in_row_width, int burst_phase, int in_width, int in_height, + void* rgb_out, long out_pitch ); + +/* Number of output pixels written by blitter for given input width. Width might +be rounded down slightly; use NES_NTSC_IN_WIDTH() on result to find rounded +value. Guaranteed not to round 256 down at all. */ +#define NES_NTSC_OUT_WIDTH( in_width ) \ + ((((in_width) - 1) / nes_ntsc_in_chunk + 1) * nes_ntsc_out_chunk) + +/* Number of input pixels that will fit within given output width. Might be +rounded down slightly; use NES_NTSC_OUT_WIDTH() on result to find rounded +value. */ +#define NES_NTSC_IN_WIDTH( out_width ) \ + (((out_width) / nes_ntsc_out_chunk - 1) * nes_ntsc_in_chunk + 1) + + +/* Interface for user-defined custom blitters */ + +enum { nes_ntsc_in_chunk = 3 }; /* number of input pixels read per chunk */ +enum { nes_ntsc_out_chunk = 7 }; /* number of output pixels generated per chunk */ +enum { nes_ntsc_black = 15 }; /* palette index for black */ +enum { nes_ntsc_burst_count = 3 }; /* burst phase cycles through 0, 1, and 2 */ + +/* Begins outputting row and starts three pixels. First pixel will be cut off a bit. +Use nes_ntsc_black for unused pixels. Declares variables, so must be before first +statement in a block (unless you're using C++). */ +#define NES_NTSC_BEGIN_ROW( ntsc, burst, pixel0, pixel1, pixel2 ) \ + char const* const ktable = \ + (char const*) (ntsc)->table [0] + burst * (nes_ntsc_burst_size * sizeof (nes_ntsc_rgb_t));\ + NES_NTSC_BEGIN_ROW_6_( pixel0, pixel1, pixel2, NES_NTSC_ENTRY_, ktable ) + +/* Begins input pixel */ +#define NES_NTSC_COLOR_IN( in_index, color_in ) \ + NES_NTSC_COLOR_IN_( in_index, color_in, NES_NTSC_ENTRY_, ktable ) + +/* Generates output pixel. Bits can be 24, 16, 15, 32 (treated as 24), or 0: +24: RRRRRRRR GGGGGGGG BBBBBBBB (8-8-8 RGB) +16: RRRRRGGG GGGBBBBB (5-6-5 RGB) +15: RRRRRGG GGGBBBBB (5-5-5 RGB) + 0: xxxRRRRR RRRxxGGG GGGGGxxB BBBBBBBx (native internal format; x = junk bits) */ +#define NES_NTSC_RGB_OUT( index, rgb_out, bits ) \ + NES_NTSC_RGB_OUT_14_( index, rgb_out, bits, 0 ) + + +/* private */ +enum { nes_ntsc_entry_size = 128 }; +typedef unsigned long nes_ntsc_rgb_t; +struct nes_ntsc_t { + nes_ntsc_rgb_t table [nes_ntsc_palette_size] [nes_ntsc_entry_size]; +}; +enum { nes_ntsc_burst_size = nes_ntsc_entry_size / nes_ntsc_burst_count }; + +#define NES_NTSC_ENTRY_( ktable, n ) \ + (nes_ntsc_rgb_t const*) (ktable + (n) * (nes_ntsc_entry_size * sizeof (nes_ntsc_rgb_t))) + +/* deprecated */ +#define NES_NTSC_RGB24_OUT( x, out ) NES_NTSC_RGB_OUT( x, out, 24 ) +#define NES_NTSC_RGB16_OUT( x, out ) NES_NTSC_RGB_OUT( x, out, 16 ) +#define NES_NTSC_RGB15_OUT( x, out ) NES_NTSC_RGB_OUT( x, out, 15 ) +#define NES_NTSC_RAW_OUT( x, out ) NES_NTSC_RGB_OUT( x, out, 0 ) + +enum { nes_ntsc_min_in_width = 256 }; +enum { nes_ntsc_min_out_width = NES_NTSC_OUT_WIDTH( nes_ntsc_min_in_width ) }; + +enum { nes_ntsc_640_in_width = 271 }; +enum { nes_ntsc_640_out_width = NES_NTSC_OUT_WIDTH( nes_ntsc_640_in_width ) }; +enum { nes_ntsc_640_overscan_left = 8 }; +enum { nes_ntsc_640_overscan_right = nes_ntsc_640_in_width - 256 - nes_ntsc_640_overscan_left }; + +enum { nes_ntsc_full_in_width = 283 }; +enum { nes_ntsc_full_out_width = NES_NTSC_OUT_WIDTH( nes_ntsc_full_in_width ) }; +enum { nes_ntsc_full_overscan_left = 16 }; +enum { nes_ntsc_full_overscan_right = nes_ntsc_full_in_width - 256 - nes_ntsc_full_overscan_left }; + +/* common 3->7 ntsc macros */ +#define NES_NTSC_BEGIN_ROW_6_( pixel0, pixel1, pixel2, ENTRY, table ) \ + unsigned const nes_ntsc_pixel0_ = (pixel0);\ + nes_ntsc_rgb_t const* kernel0 = ENTRY( table, nes_ntsc_pixel0_ );\ + unsigned const nes_ntsc_pixel1_ = (pixel1);\ + nes_ntsc_rgb_t const* kernel1 = ENTRY( table, nes_ntsc_pixel1_ );\ + unsigned const nes_ntsc_pixel2_ = (pixel2);\ + nes_ntsc_rgb_t const* kernel2 = ENTRY( table, nes_ntsc_pixel2_ );\ + nes_ntsc_rgb_t const* kernelx0;\ + nes_ntsc_rgb_t const* kernelx1 = kernel0;\ + nes_ntsc_rgb_t const* kernelx2 = kernel0 + +#define NES_NTSC_RGB_OUT_14_( x, rgb_out, bits, shift ) {\ + nes_ntsc_rgb_t raw_ =\ + kernel0 [x ] + kernel1 [(x+12)%7+14] + kernel2 [(x+10)%7+28] +\ + kernelx0 [(x+7)%14] + kernelx1 [(x+ 5)%7+21] + kernelx2 [(x+ 3)%7+35];\ + NES_NTSC_CLAMP_( raw_, shift );\ + NES_NTSC_RGB_OUT_( rgb_out, bits, shift );\ +} + +/* common ntsc macros */ +#define nes_ntsc_rgb_builder ((1L << 21) | (1 << 11) | (1 << 1)) +#define nes_ntsc_clamp_mask (nes_ntsc_rgb_builder * 3 / 2) +#define nes_ntsc_clamp_add (nes_ntsc_rgb_builder * 0x101) +#define NES_NTSC_CLAMP_( io, shift ) {\ + nes_ntsc_rgb_t sub = (io) >> (9-(shift)) & nes_ntsc_clamp_mask;\ + nes_ntsc_rgb_t clamp = nes_ntsc_clamp_add - sub;\ + io |= clamp;\ + clamp -= sub;\ + io &= clamp;\ +} + +#define NES_NTSC_COLOR_IN_( index, color, ENTRY, table ) {\ + unsigned color_;\ + kernelx##index = kernel##index;\ + kernel##index = (color_ = (color), ENTRY( table, color_ ));\ +} + +/* x is always zero except in snes_ntsc library */ +#define NES_NTSC_RGB_OUT_( rgb_out, bits, x ) {\ + if ( bits == 16 )\ + rgb_out = (raw_>>(13-x)& 0xF800)|(raw_>>(8-x)&0x07E0)|(raw_>>(4-x)&0x001F);\ + if ( bits == 24 || bits == 32 )\ + rgb_out = (raw_>>(5-x)&0xFF0000)|(raw_>>(3-x)&0xFF00)|(raw_>>(1-x)&0xFF);\ + if ( bits == 15 )\ + rgb_out = (raw_>>(14-x)& 0x7C00)|(raw_>>(9-x)&0x03E0)|(raw_>>(4-x)&0x001F);\ + if ( bits == 0 )\ + rgb_out = raw_ << x;\ +} + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/core/nes_ntsc_config.h b/core/nes_ntsc_config.h new file mode 100644 index 0000000..c006ed8 --- /dev/null +++ b/core/nes_ntsc_config.h @@ -0,0 +1,27 @@ +/* Configure library by modifying this file */ + +#ifndef NES_NTSC_CONFIG_H +#define NES_NTSC_CONFIG_H + +/* Uncomment to enable emphasis support and use a 512 color palette instead +of the base 64 color palette. */ +#define NES_NTSC_EMPHASIS 1 + +/* The following affect the built-in blitter only; a custom blitter can +handle things however it wants. */ + +/* Bits per pixel of output. Can be 15, 16, 32, or 24 (same as 32). */ +#define NES_NTSC_OUT_DEPTH 16 + +/* Type of input pixel values. You'll probably use unsigned short +if you enable emphasis above. */ +#define NES_NTSC_IN_T unsigned short + +/* Each raw pixel input value is passed through this. You might want to mask +the pixel index if you use the high bits as flags, etc. */ +#define NES_NTSC_ADJ_IN( in ) in + +/* For each pixel, this is the basic operation: +output_color = color_palette [NES_NTSC_ADJ_IN( NES_NTSC_IN_T )] */ + +#endif diff --git a/core/nes_ntsc_impl.h b/core/nes_ntsc_impl.h new file mode 100644 index 0000000..de3672b --- /dev/null +++ b/core/nes_ntsc_impl.h @@ -0,0 +1,439 @@ +/* nes_ntsc 0.2.2. http://www.slack.net/~ant/ */ + +/* Common implementation of NTSC filters */ + +#include +#include + +/* Copyright (C) 2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#define DISABLE_CORRECTION 0 + +#undef PI +#define PI 3.14159265358979323846f + +#ifndef LUMA_CUTOFF + #define LUMA_CUTOFF 0.20 +#endif +#ifndef gamma_size + #define gamma_size 1 +#endif +#ifndef rgb_bits + #define rgb_bits 8 +#endif +#ifndef artifacts_max + #define artifacts_max (artifacts_mid * 1.5f) +#endif +#ifndef fringing_max + #define fringing_max (fringing_mid * 2) +#endif +#ifndef STD_HUE_CONDITION + #define STD_HUE_CONDITION( setup ) 1 +#endif + +#define ext_decoder_hue (std_decoder_hue + 15) +#define rgb_unit (1 << rgb_bits) +#define rgb_offset (rgb_unit * 2 + 0.5f) + +enum { burst_size = nes_ntsc_entry_size / burst_count }; +enum { kernel_half = 16 }; +enum { kernel_size = kernel_half * 2 + 1 }; + +typedef struct init_t +{ + float to_rgb [burst_count * 6]; + float to_float [gamma_size]; + float contrast; + float brightness; + float artifacts; + float fringing; + float kernel [rescale_out * kernel_size * 2]; +} init_t; + +#define ROTATE_IQ( i, q, sin_b, cos_b ) {\ + float t;\ + t = i * cos_b - q * sin_b;\ + q = i * sin_b + q * cos_b;\ + i = t;\ +} + +static void init_filters( init_t* impl, nes_ntsc_setup_t const* setup ) +{ +#if rescale_out > 1 + float kernels [kernel_size * 2]; +#else + float* const kernels = impl->kernel; +#endif + + /* generate luma (y) filter using sinc kernel */ + { + /* sinc with rolloff (dsf) */ + float const rolloff = 1 + (float) setup->sharpness * (float) 0.032; + float const maxh = 32; + float const pow_a_n = (float) pow( rolloff, maxh ); + float sum; + int i; + /* quadratic mapping to reduce negative (blurring) range */ + float to_angle = (float) setup->resolution + 1; + to_angle = PI / maxh * (float) LUMA_CUTOFF * (to_angle * to_angle + 1); + + kernels [kernel_size * 3 / 2] = maxh; /* default center value */ + for ( i = 0; i < kernel_half * 2 + 1; i++ ) + { + int x = i - kernel_half; + float angle = x * to_angle; + /* instability occurs at center point with rolloff very close to 1.0 */ + if ( x || pow_a_n > (float) 1.056 || pow_a_n < (float) 0.981 ) + { + float rolloff_cos_a = rolloff * (float) cos( angle ); + float num = 1 - rolloff_cos_a - + pow_a_n * (float) cos( maxh * angle ) + + pow_a_n * rolloff * (float) cos( (maxh - 1) * angle ); + float den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff; + float dsf = num / den; + kernels [kernel_size * 3 / 2 - kernel_half + i] = dsf - (float) 0.5; + } + } + + /* apply blackman window and find sum */ + sum = 0; + for ( i = 0; i < kernel_half * 2 + 1; i++ ) + { + float x = PI * 2 / (kernel_half * 2) * i; + float blackman = 0.42f - 0.5f * (float) cos( x ) + 0.08f * (float) cos( x * 2 ); + sum += (kernels [kernel_size * 3 / 2 - kernel_half + i] *= blackman); + } + + /* normalize kernel */ + sum = 1.0f / sum; + for ( i = 0; i < kernel_half * 2 + 1; i++ ) + { + int x = kernel_size * 3 / 2 - kernel_half + i; + kernels [x] *= sum; + assert( kernels [x] == kernels [x] ); /* catch numerical instability */ + } + } + + /* generate chroma (iq) filter using gaussian kernel */ + { + float const cutoff_factor = -0.03125f; + float cutoff = (float) setup->bleed; + int i; + + if ( cutoff < 0 ) + { + /* keep extreme value accessible only near upper end of scale (1.0) */ + cutoff *= cutoff; + cutoff *= cutoff; + cutoff *= cutoff; + cutoff *= -30.0f / 0.65f; + } + cutoff = cutoff_factor - 0.65f * cutoff_factor * cutoff; + + for ( i = -kernel_half; i <= kernel_half; i++ ) + kernels [kernel_size / 2 + i] = (float) exp( i * i * cutoff ); + + /* normalize even and odd phases separately */ + for ( i = 0; i < 2; i++ ) + { + float sum = 0; + int x; + for ( x = i; x < kernel_size; x += 2 ) + sum += kernels [x]; + + sum = 1.0f / sum; + for ( x = i; x < kernel_size; x += 2 ) + { + kernels [x] *= sum; + assert( kernels [x] == kernels [x] ); /* catch numerical instability */ + } + } + } + + /* + printf( "luma:\n" ); + for ( i = kernel_size; i < kernel_size * 2; i++ ) + printf( "%f\n", kernels [i] ); + printf( "chroma:\n" ); + for ( i = 0; i < kernel_size; i++ ) + printf( "%f\n", kernels [i] ); + */ + + /* generate linear rescale kernels */ + #if rescale_out > 1 + { + float weight = 1.0f; + float* out = impl->kernel; + int n = rescale_out; + do + { + float remain = 0; + int i; + weight -= 1.0f / rescale_in; + for ( i = 0; i < kernel_size * 2; i++ ) + { + float cur = kernels [i]; + float m = cur * weight; + *out++ = m + remain; + remain = cur - m; + } + } + while ( --n ); + } + #endif +} + +static float const default_decoder [6] = + { 0.956f, 0.621f, -0.272f, -0.647f, -1.105f, 1.702f }; + +static void init( init_t* impl, nes_ntsc_setup_t const* setup ) +{ + impl->brightness = (float) setup->brightness * (0.5f * rgb_unit) + rgb_offset; + impl->contrast = (float) setup->contrast * (0.5f * rgb_unit) + rgb_unit; + #ifdef default_palette_contrast + if ( !setup->palette ) + impl->contrast *= default_palette_contrast; + #endif + + impl->artifacts = (float) setup->artifacts; + if ( impl->artifacts > 0 ) + impl->artifacts *= artifacts_max - artifacts_mid; + impl->artifacts = impl->artifacts * artifacts_mid + artifacts_mid; + + impl->fringing = (float) setup->fringing; + if ( impl->fringing > 0 ) + impl->fringing *= fringing_max - fringing_mid; + impl->fringing = impl->fringing * fringing_mid + fringing_mid; + + init_filters( impl, setup ); + + /* generate gamma table */ + if ( gamma_size > 1 ) + { + float const to_float = 1.0f / (gamma_size - (gamma_size > 1)); + float const gamma = 1.1333f - (float) setup->gamma * 0.5f; + /* match common PC's 2.2 gamma to TV's 2.65 gamma */ + int i; + for ( i = 0; i < gamma_size; i++ ) + impl->to_float [i] = + (float) pow( i * to_float, gamma ) * impl->contrast + impl->brightness; + } + + /* setup decoder matricies */ + { + float hue = (float) setup->hue * PI + PI / 180 * ext_decoder_hue; + float sat = (float) setup->saturation + 1; + float const* decoder = setup->decoder_matrix; + if ( !decoder ) + { + decoder = default_decoder; + if ( STD_HUE_CONDITION( setup ) ) + hue += PI / 180 * (std_decoder_hue - ext_decoder_hue); + } + + { + float s = (float) sin( hue ) * sat; + float c = (float) cos( hue ) * sat; + float* out = impl->to_rgb; + int n; + + n = burst_count; + do + { + float const* in = decoder; + int n = 3; + do + { + float i = *in++; + float q = *in++; + *out++ = i * c - q * s; + *out++ = i * s + q * c; + } + while ( --n ); + if ( burst_count <= 1 ) + break; + ROTATE_IQ( s, c, 0.866025f, -0.5f ); /* +120 degrees */ + } + while ( --n ); + } + } +} + +/* kernel generation */ + +#define RGB_TO_YIQ( r, g, b, y, i ) (\ + (y = (r) * 0.299f + (g) * 0.587f + (b) * 0.114f),\ + (i = (r) * 0.596f - (g) * 0.275f - (b) * 0.321f),\ + ((r) * 0.212f - (g) * 0.523f + (b) * 0.311f)\ +) + +#define YIQ_TO_RGB( y, i, q, to_rgb, type, r, g ) (\ + r = (type) (y + to_rgb [0] * i + to_rgb [1] * q),\ + g = (type) (y + to_rgb [2] * i + to_rgb [3] * q),\ + (type) (y + to_rgb [4] * i + to_rgb [5] * q)\ +) + +#define PACK_RGB( r, g, b ) ((r) << 21 | (g) << 11 | (b) << 1) + +enum { rgb_kernel_size = burst_size / alignment_count }; +enum { rgb_bias = rgb_unit * 2 * nes_ntsc_rgb_builder }; + +typedef struct pixel_info_t +{ + int offset; + float negate; + float kernel [4]; +} pixel_info_t; + +#if rescale_in > 1 + #define PIXEL_OFFSET_( ntsc, scaled ) \ + (kernel_size / 2 + ntsc + (scaled != 0) + (rescale_out - scaled) % rescale_out + \ + (kernel_size * 2 * scaled)) + + #define PIXEL_OFFSET( ntsc, scaled ) \ + PIXEL_OFFSET_( ((ntsc) - (scaled) / rescale_out * rescale_in),\ + (((scaled) + rescale_out * 10) % rescale_out) ),\ + (1.0f - (((ntsc) + 100) & 2)) +#else + #define PIXEL_OFFSET( ntsc, scaled ) \ + (kernel_size / 2 + (ntsc) - (scaled)),\ + (1.0f - (((ntsc) + 100) & 2)) +#endif + +extern pixel_info_t const nes_ntsc_pixels [alignment_count]; + +/* Generate pixel at all burst phases and column alignments */ +static void gen_kernel( init_t* impl, float y, float i, float q, nes_ntsc_rgb_t* out ) +{ + /* generate for each scanline burst phase */ + float const* to_rgb = impl->to_rgb; + int burst_remain = burst_count; + y -= rgb_offset; + do + { + /* Encode yiq into *two* composite signals (to allow control over artifacting). + Convolve these with kernels which: filter respective components, apply + sharpening, and rescale horizontally. Convert resulting yiq to rgb and pack + into integer. Based on algorithm by NewRisingSun. */ + pixel_info_t const* pixel = nes_ntsc_pixels; + int alignment_remain = alignment_count; + do + { + /* negate is -1 when composite starts at odd multiple of 2 */ + float const yy = y * impl->fringing * pixel->negate; + float const ic0 = (i + yy) * pixel->kernel [0]; + float const qc1 = (q + yy) * pixel->kernel [1]; + float const ic2 = (i - yy) * pixel->kernel [2]; + float const qc3 = (q - yy) * pixel->kernel [3]; + + float const factor = impl->artifacts * pixel->negate; + float const ii = i * factor; + float const yc0 = (y + ii) * pixel->kernel [0]; + float const yc2 = (y - ii) * pixel->kernel [2]; + + float const qq = q * factor; + float const yc1 = (y + qq) * pixel->kernel [1]; + float const yc3 = (y - qq) * pixel->kernel [3]; + + float const* k = &impl->kernel [pixel->offset]; + int n; + ++pixel; + for ( n = rgb_kernel_size; n; --n ) + { + float i = k[0]*ic0 + k[2]*ic2; + float q = k[1]*qc1 + k[3]*qc3; + float y = k[kernel_size+0]*yc0 + k[kernel_size+1]*yc1 + + k[kernel_size+2]*yc2 + k[kernel_size+3]*yc3 + rgb_offset; + if ( rescale_out <= 1 ) + k--; + else if ( k < &impl->kernel [kernel_size * 2 * (rescale_out - 1)] ) + k += kernel_size * 2 - 1; + else + k -= kernel_size * 2 * (rescale_out - 1) + 2; + { + int r, g, b = YIQ_TO_RGB( y, i, q, to_rgb, int, r, g ); + *out++ = PACK_RGB( r, g, b ) - rgb_bias; + } + } + } + while ( alignment_count > 1 && --alignment_remain ); + + if ( burst_count <= 1 ) + break; + + to_rgb += 6; + + ROTATE_IQ( i, q, -0.866025f, -0.5f ); /* -120 degrees */ + } + while ( --burst_remain ); +} + +static void correct_errors( nes_ntsc_rgb_t color, nes_ntsc_rgb_t* out ); + +#if DISABLE_CORRECTION + #define CORRECT_ERROR( a ) { out [i] += rgb_bias; } + #define DISTRIBUTE_ERROR( a, b, c ) { out [i] += rgb_bias; } +#else + #define CORRECT_ERROR( a ) { out [a] += error; } + #define DISTRIBUTE_ERROR( a, b, c ) {\ + nes_ntsc_rgb_t fourth = (error + 2 * nes_ntsc_rgb_builder) >> 2;\ + fourth &= (rgb_bias >> 1) - nes_ntsc_rgb_builder;\ + fourth -= rgb_bias >> 2;\ + out [a] += fourth;\ + out [b] += fourth;\ + out [c] += fourth;\ + out [i] += error - (fourth * 3);\ + } +#endif + +#define RGB_PALETTE_OUT( rgb, out_ )\ +{\ + unsigned char* out = (out_);\ + nes_ntsc_rgb_t clamped = (rgb);\ + NES_NTSC_CLAMP_( clamped, (8 - rgb_bits) );\ + out [0] = (unsigned char) (clamped >> 21);\ + out [1] = (unsigned char) (clamped >> 11);\ + out [2] = (unsigned char) (clamped >> 1);\ +} + +/* blitter related */ + +#ifndef restrict + #if defined (__GNUC__) + #define restrict __restrict__ + #elif defined (_MSC_VER) && _MSC_VER > 1300 + #define restrict __restrict + #else + /* no support for restricted pointers */ + #define restrict + #endif +#endif + +#include + +#if NES_NTSC_OUT_DEPTH <= 16 + #if USHRT_MAX == 0xFFFF + typedef unsigned short nes_ntsc_out_t; + #else + #error "Need 16-bit int type" + #endif + +#else + #if UINT_MAX == 0xFFFFFFFF + typedef unsigned int nes_ntsc_out_t; + #elif ULONG_MAX == 0xFFFFFFFF + typedef unsigned long nes_ntsc_out_t; + #else + #error "Need 32-bit int type" + #endif + +#endif diff --git a/core/nes_util.cpp b/core/nes_util.cpp new file mode 100644 index 0000000..93e371f --- /dev/null +++ b/core/nes_util.cpp @@ -0,0 +1,153 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "nes_util.h" + +#include "Nes_Cart.h" +#include "Nes_Emu.h" +#include +#include + +/* Copyright (C) 2004-2006 Shay Green. This module 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 +module 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 module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// game_genie_patch_t + +const char *game_genie_patch_t::decode( const char* in ) +{ + int const code_len = 8; + unsigned char result [code_len] = { 0 }; + int in_len = strlen( in ); + if ( in_len != 6 && in_len != 8 ) + return "Game Genie code is wrong length"; + for ( int i = 0; i < code_len; i++ ) + { + char c = 'A'; + if ( i < in_len ) + c = toupper( in [i] ); + + static char const letters [17] = "AEPOZXLUGKISTVYN"; + char const* p = strchr( (char*) letters, c ); + if ( !p ) + return "Game Genie code had invalid character"; + int n = p - letters; + + result [i] |= n >> 1; + result [(i + 1) % code_len] |= (n << 3) & 0x0f; + } + + addr = result [3]<<12 | result [5]<<8 | result [2]<<4 | result [4]; + change_to = result [1]<<4 | result [0]; + compare_with = -1; + if ( addr & 0x8000 ) + compare_with = result [7]<<4 | result [6]; + addr |= 0x8000; + + return 0; +} + +int game_genie_patch_t::apply( Nes_Cart& cart ) const +{ + // determine bank size + long bank_size = 32 * 1024L; // mappers 0, 2, 3, 7, 11, 34, 71, 87 + switch ( cart.mapper_code() ) + { + case 1: // MMC1 + case 71: // Camerica + case 232: // Quattro + bank_size = 16 * 1024L; + break; + + case 4: // MMC3 + case 5: // MMC5 + case 24: // VRC6 + case 26: // VRC6 + case 69: // FME7 + bank_size = 8 * 1024L; + break; + } + + // patch each bank (not very good, since it might patch banks that never occupy + // that address) + int mask = (compare_with >= 0 ? ~0 : 0); + uint8_t* p = cart.prg() + addr % bank_size; + int count = 0; + for ( int n = cart.prg_size() / bank_size; n--; p += bank_size ) + { + if ( !((*p ^ compare_with) & mask) ) + { + *p = change_to; + count++; + } + } + return count; +} + +// Cheat_Value_Finder + +Cheat_Value_Finder::Cheat_Value_Finder() +{ + emu = NULL; +} + +void Cheat_Value_Finder::start( Nes_Emu* new_emu ) +{ + emu = new_emu; + pos = 0; + memcpy( original, emu->low_mem(), low_mem_size ); + memset( changed, 0, low_mem_size ); +} + +void Cheat_Value_Finder::rescan() +{ + uint8_t const* low_mem = emu->low_mem(); + for ( int i = 0; i < low_mem_size; i++ ) + changed [i] |= original [i] ^ low_mem [i]; + memcpy( original, emu->low_mem(), low_mem_size ); +} + +void Cheat_Value_Finder::search( int new_original, int new_changed ) +{ + original_value = new_original; + changed_value = new_changed; + pos = -1; +} + +int Cheat_Value_Finder::next_match( int* addr ) +{ + uint8_t const* low_mem = emu->low_mem(); + while ( ++pos < low_mem_size ) + { + if ( !changed [pos] ) + { + int old = (original [pos] - original_value) & 0xff; + int cur = (low_mem [pos] - changed_value) & 0xff; + + if ( old == cur ) + { + if ( addr ) + *addr = pos; + return (char) old; // sign-extend + } + } + } + + return no_match; +} + +int Cheat_Value_Finder::change_value( int new_value ) +{ + int result = emu->low_mem() [pos]; + emu->low_mem() [pos] = new_value; + return result; +} diff --git a/core/nes_util.h b/core/nes_util.h new file mode 100644 index 0000000..f604acb --- /dev/null +++ b/core/nes_util.h @@ -0,0 +1,64 @@ + +// Experimental utilities for NES emulator + +// Nes_Emu 0.7.0 + +#ifndef NES_UTIL_H +#define NES_UTIL_H + +#include +#include "blargg_common.h" + +class Nes_Emu; +class Nes_Cart; + +struct game_genie_patch_t +{ + unsigned addr; // always 0x8000 or greater + int change_to; + int compare_with; // if -1, always change byte + + // Decode Game Genie code + const char *decode( const char* in ); + + // Apply patch to cartridge data. Might not work for some codes, since this really + // requires emulator support. Returns number of bytes changed, where 0 + // means patch wasn't for that cartridge. + int apply( Nes_Cart& ) const; +}; + +class Cheat_Value_Finder { +public: + Cheat_Value_Finder(); + + // Start scanning emulator's memory for values that are constantly changing. + void start( Nes_Emu* ); + + // Rescan memory and eliminate any changed bytes from later matching. + // Should be called many times after begin_scan() and before begin_matching(). + void rescan(); + + // Start search for any bytes which changed by difference between original and + // changed values. + void search( int original, int changed ); + + // Get next match and return its delta from changed value (closer to 0 + // is more likely to be a match), or no_match if there are no more matches. + // Optionally returns address of matched byte. + enum { no_match = 0x100 }; + int next_match( int* addr = NULL ); + + // Change current match to new value. Returns previous value. + int change_value( int new_value ); + +private: + Nes_Emu* emu; + int original_value; + int changed_value; + int pos; + enum { low_mem_size = 0x800 }; + uint8_t original [low_mem_size]; + uint8_t changed [low_mem_size]; +}; + +#endif