mirror of
https://github.com/tildearrow/furnace.git
synced 2025-01-03 14:11:11 +00:00
coming soon: reSIDfp core
This commit is contained in:
parent
de3ed24e1f
commit
c3ced46fa3
53 changed files with 7639 additions and 0 deletions
|
@ -368,6 +368,24 @@ src/engine/platform/sound/c64/wave8580_PST.cc
|
||||||
src/engine/platform/sound/c64/wave8580_P_T.cc
|
src/engine/platform/sound/c64/wave8580_P_T.cc
|
||||||
src/engine/platform/sound/c64/wave8580__ST.cc
|
src/engine/platform/sound/c64/wave8580__ST.cc
|
||||||
|
|
||||||
|
src/engine/platform/sound/c64_fp/Dac.cpp
|
||||||
|
src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp
|
||||||
|
src/engine/platform/sound/c64_fp/ExternalFilter.cpp
|
||||||
|
src/engine/platform/sound/c64_fp/Filter6581.cpp
|
||||||
|
src/engine/platform/sound/c64_fp/Filter8580.cpp
|
||||||
|
src/engine/platform/sound/c64_fp/Filter.cpp
|
||||||
|
src/engine/platform/sound/c64_fp/FilterModelConfig6581.cpp
|
||||||
|
src/engine/platform/sound/c64_fp/FilterModelConfig8580.cpp
|
||||||
|
src/engine/platform/sound/c64_fp/FilterModelConfig.cpp
|
||||||
|
src/engine/platform/sound/c64_fp/Integrator6581.cpp
|
||||||
|
src/engine/platform/sound/c64_fp/Integrator8580.cpp
|
||||||
|
src/engine/platform/sound/c64_fp/OpAmp.cpp
|
||||||
|
src/engine/platform/sound/c64_fp/SID.cpp
|
||||||
|
src/engine/platform/sound/c64_fp/Spline.cpp
|
||||||
|
src/engine/platform/sound/c64_fp/WaveformCalculator.cpp
|
||||||
|
src/engine/platform/sound/c64_fp/WaveformGenerator.cpp
|
||||||
|
src/engine/platform/sound/c64_fp/resample/SincResampler.cpp
|
||||||
|
|
||||||
src/engine/platform/sound/tia/TIASnd.cpp
|
src/engine/platform/sound/tia/TIASnd.cpp
|
||||||
|
|
||||||
src/engine/platform/sound/ymfm/ymfm_adpcm.cpp
|
src/engine/platform/sound/ymfm/ymfm_adpcm.cpp
|
||||||
|
|
6
src/engine/platform/sound/c64_fp/AUTHORS
Normal file
6
src/engine/platform/sound/c64_fp/AUTHORS
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
Authors of reSIDfp.
|
||||||
|
|
||||||
|
Dag Lem: Designed and programmed complete emulation engine.
|
||||||
|
Antti S. Lankila: Distortion simulation and calculation of combined waveforms
|
||||||
|
Ken Händel: source code conversion to Java
|
||||||
|
Leandro Nini: port to c++, merge with reSID 1.0
|
339
src/engine/platform/sound/c64_fp/COPYING
Normal file
339
src/engine/platform/sound/c64_fp/COPYING
Normal file
|
@ -0,0 +1,339 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Lesser General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) year name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License.
|
123
src/engine/platform/sound/c64_fp/Dac.cpp
Normal file
123
src/engine/platform/sound/c64_fp/Dac.cpp
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Dac.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
Dac::Dac(unsigned int bits) :
|
||||||
|
dac(new double[bits]),
|
||||||
|
dacLength(bits)
|
||||||
|
{}
|
||||||
|
|
||||||
|
Dac::~Dac()
|
||||||
|
{
|
||||||
|
delete [] dac;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Dac::getOutput(unsigned int input) const
|
||||||
|
{
|
||||||
|
double dacValue = 0.;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < dacLength; i++)
|
||||||
|
{
|
||||||
|
if ((input & (1 << i)) != 0)
|
||||||
|
{
|
||||||
|
dacValue += dac[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dacValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dac::kinkedDac(ChipModel chipModel)
|
||||||
|
{
|
||||||
|
const double R_INFINITY = 1e6;
|
||||||
|
|
||||||
|
// Non-linearity parameter, 8580 DACs are perfectly linear
|
||||||
|
const double _2R_div_R = chipModel == MOS6581 ? 2.20 : 2.00;
|
||||||
|
|
||||||
|
// 6581 DACs are not terminated by a 2R resistor
|
||||||
|
const bool term = chipModel == MOS8580;
|
||||||
|
|
||||||
|
// Calculate voltage contribution by each individual bit in the R-2R ladder.
|
||||||
|
for (unsigned int set_bit = 0; set_bit < dacLength; set_bit++)
|
||||||
|
{
|
||||||
|
double Vn = 1.; // Normalized bit voltage.
|
||||||
|
double R = 1.; // Normalized R
|
||||||
|
const double _2R = _2R_div_R * R; // 2R
|
||||||
|
double Rn = term ? // Rn = 2R for correct termination,
|
||||||
|
_2R : R_INFINITY; // INFINITY for missing termination.
|
||||||
|
|
||||||
|
unsigned int bit;
|
||||||
|
|
||||||
|
// Calculate DAC "tail" resistance by repeated parallel substitution.
|
||||||
|
for (bit = 0; bit < set_bit; bit++)
|
||||||
|
{
|
||||||
|
Rn = (Rn == R_INFINITY) ?
|
||||||
|
R + _2R :
|
||||||
|
R + (_2R * Rn) / (_2R + Rn); // R + 2R || Rn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source transformation for bit voltage.
|
||||||
|
if (Rn == R_INFINITY)
|
||||||
|
{
|
||||||
|
Rn = _2R;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Rn = (_2R * Rn) / (_2R + Rn); // 2R || Rn
|
||||||
|
Vn = Vn * Rn / _2R;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate DAC output voltage by repeated source transformation from
|
||||||
|
// the "tail".
|
||||||
|
|
||||||
|
for (++bit; bit < dacLength; bit++)
|
||||||
|
{
|
||||||
|
Rn += R;
|
||||||
|
const double I = Vn / Rn;
|
||||||
|
Rn = (_2R * Rn) / (_2R + Rn); // 2R || Rn
|
||||||
|
Vn = Rn * I;
|
||||||
|
}
|
||||||
|
|
||||||
|
dac[set_bit] = Vn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize to integerish behavior
|
||||||
|
double Vsum = 0.;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < dacLength; i++)
|
||||||
|
{
|
||||||
|
Vsum += dac[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
Vsum /= 1 << dacLength;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < dacLength; i++)
|
||||||
|
{
|
||||||
|
dac[i] /= Vsum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
111
src/engine/platform/sound/c64_fp/Dac.h
Normal file
111
src/engine/platform/sound/c64_fp/Dac.h
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DAC_H
|
||||||
|
#define DAC_H
|
||||||
|
|
||||||
|
#include "siddefs-fp.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimate DAC nonlinearity.
|
||||||
|
* The SID DACs are built up as R-2R ladder as follows:
|
||||||
|
*
|
||||||
|
* n n-1 2 1 0 VGND
|
||||||
|
* | | | | | | Termination
|
||||||
|
* 2R 2R 2R 2R 2R 2R only for
|
||||||
|
* | | | | | | MOS 8580
|
||||||
|
* Vo -o-R-o-R-...-o-R-o-R-- --+
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* All MOS 6581 DACs are missing a termination resistor at bit 0. This causes
|
||||||
|
* pronounced errors for the lower 4 - 5 bits (e.g. the output for bit 0 is
|
||||||
|
* actually equal to the output for bit 1), resulting in DAC discontinuities
|
||||||
|
* for the lower bits.
|
||||||
|
* In addition to this, the 6581 DACs exhibit further severe discontinuities
|
||||||
|
* for higher bits, which may be explained by a less than perfect match between
|
||||||
|
* the R and 2R resistors, or by output impedance in the NMOS transistors
|
||||||
|
* providing the bit voltages. A good approximation of the actual DAC output is
|
||||||
|
* achieved for 2R/R ~ 2.20.
|
||||||
|
*
|
||||||
|
* The MOS 8580 DACs, on the other hand, do not exhibit any discontinuities.
|
||||||
|
* These DACs include the correct termination resistor, and also seem to have
|
||||||
|
* very accurately matched R and 2R resistors (2R/R = 2.00).
|
||||||
|
*
|
||||||
|
* On the 6581 the output of the waveform and envelope DACs go through
|
||||||
|
* a voltage follower built with two NMOS:
|
||||||
|
*
|
||||||
|
* Vdd
|
||||||
|
*
|
||||||
|
* |
|
||||||
|
* |-+
|
||||||
|
* Vin -------| T1 (enhancement-mode)
|
||||||
|
* |-+
|
||||||
|
* |
|
||||||
|
* o-------- Vout
|
||||||
|
* |
|
||||||
|
* |-+
|
||||||
|
* +---| T2 (depletion-mode)
|
||||||
|
* | |-+
|
||||||
|
* | |
|
||||||
|
*
|
||||||
|
* GND GND
|
||||||
|
*/
|
||||||
|
class Dac
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
/// analog values
|
||||||
|
double * const dac;
|
||||||
|
|
||||||
|
/// the dac array length
|
||||||
|
const unsigned int dacLength;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Initialize DAC model.
|
||||||
|
*
|
||||||
|
* @param bits the number of input bits
|
||||||
|
*/
|
||||||
|
Dac(unsigned int bits);
|
||||||
|
~Dac();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build DAC model for specific chip.
|
||||||
|
*
|
||||||
|
* @param chipModel 6581 or 8580
|
||||||
|
*/
|
||||||
|
void kinkedDac(ChipModel chipModel);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Vo output for a given combination of input bits.
|
||||||
|
*
|
||||||
|
* @param input the digital input
|
||||||
|
* @return the analog output value
|
||||||
|
*/
|
||||||
|
double getOutput(unsigned int input) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
155
src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp
Normal file
155
src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2018 VICE Project
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define ENVELOPEGENERATOR_CPP
|
||||||
|
|
||||||
|
#include "EnvelopeGenerator.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup table to convert from attack, decay, or release value to rate
|
||||||
|
* counter period.
|
||||||
|
*
|
||||||
|
* The rate counter is a 15 bit register which is left shifted each cycle.
|
||||||
|
* When the counter reaches a specific comparison value,
|
||||||
|
* the envelope counter is incremented (attack) or decremented
|
||||||
|
* (decay/release) and the rate counter is resetted.
|
||||||
|
*
|
||||||
|
* see [kevtris.org](http://blog.kevtris.org/?p=13)
|
||||||
|
*/
|
||||||
|
const unsigned int EnvelopeGenerator::adsrtable[16] =
|
||||||
|
{
|
||||||
|
0x007f,
|
||||||
|
0x3000,
|
||||||
|
0x1e00,
|
||||||
|
0x0660,
|
||||||
|
0x0182,
|
||||||
|
0x5573,
|
||||||
|
0x000e,
|
||||||
|
0x3805,
|
||||||
|
0x2424,
|
||||||
|
0x2220,
|
||||||
|
0x090c,
|
||||||
|
0x0ecd,
|
||||||
|
0x010e,
|
||||||
|
0x23f7,
|
||||||
|
0x5237,
|
||||||
|
0x64a8
|
||||||
|
};
|
||||||
|
|
||||||
|
void EnvelopeGenerator::reset()
|
||||||
|
{
|
||||||
|
// counter is not changed on reset
|
||||||
|
envelope_pipeline = 0;
|
||||||
|
|
||||||
|
state_pipeline = 0;
|
||||||
|
|
||||||
|
attack = 0;
|
||||||
|
decay = 0;
|
||||||
|
sustain = 0;
|
||||||
|
release = 0;
|
||||||
|
|
||||||
|
gate = false;
|
||||||
|
|
||||||
|
resetLfsr = true;
|
||||||
|
|
||||||
|
exponential_counter = 0;
|
||||||
|
exponential_counter_period = 1;
|
||||||
|
new_exponential_counter_period = 0;
|
||||||
|
|
||||||
|
state = RELEASE;
|
||||||
|
counter_enabled = true;
|
||||||
|
rate = adsrtable[release];
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnvelopeGenerator::writeCONTROL_REG(unsigned char control)
|
||||||
|
{
|
||||||
|
const bool gate_next = (control & 0x01) != 0;
|
||||||
|
|
||||||
|
if (gate_next != gate)
|
||||||
|
{
|
||||||
|
gate = gate_next;
|
||||||
|
|
||||||
|
// The rate counter is never reset, thus there will be a delay before the
|
||||||
|
// envelope counter starts counting up (attack) or down (release).
|
||||||
|
|
||||||
|
if (gate_next)
|
||||||
|
{
|
||||||
|
// Gate bit on: Start attack, decay, sustain.
|
||||||
|
next_state = ATTACK;
|
||||||
|
state_pipeline = 2;
|
||||||
|
|
||||||
|
if (resetLfsr || (exponential_pipeline == 2))
|
||||||
|
{
|
||||||
|
envelope_pipeline = (exponential_counter_period == 1) || (exponential_pipeline == 2) ? 2 : 4;
|
||||||
|
}
|
||||||
|
else if (exponential_pipeline == 1)
|
||||||
|
{
|
||||||
|
state_pipeline = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Gate bit off: Start release.
|
||||||
|
next_state = RELEASE;
|
||||||
|
state_pipeline = envelope_pipeline > 0 ? 3 : 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnvelopeGenerator::writeATTACK_DECAY(unsigned char attack_decay)
|
||||||
|
{
|
||||||
|
attack = (attack_decay >> 4) & 0x0f;
|
||||||
|
decay = attack_decay & 0x0f;
|
||||||
|
|
||||||
|
if (state == ATTACK)
|
||||||
|
{
|
||||||
|
rate = adsrtable[attack];
|
||||||
|
}
|
||||||
|
else if (state == DECAY_SUSTAIN)
|
||||||
|
{
|
||||||
|
rate = adsrtable[decay];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnvelopeGenerator::writeSUSTAIN_RELEASE(unsigned char sustain_release)
|
||||||
|
{
|
||||||
|
// From the sustain levels it follows that both the low and high 4 bits
|
||||||
|
// of the envelope counter are compared to the 4-bit sustain value.
|
||||||
|
// This has been verified by sampling ENV3.
|
||||||
|
//
|
||||||
|
// For a detailed description see:
|
||||||
|
// http://ploguechipsounds.blogspot.it/2010/11/new-research-on-sid-adsr.html
|
||||||
|
sustain = (sustain_release & 0xf0) | ((sustain_release >> 4) & 0x0f);
|
||||||
|
|
||||||
|
release = sustain_release & 0x0f;
|
||||||
|
|
||||||
|
if (state == RELEASE)
|
||||||
|
{
|
||||||
|
rate = adsrtable[release];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
419
src/engine/platform/sound/c64_fp/EnvelopeGenerator.h
Normal file
419
src/engine/platform/sound/c64_fp/EnvelopeGenerator.h
Normal file
|
@ -0,0 +1,419 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2018 VICE Project
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ENVELOPEGENERATOR_H
|
||||||
|
#define ENVELOPEGENERATOR_H
|
||||||
|
|
||||||
|
#include "siddefs-fp.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 15 bit [LFSR] is used to implement the envelope rates, in effect dividing
|
||||||
|
* the clock to the envelope counter by the currently selected rate period.
|
||||||
|
*
|
||||||
|
* In addition, another 5 bit counter is used to implement the exponential envelope decay,
|
||||||
|
* in effect further dividing the clock to the envelope counter.
|
||||||
|
* The period of this counter is set to 1, 2, 4, 8, 16, 30 at the envelope counter
|
||||||
|
* values 255, 93, 54, 26, 14, 6, respectively.
|
||||||
|
*
|
||||||
|
* [LFSR]: https://en.wikipedia.org/wiki/Linear_feedback_shift_register
|
||||||
|
*/
|
||||||
|
class EnvelopeGenerator
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* The envelope state machine's distinct states. In addition to this,
|
||||||
|
* envelope has a hold mode, which freezes envelope counter to zero.
|
||||||
|
*/
|
||||||
|
enum State
|
||||||
|
{
|
||||||
|
ATTACK, DECAY_SUSTAIN, RELEASE
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// XOR shift register for ADSR prescaling.
|
||||||
|
unsigned int lfsr;
|
||||||
|
|
||||||
|
/// Comparison value (period) of the rate counter before next event.
|
||||||
|
unsigned int rate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* During release mode, the SID approximates envelope decay via piecewise
|
||||||
|
* linear decay rate.
|
||||||
|
*/
|
||||||
|
unsigned int exponential_counter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparison value (period) of the exponential decay counter before next
|
||||||
|
* decrement.
|
||||||
|
*/
|
||||||
|
unsigned int exponential_counter_period;
|
||||||
|
unsigned int new_exponential_counter_period;
|
||||||
|
|
||||||
|
unsigned int state_pipeline;
|
||||||
|
|
||||||
|
///
|
||||||
|
unsigned int envelope_pipeline;
|
||||||
|
|
||||||
|
unsigned int exponential_pipeline;
|
||||||
|
|
||||||
|
/// Current envelope state
|
||||||
|
State state;
|
||||||
|
State next_state;
|
||||||
|
|
||||||
|
/// Whether counter is enabled. Only switching to ATTACK can release envelope.
|
||||||
|
bool counter_enabled;
|
||||||
|
|
||||||
|
/// Gate bit
|
||||||
|
bool gate;
|
||||||
|
|
||||||
|
///
|
||||||
|
bool resetLfsr;
|
||||||
|
|
||||||
|
/// The current digital value of envelope output.
|
||||||
|
unsigned char envelope_counter;
|
||||||
|
|
||||||
|
/// Attack register
|
||||||
|
unsigned char attack;
|
||||||
|
|
||||||
|
/// Decay register
|
||||||
|
unsigned char decay;
|
||||||
|
|
||||||
|
/// Sustain register
|
||||||
|
unsigned char sustain;
|
||||||
|
|
||||||
|
/// Release register
|
||||||
|
unsigned char release;
|
||||||
|
|
||||||
|
/// The ENV3 value, sampled at the first phase of the clock
|
||||||
|
unsigned char env3;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const unsigned int adsrtable[16];
|
||||||
|
|
||||||
|
private:
|
||||||
|
void set_exponential_counter();
|
||||||
|
|
||||||
|
void state_change();
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* SID clocking.
|
||||||
|
*/
|
||||||
|
void clock();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Envelope Generator digital output.
|
||||||
|
*/
|
||||||
|
unsigned int output() const { return envelope_counter; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
EnvelopeGenerator() :
|
||||||
|
lfsr(0x7fff),
|
||||||
|
rate(0),
|
||||||
|
exponential_counter(0),
|
||||||
|
exponential_counter_period(1),
|
||||||
|
new_exponential_counter_period(0),
|
||||||
|
state_pipeline(0),
|
||||||
|
envelope_pipeline(0),
|
||||||
|
exponential_pipeline(0),
|
||||||
|
state(RELEASE),
|
||||||
|
next_state(RELEASE),
|
||||||
|
counter_enabled(true),
|
||||||
|
gate(false),
|
||||||
|
resetLfsr(false),
|
||||||
|
envelope_counter(0xaa),
|
||||||
|
attack(0),
|
||||||
|
decay(0),
|
||||||
|
sustain(0),
|
||||||
|
release(0),
|
||||||
|
env3(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SID reset.
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write control register.
|
||||||
|
*
|
||||||
|
* @param control
|
||||||
|
* control register value
|
||||||
|
*/
|
||||||
|
void writeCONTROL_REG(unsigned char control);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write Attack/Decay register.
|
||||||
|
*
|
||||||
|
* @param attack_decay
|
||||||
|
* attack/decay value
|
||||||
|
*/
|
||||||
|
void writeATTACK_DECAY(unsigned char attack_decay);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write Sustain/Release register.
|
||||||
|
*
|
||||||
|
* @param sustain_release
|
||||||
|
* sustain/release value
|
||||||
|
*/
|
||||||
|
void writeSUSTAIN_RELEASE(unsigned char sustain_release);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the envelope current value.
|
||||||
|
*
|
||||||
|
* @return envelope counter value
|
||||||
|
*/
|
||||||
|
unsigned char readENV() const { return env3; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#if RESID_INLINING || defined(ENVELOPEGENERATOR_CPP)
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
RESID_INLINE
|
||||||
|
void EnvelopeGenerator::clock()
|
||||||
|
{
|
||||||
|
env3 = envelope_counter;
|
||||||
|
|
||||||
|
if (unlikely(new_exponential_counter_period > 0))
|
||||||
|
{
|
||||||
|
exponential_counter_period = new_exponential_counter_period;
|
||||||
|
new_exponential_counter_period = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unlikely(state_pipeline))
|
||||||
|
{
|
||||||
|
state_change();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unlikely(envelope_pipeline != 0) && (--envelope_pipeline == 0))
|
||||||
|
{
|
||||||
|
if (likely(counter_enabled))
|
||||||
|
{
|
||||||
|
if (state == ATTACK)
|
||||||
|
{
|
||||||
|
if (++envelope_counter==0xff)
|
||||||
|
{
|
||||||
|
next_state = DECAY_SUSTAIN;
|
||||||
|
state_pipeline = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((state == DECAY_SUSTAIN) || (state == RELEASE))
|
||||||
|
{
|
||||||
|
if (--envelope_counter==0x00)
|
||||||
|
{
|
||||||
|
counter_enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_exponential_counter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (unlikely(exponential_pipeline != 0) && (--exponential_pipeline == 0))
|
||||||
|
{
|
||||||
|
exponential_counter = 0;
|
||||||
|
|
||||||
|
if (((state == DECAY_SUSTAIN) && (envelope_counter != sustain))
|
||||||
|
|| (state == RELEASE))
|
||||||
|
{
|
||||||
|
// The envelope counter can flip from 0x00 to 0xff by changing state to
|
||||||
|
// attack, then to release. The envelope counter will then continue
|
||||||
|
// counting down in the release state.
|
||||||
|
// This has been verified by sampling ENV3.
|
||||||
|
|
||||||
|
envelope_pipeline = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (unlikely(resetLfsr))
|
||||||
|
{
|
||||||
|
lfsr = 0x7fff;
|
||||||
|
resetLfsr = false;
|
||||||
|
|
||||||
|
if (state == ATTACK)
|
||||||
|
{
|
||||||
|
// The first envelope step in the attack state also resets the exponential
|
||||||
|
// counter. This has been verified by sampling ENV3.
|
||||||
|
exponential_counter = 0; // NOTE this is actually delayed one cycle, not modeled
|
||||||
|
|
||||||
|
// The envelope counter can flip from 0xff to 0x00 by changing state to
|
||||||
|
// release, then to attack. The envelope counter is then frozen at
|
||||||
|
// zero; to unlock this situation the state must be changed to release,
|
||||||
|
// then to attack. This has been verified by sampling ENV3.
|
||||||
|
|
||||||
|
envelope_pipeline = 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (counter_enabled && (++exponential_counter == exponential_counter_period))
|
||||||
|
exponential_pipeline = exponential_counter_period != 1 ? 2 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADSR delay bug.
|
||||||
|
// If the rate counter comparison value is set below the current value of the
|
||||||
|
// rate counter, the counter will continue counting up until it wraps around
|
||||||
|
// to zero at 2^15 = 0x8000, and then count rate_period - 1 before the
|
||||||
|
// envelope can constly be stepped.
|
||||||
|
// This has been verified by sampling ENV3.
|
||||||
|
|
||||||
|
// check to see if LFSR matches table value
|
||||||
|
if (likely(lfsr != rate))
|
||||||
|
{
|
||||||
|
// it wasn't a match, clock the LFSR once
|
||||||
|
// by performing XOR on last 2 bits
|
||||||
|
const unsigned int feedback = ((lfsr << 14) ^ (lfsr << 13)) & 0x4000;
|
||||||
|
lfsr = (lfsr >> 1) | feedback;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resetLfsr = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is what happens on chip during state switching,
|
||||||
|
* based on die reverse engineering and transistor level
|
||||||
|
* emulation.
|
||||||
|
*
|
||||||
|
* Attack
|
||||||
|
*
|
||||||
|
* 0 - Gate on
|
||||||
|
* 1 - Counting direction changes
|
||||||
|
* During this cycle the decay rate is "accidentally" activated
|
||||||
|
* 2 - Counter is being inverted
|
||||||
|
* Now the attack rate is correctly activated
|
||||||
|
* Counter is enabled
|
||||||
|
* 3 - Counter will be counting upward from now on
|
||||||
|
*
|
||||||
|
* Decay
|
||||||
|
*
|
||||||
|
* 0 - Counter == $ff
|
||||||
|
* 1 - Counting direction changes
|
||||||
|
* The attack state is still active
|
||||||
|
* 2 - Counter is being inverted
|
||||||
|
* During this cycle the decay state is activated
|
||||||
|
* 3 - Counter will be counting downward from now on
|
||||||
|
*
|
||||||
|
* Release
|
||||||
|
*
|
||||||
|
* 0 - Gate off
|
||||||
|
* 1 - During this cycle the release state is activated if coming from sustain/decay
|
||||||
|
* *2 - Counter is being inverted, the release state is activated
|
||||||
|
* *3 - Counter will be counting downward from now on
|
||||||
|
*
|
||||||
|
* (* only if coming directly from Attack state)
|
||||||
|
*
|
||||||
|
* Freeze
|
||||||
|
*
|
||||||
|
* 0 - Counter == $00
|
||||||
|
* 1 - Nothing
|
||||||
|
* 2 - Counter is disabled
|
||||||
|
*/
|
||||||
|
RESID_INLINE
|
||||||
|
void EnvelopeGenerator::state_change()
|
||||||
|
{
|
||||||
|
state_pipeline--;
|
||||||
|
|
||||||
|
switch (next_state)
|
||||||
|
{
|
||||||
|
case ATTACK:
|
||||||
|
if (state_pipeline == 1)
|
||||||
|
{
|
||||||
|
// The decay rate is "accidentally" enabled during first cycle of attack phase
|
||||||
|
rate = adsrtable[decay];
|
||||||
|
}
|
||||||
|
else if (state_pipeline == 0)
|
||||||
|
{
|
||||||
|
state = ATTACK;
|
||||||
|
// The attack rate is correctly enabled during second cycle of attack phase
|
||||||
|
rate = adsrtable[attack];
|
||||||
|
counter_enabled = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DECAY_SUSTAIN:
|
||||||
|
if (state_pipeline == 0)
|
||||||
|
{
|
||||||
|
state = DECAY_SUSTAIN;
|
||||||
|
rate = adsrtable[decay];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RELEASE:
|
||||||
|
if (((state == ATTACK) && (state_pipeline == 0))
|
||||||
|
|| ((state == DECAY_SUSTAIN) && (state_pipeline == 1)))
|
||||||
|
{
|
||||||
|
state = RELEASE;
|
||||||
|
rate = adsrtable[release];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RESID_INLINE
|
||||||
|
void EnvelopeGenerator::set_exponential_counter()
|
||||||
|
{
|
||||||
|
// Check for change of exponential counter period.
|
||||||
|
//
|
||||||
|
// For a detailed description see:
|
||||||
|
// http://ploguechipsounds.blogspot.it/2010/03/sid-6581r3-adsr-tables-up-close.html
|
||||||
|
switch (envelope_counter)
|
||||||
|
{
|
||||||
|
case 0xff:
|
||||||
|
case 0x00:
|
||||||
|
new_exponential_counter_period = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x5d:
|
||||||
|
new_exponential_counter_period = 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x36:
|
||||||
|
new_exponential_counter_period = 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x1a:
|
||||||
|
new_exponential_counter_period = 8;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x0e:
|
||||||
|
new_exponential_counter_period = 16;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x06:
|
||||||
|
new_exponential_counter_period = 30;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
68
src/engine/platform/sound/c64_fp/ExternalFilter.cpp
Normal file
68
src/engine/platform/sound/c64_fp/ExternalFilter.cpp
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define EXTERNALFILTER_CPP
|
||||||
|
|
||||||
|
#include "ExternalFilter.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the 3 dB attenuation point.
|
||||||
|
*
|
||||||
|
* @param res the resistance value in Ohms
|
||||||
|
* @param cap the capacitance value in Farads
|
||||||
|
*/
|
||||||
|
inline double getRC(double res, double cap)
|
||||||
|
{
|
||||||
|
return res * cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExternalFilter::ExternalFilter() :
|
||||||
|
w0lp_1_s7(0),
|
||||||
|
w0hp_1_s17(0)
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExternalFilter::setClockFrequency(double frequency)
|
||||||
|
{
|
||||||
|
const double dt = 1. / frequency;
|
||||||
|
|
||||||
|
// Low-pass: R = 10kOhm, C = 1000pF; w0l = dt/(dt+RC) = 1e-6/(1e-6+1e4*1e-9) = 0.091
|
||||||
|
// Cutoff 1/2*PI*RC = 1/2*PI*1e4*1e-9 = 15915.5 Hz
|
||||||
|
w0lp_1_s7 = static_cast<int>((dt / (dt + getRC(10e3, 1000e-12))) * (1 << 7) + 0.5);
|
||||||
|
|
||||||
|
// High-pass: R = 10kOhm, C = 10uF; w0h = dt/(dt+RC) = 1e-6/(1e-6+1e4*1e-5) = 0.00000999
|
||||||
|
// Cutoff 1/2*PI*RC = 1/2*PI*1e4*1e-5 = 1.59155 Hz
|
||||||
|
w0hp_1_s17 = static_cast<int>((dt / (dt + getRC(10e3, 10e-6))) * (1 << 17) + 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExternalFilter::reset()
|
||||||
|
{
|
||||||
|
// State of filter.
|
||||||
|
Vlp = 0; //1 << (15 + 11);
|
||||||
|
Vhp = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
125
src/engine/platform/sound/c64_fp/ExternalFilter.h
Normal file
125
src/engine/platform/sound/c64_fp/ExternalFilter.h
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef EXTERNALFILTER_H
|
||||||
|
#define EXTERNALFILTER_H
|
||||||
|
|
||||||
|
#include "siddefs-fp.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The audio output stage in a Commodore 64 consists of two STC networks, a
|
||||||
|
* low-pass RC filter with 3 dB frequency 16kHz followed by a DC-blocker which
|
||||||
|
* acts as a high-pass filter with a cutoff dependent on the attached audio
|
||||||
|
* equipment impedance. Here we suppose an impedance of 10kOhm resulting
|
||||||
|
* in a 3 dB attenuation at 1.6Hz.
|
||||||
|
* To operate properly the 6581 audio output needs a pull-down resistor
|
||||||
|
*(1KOhm recommended, not needed on 8580)
|
||||||
|
*
|
||||||
|
* ~~~
|
||||||
|
* 9/12V
|
||||||
|
* -----+
|
||||||
|
* audio| 10k |
|
||||||
|
* +---o----R---o--------o-----(K) +-----
|
||||||
|
* out | | | | | |audio
|
||||||
|
* -----+ R 1k C 1000 | | 10 uF |
|
||||||
|
* | | pF +-C----o-----C-----+ 10k
|
||||||
|
* 470 | |
|
||||||
|
* GND GND pF R 1K | amp
|
||||||
|
* * * | +-----
|
||||||
|
*
|
||||||
|
* GND
|
||||||
|
* ~~~
|
||||||
|
*
|
||||||
|
* The STC networks are connected with a [BJT] based [common collector]
|
||||||
|
* used as a voltage follower (featuring a 2SC1815 NPN transistor).
|
||||||
|
* * The C64c board additionally includes a [bootstrap] condenser to increase
|
||||||
|
* the input impedance of the common collector.
|
||||||
|
*
|
||||||
|
* [BJT]: https://en.wikipedia.org/wiki/Bipolar_junction_transistor
|
||||||
|
* [common collector]: https://en.wikipedia.org/wiki/Common_collector
|
||||||
|
* [bootstrap]: https://en.wikipedia.org/wiki/Bootstrapping_(electronics)
|
||||||
|
*/
|
||||||
|
class ExternalFilter
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
/// Lowpass filter voltage
|
||||||
|
int Vlp;
|
||||||
|
|
||||||
|
/// Highpass filter voltage
|
||||||
|
int Vhp;
|
||||||
|
|
||||||
|
int w0lp_1_s7;
|
||||||
|
|
||||||
|
int w0hp_1_s17;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* SID clocking.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
*/
|
||||||
|
int clock(unsigned short input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
ExternalFilter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup of the external filter sampling parameters.
|
||||||
|
*
|
||||||
|
* @param frequency the main system clock frequency
|
||||||
|
*/
|
||||||
|
void setClockFrequency(double frequency);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SID reset.
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#if RESID_INLINING || defined(EXTERNALFILTER_CPP)
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
RESID_INLINE
|
||||||
|
int ExternalFilter::clock(unsigned short input)
|
||||||
|
{
|
||||||
|
const int Vi = (static_cast<unsigned int>(input)<<11) - (1 << (11+15));
|
||||||
|
const int dVlp = (w0lp_1_s7 * (Vi - Vlp) >> 7);
|
||||||
|
const int dVhp = (w0hp_1_s17 * (Vlp - Vhp) >> 17);
|
||||||
|
Vlp += dVlp;
|
||||||
|
Vhp += dVhp;
|
||||||
|
return (Vlp - Vhp) >> 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
90
src/engine/platform/sound/c64_fp/Filter.cpp
Normal file
90
src/engine/platform/sound/c64_fp/Filter.cpp
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2013 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Filter.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
void Filter::enable(bool enable)
|
||||||
|
{
|
||||||
|
enabled = enable;
|
||||||
|
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
writeRES_FILT(filt);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
filt1 = filt2 = filt3 = filtE = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Filter::reset()
|
||||||
|
{
|
||||||
|
writeFC_LO(0);
|
||||||
|
writeFC_HI(0);
|
||||||
|
writeMODE_VOL(0);
|
||||||
|
writeRES_FILT(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Filter::writeFC_LO(unsigned char fc_lo)
|
||||||
|
{
|
||||||
|
fc = (fc & 0x7f8) | (fc_lo & 0x007);
|
||||||
|
updatedCenterFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Filter::writeFC_HI(unsigned char fc_hi)
|
||||||
|
{
|
||||||
|
fc = (fc_hi << 3 & 0x7f8) | (fc & 0x007);
|
||||||
|
updatedCenterFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Filter::writeRES_FILT(unsigned char res_filt)
|
||||||
|
{
|
||||||
|
filt = res_filt;
|
||||||
|
|
||||||
|
updateResonance((res_filt >> 4) & 0x0f);
|
||||||
|
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
filt1 = (filt & 0x01) != 0;
|
||||||
|
filt2 = (filt & 0x02) != 0;
|
||||||
|
filt3 = (filt & 0x04) != 0;
|
||||||
|
filtE = (filt & 0x08) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedMixing();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Filter::writeMODE_VOL(unsigned char mode_vol)
|
||||||
|
{
|
||||||
|
vol = mode_vol & 0x0f;
|
||||||
|
lp = (mode_vol & 0x10) != 0;
|
||||||
|
bp = (mode_vol & 0x20) != 0;
|
||||||
|
hp = (mode_vol & 0x40) != 0;
|
||||||
|
voice3off = (mode_vol & 0x80) != 0;
|
||||||
|
|
||||||
|
updatedMixing();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
177
src/engine/platform/sound/c64_fp/Filter.h
Normal file
177
src/engine/platform/sound/c64_fp/Filter.h
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2017 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FILTER_H
|
||||||
|
#define FILTER_H
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SID filter base class
|
||||||
|
*/
|
||||||
|
class Filter
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
/// Current volume amplifier setting.
|
||||||
|
unsigned short* currentGain;
|
||||||
|
|
||||||
|
/// Current filter/voice mixer setting.
|
||||||
|
unsigned short* currentMixer;
|
||||||
|
|
||||||
|
/// Filter input summer setting.
|
||||||
|
unsigned short* currentSummer;
|
||||||
|
|
||||||
|
/// Filter resonance value.
|
||||||
|
unsigned short* currentResonance;
|
||||||
|
|
||||||
|
/// Filter highpass state.
|
||||||
|
int Vhp;
|
||||||
|
|
||||||
|
/// Filter bandpass state.
|
||||||
|
int Vbp;
|
||||||
|
|
||||||
|
/// Filter lowpass state.
|
||||||
|
int Vlp;
|
||||||
|
|
||||||
|
/// Filter external input.
|
||||||
|
int ve;
|
||||||
|
|
||||||
|
/// Filter cutoff frequency.
|
||||||
|
unsigned int fc;
|
||||||
|
|
||||||
|
/// Routing to filter or outside filter
|
||||||
|
bool filt1, filt2, filt3, filtE;
|
||||||
|
|
||||||
|
/// Switch voice 3 off.
|
||||||
|
bool voice3off;
|
||||||
|
|
||||||
|
/// Highpass, bandpass, and lowpass filter modes.
|
||||||
|
bool hp, bp, lp;
|
||||||
|
|
||||||
|
/// Current volume.
|
||||||
|
unsigned char vol;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Filter enabled.
|
||||||
|
bool enabled;
|
||||||
|
|
||||||
|
/// Selects which inputs to route through filter.
|
||||||
|
unsigned char filt;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Set filter cutoff frequency.
|
||||||
|
*/
|
||||||
|
virtual void updatedCenterFrequency() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set filter resonance.
|
||||||
|
*/
|
||||||
|
virtual void updateResonance(unsigned char res) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mixing configuration modified (offsets change)
|
||||||
|
*/
|
||||||
|
virtual void updatedMixing() = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Filter() :
|
||||||
|
currentGain(nullptr),
|
||||||
|
currentMixer(nullptr),
|
||||||
|
currentSummer(nullptr),
|
||||||
|
currentResonance(nullptr),
|
||||||
|
Vhp(0),
|
||||||
|
Vbp(0),
|
||||||
|
Vlp(0),
|
||||||
|
ve(0),
|
||||||
|
fc(0),
|
||||||
|
filt1(false),
|
||||||
|
filt2(false),
|
||||||
|
filt3(false),
|
||||||
|
filtE(false),
|
||||||
|
voice3off(false),
|
||||||
|
hp(false),
|
||||||
|
bp(false),
|
||||||
|
lp(false),
|
||||||
|
vol(0),
|
||||||
|
enabled(true),
|
||||||
|
filt(0) {}
|
||||||
|
|
||||||
|
virtual ~Filter() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SID clocking - 1 cycle
|
||||||
|
*
|
||||||
|
* @param v1 voice 1 in
|
||||||
|
* @param v2 voice 2 in
|
||||||
|
* @param v3 voice 3 in
|
||||||
|
* @return filtered output
|
||||||
|
*/
|
||||||
|
virtual unsigned short clock(int v1, int v2, int v3) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable filter.
|
||||||
|
*
|
||||||
|
* @param enable
|
||||||
|
*/
|
||||||
|
void enable(bool enable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SID reset.
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write Frequency Cutoff Low register.
|
||||||
|
*
|
||||||
|
* @param fc_lo Frequency Cutoff Low-Byte
|
||||||
|
*/
|
||||||
|
void writeFC_LO(unsigned char fc_lo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write Frequency Cutoff High register.
|
||||||
|
*
|
||||||
|
* @param fc_hi Frequency Cutoff High-Byte
|
||||||
|
*/
|
||||||
|
void writeFC_HI(unsigned char fc_hi);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write Resonance/Filter register.
|
||||||
|
*
|
||||||
|
* @param res_filt Resonance/Filter
|
||||||
|
*/
|
||||||
|
void writeRES_FILT(unsigned char res_filt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write filter Mode/Volume register.
|
||||||
|
*
|
||||||
|
* @param mode_vol Filter Mode/Volume
|
||||||
|
*/
|
||||||
|
void writeMODE_VOL(unsigned char mode_vol);
|
||||||
|
|
||||||
|
virtual void input(int input) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
75
src/engine/platform/sound/c64_fp/Filter6581.cpp
Normal file
75
src/engine/platform/sound/c64_fp/Filter6581.cpp
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2015 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define FILTER6581_CPP
|
||||||
|
|
||||||
|
#include "Filter6581.h"
|
||||||
|
|
||||||
|
#include "Integrator6581.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
Filter6581::~Filter6581()
|
||||||
|
{
|
||||||
|
delete [] f0_dac;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Filter6581::updatedCenterFrequency()
|
||||||
|
{
|
||||||
|
const unsigned short Vw = f0_dac[fc];
|
||||||
|
hpIntegrator->setVw(Vw);
|
||||||
|
bpIntegrator->setVw(Vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Filter6581::updatedMixing()
|
||||||
|
{
|
||||||
|
currentGain = gain_vol[vol];
|
||||||
|
|
||||||
|
unsigned int ni = 0;
|
||||||
|
unsigned int no = 0;
|
||||||
|
|
||||||
|
(filt1 ? ni : no)++;
|
||||||
|
(filt2 ? ni : no)++;
|
||||||
|
|
||||||
|
if (filt3) ni++;
|
||||||
|
else if (!voice3off) no++;
|
||||||
|
|
||||||
|
(filtE ? ni : no)++;
|
||||||
|
|
||||||
|
currentSummer = summer[ni];
|
||||||
|
|
||||||
|
if (lp) no++;
|
||||||
|
if (bp) no++;
|
||||||
|
if (hp) no++;
|
||||||
|
|
||||||
|
currentMixer = mixer[no];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Filter6581::setFilterCurve(double curvePosition)
|
||||||
|
{
|
||||||
|
delete [] f0_dac;
|
||||||
|
f0_dac = FilterModelConfig6581::getInstance()->getDAC(curvePosition);
|
||||||
|
updatedCenterFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
425
src/engine/platform/sound/c64_fp/Filter6581.h
Normal file
425
src/engine/platform/sound/c64_fp/Filter6581.h
Normal file
|
@ -0,0 +1,425 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FILTER6581_H
|
||||||
|
#define FILTER6581_H
|
||||||
|
|
||||||
|
#include "siddefs-fp.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "Filter.h"
|
||||||
|
#include "FilterModelConfig6581.h"
|
||||||
|
|
||||||
|
#include "sidcxx11.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
class Integrator6581;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SID filter is modeled with a two-integrator-loop biquadratic filter,
|
||||||
|
* which has been confirmed by Bob Yannes to be the actual circuit used in
|
||||||
|
* the SID chip.
|
||||||
|
*
|
||||||
|
* Measurements show that excellent emulation of the SID filter is achieved,
|
||||||
|
* except when high resonance is combined with high sustain levels.
|
||||||
|
* In this case the SID op-amps are performing less than ideally and are
|
||||||
|
* causing some peculiar behavior of the SID filter. This however seems to
|
||||||
|
* have more effect on the overall amplitude than on the color of the sound.
|
||||||
|
*
|
||||||
|
* The theory for the filter circuit can be found in "Microelectric Circuits"
|
||||||
|
* by Adel S. Sedra and Kenneth C. Smith.
|
||||||
|
* The circuit is modeled based on the explanation found there except that
|
||||||
|
* an additional inverter is used in the feedback from the bandpass output,
|
||||||
|
* allowing the summer op-amp to operate in single-ended mode. This yields
|
||||||
|
* filter outputs with levels independent of Q, which corresponds with the
|
||||||
|
* results obtained from a real SID.
|
||||||
|
*
|
||||||
|
* We have been able to model the summer and the two integrators of the circuit
|
||||||
|
* to form components of an IIR filter.
|
||||||
|
* Vhp is the output of the summer, Vbp is the output of the first integrator,
|
||||||
|
* and Vlp is the output of the second integrator in the filter circuit.
|
||||||
|
*
|
||||||
|
* According to Bob Yannes, the active stages of the SID filter are not really
|
||||||
|
* op-amps. Rather, simple NMOS inverters are used. By biasing an inverter
|
||||||
|
* into its region of quasi-linear operation using a feedback resistor from
|
||||||
|
* input to output, a MOS inverter can be made to act like an op-amp for
|
||||||
|
* small signals centered around the switching threshold.
|
||||||
|
*
|
||||||
|
* In 2008, Michael Huth facilitated closer investigation of the SID 6581
|
||||||
|
* filter circuit by publishing high quality microscope photographs of the die.
|
||||||
|
* Tommi Lempinen has done an impressive work on re-vectorizing and annotating
|
||||||
|
* the die photographs, substantially simplifying further analysis of the
|
||||||
|
* filter circuit.
|
||||||
|
*
|
||||||
|
* The filter schematics below are reverse engineered from these re-vectorized
|
||||||
|
* and annotated die photographs. While the filter first depicted in reSID 0.9
|
||||||
|
* is a correct model of the basic filter, the schematics are now completed
|
||||||
|
* with the audio mixer and output stage, including details on intended
|
||||||
|
* relative resistor values. Also included are schematics for the NMOS FET
|
||||||
|
* voltage controlled resistors (VCRs) used to control cutoff frequency, the
|
||||||
|
* DAC which controls the VCRs, the NMOS op-amps, and the output buffer.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* SID filter / mixer / output
|
||||||
|
* ---------------------------
|
||||||
|
* ~~~
|
||||||
|
* +---------------------------------------------------+
|
||||||
|
* | |
|
||||||
|
* | +--1R1-- \--+ D7 |
|
||||||
|
* | +---R1--+ | | |
|
||||||
|
* | | | o--2R1-- \--o D6 |
|
||||||
|
* | +---------o--<A]--o--o | $17 |
|
||||||
|
* | | o--4R1-- \--o D5 1=open | (3.5R1)
|
||||||
|
* | | | | |
|
||||||
|
* | | +--8R1-- \--o D4 | (7.0R1)
|
||||||
|
* | | | |
|
||||||
|
* $17 | | (CAP2B) | (CAP1B) |
|
||||||
|
* 0=to mixer | +--R8--+ +---R8--+ +---C---o +---C---o
|
||||||
|
* 1=to filter | | | | | | | |
|
||||||
|
* ------R8--o--o--[A>--o--Rw--o--[A>--o--Rw--o--[A>--o
|
||||||
|
* ve (EXT IN) | | | |
|
||||||
|
* D3 \ ---------------R8--o | | (CAP2A) | (CAP1A)
|
||||||
|
* | v3 | | vhp | vbp | vlp
|
||||||
|
* D2 | \ -----------R8--o +-----+ | |
|
||||||
|
* | | v2 | | | |
|
||||||
|
* D1 | | \ -------R8--o | +----------------+ |
|
||||||
|
* | | | v1 | | | |
|
||||||
|
* D0 | | | \ ---R8--+ | | +---------------------------+
|
||||||
|
* | | | | | | |
|
||||||
|
* R6 R6 R6 R6 R6 R6 R6
|
||||||
|
* | | | | $18 | | | $18
|
||||||
|
* | \ | | D7: 1=open \ \ \ D6 - D4: 0=open
|
||||||
|
* | | | | | | |
|
||||||
|
* +---o---o---o-------------o---o---+ 12V
|
||||||
|
* |
|
||||||
|
* | D3 +--/ --1R2--+ |
|
||||||
|
* | +---R8--+ | | +---R2--+ |
|
||||||
|
* | | | D2 o--/ --2R2--o | | ||--+
|
||||||
|
* +---o--[A>--o------o o--o--[A>--o--||
|
||||||
|
* D1 o--/ --4R2--o (4.25R2) ||--+
|
||||||
|
* $18 | | |
|
||||||
|
* 0=open D0 +--/ --8R2--+ (8.75R2) |
|
||||||
|
*
|
||||||
|
* vo (AUDIO
|
||||||
|
* OUT)
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* v1 - voice 1
|
||||||
|
* v2 - voice 2
|
||||||
|
* v3 - voice 3
|
||||||
|
* ve - ext in
|
||||||
|
* vhp - highpass output
|
||||||
|
* vbp - bandpass output
|
||||||
|
* vlp - lowpass output
|
||||||
|
* vo - audio out
|
||||||
|
* [A> - single ended inverting op-amp (self-biased NMOS inverter)
|
||||||
|
* Rn - "resistors", implemented with custom NMOS FETs
|
||||||
|
* Rw - cutoff frequency resistor (VCR)
|
||||||
|
* C - capacitor
|
||||||
|
* ~~~
|
||||||
|
* Notes:
|
||||||
|
*
|
||||||
|
* R2 ~ 2.0*R1
|
||||||
|
* R6 ~ 6.0*R1
|
||||||
|
* R8 ~ 8.0*R1
|
||||||
|
* R24 ~ 24.0*R1
|
||||||
|
*
|
||||||
|
* The Rn "resistors" in the circuit are implemented with custom NMOS FETs,
|
||||||
|
* probably because of space constraints on the SID die. The silicon substrate
|
||||||
|
* is laid out in a narrow strip or "snake", with a strip length proportional
|
||||||
|
* to the intended resistance. The polysilicon gate electrode covers the entire
|
||||||
|
* silicon substrate and is fixed at 12V in order for the NMOS FET to operate
|
||||||
|
* in triode mode (a.k.a. linear mode or ohmic mode).
|
||||||
|
*
|
||||||
|
* Even in "linear mode", an NMOS FET is only an approximation of a resistor,
|
||||||
|
* as the apparant resistance increases with increasing drain-to-source
|
||||||
|
* voltage. If the drain-to-source voltage should approach the gate voltage
|
||||||
|
* of 12V, the NMOS FET will enter saturation mode (a.k.a. active mode), and
|
||||||
|
* the NMOS FET will not operate anywhere like a resistor.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* NMOS FET voltage controlled resistor (VCR)
|
||||||
|
* ------------------------------------------
|
||||||
|
* ~~~
|
||||||
|
* Vw
|
||||||
|
*
|
||||||
|
* |
|
||||||
|
* |
|
||||||
|
* R1
|
||||||
|
* |
|
||||||
|
* +--R1--o
|
||||||
|
* | __|__
|
||||||
|
* | -----
|
||||||
|
* | | |
|
||||||
|
* vi -----o----+ +--o----- vo
|
||||||
|
* | |
|
||||||
|
* +----R24----+
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* vi - input
|
||||||
|
* vo - output
|
||||||
|
* Rn - "resistors", implemented with custom NMOS FETs
|
||||||
|
* Vw - voltage from 11-bit DAC (frequency cutoff control)
|
||||||
|
* ~~~
|
||||||
|
* Notes:
|
||||||
|
*
|
||||||
|
* An approximate value for R24 can be found by using the formula for the
|
||||||
|
* filter cutoff frequency:
|
||||||
|
*
|
||||||
|
* FCmin = 1/(2*pi*Rmax*C)
|
||||||
|
*
|
||||||
|
* Assuming that a the setting for minimum cutoff frequency in combination with
|
||||||
|
* a low level input signal ensures that only negligible current will flow
|
||||||
|
* through the transistor in the schematics above, values for FCmin and C can
|
||||||
|
* be substituted in this formula to find Rmax.
|
||||||
|
* Using C = 470pF and FCmin = 220Hz (measured value), we get:
|
||||||
|
*
|
||||||
|
* FCmin = 1/(2*pi*Rmax*C)
|
||||||
|
* Rmax = 1/(2*pi*FCmin*C) = 1/(2*pi*220*470e-12) ~ 1.5MOhm
|
||||||
|
*
|
||||||
|
* From this it follows that:
|
||||||
|
* R24 = Rmax ~ 1.5MOhm
|
||||||
|
* R1 ~ R24/24 ~ 64kOhm
|
||||||
|
* R2 ~ 2.0*R1 ~ 128kOhm
|
||||||
|
* R6 ~ 6.0*R1 ~ 384kOhm
|
||||||
|
* R8 ~ 8.0*R1 ~ 512kOhm
|
||||||
|
*
|
||||||
|
* Note that these are only approximate values for one particular SID chip,
|
||||||
|
* due to process variations the values can be substantially different in
|
||||||
|
* other chips.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Filter frequency cutoff DAC
|
||||||
|
* ---------------------------
|
||||||
|
*
|
||||||
|
* ~~~
|
||||||
|
* 12V 10 9 8 7 6 5 4 3 2 1 0 VGND
|
||||||
|
* | | | | | | | | | | | | | Missing
|
||||||
|
* 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R termination
|
||||||
|
* | | | | | | | | | | | | |
|
||||||
|
* Vw --o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o- -+
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Bit on: 12V
|
||||||
|
* Bit off: 5V (VGND)
|
||||||
|
* ~~~
|
||||||
|
* As is the case with all MOS 6581 DACs, the termination to (virtual) ground
|
||||||
|
* at bit 0 is missing.
|
||||||
|
*
|
||||||
|
* Furthermore, the control of the two VCRs imposes a load on the DAC output
|
||||||
|
* which varies with the input signals to the VCRs. This can be seen from the
|
||||||
|
* VCR figure above.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* "Op-amp" (self-biased NMOS inverter)
|
||||||
|
* ------------------------------------
|
||||||
|
* ~~~
|
||||||
|
*
|
||||||
|
* 12V
|
||||||
|
*
|
||||||
|
* |
|
||||||
|
* +-----------o
|
||||||
|
* | |
|
||||||
|
* | +------o
|
||||||
|
* | | |
|
||||||
|
* | | ||--+
|
||||||
|
* | +--||
|
||||||
|
* | ||--+
|
||||||
|
* ||--+ |
|
||||||
|
* vi -----|| o---o----- vo
|
||||||
|
* ||--+ | |
|
||||||
|
* | ||--+ |
|
||||||
|
* |-------|| |
|
||||||
|
* | ||--+ |
|
||||||
|
* ||--+ | |
|
||||||
|
* +--|| | |
|
||||||
|
* | ||--+ | |
|
||||||
|
* | | | |
|
||||||
|
* | +-----------o |
|
||||||
|
* | | |
|
||||||
|
* | |
|
||||||
|
* | GND |
|
||||||
|
* | |
|
||||||
|
* +----------------------+
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* vi - input
|
||||||
|
* vo - output
|
||||||
|
* ~~~
|
||||||
|
* Notes:
|
||||||
|
*
|
||||||
|
* The schematics above are laid out to show that the "op-amp" logically
|
||||||
|
* consists of two building blocks; a saturated load NMOS inverter (on the
|
||||||
|
* right hand side of the schematics) with a buffer / bias input stage
|
||||||
|
* consisting of a variable saturated load NMOS inverter (on the left hand
|
||||||
|
* side of the schematics).
|
||||||
|
*
|
||||||
|
* Provided a reasonably high input impedance and a reasonably low output
|
||||||
|
* impedance, the "op-amp" can be modeled as a voltage transfer function
|
||||||
|
* mapping input voltage to output voltage.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Output buffer (NMOS voltage follower)
|
||||||
|
* -------------------------------------
|
||||||
|
* ~~~
|
||||||
|
*
|
||||||
|
* 12V
|
||||||
|
*
|
||||||
|
* |
|
||||||
|
* |
|
||||||
|
* ||--+
|
||||||
|
* vi -----||
|
||||||
|
* ||--+
|
||||||
|
* |
|
||||||
|
* o------ vo
|
||||||
|
* | (AUDIO
|
||||||
|
* Rext OUT)
|
||||||
|
* |
|
||||||
|
* |
|
||||||
|
*
|
||||||
|
* GND
|
||||||
|
*
|
||||||
|
* vi - input
|
||||||
|
* vo - output
|
||||||
|
* Rext - external resistor, 1kOhm
|
||||||
|
* ~~~
|
||||||
|
* Notes:
|
||||||
|
*
|
||||||
|
* The external resistor Rext is needed to complete the NMOS voltage follower,
|
||||||
|
* this resistor has a recommended value of 1kOhm.
|
||||||
|
*
|
||||||
|
* Die photographs show that actually, two NMOS transistors are used in the
|
||||||
|
* voltage follower. However the two transistors are coupled in parallel (all
|
||||||
|
* terminals are pairwise common), which implies that we can model the two
|
||||||
|
* transistors as one.
|
||||||
|
*/
|
||||||
|
class Filter6581 final : public Filter
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
const unsigned short* f0_dac;
|
||||||
|
|
||||||
|
unsigned short** mixer;
|
||||||
|
unsigned short** summer;
|
||||||
|
unsigned short** gain_res;
|
||||||
|
unsigned short** gain_vol;
|
||||||
|
|
||||||
|
const int voiceScaleS11;
|
||||||
|
const int voiceDC;
|
||||||
|
|
||||||
|
/// VCR + associated capacitor connected to highpass output.
|
||||||
|
std::unique_ptr<Integrator6581> const hpIntegrator;
|
||||||
|
|
||||||
|
/// VCR + associated capacitor connected to bandpass output.
|
||||||
|
std::unique_ptr<Integrator6581> const bpIntegrator;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Set filter cutoff frequency.
|
||||||
|
*/
|
||||||
|
void updatedCenterFrequency() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set filter resonance.
|
||||||
|
*
|
||||||
|
* In the MOS 6581, 1/Q is controlled linearly by res.
|
||||||
|
*/
|
||||||
|
void updateResonance(unsigned char res) override { currentResonance = gain_res[res]; }
|
||||||
|
|
||||||
|
void updatedMixing() override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Filter6581() :
|
||||||
|
f0_dac(FilterModelConfig6581::getInstance()->getDAC(0.5)),
|
||||||
|
mixer(FilterModelConfig6581::getInstance()->getMixer()),
|
||||||
|
summer(FilterModelConfig6581::getInstance()->getSummer()),
|
||||||
|
gain_res(FilterModelConfig6581::getInstance()->getGainRes()),
|
||||||
|
gain_vol(FilterModelConfig6581::getInstance()->getGainVol()),
|
||||||
|
voiceScaleS11(FilterModelConfig6581::getInstance()->getVoiceScaleS11()),
|
||||||
|
voiceDC(FilterModelConfig6581::getInstance()->getNormalizedVoiceDC()),
|
||||||
|
hpIntegrator(FilterModelConfig6581::getInstance()->buildIntegrator()),
|
||||||
|
bpIntegrator(FilterModelConfig6581::getInstance()->buildIntegrator())
|
||||||
|
{
|
||||||
|
input(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
~Filter6581();
|
||||||
|
|
||||||
|
unsigned short clock(int voice1, int voice2, int voice3) override;
|
||||||
|
|
||||||
|
void input(int sample) override { ve = (sample * voiceScaleS11 * 3 >> 11) + mixer[0][0]; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set filter curve type based on single parameter.
|
||||||
|
*
|
||||||
|
* @param curvePosition 0 .. 1, where 0 sets center frequency high ("light") and 1 sets it low ("dark"), default is 0.5
|
||||||
|
*/
|
||||||
|
void setFilterCurve(double curvePosition);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#if RESID_INLINING || defined(FILTER6581_CPP)
|
||||||
|
|
||||||
|
#include "Integrator6581.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
RESID_INLINE
|
||||||
|
unsigned short Filter6581::clock(int voice1, int voice2, int voice3)
|
||||||
|
{
|
||||||
|
voice1 = (voice1 * voiceScaleS11 >> 15) + voiceDC;
|
||||||
|
voice2 = (voice2 * voiceScaleS11 >> 15) + voiceDC;
|
||||||
|
// Voice 3 is silenced by voice3off if it is not routed through the filter.
|
||||||
|
voice3 = (filt3 || !voice3off) ? (voice3 * voiceScaleS11 >> 15) + voiceDC : 0;
|
||||||
|
|
||||||
|
int Vi = 0;
|
||||||
|
int Vo = 0;
|
||||||
|
|
||||||
|
(filt1 ? Vi : Vo) += voice1;
|
||||||
|
(filt2 ? Vi : Vo) += voice2;
|
||||||
|
(filt3 ? Vi : Vo) += voice3;
|
||||||
|
(filtE ? Vi : Vo) += ve;
|
||||||
|
|
||||||
|
Vhp = currentSummer[currentResonance[Vbp] + Vlp + Vi];
|
||||||
|
Vbp = hpIntegrator->solve(Vhp);
|
||||||
|
Vlp = bpIntegrator->solve(Vbp);
|
||||||
|
|
||||||
|
if (lp) Vo += Vlp;
|
||||||
|
if (bp) Vo += Vbp;
|
||||||
|
if (hp) Vo += Vhp;
|
||||||
|
|
||||||
|
return currentGain[currentMixer[Vo]];
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
101
src/engine/platform/sound/c64_fp/Filter8580.cpp
Normal file
101
src/engine/platform/sound/c64_fp/Filter8580.cpp
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2019 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define FILTER8580_CPP
|
||||||
|
|
||||||
|
#include "Filter8580.h"
|
||||||
|
|
||||||
|
#include "Integrator8580.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* W/L ratio of frequency DAC bit 0,
|
||||||
|
* other bit are proportional.
|
||||||
|
* When no bit are selected a resistance with half
|
||||||
|
* W/L ratio is selected.
|
||||||
|
*/
|
||||||
|
const double DAC_WL0 = 0.00615;
|
||||||
|
|
||||||
|
Filter8580::~Filter8580() {}
|
||||||
|
|
||||||
|
void Filter8580::updatedCenterFrequency()
|
||||||
|
{
|
||||||
|
double wl;
|
||||||
|
double dacWL = DAC_WL0;
|
||||||
|
if (fc)
|
||||||
|
{
|
||||||
|
wl = 0.;
|
||||||
|
for (unsigned int i = 0; i < 11; i++)
|
||||||
|
{
|
||||||
|
if (fc & (1 << i))
|
||||||
|
{
|
||||||
|
wl += dacWL;
|
||||||
|
}
|
||||||
|
dacWL *= 2.;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wl = dacWL/2.;
|
||||||
|
}
|
||||||
|
|
||||||
|
hpIntegrator->setFc(wl);
|
||||||
|
bpIntegrator->setFc(wl);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Filter8580::updatedMixing()
|
||||||
|
{
|
||||||
|
currentGain = gain_vol[vol];
|
||||||
|
|
||||||
|
unsigned int ni = 0;
|
||||||
|
unsigned int no = 0;
|
||||||
|
|
||||||
|
(filt1 ? ni : no)++;
|
||||||
|
(filt2 ? ni : no)++;
|
||||||
|
|
||||||
|
if (filt3) ni++;
|
||||||
|
else if (!voice3off) no++;
|
||||||
|
|
||||||
|
(filtE ? ni : no)++;
|
||||||
|
|
||||||
|
currentSummer = summer[ni];
|
||||||
|
|
||||||
|
if (lp) no++;
|
||||||
|
if (bp) no++;
|
||||||
|
if (hp) no++;
|
||||||
|
|
||||||
|
currentMixer = mixer[no];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Filter8580::setFilterCurve(double curvePosition)
|
||||||
|
{
|
||||||
|
// Adjust cp
|
||||||
|
// 1.2 <= cp <= 1.8
|
||||||
|
cp = 1.8 - curvePosition * 3./5.;
|
||||||
|
|
||||||
|
hpIntegrator->setV(cp);
|
||||||
|
bpIntegrator->setV(cp);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
383
src/engine/platform/sound/c64_fp/Filter8580.h
Normal file
383
src/engine/platform/sound/c64_fp/Filter8580.h
Normal file
|
@ -0,0 +1,383 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FILTER8580_H
|
||||||
|
#define FILTER8580_H
|
||||||
|
|
||||||
|
#include "siddefs-fp.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "Filter.h"
|
||||||
|
#include "FilterModelConfig8580.h"
|
||||||
|
#include "Integrator8580.h"
|
||||||
|
|
||||||
|
#include "sidcxx11.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
class Integrator8580;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter for 8580 chip
|
||||||
|
* --------------------
|
||||||
|
* The 8580 filter stage had been redesigned to be more linear and robust
|
||||||
|
* against temperature change. It also features real op-amps and a
|
||||||
|
* revisited resonance model.
|
||||||
|
* The filter schematics below are reverse engineered from re-vectorized
|
||||||
|
* and annotated die photographs. Credits to Michael Huth for the microscope
|
||||||
|
* photographs of the die, Tommi Lempinen for re-vectorizating and annotating
|
||||||
|
* the images and ttlworks from forum.6502.org for the circuit analysis.
|
||||||
|
*
|
||||||
|
* ~~~
|
||||||
|
*
|
||||||
|
* +---------------------------------------------------+
|
||||||
|
* | $17 +----Rf-+ |
|
||||||
|
* | | | |
|
||||||
|
* | D4&!D5 o- \-R3-o |
|
||||||
|
* | | | $17 |
|
||||||
|
* | !D4&!D5 o- \-R2-o |
|
||||||
|
* | | | +---R8-- \--+ !D6&D7 |
|
||||||
|
* | D4&!D5 o- \-R1-o | | |
|
||||||
|
* | | | o---RC-- \--o D6&D7 |
|
||||||
|
* | +---------o--<A]--o--o | |
|
||||||
|
* | | o---R4-- \--o D6&!D7 |
|
||||||
|
* | | | | |
|
||||||
|
* | | +---Ri-- \--o !D6&!D7 |
|
||||||
|
* | | | |
|
||||||
|
* $17 | | (CAP2B) | (CAP1B) |
|
||||||
|
* 0=to mixer | +--R7--+ +---R7--+ +---C---o +---C---o
|
||||||
|
* 1=to filter | | | | | | | |
|
||||||
|
* +------R7--o--o--[A>--o--Rfc-o--[A>--o--Rfc-o--[A>--o
|
||||||
|
* ve (EXT IN) | | | |
|
||||||
|
* D3 \ --------------R12--o | | (CAP2A) | (CAP1A)
|
||||||
|
* | v3 | | vhp | vbp | vlp
|
||||||
|
* D2 | \ -----------R7--o +-----+ | |
|
||||||
|
* | | v2 | | | |
|
||||||
|
* D1 | | \ -------R7--o | +----------------+ |
|
||||||
|
* | | | v1 | | | |
|
||||||
|
* D0 | | | \ ---R7--+ | | +---------------------------+
|
||||||
|
* | | | | | | |
|
||||||
|
* R9 R5 R5 R5 R5 R5 R5
|
||||||
|
* | | | | $18 | | | $18
|
||||||
|
* | \ | | D7: 1=open \ \ \ D6 - D4: 0=open
|
||||||
|
* | | | | | | |
|
||||||
|
* +---o---o---o-------------o---o---+
|
||||||
|
* |
|
||||||
|
* | D3 +--/ --1R4--+
|
||||||
|
* | +---R8--+ | | +---R2--+
|
||||||
|
* | | | D2 o--/ --2R4--o | |
|
||||||
|
* +---o--[A>--o------o o--o--[A>--o-- vo (AUDIO OUT)
|
||||||
|
* D1 o--/ --4R4--o
|
||||||
|
* $18 | |
|
||||||
|
* 0=open D0 +--/ --8R4--+
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Resonance
|
||||||
|
* ---------
|
||||||
|
* For resonance, we have two tiny DACs that controls both the input
|
||||||
|
* and feedback resistances.
|
||||||
|
*
|
||||||
|
* The "resistors" are switched in as follows by bits in register $17:
|
||||||
|
*
|
||||||
|
* feedback:
|
||||||
|
* R1: bit4&!bit5
|
||||||
|
* R2: !bit4&bit5
|
||||||
|
* R3: bit4&bit5
|
||||||
|
* Rf: always on
|
||||||
|
*
|
||||||
|
* input:
|
||||||
|
* R4: bit6&!bit7
|
||||||
|
* R8: !bit6&bit7
|
||||||
|
* RC: bit6&bit7
|
||||||
|
* Ri: !(R4|R8|RC) = !(bit6|bit7) = !bit6&!bit7
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The relative "resistor" values are approximately (using channel length):
|
||||||
|
*
|
||||||
|
* R1 = 15.3*Ri
|
||||||
|
* R2 = 7.3*Ri
|
||||||
|
* R3 = 4.7*Ri
|
||||||
|
* Rf = 1.4*Ri
|
||||||
|
* R4 = 1.4*Ri
|
||||||
|
* R8 = 2.0*Ri
|
||||||
|
* RC = 2.8*Ri
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Approximate values for 1/Q can now be found as follows (assuming an
|
||||||
|
* ideal op-amp):
|
||||||
|
*
|
||||||
|
* res feedback input -gain (1/Q)
|
||||||
|
* --- -------- ----- ----------
|
||||||
|
* 0 Rf Ri Rf/Ri = 1/(Ri*(1/Rf)) = 1/0.71
|
||||||
|
* 1 Rf|R1 Ri (Rf|R1)/Ri = 1/(Ri*(1/Rf+1/R1)) = 1/0.78
|
||||||
|
* 2 Rf|R2 Ri (Rf|R2)/Ri = 1/(Ri*(1/Rf+1/R2)) = 1/0.85
|
||||||
|
* 3 Rf|R3 Ri (Rf|R3)/Ri = 1/(Ri*(1/Rf+1/R3)) = 1/0.92
|
||||||
|
* 4 Rf R4 Rf/R4 = 1/(R4*(1/Rf)) = 1/1.00
|
||||||
|
* 5 Rf|R1 R4 (Rf|R1)/R4 = 1/(R4*(1/Rf+1/R1)) = 1/1.10
|
||||||
|
* 6 Rf|R2 R4 (Rf|R2)/R4 = 1/(R4*(1/Rf+1/R2)) = 1/1.20
|
||||||
|
* 7 Rf|R3 R4 (Rf|R3)/R4 = 1/(R4*(1/Rf+1/R3)) = 1/1.30
|
||||||
|
* 8 Rf R8 Rf/R8 = 1/(R8*(1/Rf)) = 1/1.43
|
||||||
|
* 9 Rf|R1 R8 (Rf|R1)/R8 = 1/(R8*(1/Rf+1/R1)) = 1/1.56
|
||||||
|
* A Rf|R2 R8 (Rf|R2)/R8 = 1/(R8*(1/Rf+1/R2)) = 1/1.70
|
||||||
|
* B Rf|R3 R8 (Rf|R3)/R8 = 1/(R8*(1/Rf+1/R3)) = 1/1.86
|
||||||
|
* C Rf RC Rf/RC = 1/(RC*(1/Rf)) = 1/2.00
|
||||||
|
* D Rf|R1 RC (Rf|R1)/RC = 1/(RC*(1/Rf+1/R1)) = 1/2.18
|
||||||
|
* E Rf|R2 RC (Rf|R2)/RC = 1/(RC*(1/Rf+1/R2)) = 1/2.38
|
||||||
|
* F Rf|R3 RC (Rf|R3)/RC = 1/(RC*(1/Rf+1/R3)) = 1/2.60
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* These data indicate that the following function for 1/Q has been
|
||||||
|
* modeled in the MOS 8580:
|
||||||
|
*
|
||||||
|
* 1/Q = 2^(1/2)*2^(-x/8) = 2^(1/2 - x/8) = 2^((4 - x)/8)
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Op-amps
|
||||||
|
* -------
|
||||||
|
* Unlike the 6581, the 8580 has real OpAmps.
|
||||||
|
*
|
||||||
|
* Temperature compensated differential amplifier:
|
||||||
|
*
|
||||||
|
* 9V
|
||||||
|
*
|
||||||
|
* |
|
||||||
|
* +-------o-o-o-------+
|
||||||
|
* | | | |
|
||||||
|
* | R R |
|
||||||
|
* +--|| | | ||--+
|
||||||
|
* ||---o o---||
|
||||||
|
* +--|| | | ||--+
|
||||||
|
* | | | |
|
||||||
|
* o-----+ | | o--- Va
|
||||||
|
* | | | | |
|
||||||
|
* +--|| | | | ||--+
|
||||||
|
* ||-o-+---+---||
|
||||||
|
* +--|| | | ||--+
|
||||||
|
* | | | |
|
||||||
|
* | |
|
||||||
|
* GND | | GND
|
||||||
|
* ||--+ +--||
|
||||||
|
* in- -----|| ||------ in+
|
||||||
|
* ||----o----||
|
||||||
|
* |
|
||||||
|
* 8 Current sink
|
||||||
|
* |
|
||||||
|
*
|
||||||
|
* GND
|
||||||
|
*
|
||||||
|
* Inverter + non-inverting output amplifier:
|
||||||
|
*
|
||||||
|
* Va ---o---||-------------------o--------------------+
|
||||||
|
* | | 9V |
|
||||||
|
* | +----------+----------+ | |
|
||||||
|
* | 9V | | 9V | ||--+ |
|
||||||
|
* | | | 9V | | +-|| |
|
||||||
|
* | R | | | ||--+ ||--+ |
|
||||||
|
* | | | ||--+ +--|| o---o--- Vout
|
||||||
|
* | o---o---|| ||--+ ||--+
|
||||||
|
* | | ||--+ o-----||
|
||||||
|
* | ||--+ | ||--+ ||--+
|
||||||
|
* +-----|| o-----|| |
|
||||||
|
* ||--+ | ||--+
|
||||||
|
* | R | GND
|
||||||
|
* |
|
||||||
|
* GND GND
|
||||||
|
* GND
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Virtual ground
|
||||||
|
* --------------
|
||||||
|
* A PolySi resitive voltage divider provides the voltage
|
||||||
|
* for the positive input of the filter op-amps.
|
||||||
|
*
|
||||||
|
* 5V
|
||||||
|
* +----------+
|
||||||
|
* | | |\ |
|
||||||
|
* R1 +---|-\ |
|
||||||
|
* 5V | |A >---o--- Vref
|
||||||
|
* o-------|+/
|
||||||
|
* | | |/
|
||||||
|
* R10 R4
|
||||||
|
* | |
|
||||||
|
* o---+
|
||||||
|
* |
|
||||||
|
* R10
|
||||||
|
* |
|
||||||
|
*
|
||||||
|
* GND
|
||||||
|
*
|
||||||
|
* Rn = n*R1
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Rfc - freq control DAC resistance ladder
|
||||||
|
* ----------------------------------------
|
||||||
|
* The 8580 has 11 bits for frequency control, but 12 bit DACs.
|
||||||
|
* If those 11 bits would be '0', the impedance of the DACs would be "infinitely high".
|
||||||
|
* To get around this, there is an 11 input NOR gate below the DACs sensing those 11 bits.
|
||||||
|
* If all are 0, the NOR gate gives the gate control voltage to the 12 bit DAC LSB.
|
||||||
|
*
|
||||||
|
* ----o---o--...--o---o---o---
|
||||||
|
* | | | | |
|
||||||
|
* Rb10 Rb9 ... Rb1 Rb0 R0
|
||||||
|
* | | | | |
|
||||||
|
* ----o---o--...--o---o---o---
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Crystal stabilized precision switched capacitor voltage divider
|
||||||
|
* ---------------------------------------------------------------
|
||||||
|
* There is a FET working as a temperature sensor close to the DACs which changes the gate voltage
|
||||||
|
* of the frequency control DACs according to the temperature of the DACs,
|
||||||
|
* to reduce the effects of temperature on the filter curve.
|
||||||
|
* An asynchronous 3 bit binary counter, running at the speed of PHI2, drives two big capacitors
|
||||||
|
* whose AC resistance is then used as a voltage divider.
|
||||||
|
* This implicates that frequency difference between PAL and NTSC might shift the filter curve by 4% or such.
|
||||||
|
*
|
||||||
|
* |\ OpAmp has a smaller capacitor than the other OPs
|
||||||
|
* Vref ---|+\
|
||||||
|
* |A >---o--- Vdac
|
||||||
|
* +-------|-/ |
|
||||||
|
* | |/ |
|
||||||
|
* | |
|
||||||
|
* C1 | C2 |
|
||||||
|
* +---||---o---+ +---o-----||-------o
|
||||||
|
* | | | | | |
|
||||||
|
* o----+ | ----- | |
|
||||||
|
* | | | ----- +----+ +-----o
|
||||||
|
* | ----- | | | |
|
||||||
|
* | ----- | ----- |
|
||||||
|
* | | | ----- |
|
||||||
|
* | +-----------+ | |
|
||||||
|
* | /Q Q | +-------+
|
||||||
|
* GND +-----------+ FET close to DAC
|
||||||
|
* | clk/8 | working as temperature sensor
|
||||||
|
* +-----------+
|
||||||
|
*/
|
||||||
|
class Filter8580 final : public Filter
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
unsigned short** mixer;
|
||||||
|
unsigned short** summer;
|
||||||
|
unsigned short** gain_res;
|
||||||
|
unsigned short** gain_vol;
|
||||||
|
|
||||||
|
const int voiceScaleS11;
|
||||||
|
const int voiceDC;
|
||||||
|
|
||||||
|
double cp;
|
||||||
|
|
||||||
|
/// VCR + associated capacitor connected to highpass output.
|
||||||
|
std::unique_ptr<Integrator8580> const hpIntegrator;
|
||||||
|
|
||||||
|
/// VCR + associated capacitor connected to bandpass output.
|
||||||
|
std::unique_ptr<Integrator8580> const bpIntegrator;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Set filter cutoff frequency.
|
||||||
|
*/
|
||||||
|
void updatedCenterFrequency() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set filter resonance.
|
||||||
|
*
|
||||||
|
* @param res the new resonance value
|
||||||
|
*/
|
||||||
|
void updateResonance(unsigned char res) override { currentResonance = gain_res[res]; }
|
||||||
|
|
||||||
|
void updatedMixing() override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Filter8580() :
|
||||||
|
mixer(FilterModelConfig8580::getInstance()->getMixer()),
|
||||||
|
summer(FilterModelConfig8580::getInstance()->getSummer()),
|
||||||
|
gain_res(FilterModelConfig8580::getInstance()->getGainRes()),
|
||||||
|
gain_vol(FilterModelConfig8580::getInstance()->getGainVol()),
|
||||||
|
voiceScaleS11(FilterModelConfig8580::getInstance()->getVoiceScaleS11()),
|
||||||
|
voiceDC(FilterModelConfig8580::getInstance()->getNormalizedVoiceDC()),
|
||||||
|
cp(0.5),
|
||||||
|
hpIntegrator(FilterModelConfig8580::getInstance()->buildIntegrator()),
|
||||||
|
bpIntegrator(FilterModelConfig8580::getInstance()->buildIntegrator())
|
||||||
|
{
|
||||||
|
setFilterCurve(cp);
|
||||||
|
input(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
~Filter8580();
|
||||||
|
|
||||||
|
unsigned short clock(int voice1, int voice2, int voice3) override;
|
||||||
|
|
||||||
|
void input(int sample) override { ve = (sample * voiceScaleS11 * 3 >> 11) + mixer[0][0]; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set filter curve type based on single parameter.
|
||||||
|
*
|
||||||
|
* @param curvePosition 0 .. 1, where 0 sets center frequency high ("light") and 1 sets it low ("dark"), default is 0.5
|
||||||
|
*/
|
||||||
|
void setFilterCurve(double curvePosition);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#if RESID_INLINING || defined(FILTER8580_CPP)
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
RESID_INLINE
|
||||||
|
unsigned short Filter8580::clock(int voice1, int voice2, int voice3)
|
||||||
|
{
|
||||||
|
voice1 = (voice1 * voiceScaleS11 >> 15) + voiceDC;
|
||||||
|
voice2 = (voice2 * voiceScaleS11 >> 15) + voiceDC;
|
||||||
|
// Voice 3 is silenced by voice3off if it is not routed through the filter.
|
||||||
|
voice3 = (filt3 || !voice3off) ? (voice3 * voiceScaleS11 >> 15) + voiceDC : 0;
|
||||||
|
|
||||||
|
int Vi = 0;
|
||||||
|
int Vo = 0;
|
||||||
|
|
||||||
|
(filt1 ? Vi : Vo) += voice1;
|
||||||
|
(filt2 ? Vi : Vo) += voice2;
|
||||||
|
(filt3 ? Vi : Vo) += voice3;
|
||||||
|
(filtE ? Vi : Vo) += ve;
|
||||||
|
|
||||||
|
Vhp = currentSummer[currentResonance[Vbp] + Vlp + Vi];
|
||||||
|
Vbp = hpIntegrator->solve(Vhp);
|
||||||
|
Vlp = bpIntegrator->solve(Vbp);
|
||||||
|
|
||||||
|
if (lp) Vo += Vlp;
|
||||||
|
if (bp) Vo += Vbp;
|
||||||
|
if (hp) Vo += Vhp;
|
||||||
|
|
||||||
|
return currentGain[currentMixer[Vo]];
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
79
src/engine/platform/sound/c64_fp/FilterModelConfig.cpp
Normal file
79
src/engine/platform/sound/c64_fp/FilterModelConfig.cpp
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004,2010 Dag Lem
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "FilterModelConfig.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
FilterModelConfig::FilterModelConfig(
|
||||||
|
double vvr,
|
||||||
|
double vdv,
|
||||||
|
double c,
|
||||||
|
double vdd,
|
||||||
|
double vth,
|
||||||
|
double ucox,
|
||||||
|
const Spline::Point *opamp_voltage,
|
||||||
|
int opamp_size
|
||||||
|
) :
|
||||||
|
voice_voltage_range(vvr),
|
||||||
|
voice_DC_voltage(vdv),
|
||||||
|
C(c),
|
||||||
|
Vdd(vdd),
|
||||||
|
Vth(vth),
|
||||||
|
Ut(26.0e-3),
|
||||||
|
uCox(ucox),
|
||||||
|
Vddt(Vdd - Vth),
|
||||||
|
vmin(opamp_voltage[0].x),
|
||||||
|
vmax(std::max(Vddt, opamp_voltage[0].y)),
|
||||||
|
denorm(vmax - vmin),
|
||||||
|
norm(1.0 / denorm),
|
||||||
|
N16(norm * ((1 << 16) - 1)),
|
||||||
|
currFactorCoeff(denorm * (uCox / 2. * 1.0e-6 / C))
|
||||||
|
{
|
||||||
|
// Convert op-amp voltage transfer to 16 bit values.
|
||||||
|
|
||||||
|
std::vector<Spline::Point> scaled_voltage(opamp_size);
|
||||||
|
|
||||||
|
for (int i = 0; i < opamp_size; i++)
|
||||||
|
{
|
||||||
|
scaled_voltage[i].x = N16 * (opamp_voltage[i].x - opamp_voltage[i].y + denorm) / 2.;
|
||||||
|
scaled_voltage[i].y = N16 * (opamp_voltage[i].x - vmin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create lookup table mapping capacitor voltage to op-amp input voltage:
|
||||||
|
|
||||||
|
Spline s(scaled_voltage);
|
||||||
|
|
||||||
|
for (int x = 0; x < (1 << 16); x++)
|
||||||
|
{
|
||||||
|
const Spline::Point out = s.evaluate(x);
|
||||||
|
// If Vmax > max opamp_voltage the first elements may be negative
|
||||||
|
double tmp = out.x > 0. ? out.x : 0.;
|
||||||
|
assert(tmp < 65535.5);
|
||||||
|
opamp_rev[x] = static_cast<unsigned short>(tmp + 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
166
src/engine/platform/sound/c64_fp/FilterModelConfig.h
Normal file
166
src/engine/platform/sound/c64_fp/FilterModelConfig.h
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004,2010 Dag Lem
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FILTERMODELCONFIG_H
|
||||||
|
#define FILTERMODELCONFIG_H
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#include "Spline.h"
|
||||||
|
|
||||||
|
#include "sidcxx11.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
class FilterModelConfig
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
const double voice_voltage_range;
|
||||||
|
const double voice_DC_voltage;
|
||||||
|
|
||||||
|
/// Capacitor value.
|
||||||
|
const double C;
|
||||||
|
|
||||||
|
/// Transistor parameters.
|
||||||
|
//@{
|
||||||
|
const double Vdd;
|
||||||
|
const double Vth; ///< Threshold voltage
|
||||||
|
const double Ut; ///< Thermal voltage: Ut = kT/q = 8.61734315e-5*T ~ 26mV
|
||||||
|
const double uCox; ///< Transconductance coefficient: u*Cox
|
||||||
|
const double Vddt; ///< Vdd - Vth
|
||||||
|
//@}
|
||||||
|
|
||||||
|
// Derived stuff
|
||||||
|
const double vmin, vmax;
|
||||||
|
const double denorm, norm;
|
||||||
|
|
||||||
|
/// Fixed point scaling for 16 bit op-amp output.
|
||||||
|
const double N16;
|
||||||
|
|
||||||
|
/// Current factor coefficient for op-amp integrators.
|
||||||
|
const double currFactorCoeff;
|
||||||
|
|
||||||
|
/// Lookup tables for gain and summer op-amps in output stage / filter.
|
||||||
|
//@{
|
||||||
|
unsigned short* mixer[8]; //-V730_NOINIT this is initialized in the derived class constructor
|
||||||
|
unsigned short* summer[5]; //-V730_NOINIT this is initialized in the derived class constructor
|
||||||
|
unsigned short* gain_vol[16]; //-V730_NOINIT this is initialized in the derived class constructor
|
||||||
|
unsigned short* gain_res[16]; //-V730_NOINIT this is initialized in the derived class constructor
|
||||||
|
//@}
|
||||||
|
|
||||||
|
/// Reverse op-amp transfer function.
|
||||||
|
unsigned short opamp_rev[1 << 16]; //-V730_NOINIT this is initialized in the derived class constructor
|
||||||
|
|
||||||
|
private:
|
||||||
|
FilterModelConfig (const FilterModelConfig&) DELETE;
|
||||||
|
FilterModelConfig& operator= (const FilterModelConfig&) DELETE;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* @param vvr voice voltage range
|
||||||
|
* @param vdv voice DC voltage
|
||||||
|
* @param c capacitor value
|
||||||
|
* @param vdd Vdd
|
||||||
|
* @param vth threshold voltage
|
||||||
|
* @param ucox u*Cox
|
||||||
|
* @param ominv opamp min voltage
|
||||||
|
* @param omaxv opamp max voltage
|
||||||
|
*/
|
||||||
|
FilterModelConfig(
|
||||||
|
double vvr,
|
||||||
|
double vdv,
|
||||||
|
double c,
|
||||||
|
double vdd,
|
||||||
|
double vth,
|
||||||
|
double ucox,
|
||||||
|
const Spline::Point *opamp_voltage,
|
||||||
|
int opamp_size
|
||||||
|
);
|
||||||
|
|
||||||
|
~FilterModelConfig()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
delete [] mixer[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
delete [] summer[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
{
|
||||||
|
delete [] gain_vol[i];
|
||||||
|
delete [] gain_res[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
unsigned short** getGainVol() { return gain_vol; }
|
||||||
|
unsigned short** getGainRes() { return gain_res; }
|
||||||
|
unsigned short** getSummer() { return summer; }
|
||||||
|
unsigned short** getMixer() { return mixer; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The digital range of one voice is 20 bits; create a scaling term
|
||||||
|
* for multiplication which fits in 11 bits.
|
||||||
|
*/
|
||||||
|
int getVoiceScaleS11() const { return static_cast<int>((norm * ((1 << 11) - 1)) * voice_voltage_range); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "zero" output level of the voices.
|
||||||
|
*/
|
||||||
|
int getNormalizedVoiceDC() const { return static_cast<int>(N16 * (voice_DC_voltage - vmin)); }
|
||||||
|
|
||||||
|
inline unsigned short getOpampRev(int i) const { return opamp_rev[i]; }
|
||||||
|
inline double getVddt() const { return Vddt; }
|
||||||
|
inline double getVth() const { return Vth; }
|
||||||
|
inline double getVoiceDCVoltage() const { return voice_DC_voltage; }
|
||||||
|
|
||||||
|
// helper functions
|
||||||
|
inline unsigned short getNormalizedValue(double value) const
|
||||||
|
{
|
||||||
|
const double tmp = N16 * (value - vmin);
|
||||||
|
assert(tmp > -0.5 && tmp < 65535.5);
|
||||||
|
return static_cast<unsigned short>(tmp + 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned short getNormalizedCurrentFactor(double wl) const
|
||||||
|
{
|
||||||
|
const double tmp = (1 << 13) * currFactorCoeff * wl;
|
||||||
|
assert(tmp > -0.5 && tmp < 65535.5);
|
||||||
|
return static_cast<unsigned short>(tmp + 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned short getNVmin() const {
|
||||||
|
const double tmp = N16 * vmin;
|
||||||
|
assert(tmp > -0.5 && tmp < 65535.5);
|
||||||
|
return static_cast<unsigned short>(tmp + 0.5);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
263
src/engine/platform/sound/c64_fp/FilterModelConfig6581.cpp
Normal file
263
src/engine/platform/sound/c64_fp/FilterModelConfig6581.cpp
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2010 Dag Lem
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "FilterModelConfig6581.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "Integrator6581.h"
|
||||||
|
#include "OpAmp.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifndef HAVE_CXX11
|
||||||
|
/**
|
||||||
|
* Compute log(1+x) without losing precision for small values of x
|
||||||
|
*
|
||||||
|
* @note when compiling with -ffastm-math the compiler will
|
||||||
|
* optimize the expression away leaving a plain log(1. + x)
|
||||||
|
*/
|
||||||
|
inline double log1p(double x)
|
||||||
|
{
|
||||||
|
return log(1. + x) - (((1. + x) - 1.) - x) / (1. + x);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const unsigned int OPAMP_SIZE = 33;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the SID 6581 op-amp voltage transfer function, measured on
|
||||||
|
* CAP1B/CAP1A on a chip marked MOS 6581R4AR 0687 14.
|
||||||
|
* All measured chips have op-amps with output voltages (and thus input
|
||||||
|
* voltages) within the range of 0.81V - 10.31V.
|
||||||
|
*/
|
||||||
|
const Spline::Point opamp_voltage[OPAMP_SIZE] =
|
||||||
|
{
|
||||||
|
{ 0.81, 10.31 }, // Approximate start of actual range
|
||||||
|
{ 2.40, 10.31 },
|
||||||
|
{ 2.60, 10.30 },
|
||||||
|
{ 2.70, 10.29 },
|
||||||
|
{ 2.80, 10.26 },
|
||||||
|
{ 2.90, 10.17 },
|
||||||
|
{ 3.00, 10.04 },
|
||||||
|
{ 3.10, 9.83 },
|
||||||
|
{ 3.20, 9.58 },
|
||||||
|
{ 3.30, 9.32 },
|
||||||
|
{ 3.50, 8.69 },
|
||||||
|
{ 3.70, 8.00 },
|
||||||
|
{ 4.00, 6.89 },
|
||||||
|
{ 4.40, 5.21 },
|
||||||
|
{ 4.54, 4.54 }, // Working point (vi = vo)
|
||||||
|
{ 4.60, 4.19 },
|
||||||
|
{ 4.80, 3.00 },
|
||||||
|
{ 4.90, 2.30 }, // Change of curvature
|
||||||
|
{ 4.95, 2.03 },
|
||||||
|
{ 5.00, 1.88 },
|
||||||
|
{ 5.05, 1.77 },
|
||||||
|
{ 5.10, 1.69 },
|
||||||
|
{ 5.20, 1.58 },
|
||||||
|
{ 5.40, 1.44 },
|
||||||
|
{ 5.60, 1.33 },
|
||||||
|
{ 5.80, 1.26 },
|
||||||
|
{ 6.00, 1.21 },
|
||||||
|
{ 6.40, 1.12 },
|
||||||
|
{ 7.00, 1.02 },
|
||||||
|
{ 7.50, 0.97 },
|
||||||
|
{ 8.50, 0.89 },
|
||||||
|
{ 10.00, 0.81 },
|
||||||
|
{ 10.31, 0.81 }, // Approximate end of actual range
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<FilterModelConfig6581> FilterModelConfig6581::instance(nullptr);
|
||||||
|
|
||||||
|
FilterModelConfig6581* FilterModelConfig6581::getInstance()
|
||||||
|
{
|
||||||
|
if (!instance.get())
|
||||||
|
{
|
||||||
|
instance.reset(new FilterModelConfig6581());
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterModelConfig6581::FilterModelConfig6581() :
|
||||||
|
FilterModelConfig(
|
||||||
|
1.5, // voice voltage range
|
||||||
|
5.075, // voice DC voltage
|
||||||
|
470e-12, // capacitor value
|
||||||
|
12.18, // Vdd
|
||||||
|
1.31, // Vth
|
||||||
|
20e-6, // uCox
|
||||||
|
opamp_voltage,
|
||||||
|
OPAMP_SIZE
|
||||||
|
),
|
||||||
|
WL_vcr(9.0 / 1.0),
|
||||||
|
WL_snake(1.0 / 115.0),
|
||||||
|
dac_zero(6.65),
|
||||||
|
dac_scale(2.63),
|
||||||
|
dac(DAC_BITS)
|
||||||
|
{
|
||||||
|
dac.kinkedDac(MOS6581);
|
||||||
|
|
||||||
|
// Create lookup tables for gains / summers.
|
||||||
|
|
||||||
|
OpAmp opampModel(std::vector<Spline::Point>(std::begin(opamp_voltage), std::end(opamp_voltage)), Vddt);
|
||||||
|
|
||||||
|
// The filter summer operates at n ~ 1, and has 5 fundamentally different
|
||||||
|
// input configurations (2 - 6 input "resistors").
|
||||||
|
//
|
||||||
|
// Note that all "on" transistors are modeled as one. This is not
|
||||||
|
// entirely accurate, since the input for each transistor is different,
|
||||||
|
// and transistors are not linear components. However modeling all
|
||||||
|
// transistors separately would be extremely costly.
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
const int idiv = 2 + i; // 2 - 6 input "resistors".
|
||||||
|
const int size = idiv << 16;
|
||||||
|
const double n = idiv;
|
||||||
|
opampModel.reset();
|
||||||
|
summer[i] = new unsigned short[size];
|
||||||
|
|
||||||
|
for (int vi = 0; vi < size; vi++)
|
||||||
|
{
|
||||||
|
const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */
|
||||||
|
summer[i][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The audio mixer operates at n ~ 8/6, and has 8 fundamentally different
|
||||||
|
// input configurations (0 - 7 input "resistors").
|
||||||
|
//
|
||||||
|
// All "on", transistors are modeled as one - see comments above for
|
||||||
|
// the filter summer.
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
const int idiv = (i == 0) ? 1 : i;
|
||||||
|
const int size = (i == 0) ? 1 : i << 16;
|
||||||
|
const double n = i * 8.0 / 6.0;
|
||||||
|
opampModel.reset();
|
||||||
|
mixer[i] = new unsigned short[size];
|
||||||
|
|
||||||
|
for (int vi = 0; vi < size; vi++)
|
||||||
|
{
|
||||||
|
const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */
|
||||||
|
mixer[i][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4 bit "resistor" ladders in the audio
|
||||||
|
// output gain necessitate 16 gain tables.
|
||||||
|
// From die photographs of the bandpass and volume "resistor" ladders
|
||||||
|
// it follows that gain ~ vol/12 (assuming ideal
|
||||||
|
// op-amps and ideal "resistors").
|
||||||
|
for (int n8 = 0; n8 < 16; n8++)
|
||||||
|
{
|
||||||
|
const int size = 1 << 16;
|
||||||
|
const double n = n8 / 12.0;
|
||||||
|
opampModel.reset();
|
||||||
|
gain_vol[n8] = new unsigned short[size];
|
||||||
|
|
||||||
|
for (int vi = 0; vi < size; vi++)
|
||||||
|
{
|
||||||
|
const double vin = vmin + vi / N16; /* vmin .. vmax */
|
||||||
|
gain_vol[n8][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4 bit "resistor" ladders in the bandpass resonance gain
|
||||||
|
// necessitate 16 gain tables.
|
||||||
|
// From die photographs of the bandpass and volume "resistor" ladders
|
||||||
|
// it follows that 1/Q ~ ~res/8 (assuming ideal
|
||||||
|
// op-amps and ideal "resistors").
|
||||||
|
for (int n8 = 0; n8 < 16; n8++)
|
||||||
|
{
|
||||||
|
const int size = 1 << 16;
|
||||||
|
const double n = (~n8 & 0xf) / 8.0;
|
||||||
|
opampModel.reset();
|
||||||
|
gain_res[n8] = new unsigned short[size];
|
||||||
|
|
||||||
|
for (int vi = 0; vi < size; vi++)
|
||||||
|
{
|
||||||
|
const double vin = vmin + vi / N16; /* vmin .. vmax */
|
||||||
|
gain_res[n8][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const double nVddt = N16 * (Vddt - vmin);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < (1 << 16); i++)
|
||||||
|
{
|
||||||
|
// The table index is right-shifted 16 times in order to fit in
|
||||||
|
// 16 bits; the argument to sqrt is thus multiplied by (1 << 16).
|
||||||
|
const double tmp = nVddt - sqrt(static_cast<double>(i << 16));
|
||||||
|
assert(tmp > -0.5 && tmp < 65535.5);
|
||||||
|
vcr_nVg[i] = static_cast<unsigned short>(tmp + 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// EKV model:
|
||||||
|
//
|
||||||
|
// Ids = Is * (if - ir)
|
||||||
|
// Is = (2 * u*Cox * Ut^2)/k * W/L
|
||||||
|
// if = ln^2(1 + e^((k*(Vg - Vt) - Vs)/(2*Ut))
|
||||||
|
// ir = ln^2(1 + e^((k*(Vg - Vt) - Vd)/(2*Ut))
|
||||||
|
|
||||||
|
// moderate inversion characteristic current
|
||||||
|
const double Is = (2. * uCox * Ut * Ut) * WL_vcr;
|
||||||
|
|
||||||
|
// Normalized current factor for 1 cycle at 1MHz.
|
||||||
|
const double N15 = norm * ((1 << 15) - 1);
|
||||||
|
const double n_Is = N15 * 1.0e-6 / C * Is;
|
||||||
|
|
||||||
|
// kVgt_Vx = k*(Vg - Vt) - Vx
|
||||||
|
// I.e. if k != 1.0, Vg must be scaled accordingly.
|
||||||
|
for (int kVgt_Vx = 0; kVgt_Vx < (1 << 16); kVgt_Vx++)
|
||||||
|
{
|
||||||
|
const double log_term = log1p(exp((kVgt_Vx / N16) / (2. * Ut)));
|
||||||
|
// Scaled by m*2^15
|
||||||
|
const double tmp = n_Is * log_term * log_term;
|
||||||
|
assert(tmp > -0.5 && tmp < 65535.5);
|
||||||
|
vcr_n_Ids_term[kVgt_Vx] = static_cast<unsigned short>(tmp + 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned short* FilterModelConfig6581::getDAC(double adjustment) const
|
||||||
|
{
|
||||||
|
const double dac_zero = getDacZero(adjustment);
|
||||||
|
|
||||||
|
unsigned short* f0_dac = new unsigned short[1 << DAC_BITS];
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < (1 << DAC_BITS); i++)
|
||||||
|
{
|
||||||
|
const double fcd = dac.getOutput(i);
|
||||||
|
f0_dac[i] = getNormalizedValue(dac_zero + fcd * dac_scale / (1 << DAC_BITS));
|
||||||
|
}
|
||||||
|
|
||||||
|
return f0_dac;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Integrator6581> FilterModelConfig6581::buildIntegrator()
|
||||||
|
{
|
||||||
|
return MAKE_UNIQUE(Integrator6581, this, WL_snake);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
112
src/engine/platform/sound/c64_fp/FilterModelConfig6581.h
Normal file
112
src/engine/platform/sound/c64_fp/FilterModelConfig6581.h
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004,2010 Dag Lem
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FILTERMODELCONFIG6581_H
|
||||||
|
#define FILTERMODELCONFIG6581_H
|
||||||
|
|
||||||
|
#include "FilterModelConfig.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "Dac.h"
|
||||||
|
|
||||||
|
#include "sidcxx14.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
class Integrator6581;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate parameters for 6581 filter emulation.
|
||||||
|
*/
|
||||||
|
class FilterModelConfig6581 final : public FilterModelConfig
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static const unsigned int DAC_BITS = 11;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::unique_ptr<FilterModelConfig6581> instance;
|
||||||
|
// This allows access to the private constructor
|
||||||
|
#ifdef HAVE_CXX11
|
||||||
|
friend std::unique_ptr<FilterModelConfig6581>::deleter_type;
|
||||||
|
#else
|
||||||
|
friend class std::auto_ptr<FilterModelConfig6581>;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Transistor parameters.
|
||||||
|
//@{
|
||||||
|
const double WL_vcr; ///< W/L for VCR
|
||||||
|
const double WL_snake; ///< W/L for "snake"
|
||||||
|
//@}
|
||||||
|
|
||||||
|
/// DAC parameters.
|
||||||
|
//@{
|
||||||
|
const double dac_zero;
|
||||||
|
const double dac_scale;
|
||||||
|
//@}
|
||||||
|
|
||||||
|
/// DAC lookup table
|
||||||
|
Dac dac;
|
||||||
|
|
||||||
|
/// VCR - 6581 only.
|
||||||
|
//@{
|
||||||
|
unsigned short vcr_nVg[1 << 16];
|
||||||
|
unsigned short vcr_n_Ids_term[1 << 16];
|
||||||
|
//@}
|
||||||
|
|
||||||
|
private:
|
||||||
|
double getDacZero(double adjustment) const { return dac_zero + (1. - adjustment); }
|
||||||
|
|
||||||
|
FilterModelConfig6581();
|
||||||
|
~FilterModelConfig6581() DEFAULT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static FilterModelConfig6581* getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an 11 bit cutoff frequency DAC output voltage table.
|
||||||
|
* Ownership is transferred to the requester which becomes responsible
|
||||||
|
* of freeing the object when done.
|
||||||
|
*
|
||||||
|
* @param adjustment
|
||||||
|
* @return the DAC table
|
||||||
|
*/
|
||||||
|
unsigned short* getDAC(double adjustment) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an integrator solver.
|
||||||
|
*
|
||||||
|
* @return the integrator
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Integrator6581> buildIntegrator();
|
||||||
|
|
||||||
|
inline unsigned short getVcr_nVg(int i) const { return vcr_nVg[i]; }
|
||||||
|
inline unsigned short getVcr_n_Ids_term(int i) const { return vcr_n_Ids_term[i]; }
|
||||||
|
// only used if SLOPE_FACTOR is defined
|
||||||
|
inline double getUt() const { return Ut; }
|
||||||
|
inline double getN16() const { return N16; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
222
src/engine/platform/sound/c64_fp/FilterModelConfig8580.cpp
Normal file
222
src/engine/platform/sound/c64_fp/FilterModelConfig8580.cpp
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2010 Dag Lem
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "FilterModelConfig8580.h"
|
||||||
|
|
||||||
|
#include "Integrator8580.h"
|
||||||
|
#include "OpAmp.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/*
|
||||||
|
* R1 = 15.3*Ri
|
||||||
|
* R2 = 7.3*Ri
|
||||||
|
* R3 = 4.7*Ri
|
||||||
|
* Rf = 1.4*Ri
|
||||||
|
* R4 = 1.4*Ri
|
||||||
|
* R8 = 2.0*Ri
|
||||||
|
* RC = 2.8*Ri
|
||||||
|
*
|
||||||
|
* res feedback input
|
||||||
|
* --- -------- -----
|
||||||
|
* 0 Rf Ri
|
||||||
|
* 1 Rf|R1 Ri
|
||||||
|
* 2 Rf|R2 Ri
|
||||||
|
* 3 Rf|R3 Ri
|
||||||
|
* 4 Rf R4
|
||||||
|
* 5 Rf|R1 R4
|
||||||
|
* 6 Rf|R2 R4
|
||||||
|
* 7 Rf|R3 R4
|
||||||
|
* 8 Rf R8
|
||||||
|
* 9 Rf|R1 R8
|
||||||
|
* A Rf|R2 R8
|
||||||
|
* B Rf|R3 R8
|
||||||
|
* C Rf RC
|
||||||
|
* D Rf|R1 RC
|
||||||
|
* E Rf|R2 RC
|
||||||
|
* F Rf|R3 RC
|
||||||
|
*/
|
||||||
|
const double resGain[16] =
|
||||||
|
{
|
||||||
|
1.4/1.0, // Rf/Ri 1.4
|
||||||
|
((1.4*15.3)/(1.4+15.3))/1.0, // (Rf|R1)/Ri 1.28263
|
||||||
|
((1.4*7.3)/(1.4+7.3))/1.0, // (Rf|R2)/Ri 1.17471
|
||||||
|
((1.4*4.7)/(1.4+4.7))/1.0, // (Rf|R3)/Ri 1.07869
|
||||||
|
1.4/1.4, // Rf/R4 1
|
||||||
|
((1.4*15.3)/(1.4+15.3))/1.4, // (Rf|R1)/R4 0.916168
|
||||||
|
((1.4*7.3)/(1.4+7.3))/1.4, // (Rf|R2)/R4 0.83908
|
||||||
|
((1.4*4.7)/(1.4+4.7))/1.4, // (Rf|R3)/R4 0.770492
|
||||||
|
1.4/2.0, // Rf/R8 0.7
|
||||||
|
((1.4*15.3)/(1.4+15.3))/2.0, // (Rf|R1)/R8 0.641317
|
||||||
|
((1.4*7.3)/(1.4+7.3))/2.0, // (Rf|R2)/R8 0.587356
|
||||||
|
((1.4*4.7)/(1.4+4.7))/2.0, // (Rf|R3)/R8 0.539344
|
||||||
|
1.4/2.8, // Rf/RC 0.5
|
||||||
|
((1.4*15.3)/(1.4+15.3))/2.8, // (Rf|R1)/RC 0.458084
|
||||||
|
((1.4*7.3)/(1.4+7.3))/2.8, // (Rf|R2)/RC 0.41954
|
||||||
|
((1.4*4.7)/(1.4+4.7))/2.8, // (Rf|R3)/RC 0.385246
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsigned int OPAMP_SIZE = 21;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the SID 8580 op-amp voltage transfer function, measured on
|
||||||
|
* CAP1B/CAP1A on a chip marked CSG 8580R5 1690 25.
|
||||||
|
*/
|
||||||
|
const Spline::Point opamp_voltage[OPAMP_SIZE] =
|
||||||
|
{
|
||||||
|
{ 1.30, 8.91 }, // Approximate start of actual range
|
||||||
|
{ 4.76, 8.91 },
|
||||||
|
{ 4.77, 8.90 },
|
||||||
|
{ 4.78, 8.88 },
|
||||||
|
{ 4.785, 8.86 },
|
||||||
|
{ 4.79, 8.80 },
|
||||||
|
{ 4.795, 8.60 },
|
||||||
|
{ 4.80, 8.25 },
|
||||||
|
{ 4.805, 7.50 },
|
||||||
|
{ 4.81, 6.10 },
|
||||||
|
{ 4.815, 4.05 }, // Change of curvature
|
||||||
|
{ 4.82, 2.27 },
|
||||||
|
{ 4.825, 1.65 },
|
||||||
|
{ 4.83, 1.55 },
|
||||||
|
{ 4.84, 1.47 },
|
||||||
|
{ 4.85, 1.43 },
|
||||||
|
{ 4.87, 1.37 },
|
||||||
|
{ 4.90, 1.34 },
|
||||||
|
{ 5.00, 1.30 },
|
||||||
|
{ 5.10, 1.30 },
|
||||||
|
{ 8.91, 1.30 }, // Approximate end of actual range
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<FilterModelConfig8580> FilterModelConfig8580::instance(nullptr);
|
||||||
|
|
||||||
|
FilterModelConfig8580* FilterModelConfig8580::getInstance()
|
||||||
|
{
|
||||||
|
if (!instance.get())
|
||||||
|
{
|
||||||
|
instance.reset(new FilterModelConfig8580());
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterModelConfig8580::FilterModelConfig8580() :
|
||||||
|
FilterModelConfig(
|
||||||
|
0.25, // voice voltage range FIXME measure
|
||||||
|
4.80, // voice DC voltage FIXME was 4.76
|
||||||
|
22e-9, // capacitor value
|
||||||
|
9.09, // Vdd
|
||||||
|
0.80, // Vth
|
||||||
|
100e-6, // uCox
|
||||||
|
opamp_voltage,
|
||||||
|
OPAMP_SIZE
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// Create lookup tables for gains / summers.
|
||||||
|
|
||||||
|
OpAmp opampModel(std::vector<Spline::Point>(std::begin(opamp_voltage), std::end(opamp_voltage)), Vddt);
|
||||||
|
|
||||||
|
// The filter summer operates at n ~ 1, and has 5 fundamentally different
|
||||||
|
// input configurations (2 - 6 input "resistors").
|
||||||
|
//
|
||||||
|
// Note that all "on" transistors are modeled as one. This is not
|
||||||
|
// entirely accurate, since the input for each transistor is different,
|
||||||
|
// and transistors are not linear components. However modeling all
|
||||||
|
// transistors separately would be extremely costly.
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
const int idiv = 2 + i; // 2 - 6 input "resistors".
|
||||||
|
const int size = idiv << 16;
|
||||||
|
const double n = idiv;
|
||||||
|
opampModel.reset();
|
||||||
|
summer[i] = new unsigned short[size];
|
||||||
|
|
||||||
|
for (int vi = 0; vi < size; vi++)
|
||||||
|
{
|
||||||
|
const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */
|
||||||
|
summer[i][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The audio mixer operates at n ~ 8/5, and has 8 fundamentally different
|
||||||
|
// input configurations (0 - 7 input "resistors").
|
||||||
|
//
|
||||||
|
// All "on", transistors are modeled as one - see comments above for
|
||||||
|
// the filter summer.
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
const int idiv = (i == 0) ? 1 : i;
|
||||||
|
const int size = (i == 0) ? 1 : i << 16;
|
||||||
|
const double n = i * 8.0 / 5.0;
|
||||||
|
opampModel.reset();
|
||||||
|
mixer[i] = new unsigned short[size];
|
||||||
|
|
||||||
|
for (int vi = 0; vi < size; vi++)
|
||||||
|
{
|
||||||
|
const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */
|
||||||
|
mixer[i][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4 bit "resistor" ladders in the audio output gain
|
||||||
|
// necessitate 16 gain tables.
|
||||||
|
// From die photographs of the volume "resistor" ladders
|
||||||
|
// it follows that gain ~ vol/16 (assuming ideal op-amps
|
||||||
|
for (int n8 = 0; n8 < 16; n8++)
|
||||||
|
{
|
||||||
|
const int size = 1 << 16;
|
||||||
|
const double n = n8 / 16.0;
|
||||||
|
opampModel.reset();
|
||||||
|
gain_vol[n8] = new unsigned short[size];
|
||||||
|
|
||||||
|
for (int vi = 0; vi < size; vi++)
|
||||||
|
{
|
||||||
|
const double vin = vmin + vi / N16; /* vmin .. vmax */
|
||||||
|
gain_vol[n8][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4 bit "resistor" ladders in the bandpass resonance gain
|
||||||
|
// necessitate 16 gain tables.
|
||||||
|
// From die photographs of the bandpass and volume "resistor" ladders
|
||||||
|
// it follows that 1/Q ~ 2^((4 - res)/8) (assuming ideal
|
||||||
|
// op-amps and ideal "resistors").
|
||||||
|
for (int n8 = 0; n8 < 16; n8++)
|
||||||
|
{
|
||||||
|
const int size = 1 << 16;
|
||||||
|
opampModel.reset();
|
||||||
|
gain_res[n8] = new unsigned short[size];
|
||||||
|
|
||||||
|
for (int vi = 0; vi < size; vi++)
|
||||||
|
{
|
||||||
|
const double vin = vmin + vi / N16; /* vmin .. vmax */
|
||||||
|
gain_res[n8][vi] = getNormalizedValue(opampModel.solve(resGain[n8], vin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Integrator8580> FilterModelConfig8580::buildIntegrator()
|
||||||
|
{
|
||||||
|
return MAKE_UNIQUE(Integrator8580, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
68
src/engine/platform/sound/c64_fp/FilterModelConfig8580.h
Normal file
68
src/engine/platform/sound/c64_fp/FilterModelConfig8580.h
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004,2010 Dag Lem
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FILTERMODELCONFIG8580_H
|
||||||
|
#define FILTERMODELCONFIG8580_H
|
||||||
|
|
||||||
|
#include "FilterModelConfig.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "sidcxx14.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
class Integrator8580;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate parameters for 8580 filter emulation.
|
||||||
|
*/
|
||||||
|
class FilterModelConfig8580 final : public FilterModelConfig
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static std::unique_ptr<FilterModelConfig8580> instance;
|
||||||
|
// This allows access to the private constructor
|
||||||
|
#ifdef HAVE_CXX11
|
||||||
|
friend std::unique_ptr<FilterModelConfig8580>::deleter_type;
|
||||||
|
#else
|
||||||
|
friend class std::auto_ptr<FilterModelConfig8580>;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
FilterModelConfig8580();
|
||||||
|
~FilterModelConfig8580() DEFAULT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static FilterModelConfig8580* getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an integrator solver.
|
||||||
|
*
|
||||||
|
* @return the integrator
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Integrator8580> buildIntegrator();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
25
src/engine/platform/sound/c64_fp/Integrator6581.cpp
Normal file
25
src/engine/platform/sound/c64_fp/Integrator6581.cpp
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2014 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define INTEGRATOR_CPP
|
||||||
|
|
||||||
|
#include "Integrator6581.h"
|
||||||
|
|
||||||
|
// This is needed when compiling with --disable-inline
|
285
src/engine/platform/sound/c64_fp/Integrator6581.h
Normal file
285
src/engine/platform/sound/c64_fp/Integrator6581.h
Normal file
|
@ -0,0 +1,285 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004, 2010 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef INTEGRATOR6581_H
|
||||||
|
#define INTEGRATOR6581_H
|
||||||
|
|
||||||
|
#include "FilterModelConfig6581.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
// uncomment to enable use of the slope factor
|
||||||
|
// in the EKV model
|
||||||
|
// actually produces worse results, needs investigation
|
||||||
|
//#define SLOPE_FACTOR
|
||||||
|
|
||||||
|
#ifdef SLOPE_FACTOR
|
||||||
|
# include <cmath>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "siddefs-fp.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find output voltage in inverting integrator SID op-amp circuits, using a
|
||||||
|
* single fixpoint iteration step.
|
||||||
|
*
|
||||||
|
* A circuit diagram of a MOS 6581 integrator is shown below.
|
||||||
|
*
|
||||||
|
* +---C---+
|
||||||
|
* | |
|
||||||
|
* vi --o--Rw--o-o--[A>--o-- vo
|
||||||
|
* | | vx
|
||||||
|
* +--Rs--+
|
||||||
|
*
|
||||||
|
* From Kirchoff's current law it follows that
|
||||||
|
*
|
||||||
|
* IRw + IRs + ICr = 0
|
||||||
|
*
|
||||||
|
* Using the formula for current through a capacitor, i = C*dv/dt, we get
|
||||||
|
*
|
||||||
|
* IRw + IRs + C*(vc - vc0)/dt = 0
|
||||||
|
* dt/C*(IRw + IRs) + vc - vc0 = 0
|
||||||
|
* vc = vc0 - n*(IRw(vi,vx) + IRs(vi,vx))
|
||||||
|
*
|
||||||
|
* which may be rewritten as the following iterative fixpoint function:
|
||||||
|
*
|
||||||
|
* vc = vc0 - n*(IRw(vi,g(vc)) + IRs(vi,g(vc)))
|
||||||
|
*
|
||||||
|
* To accurately calculate the currents through Rs and Rw, we need to use
|
||||||
|
* transistor models. Rs has a gate voltage of Vdd = 12V, and can be
|
||||||
|
* assumed to always be in triode mode. For Rw, the situation is rather
|
||||||
|
* more complex, as it turns out that this transistor will operate in
|
||||||
|
* both subthreshold, triode, and saturation modes.
|
||||||
|
*
|
||||||
|
* The Shichman-Hodges transistor model routinely used in textbooks may
|
||||||
|
* be written as follows:
|
||||||
|
*
|
||||||
|
* Ids = 0 , Vgst < 0 (subthreshold mode)
|
||||||
|
* Ids = K*W/L*(2*Vgst - Vds)*Vds , Vgst >= 0, Vds < Vgst (triode mode)
|
||||||
|
* Ids = K*W/L*Vgst^2 , Vgst >= 0, Vds >= Vgst (saturation mode)
|
||||||
|
*
|
||||||
|
* where
|
||||||
|
* K = u*Cox/2 (transconductance coefficient)
|
||||||
|
* W/L = ratio between substrate width and length
|
||||||
|
* Vgst = Vg - Vs - Vt (overdrive voltage)
|
||||||
|
*
|
||||||
|
* This transistor model is also called the quadratic model.
|
||||||
|
*
|
||||||
|
* Note that the equation for the triode mode can be reformulated as
|
||||||
|
* independent terms depending on Vgs and Vgd, respectively, by the
|
||||||
|
* following substitution:
|
||||||
|
*
|
||||||
|
* Vds = Vgst - (Vgst - Vds) = Vgst - Vgdt
|
||||||
|
*
|
||||||
|
* Ids = K*W/L*(2*Vgst - Vds)*Vds
|
||||||
|
* = K*W/L*(2*Vgst - (Vgst - Vgdt)*(Vgst - Vgdt)
|
||||||
|
* = K*W/L*(Vgst + Vgdt)*(Vgst - Vgdt)
|
||||||
|
* = K*W/L*(Vgst^2 - Vgdt^2)
|
||||||
|
*
|
||||||
|
* This turns out to be a general equation which covers both the triode
|
||||||
|
* and saturation modes (where the second term is 0 in saturation mode).
|
||||||
|
* The equation is also symmetrical, i.e. it can calculate negative
|
||||||
|
* currents without any change of parameters (since the terms for drain
|
||||||
|
* and source are identical except for the sign).
|
||||||
|
*
|
||||||
|
* FIXME: Subthreshold as function of Vgs, Vgd.
|
||||||
|
*
|
||||||
|
* Ids = I0*W/L*e^(Vgst/(Ut/k)) , Vgst < 0 (subthreshold mode)
|
||||||
|
*
|
||||||
|
* where
|
||||||
|
* I0 = (2 * uCox * Ut^2) / k
|
||||||
|
*
|
||||||
|
* The remaining problem with the textbook model is that the transition
|
||||||
|
* from subthreshold the triode/saturation is not continuous.
|
||||||
|
*
|
||||||
|
* Realizing that the subthreshold and triode/saturation modes may both
|
||||||
|
* be defined by independent (and equal) terms of Vgs and Vds,
|
||||||
|
* respectively, the corresponding terms can be blended into (equal)
|
||||||
|
* continuous functions suitable for table lookup.
|
||||||
|
*
|
||||||
|
* The EKV model (Enz, Krummenacher and Vittoz) essentially performs this
|
||||||
|
* blending using an elegant mathematical formulation:
|
||||||
|
*
|
||||||
|
* Ids = Is * (if - ir)
|
||||||
|
* Is = ((2 * u*Cox * Ut^2)/k) * W/L
|
||||||
|
* if = ln^2(1 + e^((k*(Vg - Vt) - Vs)/(2*Ut))
|
||||||
|
* ir = ln^2(1 + e^((k*(Vg - Vt) - Vd)/(2*Ut))
|
||||||
|
*
|
||||||
|
* For our purposes, the EKV model preserves two important properties
|
||||||
|
* discussed above:
|
||||||
|
*
|
||||||
|
* - It consists of two independent terms, which can be represented by
|
||||||
|
* the same lookup table.
|
||||||
|
* - It is symmetrical, i.e. it calculates current in both directions,
|
||||||
|
* facilitating a branch-free implementation.
|
||||||
|
*
|
||||||
|
* Rw in the circuit diagram above is a VCR (voltage controlled resistor),
|
||||||
|
* as shown in the circuit diagram below.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Vdd
|
||||||
|
* |
|
||||||
|
* Vdd _|_
|
||||||
|
* | +---+ +---- Vw
|
||||||
|
* _|_ |
|
||||||
|
* +--+ +---o Vg
|
||||||
|
* | __|__
|
||||||
|
* | ----- Rw
|
||||||
|
* | | |
|
||||||
|
* vi -----o------+ +-------- vo
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* In order to calculalate the current through the VCR, its gate voltage
|
||||||
|
* must be determined.
|
||||||
|
*
|
||||||
|
* Assuming triode mode and applying Kirchoff's current law, we get the
|
||||||
|
* following equation for Vg:
|
||||||
|
*
|
||||||
|
* u*Cox/2*W/L*((nVddt - Vg)^2 - (nVddt - vi)^2 + (nVddt - Vg)^2 - (nVddt - Vw)^2) = 0
|
||||||
|
* 2*(nVddt - Vg)^2 - (nVddt - vi)^2 - (nVddt - Vw)^2 = 0
|
||||||
|
* (nVddt - Vg) = sqrt(((nVddt - vi)^2 + (nVddt - Vw)^2)/2)
|
||||||
|
*
|
||||||
|
* Vg = nVddt - sqrt(((nVddt - vi)^2 + (nVddt - Vw)^2)/2)
|
||||||
|
*/
|
||||||
|
class Integrator6581
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
unsigned int nVddt_Vw_2;
|
||||||
|
mutable int vx;
|
||||||
|
mutable int vc;
|
||||||
|
|
||||||
|
#ifdef SLOPE_FACTOR
|
||||||
|
// Slope factor n = 1/k
|
||||||
|
// where k is the gate coupling coefficient
|
||||||
|
// k = Cox/(Cox+Cdep) ~ 0.7 (depends on gate voltage)
|
||||||
|
mutable double n;
|
||||||
|
#endif
|
||||||
|
const unsigned short nVddt;
|
||||||
|
const unsigned short nVt;
|
||||||
|
const unsigned short nVmin;
|
||||||
|
const unsigned short nSnake;
|
||||||
|
|
||||||
|
const FilterModelConfig6581* fmc;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Integrator6581(const FilterModelConfig6581* fmc,
|
||||||
|
double WL_snake) :
|
||||||
|
nVddt_Vw_2(0),
|
||||||
|
vx(0),
|
||||||
|
vc(0),
|
||||||
|
#ifdef SLOPE_FACTOR
|
||||||
|
n(1.4),
|
||||||
|
#endif
|
||||||
|
nVddt(fmc->getNormalizedValue(fmc->getVddt())),
|
||||||
|
nVt(fmc->getNormalizedValue(fmc->getVth())),
|
||||||
|
nVmin(fmc->getNVmin()),
|
||||||
|
nSnake(fmc->getNormalizedCurrentFactor(WL_snake)),
|
||||||
|
fmc(fmc) {}
|
||||||
|
|
||||||
|
void setVw(unsigned short Vw) { nVddt_Vw_2 = ((nVddt - Vw) * (nVddt - Vw)) >> 1; }
|
||||||
|
|
||||||
|
int solve(int vi) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#if RESID_INLINING || defined(INTEGRATOR_CPP)
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
RESID_INLINE
|
||||||
|
int Integrator6581::solve(int vi) const
|
||||||
|
{
|
||||||
|
// Make sure Vgst>0 so we're not in subthreshold mode
|
||||||
|
assert(vx < nVddt);
|
||||||
|
|
||||||
|
// Check that transistor is actually in triode mode
|
||||||
|
// Vds < Vgs - Vth
|
||||||
|
assert(vi < nVddt);
|
||||||
|
|
||||||
|
// "Snake" voltages for triode mode calculation.
|
||||||
|
const unsigned int Vgst = nVddt - vx;
|
||||||
|
const unsigned int Vgdt = nVddt - vi;
|
||||||
|
|
||||||
|
const unsigned int Vgst_2 = Vgst * Vgst;
|
||||||
|
const unsigned int Vgdt_2 = Vgdt * Vgdt;
|
||||||
|
|
||||||
|
// "Snake" current, scaled by (1/m)*2^13*m*2^16*m*2^16*2^-15 = m*2^30
|
||||||
|
const int n_I_snake = nSnake * (static_cast<int>(Vgst_2 - Vgdt_2) >> 15);
|
||||||
|
|
||||||
|
// VCR gate voltage. // Scaled by m*2^16
|
||||||
|
// Vg = Vddt - sqrt(((Vddt - Vw)^2 + Vgdt^2)/2)
|
||||||
|
const int nVg = static_cast<int>(fmc->getVcr_nVg((nVddt_Vw_2 + (Vgdt_2 >> 1)) >> 16));
|
||||||
|
#ifdef SLOPE_FACTOR
|
||||||
|
const double nVp = static_cast<double>(nVg - nVt) / n; // Pinch-off voltage
|
||||||
|
const int kVg = static_cast<int>(nVp + 0.5) - nVmin;
|
||||||
|
#else
|
||||||
|
const int kVg = (nVg - nVt) - nVmin;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// VCR voltages for EKV model table lookup.
|
||||||
|
const int kVgt_Vs = (vx < kVg) ? kVg - vx : 0;
|
||||||
|
assert(kVgt_Vs < (1 << 16));
|
||||||
|
const int kVgt_Vd = (vi < kVg) ? kVg - vi : 0;
|
||||||
|
assert(kVgt_Vd < (1 << 16));
|
||||||
|
|
||||||
|
// VCR current, scaled by m*2^15*2^15 = m*2^30
|
||||||
|
const unsigned int If = static_cast<unsigned int>(fmc->getVcr_n_Ids_term(kVgt_Vs)) << 15;
|
||||||
|
const unsigned int Ir = static_cast<unsigned int>(fmc->getVcr_n_Ids_term(kVgt_Vd)) << 15;
|
||||||
|
#ifdef SLOPE_FACTOR
|
||||||
|
const double iVcr = static_cast<double>(If - Ir);
|
||||||
|
const int n_I_vcr = static_cast<int>((iVcr * n) + 0.5);
|
||||||
|
#else
|
||||||
|
const int n_I_vcr = If - Ir;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef SLOPE_FACTOR
|
||||||
|
// estimate new slope factor based on gate voltage
|
||||||
|
const double gamma = 1.0; // body effect factor
|
||||||
|
const double phi = 0.8; // bulk Fermi potential
|
||||||
|
const double Vp = nVp / fmc->getN16();
|
||||||
|
n = 1. + (gamma / (2. * sqrt(Vp + phi + 4. * fmc->getUt())));
|
||||||
|
assert((n > 1.2) && (n < 1.8));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Change in capacitor charge.
|
||||||
|
vc += n_I_snake + n_I_vcr;
|
||||||
|
|
||||||
|
// vx = g(vc)
|
||||||
|
const int tmp = (vc >> 15) + (1 << 15);
|
||||||
|
assert(tmp < (1 << 16));
|
||||||
|
vx = fmc->getOpampRev(tmp);
|
||||||
|
|
||||||
|
// Return vo.
|
||||||
|
return vx - (vc >> 14);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
25
src/engine/platform/sound/c64_fp/Integrator8580.cpp
Normal file
25
src/engine/platform/sound/c64_fp/Integrator8580.cpp
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2014-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define INTEGRATOR8580_CPP
|
||||||
|
|
||||||
|
#include "Integrator8580.h"
|
||||||
|
|
||||||
|
// This is needed when compiling with --disable-inline
|
142
src/engine/platform/sound/c64_fp/Integrator8580.h
Normal file
142
src/engine/platform/sound/c64_fp/Integrator8580.h
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004, 2010 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef INTEGRATOR8580_H
|
||||||
|
#define INTEGRATOR8580_H
|
||||||
|
|
||||||
|
#include "FilterModelConfig8580.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#include "siddefs-fp.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 8580 integrator
|
||||||
|
*
|
||||||
|
* +---C---+
|
||||||
|
* | |
|
||||||
|
* vi -----Rfc---o--[A>--o-- vo
|
||||||
|
* vx
|
||||||
|
*
|
||||||
|
* IRfc + ICr = 0
|
||||||
|
* IRfc + C*(vc - vc0)/dt = 0
|
||||||
|
* dt/C*(IRfc) + vc - vc0 = 0
|
||||||
|
* vc = vc0 - n*(IRfc(vi,vx))
|
||||||
|
* vc = vc0 - n*(IRfc(vi,g(vc)))
|
||||||
|
*
|
||||||
|
* IRfc = K*W/L*(Vgst^2 - Vgdt^2) = n*((Vddt - vx)^2 - (Vddt - vi)^2)
|
||||||
|
*
|
||||||
|
* Rfc gate voltage is generated by an OP Amp and depends on chip temperature.
|
||||||
|
*/
|
||||||
|
class Integrator8580
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
mutable int vx;
|
||||||
|
mutable int vc;
|
||||||
|
|
||||||
|
unsigned short nVgt;
|
||||||
|
unsigned short n_dac;
|
||||||
|
|
||||||
|
const FilterModelConfig8580* fmc;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Integrator8580(const FilterModelConfig8580* fmc) :
|
||||||
|
vx(0),
|
||||||
|
vc(0),
|
||||||
|
fmc(fmc)
|
||||||
|
{
|
||||||
|
setV(1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Filter Cutoff resistor ratio.
|
||||||
|
*/
|
||||||
|
void setFc(double wl)
|
||||||
|
{
|
||||||
|
// Normalized current factor, 1 cycle at 1MHz.
|
||||||
|
// Fit in 5 bits.
|
||||||
|
n_dac = fmc->getNormalizedCurrentFactor(wl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set FC gate voltage multiplier.
|
||||||
|
*/
|
||||||
|
void setV(double v)
|
||||||
|
{
|
||||||
|
// Gate voltage is controlled by the switched capacitor voltage divider
|
||||||
|
// Ua = Ue * v = 4.76v 1<v<2
|
||||||
|
assert(v > 1.0 && v < 2.0);
|
||||||
|
const double Vg = fmc->getVoiceDCVoltage() * v;
|
||||||
|
const double Vgt = Vg - fmc->getVth();
|
||||||
|
|
||||||
|
// Vg - Vth, normalized so that translated values can be subtracted:
|
||||||
|
// Vgt - x = (Vgt - t) - (x - t)
|
||||||
|
nVgt = fmc->getNormalizedValue(Vgt);
|
||||||
|
}
|
||||||
|
|
||||||
|
int solve(int vi) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#if RESID_INLINING || defined(INTEGRATOR8580_CPP)
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
RESID_INLINE
|
||||||
|
int Integrator8580::solve(int vi) const
|
||||||
|
{
|
||||||
|
// Make sure we're not in subthreshold mode
|
||||||
|
assert(vx < nVgt);
|
||||||
|
|
||||||
|
// DAC voltages
|
||||||
|
const unsigned int Vgst = nVgt - vx;
|
||||||
|
const unsigned int Vgdt = (vi < nVgt) ? nVgt - vi : 0; // triode/saturation mode
|
||||||
|
|
||||||
|
const unsigned int Vgst_2 = Vgst * Vgst;
|
||||||
|
const unsigned int Vgdt_2 = Vgdt * Vgdt;
|
||||||
|
|
||||||
|
// DAC current, scaled by (1/m)*2^13*m*2^16*m*2^16*2^-15 = m*2^30
|
||||||
|
const int n_I_dac = n_dac * (static_cast<int>(Vgst_2 - Vgdt_2) >> 15);
|
||||||
|
|
||||||
|
// Change in capacitor charge.
|
||||||
|
vc += n_I_dac;
|
||||||
|
|
||||||
|
// vx = g(vc)
|
||||||
|
const int tmp = (vc >> 15) + (1 << 15);
|
||||||
|
assert(tmp < (1 << 16));
|
||||||
|
vx = fmc->getOpampRev(tmp);
|
||||||
|
|
||||||
|
// Return vo.
|
||||||
|
return vx - (vc >> 14);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
84
src/engine/platform/sound/c64_fp/OpAmp.cpp
Normal file
84
src/engine/platform/sound/c64_fp/OpAmp.cpp
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2015 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "OpAmp.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "siddefs-fp.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
const double EPSILON = 1e-8;
|
||||||
|
|
||||||
|
double OpAmp::solve(double n, double vi) const
|
||||||
|
{
|
||||||
|
// Start off with an estimate of x and a root bracket [ak, bk].
|
||||||
|
// f is decreasing, so that f(ak) > 0 and f(bk) < 0.
|
||||||
|
double ak = vmin;
|
||||||
|
double bk = vmax;
|
||||||
|
|
||||||
|
const double a = n + 1.;
|
||||||
|
const double b = Vddt;
|
||||||
|
const double b_vi = (b > vi) ? (b - vi) : 0.;
|
||||||
|
const double c = n * (b_vi * b_vi);
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
const double xk = x;
|
||||||
|
|
||||||
|
// Calculate f and df.
|
||||||
|
|
||||||
|
Spline::Point out = opamp->evaluate(x);
|
||||||
|
const double vo = out.x;
|
||||||
|
const double dvo = out.y;
|
||||||
|
|
||||||
|
const double b_vx = (b > x) ? b - x : 0.;
|
||||||
|
const double b_vo = (b > vo) ? b - vo : 0.;
|
||||||
|
|
||||||
|
// f = a*(b - vx)^2 - c - (b - vo)^2
|
||||||
|
const double f = a * (b_vx * b_vx) - c - (b_vo * b_vo);
|
||||||
|
|
||||||
|
// df = 2*((b - vo)*dvo - a*(b - vx))
|
||||||
|
const double df = 2. * (b_vo * dvo - a * b_vx);
|
||||||
|
|
||||||
|
// Newton-Raphson step: xk1 = xk - f(xk)/f'(xk)
|
||||||
|
x -= f / df;
|
||||||
|
|
||||||
|
if (unlikely(fabs(x - xk) < EPSILON))
|
||||||
|
{
|
||||||
|
out = opamp->evaluate(x);
|
||||||
|
return out.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Narrow down root bracket.
|
||||||
|
(f < 0. ? bk : ak) = xk;
|
||||||
|
|
||||||
|
if (unlikely(x <= ak) || unlikely(x >= bk))
|
||||||
|
{
|
||||||
|
// Bisection step (ala Dekker's method).
|
||||||
|
x = (ak + bk) * 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
113
src/engine/platform/sound/c64_fp/OpAmp.h
Normal file
113
src/engine/platform/sound/c64_fp/OpAmp.h
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2015 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004,2010 Dag Lem
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef OPAMP_H
|
||||||
|
#define OPAMP_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Spline.h"
|
||||||
|
|
||||||
|
#include "sidcxx11.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find output voltage in inverting gain and inverting summer SID op-amp
|
||||||
|
* circuits, using a combination of Newton-Raphson and bisection.
|
||||||
|
*
|
||||||
|
* +---R2--+
|
||||||
|
* | |
|
||||||
|
* vi ---R1--o--[A>--o-- vo
|
||||||
|
* vx
|
||||||
|
*
|
||||||
|
* From Kirchoff's current law it follows that
|
||||||
|
*
|
||||||
|
* IR1f + IR2r = 0
|
||||||
|
*
|
||||||
|
* Substituting the triode mode transistor model K*W/L*(Vgst^2 - Vgdt^2)
|
||||||
|
* for the currents, we get:
|
||||||
|
*
|
||||||
|
* n*((Vddt - vx)^2 - (Vddt - vi)^2) + (Vddt - vx)^2 - (Vddt - vo)^2 = 0
|
||||||
|
*
|
||||||
|
* Our root function f can thus be written as:
|
||||||
|
*
|
||||||
|
* f = (n + 1)*(Vddt - vx)^2 - n*(Vddt - vi)^2 - (Vddt - vo)^2 = 0
|
||||||
|
*
|
||||||
|
* Using substitution constants
|
||||||
|
*
|
||||||
|
* a = n + 1
|
||||||
|
* b = Vddt
|
||||||
|
* c = n*(Vddt - vi)^2
|
||||||
|
*
|
||||||
|
* the equations for the root function and its derivative can be written as:
|
||||||
|
*
|
||||||
|
* f = a*(b - vx)^2 - c - (b - vo)^2
|
||||||
|
* df = 2*((b - vo)*dvo - a*(b - vx))
|
||||||
|
*/
|
||||||
|
class OpAmp
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
/// Current root position (cached as guess to speed up next iteration)
|
||||||
|
mutable double x;
|
||||||
|
|
||||||
|
const double Vddt;
|
||||||
|
const double vmin;
|
||||||
|
const double vmax;
|
||||||
|
|
||||||
|
std::unique_ptr<Spline> const opamp;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Opamp input -> output voltage conversion
|
||||||
|
*
|
||||||
|
* @param opamp opamp mapping table as pairs of points (in -> out)
|
||||||
|
* @param opamplength length of the opamp array
|
||||||
|
* @param kVddt transistor dt parameter (in volts)
|
||||||
|
*/
|
||||||
|
OpAmp(const std::vector<Spline::Point> &opamp, double Vddt) :
|
||||||
|
x(0.),
|
||||||
|
Vddt(Vddt),
|
||||||
|
vmin(opamp.front().x),
|
||||||
|
vmax(opamp.back().x),
|
||||||
|
opamp(new Spline(opamp)) {}
|
||||||
|
|
||||||
|
void reset() const
|
||||||
|
{
|
||||||
|
x = vmin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Solve the opamp equation for input vi in loading context n
|
||||||
|
*
|
||||||
|
* @param n the ratio of input/output loading
|
||||||
|
* @param vi input
|
||||||
|
* @return vo
|
||||||
|
*/
|
||||||
|
double solve(double n, double vi) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
50
src/engine/platform/sound/c64_fp/Potentiometer.h
Normal file
50
src/engine/platform/sound/c64_fp/Potentiometer.h
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2013 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright (C) 2004 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef POTENTIOMETER_H
|
||||||
|
#define POTENTIOMETER_H
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Potentiometer representation.
|
||||||
|
*
|
||||||
|
* This class will probably never be implemented in any real way.
|
||||||
|
*
|
||||||
|
* @author Ken Händel
|
||||||
|
* @author Dag Lem
|
||||||
|
*/
|
||||||
|
class Potentiometer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Read paddle value. Not modeled.
|
||||||
|
*
|
||||||
|
* @return paddle value (always 0xff)
|
||||||
|
*/
|
||||||
|
unsigned char readPOT() const { return 0xff; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
20
src/engine/platform/sound/c64_fp/README
Normal file
20
src/engine/platform/sound/c64_fp/README
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
reSIDfp is a fork of Dag Lem's reSID 0.16, a reverse engineered software emulation
|
||||||
|
of the MOS6581/8580 SID (Sound Interface Device).
|
||||||
|
|
||||||
|
The project was started by Antti S. Lankila in order to improve SID emulation
|
||||||
|
with special focus on the 6581 filter.
|
||||||
|
The codebase has been later on ported to java by Ken Händel within the jsidplay2 project
|
||||||
|
and has seen further work by Antti Lankila.
|
||||||
|
It was then ported back to c++ and integrated with improvements from reSID 1.0 by Leandro Nini.
|
||||||
|
|
||||||
|
|
||||||
|
Main differences from reSID:
|
||||||
|
|
||||||
|
* combined waveforms are emulated by a parametrized model based on samplings from Kevtris;
|
||||||
|
* envelope generator is implemented like in the real machine with a shift register;
|
||||||
|
* high quality resampling is done in two steps to allow computational savings using lower order filters;
|
||||||
|
* part of the calculations are done with floats instead of fixed point;
|
||||||
|
* interpolation is accomplished with Fritsch-Carlson method to preserve monotonicity.
|
||||||
|
|
||||||
|
|
||||||
|
reSIDfp is free software. See the file COPYING for copying permission.
|
504
src/engine/platform/sound/c64_fp/SID.cpp
Normal file
504
src/engine/platform/sound/c64_fp/SID.cpp
Normal file
|
@ -0,0 +1,504 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define SID_CPP
|
||||||
|
|
||||||
|
#include "SID.h"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include "array.h"
|
||||||
|
#include "Dac.h"
|
||||||
|
#include "Filter6581.h"
|
||||||
|
#include "Filter8580.h"
|
||||||
|
#include "Potentiometer.h"
|
||||||
|
#include "WaveformCalculator.h"
|
||||||
|
#include "resample/TwoPassSincResampler.h"
|
||||||
|
#include "resample/ZeroOrderResampler.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
const unsigned int ENV_DAC_BITS = 8;
|
||||||
|
const unsigned int OSC_DAC_BITS = 12;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The waveform D/A converter introduces a DC offset in the signal
|
||||||
|
* to the envelope multiplying D/A converter. The "zero" level of
|
||||||
|
* the waveform D/A converter can be found as follows:
|
||||||
|
*
|
||||||
|
* Measure the "zero" voltage of voice 3 on the SID audio output
|
||||||
|
* pin, routing only voice 3 to the mixer ($d417 = $0b, $d418 =
|
||||||
|
* $0f, all other registers zeroed).
|
||||||
|
*
|
||||||
|
* Then set the sustain level for voice 3 to maximum and search for
|
||||||
|
* the waveform output value yielding the same voltage as found
|
||||||
|
* above. This is done by trying out different waveform output
|
||||||
|
* values until the correct value is found, e.g. with the following
|
||||||
|
* program:
|
||||||
|
*
|
||||||
|
* lda #$08
|
||||||
|
* sta $d412
|
||||||
|
* lda #$0b
|
||||||
|
* sta $d417
|
||||||
|
* lda #$0f
|
||||||
|
* sta $d418
|
||||||
|
* lda #$f0
|
||||||
|
* sta $d414
|
||||||
|
* lda #$21
|
||||||
|
* sta $d412
|
||||||
|
* lda #$01
|
||||||
|
* sta $d40e
|
||||||
|
*
|
||||||
|
* ldx #$00
|
||||||
|
* lda #$38 ; Tweak this to find the "zero" level
|
||||||
|
*l cmp $d41b
|
||||||
|
* bne l
|
||||||
|
* stx $d40e ; Stop frequency counter - freeze waveform output
|
||||||
|
* brk
|
||||||
|
*
|
||||||
|
* The waveform output range is 0x000 to 0xfff, so the "zero"
|
||||||
|
* level should ideally have been 0x800. In the measured chip, the
|
||||||
|
* waveform output "zero" level was found to be 0x380 (i.e. $d41b
|
||||||
|
* = 0x38) at an audio output voltage of 5.94V.
|
||||||
|
*
|
||||||
|
* With knowledge of the mixer op-amp characteristics, further estimates
|
||||||
|
* of waveform voltages can be obtained by sampling the EXT IN pin.
|
||||||
|
* From EXT IN samples, the corresponding waveform output can be found by
|
||||||
|
* using the model for the mixer.
|
||||||
|
*
|
||||||
|
* Such measurements have been done on a chip marked MOS 6581R4AR
|
||||||
|
* 0687 14, and the following results have been obtained:
|
||||||
|
* * The full range of one voice is approximately 1.5V.
|
||||||
|
* * The "zero" level rides at approximately 5.0V.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* zero-x did the measuring on the 8580 (https://sourceforge.net/p/vice-emu/bugs/1036/#c5b3):
|
||||||
|
* When it sits on basic from powerup it's at 4.72
|
||||||
|
* Run 1.prg and check the output pin level.
|
||||||
|
* Then run 2.prg andadjust it until the output level is the same...
|
||||||
|
* 0x94-0xA8 gives me the same 4.72 1.prg shows.
|
||||||
|
* On another 8580 it's 0x90-0x9C
|
||||||
|
* Third chip 0x94-0xA8
|
||||||
|
* Fourth chip 0x90-0xA4
|
||||||
|
* On the 8580 that plays digis the output is 4.66 and 0x93 is the only value to reach that.
|
||||||
|
* To me that seems as regular 8580s have somewhat wide 0-level range,
|
||||||
|
* whereas that digi-compatible 8580 has it very narrow.
|
||||||
|
* On my 6581R4AR has 0x3A as the only value giving the same output level as 1.prg
|
||||||
|
*/
|
||||||
|
//@{
|
||||||
|
unsigned int constexpr OFFSET_6581 = 0x380;
|
||||||
|
unsigned int constexpr OFFSET_8580 = 0x9c0;
|
||||||
|
//@}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bus value stays alive for some time after each operation.
|
||||||
|
* Values differs between chip models, the timings used here
|
||||||
|
* are taken from VICE [1].
|
||||||
|
* See also the discussion "How do I reliably detect 6581/8580 sid?" on CSDb [2].
|
||||||
|
*
|
||||||
|
* Results from real C64 (testprogs/SID/bitfade/delayfrq0.prg):
|
||||||
|
*
|
||||||
|
* (new SID) (250469/8580R5) (250469/8580R5)
|
||||||
|
* delayfrq0 ~7a000 ~108000
|
||||||
|
*
|
||||||
|
* (old SID) (250407/6581)
|
||||||
|
* delayfrq0 ~01d00
|
||||||
|
*
|
||||||
|
* [1]: http://sourceforge.net/p/vice-emu/patches/99/
|
||||||
|
* [2]: http://noname.c64.org/csdb/forums/?roomid=11&topicid=29025&showallposts=1
|
||||||
|
*/
|
||||||
|
//@{
|
||||||
|
int constexpr BUS_TTL_6581 = 0x01d00;
|
||||||
|
int constexpr BUS_TTL_8580 = 0xa2000;
|
||||||
|
//@}
|
||||||
|
|
||||||
|
SID::SID() :
|
||||||
|
filter6581(new Filter6581()),
|
||||||
|
filter8580(new Filter8580()),
|
||||||
|
externalFilter(new ExternalFilter()),
|
||||||
|
resampler(nullptr),
|
||||||
|
potX(new Potentiometer()),
|
||||||
|
potY(new Potentiometer())
|
||||||
|
{
|
||||||
|
voice[0].reset(new Voice());
|
||||||
|
voice[1].reset(new Voice());
|
||||||
|
voice[2].reset(new Voice());
|
||||||
|
|
||||||
|
muted[0] = muted[1] = muted[2] = false;
|
||||||
|
|
||||||
|
reset();
|
||||||
|
setChipModel(MOS8580);
|
||||||
|
}
|
||||||
|
|
||||||
|
SID::~SID()
|
||||||
|
{
|
||||||
|
// Needed to delete auto_ptr with complete type
|
||||||
|
}
|
||||||
|
|
||||||
|
void SID::setFilter6581Curve(double filterCurve)
|
||||||
|
{
|
||||||
|
filter6581->setFilterCurve(filterCurve);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SID::setFilter8580Curve(double filterCurve)
|
||||||
|
{
|
||||||
|
filter8580->setFilterCurve(filterCurve);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SID::enableFilter(bool enable)
|
||||||
|
{
|
||||||
|
filter6581->enable(enable);
|
||||||
|
filter8580->enable(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SID::voiceSync(bool sync)
|
||||||
|
{
|
||||||
|
if (sync)
|
||||||
|
{
|
||||||
|
// Synchronize the 3 waveform generators.
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
voice[i]->wave()->synchronize(voice[(i + 1) % 3]->wave(), voice[(i + 2) % 3]->wave());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the time to next voice sync
|
||||||
|
nextVoiceSync = std::numeric_limits<int>::max();
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
WaveformGenerator* const wave = voice[i]->wave();
|
||||||
|
const unsigned int freq = wave->readFreq();
|
||||||
|
|
||||||
|
if (wave->readTest() || freq == 0 || !voice[(i + 1) % 3]->wave()->readSync())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned int accumulator = wave->readAccumulator();
|
||||||
|
const unsigned int thisVoiceSync = ((0x7fffff - accumulator) & 0xffffff) / freq + 1;
|
||||||
|
|
||||||
|
if (thisVoiceSync < nextVoiceSync)
|
||||||
|
{
|
||||||
|
nextVoiceSync = thisVoiceSync;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SID::setChipModel(ChipModel model)
|
||||||
|
{
|
||||||
|
switch (model)
|
||||||
|
{
|
||||||
|
case MOS6581:
|
||||||
|
filter = filter6581.get();
|
||||||
|
modelTTL = BUS_TTL_6581;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MOS8580:
|
||||||
|
filter = filter8580.get();
|
||||||
|
modelTTL = BUS_TTL_8580;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw SIDError("Unknown chip type");
|
||||||
|
}
|
||||||
|
|
||||||
|
this->model = model;
|
||||||
|
|
||||||
|
// calculate waveform-related tables
|
||||||
|
matrix_t* tables = WaveformCalculator::getInstance()->buildTable(model);
|
||||||
|
|
||||||
|
// calculate envelope DAC table
|
||||||
|
{
|
||||||
|
Dac dacBuilder(ENV_DAC_BITS);
|
||||||
|
dacBuilder.kinkedDac(model);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < (1 << ENV_DAC_BITS); i++)
|
||||||
|
{
|
||||||
|
envDAC[i] = static_cast<float>(dacBuilder.getOutput(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate oscillator DAC table
|
||||||
|
const bool is6581 = model == MOS6581;
|
||||||
|
|
||||||
|
{
|
||||||
|
Dac dacBuilder(OSC_DAC_BITS);
|
||||||
|
dacBuilder.kinkedDac(model);
|
||||||
|
|
||||||
|
const double offset = dacBuilder.getOutput(is6581 ? OFFSET_6581 : OFFSET_8580);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < (1 << OSC_DAC_BITS); i++)
|
||||||
|
{
|
||||||
|
const double dacValue = dacBuilder.getOutput(i);
|
||||||
|
oscDAC[i] = static_cast<float>(dacValue - offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set voice tables
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
voice[i]->setEnvDAC(envDAC);
|
||||||
|
voice[i]->setWavDAC(oscDAC);
|
||||||
|
voice[i]->wave()->setModel(is6581);
|
||||||
|
voice[i]->wave()->setWaveformModels(tables);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SID::reset()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
voice[i]->reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
filter6581->reset();
|
||||||
|
filter8580->reset();
|
||||||
|
externalFilter->reset();
|
||||||
|
|
||||||
|
if (resampler.get())
|
||||||
|
{
|
||||||
|
resampler->reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
busValue = 0;
|
||||||
|
busValueTtl = 0;
|
||||||
|
voiceSync(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SID::input(int value)
|
||||||
|
{
|
||||||
|
filter6581->input(value);
|
||||||
|
filter8580->input(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char SID::read(int offset)
|
||||||
|
{
|
||||||
|
switch (offset)
|
||||||
|
{
|
||||||
|
case 0x19: // X value of paddle
|
||||||
|
busValue = potX->readPOT();
|
||||||
|
busValueTtl = modelTTL;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x1a: // Y value of paddle
|
||||||
|
busValue = potY->readPOT();
|
||||||
|
busValueTtl = modelTTL;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x1b: // Voice #3 waveform output
|
||||||
|
busValue = voice[2]->wave()->readOSC();
|
||||||
|
busValueTtl = modelTTL;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x1c: // Voice #3 ADSR output
|
||||||
|
busValue = voice[2]->envelope()->readENV();
|
||||||
|
busValueTtl = modelTTL;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Reading from a write-only or non-existing register
|
||||||
|
// makes the bus discharge faster.
|
||||||
|
// Emulate this by halving the residual TTL.
|
||||||
|
busValueTtl /= 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return busValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SID::write(int offset, unsigned char value)
|
||||||
|
{
|
||||||
|
busValue = value;
|
||||||
|
busValueTtl = modelTTL;
|
||||||
|
|
||||||
|
switch (offset)
|
||||||
|
{
|
||||||
|
case 0x00: // Voice #1 frequency (Low-byte)
|
||||||
|
voice[0]->wave()->writeFREQ_LO(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x01: // Voice #1 frequency (High-byte)
|
||||||
|
voice[0]->wave()->writeFREQ_HI(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x02: // Voice #1 pulse width (Low-byte)
|
||||||
|
voice[0]->wave()->writePW_LO(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x03: // Voice #1 pulse width (bits #8-#15)
|
||||||
|
voice[0]->wave()->writePW_HI(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x04: // Voice #1 control register
|
||||||
|
voice[0]->writeCONTROL_REG(muted[0] ? 0 : value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x05: // Voice #1 Attack and Decay length
|
||||||
|
voice[0]->envelope()->writeATTACK_DECAY(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x06: // Voice #1 Sustain volume and Release length
|
||||||
|
voice[0]->envelope()->writeSUSTAIN_RELEASE(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x07: // Voice #2 frequency (Low-byte)
|
||||||
|
voice[1]->wave()->writeFREQ_LO(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x08: // Voice #2 frequency (High-byte)
|
||||||
|
voice[1]->wave()->writeFREQ_HI(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x09: // Voice #2 pulse width (Low-byte)
|
||||||
|
voice[1]->wave()->writePW_LO(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x0a: // Voice #2 pulse width (bits #8-#15)
|
||||||
|
voice[1]->wave()->writePW_HI(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x0b: // Voice #2 control register
|
||||||
|
voice[1]->writeCONTROL_REG(muted[1] ? 0 : value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x0c: // Voice #2 Attack and Decay length
|
||||||
|
voice[1]->envelope()->writeATTACK_DECAY(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x0d: // Voice #2 Sustain volume and Release length
|
||||||
|
voice[1]->envelope()->writeSUSTAIN_RELEASE(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x0e: // Voice #3 frequency (Low-byte)
|
||||||
|
voice[2]->wave()->writeFREQ_LO(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x0f: // Voice #3 frequency (High-byte)
|
||||||
|
voice[2]->wave()->writeFREQ_HI(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x10: // Voice #3 pulse width (Low-byte)
|
||||||
|
voice[2]->wave()->writePW_LO(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x11: // Voice #3 pulse width (bits #8-#15)
|
||||||
|
voice[2]->wave()->writePW_HI(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x12: // Voice #3 control register
|
||||||
|
voice[2]->writeCONTROL_REG(muted[2] ? 0 : value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x13: // Voice #3 Attack and Decay length
|
||||||
|
voice[2]->envelope()->writeATTACK_DECAY(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x14: // Voice #3 Sustain volume and Release length
|
||||||
|
voice[2]->envelope()->writeSUSTAIN_RELEASE(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x15: // Filter cut off frequency (bits #0-#2)
|
||||||
|
filter6581->writeFC_LO(value);
|
||||||
|
filter8580->writeFC_LO(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x16: // Filter cut off frequency (bits #3-#10)
|
||||||
|
filter6581->writeFC_HI(value);
|
||||||
|
filter8580->writeFC_HI(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x17: // Filter control
|
||||||
|
filter6581->writeRES_FILT(value);
|
||||||
|
filter8580->writeRES_FILT(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x18: // Volume and filter modes
|
||||||
|
filter6581->writeMODE_VOL(value);
|
||||||
|
filter8580->writeMODE_VOL(value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update voicesync just in case.
|
||||||
|
voiceSync(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SID::setSamplingParameters(double clockFrequency, SamplingMethod method, double samplingFrequency, double highestAccurateFrequency)
|
||||||
|
{
|
||||||
|
externalFilter->setClockFrequency(clockFrequency);
|
||||||
|
|
||||||
|
switch (method)
|
||||||
|
{
|
||||||
|
case DECIMATE:
|
||||||
|
resampler.reset(new ZeroOrderResampler(clockFrequency, samplingFrequency));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RESAMPLE:
|
||||||
|
resampler.reset(TwoPassSincResampler::create(clockFrequency, samplingFrequency, highestAccurateFrequency));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw SIDError("Unknown sampling method");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SID::clockSilent(unsigned int cycles)
|
||||||
|
{
|
||||||
|
ageBusValue(cycles);
|
||||||
|
|
||||||
|
while (cycles != 0)
|
||||||
|
{
|
||||||
|
int delta_t = std::min(nextVoiceSync, cycles);
|
||||||
|
|
||||||
|
if (delta_t > 0)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < delta_t; i++)
|
||||||
|
{
|
||||||
|
// clock waveform generators (can affect OSC3)
|
||||||
|
voice[0]->wave()->clock();
|
||||||
|
voice[1]->wave()->clock();
|
||||||
|
voice[2]->wave()->clock();
|
||||||
|
|
||||||
|
voice[0]->wave()->output(voice[2]->wave());
|
||||||
|
voice[1]->wave()->output(voice[0]->wave());
|
||||||
|
voice[2]->wave()->output(voice[1]->wave());
|
||||||
|
|
||||||
|
// clock ENV3 only
|
||||||
|
voice[2]->envelope()->clock();
|
||||||
|
}
|
||||||
|
|
||||||
|
cycles -= delta_t;
|
||||||
|
nextVoiceSync -= delta_t;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextVoiceSync == 0)
|
||||||
|
{
|
||||||
|
voiceSync(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
372
src/engine/platform/sound/c64_fp/SID.h
Normal file
372
src/engine/platform/sound/c64_fp/SID.h
Normal file
|
@ -0,0 +1,372 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SIDFP_H
|
||||||
|
#define SIDFP_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "siddefs-fp.h"
|
||||||
|
|
||||||
|
#include "sidcxx11.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
class Filter;
|
||||||
|
class Filter6581;
|
||||||
|
class Filter8580;
|
||||||
|
class ExternalFilter;
|
||||||
|
class Potentiometer;
|
||||||
|
class Voice;
|
||||||
|
class Resampler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SID error exception.
|
||||||
|
*/
|
||||||
|
class SIDError
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
const char* message;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SIDError(const char* msg) :
|
||||||
|
message(msg) {}
|
||||||
|
const char* getMessage() const { return message; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MOS6581/MOS8580 emulation.
|
||||||
|
*/
|
||||||
|
class SID
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
/// Currently active filter
|
||||||
|
Filter* filter;
|
||||||
|
|
||||||
|
/// Filter used, if model is set to 6581
|
||||||
|
std::unique_ptr<Filter6581> const filter6581;
|
||||||
|
|
||||||
|
/// Filter used, if model is set to 8580
|
||||||
|
std::unique_ptr<Filter8580> const filter8580;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* External filter that provides high-pass and low-pass filtering
|
||||||
|
* to adjust sound tone slightly.
|
||||||
|
*/
|
||||||
|
std::unique_ptr<ExternalFilter> const externalFilter;
|
||||||
|
|
||||||
|
/// Resampler used by audio generation code.
|
||||||
|
std::unique_ptr<Resampler> resampler;
|
||||||
|
|
||||||
|
/// Paddle X register support
|
||||||
|
std::unique_ptr<Potentiometer> const potX;
|
||||||
|
|
||||||
|
/// Paddle Y register support
|
||||||
|
std::unique_ptr<Potentiometer> const potY;
|
||||||
|
|
||||||
|
/// SID voices
|
||||||
|
std::unique_ptr<Voice> voice[3];
|
||||||
|
|
||||||
|
/// Time to live for the last written value
|
||||||
|
int busValueTtl;
|
||||||
|
|
||||||
|
/// Current chip model's bus value TTL
|
||||||
|
int modelTTL;
|
||||||
|
|
||||||
|
/// Time until #voiceSync must be run.
|
||||||
|
unsigned int nextVoiceSync;
|
||||||
|
|
||||||
|
/// Currently active chip model.
|
||||||
|
ChipModel model;
|
||||||
|
|
||||||
|
/// Last written value
|
||||||
|
unsigned char busValue;
|
||||||
|
|
||||||
|
/// Flags for muted channels
|
||||||
|
bool muted[3];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulated nonlinearity of the envelope DAC.
|
||||||
|
*
|
||||||
|
* @See Dac
|
||||||
|
*/
|
||||||
|
float envDAC[256];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulated nonlinearity of the oscillator DAC.
|
||||||
|
*
|
||||||
|
* @See Dac
|
||||||
|
*/
|
||||||
|
float oscDAC[4096];
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Age the bus value and zero it if it's TTL has expired.
|
||||||
|
*
|
||||||
|
* @param n the number of cycles
|
||||||
|
*/
|
||||||
|
void ageBusValue(unsigned int n);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get output sample.
|
||||||
|
*
|
||||||
|
* @return the output sample
|
||||||
|
*/
|
||||||
|
int output() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the numebr of cycles according to current parameters
|
||||||
|
* that it takes to reach sync.
|
||||||
|
*
|
||||||
|
* @param sync whether to do the actual voice synchronization
|
||||||
|
*/
|
||||||
|
void voiceSync(bool sync);
|
||||||
|
|
||||||
|
public:
|
||||||
|
SID();
|
||||||
|
~SID();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set chip model.
|
||||||
|
*
|
||||||
|
* @param model chip model to use
|
||||||
|
* @throw SIDError
|
||||||
|
*/
|
||||||
|
void setChipModel(ChipModel model);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get currently emulated chip model.
|
||||||
|
*/
|
||||||
|
ChipModel getChipModel() const { return model; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SID reset.
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 16-bit input (EXT IN). Write 16-bit sample to audio input. NB! The caller
|
||||||
|
* is responsible for keeping the value within 16 bits. Note that to mix in
|
||||||
|
* an external audio signal, the signal should be resampled to 1MHz first to
|
||||||
|
* avoid sampling noise.
|
||||||
|
*
|
||||||
|
* @param value input level to set
|
||||||
|
*/
|
||||||
|
void input(int value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read registers.
|
||||||
|
*
|
||||||
|
* Reading a write only register returns the last char written to any SID register.
|
||||||
|
* The individual bits in this value start to fade down towards zero after a few cycles.
|
||||||
|
* All bits reach zero within approximately $2000 - $4000 cycles.
|
||||||
|
* It has been claimed that this fading happens in an orderly fashion,
|
||||||
|
* however sampling of write only registers reveals that this is not the case.
|
||||||
|
* NOTE: This is not correctly modeled.
|
||||||
|
* The actual use of write only registers has largely been made
|
||||||
|
* in the belief that all SID registers are readable.
|
||||||
|
* To support this belief the read would have to be done immediately
|
||||||
|
* after a write to the same register (remember that an intermediate write
|
||||||
|
* to another register would yield that value instead).
|
||||||
|
* With this in mind we return the last value written to any SID register
|
||||||
|
* for $2000 cycles without modeling the bit fading.
|
||||||
|
*
|
||||||
|
* @param offset SID register to read
|
||||||
|
* @return value read from chip
|
||||||
|
*/
|
||||||
|
unsigned char read(int offset);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write registers.
|
||||||
|
*
|
||||||
|
* @param offset chip register to write
|
||||||
|
* @param value value to write
|
||||||
|
*/
|
||||||
|
void write(int offset, unsigned char value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SID voice muting.
|
||||||
|
*
|
||||||
|
* @param channel channel to modify
|
||||||
|
* @param enable is muted?
|
||||||
|
*/
|
||||||
|
void mute(int channel, bool enable) { muted[channel] = enable; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setting of SID sampling parameters.
|
||||||
|
*
|
||||||
|
* Use a clock freqency of 985248Hz for PAL C64, 1022730Hz for NTSC C64.
|
||||||
|
* The default end of passband frequency is pass_freq = 0.9*sample_freq/2
|
||||||
|
* for sample frequencies up to ~ 44.1kHz, and 20kHz for higher sample frequencies.
|
||||||
|
*
|
||||||
|
* For resampling, the ratio between the clock frequency and the sample frequency
|
||||||
|
* is limited as follows: 125*clock_freq/sample_freq < 16384
|
||||||
|
* E.g. provided a clock frequency of ~ 1MHz, the sample frequency can not be set
|
||||||
|
* lower than ~ 8kHz. A lower sample frequency would make the resampling code
|
||||||
|
* overfill its 16k sample ring buffer.
|
||||||
|
*
|
||||||
|
* The end of passband frequency is also limited: pass_freq <= 0.9*sample_freq/2
|
||||||
|
*
|
||||||
|
* E.g. for a 44.1kHz sampling rate the end of passband frequency
|
||||||
|
* is limited to slightly below 20kHz.
|
||||||
|
* This constraint ensures that the FIR table is not overfilled.
|
||||||
|
*
|
||||||
|
* @param clockFrequency System clock frequency at Hz
|
||||||
|
* @param method sampling method to use
|
||||||
|
* @param samplingFrequency Desired output sampling rate
|
||||||
|
* @param highestAccurateFrequency
|
||||||
|
* @throw SIDError
|
||||||
|
*/
|
||||||
|
void setSamplingParameters(double clockFrequency, SamplingMethod method, double samplingFrequency, double highestAccurateFrequency);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clock SID forward using chosen output sampling algorithm.
|
||||||
|
*
|
||||||
|
* @param cycles c64 clocks to clock
|
||||||
|
* @param buf audio output buffer
|
||||||
|
* @return number of samples produced
|
||||||
|
*/
|
||||||
|
int clock(unsigned int cycles, short* buf);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clock SID forward with no audio production.
|
||||||
|
*
|
||||||
|
* _Warning_:
|
||||||
|
* You can't mix this method of clocking with the audio-producing
|
||||||
|
* clock() because components that don't affect OSC3/ENV3 are not
|
||||||
|
* emulated.
|
||||||
|
*
|
||||||
|
* @param cycles c64 clocks to clock.
|
||||||
|
*/
|
||||||
|
void clockSilent(unsigned int cycles);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set filter curve parameter for 6581 model.
|
||||||
|
*
|
||||||
|
* @see Filter6581::setFilterCurve(double)
|
||||||
|
*/
|
||||||
|
void setFilter6581Curve(double filterCurve);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set filter curve parameter for 8580 model.
|
||||||
|
*
|
||||||
|
* @see Filter8580::setFilterCurve(double)
|
||||||
|
*/
|
||||||
|
void setFilter8580Curve(double filterCurve);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable filter emulation.
|
||||||
|
*
|
||||||
|
* @param enable false to turn off filter emulation
|
||||||
|
*/
|
||||||
|
void enableFilter(bool enable);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#if RESID_INLINING || defined(SID_CPP)
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "Filter.h"
|
||||||
|
#include "ExternalFilter.h"
|
||||||
|
#include "Voice.h"
|
||||||
|
#include "resample/Resampler.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
RESID_INLINE
|
||||||
|
void SID::ageBusValue(unsigned int n)
|
||||||
|
{
|
||||||
|
if (likely(busValueTtl != 0))
|
||||||
|
{
|
||||||
|
busValueTtl -= n;
|
||||||
|
|
||||||
|
if (unlikely(busValueTtl <= 0))
|
||||||
|
{
|
||||||
|
busValue = 0;
|
||||||
|
busValueTtl = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RESID_INLINE
|
||||||
|
int SID::output() const
|
||||||
|
{
|
||||||
|
const int v1 = voice[0]->output(voice[2]->wave());
|
||||||
|
const int v2 = voice[1]->output(voice[0]->wave());
|
||||||
|
const int v3 = voice[2]->output(voice[1]->wave());
|
||||||
|
|
||||||
|
return externalFilter->clock(filter->clock(v1, v2, v3));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
RESID_INLINE
|
||||||
|
int SID::clock(unsigned int cycles, short* buf)
|
||||||
|
{
|
||||||
|
ageBusValue(cycles);
|
||||||
|
int s = 0;
|
||||||
|
|
||||||
|
while (cycles != 0)
|
||||||
|
{
|
||||||
|
unsigned int delta_t = std::min(nextVoiceSync, cycles);
|
||||||
|
|
||||||
|
if (likely(delta_t > 0))
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i < delta_t; i++)
|
||||||
|
{
|
||||||
|
// clock waveform generators
|
||||||
|
voice[0]->wave()->clock();
|
||||||
|
voice[1]->wave()->clock();
|
||||||
|
voice[2]->wave()->clock();
|
||||||
|
|
||||||
|
// clock envelope generators
|
||||||
|
voice[0]->envelope()->clock();
|
||||||
|
voice[1]->envelope()->clock();
|
||||||
|
voice[2]->envelope()->clock();
|
||||||
|
|
||||||
|
if (unlikely(resampler->input(output())))
|
||||||
|
{
|
||||||
|
buf[s++] = resampler->getOutput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cycles -= delta_t;
|
||||||
|
nextVoiceSync -= delta_t;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unlikely(nextVoiceSync == 0))
|
||||||
|
{
|
||||||
|
voiceSync(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
119
src/engine/platform/sound/c64_fp/Spline.cpp
Normal file
119
src/engine/platform/sound/c64_fp/Spline.cpp
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2015 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Spline.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
Spline::Spline(const std::vector<Point> &input) :
|
||||||
|
params(input.size()),
|
||||||
|
c(¶ms[0])
|
||||||
|
{
|
||||||
|
assert(input.size() > 2);
|
||||||
|
|
||||||
|
const size_t coeffLength = input.size() - 1;
|
||||||
|
|
||||||
|
std::vector<double> dxs(coeffLength);
|
||||||
|
std::vector<double> ms(coeffLength);
|
||||||
|
|
||||||
|
// Get consecutive differences and slopes
|
||||||
|
for (size_t i = 0; i < coeffLength; i++)
|
||||||
|
{
|
||||||
|
assert(input[i].x < input[i + 1].x);
|
||||||
|
|
||||||
|
const double dx = input[i + 1].x - input[i].x;
|
||||||
|
const double dy = input[i + 1].y - input[i].y;
|
||||||
|
dxs[i] = dx;
|
||||||
|
ms[i] = dy/dx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get degree-1 coefficients
|
||||||
|
params[0].c = ms[0];
|
||||||
|
for (size_t i = 1; i < coeffLength; i++)
|
||||||
|
{
|
||||||
|
const double m = ms[i - 1];
|
||||||
|
const double mNext = ms[i];
|
||||||
|
if (m * mNext <= 0)
|
||||||
|
{
|
||||||
|
params[i].c = 0.0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const double dx = dxs[i - 1];
|
||||||
|
const double dxNext = dxs[i];
|
||||||
|
const double common = dx + dxNext;
|
||||||
|
params[i].c = 3.0 * common / ((common + dxNext) / m + (common + dx) / mNext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params[coeffLength].c = ms[coeffLength - 1];
|
||||||
|
|
||||||
|
// Get degree-2 and degree-3 coefficients
|
||||||
|
for (size_t i = 0; i < coeffLength; i++)
|
||||||
|
{
|
||||||
|
params[i].x1 = input[i].x;
|
||||||
|
params[i].x2 = input[i + 1].x;
|
||||||
|
params[i].d = input[i].y;
|
||||||
|
|
||||||
|
const double c1 = params[i].c;
|
||||||
|
const double m = ms[i];
|
||||||
|
const double invDx = 1.0 / dxs[i];
|
||||||
|
const double common = c1 + params[i + 1].c - m - m;
|
||||||
|
params[i].b = (m - c1 - common) * invDx;
|
||||||
|
params[i].a = common * invDx * invDx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix the upper range, because we interpolate outside original bounds if necessary.
|
||||||
|
params[coeffLength - 1].x2 = std::numeric_limits<double>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
Spline::Point Spline::evaluate(double x) const
|
||||||
|
{
|
||||||
|
if ((x < c->x1) || (x > c->x2))
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < params.size(); i++)
|
||||||
|
{
|
||||||
|
if (x <= params[i].x2)
|
||||||
|
{
|
||||||
|
c = ¶ms[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpolate
|
||||||
|
const double diff = x - c->x1;
|
||||||
|
|
||||||
|
Point out;
|
||||||
|
|
||||||
|
// y = a*x^3 + b*x^2 + c*x + d
|
||||||
|
out.x = ((c->a * diff + c->b) * diff + c->c) * diff + c->d;
|
||||||
|
|
||||||
|
// dy = 3*a*x^2 + 2*b*x + c
|
||||||
|
out.y = (3.0 * c->a * diff + 2.0 * c->b) * diff + c->c;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
78
src/engine/platform/sound/c64_fp/Spline.h
Normal file
78
src/engine/platform/sound/c64_fp/Spline.h
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2015 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SPLINE_H
|
||||||
|
#define SPLINE_H
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fritsch-Carlson monotone cubic spline interpolation.
|
||||||
|
*
|
||||||
|
* Based on the implementation from the [Monotone cubic interpolation] wikipedia page.
|
||||||
|
*
|
||||||
|
* [Monotone cubic interpolation]: https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
|
||||||
|
*/
|
||||||
|
class Spline
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
double x;
|
||||||
|
double y;
|
||||||
|
} Point;
|
||||||
|
|
||||||
|
private:
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
double x1;
|
||||||
|
double x2;
|
||||||
|
double a;
|
||||||
|
double b;
|
||||||
|
double c;
|
||||||
|
double d;
|
||||||
|
} Param;
|
||||||
|
|
||||||
|
typedef std::vector<Param> ParamVector;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Interpolation parameters
|
||||||
|
ParamVector params;
|
||||||
|
|
||||||
|
/// Last used parameters, cached for speed up
|
||||||
|
mutable ParamVector::const_pointer c;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Spline(const std::vector<Point> &input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate y and its derivative at given point x.
|
||||||
|
*/
|
||||||
|
Point evaluate(double x) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
130
src/engine/platform/sound/c64_fp/Voice.h
Normal file
130
src/engine/platform/sound/c64_fp/Voice.h
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef VOICE_H
|
||||||
|
#define VOICE_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "siddefs-fp.h"
|
||||||
|
#include "WaveformGenerator.h"
|
||||||
|
#include "EnvelopeGenerator.h"
|
||||||
|
|
||||||
|
#include "sidcxx11.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of SID voice block.
|
||||||
|
*/
|
||||||
|
class Voice
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::unique_ptr<WaveformGenerator> const waveformGenerator;
|
||||||
|
|
||||||
|
std::unique_ptr<EnvelopeGenerator> const envelopeGenerator;
|
||||||
|
|
||||||
|
/// The DAC LUT for analog waveform output
|
||||||
|
float* wavDAC; //-V730_NOINIT this is initialized in the SID constructor
|
||||||
|
|
||||||
|
/// The DAC LUT for analog envelope output
|
||||||
|
float* envDAC; //-V730_NOINIT this is initialized in the SID constructor
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Amplitude modulated waveform output.
|
||||||
|
*
|
||||||
|
* The waveform DAC generates a voltage between virtual ground and Vdd
|
||||||
|
* (5-12 V for the 6581 and 4.75-9 V for the 8580)
|
||||||
|
* corresponding to oscillator state 0 .. 4095.
|
||||||
|
*
|
||||||
|
* The envelope DAC generates a voltage between waveform gen output and
|
||||||
|
* the virtual ground level, corresponding to envelope state 0 .. 255.
|
||||||
|
*
|
||||||
|
* Ideal range [-2048*255, 2047*255].
|
||||||
|
*
|
||||||
|
* @param ringModulator Ring-modulator for waveform
|
||||||
|
* @return the voice analog output
|
||||||
|
*/
|
||||||
|
RESID_INLINE
|
||||||
|
int output(const WaveformGenerator* ringModulator) const
|
||||||
|
{
|
||||||
|
unsigned int const wav = waveformGenerator->output(ringModulator);
|
||||||
|
unsigned int const env = envelopeGenerator->output();
|
||||||
|
|
||||||
|
// DAC imperfections are emulated by using the digital output
|
||||||
|
// as an index into a DAC lookup table.
|
||||||
|
return static_cast<int>(wavDAC[wav] * envDAC[env]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
Voice() :
|
||||||
|
waveformGenerator(new WaveformGenerator()),
|
||||||
|
envelopeGenerator(new EnvelopeGenerator()) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the analog DAC emulation for waveform generator.
|
||||||
|
* Must be called before any operation.
|
||||||
|
*
|
||||||
|
* @param dac
|
||||||
|
*/
|
||||||
|
void setWavDAC(float* dac) { wavDAC = dac; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the analog DAC emulation for envelope.
|
||||||
|
* Must be called before any operation.
|
||||||
|
*
|
||||||
|
* @param dac
|
||||||
|
*/
|
||||||
|
void setEnvDAC(float* dac) { envDAC = dac; }
|
||||||
|
|
||||||
|
WaveformGenerator* wave() const { return waveformGenerator.get(); }
|
||||||
|
|
||||||
|
EnvelopeGenerator* envelope() const { return envelopeGenerator.get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write control register.
|
||||||
|
*
|
||||||
|
* @param control Control register value.
|
||||||
|
*/
|
||||||
|
void writeCONTROL_REG(unsigned char control)
|
||||||
|
{
|
||||||
|
waveformGenerator->writeCONTROL_REG(control);
|
||||||
|
envelopeGenerator->writeCONTROL_REG(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SID reset.
|
||||||
|
*/
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
waveformGenerator->reset();
|
||||||
|
envelopeGenerator->reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
204
src/engine/platform/sound/c64_fp/WaveformCalculator.cpp
Normal file
204
src/engine/platform/sound/c64_fp/WaveformCalculator.cpp
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "WaveformCalculator.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
WaveformCalculator* WaveformCalculator::getInstance()
|
||||||
|
{
|
||||||
|
static WaveformCalculator instance;
|
||||||
|
return &instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters derived with the Monte Carlo method based on
|
||||||
|
* samplings by kevtris. Code and data available in the project repository [1].
|
||||||
|
*
|
||||||
|
* The score here reported is the acoustic error
|
||||||
|
* calculated XORing the estimated and the sampled values.
|
||||||
|
* In parentheses the number of mispredicted bits
|
||||||
|
* on a total of 32768.
|
||||||
|
*
|
||||||
|
* [1] https://github.com/libsidplayfp/combined-waveforms
|
||||||
|
*/
|
||||||
|
const CombinedWaveformConfig config[2][4] =
|
||||||
|
{
|
||||||
|
{ /* kevtris chip G (6581 R2) */
|
||||||
|
{0.90251f, 0.f, 0.f, 1.9147f, 1.6747f, 0.62376f }, // error 1689 (280)
|
||||||
|
{0.93088f, 2.4843f, 0.f, 1.0353f, 1.1484f, 0.f }, // error 6128 (130)
|
||||||
|
{0.90988f, 2.26303f, 1.13126f, 1.0035f, 1.13801f, 0.f }, // error 14243 (632)
|
||||||
|
{0.91f, 1.192f, 0.f, 1.0169f, 1.2f, 0.637f }, // error 64 (2)
|
||||||
|
},
|
||||||
|
{ /* kevtris chip V (8580 R5) */
|
||||||
|
{0.9632f, 0.f, 0.975f, 1.7467f, 2.36132f, 0.975395f}, // error 1380 (169)
|
||||||
|
{0.92886f, 1.67696f, 0.f, 1.1014f, 1.4352f, 0.f }, // error 8007 (218)
|
||||||
|
{0.94043f, 1.7937f, 0.981f, 1.1213f, 1.4259f, 0.f }, // error 11957 (362)
|
||||||
|
{0.96211f, 0.98695f, 1.00387f, 1.46499f, 1.98375f, 0.77777f }, // error 2369 (89)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate bitstate based on emulation of combined waves.
|
||||||
|
*
|
||||||
|
* @param config model parameters matrix
|
||||||
|
* @param waveform the waveform to emulate, 1 .. 7
|
||||||
|
* @param accumulator the high bits of the accumulator value
|
||||||
|
*/
|
||||||
|
short calculateCombinedWaveform(const CombinedWaveformConfig& config, int waveform, int accumulator)
|
||||||
|
{
|
||||||
|
float o[12];
|
||||||
|
|
||||||
|
// Saw
|
||||||
|
for (unsigned int i = 0; i < 12; i++)
|
||||||
|
{
|
||||||
|
o[i] = (accumulator & (1 << i)) != 0 ? 1.f : 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert to Triangle
|
||||||
|
if ((waveform & 3) == 1)
|
||||||
|
{
|
||||||
|
const bool top = (accumulator & 0x800) != 0;
|
||||||
|
|
||||||
|
for (int i = 11; i > 0; i--)
|
||||||
|
{
|
||||||
|
o[i] = top ? 1.0f - o[i - 1] : o[i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
o[0] = 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// or to Saw+Triangle
|
||||||
|
else if ((waveform & 3) == 3)
|
||||||
|
{
|
||||||
|
// bottom bit is grounded via T waveform selector
|
||||||
|
o[0] *= config.stmix;
|
||||||
|
|
||||||
|
for (int i = 1; i < 12; i++)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Enabling the S waveform pulls the XOR circuit selector transistor down
|
||||||
|
* (which would normally make the descending ramp of the triangle waveform),
|
||||||
|
* so ST does not actually have a sawtooth and triangle waveform combined,
|
||||||
|
* but merely combines two sawtooths, one rising double the speed the other.
|
||||||
|
*
|
||||||
|
* http://www.lemon64.com/forum/viewtopic.php?t=25442&postdays=0&postorder=asc&start=165
|
||||||
|
*/
|
||||||
|
o[i] = o[i - 1] * (1.f - config.stmix) + o[i] * config.stmix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// topbit for Saw
|
||||||
|
if ((waveform & 2) == 2)
|
||||||
|
{
|
||||||
|
o[11] *= config.topbit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ST, P* waveforms
|
||||||
|
if (waveform == 3 || waveform > 4)
|
||||||
|
{
|
||||||
|
float distancetable[12 * 2 + 1];
|
||||||
|
distancetable[12] = 1.f;
|
||||||
|
for (int i = 12; i > 0; i--)
|
||||||
|
{
|
||||||
|
distancetable[12-i] = 1.0f / pow(config.distance1, i);
|
||||||
|
distancetable[12+i] = 1.0f / pow(config.distance2, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
float tmp[12];
|
||||||
|
|
||||||
|
for (int i = 0; i < 12; i++)
|
||||||
|
{
|
||||||
|
float avg = 0.f;
|
||||||
|
float n = 0.f;
|
||||||
|
|
||||||
|
for (int j = 0; j < 12; j++)
|
||||||
|
{
|
||||||
|
const float weight = distancetable[i - j + 12];
|
||||||
|
avg += o[j] * weight;
|
||||||
|
n += weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pulse control bit
|
||||||
|
if (waveform > 4)
|
||||||
|
{
|
||||||
|
const float weight = distancetable[i - 12 + 12];
|
||||||
|
avg += config.pulsestrength * weight;
|
||||||
|
n += weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp[i] = (o[i] + avg / n) * 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 12; i++)
|
||||||
|
{
|
||||||
|
o[i] = tmp[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
short value = 0;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < 12; i++)
|
||||||
|
{
|
||||||
|
if (o[i] > config.bias)
|
||||||
|
{
|
||||||
|
value |= 1 << i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix_t* WaveformCalculator::buildTable(ChipModel model)
|
||||||
|
{
|
||||||
|
const CombinedWaveformConfig* cfgArray = config[model == MOS6581 ? 0 : 1];
|
||||||
|
|
||||||
|
cw_cache_t::iterator lb = CACHE.lower_bound(cfgArray);
|
||||||
|
|
||||||
|
if (lb != CACHE.end() && !(CACHE.key_comp()(cfgArray, lb->first)))
|
||||||
|
{
|
||||||
|
return &(lb->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix_t wftable(8, 4096);
|
||||||
|
|
||||||
|
for (unsigned int idx = 0; idx < 1 << 12; idx++)
|
||||||
|
{
|
||||||
|
wftable[0][idx] = 0xfff;
|
||||||
|
wftable[1][idx] = static_cast<short>((idx & 0x800) == 0 ? idx << 1 : (idx ^ 0xfff) << 1);
|
||||||
|
wftable[2][idx] = static_cast<short>(idx);
|
||||||
|
wftable[3][idx] = calculateCombinedWaveform(cfgArray[0], 3, idx);
|
||||||
|
wftable[4][idx] = 0xfff;
|
||||||
|
wftable[5][idx] = calculateCombinedWaveform(cfgArray[1], 5, idx);
|
||||||
|
wftable[6][idx] = calculateCombinedWaveform(cfgArray[2], 6, idx);
|
||||||
|
wftable[7][idx] = calculateCombinedWaveform(cfgArray[3], 7, idx);
|
||||||
|
}
|
||||||
|
#ifdef HAVE_CXX11
|
||||||
|
return &(CACHE.emplace_hint(lb, cw_cache_t::value_type(cfgArray, wftable))->second);
|
||||||
|
#else
|
||||||
|
return &(CACHE.insert(lb, cw_cache_t::value_type(cfgArray, wftable))->second);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
128
src/engine/platform/sound/c64_fp/WaveformCalculator.h
Normal file
128
src/engine/platform/sound/c64_fp/WaveformCalculator.h
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WAVEFORMCALCULATOR_h
|
||||||
|
#define WAVEFORMCALCULATOR_h
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "array.h"
|
||||||
|
#include "sidcxx11.h"
|
||||||
|
#include "siddefs-fp.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combined waveform model parameters.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
float bias;
|
||||||
|
float pulsestrength;
|
||||||
|
float topbit;
|
||||||
|
float distance1;
|
||||||
|
float distance2;
|
||||||
|
float stmix;
|
||||||
|
} CombinedWaveformConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combined waveform calculator for WaveformGenerator.
|
||||||
|
* By combining waveforms, the bits of each waveform are effectively short
|
||||||
|
* circuited. A zero bit in one waveform will result in a zero output bit
|
||||||
|
* (thus the infamous claim that the waveforms are AND'ed).
|
||||||
|
* However, a zero bit in one waveform may also affect the neighboring bits
|
||||||
|
* in the output.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* 1 1
|
||||||
|
* Bit # 1 0 9 8 7 6 5 4 3 2 1 0
|
||||||
|
* -----------------------
|
||||||
|
* Sawtooth 0 0 0 1 1 1 1 1 1 0 0 0
|
||||||
|
*
|
||||||
|
* Triangle 0 0 1 1 1 1 1 1 0 0 0 0
|
||||||
|
*
|
||||||
|
* AND 0 0 0 1 1 1 1 1 0 0 0 0
|
||||||
|
*
|
||||||
|
* Output 0 0 0 0 1 1 1 0 0 0 0 0
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Re-vectorized die photographs reveal the mechanism behind this behavior.
|
||||||
|
* Each waveform selector bit acts as a switch, which directly connects
|
||||||
|
* internal outputs into the waveform DAC inputs as follows:
|
||||||
|
*
|
||||||
|
* - Noise outputs the shift register bits to DAC inputs as described above.
|
||||||
|
* Each output is also used as input to the next bit when the shift register
|
||||||
|
* is shifted. Lower four bits are grounded.
|
||||||
|
* - Pulse connects a single line to all DAC inputs. The line is connected to
|
||||||
|
* either 5V (pulse on) or 0V (pulse off) at bit 11, and ends at bit 0.
|
||||||
|
* - Triangle connects the upper 11 bits of the (MSB EOR'ed) accumulator to the
|
||||||
|
* DAC inputs, so that DAC bit 0 = 0, DAC bit n = accumulator bit n - 1.
|
||||||
|
* - Sawtooth connects the upper 12 bits of the accumulator to the DAC inputs,
|
||||||
|
* so that DAC bit n = accumulator bit n. Sawtooth blocks out the MSB from
|
||||||
|
* the EOR used to generate the triangle waveform.
|
||||||
|
*
|
||||||
|
* We can thus draw the following conclusions:
|
||||||
|
*
|
||||||
|
* - The shift register may be written to by combined waveforms.
|
||||||
|
* - The pulse waveform interconnects all bits in combined waveforms via the
|
||||||
|
* pulse line.
|
||||||
|
* - The combination of triangle and sawtooth interconnects neighboring bits
|
||||||
|
* of the sawtooth waveform.
|
||||||
|
*
|
||||||
|
* Also in the 6581 the MSB of the oscillator, used as input for the
|
||||||
|
* triangle xor logic and the pulse adder's last bit, is connected directly
|
||||||
|
* to the waveform selector, while in the 8580 it is latched at sid_clk2
|
||||||
|
* before being forwarded to the selector. Thus in the 6581 if the sawtooth MSB
|
||||||
|
* is pulled down it might affect the oscillator's adder
|
||||||
|
* driving the top bit low.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class WaveformCalculator
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
typedef std::map<const CombinedWaveformConfig*, matrix_t> cw_cache_t;
|
||||||
|
|
||||||
|
private:
|
||||||
|
cw_cache_t CACHE;
|
||||||
|
|
||||||
|
WaveformCalculator() DEFAULT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Get the singleton instance.
|
||||||
|
*/
|
||||||
|
static WaveformCalculator* getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build waveform tables for use by WaveformGenerator.
|
||||||
|
*
|
||||||
|
* @param model Chip model to use
|
||||||
|
* @return Waveform table
|
||||||
|
*/
|
||||||
|
matrix_t* buildTable(ChipModel model);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
357
src/engine/platform/sound/c64_fp/WaveformGenerator.cpp
Normal file
357
src/engine/platform/sound/c64_fp/WaveformGenerator.cpp
Normal file
|
@ -0,0 +1,357 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2021 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define WAVEFORMGENERATOR_CPP
|
||||||
|
|
||||||
|
#include "WaveformGenerator.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This fixes tests
|
||||||
|
* SID/wb_testsuite/noise_writeback_check_8_to_C_old
|
||||||
|
* SID/wb_testsuite/noise_writeback_check_9_to_C_old
|
||||||
|
* SID/wb_testsuite/noise_writeback_check_A_to_C_old
|
||||||
|
* SID/wb_testsuite/noise_writeback_check_C_to_C_old
|
||||||
|
*
|
||||||
|
* but breaks SID/wf12nsr/wf12nsr
|
||||||
|
*
|
||||||
|
* needs more digging...
|
||||||
|
*/
|
||||||
|
//#define NO_WB_NOI_PUL
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of cycles after which the waveform output fades to 0 when setting
|
||||||
|
* the waveform register to 0.
|
||||||
|
* Values measured on warm chips (6581R3/R4 and 8580R5)
|
||||||
|
* checking OSC3.
|
||||||
|
* Times vary wildly with temperature and may differ
|
||||||
|
* from chip to chip so the numbers here represent
|
||||||
|
* only the big difference between the old and new models.
|
||||||
|
*
|
||||||
|
* See [VICE Bug #290](http://sourceforge.net/p/vice-emu/bugs/290/)
|
||||||
|
* and [VICE Bug #1128](http://sourceforge.net/p/vice-emu/bugs/1128/)
|
||||||
|
*/
|
||||||
|
// ~95ms
|
||||||
|
const unsigned int FLOATING_OUTPUT_TTL_6581R3 = 54000;
|
||||||
|
const unsigned int FLOATING_OUTPUT_FADE_6581R3 = 1400;
|
||||||
|
// ~1s
|
||||||
|
const unsigned int FLOATING_OUTPUT_TTL_6581R4 = 1000000;
|
||||||
|
// ~1s
|
||||||
|
const unsigned int FLOATING_OUTPUT_TTL_8580R5 = 800000;
|
||||||
|
const unsigned int FLOATING_OUTPUT_FADE_8580R5 = 50000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of cycles after which the shift register is reset
|
||||||
|
* when the test bit is set.
|
||||||
|
* Values measured on warm chips (6581R3/R4 and 8580R5)
|
||||||
|
* checking OSC3.
|
||||||
|
* Times vary wildly with temperature and may differ
|
||||||
|
* from chip to chip so the numbers here represent
|
||||||
|
* only the big difference between the old and new models.
|
||||||
|
*/
|
||||||
|
// ~210ms
|
||||||
|
const unsigned int SHIFT_REGISTER_RESET_6581R3 = 50000;
|
||||||
|
const unsigned int SHIFT_REGISTER_FADE_6581R3 = 15000;
|
||||||
|
// ~2.15s
|
||||||
|
const unsigned int SHIFT_REGISTER_RESET_6581R4 = 2150000;
|
||||||
|
// ~2.8s
|
||||||
|
const unsigned int SHIFT_REGISTER_RESET_8580R5 = 986000;
|
||||||
|
const unsigned int SHIFT_REGISTER_FADE_8580R5 = 314300;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is what happens when the lfsr is clocked:
|
||||||
|
*
|
||||||
|
* cycle 0: bit 19 of the accumulator goes from low to high, the noise register acts normally,
|
||||||
|
* the output may overwrite a bit;
|
||||||
|
*
|
||||||
|
* cycle 1: first phase of the shift, the bits are interconnected and the output of each bit
|
||||||
|
* is latched into the following. The output may overwrite the latched value.
|
||||||
|
*
|
||||||
|
* cycle 2: second phase of the shift, the latched value becomes active in the first
|
||||||
|
* half of the clock and from the second half the register returns to normal operation.
|
||||||
|
*
|
||||||
|
* When the test or reset lines are active the first phase is executed at every cyle
|
||||||
|
* until the signal is released triggering the second phase.
|
||||||
|
*/
|
||||||
|
void WaveformGenerator::clock_shift_register(unsigned int bit0)
|
||||||
|
{
|
||||||
|
shift_register = (shift_register >> 1) | bit0;
|
||||||
|
|
||||||
|
// New noise waveform output.
|
||||||
|
set_noise_output();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int WaveformGenerator::get_noise_writeback()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
~(
|
||||||
|
(1 << 2) | // Bit 20
|
||||||
|
(1 << 4) | // Bit 18
|
||||||
|
(1 << 8) | // Bit 14
|
||||||
|
(1 << 11) | // Bit 11
|
||||||
|
(1 << 13) | // Bit 9
|
||||||
|
(1 << 17) | // Bit 5
|
||||||
|
(1 << 20) | // Bit 2
|
||||||
|
(1 << 22) // Bit 0
|
||||||
|
) |
|
||||||
|
((waveform_output & (1 << 11)) >> 9) | // Bit 11 -> bit 20
|
||||||
|
((waveform_output & (1 << 10)) >> 6) | // Bit 10 -> bit 18
|
||||||
|
((waveform_output & (1 << 9)) >> 1) | // Bit 9 -> bit 14
|
||||||
|
((waveform_output & (1 << 8)) << 3) | // Bit 8 -> bit 11
|
||||||
|
((waveform_output & (1 << 7)) << 6) | // Bit 7 -> bit 9
|
||||||
|
((waveform_output & (1 << 6)) << 11) | // Bit 6 -> bit 5
|
||||||
|
((waveform_output & (1 << 5)) << 15) | // Bit 5 -> bit 2
|
||||||
|
((waveform_output & (1 << 4)) << 18); // Bit 4 -> bit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveformGenerator::write_shift_register()
|
||||||
|
{
|
||||||
|
if (unlikely(waveform > 0x8) && likely(!test) && likely(shift_pipeline != 1))
|
||||||
|
{
|
||||||
|
// Write changes to the shift register output caused by combined waveforms
|
||||||
|
// back into the shift register. This happens only when the register is clocked
|
||||||
|
// (see $D1+$81_wave_test [1]) or when the test bit is falling.
|
||||||
|
// A bit once set to zero cannot be changed, hence the and'ing.
|
||||||
|
//
|
||||||
|
// [1] ftp://ftp.untergrund.net/users/nata/sid_test/$D1+$81_wave_test.7z
|
||||||
|
//
|
||||||
|
// FIXME: Write test program to check the effect of 1 bits and whether
|
||||||
|
// neighboring bits are affected.
|
||||||
|
|
||||||
|
#ifdef NO_WB_NOI_PUL
|
||||||
|
if (waveform == 0xc)
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
shift_register &= get_noise_writeback();
|
||||||
|
|
||||||
|
noise_output &= waveform_output;
|
||||||
|
set_no_noise_or_noise_output();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveformGenerator::set_noise_output()
|
||||||
|
{
|
||||||
|
noise_output =
|
||||||
|
((shift_register & (1 << 2)) << 9) | // Bit 20 -> bit 11
|
||||||
|
((shift_register & (1 << 4)) << 6) | // Bit 18 -> bit 10
|
||||||
|
((shift_register & (1 << 8)) << 1) | // Bit 14 -> bit 9
|
||||||
|
((shift_register & (1 << 11)) >> 3) | // Bit 11 -> bit 8
|
||||||
|
((shift_register & (1 << 13)) >> 6) | // Bit 9 -> bit 7
|
||||||
|
((shift_register & (1 << 17)) >> 11) | // Bit 5 -> bit 6
|
||||||
|
((shift_register & (1 << 20)) >> 15) | // Bit 2 -> bit 5
|
||||||
|
((shift_register & (1 << 22)) >> 18); // Bit 0 -> bit 4
|
||||||
|
|
||||||
|
set_no_noise_or_noise_output();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveformGenerator::setWaveformModels(matrix_t* models)
|
||||||
|
{
|
||||||
|
model_wave = models;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveformGenerator::synchronize(WaveformGenerator* syncDest, const WaveformGenerator* syncSource) const
|
||||||
|
{
|
||||||
|
// A special case occurs when a sync source is synced itself on the same
|
||||||
|
// cycle as when its MSB is set high. In this case the destination will
|
||||||
|
// not be synced. This has been verified by sampling OSC3.
|
||||||
|
if (unlikely(msb_rising) && syncDest->sync && !(sync && syncSource->msb_rising))
|
||||||
|
{
|
||||||
|
syncDest->accumulator = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool do_pre_writeback(unsigned int waveform_prev, unsigned int waveform, bool is6581)
|
||||||
|
{
|
||||||
|
// no writeback without combined waveforms
|
||||||
|
if (likely(waveform_prev <= 0x8))
|
||||||
|
return false;
|
||||||
|
// no writeback when changing to noise
|
||||||
|
if (waveform == 8)
|
||||||
|
return false;
|
||||||
|
// What's happening here?
|
||||||
|
if (is6581 &&
|
||||||
|
((((waveform_prev & 0x3) == 0x1) && ((waveform & 0x3) == 0x2))
|
||||||
|
|| (((waveform_prev & 0x3) == 0x2) && ((waveform & 0x3) == 0x1))))
|
||||||
|
return false;
|
||||||
|
if (waveform_prev == 0xc)
|
||||||
|
{
|
||||||
|
if (is6581)
|
||||||
|
return false;
|
||||||
|
else if ((waveform != 0x9) && (waveform != 0xe))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#ifdef NO_WB_NOI_PUL
|
||||||
|
if (waveform == 0xc)
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
// ok do the writeback
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When noise and pulse are combined all the bits are
|
||||||
|
* connected and the four lower ones are grounded.
|
||||||
|
* This causes the adjacent bits to be pulled down,
|
||||||
|
* with different strength depending on model.
|
||||||
|
*
|
||||||
|
* This is just a rough attempt at modelling the effect.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static unsigned int noise_pulse6581(unsigned int noise)
|
||||||
|
{
|
||||||
|
return (noise < 0xf00) ? 0x000 : noise & (noise << 1) & (noise << 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int noise_pulse8580(unsigned int noise)
|
||||||
|
{
|
||||||
|
return (noise < 0xfc0) ? noise & (noise << 1) : 0xfc0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveformGenerator::set_no_noise_or_noise_output()
|
||||||
|
{
|
||||||
|
no_noise_or_noise_output = no_noise | noise_output;
|
||||||
|
|
||||||
|
// pulse+noise
|
||||||
|
if (unlikely((waveform & 0xc) == 0xc))
|
||||||
|
no_noise_or_noise_output = is6581
|
||||||
|
? noise_pulse6581(no_noise_or_noise_output)
|
||||||
|
: noise_pulse8580(no_noise_or_noise_output);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveformGenerator::writeCONTROL_REG(unsigned char control)
|
||||||
|
{
|
||||||
|
const unsigned int waveform_prev = waveform;
|
||||||
|
const bool test_prev = test;
|
||||||
|
|
||||||
|
waveform = (control >> 4) & 0x0f;
|
||||||
|
test = (control & 0x08) != 0;
|
||||||
|
sync = (control & 0x02) != 0;
|
||||||
|
|
||||||
|
// Substitution of accumulator MSB when sawtooth = 0, ring_mod = 1.
|
||||||
|
ring_msb_mask = ((~control >> 5) & (control >> 2) & 0x1) << 23;
|
||||||
|
|
||||||
|
if (waveform != waveform_prev)
|
||||||
|
{
|
||||||
|
// Set up waveform table.
|
||||||
|
wave = (*model_wave)[waveform & 0x7];
|
||||||
|
|
||||||
|
// no_noise and no_pulse are used in set_waveform_output() as bitmasks to
|
||||||
|
// only let the noise or pulse influence the output when the noise or pulse
|
||||||
|
// waveforms are selected.
|
||||||
|
no_noise = (waveform & 0x8) != 0 ? 0x000 : 0xfff;
|
||||||
|
set_no_noise_or_noise_output();
|
||||||
|
no_pulse = (waveform & 0x4) != 0 ? 0x000 : 0xfff;
|
||||||
|
|
||||||
|
if (waveform == 0)
|
||||||
|
{
|
||||||
|
// Change to floating DAC input.
|
||||||
|
// Reset fading time for floating DAC input.
|
||||||
|
floating_output_ttl = is6581 ? FLOATING_OUTPUT_TTL_6581R3 : FLOATING_OUTPUT_TTL_8580R5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (test != test_prev)
|
||||||
|
{
|
||||||
|
if (test)
|
||||||
|
{
|
||||||
|
// Reset accumulator.
|
||||||
|
accumulator = 0;
|
||||||
|
|
||||||
|
// Flush shift pipeline.
|
||||||
|
shift_pipeline = 0;
|
||||||
|
|
||||||
|
// Set reset time for shift register.
|
||||||
|
shift_register_reset = is6581 ? SHIFT_REGISTER_RESET_6581R3 : SHIFT_REGISTER_RESET_8580R5;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// When the test bit is falling, the second phase of the shift is
|
||||||
|
// completed by enabling SRAM write.
|
||||||
|
|
||||||
|
// During first phase of the shift the bits are interconnected
|
||||||
|
// and the output of each bit is latched into the following.
|
||||||
|
// The output may overwrite the latched value.
|
||||||
|
if (do_pre_writeback(waveform_prev, waveform, is6581))
|
||||||
|
{
|
||||||
|
shift_register &= get_noise_writeback();
|
||||||
|
}
|
||||||
|
|
||||||
|
// bit0 = (bit22 | test) ^ bit17 = 1 ^ bit17 = ~bit17
|
||||||
|
clock_shift_register((~shift_register << 17) & (1 << 22));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveformGenerator::waveBitfade()
|
||||||
|
{
|
||||||
|
waveform_output &= waveform_output >> 1;
|
||||||
|
osc3 = waveform_output;
|
||||||
|
if (waveform_output != 0)
|
||||||
|
floating_output_ttl = is6581 ? FLOATING_OUTPUT_FADE_6581R3 : FLOATING_OUTPUT_FADE_8580R5;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveformGenerator::shiftregBitfade()
|
||||||
|
{
|
||||||
|
shift_register |= shift_register >> 1;
|
||||||
|
shift_register |= 0x400000;
|
||||||
|
if (shift_register != 0x7fffff)
|
||||||
|
shift_register_reset = is6581 ? SHIFT_REGISTER_FADE_6581R3 : SHIFT_REGISTER_FADE_8580R5;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveformGenerator::reset()
|
||||||
|
{
|
||||||
|
// accumulator is not changed on reset
|
||||||
|
freq = 0;
|
||||||
|
pw = 0;
|
||||||
|
|
||||||
|
msb_rising = false;
|
||||||
|
|
||||||
|
waveform = 0;
|
||||||
|
osc3 = 0;
|
||||||
|
|
||||||
|
test = false;
|
||||||
|
sync = false;
|
||||||
|
|
||||||
|
wave = model_wave ? (*model_wave)[0] : nullptr;
|
||||||
|
|
||||||
|
ring_msb_mask = 0;
|
||||||
|
no_noise = 0xfff;
|
||||||
|
no_pulse = 0xfff;
|
||||||
|
pulse_output = 0xfff;
|
||||||
|
|
||||||
|
shift_register_reset = 0;
|
||||||
|
shift_register = 0x7fffff;
|
||||||
|
// when reset is released the shift register is clocked once
|
||||||
|
// so the lower bit is zeroed out
|
||||||
|
// bit0 = (bit22 | test) ^ bit17 = 1 ^ 1 = 0
|
||||||
|
clock_shift_register(0);
|
||||||
|
|
||||||
|
shift_pipeline = 0;
|
||||||
|
|
||||||
|
waveform_output = 0;
|
||||||
|
floating_output_ttl = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
396
src/engine/platform/sound/c64_fp/WaveformGenerator.h
Normal file
396
src/engine/platform/sound/c64_fp/WaveformGenerator.h
Normal file
|
@ -0,0 +1,396 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WAVEFORMGENERATOR_H
|
||||||
|
#define WAVEFORMGENERATOR_H
|
||||||
|
|
||||||
|
#include "siddefs-fp.h"
|
||||||
|
#include "array.h"
|
||||||
|
|
||||||
|
#include "sidcxx11.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 24 bit accumulator is the basis for waveform generation.
|
||||||
|
* FREQ is added to the lower 16 bits of the accumulator each cycle.
|
||||||
|
* The accumulator is set to zero when TEST is set, and starts counting
|
||||||
|
* when TEST is cleared.
|
||||||
|
*
|
||||||
|
* Waveforms are generated as follows:
|
||||||
|
*
|
||||||
|
* - No waveform:
|
||||||
|
* When no waveform is selected, the DAC input is floating.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* - Triangle:
|
||||||
|
* The upper 12 bits of the accumulator are used.
|
||||||
|
* The MSB is used to create the falling edge of the triangle by inverting
|
||||||
|
* the lower 11 bits. The MSB is thrown away and the lower 11 bits are
|
||||||
|
* left-shifted (half the resolution, full amplitude).
|
||||||
|
* Ring modulation substitutes the MSB with MSB EOR NOT sync_source MSB.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* - Sawtooth:
|
||||||
|
* The output is identical to the upper 12 bits of the accumulator.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* - Pulse:
|
||||||
|
* The upper 12 bits of the accumulator are used.
|
||||||
|
* These bits are compared to the pulse width register by a 12 bit digital
|
||||||
|
* comparator; output is either all one or all zero bits.
|
||||||
|
* The pulse setting is delayed one cycle after the compare.
|
||||||
|
* The test bit, when set to one, holds the pulse waveform output at 0xfff
|
||||||
|
* regardless of the pulse width setting.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* - Noise:
|
||||||
|
* The noise output is taken from intermediate bits of a 23-bit shift register
|
||||||
|
* which is clocked by bit 19 of the accumulator.
|
||||||
|
* The shift is delayed 2 cycles after bit 19 is set high.
|
||||||
|
*
|
||||||
|
* Operation: Calculate EOR result, shift register, set bit 0 = result.
|
||||||
|
*
|
||||||
|
* reset +--------------------------------------------+
|
||||||
|
* | | |
|
||||||
|
* test--OR-->EOR<--+ |
|
||||||
|
* | | |
|
||||||
|
* 2 2 2 1 1 1 1 1 1 1 1 1 1 |
|
||||||
|
* Register bits: 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 <---+
|
||||||
|
* | | | | | | | |
|
||||||
|
* Waveform bits: 1 1 9 8 7 6 5 4
|
||||||
|
* 1 0
|
||||||
|
*
|
||||||
|
* The low 4 waveform bits are zero (grounded).
|
||||||
|
*/
|
||||||
|
class WaveformGenerator
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
matrix_t* model_wave;
|
||||||
|
|
||||||
|
short* wave;
|
||||||
|
|
||||||
|
// PWout = (PWn/40.95)%
|
||||||
|
unsigned int pw;
|
||||||
|
|
||||||
|
unsigned int shift_register;
|
||||||
|
|
||||||
|
/// Emulation of pipeline causing bit 19 to clock the shift register.
|
||||||
|
int shift_pipeline;
|
||||||
|
|
||||||
|
unsigned int ring_msb_mask;
|
||||||
|
unsigned int no_noise;
|
||||||
|
unsigned int noise_output;
|
||||||
|
unsigned int no_noise_or_noise_output;
|
||||||
|
unsigned int no_pulse;
|
||||||
|
unsigned int pulse_output;
|
||||||
|
|
||||||
|
/// The control register right-shifted 4 bits; used for output function table lookup.
|
||||||
|
unsigned int waveform;
|
||||||
|
|
||||||
|
unsigned int waveform_output;
|
||||||
|
|
||||||
|
/// Current accumulator value.
|
||||||
|
unsigned int accumulator;
|
||||||
|
|
||||||
|
// Fout = (Fn*Fclk/16777216)Hz
|
||||||
|
unsigned int freq;
|
||||||
|
|
||||||
|
/// 8580 tri/saw pipeline
|
||||||
|
unsigned int tri_saw_pipeline;
|
||||||
|
|
||||||
|
/// The OSC3 value
|
||||||
|
unsigned int osc3;
|
||||||
|
|
||||||
|
/// Remaining time to fully reset shift register.
|
||||||
|
unsigned int shift_register_reset;
|
||||||
|
|
||||||
|
// The wave signal TTL when no waveform is selected
|
||||||
|
unsigned int floating_output_ttl;
|
||||||
|
|
||||||
|
/// The control register bits. Gate is handled by EnvelopeGenerator.
|
||||||
|
//@{
|
||||||
|
bool test;
|
||||||
|
bool sync;
|
||||||
|
//@}
|
||||||
|
|
||||||
|
/// Tell whether the accumulator MSB was set high on this cycle.
|
||||||
|
bool msb_rising;
|
||||||
|
|
||||||
|
bool is6581; //-V730_NOINIT this is initialized in the SID constructor
|
||||||
|
|
||||||
|
private:
|
||||||
|
void clock_shift_register(unsigned int bit0);
|
||||||
|
|
||||||
|
unsigned int get_noise_writeback();
|
||||||
|
|
||||||
|
void write_shift_register();
|
||||||
|
|
||||||
|
void set_noise_output();
|
||||||
|
|
||||||
|
void set_no_noise_or_noise_output();
|
||||||
|
|
||||||
|
void waveBitfade();
|
||||||
|
|
||||||
|
void shiftregBitfade();
|
||||||
|
|
||||||
|
public:
|
||||||
|
void setWaveformModels(matrix_t* models);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the chip model.
|
||||||
|
* Must be called before any operation.
|
||||||
|
*
|
||||||
|
* @param is6581 true if MOS6581, false if CSG8580
|
||||||
|
*/
|
||||||
|
void setModel(bool is6581) { this->is6581 = is6581; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SID clocking.
|
||||||
|
*/
|
||||||
|
void clock();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize oscillators.
|
||||||
|
* This must be done after all the oscillators have been clock()'ed,
|
||||||
|
* so that they are in the same state.
|
||||||
|
*
|
||||||
|
* @param syncDest The oscillator that will be synced
|
||||||
|
* @param syncSource The sync source oscillator
|
||||||
|
*/
|
||||||
|
void synchronize(WaveformGenerator* syncDest, const WaveformGenerator* syncSource) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
WaveformGenerator() :
|
||||||
|
model_wave(nullptr),
|
||||||
|
wave(nullptr),
|
||||||
|
pw(0),
|
||||||
|
shift_register(0),
|
||||||
|
shift_pipeline(0),
|
||||||
|
ring_msb_mask(0),
|
||||||
|
no_noise(0),
|
||||||
|
noise_output(0),
|
||||||
|
no_noise_or_noise_output(0),
|
||||||
|
no_pulse(0),
|
||||||
|
pulse_output(0),
|
||||||
|
waveform(0),
|
||||||
|
waveform_output(0),
|
||||||
|
accumulator(0x555555), // Accumulator's even bits are high on powerup
|
||||||
|
freq(0),
|
||||||
|
tri_saw_pipeline(0x555),
|
||||||
|
osc3(0),
|
||||||
|
shift_register_reset(0),
|
||||||
|
floating_output_ttl(0),
|
||||||
|
test(false),
|
||||||
|
sync(false),
|
||||||
|
msb_rising(false) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write FREQ LO register.
|
||||||
|
*
|
||||||
|
* @param freq_lo low 8 bits of frequency
|
||||||
|
*/
|
||||||
|
void writeFREQ_LO(unsigned char freq_lo) { freq = (freq & 0xff00) | (freq_lo & 0xff); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write FREQ HI register.
|
||||||
|
*
|
||||||
|
* @param freq_hi high 8 bits of frequency
|
||||||
|
*/
|
||||||
|
void writeFREQ_HI(unsigned char freq_hi) { freq = (freq_hi << 8 & 0xff00) | (freq & 0xff); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write PW LO register.
|
||||||
|
*
|
||||||
|
* @param pw_lo low 8 bits of pulse width
|
||||||
|
*/
|
||||||
|
void writePW_LO(unsigned char pw_lo) { pw = (pw & 0xf00) | (pw_lo & 0x0ff); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write PW HI register.
|
||||||
|
*
|
||||||
|
* @param pw_hi high 8 bits of pulse width
|
||||||
|
*/
|
||||||
|
void writePW_HI(unsigned char pw_hi) { pw = (pw_hi << 8 & 0xf00) | (pw & 0x0ff); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write CONTROL REGISTER register.
|
||||||
|
*
|
||||||
|
* @param control control register value
|
||||||
|
*/
|
||||||
|
void writeCONTROL_REG(unsigned char control);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SID reset.
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 12-bit waveform output.
|
||||||
|
*
|
||||||
|
* @param ringModulator The oscillator ring-modulating current one.
|
||||||
|
* @return the waveform generator digital output
|
||||||
|
*/
|
||||||
|
unsigned int output(const WaveformGenerator* ringModulator);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read OSC3 value.
|
||||||
|
*/
|
||||||
|
unsigned char readOSC() const { return static_cast<unsigned char>(osc3 >> 4); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read accumulator value.
|
||||||
|
*/
|
||||||
|
unsigned int readAccumulator() const { return accumulator; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read freq value.
|
||||||
|
*/
|
||||||
|
unsigned int readFreq() const { return freq; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read test value.
|
||||||
|
*/
|
||||||
|
bool readTest() const { return test; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read sync value.
|
||||||
|
*/
|
||||||
|
bool readSync() const { return sync; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#if RESID_INLINING || defined(WAVEFORMGENERATOR_CPP)
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
RESID_INLINE
|
||||||
|
void WaveformGenerator::clock()
|
||||||
|
{
|
||||||
|
if (unlikely(test))
|
||||||
|
{
|
||||||
|
if (unlikely(shift_register_reset != 0) && unlikely(--shift_register_reset == 0))
|
||||||
|
{
|
||||||
|
shiftregBitfade();
|
||||||
|
|
||||||
|
// New noise waveform output.
|
||||||
|
set_noise_output();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The test bit sets pulse high.
|
||||||
|
pulse_output = 0xfff;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Calculate new accumulator value;
|
||||||
|
const unsigned int accumulator_old = accumulator;
|
||||||
|
accumulator = (accumulator + freq) & 0xffffff;
|
||||||
|
|
||||||
|
// Check which bit have changed
|
||||||
|
const unsigned int accumulator_bits_set = ~accumulator_old & accumulator;
|
||||||
|
|
||||||
|
// Check whether the MSB is set high. This is used for synchronization.
|
||||||
|
msb_rising = (accumulator_bits_set & 0x800000) != 0;
|
||||||
|
|
||||||
|
// Shift noise register once for each time accumulator bit 19 is set high.
|
||||||
|
// The shift is delayed 2 cycles.
|
||||||
|
if (unlikely((accumulator_bits_set & 0x080000) != 0))
|
||||||
|
{
|
||||||
|
// Pipeline: Detect rising bit, shift phase 1, shift phase 2.
|
||||||
|
shift_pipeline = 2;
|
||||||
|
}
|
||||||
|
else if (unlikely(shift_pipeline != 0) && --shift_pipeline == 0)
|
||||||
|
{
|
||||||
|
// bit0 = (bit22 | test) ^ bit17
|
||||||
|
clock_shift_register(((shift_register << 22) ^ (shift_register << 17)) & (1 << 22));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RESID_INLINE
|
||||||
|
unsigned int WaveformGenerator::output(const WaveformGenerator* ringModulator)
|
||||||
|
{
|
||||||
|
// Set output value.
|
||||||
|
if (likely(waveform != 0))
|
||||||
|
{
|
||||||
|
const unsigned int ix = (accumulator ^ (~ringModulator->accumulator & ring_msb_mask)) >> 12;
|
||||||
|
|
||||||
|
// The bit masks no_pulse and no_noise are used to achieve branch-free
|
||||||
|
// calculation of the output value.
|
||||||
|
waveform_output = wave[ix] & (no_pulse | pulse_output) & no_noise_or_noise_output;
|
||||||
|
|
||||||
|
// Triangle/Sawtooth output is delayed half cycle on 8580.
|
||||||
|
// This will appear as a one cycle delay on OSC3 as it is latched first phase of the clock.
|
||||||
|
if ((waveform & 3) && !is6581)
|
||||||
|
{
|
||||||
|
osc3 = tri_saw_pipeline & (no_pulse | pulse_output) & no_noise_or_noise_output;
|
||||||
|
tri_saw_pipeline = wave[ix];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
osc3 = waveform_output;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the 6581 the top bit of the accumulator may be driven low by combined waveforms
|
||||||
|
// when the sawtooth is selected
|
||||||
|
// FIXME doesn't seem to always happen
|
||||||
|
if ((waveform & 2) && unlikely(waveform & 0xd) && is6581)
|
||||||
|
accumulator &= (waveform_output << 12) | 0x7fffff;
|
||||||
|
|
||||||
|
write_shift_register();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Age floating DAC input.
|
||||||
|
if (likely(floating_output_ttl != 0) && unlikely(--floating_output_ttl == 0))
|
||||||
|
{
|
||||||
|
waveBitfade();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The pulse level is defined as (accumulator >> 12) >= pw ? 0xfff : 0x000.
|
||||||
|
// The expression -((accumulator >> 12) >= pw) & 0xfff yields the same
|
||||||
|
// results without any branching (and thus without any pipeline stalls).
|
||||||
|
// NB! This expression relies on that the result of a boolean expression
|
||||||
|
// is either 0 or 1, and furthermore requires two's complement integer.
|
||||||
|
// A few more cycles may be saved by storing the pulse width left shifted
|
||||||
|
// 12 bits, and dropping the and with 0xfff (this is valid since pulse is
|
||||||
|
// used as a bit mask on 12 bit values), yielding the expression
|
||||||
|
// -(accumulator >= pw24). However this only results in negligible savings.
|
||||||
|
|
||||||
|
// The result of the pulse width compare is delayed one cycle.
|
||||||
|
// Push next pulse level into pulse level pipeline.
|
||||||
|
pulse_output = ((accumulator >> 12) >= pw) ? 0xfff : 0x000;
|
||||||
|
|
||||||
|
return waveform_output;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
73
src/engine/platform/sound/c64_fp/array.h
Normal file
73
src/engine/platform/sound/c64_fp/array.h
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2011-2014 Leandro Nini
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARRAY_H
|
||||||
|
#define ARRAY_H
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counter.
|
||||||
|
*/
|
||||||
|
class counter
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
unsigned int c;
|
||||||
|
|
||||||
|
public:
|
||||||
|
counter() : c(1) {}
|
||||||
|
void increase() { ++c; }
|
||||||
|
unsigned int decrease() { return --c; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference counted pointer to matrix wrapper, for use with standard containers.
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
class matrix
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
T* data;
|
||||||
|
counter* count;
|
||||||
|
const unsigned int x, y;
|
||||||
|
|
||||||
|
public:
|
||||||
|
matrix(unsigned int x, unsigned int y) :
|
||||||
|
data(new T[x * y]),
|
||||||
|
count(new counter()),
|
||||||
|
x(x),
|
||||||
|
y(y) {}
|
||||||
|
|
||||||
|
matrix(const matrix& p) :
|
||||||
|
data(p.data),
|
||||||
|
count(p.count),
|
||||||
|
x(p.x),
|
||||||
|
y(p.y) { count->increase(); }
|
||||||
|
|
||||||
|
~matrix() { if (count->decrease() == 0) { delete count; delete [] data; } }
|
||||||
|
|
||||||
|
unsigned int length() const { return x * y; }
|
||||||
|
|
||||||
|
T* operator[](unsigned int a) { return &data[a * y]; }
|
||||||
|
|
||||||
|
T const* operator[](unsigned int a) const { return &data[a * y]; }
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef matrix<short> matrix_t;
|
||||||
|
|
||||||
|
#endif
|
86
src/engine/platform/sound/c64_fp/resample/Resampler.h
Normal file
86
src/engine/platform/sound/c64_fp/resample/Resampler.h
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RESAMPLER_H
|
||||||
|
#define RESAMPLER_H
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "../sidcxx11.h"
|
||||||
|
|
||||||
|
#include "../siddefs-fp.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction of a resampling process. Given enough input, produces output.
|
||||||
|
* Constructors take additional arguments that configure these objects.
|
||||||
|
*/
|
||||||
|
class Resampler
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
inline short softClip(int x) const
|
||||||
|
{
|
||||||
|
constexpr int threshold = 28000;
|
||||||
|
if (likely(x < threshold))
|
||||||
|
return x;
|
||||||
|
|
||||||
|
constexpr double t = threshold / 32768.;
|
||||||
|
constexpr double a = 1. - t;
|
||||||
|
constexpr double b = 1. / a;
|
||||||
|
|
||||||
|
double value = static_cast<double>(x - threshold) / 32768.;
|
||||||
|
value = t + a * tanh(b * value);
|
||||||
|
return static_cast<short>(value * 32768.);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int output() const = 0;
|
||||||
|
|
||||||
|
Resampler() {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~Resampler() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input a sample into resampler. Output "true" when resampler is ready with new sample.
|
||||||
|
*
|
||||||
|
* @param sample input sample
|
||||||
|
* @return true when a sample is ready
|
||||||
|
*/
|
||||||
|
virtual bool input(int sample) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output a sample from resampler.
|
||||||
|
*
|
||||||
|
* @return resampled sample
|
||||||
|
*/
|
||||||
|
short getOutput() const
|
||||||
|
{
|
||||||
|
return softClip(output());
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void reset() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
393
src/engine/platform/sound/c64_fp/resample/SincResampler.cpp
Executable file
393
src/engine/platform/sound/c64_fp/resample/SincResampler.cpp
Executable file
|
@ -0,0 +1,393 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "SincResampler.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cmath>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "../siddefs-fp.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_EMMINTRIN_H
|
||||||
|
# include <emmintrin.h>
|
||||||
|
#elif defined HAVE_MMINTRIN_H
|
||||||
|
# include <mmintrin.h>
|
||||||
|
#elif defined(HAVE_ARM_NEON_H)
|
||||||
|
# include <arm_neon.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
typedef std::map<std::string, matrix_t> fir_cache_t;
|
||||||
|
|
||||||
|
/// Cache for the expensive FIR table computation results.
|
||||||
|
fir_cache_t FIR_CACHE;
|
||||||
|
|
||||||
|
/// Maximum error acceptable in I0 is 1e-6, or ~96 dB.
|
||||||
|
const double I0E = 1e-6;
|
||||||
|
|
||||||
|
const int BITS = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the 0th order modified Bessel function of the first kind.
|
||||||
|
* This function is originally from resample-1.5/filterkit.c by J. O. Smith.
|
||||||
|
* It is used to build the Kaiser window for resampling.
|
||||||
|
*
|
||||||
|
* @param x evaluate I0 at x
|
||||||
|
* @return value of I0 at x.
|
||||||
|
*/
|
||||||
|
double I0(double x)
|
||||||
|
{
|
||||||
|
double sum = 1.;
|
||||||
|
double u = 1.;
|
||||||
|
double n = 1.;
|
||||||
|
const double halfx = x / 2.;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
const double temp = halfx / n;
|
||||||
|
u *= temp * temp;
|
||||||
|
sum += u;
|
||||||
|
n += 1.;
|
||||||
|
}
|
||||||
|
while (u >= I0E * sum);
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate convolution with sample and sinc.
|
||||||
|
*
|
||||||
|
* @param a sample buffer input
|
||||||
|
* @param b sinc buffer
|
||||||
|
* @param bLength length of the sinc buffer
|
||||||
|
* @return convolved result
|
||||||
|
*/
|
||||||
|
int convolve(const short* a, const short* b, int bLength)
|
||||||
|
{
|
||||||
|
#ifdef HAVE_EMMINTRIN_H
|
||||||
|
int out = 0;
|
||||||
|
|
||||||
|
const uintptr_t offset = (uintptr_t)(a) & 0x0f;
|
||||||
|
|
||||||
|
// check for aligned accesses
|
||||||
|
if (offset == ((uintptr_t)(b) & 0x0f))
|
||||||
|
{
|
||||||
|
if (offset)
|
||||||
|
{
|
||||||
|
const int l = (0x10 - offset)/2;
|
||||||
|
|
||||||
|
for (int i = 0; i < l; i++)
|
||||||
|
{
|
||||||
|
out += *a++ * *b++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bLength -= offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
__m128i acc = _mm_setzero_si128();
|
||||||
|
|
||||||
|
const int n = bLength / 8;
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
const __m128i tmp = _mm_madd_epi16(*(__m128i*)a, *(__m128i*)b);
|
||||||
|
acc = _mm_add_epi16(acc, tmp);
|
||||||
|
a += 8;
|
||||||
|
b += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
__m128i vsum = _mm_add_epi32(acc, _mm_srli_si128(acc, 8));
|
||||||
|
vsum = _mm_add_epi32(vsum, _mm_srli_si128(vsum, 4));
|
||||||
|
out += _mm_cvtsi128_si32(vsum);
|
||||||
|
|
||||||
|
bLength &= 7;
|
||||||
|
}
|
||||||
|
#elif defined HAVE_MMINTRIN_H
|
||||||
|
__m64 acc = _mm_setzero_si64();
|
||||||
|
|
||||||
|
const int n = bLength / 4;
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
const __m64 tmp = _mm_madd_pi16(*(__m64*)a, *(__m64*)b);
|
||||||
|
acc = _mm_add_pi16(acc, tmp);
|
||||||
|
a += 4;
|
||||||
|
b += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
int out = _mm_cvtsi64_si32(acc) + _mm_cvtsi64_si32(_mm_srli_si64(acc, 32));
|
||||||
|
_mm_empty();
|
||||||
|
|
||||||
|
bLength &= 3;
|
||||||
|
#elif defined(HAVE_ARM_NEON_H)
|
||||||
|
#if (defined(__arm64__) && defined(__APPLE__)) || defined(__aarch64__)
|
||||||
|
int32x4_t acc1Low = vdupq_n_s32(0);
|
||||||
|
int32x4_t acc1High = vdupq_n_s32(0);
|
||||||
|
int32x4_t acc2Low = vdupq_n_s32(0);
|
||||||
|
int32x4_t acc2High = vdupq_n_s32(0);
|
||||||
|
|
||||||
|
const int n = bLength / 16;
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
int16x8_t v11 = vld1q_s16(a);
|
||||||
|
int16x8_t v12 = vld1q_s16(a + 8);
|
||||||
|
int16x8_t v21 = vld1q_s16(b);
|
||||||
|
int16x8_t v22 = vld1q_s16(b + 8);
|
||||||
|
|
||||||
|
acc1Low = vmlal_s16(acc1Low, vget_low_s16(v11), vget_low_s16(v21));
|
||||||
|
acc1High = vmlal_high_s16(acc1High, v11, v21);
|
||||||
|
acc2Low = vmlal_s16(acc2Low, vget_low_s16(v12), vget_low_s16(v22));
|
||||||
|
acc2High = vmlal_high_s16(acc2High, v12, v22);
|
||||||
|
|
||||||
|
a += 16;
|
||||||
|
b += 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
bLength &= 15;
|
||||||
|
|
||||||
|
if (bLength >= 8)
|
||||||
|
{
|
||||||
|
int16x8_t v1 = vld1q_s16(a);
|
||||||
|
int16x8_t v2 = vld1q_s16(b);
|
||||||
|
|
||||||
|
acc1Low = vmlal_s16(acc1Low, vget_low_s16(v1), vget_low_s16(v2));
|
||||||
|
acc1High = vmlal_high_s16(acc1High, v1, v2);
|
||||||
|
|
||||||
|
a += 8;
|
||||||
|
b += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
bLength &= 7;
|
||||||
|
|
||||||
|
if (bLength >= 4)
|
||||||
|
{
|
||||||
|
int16x4_t v1 = vld1_s16(a);
|
||||||
|
int16x4_t v2 = vld1_s16(b);
|
||||||
|
|
||||||
|
acc1Low = vmlal_s16(acc1Low, v1, v2);
|
||||||
|
|
||||||
|
a += 4;
|
||||||
|
b += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32x4_t accSumsNeon = vaddq_s32(acc1Low, acc1High);
|
||||||
|
accSumsNeon = vaddq_s32(accSumsNeon, acc2Low);
|
||||||
|
accSumsNeon = vaddq_s32(accSumsNeon, acc2High);
|
||||||
|
|
||||||
|
int out = vaddvq_s32(accSumsNeon);
|
||||||
|
|
||||||
|
bLength &= 3;
|
||||||
|
#else
|
||||||
|
int32x4_t acc = vdupq_n_s32(0);
|
||||||
|
|
||||||
|
const int n = bLength / 4;
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
const int16x4_t h_vec = vld1_s16(a);
|
||||||
|
const int16x4_t x_vec = vld1_s16(b);
|
||||||
|
acc = vmlal_s16(acc, h_vec, x_vec);
|
||||||
|
a += 4;
|
||||||
|
b += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
int out = vgetq_lane_s32(acc, 0) +
|
||||||
|
vgetq_lane_s32(acc, 1) +
|
||||||
|
vgetq_lane_s32(acc, 2) +
|
||||||
|
vgetq_lane_s32(acc, 3);
|
||||||
|
|
||||||
|
bLength &= 3;
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
int out = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (int i = 0; i < bLength; i++)
|
||||||
|
{
|
||||||
|
out += *a++ * *b++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (out + (1 << 14)) >> 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SincResampler::fir(int subcycle)
|
||||||
|
{
|
||||||
|
// Find the first of the nearest fir tables close to the phase
|
||||||
|
int firTableFirst = (subcycle * firRES >> 10);
|
||||||
|
const int firTableOffset = (subcycle * firRES) & 0x3ff;
|
||||||
|
|
||||||
|
// Find firN most recent samples, plus one extra in case the FIR wraps.
|
||||||
|
int sampleStart = sampleIndex - firN + RINGSIZE - 1;
|
||||||
|
|
||||||
|
const int v1 = convolve(sample + sampleStart, (*firTable)[firTableFirst], firN);
|
||||||
|
|
||||||
|
// Use next FIR table, wrap around to first FIR table using
|
||||||
|
// previous sample.
|
||||||
|
if (unlikely(++firTableFirst == firRES))
|
||||||
|
{
|
||||||
|
firTableFirst = 0;
|
||||||
|
++sampleStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int v2 = convolve(sample + sampleStart, (*firTable)[firTableFirst], firN);
|
||||||
|
|
||||||
|
// Linear interpolation between the sinc tables yields good
|
||||||
|
// approximation for the exact value.
|
||||||
|
return v1 + (firTableOffset * (v2 - v1) >> 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
SincResampler::SincResampler(double clockFrequency, double samplingFrequency, double highestAccurateFrequency) :
|
||||||
|
sampleIndex(0),
|
||||||
|
cyclesPerSample(static_cast<int>(clockFrequency / samplingFrequency * 1024.)),
|
||||||
|
sampleOffset(0),
|
||||||
|
outputValue(0)
|
||||||
|
{
|
||||||
|
// 16 bits -> -96dB stopband attenuation.
|
||||||
|
const double A = -20. * log10(1.0 / (1 << BITS));
|
||||||
|
// A fraction of the bandwidth is allocated to the transition band, which we double
|
||||||
|
// because we design the filter to transition halfway at nyquist.
|
||||||
|
const double dw = (1. - 2.*highestAccurateFrequency / samplingFrequency) * M_PI * 2.;
|
||||||
|
|
||||||
|
// For calculation of beta and N see the reference for the kaiserord
|
||||||
|
// function in the MATLAB Signal Processing Toolbox:
|
||||||
|
// http://www.mathworks.com/help/signal/ref/kaiserord.html
|
||||||
|
const double beta = 0.1102 * (A - 8.7);
|
||||||
|
const double I0beta = I0(beta);
|
||||||
|
const double cyclesPerSampleD = clockFrequency / samplingFrequency;
|
||||||
|
|
||||||
|
{
|
||||||
|
// The filter order will maximally be 124 with the current constraints.
|
||||||
|
// N >= (96.33 - 7.95)/(2 * pi * 2.285 * (maxfreq - passbandfreq) >= 123
|
||||||
|
// The filter order is equal to the number of zero crossings, i.e.
|
||||||
|
// it should be an even number (sinc is symmetric with respect to x = 0).
|
||||||
|
int N = static_cast<int>((A - 7.95) / (2.285 * dw) + 0.5);
|
||||||
|
N += N & 1;
|
||||||
|
|
||||||
|
// The filter length is equal to the filter order + 1.
|
||||||
|
// The filter length must be an odd number (sinc is symmetric with respect to
|
||||||
|
// x = 0).
|
||||||
|
firN = static_cast<int>(N * cyclesPerSampleD) + 1;
|
||||||
|
firN |= 1;
|
||||||
|
|
||||||
|
// Check whether the sample ring buffer would overflow.
|
||||||
|
assert(firN < RINGSIZE);
|
||||||
|
|
||||||
|
// Error is bounded by err < 1.234 / L^2, so L = sqrt(1.234 / (2^-16)) = sqrt(1.234 * 2^16).
|
||||||
|
firRES = static_cast<int>(ceil(sqrt(1.234 * (1 << BITS)) / cyclesPerSampleD));
|
||||||
|
|
||||||
|
// firN*firRES represent the total resolution of the sinc sampling. JOS
|
||||||
|
// recommends a length of 2^BITS, but we don't quite use that good a filter.
|
||||||
|
// The filter test program indicates that the filter performs well, though.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the map key
|
||||||
|
std::ostringstream o;
|
||||||
|
o << firN << "," << firRES << "," << cyclesPerSampleD;
|
||||||
|
const std::string firKey = o.str();
|
||||||
|
fir_cache_t::iterator lb = FIR_CACHE.lower_bound(firKey);
|
||||||
|
|
||||||
|
// The FIR computation is expensive and we set sampling parameters often, but
|
||||||
|
// from a very small set of choices. Thus, caching is used to speed initialization.
|
||||||
|
if (lb != FIR_CACHE.end() && !(FIR_CACHE.key_comp()(firKey, lb->first)))
|
||||||
|
{
|
||||||
|
firTable = &(lb->second);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Allocate memory for FIR tables.
|
||||||
|
matrix_t tempTable(firRES, firN);
|
||||||
|
#ifdef HAVE_CXX11
|
||||||
|
firTable = &(FIR_CACHE.emplace_hint(lb, fir_cache_t::value_type(firKey, tempTable))->second);
|
||||||
|
#else
|
||||||
|
firTable = &(FIR_CACHE.insert(lb, fir_cache_t::value_type(firKey, tempTable))->second);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// The cutoff frequency is midway through the transition band, in effect the same as nyquist.
|
||||||
|
const double wc = M_PI;
|
||||||
|
|
||||||
|
// Calculate the sinc tables.
|
||||||
|
const double scale = 32768.0 * wc / cyclesPerSampleD / M_PI;
|
||||||
|
|
||||||
|
// we're not interested in the fractional part
|
||||||
|
// so use int division before converting to double
|
||||||
|
const int tmp = firN / 2;
|
||||||
|
const double firN_2 = static_cast<double>(tmp);
|
||||||
|
|
||||||
|
for (int i = 0; i < firRES; i++)
|
||||||
|
{
|
||||||
|
const double jPhase = (double) i / firRES + firN_2;
|
||||||
|
|
||||||
|
for (int j = 0; j < firN; j++)
|
||||||
|
{
|
||||||
|
const double x = j - jPhase;
|
||||||
|
|
||||||
|
const double xt = x / firN_2;
|
||||||
|
const double kaiserXt = fabs(xt) < 1. ? I0(beta * sqrt(1. - xt * xt)) / I0beta : 0.;
|
||||||
|
|
||||||
|
const double wt = wc * x / cyclesPerSampleD;
|
||||||
|
const double sincWt = fabs(wt) >= 1e-8 ? sin(wt) / wt : 1.;
|
||||||
|
|
||||||
|
(*firTable)[i][j] = static_cast<short>(scale * sincWt * kaiserXt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SincResampler::input(int input)
|
||||||
|
{
|
||||||
|
bool ready = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clip the input as it may overflow the 16 bit range.
|
||||||
|
*
|
||||||
|
* Approximate measured input ranges:
|
||||||
|
* 6581: [-24262,+25080] (Kawasaki_Synthesizer_Demo)
|
||||||
|
* 8580: [-21514,+35232] (64_Forever, Drum_Fool)
|
||||||
|
*/
|
||||||
|
sample[sampleIndex] = sample[sampleIndex + RINGSIZE] = softClip(input);
|
||||||
|
sampleIndex = (sampleIndex + 1) & (RINGSIZE - 1);
|
||||||
|
|
||||||
|
if (sampleOffset < 1024)
|
||||||
|
{
|
||||||
|
outputValue = fir(sampleOffset);
|
||||||
|
ready = true;
|
||||||
|
sampleOffset += cyclesPerSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
sampleOffset -= 1024;
|
||||||
|
|
||||||
|
return ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SincResampler::reset()
|
||||||
|
{
|
||||||
|
memset(sample, 0, sizeof(sample));
|
||||||
|
sampleOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
114
src/engine/platform/sound/c64_fp/resample/SincResampler.h
Normal file
114
src/engine/platform/sound/c64_fp/resample/SincResampler.h
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2013 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SINCRESAMPLER_H
|
||||||
|
#define SINCRESAMPLER_H
|
||||||
|
|
||||||
|
#include "Resampler.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "../array.h"
|
||||||
|
|
||||||
|
#include "../sidcxx11.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the theoretically correct (and computationally intensive) audio sample generation.
|
||||||
|
* The samples are generated by resampling to the specified sampling frequency.
|
||||||
|
* The work rate is inversely proportional to the percentage of the bandwidth
|
||||||
|
* allocated to the filter transition band.
|
||||||
|
*
|
||||||
|
* This implementation is based on the paper "A Flexible Sampling-Rate Conversion Method",
|
||||||
|
* by J. O. Smith and P. Gosset, or rather on the expanded tutorial on the
|
||||||
|
* [Digital Audio Resampling Home Page](http://www-ccrma.stanford.edu/~jos/resample/).
|
||||||
|
*
|
||||||
|
* By building shifted FIR tables with samples according to the sampling frequency,
|
||||||
|
* this implementation dramatically reduces the computational effort in the
|
||||||
|
* filter convolutions, without any loss of accuracy.
|
||||||
|
* The filter convolutions are also vectorizable on current hardware.
|
||||||
|
*/
|
||||||
|
class SincResampler final : public Resampler
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
/// Size of the ring buffer, must be a power of 2
|
||||||
|
static const int RINGSIZE = 2048;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Table of the fir filter coefficients
|
||||||
|
matrix_t* firTable;
|
||||||
|
|
||||||
|
int sampleIndex;
|
||||||
|
|
||||||
|
/// Filter resolution
|
||||||
|
int firRES;
|
||||||
|
|
||||||
|
/// Filter length
|
||||||
|
int firN;
|
||||||
|
|
||||||
|
const int cyclesPerSample;
|
||||||
|
|
||||||
|
int sampleOffset;
|
||||||
|
|
||||||
|
int outputValue;
|
||||||
|
|
||||||
|
short sample[RINGSIZE * 2];
|
||||||
|
|
||||||
|
private:
|
||||||
|
int fir(int subcycle);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Use a clock freqency of 985248Hz for PAL C64, 1022730Hz for NTSC C64.
|
||||||
|
* The default end of passband frequency is pass_freq = 0.9*sample_freq/2
|
||||||
|
* for sample frequencies up to ~ 44.1kHz, and 20kHz for higher sample frequencies.
|
||||||
|
*
|
||||||
|
* For resampling, the ratio between the clock frequency and the sample frequency
|
||||||
|
* is limited as follows: 125*clock_freq/sample_freq < 16384
|
||||||
|
* E.g. provided a clock frequency of ~ 1MHz, the sample frequency
|
||||||
|
* can not be set lower than ~ 8kHz.
|
||||||
|
* A lower sample frequency would make the resampling code overfill its 16k sample ring buffer.
|
||||||
|
*
|
||||||
|
* The end of passband frequency is also limited: pass_freq <= 0.9*sample_freq/2
|
||||||
|
*
|
||||||
|
* E.g. for a 44.1kHz sampling rate the end of passband frequency is limited
|
||||||
|
* to slightly below 20kHz. This constraint ensures that the FIR table is not overfilled.
|
||||||
|
*
|
||||||
|
* @param clockFrequency System clock frequency at Hz
|
||||||
|
* @param samplingFrequency Desired output sampling rate
|
||||||
|
* @param highestAccurateFrequency
|
||||||
|
*/
|
||||||
|
SincResampler(double clockFrequency, double samplingFrequency, double highestAccurateFrequency);
|
||||||
|
|
||||||
|
bool input(int input) override;
|
||||||
|
|
||||||
|
int output() const override { return outputValue; }
|
||||||
|
|
||||||
|
void reset() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2015 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TWOPASSSINCRESAMPLER_H
|
||||||
|
#define TWOPASSSINCRESAMPLER_H
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "Resampler.h"
|
||||||
|
#include "SincResampler.h"
|
||||||
|
|
||||||
|
#include "../sidcxx11.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose a more efficient SINC from chaining two other SINCs.
|
||||||
|
*/
|
||||||
|
class TwoPassSincResampler final : public Resampler
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::unique_ptr<SincResampler> const s1;
|
||||||
|
std::unique_ptr<SincResampler> const s2;
|
||||||
|
|
||||||
|
private:
|
||||||
|
TwoPassSincResampler(double clockFrequency, double samplingFrequency, double highestAccurateFrequency, double intermediateFrequency) :
|
||||||
|
s1(new SincResampler(clockFrequency, intermediateFrequency, highestAccurateFrequency)),
|
||||||
|
s2(new SincResampler(intermediateFrequency, samplingFrequency, highestAccurateFrequency))
|
||||||
|
{}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Named constructor
|
||||||
|
static TwoPassSincResampler* create(double clockFrequency, double samplingFrequency, double highestAccurateFrequency)
|
||||||
|
{
|
||||||
|
// Calculation according to Laurent Ganier. It evaluates to about 120 kHz at typical settings.
|
||||||
|
// Some testing around the chosen value seems to confirm that this does work.
|
||||||
|
double const intermediateFrequency = 2. * highestAccurateFrequency
|
||||||
|
+ sqrt(2. * highestAccurateFrequency * clockFrequency
|
||||||
|
* (samplingFrequency - 2. * highestAccurateFrequency) / samplingFrequency);
|
||||||
|
return new TwoPassSincResampler(clockFrequency, samplingFrequency, highestAccurateFrequency, intermediateFrequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool input(int sample) override
|
||||||
|
{
|
||||||
|
return s1->input(sample) && s2->input(s1->output());
|
||||||
|
}
|
||||||
|
|
||||||
|
int output() const override
|
||||||
|
{
|
||||||
|
return s2->output();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() override
|
||||||
|
{
|
||||||
|
s1->reset();
|
||||||
|
s2->reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2011-2013 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ZEROORDER_RESAMPLER_H
|
||||||
|
#define ZEROORDER_RESAMPLER_H
|
||||||
|
|
||||||
|
#include "Resampler.h"
|
||||||
|
|
||||||
|
#include "../sidcxx11.h"
|
||||||
|
|
||||||
|
namespace reSIDfp
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return sample with linear interpolation.
|
||||||
|
*
|
||||||
|
* @author Antti Lankila
|
||||||
|
*/
|
||||||
|
class ZeroOrderResampler final : public Resampler
|
||||||
|
{
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Last sample
|
||||||
|
int cachedSample;
|
||||||
|
|
||||||
|
/// Number of cycles per sample
|
||||||
|
const int cyclesPerSample;
|
||||||
|
|
||||||
|
int sampleOffset;
|
||||||
|
|
||||||
|
/// Calculated sample
|
||||||
|
int outputValue;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ZeroOrderResampler(double clockFrequency, double samplingFrequency) :
|
||||||
|
cachedSample(0),
|
||||||
|
cyclesPerSample(static_cast<int>(clockFrequency / samplingFrequency * 1024.)),
|
||||||
|
sampleOffset(0),
|
||||||
|
outputValue(0) {}
|
||||||
|
|
||||||
|
bool input(int sample) override
|
||||||
|
{
|
||||||
|
bool ready = false;
|
||||||
|
|
||||||
|
if (sampleOffset < 1024)
|
||||||
|
{
|
||||||
|
outputValue = cachedSample + (sampleOffset * (sample - cachedSample) >> 10);
|
||||||
|
ready = true;
|
||||||
|
sampleOffset += cyclesPerSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
sampleOffset -= 1024;
|
||||||
|
|
||||||
|
cachedSample = sample;
|
||||||
|
|
||||||
|
return ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
int output() const override { return outputValue; }
|
||||||
|
|
||||||
|
void reset() override
|
||||||
|
{
|
||||||
|
sampleOffset = 0;
|
||||||
|
cachedSample = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace reSIDfp
|
||||||
|
|
||||||
|
#endif
|
87
src/engine/platform/sound/c64_fp/resample/test.cpp
Normal file
87
src/engine/platform/sound/c64_fp/resample/test.cpp
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2012-2013 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||||
|
* Copyright 2007-2010 Antti Lankila
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <ctime>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "siddefs-fp.h"
|
||||||
|
|
||||||
|
#include "Resampler.h"
|
||||||
|
#include "TwoPassSincResampler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple sin waveform in, power output measurement function.
|
||||||
|
* It would be far better to use FFT.
|
||||||
|
*/
|
||||||
|
int main(int argc, const char* argv[])
|
||||||
|
{
|
||||||
|
const double RATE = 985248.4;
|
||||||
|
const int RINGSIZE = 2048;
|
||||||
|
|
||||||
|
std::auto_ptr<reSIDfp::TwoPassSincResampler> r(reSIDfp::TwoPassSincResampler::create(RATE, 48000.0, 20000.0));
|
||||||
|
|
||||||
|
std::map<double, double> results;
|
||||||
|
clock_t start = clock();
|
||||||
|
|
||||||
|
for (double freq = 1000.; freq < RATE / 2.; freq *= 1.01)
|
||||||
|
{
|
||||||
|
/* prefill resampler buffer */
|
||||||
|
int k = 0;
|
||||||
|
double omega = 2 * M_PI * freq / RATE;
|
||||||
|
|
||||||
|
for (int j = 0; j < RINGSIZE; j ++)
|
||||||
|
{
|
||||||
|
int signal = static_cast<int>(32768.0 * sin(k++ * omega) * sqrt(2));
|
||||||
|
r->input(signal);
|
||||||
|
}
|
||||||
|
|
||||||
|
int n = 0;
|
||||||
|
float pwr = 0;
|
||||||
|
|
||||||
|
/* Now, during measurement stage, put 100 cycles of waveform through filter. */
|
||||||
|
for (int j = 0; j < 100000; j ++)
|
||||||
|
{
|
||||||
|
int signal = static_cast<int>(32768.0 * sin(k++ * omega) * sqrt(2));
|
||||||
|
|
||||||
|
if (r->input(signal))
|
||||||
|
{
|
||||||
|
float out = r->output();
|
||||||
|
pwr += out * out;
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results.insert(std::make_pair(freq, 10 * log10(pwr / n)));
|
||||||
|
}
|
||||||
|
|
||||||
|
clock_t end = clock();
|
||||||
|
|
||||||
|
for (std::map<double, double>::iterator it = results.begin(); it != results.end(); ++it)
|
||||||
|
{
|
||||||
|
std::cout << std::fixed << std::setprecision(0) << std::setw(6) << (*it).first << " Hz " << (*it).second << " dB" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Filtering time " << (end - start) * 1000. / CLOCKS_PER_SEC << " ms" << std::endl;
|
||||||
|
}
|
29
src/engine/platform/sound/c64_fp/sidcxx11.h
Normal file
29
src/engine/platform/sound/c64_fp/sidcxx11.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2014-2015 Leandro Nini
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SIDCXX11_H
|
||||||
|
#define SIDCXX11_H
|
||||||
|
|
||||||
|
#define DEFAULT = default
|
||||||
|
#define DELETE = delete
|
||||||
|
|
||||||
|
#define HAVE_CXX11
|
||||||
|
|
||||||
|
#endif
|
29
src/engine/platform/sound/c64_fp/sidcxx14.h
Normal file
29
src/engine/platform/sound/c64_fp/sidcxx14.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* This file is part of libsidplayfp, a SID player engine.
|
||||||
|
*
|
||||||
|
* Copyright 2014-2015 Leandro Nini
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SIDCXX14_H
|
||||||
|
#define SIDCXX14_H
|
||||||
|
|
||||||
|
#include "sidcxx11.h"
|
||||||
|
|
||||||
|
#define MAKE_UNIQUE(type, ...) std::make_unique<type>(__VA_ARGS__)
|
||||||
|
#define HAVE_CXX14
|
||||||
|
|
||||||
|
#endif
|
62
src/engine/platform/sound/c64_fp/siddefs-fp.h
Normal file
62
src/engine/platform/sound/c64_fp/siddefs-fp.h
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// This file is part of reSID, a MOS6581 SID emulator engine.
|
||||||
|
// Copyright (C) 1999 Dag Lem <resid@nimrod.no>
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#ifndef SIDDEFS_FP_H
|
||||||
|
#define SIDDEFS_FP_H
|
||||||
|
|
||||||
|
// Compilation configuration.
|
||||||
|
#define RESID_BRANCH_HINTS 0
|
||||||
|
|
||||||
|
// Compiler specifics.
|
||||||
|
#define HAVE_BUILTIN_EXPECT 0
|
||||||
|
|
||||||
|
#ifndef M_PI
|
||||||
|
# define M_PI 3.14159265358979323846
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Branch prediction macros, lifted off the Linux kernel.
|
||||||
|
#if RESID_BRANCH_HINTS && HAVE_BUILTIN_EXPECT
|
||||||
|
# define likely(x) __builtin_expect(!!(x), 1)
|
||||||
|
# define unlikely(x) __builtin_expect(!!(x), 0)
|
||||||
|
#else
|
||||||
|
# define likely(x) (x)
|
||||||
|
# define unlikely(x) (x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace reSIDfp {
|
||||||
|
|
||||||
|
typedef enum { MOS6581=1, MOS8580 } ChipModel;
|
||||||
|
|
||||||
|
typedef enum { DECIMATE=1, RESAMPLE } SamplingMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#ifndef __VERSION_CC__
|
||||||
|
extern const char* residfp_version_string;
|
||||||
|
#else
|
||||||
|
const char* residfp_version_string = "furnace";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inlining on/off.
|
||||||
|
#define RESID_INLINING 1
|
||||||
|
#define RESID_INLINE inline
|
||||||
|
|
||||||
|
#endif // SIDDEFS_FP_H
|
21
src/engine/platform/sound/c64_fp/version.cc
Normal file
21
src/engine/platform/sound/c64_fp/version.cc
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// This file is part of reSID, a MOS6581 SID emulator engine.
|
||||||
|
// Copyright (C) 2004 Dag Lem <resid@nimrod.no>
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#define __VERSION_CC__
|
||||||
|
#include "siddefs-fp.h"
|
|
@ -133,6 +133,8 @@ const char* aboutLine[]={
|
||||||
"puNES (NES, MMC5 and FDS) by FHorse",
|
"puNES (NES, MMC5 and FDS) by FHorse",
|
||||||
"NSFPlay (NES and FDS) by Brad Smith and Brezza",
|
"NSFPlay (NES and FDS) by Brad Smith and Brezza",
|
||||||
"reSID by Dag Lem",
|
"reSID by Dag Lem",
|
||||||
|
"reSIDfp by Dag Lem, Antti Lankila",
|
||||||
|
"and Leandro Nini",
|
||||||
"Stella by Stella Team",
|
"Stella by Stella Team",
|
||||||
"QSound emulator by superctr and Valley Bell",
|
"QSound emulator by superctr and Valley Bell",
|
||||||
"VICE VIC-20 sound core by Rami Rasanen and viznut",
|
"VICE VIC-20 sound core by Rami Rasanen and viznut",
|
||||||
|
|
|
@ -1049,6 +1049,7 @@ class FurnaceGUI {
|
||||||
int snCore;
|
int snCore;
|
||||||
int nesCore;
|
int nesCore;
|
||||||
int fdsCore;
|
int fdsCore;
|
||||||
|
int c64Core;
|
||||||
int pcSpeakerOutMethod;
|
int pcSpeakerOutMethod;
|
||||||
String yrw801Path;
|
String yrw801Path;
|
||||||
String tg100Path;
|
String tg100Path;
|
||||||
|
@ -1168,6 +1169,7 @@ class FurnaceGUI {
|
||||||
snCore(0),
|
snCore(0),
|
||||||
nesCore(0),
|
nesCore(0),
|
||||||
fdsCore(0),
|
fdsCore(0),
|
||||||
|
c64Core(1),
|
||||||
pcSpeakerOutMethod(0),
|
pcSpeakerOutMethod(0),
|
||||||
yrw801Path(""),
|
yrw801Path(""),
|
||||||
tg100Path(""),
|
tg100Path(""),
|
||||||
|
|
|
@ -97,6 +97,11 @@ const char* nesCores[]={
|
||||||
"NSFplay"
|
"NSFplay"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const char* c64Cores[]={
|
||||||
|
"reSID",
|
||||||
|
"reSIDfp"
|
||||||
|
};
|
||||||
|
|
||||||
const char* pcspkrOutMethods[]={
|
const char* pcspkrOutMethods[]={
|
||||||
"evdev SND_TONE",
|
"evdev SND_TONE",
|
||||||
"KIOCSOUND on /dev/tty1",
|
"KIOCSOUND on /dev/tty1",
|
||||||
|
@ -972,6 +977,10 @@ void FurnaceGUI::drawSettings() {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Combo("##FDSCore",&settings.fdsCore,nesCores,2);
|
ImGui::Combo("##FDSCore",&settings.fdsCore,nesCores,2);
|
||||||
|
|
||||||
|
ImGui::Text("SID core");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Combo("##C64Core",&settings.c64Core,c64Cores,2);
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
ImGui::Text("PC Speaker strategy");
|
ImGui::Text("PC Speaker strategy");
|
||||||
|
@ -2155,6 +2164,7 @@ void FurnaceGUI::syncSettings() {
|
||||||
settings.snCore=e->getConfInt("snCore",0);
|
settings.snCore=e->getConfInt("snCore",0);
|
||||||
settings.nesCore=e->getConfInt("nesCore",0);
|
settings.nesCore=e->getConfInt("nesCore",0);
|
||||||
settings.fdsCore=e->getConfInt("fdsCore",0);
|
settings.fdsCore=e->getConfInt("fdsCore",0);
|
||||||
|
settings.c64Core=e->getConfInt("c64Core",1);
|
||||||
settings.pcSpeakerOutMethod=e->getConfInt("pcSpeakerOutMethod",0);
|
settings.pcSpeakerOutMethod=e->getConfInt("pcSpeakerOutMethod",0);
|
||||||
settings.yrw801Path=e->getConfString("yrw801Path","");
|
settings.yrw801Path=e->getConfString("yrw801Path","");
|
||||||
settings.tg100Path=e->getConfString("tg100Path","");
|
settings.tg100Path=e->getConfString("tg100Path","");
|
||||||
|
@ -2267,6 +2277,7 @@ void FurnaceGUI::syncSettings() {
|
||||||
clampSetting(settings.snCore,0,1);
|
clampSetting(settings.snCore,0,1);
|
||||||
clampSetting(settings.nesCore,0,1);
|
clampSetting(settings.nesCore,0,1);
|
||||||
clampSetting(settings.fdsCore,0,1);
|
clampSetting(settings.fdsCore,0,1);
|
||||||
|
clampSetting(settings.c64Core,0,1);
|
||||||
clampSetting(settings.pcSpeakerOutMethod,0,4);
|
clampSetting(settings.pcSpeakerOutMethod,0,4);
|
||||||
clampSetting(settings.mainFont,0,6);
|
clampSetting(settings.mainFont,0,6);
|
||||||
clampSetting(settings.patFont,0,6);
|
clampSetting(settings.patFont,0,6);
|
||||||
|
@ -2402,6 +2413,7 @@ void FurnaceGUI::commitSettings() {
|
||||||
e->setConf("snCore",settings.snCore);
|
e->setConf("snCore",settings.snCore);
|
||||||
e->setConf("nesCore",settings.nesCore);
|
e->setConf("nesCore",settings.nesCore);
|
||||||
e->setConf("fdsCore",settings.fdsCore);
|
e->setConf("fdsCore",settings.fdsCore);
|
||||||
|
e->setConf("c64Core",settings.c64Core);
|
||||||
e->setConf("pcSpeakerOutMethod",settings.pcSpeakerOutMethod);
|
e->setConf("pcSpeakerOutMethod",settings.pcSpeakerOutMethod);
|
||||||
e->setConf("yrw801Path",settings.yrw801Path);
|
e->setConf("yrw801Path",settings.yrw801Path);
|
||||||
e->setConf("tg100Path",settings.tg100Path);
|
e->setConf("tg100Path",settings.tg100Path);
|
||||||
|
|
|
@ -181,6 +181,7 @@ TAParamResult pVersion(String) {
|
||||||
printf("- puNES by FHorse (GPLv2)\n");
|
printf("- puNES by FHorse (GPLv2)\n");
|
||||||
printf("- NSFPlay by Brad Smith and Brezza (unknown open-source license)\n");
|
printf("- NSFPlay by Brad Smith and Brezza (unknown open-source license)\n");
|
||||||
printf("- reSID by Dag Lem (GPLv2)\n");
|
printf("- reSID by Dag Lem (GPLv2)\n");
|
||||||
|
printf("- reSIDfp by Dag Lem, Antti Lankila and Leandro Nini (GPLv2)\n");
|
||||||
printf("- Stella by Stella Team (GPLv2)\n");
|
printf("- Stella by Stella Team (GPLv2)\n");
|
||||||
printf("- vgsound_emu (first version) by cam900 (BSD 3-clause)\n");
|
printf("- vgsound_emu (first version) by cam900 (BSD 3-clause)\n");
|
||||||
return TA_PARAM_QUIT;
|
return TA_PARAM_QUIT;
|
||||||
|
|
Loading…
Reference in a new issue