diff --git a/CMakeLists.txt b/CMakeLists.txt index a08f9d9f5..3c2d8853f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -552,6 +552,7 @@ src/engine/platform/sound/nes/fds.c src/engine/platform/sound/nes/mmc5.c src/engine/platform/sound/vera_psg.c src/engine/platform/sound/vera_pcm.c +src/engine/platform/sound/ymf278b/ymf278.cpp src/engine/platform/sound/atomicssg/ssg.c diff --git a/src/engine/platform/sound/ymf278b/License.txt b/src/engine/platform/sound/ymf278b/License.txt new file mode 100644 index 000000000..d159169d1 --- /dev/null +++ b/src/engine/platform/sound/ymf278b/License.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/src/engine/platform/sound/ymf278b/ymf278.cpp b/src/engine/platform/sound/ymf278b/ymf278.cpp new file mode 100644 index 000000000..ae4529985 --- /dev/null +++ b/src/engine/platform/sound/ymf278b/ymf278.cpp @@ -0,0 +1,1084 @@ +// Based on ymf278b.c written by R. Belmont and O. Galibert + +// Improved by Valley Bell, 2018 +// Thanks to niekniek and l_oliveira for providing recordings from OPL4 hardware. +// Thanks to superctr and wouterv for discussing changes. +// +// Improvements: +// - added TL interpolation, recordings show that internal TL levels are 0x00..0xff +// - fixed ADSR speeds, attack rate 15 is now instant +// - correct clamping of intermediate Rate Correction values +// - emulation of "loop glitch" (going out-of-bounds by playing a sample faster than it the loop is long) +// - made calculation of sample position cleaner and closer to how the HW works +// - increased output resolution from TL (0.375dB) to envelope (0.09375dB) +// - fixed volume table -6dB steps are done using bit shifts, steps in between are multiplicators +// - made octave -8 freeze the sample +// - verified that TL and envelope levels are applied separately, both go silent at -60dB +// - implemented pseudo-reverb and damping according to manual +// - made pseudo-reverb ignore Rate Correction (real hardware ignores it) +// - reimplemented LFO, speed exactly matches the formulas that were probably used when creating the manual +// - fixed LFO (tremolo) amplitude modulation +// - made LFO vibrato and tremolo accurate to hardware +// +// Known issues: +// - Octave -8 was only tested with fnum 0. Other fnum values might behave differently. + +// This class doesn't model a full YMF278b chip. Instead it only models the +// wave part. The FM part in modeled in YMF262 (it's almost 100% compatible, +// the small differences are handled in YMF262). The status register and +// interaction with the FM registers (e.g. the NEW2 bit) is currently handled +// in the MSXMoonSound class. + +#include "ymf278.h" +#include +#include + +// envelope output entries +// fixed to match recordings from actual OPL4 -Valley Bell +constexpr int MAX_ATT_INDEX = 0x280; // makes attack phase right and also goes well with "envelope stops at -60dB" +constexpr int MIN_ATT_INDEX = 0; +constexpr int TL_SHIFT = 2; // envelope values are 4x as fine as TL levels + +constexpr unsigned LFO_SHIFT = 18; // LFO period of up to 0x40000 sample +constexpr unsigned LFO_PERIOD = 1 << LFO_SHIFT; + +// Envelope Generator phases +constexpr int EG_ATT = 4; +constexpr int EG_DEC = 3; +constexpr int EG_SUS = 2; +constexpr int EG_REL = 1; +constexpr int EG_OFF = 0; + +// Pan values, units are -3dB, i.e. 8. +constexpr uint8_t pan_left[16] = { + 0, 8, 16, 24, 32, 40, 48, 255, 255, 0, 0, 0, 0, 0, 0, 0 +}; +constexpr uint8_t pan_right[16] = { + 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 48, 40, 32, 24, 16, 8 +}; + +// decay level table (3dB per step) +// 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB) +static constexpr int16_t SC(int dB) { return int16_t(dB / 3 * 0x20); } +constexpr int16_t dl_tab[16] = { + SC( 0), SC( 3), SC( 6), SC( 9), SC(12), SC(15), SC(18), SC(21), + SC(24), SC(27), SC(30), SC(33), SC(36), SC(39), SC(42), SC(93) +}; + +constexpr byte RATE_STEPS = 8; +constexpr byte eg_inc[15 * RATE_STEPS] = { +//cycle:0 1 2 3 4 5 6 7 + 0, 1, 0, 1, 0, 1, 0, 1, // 0 rates 00..12 0 (increment by 0 or 1) + 0, 1, 0, 1, 1, 1, 0, 1, // 1 rates 00..12 1 + 0, 1, 1, 1, 0, 1, 1, 1, // 2 rates 00..12 2 + 0, 1, 1, 1, 1, 1, 1, 1, // 3 rates 00..12 3 + + 1, 1, 1, 1, 1, 1, 1, 1, // 4 rate 13 0 (increment by 1) + 1, 1, 1, 2, 1, 1, 1, 2, // 5 rate 13 1 + 1, 2, 1, 2, 1, 2, 1, 2, // 6 rate 13 2 + 1, 2, 2, 2, 1, 2, 2, 2, // 7 rate 13 3 + + 2, 2, 2, 2, 2, 2, 2, 2, // 8 rate 14 0 (increment by 2) + 2, 2, 2, 4, 2, 2, 2, 4, // 9 rate 14 1 + 2, 4, 2, 4, 2, 4, 2, 4, // 10 rate 14 2 + 2, 4, 4, 4, 2, 4, 4, 4, // 11 rate 14 3 + + 4, 4, 4, 4, 4, 4, 4, 4, // 12 rates 15 0, 15 1, 15 2, 15 3 for decay + 8, 8, 8, 8, 8, 8, 8, 8, // 13 rates 15 0, 15 1, 15 2, 15 3 for attack (zero time) + 0, 0, 0, 0, 0, 0, 0, 0, // 14 infinity rates for attack and decay(s) +}; + +static constexpr byte O(int a) { return a * RATE_STEPS; } +constexpr byte eg_rate_select[64] = { + O(14),O(14),O(14),O(14), // inf rate + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 4),O( 5),O( 6),O( 7), + O( 8),O( 9),O(10),O(11), + O(12),O(12),O(12),O(12), +}; + +// rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 +// shift 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0 +// mask 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0 +constexpr byte eg_rate_shift[64] = { + 12, 12, 12, 12, + 11, 11, 11, 11, + 10, 10, 10, 10, + 9, 9, 9, 9, + 8, 8, 8, 8, + 7, 7, 7, 7, + 6, 6, 6, 6, + 5, 5, 5, 5, + 4, 4, 4, 4, + 3, 3, 3, 3, + 2, 2, 2, 2, + 1, 1, 1, 1, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, +}; + + +// number of steps the LFO counter advances per sample +// LFO frequency (Hz) -> LFO counter steps per sample +static constexpr int L(double a) { return int((LFO_PERIOD * a) / 44100.0 + 0.5); } +constexpr int lfo_period[8] = { + L(0.168), // step: 1, period: 262144 samples + L(2.019), // step: 12, period: 21845 samples + L(3.196), // step: 19, period: 13797 samples + L(4.206), // step: 25, period: 10486 samples + L(5.215), // step: 31, period: 8456 samples + L(5.888), // step: 35, period: 7490 samples + L(6.224), // step: 37, period: 7085 samples + L(7.066), // step: 42, period: 6242 samples +}; + + +// formula used by Yamaha docs: +// vib_depth_cents(x) = (log2(0x400 + x) - 10) * 1200 +constexpr int16_t vib_depth[8] = { + 0, // 0.000 cents + 2, // 3.378 cents + 3, // 5.065 cents + 4, // 6.750 cents + 6, // 10.114 cents + 12, // 20.170 cents + 24, // 40.106 cents + 48, // 79.307 cents +}; + + +// formula used by Yamaha docs: +// am_depth_db(x) = (x-1) / 0x40 * 6.0 +// They use (x-1), because the depth is multiplied with the AM counter, which has a range of 0..0x7F. +// Thus the maximum attenuation with x=0x80 is (0x7F * 0x80) >> 7 = 0x7F. +// reversed formula: +// am_depth(dB) = round(dB / 6.0 * 0x40) + 1 +constexpr uint8_t am_depth[8] = { + 0x00, // 0.000 dB + 0x14, // 1.781 dB + 0x20, // 2.906 dB + 0x28, // 3.656 dB + 0x30, // 4.406 dB + 0x40, // 5.906 dB + 0x50, // 7.406 dB + 0x80, // 11.910 dB +}; + +// divisions of 16 +constexpr int mix_level[8] = { + 16, // 0dB + 12, // -3dB (approx) + 8, // -6dB + 6, // -9dB (approx) + 4, // -12dB + 3, // -15dB (approx) + 2, // -18dB + 0, // -inf dB +}; + +YMF278::Slot::Slot() +{ + reset(); +} + +// Sign extend a 4-bit value to int (32-bit) +// require: x in range [0..15] +static constexpr int sign_extend_4(int x) +{ + return (x ^ 8) - 8; +} + +// Params: oct in [-8 .. +7] +// fn in [ 0 .. 1023] +// We want to interpret oct as a signed 4-bit number and calculate +// ((fn | 1024) + vib) << (5 + sign_extend_4(oct)) +// Though in this formula the shift can go over a negative distance (in that +// case we should shift in the other direction). +static constexpr unsigned calcStep(int8_t oct, uint16_t fn, int16_t vib = 0) +{ + if (oct == -8) return 0; + unsigned t = (fn + 1024 + vib) << (8 + oct); // use '+' iso '|' (generates slightly better code) + return t >> 3; // was shifted 3 positions too far +} + +void YMF278::Slot::reset() +{ + wave = FN = OCT = TLdest = TL = pan = vib = AM = 0; + DL = AR = D1R = D2R = RC = RR = 0; + PRVB = keyon = DAMP = false; + stepptr = 0; + step = calcStep(OCT, FN); + bits = startaddr = loopaddr = endaddr = 0; + env_vol = MAX_ATT_INDEX; + + lfo_active = false; + lfo_cnt = 0; + lfo = 0; + + state = EG_OFF; + + // not strictly needed, but avoid UMR on savestate + pos = 0; +} + +int YMF278::Slot::compute_rate(int val) const +{ + if (val == 0) { + return 0; + } else if (val == 15) { + return 63; + } + int res = val * 4; + if (RC != 15) { + // clamping verified with HW tests -Valley Bell + res += 2 * YMF_clamp(OCT + RC, 0, 15); + res += (FN & 0x200) ? 1 : 0; + } + return YMF_clamp(res, 0, 63); +} + +int YMF278::Slot::compute_decay_rate(int val) const +{ + if (DAMP) { + // damping + // The manual lists these values for time and attenuation: (44100 samples/second) + // -12dB at 5.8ms, sample 256 + // -48dB at 8.0ms, sample 352 + // -72dB at 9.4ms, sample 416 + // -96dB at 10.9ms, sample 480 + // This results in these durations and rate values for the respective phases: + // 0dB .. -12dB: 256 samples (5.80ms) -> 128 samples per -6dB = rate 48 + // -12dB .. -48dB: 96 samples (2.18ms) -> 16 samples per -6dB = rate 63 + // -48dB .. -72dB: 64 samples (1.45ms) -> 16 samples per -6dB = rate 63 + // -72dB .. -96dB: 64 samples (1.45ms) -> 16 samples per -6dB = rate 63 + // Damping was verified to ignore rate correction. + if (env_vol < dl_tab[4]) { + return 48; // 0dB .. -12dB + } else { + return 63; // -12dB .. -96dB + } + } + if (PRVB) { + // pseudo reverb + // activated when reaching -18dB, overrides D1R/D2R/RR with reverb rate 5 + // + // The manual is actually a bit unclear and just says "RATE=5", + // referring to the D1R/D2R/RR register value. However, later + // pages use "RATE" to refer to the "internal" rate, which is + // (register * 4) + rate correction. HW recordings prove that + // Rate Correction is ignored, so pseudo reverb just sets the + // "internal" rate to a value of 4*5 = 20. + if (env_vol >= dl_tab[6]) { + return 20; + } + } + return compute_rate(val); +} + +int16_t YMF278::Slot::compute_vib() const +{ + // verified via hardware recording: + // With LFO speed 0 (period 262144 samples), each vibrato step takes + // 4096 samples. + // -> 64 steps total + // Also, with vibrato depth 7 (80 cents) and an F-Num of 0x400, the + // final F-Nums are: 0x400 .. 0x43C, 0x43C .. 0x400, 0x400 .. 0x3C4, + // 0x3C4 .. 0x400 + int16_t lfo_fm = lfo_cnt / (LFO_PERIOD / 0x40); + // results in +0x00..+0x0F, +0x0F..+0x00, -0x00..-0x0F, -0x0F..-0x00 + if (lfo_fm & 0x10) lfo_fm ^= 0x1F; + if (lfo_fm & 0x20) lfo_fm = -(lfo_fm & 0x0F); + + return (lfo_fm * vib_depth[vib]) / 12; +} + +uint16_t YMF278::Slot::compute_am() const +{ + // verified via hardware recording: + // With LFO speed 0 (period 262144 samples), each tremolo step takes + // 1024 samples. + // -> 256 steps total + uint16_t lfo_am = lfo_cnt / (LFO_PERIOD / 0x100); + // results in 0x00..0x7F, 0x7F..0x00 + if (lfo_am >= 0x80) lfo_am ^= 0xFF; + + return (lfo_am * am_depth[AM]) >> 7; +} + + +void YMF278Base::advance() +{ + eg_cnt++; + + // modulo counters for volume interpolation + int tl_int_cnt = eg_cnt % 9; // 0 .. 8 + int tl_int_step = (eg_cnt / 9) % 3; // 0 .. 2 + + for (auto& op : slots) { + // volume interpolation + if (tl_int_cnt == 0) { + if (tl_int_step == 0) { + // decrease volume by one step every 27 samples + if (op.TL < op.TLdest) ++op.TL; + } else { + // increase volume by one step every 13.5 samples + if (op.TL > op.TLdest) --op.TL; + } + } + + if (op.lfo_active) { + op.lfo_cnt = (op.lfo_cnt + lfo_period[op.lfo]) & (LFO_PERIOD - 1); + } + + // Envelope Generator + switch (op.state) { + case EG_ATT: { // attack phase + uint8_t rate = op.compute_rate(op.AR); + // Verified by HW recording (and matches Nemesis' tests of the YM2612): + // AR = 0xF during KeyOn results in instant switch to EG_DEC. (see keyOnHelper) + // Setting AR = 0xF while the attack phase is in progress freezes the envelope. + if (rate >= 63) { + break; + } + uint8_t shift = eg_rate_shift[rate]; + if (!(eg_cnt & ((1 << shift) - 1))) { + uint8_t select = eg_rate_select[rate]; + // >>4 makes the attack phase's shape match the actual chip -Valley Bell + op.env_vol += (~op.env_vol * eg_inc[select + ((eg_cnt >> shift) & 7)]) >> 4; + if (op.env_vol <= MIN_ATT_INDEX) { + op.env_vol = MIN_ATT_INDEX; + // TODO does the real HW skip EG_DEC completely, + // or is it active for 1 sample? + op.state = op.DL ? EG_DEC : EG_SUS; + } + } + break; + } + case EG_DEC: { // decay phase + uint8_t rate = op.compute_decay_rate(op.D1R); + uint8_t shift = eg_rate_shift[rate]; + if (!(eg_cnt & ((1 << shift) - 1))) { + uint8_t select = eg_rate_select[rate]; + op.env_vol += eg_inc[select + ((eg_cnt >> shift) & 7)]; + if (op.env_vol >= op.DL) { + op.state = (op.env_vol < MAX_ATT_INDEX) ? EG_SUS : EG_OFF; + } + } + break; + } + case EG_SUS: { // sustain phase + uint8_t rate = op.compute_decay_rate(op.D2R); + uint8_t shift = eg_rate_shift[rate]; + if (!(eg_cnt & ((1 << shift) - 1))) { + uint8_t select = eg_rate_select[rate]; + op.env_vol += eg_inc[select + ((eg_cnt >> shift) & 7)]; + if (op.env_vol >= MAX_ATT_INDEX) { + op.env_vol = MAX_ATT_INDEX; + op.state = EG_OFF; + } + } + break; + } + case EG_REL: { // release phase + uint8_t rate = op.compute_decay_rate(op.RR); + uint8_t shift = eg_rate_shift[rate]; + if (!(eg_cnt & ((1 << shift) - 1))) { + uint8_t select = eg_rate_select[rate]; + op.env_vol += eg_inc[select + ((eg_cnt >> shift) & 7)]; + if (op.env_vol >= MAX_ATT_INDEX) { + op.env_vol = MAX_ATT_INDEX; + op.state = EG_OFF; + } + } + break; + } + case EG_OFF: + // nothing + break; + + default: + UNREACHABLE; + } + } +} + +int16_t YMF278Base::getSample(Slot& slot, uint16_t pos) const +{ + // TODO How does this behave when R#2 bit 0 = 1? + // As-if read returns 0xff? (Like for CPU memory reads.) Or is + // sound generation blocked at some higher level? + switch (slot.bits) { + case 0: { + // 8 bit + return memory[slot.startaddr + pos] << 8; + } + case 1: { + // 12 bit + unsigned addr = slot.startaddr + ((pos / 2) * 3); + if (pos & 1) { + return (memory[addr + 2] << 8) | + (memory[addr + 1] & 0xF0); + } else { + return (memory[addr + 0] << 8) | + ((memory[addr + 1] << 4) & 0xF0); + } + } + case 2: { + // 16 bit + unsigned addr = slot.startaddr + (pos * 2); + return (memory[addr + 0] << 8) | + (memory[addr + 1]); + } + default: + // TODO unspecified + return 0; + } +} + +uint16_t YMF278Base::nextPos(Slot& slot, uint16_t pos, uint16_t increment) +{ + // If there is a 4-sample loop and you advance 12 samples per step, + // it may exceed the end offset. + // This is abused by the "Lizard Star" song to generate noise at 0:52. -Valley Bell + pos += increment; + if ((uint32_t(pos) + slot.endaddr) >= 0x10000) // check position >= (negated) end address + pos += slot.endaddr + slot.loopaddr; // This is how the actual chip does it. + return pos; +} + +bool YMF278Base::anyActive() +{ + return std::any_of(std::begin(slots), std::end(slots), [](auto& op) { return op.state != EG_OFF; }); +} + +// In: 'envVol', 0=max volume, others -> -3/32 = -0.09375 dB/step +// Out: 'x' attenuated by the corresponding factor. +// Note: microbenchmarks have shown that re-doing this calculation is about the +// same speed as using a 4kB lookup table. +static constexpr int vol_factor(int x, unsigned envVol) +{ + if (envVol >= MAX_ATT_INDEX) return 0; // hardware clips to silence below -60dB + int vol_mul = 0x80 - (envVol & 0x3F); // 0x40 values per 6dB + int vol_shift = 7 + (envVol >> 6); + return (x * ((0x8000 * vol_mul) >> vol_shift)) >> 15; +} + +void YMF278Base::generate(short& left, short& right, short* channelBufs) +{ + int sampleLeft = 0; + int sampleRight = 0; + for (size_t i = 0, count = slots.size(); i < count; i++) { + Slot& sl = slots[i]; + if (sl.state == EG_OFF) { + //sampleLeft += 0; + //sampleRight += 0; + if (channelBufs != nullptr) { + channelBufs[i] = 0; + } + continue; + } + + int16_t sample = (getSample(sl, sl.pos) * (0x10000 - sl.stepptr) + + getSample(sl, nextPos(sl, sl.pos, 1)) * sl.stepptr) >> 16; + // TL levels are 00..FF internally (TL register value 7F is mapped to TL level FF) + // Envelope levels have 4x the resolution (000..3FF) + // Volume levels are approximate logarithmic. -6dB result in half volume. Steps in between use linear interpolation. + // A volume of -60dB or lower results in silence. (value 0x280..0x3FF). + // Recordings from actual hardware indicate that TL level and envelope level are applied separarely. + // Each of them is clipped to silence below -60dB, but TL+envelope might result in a lower volume. -Valley Bell + uint16_t envVol = std::min(sl.env_vol + ((sl.lfo_active && sl.AM) ? sl.compute_am() : 0), + MAX_ATT_INDEX); + int smplOut = vol_factor(vol_factor(sample, envVol), sl.TL << TL_SHIFT); + + // Panning is also done separately. (low-volume TL + low-volume panning goes below -60dB) + // I'll be taking wild guess and assume that -3dB is approximated with 75%. (same as with TL and envelope levels) + // The same applies to the PCM mix level. + int32_t volLeft = pan_left [sl.pan]; // note: register 0xF9 is handled externally + int32_t volRight = pan_right[sl.pan]; + // 0 -> 0x20, 8 -> 0x18, 16 -> 0x10, 24 -> 0x0C, etc. (not using vol_factor here saves array boundary checks) + volLeft = (0x20 - (volLeft & 0x0f)) >> (volLeft >> 4); + volRight = (0x20 - (volRight & 0x0f)) >> (volRight >> 4); + + sampleLeft += (smplOut * volLeft ) >> 5; + sampleRight += (smplOut * volRight) >> 5; + + unsigned step = (sl.lfo_active && sl.vib) + ? calcStep(sl.OCT, sl.FN, sl.compute_vib()) + : sl.step; + sl.stepptr += step; + + if (sl.stepptr >= 0x10000) { + sl.pos = nextPos(sl, sl.pos, sl.stepptr >> 16); + sl.stepptr &= 0xffff; + } + + if (channelBufs != nullptr) { + channelBufs[i] = sl.pan != 8 ? smplOut : 0; + } + } + advance(); + + left = sampleLeft >> 4; + right = sampleRight >> 4; +} + +void YMF278Base::keyOnHelper(Slot& slot) +{ + // Unlike FM, the envelope level is reset. (And it makes sense, because you restart the sample.) + slot.env_vol = MAX_ATT_INDEX; + if (slot.compute_rate(slot.AR) < 63) { + slot.state = EG_ATT; + } else { + // Nuke.YKT verified that the FM part does it exactly this way, + // and the OPL4 manual says it's instant as well. + slot.env_vol = MIN_ATT_INDEX; + // see comment in 'case EG_ATT' in YMF278::advance() + slot.state = slot.DL ? EG_DEC : EG_SUS; + } + slot.stepptr = 0; + slot.pos = 0; +} + +YMF278Base::YMF278Base(MemoryInterface& memory, int channelCount, int clockDivider, double clockFrequency) + : memory(memory) + , slots(channelCount) + , channelCount(channelCount) + , clockDivider(clockDivider) + , clockFrequency(clockFrequency) +{ + reset(); +} + +YMF278Base::~YMF278Base() +{ +} + +int YMF278Base::getChannelCount() +{ + return channelCount; +} + +int YMF278Base::getClockDivider() +{ + return clockDivider; +} + +double YMF278Base::getClockFrequency() +{ + return clockFrequency; +} + +void YMF278Base::setClockFrequency(double clockFrequency_) +{ + clockFrequency = clockFrequency_; +} + +double YMF278Base::getSampleRate() +{ + return clockFrequency / (channelCount * clockDivider); +} + +void YMF278Base::reset() +{ + eg_cnt = 0; + for (auto& op : slots) { + op.reset(); + } + memory.setMemoryType(false); +} + +YMF278::YMF278(MemoryInterface& memory) + : YMF278Base(memory, 24, 32, 33868800) + , fmMixL(0), fmMixR(0), pcmMixL(0), pcmMixR(0) +{ + memAdr = 0; // avoid UMR + std::fill(std::begin(regs), std::end(regs), 0); +} + +void YMF278::reset() +{ + YMF278Base::reset(); + + regs[2] = 0; // avoid UMR + for (int i = 0xf7; i >= 0; --i) { // reverse order to avoid UMR + writeReg(i, 0); + } + writeReg(0xf8, 0x1b); + writeReg(0xf9, 0x00); + memAdr = 0; +} + +void YMF278::writeReg(byte reg, byte data) +{ + // Handle slot registers specifically + if (reg >= 0x08 && reg <= 0xF7) { + int sNum = (reg - 8) % 24; + auto& slot = slots[sNum]; + switch ((reg - 8) / 24) { + case 0: { + slot.wave = (slot.wave & 0x100) | data; + int waveTblHdr = (regs[2] >> 2) & 0x7; + int base = (slot.wave < 384 || !waveTblHdr) ? + (slot.wave * 12) : + (waveTblHdr * 0x80000 + ((slot.wave - 384) * 12)); + byte buf[12]; + for (unsigned i = 0; i < 12; ++i) { + // TODO What if R#2 bit 0 = 1? + // See also getSample() + buf[i] = memory[base + i]; + } + slot.bits = (buf[0] & 0xC0) >> 6; + slot.startaddr = buf[2] | (buf[1] << 8) | ((buf[0] & 0x3F) << 16); + slot.loopaddr = buf[4] | (buf[3] << 8); + slot.endaddr = buf[6] | (buf[5] << 8); + for (unsigned i = 7; i < 12; ++i) { + // Verified on real YMF278: + // After tone loading, if you read these + // registers, their value actually has changed. + writeReg(8 + sNum + (i - 2) * 24, buf[i]); + } + if (slot.keyon) { + keyOnHelper(slot); + } else { + slot.stepptr = 0; + slot.pos = 0; + } + break; + } + case 1: { + slot.wave = (slot.wave & 0xFF) | ((data & 0x1) << 8); + slot.FN = (slot.FN & 0x380) | (data >> 1); + slot.step = calcStep(slot.OCT, slot.FN); + break; + } + case 2: { + slot.FN = (slot.FN & 0x07F) | ((data & 0x07) << 7); + slot.PRVB = (data & 0x08) != 0; + slot.OCT = sign_extend_4((data & 0xF0) >> 4); + slot.step = calcStep(slot.OCT, slot.FN); + break; + } + case 3: { + uint8_t t = data >> 1; + slot.TLdest = (t != 0x7f) ? t : 0xff; // verified on HW via volume interpolation + if (data & 1) { + // directly change volume + slot.TL = slot.TLdest; + } else { + // interpolate volume + } + break; + } + case 4: + if (data & 0x10) { + // output to DO1 pin: + // this pin is not used in moonsound + // we emulate this by muting the sound + slot.pan = 8; // both left/right -inf dB + } else { + slot.pan = data & 0x0F; + } + + if (data & 0x20) { + // LFO reset + slot.lfo_active = false; + slot.lfo_cnt = 0; + } else { + // LFO activate + slot.lfo_active = true; + } + + slot.DAMP = (data & 0x40) != 0; + + if (data & 0x80) { + if (!slot.keyon) { + slot.keyon = true; + keyOnHelper(slot); + } + } else { + if (slot.keyon) { + slot.keyon = false; + slot.state = EG_REL; + } + } + break; + case 5: + slot.lfo = (data >> 3) & 0x7; + slot.vib = data & 0x7; + break; + case 6: + slot.AR = data >> 4; + slot.D1R = data & 0xF; + break; + case 7: + slot.DL = dl_tab[data >> 4]; + slot.D2R = data & 0xF; + break; + case 8: + slot.RC = data >> 4; + slot.RR = data & 0xF; + break; + case 9: + slot.AM = data & 0x7; + break; + } + } else { + // All non-slot registers + switch (reg) { + case 0x00: // TEST + case 0x01: + break; + + case 0x02: + // wave-table-header / memory-type / memory-access-mode + // Simply store in regs[2] + memory.setMemoryType(regs[2] & 2); + break; + + case 0x03: + // Verified on real YMF278: + // * Don't update the 'memAdr' variable on writes to + // reg 3 and 4. Only store the value in the 'regs' + // array for later use. + // * The upper 2 bits are not used to address the + // external memories (so from a HW pov they don't + // matter). But if you read back this register, the + // upper 2 bits always read as '0' (even if you wrote + // '1'). So we mask the bits here already. + data &= 0x3F; + break; + + case 0x04: + // See reg 3. + break; + + case 0x05: + // Verified on real YMF278: (see above) + // Only writes to reg 5 change the (full) 'memAdr'. + memAdr = (regs[3] << 16) | (regs[4] << 8) | data; + break; + + case 0x06: // memory data + if (regs[2] & 1) { + memory.write(memAdr, data); + ++memAdr; // no need to mask (again) here + } else { + // Verified on real YMF278: + // - writes are ignored + // - memAdr is NOT increased + } + break; + + case 0xf8: + fmMixL = mix_level[data & 0x7]; + fmMixR = mix_level[data >> 3 & 0x7]; + break; + case 0xf9: + pcmMixL = mix_level[data & 0x7]; + pcmMixR = mix_level[data >> 3 & 0x7]; + break; + } + } + + regs[reg] = data; +} + +byte YMF278::readReg(byte reg) +{ + // no need to call updateStream(time) + byte result = peekReg(reg); + if (reg == 6) { + // Memory Data Register + if (regs[2] & 1) { + // Verified on real YMF278: + // memAdr is only increased when 'regs[2] & 1' + ++memAdr; // no need to mask (again) here + } + } + return result; +} + +byte YMF278::peekReg(byte reg) const +{ + switch (reg) { + case 2: // 3 upper bits are device ID + return (regs[2] & 0x1F) | 0x20; + + case 6: // Memory Data Register + if (regs[2] & 1) { + return memory[memAdr]; + } else { + // Verified on real YMF278 + return 0xff; + } + + default: + return regs[reg]; + } +} + +YMW258::YMW258(MemoryInterface& memory) + : YMF278Base(memory, 28, 8, 9878400) +{ +} + +void YMW258::writeReg(byte channel, byte reg, byte data) +{ + if ((channel & 0x7) == 0x7 || channel >= 0x20 || reg >= 0x8) + return; + int sNum = (channel >> 3) * 7 + (channel & 0x7); + auto& slot = slots[sNum]; + + switch (reg) { + case 0: { + slot.pan = data >> 4; + break; + } + case 1: { + slot.wave = (slot.wave & 0x100) | data; + int base = slot.wave * 12; + byte buf[12]; + for (unsigned i = 0; i < 12; ++i) { + buf[i] = memory[base + i]; + } + slot.bits = (buf[0] >> 6) == 0x3 ? 1 : 0; // 00 / 10: 8 bit, 11: 12 bit, 01: unknown + slot.startaddr = buf[2] | (buf[1] << 8) | ((buf[0] & 0x1F) << 16); + slot.loopaddr = buf[4] | (buf[3] << 8); + slot.endaddr = buf[6] | (buf[5] << 8); + slot.lfo = (buf[7] >> 3) & 0x7; + slot.vib = buf[7] & 0x7; + slot.AR = buf[8] >> 4; + slot.D1R = buf[8] & 0xF; + slot.DL = dl_tab[buf[9] >> 4]; + slot.D2R = buf[9] & 0xF; + slot.RC = buf[10] >> 4; + slot.RR = buf[10] & 0xF; + slot.AM = buf[11] & 0x7; + if (slot.keyon) { + keyOnHelper(slot); + } else { + slot.stepptr = 0; + slot.pos = 0; + } + break; + } + case 2: { + slot.wave = (slot.wave & 0xFF) | ((data & 0x1) << 8); + slot.FN = (slot.FN & 0x3C0) | (data >> 2); + slot.step = calcStep(slot.OCT, slot.FN); + break; + } + case 3: { + slot.FN = (slot.FN & 0x03F) | ((data & 0x0F) << 6); + slot.OCT = sign_extend_4((data & 0xF0) >> 4); + slot.step = calcStep(slot.OCT, slot.FN); + break; + } + case 4: { + slot.lfo_active = true; + if (data & 0x80) { + if (!slot.keyon) { + slot.keyon = true; + keyOnHelper(slot); + } + } else { + if (slot.keyon) { + slot.keyon = false; + slot.state = EG_REL; + } + } + break; + } + case 5: { + uint8_t t = data >> 1; + slot.TLdest = (t != 0x7f) ? t : 0xff; // verified on YMF278 via volume interpolation + if (data & 1) { + // directly change volume + slot.TL = slot.TLdest; + } else { + // interpolate volume + } + break; + } + case 6: { + slot.lfo = (data >> 3) & 0x7; + slot.vib = data & 0x7; + break; + } + case 7: { + slot.AM = data & 0x7; + break; + } + } +} + +MemoryMoonSound::MemoryMoonSound(MemoryInterface& rom, MemoryInterface& ram) + : rom(rom) + , ram(ram) + , memoryType(false) +{ + if (rom.getSize() != 0x200000) { // 2MB + assert(false); + } + assert((ram.getSize() & (1024 - 1)) == 0); + int ramSize_ = ram.getSize() / 1024; + if ((ramSize_ != 0) && // - - + (ramSize_ != 128) && // 128kB - + (ramSize_ != 256) && // 128kB 128kB + (ramSize_ != 512) && // 512kB - + (ramSize_ != 640) && // 512kB 128kB + (ramSize_ != 1024) && // 512kB 512kB + (ramSize_ != 2048)) { // 512kB 512kB 512kB 512kB + assert(false); + } +} + +// This routine translates an address from the (upper) MoonSound address space +// to an address inside the (linearized) SRAM address space. +// +// The following info is based on measurements on a real MoonSound (v2.0) +// PCB. This PCB can have several possible SRAM configurations: +// 128kB: +// 1 SRAM chip of 128kB, chip enable (/CE) of this SRAM chip is connected to +// the 1Y0 output of a 74LS139 (2-to-4 decoder). The enable input of the +// 74LS139 is connected to YMF278 pin /MCS6 and the 74LS139 1B:1A inputs are +// connected to YMF278 pins MA18:MA17. So the SRAM is selected when /MC6 is +// active and MA18:MA17 == 0:0. +// 256kB: +// 2 SRAM chips of 128kB. First one connected as above. Second one has /CE +// connected to 74LS139 pin 1Y1. So SRAM2 is selected when /MSC6 is active +// and MA18:MA17 == 0:1. +// 512kB: +// 1 SRAM chip of 512kB, /CE connected to /MCS6 +// 640kB: +// 1 SRAM chip of 512kB, /CE connected to /MCS6 +// 1 SRAM chip of 128kB, /CE connected to /MCS7. +// (This means SRAM2 is potentially mirrored over a 512kB region) +// 1024kB: +// 1 SRAM chip of 512kB, /CE connected to /MCS6 +// 1 SRAM chip of 512kB, /CE connected to /MCS7 +// 2048kB: +// 1 SRAM chip of 512kB, /CE connected to /MCS6 +// 1 SRAM chip of 512kB, /CE connected to /MCS7 +// 1 SRAM chip of 512kB, /CE connected to /MCS8 +// 1 SRAM chip of 512kB, /CE connected to /MCS9 +// This configuration is not so easy to create on the v2.0 PCB. So it's +// very rare. +// +// So the /MCS6 and /MCS7 (and /MCS8 and /MCS9 in case of 2048kB) signals are +// used to select the different SRAM chips. The meaning of these signals +// depends on the 'memory access mode'. This mode can be changed at run-time +// via bit 1 in register 2. The following table indicates for which regions +// these signals are active (normally MoonSound should be used with mode=0): +// mode=0 mode=1 +// /MCS6 0x200000-0x27FFFF 0x380000-0x39FFFF +// /MCS7 0x280000-0x2FFFFF 0x3A0000-0x3BFFFF +// /MCS8 0x300000-0x37FFFF 0x3C0000-0x3DFFFF +// /MCS9 0x380000-0x3FFFFF 0x3E0000-0x3FFFFF +// +// (For completeness) MoonSound also has 2MB ROM (YRW801), /CE of this ROM is +// connected to YMF278 /MCS0. In both mode=0 and mode=1 this signal is active +// for the region 0x000000-0x1FFFFF. (But this routine does not handle ROM). +unsigned MemoryMoonSound::getRamAddress(unsigned addr) const +{ + addr -= 0x200000; // RAM starts at 0x200000 + if (memoryType) { + // Normally MoonSound is used in 'memory access mode = 0'. But + // in the rare case that mode=1 we adjust the address. + if ((0x180000 <= addr) && (addr <= 0x1FFFFF)) { + addr -= 0x180000; + switch (addr & 0x060000) { + case 0x000000: // [0x380000-0x39FFFF] + // 1st 128kB of SRAM1 + break; + case 0x020000: // [0x3A0000-0x3BFFFF] + if (ram.getSize() == 256 * 1024) { + // 2nd 128kB SRAM chip + } else { + // 2nd block of 128kB in SRAM2 + // In case of 512+128, we use mirroring + addr += 0x080000; + } + break; + case 0x040000: // [0x3C0000-0x3DFFFF] + // 3rd 128kB block in SRAM3 + addr += 0x100000; + break; + case 0x060000: // [0x3EFFFF-0x3FFFFF] + // 4th 128kB block in SRAM4 + addr += 0x180000; + break; + } + } else { + addr = unsigned(-1); // unmapped + } + } + if (ram.getSize() == 640 * 1024) { + // Verified on real MoonSound cartridge (v2.0): In case of + // 640kB (1x512kB + 1x128kB), the 128kB SRAM chip is 4 times + // visible. None of the other SRAM configurations show similar + // mirroring (because the others are powers of two). + if (addr > 0x080000) { + addr &= ~0x060000; + } + } + return addr; +} + +byte MemoryMoonSound::operator[](unsigned address) const +{ + // Verified on real YMF278: address space wraps at 4MB. + address &= 0x3FFFFF; + if (address < 0x200000) { + // ROM connected to /MCS0 + return rom[address]; + } else { + unsigned ramAddr = getRamAddress(address); + if (ramAddr < ram.getSize()) { + return ram[ramAddr]; + } else { + // unmapped region + return 255; // TODO check + } + } +} + +unsigned MemoryMoonSound::getSize() const { + return 0x400000; +} + +void MemoryMoonSound::write(unsigned address, byte value) +{ + address &= 0x3FFFFF; + if (address < 0x200000) { + // can't write to ROM + } else { + unsigned ramAddr = getRamAddress(address); + if (ramAddr < ram.getSize()) { + ram.write(ramAddr, value); + } else { + // can't write to unmapped memory + } + } +} + +void MemoryMoonSound::clear(byte value) { + ram.clear(value); +} + +void MemoryMoonSound::setMemoryType(bool memoryType_) { + memoryType = memoryType_; +} diff --git a/src/engine/platform/sound/ymf278b/ymf278.h b/src/engine/platform/sound/ymf278b/ymf278.h new file mode 100644 index 000000000..f02b179aa --- /dev/null +++ b/src/engine/platform/sound/ymf278b/ymf278.h @@ -0,0 +1,154 @@ +#ifndef YMF278_HH +#define YMF278_HH + +#include +#include +#include + +#define UNREACHABLE while (1) assert(false) + +using byte = uint8_t; + +template +const T& YMF_clamp(const T& value, const T& min, const T& max) { + return std::min(std::max(value, min), max); +} + +class MemoryInterface { +public: + virtual byte operator[](unsigned address) const = 0; + virtual unsigned getSize() const = 0; + virtual void write(unsigned address, byte value) = 0; + virtual void clear(byte value) = 0; + virtual void setMemoryType(bool memoryType) {}; +}; + +class YMF278Base +{ +public: + YMF278Base(MemoryInterface& memory, int channelCount, int clockDivider, double clockFrequency); + ~YMF278Base(); + int getChannelCount(); + int getClockDivider(); + double getClockFrequency(); + void setClockFrequency(double clockFrequency); + double getSampleRate(); + virtual void reset(); + + void generate(short& left, short& right, short* channelBufs = nullptr); + + class Slot final { + public: + Slot(); + void reset(); + int compute_rate(int val) const; + int compute_decay_rate(int val) const; + unsigned decay_rate(int num, int sample_rate); + void envelope_next(int sample_rate); + int16_t compute_vib() const; + uint16_t compute_am() const; + + template + void serialize(Archive& ar, unsigned version); + + uint32_t startaddr; + uint16_t loopaddr; + uint16_t endaddr; // Note: stored in 2s complement (0x0000 = 0, 0x0001 = -65536, 0xffff = -1) + uint32_t step; // fixed-point frequency step + // invariant: step == calcStep(OCT, FN) + uint32_t stepptr; // fixed-point pointer into the sample + uint16_t pos; + + int16_t env_vol; + + uint32_t lfo_cnt; + + int16_t DL; + uint16_t wave; // wavetable number + uint16_t FN; // f-number TODO store 'FN | 1024'? + int8_t OCT; // octave [-8..+7] + bool PRVB; // pseudo-reverb + uint8_t TLdest; // destination total level + uint8_t TL; // total level (goes towards TLdest) + uint8_t pan; // panpot 0..15 + bool keyon; // slot keyed on + bool DAMP; + uint8_t lfo; // LFO speed 0..7 + uint8_t vib; // vibrato 0..7 + uint8_t AM; // AM level 0..7 + uint8_t AR; // 0..15 + uint8_t D1R; // 0..15 + uint8_t D2R; // 0..15 + uint8_t RC; // rate correction 0..15 + uint8_t RR; // 0..15 + + uint8_t bits; // width of the samples + + uint8_t state; // envelope generator state + bool lfo_active; + }; + +protected: + void keyOnHelper(Slot& slot); + + MemoryInterface& memory; + std::vector slots; + +private: + int16_t getSample(Slot& slot, uint16_t pos) const; + static uint16_t nextPos(Slot& slot, uint16_t pos, uint16_t increment); + void advance(); + bool anyActive(); + + /** Global envelope generator counter. */ + unsigned eg_cnt; + + unsigned channelCount, clockDivider; + double clockFrequency; +}; + +class YMF278 final : public YMF278Base { +public: + YMF278(MemoryInterface& memory); + void reset() override; + void writeReg(byte reg, byte data); + byte readReg(byte reg); + byte peekReg(byte reg) const; + + void generateMix(short fmL, short fmR, short& bufL, short& bufR, short* channelBufs = nullptr) { + generate(bufL, bufR, channelBufs); + bufL = std::min(std::max((pcmMixL * bufL + fmMixL * fmL) >> 4, -0x8000), 0x7fff); + bufR = std::min(std::max((pcmMixR * bufR + fmMixR * fmR) >> 4, -0x8000), 0x7fff);; + } + +private: + int fmMixL, fmMixR, pcmMixL, pcmMixR; + int memAdr; + byte regs[256]; +}; + +class YMW258 final : public YMF278Base { +public: + YMW258(MemoryInterface& memory); + void writeReg(byte channel, byte reg, byte data); +}; + +class MemoryMoonSound : MemoryInterface { +public: + MemoryMoonSound(MemoryInterface& rom, MemoryInterface& ram); + byte operator[](unsigned address) const override; + unsigned getSize() const override; + void write(unsigned address, byte value) override; + void clear(byte value) override; + void setMemoryType(bool memoryType) override; + +private: + unsigned getRamAddress(unsigned addr) const; + + MemoryInterface& rom; + MemoryInterface& ram; + + bool memoryType; +}; + +#endif