prepare the addition of more OPL emulation cores

This commit is contained in:
tildearrow 2023-11-22 16:35:02 -05:00
parent a9dd2ee6b5
commit 1bcdedda3e
13 changed files with 8560 additions and 0 deletions

View file

@ -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
View 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
View 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

File diff suppressed because it is too large Load diff

272
extern/YM3812-LLE/fmopl2.h vendored Normal file
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

333
extern/YMF262-LLE/fmopl3.h vendored Normal file
View 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;

File diff suppressed because it is too large Load diff

View 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

View 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();
}
}

View 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 &regs() { 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