mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-25 22:15:14 +00:00
prepare the addition of more OPL emulation cores
This commit is contained in:
parent
a9dd2ee6b5
commit
1bcdedda3e
13 changed files with 8560 additions and 0 deletions
|
@ -556,9 +556,11 @@ src/engine/platform/sound/tia/AudioChannel.cpp
|
|||
src/engine/platform/sound/tia/Audio.cpp
|
||||
|
||||
src/engine/platform/sound/ymfm/ymfm_adpcm.cpp
|
||||
src/engine/platform/sound/ymfm/ymfm_opl.cpp
|
||||
src/engine/platform/sound/ymfm/ymfm_opm.cpp
|
||||
src/engine/platform/sound/ymfm/ymfm_opn.cpp
|
||||
src/engine/platform/sound/ymfm/ymfm_opz.cpp
|
||||
src/engine/platform/sound/ymfm/ymfm_pcm.cpp
|
||||
src/engine/platform/sound/ymfm/ymfm_ssg.cpp
|
||||
|
||||
src/engine/platform/sound/lynx/Mikey.cpp
|
||||
|
|
339
extern/YM3812-LLE/LICENSE
vendored
Normal file
339
extern/YM3812-LLE/LICENSE
vendored
Normal file
|
@ -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.
|
13
extern/YM3812-LLE/Readme.md
vendored
Normal file
13
extern/YM3812-LLE/Readme.md
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
# YM3812-LLE
|
||||
|
||||
Yamaha YM3812 (OPL2) emulator using YM3812 die shot.
|
||||
|
||||
Special thanks to Travis Goodspeed for decapping YM3812.
|
||||
|
||||
https://twitter.com/travisgoodspeed/status/1652334901230723072
|
||||
|
||||
# MODIFICATION DISCLAIMER
|
||||
|
||||
this is a modified version of YM3812-LLE which adds functions to allow its usage.
|
||||
|
||||
the original Git commit is 7f0c6537ccd61e9e7dbddb4e4a353e007ea69c50.
|
1501
extern/YM3812-LLE/fmopl2.c
vendored
Normal file
1501
extern/YM3812-LLE/fmopl2.c
vendored
Normal file
File diff suppressed because it is too large
Load diff
272
extern/YM3812-LLE/fmopl2.h
vendored
Normal file
272
extern/YM3812-LLE/fmopl2.h
vendored
Normal file
|
@ -0,0 +1,272 @@
|
|||
/*
|
||||
* Copyright (C) 2023 nukeykt
|
||||
*
|
||||
* This file is part of YM3812-LLE.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* YM3812 emulator
|
||||
* Thanks:
|
||||
* Travis Goodspeed:
|
||||
* YM3812 decap and die shot
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int mclk;
|
||||
int address;
|
||||
int data_i;
|
||||
int ic;
|
||||
int cs;
|
||||
int rd;
|
||||
int wr;
|
||||
} fmopl2_input_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
fmopl2_input_t input;
|
||||
|
||||
int mclk1;
|
||||
int mclk2;
|
||||
int clk1;
|
||||
int clk2;
|
||||
|
||||
int prescaler_reset_l[2];
|
||||
int prescaler_cnt[2];
|
||||
int prescaler_l1[2];
|
||||
int prescaler_l2[2];
|
||||
|
||||
int reset1;
|
||||
|
||||
int fsm_reset_l[2];
|
||||
int fsm_reset; // wire
|
||||
int fsm_cnt1[2];
|
||||
int fsm_cnt2[2];
|
||||
int fsm_cnt1_of; // wire
|
||||
int fsm_cnt2_of; // wire
|
||||
int fsm_sel[13];
|
||||
int fsm_cnt; // wire
|
||||
int fsm_ch_out;
|
||||
int fsm_do_fb;
|
||||
int fsm_load_fb;
|
||||
int fsm_l1[2];
|
||||
int fsm_l2[2];
|
||||
int fsm_l3[2];
|
||||
int fsm_l4[2];
|
||||
int fsm_l5[2];
|
||||
int fsm_l6[2];
|
||||
int fsm_out[16];
|
||||
|
||||
int io_rd;
|
||||
int io_wr;
|
||||
int io_cs;
|
||||
int io_a0;
|
||||
|
||||
int io_read0;
|
||||
int io_read1;
|
||||
int io_write;
|
||||
int io_write0;
|
||||
int io_write1;
|
||||
int io_dir;
|
||||
int io_data;
|
||||
|
||||
int data_latch;
|
||||
|
||||
int write0;
|
||||
int write0_sr;
|
||||
int write0_latch[6];
|
||||
int write1;
|
||||
int write1_sr;
|
||||
int write1_latch[6];
|
||||
|
||||
int reg_sel1;
|
||||
int reg_sel2;
|
||||
int reg_sel3;
|
||||
int reg_sel4;
|
||||
int reg_sel8;
|
||||
int reg_selbd;
|
||||
int reg_test;
|
||||
int reg_timer1;
|
||||
int reg_timer2;
|
||||
int reg_notesel;
|
||||
int reg_csm;
|
||||
int reg_da;
|
||||
int reg_dv;
|
||||
int rhythm;
|
||||
int reg_rh_kon;
|
||||
int reg_sel4_wr; // wire
|
||||
int reg_sel4_rst; // wire
|
||||
int reg_t1_mask;
|
||||
int reg_t2_mask;
|
||||
int reg_t1_start;
|
||||
int reg_t2_start;
|
||||
int reg_mode_b3;
|
||||
int reg_mode_b4;
|
||||
|
||||
int t1_cnt[2];
|
||||
int t2_cnt[2];
|
||||
int t1_of[2];
|
||||
int t2_of[2];
|
||||
int t1_status;
|
||||
int t2_status;
|
||||
int unk_status1;
|
||||
int unk_status2;
|
||||
int timer_st_load_l;
|
||||
int timer_st_load;
|
||||
int t1_start;
|
||||
int t1_start_l[2];
|
||||
int t2_start_l[2];
|
||||
int t1_load; // wire
|
||||
int csm_load_l;
|
||||
int csm_load;
|
||||
int csm_kon;
|
||||
int rh_sel0;
|
||||
int rh_sel[2];
|
||||
|
||||
int keyon_comb;
|
||||
int address;
|
||||
int address_valid;
|
||||
int address_valid_l[2];
|
||||
int address_valid2;
|
||||
int data;
|
||||
int slot_cnt1[2];
|
||||
int slot_cnt2[2];
|
||||
int slot_cnt;
|
||||
int sel_ch;
|
||||
|
||||
int ch_fnum[10][2];
|
||||
int ch_block[3][2];
|
||||
int ch_keyon[2];
|
||||
int ch_connect[2];
|
||||
int ch_fb[3][2];
|
||||
int op_multi[4][2];
|
||||
int op_ksr[2];
|
||||
int op_egt[2];
|
||||
int op_vib[2];
|
||||
int op_am[2];
|
||||
int op_tl[6][2];
|
||||
int op_ksl[2][2];
|
||||
int op_ar[4][2];
|
||||
int op_dr[4][2];
|
||||
int op_sl[4][2];
|
||||
int op_rr[4][2];
|
||||
int op_wf[2][2];
|
||||
int op_mod[2];
|
||||
int op_value; // wire
|
||||
|
||||
int eg_load1_l;
|
||||
int eg_load1;
|
||||
int eg_load2_l;
|
||||
int eg_load2;
|
||||
int eg_load3_l;
|
||||
int eg_load3;
|
||||
|
||||
int trem_carry[2];
|
||||
int trem_value[2];
|
||||
int trem_dir[2];
|
||||
int trem_step;
|
||||
int trem_out;
|
||||
int trem_of[2];
|
||||
|
||||
int eg_timer[2];
|
||||
int eg_timer_masked[2];
|
||||
int eg_carry[2];
|
||||
int eg_mask[2];
|
||||
int eg_subcnt[2];
|
||||
int eg_subcnt_l[2];
|
||||
int eg_sync_l[2];
|
||||
int eg_timer_low;
|
||||
int eg_shift;
|
||||
int eg_state[2][2];
|
||||
int eg_level[9][2];
|
||||
int eg_out[2];
|
||||
int eg_dokon; // wire
|
||||
int eg_mute[2];
|
||||
|
||||
int block;
|
||||
int fnum;
|
||||
int keyon;
|
||||
int connect;
|
||||
int connect_l[2];
|
||||
int fb;
|
||||
int fb_l[2][2];
|
||||
int multi;
|
||||
int ksr;
|
||||
int egt;
|
||||
int vib;
|
||||
int am;
|
||||
int tl;
|
||||
int ksl;
|
||||
int ar;
|
||||
int dr;
|
||||
int sl;
|
||||
int rr;
|
||||
int wf;
|
||||
|
||||
int lfo_cnt[2];
|
||||
int t1_step; // wire
|
||||
int t2_step; // wire
|
||||
int am_step; // wire
|
||||
int vib_step; // wire
|
||||
int vib_cnt[2];
|
||||
int pg_phase[19][2];
|
||||
int dbg_serial[2];
|
||||
|
||||
int noise_lfsr[2];
|
||||
|
||||
int hh_load;
|
||||
int tc_load;
|
||||
int hh_bit2;
|
||||
int hh_bit3;
|
||||
int hh_bit7;
|
||||
int hh_bit8;
|
||||
int tc_bit3;
|
||||
int tc_bit5;
|
||||
int op_logsin[2];
|
||||
int op_shift[2];
|
||||
int op_pow[2];
|
||||
int op_mute[2];
|
||||
int op_sign[2];
|
||||
int op_fb[2][13][2];
|
||||
|
||||
int pg_out; // wire
|
||||
int pg_out_rhy; // wire
|
||||
|
||||
int accm_value[2];
|
||||
int accm_shifter[2];
|
||||
int accm_load1_l;
|
||||
int accm_load1;
|
||||
int accm_clamplow;
|
||||
int accm_clamphigh;
|
||||
int accm_top;
|
||||
int accm_sel[2];
|
||||
int accm_mo[2];
|
||||
|
||||
int o_sh;
|
||||
int o_mo;
|
||||
int o_irq_pull;
|
||||
int o_sy;
|
||||
|
||||
int data_o;
|
||||
int data_z;
|
||||
|
||||
int o_clk1;
|
||||
int o_clk2;
|
||||
int o_reset1;
|
||||
int o_write0;
|
||||
int o_write1;
|
||||
int o_data_latch;
|
||||
|
||||
} fmopl2_t;
|
||||
|
339
extern/YMF262-LLE/LICENSE
vendored
Normal file
339
extern/YMF262-LLE/LICENSE
vendored
Normal file
|
@ -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.
|
13
extern/YMF262-LLE/Readme.md
vendored
Normal file
13
extern/YMF262-LLE/Readme.md
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
# YMF262-LLE
|
||||
|
||||
Yamaha YMF262 (OPL3) emulator using YMF262 die shot.
|
||||
|
||||
Special thanks to John McMaster for decapping YMF262.
|
||||
|
||||
https://siliconpr0n.org/map/yamaha/ymf262-m/
|
||||
|
||||
# MODIFICATION DISCLAIMER
|
||||
|
||||
this is a modified version of YMF262-LLE which adds functions to allow its usage.
|
||||
|
||||
the original Git commit is 63406354d05bc860a6762377ddbb9e2609bd6c36.
|
1646
extern/YMF262-LLE/fmopl3.c
vendored
Normal file
1646
extern/YMF262-LLE/fmopl3.c
vendored
Normal file
File diff suppressed because it is too large
Load diff
333
extern/YMF262-LLE/fmopl3.h
vendored
Normal file
333
extern/YMF262-LLE/fmopl3.h
vendored
Normal file
|
@ -0,0 +1,333 @@
|
|||
/*
|
||||
* Copyright (C) 2023 nukeykt
|
||||
*
|
||||
* This file is part of YMF262-LLE.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* YMF262 emulator
|
||||
* Thanks:
|
||||
* John McMaster (siliconpr0n.org):
|
||||
* YMF262 decap and die shot
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int mclk;
|
||||
int address;
|
||||
int data_i;
|
||||
int ic;
|
||||
int cs;
|
||||
int rd;
|
||||
int wr;
|
||||
} fmopl3_input_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
fmopl3_input_t input;
|
||||
|
||||
int mclk1;
|
||||
int mclk2;
|
||||
int aclk1;
|
||||
int aclk2;
|
||||
int clk1;
|
||||
int clk2;
|
||||
int rclk1;
|
||||
int rclk2;
|
||||
|
||||
int o_clk1;
|
||||
int o_clk2;
|
||||
int o_rclk1;
|
||||
int o_rclk2;
|
||||
int o_wrcheck;
|
||||
int o_data_latch;
|
||||
int o_bank_latch;
|
||||
int o_reset0;
|
||||
int o_ra_w1_l1;
|
||||
|
||||
int prescaler1_reset[2];
|
||||
int prescaler1_cnt[2];
|
||||
|
||||
int prescaler2_reset_l[2];
|
||||
int prescaler2_cnt[2];
|
||||
int prescaler2_reset;
|
||||
int prescaler2_l1[2];
|
||||
int prescaler2_l2;
|
||||
int prescaler2_l3[2];
|
||||
int prescaler2_l4;
|
||||
int prescaler2_l5[2];
|
||||
int prescaler2_l6[2];
|
||||
int prescaler2_l7;
|
||||
|
||||
int fsm_cnt1[2];
|
||||
int fsm_cnt2[2];
|
||||
int fsm_cnt3[2];
|
||||
int fsm_cnt;
|
||||
|
||||
int fsm_reset_l[2];
|
||||
int fsm_out[17];
|
||||
int fsm_l1[2];
|
||||
int fsm_l2[2];
|
||||
int fsm_l3[2];
|
||||
int fsm_l4[2];
|
||||
int fsm_l5[2];
|
||||
int fsm_l6[2];
|
||||
int fsm_l7[2];
|
||||
int fsm_l8[2];
|
||||
int fsm_l9[2];
|
||||
int fsm_l10[2];
|
||||
|
||||
int ic_latch[2];
|
||||
|
||||
int io_rd;
|
||||
int io_wr;
|
||||
int io_cs;
|
||||
int io_a0;
|
||||
int io_a1;
|
||||
|
||||
int io_read;
|
||||
int io_write;
|
||||
int io_write0;
|
||||
int io_write1;
|
||||
int io_bank;
|
||||
|
||||
int data_latch;
|
||||
int bank_latch;
|
||||
int bank_masked;
|
||||
|
||||
int reg_sel1;
|
||||
int reg_sel2;
|
||||
int reg_sel3;
|
||||
int reg_sel4;
|
||||
int reg_sel5;
|
||||
int reg_sel8;
|
||||
int reg_selbd;
|
||||
|
||||
int reg_test0;
|
||||
int reg_timer1;
|
||||
int reg_timer2;
|
||||
int reg_notesel;
|
||||
int rhythm;
|
||||
int reg_rh_kon;
|
||||
int reg_da;
|
||||
int reg_dv;
|
||||
|
||||
int reg_test1;
|
||||
int reg_new;
|
||||
int reg_4op;
|
||||
|
||||
int reg_t1_mask;
|
||||
int reg_t2_mask;
|
||||
int reg_t1_start;
|
||||
int reg_t2_start;
|
||||
|
||||
int lfo_cnt[2];
|
||||
int vib_cnt[2];
|
||||
int t1_step;
|
||||
int t2_step;
|
||||
int am_step;
|
||||
int vib_step;
|
||||
|
||||
int rh_sel0;
|
||||
int rh_sel[2];
|
||||
|
||||
int keyon_comb;
|
||||
|
||||
int ra_address_latch;
|
||||
int ra_address_good;
|
||||
int ra_data_latch;
|
||||
int ra_cnt1[2];
|
||||
int ra_cnt2[2];
|
||||
int ra_cnt3[2];
|
||||
int ra_cnt4[2];
|
||||
int ra_cnt;
|
||||
int ra_rst_l[2];
|
||||
int ra_w1_l1;
|
||||
int ra_w1_l2;
|
||||
int ra_write;
|
||||
int ra_write_a;
|
||||
|
||||
int ra_multi[36];
|
||||
int ra_ksr[36];
|
||||
int ra_egt[36];
|
||||
int ra_am[36];
|
||||
int ra_vib[36];
|
||||
int ra_tl[36];
|
||||
int ra_ksl[36];
|
||||
int ra_ar[36];
|
||||
int ra_dr[36];
|
||||
int ra_sl[36];
|
||||
int ra_rr[36];
|
||||
int ra_wf[36];
|
||||
int ra_fnum[18];
|
||||
int ra_block[18];
|
||||
int ra_keyon[18];
|
||||
int ra_connect[18];
|
||||
int ra_fb[18];
|
||||
int ra_pan[18];
|
||||
int ra_connect_pair[18];
|
||||
int multi[2];
|
||||
int ksr[2];
|
||||
int egt[2];
|
||||
int am[2];
|
||||
int vib[2];
|
||||
int tl[2];
|
||||
int ksl[2];
|
||||
int ar[2];
|
||||
int dr[2];
|
||||
int sl[2];
|
||||
int rr[2];
|
||||
int wf[2];
|
||||
int fnum[2];
|
||||
int block[2];
|
||||
int keyon[2];
|
||||
int connect[2];
|
||||
int fb[2];
|
||||
int pan[2];
|
||||
int connect_pair[2];
|
||||
|
||||
int64_t ra_dbg1[2];
|
||||
int ra_dbg2[2];
|
||||
int ra_dbg_load[2];
|
||||
|
||||
int fb_l[2][2];
|
||||
int pan_l[2][2];
|
||||
|
||||
int write0_sr;
|
||||
int write0_l[4];
|
||||
int write0;
|
||||
|
||||
int write1_sr;
|
||||
int write1_l[4];
|
||||
int write1;
|
||||
|
||||
int connect_l[2];
|
||||
int connect_pair_l[2];
|
||||
|
||||
int t1_cnt[2];
|
||||
int t2_cnt[2];
|
||||
int t1_of[2];
|
||||
int t2_of[2];
|
||||
int t1_status;
|
||||
int t2_status;
|
||||
int timer_st_load_l;
|
||||
int timer_st_load;
|
||||
int t1_start;
|
||||
int t2_start;
|
||||
int t1_start_l[2];
|
||||
int t2_start_l[2];
|
||||
|
||||
int reset0;
|
||||
int reset1;
|
||||
|
||||
int pg_phase_o[4];
|
||||
int pg_dbg[2];
|
||||
int pg_dbg_load_l[2];
|
||||
int noise_lfsr[2];
|
||||
int pg_index[2];
|
||||
int pg_cells[36];
|
||||
int pg_out_rhy;
|
||||
|
||||
int trem_load_l;
|
||||
int trem_load;
|
||||
int trem_st_load_l;
|
||||
int trem_st_load;
|
||||
int trem_carry[2];
|
||||
int trem_value[2];
|
||||
int trem_dir[2];
|
||||
int trem_step;
|
||||
int trem_out;
|
||||
int trem_of[2];
|
||||
|
||||
int eg_load_l1[2];
|
||||
int eg_load_l;
|
||||
int eg_load;
|
||||
|
||||
int64_t eg_timer_masked[2];
|
||||
int eg_carry[2];
|
||||
int eg_mask[2];
|
||||
int eg_subcnt[2];
|
||||
int eg_subcnt_l[2];
|
||||
int eg_sync_l[2];
|
||||
int eg_timer_low;
|
||||
int eg_shift;
|
||||
int eg_timer_dbg[2];
|
||||
|
||||
int eg_timer_i;
|
||||
int eg_timer_o[4];
|
||||
int eg_state_o[4];
|
||||
int eg_level_o[4];
|
||||
int eg_index[2];
|
||||
int eg_cells[36];
|
||||
|
||||
int eg_out[2];
|
||||
int eg_dbg[2];
|
||||
int eg_dbg_load_l[2];
|
||||
|
||||
int hh_load;
|
||||
int tc_load;
|
||||
int hh_bit2;
|
||||
int hh_bit3;
|
||||
int hh_bit7;
|
||||
int hh_bit8;
|
||||
int tc_bit3;
|
||||
int tc_bit5;
|
||||
|
||||
int op_logsin[2];
|
||||
int op_saw[2];
|
||||
int op_saw_phase[2];
|
||||
int op_shift[2];
|
||||
int op_pow[2];
|
||||
int op_mute[2];
|
||||
int op_sign[2];
|
||||
int op_fb[4][13][2];
|
||||
int op_mod[2];
|
||||
|
||||
int op_value;
|
||||
|
||||
int accm_a[2];
|
||||
int accm_b[2];
|
||||
int accm_c[2];
|
||||
int accm_d[2];
|
||||
int accm_shift_a[2];
|
||||
int accm_shift_b[2];
|
||||
int accm_shift_c[2];
|
||||
int accm_shift_d[2];
|
||||
int accm_load_ac_l;
|
||||
int accm_load_ac;
|
||||
int accm_load_bd_l;
|
||||
int accm_load_bd;
|
||||
int accm_a_of;
|
||||
int accm_a_sign;
|
||||
int accm_b_of;
|
||||
int accm_b_sign;
|
||||
int accm_c_of;
|
||||
int accm_c_sign;
|
||||
int accm_d_of;
|
||||
int accm_d_sign;
|
||||
|
||||
int o_doab;
|
||||
int o_docd;
|
||||
int o_sy;
|
||||
int o_smpac;
|
||||
int o_smpbd;
|
||||
int o_irq_pull;
|
||||
int o_test;
|
||||
|
||||
int data_o;
|
||||
int data_z;
|
||||
} fmopl3_t;
|
||||
|
2139
src/engine/platform/sound/ymfm/ymfm_opl.cpp
Normal file
2139
src/engine/platform/sound/ymfm/ymfm_opl.cpp
Normal file
File diff suppressed because it is too large
Load diff
902
src/engine/platform/sound/ymfm/ymfm_opl.h
Normal file
902
src/engine/platform/sound/ymfm/ymfm_opl.h
Normal file
|
@ -0,0 +1,902 @@
|
|||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2021, Aaron Giles
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef YMFM_OPL_H
|
||||
#define YMFM_OPL_H
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ymfm.h"
|
||||
#include "ymfm_adpcm.h"
|
||||
#include "ymfm_fm.h"
|
||||
#include "ymfm_pcm.h"
|
||||
|
||||
namespace ymfm
|
||||
{
|
||||
|
||||
//*********************************************************
|
||||
// REGISTER CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// ======================> opl_registers_base
|
||||
|
||||
//
|
||||
// OPL/OPL2/OPL3/OPL4 register map:
|
||||
//
|
||||
// System-wide registers:
|
||||
// 01 xxxxxxxx Test register
|
||||
// --x----- Enable OPL compatibility mode [OPL2 only] (1 = enable)
|
||||
// 02 xxxxxxxx Timer A value (4 * OPN)
|
||||
// 03 xxxxxxxx Timer B value
|
||||
// 04 x------- RST
|
||||
// -x------ Mask timer A
|
||||
// --x----- Mask timer B
|
||||
// ------x- Load timer B
|
||||
// -------x Load timer A
|
||||
// 08 x------- CSM mode [OPL/OPL2 only]
|
||||
// -x------ Note select
|
||||
// BD x------- AM depth
|
||||
// -x------ PM depth
|
||||
// --x----- Rhythm enable
|
||||
// ---x---- Bass drum key on
|
||||
// ----x--- Snare drum key on
|
||||
// -----x-- Tom key on
|
||||
// ------x- Top cymbal key on
|
||||
// -------x High hat key on
|
||||
// 101 --xxxxxx Test register 2 [OPL3 only]
|
||||
// 104 --x----- Channel 6 4-operator mode [OPL3 only]
|
||||
// ---x---- Channel 5 4-operator mode [OPL3 only]
|
||||
// ----x--- Channel 4 4-operator mode [OPL3 only]
|
||||
// -----x-- Channel 3 4-operator mode [OPL3 only]
|
||||
// ------x- Channel 2 4-operator mode [OPL3 only]
|
||||
// -------x Channel 1 4-operator mode [OPL3 only]
|
||||
// 105 -------x New [OPL3 only]
|
||||
// ------x- New2 [OPL4 only]
|
||||
//
|
||||
// Per-channel registers (channel in address bits 0-3)
|
||||
// Note that all these apply to address+100 as well on OPL3+
|
||||
// A0-A8 xxxxxxxx F-number (low 8 bits)
|
||||
// B0-B8 --x----- Key on
|
||||
// ---xxx-- Block (octvate, 0-7)
|
||||
// ------xx F-number (high two bits)
|
||||
// C0-C8 x------- CHD output (to DO0 pin) [OPL3+ only]
|
||||
// -x------ CHC output (to DO0 pin) [OPL3+ only]
|
||||
// --x----- CHB output (mixed right, to DO2 pin) [OPL3+ only]
|
||||
// ---x---- CHA output (mixed left, to DO2 pin) [OPL3+ only]
|
||||
// ----xxx- Feedback level for operator 1 (0-7)
|
||||
// -------x Operator connection algorithm
|
||||
//
|
||||
// Per-operator registers (operator in bits 0-5)
|
||||
// Note that all these apply to address+100 as well on OPL3+
|
||||
// 20-35 x------- AM enable
|
||||
// -x------ PM enable (VIB)
|
||||
// --x----- EG type
|
||||
// ---x---- Key scale rate
|
||||
// ----xxxx Multiple value (0-15)
|
||||
// 40-55 xx------ Key scale level (0-3)
|
||||
// --xxxxxx Total level (0-63)
|
||||
// 60-75 xxxx---- Attack rate (0-15)
|
||||
// ----xxxx Decay rate (0-15)
|
||||
// 80-95 xxxx---- Sustain level (0-15)
|
||||
// ----xxxx Release rate (0-15)
|
||||
// E0-F5 ------xx Wave select (0-3) [OPL2 only]
|
||||
// -----xxx Wave select (0-7) [OPL3+ only]
|
||||
//
|
||||
|
||||
template<int Revision>
|
||||
class opl_registers_base : public fm_registers_base
|
||||
{
|
||||
static constexpr bool IsOpl2 = (Revision == 2);
|
||||
static constexpr bool IsOpl2Plus = (Revision >= 2);
|
||||
static constexpr bool IsOpl3Plus = (Revision >= 3);
|
||||
static constexpr bool IsOpl4Plus = (Revision >= 4);
|
||||
|
||||
public:
|
||||
// constants
|
||||
static constexpr uint32_t OUTPUTS = IsOpl3Plus ? 4 : 1;
|
||||
static constexpr uint32_t CHANNELS = IsOpl3Plus ? 18 : 9;
|
||||
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
|
||||
static constexpr uint32_t OPERATORS = CHANNELS * 2;
|
||||
static constexpr uint32_t WAVEFORMS = IsOpl3Plus ? 8 : (IsOpl2Plus ? 4 : 1);
|
||||
static constexpr uint32_t REGISTERS = IsOpl3Plus ? 0x200 : 0x100;
|
||||
static constexpr uint32_t REG_MODE = 0x04;
|
||||
static constexpr uint32_t DEFAULT_PRESCALE = IsOpl4Plus ? 19 : (IsOpl3Plus ? 8 : 4);
|
||||
static constexpr uint32_t EG_CLOCK_DIVIDER = 1;
|
||||
static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS;
|
||||
static constexpr bool DYNAMIC_OPS = IsOpl3Plus;
|
||||
static constexpr bool MODULATOR_DELAY = !IsOpl3Plus;
|
||||
static constexpr uint8_t STATUS_TIMERA = 0x40;
|
||||
static constexpr uint8_t STATUS_TIMERB = 0x20;
|
||||
static constexpr uint8_t STATUS_BUSY = 0;
|
||||
static constexpr uint8_t STATUS_IRQ = 0x80;
|
||||
|
||||
// constructor
|
||||
opl_registers_base();
|
||||
|
||||
// reset to initial state
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// map channel number to register offset
|
||||
static constexpr uint32_t channel_offset(uint32_t chnum)
|
||||
{
|
||||
assert(chnum < CHANNELS);
|
||||
if (!IsOpl3Plus)
|
||||
return chnum;
|
||||
else
|
||||
return (chnum % 9) + 0x100 * (chnum / 9);
|
||||
}
|
||||
|
||||
// map operator number to register offset
|
||||
static constexpr uint32_t operator_offset(uint32_t opnum)
|
||||
{
|
||||
assert(opnum < OPERATORS);
|
||||
if (!IsOpl3Plus)
|
||||
return opnum + 2 * (opnum / 6);
|
||||
else
|
||||
return (opnum % 18) + 2 * ((opnum % 18) / 6) + 0x100 * (opnum / 18);
|
||||
}
|
||||
|
||||
// return an array of operator indices for each channel
|
||||
struct operator_mapping { uint32_t chan[CHANNELS]; };
|
||||
void operator_map(operator_mapping &dest) const;
|
||||
|
||||
// OPL4 apparently can read back FM registers?
|
||||
uint8_t read(uint16_t index) const { return m_regdata[index]; }
|
||||
|
||||
// handle writes to the register array
|
||||
bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask);
|
||||
|
||||
// clock the noise and LFO, if present, returning LFO PM value
|
||||
int32_t clock_noise_and_lfo();
|
||||
|
||||
// reset the LFO
|
||||
void reset_lfo() { m_lfo_am_counter = m_lfo_pm_counter = 0; }
|
||||
|
||||
// return the AM offset from LFO for the given channel
|
||||
// on OPL this is just a fixed value
|
||||
uint32_t lfo_am_offset(uint32_t choffs) const { return m_lfo_am; }
|
||||
|
||||
// return LFO/noise states
|
||||
uint32_t noise_state() const { return m_noise_lfsr >> 23; }
|
||||
|
||||
// caching helpers
|
||||
void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache);
|
||||
|
||||
// compute the phase step, given a PM value
|
||||
uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm);
|
||||
|
||||
// log a key-on event
|
||||
std::string log_keyon(uint32_t choffs, uint32_t opoffs);
|
||||
|
||||
// system-wide registers
|
||||
uint32_t test() const { return byte(0x01, 0, 8); }
|
||||
uint32_t waveform_enable() const { return IsOpl2 ? byte(0x01, 5, 1) : (IsOpl3Plus ? 1 : 0); }
|
||||
uint32_t timer_a_value() const { return byte(0x02, 0, 8) * 4; } // 8->10 bits
|
||||
uint32_t timer_b_value() const { return byte(0x03, 0, 8); }
|
||||
uint32_t status_mask() const { return byte(0x04, 0, 8) & 0x78; }
|
||||
uint32_t irq_reset() const { return byte(0x04, 7, 1); }
|
||||
uint32_t reset_timer_b() const { return byte(0x04, 7, 1) | byte(0x04, 5, 1); }
|
||||
uint32_t reset_timer_a() const { return byte(0x04, 7, 1) | byte(0x04, 6, 1); }
|
||||
uint32_t enable_timer_b() const { return 1; }
|
||||
uint32_t enable_timer_a() const { return 1; }
|
||||
uint32_t load_timer_b() const { return byte(0x04, 1, 1); }
|
||||
uint32_t load_timer_a() const { return byte(0x04, 0, 1); }
|
||||
uint32_t csm() const { return IsOpl3Plus ? 0 : byte(0x08, 7, 1); }
|
||||
uint32_t note_select() const { return byte(0x08, 6, 1); }
|
||||
uint32_t lfo_am_depth() const { return byte(0xbd, 7, 1); }
|
||||
uint32_t lfo_pm_depth() const { return byte(0xbd, 6, 1); }
|
||||
uint32_t rhythm_enable() const { return byte(0xbd, 5, 1); }
|
||||
uint32_t rhythm_keyon() const { return byte(0xbd, 4, 0); }
|
||||
uint32_t newflag() const { return IsOpl3Plus ? byte(0x105, 0, 1) : 0; }
|
||||
uint32_t new2flag() const { return IsOpl4Plus ? byte(0x105, 1, 1) : 0; }
|
||||
uint32_t fourop_enable() const { return IsOpl3Plus ? byte(0x104, 0, 6) : 0; }
|
||||
|
||||
// per-channel registers
|
||||
uint32_t ch_block_freq(uint32_t choffs) const { return word(0xb0, 0, 5, 0xa0, 0, 8, choffs); }
|
||||
uint32_t ch_feedback(uint32_t choffs) const { return byte(0xc0, 1, 3, choffs); }
|
||||
uint32_t ch_algorithm(uint32_t choffs) const { return byte(0xc0, 0, 1, choffs) | (IsOpl3Plus ? (8 | (byte(0xc3, 0, 1, choffs) << 1)) : 0); }
|
||||
uint32_t ch_output_any(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 4, 4) : 1; }
|
||||
uint32_t ch_output_0(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 4, 1) : 1; }
|
||||
uint32_t ch_output_1(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 5, 1) : (IsOpl3Plus ? 1 : 0); }
|
||||
uint32_t ch_output_2(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 6, 1) : 0; }
|
||||
uint32_t ch_output_3(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 7, 1) : 0; }
|
||||
|
||||
// per-operator registers
|
||||
uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0x20, 7, 1, opoffs); }
|
||||
uint32_t op_lfo_pm_enable(uint32_t opoffs) const { return byte(0x20, 6, 1, opoffs); }
|
||||
uint32_t op_eg_sustain(uint32_t opoffs) const { return byte(0x20, 5, 1, opoffs); }
|
||||
uint32_t op_ksr(uint32_t opoffs) const { return byte(0x20, 4, 1, opoffs); }
|
||||
uint32_t op_multiple(uint32_t opoffs) const { return byte(0x20, 0, 4, opoffs); }
|
||||
uint32_t op_ksl(uint32_t opoffs) const { uint32_t temp = byte(0x40, 6, 2, opoffs); return bitfield(temp, 1) | (bitfield(temp, 0) << 1); }
|
||||
uint32_t op_total_level(uint32_t opoffs) const { return byte(0x40, 0, 6, opoffs); }
|
||||
uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x60, 4, 4, opoffs); }
|
||||
uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0x60, 0, 4, opoffs); }
|
||||
uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0x80, 4, 4, opoffs); }
|
||||
uint32_t op_release_rate(uint32_t opoffs) const { return byte(0x80, 0, 4, opoffs); }
|
||||
uint32_t op_waveform(uint32_t opoffs) const { return IsOpl2Plus ? byte(0xe0, 0, newflag() ? 3 : 2, opoffs) : 0; }
|
||||
|
||||
protected:
|
||||
// return a bitfield extracted from a byte
|
||||
uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const
|
||||
{
|
||||
return bitfield(m_regdata[offset + extra_offset], start, count);
|
||||
}
|
||||
|
||||
// return a bitfield extracted from a pair of bytes, MSBs listed first
|
||||
uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const
|
||||
{
|
||||
return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
|
||||
}
|
||||
|
||||
// helper to determine if the this channel is an active rhythm channel
|
||||
bool is_rhythm(uint32_t choffs) const
|
||||
{
|
||||
return rhythm_enable() && (choffs >= 6 && choffs <= 8);
|
||||
}
|
||||
|
||||
// internal state
|
||||
uint16_t m_lfo_am_counter; // LFO AM counter
|
||||
uint16_t m_lfo_pm_counter; // LFO PM counter
|
||||
uint32_t m_noise_lfsr; // noise LFSR state
|
||||
uint8_t m_lfo_am; // current LFO AM value
|
||||
uint8_t m_regdata[REGISTERS]; // register data
|
||||
uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
|
||||
};
|
||||
|
||||
using opl_registers = opl_registers_base<1>;
|
||||
using opl2_registers = opl_registers_base<2>;
|
||||
using opl3_registers = opl_registers_base<3>;
|
||||
using opl4_registers = opl_registers_base<4>;
|
||||
|
||||
|
||||
|
||||
// ======================> opll_registers
|
||||
|
||||
//
|
||||
// OPLL register map:
|
||||
//
|
||||
// System-wide registers:
|
||||
// 0E --x----- Rhythm enable
|
||||
// ---x---- Bass drum key on
|
||||
// ----x--- Snare drum key on
|
||||
// -----x-- Tom key on
|
||||
// ------x- Top cymbal key on
|
||||
// -------x High hat key on
|
||||
// 0F xxxxxxxx Test register
|
||||
//
|
||||
// Per-channel registers (channel in address bits 0-3)
|
||||
// 10-18 xxxxxxxx F-number (low 8 bits)
|
||||
// 20-28 --x----- Sustain on
|
||||
// ---x---- Key on
|
||||
// --- xxx- Block (octvate, 0-7)
|
||||
// -------x F-number (high bit)
|
||||
// 30-38 xxxx---- Instrument selection
|
||||
// ----xxxx Volume
|
||||
//
|
||||
// User instrument registers (for carrier, modulator operators)
|
||||
// 00-01 x------- AM enable
|
||||
// -x------ PM enable (VIB)
|
||||
// --x----- EG type
|
||||
// ---x---- Key scale rate
|
||||
// ----xxxx Multiple value (0-15)
|
||||
// 02 xx------ Key scale level (carrier, 0-3)
|
||||
// --xxxxxx Total level (modulator, 0-63)
|
||||
// 03 xx------ Key scale level (modulator, 0-3)
|
||||
// ---x---- Rectified wave (carrier)
|
||||
// ----x--- Rectified wave (modulator)
|
||||
// -----xxx Feedback level for operator 1 (0-7)
|
||||
// 04-05 xxxx---- Attack rate (0-15)
|
||||
// ----xxxx Decay rate (0-15)
|
||||
// 06-07 xxxx---- Sustain level (0-15)
|
||||
// ----xxxx Release rate (0-15)
|
||||
//
|
||||
// Internal (fake) registers:
|
||||
// 40-48 xxxxxxxx Current instrument base address
|
||||
// 4E-5F xxxxxxxx Current instrument base address + operator slot (0/1)
|
||||
// 70-FF xxxxxxxx Data for instruments (1-16 plus 3 drums)
|
||||
//
|
||||
|
||||
class opll_registers : public fm_registers_base
|
||||
{
|
||||
public:
|
||||
static constexpr uint32_t OUTPUTS = 2;
|
||||
static constexpr uint32_t CHANNELS = 9;
|
||||
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
|
||||
static constexpr uint32_t OPERATORS = CHANNELS * 2;
|
||||
static constexpr uint32_t WAVEFORMS = 2;
|
||||
static constexpr uint32_t REGISTERS = 0x40;
|
||||
static constexpr uint32_t REG_MODE = 0x3f;
|
||||
static constexpr uint32_t DEFAULT_PRESCALE = 4;
|
||||
static constexpr uint32_t EG_CLOCK_DIVIDER = 1;
|
||||
static constexpr uint32_t CSM_TRIGGER_MASK = 0;
|
||||
static constexpr bool EG_HAS_DEPRESS = true;
|
||||
static constexpr bool MODULATOR_DELAY = true;
|
||||
static constexpr uint8_t STATUS_TIMERA = 0;
|
||||
static constexpr uint8_t STATUS_TIMERB = 0;
|
||||
static constexpr uint8_t STATUS_BUSY = 0;
|
||||
static constexpr uint8_t STATUS_IRQ = 0;
|
||||
|
||||
// OPLL-specific constants
|
||||
static constexpr uint32_t INSTDATA_SIZE = 0x90;
|
||||
|
||||
// constructor
|
||||
opll_registers();
|
||||
|
||||
// reset to initial state
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// map channel number to register offset
|
||||
static constexpr uint32_t channel_offset(uint32_t chnum)
|
||||
{
|
||||
assert(chnum < CHANNELS);
|
||||
return chnum;
|
||||
}
|
||||
|
||||
// map operator number to register offset
|
||||
static constexpr uint32_t operator_offset(uint32_t opnum)
|
||||
{
|
||||
assert(opnum < OPERATORS);
|
||||
return opnum;
|
||||
}
|
||||
|
||||
// return an array of operator indices for each channel
|
||||
struct operator_mapping { uint32_t chan[CHANNELS]; };
|
||||
void operator_map(operator_mapping &dest) const;
|
||||
|
||||
// read a register value
|
||||
uint8_t read(uint16_t index) const { return m_regdata[index]; }
|
||||
|
||||
// handle writes to the register array
|
||||
bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask);
|
||||
|
||||
// clock the noise and LFO, if present, returning LFO PM value
|
||||
int32_t clock_noise_and_lfo();
|
||||
|
||||
// reset the LFO
|
||||
void reset_lfo() { m_lfo_am_counter = m_lfo_pm_counter = 0; }
|
||||
|
||||
// return the AM offset from LFO for the given channel
|
||||
// on OPL this is just a fixed value
|
||||
uint32_t lfo_am_offset(uint32_t choffs) const { return m_lfo_am; }
|
||||
|
||||
// return LFO/noise states
|
||||
uint32_t noise_state() const { return m_noise_lfsr >> 23; }
|
||||
|
||||
// caching helpers
|
||||
void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache);
|
||||
|
||||
// compute the phase step, given a PM value
|
||||
uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm);
|
||||
|
||||
// log a key-on event
|
||||
std::string log_keyon(uint32_t choffs, uint32_t opoffs);
|
||||
|
||||
// set the instrument data
|
||||
void set_instrument_data(uint8_t const *data)
|
||||
{
|
||||
std::copy_n(data, INSTDATA_SIZE, &m_instdata[0]);
|
||||
}
|
||||
|
||||
// system-wide registers
|
||||
uint32_t rhythm_enable() const { return byte(0x0e, 5, 1); }
|
||||
uint32_t rhythm_keyon() const { return byte(0x0e, 4, 0); }
|
||||
uint32_t test() const { return byte(0x0f, 0, 8); }
|
||||
uint32_t waveform_enable() const { return 1; }
|
||||
uint32_t timer_a_value() const { return 0; }
|
||||
uint32_t timer_b_value() const { return 0; }
|
||||
uint32_t status_mask() const { return 0; }
|
||||
uint32_t irq_reset() const { return 0; }
|
||||
uint32_t reset_timer_b() const { return 0; }
|
||||
uint32_t reset_timer_a() const { return 0; }
|
||||
uint32_t enable_timer_b() const { return 0; }
|
||||
uint32_t enable_timer_a() const { return 0; }
|
||||
uint32_t load_timer_b() const { return 0; }
|
||||
uint32_t load_timer_a() const { return 0; }
|
||||
uint32_t csm() const { return 0; }
|
||||
|
||||
// per-channel registers
|
||||
uint32_t ch_block_freq(uint32_t choffs) const { return word(0x20, 0, 4, 0x10, 0, 8, choffs); }
|
||||
uint32_t ch_sustain(uint32_t choffs) const { return byte(0x20, 5, 1, choffs); }
|
||||
uint32_t ch_total_level(uint32_t choffs) const { return instchbyte(0x02, 0, 6, choffs); }
|
||||
uint32_t ch_feedback(uint32_t choffs) const { return instchbyte(0x03, 0, 3, choffs); }
|
||||
uint32_t ch_algorithm(uint32_t choffs) const { return 0; }
|
||||
uint32_t ch_instrument(uint32_t choffs) const { return byte(0x30, 4, 4, choffs); }
|
||||
uint32_t ch_output_any(uint32_t choffs) const { return 1; }
|
||||
uint32_t ch_output_0(uint32_t choffs) const { return !is_rhythm(choffs); }
|
||||
uint32_t ch_output_1(uint32_t choffs) const { return is_rhythm(choffs); }
|
||||
uint32_t ch_output_2(uint32_t choffs) const { return 0; }
|
||||
uint32_t ch_output_3(uint32_t choffs) const { return 0; }
|
||||
|
||||
// per-operator registers
|
||||
uint32_t op_lfo_am_enable(uint32_t opoffs) const { return instopbyte(0x00, 7, 1, opoffs); }
|
||||
uint32_t op_lfo_pm_enable(uint32_t opoffs) const { return instopbyte(0x00, 6, 1, opoffs); }
|
||||
uint32_t op_eg_sustain(uint32_t opoffs) const { return instopbyte(0x00, 5, 1, opoffs); }
|
||||
uint32_t op_ksr(uint32_t opoffs) const { return instopbyte(0x00, 4, 1, opoffs); }
|
||||
uint32_t op_multiple(uint32_t opoffs) const { return instopbyte(0x00, 0, 4, opoffs); }
|
||||
uint32_t op_ksl(uint32_t opoffs) const { return instopbyte(0x02, 6, 2, opoffs); }
|
||||
uint32_t op_waveform(uint32_t opoffs) const { return instchbyte(0x03, 3 + bitfield(opoffs, 0), 1, opoffs >> 1); }
|
||||
uint32_t op_attack_rate(uint32_t opoffs) const { return instopbyte(0x04, 4, 4, opoffs); }
|
||||
uint32_t op_decay_rate(uint32_t opoffs) const { return instopbyte(0x04, 0, 4, opoffs); }
|
||||
uint32_t op_sustain_level(uint32_t opoffs) const { return instopbyte(0x06, 4, 4, opoffs); }
|
||||
uint32_t op_release_rate(uint32_t opoffs) const { return instopbyte(0x06, 0, 4, opoffs); }
|
||||
uint32_t op_volume(uint32_t opoffs) const { return byte(0x30, 4 * bitfield(~opoffs, 0), 4, opoffs >> 1); }
|
||||
|
||||
private:
|
||||
// return a bitfield extracted from a byte
|
||||
uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const
|
||||
{
|
||||
return bitfield(m_regdata[offset + extra_offset], start, count);
|
||||
}
|
||||
|
||||
// return a bitfield extracted from a pair of bytes, MSBs listed first
|
||||
uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const
|
||||
{
|
||||
return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
|
||||
}
|
||||
|
||||
// helpers to read from instrument channel/operator data
|
||||
uint32_t instchbyte(uint32_t offset, uint32_t start, uint32_t count, uint32_t choffs) const { return bitfield(m_chinst[choffs][offset], start, count); }
|
||||
uint32_t instopbyte(uint32_t offset, uint32_t start, uint32_t count, uint32_t opoffs) const { return bitfield(m_opinst[opoffs][offset], start, count); }
|
||||
|
||||
// helper to determine if the this channel is an active rhythm channel
|
||||
bool is_rhythm(uint32_t choffs) const
|
||||
{
|
||||
return rhythm_enable() && choffs >= 6;
|
||||
}
|
||||
|
||||
// internal state
|
||||
uint16_t m_lfo_am_counter; // LFO AM counter
|
||||
uint16_t m_lfo_pm_counter; // LFO PM counter
|
||||
uint32_t m_noise_lfsr; // noise LFSR state
|
||||
uint8_t m_lfo_am; // current LFO AM value
|
||||
uint8_t const *m_chinst[CHANNELS]; // pointer to instrument data for each channel
|
||||
uint8_t const *m_opinst[OPERATORS]; // pointer to instrument data for each operator
|
||||
uint8_t m_regdata[REGISTERS]; // register data
|
||||
uint8_t m_instdata[INSTDATA_SIZE]; // instrument data
|
||||
uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
|
||||
};
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// OPL IMPLEMENTATION CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// ======================> ym3526
|
||||
|
||||
class ym3526
|
||||
{
|
||||
public:
|
||||
using fm_engine = fm_engine_base<opl_registers>;
|
||||
using output_data = fm_engine::output_data;
|
||||
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
|
||||
|
||||
// constructor
|
||||
ym3526(ymfm_interface &intf);
|
||||
|
||||
// reset
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// pass-through helpers
|
||||
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
|
||||
void invalidate_caches() { m_fm.invalidate_caches(); }
|
||||
|
||||
// read access
|
||||
uint8_t read_status();
|
||||
uint8_t read(uint32_t offset);
|
||||
|
||||
// write access
|
||||
void write_address(uint8_t data);
|
||||
void write_data(uint8_t data);
|
||||
void write(uint32_t offset, uint8_t data);
|
||||
|
||||
// generate samples of sound
|
||||
void generate(output_data *output, uint32_t numsamples = 1);
|
||||
protected:
|
||||
// internal state
|
||||
uint8_t m_address; // address register
|
||||
fm_engine m_fm; // core FM engine
|
||||
};
|
||||
|
||||
|
||||
// ======================> y8950
|
||||
|
||||
class y8950
|
||||
{
|
||||
public:
|
||||
using fm_engine = fm_engine_base<opl_registers>;
|
||||
using output_data = fm_engine::output_data;
|
||||
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
|
||||
|
||||
static constexpr uint8_t STATUS_ADPCM_B_PLAYING = 0x01;
|
||||
static constexpr uint8_t STATUS_ADPCM_B_BRDY = 0x08;
|
||||
static constexpr uint8_t STATUS_ADPCM_B_EOS = 0x10;
|
||||
static constexpr uint8_t ALL_IRQS = STATUS_ADPCM_B_BRDY | STATUS_ADPCM_B_EOS | fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB;
|
||||
|
||||
// constructor
|
||||
y8950(ymfm_interface &intf);
|
||||
|
||||
// reset
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// pass-through helpers
|
||||
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
|
||||
void invalidate_caches() { m_fm.invalidate_caches(); }
|
||||
|
||||
// read access
|
||||
uint8_t read_status();
|
||||
uint8_t read_data();
|
||||
uint8_t read(uint32_t offset);
|
||||
|
||||
// write access
|
||||
void write_address(uint8_t data);
|
||||
void write_data(uint8_t data);
|
||||
void write(uint32_t offset, uint8_t data);
|
||||
|
||||
// generate samples of sound
|
||||
void generate(output_data *output, uint32_t numsamples = 1);
|
||||
|
||||
protected:
|
||||
// internal state
|
||||
uint8_t m_address; // address register
|
||||
uint8_t m_io_ddr; // data direction register for I/O
|
||||
fm_engine m_fm; // core FM engine
|
||||
adpcm_b_engine m_adpcm_b; // ADPCM-B engine
|
||||
};
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// OPL2 IMPLEMENTATION CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// ======================> ym3812
|
||||
|
||||
class ym3812
|
||||
{
|
||||
public:
|
||||
using fm_engine = fm_engine_base<opl2_registers>;
|
||||
using output_data = fm_engine::output_data;
|
||||
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
|
||||
|
||||
// constructor
|
||||
ym3812(ymfm_interface &intf);
|
||||
|
||||
// reset
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// pass-through helpers
|
||||
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
|
||||
void invalidate_caches() { m_fm.invalidate_caches(); }
|
||||
|
||||
// read access
|
||||
uint8_t read_status();
|
||||
uint8_t read(uint32_t offset);
|
||||
|
||||
// write access
|
||||
void write_address(uint8_t data);
|
||||
void write_data(uint8_t data);
|
||||
void write(uint32_t offset, uint8_t data);
|
||||
|
||||
// generate samples of sound
|
||||
void generate(output_data *output, uint32_t numsamples = 1);
|
||||
|
||||
protected:
|
||||
// internal state
|
||||
uint8_t m_address; // address register
|
||||
fm_engine m_fm; // core FM engine
|
||||
};
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// OPL3 IMPLEMENTATION CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// ======================> ymf262
|
||||
|
||||
class ymf262
|
||||
{
|
||||
public:
|
||||
using fm_engine = fm_engine_base<opl3_registers>;
|
||||
using output_data = fm_engine::output_data;
|
||||
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
|
||||
|
||||
// constructor
|
||||
ymf262(ymfm_interface &intf);
|
||||
|
||||
// reset
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// pass-through helpers
|
||||
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
|
||||
void invalidate_caches() { m_fm.invalidate_caches(); }
|
||||
|
||||
// read access
|
||||
uint8_t read_status();
|
||||
uint8_t read(uint32_t offset);
|
||||
|
||||
// write access
|
||||
void write_address(uint8_t data);
|
||||
void write_data(uint8_t data);
|
||||
void write_address_hi(uint8_t data);
|
||||
void write(uint32_t offset, uint8_t data);
|
||||
|
||||
// generate samples of sound
|
||||
void generate(output_data *output, uint32_t numsamples = 1);
|
||||
|
||||
protected:
|
||||
// internal state
|
||||
uint16_t m_address; // address register
|
||||
fm_engine m_fm; // core FM engine
|
||||
};
|
||||
|
||||
|
||||
// ======================> ymf289b
|
||||
|
||||
class ymf289b
|
||||
{
|
||||
static constexpr uint8_t STATUS_BUSY_FLAGS = 0x05;
|
||||
|
||||
public:
|
||||
using fm_engine = fm_engine_base<opl3_registers>;
|
||||
using output_data = fm_engine::output_data;
|
||||
static constexpr uint32_t OUTPUTS = 2;
|
||||
|
||||
// constructor
|
||||
ymf289b(ymfm_interface &intf);
|
||||
|
||||
// reset
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// pass-through helpers
|
||||
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
|
||||
void invalidate_caches() { m_fm.invalidate_caches(); }
|
||||
|
||||
// read access
|
||||
uint8_t read_status();
|
||||
uint8_t read_data();
|
||||
uint8_t read(uint32_t offset);
|
||||
|
||||
// write access
|
||||
void write_address(uint8_t data);
|
||||
void write_data(uint8_t data);
|
||||
void write_address_hi(uint8_t data);
|
||||
void write(uint32_t offset, uint8_t data);
|
||||
|
||||
// generate samples of sound
|
||||
void generate(output_data *output, uint32_t numsamples = 1);
|
||||
|
||||
protected:
|
||||
// internal helpers
|
||||
bool ymf289b_mode() { return ((m_fm.regs().read(0x105) & 0x04) != 0); }
|
||||
|
||||
// internal state
|
||||
uint16_t m_address; // address register
|
||||
fm_engine m_fm; // core FM engine
|
||||
};
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// OPL4 IMPLEMENTATION CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// ======================> ymf278b
|
||||
|
||||
class ymf278b
|
||||
{
|
||||
// Using the nominal datasheet frequency of 33.868MHz, the output of the
|
||||
// chip will be clock/768 = 44.1kHz. However, the FM engine is clocked
|
||||
// internally at clock/(19*36), or 49.515kHz, so the FM output needs to
|
||||
// be downsampled. We treat this as needing to clock the FM engine an
|
||||
// extra tick every few samples. The exact ratio is 768/(19*36) or
|
||||
// 768/684 = 192/171. So if we always clock the FM once, we'll have
|
||||
// 192/171 - 1 = 21/171 left. Thus we count 21 for each sample and when
|
||||
// it gets above 171, we tick an extra time.
|
||||
static constexpr uint32_t FM_EXTRA_SAMPLE_THRESH = 171;
|
||||
static constexpr uint32_t FM_EXTRA_SAMPLE_STEP = 192 - FM_EXTRA_SAMPLE_THRESH;
|
||||
|
||||
public:
|
||||
using fm_engine = fm_engine_base<opl4_registers>;
|
||||
static constexpr uint32_t OUTPUTS = 6;
|
||||
using output_data = ymfm_output<OUTPUTS>;
|
||||
|
||||
static constexpr uint8_t STATUS_BUSY = 0x01;
|
||||
static constexpr uint8_t STATUS_LD = 0x02;
|
||||
|
||||
// constructor
|
||||
ymf278b(ymfm_interface &intf);
|
||||
|
||||
// reset
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// pass-through helpers
|
||||
uint32_t sample_rate(uint32_t input_clock) const { return input_clock / 768; }
|
||||
void invalidate_caches() { m_fm.invalidate_caches(); }
|
||||
|
||||
// read access
|
||||
uint8_t read_status();
|
||||
uint8_t read_data_pcm();
|
||||
uint8_t read(uint32_t offset);
|
||||
|
||||
// write access
|
||||
void write_address(uint8_t data);
|
||||
void write_data(uint8_t data);
|
||||
void write_address_hi(uint8_t data);
|
||||
void write_address_pcm(uint8_t data);
|
||||
void write_data_pcm(uint8_t data);
|
||||
void write(uint32_t offset, uint8_t data);
|
||||
|
||||
// generate samples of sound
|
||||
void generate(output_data *output, uint32_t numsamples = 1);
|
||||
|
||||
protected:
|
||||
// internal state
|
||||
uint16_t m_address; // address register
|
||||
uint32_t m_fm_pos; // FM resampling position
|
||||
uint32_t m_load_remaining; // how many more samples until LD flag clears
|
||||
bool m_next_status_id; // flag to track which status ID to return
|
||||
fm_engine m_fm; // core FM engine
|
||||
pcm_engine m_pcm; // core PCM engine
|
||||
};
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// OPLL IMPLEMENTATION CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// ======================> opll_base
|
||||
|
||||
class opll_base
|
||||
{
|
||||
public:
|
||||
using fm_engine = fm_engine_base<opll_registers>;
|
||||
using output_data = fm_engine::output_data;
|
||||
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
|
||||
|
||||
// constructor
|
||||
opll_base(ymfm_interface &intf, uint8_t const *data);
|
||||
|
||||
// configuration
|
||||
void set_instrument_data(uint8_t const *data) { m_fm.regs().set_instrument_data(data); }
|
||||
|
||||
// reset
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// pass-through helpers
|
||||
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
|
||||
void invalidate_caches() { m_fm.invalidate_caches(); }
|
||||
|
||||
// read access -- doesn't really have any, but provide these for consistency
|
||||
uint8_t read_status() { return 0x00; }
|
||||
uint8_t read(uint32_t offset) { return 0x00; }
|
||||
|
||||
// write access
|
||||
void write_address(uint8_t data);
|
||||
void write_data(uint8_t data);
|
||||
void write(uint32_t offset, uint8_t data);
|
||||
|
||||
// generate samples of sound
|
||||
void generate(output_data *output, uint32_t numsamples = 1);
|
||||
|
||||
protected:
|
||||
// internal state
|
||||
uint8_t m_address; // address register
|
||||
fm_engine m_fm; // core FM engine
|
||||
};
|
||||
|
||||
|
||||
// ======================> ym2413
|
||||
|
||||
class ym2413 : public opll_base
|
||||
{
|
||||
public:
|
||||
// constructor
|
||||
ym2413(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
|
||||
|
||||
private:
|
||||
// internal state
|
||||
static uint8_t const s_default_instruments[];
|
||||
};
|
||||
|
||||
|
||||
// ======================> ym2413
|
||||
|
||||
class ym2423 : public opll_base
|
||||
{
|
||||
public:
|
||||
// constructor
|
||||
ym2423(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
|
||||
|
||||
private:
|
||||
// internal state
|
||||
static uint8_t const s_default_instruments[];
|
||||
};
|
||||
|
||||
|
||||
// ======================> ymf281
|
||||
|
||||
class ymf281 : public opll_base
|
||||
{
|
||||
public:
|
||||
// constructor
|
||||
ymf281(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
|
||||
|
||||
private:
|
||||
// internal state
|
||||
static uint8_t const s_default_instruments[];
|
||||
};
|
||||
|
||||
|
||||
// ======================> ds1001
|
||||
|
||||
class ds1001 : public opll_base
|
||||
{
|
||||
public:
|
||||
// constructor
|
||||
ds1001(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
|
||||
|
||||
private:
|
||||
// internal state
|
||||
static uint8_t const s_default_instruments[];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // YMFM_OPL_H
|
714
src/engine/platform/sound/ymfm/ymfm_pcm.cpp
Normal file
714
src/engine/platform/sound/ymfm/ymfm_pcm.cpp
Normal file
|
@ -0,0 +1,714 @@
|
|||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2021, Aaron Giles
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "ymfm_pcm.h"
|
||||
#include "ymfm_fm.h"
|
||||
#include "ymfm_fm.ipp"
|
||||
|
||||
namespace ymfm
|
||||
{
|
||||
|
||||
//*********************************************************
|
||||
// PCM REGISTERS
|
||||
//*********************************************************
|
||||
|
||||
//-------------------------------------------------
|
||||
// reset - reset the register state
|
||||
//-------------------------------------------------
|
||||
|
||||
void pcm_registers::reset()
|
||||
{
|
||||
std::fill_n(&m_regdata[0], REGISTERS, 0);
|
||||
m_regdata[0xf8] = 0x1b;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// save_restore - save or restore the data
|
||||
//-------------------------------------------------
|
||||
|
||||
void pcm_registers::save_restore(ymfm_saved_state &state)
|
||||
{
|
||||
state.save_restore(m_regdata);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// cache_channel_data - update the cache with
|
||||
// data from the registers
|
||||
//-------------------------------------------------
|
||||
|
||||
void pcm_registers::cache_channel_data(uint32_t choffs, pcm_cache &cache)
|
||||
{
|
||||
// compute step from octave and fnumber; the math here implies
|
||||
// a .18 fraction but .16 should be perfectly fine
|
||||
int32_t octave = int8_t(ch_octave(choffs) << 4) >> 4;
|
||||
uint32_t fnum = ch_fnumber(choffs);
|
||||
cache.step = ((0x400 | fnum) << (octave + 7)) >> 2;
|
||||
|
||||
// total level is computed as a .10 value for interpolation
|
||||
cache.total_level = ch_total_level(choffs) << 10;
|
||||
|
||||
// compute panning values in terms of envelope attenuation
|
||||
int32_t panpot = int8_t(ch_panpot(choffs) << 4) >> 4;
|
||||
if (panpot >= 0)
|
||||
{
|
||||
cache.pan_left = (panpot == 7) ? 0x3ff : 0x20 * panpot;
|
||||
cache.pan_right = 0;
|
||||
}
|
||||
else if (panpot >= -7)
|
||||
{
|
||||
cache.pan_left = 0;
|
||||
cache.pan_right = (panpot == -7) ? 0x3ff : -0x20 * panpot;
|
||||
}
|
||||
else
|
||||
cache.pan_left = cache.pan_right = 0x3ff;
|
||||
|
||||
// determine the LFO stepping value; this how much to add to a running
|
||||
// x.18 value for the LFO; steps were derived from frequencies in the
|
||||
// manual and come out very close with these values
|
||||
static const uint8_t s_lfo_steps[8] = { 1, 12, 19, 25, 31, 35, 37, 42 };
|
||||
cache.lfo_step = s_lfo_steps[ch_lfo_speed(choffs)];
|
||||
|
||||
// AM LFO depth values, derived from the manual; note each has at most
|
||||
// 2 bits to make the "multiply" easy in hardware
|
||||
static const uint8_t s_am_depth[8] = { 0, 0x14, 0x20, 0x28, 0x30, 0x40, 0x50, 0x80 };
|
||||
cache.am_depth = s_am_depth[ch_am_depth(choffs)];
|
||||
|
||||
// PM LFO depth values; these are converted from the manual's cents values
|
||||
// into f-numbers; the computations come out quite cleanly so pretty sure
|
||||
// these are correct
|
||||
static const uint8_t s_pm_depth[8] = { 0, 2, 3, 4, 6, 12, 24, 48 };
|
||||
cache.pm_depth = s_pm_depth[ch_vibrato(choffs)];
|
||||
|
||||
// 4-bit sustain level, but 15 means 31 so effectively 5 bits
|
||||
cache.eg_sustain = ch_sustain_level(choffs);
|
||||
cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
|
||||
cache.eg_sustain <<= 5;
|
||||
|
||||
// compute the key scaling correction factor; 15 means don't do any correction
|
||||
int32_t correction = ch_rate_correction(choffs);
|
||||
if (correction == 15)
|
||||
correction = 0;
|
||||
else
|
||||
correction = (octave + correction) * 2 + bitfield(fnum, 9);
|
||||
|
||||
// compute the envelope generator rates
|
||||
cache.eg_rate[EG_ATTACK] = effective_rate(ch_attack_rate(choffs), correction);
|
||||
cache.eg_rate[EG_DECAY] = effective_rate(ch_decay_rate(choffs), correction);
|
||||
cache.eg_rate[EG_SUSTAIN] = effective_rate(ch_sustain_rate(choffs), correction);
|
||||
cache.eg_rate[EG_RELEASE] = effective_rate(ch_release_rate(choffs), correction);
|
||||
cache.eg_rate[EG_REVERB] = 5;
|
||||
|
||||
// if damping is on, override some things; essentially decay at a hardcoded
|
||||
// rate of 48 until -12db (0x80), then at maximum rate for the rest
|
||||
if (ch_damp(choffs) != 0)
|
||||
{
|
||||
cache.eg_rate[EG_DECAY] = 48;
|
||||
cache.eg_rate[EG_SUSTAIN] = 63;
|
||||
cache.eg_rate[EG_RELEASE] = 63;
|
||||
cache.eg_sustain = 0x80;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// effective_rate - return the effective rate,
|
||||
// clamping and applying corrections as needed
|
||||
//-------------------------------------------------
|
||||
|
||||
uint32_t pcm_registers::effective_rate(uint32_t raw, uint32_t correction)
|
||||
{
|
||||
// raw rates of 0 and 15 just pin to min/max
|
||||
if (raw == 0)
|
||||
return 0;
|
||||
if (raw == 15)
|
||||
return 63;
|
||||
|
||||
// otherwise add the correction and clamp to range
|
||||
return clamp(raw * 4 + correction, 0, 63);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// PCM CHANNEL
|
||||
//*********************************************************
|
||||
|
||||
//-------------------------------------------------
|
||||
// pcm_channel - constructor
|
||||
//-------------------------------------------------
|
||||
|
||||
pcm_channel::pcm_channel(pcm_engine &owner, uint32_t choffs) :
|
||||
m_choffs(choffs),
|
||||
m_baseaddr(0),
|
||||
m_endpos(0),
|
||||
m_looppos(0),
|
||||
m_curpos(0),
|
||||
m_nextpos(0),
|
||||
m_lfo_counter(0),
|
||||
m_eg_state(EG_RELEASE),
|
||||
m_env_attenuation(0x3ff),
|
||||
m_total_level(0x7f << 10),
|
||||
m_format(0),
|
||||
m_key_state(0),
|
||||
m_regs(owner.regs()),
|
||||
m_owner(owner)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// reset - reset the channel state
|
||||
//-------------------------------------------------
|
||||
|
||||
void pcm_channel::reset()
|
||||
{
|
||||
m_baseaddr = 0;
|
||||
m_endpos = 0;
|
||||
m_looppos = 0;
|
||||
m_curpos = 0;
|
||||
m_nextpos = 0;
|
||||
m_lfo_counter = 0;
|
||||
m_eg_state = EG_RELEASE;
|
||||
m_env_attenuation = 0x3ff;
|
||||
m_total_level = 0x7f << 10;
|
||||
m_format = 0;
|
||||
m_key_state = 0;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// save_restore - save or restore the data
|
||||
//-------------------------------------------------
|
||||
|
||||
void pcm_channel::save_restore(ymfm_saved_state &state)
|
||||
{
|
||||
state.save_restore(m_baseaddr);
|
||||
state.save_restore(m_endpos);
|
||||
state.save_restore(m_looppos);
|
||||
state.save_restore(m_curpos);
|
||||
state.save_restore(m_nextpos);
|
||||
state.save_restore(m_lfo_counter);
|
||||
state.save_restore(m_eg_state);
|
||||
state.save_restore(m_env_attenuation);
|
||||
state.save_restore(m_total_level);
|
||||
state.save_restore(m_format);
|
||||
state.save_restore(m_key_state);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// prepare - prepare for clocking
|
||||
//-------------------------------------------------
|
||||
|
||||
bool pcm_channel::prepare()
|
||||
{
|
||||
// cache the data
|
||||
m_regs.cache_channel_data(m_choffs, m_cache);
|
||||
|
||||
// clock the key state
|
||||
if ((m_key_state & KEY_PENDING) != 0)
|
||||
{
|
||||
uint8_t oldstate = m_key_state;
|
||||
m_key_state = (m_key_state >> 1) & KEY_ON;
|
||||
if (((oldstate ^ m_key_state) & KEY_ON) != 0)
|
||||
{
|
||||
if ((m_key_state & KEY_ON) != 0)
|
||||
start_attack();
|
||||
else
|
||||
start_release();
|
||||
}
|
||||
}
|
||||
|
||||
// set the total level directly if not interpolating
|
||||
if (m_regs.ch_level_direct(m_choffs))
|
||||
m_total_level = m_cache.total_level;
|
||||
|
||||
// we're active until we're quiet after the release
|
||||
return (m_eg_state < EG_RELEASE || m_env_attenuation < EG_QUIET);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// clock - master clocking function
|
||||
//-------------------------------------------------
|
||||
|
||||
void pcm_channel::clock(uint32_t env_counter)
|
||||
{
|
||||
// clock the LFO, which is an x.18 value incremented based on the
|
||||
// LFO speed value
|
||||
m_lfo_counter += m_cache.lfo_step;
|
||||
|
||||
// clock the envelope
|
||||
clock_envelope(env_counter);
|
||||
|
||||
// determine the step after applying vibrato
|
||||
uint32_t step = m_cache.step;
|
||||
if (m_cache.pm_depth != 0)
|
||||
{
|
||||
// shift the LFO by 1/4 cycle for PM so that it starts at 0
|
||||
uint32_t lfo_shifted = m_lfo_counter + (1 << 16);
|
||||
int32_t lfo_value = bitfield(lfo_shifted, 10, 7);
|
||||
if (bitfield(lfo_shifted, 17) != 0)
|
||||
lfo_value ^= 0x7f;
|
||||
lfo_value -= 0x40;
|
||||
step += (lfo_value * int32_t(m_cache.pm_depth)) >> 7;
|
||||
}
|
||||
|
||||
// advance the sample step and loop as needed
|
||||
m_curpos = m_nextpos;
|
||||
m_nextpos = m_curpos + step;
|
||||
if (m_nextpos >= m_endpos)
|
||||
m_nextpos += m_looppos - m_endpos;
|
||||
|
||||
// interpolate total level if needed
|
||||
if (m_total_level != m_cache.total_level)
|
||||
{
|
||||
// max->min volume takes 156.4ms, or pretty close to 19/1024 per 44.1kHz sample
|
||||
// min->max volume is half that, so advance by 38/1024 per sample
|
||||
if (m_total_level < m_cache.total_level)
|
||||
m_total_level = std::min<int32_t>(m_total_level + 19, m_cache.total_level);
|
||||
else
|
||||
m_total_level = std::max<int32_t>(m_total_level - 38, m_cache.total_level);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// output - return the computed output value, with
|
||||
// panning applied
|
||||
//-------------------------------------------------
|
||||
|
||||
void pcm_channel::output(output_data &output) const
|
||||
{
|
||||
// early out if the envelope is effectively off
|
||||
uint32_t envelope = m_env_attenuation;
|
||||
if (envelope > EG_QUIET)
|
||||
return;
|
||||
|
||||
// add in LFO AM modulation
|
||||
if (m_cache.am_depth != 0)
|
||||
{
|
||||
uint32_t lfo_value = bitfield(m_lfo_counter, 10, 7);
|
||||
if (bitfield(m_lfo_counter, 17) != 0)
|
||||
lfo_value ^= 0x7f;
|
||||
envelope += (lfo_value * m_cache.am_depth) >> 7;
|
||||
}
|
||||
|
||||
// add in the current interpolated total level value, which is a .10
|
||||
// value shifted left by 2
|
||||
envelope += m_total_level >> 8;
|
||||
|
||||
// add in panning effect and clamp
|
||||
uint32_t lenv = std::min<uint32_t>(envelope + m_cache.pan_left, 0x3ff);
|
||||
uint32_t renv = std::min<uint32_t>(envelope + m_cache.pan_right, 0x3ff);
|
||||
|
||||
// convert to volume as a .11 fraction
|
||||
int32_t lvol = attenuation_to_volume(lenv << 2);
|
||||
int32_t rvol = attenuation_to_volume(renv << 2);
|
||||
|
||||
// fetch current sample and add
|
||||
int16_t sample = fetch_sample();
|
||||
uint32_t outnum = m_regs.ch_output_channel(m_choffs) * 2;
|
||||
output.data[outnum + 0] += (lvol * sample) >> 15;
|
||||
output.data[outnum + 1] += (rvol * sample) >> 15;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// keyonoff - signal key on/off
|
||||
//-------------------------------------------------
|
||||
|
||||
void pcm_channel::keyonoff(bool on)
|
||||
{
|
||||
// mark the key state as pending
|
||||
m_key_state |= KEY_PENDING | (on ? KEY_PENDING_ON : 0);
|
||||
|
||||
// don't log masked channels
|
||||
if ((m_key_state & (KEY_PENDING_ON | KEY_ON)) == KEY_PENDING_ON && ((debug::GLOBAL_PCM_CHANNEL_MASK >> m_choffs) & 1) != 0)
|
||||
{
|
||||
debug::log_keyon("KeyOn PCM-%02d: num=%3d oct=%2d fnum=%03X level=%02X%c ADSR=%X/%X/%X/%X SL=%X",
|
||||
m_choffs,
|
||||
m_regs.ch_wave_table_num(m_choffs),
|
||||
int8_t(m_regs.ch_octave(m_choffs) << 4) >> 4,
|
||||
m_regs.ch_fnumber(m_choffs),
|
||||
m_regs.ch_total_level(m_choffs),
|
||||
m_regs.ch_level_direct(m_choffs) ? '!' : '/',
|
||||
m_regs.ch_attack_rate(m_choffs),
|
||||
m_regs.ch_decay_rate(m_choffs),
|
||||
m_regs.ch_sustain_rate(m_choffs),
|
||||
m_regs.ch_release_rate(m_choffs),
|
||||
m_regs.ch_sustain_level(m_choffs));
|
||||
|
||||
if (m_regs.ch_rate_correction(m_choffs) != 15)
|
||||
debug::log_keyon(" RC=%X", m_regs.ch_rate_correction(m_choffs));
|
||||
|
||||
if (m_regs.ch_pseudo_reverb(m_choffs) != 0)
|
||||
debug::log_keyon(" %s", "REV");
|
||||
if (m_regs.ch_damp(m_choffs) != 0)
|
||||
debug::log_keyon(" %s", "DAMP");
|
||||
|
||||
if (m_regs.ch_vibrato(m_choffs) != 0 || m_regs.ch_am_depth(m_choffs) != 0)
|
||||
{
|
||||
if (m_regs.ch_vibrato(m_choffs) != 0)
|
||||
debug::log_keyon(" VIB=%d", m_regs.ch_vibrato(m_choffs));
|
||||
if (m_regs.ch_am_depth(m_choffs) != 0)
|
||||
debug::log_keyon(" AM=%d", m_regs.ch_am_depth(m_choffs));
|
||||
debug::log_keyon(" LFO=%d", m_regs.ch_lfo_speed(m_choffs));
|
||||
}
|
||||
debug::log_keyon("%s", "\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// load_wavetable - load a wavetable by fetching
|
||||
// its data from external memory
|
||||
//-------------------------------------------------
|
||||
|
||||
void pcm_channel::load_wavetable()
|
||||
{
|
||||
// determine the address of the wave table header
|
||||
uint32_t wavnum = m_regs.ch_wave_table_num(m_choffs);
|
||||
uint32_t wavheader = 12 * wavnum;
|
||||
|
||||
// above 384 it may be in a different bank
|
||||
if (wavnum >= 384)
|
||||
{
|
||||
uint32_t bank = m_regs.wave_table_header();
|
||||
if (bank != 0)
|
||||
wavheader = 512*1024 * bank + (wavnum - 384) * 12;
|
||||
}
|
||||
|
||||
// fetch the 22-bit base address and 2-bit format
|
||||
uint8_t byte = read_pcm(wavheader + 0);
|
||||
m_format = bitfield(byte, 6, 2);
|
||||
m_baseaddr = bitfield(byte, 0, 6) << 16;
|
||||
m_baseaddr |= read_pcm(wavheader + 1) << 8;
|
||||
m_baseaddr |= read_pcm(wavheader + 2) << 0;
|
||||
|
||||
// fetch the 16-bit loop position
|
||||
m_looppos = read_pcm(wavheader + 3) << 8;
|
||||
m_looppos |= read_pcm(wavheader + 4);
|
||||
m_looppos <<= 16;
|
||||
|
||||
// fetch the 16-bit end position, which is stored as a negative value
|
||||
// for some reason that is unclear
|
||||
m_endpos = read_pcm(wavheader + 5) << 8;
|
||||
m_endpos |= read_pcm(wavheader + 6);
|
||||
m_endpos = -int32_t(m_endpos) << 16;
|
||||
|
||||
// remaining data values set registers
|
||||
m_owner.write(0x80 + m_choffs, read_pcm(wavheader + 7));
|
||||
m_owner.write(0x98 + m_choffs, read_pcm(wavheader + 8));
|
||||
m_owner.write(0xb0 + m_choffs, read_pcm(wavheader + 9));
|
||||
m_owner.write(0xc8 + m_choffs, read_pcm(wavheader + 10));
|
||||
m_owner.write(0xe0 + m_choffs, read_pcm(wavheader + 11));
|
||||
|
||||
// reset the envelope so we don't continue playing mid-sample from previous key ons
|
||||
m_env_attenuation = 0x3ff;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// read_pcm - read a byte from the external PCM
|
||||
// memory interface
|
||||
//-------------------------------------------------
|
||||
|
||||
uint8_t pcm_channel::read_pcm(uint32_t address) const
|
||||
{
|
||||
return m_owner.intf().ymfm_external_read(ACCESS_PCM, address);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// start_attack - start the attack phase
|
||||
//-------------------------------------------------
|
||||
|
||||
void pcm_channel::start_attack()
|
||||
{
|
||||
// don't change anything if already in attack state
|
||||
if (m_eg_state == EG_ATTACK)
|
||||
return;
|
||||
m_eg_state = EG_ATTACK;
|
||||
|
||||
// reset the LFO if requested
|
||||
if (m_regs.ch_lfo_reset(m_choffs))
|
||||
m_lfo_counter = 0;
|
||||
|
||||
// if the attack rate == 63 then immediately go to max attenuation
|
||||
if (m_cache.eg_rate[EG_ATTACK] == 63)
|
||||
m_env_attenuation = 0;
|
||||
|
||||
// reset the positions
|
||||
m_curpos = m_nextpos = 0;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// start_release - start the release phase
|
||||
//-------------------------------------------------
|
||||
|
||||
void pcm_channel::start_release()
|
||||
{
|
||||
// don't change anything if already in release or reverb state
|
||||
if (m_eg_state >= EG_RELEASE)
|
||||
return;
|
||||
m_eg_state = EG_RELEASE;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// clock_envelope - clock the envelope generator
|
||||
//-------------------------------------------------
|
||||
|
||||
void pcm_channel::clock_envelope(uint32_t env_counter)
|
||||
{
|
||||
// handle attack->decay transitions
|
||||
if (m_eg_state == EG_ATTACK && m_env_attenuation == 0)
|
||||
m_eg_state = EG_DECAY;
|
||||
|
||||
// handle decay->sustain transitions
|
||||
if (m_eg_state == EG_DECAY && m_env_attenuation >= m_cache.eg_sustain)
|
||||
m_eg_state = EG_SUSTAIN;
|
||||
|
||||
// fetch the appropriate 6-bit rate value from the cache
|
||||
uint32_t rate = m_cache.eg_rate[m_eg_state];
|
||||
|
||||
// compute the rate shift value; this is the shift needed to
|
||||
// apply to the env_counter such that it becomes a 5.11 fixed
|
||||
// point number
|
||||
uint32_t rate_shift = rate >> 2;
|
||||
env_counter <<= rate_shift;
|
||||
|
||||
// see if the fractional part is 0; if not, it's not time to clock
|
||||
if (bitfield(env_counter, 0, 11) != 0)
|
||||
return;
|
||||
|
||||
// determine the increment based on the non-fractional part of env_counter
|
||||
uint32_t relevant_bits = bitfield(env_counter, (rate_shift <= 11) ? 11 : rate_shift, 3);
|
||||
uint32_t increment = attenuation_increment(rate, relevant_bits);
|
||||
|
||||
// attack is the only one that increases
|
||||
if (m_eg_state == EG_ATTACK)
|
||||
m_env_attenuation += (~m_env_attenuation * increment) >> 4;
|
||||
|
||||
// all other cases are similar
|
||||
else
|
||||
{
|
||||
// apply the increment
|
||||
m_env_attenuation += increment;
|
||||
|
||||
// clamp the final attenuation
|
||||
if (m_env_attenuation >= 0x400)
|
||||
m_env_attenuation = 0x3ff;
|
||||
|
||||
// transition to reverb at -18dB if enabled
|
||||
if (m_env_attenuation >= 0xc0 && m_eg_state < EG_REVERB && m_regs.ch_pseudo_reverb(m_choffs))
|
||||
m_eg_state = EG_REVERB;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// fetch_sample - fetch a sample at the current
|
||||
// position
|
||||
//-------------------------------------------------
|
||||
|
||||
int16_t pcm_channel::fetch_sample() const
|
||||
{
|
||||
uint32_t addr = m_baseaddr;
|
||||
uint32_t pos = m_curpos >> 16;
|
||||
|
||||
// 8-bit PCM: shift up by 8
|
||||
if (m_format == 0)
|
||||
return read_pcm(addr + pos) << 8;
|
||||
|
||||
// 16-bit PCM: assemble from 2 halves
|
||||
if (m_format == 2)
|
||||
{
|
||||
addr += pos * 2;
|
||||
return (read_pcm(addr) << 8) | read_pcm(addr + 1);
|
||||
}
|
||||
|
||||
// 12-bit PCM: assemble out of half of 3 bytes
|
||||
addr += (pos / 2) * 3;
|
||||
if ((pos & 1) == 0)
|
||||
return (read_pcm(addr + 0) << 8) | ((read_pcm(addr + 1) << 4) & 0xf0);
|
||||
else
|
||||
return (read_pcm(addr + 2) << 8) | ((read_pcm(addr + 1) << 0) & 0xf0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//*********************************************************
|
||||
// PCM ENGINE
|
||||
//*********************************************************
|
||||
|
||||
//-------------------------------------------------
|
||||
// pcm_engine - constructor
|
||||
//-------------------------------------------------
|
||||
|
||||
pcm_engine::pcm_engine(ymfm_interface &intf) :
|
||||
m_intf(intf),
|
||||
m_env_counter(0),
|
||||
m_modified_channels(ALL_CHANNELS),
|
||||
m_active_channels(ALL_CHANNELS)
|
||||
{
|
||||
// create the channels
|
||||
for (int chnum = 0; chnum < CHANNELS; chnum++)
|
||||
m_channel[chnum] = std::make_unique<pcm_channel>(*this, chnum);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// reset - reset the engine state
|
||||
//-------------------------------------------------
|
||||
|
||||
void pcm_engine::reset()
|
||||
{
|
||||
// reset register state
|
||||
m_regs.reset();
|
||||
|
||||
// reset each channel
|
||||
for (auto &chan : m_channel)
|
||||
chan->reset();
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// save_restore - save or restore the data
|
||||
//-------------------------------------------------
|
||||
|
||||
void pcm_engine::save_restore(ymfm_saved_state &state)
|
||||
{
|
||||
// save our data
|
||||
state.save_restore(m_env_counter);
|
||||
|
||||
// save channel state
|
||||
for (int chnum = 0; chnum < CHANNELS; chnum++)
|
||||
m_channel[chnum]->save_restore(state);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// clock - master clocking function
|
||||
//-------------------------------------------------
|
||||
|
||||
void pcm_engine::clock(uint32_t chanmask)
|
||||
{
|
||||
// if something was modified, prepare
|
||||
// also prepare every 4k samples to catch ending notes
|
||||
if (m_modified_channels != 0 || m_prepare_count++ >= 4096)
|
||||
{
|
||||
// call each channel to prepare
|
||||
m_active_channels = 0;
|
||||
for (int chnum = 0; chnum < CHANNELS; chnum++)
|
||||
if (bitfield(chanmask, chnum))
|
||||
if (m_channel[chnum]->prepare())
|
||||
m_active_channels |= 1 << chnum;
|
||||
|
||||
// reset the modified channels and prepare count
|
||||
m_modified_channels = m_prepare_count = 0;
|
||||
}
|
||||
|
||||
// increment the envelope counter; the envelope generator
|
||||
// only clocks every other sample in order to make the PCM
|
||||
// envelopes line up with the FM envelopes (after taking into
|
||||
// account the different FM sampling rate)
|
||||
m_env_counter++;
|
||||
|
||||
// now update the state of all the channels and operators
|
||||
for (int chnum = 0; chnum < CHANNELS; chnum++)
|
||||
if (bitfield(chanmask, chnum))
|
||||
m_channel[chnum]->clock(m_env_counter >> 1);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// update - master update function
|
||||
//-------------------------------------------------
|
||||
|
||||
void pcm_engine::output(output_data &output, uint32_t chanmask)
|
||||
{
|
||||
// mask out some channels for debug purposes
|
||||
chanmask &= debug::GLOBAL_PCM_CHANNEL_MASK;
|
||||
|
||||
// compute the output of each channel
|
||||
for (int chnum = 0; chnum < CHANNELS; chnum++)
|
||||
if (bitfield(chanmask, chnum))
|
||||
m_channel[chnum]->output(output);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// read - handle reads from the PCM registers
|
||||
//-------------------------------------------------
|
||||
|
||||
uint8_t pcm_engine::read(uint32_t regnum)
|
||||
{
|
||||
// handle reads from the data register
|
||||
if (regnum == 0x06 && m_regs.memory_access_mode() != 0)
|
||||
return m_intf.ymfm_external_read(ACCESS_PCM, m_regs.memory_address_autoinc());
|
||||
|
||||
return m_regs.read(regnum);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// write - handle writes to the PCM registers
|
||||
//-------------------------------------------------
|
||||
|
||||
void pcm_engine::write(uint32_t regnum, uint8_t data)
|
||||
{
|
||||
// handle reads to the data register
|
||||
if (regnum == 0x06 && m_regs.memory_access_mode() != 0)
|
||||
{
|
||||
m_intf.ymfm_external_write(ACCESS_PCM, m_regs.memory_address_autoinc(), data);
|
||||
return;
|
||||
}
|
||||
|
||||
// for now just mark all channels as modified
|
||||
m_modified_channels = ALL_CHANNELS;
|
||||
|
||||
// most writes are passive, consumed only when needed
|
||||
m_regs.write(regnum, data);
|
||||
|
||||
// however, process keyons immediately
|
||||
if (regnum >= 0x68 && regnum <= 0x7f)
|
||||
m_channel[regnum - 0x68]->keyonoff(bitfield(data, 7));
|
||||
|
||||
// and also wavetable writes
|
||||
else if (regnum >= 0x08 && regnum <= 0x1f)
|
||||
m_channel[regnum - 0x08]->load_wavetable();
|
||||
}
|
||||
|
||||
}
|
347
src/engine/platform/sound/ymfm/ymfm_pcm.h
Normal file
347
src/engine/platform/sound/ymfm/ymfm_pcm.h
Normal file
|
@ -0,0 +1,347 @@
|
|||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2021, Aaron Giles
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef YMFM_PCM_H
|
||||
#define YMFM_PCM_H
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ymfm.h"
|
||||
|
||||
namespace ymfm
|
||||
{
|
||||
|
||||
/*
|
||||
Note to self: Sega "Multi-PCM" is almost identical to this
|
||||
|
||||
28 channels
|
||||
|
||||
Writes:
|
||||
00 = data reg, causes write
|
||||
01 = target slot = data - (data / 8)
|
||||
02 = address (clamped to 7)
|
||||
|
||||
Slot data (registers with ADSR/KSR seem to be inaccessible):
|
||||
0: xxxx---- panpot
|
||||
1: xxxxxxxx wavetable low
|
||||
2: xxxxxx-- pitch low
|
||||
-------x wavetable high
|
||||
3: xxxx---- octave
|
||||
----xxxx pitch hi
|
||||
4: x------- key on
|
||||
5: xxxxxxx- total level
|
||||
-------x level direct (0=interpolate)
|
||||
6: --xxx--- LFO frequency
|
||||
-----xxx PM sensitivity
|
||||
7: -----xxx AM sensitivity
|
||||
|
||||
Sample data:
|
||||
+00: start hi
|
||||
+01: start mid
|
||||
+02: start low
|
||||
+03: loop hi
|
||||
+04: loop low
|
||||
+05: -end hi
|
||||
+06: -end low
|
||||
+07: vibrato (reg 6)
|
||||
+08: attack/decay
|
||||
+09: sustain level/rate
|
||||
+0A: ksr/release
|
||||
+0B: LFO amplitude (reg 7)
|
||||
|
||||
*/
|
||||
|
||||
//*********************************************************
|
||||
// INTERFACE CLASSES
|
||||
//*********************************************************
|
||||
|
||||
class pcm_engine;
|
||||
|
||||
|
||||
// ======================> pcm_cache
|
||||
|
||||
// this class holds data that is computed once at the start of clocking
|
||||
// and remains static during subsequent sound generation
|
||||
struct pcm_cache
|
||||
{
|
||||
uint32_t step; // sample position step, as a .16 value
|
||||
uint32_t total_level; // target total level, as a .10 value
|
||||
uint32_t pan_left; // left panning attenuation
|
||||
uint32_t pan_right; // right panning attenuation
|
||||
uint32_t eg_sustain; // sustain level, shifted up to envelope values
|
||||
uint8_t eg_rate[EG_STATES]; // envelope rate, including KSR
|
||||
uint8_t lfo_step; // stepping value for LFO
|
||||
uint8_t am_depth; // scale value for AM LFO
|
||||
uint8_t pm_depth; // scale value for PM LFO
|
||||
};
|
||||
|
||||
|
||||
// ======================> pcm_registers
|
||||
|
||||
//
|
||||
// PCM register map:
|
||||
//
|
||||
// System-wide registers:
|
||||
// 00-01 xxxxxxxx LSI Test
|
||||
// 02 -------x Memory access mode (0=sound gen, 1=read/write)
|
||||
// ------x- Memory type (0=ROM, 1=ROM+SRAM)
|
||||
// ---xxx-- Wave table header
|
||||
// xxx----- Device ID (=1 for YMF278B)
|
||||
// 03 --xxxxxx Memory address high
|
||||
// 04 xxxxxxxx Memory address mid
|
||||
// 05 xxxxxxxx Memory address low
|
||||
// 06 xxxxxxxx Memory data
|
||||
// F8 --xxx--- Mix control (FM_R)
|
||||
// -----xxx Mix control (FM_L)
|
||||
// F9 --xxx--- Mix control (PCM_R)
|
||||
// -----xxx Mix control (PCM_L)
|
||||
//
|
||||
// Channel-specific registers:
|
||||
// 08-1F xxxxxxxx Wave table number low
|
||||
// 20-37 -------x Wave table number high
|
||||
// xxxxxxx- F-number low
|
||||
// 38-4F -----xxx F-number high
|
||||
// ----x--- Pseudo-reverb
|
||||
// xxxx---- Octave
|
||||
// 50-67 xxxxxxx- Total level
|
||||
// -------x Level direct
|
||||
// 68-7F x------- Key on
|
||||
// -x------ Damp
|
||||
// --x----- LFO reset
|
||||
// ---x---- Output channel
|
||||
// ----xxxx Panpot
|
||||
// 80-97 --xxx--- LFO speed
|
||||
// -----xxx Vibrato
|
||||
// 98-AF xxxx---- Attack rate
|
||||
// ----xxxx Decay rate
|
||||
// B0-C7 xxxx---- Sustain level
|
||||
// ----xxxx Sustain rate
|
||||
// C8-DF xxxx---- Rate correction
|
||||
// ----xxxx Release rate
|
||||
// E0-F7 -----xxx AM depth
|
||||
|
||||
class pcm_registers
|
||||
{
|
||||
public:
|
||||
// constants
|
||||
static constexpr uint32_t OUTPUTS = 4;
|
||||
static constexpr uint32_t CHANNELS = 24;
|
||||
static constexpr uint32_t REGISTERS = 0x100;
|
||||
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
|
||||
|
||||
// constructor
|
||||
pcm_registers() { }
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// reset to initial state
|
||||
void reset();
|
||||
|
||||
// update cache information
|
||||
void cache_channel_data(uint32_t choffs, pcm_cache &cache);
|
||||
|
||||
// direct read/write access
|
||||
uint8_t read(uint32_t index ) { return m_regdata[index]; }
|
||||
void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
|
||||
|
||||
// system-wide registers
|
||||
uint32_t memory_access_mode() const { return bitfield(m_regdata[0x02], 0); }
|
||||
uint32_t memory_type() const { return bitfield(m_regdata[0x02], 1); }
|
||||
uint32_t wave_table_header() const { return bitfield(m_regdata[0x02], 2, 3); }
|
||||
uint32_t device_id() const { return bitfield(m_regdata[0x02], 5, 3); }
|
||||
uint32_t memory_address() const { return (bitfield(m_regdata[0x03], 0, 6) << 16) | (m_regdata[0x04] << 8) | m_regdata[0x05]; }
|
||||
uint32_t memory_data() const { return m_regdata[0x06]; }
|
||||
uint32_t mix_fm_r() const { return bitfield(m_regdata[0xf8], 3, 3); }
|
||||
uint32_t mix_fm_l() const { return bitfield(m_regdata[0xf8], 0, 3); }
|
||||
uint32_t mix_pcm_r() const { return bitfield(m_regdata[0xf9], 3, 3); }
|
||||
uint32_t mix_pcm_l() const { return bitfield(m_regdata[0xf9], 0, 3); }
|
||||
|
||||
// per-channel registers
|
||||
uint32_t ch_wave_table_num(uint32_t choffs) const { return m_regdata[choffs + 0x08] | (bitfield(m_regdata[choffs + 0x20], 0) << 8); }
|
||||
uint32_t ch_fnumber(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x20], 1, 7) | (bitfield(m_regdata[choffs + 0x38], 0, 3) << 7); }
|
||||
uint32_t ch_pseudo_reverb(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x38], 3); }
|
||||
uint32_t ch_octave(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x38], 4, 4); }
|
||||
uint32_t ch_total_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x50], 1, 7); }
|
||||
uint32_t ch_level_direct(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x50], 0); }
|
||||
uint32_t ch_keyon(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 7); }
|
||||
uint32_t ch_damp(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 6); }
|
||||
uint32_t ch_lfo_reset(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 5); }
|
||||
uint32_t ch_output_channel(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 4); }
|
||||
uint32_t ch_panpot(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 0, 4); }
|
||||
uint32_t ch_lfo_speed(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x80], 3, 3); }
|
||||
uint32_t ch_vibrato(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x80], 0, 3); }
|
||||
uint32_t ch_attack_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x98], 4, 4); }
|
||||
uint32_t ch_decay_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x98], 0, 4); }
|
||||
uint32_t ch_sustain_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xb0], 4, 4); }
|
||||
uint32_t ch_sustain_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xb0], 0, 4); }
|
||||
uint32_t ch_rate_correction(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xc8], 4, 4); }
|
||||
uint32_t ch_release_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xc8], 0, 4); }
|
||||
uint32_t ch_am_depth(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xe0], 0, 3); }
|
||||
|
||||
// return the memory address and increment it
|
||||
uint32_t memory_address_autoinc()
|
||||
{
|
||||
uint32_t result = memory_address();
|
||||
uint32_t newval = result + 1;
|
||||
m_regdata[0x05] = newval >> 0;
|
||||
m_regdata[0x04] = newval >> 8;
|
||||
m_regdata[0x03] = (newval >> 16) & 0x3f;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
// internal helpers
|
||||
uint32_t effective_rate(uint32_t raw, uint32_t correction);
|
||||
|
||||
// internal state
|
||||
uint8_t m_regdata[REGISTERS]; // register data
|
||||
};
|
||||
|
||||
|
||||
// ======================> pcm_channel
|
||||
|
||||
class pcm_channel
|
||||
{
|
||||
static constexpr uint8_t KEY_ON = 0x01;
|
||||
static constexpr uint8_t KEY_PENDING_ON = 0x02;
|
||||
static constexpr uint8_t KEY_PENDING = 0x04;
|
||||
|
||||
// "quiet" value, used to optimize when we can skip doing working
|
||||
static constexpr uint32_t EG_QUIET = 0x200;
|
||||
|
||||
public:
|
||||
using output_data = ymfm_output<pcm_registers::OUTPUTS>;
|
||||
|
||||
// constructor
|
||||
pcm_channel(pcm_engine &owner, uint32_t choffs);
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// reset the channel state
|
||||
void reset();
|
||||
|
||||
// return the channel offset
|
||||
uint32_t choffs() const { return m_choffs; }
|
||||
|
||||
// prepare prior to clocking
|
||||
bool prepare();
|
||||
|
||||
// master clocking function
|
||||
void clock(uint32_t env_counter);
|
||||
|
||||
// return the computed output value, with panning applied
|
||||
void output(output_data &output) const;
|
||||
|
||||
// signal key on/off
|
||||
void keyonoff(bool on);
|
||||
|
||||
// load a new wavetable entry
|
||||
void load_wavetable();
|
||||
|
||||
private:
|
||||
// internal helpers
|
||||
void start_attack();
|
||||
void start_release();
|
||||
void clock_envelope(uint32_t env_counter);
|
||||
int16_t fetch_sample() const;
|
||||
uint8_t read_pcm(uint32_t address) const;
|
||||
|
||||
// internal state
|
||||
uint32_t const m_choffs; // channel offset
|
||||
uint32_t m_baseaddr; // base address
|
||||
uint32_t m_endpos; // ending position
|
||||
uint32_t m_looppos; // loop position
|
||||
uint32_t m_curpos; // current position
|
||||
uint32_t m_nextpos; // next position
|
||||
uint32_t m_lfo_counter; // LFO counter
|
||||
envelope_state m_eg_state; // envelope state
|
||||
uint16_t m_env_attenuation; // computed envelope attenuation
|
||||
uint32_t m_total_level; // total level with as 7.10 for interp
|
||||
uint8_t m_format; // sample format
|
||||
uint8_t m_key_state; // current key state
|
||||
pcm_cache m_cache; // cached data
|
||||
pcm_registers &m_regs; // reference to registers
|
||||
pcm_engine &m_owner; // reference to our owner
|
||||
};
|
||||
|
||||
|
||||
// ======================> pcm_engine
|
||||
|
||||
class pcm_engine
|
||||
{
|
||||
public:
|
||||
static constexpr int OUTPUTS = pcm_registers::OUTPUTS;
|
||||
static constexpr int CHANNELS = pcm_registers::CHANNELS;
|
||||
static constexpr uint32_t ALL_CHANNELS = pcm_registers::ALL_CHANNELS;
|
||||
using output_data = pcm_channel::output_data;
|
||||
|
||||
// constructor
|
||||
pcm_engine(ymfm_interface &intf);
|
||||
|
||||
// reset our status
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// master clocking function
|
||||
void clock(uint32_t chanmask);
|
||||
|
||||
// compute sum of channel outputs
|
||||
void output(output_data &output, uint32_t chanmask);
|
||||
|
||||
// read from the PCM registers
|
||||
uint8_t read(uint32_t regnum);
|
||||
|
||||
// write to the PCM registers
|
||||
void write(uint32_t regnum, uint8_t data);
|
||||
|
||||
// return a reference to our interface
|
||||
ymfm_interface &intf() { return m_intf; }
|
||||
|
||||
// return a reference to our registers
|
||||
pcm_registers ®s() { return m_regs; }
|
||||
|
||||
private:
|
||||
// internal state
|
||||
ymfm_interface &m_intf; // reference to the interface
|
||||
uint32_t m_env_counter; // envelope counter
|
||||
uint32_t m_modified_channels; // bitmask of modified channels
|
||||
uint32_t m_active_channels; // bitmask of active channels
|
||||
uint32_t m_prepare_count; // counter to do periodic prepare sweeps
|
||||
std::unique_ptr<pcm_channel> m_channel[CHANNELS]; // array of channels
|
||||
pcm_registers m_regs; // registers
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // YMFM_PCM_H
|
Loading…
Reference in a new issue