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__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/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",
|
||||
"NSFPlay (NES and FDS) by Brad Smith and Brezza",
|
||||
"reSID by Dag Lem",
|
||||
"reSIDfp by Dag Lem, Antti Lankila",
|
||||
"and Leandro Nini",
|
||||
"Stella by Stella Team",
|
||||
"QSound emulator by superctr and Valley Bell",
|
||||
"VICE VIC-20 sound core by Rami Rasanen and viznut",
|
||||
|
|
|
@ -1049,6 +1049,7 @@ class FurnaceGUI {
|
|||
int snCore;
|
||||
int nesCore;
|
||||
int fdsCore;
|
||||
int c64Core;
|
||||
int pcSpeakerOutMethod;
|
||||
String yrw801Path;
|
||||
String tg100Path;
|
||||
|
@ -1168,6 +1169,7 @@ class FurnaceGUI {
|
|||
snCore(0),
|
||||
nesCore(0),
|
||||
fdsCore(0),
|
||||
c64Core(1),
|
||||
pcSpeakerOutMethod(0),
|
||||
yrw801Path(""),
|
||||
tg100Path(""),
|
||||
|
|
|
@ -97,6 +97,11 @@ const char* nesCores[]={
|
|||
"NSFplay"
|
||||
};
|
||||
|
||||
const char* c64Cores[]={
|
||||
"reSID",
|
||||
"reSIDfp"
|
||||
};
|
||||
|
||||
const char* pcspkrOutMethods[]={
|
||||
"evdev SND_TONE",
|
||||
"KIOCSOUND on /dev/tty1",
|
||||
|
@ -972,6 +977,10 @@ void FurnaceGUI::drawSettings() {
|
|||
ImGui::SameLine();
|
||||
ImGui::Combo("##FDSCore",&settings.fdsCore,nesCores,2);
|
||||
|
||||
ImGui::Text("SID core");
|
||||
ImGui::SameLine();
|
||||
ImGui::Combo("##C64Core",&settings.c64Core,c64Cores,2);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::Text("PC Speaker strategy");
|
||||
|
@ -2155,6 +2164,7 @@ void FurnaceGUI::syncSettings() {
|
|||
settings.snCore=e->getConfInt("snCore",0);
|
||||
settings.nesCore=e->getConfInt("nesCore",0);
|
||||
settings.fdsCore=e->getConfInt("fdsCore",0);
|
||||
settings.c64Core=e->getConfInt("c64Core",1);
|
||||
settings.pcSpeakerOutMethod=e->getConfInt("pcSpeakerOutMethod",0);
|
||||
settings.yrw801Path=e->getConfString("yrw801Path","");
|
||||
settings.tg100Path=e->getConfString("tg100Path","");
|
||||
|
@ -2267,6 +2277,7 @@ void FurnaceGUI::syncSettings() {
|
|||
clampSetting(settings.snCore,0,1);
|
||||
clampSetting(settings.nesCore,0,1);
|
||||
clampSetting(settings.fdsCore,0,1);
|
||||
clampSetting(settings.c64Core,0,1);
|
||||
clampSetting(settings.pcSpeakerOutMethod,0,4);
|
||||
clampSetting(settings.mainFont,0,6);
|
||||
clampSetting(settings.patFont,0,6);
|
||||
|
@ -2402,6 +2413,7 @@ void FurnaceGUI::commitSettings() {
|
|||
e->setConf("snCore",settings.snCore);
|
||||
e->setConf("nesCore",settings.nesCore);
|
||||
e->setConf("fdsCore",settings.fdsCore);
|
||||
e->setConf("c64Core",settings.c64Core);
|
||||
e->setConf("pcSpeakerOutMethod",settings.pcSpeakerOutMethod);
|
||||
e->setConf("yrw801Path",settings.yrw801Path);
|
||||
e->setConf("tg100Path",settings.tg100Path);
|
||||
|
|
|
@ -181,6 +181,7 @@ TAParamResult pVersion(String) {
|
|||
printf("- puNES by FHorse (GPLv2)\n");
|
||||
printf("- NSFPlay by Brad Smith and Brezza (unknown open-source license)\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("- vgsound_emu (first version) by cam900 (BSD 3-clause)\n");
|
||||
return TA_PARAM_QUIT;
|
||||
|
|
Loading…
Reference in a new issue