diff --git a/.gitmodules b/.gitmodules index b256a4347..66a8a3414 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,9 +10,6 @@ [submodule "modules/dasXbyak"] path = modules/dasXbyak url = https://github.com/borisbat/dasXbyak.git -[submodule "modules/dasSound"] - path = modules/dasSound - url = https://github.com/imp5imp5/dasSound.git [submodule "modules/dasMinfft"] path = modules/dasMinfft url = https://github.com/imp5imp5/dasMinfft.git diff --git a/CMakeLists.txt b/CMakeLists.txt index fbeb39c15..b507bfef0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ option(DAS_IMGUI_DISABLED "Disable dasIMGUI (IMGUI, IMNODES, IMGUI-NODE-EDITOR g option(DAS_BGFX_DISABLED "Disable dasBGFX (BGFX graphics API)" ON) option(DAS_XBYAK_DISABLED "Disable dasXbyak (XBYAK and ZYDIS, x86 assembly, jit)" ON) option(DAS_MINFFT_DISABLED "Disable dasMinfft (Minimal FFT library)" ON) -option(DAS_SOUND_DISABLED "Disable dasSound (Miniaudio sound library)" ON) +option(DAS_AUDIO_DISABLED "Disable dasAudio (Miniaudio sound library)" ON) option(DAS_STDDLG_DISABLED "Disable dasStdDlg (File new,open,save etc dialogs)" OFF) option(DAS_STBIMAGE_DISABLED "Disable dasStbImage (StbImage bindings, image loading and saving)" OFF) option(DAS_STBTRUETYPE_DISABLED "Disable dasStbTrueType (StbTrueType bindings, ttf rasterization)" OFF) diff --git a/generate_gcc_linux_min.sh b/generate_gcc_linux_min.sh index 431432895..371f5f4ef 100644 --- a/generate_gcc_linux_min.sh +++ b/generate_gcc_linux_min.sh @@ -13,7 +13,7 @@ CC=gcc-9 CXX=g++-9 cmake -DCMAKE_OSX_ARCHITECTURES="x86_64" \ -DDAS_LLVM_DISABLED:BOOL=TRUE \ -DDAS_QUIRREL_DISABLED:BOOL=TRUE \ -DDAS_MINFFT_DISABLED:BOOL=TRUE \ - -DDAS_SOUND_DISABLED:BOOL=TRUE \ + -DDAS_AUDIO_DISABLED:BOOL=TRUE \ -DDAS_STDDLG_DISABLED:BOOL=TRUE \ -DDAS_STBIMAGE_DISABLED:BOOL=TRUE \ -DDAS_STBTRUETYPE_DISABLED:BOOL=TRUE \ diff --git a/generate_xcode_min.sh b/generate_xcode_min.sh index 119406095..33f04f014 100755 --- a/generate_xcode_min.sh +++ b/generate_xcode_min.sh @@ -14,7 +14,7 @@ time { -DDAS_CLANG_BIND_DISABLED:BOOL=TRUE \ -DDAS_LLVM_DISABLED:BOOL=TRUE \ -DDAS_MINFFT_DISABLED:BOOL=TRUE \ - -DDAS_SOUND_DISABLED:BOOL=TRUE \ + -DDAS_AUDIO_DISABLED:BOOL=TRUE \ -DDAS_STDDLG_DISABLED:BOOL=TRUE \ -DDAS_STBIMAGE_DISABLED:BOOL=TRUE \ -DDAS_STBTRUETYPE_DISABLED:BOOL=TRUE \ diff --git a/modules.py b/modules.py index ca094d739..c7eacde3c 100644 --- a/modules.py +++ b/modules.py @@ -17,7 +17,7 @@ "dasOpenGL": "", "dasQuirrel": "DAS_QUIRREL_DISABLED", "dasSFML": "DAS_SFML_DISABLED", - "dasSound": "DAS_SOUND_DISABLED", + "dasAudio": "DAS_AUDIO_DISABLED", "dasStbImage": "DAS_STBIMAGE_DISABLED", "dasStbTrueType": "DAS_STBTRUETYPE_DISABLED", "dasStdDlg": "DAS_STDDLG_DISABLED", diff --git a/modules/dasAudio/CMakeLists.txt b/modules/dasAudio/CMakeLists.txt new file mode 100644 index 000000000..4884c3b1e --- /dev/null +++ b/modules/dasAudio/CMakeLists.txt @@ -0,0 +1,54 @@ + +IF ((NOT DAS_AUDIO_INCLUDED) AND ((NOT ${DAS_AUDIO_DISABLED}) OR (NOT DEFINED DAS_AUDIO_DISABLED))) + SET(DAS_AUDIO_INCLUDED TRUE) + MESSAGE(STATUS "dasAudio module included.") + + SET(DAS_AUDIO_DIR ${PROJECT_SOURCE_DIR}/modules/dasAudio) + SET(SOUND_INCLUDE_DIR ${DAS_AUDIO_DIR}/src ${DAS_AUDIO_DIR}/miniaudio) + + LIST(APPEND CMAKE_MODULE_PATH ${DAS_AUDIO_DIR}) + + SET(NUKED_OPL3_INCLUDE_DIR ${DAS_AUDIO_DIR}/Nuked-OPL3) + + SET(NUKED_OPL3_SOURCE + ${NUKED_OPL3_INCLUDE_DIR}/opl3.h + ${NUKED_OPL3_INCLUDE_DIR}/opl3.c + ) + + # libDasModuleSound + SET(DAS_AUDIO_MODULE_SRC + ${DAS_AUDIO_DIR}/src/dasAudio.h + ${DAS_AUDIO_DIR}/src/dasAudio.cpp + ) + + ADD_MODULE_LIB(libDasModuleSound) + ADD_MODULE_CPP(Audio) + # ADD_MODULE_NATIVE(SOUND_boost) + ADD_LIBRARY(libDasModuleSound ${DAS_AUDIO_MODULE_SRC} ${DAS_AUDIO_MODULE_PLATFORM_SRC} ${NUKED_OPL3_SOURCE}) + TARGET_LINK_LIBRARIES(libDasModuleSound ${SOUND_LIBRARIES}) + # ADD_DEPENDENCIES(libDasModuleSound) + TARGET_INCLUDE_DIRECTORIES(libDasModuleSound PUBLIC ${SOUND_INCLUDE_DIR} ${NUKED_OPL3_INCLUDE_DIR}) + + SETUP_CPP11(libDasModuleSound) + + #ADD_MODULE_DAS(medialib medialib dasbox_sound_utils) + ADD_MODULE_DAS(audio audio opl3) + ADD_MODULE_DAS(audio audio audio_boost) + + install(DIRECTORY ${PROJECT_SOURCE_DIR}/modules/dasAudio/medialib + DESTINATION modules/dasAudio + FILES_MATCHING + PATTERN "*.das" + ) + + file(GLOB DAS_AUDIO_EXAMPLES + ${PROJECT_SOURCE_DIR}/modules/dasAudio/examples/*.das + ) + install(FILES ${DAS_AUDIO_EXAMPLES} + DESTINATION examples/audio + ) + + install(FILES ${PROJECT_SOURCE_DIR}/modules/dasAudio/miniaudio/LICENSE DESTINATION . RENAME MINIAUDIO.LICENSE) + install(FILES ${PROJECT_SOURCE_DIR}/modules/dasAudio/Nuked-OPL3/LICENSE DESTINATION . RENAME NUKED-OPL3.LICENSE) + +ENDIF() diff --git a/modules/dasAudio/Nuked-OPL3/LICENSE b/modules/dasAudio/Nuked-OPL3/LICENSE new file mode 100644 index 000000000..8000a6faa --- /dev/null +++ b/modules/dasAudio/Nuked-OPL3/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 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. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. 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 library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random + Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/modules/dasAudio/Nuked-OPL3/opl3.c b/modules/dasAudio/Nuked-OPL3/opl3.c new file mode 100644 index 000000000..9128ba8ab --- /dev/null +++ b/modules/dasAudio/Nuked-OPL3/opl3.c @@ -0,0 +1,1530 @@ +/* Nuked OPL3 + * Copyright (C) 2013-2020 Nuke.YKT + * + * This file is part of Nuked OPL3. + * + * Nuked OPL3 is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 + * of the License, or (at your option) any later version. + * + * Nuked OPL3 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Nuked OPL3. If not, see . + + * Nuked OPL3 emulator. + * Thanks: + * MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): + * Feedback and Rhythm part calculation information. + * forums.submarine.org.uk(carbon14, opl3): + * Tremolo and phase generator calculation information. + * OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): + * OPL2 ROMs. + * siliconpr0n.org(John McMaster, digshadow): + * YMF262 and VRC VII decaps and die shots. + * + * version: 1.8 + */ + +#include +#include +#include +#include "opl3.h" + +#if OPL_ENABLE_STEREOEXT && !defined OPL_SIN +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES 1 +#endif +#include +/* input: [0, 256), output: [0, 65536] */ +#define OPL_SIN(x) ((int32_t)(sin((x) * M_PI / 512.0) * 65536.0)) +#endif + +/* Quirk: Some FM channels are output one sample later on the left side than the right. */ +#ifndef OPL_QUIRK_CHANNELSAMPLEDELAY +#define OPL_QUIRK_CHANNELSAMPLEDELAY (!OPL_ENABLE_STEREOEXT) +#endif + +#define RSM_FRAC 10 + +/* Channel types */ + +enum { + ch_2op = 0, + ch_4op = 1, + ch_4op2 = 2, + ch_drum = 3 +}; + +/* Envelope key types */ + +enum { + egk_norm = 0x01, + egk_drum = 0x02 +}; + + +/* + logsin table +*/ + +static const uint16_t logsinrom[256] = { + 0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471, + 0x443, 0x41a, 0x3f5, 0x3d3, 0x3b5, 0x398, 0x37e, 0x365, + 0x34e, 0x339, 0x324, 0x311, 0x2ff, 0x2ed, 0x2dc, 0x2cd, + 0x2bd, 0x2af, 0x2a0, 0x293, 0x286, 0x279, 0x26d, 0x261, + 0x256, 0x24b, 0x240, 0x236, 0x22c, 0x222, 0x218, 0x20f, + 0x206, 0x1fd, 0x1f5, 0x1ec, 0x1e4, 0x1dc, 0x1d4, 0x1cd, + 0x1c5, 0x1be, 0x1b7, 0x1b0, 0x1a9, 0x1a2, 0x19b, 0x195, + 0x18f, 0x188, 0x182, 0x17c, 0x177, 0x171, 0x16b, 0x166, + 0x160, 0x15b, 0x155, 0x150, 0x14b, 0x146, 0x141, 0x13c, + 0x137, 0x133, 0x12e, 0x129, 0x125, 0x121, 0x11c, 0x118, + 0x114, 0x10f, 0x10b, 0x107, 0x103, 0x0ff, 0x0fb, 0x0f8, + 0x0f4, 0x0f0, 0x0ec, 0x0e9, 0x0e5, 0x0e2, 0x0de, 0x0db, + 0x0d7, 0x0d4, 0x0d1, 0x0cd, 0x0ca, 0x0c7, 0x0c4, 0x0c1, + 0x0be, 0x0bb, 0x0b8, 0x0b5, 0x0b2, 0x0af, 0x0ac, 0x0a9, + 0x0a7, 0x0a4, 0x0a1, 0x09f, 0x09c, 0x099, 0x097, 0x094, + 0x092, 0x08f, 0x08d, 0x08a, 0x088, 0x086, 0x083, 0x081, + 0x07f, 0x07d, 0x07a, 0x078, 0x076, 0x074, 0x072, 0x070, + 0x06e, 0x06c, 0x06a, 0x068, 0x066, 0x064, 0x062, 0x060, + 0x05e, 0x05c, 0x05b, 0x059, 0x057, 0x055, 0x053, 0x052, + 0x050, 0x04e, 0x04d, 0x04b, 0x04a, 0x048, 0x046, 0x045, + 0x043, 0x042, 0x040, 0x03f, 0x03e, 0x03c, 0x03b, 0x039, + 0x038, 0x037, 0x035, 0x034, 0x033, 0x031, 0x030, 0x02f, + 0x02e, 0x02d, 0x02b, 0x02a, 0x029, 0x028, 0x027, 0x026, + 0x025, 0x024, 0x023, 0x022, 0x021, 0x020, 0x01f, 0x01e, + 0x01d, 0x01c, 0x01b, 0x01a, 0x019, 0x018, 0x017, 0x017, + 0x016, 0x015, 0x014, 0x014, 0x013, 0x012, 0x011, 0x011, + 0x010, 0x00f, 0x00f, 0x00e, 0x00d, 0x00d, 0x00c, 0x00c, + 0x00b, 0x00a, 0x00a, 0x009, 0x009, 0x008, 0x008, 0x007, + 0x007, 0x007, 0x006, 0x006, 0x005, 0x005, 0x005, 0x004, + 0x004, 0x004, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002, + 0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000 +}; + +/* + exp table +*/ + +static const uint16_t exprom[256] = { + 0x7fa, 0x7f5, 0x7ef, 0x7ea, 0x7e4, 0x7df, 0x7da, 0x7d4, + 0x7cf, 0x7c9, 0x7c4, 0x7bf, 0x7b9, 0x7b4, 0x7ae, 0x7a9, + 0x7a4, 0x79f, 0x799, 0x794, 0x78f, 0x78a, 0x784, 0x77f, + 0x77a, 0x775, 0x770, 0x76a, 0x765, 0x760, 0x75b, 0x756, + 0x751, 0x74c, 0x747, 0x742, 0x73d, 0x738, 0x733, 0x72e, + 0x729, 0x724, 0x71f, 0x71a, 0x715, 0x710, 0x70b, 0x706, + 0x702, 0x6fd, 0x6f8, 0x6f3, 0x6ee, 0x6e9, 0x6e5, 0x6e0, + 0x6db, 0x6d6, 0x6d2, 0x6cd, 0x6c8, 0x6c4, 0x6bf, 0x6ba, + 0x6b5, 0x6b1, 0x6ac, 0x6a8, 0x6a3, 0x69e, 0x69a, 0x695, + 0x691, 0x68c, 0x688, 0x683, 0x67f, 0x67a, 0x676, 0x671, + 0x66d, 0x668, 0x664, 0x65f, 0x65b, 0x657, 0x652, 0x64e, + 0x649, 0x645, 0x641, 0x63c, 0x638, 0x634, 0x630, 0x62b, + 0x627, 0x623, 0x61e, 0x61a, 0x616, 0x612, 0x60e, 0x609, + 0x605, 0x601, 0x5fd, 0x5f9, 0x5f5, 0x5f0, 0x5ec, 0x5e8, + 0x5e4, 0x5e0, 0x5dc, 0x5d8, 0x5d4, 0x5d0, 0x5cc, 0x5c8, + 0x5c4, 0x5c0, 0x5bc, 0x5b8, 0x5b4, 0x5b0, 0x5ac, 0x5a8, + 0x5a4, 0x5a0, 0x59c, 0x599, 0x595, 0x591, 0x58d, 0x589, + 0x585, 0x581, 0x57e, 0x57a, 0x576, 0x572, 0x56f, 0x56b, + 0x567, 0x563, 0x560, 0x55c, 0x558, 0x554, 0x551, 0x54d, + 0x549, 0x546, 0x542, 0x53e, 0x53b, 0x537, 0x534, 0x530, + 0x52c, 0x529, 0x525, 0x522, 0x51e, 0x51b, 0x517, 0x514, + 0x510, 0x50c, 0x509, 0x506, 0x502, 0x4ff, 0x4fb, 0x4f8, + 0x4f4, 0x4f1, 0x4ed, 0x4ea, 0x4e7, 0x4e3, 0x4e0, 0x4dc, + 0x4d9, 0x4d6, 0x4d2, 0x4cf, 0x4cc, 0x4c8, 0x4c5, 0x4c2, + 0x4be, 0x4bb, 0x4b8, 0x4b5, 0x4b1, 0x4ae, 0x4ab, 0x4a8, + 0x4a4, 0x4a1, 0x49e, 0x49b, 0x498, 0x494, 0x491, 0x48e, + 0x48b, 0x488, 0x485, 0x482, 0x47e, 0x47b, 0x478, 0x475, + 0x472, 0x46f, 0x46c, 0x469, 0x466, 0x463, 0x460, 0x45d, + 0x45a, 0x457, 0x454, 0x451, 0x44e, 0x44b, 0x448, 0x445, + 0x442, 0x43f, 0x43c, 0x439, 0x436, 0x433, 0x430, 0x42d, + 0x42a, 0x428, 0x425, 0x422, 0x41f, 0x41c, 0x419, 0x416, + 0x414, 0x411, 0x40e, 0x40b, 0x408, 0x406, 0x403, 0x400 +}; + +/* + freq mult table multiplied by 2 + + 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 12, 12, 15, 15 +*/ + +static const uint8_t mt[16] = { + 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30 +}; + +/* + ksl table +*/ + +static const uint8_t kslrom[16] = { + 0, 32, 40, 45, 48, 51, 53, 55, 56, 58, 59, 60, 61, 62, 63, 64 +}; + +static const uint8_t kslshift[4] = { + 8, 1, 2, 0 +}; + +/* + envelope generator constants +*/ + +static const uint8_t eg_incstep[4][4] = { + { 0, 0, 0, 0 }, + { 1, 0, 0, 0 }, + { 1, 0, 1, 0 }, + { 1, 1, 1, 0 } +}; + +/* + address decoding +*/ + +static const int8_t ad_slot[0x20] = { + 0, 1, 2, 3, 4, 5, -1, -1, 6, 7, 8, 9, 10, 11, -1, -1, + 12, 13, 14, 15, 16, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +static const uint8_t ch_slot[18] = { + 0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32 +}; + +#if OPL_ENABLE_STEREOEXT +/* + stereo extension panning table +*/ + +static int32_t panpot_lut[256]; +static uint8_t panpot_lut_build = 0; +#endif + +/* + Envelope generator +*/ + +typedef int16_t(*envelope_sinfunc)(uint16_t phase, uint16_t envelope); +typedef void(*envelope_genfunc)(opl3_slot *slott); + +static int16_t OPL3_EnvelopeCalcExp(uint32_t level) +{ + if (level > 0x1fff) + { + level = 0x1fff; + } + return (exprom[level & 0xff] << 1) >> (level >> 8); +} + +static int16_t OPL3_EnvelopeCalcSin0(uint16_t phase, uint16_t envelope) +{ + uint16_t out = 0; + uint16_t neg = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + neg = 0xffff; + } + if (phase & 0x100) + { + out = logsinrom[(phase & 0xff) ^ 0xff]; + } + else + { + out = logsinrom[phase & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)) ^ neg; +} + +static int16_t OPL3_EnvelopeCalcSin1(uint16_t phase, uint16_t envelope) +{ + uint16_t out = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + out = 0x1000; + } + else if (phase & 0x100) + { + out = logsinrom[(phase & 0xff) ^ 0xff]; + } + else + { + out = logsinrom[phase & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)); +} + +static int16_t OPL3_EnvelopeCalcSin2(uint16_t phase, uint16_t envelope) +{ + uint16_t out = 0; + phase &= 0x3ff; + if (phase & 0x100) + { + out = logsinrom[(phase & 0xff) ^ 0xff]; + } + else + { + out = logsinrom[phase & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)); +} + +static int16_t OPL3_EnvelopeCalcSin3(uint16_t phase, uint16_t envelope) +{ + uint16_t out = 0; + phase &= 0x3ff; + if (phase & 0x100) + { + out = 0x1000; + } + else + { + out = logsinrom[phase & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)); +} + +static int16_t OPL3_EnvelopeCalcSin4(uint16_t phase, uint16_t envelope) +{ + uint16_t out = 0; + uint16_t neg = 0; + phase &= 0x3ff; + if ((phase & 0x300) == 0x100) + { + neg = 0xffff; + } + if (phase & 0x200) + { + out = 0x1000; + } + else if (phase & 0x80) + { + out = logsinrom[((phase ^ 0xff) << 1) & 0xff]; + } + else + { + out = logsinrom[(phase << 1) & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)) ^ neg; +} + +static int16_t OPL3_EnvelopeCalcSin5(uint16_t phase, uint16_t envelope) +{ + uint16_t out = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + out = 0x1000; + } + else if (phase & 0x80) + { + out = logsinrom[((phase ^ 0xff) << 1) & 0xff]; + } + else + { + out = logsinrom[(phase << 1) & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)); +} + +static int16_t OPL3_EnvelopeCalcSin6(uint16_t phase, uint16_t envelope) +{ + uint16_t neg = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + neg = 0xffff; + } + return OPL3_EnvelopeCalcExp(envelope << 3) ^ neg; +} + +static int16_t OPL3_EnvelopeCalcSin7(uint16_t phase, uint16_t envelope) +{ + uint16_t out = 0; + uint16_t neg = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + neg = 0xffff; + phase = (phase & 0x1ff) ^ 0x1ff; + } + out = phase << 3; + return OPL3_EnvelopeCalcExp(out + (envelope << 3)) ^ neg; +} + +static const envelope_sinfunc envelope_sin[8] = { + OPL3_EnvelopeCalcSin0, + OPL3_EnvelopeCalcSin1, + OPL3_EnvelopeCalcSin2, + OPL3_EnvelopeCalcSin3, + OPL3_EnvelopeCalcSin4, + OPL3_EnvelopeCalcSin5, + OPL3_EnvelopeCalcSin6, + OPL3_EnvelopeCalcSin7 +}; + +enum envelope_gen_num +{ + envelope_gen_num_attack = 0, + envelope_gen_num_decay = 1, + envelope_gen_num_sustain = 2, + envelope_gen_num_release = 3 +}; + +static void OPL3_EnvelopeUpdateKSL(opl3_slot *slot) +{ + int16_t ksl = (kslrom[slot->channel->f_num >> 6] << 2) + - ((0x08 - slot->channel->block) << 5); + if (ksl < 0) + { + ksl = 0; + } + slot->eg_ksl = (uint8_t)ksl; +} + +static void OPL3_EnvelopeCalc(opl3_slot *slot) +{ + uint8_t nonzero; + uint8_t rate; + uint8_t rate_hi; + uint8_t rate_lo; + uint8_t reg_rate = 0; + uint8_t ks; + uint8_t eg_shift, shift; + uint16_t eg_rout; + int16_t eg_inc; + uint8_t eg_off; + uint8_t reset = 0; + slot->eg_out = slot->eg_rout + (slot->reg_tl << 2) + + (slot->eg_ksl >> kslshift[slot->reg_ksl]) + *slot->trem; + if (slot->key && slot->eg_gen == envelope_gen_num_release) + { + reset = 1; + reg_rate = slot->reg_ar; + } + else + { + switch (slot->eg_gen) + { + case envelope_gen_num_attack: + reg_rate = slot->reg_ar; + break; + case envelope_gen_num_decay: + reg_rate = slot->reg_dr; + break; + case envelope_gen_num_sustain: + if (!slot->reg_type) + { + reg_rate = slot->reg_rr; + } + break; + case envelope_gen_num_release: + reg_rate = slot->reg_rr; + break; + } + } + slot->pg_reset = reset; + ks = slot->channel->ksv >> ((slot->reg_ksr ^ 1) << 1); + nonzero = (reg_rate != 0); + rate = ks + (reg_rate << 2); + rate_hi = rate >> 2; + rate_lo = rate & 0x03; + if (rate_hi & 0x10) + { + rate_hi = 0x0f; + } + eg_shift = rate_hi + slot->chip->eg_add; + shift = 0; + if (nonzero) + { + if (rate_hi < 12) + { + if (slot->chip->eg_state) + { + switch (eg_shift) + { + case 12: + shift = 1; + break; + case 13: + shift = (rate_lo >> 1) & 0x01; + break; + case 14: + shift = rate_lo & 0x01; + break; + default: + break; + } + } + } + else + { + shift = (rate_hi & 0x03) + eg_incstep[rate_lo][slot->chip->timer & 0x03]; + if (shift & 0x04) + { + shift = 0x03; + } + if (!shift) + { + shift = slot->chip->eg_state; + } + } + } + eg_rout = slot->eg_rout; + eg_inc = 0; + eg_off = 0; + /* Instant attack */ + if (reset && rate_hi == 0x0f) + { + eg_rout = 0x00; + } + /* Envelope off */ + if ((slot->eg_rout & 0x1f8) == 0x1f8) + { + eg_off = 1; + } + if (slot->eg_gen != envelope_gen_num_attack && !reset && eg_off) + { + eg_rout = 0x1ff; + } + switch (slot->eg_gen) + { + case envelope_gen_num_attack: + if (!slot->eg_rout) + { + slot->eg_gen = envelope_gen_num_decay; + } + else if (slot->key && shift > 0 && rate_hi != 0x0f) + { + eg_inc = ~slot->eg_rout >> (4 - shift); + } + break; + case envelope_gen_num_decay: + if ((slot->eg_rout >> 4) == slot->reg_sl) + { + slot->eg_gen = envelope_gen_num_sustain; + } + else if (!eg_off && !reset && shift > 0) + { + eg_inc = 1 << (shift - 1); + } + break; + case envelope_gen_num_sustain: + case envelope_gen_num_release: + if (!eg_off && !reset && shift > 0) + { + eg_inc = 1 << (shift - 1); + } + break; + } + slot->eg_rout = (eg_rout + eg_inc) & 0x1ff; + /* Key off */ + if (reset) + { + slot->eg_gen = envelope_gen_num_attack; + } + if (!slot->key) + { + slot->eg_gen = envelope_gen_num_release; + } +} + +static void OPL3_EnvelopeKeyOn(opl3_slot *slot, uint8_t type) +{ + slot->key |= type; +} + +static void OPL3_EnvelopeKeyOff(opl3_slot *slot, uint8_t type) +{ + slot->key &= ~type; +} + +/* + Phase Generator +*/ + +static void OPL3_PhaseGenerate(opl3_slot *slot) +{ + opl3_chip *chip; + uint16_t f_num; + uint32_t basefreq; + uint8_t rm_xor, n_bit; + uint32_t noise; + uint16_t phase; + + chip = slot->chip; + f_num = slot->channel->f_num; + if (slot->reg_vib) + { + int8_t range; + uint8_t vibpos; + + range = (f_num >> 7) & 7; + vibpos = slot->chip->vibpos; + + if (!(vibpos & 3)) + { + range = 0; + } + else if (vibpos & 1) + { + range >>= 1; + } + range >>= slot->chip->vibshift; + + if (vibpos & 4) + { + range = -range; + } + f_num += range; + } + basefreq = (f_num << slot->channel->block) >> 1; + phase = (uint16_t)(slot->pg_phase >> 9); + if (slot->pg_reset) + { + slot->pg_phase = 0; + } + slot->pg_phase += (basefreq * mt[slot->reg_mult]) >> 1; + /* Rhythm mode */ + noise = chip->noise; + slot->pg_phase_out = phase; + if (slot->slot_num == 13) /* hh */ + { + chip->rm_hh_bit2 = (phase >> 2) & 1; + chip->rm_hh_bit3 = (phase >> 3) & 1; + chip->rm_hh_bit7 = (phase >> 7) & 1; + chip->rm_hh_bit8 = (phase >> 8) & 1; + } + if (slot->slot_num == 17 && (chip->rhy & 0x20)) /* tc */ + { + chip->rm_tc_bit3 = (phase >> 3) & 1; + chip->rm_tc_bit5 = (phase >> 5) & 1; + } + if (chip->rhy & 0x20) + { + rm_xor = (chip->rm_hh_bit2 ^ chip->rm_hh_bit7) + | (chip->rm_hh_bit3 ^ chip->rm_tc_bit5) + | (chip->rm_tc_bit3 ^ chip->rm_tc_bit5); + switch (slot->slot_num) + { + case 13: /* hh */ + slot->pg_phase_out = rm_xor << 9; + if (rm_xor ^ (noise & 1)) + { + slot->pg_phase_out |= 0xd0; + } + else + { + slot->pg_phase_out |= 0x34; + } + break; + case 16: /* sd */ + slot->pg_phase_out = (chip->rm_hh_bit8 << 9) + | ((chip->rm_hh_bit8 ^ (noise & 1)) << 8); + break; + case 17: /* tc */ + slot->pg_phase_out = (rm_xor << 9) | 0x80; + break; + default: + break; + } + } + n_bit = ((noise >> 14) ^ noise) & 0x01; + chip->noise = (noise >> 1) | (n_bit << 22); +} + +/* + Slot +*/ + +static void OPL3_SlotWrite20(opl3_slot *slot, uint8_t data) +{ + if ((data >> 7) & 0x01) + { + slot->trem = &slot->chip->tremolo; + } + else + { + slot->trem = (uint8_t*)&slot->chip->zeromod; + } + slot->reg_vib = (data >> 6) & 0x01; + slot->reg_type = (data >> 5) & 0x01; + slot->reg_ksr = (data >> 4) & 0x01; + slot->reg_mult = data & 0x0f; +} + +static void OPL3_SlotWrite40(opl3_slot *slot, uint8_t data) +{ + slot->reg_ksl = (data >> 6) & 0x03; + slot->reg_tl = data & 0x3f; + OPL3_EnvelopeUpdateKSL(slot); +} + +static void OPL3_SlotWrite60(opl3_slot *slot, uint8_t data) +{ + slot->reg_ar = (data >> 4) & 0x0f; + slot->reg_dr = data & 0x0f; +} + +static void OPL3_SlotWrite80(opl3_slot *slot, uint8_t data) +{ + slot->reg_sl = (data >> 4) & 0x0f; + if (slot->reg_sl == 0x0f) + { + slot->reg_sl = 0x1f; + } + slot->reg_rr = data & 0x0f; +} + +static void OPL3_SlotWriteE0(opl3_slot *slot, uint8_t data) +{ + slot->reg_wf = data & 0x07; + if (slot->chip->newm == 0x00) + { + slot->reg_wf &= 0x03; + } +} + +static void OPL3_SlotGenerate(opl3_slot *slot) +{ + slot->out = envelope_sin[slot->reg_wf](slot->pg_phase_out + *slot->mod, slot->eg_out); +} + +static void OPL3_SlotCalcFB(opl3_slot *slot) +{ + if (slot->channel->fb != 0x00) + { + slot->fbmod = (slot->prout + slot->out) >> (0x09 - slot->channel->fb); + } + else + { + slot->fbmod = 0; + } + slot->prout = slot->out; +} + +/* + Channel +*/ + +static void OPL3_ChannelSetupAlg(opl3_channel *channel); + +static void OPL3_ChannelUpdateRhythm(opl3_chip *chip, uint8_t data) +{ + opl3_channel *channel6; + opl3_channel *channel7; + opl3_channel *channel8; + uint8_t chnum; + + chip->rhy = data & 0x3f; + if (chip->rhy & 0x20) + { + channel6 = &chip->channel[6]; + channel7 = &chip->channel[7]; + channel8 = &chip->channel[8]; + channel6->out[0] = &channel6->slots[1]->out; + channel6->out[1] = &channel6->slots[1]->out; + channel6->out[2] = &chip->zeromod; + channel6->out[3] = &chip->zeromod; + channel7->out[0] = &channel7->slots[0]->out; + channel7->out[1] = &channel7->slots[0]->out; + channel7->out[2] = &channel7->slots[1]->out; + channel7->out[3] = &channel7->slots[1]->out; + channel8->out[0] = &channel8->slots[0]->out; + channel8->out[1] = &channel8->slots[0]->out; + channel8->out[2] = &channel8->slots[1]->out; + channel8->out[3] = &channel8->slots[1]->out; + for (chnum = 6; chnum < 9; chnum++) + { + chip->channel[chnum].chtype = ch_drum; + } + OPL3_ChannelSetupAlg(channel6); + OPL3_ChannelSetupAlg(channel7); + OPL3_ChannelSetupAlg(channel8); + /* hh */ + if (chip->rhy & 0x01) + { + OPL3_EnvelopeKeyOn(channel7->slots[0], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel7->slots[0], egk_drum); + } + /* tc */ + if (chip->rhy & 0x02) + { + OPL3_EnvelopeKeyOn(channel8->slots[1], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel8->slots[1], egk_drum); + } + /* tom */ + if (chip->rhy & 0x04) + { + OPL3_EnvelopeKeyOn(channel8->slots[0], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel8->slots[0], egk_drum); + } + /* sd */ + if (chip->rhy & 0x08) + { + OPL3_EnvelopeKeyOn(channel7->slots[1], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel7->slots[1], egk_drum); + } + /* bd */ + if (chip->rhy & 0x10) + { + OPL3_EnvelopeKeyOn(channel6->slots[0], egk_drum); + OPL3_EnvelopeKeyOn(channel6->slots[1], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel6->slots[0], egk_drum); + OPL3_EnvelopeKeyOff(channel6->slots[1], egk_drum); + } + } + else + { + for (chnum = 6; chnum < 9; chnum++) + { + chip->channel[chnum].chtype = ch_2op; + OPL3_ChannelSetupAlg(&chip->channel[chnum]); + OPL3_EnvelopeKeyOff(chip->channel[chnum].slots[0], egk_drum); + OPL3_EnvelopeKeyOff(chip->channel[chnum].slots[1], egk_drum); + } + } +} + +static void OPL3_ChannelWriteA0(opl3_channel *channel, uint8_t data) +{ + if (channel->chip->newm && channel->chtype == ch_4op2) + { + return; + } + channel->f_num = (channel->f_num & 0x300) | data; + channel->ksv = (channel->block << 1) + | ((channel->f_num >> (0x09 - channel->chip->nts)) & 0x01); + OPL3_EnvelopeUpdateKSL(channel->slots[0]); + OPL3_EnvelopeUpdateKSL(channel->slots[1]); + if (channel->chip->newm && channel->chtype == ch_4op) + { + channel->pair->f_num = channel->f_num; + channel->pair->ksv = channel->ksv; + OPL3_EnvelopeUpdateKSL(channel->pair->slots[0]); + OPL3_EnvelopeUpdateKSL(channel->pair->slots[1]); + } +} + +static void OPL3_ChannelWriteB0(opl3_channel *channel, uint8_t data) +{ + if (channel->chip->newm && channel->chtype == ch_4op2) + { + return; + } + channel->f_num = (channel->f_num & 0xff) | ((data & 0x03) << 8); + channel->block = (data >> 2) & 0x07; + channel->ksv = (channel->block << 1) + | ((channel->f_num >> (0x09 - channel->chip->nts)) & 0x01); + OPL3_EnvelopeUpdateKSL(channel->slots[0]); + OPL3_EnvelopeUpdateKSL(channel->slots[1]); + if (channel->chip->newm && channel->chtype == ch_4op) + { + channel->pair->f_num = channel->f_num; + channel->pair->block = channel->block; + channel->pair->ksv = channel->ksv; + OPL3_EnvelopeUpdateKSL(channel->pair->slots[0]); + OPL3_EnvelopeUpdateKSL(channel->pair->slots[1]); + } +} + +static void OPL3_ChannelSetupAlg(opl3_channel *channel) +{ + if (channel->chtype == ch_drum) + { + if (channel->ch_num == 7 || channel->ch_num == 8) + { + channel->slots[0]->mod = &channel->chip->zeromod; + channel->slots[1]->mod = &channel->chip->zeromod; + return; + } + switch (channel->alg & 0x01) + { + case 0x00: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->slots[0]->out; + break; + case 0x01: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->chip->zeromod; + break; + } + return; + } + if (channel->alg & 0x08) + { + return; + } + if (channel->alg & 0x04) + { + channel->pair->out[0] = &channel->chip->zeromod; + channel->pair->out[1] = &channel->chip->zeromod; + channel->pair->out[2] = &channel->chip->zeromod; + channel->pair->out[3] = &channel->chip->zeromod; + switch (channel->alg & 0x03) + { + case 0x00: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->pair->slots[0]->out; + channel->slots[0]->mod = &channel->pair->slots[1]->out; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->slots[1]->out; + channel->out[1] = &channel->chip->zeromod; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x01: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->pair->slots[0]->out; + channel->slots[0]->mod = &channel->chip->zeromod; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->pair->slots[1]->out; + channel->out[1] = &channel->slots[1]->out; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x02: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->chip->zeromod; + channel->slots[0]->mod = &channel->pair->slots[1]->out; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->pair->slots[0]->out; + channel->out[1] = &channel->slots[1]->out; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x03: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->chip->zeromod; + channel->slots[0]->mod = &channel->pair->slots[1]->out; + channel->slots[1]->mod = &channel->chip->zeromod; + channel->out[0] = &channel->pair->slots[0]->out; + channel->out[1] = &channel->slots[0]->out; + channel->out[2] = &channel->slots[1]->out; + channel->out[3] = &channel->chip->zeromod; + break; + } + } + else + { + switch (channel->alg & 0x01) + { + case 0x00: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->slots[1]->out; + channel->out[1] = &channel->chip->zeromod; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x01: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->chip->zeromod; + channel->out[0] = &channel->slots[0]->out; + channel->out[1] = &channel->slots[1]->out; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + } + } +} + +static void OPL3_ChannelUpdateAlg(opl3_channel *channel) +{ + channel->alg = channel->con; + if (channel->chip->newm) + { + if (channel->chtype == ch_4op) + { + channel->pair->alg = 0x04 | (channel->con << 1) | (channel->pair->con); + channel->alg = 0x08; + OPL3_ChannelSetupAlg(channel->pair); + } + else if (channel->chtype == ch_4op2) + { + channel->alg = 0x04 | (channel->pair->con << 1) | (channel->con); + channel->pair->alg = 0x08; + OPL3_ChannelSetupAlg(channel); + } + else + { + OPL3_ChannelSetupAlg(channel); + } + } + else + { + OPL3_ChannelSetupAlg(channel); + } +} + +static void OPL3_ChannelWriteC0(opl3_channel *channel, uint8_t data) +{ + channel->fb = (data & 0x0e) >> 1; + channel->con = data & 0x01; + OPL3_ChannelUpdateAlg(channel); + if (channel->chip->newm) + { + channel->cha = ((data >> 4) & 0x01) ? ~0 : 0; + channel->chb = ((data >> 5) & 0x01) ? ~0 : 0; + channel->chc = ((data >> 6) & 0x01) ? ~0 : 0; + channel->chd = ((data >> 7) & 0x01) ? ~0 : 0; + } + else + { + channel->cha = channel->chb = (uint16_t)~0; + // TODO: Verify on real chip if DAC2 output is disabled in compat mode + channel->chc = channel->chd = 0; + } +#if OPL_ENABLE_STEREOEXT + if (!channel->chip->stereoext) + { + channel->leftpan = channel->cha << 16; + channel->rightpan = channel->chb << 16; + } +#endif +} + +#if OPL_ENABLE_STEREOEXT +static void OPL3_ChannelWriteD0(opl3_channel* channel, uint8_t data) +{ + if (channel->chip->stereoext) + { + channel->leftpan = panpot_lut[data ^ 0xff]; + channel->rightpan = panpot_lut[data]; + } +} +#endif + +static void OPL3_ChannelKeyOn(opl3_channel *channel) +{ + if (channel->chip->newm) + { + if (channel->chtype == ch_4op) + { + OPL3_EnvelopeKeyOn(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOn(channel->slots[1], egk_norm); + OPL3_EnvelopeKeyOn(channel->pair->slots[0], egk_norm); + OPL3_EnvelopeKeyOn(channel->pair->slots[1], egk_norm); + } + else if (channel->chtype == ch_2op || channel->chtype == ch_drum) + { + OPL3_EnvelopeKeyOn(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOn(channel->slots[1], egk_norm); + } + } + else + { + OPL3_EnvelopeKeyOn(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOn(channel->slots[1], egk_norm); + } +} + +static void OPL3_ChannelKeyOff(opl3_channel *channel) +{ + if (channel->chip->newm) + { + if (channel->chtype == ch_4op) + { + OPL3_EnvelopeKeyOff(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOff(channel->slots[1], egk_norm); + OPL3_EnvelopeKeyOff(channel->pair->slots[0], egk_norm); + OPL3_EnvelopeKeyOff(channel->pair->slots[1], egk_norm); + } + else if (channel->chtype == ch_2op || channel->chtype == ch_drum) + { + OPL3_EnvelopeKeyOff(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOff(channel->slots[1], egk_norm); + } + } + else + { + OPL3_EnvelopeKeyOff(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOff(channel->slots[1], egk_norm); + } +} + +static void OPL3_ChannelSet4Op(opl3_chip *chip, uint8_t data) +{ + uint8_t bit; + uint8_t chnum; + for (bit = 0; bit < 6; bit++) + { + chnum = bit; + if (bit >= 3) + { + chnum += 9 - 3; + } + if ((data >> bit) & 0x01) + { + chip->channel[chnum].chtype = ch_4op; + chip->channel[chnum + 3].chtype = ch_4op2; + OPL3_ChannelUpdateAlg(&chip->channel[chnum]); + } + else + { + chip->channel[chnum].chtype = ch_2op; + chip->channel[chnum + 3].chtype = ch_2op; + OPL3_ChannelUpdateAlg(&chip->channel[chnum]); + OPL3_ChannelUpdateAlg(&chip->channel[chnum+3]); + } + } +} + +static int16_t OPL3_ClipSample(int32_t sample) +{ + if (sample > 32767) + { + sample = 32767; + } + else if (sample < -32768) + { + sample = -32768; + } + return (int16_t)sample; +} + +static void OPL3_ProcessSlot(opl3_slot *slot) +{ + OPL3_SlotCalcFB(slot); + OPL3_EnvelopeCalc(slot); + OPL3_PhaseGenerate(slot); + OPL3_SlotGenerate(slot); +} + +inline void OPL3_Generate4Ch(opl3_chip *chip, int16_t *buf4) +{ + opl3_channel *channel; + opl3_writebuf *writebuf; + int16_t **out; + int32_t mix[2]; + uint8_t ii; + int16_t accm; + uint8_t shift = 0; + + buf4[1] = OPL3_ClipSample(chip->mixbuff[1]); + buf4[3] = OPL3_ClipSample(chip->mixbuff[3]); + +#if OPL_QUIRK_CHANNELSAMPLEDELAY + for (ii = 0; ii < 15; ii++) +#else + for (ii = 0; ii < 36; ii++) +#endif + { + OPL3_ProcessSlot(&chip->slot[ii]); + } + + mix[0] = mix[1] = 0; + for (ii = 0; ii < 18; ii++) + { + channel = &chip->channel[ii]; + out = channel->out; + accm = *out[0] + *out[1] + *out[2] + *out[3]; +#if OPL_ENABLE_STEREOEXT + mix[0] += (int16_t)((accm * channel->leftpan) >> 16); +#else + mix[0] += (int16_t)(accm & channel->cha); + #endif + mix[1] += (int16_t)(accm & channel->chc); + } + chip->mixbuff[0] = mix[0]; + chip->mixbuff[2] = mix[1]; + +#if OPL_QUIRK_CHANNELSAMPLEDELAY + for (ii = 15; ii < 18; ii++) + { + OPL3_ProcessSlot(&chip->slot[ii]); + } +#endif + + buf4[0] = OPL3_ClipSample(chip->mixbuff[0]); + buf4[2] = OPL3_ClipSample(chip->mixbuff[2]); + +#if OPL_QUIRK_CHANNELSAMPLEDELAY + for (ii = 18; ii < 33; ii++) + { + OPL3_ProcessSlot(&chip->slot[ii]); + } +#endif + + mix[0] = mix[1] = 0; + for (ii = 0; ii < 18; ii++) + { + channel = &chip->channel[ii]; + out = channel->out; + accm = *out[0] + *out[1] + *out[2] + *out[3]; +#if OPL_ENABLE_STEREOEXT + mix[0] += (int16_t)((accm * channel->rightpan) >> 16); +#else + mix[0] += (int16_t)(accm & channel->chb); + #endif + mix[1] += (int16_t)(accm & channel->chd); + } + chip->mixbuff[1] = mix[0]; + chip->mixbuff[3] = mix[1]; + +#if OPL_QUIRK_CHANNELSAMPLEDELAY + for (ii = 33; ii < 36; ii++) + { + OPL3_ProcessSlot(&chip->slot[ii]); + } +#endif + + if ((chip->timer & 0x3f) == 0x3f) + { + chip->tremolopos = (chip->tremolopos + 1) % 210; + } + if (chip->tremolopos < 105) + { + chip->tremolo = chip->tremolopos >> chip->tremoloshift; + } + else + { + chip->tremolo = (210 - chip->tremolopos) >> chip->tremoloshift; + } + + if ((chip->timer & 0x3ff) == 0x3ff) + { + chip->vibpos = (chip->vibpos + 1) & 7; + } + + chip->timer++; + + chip->eg_add = 0; + if (chip->eg_timer) + { + while (shift < 36 && ((chip->eg_timer >> shift) & 1) == 0) + { + shift++; + } + if (shift > 12) + { + chip->eg_add = 0; + } + else + { + chip->eg_add = shift + 1; + } + } + + if (chip->eg_timerrem || chip->eg_state) + { + if (chip->eg_timer == 0xfffffffff) + { + chip->eg_timer = 0; + chip->eg_timerrem = 1; + } + else + { + chip->eg_timer++; + chip->eg_timerrem = 0; + } + } + + chip->eg_state ^= 1; + + while ((writebuf = &chip->writebuf[chip->writebuf_cur]), writebuf->time <= chip->writebuf_samplecnt) + { + if (!(writebuf->reg & 0x200)) + { + break; + } + writebuf->reg &= 0x1ff; + OPL3_WriteReg(chip, writebuf->reg, writebuf->data); + chip->writebuf_cur = (chip->writebuf_cur + 1) % OPL_WRITEBUF_SIZE; + } + chip->writebuf_samplecnt++; +} + +void OPL3_Generate(opl3_chip *chip, int16_t *buf) +{ + int16_t samples[4]; + OPL3_Generate4Ch(chip, samples); + buf[0] = samples[0]; + buf[1] = samples[1]; +} + +void OPL3_Generate4ChResampled(opl3_chip *chip, int16_t *buf4) +{ + while (chip->samplecnt >= chip->rateratio) + { + chip->oldsamples[0] = chip->samples[0]; + chip->oldsamples[1] = chip->samples[1]; + chip->oldsamples[2] = chip->samples[2]; + chip->oldsamples[3] = chip->samples[3]; + OPL3_Generate4Ch(chip, chip->samples); + chip->samplecnt -= chip->rateratio; + } + buf4[0] = (int16_t)((chip->oldsamples[0] * (chip->rateratio - chip->samplecnt) + + chip->samples[0] * chip->samplecnt) / chip->rateratio); + buf4[1] = (int16_t)((chip->oldsamples[1] * (chip->rateratio - chip->samplecnt) + + chip->samples[1] * chip->samplecnt) / chip->rateratio); + buf4[2] = (int16_t)((chip->oldsamples[2] * (chip->rateratio - chip->samplecnt) + + chip->samples[2] * chip->samplecnt) / chip->rateratio); + buf4[3] = (int16_t)((chip->oldsamples[3] * (chip->rateratio - chip->samplecnt) + + chip->samples[3] * chip->samplecnt) / chip->rateratio); + chip->samplecnt += 1 << RSM_FRAC; +} + +void OPL3_GenerateResampled(opl3_chip *chip, int16_t *buf) +{ + int16_t samples[4]; + OPL3_Generate4ChResampled(chip, samples); + buf[0] = samples[0]; + buf[1] = samples[1]; +} + +void OPL3_Reset(opl3_chip *chip, uint32_t samplerate) +{ + opl3_slot *slot; + opl3_channel *channel; + uint8_t slotnum; + uint8_t channum; + uint8_t local_ch_slot; + + memset(chip, 0, sizeof(opl3_chip)); + for (slotnum = 0; slotnum < 36; slotnum++) + { + slot = &chip->slot[slotnum]; + slot->chip = chip; + slot->mod = &chip->zeromod; + slot->eg_rout = 0x1ff; + slot->eg_out = 0x1ff; + slot->eg_gen = envelope_gen_num_release; + slot->trem = (uint8_t*)&chip->zeromod; + slot->slot_num = slotnum; + } + for (channum = 0; channum < 18; channum++) + { + channel = &chip->channel[channum]; + local_ch_slot = ch_slot[channum]; + channel->slots[0] = &chip->slot[local_ch_slot]; + channel->slots[1] = &chip->slot[local_ch_slot + 3]; + chip->slot[local_ch_slot].channel = channel; + chip->slot[local_ch_slot + 3].channel = channel; + if ((channum % 9) < 3) + { + channel->pair = &chip->channel[channum + 3]; + } + else if ((channum % 9) < 6) + { + channel->pair = &chip->channel[channum - 3]; + } + channel->chip = chip; + channel->out[0] = &chip->zeromod; + channel->out[1] = &chip->zeromod; + channel->out[2] = &chip->zeromod; + channel->out[3] = &chip->zeromod; + channel->chtype = ch_2op; + channel->cha = 0xffff; + channel->chb = 0xffff; +#if OPL_ENABLE_STEREOEXT + channel->leftpan = 0x10000; + channel->rightpan = 0x10000; +#endif + channel->ch_num = channum; + OPL3_ChannelSetupAlg(channel); + } + chip->noise = 1; + chip->rateratio = (samplerate << RSM_FRAC) / 49716; + chip->tremoloshift = 4; + chip->vibshift = 1; + +#if OPL_ENABLE_STEREOEXT + if (!panpot_lut_build) + { + int32_t i; + for (i = 0; i < 256; i++) + { + panpot_lut[i] = OPL_SIN(i); + } + panpot_lut_build = 1; + } +#endif +} + +void OPL3_WriteReg(opl3_chip *chip, uint16_t reg, uint8_t v) +{ + uint8_t high = (reg >> 8) & 0x01; + uint8_t regm = reg & 0xff; + switch (regm & 0xf0) + { + case 0x00: + if (high) + { + switch (regm & 0x0f) + { + case 0x04: + OPL3_ChannelSet4Op(chip, v); + break; + case 0x05: + chip->newm = v & 0x01; +#if OPL_ENABLE_STEREOEXT + chip->stereoext = (v >> 1) & 0x01; +#endif + break; + } + } + else + { + switch (regm & 0x0f) + { + case 0x08: + chip->nts = (v >> 6) & 0x01; + break; + } + } + break; + case 0x20: + case 0x30: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWrite20(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0x40: + case 0x50: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWrite40(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0x60: + case 0x70: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWrite60(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0x80: + case 0x90: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWrite80(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0xe0: + case 0xf0: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWriteE0(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0xa0: + if ((regm & 0x0f) < 9) + { + OPL3_ChannelWriteA0(&chip->channel[9 * high + (regm & 0x0f)], v); + } + break; + case 0xb0: + if (regm == 0xbd && !high) + { + chip->tremoloshift = (((v >> 7) ^ 1) << 1) + 2; + chip->vibshift = ((v >> 6) & 0x01) ^ 1; + OPL3_ChannelUpdateRhythm(chip, v); + } + else if ((regm & 0x0f) < 9) + { + OPL3_ChannelWriteB0(&chip->channel[9 * high + (regm & 0x0f)], v); + if (v & 0x20) + { + OPL3_ChannelKeyOn(&chip->channel[9 * high + (regm & 0x0f)]); + } + else + { + OPL3_ChannelKeyOff(&chip->channel[9 * high + (regm & 0x0f)]); + } + } + break; + case 0xc0: + if ((regm & 0x0f) < 9) + { + OPL3_ChannelWriteC0(&chip->channel[9 * high + (regm & 0x0f)], v); + } + break; +#if OPL_ENABLE_STEREOEXT + case 0xd0: + if ((regm & 0x0f) < 9) + { + OPL3_ChannelWriteD0(&chip->channel[9 * high + (regm & 0x0f)], v); + } + break; +#endif + } +} + +void OPL3_WriteRegBuffered(opl3_chip *chip, uint16_t reg, uint8_t v) +{ + uint64_t time1, time2; + opl3_writebuf *writebuf; + uint32_t writebuf_last; + + writebuf_last = chip->writebuf_last; + writebuf = &chip->writebuf[writebuf_last]; + + if (writebuf->reg & 0x200) + { + OPL3_WriteReg(chip, writebuf->reg & 0x1ff, writebuf->data); + + chip->writebuf_cur = (writebuf_last + 1) % OPL_WRITEBUF_SIZE; + chip->writebuf_samplecnt = writebuf->time; + } + + writebuf->reg = reg | 0x200; + writebuf->data = v; + time1 = chip->writebuf_lasttime + OPL_WRITEBUF_DELAY; + time2 = chip->writebuf_samplecnt; + + if (time1 < time2) + { + time1 = time2; + } + + writebuf->time = time1; + chip->writebuf_lasttime = time1; + chip->writebuf_last = (writebuf_last + 1) % OPL_WRITEBUF_SIZE; +} + +void OPL3_Generate4ChStream(opl3_chip *chip, int16_t *sndptr1, int16_t *sndptr2, uint32_t numsamples) +{ + uint_fast32_t i; + int16_t samples[4]; + + for(i = 0; i < numsamples; i++) + { + OPL3_Generate4ChResampled(chip, samples); + sndptr1[0] = samples[0]; + sndptr1[1] = samples[1]; + sndptr2[0] = samples[2]; + sndptr2[1] = samples[3]; + sndptr1 += 2; + sndptr2 += 2; + } +} + +void OPL3_GenerateStream(opl3_chip *chip, int16_t *sndptr, uint32_t numsamples) +{ + uint_fast32_t i; + + for(i = 0; i < numsamples; i++) + { + OPL3_GenerateResampled(chip, sndptr); + sndptr += 2; + } +} diff --git a/modules/dasAudio/Nuked-OPL3/opl3.h b/modules/dasAudio/Nuked-OPL3/opl3.h new file mode 100644 index 000000000..3977f8008 --- /dev/null +++ b/modules/dasAudio/Nuked-OPL3/opl3.h @@ -0,0 +1,172 @@ +/* Nuked OPL3 + * Copyright (C) 2013-2020 Nuke.YKT + * + * This file is part of Nuked OPL3. + * + * Nuked OPL3 is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 + * of the License, or (at your option) any later version. + * + * Nuked OPL3 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Nuked OPL3. If not, see . + + * Nuked OPL3 emulator. + * Thanks: + * MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): + * Feedback and Rhythm part calculation information. + * forums.submarine.org.uk(carbon14, opl3): + * Tremolo and phase generator calculation information. + * OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): + * OPL2 ROMs. + * siliconpr0n.org(John McMaster, digshadow): + * YMF262 and VRC VII decaps and die shots. + * + * version: 1.8 + */ + +#ifndef OPL_OPL3_H +#define OPL_OPL3_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#ifndef OPL_ENABLE_STEREOEXT +#define OPL_ENABLE_STEREOEXT 0 +#endif + +#define OPL_WRITEBUF_SIZE 1024 +#define OPL_WRITEBUF_DELAY 2 + +typedef struct _opl3_slot opl3_slot; +typedef struct _opl3_channel opl3_channel; +typedef struct _opl3_chip opl3_chip; + +struct _opl3_slot { + opl3_channel *channel; + opl3_chip *chip; + int16_t out; + int16_t fbmod; + int16_t *mod; + int16_t prout; + uint16_t eg_rout; + uint16_t eg_out; + uint8_t eg_inc; + uint8_t eg_gen; + uint8_t eg_rate; + uint8_t eg_ksl; + uint8_t *trem; + uint8_t reg_vib; + uint8_t reg_type; + uint8_t reg_ksr; + uint8_t reg_mult; + uint8_t reg_ksl; + uint8_t reg_tl; + uint8_t reg_ar; + uint8_t reg_dr; + uint8_t reg_sl; + uint8_t reg_rr; + uint8_t reg_wf; + uint8_t key; + uint32_t pg_reset; + uint32_t pg_phase; + uint16_t pg_phase_out; + uint8_t slot_num; +}; + +struct _opl3_channel { + opl3_slot *slots[2]; + opl3_channel *pair; + opl3_chip *chip; + int16_t *out[4]; + +#if OPL_ENABLE_STEREOEXT + int32_t leftpan; + int32_t rightpan; +#endif + + uint8_t chtype; + uint16_t f_num; + uint8_t block; + uint8_t fb; + uint8_t con; + uint8_t alg; + uint8_t ksv; + uint16_t cha, chb; + uint16_t chc, chd; + uint8_t ch_num; +}; + +typedef struct _opl3_writebuf { + uint64_t time; + uint16_t reg; + uint8_t data; +} opl3_writebuf; + +struct _opl3_chip { + opl3_channel channel[18]; + opl3_slot slot[36]; + uint16_t timer; + uint64_t eg_timer; + uint8_t eg_timerrem; + uint8_t eg_state; + uint8_t eg_add; + uint8_t newm; + uint8_t nts; + uint8_t rhy; + uint8_t vibpos; + uint8_t vibshift; + uint8_t tremolo; + uint8_t tremolopos; + uint8_t tremoloshift; + uint32_t noise; + int16_t zeromod; + int32_t mixbuff[4]; + uint8_t rm_hh_bit2; + uint8_t rm_hh_bit3; + uint8_t rm_hh_bit7; + uint8_t rm_hh_bit8; + uint8_t rm_tc_bit3; + uint8_t rm_tc_bit5; + +#if OPL_ENABLE_STEREOEXT + uint8_t stereoext; +#endif + + /* OPL3L */ + int32_t rateratio; + int32_t samplecnt; + int16_t oldsamples[4]; + int16_t samples[4]; + + uint64_t writebuf_samplecnt; + uint32_t writebuf_cur; + uint32_t writebuf_last; + uint64_t writebuf_lasttime; + opl3_writebuf writebuf[OPL_WRITEBUF_SIZE]; +}; + +void OPL3_Generate(opl3_chip *chip, int16_t *buf); +void OPL3_GenerateResampled(opl3_chip *chip, int16_t *buf); +void OPL3_Reset(opl3_chip *chip, uint32_t samplerate); +void OPL3_WriteReg(opl3_chip *chip, uint16_t reg, uint8_t v); +void OPL3_WriteRegBuffered(opl3_chip *chip, uint16_t reg, uint8_t v); +void OPL3_GenerateStream(opl3_chip *chip, int16_t *sndptr, uint32_t numsamples); + +void OPL3_Generate4Ch(opl3_chip *chip, int16_t *buf4); +void OPL3_Generate4ChResampled(opl3_chip *chip, int16_t *buf4); +void OPL3_Generate4ChStream(opl3_chip *chip, int16_t *sndptr1, int16_t *sndptr2, uint32_t numsamples); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/modules/dasAudio/README.md b/modules/dasAudio/README.md new file mode 100644 index 000000000..b3788af21 --- /dev/null +++ b/modules/dasAudio/README.md @@ -0,0 +1 @@ +# dasAudio diff --git a/modules/dasAudio/audio/audio_boost.das b/modules/dasAudio/audio/audio_boost.das new file mode 100644 index 000000000..dcf6dd7e7 --- /dev/null +++ b/modules/dasAudio/audio/audio_boost.das @@ -0,0 +1,392 @@ +options indenting = 4 +options no_unused_block_arguments = false +options no_unused_function_arguments = false + +module audio_boost shared private + +require audio +require math +require rtti +require debugapi + +require daslib/array_boost +require daslib/jobque_boost +require daslib/apply_in_context + +// audio config +let public MA_SAMPLE_RATE = 48000 +let public MA_CHANNELS = 2 + +// limiter config +let MA_LIMITER_THRESHOLD = 1.0 +let MA_LIMITER_ATTACK_TIME = 0.005 +let MA_LIMITER_RELEASE_TIME = 0.100 + +typedef SID = uint64 + +class AudioSource + bitrate : int = MA_SAMPLE_RATE // samples per second + channels : int = 1 // 1 or 2 + def ready : bool + return true + def abstract get_samples ( nsamples:int ) : array + def abstract append ( var data : array ) : bool + +class AudioChannel + sid : SID = 0ul + paused : bool = false + stop : bool = false + pitch : float = 1. + source : AudioSource? + resampler : ma_resampler + channel_converter : ma_channel_converter + volume_mixer : ma_volume_mixer + playback_position : uint64 = 0ul + def AudioChannel ( src:AudioSource? ) + source = src + // resampler + var resampler_config <- ma_resampler_config_init( + ma_format ma_format_f32, + uint(source.channels), + uint(source.bitrate), + uint(MA_SAMPLE_RATE), + ma_resample_algorithm ma_resample_algorithm_linear + ) + ma_resampler_init(unsafe(addr(resampler_config)), unsafe(addr(resampler))) + // channel converter + var channel_converter_config <- ma_channel_converter_config_init( + ma_format ma_format_f32, + uint(source.channels), + null, + uint(MA_CHANNELS), + null, + ma_channel_mix_mode ma_channel_mix_mode_default + ) + ma_channel_converter_init(unsafe(addr(channel_converter_config)), unsafe(addr(channel_converter))) + // volume mixer + ma_volume_mixer_init(unsafe(addr(volume_mixer)),1u) + def finalize + unsafe + delete source + ma_volume_mixer_uninit(unsafe(addr(volume_mixer))) + ma_channel_converter_uninit(unsafe(addr(channel_converter))) + ma_resampler_uninit(unsafe(addr(resampler))) + def mix ( var data:array#; channels,rate:int; dt:float ) : bool + if paused || !source->ready() + return true + if stop && volume_mixer.volume==0. + print("stop with volume 0\n") + return false + let inputRate = uint(float(source.bitrate) * pitch) + ma_resampler_set_rate(unsafe(addr(resampler)), inputRate, uint(rate)) + var outputSamples = uint64(data|>length/channels) + var inputSamples = ma_resampler_get_required_input_frame_count(unsafe(addr(resampler)), outputSamples) + var samples <- source->get_samples(int(inputSamples)) + if length(samples)==0 + print("eos\n") + return false // reached end of stream + if length(samples) < int(inputSamples) + samples |> resize(int(inputSamples)) + if inputSamples != outputSamples + // resample + var temp : array + temp |> resize(int(outputSamples)) + ma_resampler_process_pcm_frames(unsafe(addr(resampler)), + unsafe(addr(samples[0])), + unsafe(addr(inputSamples)), + unsafe(addr(temp[0])), + unsafe(addr(outputSamples))) + delete samples + samples <- temp + // convert channels + var channel_data : array + channel_data |> resize(data |> length) + ma_channel_converter_process_pcm_frames(unsafe(addr(channel_converter)), + unsafe(addr(channel_data[0])), + unsafe(addr(samples[0])), + outputSamples) + unsafe + delete samples + // apply volume envelope + ma_volume_mixer_set_channels(unsafe(addr(volume_mixer)), uint(channels)) + ma_volume_mixer_process_pcm_frames( + unsafe(addr(volume_mixer)), + unsafe(addr(channel_data[0])), + unsafe(addr(data[0])), + outputSamples) + delete channel_data + // update position + playback_position += inputSamples + return true + +class AudioChannel2D : AudioChannel + pan : float = 0. // -1 left to 1 right + +class AudioChannel3D : AudioChannel + position : float3 // meters + velocity : float3 // meters per second + attenuation : float + +var g_channels : array +var g_sid_2_channel : table +var g_mixer_total_time = 0.lf +var g_mixer_total_samples = 0ul + +class AudioSourcePCM : AudioSource + samples : array + def AudioSourcePCM ( ch,rate:int; var smp:array ) + bitrate = rate + channels = ch + samples <- smp + def override append ( var data : array ) : bool + let ofs = length(samples) + let nsmp = length(data) + samples |> resize(ofs+nsmp) + unsafe + memcpy ( unsafe(addr(samples[ofs])), unsafe(addr(data[0])), nsmp * 4 ) + delete data + return true + def finalize + delete samples + def override get_samples ( nsamples:int ) : array + let nsmp = min(length(samples), nsamples * channels) + var data : array + if nsmp!=0 + data |> resize(nsmp) + unsafe + memcpy ( unsafe(addr(data[0])), unsafe(addr(samples[0])), nsmp * 4 ) + samples |> erase(0,nsmp) + return <- data + +let MA_RESULT_OK = 0 + +class AudioSourceDecoder : AudioSource + decoder : ma_decoder? + isReady : bool = false + def AudioSourceDecoder + pass + def initFromFile ( filename:string; rate,nChannels:int ) + decoder = new ma_decoder + var config <- ma_decoder_config_init(ma_format ma_format_f32, uint(channels), uint(rate)) + let result = ma_decoder_init_file(filename, unsafe(addr(config)), decoder) + if result == MA_RESULT_OK + initFromDecoder(decoder, rate, nChannels) + def initFromDecoder ( dec:ma_decoder?; rate,nChannels:int ) + bitrate = rate + channels = nChannels + decoder = dec + isReady = true + def finalize + if isReady + ma_decoder_uninit(decoder) + unsafe + delete decoder + def override ready : bool + return isReady + def override get_samples ( nsamples:int ) : array + var samples : array + samples |> resize(nsamples * channels) + let nframes = ma_decoder_read_pcm_frames( + decoder, + unsafe(addr(samples[0])), + uint64(nsamples)) + samples |> resize(int(nframes)) + return <- samples + def override append ( var data : array ) : bool + return false + +def remove_channel ( srci:int ) + let sid = g_channels[srci].sid + if sid != 0ul + g_sid_2_channel |> erase(sid) + unsafe + delete g_channels[srci] + g_channels |> erase(srci) + +variant AudioCommand + shutdown : bool + add_decoder : tuple + add_pcm : tuple> + append_pcm : tuple> + pause : tuple + volume : tuple + stop : tuple + +var g_command_channel : Channel? + +[pinvoke, export] +def setup_command_processor ( ch:Channel? ) + g_command_channel = ch + g_command_channel |> add_ref + +def command_processor + return if g_command_channel==null + let nCmd = g_command_channel.total + for _ in range(nCmd) + g_command_channel |> pop_one <| $ ( cmd:AudioCommand# ) + if cmd is shutdown + delete g_channels + g_command_channel |> notify_and_release + elif cmd is add_decoder + var dcmd = cmd as add_decoder + var decoder = new AudioSourceDecoder() + decoder->initFromDecoder(unsafe(reinterpret dcmd.decoder),dcmd.rate,dcmd.channels) + var channel = new AudioChannel(decoder) + g_channels |> push(channel) + if dcmd.sid != 0ul + channel.sid = dcmd.sid + g_sid_2_channel[dcmd.sid] = channel + elif cmd is add_pcm + assume pcmd = cmd as add_pcm + var decoder = new AudioSourcePCM(pcmd.channels, pcmd.rate, clone_to_move(pcmd.samples)) + var channel = new AudioChannel(decoder) + g_channels |> push(channel) + if pcmd.sid != 0ul + channel.sid = pcmd.sid + g_sid_2_channel[pcmd.sid] = channel + elif cmd is append_pcm + assume pcmd = cmd as append_pcm + g_sid_2_channel |> get(pcmd.sid) <| $ ( var ch:AudioChannel?& ) + ch.source->append(clone_to_move(pcmd.samples)) + elif cmd is pause + var pcmd = cmd as pause + g_sid_2_channel |> get(pcmd.sid) <| $ ( var ch:AudioChannel?& ) + ch.paused = pcmd.paused + elif cmd is volume + var vcmd = cmd as volume + g_sid_2_channel |> get(vcmd.sid) <| $ ( var ch:AudioChannel?& ) + if vcmd.time > 0. + let nFrames = uint64(vcmd.time * float(MA_SAMPLE_RATE)) + ma_volume_mixer_set_volume_over_time(unsafe(addr(ch.volume_mixer)), vcmd.volume, nFrames) + else + ma_volume_mixer_set_volume(unsafe(addr(ch.volume_mixer)), vcmd.volume) + elif cmd is stop + var scmd = cmd as stop + g_sid_2_channel |> get(scmd.sid) <| $ ( var ch:AudioChannel?& ) + if scmd.time > 0. + let nFrames = uint64(scmd.time * float(MA_SAMPLE_RATE)) + ma_volume_mixer_set_volume_over_time(unsafe(addr(ch.volume_mixer)), 0., nFrames) + ch.stop = true + else + ma_volume_mixer_set_volume(unsafe(addr(ch.volume_mixer)), 0.) + ch.stop = true + else + panic("unknown audio command") + +var g_limiter : ma_limiter +var g_mix_buffer : array + +[export] +def mixer ( var data:array#; channels,rate:int; dt:float ) + let t0 = ref_time_ticks() + command_processor() + let output_samples = length(data)/channels + let input_samples = int(ma_limiter_get_required_input_frame_count(unsafe(addr(g_limiter)),uint64(output_samples))) + let current_samples = length(g_mix_buffer)/channels + let missing_samples = input_samples - current_samples + if missing_samples < 0 + panic("{input_samples} - {current_samples}, need {output_samples}; mixer: missing_samples < 0") + g_mix_buffer |> resize(input_samples*channels) + array_view(g_mix_buffer, current_samples * channels, missing_samples * channels) <| $ ( mix_data ) + let srct = g_channels |> length + for i in range(srct) + let srci = srct - i - 1 + if !g_channels[srci]->mix(mix_data,channels,rate,dt) + remove_channel(srci) + ma_limiter_process_pcm_frames(unsafe(addr(g_limiter)), + unsafe(addr(g_mix_buffer[0])), + unsafe(addr(data[0])), + uint64(output_samples)) + g_mix_buffer |> erase(0, output_samples * channels) + g_mixer_total_time += double(get_time_usec(t0)) / 1000.lf + g_mixer_total_samples += uint64(length(data)/channels) + +[init] +def initialize_mixer + if this_context().category.audio + this_context().name := "audio_mixer" + ma_limiter_init(unsafe(addr(g_limiter)), + MA_LIMITER_THRESHOLD, + MA_LIMITER_ATTACK_TIME, + MA_LIMITER_RELEASE_TIME, + float(MA_SAMPLE_RATE), + uint(MA_CHANNELS)) + +[finalize] +def finalize_mixer + if this_context().category.audio + ma_limiter_uninit(unsafe(addr(g_limiter))) + delete g_mix_buffer + let SPEED = g_mixer_total_time / double(g_mixer_total_samples) + let SPEED_OF_LIGHT = 1000.lf / 48000.lf + let UTILIZATION = int(SPEED / SPEED_OF_LIGHT * 1000.lf) + to_log(LOG_INFO, "mixer {UTILIZATION/10}.{UTILIZATION%10}% utilization\n") + +def public with_audio_system ( blk:block ) + sound_initalize(@@mixer,MA_SAMPLE_RATE,MA_CHANNELS,this_context()) + with_channel(1) <| $ ( channel ) + g_command_channel = channel + unsafe + invoke_in_context(mixer_context(),"setup_command_processor",channel) + invoke(blk) + channel |> push_clone <| [[AudioCommand shutdown=true]] + g_command_channel = null + channel |> join + sound_finalize() + +var private g_sound_sid = 1ul + +def private generate_sid + //! generate unique ID for sound + return g_sound_sid ++ + +def public play_sound_from_file ( filename:string; rate,channels:int ) + //! plays sound from file + //! note - this function is blocking for the duration of the decoder creation + var decoder = new ma_decoder + var config <- ma_decoder_config_init(ma_format ma_format_f32, uint(channels), uint(rate)) + let result = ma_decoder_init_file(filename, unsafe(addr(config)), decoder) + if result != MA_RESULT_OK + return 0ul + let sid = generate_sid() + g_command_channel |> push_clone <| [[AudioCommand add_decoder=[[auto sid, decoder, rate, channels]]]] + return sid + +def public play_sound_from_pcm ( rate,channels:int; var samples:array ) + //! plays sound from PCM data + var sid = generate_sid() + g_command_channel |> push_clone <| [[AudioCommand add_pcm<-[[auto sid, rate, channels, samples]]]] + return sid + +def public append_to_pcm ( sid:SID; var samples:array ) + //! append samples to PCM stream + g_command_channel |> push_clone <| [[AudioCommand append_pcm<-[[auto sid, samples]]]] + +def public set_pause ( sid:SID; paused:bool ) + //! pause or unpause sound + g_command_channel |> push_clone <| [[AudioCommand pause=[[auto sid, paused]]]] + +def public set_volume ( sid:SID; volume:float; time:float = 0.0f ) + //! set volume of sound + g_command_channel |> push_clone <| [[AudioCommand volume=[[auto sid, volume, time]]]] + +def public stop ( sid:SID; time:float = 0.0f ) + //! stop sound + g_command_channel |> push_clone <| [[AudioCommand stop=[[auto sid, time]]]] + +def public decode_audio ( data:array | #; var channels,rate:int& ) : array + var decoder = new ma_decoder + var config <- ma_decoder_config_init(ma_format ma_format_f32, 0u, 0u) + let result = ma_decoder_init_memory(unsafe(addr(data[0])), uint64(length(data)), unsafe(addr(config)), decoder) + channels = int(decoder.outputChannels) + rate = int(decoder.outputSampleRate) + let nframes = ma_decoder_get_length_in_pcm_frames(decoder) + var samples : array + samples |> resize(int(nframes)) + let rframes = ma_decoder_read_pcm_frames( + decoder, + unsafe(addr(samples[0])), + nframes) + samples |> resize(int(rframes)) + ma_decoder_uninit(decoder) + return <- samples diff --git a/modules/dasAudio/audio/opl3.das b/modules/dasAudio/audio/opl3.das new file mode 100644 index 000000000..6d16eecf5 --- /dev/null +++ b/modules/dasAudio/audio/opl3.das @@ -0,0 +1,26 @@ +options indenting = 4 +options no_unused_block_arguments = false +options no_unused_function_arguments = false + +module opl3 shared private + +require audio public + +def public OPL3_Reset ( var chip:Opl3Chip; rate:int ) + OPL3_Reset(unsafe(addr(chip)),uint(rate)) + +def public OPL3_WriteReg ( var chip:Opl3Chip; reg:uint; val:uint8 ) + OPL3_WriteReg(unsafe(addr(chip)), uint16(reg), val) + +def public OPL3_GenerateStreamMono ( var chip:Opl3Chip; var data:array ) + var tdata : array + tdata |> resize(length(data)*2) + let pdata : void? = unsafe(addr(tdata[0])) + OPL3_GenerateStream(unsafe(addr(chip)), pdata, uint(data |> length)) + for i in range(data |> length) + data[i] = tdata[i*2] // only left channel + delete tdata + +def public OPL3_GenerateStream ( var chip:Opl3Chip; var data:array ) + let pdata : void? = unsafe(addr(data[0])) + OPL3_GenerateStream(unsafe(addr(chip)), pdata, uint(length(data)/2)) diff --git a/modules/dasAudio/examples/mixer.das b/modules/dasAudio/examples/mixer.das new file mode 100644 index 000000000..663d7cf09 --- /dev/null +++ b/modules/dasAudio/examples/mixer.das @@ -0,0 +1,55 @@ +options persistent_heap + +require audio/audio_boost +require math +require fio + + +[export] +def main + + with_audio_system <| + print("with first file system\n") + var sound_data : array + var channels, rate : int + fopen("{get_das_root()}/modules/dasAudio/examples/test.mp3","rb") <| $ ( fr ) + if fr == null + panic("cannot open file") + fmap(fr) <| $ ( data ) + var samples <- decode_audio(data, channels, rate) + sound_data <- samples + print("{length(sound_data)} samples, {channels} channels, {rate} rate\n") + let sid_pcm = play_sound_from_pcm(MA_SAMPLE_RATE,channels,sound_data) + sleep(1000u) + + with_audio_system <| + print("with second file system\n") + print("play sin-440\n") + var sin1 <- [{for x in range(MA_SAMPLE_RATE); sin(2.*PI*440.*float(x)/float(48000))}] + let sid_pcm = play_sound_from_pcm(MA_SAMPLE_RATE,1,sin1) + sid_pcm |> set_volume(0.25) + sleep(500u) + print("gen and append sin-880\n") + var sin2 <- [{for x in range(MA_SAMPLE_RATE); sin(2.*PI*880.*float(x)/float(48000))}] + sid_pcm |> append_to_pcm(sin2) + sleep(1500u) + print("play\n") + let sid = play_sound_from_file("{get_das_root()}/modules/dasAudio/examples/test.mp3", MA_SAMPLE_RATE, 2) + sleep(2000u) + print("set volume 0.5\n") + sid |> set_volume(0.5) + sleep(1000u) + print("set volume 1.0\n") + sid |> set_volume(1.0, 1.0) + sleep(2000u) + print("pause\n") + sid |> set_pause(true) + sleep(1000u) + print("unpause\n") + sid |> set_pause(false) + sid |> set_volume(0.25, 1.0) + sleep(1000u) + print("stop\n") + sid |> stop(0.5) + sleep(1000u) + print("done\n") diff --git a/modules/dasAudio/examples/test.mp3 b/modules/dasAudio/examples/test.mp3 new file mode 100644 index 000000000..b95db09a6 Binary files /dev/null and b/modules/dasAudio/examples/test.mp3 differ diff --git a/modules/dasAudio/examples/test.wav b/modules/dasAudio/examples/test.wav new file mode 100644 index 000000000..b9553598b Binary files /dev/null and b/modules/dasAudio/examples/test.wav differ diff --git a/modules/dasAudio/src/dasAudio.cpp b/modules/dasAudio/src/dasAudio.cpp new file mode 100644 index 000000000..6a6869239 --- /dev/null +++ b/modules/dasAudio/src/dasAudio.cpp @@ -0,0 +1,370 @@ +#include "daScript/misc/platform.h" + +#include "daScript/ast/ast.h" +#include "daScript/ast/ast_interop.h" +#include "daScript/ast/ast_typefactory_bind.h" +#include "daScript/ast/ast_handle.h" +#include "daScript/simulate/bind_enum.h" + +#include "dasAudio.h" + +#include + +#define MINIAUDIO_IMPLEMENTATION +#include +#include + +MAKE_TYPE_FACTORY(Opl3Chip,opl3_chip) +MAKE_EXTERNAL_TYPE_FACTORY(Context,Context); + +das::Context* get_clone_context( das::Context * ctx, uint32_t category );//link time resolved dependencies + +MAKE_TYPE_FACTORY(ma_resampler_config,ma_resampler_config); +MAKE_TYPE_FACTORY(ma_resampler,ma_resampler); + +MAKE_TYPE_FACTORY(ma_channel_converter_config,ma_channel_converter_config); +MAKE_TYPE_FACTORY(ma_channel_converter,ma_channel_converter); + +MAKE_TYPE_FACTORY(ma_volume_mixer,ma_volume_mixer); + +MAKE_TYPE_FACTORY(ma_decoder_config,ma_decoder_config); +MAKE_TYPE_FACTORY(ma_decoder,ma_decoder); + +MAKE_TYPE_FACTORY(ma_limiter,ma_limiter); + +DAS_BASE_BIND_ENUM ( ma_format, ma_format, \ + ma_format_unknown, \ + ma_format_u8, \ + ma_format_s16, \ + ma_format_s24, \ + ma_format_s32, \ + ma_format_f32 \ +); +DAS_BIND_ENUM_CAST ( ma_format ); + +DAS_BASE_BIND_ENUM ( ma_resample_algorithm, ma_resample_algorithm, \ + ma_resample_algorithm_linear, \ + ma_resample_algorithm_speex \ +); +DAS_BIND_ENUM_CAST ( ma_resample_algorithm ); + +DAS_BASE_BIND_ENUM ( ma_channel_mix_mode, ma_channel_mix_mode, \ + ma_channel_mix_mode_rectangular, \ + ma_channel_mix_mode_simple, \ + ma_channel_mix_mode_custom_weights, \ + ma_channel_mix_mode_planar_blend, \ + ma_channel_mix_mode_default \ +); +DAS_BIND_ENUM_CAST ( ma_channel_mix_mode ); + +DAS_BASE_BIND_ENUM ( ma_dither_mode, ma_dither_mode, \ + ma_dither_mode_none, \ + ma_dither_mode_rectangle, \ + ma_dither_mode_triangle \ +); +DAS_BIND_ENUM_CAST ( ma_dither_mode ); + +namespace das { + +struct Opl3ChipAnnotation : ManagedStructureAnnotation { + Opl3ChipAnnotation ( ModuleLibrary & mlib ) : ManagedStructureAnnotation("Opl3Chip", mlib, "opl3_chip") { + } +}; + +static ma_device g_device; +static ma_log g_ma_log_struct; +static Context * g_mixer_context = nullptr; +static daScriptEnvironment * g_mixer_env = nullptr; +static Func g_mixer_function = nullptr; +static bool g_mixer_initialized = false; +static int g_rate = 0; +static int g_channels = 0; + +void on_error_log ( void * , ma_uint32 level, const char * message ) { + if (level <= 1) { + LOG(LogLevel::error) << message; + } else { + LOG(LogLevel::info) << message; + } +} + +void data_callback(ma_device*, void* pOutput, const void*, ma_uint32 frameCount) { + float fdt = 1.0f / float(g_rate); + Array buffer; + buffer.data = (char *) pOutput; + buffer.size = buffer.capacity = frameCount * g_channels; + buffer.lock = 1; + lock_guard guard(*g_mixer_context->contextMutex); + auto saved = daScriptEnvironment::bound; + daScriptEnvironment::bound = g_mixer_env; + das_invoke_function::invoke(g_mixer_context,nullptr,g_mixer_function,buffer,g_channels,g_rate,fdt); + daScriptEnvironment::bound = saved; +} + +Context & dasAudio_mixerContext ( Context * context, LineInfoArg * at ) { + if ( !g_mixer_context ) context->throw_error_at(at,"sound mixer is not initialized"); + return *g_mixer_context; +} + +bool dasAudio_init ( TFunc>,int32_t,int32_t,float> mixer, int32_t rate, int32_t channels, Context & context ) { + g_mixer_initialized = false; + g_rate = rate; + g_channels = channels; + // log + ma_log_init(nullptr, &g_ma_log_struct); + ma_log_register_callback(&g_ma_log_struct, {on_error_log, nullptr}); + // device + ma_device_config deviceConfig; + deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.playback.format = ma_format_f32; + deviceConfig.playback.channels = g_channels; + deviceConfig.sampleRate = g_rate; + deviceConfig.dataCallback = data_callback; + deviceConfig.pUserData = NULL; + if ( ma_device_init(nullptr, &deviceConfig, &g_device) != MA_SUCCESS ) { + LOG(LogLevel::error) << "failed to open playback device.\n"; + return false; + } + g_mixer_context = get_clone_context(&context,uint32_t(ContextCategory::audio_context)); + g_mixer_function = mixer; + g_mixer_env = daScriptEnvironment::bound; + if ( ma_device_start(&g_device) != MA_SUCCESS ) { + ma_device_uninit(&g_device); + delete g_mixer_context; + g_mixer_context = nullptr; + return false; + } + g_mixer_initialized = true; + return true; +} + +void dasAudio_finalize ( void ) { + if ( g_mixer_initialized ) { + ma_device_uninit(&g_device); + delete g_mixer_context; + g_mixer_context = nullptr; + g_mixer_initialized = false; + } +} +struct MAResamplerConfigAnnotation : ManagedStructureAnnotation { + MAResamplerConfigAnnotation ( ModuleLibrary & mlib ) + : ManagedStructureAnnotation("ma_resampler_config", mlib, "ma_resampler_config") { + addField("format","format"); + addField("channels","channels"); + addField("sampleRateIn","sampleRateIn"); + addField("sampleRateOut","sampleRateOut"); + /* + addField("lpfOrder","lpfOrder"); + addField("lpfNyquistFactor","lpfNyquistFactor"); + addField("quality","quality"); + */ + } +}; + +struct MAResamplerAnnotation : ManagedStructureAnnotation { + MAResamplerAnnotation ( ModuleLibrary & mlib ) + : ManagedStructureAnnotation("ma_resampler", mlib, "ma_resampler") { + addField("config","config"); + } +}; + +struct MAChannelConvertorConfigAnnotation : ManagedStructureAnnotation { + MAChannelConvertorConfigAnnotation ( ModuleLibrary & mlib ) + : ManagedStructureAnnotation("ma_channel_converter_config", mlib, "ma_channel_converter_config") { + addField("format","format"); + addField("channelsIn","channelsIn"); + addField("channelsOut","channelsOut"); + addField("channelMapIn","channelMapIn"); + addField("channelMapOut","channelMapOut"); + addField("mixingMode","mixingMode"); + addField("weights","weights"); + } +}; + +struct MAChannelConverterAnnotation : ManagedStructureAnnotation { + MAChannelConverterAnnotation ( ModuleLibrary & mlib ) + : ManagedStructureAnnotation("ma_channel_converter", mlib, "ma_channel_converter") { + } +}; + +struct MAVolumeMixerAnnotation : ManagedStructureAnnotation { + MAVolumeMixerAnnotation ( ModuleLibrary & mlib ) + : ManagedStructureAnnotation("ma_volume_mixer", mlib, "ma_volume_mixer") { + addField("volume","volume"); + addField("dvolume","dvolume"); + addField("tvolume","tvolume"); + addField("channels","nChannels"); + } +}; + +struct MADecoderConfigAnnotation : ManagedStructureAnnotation { + MADecoderConfigAnnotation ( ModuleLibrary & mlib ) + : ManagedStructureAnnotation("ma_decoder_config", mlib, "ma_decoder_config") { + addField("format","format"); + addField("channels","channels"); + addField("channelMap","channelMap"); + addField("channelMixMode","channelMixMode"); + addField("ditherMode","ditherMode"); + } +}; + +struct MADecoderAnnotation : ManagedStructureAnnotation { + MADecoderAnnotation ( ModuleLibrary & mlib ) + : ManagedStructureAnnotation("ma_decoder", mlib, "ma_decoder") { + addField("outputFormat","outputFormat"); + addField("outputChannels","outputChannels"); + addField("outputSampleRate","outputSampleRate"); + } +}; + +struct MALimiterAnnotation : ManagedStructureAnnotation { + MALimiterAnnotation ( ModuleLibrary & mlib ) + : ManagedStructureAnnotation("ma_limiter", mlib, "ma_limiter") { + addField("gain","gain"); + addField("nChannels","nChannels"); + addField("attack_samples","attack_samples"); + addField("threshold","threshold"); + addField("attack_coeff","attack_coeff"); + addField("release_coeff","release_coeff"); + } +}; + +class Module_Audio : public das::Module { +protected: + bool initialized = false; +public: + Module_Audio() : Module("audio") {} + bool initDependencies() override { + if ( initialized ) return true; + if ( !Module::require("rtti") ) return false; + initialized = true; + // now, initialize + ModuleLibrary lib; + lib.addModule(this); + lib.addBuiltInModule(); + // opl3 + addAnnotation(make_smart(lib)); + addExtern(*this, lib, "OPL3_Generate", + SideEffects::worstDefault, "OPL3_Generate")->args({"chip", "buf"}); + addExtern(*this, lib, "OPL3_GenerateResampled", + SideEffects::worstDefault, "OPL3_GenerateResampled")->args({"chip", "buf"}); + addExtern(*this, lib, "OPL3_Reset", + SideEffects::worstDefault, "OPL3_Reset")->args({"chip", "sampleRate"}); + addExtern(*this, lib, "OPL3_WriteReg", + SideEffects::worstDefault, "OPL3_WriteReg")->args({"chip", "reg", "v"}); + addExtern(*this, lib, "OPL3_WriteRegBuffered", + SideEffects::worstDefault, "OPL3_WriteRegBuffered")->args({"chip", "reg", "v"}); + addExtern(*this, lib, "OPL3_GenerateStream", + SideEffects::worstDefault, "OPL3_GenerateStream")->args({"OPL3_GenerateStream", "sndptr", "numsamples"}); + addExtern(*this, lib, "OPL3_Generate4Ch", + SideEffects::worstDefault, "OPL3_Generate4Ch")->args({"chip", "buf"}); + addExtern(*this, lib, "OPL3_Generate4ChResampled", + SideEffects::worstDefault, "OPL3_Generate4ChResampled")->args({"chip", "buf"}); + addExtern(*this, lib, "OPL3_Generate4ChStream", + SideEffects::worstDefault, "OPL3_Generate4ChStream")->args({"chip", "sndptr1", "sndptr2", "numsamples"}); + // mixer + addExtern(*this, lib, "sound_initalize", + SideEffects::modifyExternal, "dasAudio_init")->args({"mixer", "rate", "channels","context"}); + addExtern(*this, lib, "sound_finalize", + SideEffects::modifyExternal, "dasAudio_finalize"); + addExtern(*this, lib, "mixer_context", + SideEffects::modifyExternal, "dasAudio_mixerContext"); + // enums + addEnumeration(make_smart()); + addEnumeration(make_smart()); + addEnumeration(make_smart()); + addEnumeration(make_smart()); + // resampler + addAnnotation(make_smart(lib)); + addAnnotation(make_smart(lib)); + addExtern(*this, lib, "ma_resampler_config_init", + SideEffects::none, "ma_resampler_config_init")->args({"format", "channels", "sampleRateIn", "sampleRateOut","algorithm"}); + addExtern(*this, lib, "ma_resampler_init", + SideEffects::modifyArgumentAndExternal, "ma_resampler_init")->args({"config", "resampler"}); + addExtern(*this, lib, "ma_resampler_uninit", + SideEffects::modifyArgumentAndExternal, "ma_resampler_uninit")->args({"resampler"}); + addExtern(*this, lib, "ma_resampler_process_pcm_frames", + SideEffects::modifyArgument, "ma_resampler_process_pcm_frames")->args({"resampler", "pFramesIn", "pFrameCountIn", "pFramesOut", "pFrameCountOut"}); + addExtern(*this, lib, "ma_resampler_set_rate", + SideEffects::modifyArgument, "ma_resampler_set_rate")->args({"resampler", "sampleRateIn", "sampleRateOut"}); + addExtern(*this, lib, "ma_resampler_set_rate_ratio", + SideEffects::modifyArgument, "ma_resampler_set_rate_ratio")->args({"resampler", "ratioInOut"}); + addExtern(*this, lib, "ma_resampler_get_required_input_frame_count", + SideEffects::none, "ma_resampler_get_required_input_frame_count")->args({"resampler", "outputFrameCount"}); + addExtern(*this, lib, "ma_resampler_get_expected_output_frame_count", + SideEffects::none, "ma_resampler_get_expected_output_frame_count")->args({"resampler", "inputFrameCount"}); + addExtern(*this, lib, "ma_resampler_get_input_latency", + SideEffects::none, "ma_resampler_get_input_latency")->args({"resampler"}); + addExtern(*this, lib, "ma_resampler_get_output_latency", + SideEffects::none, "ma_resampler_get_output_latency")->args({"resampler"}); + // channel converter + addAnnotation(make_smart(lib)); + addAnnotation(make_smart(lib)); + addExtern(*this, lib, "ma_channel_converter_config_init", + SideEffects::none, "ma_channel_converter_config_init")->args({"format", "channelsIn", "channelsOut", "channelMapIn", "channelMapOut", "mixingMode"}); + addExtern(*this, lib, "ma_channel_converter_init", + SideEffects::modifyArgumentAndExternal, "ma_channel_converter_init")->args({"config", "converter"}); + addExtern(*this, lib, "ma_channel_converter_uninit", + SideEffects::modifyArgumentAndExternal, "ma_channel_converter_uninit")->args({"converter"}); + addExtern(*this, lib, "ma_channel_converter_process_pcm_frames", + SideEffects::modifyArgument, "ma_channel_converter_process_pcm_frames")->args({"converter", "pFramesOut", "pFramesIn", "frameCount"}); + // volume mixer + addAnnotation(make_smart(lib)); + addExtern(*this, lib, "ma_volume_mixer_init", + SideEffects::modifyArgument, "ma_volume_mixer_init")->args({"mixer","nChannels"}); + addExtern(*this, lib, "ma_volume_mixer_uninit", + SideEffects::modifyArgument, "ma_volume_mixer_uninit")->args({"mixer"}); + addExtern(*this, lib, "ma_volume_mixer_set_channels", + SideEffects::modifyArgument, "ma_volume_mixer_set_channels")->args({"mixer","nChannels"}); + addExtern(*this, lib, "ma_volume_mixer_set_volume", + SideEffects::modifyArgument, "ma_volume_mixer_set_volume")->args({"mixer", "volume"}); + addExtern(*this, lib, "ma_volume_mixer_set_volume_over_time", + SideEffects::modifyArgument, "ma_volume_mixer_set_volume_over_time")->args({"mixer", "volume", "nFrames"}); + addExtern(*this, lib, "ma_volume_mixer_process_pcm_frames", + SideEffects::modifyArgument, "ma_volume_mixer_process_pcm_frames")->args({"mixer", "pFramesOut", "pFramesIn", "frameCount"}); + // decoder + addAnnotation(make_smart(lib)); + addAnnotation(make_smart(lib)); + addExtern(*this, lib, "ma_decoder_config_init", + SideEffects::none, "ma_decoder_config_init")->args({"outputFormat", "outputChannels", "outputSampleRate"}); + addExtern(*this, lib, "ma_decoder_config_init_default", + SideEffects::none, "ma_decoder_config_init_default"); + addExtern(*this, lib, "ma_decoder_init_memory", + SideEffects::modifyArgumentAndExternal, "ma_decoder_init_memory")->args({"pData", "dataSize", "config", "decoder"}); + addExtern(*this, lib, "ma_decoder_init_file", + SideEffects::modifyArgumentAndExternal, "ma_decoder_init_file")->args({"pFilePath", "config", "decoder"}); + addExtern(*this, lib, "ma_decoder_uninit", + SideEffects::modifyArgumentAndExternal, "ma_decoder_uninit")->args({"decoder"}); + addExtern(*this, lib, "ma_decoder_get_cursor_in_pcm_frames", + SideEffects::none, "ma_decoder_get_cursor_in_pcm_frames")->args({"decoder", "pCursor"}); + addExtern(*this, lib, "ma_decoder_get_length_in_pcm_frames", + SideEffects::none, "ma_decoder_get_length_in_pcm_frames")->args({"decoder"}); + addExtern(*this, lib, "ma_decoder_read_pcm_frames", + SideEffects::modifyArgument, "ma_decoder_read_pcm_frames")->args({"decoder", "pFramesOut", "frameCount"}); + addExtern(*this, lib, "ma_decoder_seek_to_pcm_frame", + SideEffects::modifyArgument, "ma_decoder_seek_to_pcm_frame")->args({"decoder", "frameIndex"}); + addExtern(*this, lib, "ma_decoder_get_available_frames", + SideEffects::none, "ma_decoder_get_available_frames")->args({"decoder", "pAvailableFrames"}); + // limiter + addAnnotation(make_smart(lib)); + addExtern(*this, lib, "ma_limiter_init", + SideEffects::modifyArgument, "ma_limiter_init")->args({"limiter", "threshold", "attack_time", "release_time", "sample_rate", "nChannels"}); + addExtern(*this, lib, "ma_limiter_process_pcm_frames", + SideEffects::modifyArgument, "ma_limiter_process_pcm_frames")->args({"limiter", "InFames", "OutFrames", "nFrames"}); + addExtern(*this, lib, "ma_limiter_get_required_input_frame_count", + SideEffects::none, "ma_limiter_get_required_input_frame_count")->args({"limiter", "out_len"}); + addExtern(*this, lib, "ma_limiter_uninit", + SideEffects::modifyArgument, "ma_limiter_uninit")->args({"limiter"}); + return true; + } + virtual ModuleAotType aotRequire ( TextWriter & tw ) const override { + tw << "#include \"../modules/dasAudio/src/dasAudio.h\"\n"; + tw << "#include \n"; + tw << "#include \n"; + return ModuleAotType::cpp; + } +}; + +} // namespace das + +REGISTER_MODULE_IN_NAMESPACE(Module_Audio, das); diff --git a/modules/dasAudio/src/dasAudio.h b/modules/dasAudio/src/dasAudio.h new file mode 100644 index 000000000..35d855264 --- /dev/null +++ b/modules/dasAudio/src/dasAudio.h @@ -0,0 +1,3 @@ +#include +namespace das { +} diff --git a/modules/dasAudio/src/volume_mixer.h b/modules/dasAudio/src/volume_mixer.h new file mode 100644 index 000000000..1694a4def --- /dev/null +++ b/modules/dasAudio/src/volume_mixer.h @@ -0,0 +1,281 @@ +#ifndef volume_mixer_h +#define volume_mixer_h + +// i keep it in ma_ style, and potentially will make a PR to miniaudio at some point +// #include + +// volume mixer + +struct ma_volume_mixer { + float volume; + float dvolume; + float tvolume; + uint32_t nChannels; +}; + +void ma_volume_mixer_init ( ma_volume_mixer * mixer, uint32_t nChannels ); +void ma_volume_mixer_uninit ( ma_volume_mixer * mixer ); +void ma_volume_mixer_set_channels ( ma_volume_mixer * mixer, uint32_t nChannels ); +void ma_volume_mixer_set_volume ( ma_volume_mixer * mixer, float volume ); +void ma_volume_mixer_set_volume_over_time ( ma_volume_mixer * mixer, float volume, uint64_t nFrames ); +void ma_volume_mixer_process_pcm_frames ( ma_volume_mixer * mixer, float * InFrames, float * OutFrames, uint64_t nFrames ); + +// look-ahead limiter + +struct ma_limiter { + float gain[MA_MAX_CHANNELS]; + uint32_t nChannels; + uint32_t attack_samples; + float threshold; + float attack_coeff; + float release_coeff; +}; + +void ma_limiter_init ( ma_limiter * limiter, float threshold, float attack_time, float release_time, float sample_rate, uint32_t nChannels ); +void ma_limiter_process_pcm_frames ( ma_limiter * limiter, float * InFames, float * OutFrames, uint64_t nFrames ); +uint64_t ma_limiter_get_required_input_frame_count ( ma_limiter * limiter, uint64_t out_len ); +void ma_limiter_uninit ( ma_limiter * ); + +#ifdef MINIAUDIO_IMPLEMENTATION + +void ma_volume_mixer_init ( ma_volume_mixer * mixer, uint32_t nChannels ) { + mixer->volume = 1.0f; + mixer->dvolume = 0.0f; + mixer->tvolume = 1.0f; + mixer->nChannels = nChannels; +} + +void ma_volume_mixer_uninit ( ma_volume_mixer * ) { + // do nothing +} + +void ma_volume_mixer_set_channels ( ma_volume_mixer * mixer, uint32_t nChannels ) { + mixer->nChannels = nChannels; +} + +void ma_volume_mixer_set_volume ( ma_volume_mixer * mixer, float volume ) { + mixer->volume = volume; + mixer->dvolume = 0.0f; + mixer->tvolume = volume; +} + +void ma_volume_mixer_set_volume_over_time ( ma_volume_mixer * mixer, float volume, uint64_t nFrames ) { + mixer->dvolume = (volume - mixer->volume) / float(nFrames); + mixer->tvolume = volume; +} + +void ma_volume_mixer_process_pcm_frames_linear ( ma_volume_mixer * mixer, float * InFrames, float * OutFrames, uint64_t nFrames ) { + float volume = mixer->volume; + uint32_t nChannels = mixer->nChannels; + if ( nChannels==1 ) { + for ( uint64_t i=0; i!=nFrames; ++i ) { + OutFrames[i] += InFrames[i] * volume; + } + } else if ( nChannels==2 ) { + for ( uint64_t i=0; i!=nFrames; ++i ) { + OutFrames[i*2+0] += InFrames[i*2+0] * volume; + OutFrames[i*2+1] += InFrames[i*2+1] * volume; + } + } else if ( nChannels==4 ) { + for ( uint64_t i=0; i!=nFrames; ++i ) { + OutFrames[i*4+0] += InFrames[i*4+0] * volume; + OutFrames[i*4+1] += InFrames[i*4+1] * volume; + OutFrames[i*4+2] += InFrames[i*4+2] * volume; + OutFrames[i*4+3] += InFrames[i*4+3] * volume; + } + } else { + for ( uint64_t i=0; i!=nFrames; ++i ) { + for ( uint32_t j=0; j!=nChannels; ++j ) { + OutFrames[i*nChannels+j] += InFrames[i*nChannels+j] * volume; + } + } + } +} + +void ma_volume_mixer_process_pcm_frames_descending ( ma_volume_mixer * mixer, float * InFrames, float * OutFrames, uint64_t nFrames ) { + uint32_t nChannels = mixer->nChannels; + float volume = mixer->volume; + float dvolume = mixer->dvolume; + float tvolume = mixer->tvolume; + if ( nChannels==1 ) { + for ( uint64_t i=0; i!=nFrames; ++i ) { + OutFrames[i] += InFrames[i] * volume; + volume = ma_max(volume+dvolume,tvolume); + } + } else if ( nChannels==2 ) { + for ( uint64_t i=0; i!=nFrames; ++i ) { + OutFrames[i*2+0] += InFrames[i*2+0] * volume; + OutFrames[i*2+1] += InFrames[i*2+1] * volume; + volume = ma_max(volume+dvolume,tvolume); + } + } else if ( nChannels==4 ) { + for ( uint64_t i=0; i!=nFrames; ++i ) { + OutFrames[i*4+0] += InFrames[i*4+0] * volume; + OutFrames[i*4+1] += InFrames[i*4+1] * volume; + OutFrames[i*4+2] += InFrames[i*4+2] * volume; + OutFrames[i*4+3] += InFrames[i*4+3] * volume; + volume = ma_max(volume+dvolume,tvolume); + } + } else { + for ( uint64_t i=0; i!=nFrames; ++i ) { + for ( uint32_t j=0; j!=nChannels; ++j ) { + OutFrames[i*nChannels+j] += InFrames[i*nChannels+j] * volume; + volume = ma_max(volume+dvolume,tvolume); + } + } + } + mixer->volume = volume; + mixer->dvolume = volume==tvolume ? 0.0f : dvolume; +} + +void ma_volume_mixer_process_pcm_frames_ascending ( ma_volume_mixer * mixer, float * InFrames, float * OutFrames, uint64_t nFrames ) { + uint32_t nChannels = mixer->nChannels; + float volume = mixer->volume; + float dvolume = mixer->dvolume; + float tvolume = mixer->tvolume; + if ( nChannels==1 ) { + for ( uint64_t i=0; i!=nFrames; ++i ) { + OutFrames[i] += InFrames[i] * volume; + volume = ma_min(volume+dvolume,tvolume); + } + } else if ( nChannels==2 ) { + for ( uint64_t i=0; i!=nFrames; ++i ) { + OutFrames[i*2+0] += InFrames[i*2+0] * volume; + OutFrames[i*2+1] += InFrames[i*2+1] * volume; + volume = ma_min(volume+dvolume,tvolume); + } + } else if ( nChannels==4 ) { + for ( uint64_t i=0; i!=nFrames; ++i ) { + OutFrames[i*4+0] += InFrames[i*4+0] * volume; + OutFrames[i*4+1] += InFrames[i*4+1] * volume; + OutFrames[i*4+2] += InFrames[i*4+2] * volume; + OutFrames[i*4+3] += InFrames[i*4+3] * volume; + volume = ma_min(volume+dvolume,tvolume); + } + } else { + for ( uint64_t i=0; i!=nFrames; ++i ) { + for ( uint32_t j=0; j!=nChannels; ++j ) { + OutFrames[i*nChannels+j] += InFrames[i*nChannels+j] * volume; + volume = ma_min(volume+dvolume,tvolume); + } + } + } + mixer->volume = volume; + mixer->dvolume = volume==tvolume ? 0.0f : dvolume; +} + +void ma_volume_mixer_process_pcm_frames ( ma_volume_mixer * mixer, float * InFrames, float * OutFrames, uint64_t nFrames ) { + if ( mixer->dvolume==0.0f ) { + ma_volume_mixer_process_pcm_frames_linear(mixer,InFrames,OutFrames,nFrames); + } else if ( mixer->dvolume<0.0f ) { + ma_volume_mixer_process_pcm_frames_descending(mixer,InFrames,OutFrames,nFrames); + } else { + ma_volume_mixer_process_pcm_frames_ascending(mixer,InFrames,OutFrames,nFrames); + } +} + +void ma_limiter_init ( ma_limiter * limiter, float threshold, float attack_time, float release_time, float sample_rate, uint32_t nChannels ) { + limiter->nChannels = nChannels; + limiter->threshold = threshold; + limiter->attack_samples = (int)(attack_time * sample_rate); + limiter->attack_coeff = expf(-1.0f / (limiter->attack_samples)); + limiter->release_coeff = expf(-1.0f / (release_time)); + for ( uint32_t i=0; i!=MA_MAX_CHANNELS; ++i ) { + limiter->gain[i] = 1.0f; + } +} + +void ma_limiter_uninit ( ma_limiter * ) { + // do nothing +} + +uint64_t ma_limiter_get_required_input_frame_count ( ma_limiter * limiter, uint64_t out_len ) { + return out_len + limiter->attack_samples; +} + +void ma_limiter_porcess_pcm_frames_mono ( ma_limiter * limiter, float * InFames, float * OutFrames, uint64_t nFrames ) { + float gain = limiter->gain[0]; + float attack_coeff = limiter->attack_coeff; + float release_coeff = limiter->release_coeff; + float threshold = limiter->threshold; + uint32_t attack_samples = limiter->attack_samples; + for ( uint64_t i=0; i!=nFrames; ++i ) { + float lookahead_sample = InFames[i + attack_samples]; + if (fabs(lookahead_sample) * gain > threshold) { + float desired_gain = threshold / fabs(lookahead_sample); + gain = gain * attack_coeff + desired_gain * (1 - attack_coeff); + } else { + gain = gain * release_coeff + 1.0f * (1 - release_coeff); + } + OutFrames[i] = InFames[i] * gain; + } + limiter->gain[0] = gain; +} + +void ma_limiter_porcess_pcm_frames_stereo ( ma_limiter * limiter, float * InFames, float * OutFrames, uint64_t nFrames ) { + float gain[2] = { limiter->gain[0], limiter->gain[1] }; + float attack_coeff = limiter->attack_coeff; + float release_coeff = limiter->release_coeff; + float threshold = limiter->threshold; + uint32_t attack_samples = limiter->attack_samples; + for ( uint64_t i=0; i!=nFrames; ++i ) { + float lookahead_sample = InFames[i*2 + attack_samples*2]; + if (fabs(lookahead_sample) * gain[0] > threshold) { + float desired_gain = threshold / fabs(lookahead_sample); + gain[0] = gain[0] * attack_coeff + desired_gain * (1 - attack_coeff); + } else { + gain[0] = gain[0] * release_coeff + 1.0f * (1 - release_coeff); + } + lookahead_sample = InFames[i*2 + attack_samples*2 + 1]; + if (fabs(lookahead_sample) * gain[1] > threshold) { + float desired_gain = threshold / fabs(lookahead_sample); + gain[1] = gain[1] * attack_coeff + desired_gain * (1 - attack_coeff); + } else { + gain[1] = gain[1] * release_coeff + 1.0f * (1 - release_coeff); + } + OutFrames[i*2+0] = InFames[i*2+0] * gain[0]; + OutFrames[i*2+1] = InFames[i*2+1] * gain[1]; + } + limiter->gain[0] = gain[0]; + limiter->gain[1] = gain[1]; +} + +void ma_limiter_porcess_pcm_frames_any ( ma_limiter * limiter, float * InFames, float * OutFrames, uint64_t nFrames ) { + float gain[MA_MAX_CHANNELS]; + for ( uint32_t i=0; i!=limiter->nChannels; ++i ) { + gain[i] = limiter->gain[i]; + } + float attack_coeff = limiter->attack_coeff; + float release_coeff = limiter->release_coeff; + float threshold = limiter->threshold; + uint32_t attack_samples = limiter->attack_samples; + for ( uint64_t i=0; i!=nFrames; ++i ) { + for ( uint32_t j=0; j!=limiter->nChannels; ++j ) { + float lookahead_sample = InFames[i*limiter->nChannels + j + attack_samples*limiter->nChannels]; + if (fabs(lookahead_sample) * gain[j] > threshold) { + float desired_gain = threshold / fabs(lookahead_sample); + gain[j] = gain[j] * attack_coeff + desired_gain * (1 - attack_coeff); + } else { + gain[j] = gain[j] * release_coeff + 1.0f * (1 - release_coeff); + } + OutFrames[i*limiter->nChannels+j] = InFames[i*limiter->nChannels+j] * gain[j]; + } + } + for ( uint32_t i=0; i!=limiter->nChannels; ++i ) { + limiter->gain[i] = gain[i]; + } +} + +void ma_limiter_process_pcm_frames ( ma_limiter * limiter, float * InFames, float * OutFrames, uint64_t nFrames ) { + if ( limiter->nChannels==1 ) { + ma_limiter_porcess_pcm_frames_mono(limiter, InFames, OutFrames, nFrames); + } else if ( limiter->nChannels==2 ) { + ma_limiter_porcess_pcm_frames_stereo(limiter, InFames, OutFrames, nFrames); + } else { + ma_limiter_porcess_pcm_frames_any(limiter, InFames, OutFrames, nFrames); + } +} + +#endif + +#endif diff --git a/modules/dasSound b/modules/dasSound deleted file mode 160000 index e3c221f70..000000000 --- a/modules/dasSound +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e3c221f70c57d3ee01fd1892533b088e220c45ef diff --git a/web/CMakeLists.txt b/web/CMakeLists.txt index 7ded6221a..c87bffc03 100644 --- a/web/CMakeLists.txt +++ b/web/CMakeLists.txt @@ -11,7 +11,7 @@ option(DAS_IMGUI_DISABLED "Disable dasIMGUI (IMGUI, IMNODES, IMGUI-NODE-EDITOR g option(DAS_BGFX_DISABLED "Disable dasBGFX (BGFX graphics API)" ON) option(DAS_XBYAK_DISABLED "Disable dasXbyak (XBYAK and ZYDIS, x86 assembly, jit)" ON) option(DAS_MINFFT_DISABLED "Disable dasMinfft (Minimal FFT library)" ON) -option(DAS_SOUND_DISABLED "Disable dasSound (Miniaudio sound library)" ON) +option(DAS_AUDIO_DISABLED "Disable dasAudio (Miniaudio sound library)" ON) option(DAS_STDDLG_DISABLED "Disable dasStdDlg (File new,open,save etc dialogs)" ON) option(DAS_STBIMAGE_DISABLED "Disable dasStbImage (StbImage bindings, image loading and saving)" ON) option(DAS_STBTRUETYPE_DISABLED "Disable dasStbTrueType (StbTrueType bindings, ttf rasterization)" ON)