Adding initial version
This commit is contained in:
parent
bab4370830
commit
d9ae898b76
|
@ -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.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 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.
|
|
@ -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.
|
||||||
|
|
|
@ -0,0 +1,423 @@
|
||||||
|
|
||||||
|
// Blip_Buffer 0.4.0. http://www.slack.net/~ant/
|
||||||
|
|
||||||
|
#include "Blip_Buffer.h"
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <math.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 */
|
||||||
|
|
||||||
|
#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));
|
||||||
|
}
|
|
@ -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<int quality,int range>
|
||||||
|
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<int quality,int range>
|
||||||
|
inline void Blip_Synth<quality,range>::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<int quality,int range>
|
||||||
|
void Blip_Synth<quality,range>::offset( blip_time_t t, int delta, Blip_Buffer* buf ) const
|
||||||
|
{
|
||||||
|
offset_resampled( t * buf->factor_ + buf->offset_, delta, buf );
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int quality,int range>
|
||||||
|
void Blip_Synth<quality,range>::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
|
|
@ -0,0 +1,115 @@
|
||||||
|
// File_Extractor 1.0.0. http://www.slack.net/~ant/
|
||||||
|
|
||||||
|
#include "Data_Reader.h"
|
||||||
|
|
||||||
|
#include "blargg_endian.h"
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
|
@ -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 <stdint.h>
|
||||||
|
#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
|
|
@ -0,0 +1,515 @@
|
||||||
|
|
||||||
|
// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/
|
||||||
|
|
||||||
|
#include "Effects_Buffer.h"
|
||||||
|
|
||||||
|
#include <string.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
|
||||||
|
|
||||||
|
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] );
|
||||||
|
}
|
|
@ -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 <stdint.h>
|
||||||
|
#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
|
|
@ -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();
|
||||||
|
}
|
|
@ -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
|
|
@ -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<class T>
|
||||||
|
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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -0,0 +1,120 @@
|
||||||
|
|
||||||
|
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||||
|
|
||||||
|
#include "Nes_Cart.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.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"
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
|
||||||
|
// NES cartridge data (PRG, CHR, mapper)
|
||||||
|
|
||||||
|
// Nes_Emu 0.7.0
|
||||||
|
|
||||||
|
#ifndef NES_CART_H
|
||||||
|
#define NES_CART_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#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
|
|
@ -0,0 +1,537 @@
|
||||||
|
|
||||||
|
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||||
|
|
||||||
|
#include "Nes_Core.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#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<Nes_State_*>(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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,126 @@
|
||||||
|
|
||||||
|
// NES 6502 CPU emulator
|
||||||
|
|
||||||
|
// Nes_Emu 0.7.0
|
||||||
|
|
||||||
|
#ifndef NES_CPU_H
|
||||||
|
#define NES_CPU_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#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
|
|
@ -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()
|
||||||
|
{
|
||||||
|
}
|
|
@ -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
|
|
@ -0,0 +1,519 @@
|
||||||
|
|
||||||
|
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||||
|
|
||||||
|
#include "Nes_Emu.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#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();
|
||||||
|
}
|
|
@ -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
|
|
@ -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 );
|
||||||
|
}
|
|
@ -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<class T>
|
||||||
|
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<class T>
|
||||||
|
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<class T>
|
||||||
|
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
|
|
@ -0,0 +1,110 @@
|
||||||
|
|
||||||
|
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||||
|
|
||||||
|
#include "Nes_Fme7_Apu.h"
|
||||||
|
|
||||||
|
#include <string.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"
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
|
@ -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 <stdint.h>
|
||||||
|
#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<blip_good_quality,1> 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
|
|
@ -0,0 +1,302 @@
|
||||||
|
|
||||||
|
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||||
|
|
||||||
|
#include "Nes_Mapper.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <string.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 */
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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 );
|
||||||
|
}
|
|
@ -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 <stdint.h>
|
||||||
|
#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<blip_good_quality,15> 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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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<blip_good_quality,1> 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<blip_med_quality,1> 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<blip_med_quality,1> 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<blip_med_quality,1> 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
|
|
@ -0,0 +1,657 @@
|
||||||
|
|
||||||
|
// Timing and behavior of PPU
|
||||||
|
|
||||||
|
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||||
|
|
||||||
|
#include "Nes_Ppu.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1,492 @@
|
||||||
|
|
||||||
|
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||||
|
|
||||||
|
#include "Nes_Ppu_Impl.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include "blargg_endian.h"
|
||||||
|
#include "Nes_State.h"
|
||||||
|
#include <stdint.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 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<int height>
|
||||||
|
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;
|
||||||
|
}
|
|
@ -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
|
|
@ -0,0 +1,464 @@
|
||||||
|
|
||||||
|
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||||
|
|
||||||
|
#include "Nes_Ppu_Rendering.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stddef.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"
|
||||||
|
|
||||||
|
#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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,284 @@
|
||||||
|
|
||||||
|
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||||
|
|
||||||
|
#include "Nes_State.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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<unsigned>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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 <stdint.h>
|
||||||
|
|
||||||
|
#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<blip_med_quality,1> saw_synth;
|
||||||
|
Blip_Synth<blip_good_quality,1> 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
|
|
@ -0,0 +1,205 @@
|
||||||
|
#include "Nes_Mapper.h"
|
||||||
|
#include "Nes_Vrc7.h"
|
||||||
|
#include "emu2413.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<blip_med_quality,2048*2> 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
|
|
@ -0,0 +1,107 @@
|
||||||
|
|
||||||
|
#include "abstract_file.h"
|
||||||
|
|
||||||
|
#include "blargg_config.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/* 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()
|
||||||
|
{
|
||||||
|
}
|
|
@ -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
|
|
@ -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<int mode>
|
||||||
|
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);
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
|
||||||
|
// NES APU state snapshot support
|
||||||
|
|
||||||
|
// Nes_Snd_Emu 0.1.7
|
||||||
|
|
||||||
|
#ifndef APU_STATE_H
|
||||||
|
#define APU_STATE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#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
|
|
@ -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 <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
/* 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<T> (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
|
|
@ -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
|
|
@ -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
|
|
@ -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 <string.h> /* memcpy(), memset(), memmove() */
|
||||||
|
#include <stddef.h> /* 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<typename T> T min( T x, T y ) { return x < y ? x : y; }
|
||||||
|
template<typename T> T max( T x, T y ) { return x > y ? x : y; }
|
||||||
|
|
||||||
|
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -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<<PG_BITS)
|
||||||
|
|
||||||
|
/* Phase increment counter */
|
||||||
|
#define DP_BITS 18
|
||||||
|
#define DP_WIDTH (1<<DP_BITS)
|
||||||
|
#define DP_BASE_BITS (DP_BITS - PG_BITS)
|
||||||
|
|
||||||
|
/* Dynamic range (Accuracy of sin table) */
|
||||||
|
#define DB_BITS 8
|
||||||
|
#define DB_STEP (48.0/(1<<DB_BITS))
|
||||||
|
#define DB_MUTE (1<<DB_BITS)
|
||||||
|
|
||||||
|
/* Dynamic range of envelope */
|
||||||
|
#define EG_STEP 0.375
|
||||||
|
#define EG_BITS 7
|
||||||
|
#define EG_MUTE (1<<EG_BITS)
|
||||||
|
|
||||||
|
/* Dynamic range of total level */
|
||||||
|
#define TL_STEP 0.75
|
||||||
|
#define TL_BITS 6
|
||||||
|
#define TL_MUTE (1<<TL_BITS)
|
||||||
|
|
||||||
|
/* Dynamic range of sustine level */
|
||||||
|
#define SL_STEP 3.0
|
||||||
|
#define SL_BITS 4
|
||||||
|
#define SL_MUTE (1<<SL_BITS)
|
||||||
|
|
||||||
|
|
||||||
|
/* Bits for Pitch and Amp modulator */
|
||||||
|
#define PM_PG_BITS 8
|
||||||
|
#define PM_PG_WIDTH (1<<PM_PG_BITS)
|
||||||
|
#define PM_DP_BITS 16
|
||||||
|
#define PM_DP_WIDTH (1<<PM_DP_BITS)
|
||||||
|
#define AM_PG_BITS 8
|
||||||
|
#define AM_PG_WIDTH (1<<AM_PG_BITS)
|
||||||
|
#define AM_DP_BITS 16
|
||||||
|
#define AM_DP_WIDTH (1<<AM_DP_BITS)
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef EMU2413_DLL_EXPORTS
|
||||||
|
#define EMU2413_API __declspec(dllexport)
|
||||||
|
#elif defined(EMU2413_DLL_IMPORTS)
|
||||||
|
#define EMU2413_API __declspec(dllimport)
|
||||||
|
#else
|
||||||
|
#define EMU2413_API
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define PI 3.14159265358979323846
|
||||||
|
|
||||||
|
enum {OPLL_VRC7_TONE=0} ;
|
||||||
|
|
||||||
|
/* voice data */
|
||||||
|
typedef struct {
|
||||||
|
e_uint32 TL,FB,EG,ML,AR,DR,SL,RR,KR,KL,AM,PM,WF ;
|
||||||
|
} OPLL_PATCH ;
|
||||||
|
|
||||||
|
/* slot */
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
OPLL_PATCH patch;
|
||||||
|
|
||||||
|
e_int32 type ; /* 0 : modulator 1 : carrier */
|
||||||
|
|
||||||
|
/* OUTPUT */
|
||||||
|
e_int32 feedback ;
|
||||||
|
e_int32 output[2] ; /* Output value of slot */
|
||||||
|
|
||||||
|
/* for Phase Generator (PG) */
|
||||||
|
e_uint16 *sintbl ; /* Wavetable */
|
||||||
|
e_uint32 phase ; /* Phase */
|
||||||
|
e_uint32 dphase ; /* Phase increment amount */
|
||||||
|
e_uint32 pgout ; /* output */
|
||||||
|
|
||||||
|
/* for Envelope Generator (EG) */
|
||||||
|
e_int32 fnum ; /* F-Number */
|
||||||
|
e_int32 block ; /* Block */
|
||||||
|
e_int32 volume ; /* Current volume */
|
||||||
|
e_int32 sustine ; /* Sustine 1 = ON, 0 = OFF */
|
||||||
|
e_uint32 tll ; /* Total Level + Key scale level*/
|
||||||
|
e_uint32 rks ; /* Key scale offset (Rks) */
|
||||||
|
e_int32 eg_mode ; /* Current state */
|
||||||
|
e_uint32 eg_phase ; /* Phase */
|
||||||
|
e_uint32 eg_dphase ; /* Phase increment amount */
|
||||||
|
e_uint32 egout ; /* output */
|
||||||
|
|
||||||
|
} OPLL_SLOT ;
|
||||||
|
|
||||||
|
/* Mask */
|
||||||
|
#define OPLL_MASK_CH(x) (1<<(x))
|
||||||
|
|
||||||
|
/* opll */
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
e_uint32 adr ;
|
||||||
|
e_int32 out ;
|
||||||
|
|
||||||
|
#ifndef EMU2413_COMPACTION
|
||||||
|
e_uint32 realstep ;
|
||||||
|
e_uint32 oplltime ;
|
||||||
|
e_uint32 opllstep ;
|
||||||
|
e_int32 prev, next ;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Register */
|
||||||
|
e_uint8 LowFreq[6];
|
||||||
|
e_uint8 HiFreq[6];
|
||||||
|
e_uint8 InstVol[6];
|
||||||
|
|
||||||
|
e_uint8 CustInst[8];
|
||||||
|
|
||||||
|
e_int32 slot_on_flag[6 * 2] ;
|
||||||
|
|
||||||
|
/* Pitch Modulator */
|
||||||
|
e_uint32 pm_phase ;
|
||||||
|
e_int32 lfo_pm ;
|
||||||
|
|
||||||
|
/* Amp Modulator */
|
||||||
|
e_int32 am_phase ;
|
||||||
|
e_int32 lfo_am ;
|
||||||
|
|
||||||
|
e_uint32 quality;
|
||||||
|
|
||||||
|
/* Channel Data */
|
||||||
|
e_int32 patch_number[6];
|
||||||
|
e_int32 key_status[6] ;
|
||||||
|
|
||||||
|
/* Slot */
|
||||||
|
OPLL_SLOT slot[6 * 2] ;
|
||||||
|
|
||||||
|
e_uint32 mask ;
|
||||||
|
|
||||||
|
/* Input clock */
|
||||||
|
e_uint32 clk;
|
||||||
|
|
||||||
|
/* WaveTable for each envelope amp */
|
||||||
|
e_uint16 fullsintable[PG_WIDTH];
|
||||||
|
e_uint16 halfsintable[PG_WIDTH];
|
||||||
|
|
||||||
|
e_uint16 *waveform[2];
|
||||||
|
|
||||||
|
/* LFO Table */
|
||||||
|
e_int32 pmtable[PM_PG_WIDTH];
|
||||||
|
e_int32 amtable[AM_PG_WIDTH];
|
||||||
|
|
||||||
|
/* Phase delta for LFO */
|
||||||
|
e_uint32 pm_dphase;
|
||||||
|
e_uint32 am_dphase;
|
||||||
|
|
||||||
|
/* dB to Liner table */
|
||||||
|
e_int16 DB2LIN_TABLE[(DB_MUTE + DB_MUTE) * 2];
|
||||||
|
|
||||||
|
/* Liner to Log curve conversion table (for Attack rate). */
|
||||||
|
e_uint16 AR_ADJUST_TABLE[1 << EG_BITS];
|
||||||
|
|
||||||
|
/* Phase incr table for Attack */
|
||||||
|
e_uint32 dphaseARTable[16][16];
|
||||||
|
|
||||||
|
/* Phase incr table for Decay and Release */
|
||||||
|
e_uint32 dphaseDRTable[16][16];
|
||||||
|
|
||||||
|
/* KSL + TL Table */
|
||||||
|
e_uint32 tllTable[16][8][1 << TL_BITS][4];
|
||||||
|
e_int32 rksTable[2][8][2];
|
||||||
|
|
||||||
|
/* Phase incr table for PG */
|
||||||
|
e_uint32 dphaseTable[512][8][16];
|
||||||
|
} OPLL ;
|
||||||
|
|
||||||
|
/* Create Object */
|
||||||
|
EMU2413_API OPLL *OPLL_new(e_uint32 clk) ;
|
||||||
|
EMU2413_API void OPLL_delete(OPLL *) ;
|
||||||
|
|
||||||
|
/* Setup */
|
||||||
|
EMU2413_API void OPLL_reset(OPLL *) ;
|
||||||
|
//EMU2413_API void OPLL_set_rate(OPLL *opll, e_uint32 r) ;
|
||||||
|
|
||||||
|
/* Port/Register access */
|
||||||
|
EMU2413_API void OPLL_writeIO(OPLL *, e_uint32 reg, e_uint32 val) ;
|
||||||
|
EMU2413_API void OPLL_writeReg(OPLL *, e_uint32 reg, e_uint32 val) ;
|
||||||
|
|
||||||
|
/* Synthsize */
|
||||||
|
EMU2413_API e_int16 OPLL_calc(OPLL *) ;
|
||||||
|
/* or */
|
||||||
|
EMU2413_API void OPLL_run(OPLL *) ;
|
||||||
|
EMU2413_API e_uint32 OPLL_calcCh(OPLL *, e_uint32 ch) ;
|
||||||
|
|
||||||
|
/* Misc */
|
||||||
|
EMU2413_API void OPLL_forceRefresh(OPLL *) ;
|
||||||
|
|
||||||
|
/* Channel Mask */
|
||||||
|
EMU2413_API e_uint32 OPLL_setMask(OPLL *, e_uint32 mask) ;
|
||||||
|
EMU2413_API e_uint32 OPLL_toggleMask(OPLL *, e_uint32 mask) ;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,107 @@
|
||||||
|
#include "emu2413_state.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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
|
|
@ -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
|
|
@ -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
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,121 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||||
|
|
||||||
|
#include "Nes_Mapper.h"
|
||||||
|
|
||||||
|
#include <string.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"
|
||||||
|
|
||||||
|
// 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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,241 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||||
|
|
||||||
|
#include "Nes_Mapper.h"
|
||||||
|
|
||||||
|
#include <string.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 */
|
||||||
|
|
||||||
|
#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
|
||||||
|
};
|
||||||
|
|
|
@ -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 <string.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 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
#pragma once
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
};
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -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 <bool type_a, bool type_b>
|
||||||
|
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 <bool type_a, bool type_b>
|
||||||
|
void Mapper_VRC2_4<type_a, type_b>::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<true,true> Mapper021;
|
|
@ -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<false,true> Mapper022;
|
|
@ -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<false, false> Mapper023;
|
|
@ -0,0 +1,244 @@
|
||||||
|
|
||||||
|
// Konami VRC6 mapper
|
||||||
|
|
||||||
|
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||||
|
|
||||||
|
#include "Nes_Mapper.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#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 <int swapMask>
|
||||||
|
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;
|
|
@ -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<true,false> Mapper025;
|
|
@ -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;
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 );
|
||||||
|
// }
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -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<false> Mapper079;
|
|
@ -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 <string.h>
|
||||||
|
|
||||||
|
#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 };
|
||||||
|
};
|
||||||
|
|
|
@ -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;
|
|
@ -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 ) { }
|
||||||
|
};
|
||||||
|
|
|
@ -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<false> Mapper088;
|
||||||
|
|
||||||
|
|
||||||
|
// void register_mapper_namco_34xx();
|
||||||
|
// void register_mapper_namco_34xx()
|
||||||
|
// {
|
||||||
|
// register_mapper< Mapper_Namco_34x3 <false> > ( 88 );
|
||||||
|
// register_mapper< Mapper_Namco_34x3 <true> > ( 154 );
|
||||||
|
// register_mapper< Mapper_Namco_34xx > ( 206 );
|
||||||
|
// }
|
|
@ -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;
|
||||||
|
};
|
|
@ -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;
|
||||||
|
};
|
|
@ -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;
|
||||||
|
};
|
|
@ -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;
|
||||||
|
};
|
|
@ -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<true> Mapper113;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue