synced with scummvm
This commit is contained in:
parent
cdd1831d23
commit
5fc7ac39ee
97 changed files with 8634 additions and 6380 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -58,6 +58,11 @@ project.xcworkspace
|
|||
/dists/msvc*/*.dll
|
||||
|
||||
/plugins
|
||||
|
||||
/test/runner
|
||||
/test/runner.cpp
|
||||
/test/*.dSYM
|
||||
|
||||
/devtools/create_project/create_project
|
||||
|
||||
#ignore thumbnails created by windows
|
||||
|
|
690
COPYING.FREEFONT
Normal file
690
COPYING.FREEFONT
Normal file
|
@ -0,0 +1,690 @@
|
|||
NOTE: This license file only applies to the GNU FreeFont files:
|
||||
"FreeSansBold.ttf", "FreeSans.ttf" and "FreeMonoBold.ttf" distributed along
|
||||
with our theme files.
|
||||
|
||||
The following license applies with this exception:
|
||||
As a special exception, if you create a document which uses this font, and
|
||||
embed this font or unaltered portions of this font into the document, this
|
||||
font does not by itself cause the resulting document to be covered by the
|
||||
GNU General Public License. This exception does not however invalidate any
|
||||
other reasons why the document might be covered by the GNU General Public
|
||||
License. If you modify this font, you may extend this exception to your
|
||||
version of the font, but you are not obligated to do so. If you do not
|
||||
wish to do so, delete this exception statement from your version.
|
||||
|
||||
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. 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
|
||||
them 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 prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. 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.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey 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;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If 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 convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU 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 that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
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.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
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.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
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
|
||||
state 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program 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, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU 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. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
|
@ -15,7 +15,7 @@ all: $(EXECUTABLE) plugins
|
|||
#ResidualVM: do not include 'test' but add 'math':
|
||||
|
||||
PLUGINS :=
|
||||
MODULES := devtools base $(MODULES)
|
||||
MODULES := test devtools base $(MODULES)
|
||||
|
||||
-include $(srcdir)/engines/engines.mk
|
||||
|
||||
|
|
|
@ -84,42 +84,6 @@ public:
|
|||
int getRate() const { return _outputRate; }
|
||||
};
|
||||
|
||||
class MT32File : public MT32Emu::File {
|
||||
Common::File _in;
|
||||
Common::DumpFile _out;
|
||||
public:
|
||||
bool open(const char *filename, OpenMode mode) {
|
||||
if (mode == OpenMode_read)
|
||||
return _in.open(filename);
|
||||
else
|
||||
return _out.open(filename);
|
||||
}
|
||||
void close() {
|
||||
_in.close();
|
||||
_out.close();
|
||||
}
|
||||
size_t read(void *in, size_t size) {
|
||||
return _in.read(in, size);
|
||||
}
|
||||
bool readBit8u(MT32Emu::Bit8u *in) {
|
||||
byte b = _in.readByte();
|
||||
if (_in.eos())
|
||||
return false;
|
||||
*in = b;
|
||||
return true;
|
||||
}
|
||||
size_t write(const void *in, size_t size) {
|
||||
return _out.write(in, size);
|
||||
}
|
||||
bool writeBit8u(MT32Emu::Bit8u out) {
|
||||
_out.writeByte(out);
|
||||
return !_out.err();
|
||||
}
|
||||
bool isEOF() {
|
||||
return _in.isOpen() && _in.eos();
|
||||
}
|
||||
};
|
||||
|
||||
static int eatSystemEvents() {
|
||||
Common::Event event;
|
||||
Common::EventManager *eventMan = g_system->getEventManager();
|
||||
|
@ -210,9 +174,9 @@ static void drawMessage(int offset, const Common::String &text) {
|
|||
*/
|
||||
}
|
||||
|
||||
static MT32Emu::File *MT32_OpenFile(void *userData, const char *filename, MT32Emu::File::OpenMode mode) {
|
||||
MT32File *file = new MT32File();
|
||||
if (!file->open(filename, mode)) {
|
||||
static Common::File *MT32_OpenFile(void *userData, const char *filename) {
|
||||
Common::File *file = new Common::File();
|
||||
if (!file->open(filename)) {
|
||||
delete file;
|
||||
return NULL;
|
||||
}
|
||||
|
@ -335,6 +299,11 @@ int MidiDriver_MT32::open() {
|
|||
drawMessage(-1, _s("Initializing MT-32 Emulator"));
|
||||
if (!_synth->open(prop))
|
||||
return MERR_DEVICE_NOT_AVAILABLE;
|
||||
|
||||
double gain = (double)ConfMan.getInt("midi_gain") / 100.0;
|
||||
_synth->setOutputGain(1.0f * gain);
|
||||
_synth->setReverbOutputGain(0.68f * gain);
|
||||
|
||||
_initializing = false;
|
||||
|
||||
// TODO implement in ResidualVM
|
||||
|
|
237
audio/softsynth/mt32/AReverbModel.cpp
Normal file
237
audio/softsynth/mt32/AReverbModel.cpp
Normal file
|
@ -0,0 +1,237 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "mt32emu.h"
|
||||
#include "AReverbModel.h"
|
||||
|
||||
using namespace MT32Emu;
|
||||
|
||||
// Default reverb settings for modes 0-2
|
||||
|
||||
static const unsigned int NUM_ALLPASSES = 6;
|
||||
static const unsigned int NUM_DELAYS = 5;
|
||||
|
||||
static const Bit32u MODE_0_ALLPASSES[] = {729, 78, 394, 994, 1250, 1889};
|
||||
static const Bit32u MODE_0_DELAYS[] = {846, 4, 1819, 778, 346};
|
||||
static const float MODE_0_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.9f};
|
||||
static const float MODE_0_LEVELS[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 1.01575f};
|
||||
|
||||
static const Bit32u MODE_1_ALLPASSES[] = {176, 809, 1324, 1258};
|
||||
static const Bit32u MODE_1_DELAYS[] = {2262, 124, 974, 2516, 356};
|
||||
static const float MODE_1_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.95f};
|
||||
static const float MODE_1_LEVELS[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 1.01575f};
|
||||
|
||||
static const Bit32u MODE_2_ALLPASSES[] = {78, 729, 994, 389};
|
||||
static const Bit32u MODE_2_DELAYS[] = {846, 4, 1819, 778, 346};
|
||||
static const float MODE_2_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f};
|
||||
static const float MODE_2_LEVELS[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f};
|
||||
|
||||
const AReverbSettings AReverbModel::REVERB_MODE_0_SETTINGS = {MODE_0_ALLPASSES, MODE_0_DELAYS, MODE_0_TIMES, MODE_0_LEVELS, 0.687770909f, 0.5f, 0.5f};
|
||||
const AReverbSettings AReverbModel::REVERB_MODE_1_SETTINGS = {MODE_1_ALLPASSES, MODE_1_DELAYS, MODE_1_TIMES, MODE_1_LEVELS, 0.712025098f, 0.375f, 0.625f};
|
||||
const AReverbSettings AReverbModel::REVERB_MODE_2_SETTINGS = {MODE_2_ALLPASSES, MODE_2_DELAYS, MODE_2_TIMES, MODE_2_LEVELS, 0.939522749f, 0.0f, 0.0f};
|
||||
|
||||
RingBuffer::RingBuffer(Bit32u newsize) {
|
||||
index = 0;
|
||||
size = newsize;
|
||||
buffer = new float[size];
|
||||
}
|
||||
|
||||
RingBuffer::~RingBuffer() {
|
||||
delete[] buffer;
|
||||
buffer = NULL;
|
||||
size = 0;
|
||||
}
|
||||
|
||||
float RingBuffer::next() {
|
||||
index++;
|
||||
if (index >= size) {
|
||||
index = 0;
|
||||
}
|
||||
return buffer[index];
|
||||
}
|
||||
|
||||
bool RingBuffer::isEmpty() {
|
||||
if (buffer == NULL) return true;
|
||||
|
||||
float *buf = buffer;
|
||||
float total = 0;
|
||||
for (Bit32u i = 0; i < size; i++) {
|
||||
total += (*buf < 0 ? -*buf : *buf);
|
||||
buf++;
|
||||
}
|
||||
return ((total / size) < .0002 ? true : false);
|
||||
}
|
||||
|
||||
void RingBuffer::mute() {
|
||||
float *buf = buffer;
|
||||
for (Bit32u i = 0; i < size; i++) {
|
||||
*buf++ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
AllpassFilter::AllpassFilter(Bit32u useSize) : RingBuffer(useSize) {
|
||||
}
|
||||
|
||||
Delay::Delay(Bit32u useSize) : RingBuffer(useSize) {
|
||||
}
|
||||
|
||||
float AllpassFilter::process(float in) {
|
||||
// This model corresponds to the allpass filter implementation in the real CM-32L device
|
||||
// found from sample analysis
|
||||
|
||||
float out;
|
||||
|
||||
out = next();
|
||||
|
||||
// store input - feedback / 2
|
||||
buffer[index] = in - 0.5f * out;
|
||||
|
||||
// return buffer output + feedforward / 2
|
||||
return out + 0.5f * buffer[index];
|
||||
}
|
||||
|
||||
float Delay::process(float in) {
|
||||
// Implements a very simple delay
|
||||
|
||||
float out;
|
||||
|
||||
out = next();
|
||||
|
||||
// store input
|
||||
buffer[index] = in;
|
||||
|
||||
// return buffer output
|
||||
return out;
|
||||
}
|
||||
|
||||
AReverbModel::AReverbModel(const AReverbSettings *useSettings) : allpasses(NULL), delays(NULL), currentSettings(useSettings) {
|
||||
}
|
||||
|
||||
AReverbModel::~AReverbModel() {
|
||||
close();
|
||||
}
|
||||
|
||||
void AReverbModel::open(unsigned int /*sampleRate*/) {
|
||||
// FIXME: filter sizes must be multiplied by sample rate to 32000Hz ratio
|
||||
// IIR filter values depend on sample rate as well
|
||||
allpasses = new AllpassFilter*[NUM_ALLPASSES];
|
||||
for (Bit32u i = 0; i < NUM_ALLPASSES; i++) {
|
||||
allpasses[i] = new AllpassFilter(currentSettings->allpassSizes[i]);
|
||||
}
|
||||
delays = new Delay*[NUM_DELAYS];
|
||||
for (Bit32u i = 0; i < NUM_DELAYS; i++) {
|
||||
delays[i] = new Delay(currentSettings->delaySizes[i]);
|
||||
}
|
||||
mute();
|
||||
}
|
||||
|
||||
void AReverbModel::close() {
|
||||
if (allpasses != NULL) {
|
||||
for (Bit32u i = 0; i < NUM_ALLPASSES; i++) {
|
||||
if (allpasses[i] != NULL) {
|
||||
delete allpasses[i];
|
||||
allpasses[i] = NULL;
|
||||
}
|
||||
}
|
||||
delete[] allpasses;
|
||||
allpasses = NULL;
|
||||
}
|
||||
if (delays != NULL) {
|
||||
for (Bit32u i = 0; i < NUM_DELAYS; i++) {
|
||||
if (delays[i] != NULL) {
|
||||
delete delays[i];
|
||||
delays[i] = NULL;
|
||||
}
|
||||
}
|
||||
delete[] delays;
|
||||
delays = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void AReverbModel::mute() {
|
||||
for (Bit32u i = 0; i < NUM_ALLPASSES; i++) {
|
||||
allpasses[i]->mute();
|
||||
}
|
||||
for (Bit32u i = 0; i < NUM_DELAYS; i++) {
|
||||
delays[i]->mute();
|
||||
}
|
||||
filterhist1 = 0;
|
||||
filterhist2 = 0;
|
||||
combhist = 0;
|
||||
}
|
||||
|
||||
void AReverbModel::setParameters(Bit8u time, Bit8u level) {
|
||||
// FIXME: wetLevel definitely needs ramping when changed
|
||||
// Although, most games don't set reverb level during MIDI playback
|
||||
decayTime = currentSettings->decayTimes[time];
|
||||
wetLevel = currentSettings->wetLevels[level];
|
||||
}
|
||||
|
||||
bool AReverbModel::isActive() const {
|
||||
bool bActive = false;
|
||||
for (Bit32u i = 0; i < NUM_ALLPASSES; i++) {
|
||||
bActive |= !allpasses[i]->isEmpty();
|
||||
}
|
||||
for (Bit32u i = 0; i < NUM_DELAYS; i++) {
|
||||
bActive |= !delays[i]->isEmpty();
|
||||
}
|
||||
return bActive;
|
||||
}
|
||||
|
||||
void AReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
|
||||
// Three series allpass filters followed by a delay, fourth allpass filter and another delay
|
||||
float dry, link, outL1, outL2, outR1, outR2;
|
||||
|
||||
for (unsigned long i = 0; i < numSamples; i++) {
|
||||
dry = *inLeft + *inRight;
|
||||
|
||||
// Implementation of 2-stage IIR single-pole low-pass filter
|
||||
// found at the entrance of reverb processing on real devices
|
||||
filterhist1 += (dry - filterhist1) * currentSettings->filtVal;
|
||||
filterhist2 += (filterhist1 - filterhist2) * currentSettings->filtVal;
|
||||
|
||||
link = allpasses[0]->process(-filterhist2);
|
||||
link = allpasses[1]->process(link);
|
||||
|
||||
// this implements a comb filter cross-linked with the fourth allpass filter
|
||||
link += combhist * decayTime;
|
||||
link = allpasses[2]->process(link);
|
||||
link = delays[0]->process(link);
|
||||
outL1 = link;
|
||||
link = allpasses[3]->process(link);
|
||||
link = delays[1]->process(link);
|
||||
outR1 = link;
|
||||
link = allpasses[4]->process(link);
|
||||
link = delays[2]->process(link);
|
||||
outL2 = link;
|
||||
link = allpasses[5]->process(link);
|
||||
link = delays[3]->process(link);
|
||||
outR2 = link;
|
||||
link = delays[4]->process(link);
|
||||
|
||||
// comb filter end point
|
||||
combhist = combhist * currentSettings->damp1 + link * currentSettings->damp2;
|
||||
|
||||
*outLeft = (outL1 + outL2) * wetLevel;
|
||||
*outRight = (outR1 + outR2) * wetLevel;
|
||||
|
||||
inLeft++;
|
||||
inRight++;
|
||||
outLeft++;
|
||||
outRight++;
|
||||
}
|
||||
}
|
86
audio/softsynth/mt32/AReverbModel.h
Normal file
86
audio/softsynth/mt32/AReverbModel.h
Normal file
|
@ -0,0 +1,86 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_A_REVERB_MODEL_H
|
||||
#define MT32EMU_A_REVERB_MODEL_H
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
struct AReverbSettings {
|
||||
const Bit32u *allpassSizes;
|
||||
const Bit32u *delaySizes;
|
||||
const float *decayTimes;
|
||||
const float *wetLevels;
|
||||
float filtVal;
|
||||
float damp1;
|
||||
float damp2;
|
||||
};
|
||||
|
||||
class RingBuffer {
|
||||
protected:
|
||||
float *buffer;
|
||||
Bit32u size;
|
||||
Bit32u index;
|
||||
public:
|
||||
RingBuffer(Bit32u size);
|
||||
virtual ~RingBuffer();
|
||||
float next();
|
||||
bool isEmpty();
|
||||
void mute();
|
||||
};
|
||||
|
||||
class AllpassFilter : public RingBuffer {
|
||||
public:
|
||||
AllpassFilter(Bit32u size);
|
||||
float process(float in);
|
||||
};
|
||||
|
||||
class Delay : public RingBuffer {
|
||||
public:
|
||||
Delay(Bit32u size);
|
||||
float process(float in);
|
||||
};
|
||||
|
||||
class AReverbModel : public ReverbModel {
|
||||
AllpassFilter **allpasses;
|
||||
Delay **delays;
|
||||
|
||||
const AReverbSettings *currentSettings;
|
||||
float decayTime;
|
||||
float wetLevel;
|
||||
float filterhist1, filterhist2;
|
||||
float combhist;
|
||||
void mute();
|
||||
public:
|
||||
AReverbModel(const AReverbSettings *newSettings);
|
||||
~AReverbModel();
|
||||
void open(unsigned int sampleRate);
|
||||
void close();
|
||||
void setParameters(Bit8u time, Bit8u level);
|
||||
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
|
||||
bool isActive() const;
|
||||
|
||||
static const AReverbSettings REVERB_MODE_0_SETTINGS;
|
||||
static const AReverbSettings REVERB_MODE_1_SETTINGS;
|
||||
static const AReverbSettings REVERB_MODE_2_SETTINGS;
|
||||
};
|
||||
|
||||
// Default reverb settings for modes 0-2
|
||||
|
||||
}
|
||||
|
||||
#endif
|
150
audio/softsynth/mt32/DelayReverb.cpp
Normal file
150
audio/softsynth/mt32/DelayReverb.cpp
Normal file
|
@ -0,0 +1,150 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//#include <cmath>
|
||||
//#include <cstring>
|
||||
#include "mt32emu.h"
|
||||
#include "DelayReverb.h"
|
||||
|
||||
using namespace MT32Emu;
|
||||
|
||||
|
||||
// CONFIRMED: The values below are found via analysis of digital samples. Checked with all time and level combinations.
|
||||
// Obviously:
|
||||
// rightDelay = (leftDelay - 2) * 2 + 2
|
||||
// echoDelay = rightDelay - 1
|
||||
// Leaving these separate in case it's useful for work on other reverb modes...
|
||||
const Bit32u REVERB_TIMINGS[8][3]= {
|
||||
// {leftDelay, rightDelay, feedbackDelay}
|
||||
{402, 802, 801},
|
||||
{626, 1250, 1249},
|
||||
{962, 1922, 1921},
|
||||
{1490, 2978, 2977},
|
||||
{2258, 4514, 4513},
|
||||
{3474, 6946, 6945},
|
||||
{5282, 10562, 10561},
|
||||
{8002, 16002, 16001}
|
||||
};
|
||||
|
||||
const float REVERB_FADE[8] = {0.0f, -0.049400051f, -0.08220577f, -0.131861118f, -0.197344907f, -0.262956344f, -0.345162114f, -0.509508615f};
|
||||
const float REVERB_FEEDBACK67 = -0.629960524947437f; // = -EXP2F(-2 / 3)
|
||||
const float REVERB_FEEDBACK = -0.682034520443118f; // = -EXP2F(-53 / 96)
|
||||
const float LPF_VALUE = 0.594603558f; // = EXP2F(-0.75f)
|
||||
|
||||
DelayReverb::DelayReverb() {
|
||||
buf = NULL;
|
||||
sampleRate = 0;
|
||||
setParameters(0, 0);
|
||||
}
|
||||
|
||||
DelayReverb::~DelayReverb() {
|
||||
delete[] buf;
|
||||
}
|
||||
|
||||
void DelayReverb::open(unsigned int newSampleRate) {
|
||||
if (newSampleRate != sampleRate || buf == NULL) {
|
||||
sampleRate = newSampleRate;
|
||||
|
||||
delete[] buf;
|
||||
|
||||
// If we ever need a speedup, set bufSize to EXP2F(ceil(log2(bufSize))) and use & instead of % to find buf indexes
|
||||
bufSize = 16384 * sampleRate / 32000;
|
||||
buf = new float[bufSize];
|
||||
|
||||
recalcParameters();
|
||||
|
||||
// mute buffer
|
||||
bufIx = 0;
|
||||
if (buf != NULL) {
|
||||
for (unsigned int i = 0; i < bufSize; i++) {
|
||||
buf[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
// FIXME: IIR filter value depends on sample rate as well
|
||||
}
|
||||
|
||||
void DelayReverb::close() {
|
||||
delete[] buf;
|
||||
buf = NULL;
|
||||
}
|
||||
|
||||
// This method will always trigger a flush of the buffer
|
||||
void DelayReverb::setParameters(Bit8u newTime, Bit8u newLevel) {
|
||||
time = newTime;
|
||||
level = newLevel;
|
||||
recalcParameters();
|
||||
}
|
||||
|
||||
void DelayReverb::recalcParameters() {
|
||||
// Number of samples between impulse and eventual appearance on the left channel
|
||||
delayLeft = REVERB_TIMINGS[time][0] * sampleRate / 32000;
|
||||
// Number of samples between impulse and eventual appearance on the right channel
|
||||
delayRight = REVERB_TIMINGS[time][1] * sampleRate / 32000;
|
||||
// Number of samples between a response and that response feeding back/echoing
|
||||
delayFeedback = REVERB_TIMINGS[time][2] * sampleRate / 32000;
|
||||
|
||||
if (time < 6) {
|
||||
feedback = REVERB_FEEDBACK;
|
||||
} else {
|
||||
feedback = REVERB_FEEDBACK67;
|
||||
}
|
||||
|
||||
// Fading speed, i.e. amplitude ratio of neighbor responses
|
||||
fade = REVERB_FADE[level];
|
||||
}
|
||||
|
||||
void DelayReverb::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
|
||||
if (buf == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (unsigned int sampleIx = 0; sampleIx < numSamples; sampleIx++) {
|
||||
// The ring buffer write index moves backwards; reads are all done with positive offsets.
|
||||
Bit32u bufIxPrev = (bufIx + 1) % bufSize;
|
||||
Bit32u bufIxLeft = (bufIx + delayLeft) % bufSize;
|
||||
Bit32u bufIxRight = (bufIx + delayRight) % bufSize;
|
||||
Bit32u bufIxFeedback = (bufIx + delayFeedback) % bufSize;
|
||||
|
||||
// Attenuated input samples and feedback response are directly added to the current ring buffer location
|
||||
float sample = fade * (inLeft[sampleIx] + inRight[sampleIx]) + feedback * buf[bufIxFeedback];
|
||||
|
||||
// Single-pole IIR filter found on real devices
|
||||
buf[bufIx] = buf[bufIxPrev] + (sample - buf[bufIxPrev]) * LPF_VALUE;
|
||||
|
||||
outLeft[sampleIx] = buf[bufIxLeft];
|
||||
outRight[sampleIx] = buf[bufIxRight];
|
||||
|
||||
bufIx = (bufSize + bufIx - 1) % bufSize;
|
||||
}
|
||||
}
|
||||
|
||||
bool DelayReverb::isActive() const {
|
||||
// Quick hack: Return true iff all samples in the left buffer are the same and
|
||||
// all samples in the right buffers are the same (within the sample output threshold).
|
||||
if (buf == NULL) {
|
||||
return false;
|
||||
}
|
||||
float last = buf[0] * 8192.0f;
|
||||
for (unsigned int i = 1; i < bufSize; i++) {
|
||||
float s = (buf[i] * 8192.0f);
|
||||
if (fabs(s - last) > 1.0f) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
53
audio/softsynth/mt32/DelayReverb.h
Normal file
53
audio/softsynth/mt32/DelayReverb.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_DELAYREVERB_H
|
||||
#define MT32EMU_DELAYREVERB_H
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class DelayReverb : public ReverbModel {
|
||||
private:
|
||||
Bit8u time;
|
||||
Bit8u level;
|
||||
|
||||
unsigned int sampleRate;
|
||||
Bit32u bufSize;
|
||||
Bit32u bufIx;
|
||||
|
||||
float *buf;
|
||||
|
||||
Bit32u delayLeft;
|
||||
Bit32u delayRight;
|
||||
Bit32u delayFeedback;
|
||||
|
||||
float fade;
|
||||
float feedback;
|
||||
|
||||
void recalcParameters();
|
||||
|
||||
public:
|
||||
DelayReverb();
|
||||
~DelayReverb();
|
||||
void open(unsigned int sampleRate);
|
||||
void close();
|
||||
void setParameters(Bit8u time, Bit8u level);
|
||||
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
|
||||
bool isActive() const;
|
||||
};
|
||||
}
|
||||
#endif
|
78
audio/softsynth/mt32/FreeverbModel.cpp
Normal file
78
audio/softsynth/mt32/FreeverbModel.cpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "mt32emu.h"
|
||||
#include "FreeverbModel.h"
|
||||
|
||||
#include "freeverb.h"
|
||||
|
||||
using namespace MT32Emu;
|
||||
|
||||
FreeverbModel::FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp) {
|
||||
freeverb = NULL;
|
||||
scaleTuning = useScaleTuning;
|
||||
filtVal = useFiltVal;
|
||||
wet = useWet;
|
||||
room = useRoom;
|
||||
damp = useDamp;
|
||||
}
|
||||
|
||||
FreeverbModel::~FreeverbModel() {
|
||||
delete freeverb;
|
||||
}
|
||||
|
||||
void FreeverbModel::open(unsigned int /*sampleRate*/) {
|
||||
// FIXME: scaleTuning must be multiplied by sample rate to 32000Hz ratio
|
||||
// IIR filter values depend on sample rate as well
|
||||
if (freeverb == NULL) {
|
||||
freeverb = new revmodel(scaleTuning);
|
||||
}
|
||||
freeverb->mute();
|
||||
|
||||
// entrance Lowpass filter factor
|
||||
freeverb->setfiltval(filtVal);
|
||||
|
||||
// decay speed of high frequencies in the wet signal
|
||||
freeverb->setdamp(damp);
|
||||
}
|
||||
|
||||
void FreeverbModel::close() {
|
||||
delete freeverb;
|
||||
freeverb = NULL;
|
||||
}
|
||||
|
||||
void FreeverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
|
||||
freeverb->process(inLeft, inRight, outLeft, outRight, numSamples);
|
||||
}
|
||||
|
||||
void FreeverbModel::setParameters(Bit8u time, Bit8u level) {
|
||||
// wet signal level
|
||||
// FIXME: need to implement some sort of reverb level ramping
|
||||
freeverb->setwet((float)level / 7.0f * wet);
|
||||
|
||||
// wet signal decay speed
|
||||
static float roomTable[] = {
|
||||
0.25f, 0.37f, 0.54f, 0.71f, 0.78f, 0.86f, 0.93f, 1.00f,
|
||||
-1.00f, -0.50f, 0.00f, 0.30f, 0.51f, 0.64f, 0.77f, 0.90f,
|
||||
0.50f, 0.57f, 0.70f, 0.77f, 0.85f, 0.93f, 0.96f, 1.01f};
|
||||
freeverb->setroomsize(roomTable[8 * room + time]);
|
||||
}
|
||||
|
||||
bool FreeverbModel::isActive() const {
|
||||
// FIXME: Not bothering to do this properly since we'll be replacing Freeverb soon...
|
||||
return false;
|
||||
}
|
44
audio/softsynth/mt32/FreeverbModel.h
Normal file
44
audio/softsynth/mt32/FreeverbModel.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_FREEVERB_MODEL_H
|
||||
#define MT32EMU_FREEVERB_MODEL_H
|
||||
|
||||
class revmodel;
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class FreeverbModel : public ReverbModel {
|
||||
revmodel *freeverb;
|
||||
float scaleTuning;
|
||||
float filtVal;
|
||||
float wet;
|
||||
Bit8u room;
|
||||
float damp;
|
||||
public:
|
||||
FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp);
|
||||
~FreeverbModel();
|
||||
void open(unsigned int sampleRate);
|
||||
void close();
|
||||
void setParameters(Bit8u time, Bit8u level);
|
||||
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
|
||||
bool isActive() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
150
audio/softsynth/mt32/LA32Ramp.cpp
Normal file
150
audio/softsynth/mt32/LA32Ramp.cpp
Normal file
|
@ -0,0 +1,150 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
Some notes on this class:
|
||||
|
||||
This emulates the LA-32's implementation of "ramps". A ramp in this context is a smooth transition from one value to another, handled entirely within the LA-32.
|
||||
The LA-32 provides this feature for amplitude and filter cutoff values.
|
||||
|
||||
The 8095 starts ramps on the LA-32 by setting two values in memory-mapped registers:
|
||||
|
||||
(1) The target value (between 0 and 255) for the ramp to end on. This is represented by the "target" argument to startRamp().
|
||||
(2) The speed at which that value should be approached. This is represented by the "increment" argument to startRamp().
|
||||
|
||||
Once the ramp target value has been hit, the LA-32 raises an interrupt.
|
||||
|
||||
Note that the starting point of the ramp is whatever internal value the LA-32 had when the registers were set. This is usually the end point of a previously completed ramp.
|
||||
|
||||
Our handling of the "target" and "increment" values is based on sample analysis and a little guesswork.
|
||||
Here's what we're pretty confident about:
|
||||
- The most significant bit of "increment" indicates the direction that the LA32's current internal value ("current" in our emulation) should change in.
|
||||
Set means downward, clear means upward.
|
||||
- The lower 7 bits of "increment" indicate how quickly "current" should be changed.
|
||||
- If "increment" is 0, no change to "current" is made and no interrupt is raised. [SEMI-CONFIRMED by sample analysis]
|
||||
- Otherwise, if the MSb is set:
|
||||
- If "current" already corresponds to a value <= "target", "current" is set immediately to the equivalent of "target" and an interrupt is raised.
|
||||
- Otherwise, "current" is gradually reduced (at a rate determined by the lower 7 bits of "increment"), and once it reaches the equivalent of "target" an interrupt is raised.
|
||||
- Otherwise (the MSb is unset):
|
||||
- If "current" already corresponds to a value >= "target", "current" is set immediately to the equivalent of "target" and an interrupt is raised.
|
||||
- Otherwise, "current" is gradually increased (at a rate determined by the lower 7 bits of "increment"), and once it reaches the equivalent of "target" an interrupt is raised.
|
||||
|
||||
We haven't fully explored:
|
||||
- Values when ramping between levels (though this is probably correct).
|
||||
- Transition timing (may not be 100% accurate, especially for very fast ramps).
|
||||
*/
|
||||
//#include <cmath>
|
||||
|
||||
#include "mt32emu.h"
|
||||
#include "LA32Ramp.h"
|
||||
#include "mmath.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// SEMI-CONFIRMED from sample analysis.
|
||||
const int TARGET_MULT = 0x40000;
|
||||
const unsigned int MAX_CURRENT = 0xFF * TARGET_MULT;
|
||||
|
||||
// We simulate the delay in handling "target was reached" interrupts by waiting
|
||||
// this many samples before setting interruptRaised.
|
||||
// FIXME: This should vary with the sample rate, but doesn't.
|
||||
// SEMI-CONFIRMED: Since this involves asynchronous activity between the LA32
|
||||
// and the 8095, a good value is hard to pin down.
|
||||
// This one matches observed behaviour on a few digital captures I had handy,
|
||||
// and should be double-checked. We may also need a more sophisticated delay
|
||||
// scheme eventually.
|
||||
const int INTERRUPT_TIME = 7;
|
||||
|
||||
LA32Ramp::LA32Ramp() :
|
||||
current(0),
|
||||
largeTarget(0),
|
||||
largeIncrement(0),
|
||||
interruptCountdown(0),
|
||||
interruptRaised(false) {
|
||||
}
|
||||
|
||||
void LA32Ramp::startRamp(Bit8u target, Bit8u increment) {
|
||||
// CONFIRMED: From sample analysis, this appears to be very accurate.
|
||||
// FIXME: We could use a table for this in future
|
||||
if (increment == 0) {
|
||||
largeIncrement = 0;
|
||||
} else {
|
||||
largeIncrement = (unsigned int)(EXP2F(((increment & 0x7F) + 24) / 8.0f) + 0.125f);
|
||||
}
|
||||
descending = (increment & 0x80) != 0;
|
||||
if (descending) {
|
||||
// CONFIRMED: From sample analysis, descending increments are slightly faster
|
||||
largeIncrement++;
|
||||
}
|
||||
|
||||
largeTarget = target * TARGET_MULT;
|
||||
interruptCountdown = 0;
|
||||
interruptRaised = false;
|
||||
}
|
||||
|
||||
Bit32u LA32Ramp::nextValue() {
|
||||
if (interruptCountdown > 0) {
|
||||
if (--interruptCountdown == 0) {
|
||||
interruptRaised = true;
|
||||
}
|
||||
} else if (largeIncrement != 0) {
|
||||
// CONFIRMED from sample analysis: When increment is 0, the LA32 does *not* change the current value at all (and of course doesn't fire an interrupt).
|
||||
if (descending) {
|
||||
// Lowering current value
|
||||
if (largeIncrement > current) {
|
||||
current = largeTarget;
|
||||
interruptCountdown = INTERRUPT_TIME;
|
||||
} else {
|
||||
current -= largeIncrement;
|
||||
if (current <= largeTarget) {
|
||||
current = largeTarget;
|
||||
interruptCountdown = INTERRUPT_TIME;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Raising current value
|
||||
if (MAX_CURRENT - current < largeIncrement) {
|
||||
current = largeTarget;
|
||||
interruptCountdown = INTERRUPT_TIME;
|
||||
} else {
|
||||
current += largeIncrement;
|
||||
if (current >= largeTarget) {
|
||||
current = largeTarget;
|
||||
interruptCountdown = INTERRUPT_TIME;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
bool LA32Ramp::checkInterrupt() {
|
||||
bool wasRaised = interruptRaised;
|
||||
interruptRaised = false;
|
||||
return wasRaised;
|
||||
}
|
||||
|
||||
void LA32Ramp::reset() {
|
||||
current = 0;
|
||||
largeTarget = 0;
|
||||
largeIncrement = 0;
|
||||
descending = false;
|
||||
interruptCountdown = 0;
|
||||
interruptRaised = false;
|
||||
}
|
||||
|
||||
}
|
43
audio/softsynth/mt32/LA32Ramp.h
Normal file
43
audio/softsynth/mt32/LA32Ramp.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_LA32RAMP_H
|
||||
#define MT32EMU_LA32RAMP_H
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class LA32Ramp {
|
||||
private:
|
||||
Bit32u current;
|
||||
unsigned int largeTarget;
|
||||
unsigned int largeIncrement;
|
||||
bool descending;
|
||||
|
||||
int interruptCountdown;
|
||||
bool interruptRaised;
|
||||
|
||||
public:
|
||||
LA32Ramp();
|
||||
void startRamp(Bit8u target, Bit8u increment);
|
||||
Bit32u nextValue();
|
||||
bool checkInterrupt();
|
||||
void reset();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* TVA_H_ */
|
174
audio/softsynth/mt32/Poly.cpp
Normal file
174
audio/softsynth/mt32/Poly.cpp
Normal file
|
@ -0,0 +1,174 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "mt32emu.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
Poly::Poly(Part *usePart) {
|
||||
part = usePart;
|
||||
key = 255;
|
||||
velocity = 255;
|
||||
sustain = false;
|
||||
activePartialCount = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
partials[i] = NULL;
|
||||
}
|
||||
state = POLY_Inactive;
|
||||
}
|
||||
|
||||
void Poly::reset(unsigned int newKey, unsigned int newVelocity, bool newSustain, Partial **newPartials) {
|
||||
if (isActive()) {
|
||||
// FIXME: Throw out some big ugly debug output with a lot of exclamation marks - we should never get here
|
||||
terminate();
|
||||
}
|
||||
|
||||
key = newKey;
|
||||
velocity = newVelocity;
|
||||
sustain = newSustain;
|
||||
|
||||
activePartialCount = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
partials[i] = newPartials[i];
|
||||
if (newPartials[i] != NULL) {
|
||||
activePartialCount++;
|
||||
state = POLY_Playing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Poly::noteOff(bool pedalHeld) {
|
||||
// Generally, non-sustaining instruments ignore note off. They die away eventually anyway.
|
||||
// Key 0 (only used by special cases on rhythm part) reacts to note off even if non-sustaining or pedal held.
|
||||
if (state == POLY_Inactive || state == POLY_Releasing) {
|
||||
return false;
|
||||
}
|
||||
if (pedalHeld) {
|
||||
state = POLY_Held;
|
||||
} else {
|
||||
startDecay();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Poly::stopPedalHold() {
|
||||
if (state != POLY_Held) {
|
||||
return false;
|
||||
}
|
||||
return startDecay();
|
||||
}
|
||||
|
||||
bool Poly::startDecay() {
|
||||
if (state == POLY_Inactive || state == POLY_Releasing) {
|
||||
return false;
|
||||
}
|
||||
state = POLY_Releasing;
|
||||
|
||||
for (int t = 0; t < 4; t++) {
|
||||
Partial *partial = partials[t];
|
||||
if (partial != NULL) {
|
||||
partial->startDecayAll();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Poly::startAbort() {
|
||||
if (state == POLY_Inactive) {
|
||||
return false;
|
||||
}
|
||||
for (int t = 0; t < 4; t++) {
|
||||
Partial *partial = partials[t];
|
||||
if (partial != NULL) {
|
||||
partial->startAbort();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Poly::terminate() {
|
||||
if (state == POLY_Inactive) {
|
||||
return;
|
||||
}
|
||||
for (int t = 0; t < 4; t++) {
|
||||
Partial *partial = partials[t];
|
||||
if (partial != NULL) {
|
||||
partial->deactivate();
|
||||
}
|
||||
}
|
||||
if (state != POLY_Inactive) {
|
||||
// FIXME: Throw out lots of debug output - this should never happen
|
||||
// (Deactivating the partials above should've made them each call partialDeactivated(), ultimately changing the state to POLY_Inactive)
|
||||
state = POLY_Inactive;
|
||||
}
|
||||
}
|
||||
|
||||
void Poly::backupCacheToPartials(PatchCache cache[4]) {
|
||||
for (int partialNum = 0; partialNum < 4; partialNum++) {
|
||||
Partial *partial = partials[partialNum];
|
||||
if (partial != NULL && partial->patchCache == &cache[partialNum]) {
|
||||
partial->cachebackup = cache[partialNum];
|
||||
partial->patchCache = &partial->cachebackup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the internal key identifier.
|
||||
* For non-rhythm, this is within the range 12 to 108.
|
||||
* For rhythm on MT-32, this is 0 or 1 (special cases) or within the range 24 to 87.
|
||||
* For rhythm on devices with extended PCM sounds (e.g. CM-32L), this is 0, 1 or 24 to 108
|
||||
*/
|
||||
unsigned int Poly::getKey() const {
|
||||
return key;
|
||||
}
|
||||
|
||||
unsigned int Poly::getVelocity() const {
|
||||
return velocity;
|
||||
}
|
||||
|
||||
bool Poly::canSustain() const {
|
||||
return sustain;
|
||||
}
|
||||
|
||||
PolyState Poly::getState() const {
|
||||
return state;
|
||||
}
|
||||
|
||||
unsigned int Poly::getActivePartialCount() const {
|
||||
return activePartialCount;
|
||||
}
|
||||
|
||||
bool Poly::isActive() const {
|
||||
return state != POLY_Inactive;
|
||||
}
|
||||
|
||||
// This is called by Partial to inform the poly that the Partial has deactivated
|
||||
void Poly::partialDeactivated(Partial *partial) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (partials[i] == partial) {
|
||||
partials[i] = NULL;
|
||||
activePartialCount--;
|
||||
}
|
||||
}
|
||||
if (activePartialCount == 0) {
|
||||
state = POLY_Inactive;
|
||||
}
|
||||
part->partialDeactivated(this);
|
||||
}
|
||||
|
||||
}
|
67
audio/softsynth/mt32/Poly.h
Normal file
67
audio/softsynth/mt32/Poly.h
Normal file
|
@ -0,0 +1,67 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_POLY_H
|
||||
#define MT32EMU_POLY_H
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class Part;
|
||||
|
||||
enum PolyState {
|
||||
POLY_Playing,
|
||||
POLY_Held, // This marks keys that have been released on the keyboard, but are being held by the pedal
|
||||
POLY_Releasing,
|
||||
POLY_Inactive
|
||||
};
|
||||
|
||||
class Poly {
|
||||
private:
|
||||
Part *part;
|
||||
unsigned int key;
|
||||
unsigned int velocity;
|
||||
unsigned int activePartialCount;
|
||||
bool sustain;
|
||||
|
||||
PolyState state;
|
||||
|
||||
Partial *partials[4];
|
||||
|
||||
public:
|
||||
Poly(Part *part);
|
||||
void reset(unsigned int key, unsigned int velocity, bool sustain, Partial **partials);
|
||||
bool noteOff(bool pedalHeld);
|
||||
bool stopPedalHold();
|
||||
bool startDecay();
|
||||
bool startAbort();
|
||||
void terminate();
|
||||
|
||||
void backupCacheToPartials(PatchCache cache[4]);
|
||||
|
||||
unsigned int getKey() const;
|
||||
unsigned int getVelocity() const;
|
||||
bool canSustain() const;
|
||||
PolyState getState() const;
|
||||
unsigned int getActivePartialCount() const;
|
||||
bool isActive() const;
|
||||
|
||||
void partialDeactivated(Partial *partial);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* POLY_H_ */
|
365
audio/softsynth/mt32/TVA.cpp
Normal file
365
audio/softsynth/mt32/TVA.cpp
Normal file
|
@ -0,0 +1,365 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This class emulates the calculations performed by the 8095 microcontroller in order to configure the LA-32's amplitude ramp for a single partial at each stage of its TVA envelope.
|
||||
* Unless we introduced bugs, it should be pretty much 100% accurate according to Mok's specifications.
|
||||
*/
|
||||
//#include <cmath>
|
||||
|
||||
#include "mt32emu.h"
|
||||
#include "mmath.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// CONFIRMED: Matches a table in ROM - haven't got around to coming up with a formula for it yet.
|
||||
static Bit8u biasLevelToAmpSubtractionCoeff[13] = {255, 187, 137, 100, 74, 54, 40, 29, 21, 15, 10, 5, 0};
|
||||
|
||||
TVA::TVA(const Partial *usePartial, LA32Ramp *useAmpRamp) :
|
||||
partial(usePartial), ampRamp(useAmpRamp), system_(&usePartial->getSynth()->mt32ram.system) {
|
||||
}
|
||||
|
||||
void TVA::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) {
|
||||
target = newTarget;
|
||||
phase = newPhase;
|
||||
ampRamp->startRamp(newTarget, newIncrement);
|
||||
#if MT32EMU_MONITOR_TVA >= 1
|
||||
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVA,ramp,%d,%d,%d,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), (newIncrement & 0x80) ? -1 : 1, (newIncrement & 0x7F), newPhase);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TVA::end(int newPhase) {
|
||||
phase = newPhase;
|
||||
playing = false;
|
||||
#if MT32EMU_MONITOR_TVA >= 1
|
||||
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVA,end,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newPhase);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int multBias(Bit8u biasLevel, int bias) {
|
||||
return (bias * biasLevelToAmpSubtractionCoeff[biasLevel]) >> 5;
|
||||
}
|
||||
|
||||
static int calcBiasAmpSubtraction(Bit8u biasPoint, Bit8u biasLevel, int key) {
|
||||
if ((biasPoint & 0x40) == 0) {
|
||||
int bias = biasPoint + 33 - key;
|
||||
if (bias > 0) {
|
||||
return multBias(biasLevel, bias);
|
||||
}
|
||||
} else {
|
||||
int bias = biasPoint - 31 - key;
|
||||
if (bias < 0) {
|
||||
bias = -bias;
|
||||
return multBias(biasLevel, bias);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int calcBiasAmpSubtractions(const TimbreParam::PartialParam *partialParam, int key) {
|
||||
int biasAmpSubtraction1 = calcBiasAmpSubtraction(partialParam->tva.biasPoint1, partialParam->tva.biasLevel1, key);
|
||||
if (biasAmpSubtraction1 > 255) {
|
||||
return 255;
|
||||
}
|
||||
int biasAmpSubtraction2 = calcBiasAmpSubtraction(partialParam->tva.biasPoint2, partialParam->tva.biasLevel2, key);
|
||||
if (biasAmpSubtraction2 > 255) {
|
||||
return 255;
|
||||
}
|
||||
int biasAmpSubtraction = biasAmpSubtraction1 + biasAmpSubtraction2;
|
||||
if (biasAmpSubtraction > 255) {
|
||||
return 255;
|
||||
}
|
||||
return biasAmpSubtraction;
|
||||
}
|
||||
|
||||
static int calcVeloAmpSubtraction(Bit8u veloSensitivity, unsigned int velocity) {
|
||||
// FIXME:KG: Better variable names
|
||||
int velocityMult = veloSensitivity - 50;
|
||||
int absVelocityMult = velocityMult < 0 ? -velocityMult : velocityMult;
|
||||
velocityMult = (signed)((unsigned)(velocityMult * ((signed)velocity - 64)) << 2);
|
||||
return absVelocityMult - (velocityMult >> 8); // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
}
|
||||
|
||||
static int calcBasicAmp(const Tables *tables, const Partial *partial, const MemParams::System *system_, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, const MemParams::RhythmTemp *rhythmTemp, int biasAmpSubtraction, int veloAmpSubtraction, Bit8u expression) {
|
||||
int amp = 155;
|
||||
|
||||
if (!partial->isRingModulatingSlave()) {
|
||||
amp -= tables->masterVolToAmpSubtraction[system_->masterVol];
|
||||
if (amp < 0) {
|
||||
return 0;
|
||||
}
|
||||
amp -= tables->levelToAmpSubtraction[patchTemp->outputLevel];
|
||||
if (amp < 0) {
|
||||
return 0;
|
||||
}
|
||||
amp -= tables->levelToAmpSubtraction[expression];
|
||||
if (amp < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (rhythmTemp != NULL) {
|
||||
amp -= tables->levelToAmpSubtraction[rhythmTemp->outputLevel];
|
||||
if (amp < 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
amp -= biasAmpSubtraction;
|
||||
if (amp < 0) {
|
||||
return 0;
|
||||
}
|
||||
amp -= tables->levelToAmpSubtraction[partialParam->tva.level];
|
||||
if (amp < 0) {
|
||||
return 0;
|
||||
}
|
||||
amp -= veloAmpSubtraction;
|
||||
if (amp < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (amp > 155) {
|
||||
amp = 155;
|
||||
}
|
||||
amp -= partialParam->tvf.resonance >> 1;
|
||||
if (amp < 0) {
|
||||
return 0;
|
||||
}
|
||||
return amp;
|
||||
}
|
||||
|
||||
int calcKeyTimeSubtraction(Bit8u envTimeKeyfollow, int key) {
|
||||
if (envTimeKeyfollow == 0) {
|
||||
return 0;
|
||||
}
|
||||
return (key - 60) >> (5 - envTimeKeyfollow); // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
}
|
||||
|
||||
void TVA::reset(const Part *newPart, const TimbreParam::PartialParam *newPartialParam, const MemParams::RhythmTemp *newRhythmTemp) {
|
||||
part = newPart;
|
||||
partialParam = newPartialParam;
|
||||
patchTemp = newPart->getPatchTemp();
|
||||
rhythmTemp = newRhythmTemp;
|
||||
|
||||
playing = true;
|
||||
|
||||
Tables *tables = &partial->getSynth()->tables;
|
||||
|
||||
int key = partial->getPoly()->getKey();
|
||||
int velocity = partial->getPoly()->getVelocity();
|
||||
|
||||
keyTimeSubtraction = calcKeyTimeSubtraction(partialParam->tva.envTimeKeyfollow, key);
|
||||
|
||||
biasAmpSubtraction = calcBiasAmpSubtractions(partialParam, key);
|
||||
veloAmpSubtraction = calcVeloAmpSubtraction(partialParam->tva.veloSensitivity, velocity);
|
||||
|
||||
int newTarget = calcBasicAmp(tables, partial, system_, partialParam, patchTemp, newRhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
|
||||
int newPhase;
|
||||
if (partialParam->tva.envTime[0] == 0) {
|
||||
// Initially go to the TVA_PHASE_ATTACK target amp, and spend the next phase going from there to the TVA_PHASE_2 target amp
|
||||
// Note that this means that velocity never affects time for this partial.
|
||||
newTarget += partialParam->tva.envLevel[0];
|
||||
newPhase = TVA_PHASE_ATTACK; // The first target used in nextPhase() will be TVA_PHASE_2
|
||||
} else {
|
||||
// Initially go to the base amp determined by TVA level, part volume, etc., and spend the next phase going from there to the full TVA_PHASE_ATTACK target amp.
|
||||
newPhase = TVA_PHASE_BASIC; // The first target used in nextPhase() will be TVA_PHASE_ATTACK
|
||||
}
|
||||
|
||||
ampRamp->reset();//currentAmp = 0;
|
||||
|
||||
// "Go downward as quickly as possible".
|
||||
// Since the current value is 0, the LA32Ramp will notice that we're already at or below the target and trying to go downward,
|
||||
// and therefore jump to the target immediately and raise an interrupt.
|
||||
startRamp((Bit8u)newTarget, 0x80 | 127, newPhase);
|
||||
}
|
||||
|
||||
void TVA::startAbort() {
|
||||
startRamp(64, 0x80 | 127, TVA_PHASE_RELEASE);
|
||||
}
|
||||
|
||||
void TVA::startDecay() {
|
||||
if (phase >= TVA_PHASE_RELEASE) {
|
||||
return;
|
||||
}
|
||||
Bit8u newIncrement;
|
||||
if (partialParam->tva.envTime[4] == 0) {
|
||||
newIncrement = 1;
|
||||
} else {
|
||||
newIncrement = -partialParam->tva.envTime[4];
|
||||
}
|
||||
// The next time nextPhase() is called, it will think TVA_PHASE_RELEASE has finished and the partial will be aborted
|
||||
startRamp(0, newIncrement, TVA_PHASE_RELEASE);
|
||||
}
|
||||
|
||||
void TVA::handleInterrupt() {
|
||||
nextPhase();
|
||||
}
|
||||
|
||||
void TVA::recalcSustain() {
|
||||
// We get pinged periodically by the pitch code to recalculate our values when in sustain.
|
||||
// This is done so that the TVA will respond to things like MIDI expression and volume changes while it's sustaining, which it otherwise wouldn't do.
|
||||
|
||||
// The check for envLevel[3] == 0 strikes me as slightly dumb. FIXME: Explain why
|
||||
if (phase != TVA_PHASE_SUSTAIN || partialParam->tva.envLevel[3] == 0) {
|
||||
return;
|
||||
}
|
||||
// We're sustaining. Recalculate all the values
|
||||
Tables *tables = &partial->getSynth()->tables;
|
||||
int newTarget = calcBasicAmp(tables, partial, system_, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
|
||||
newTarget += partialParam->tva.envLevel[3];
|
||||
// Since we're in TVA_PHASE_SUSTAIN at this point, we know that target has been reached and an interrupt fired, so we can rely on it being the current amp.
|
||||
int targetDelta = newTarget - target;
|
||||
|
||||
// Calculate an increment to get to the new amp value in a short, more or less consistent amount of time
|
||||
Bit8u newIncrement;
|
||||
if (targetDelta >= 0) {
|
||||
newIncrement = tables->envLogarithmicTime[(Bit8u)targetDelta] - 2;
|
||||
} else {
|
||||
newIncrement = (tables->envLogarithmicTime[(Bit8u)-targetDelta] - 2) | 0x80;
|
||||
}
|
||||
// Configure so that once the transition's complete and nextPhase() is called, we'll just re-enter sustain phase (or decay phase, depending on parameters at the time).
|
||||
startRamp(newTarget, newIncrement, TVA_PHASE_SUSTAIN - 1);
|
||||
}
|
||||
|
||||
bool TVA::isPlaying() const {
|
||||
return playing;
|
||||
}
|
||||
|
||||
int TVA::getPhase() const {
|
||||
return phase;
|
||||
}
|
||||
|
||||
void TVA::nextPhase() {
|
||||
Tables *tables = &partial->getSynth()->tables;
|
||||
|
||||
if (phase >= TVA_PHASE_DEAD || !playing) {
|
||||
partial->getSynth()->printDebug("TVA::nextPhase(): Shouldn't have got here with phase %d, playing=%s", phase, playing ? "true" : "false");
|
||||
return;
|
||||
}
|
||||
int newPhase = phase + 1;
|
||||
|
||||
if (newPhase == TVA_PHASE_DEAD) {
|
||||
end(newPhase);
|
||||
return;
|
||||
}
|
||||
|
||||
bool allLevelsZeroFromNowOn = false;
|
||||
if (partialParam->tva.envLevel[3] == 0) {
|
||||
if (newPhase == TVA_PHASE_4) {
|
||||
allLevelsZeroFromNowOn = true;
|
||||
} else if (partialParam->tva.envLevel[2] == 0) {
|
||||
if (newPhase == TVA_PHASE_3) {
|
||||
allLevelsZeroFromNowOn = true;
|
||||
} else if (partialParam->tva.envLevel[1] == 0) {
|
||||
if (newPhase == TVA_PHASE_2) {
|
||||
allLevelsZeroFromNowOn = true;
|
||||
} else if (partialParam->tva.envLevel[0] == 0) {
|
||||
if (newPhase == TVA_PHASE_ATTACK) { // this line added, missing in ROM - FIXME: Add description of repercussions
|
||||
allLevelsZeroFromNowOn = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int newTarget;
|
||||
int newIncrement;
|
||||
int envPointIndex = phase;
|
||||
|
||||
if (!allLevelsZeroFromNowOn) {
|
||||
newTarget = calcBasicAmp(tables, partial, system_, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
|
||||
|
||||
if (newPhase == TVA_PHASE_SUSTAIN || newPhase == TVA_PHASE_RELEASE) {
|
||||
if (partialParam->tva.envLevel[3] == 0) {
|
||||
end(newPhase);
|
||||
return;
|
||||
}
|
||||
if (!partial->getPoly()->canSustain()) {
|
||||
newPhase = TVA_PHASE_RELEASE;
|
||||
newTarget = 0;
|
||||
newIncrement = -partialParam->tva.envTime[4];
|
||||
if (newIncrement == 0) {
|
||||
// We can't let the increment be 0, or there would be no emulated interrupt.
|
||||
// So we do an "upward" increment, which should set the amp to 0 extremely quickly
|
||||
// and cause an "interrupt" to bring us back to nextPhase().
|
||||
newIncrement = 1;
|
||||
}
|
||||
} else {
|
||||
newTarget += partialParam->tva.envLevel[3];
|
||||
newIncrement = 0;
|
||||
}
|
||||
} else {
|
||||
newTarget += partialParam->tva.envLevel[envPointIndex];
|
||||
}
|
||||
} else {
|
||||
newTarget = 0;
|
||||
}
|
||||
|
||||
if ((newPhase != TVA_PHASE_SUSTAIN && newPhase != TVA_PHASE_RELEASE) || allLevelsZeroFromNowOn) {
|
||||
int envTimeSetting = partialParam->tva.envTime[envPointIndex];
|
||||
|
||||
if (newPhase == TVA_PHASE_ATTACK) {
|
||||
envTimeSetting -= ((signed)partial->getPoly()->getVelocity() - 64) >> (6 - partialParam->tva.envTimeVeloSensitivity); // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
|
||||
if (envTimeSetting <= 0 && partialParam->tva.envTime[envPointIndex] != 0) {
|
||||
envTimeSetting = 1;
|
||||
}
|
||||
} else {
|
||||
envTimeSetting -= keyTimeSubtraction;
|
||||
}
|
||||
if (envTimeSetting > 0) {
|
||||
int targetDelta = newTarget - target;
|
||||
if (targetDelta <= 0) {
|
||||
if (targetDelta == 0) {
|
||||
// target and newTarget are the same.
|
||||
// We can't have an increment of 0 or we wouldn't get an emulated interrupt.
|
||||
// So instead make the target one less than it really should be and set targetDelta accordingly.
|
||||
targetDelta = -1;
|
||||
newTarget--;
|
||||
if (newTarget < 0) {
|
||||
// Oops, newTarget is less than zero now, so let's do it the other way:
|
||||
// Make newTarget one more than it really should've been and set targetDelta accordingly.
|
||||
// FIXME (apparent bug in real firmware):
|
||||
// This means targetDelta will be positive just below here where it's inverted, and we'll end up using envLogarithmicTime[-1], and we'll be setting newIncrement to be descending later on, etc..
|
||||
targetDelta = 1;
|
||||
newTarget = -newTarget;
|
||||
}
|
||||
}
|
||||
targetDelta = -targetDelta;
|
||||
newIncrement = tables->envLogarithmicTime[(Bit8u)targetDelta] - envTimeSetting;
|
||||
if (newIncrement <= 0) {
|
||||
newIncrement = 1;
|
||||
}
|
||||
newIncrement = newIncrement | 0x80;
|
||||
} else {
|
||||
// FIXME: The last 22 or so entries in this table are 128 - surely that fucks things up, since that ends up being -128 signed?
|
||||
newIncrement = tables->envLogarithmicTime[(Bit8u)targetDelta] - envTimeSetting;
|
||||
if (newIncrement <= 0) {
|
||||
newIncrement = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newIncrement = newTarget >= target ? (0x80 | 127) : 127;
|
||||
}
|
||||
|
||||
// FIXME: What's the point of this? It's checked or set to non-zero everywhere above
|
||||
if (newIncrement == 0) {
|
||||
newIncrement = 1;
|
||||
}
|
||||
}
|
||||
|
||||
startRamp((Bit8u)newTarget, (Bit8u)newIncrement, newPhase);
|
||||
}
|
||||
|
||||
}
|
94
audio/softsynth/mt32/TVA.h
Normal file
94
audio/softsynth/mt32/TVA.h
Normal file
|
@ -0,0 +1,94 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_TVA_H
|
||||
#define MT32EMU_TVA_H
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class Part;
|
||||
|
||||
// Note that when entering nextPhase(), newPhase is set to phase + 1, and the descriptions/names below refer to
|
||||
// newPhase's value.
|
||||
enum {
|
||||
// In this phase, the base amp (as calculated in calcBasicAmp()) is targeted with an instant time.
|
||||
// This phase is entered by reset() only if time[0] != 0.
|
||||
TVA_PHASE_BASIC = 0,
|
||||
|
||||
// In this phase, level[0] is targeted within time[0], and velocity potentially affects time
|
||||
TVA_PHASE_ATTACK = 1,
|
||||
|
||||
// In this phase, level[1] is targeted within time[1]
|
||||
TVA_PHASE_2 = 2,
|
||||
|
||||
// In this phase, level[2] is targeted within time[2]
|
||||
TVA_PHASE_3 = 3,
|
||||
|
||||
// In this phase, level[3] is targeted within time[3]
|
||||
TVA_PHASE_4 = 4,
|
||||
|
||||
// In this phase, immediately goes to PHASE_RELEASE unless the poly is set to sustain.
|
||||
// Aborts the partial if level[3] is 0.
|
||||
// Otherwise level[3] is continued, no phase change will occur until some external influence (like pedal release)
|
||||
TVA_PHASE_SUSTAIN = 5,
|
||||
|
||||
// In this phase, 0 is targeted within time[4] (the time calculation is quite different from the other phases)
|
||||
TVA_PHASE_RELEASE = 6,
|
||||
|
||||
// It's PHASE_DEAD, Jim.
|
||||
TVA_PHASE_DEAD = 7
|
||||
};
|
||||
|
||||
class TVA {
|
||||
private:
|
||||
const Partial * const partial;
|
||||
LA32Ramp *ampRamp;
|
||||
const MemParams::System * const system_;
|
||||
|
||||
const Part *part;
|
||||
const TimbreParam::PartialParam *partialParam;
|
||||
const MemParams::PatchTemp *patchTemp;
|
||||
const MemParams::RhythmTemp *rhythmTemp;
|
||||
|
||||
bool playing;
|
||||
|
||||
int biasAmpSubtraction;
|
||||
int veloAmpSubtraction;
|
||||
int keyTimeSubtraction;
|
||||
|
||||
Bit8u target;
|
||||
int phase;
|
||||
|
||||
void startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase);
|
||||
void end(int newPhase);
|
||||
void nextPhase();
|
||||
|
||||
public:
|
||||
TVA(const Partial *partial, LA32Ramp *ampRamp);
|
||||
void reset(const Part *part, const TimbreParam::PartialParam *partialParam, const MemParams::RhythmTemp *rhythmTemp);
|
||||
void handleInterrupt();
|
||||
void recalcSustain();
|
||||
void startDecay();
|
||||
void startAbort();
|
||||
|
||||
bool isPlaying() const;
|
||||
int getPhase() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* TVA_H_ */
|
230
audio/softsynth/mt32/TVF.cpp
Normal file
230
audio/softsynth/mt32/TVF.cpp
Normal file
|
@ -0,0 +1,230 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//#include <cmath>
|
||||
|
||||
#include "mt32emu.h"
|
||||
#include "mmath.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// Note that when entering nextPhase(), newPhase is set to phase + 1, and the descriptions/names below refer to
|
||||
// newPhase's value.
|
||||
enum {
|
||||
// When this is the target phase, level[0] is targeted within time[0]
|
||||
// Note that this phase is always set up in reset(), not nextPhase()
|
||||
PHASE_ATTACK = 1,
|
||||
|
||||
// When this is the target phase, level[1] is targeted within time[1]
|
||||
PHASE_2 = 2,
|
||||
|
||||
// When this is the target phase, level[2] is targeted within time[2]
|
||||
PHASE_3 = 3,
|
||||
|
||||
// When this is the target phase, level[3] is targeted within time[3]
|
||||
PHASE_4 = 4,
|
||||
|
||||
// When this is the target phase, immediately goes to PHASE_RELEASE unless the poly is set to sustain.
|
||||
// Otherwise level[3] is continued with increment 0 - no phase change will occur until some external influence (like pedal release)
|
||||
PHASE_SUSTAIN = 5,
|
||||
|
||||
// 0 is targeted within time[4] (the time calculation is quite different from the other phases)
|
||||
PHASE_RELEASE = 6,
|
||||
|
||||
// 0 is targeted with increment 0 (thus theoretically staying that way forever)
|
||||
PHASE_DONE = 7
|
||||
};
|
||||
|
||||
static int calcBaseCutoff(const TimbreParam::PartialParam *partialParam, Bit32u basePitch, unsigned int key) {
|
||||
// This table matches the values used by a real LAPC-I.
|
||||
static const Bit8s biasLevelToBiasMult[] = {85, 42, 21, 16, 10, 5, 2, 0, -2, -5, -10, -16, -21, -74, -85};
|
||||
// These values represent unique options with no consistent pattern, so we have to use something like a table in any case.
|
||||
// The table entries, when divided by 21, match approximately what the manual claims:
|
||||
// -1, -1/2, -1/4, 0, 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, 1, 5/4, 3/2, 2, s1, s2
|
||||
// Note that the entry for 1/8 is rounded to 2 (from 1/8 * 21 = 2.625), which seems strangely inaccurate compared to the others.
|
||||
static const Bit8s keyfollowMult21[] = {-21, -10, -5, 0, 2, 5, 8, 10, 13, 16, 18, 21, 26, 32, 42, 21, 21};
|
||||
int baseCutoff = keyfollowMult21[partialParam->tvf.keyfollow] - keyfollowMult21[partialParam->wg.pitchKeyfollow];
|
||||
// baseCutoff range now: -63 to 63
|
||||
baseCutoff *= (int)key - 60;
|
||||
// baseCutoff range now: -3024 to 3024
|
||||
int biasPoint = partialParam->tvf.biasPoint;
|
||||
if ((biasPoint & 0x40) == 0) {
|
||||
// biasPoint range here: 0 to 63
|
||||
int bias = biasPoint + 33 - key; // bias range here: -75 to 84
|
||||
if (bias > 0) {
|
||||
bias = -bias; // bias range here: -1 to -84
|
||||
baseCutoff += bias * biasLevelToBiasMult[partialParam->tvf.biasLevel]; // Calculation range: -7140 to 7140
|
||||
// baseCutoff range now: -10164 to 10164
|
||||
}
|
||||
} else {
|
||||
// biasPoint range here: 64 to 127
|
||||
int bias = biasPoint - 31 - key; // bias range here: -75 to 84
|
||||
if (bias < 0) {
|
||||
baseCutoff += bias * biasLevelToBiasMult[partialParam->tvf.biasLevel]; // Calculation range: −6375 to 6375
|
||||
// baseCutoff range now: -9399 to 9399
|
||||
}
|
||||
}
|
||||
// baseCutoff range now: -10164 to 10164
|
||||
baseCutoff += ((partialParam->tvf.cutoff << 4) - 800);
|
||||
// baseCutoff range now: -10964 to 10964
|
||||
if (baseCutoff >= 0) {
|
||||
// FIXME: Potentially bad if baseCutoff ends up below -2056?
|
||||
int pitchDeltaThing = (basePitch >> 4) + baseCutoff - 3584;
|
||||
if (pitchDeltaThing > 0) {
|
||||
baseCutoff -= pitchDeltaThing;
|
||||
}
|
||||
} else if (baseCutoff < -2048) {
|
||||
baseCutoff = -2048;
|
||||
}
|
||||
baseCutoff += 2056;
|
||||
baseCutoff >>= 4; // PORTABILITY NOTE: Hmm... Depends whether it could've been below -2056, but maybe arithmetic shift assumed?
|
||||
if (baseCutoff > 255) {
|
||||
baseCutoff = 255;
|
||||
}
|
||||
return (Bit8u)baseCutoff;
|
||||
}
|
||||
|
||||
TVF::TVF(const Partial *usePartial, LA32Ramp *useCutoffModifierRamp) :
|
||||
partial(usePartial), cutoffModifierRamp(useCutoffModifierRamp) {
|
||||
}
|
||||
|
||||
void TVF::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) {
|
||||
target = newTarget;
|
||||
phase = newPhase;
|
||||
cutoffModifierRamp->startRamp(newTarget, newIncrement);
|
||||
#if MT32EMU_MONITOR_TVF >= 1
|
||||
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVF,ramp,%d,%d,%d,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newTarget, (newIncrement & 0x80) ? -1 : 1, (newIncrement & 0x7F), newPhase);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TVF::reset(const TimbreParam::PartialParam *newPartialParam, unsigned int basePitch) {
|
||||
partialParam = newPartialParam;
|
||||
|
||||
unsigned int key = partial->getPoly()->getKey();
|
||||
unsigned int velocity = partial->getPoly()->getVelocity();
|
||||
|
||||
Tables *tables = &partial->getSynth()->tables;
|
||||
|
||||
baseCutoff = calcBaseCutoff(newPartialParam, basePitch, key);
|
||||
#if MT32EMU_MONITOR_TVF >= 1
|
||||
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVF,base,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), baseCutoff);
|
||||
#endif
|
||||
|
||||
int newLevelMult = velocity * newPartialParam->tvf.envVeloSensitivity;
|
||||
newLevelMult >>= 6;
|
||||
newLevelMult += 109 - newPartialParam->tvf.envVeloSensitivity;
|
||||
newLevelMult += ((signed)key - 60) >> (4 - newPartialParam->tvf.envDepthKeyfollow);
|
||||
if (newLevelMult < 0) {
|
||||
newLevelMult = 0;
|
||||
}
|
||||
newLevelMult *= newPartialParam->tvf.envDepth;
|
||||
newLevelMult >>= 6;
|
||||
if (newLevelMult > 255) {
|
||||
newLevelMult = 255;
|
||||
}
|
||||
levelMult = newLevelMult;
|
||||
|
||||
if (newPartialParam->tvf.envTimeKeyfollow != 0) {
|
||||
keyTimeSubtraction = ((signed)key - 60) >> (5 - newPartialParam->tvf.envTimeKeyfollow);
|
||||
} else {
|
||||
keyTimeSubtraction = 0;
|
||||
}
|
||||
|
||||
int newTarget = (newLevelMult * newPartialParam->tvf.envLevel[0]) >> 8;
|
||||
int envTimeSetting = newPartialParam->tvf.envTime[0] - keyTimeSubtraction;
|
||||
int newIncrement;
|
||||
if (envTimeSetting <= 0) {
|
||||
newIncrement = (0x80 | 127);
|
||||
} else {
|
||||
newIncrement = tables->envLogarithmicTime[newTarget] - envTimeSetting;
|
||||
if (newIncrement <= 0) {
|
||||
newIncrement = 1;
|
||||
}
|
||||
}
|
||||
cutoffModifierRamp->reset();
|
||||
startRamp(newTarget, newIncrement, PHASE_2 - 1);
|
||||
}
|
||||
|
||||
Bit8u TVF::getBaseCutoff() const {
|
||||
return baseCutoff;
|
||||
}
|
||||
|
||||
void TVF::handleInterrupt() {
|
||||
nextPhase();
|
||||
}
|
||||
|
||||
void TVF::startDecay() {
|
||||
if (phase >= PHASE_RELEASE) {
|
||||
return;
|
||||
}
|
||||
if (partialParam->tvf.envTime[4] == 0) {
|
||||
startRamp(0, 1, PHASE_DONE - 1);
|
||||
} else {
|
||||
startRamp(0, -partialParam->tvf.envTime[4], PHASE_DONE - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void TVF::nextPhase() {
|
||||
Tables *tables = &partial->getSynth()->tables;
|
||||
int newPhase = phase + 1;
|
||||
|
||||
switch (newPhase) {
|
||||
case PHASE_DONE:
|
||||
startRamp(0, 0, newPhase);
|
||||
return;
|
||||
case PHASE_SUSTAIN:
|
||||
case PHASE_RELEASE:
|
||||
// FIXME: Afaict newPhase should never be PHASE_RELEASE here. And if it were, this is an odd way to handle it.
|
||||
if (!partial->getPoly()->canSustain()) {
|
||||
phase = newPhase; // FIXME: Correct?
|
||||
startDecay(); // FIXME: This should actually start decay even if phase is already 6. Does that matter?
|
||||
return;
|
||||
}
|
||||
startRamp((levelMult * partialParam->tvf.envLevel[3]) >> 8, 0, newPhase);
|
||||
return;
|
||||
}
|
||||
|
||||
int envPointIndex = phase;
|
||||
int envTimeSetting = partialParam->tvf.envTime[envPointIndex] - keyTimeSubtraction;
|
||||
|
||||
int newTarget = (levelMult * partialParam->tvf.envLevel[envPointIndex]) >> 8;
|
||||
int newIncrement;
|
||||
if (envTimeSetting > 0) {
|
||||
int targetDelta = newTarget - target;
|
||||
if (targetDelta == 0) {
|
||||
if (newTarget == 0) {
|
||||
targetDelta = 1;
|
||||
newTarget = 1;
|
||||
} else {
|
||||
targetDelta = -1;
|
||||
newTarget--;
|
||||
}
|
||||
}
|
||||
newIncrement = tables->envLogarithmicTime[targetDelta < 0 ? -targetDelta : targetDelta] - envTimeSetting;
|
||||
if (newIncrement <= 0) {
|
||||
newIncrement = 1;
|
||||
}
|
||||
if (targetDelta < 0) {
|
||||
newIncrement |= 0x80;
|
||||
}
|
||||
} else {
|
||||
newIncrement = newTarget >= target ? (0x80 | 127) : 127;
|
||||
}
|
||||
startRamp(newTarget, newIncrement, newPhase);
|
||||
}
|
||||
|
||||
}
|
54
audio/softsynth/mt32/TVF.h
Normal file
54
audio/softsynth/mt32/TVF.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_TVF_H
|
||||
#define MT32EMU_TVF_H
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class TVF {
|
||||
private:
|
||||
const Partial * const partial;
|
||||
LA32Ramp *cutoffModifierRamp;
|
||||
const TimbreParam::PartialParam *partialParam;
|
||||
|
||||
Bit8u baseCutoff;
|
||||
int keyTimeSubtraction;
|
||||
unsigned int levelMult;
|
||||
|
||||
Bit8u target;
|
||||
unsigned int phase;
|
||||
|
||||
void startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase);
|
||||
void nextPhase();
|
||||
|
||||
public:
|
||||
TVF(const Partial *partial, LA32Ramp *cutoffModifierRamp);
|
||||
void reset(const TimbreParam::PartialParam *partialParam, Bit32u basePitch);
|
||||
// Returns the base cutoff (without envelope modification).
|
||||
// The base cutoff is calculated when reset() is called and remains static
|
||||
// for the lifetime of the partial.
|
||||
// Barring bugs, the number returned is confirmed accurate
|
||||
// (based on specs from Mok).
|
||||
Bit8u getBaseCutoff() const;
|
||||
void handleInterrupt();
|
||||
void startDecay();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
321
audio/softsynth/mt32/TVP.cpp
Normal file
321
audio/softsynth/mt32/TVP.cpp
Normal file
|
@ -0,0 +1,321 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//#include <cmath>
|
||||
//#include <cstdlib>
|
||||
|
||||
#include "mt32emu.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// FIXME: Add Explanation
|
||||
static Bit16u lowerDurationToDivisor[] = {34078, 37162, 40526, 44194, 48194, 52556, 57312, 62499};
|
||||
|
||||
// These values represent unique options with no consistent pattern, so we have to use something like a table in any case.
|
||||
// The table matches exactly what the manual claims (when divided by 8192):
|
||||
// -1, -1/2, -1/4, 0, 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, 1, 5/4, 3/2, 2, s1, s2
|
||||
// ...except for the last two entries, which are supposed to be "1 cent above 1" and "2 cents above 1", respectively. They can only be roughly approximated with this integer math.
|
||||
static Bit16s pitchKeyfollowMult[] = {-8192, -4096, -2048, 0, 1024, 2048, 3072, 4096, 5120, 6144, 7168, 8192, 10240, 12288, 16384, 8198, 8226};
|
||||
|
||||
// Note: Keys < 60 use keyToPitchTable[60 - key], keys >= 60 use keyToPitchTable[key - 60].
|
||||
// FIXME: This table could really be shorter, since we never use e.g. key 127.
|
||||
static Bit16u keyToPitchTable[] = {
|
||||
0, 341, 683, 1024, 1365, 1707, 2048, 2389,
|
||||
2731, 3072, 3413, 3755, 4096, 4437, 4779, 5120,
|
||||
5461, 5803, 6144, 6485, 6827, 7168, 7509, 7851,
|
||||
8192, 8533, 8875, 9216, 9557, 9899, 10240, 10581,
|
||||
10923, 11264, 11605, 11947, 12288, 12629, 12971, 13312,
|
||||
13653, 13995, 14336, 14677, 15019, 15360, 15701, 16043,
|
||||
16384, 16725, 17067, 17408, 17749, 18091, 18432, 18773,
|
||||
19115, 19456, 19797, 20139, 20480, 20821, 21163, 21504,
|
||||
21845, 22187, 22528, 22869
|
||||
};
|
||||
|
||||
TVP::TVP(const Partial *usePartial) :
|
||||
partial(usePartial), system_(&usePartial->getSynth()->mt32ram.system) {
|
||||
unsigned int sampleRate = usePartial->getSynth()->myProp.sampleRate;
|
||||
// We want to do processing 4000 times per second. FIXME: This is pretty arbitrary.
|
||||
maxCounter = sampleRate / 4000;
|
||||
// The timer runs at 500kHz. We only need to bother updating it every maxCounter samples, before we do processing.
|
||||
// This is how much to increment it by every maxCounter samples.
|
||||
processTimerIncrement = 500000 * maxCounter / sampleRate;
|
||||
}
|
||||
|
||||
static Bit16s keyToPitch(unsigned int key) {
|
||||
// We're using a table to do: return round_to_nearest_or_even((key - 60) * (4096.0 / 12.0))
|
||||
// Banker's rounding is just slightly annoying to do in C++
|
||||
int k = (int)key;
|
||||
Bit16s pitch = keyToPitchTable[abs(k - 60)];
|
||||
return key < 60 ? -pitch : pitch;
|
||||
}
|
||||
|
||||
static inline Bit32s coarseToPitch(Bit8u coarse) {
|
||||
return (coarse - 36) * 4096 / 12; // One semitone per coarse offset
|
||||
}
|
||||
|
||||
static inline Bit32s fineToPitch(Bit8u fine) {
|
||||
return (fine - 50) * 4096 / 1200; // One cent per fine offset
|
||||
}
|
||||
|
||||
static Bit32u calcBasePitch(const Partial *partial, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, unsigned int key) {
|
||||
Bit32s basePitch = keyToPitch(key);
|
||||
basePitch = (basePitch * pitchKeyfollowMult[partialParam->wg.pitchKeyfollow]) >> 13; // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
basePitch += coarseToPitch(partialParam->wg.pitchCoarse);
|
||||
basePitch += fineToPitch(partialParam->wg.pitchFine);
|
||||
// NOTE:Mok: This is done on MT-32, but not LAPC-I:
|
||||
//pitch += coarseToPitch(patchTemp->patch.keyShift + 12);
|
||||
basePitch += fineToPitch(patchTemp->patch.fineTune);
|
||||
|
||||
const ControlROMPCMStruct *controlROMPCMStruct = partial->getControlROMPCMStruct();
|
||||
if (controlROMPCMStruct != NULL) {
|
||||
basePitch += (Bit32s)((((Bit32s)controlROMPCMStruct->pitchMSB) << 8) | (Bit32s)controlROMPCMStruct->pitchLSB);
|
||||
} else {
|
||||
if ((partialParam->wg.waveform & 1) == 0) {
|
||||
basePitch += 37133; // This puts Middle C at around 261.64Hz (assuming no other modifications, masterTune of 64, etc.)
|
||||
} else {
|
||||
// Sawtooth waves are effectively double the frequency of square waves.
|
||||
// Thus we add 4096 less than for square waves here, which results in halving the frequency.
|
||||
basePitch += 33037;
|
||||
}
|
||||
}
|
||||
if (basePitch < 0) {
|
||||
basePitch = 0;
|
||||
}
|
||||
if (basePitch > 59392) {
|
||||
basePitch = 59392;
|
||||
}
|
||||
return (Bit32u)basePitch;
|
||||
}
|
||||
|
||||
static Bit32u calcVeloMult(Bit8u veloSensitivity, unsigned int velocity) {
|
||||
if (veloSensitivity == 0 || veloSensitivity > 3) {
|
||||
// Note that on CM-32L/LAPC-I veloSensitivity is never > 3, since it's clipped to 3 by the max tables.
|
||||
return 21845; // aka floor(4096 / 12 * 64), aka ~64 semitones
|
||||
}
|
||||
// When velocity is 127, the multiplier is 21845, aka ~64 semitones (regardless of veloSensitivity).
|
||||
// The lower the velocity, the lower the multiplier. The veloSensitivity determines the amount decreased per velocity value.
|
||||
// The minimum multiplier (with velocity 0, veloSensitivity 3) is 170 (~half a semitone).
|
||||
Bit32u veloMult = 32768;
|
||||
veloMult -= (127 - velocity) << (5 + veloSensitivity);
|
||||
veloMult *= 21845;
|
||||
veloMult >>= 15;
|
||||
return veloMult;
|
||||
}
|
||||
|
||||
static Bit32s calcTargetPitchOffsetWithoutLFO(const TimbreParam::PartialParam *partialParam, int levelIndex, unsigned int velocity) {
|
||||
int veloMult = calcVeloMult(partialParam->pitchEnv.veloSensitivity, velocity);
|
||||
int targetPitchOffsetWithoutLFO = partialParam->pitchEnv.level[levelIndex] - 50;
|
||||
targetPitchOffsetWithoutLFO = (Bit32s)(targetPitchOffsetWithoutLFO * veloMult) >> (16 - partialParam->pitchEnv.depth); // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
return targetPitchOffsetWithoutLFO;
|
||||
}
|
||||
|
||||
void TVP::reset(const Part *usePart, const TimbreParam::PartialParam *usePartialParam) {
|
||||
part = usePart;
|
||||
partialParam = usePartialParam;
|
||||
patchTemp = part->getPatchTemp();
|
||||
|
||||
unsigned int key = partial->getPoly()->getKey();
|
||||
unsigned int velocity = partial->getPoly()->getVelocity();
|
||||
|
||||
// FIXME: We're using a per-TVP timer instead of a system-wide one for convenience.
|
||||
timeElapsed = 0;
|
||||
|
||||
basePitch = calcBasePitch(partial, partialParam, patchTemp, key);
|
||||
currentPitchOffset = calcTargetPitchOffsetWithoutLFO(partialParam, 0, velocity);
|
||||
targetPitchOffsetWithoutLFO = currentPitchOffset;
|
||||
phase = 0;
|
||||
|
||||
if (partialParam->pitchEnv.timeKeyfollow) {
|
||||
timeKeyfollowSubtraction = (key - 60) >> (5 - partialParam->pitchEnv.timeKeyfollow); // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
} else {
|
||||
timeKeyfollowSubtraction = 0;
|
||||
}
|
||||
lfoPitchOffset = 0;
|
||||
counter = 0;
|
||||
pitch = basePitch;
|
||||
|
||||
// These don't really need to be initialised, but it aids debugging.
|
||||
pitchOffsetChangePerBigTick = 0;
|
||||
targetPitchOffsetReachedBigTick = 0;
|
||||
shifts = 0;
|
||||
}
|
||||
|
||||
Bit32u TVP::getBasePitch() const {
|
||||
return basePitch;
|
||||
}
|
||||
|
||||
void TVP::updatePitch() {
|
||||
Bit32s newPitch = basePitch + currentPitchOffset;
|
||||
if (!partial->isPCM() || (partial->getControlROMPCMStruct()->len & 0x01) == 0) { // FIXME: Use !partial->pcmWaveEntry->unaffectedByMasterTune instead
|
||||
// FIXME: masterTune recalculation doesn't really happen here, and there are various bugs not yet emulated
|
||||
// 171 is ~half a semitone.
|
||||
newPitch += ((system_->masterTune - 64) * 171) >> 6; // PORTABILITY NOTE: Assumes arithmetic shift.
|
||||
}
|
||||
if ((partialParam->wg.pitchBenderEnabled & 1) != 0) {
|
||||
newPitch += part->getPitchBend();
|
||||
}
|
||||
if (newPitch < 0) {
|
||||
newPitch = 0;
|
||||
}
|
||||
if (newPitch > 59392) {
|
||||
newPitch = 59392;
|
||||
}
|
||||
pitch = (Bit16u)newPitch;
|
||||
|
||||
// FIXME: We're doing this here because that's what the CM-32L does - we should probably move this somewhere more appropriate in future.
|
||||
partial->tva->recalcSustain();
|
||||
}
|
||||
|
||||
void TVP::targetPitchOffsetReached() {
|
||||
currentPitchOffset = targetPitchOffsetWithoutLFO + lfoPitchOffset;
|
||||
|
||||
switch (phase) {
|
||||
case 3:
|
||||
case 4:
|
||||
{
|
||||
int newLFOPitchOffset = (part->getModulation() * partialParam->pitchLFO.modSensitivity) >> 7;
|
||||
newLFOPitchOffset = (newLFOPitchOffset + partialParam->pitchLFO.depth) << 1;
|
||||
if (pitchOffsetChangePerBigTick > 0) {
|
||||
// Go in the opposite direction to last time
|
||||
newLFOPitchOffset = -newLFOPitchOffset;
|
||||
}
|
||||
lfoPitchOffset = newLFOPitchOffset;
|
||||
int targetPitchOffset = targetPitchOffsetWithoutLFO + lfoPitchOffset;
|
||||
setupPitchChange(targetPitchOffset, 101 - partialParam->pitchLFO.rate);
|
||||
updatePitch();
|
||||
break;
|
||||
}
|
||||
case 6:
|
||||
updatePitch();
|
||||
break;
|
||||
default:
|
||||
nextPhase();
|
||||
}
|
||||
}
|
||||
|
||||
void TVP::nextPhase() {
|
||||
phase++;
|
||||
int envIndex = phase == 6 ? 4 : phase;
|
||||
|
||||
targetPitchOffsetWithoutLFO = calcTargetPitchOffsetWithoutLFO(partialParam, envIndex, partial->getPoly()->getVelocity()); // pitch we'll reach at the end
|
||||
|
||||
int changeDuration = partialParam->pitchEnv.time[envIndex - 1];
|
||||
changeDuration -= timeKeyfollowSubtraction;
|
||||
if (changeDuration > 0) {
|
||||
setupPitchChange(targetPitchOffsetWithoutLFO, changeDuration); // changeDuration between 0 and 112 now
|
||||
updatePitch();
|
||||
} else {
|
||||
targetPitchOffsetReached();
|
||||
}
|
||||
}
|
||||
|
||||
// Shifts val to the left until bit 31 is 1 and returns the number of shifts
|
||||
static Bit8u normalise(Bit32u &val) {
|
||||
Bit8u leftShifts;
|
||||
for (leftShifts = 0; leftShifts < 31; leftShifts++) {
|
||||
if ((val & 0x80000000) != 0) {
|
||||
break;
|
||||
}
|
||||
val = val << 1;
|
||||
}
|
||||
return leftShifts;
|
||||
}
|
||||
|
||||
void TVP::setupPitchChange(int targetPitchOffset, Bit8u changeDuration) {
|
||||
bool negativeDelta = targetPitchOffset < currentPitchOffset;
|
||||
Bit32s pitchOffsetDelta = targetPitchOffset - currentPitchOffset;
|
||||
if (pitchOffsetDelta > 32767 || pitchOffsetDelta < -32768) {
|
||||
pitchOffsetDelta = 32767;
|
||||
}
|
||||
if (negativeDelta) {
|
||||
pitchOffsetDelta = -pitchOffsetDelta;
|
||||
}
|
||||
// We want to maximise the number of bits of the Bit16s "pitchOffsetChangePerBigTick" we use in order to get the best possible precision later
|
||||
Bit32u absPitchOffsetDelta = pitchOffsetDelta << 16;
|
||||
Bit8u normalisationShifts = normalise(absPitchOffsetDelta); // FIXME: Double-check: normalisationShifts is usually between 0 and 15 here, unless the delta is 0, in which case it's 31
|
||||
absPitchOffsetDelta = absPitchOffsetDelta >> 1; // Make room for the sign bit
|
||||
|
||||
changeDuration--; // changeDuration's now between 0 and 111
|
||||
unsigned int upperDuration = changeDuration >> 3; // upperDuration's now between 0 and 13
|
||||
shifts = normalisationShifts + upperDuration + 2;
|
||||
Bit16u divisor = lowerDurationToDivisor[changeDuration & 7];
|
||||
Bit16s newPitchOffsetChangePerBigTick = ((absPitchOffsetDelta & 0xFFFF0000) / divisor) >> 1; // Result now fits within 15 bits. FIXME: Check nothing's getting sign-extended incorrectly
|
||||
if (negativeDelta) {
|
||||
newPitchOffsetChangePerBigTick = -newPitchOffsetChangePerBigTick;
|
||||
}
|
||||
pitchOffsetChangePerBigTick = newPitchOffsetChangePerBigTick;
|
||||
|
||||
int currentBigTick = timeElapsed >> 8;
|
||||
int durationInBigTicks = divisor >> (12 - upperDuration);
|
||||
if (durationInBigTicks > 32767) {
|
||||
durationInBigTicks = 32767;
|
||||
}
|
||||
// The result of the addition may exceed 16 bits, but wrapping is fine and intended here.
|
||||
targetPitchOffsetReachedBigTick = currentBigTick + durationInBigTicks;
|
||||
}
|
||||
|
||||
void TVP::startDecay() {
|
||||
phase = 5;
|
||||
lfoPitchOffset = 0;
|
||||
targetPitchOffsetReachedBigTick = timeElapsed >> 8; // FIXME: Afaict there's no good reason for this - check
|
||||
}
|
||||
|
||||
Bit16u TVP::nextPitch() {
|
||||
// FIXME: Write explanation of counter and time increment
|
||||
if (counter == 0) {
|
||||
timeElapsed += processTimerIncrement;
|
||||
timeElapsed = timeElapsed & 0x00FFFFFF;
|
||||
process();
|
||||
}
|
||||
counter = (counter + 1) % maxCounter;
|
||||
return pitch;
|
||||
}
|
||||
|
||||
void TVP::process() {
|
||||
if (phase == 0) {
|
||||
targetPitchOffsetReached();
|
||||
return;
|
||||
}
|
||||
if (phase == 5) {
|
||||
nextPhase();
|
||||
return;
|
||||
}
|
||||
if (phase > 7) {
|
||||
updatePitch();
|
||||
return;
|
||||
}
|
||||
|
||||
Bit16s negativeBigTicksRemaining = (timeElapsed >> 8) - targetPitchOffsetReachedBigTick;
|
||||
if (negativeBigTicksRemaining >= 0) {
|
||||
// We've reached the time for a phase change
|
||||
targetPitchOffsetReached();
|
||||
return;
|
||||
}
|
||||
// FIXME: Write explanation for this stuff
|
||||
int rightShifts = shifts;
|
||||
if (rightShifts > 13) {
|
||||
rightShifts -= 13;
|
||||
negativeBigTicksRemaining = negativeBigTicksRemaining >> rightShifts; // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
rightShifts = 13;
|
||||
}
|
||||
int newResult = ((Bit32s)(negativeBigTicksRemaining * pitchOffsetChangePerBigTick)) >> rightShifts; // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
newResult += targetPitchOffsetWithoutLFO + lfoPitchOffset;
|
||||
currentPitchOffset = newResult;
|
||||
updatePitch();
|
||||
}
|
||||
|
||||
}
|
67
audio/softsynth/mt32/TVP.h
Normal file
67
audio/softsynth/mt32/TVP.h
Normal file
|
@ -0,0 +1,67 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_TVP_H
|
||||
#define MT32EMU_TVP_H
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class TVP {
|
||||
private:
|
||||
const Partial * const partial;
|
||||
const MemParams::System * const system_; // FIXME: Only necessary because masterTune calculation is done in the wrong place atm.
|
||||
|
||||
const Part *part;
|
||||
const TimbreParam::PartialParam *partialParam;
|
||||
const MemParams::PatchTemp *patchTemp;
|
||||
|
||||
int maxCounter;
|
||||
int processTimerIncrement;
|
||||
int counter;
|
||||
Bit32u timeElapsed;
|
||||
|
||||
int phase;
|
||||
Bit32u basePitch;
|
||||
Bit32s targetPitchOffsetWithoutLFO;
|
||||
Bit32s currentPitchOffset;
|
||||
|
||||
Bit16s lfoPitchOffset;
|
||||
// In range -12 - 36
|
||||
Bit8s timeKeyfollowSubtraction;
|
||||
|
||||
Bit16s pitchOffsetChangePerBigTick;
|
||||
Bit16u targetPitchOffsetReachedBigTick;
|
||||
unsigned int shifts;
|
||||
|
||||
Bit16u pitch;
|
||||
|
||||
void updatePitch();
|
||||
void setupPitchChange(int targetPitchOffset, Bit8u changeDuration);
|
||||
void targetPitchOffsetReached();
|
||||
void nextPhase();
|
||||
void process();
|
||||
public:
|
||||
TVP(const Partial *partial);
|
||||
void reset(const Part *part, const TimbreParam::PartialParam *partialParam);
|
||||
Bit32u getBasePitch() const;
|
||||
Bit16u nextPitch();
|
||||
void startDecay();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,245 +1,245 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Comb filter implementation
|
||||
// Allpass filter implementation
|
||||
//
|
||||
// Written by
|
||||
// Written by Jezar at Dreampoint, June 2000
|
||||
// http://www.dreampoint.co.uk
|
||||
// This code is public domain
|
||||
|
||||
#include "audio/softsynth/mt32/freeverb.h"
|
||||
#include "freeverb.h"
|
||||
|
||||
comb::comb() {
|
||||
allpass::allpass()
|
||||
{
|
||||
bufidx = 0;
|
||||
}
|
||||
|
||||
void allpass::setbuffer(float *buf, int size)
|
||||
{
|
||||
buffer = buf;
|
||||
bufsize = size;
|
||||
}
|
||||
|
||||
void allpass::mute()
|
||||
{
|
||||
for (int i=0; i<bufsize; i++)
|
||||
buffer[i]=0;
|
||||
}
|
||||
|
||||
void allpass::setfeedback(float val)
|
||||
{
|
||||
feedback = val;
|
||||
}
|
||||
|
||||
float allpass::getfeedback()
|
||||
{
|
||||
return feedback;
|
||||
}
|
||||
|
||||
void allpass::deletebuffer()
|
||||
{
|
||||
delete[] buffer;
|
||||
buffer = 0;
|
||||
}
|
||||
// Comb filter implementation
|
||||
//
|
||||
// Written by Jezar at Dreampoint, June 2000
|
||||
// http://www.dreampoint.co.uk
|
||||
// This code is public domain
|
||||
|
||||
comb::comb()
|
||||
{
|
||||
filterstore = 0;
|
||||
bufidx = 0;
|
||||
}
|
||||
|
||||
void comb::setbuffer(float *buf, int size) {
|
||||
void comb::setbuffer(float *buf, int size)
|
||||
{
|
||||
buffer = buf;
|
||||
bufsize = size;
|
||||
}
|
||||
|
||||
void comb::mute() {
|
||||
for (int i = 0; i < bufsize; i++)
|
||||
buffer[i] = 0;
|
||||
void comb::mute()
|
||||
{
|
||||
for (int i=0; i<bufsize; i++)
|
||||
buffer[i]=0;
|
||||
}
|
||||
|
||||
void comb::setdamp(float val) {
|
||||
void comb::setdamp(float val)
|
||||
{
|
||||
damp1 = val;
|
||||
damp2 = 1 - val;
|
||||
damp2 = 1-val;
|
||||
}
|
||||
|
||||
float comb::getdamp() {
|
||||
float comb::getdamp()
|
||||
{
|
||||
return damp1;
|
||||
}
|
||||
|
||||
void comb::setfeedback(float val) {
|
||||
void comb::setfeedback(float val)
|
||||
{
|
||||
feedback = val;
|
||||
}
|
||||
|
||||
float comb::getfeedback() {
|
||||
float comb::getfeedback()
|
||||
{
|
||||
return feedback;
|
||||
}
|
||||
|
||||
// Allpass filter implementation
|
||||
|
||||
allpass::allpass() {
|
||||
bufidx = 0;
|
||||
void comb::deletebuffer()
|
||||
{
|
||||
delete[] buffer;
|
||||
buffer = 0;
|
||||
}
|
||||
|
||||
void allpass::setbuffer(float *buf, int size) {
|
||||
buffer = buf;
|
||||
bufsize = size;
|
||||
}
|
||||
|
||||
void allpass::mute() {
|
||||
for (int i = 0; i < bufsize; i++)
|
||||
buffer[i] = 0;
|
||||
}
|
||||
|
||||
void allpass::setfeedback(float val) {
|
||||
feedback = val;
|
||||
}
|
||||
|
||||
float allpass::getfeedback() {
|
||||
return feedback;
|
||||
}
|
||||
|
||||
// Reverb model implementation
|
||||
//
|
||||
// Written by Jezar at Dreampoint, June 2000
|
||||
// Modifications by Jerome Fisher, 2009, 2011
|
||||
// http://www.dreampoint.co.uk
|
||||
// This code is public domain
|
||||
|
||||
revmodel::revmodel() {
|
||||
// Tie the components to their buffers
|
||||
combL[0].setbuffer(bufcombL1,combtuningL1);
|
||||
combR[0].setbuffer(bufcombR1,combtuningR1);
|
||||
combL[1].setbuffer(bufcombL2,combtuningL2);
|
||||
combR[1].setbuffer(bufcombR2,combtuningR2);
|
||||
combL[2].setbuffer(bufcombL3,combtuningL3);
|
||||
combR[2].setbuffer(bufcombR3,combtuningR3);
|
||||
combL[3].setbuffer(bufcombL4,combtuningL4);
|
||||
combR[3].setbuffer(bufcombR4,combtuningR4);
|
||||
combL[4].setbuffer(bufcombL5,combtuningL5);
|
||||
combR[4].setbuffer(bufcombR5,combtuningR5);
|
||||
combL[5].setbuffer(bufcombL6,combtuningL6);
|
||||
combR[5].setbuffer(bufcombR6,combtuningR6);
|
||||
combL[6].setbuffer(bufcombL7,combtuningL7);
|
||||
combR[6].setbuffer(bufcombR7,combtuningR7);
|
||||
combL[7].setbuffer(bufcombL8,combtuningL8);
|
||||
combR[7].setbuffer(bufcombR8,combtuningR8);
|
||||
allpassL[0].setbuffer(bufallpassL1,allpasstuningL1);
|
||||
allpassR[0].setbuffer(bufallpassR1,allpasstuningR1);
|
||||
allpassL[1].setbuffer(bufallpassL2,allpasstuningL2);
|
||||
allpassR[1].setbuffer(bufallpassR2,allpasstuningR2);
|
||||
allpassL[2].setbuffer(bufallpassL3,allpasstuningL3);
|
||||
allpassR[2].setbuffer(bufallpassR3,allpasstuningR3);
|
||||
allpassL[3].setbuffer(bufallpassL4,allpasstuningL4);
|
||||
allpassR[3].setbuffer(bufallpassR4,allpasstuningR4);
|
||||
revmodel::revmodel(float scaletuning)
|
||||
{
|
||||
int i;
|
||||
int bufsize;
|
||||
|
||||
// Allocate buffers for the components
|
||||
for (i = 0; i < numcombs; i++) {
|
||||
bufsize = int(scaletuning * combtuning[i]);
|
||||
combL[i].setbuffer(new float[bufsize], bufsize);
|
||||
bufsize += int(scaletuning * stereospread);
|
||||
combR[i].setbuffer(new float[bufsize], bufsize);
|
||||
}
|
||||
for (i = 0; i < numallpasses; i++) {
|
||||
bufsize = int(scaletuning * allpasstuning[i]);
|
||||
allpassL[i].setbuffer(new float[bufsize], bufsize);
|
||||
allpassL[i].setfeedback(0.5f);
|
||||
bufsize += int(scaletuning * stereospread);
|
||||
allpassR[i].setbuffer(new float[bufsize], bufsize);
|
||||
allpassR[i].setfeedback(0.5f);
|
||||
}
|
||||
|
||||
// Set default values
|
||||
allpassL[0].setfeedback(0.5f);
|
||||
allpassR[0].setfeedback(0.5f);
|
||||
allpassL[1].setfeedback(0.5f);
|
||||
allpassR[1].setfeedback(0.5f);
|
||||
allpassL[2].setfeedback(0.5f);
|
||||
allpassR[2].setfeedback(0.5f);
|
||||
allpassL[3].setfeedback(0.5f);
|
||||
allpassR[3].setfeedback(0.5f);
|
||||
setmode(initialmode);
|
||||
setwet(initialwet);
|
||||
setroomsize(initialroom);
|
||||
setdry(initialdry);
|
||||
setdamp(initialdamp);
|
||||
setwidth(initialwidth);
|
||||
dry = initialdry;
|
||||
wet = initialwet*scalewet;
|
||||
damp = initialdamp*scaledamp;
|
||||
roomsize = (initialroom*scaleroom) + offsetroom;
|
||||
width = initialwidth;
|
||||
mode = initialmode;
|
||||
update();
|
||||
|
||||
// Buffer will be full of rubbish - so we MUST mute them
|
||||
mute();
|
||||
}
|
||||
|
||||
void revmodel::mute() {
|
||||
revmodel::~revmodel()
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < numcombs; i++) {
|
||||
combL[i].deletebuffer();
|
||||
combR[i].deletebuffer();
|
||||
}
|
||||
for (i = 0; i < numallpasses; i++) {
|
||||
allpassL[i].deletebuffer();
|
||||
allpassR[i].deletebuffer();
|
||||
}
|
||||
}
|
||||
|
||||
void revmodel::mute()
|
||||
{
|
||||
int i;
|
||||
|
||||
if (getmode() >= freezemode)
|
||||
return;
|
||||
|
||||
for (i = 0; i < numcombs; i++) {
|
||||
for (i=0;i<numcombs;i++)
|
||||
{
|
||||
combL[i].mute();
|
||||
combR[i].mute();
|
||||
}
|
||||
|
||||
for (i = 0; i < numallpasses; i++) {
|
||||
for (i=0;i<numallpasses;i++)
|
||||
{
|
||||
allpassL[i].mute();
|
||||
allpassR[i].mute();
|
||||
}
|
||||
|
||||
// Init LPF history
|
||||
filtprev1 = 0;
|
||||
filtprev2 = 0;
|
||||
}
|
||||
|
||||
void revmodel::processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip) {
|
||||
float outL, outR, input;
|
||||
void revmodel::process(const float *inputL, const float *inputR, float *outputL, float *outputR, long numsamples)
|
||||
{
|
||||
float outL,outR,input;
|
||||
|
||||
while (numsamples-- > 0) {
|
||||
while (numsamples-- > 0)
|
||||
{
|
||||
int i;
|
||||
|
||||
outL = outR = 0;
|
||||
input = (*inputL + *inputR) * gain;
|
||||
|
||||
// Implementation of 2-stage IIR single-pole low-pass filter
|
||||
// found at the entrance of reverb processing on real devices
|
||||
filtprev1 += (input - filtprev1) * filtval;
|
||||
filtprev2 += (filtprev1 - filtprev2) * filtval;
|
||||
input = filtprev2;
|
||||
|
||||
int s = -1;
|
||||
// Accumulate comb filters in parallel
|
||||
for (i = 0; i < numcombs; i++) {
|
||||
outL += combL[i].process(input);
|
||||
outR += combR[i].process(input);
|
||||
for (i=0; i<numcombs; i++)
|
||||
{
|
||||
outL += s * combL[i].process(input);
|
||||
outR += s * combR[i].process(input);
|
||||
s = -s;
|
||||
}
|
||||
|
||||
// Feed through allpasses in series
|
||||
for (i = 0; i < numallpasses; i++) {
|
||||
for (i=0; i<numallpasses; i++)
|
||||
{
|
||||
outL = allpassL[i].process(outL);
|
||||
outR = allpassR[i].process(outR);
|
||||
}
|
||||
|
||||
// Calculate output REPLACING anything already there
|
||||
*outputL = outL * wet1 + outR * wet2 + *inputL * dry;
|
||||
*outputR = outR * wet1 + outL * wet2 + *inputR * dry;
|
||||
*outputL = outL*wet1 + outR*wet2;
|
||||
*outputR = outR*wet1 + outL*wet2;
|
||||
|
||||
// Increment sample pointers, allowing for interleave (if any)
|
||||
inputL += skip;
|
||||
inputR += skip;
|
||||
outputL += skip;
|
||||
outputR += skip;
|
||||
inputL++;
|
||||
inputR++;
|
||||
outputL++;
|
||||
outputR++;
|
||||
}
|
||||
}
|
||||
|
||||
void revmodel::processmix(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip) {
|
||||
float outL, outR, input;
|
||||
|
||||
while (numsamples-- > 0) {
|
||||
int i;
|
||||
|
||||
outL = outR = 0;
|
||||
input = (*inputL + *inputR) * gain;
|
||||
|
||||
// Accumulate comb filters in parallel
|
||||
for (i = 0; i < numcombs; i++) {
|
||||
outL += combL[i].process(input);
|
||||
outR += combR[i].process(input);
|
||||
}
|
||||
|
||||
// Feed through allpasses in series
|
||||
for (i = 0; i < numallpasses; i++) {
|
||||
outL = allpassL[i].process(outL);
|
||||
outR = allpassR[i].process(outR);
|
||||
}
|
||||
|
||||
// Calculate output MIXING with anything already there
|
||||
*outputL += outL * wet1 + outR * wet2 + *inputL * dry;
|
||||
*outputR += outR * wet1 + outL * wet2 + *inputR * dry;
|
||||
|
||||
// Increment sample pointers, allowing for interleave (if any)
|
||||
inputL += skip;
|
||||
inputR += skip;
|
||||
outputL += skip;
|
||||
outputR += skip;
|
||||
}
|
||||
}
|
||||
|
||||
void revmodel::update() {
|
||||
// Recalculate internal values after parameter change
|
||||
void revmodel::update()
|
||||
{
|
||||
// Recalculate internal values after parameter change
|
||||
|
||||
int i;
|
||||
|
||||
wet1 = wet * (width / 2 + 0.5f);
|
||||
wet2 = wet * ((1 - width) / 2);
|
||||
wet1 = wet*(width/2 + 0.5f);
|
||||
wet2 = wet*((1-width)/2);
|
||||
|
||||
if (mode >= freezemode) {
|
||||
if (mode >= freezemode)
|
||||
{
|
||||
roomsize1 = 1;
|
||||
damp1 = 0;
|
||||
gain = muted;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
roomsize1 = roomsize;
|
||||
damp1 = damp;
|
||||
gain = fixedgain;
|
||||
}
|
||||
|
||||
for (i = 0; i < numcombs; i++) {
|
||||
for (i=0; i<numcombs; i++)
|
||||
{
|
||||
combL[i].setfeedback(roomsize1);
|
||||
combR[i].setfeedback(roomsize1);
|
||||
}
|
||||
|
||||
for (i = 0; i < numcombs; i++) {
|
||||
for (i=0; i<numcombs; i++)
|
||||
{
|
||||
combL[i].setdamp(damp1);
|
||||
combR[i].setdamp(damp1);
|
||||
}
|
||||
|
@ -250,58 +250,75 @@ void revmodel::update() {
|
|||
// because as you develop the reverb model, you may
|
||||
// wish to take dynamic action when they are called.
|
||||
|
||||
void revmodel::setroomsize(float value) {
|
||||
roomsize = (value * scaleroom) + offsetroom;
|
||||
void revmodel::setroomsize(float value)
|
||||
{
|
||||
roomsize = (value*scaleroom) + offsetroom;
|
||||
update();
|
||||
}
|
||||
|
||||
float revmodel::getroomsize() {
|
||||
return (roomsize - offsetroom) / scaleroom;
|
||||
float revmodel::getroomsize()
|
||||
{
|
||||
return (roomsize-offsetroom)/scaleroom;
|
||||
}
|
||||
|
||||
void revmodel::setdamp(float value) {
|
||||
damp = value * scaledamp;
|
||||
void revmodel::setdamp(float value)
|
||||
{
|
||||
damp = value*scaledamp;
|
||||
update();
|
||||
}
|
||||
|
||||
float revmodel::getdamp() {
|
||||
return damp / scaledamp;
|
||||
float revmodel::getdamp()
|
||||
{
|
||||
return damp/scaledamp;
|
||||
}
|
||||
|
||||
void revmodel::setwet(float value) {
|
||||
wet = value * scalewet;
|
||||
void revmodel::setwet(float value)
|
||||
{
|
||||
wet = value*scalewet;
|
||||
update();
|
||||
}
|
||||
|
||||
float revmodel::getwet() {
|
||||
return wet / scalewet;
|
||||
float revmodel::getwet()
|
||||
{
|
||||
return wet/scalewet;
|
||||
}
|
||||
|
||||
void revmodel::setdry(float value) {
|
||||
dry = value * scaledry;
|
||||
void revmodel::setdry(float value)
|
||||
{
|
||||
dry = value*scaledry;
|
||||
}
|
||||
|
||||
float revmodel::getdry() {
|
||||
return dry / scaledry;
|
||||
float revmodel::getdry()
|
||||
{
|
||||
return dry/scaledry;
|
||||
}
|
||||
|
||||
void revmodel::setwidth(float value) {
|
||||
void revmodel::setwidth(float value)
|
||||
{
|
||||
width = value;
|
||||
update();
|
||||
}
|
||||
|
||||
float revmodel::getwidth() {
|
||||
float revmodel::getwidth()
|
||||
{
|
||||
return width;
|
||||
}
|
||||
|
||||
void revmodel::setmode(float value) {
|
||||
void revmodel::setmode(float value)
|
||||
{
|
||||
mode = value;
|
||||
update();
|
||||
}
|
||||
|
||||
float revmodel::getmode() {
|
||||
float revmodel::getmode()
|
||||
{
|
||||
if (mode >= freezemode)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
void revmodel::setfiltval(float value)
|
||||
{
|
||||
filtval = value;
|
||||
}
|
||||
|
|
|
@ -1,24 +1,32 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* 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.
|
||||
#ifndef _freeverb_
|
||||
#define _freeverb_
|
||||
|
||||
* 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.
|
||||
// Reverb model tuning values
|
||||
//
|
||||
// Written by Jezar at Dreampoint, June 2000
|
||||
// http://www.dreampoint.co.uk
|
||||
// This code is public domain
|
||||
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
const int numcombs = 8;
|
||||
const int numallpasses = 4;
|
||||
const float muted = 0;
|
||||
const float fixedgain = 0.015f;
|
||||
const float scalewet = 3;
|
||||
const float scaledry = 2;
|
||||
const float scaledamp = 0.4f;
|
||||
const float scaleroom = 0.28f;
|
||||
const float offsetroom = 0.7f;
|
||||
const float initialroom = 0.5f;
|
||||
const float initialdamp = 0.5f;
|
||||
const float initialwet = 1/scalewet;
|
||||
const float initialdry = 0;
|
||||
const float initialwidth = 1;
|
||||
const float initialmode = 0;
|
||||
const float freezemode = 0.5f;
|
||||
const int stereospread = 23;
|
||||
|
||||
const int combtuning[] = {1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617};
|
||||
const int allpasstuning[] = {556, 441, 341, 225};
|
||||
|
||||
// Macro for killing denormalled numbers
|
||||
//
|
||||
|
@ -27,22 +35,71 @@
|
|||
// Based on IS_DENORMAL macro by Jon Watte
|
||||
// This code is public domain
|
||||
|
||||
#ifndef FREEVERB_H
|
||||
#define FREEVERB_H
|
||||
|
||||
// FIXME: Fix this really ugly hack
|
||||
inline float undenormalise(void *sample) {
|
||||
if (((*(unsigned int*)sample) & 0x7f800000) == 0)
|
||||
static inline float undenormalise(float x) {
|
||||
union {
|
||||
float f;
|
||||
unsigned int i;
|
||||
} u;
|
||||
u.f = x;
|
||||
if ((u.i & 0x7f800000) == 0) {
|
||||
return 0.0f;
|
||||
return *(float*)sample;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
// Allpass filter declaration
|
||||
//
|
||||
// Written by Jezar at Dreampoint, June 2000
|
||||
// http://www.dreampoint.co.uk
|
||||
// This code is public domain
|
||||
|
||||
class allpass
|
||||
{
|
||||
public:
|
||||
allpass();
|
||||
void setbuffer(float *buf, int size);
|
||||
void deletebuffer();
|
||||
inline float process(float inp);
|
||||
void mute();
|
||||
void setfeedback(float val);
|
||||
float getfeedback();
|
||||
// private:
|
||||
float feedback;
|
||||
float *buffer;
|
||||
int bufsize;
|
||||
int bufidx;
|
||||
};
|
||||
|
||||
|
||||
// Big to inline - but crucial for speed
|
||||
|
||||
inline float allpass::process(float input)
|
||||
{
|
||||
float output;
|
||||
float bufout;
|
||||
|
||||
bufout = undenormalise(buffer[bufidx]);
|
||||
|
||||
output = -input + bufout;
|
||||
buffer[bufidx] = input + (bufout*feedback);
|
||||
|
||||
if (++bufidx>=bufsize) bufidx = 0;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// Comb filter class declaration
|
||||
//
|
||||
// Written by Jezar at Dreampoint, June 2000
|
||||
// http://www.dreampoint.co.uk
|
||||
// This code is public domain
|
||||
|
||||
class comb {
|
||||
class comb
|
||||
{
|
||||
public:
|
||||
comb();
|
||||
void setbuffer(float *buf, int size);
|
||||
void deletebuffer();
|
||||
inline float process(float inp);
|
||||
void mute();
|
||||
void setdamp(float val);
|
||||
|
@ -62,118 +119,35 @@ private:
|
|||
|
||||
// Big to inline - but crucial for speed
|
||||
|
||||
inline float comb::process(float input) {
|
||||
inline float comb::process(float input)
|
||||
{
|
||||
float output;
|
||||
|
||||
output = buffer[bufidx];
|
||||
undenormalise(&output);
|
||||
output = undenormalise(buffer[bufidx]);
|
||||
|
||||
filterstore = (output * damp2) + (filterstore * damp1);
|
||||
undenormalise(&filterstore);
|
||||
filterstore = undenormalise((output*damp2) + (filterstore*damp1));
|
||||
|
||||
buffer[bufidx] = input + (filterstore * feedback);
|
||||
buffer[bufidx] = input + (filterstore*feedback);
|
||||
|
||||
if (++bufidx >= bufsize)
|
||||
bufidx = 0;
|
||||
if (++bufidx>=bufsize) bufidx = 0;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// Allpass filter declaration
|
||||
|
||||
class allpass {
|
||||
public:
|
||||
allpass();
|
||||
void setbuffer(float *buf, int size);
|
||||
inline float process(float inp);
|
||||
void mute();
|
||||
void setfeedback(float val);
|
||||
float getfeedback();
|
||||
private:
|
||||
float feedback;
|
||||
float *buffer;
|
||||
int bufsize;
|
||||
int bufidx;
|
||||
};
|
||||
|
||||
|
||||
// Big to inline - but crucial for speed
|
||||
|
||||
inline float allpass::process(float input) {
|
||||
float output;
|
||||
float bufout;
|
||||
|
||||
bufout = buffer[bufidx];
|
||||
undenormalise(&bufout);
|
||||
|
||||
output = -input + bufout;
|
||||
buffer[bufidx] = input + (bufout * feedback);
|
||||
|
||||
if (++bufidx >= bufsize)
|
||||
bufidx = 0;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
// Reverb model tuning values
|
||||
|
||||
const int numcombs = 8;
|
||||
const int numallpasses = 4;
|
||||
const float muted = 0;
|
||||
const float fixedgain = 0.015f;
|
||||
const float scalewet = 3;
|
||||
const float scaledry = 2;
|
||||
const float scaledamp = 0.4f;
|
||||
const float scaleroom = 0.28f;
|
||||
const float offsetroom = 0.7f;
|
||||
const float initialroom = 0.5f;
|
||||
const float initialdamp = 0.5f;
|
||||
const float initialwet = 1 / scalewet;
|
||||
const float initialdry = 0;
|
||||
const float initialwidth = 1;
|
||||
const float initialmode = 0;
|
||||
const float freezemode = 0.5f;
|
||||
const int stereospread = 23;
|
||||
|
||||
// These values assume 44.1KHz sample rate
|
||||
// they will probably be OK for 48KHz sample rate
|
||||
// but would need scaling for 96KHz (or other) sample rates.
|
||||
// The values were obtained by listening tests.
|
||||
const int combtuningL1 = 1116;
|
||||
const int combtuningR1 = 1116 + stereospread;
|
||||
const int combtuningL2 = 1188;
|
||||
const int combtuningR2 = 1188 + stereospread;
|
||||
const int combtuningL3 = 1277;
|
||||
const int combtuningR3 = 1277 + stereospread;
|
||||
const int combtuningL4 = 1356;
|
||||
const int combtuningR4 = 1356 + stereospread;
|
||||
const int combtuningL5 = 1422;
|
||||
const int combtuningR5 = 1422 + stereospread;
|
||||
const int combtuningL6 = 1491;
|
||||
const int combtuningR6 = 1491 + stereospread;
|
||||
const int combtuningL7 = 1557;
|
||||
const int combtuningR7 = 1557 + stereospread;
|
||||
const int combtuningL8 = 1617;
|
||||
const int combtuningR8 = 1617 + stereospread;
|
||||
const int allpasstuningL1 = 556;
|
||||
const int allpasstuningR1 = 556 + stereospread;
|
||||
const int allpasstuningL2 = 441;
|
||||
const int allpasstuningR2 = 441 + stereospread;
|
||||
const int allpasstuningL3 = 341;
|
||||
const int allpasstuningR3 = 341 + stereospread;
|
||||
const int allpasstuningL4 = 225;
|
||||
const int allpasstuningR4 = 225 + stereospread;
|
||||
|
||||
|
||||
// Reverb model declaration
|
||||
//
|
||||
// Written by Jezar at Dreampoint, June 2000
|
||||
// Modifications by Jerome Fisher, 2009
|
||||
// http://www.dreampoint.co.uk
|
||||
// This code is public domain
|
||||
|
||||
class revmodel {
|
||||
class revmodel
|
||||
{
|
||||
public:
|
||||
revmodel();
|
||||
revmodel(float scaletuning);
|
||||
~revmodel();
|
||||
void mute();
|
||||
void processmix(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip);
|
||||
void processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip);
|
||||
void process(const float *inputL, const float *inputR, float *outputL, float *outputR, long numsamples);
|
||||
void setroomsize(float value);
|
||||
float getroomsize();
|
||||
void setdamp(float value);
|
||||
|
@ -186,20 +160,22 @@ public:
|
|||
float getwidth();
|
||||
void setmode(float value);
|
||||
float getmode();
|
||||
void setfiltval(float value);
|
||||
private:
|
||||
void update();
|
||||
|
||||
private:
|
||||
float gain;
|
||||
float roomsize, roomsize1;
|
||||
float damp, damp1;
|
||||
float wet, wet1, wet2;
|
||||
float roomsize,roomsize1;
|
||||
float damp,damp1;
|
||||
float wet,wet1,wet2;
|
||||
float dry;
|
||||
float width;
|
||||
float mode;
|
||||
|
||||
// The following are all declared inline
|
||||
// to remove the need for dynamic allocation
|
||||
// with its subsequent error-checking messiness
|
||||
// LPF stuff
|
||||
float filtval;
|
||||
float filtprev1;
|
||||
float filtprev2;
|
||||
|
||||
// Comb filters
|
||||
comb combL[numcombs];
|
||||
|
@ -208,34 +184,6 @@ private:
|
|||
// Allpass filters
|
||||
allpass allpassL[numallpasses];
|
||||
allpass allpassR[numallpasses];
|
||||
|
||||
// Buffers for the combs
|
||||
float bufcombL1[combtuningL1];
|
||||
float bufcombR1[combtuningR1];
|
||||
float bufcombL2[combtuningL2];
|
||||
float bufcombR2[combtuningR2];
|
||||
float bufcombL3[combtuningL3];
|
||||
float bufcombR3[combtuningR3];
|
||||
float bufcombL4[combtuningL4];
|
||||
float bufcombR4[combtuningR4];
|
||||
float bufcombL5[combtuningL5];
|
||||
float bufcombR5[combtuningR5];
|
||||
float bufcombL6[combtuningL6];
|
||||
float bufcombR6[combtuningR6];
|
||||
float bufcombL7[combtuningL7];
|
||||
float bufcombR7[combtuningR7];
|
||||
float bufcombL8[combtuningL8];
|
||||
float bufcombR8[combtuningR8];
|
||||
|
||||
// Buffers for the allpasses
|
||||
float bufallpassL1[allpasstuningL1];
|
||||
float bufallpassR1[allpasstuningR1];
|
||||
float bufallpassL2[allpasstuningL2];
|
||||
float bufallpassR2[allpasstuningR2];
|
||||
float bufallpassL3[allpasstuningL3];
|
||||
float bufallpassR3[allpasstuningR3];
|
||||
float bufallpassL4[allpasstuningL4];
|
||||
float bufallpassR4[allpasstuningR4];
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif//_freeverb_
|
||||
|
|
|
@ -1,849 +0,0 @@
|
|||
/* Copyright (c) 2003-2005 Various contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "mt32emu.h"
|
||||
|
||||
#ifdef MT32EMU_HAVE_X86
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
#ifndef _MSC_VER
|
||||
|
||||
#define eflag(value) __asm__ __volatile__("pushfl \n popfl \n" : : "a"(value))
|
||||
#define cpuid_flag (1 << 21)
|
||||
|
||||
static inline bool atti386_DetectCPUID() {
|
||||
unsigned int result;
|
||||
|
||||
// Is there a cpuid?
|
||||
result = cpuid_flag; // set test
|
||||
eflag(result);
|
||||
if (!(result & cpuid_flag))
|
||||
return false;
|
||||
|
||||
result = 0; // clear test
|
||||
eflag(result);
|
||||
if (result & cpuid_flag)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool atti386_DetectSIMD() {
|
||||
unsigned int result;
|
||||
|
||||
if (atti386_DetectCPUID() == false)
|
||||
return false;
|
||||
|
||||
/* check cpuid */
|
||||
__asm__ __volatile__(
|
||||
"pushl %%ebx \n" \
|
||||
"movl $1, %%eax \n" \
|
||||
"cpuid \n" \
|
||||
"movl %%edx, %0 \n" \
|
||||
"popl %%ebx \n" \
|
||||
: "=r"(result) : : "eax", "ecx", "edx");
|
||||
|
||||
if (result & (1 << 25))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool atti386_Detect3DNow() {
|
||||
unsigned int result;
|
||||
|
||||
if (atti386_DetectCPUID() == false)
|
||||
return false;
|
||||
|
||||
// get cpuid
|
||||
__asm__ __volatile__(
|
||||
"pushl %%ebx \n" \
|
||||
"movl $0x80000001, %%eax \n" \
|
||||
"cpuid \n" \
|
||||
"movl %%edx, %0 \n" \
|
||||
"popl %%ebx \n" \
|
||||
: "=r"(result) : : "eax", "ecx", "edx");
|
||||
|
||||
if (result & 0x80000000)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static inline float atti386_iir_filter_sse(float *output, float *hist1_ptr, float *coef_ptr) {
|
||||
__asm__ __volatile__ (
|
||||
"pushl %1 \n" \
|
||||
"pushl %2 \n" \
|
||||
"movss 0(%0), %%xmm1 \n" \
|
||||
"movups 0(%1), %%xmm2 \n" \
|
||||
"movlps 0(%2), %%xmm3 \n" \
|
||||
" \n" \
|
||||
"shufps $0x44, %%xmm3, %%xmm3 \n" \
|
||||
" \n" \
|
||||
"mulps %%xmm3, %%xmm2 \n" \
|
||||
" \n" \
|
||||
"subss %%xmm2, %%xmm1 \n" \
|
||||
"shufps $0x39, %%xmm2, %%xmm2 \n" \
|
||||
"subss %%xmm2, %%xmm1 \n" \
|
||||
" \n" \
|
||||
"movss %%xmm1, 0(%2) \n" \
|
||||
" \n" \
|
||||
"shufps $0x39, %%xmm2, %%xmm2 \n" \
|
||||
"addss %%xmm2, %%xmm1 \n" \
|
||||
" \n" \
|
||||
"shufps $0x39, %%xmm2, %%xmm2 \n" \
|
||||
"addss %%xmm2, %%xmm1 \n" \
|
||||
" \n" \
|
||||
"movss %%xmm3, 4(%2) \n" \
|
||||
" \n" \
|
||||
"addl $16, %1 \n" \
|
||||
"addl $8, %2 \n" \
|
||||
" \n" \
|
||||
"movups 0(%1), %%xmm2 \n" \
|
||||
" \n" \
|
||||
"movlps 0(%2), %%xmm3 \n" \
|
||||
"shufps $0x44, %%xmm3, %%xmm3 \n" \
|
||||
" \n" \
|
||||
"mulps %%xmm3, %%xmm2 \n" \
|
||||
" \n" \
|
||||
"subss %%xmm2, %%xmm1 \n" \
|
||||
"shufps $0x39, %%xmm2, %%xmm2 \n" \
|
||||
"subss %%xmm2, %%xmm1 \n" \
|
||||
" \n" \
|
||||
"movss %%xmm1, 0(%2) \n" \
|
||||
" \n" \
|
||||
"shufps $0x39, %%xmm2, %%xmm2 \n" \
|
||||
"addss %%xmm2, %%xmm1 \n" \
|
||||
" \n" \
|
||||
"shufps $0x39, %%xmm2, %%xmm2 \n" \
|
||||
"addss %%xmm2, %%xmm1 \n" \
|
||||
" \n" \
|
||||
"movss %%xmm3, 4(%2) \n" \
|
||||
"movss %%xmm1, 0(%0) \n" \
|
||||
"popl %2 \n" \
|
||||
"popl %1 \n" \
|
||||
: : "r"(output), "r"(coef_ptr), "r"(hist1_ptr)
|
||||
: "memory"
|
||||
#ifdef __SSE__
|
||||
, "xmm1", "xmm2", "xmm3"
|
||||
#endif
|
||||
);
|
||||
|
||||
return *output;
|
||||
}
|
||||
|
||||
static inline float atti386_iir_filter_3DNow(float output, float *hist1_ptr, float *coef_ptr) {
|
||||
float tmp;
|
||||
|
||||
__asm__ __volatile__ (
|
||||
"movq %0, %%mm1 \n" \
|
||||
" \n" \
|
||||
"movl %1, %%edi \n" \
|
||||
"movq 0(%%edi), %%mm2 \n" \
|
||||
" \n" \
|
||||
"movl %2, %%eax; \n" \
|
||||
"movq 0(%%eax), %%mm3 \n" \
|
||||
" \n" \
|
||||
"pfmul %%mm3, %%mm2 \n" \
|
||||
"pfsub %%mm2, %%mm1 \n" \
|
||||
" \n" \
|
||||
"psrlq $32, %%mm2 \n" \
|
||||
"pfsub %%mm2, %%mm1 \n" \
|
||||
" \n" \
|
||||
"movd %%mm1, %3 \n" \
|
||||
" \n" \
|
||||
"addl $8, %%edi \n" \
|
||||
"movq 0(%%edi), %%mm2 \n" \
|
||||
"movq 0(%%eax), %%mm3 \n" \
|
||||
" \n" \
|
||||
"pfmul %%mm3, %%mm2 \n" \
|
||||
"pfadd %%mm2, %%mm1 \n" \
|
||||
" \n" \
|
||||
"psrlq $32, %%mm2 \n" \
|
||||
"pfadd %%mm2, %%mm1 \n" \
|
||||
" \n" \
|
||||
"pushl %3 \n" \
|
||||
"popl 0(%%eax) \n" \
|
||||
" \n" \
|
||||
"movd %%mm3, 4(%%eax) \n" \
|
||||
" \n" \
|
||||
"addl $8, %%edi \n" \
|
||||
"addl $8, %%eax \n" \
|
||||
" \n" \
|
||||
"movq 0(%%edi), %%mm2 \n" \
|
||||
"movq 0(%%eax), %%mm3 \n" \
|
||||
" \n" \
|
||||
"pfmul %%mm3, %%mm2 \n" \
|
||||
"pfsub %%mm2, %%mm1 \n" \
|
||||
" \n" \
|
||||
"psrlq $32, %%mm2 \n" \
|
||||
"pfsub %%mm2, %%mm1 \n" \
|
||||
" \n" \
|
||||
"movd %%mm1, %3 \n" \
|
||||
" \n" \
|
||||
"addl $8, %%edi \n" \
|
||||
"movq 0(%%edi), %%mm2 \n" \
|
||||
"movq 0(%%eax), %%mm3 \n" \
|
||||
" \n" \
|
||||
"pfmul %%mm3, %%mm2 \n" \
|
||||
"pfadd %%mm2, %%mm1 \n" \
|
||||
" \n" \
|
||||
"psrlq $32, %%mm2 \n" \
|
||||
"pfadd %%mm2, %%mm1 \n" \
|
||||
" \n" \
|
||||
"pushl %3 \n" \
|
||||
"popl 0(%%eax) \n" \
|
||||
"movd %%mm3, 4(%%eax) \n" \
|
||||
" \n" \
|
||||
"movd %%mm1, %0 \n" \
|
||||
"femms \n" \
|
||||
: "=m"(output) : "g"(coef_ptr), "g"(hist1_ptr), "m"(tmp)
|
||||
: "eax", "edi", "memory"
|
||||
#ifdef __MMX__
|
||||
, "mm1", "mm2", "mm3"
|
||||
#endif
|
||||
);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
static inline void atti386_produceOutput1(int tmplen, Bit16s myvolume, Bit16s *useBuf, Bit16s *snd) {
|
||||
__asm__ __volatile__(
|
||||
"movl %0, %%ecx \n" \
|
||||
"movw %1, %%ax \n" \
|
||||
"shll $16, %%eax \n" \
|
||||
"movw %1, %%ax \n" \
|
||||
"movd %%eax, %%mm3 \n" \
|
||||
"movd %%eax, %%mm2 \n" \
|
||||
"psllq $32, %%mm3 \n" \
|
||||
"por %%mm2, %%mm3 \n" \
|
||||
"movl %2, %%esi \n" \
|
||||
"movl %3, %%edi \n" \
|
||||
"1: \n" \
|
||||
"movq 0(%%esi), %%mm1 \n" \
|
||||
"movq 0(%%edi), %%mm2 \n" \
|
||||
"pmulhw %%mm3, %%mm1 \n" \
|
||||
"paddw %%mm2, %%mm1 \n" \
|
||||
"movq %%mm1, 0(%%edi) \n" \
|
||||
" \n" \
|
||||
"addl $8, %%esi \n" \
|
||||
"addl $8, %%edi \n" \
|
||||
" \n" \
|
||||
"decl %%ecx \n" \
|
||||
"cmpl $0, %%ecx \n" \
|
||||
"jg 1b \n" \
|
||||
"emms \n" \
|
||||
: : "g"(tmplen), "g"(myvolume), "g"(useBuf), "g"(snd)
|
||||
: "eax", "ecx", "edi", "esi", "memory"
|
||||
#ifdef __MMX__
|
||||
, "mm1", "mm2", "mm3"
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
static inline void atti386_produceOutput2(Bit32u len, Bit16s *snd, float *sndbufl, float *sndbufr, float *multFactor) {
|
||||
__asm__ __volatile__(
|
||||
"movl %4, %%ecx \n" \
|
||||
"shrl $1, %%ecx \n" \
|
||||
"addl $4, %%ecx \n" \
|
||||
"pushl %%ecx \n" \
|
||||
" \n" \
|
||||
"movl %0, %%esi \n" \
|
||||
"movups 0(%%esi), %%xmm1 \n" \
|
||||
" \n" \
|
||||
"movl %1, %%esi \n" \
|
||||
"movl %2, %%edi \n" \
|
||||
"1: \n" \
|
||||
"xorl %%eax, %%eax \n" \
|
||||
"movw 0(%1), %%ax \n" \
|
||||
"cwde \n" \
|
||||
"incl %1 \n" \
|
||||
"incl %1 \n" \
|
||||
"movd %%eax, %%mm1 \n" \
|
||||
"psrlq $32, %%mm1 \n" \
|
||||
"movw 0(%1), %%ax \n" \
|
||||
"incl %1 \n" \
|
||||
"incl %1 \n" \
|
||||
"movd %%eax, %%mm2 \n" \
|
||||
"por %%mm2, %%mm1 \n" \
|
||||
" \n" \
|
||||
"decl %%ecx \n" \
|
||||
"jnz 1b \n" \
|
||||
" \n" \
|
||||
"popl %%ecx \n" \
|
||||
"movl %1, %%esi \n" \
|
||||
"movl %3, %%edi \n" \
|
||||
"incl %%esi \n" \
|
||||
"2: \n" \
|
||||
"decl %%ecx \n" \
|
||||
"jnz 2b \n" \
|
||||
: : "g"(multFactor), "r"(snd), "g"(sndbufl), "g"(sndbufr), "g"(len)
|
||||
: "eax", "ecx", "edi", "esi", "mm1", "mm2", "xmm1", "memory");
|
||||
}
|
||||
|
||||
static inline void atti386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) {
|
||||
__asm__ __volatile__(
|
||||
"movl %0, %%ecx \n" \
|
||||
"movl %1, %%esi \n" \
|
||||
"movl %2, %%edi \n" \
|
||||
"1: \n" \
|
||||
"movq 0(%%edi), %%mm1 \n" \
|
||||
"movq 0(%%esi), %%mm2 \n" \
|
||||
"paddw %%mm2, %%mm1 \n" \
|
||||
"movq %%mm1, 0(%%esi) \n" \
|
||||
"addl $8, %%edi \n" \
|
||||
"addl $8, %%esi \n" \
|
||||
"decl %%ecx \n" \
|
||||
"cmpl $0, %%ecx \n" \
|
||||
"jg 1b \n" \
|
||||
"emms \n" \
|
||||
: : "g"(len), "g"(buf1), "g"(buf2)
|
||||
: "ecx", "edi", "esi", "memory"
|
||||
#ifdef __MMX__
|
||||
, "mm1", "mm2"
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
static inline void atti386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) {
|
||||
__asm__ __volatile__(
|
||||
"movl %0, %%ecx \n" \
|
||||
"movl %1, %%esi \n" \
|
||||
"movl %2, %%edi \n" \
|
||||
"1: \n" \
|
||||
"movq 0(%%esi), %%mm1 \n" \
|
||||
"movq 0(%%edi), %%mm2 \n" \
|
||||
"movq %%mm1, %%mm3 \n" \
|
||||
"pmulhw %%mm2, %%mm1 \n" \
|
||||
"paddw %%mm3, %%mm1 \n" \
|
||||
"movq %%mm1, 0(%%esi) \n" \
|
||||
"addl $8, %%edi \n" \
|
||||
"addl $8, %%esi \n" \
|
||||
"decl %%ecx \n" \
|
||||
"cmpl $0, %%ecx \n" \
|
||||
"jg 1b \n" \
|
||||
"emms \n" \
|
||||
: : "g"(len), "g"(buf1), "g"(buf2)
|
||||
: "ecx", "edi", "esi", "memory"
|
||||
#ifdef __MMX__
|
||||
, "mm1", "mm2", "mm3"
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
static inline void atti386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) {
|
||||
__asm__ __volatile__(
|
||||
"movl %0, %%ecx \n" \
|
||||
"movl %1, %%esi \n" \
|
||||
"movl %2, %%edi \n" \
|
||||
"1: \n" \
|
||||
"movq 0(%%esi), %%mm1 \n" \
|
||||
"movq 0(%%edi), %%mm2 \n" \
|
||||
"pmulhw %%mm2, %%mm1 \n" \
|
||||
"movq %%mm1, 0(%%esi) \n" \
|
||||
"addl $8, %%edi \n" \
|
||||
"addl $8, %%esi \n" \
|
||||
"decl %%ecx \n" \
|
||||
"cmpl $0, %%ecx \n" \
|
||||
"jg 1b \n" \
|
||||
"emms \n" \
|
||||
: : "g"(len), "g"(buf1), "g"(buf2)
|
||||
: "ecx", "edi", "esi", "memory"
|
||||
#ifdef __MMX__
|
||||
, "mm1", "mm2"
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
static inline void atti386_partialProductOutput(int quadlen, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *p1buf) {
|
||||
__asm__ __volatile__(
|
||||
"movl %0, %%ecx \n" \
|
||||
"movw %1, %%ax \n" \
|
||||
"shll $16, %%eax \n" \
|
||||
"movw %2, %%ax \n" \
|
||||
"movd %%eax, %%mm1 \n" \
|
||||
"movd %%eax, %%mm2 \n" \
|
||||
"psllq $32, %%mm1 \n" \
|
||||
"por %%mm2, %%mm1 \n" \
|
||||
"movl %3, %%edi \n" \
|
||||
"movl %4, %%esi \n" \
|
||||
"pushl %%ebx \n" \
|
||||
"1: \n" \
|
||||
"movw 0(%%esi), %%bx \n" \
|
||||
"addl $2, %%esi \n" \
|
||||
"movw 0(%%esi), %%dx \n" \
|
||||
"addl $2, %%esi \n" \
|
||||
"" \
|
||||
"movw %%dx, %%ax \n" \
|
||||
"shll $16, %%eax \n" \
|
||||
"movw %%dx, %%ax \n" \
|
||||
"movd %%eax, %%mm2 \n" \
|
||||
"psllq $32, %%mm2 \n" \
|
||||
"movw %%bx, %%ax \n" \
|
||||
"shll $16, %%eax \n" \
|
||||
"movw %%bx, %%ax \n" \
|
||||
"movd %%eax, %%mm3 \n" \
|
||||
"por %%mm3, %%mm2 \n" \
|
||||
"" \
|
||||
"pmulhw %%mm1, %%mm2 \n" \
|
||||
"movq %%mm2, 0(%%edi) \n" \
|
||||
"addl $8, %%edi \n" \
|
||||
"" \
|
||||
"decl %%ecx \n" \
|
||||
"cmpl $0, %%ecx \n" \
|
||||
"jg 1b \n" \
|
||||
"emms \n" \
|
||||
"popl %%ebx \n" \
|
||||
: : "g"(quadlen), "g"(leftvol), "g"(rightvol), "g"(partialBuf), "g"(p1buf)
|
||||
: "eax", "ecx", "edx", "edi", "esi", "memory"
|
||||
#ifdef __MMX__
|
||||
, "mm1", "mm2", "mm3"
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool DetectSIMD() {
|
||||
#ifdef _MSC_VER
|
||||
bool found_simd;
|
||||
__asm {
|
||||
pushfd
|
||||
pop eax // get EFLAGS into eax
|
||||
mov ebx,eax // keep a copy
|
||||
xor eax,0x200000
|
||||
// toggle CPUID bit
|
||||
|
||||
push eax
|
||||
popfd // set new EFLAGS
|
||||
pushfd
|
||||
pop eax // EFLAGS back into eax
|
||||
|
||||
xor eax,ebx
|
||||
// have we changed the ID bit?
|
||||
|
||||
je NO_SIMD
|
||||
// No, no CPUID instruction
|
||||
|
||||
// we could toggle the
|
||||
// ID bit so CPUID is present
|
||||
mov eax,1
|
||||
|
||||
cpuid // get processor features
|
||||
test edx,1<<25 // check the SIMD bit
|
||||
jz NO_SIMD
|
||||
mov found_simd,1
|
||||
jmp DONE
|
||||
NO_SIMD:
|
||||
mov found_simd,0
|
||||
DONE:
|
||||
}
|
||||
return found_simd;
|
||||
#else
|
||||
return atti386_DetectSIMD();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Detect3DNow() {
|
||||
#ifdef _MSC_VER
|
||||
bool found3D = false;
|
||||
__asm {
|
||||
pushfd
|
||||
pop eax
|
||||
mov edx, eax
|
||||
xor eax, 00200000h
|
||||
push eax
|
||||
popfd
|
||||
pushfd
|
||||
pop eax
|
||||
xor eax, edx
|
||||
jz NO_3DNOW
|
||||
|
||||
mov eax, 80000000h
|
||||
cpuid
|
||||
|
||||
cmp eax, 80000000h
|
||||
jbe NO_3DNOW
|
||||
|
||||
mov eax, 80000001h
|
||||
cpuid
|
||||
test edx, 80000000h
|
||||
jz NO_3DNOW
|
||||
mov found3D, 1
|
||||
NO_3DNOW:
|
||||
|
||||
}
|
||||
return found3D;
|
||||
#else
|
||||
return atti386_Detect3DNow();
|
||||
#endif
|
||||
}
|
||||
|
||||
float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr) {
|
||||
float output;
|
||||
|
||||
// 1st number of coefficients array is overall input scale factor, or filter gain
|
||||
output = input * (*coef_ptr++);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
__asm {
|
||||
|
||||
movss xmm1, output
|
||||
|
||||
mov eax, coef_ptr
|
||||
movups xmm2, [eax]
|
||||
|
||||
mov eax, hist1_ptr
|
||||
movlps xmm3, [eax]
|
||||
shufps xmm3, xmm3, 44h
|
||||
// hist1_ptr+1, hist1_ptr, hist1_ptr+1, hist1_ptr
|
||||
|
||||
mulps xmm2, xmm3
|
||||
|
||||
subss xmm1, xmm2
|
||||
// Rotate elements right
|
||||
shufps xmm2, xmm2, 39h
|
||||
subss xmm1, xmm2
|
||||
|
||||
// Store new_hist
|
||||
movss DWORD PTR [eax], xmm1
|
||||
|
||||
// Rotate elements right
|
||||
shufps xmm2, xmm2, 39h
|
||||
addss xmm1, xmm2
|
||||
|
||||
// Rotate elements right
|
||||
shufps xmm2, xmm2, 39h
|
||||
addss xmm1, xmm2
|
||||
|
||||
// Store previous hist
|
||||
movss DWORD PTR [eax+4], xmm3
|
||||
|
||||
add coef_ptr, 16
|
||||
add hist1_ptr, 8
|
||||
|
||||
mov eax, coef_ptr
|
||||
movups xmm2, [eax]
|
||||
|
||||
mov eax, hist1_ptr
|
||||
movlps xmm3, [eax]
|
||||
shufps xmm3, xmm3, 44h
|
||||
// hist1_ptr+1, hist1_ptr, hist1_ptr+1, hist1_ptr
|
||||
|
||||
mulps xmm2, xmm3
|
||||
|
||||
subss xmm1, xmm2
|
||||
// Rotate elements right
|
||||
shufps xmm2, xmm2, 39h
|
||||
subss xmm1, xmm2
|
||||
|
||||
// Store new_hist
|
||||
movss DWORD PTR [eax], xmm1
|
||||
|
||||
// Rotate elements right
|
||||
shufps xmm2, xmm2, 39h
|
||||
addss xmm1, xmm2
|
||||
|
||||
// Rotate elements right
|
||||
shufps xmm2, xmm2, 39h
|
||||
addss xmm1, xmm2
|
||||
|
||||
// Store previous hist
|
||||
movss DWORD PTR [eax+4], xmm3
|
||||
|
||||
movss output, xmm1
|
||||
}
|
||||
#else
|
||||
output = atti386_iir_filter_sse(&output, hist1_ptr, coef_ptr);
|
||||
#endif
|
||||
return output;
|
||||
}
|
||||
|
||||
float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr) {
|
||||
float output;
|
||||
|
||||
// 1st number of coefficients array is overall input scale factor, or filter gain
|
||||
output = input * (*coef_ptr++);
|
||||
|
||||
// I find it very sad that 3DNow requires twice as many instructions as Intel's SSE
|
||||
// Intel does have the upper hand here.
|
||||
#ifdef _MSC_VER
|
||||
float tmp;
|
||||
__asm {
|
||||
movq mm1, output
|
||||
mov ebx, coef_ptr
|
||||
movq mm2, [ebx]
|
||||
|
||||
mov eax, hist1_ptr;
|
||||
movq mm3, [eax]
|
||||
|
||||
pfmul mm2, mm3
|
||||
pfsub mm1, mm2
|
||||
|
||||
psrlq mm2, 32
|
||||
pfsub mm1, mm2
|
||||
|
||||
// Store new hist
|
||||
movd tmp, mm1
|
||||
|
||||
add ebx, 8
|
||||
movq mm2, [ebx]
|
||||
movq mm3, [eax]
|
||||
|
||||
pfmul mm2, mm3
|
||||
pfadd mm1, mm2
|
||||
|
||||
psrlq mm2, 32
|
||||
pfadd mm1, mm2
|
||||
|
||||
push tmp
|
||||
pop DWORD PTR [eax]
|
||||
|
||||
movd DWORD PTR [eax+4], mm3
|
||||
|
||||
add ebx, 8
|
||||
add eax, 8
|
||||
|
||||
movq mm2, [ebx]
|
||||
movq mm3, [eax]
|
||||
|
||||
pfmul mm2, mm3
|
||||
pfsub mm1, mm2
|
||||
|
||||
psrlq mm2, 32
|
||||
pfsub mm1, mm2
|
||||
|
||||
// Store new hist
|
||||
movd tmp, mm1
|
||||
|
||||
add ebx, 8
|
||||
movq mm2, [ebx]
|
||||
movq mm3, [eax]
|
||||
|
||||
pfmul mm2, mm3
|
||||
pfadd mm1, mm2
|
||||
|
||||
psrlq mm2, 32
|
||||
pfadd mm1, mm2
|
||||
|
||||
push tmp
|
||||
pop DWORD PTR [eax]
|
||||
movd DWORD PTR [eax+4], mm3
|
||||
|
||||
movd output, mm1
|
||||
|
||||
femms
|
||||
}
|
||||
#else
|
||||
output = atti386_iir_filter_3DNow(output, hist1_ptr, coef_ptr);
|
||||
#endif
|
||||
return output;
|
||||
}
|
||||
|
||||
#if MT32EMU_USE_MMX > 0
|
||||
|
||||
int i386_partialProductOutput(int len, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *mixedBuf) {
|
||||
int tmplen = len >> 1;
|
||||
if (tmplen == 0) {
|
||||
return 0;
|
||||
}
|
||||
#ifdef _MSC_VER
|
||||
__asm {
|
||||
mov ecx,tmplen
|
||||
mov ax, leftvol
|
||||
shl eax,16
|
||||
mov ax, rightvol
|
||||
movd mm1, eax
|
||||
movd mm2, eax
|
||||
psllq mm1, 32
|
||||
por mm1, mm2
|
||||
mov edi, partialBuf
|
||||
mov esi, mixedBuf
|
||||
mmxloop1:
|
||||
mov bx, [esi]
|
||||
add esi,2
|
||||
mov dx, [esi]
|
||||
add esi,2
|
||||
|
||||
mov ax, dx
|
||||
shl eax, 16
|
||||
mov ax, dx
|
||||
movd mm2,eax
|
||||
psllq mm2, 32
|
||||
mov ax, bx
|
||||
shl eax, 16
|
||||
mov ax, bx
|
||||
movd mm3,eax
|
||||
por mm2,mm3
|
||||
|
||||
pmulhw mm2, mm1
|
||||
movq [edi], mm2
|
||||
add edi, 8
|
||||
|
||||
dec ecx
|
||||
cmp ecx,0
|
||||
jg mmxloop1
|
||||
emms
|
||||
}
|
||||
#else
|
||||
atti386_partialProductOutput(tmplen, leftvol, rightvol, partialBuf, mixedBuf);
|
||||
#endif
|
||||
return tmplen << 1;
|
||||
}
|
||||
|
||||
int i386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) {
|
||||
int tmplen = len >> 2;
|
||||
if (tmplen == 0) {
|
||||
return 0;
|
||||
}
|
||||
#ifdef _MSC_VER
|
||||
__asm {
|
||||
mov ecx, tmplen
|
||||
mov esi, buf1
|
||||
mov edi, buf2
|
||||
|
||||
mixloop1:
|
||||
movq mm1, [edi]
|
||||
movq mm2, [esi]
|
||||
paddw mm1,mm2
|
||||
movq [esi],mm1
|
||||
add edi,8
|
||||
add esi,8
|
||||
|
||||
dec ecx
|
||||
cmp ecx,0
|
||||
jg mixloop1
|
||||
emms
|
||||
}
|
||||
#else
|
||||
atti386_mixBuffers(buf1, buf2, tmplen);
|
||||
#endif
|
||||
return tmplen << 2;
|
||||
}
|
||||
|
||||
|
||||
int i386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) {
|
||||
int tmplen = len >> 2;
|
||||
if (tmplen == 0) {
|
||||
return 0;
|
||||
}
|
||||
#ifdef _MSC_VER
|
||||
__asm {
|
||||
mov ecx, tmplen
|
||||
mov esi, buf1
|
||||
mov edi, buf2
|
||||
|
||||
mixloop2:
|
||||
movq mm1, [esi]
|
||||
movq mm2, [edi]
|
||||
movq mm3, mm1
|
||||
pmulhw mm1, mm2
|
||||
paddw mm1,mm3
|
||||
movq [esi],mm1
|
||||
add edi,8
|
||||
add esi,8
|
||||
|
||||
dec ecx
|
||||
cmp ecx,0
|
||||
jg mixloop2
|
||||
emms
|
||||
}
|
||||
#else
|
||||
atti386_mixBuffersRingMix(buf1, buf2, tmplen);
|
||||
#endif
|
||||
return tmplen << 2;
|
||||
}
|
||||
|
||||
int i386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) {
|
||||
int tmplen = len >> 2;
|
||||
if (tmplen == 0) {
|
||||
return 0;
|
||||
}
|
||||
#ifdef _MSC_VER
|
||||
__asm {
|
||||
mov ecx, tmplen
|
||||
mov esi, buf1
|
||||
mov edi, buf2
|
||||
|
||||
mixloop3:
|
||||
movq mm1, [esi]
|
||||
movq mm2, [edi]
|
||||
pmulhw mm1, mm2
|
||||
movq [esi],mm1
|
||||
add edi,8
|
||||
add esi,8
|
||||
|
||||
dec ecx
|
||||
cmp ecx,0
|
||||
jg mixloop3
|
||||
emms
|
||||
}
|
||||
#else
|
||||
atti386_mixBuffersRing(buf1, buf2, tmplen);
|
||||
#endif
|
||||
return tmplen << 2;
|
||||
}
|
||||
|
||||
int i386_produceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume) {
|
||||
int tmplen = (len >> 1);
|
||||
if (tmplen == 0) {
|
||||
return 0;
|
||||
}
|
||||
#ifdef _MSC_VER
|
||||
__asm {
|
||||
mov ecx, tmplen
|
||||
mov ax,volume
|
||||
shl eax,16
|
||||
mov ax,volume
|
||||
movd mm3,eax
|
||||
movd mm2,eax
|
||||
psllq mm3, 32
|
||||
por mm3,mm2
|
||||
mov esi, useBuf
|
||||
mov edi, stream
|
||||
mixloop4:
|
||||
movq mm1, [esi]
|
||||
movq mm2, [edi]
|
||||
pmulhw mm1, mm3
|
||||
paddw mm1,mm2
|
||||
movq [edi], mm1
|
||||
|
||||
add esi,8
|
||||
add edi,8
|
||||
|
||||
dec ecx
|
||||
cmp ecx,0
|
||||
jg mixloop4
|
||||
emms
|
||||
}
|
||||
#else
|
||||
atti386_produceOutput1(tmplen, volume, useBuf, stream);
|
||||
#endif
|
||||
return tmplen << 1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,49 +0,0 @@
|
|||
/* Copyright (c) 2003-2005 Various contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_I386_H
|
||||
#define MT32EMU_I386_H
|
||||
|
||||
namespace MT32Emu {
|
||||
#ifdef MT32EMU_HAVE_X86
|
||||
|
||||
// Function that detects the availablity of SSE SIMD instructions
|
||||
bool DetectSIMD();
|
||||
// Function that detects the availablity of 3DNow instructions
|
||||
bool Detect3DNow();
|
||||
|
||||
float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr);
|
||||
float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr);
|
||||
float iir_filter_normal(float input,float *hist1_ptr, float *coef_ptr);
|
||||
|
||||
#if MT32EMU_USE_MMX > 0
|
||||
int i386_partialProductOutput(int len, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *mixedBuf);
|
||||
int i386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len);
|
||||
int i386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len);
|
||||
int i386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len);
|
||||
int i386_produceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif
|
73
audio/softsynth/mt32/mmath.h
Normal file
73
audio/softsynth/mt32/mmath.h
Normal file
|
@ -0,0 +1,73 @@
|
|||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_MMATH_H
|
||||
#define MT32EMU_MMATH_H
|
||||
|
||||
#define FIXEDPOINT_UDIV(x, y, point) (((x) << (point)) / ((y)))
|
||||
#define FIXEDPOINT_SDIV(x, y, point) (((x) * (1 << point)) / ((y)))
|
||||
#define FIXEDPOINT_UMULT(x, y, point) (((x) * (y)) >> point)
|
||||
#define FIXEDPOINT_SMULT(x, y, point) (((x) * (y)) / (1 << point))
|
||||
|
||||
#define FIXEDPOINT_MAKE(x, point) ((Bit32u)((1 << point) * x))
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// Mathematical constants
|
||||
const double DOUBLE_PI = 3.141592653589793;
|
||||
const double DOUBLE_LN_10 = 2.302585092994046;
|
||||
const float FLOAT_PI = 3.1415927f;
|
||||
const float FLOAT_2PI = 6.2831853f;
|
||||
const float FLOAT_LN_2 = 0.6931472f;
|
||||
const float FLOAT_LN_10 = 2.3025851f;
|
||||
|
||||
static inline float POWF(float x, float y) {
|
||||
return pow(x, y);
|
||||
}
|
||||
|
||||
static inline float EXPF(float x) {
|
||||
return exp(x);
|
||||
}
|
||||
|
||||
static inline float EXP2F(float x) {
|
||||
#ifdef __APPLE__
|
||||
// on OSX exp2f() is 1.59 times faster than "exp() and the multiplication with FLOAT_LN_2"
|
||||
return exp2f(x);
|
||||
#else
|
||||
return exp(FLOAT_LN_2 * x);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline float EXP10F(float x) {
|
||||
return exp(FLOAT_LN_10 * x);
|
||||
}
|
||||
|
||||
static inline float LOGF(float x) {
|
||||
return log(x);
|
||||
}
|
||||
|
||||
static inline float LOG2F(float x) {
|
||||
return log(x) / FLOAT_LN_2;
|
||||
}
|
||||
|
||||
static inline float LOG10F(float x) {
|
||||
return log10(x);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,13 +1,19 @@
|
|||
MODULE := audio/softsynth/mt32
|
||||
|
||||
MODULE_OBJS := \
|
||||
mt32_file.o \
|
||||
i386.o \
|
||||
part.o \
|
||||
partial.o \
|
||||
partialManager.o \
|
||||
synth.o \
|
||||
tables.o \
|
||||
AReverbModel.o \
|
||||
DelayReverb.o \
|
||||
FreeverbModel.o \
|
||||
LA32Ramp.o \
|
||||
Part.o \
|
||||
Partial.o \
|
||||
PartialManager.o \
|
||||
Poly.o \
|
||||
Synth.o \
|
||||
TVA.o \
|
||||
TVF.o \
|
||||
TVP.o \
|
||||
Tables.o \
|
||||
freeverb.o
|
||||
|
||||
# Include common rules
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
/* Copyright (c) 2003-2005 Various contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
#include "mt32emu.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
bool File::readBit16u(Bit16u *in) {
|
||||
Bit8u b[2];
|
||||
if (read(&b[0], 2) != 2)
|
||||
return false;
|
||||
*in = ((b[0] << 8) | b[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::readBit32u(Bit32u *in) {
|
||||
Bit8u b[4];
|
||||
if (read(&b[0], 4) != 4)
|
||||
return false;
|
||||
*in = ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::writeBit16u(Bit16u out) {
|
||||
if (!writeBit8u((Bit8u)((out & 0xFF00) >> 8))) {
|
||||
return false;
|
||||
}
|
||||
if (!writeBit8u((Bit8u)(out & 0x00FF))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::writeBit32u(Bit32u out) {
|
||||
if (!writeBit8u((Bit8u)((out & 0xFF000000) >> 24))) {
|
||||
return false;
|
||||
}
|
||||
if (!writeBit8u((Bit8u)((out & 0x00FF0000) >> 16))) {
|
||||
return false;
|
||||
}
|
||||
if (!writeBit8u((Bit8u)((out & 0x0000FF00) >> 8))) {
|
||||
return false;
|
||||
}
|
||||
if (!writeBit8u((Bit8u)(out & 0x000000FF))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // End of namespace MT32Emu
|
|
@ -1,52 +0,0 @@
|
|||
/* Copyright (c) 2003-2005 Various contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_FILE_H
|
||||
#define MT32EMU_FILE_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class File {
|
||||
public:
|
||||
enum OpenMode {
|
||||
OpenMode_read = 0,
|
||||
OpenMode_write = 1
|
||||
};
|
||||
virtual ~File() {}
|
||||
virtual void close() = 0;
|
||||
virtual size_t read(void *in, size_t size) = 0;
|
||||
virtual bool readBit8u(Bit8u *in) = 0;
|
||||
virtual bool readBit16u(Bit16u *in);
|
||||
virtual bool readBit32u(Bit32u *in);
|
||||
virtual size_t write(const void *out, size_t size) = 0;
|
||||
virtual bool writeBit8u(Bit8u out) = 0;
|
||||
// Note: May write a single byte to the file before failing
|
||||
virtual bool writeBit16u(Bit16u out);
|
||||
// Note: May write some (<4) bytes to the file before failing
|
||||
virtual bool writeBit32u(Bit32u out);
|
||||
virtual bool isEOF() = 0;
|
||||
};
|
||||
|
||||
} // End of namespace MT32Emu
|
||||
|
||||
#endif
|
|
@ -1,37 +1,70 @@
|
|||
/* Copyright (c) 2003-2005 Various contributors
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* 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 Lesser General Public License for more details.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_MT32EMU_H
|
||||
#define MT32EMU_MT32EMU_H
|
||||
|
||||
// Debugging
|
||||
// Show the instruments played
|
||||
#define MT32EMU_MONITOR_INSTRUMENTS 1
|
||||
// Shows number of partials MT-32 is playing, and on which parts
|
||||
|
||||
// 0: Standard debug output is not stamped with the rendered sample count
|
||||
// 1: Standard debug output is stamped with the rendered sample count
|
||||
// NOTE: The "samplestamp" corresponds to the end of the last completed rendering run.
|
||||
// This is important to bear in mind for debug output that occurs during a run.
|
||||
#define MT32EMU_DEBUG_SAMPLESTAMPS 0
|
||||
|
||||
// 0: No debug output for initialisation progress
|
||||
// 1: Debug output for initialisation progress
|
||||
#define MT32EMU_MONITOR_INIT 0
|
||||
|
||||
// 0: No debug output for MIDI events
|
||||
// 1: Debug output for weird MIDI events
|
||||
#define MT32EMU_MONITOR_MIDI 0
|
||||
|
||||
// 0: No debug output for note on/off
|
||||
// 1: Basic debug output for note on/off
|
||||
// 2: Comprehensive debug output for note on/off
|
||||
#define MT32EMU_MONITOR_INSTRUMENTS 0
|
||||
|
||||
// 0: No debug output for partial allocations
|
||||
// 1: Show partial stats when an allocation fails
|
||||
// 2: Show partial stats with every new poly
|
||||
// 3: Show individual partial allocations/deactivations
|
||||
#define MT32EMU_MONITOR_PARTIALS 0
|
||||
// Determines how the waveform cache file is handled (must be regenerated after sampling rate change)
|
||||
#define MT32EMU_WAVECACHEMODE 0 // Load existing cache if possible, otherwise generate and save cache
|
||||
//#define MT32EMU_WAVECACHEMODE 1 // Load existing cache if possible, otherwise generate but don't save cache
|
||||
//#define MT32EMU_WAVECACHEMODE 2 // Ignore existing cache, generate and save cache
|
||||
//#define MT32EMU_WAVECACHEMODE 3 // Ignore existing cache, generate but don't save cache
|
||||
|
||||
// 0: No debug output for sysex
|
||||
// 1: Basic debug output for sysex
|
||||
#define MT32EMU_MONITOR_SYSEX 0
|
||||
|
||||
// 0: No debug output for sysex writes to the timbre areas
|
||||
// 1: Debug output with the name and location of newly-written timbres
|
||||
// 2: Complete dump of timbre parameters for newly-written timbres
|
||||
#define MT32EMU_MONITOR_TIMBRES 0
|
||||
|
||||
// 0: No TVA/TVF-related debug output.
|
||||
// 1: Shows changes to TVA/TVF target, increment and phase.
|
||||
#define MT32EMU_MONITOR_TVA 0
|
||||
#define MT32EMU_MONITOR_TVF 0
|
||||
|
||||
|
||||
// 0: Use LUTs to speedup WG
|
||||
// 1: Use precise float math
|
||||
#define MT32EMU_ACCURATE_WG 1
|
||||
|
||||
#define MT32EMU_USE_EXTINT 0
|
||||
|
||||
// Configuration
|
||||
// The maximum number of partials playing simultaneously
|
||||
|
@ -39,32 +72,43 @@
|
|||
// The maximum number of notes playing simultaneously per part.
|
||||
// No point making it more than MT32EMU_MAX_PARTIALS, since each note needs at least one partial.
|
||||
#define MT32EMU_MAX_POLY 32
|
||||
// This calculates the exact frequencies of notes as they are played, instead of offsetting from pre-cached semitones. Potentially very slow.
|
||||
#define MT32EMU_ACCURATENOTES 0
|
||||
|
||||
#if (defined (_MSC_VER) && defined(_M_IX86))
|
||||
#define MT32EMU_HAVE_X86
|
||||
#elif defined(__GNUC__)
|
||||
#if __GNUC__ >= 3 && defined(__i386__)
|
||||
#define MT32EMU_HAVE_X86
|
||||
#endif
|
||||
#endif
|
||||
// If non-zero, deletes reverb buffers that are not in use to save memory.
|
||||
// If zero, keeps reverb buffers for all modes around all the time to avoid allocating/freeing in the critical path.
|
||||
#define MT32EMU_REDUCE_REVERB_MEMORY 1
|
||||
|
||||
#ifdef MT32EMU_HAVE_X86
|
||||
#define MT32EMU_USE_MMX 1
|
||||
#else
|
||||
#define MT32EMU_USE_MMX 0
|
||||
#endif
|
||||
// 0: Use standard Freeverb
|
||||
// 1: Use AReverb (currently not properly tuned)
|
||||
#define MT32EMU_USE_AREVERBMODEL 0
|
||||
|
||||
#include "freeverb.h"
|
||||
namespace MT32Emu
|
||||
{
|
||||
// The higher this number, the more memory will be used, but the more samples can be processed in one run -
|
||||
// various parts of sample generation can be processed more efficiently in a single run.
|
||||
// A run's maximum length is that given to Synth::render(), so giving a value here higher than render() is ever
|
||||
// called with will give no gain (but simply waste the memory).
|
||||
// Note that this value does *not* in any way impose limitations on the length given to render(), and has no effect
|
||||
// on the generated audio.
|
||||
// This value must be >= 1.
|
||||
const unsigned int MAX_SAMPLES_PER_RUN = 4096;
|
||||
|
||||
#include "structures.h"
|
||||
#include "i386.h"
|
||||
#include "mt32_file.h"
|
||||
#include "tables.h"
|
||||
#include "partial.h"
|
||||
#include "partialManager.h"
|
||||
#include "part.h"
|
||||
#include "synth.h"
|
||||
// This determines the amount of memory available for simulating delays.
|
||||
// If set too low, partials aborted to allow other partials to play will not end gracefully, but will terminate
|
||||
// abruptly and potentially cause a pop/crackle in the audio output.
|
||||
// This value must be >= 1.
|
||||
const unsigned int MAX_PRERENDER_SAMPLES = 1024;
|
||||
}
|
||||
|
||||
#include "Structures.h"
|
||||
#include "common/file.h"
|
||||
#include "Tables.h"
|
||||
#include "Poly.h"
|
||||
#include "LA32Ramp.h"
|
||||
#include "TVA.h"
|
||||
#include "TVP.h"
|
||||
#include "TVF.h"
|
||||
#include "Partial.h"
|
||||
#include "Part.h"
|
||||
#include "Synth.h"
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,178 +1,183 @@
|
|||
/* Copyright (c) 2003-2005 Various contributors
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* 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 Lesser General Public License for more details.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
//#include <cstdio>
|
||||
//#include <cstring>
|
||||
|
||||
#include "mt32emu.h"
|
||||
#include "PartialManager.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
static const Bit8u PartialStruct[13] = {
|
||||
0, 0, 2, 2, 1, 3,
|
||||
3, 0, 3, 0, 2, 1, 3 };
|
||||
3, 0, 3, 0, 2, 1, 3
|
||||
};
|
||||
|
||||
static const Bit8u PartialMixStruct[13] = {
|
||||
0, 1, 0, 1, 1, 0,
|
||||
1, 3, 3, 2, 2, 2, 2 };
|
||||
1, 3, 3, 2, 2, 2, 2
|
||||
};
|
||||
|
||||
static const float floatKeyfollow[17] = {
|
||||
-1.0f, -1.0f/2.0f, -1.0f/4.0f, 0.0f,
|
||||
1.0f/8.0f, 1.0f/4.0f, 3.0f/8.0f, 1.0f/2.0f, 5.0f/8.0f, 3.0f/4.0f, 7.0f/8.0f, 1.0f,
|
||||
5.0f/4.0f, 3.0f/2.0f, 2.0f,
|
||||
-1.0f, -1.0f / 2.0f, -1.0f / 4.0f, 0.0f,
|
||||
1.0f / 8.0f, 1.0f / 4.0f, 3.0f / 8.0f, 1.0f / 2.0f, 5.0f / 8.0f, 3.0f / 4.0f, 7.0f / 8.0f, 1.0f,
|
||||
5.0f / 4.0f, 3.0f / 2.0f, 2.0f,
|
||||
1.0009765625f, 1.0048828125f
|
||||
};
|
||||
|
||||
//FIXME:KG: Put this dpoly stuff somewhere better
|
||||
bool dpoly::isActive() const {
|
||||
return partials[0] != NULL || partials[1] != NULL || partials[2] != NULL || partials[3] != NULL;
|
||||
}
|
||||
|
||||
Bit32u dpoly::getAge() const {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (partials[i] != NULL) {
|
||||
return partials[i]->age;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
RhythmPart::RhythmPart(Synth *useSynth, unsigned int usePartNum): Part(useSynth, usePartNum) {
|
||||
strcpy(name, "Rhythm");
|
||||
rhythmTemp = &synth->mt32ram.rhythmSettings[0];
|
||||
rhythmTemp = &synth->mt32ram.rhythmTemp[0];
|
||||
refresh();
|
||||
}
|
||||
|
||||
Part::Part(Synth *useSynth, unsigned int usePartNum) {
|
||||
this->synth = useSynth;
|
||||
this->partNum = usePartNum;
|
||||
synth = useSynth;
|
||||
partNum = usePartNum;
|
||||
patchCache[0].dirty = true;
|
||||
holdpedal = false;
|
||||
patchTemp = &synth->mt32ram.patchSettings[partNum];
|
||||
patchTemp = &synth->mt32ram.patchTemp[partNum];
|
||||
if (usePartNum == 8) {
|
||||
// Nasty hack for rhythm
|
||||
timbreTemp = NULL;
|
||||
} else {
|
||||
sprintf(name, "Part %d", partNum + 1);
|
||||
timbreTemp = &synth->mt32ram.timbreSettings[partNum];
|
||||
timbreTemp = &synth->mt32ram.timbreTemp[partNum];
|
||||
}
|
||||
currentInstr[0] = 0;
|
||||
currentInstr[10] = 0;
|
||||
expression = 127;
|
||||
volumeMult = 0;
|
||||
volumesetting.leftvol = 32767;
|
||||
volumesetting.rightvol = 32767;
|
||||
bend = 0.0f;
|
||||
memset(polyTable,0,sizeof(polyTable));
|
||||
modulation = 0;
|
||||
expression = 100;
|
||||
pitchBend = 0;
|
||||
activePartialCount = 0;
|
||||
memset(patchCache, 0, sizeof(patchCache));
|
||||
for (int i = 0; i < MT32EMU_MAX_POLY; i++) {
|
||||
freePolys.push_front(new Poly(this));
|
||||
}
|
||||
}
|
||||
|
||||
void Part::setHoldPedal(bool pedalval) {
|
||||
if (holdpedal && !pedalval) {
|
||||
Part::~Part() {
|
||||
while (!activePolys.empty()) {
|
||||
delete activePolys.front();
|
||||
activePolys.pop_front();
|
||||
}
|
||||
while (!freePolys.empty()) {
|
||||
delete freePolys.front();
|
||||
freePolys.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void Part::setDataEntryMSB(unsigned char midiDataEntryMSB) {
|
||||
if (nrpn) {
|
||||
// The last RPN-related control change was for an NRPN,
|
||||
// which the real synths don't support.
|
||||
return;
|
||||
}
|
||||
if (rpn != 0) {
|
||||
// The RPN has been set to something other than 0,
|
||||
// which is the only RPN that these synths support
|
||||
return;
|
||||
}
|
||||
patchTemp->patch.benderRange = midiDataEntryMSB > 24 ? 24 : midiDataEntryMSB;
|
||||
updatePitchBenderRange();
|
||||
}
|
||||
|
||||
void Part::setNRPN() {
|
||||
nrpn = true;
|
||||
}
|
||||
|
||||
void Part::setRPNLSB(unsigned char midiRPNLSB) {
|
||||
nrpn = false;
|
||||
rpn = (rpn & 0xFF00) | midiRPNLSB;
|
||||
}
|
||||
|
||||
void Part::setRPNMSB(unsigned char midiRPNMSB) {
|
||||
nrpn = false;
|
||||
rpn = (rpn & 0x00FF) | (midiRPNMSB << 8);
|
||||
}
|
||||
|
||||
void Part::setHoldPedal(bool pressed) {
|
||||
if (holdpedal && !pressed) {
|
||||
holdpedal = false;
|
||||
stopPedalHold();
|
||||
} else {
|
||||
holdpedal = pedalval;
|
||||
holdpedal = pressed;
|
||||
}
|
||||
}
|
||||
|
||||
void RhythmPart::setBend(unsigned int midiBend) {
|
||||
synth->printDebug("%s: Setting bend (%d) not supported on rhythm", name, midiBend);
|
||||
return;
|
||||
Bit32s Part::getPitchBend() const {
|
||||
return pitchBend;
|
||||
}
|
||||
|
||||
void Part::setBend(unsigned int midiBend) {
|
||||
// FIXME:KG: Slightly unbalanced increments, but I wanted min -1.0, center 0.0 and max 1.0
|
||||
if (midiBend <= 0x2000) {
|
||||
bend = ((signed int)midiBend - 0x2000) / (float)0x2000;
|
||||
} else {
|
||||
bend = ((signed int)midiBend - 0x2000) / (float)0x1FFF;
|
||||
}
|
||||
// Loop through all partials to update their bend
|
||||
for (int i = 0; i < MT32EMU_MAX_POLY; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
if (polyTable[i].partials[j] != NULL) {
|
||||
polyTable[i].partials[j]->setBend(bend);
|
||||
}
|
||||
}
|
||||
}
|
||||
// CONFIRMED:
|
||||
pitchBend = (((signed)midiBend - 8192) * pitchBenderRange) >> 14; // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
}
|
||||
|
||||
void RhythmPart::setModulation(unsigned int midiModulation) {
|
||||
synth->printDebug("%s: Setting modulation (%d) not supported on rhythm", name, midiModulation);
|
||||
Bit8u Part::getModulation() const {
|
||||
return modulation;
|
||||
}
|
||||
|
||||
void Part::setModulation(unsigned int midiModulation) {
|
||||
// Just a bloody guess, as always, before I get things figured out
|
||||
for (int t = 0; t < 4; t++) {
|
||||
if (patchCache[t].playPartial) {
|
||||
int newrate = (patchCache[t].modsense * midiModulation) >> 7;
|
||||
//patchCache[t].lfoperiod = lfotable[newrate];
|
||||
patchCache[t].lfodepth = newrate;
|
||||
//FIXME:KG: timbreTemp->partial[t].lfo.depth =
|
||||
}
|
||||
}
|
||||
modulation = (Bit8u)midiModulation;
|
||||
}
|
||||
|
||||
void Part::resetAllControllers() {
|
||||
modulation = 0;
|
||||
expression = 100;
|
||||
pitchBend = 0;
|
||||
setHoldPedal(false);
|
||||
}
|
||||
|
||||
void Part::reset() {
|
||||
resetAllControllers();
|
||||
allSoundOff();
|
||||
rpn = 0xFFFF;
|
||||
}
|
||||
|
||||
void RhythmPart::refresh() {
|
||||
updateVolume();
|
||||
// (Re-)cache all the mapped timbres ahead of time
|
||||
for (unsigned int drumNum = 0; drumNum < synth->controlROMMap->rhythmSettingsCount; drumNum++) {
|
||||
int drumTimbreNum = rhythmTemp[drumNum].timbre;
|
||||
if (drumTimbreNum >= 127) // 94 on MT-32
|
||||
if (drumTimbreNum >= 127) { // 94 on MT-32
|
||||
continue;
|
||||
Bit16s pan = rhythmTemp[drumNum].panpot; // They use R-L 0-14...
|
||||
// FIXME:KG: Panning cache should be backed up to partials using it, too
|
||||
if (pan < 7) {
|
||||
drumPan[drumNum].leftvol = pan * 4681;
|
||||
drumPan[drumNum].rightvol = 32767;
|
||||
} else {
|
||||
drumPan[drumNum].rightvol = (14 - pan) * 4681;
|
||||
drumPan[drumNum].leftvol = 32767;
|
||||
}
|
||||
PatchCache *cache = drumCache[drumNum];
|
||||
backupCacheToPartials(cache);
|
||||
for (int t = 0; t < 4; t++) {
|
||||
// Common parameters, stored redundantly
|
||||
cache[t].dirty = true;
|
||||
cache[t].pitchShift = 0.0f;
|
||||
cache[t].benderRange = 0.0f;
|
||||
cache[t].pansetptr = &drumPan[drumNum];
|
||||
cache[t].reverb = rhythmTemp[drumNum].reverbSwitch > 0;
|
||||
}
|
||||
}
|
||||
updatePitchBenderRange();
|
||||
}
|
||||
|
||||
void Part::refresh() {
|
||||
updateVolume();
|
||||
backupCacheToPartials(patchCache);
|
||||
for (int t = 0; t < 4; t++) {
|
||||
// Common parameters, stored redundantly
|
||||
patchCache[t].dirty = true;
|
||||
patchCache[t].pitchShift = (patchTemp->patch.keyShift - 24) + (patchTemp->patch.fineTune - 50) / 100.0f;
|
||||
patchCache[t].benderRange = patchTemp->patch.benderRange;
|
||||
patchCache[t].pansetptr = &volumesetting;
|
||||
patchCache[t].reverb = patchTemp->patch.reverbSwitch > 0;
|
||||
}
|
||||
memcpy(currentInstr, timbreTemp->common.name, 10);
|
||||
updatePitchBenderRange();
|
||||
}
|
||||
|
||||
const char *Part::getCurrentInstr() const {
|
||||
|
@ -181,9 +186,10 @@ const char *Part::getCurrentInstr() const {
|
|||
|
||||
void RhythmPart::refreshTimbre(unsigned int absTimbreNum) {
|
||||
for (int m = 0; m < 85; m++) {
|
||||
if (rhythmTemp[m].timbre == absTimbreNum - 128)
|
||||
if (rhythmTemp[m].timbre == absTimbreNum - 128) {
|
||||
drumCache[m][0].dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Part::refreshTimbre(unsigned int absTimbreNum) {
|
||||
|
@ -193,42 +199,6 @@ void Part::refreshTimbre(unsigned int absTimbreNum) {
|
|||
}
|
||||
}
|
||||
|
||||
int Part::fixBiaslevel(int srcpnt, int *dir) {
|
||||
int noteat = srcpnt & 0x3F;
|
||||
int outnote;
|
||||
if (srcpnt < 64)
|
||||
*dir = 0;
|
||||
else
|
||||
*dir = 1;
|
||||
outnote = 33 + noteat;
|
||||
//synth->printDebug("Bias note %d, dir %d", outnote, *dir);
|
||||
|
||||
return outnote;
|
||||
}
|
||||
|
||||
int Part::fixKeyfollow(int srckey) {
|
||||
if (srckey>=0 && srckey<=16) {
|
||||
int keyfix[17] = { -256*16, -128*16, -64*16, 0, 32*16, 64*16, 96*16, 128*16, (128+32)*16, 192*16, (192+32)*16, 256*16, (256+64)*16, (256+128)*16, (512)*16, 4100, 4116};
|
||||
return keyfix[srckey];
|
||||
} else {
|
||||
//LOG(LOG_ERROR|LOG_MISC,"Missed key: %d", srckey);
|
||||
return 256;
|
||||
}
|
||||
}
|
||||
|
||||
void Part::abortPoly(dpoly *poly) {
|
||||
if (!poly->isPlaying) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < 4; i++) {
|
||||
Partial *partial = poly->partials[i];
|
||||
if (partial != NULL) {
|
||||
partial->deactivate();
|
||||
}
|
||||
}
|
||||
poly->isPlaying = false;
|
||||
}
|
||||
|
||||
void Part::setPatch(const PatchParam *patch) {
|
||||
patchTemp->patch = *patch;
|
||||
}
|
||||
|
@ -250,17 +220,24 @@ unsigned int Part::getAbsTimbreNum() const {
|
|||
return (patchTemp->patch.timbreGroup * 64) + patchTemp->patch.timbreNum;
|
||||
}
|
||||
|
||||
#if MT32EMU_MONITOR_MIDI > 0
|
||||
void RhythmPart::setProgram(unsigned int patchNum) {
|
||||
synth->printDebug("%s: Attempt to set program (%d) on rhythm is invalid", name, patchNum);
|
||||
}
|
||||
#else
|
||||
void RhythmPart::setProgram(unsigned int) { }
|
||||
#endif
|
||||
|
||||
void Part::setProgram(unsigned int patchNum) {
|
||||
setPatch(&synth->mt32ram.patches[patchNum]);
|
||||
holdpedal = false;
|
||||
allSoundOff();
|
||||
setTimbre(&synth->mt32ram.timbres[getAbsTimbreNum()].timbre);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
allSoundOff(); //FIXME:KG: Is this correct?
|
||||
void Part::updatePitchBenderRange() {
|
||||
pitchBenderRange = patchTemp->patch.benderRange * 683;
|
||||
}
|
||||
|
||||
void Part::backupCacheToPartials(PatchCache cache[4]) {
|
||||
|
@ -268,14 +245,8 @@ void Part::backupCacheToPartials(PatchCache cache[4]) {
|
|||
// if so then duplicate the cached data from the part to the partial so that
|
||||
// we can change the part's cache without affecting the partial.
|
||||
// We delay this until now to avoid a copy operation with every note played
|
||||
for (int m = 0; m < MT32EMU_MAX_POLY; m++) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
Partial *partial = polyTable[m].partials[i];
|
||||
if (partial != NULL && partial->patchCache == &cache[i]) {
|
||||
partial->cachebackup = cache[i];
|
||||
partial->patchCache = &partial->cachebackup;
|
||||
}
|
||||
}
|
||||
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
|
||||
(*polyIt)->backupCacheToPartials(cache);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -283,8 +254,7 @@ void Part::cacheTimbre(PatchCache cache[4], const TimbreParam *timbre) {
|
|||
backupCacheToPartials(cache);
|
||||
int partialCount = 0;
|
||||
for (int t = 0; t < 4; t++) {
|
||||
cache[t].PCMPartial = false;
|
||||
if (((timbre->common.pmute >> t) & 0x1) == 1) {
|
||||
if (((timbre->common.partialMute >> t) & 0x1) == 1) {
|
||||
cache[t].playPartial = true;
|
||||
partialCount++;
|
||||
} else {
|
||||
|
@ -293,32 +263,32 @@ void Part::cacheTimbre(PatchCache cache[4], const TimbreParam *timbre) {
|
|||
}
|
||||
|
||||
// Calculate and cache common parameters
|
||||
cache[t].srcPartial = timbre->partial[t];
|
||||
|
||||
cache[t].pcm = timbre->partial[t].wg.pcmwave;
|
||||
cache[t].useBender = (timbre->partial[t].wg.bender == 1);
|
||||
cache[t].pcm = timbre->partial[t].wg.pcmWave;
|
||||
|
||||
switch (t) {
|
||||
case 0:
|
||||
cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct12] & 0x2) ? true : false;
|
||||
cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct12];
|
||||
cache[t].PCMPartial = (PartialStruct[(int)timbre->common.partialStructure12] & 0x2) ? true : false;
|
||||
cache[t].structureMix = PartialMixStruct[(int)timbre->common.partialStructure12];
|
||||
cache[t].structurePosition = 0;
|
||||
cache[t].structurePair = 1;
|
||||
break;
|
||||
case 1:
|
||||
cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct12] & 0x1) ? true : false;
|
||||
cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct12];
|
||||
cache[t].PCMPartial = (PartialStruct[(int)timbre->common.partialStructure12] & 0x1) ? true : false;
|
||||
cache[t].structureMix = PartialMixStruct[(int)timbre->common.partialStructure12];
|
||||
cache[t].structurePosition = 1;
|
||||
cache[t].structurePair = 0;
|
||||
break;
|
||||
case 2:
|
||||
cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct34] & 0x2) ? true : false;
|
||||
cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct34];
|
||||
cache[t].PCMPartial = (PartialStruct[(int)timbre->common.partialStructure34] & 0x2) ? true : false;
|
||||
cache[t].structureMix = PartialMixStruct[(int)timbre->common.partialStructure34];
|
||||
cache[t].structurePosition = 0;
|
||||
cache[t].structurePair = 3;
|
||||
break;
|
||||
case 3:
|
||||
cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct34] & 0x1) ? true : false;
|
||||
cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct34];
|
||||
cache[t].PCMPartial = (PartialStruct[(int)timbre->common.partialStructure34] & 0x1) ? true : false;
|
||||
cache[t].structureMix = PartialMixStruct[(int)timbre->common.partialStructure34];
|
||||
cache[t].structurePosition = 1;
|
||||
cache[t].structurePair = 2;
|
||||
break;
|
||||
|
@ -326,57 +296,22 @@ void Part::cacheTimbre(PatchCache cache[4], const TimbreParam *timbre) {
|
|||
break;
|
||||
}
|
||||
|
||||
cache[t].partialParam = &timbre->partial[t];
|
||||
|
||||
cache[t].waveform = timbre->partial[t].wg.waveform;
|
||||
cache[t].pulsewidth = timbre->partial[t].wg.pulsewid;
|
||||
cache[t].pwsens = timbre->partial[t].wg.pwvelo;
|
||||
if (timbre->partial[t].wg.keyfollow > 16) {
|
||||
synth->printDebug("Bad keyfollow value in timbre!");
|
||||
cache[t].pitchKeyfollow = 1.0f;
|
||||
} else {
|
||||
cache[t].pitchKeyfollow = floatKeyfollow[timbre->partial[t].wg.keyfollow];
|
||||
}
|
||||
|
||||
cache[t].pitch = timbre->partial[t].wg.coarse + (timbre->partial[t].wg.fine - 50) / 100.0f + 24.0f;
|
||||
cache[t].pitchEnv = timbre->partial[t].env;
|
||||
cache[t].pitchEnv.sensitivity = (char)((float)cache[t].pitchEnv.sensitivity * 1.27f);
|
||||
cache[t].pitchsustain = cache[t].pitchEnv.level[3];
|
||||
|
||||
// Calculate and cache TVA envelope stuff
|
||||
cache[t].ampEnv = timbre->partial[t].tva;
|
||||
cache[t].ampEnv.level = (char)((float)cache[t].ampEnv.level * 1.27f);
|
||||
|
||||
cache[t].ampbias[0] = fixBiaslevel(cache[t].ampEnv.biaspoint1, &cache[t].ampdir[0]);
|
||||
cache[t].ampblevel[0] = 12 - cache[t].ampEnv.biaslevel1;
|
||||
cache[t].ampbias[1] = fixBiaslevel(cache[t].ampEnv.biaspoint2, &cache[t].ampdir[1]);
|
||||
cache[t].ampblevel[1] = 12 - cache[t].ampEnv.biaslevel2;
|
||||
cache[t].ampdepth = cache[t].ampEnv.envvkf * cache[t].ampEnv.envvkf;
|
||||
|
||||
// Calculate and cache filter stuff
|
||||
cache[t].filtEnv = timbre->partial[t].tvf;
|
||||
cache[t].filtkeyfollow = fixKeyfollow(cache[t].filtEnv.keyfollow);
|
||||
cache[t].filtEnv.envdepth = (char)((float)cache[t].filtEnv.envdepth * 1.27);
|
||||
cache[t].tvfbias = fixBiaslevel(cache[t].filtEnv.biaspoint, &cache[t].tvfdir);
|
||||
cache[t].tvfblevel = cache[t].filtEnv.biaslevel;
|
||||
cache[t].filtsustain = cache[t].filtEnv.envlevel[3];
|
||||
|
||||
// Calculate and cache LFO stuff
|
||||
cache[t].lfodepth = timbre->partial[t].lfo.depth;
|
||||
cache[t].lfoperiod = synth->tables.lfoPeriod[(int)timbre->partial[t].lfo.rate];
|
||||
cache[t].lforate = timbre->partial[t].lfo.rate;
|
||||
cache[t].modsense = timbre->partial[t].lfo.modsense;
|
||||
}
|
||||
for (int t = 0; t < 4; t++) {
|
||||
// Common parameters, stored redundantly
|
||||
cache[t].dirty = false;
|
||||
cache[t].partialCount = partialCount;
|
||||
cache[t].sustain = (timbre->common.nosustain == 0);
|
||||
cache[t].sustain = (timbre->common.noSustain == 0);
|
||||
}
|
||||
//synth->printDebug("Res 1: %d 2: %d 3: %d 4: %d", cache[0].waveform, cache[1].waveform, cache[2].waveform, cache[3].waveform);
|
||||
|
||||
#if MT32EMU_MONITOR_INSTRUMENTS == 1
|
||||
#if MT32EMU_MONITOR_INSTRUMENTS > 0
|
||||
synth->printDebug("%s (%s): Recached timbre", name, currentInstr);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
synth->printDebug(" %d: play=%s, pcm=%s (%d), wave=%d", i, cache[i].playPartial ? "YES" : "NO", cache[i].PCMPartial ? "YES" : "NO", timbre->partial[i].wg.pcmwave, timbre->partial[i].wg.waveform);
|
||||
synth->printDebug(" %d: play=%s, pcm=%s (%d), wave=%d", i, cache[i].playPartial ? "YES" : "NO", cache[i].PCMPartial ? "YES" : "NO", timbre->partial[i].wg.pcmWave, timbre->partial[i].wg.waveform);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -385,248 +320,302 @@ const char *Part::getName() const {
|
|||
return name;
|
||||
}
|
||||
|
||||
void Part::updateVolume() {
|
||||
volumeMult = synth->tables.volumeMult[patchTemp->outlevel * expression / 127];
|
||||
void Part::setVolume(unsigned int midiVolume) {
|
||||
// CONFIRMED: This calculation matches the table used in the control ROM
|
||||
patchTemp->outputLevel = (Bit8u)(midiVolume * 100 / 127);
|
||||
//synth->printDebug("%s (%s): Set volume to %d", name, currentInstr, midiVolume);
|
||||
}
|
||||
|
||||
int Part::getVolume() const {
|
||||
// FIXME: Use the mappings for this in the control ROM
|
||||
return patchTemp->outlevel * 127 / 100;
|
||||
Bit8u Part::getVolume() const {
|
||||
return patchTemp->outputLevel;
|
||||
}
|
||||
|
||||
void Part::setVolume(int midiVolume) {
|
||||
// FIXME: Use the mappings for this in the control ROM
|
||||
patchTemp->outlevel = (Bit8u)(midiVolume * 100 / 127);
|
||||
updateVolume();
|
||||
synth->printDebug("%s (%s): Set volume to %d", name, currentInstr, midiVolume);
|
||||
Bit8u Part::getExpression() const {
|
||||
return expression;
|
||||
}
|
||||
|
||||
void Part::setExpression(int midiExpression) {
|
||||
expression = midiExpression;
|
||||
updateVolume();
|
||||
void Part::setExpression(unsigned int midiExpression) {
|
||||
// CONFIRMED: This calculation matches the table used in the control ROM
|
||||
expression = (Bit8u)(midiExpression * 100 / 127);
|
||||
}
|
||||
|
||||
void RhythmPart::setPan(unsigned int midiPan)
|
||||
{
|
||||
// FIXME:KG: This is unchangeable for drums (they always use drumPan), is that correct?
|
||||
synth->printDebug("%s: Setting pan (%d) not supported on rhythm", name, midiPan);
|
||||
void RhythmPart::setPan(unsigned int midiPan) {
|
||||
// CONFIRMED: This does change patchTemp, but has no actual effect on playback.
|
||||
#if MT32EMU_MONITOR_MIDI > 0
|
||||
synth->printDebug("%s: Pointlessly setting pan (%d) on rhythm part", name, midiPan);
|
||||
#endif
|
||||
Part::setPan(midiPan);
|
||||
}
|
||||
|
||||
void Part::setPan(unsigned int midiPan) {
|
||||
// FIXME:KG: Tweaked this a bit so that we have a left 100%, center and right 100%
|
||||
// (But this makes the range somewhat skewed)
|
||||
// Check against the real thing
|
||||
// NOTE: Panning is inverted compared to GM.
|
||||
if (midiPan < 64) {
|
||||
volumesetting.leftvol = (Bit16s)(midiPan * 512);
|
||||
volumesetting.rightvol = 32767;
|
||||
} else if (midiPan == 64) {
|
||||
volumesetting.leftvol = 32767;
|
||||
volumesetting.rightvol = 32767;
|
||||
} else {
|
||||
volumesetting.rightvol = (Bit16s)((127 - midiPan) * 520);
|
||||
volumesetting.leftvol = 32767;
|
||||
}
|
||||
patchTemp->panpot = (Bit8u)(midiPan * 14 / 127);
|
||||
|
||||
// CM-32L: Divide by 8.5
|
||||
patchTemp->panpot = (Bit8u)((midiPan << 3) / 68);
|
||||
// FIXME: MT-32: Divide by 9
|
||||
//patchTemp->panpot = (Bit8u)(midiPan / 9);
|
||||
|
||||
//synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot);
|
||||
}
|
||||
|
||||
void RhythmPart::playNote(unsigned int key, int vel) {
|
||||
if (key < 24 || key > 108)/*> 87 on MT-32)*/ {
|
||||
synth->printDebug("%s: Attempted to play invalid key %d", name, key);
|
||||
/**
|
||||
* Applies key shift to a MIDI key and converts it into an internal key value in the range 12-108.
|
||||
*/
|
||||
unsigned int Part::midiKeyToKey(unsigned int midiKey) {
|
||||
int key = midiKey + patchTemp->patch.keyShift;
|
||||
if (key < 36) {
|
||||
// After keyShift is applied, key < 36, so move up by octaves
|
||||
while (key < 36) {
|
||||
key += 12;
|
||||
}
|
||||
} else if (key > 132) {
|
||||
// After keyShift is applied, key > 132, so move down by octaves
|
||||
while (key > 132) {
|
||||
key -= 12;
|
||||
}
|
||||
}
|
||||
key -= 24;
|
||||
return key;
|
||||
}
|
||||
|
||||
void RhythmPart::noteOn(unsigned int midiKey, unsigned int velocity) {
|
||||
if (midiKey < 24 || midiKey > 108) { /*> 87 on MT-32)*/
|
||||
synth->printDebug("%s: Attempted to play invalid key %d (velocity %d)", name, midiKey, velocity);
|
||||
return;
|
||||
}
|
||||
int drumNum = key - 24;
|
||||
unsigned int key = midiKey;
|
||||
unsigned int drumNum = key - 24;
|
||||
int drumTimbreNum = rhythmTemp[drumNum].timbre;
|
||||
if (drumTimbreNum >= 127) { // 94 on MT-32
|
||||
synth->printDebug("%s: Attempted to play unmapped key %d", name, key);
|
||||
synth->printDebug("%s: Attempted to play unmapped key %d (velocity %d)", name, midiKey, velocity);
|
||||
return;
|
||||
}
|
||||
// CONFIRMED: Two special cases described by Mok
|
||||
if (drumTimbreNum == 64 + 6) {
|
||||
noteOff(0);
|
||||
key = 1;
|
||||
} else if (drumTimbreNum == 64 + 7) {
|
||||
// This noteOff(0) is not performed on MT-32, only LAPC-I
|
||||
noteOff(0);
|
||||
key = 0;
|
||||
}
|
||||
int absTimbreNum = drumTimbreNum + 128;
|
||||
TimbreParam *timbre = &synth->mt32ram.timbres[absTimbreNum].timbre;
|
||||
memcpy(currentInstr, timbre->common.name, 10);
|
||||
#if MT32EMU_MONITOR_INSTRUMENTS == 1
|
||||
synth->printDebug("%s (%s): starting poly (drum %d, timbre %d) - Vel %d Key %d", name, currentInstr, drumNum, absTimbreNum, vel, key);
|
||||
#endif
|
||||
if (drumCache[drumNum][0].dirty) {
|
||||
cacheTimbre(drumCache[drumNum], timbre);
|
||||
}
|
||||
playPoly(drumCache[drumNum], key, MIDDLEC, vel);
|
||||
#if MT32EMU_MONITOR_INSTRUMENTS > 0
|
||||
synth->printDebug("%s (%s): Start poly (drum %d, timbre %d): midiKey %u, key %u, velo %u, mod %u, exp %u, bend %u", name, currentInstr, drumNum, absTimbreNum, midiKey, key, velocity, modulation, expression, pitchBend);
|
||||
#if MT32EMU_MONITOR_INSTRUMENTS > 1
|
||||
// According to info from Mok, keyShift does not appear to affect anything on rhythm part on LAPC-I, but may do on MT-32 - needs investigation
|
||||
synth->printDebug(" Patch: (timbreGroup %u), (timbreNum %u), (keyShift %u), fineTune %u, benderRange %u, assignMode %u, (reverbSwitch %u)", patchTemp->patch.timbreGroup, patchTemp->patch.timbreNum, patchTemp->patch.keyShift, patchTemp->patch.fineTune, patchTemp->patch.benderRange, patchTemp->patch.assignMode, patchTemp->patch.reverbSwitch);
|
||||
synth->printDebug(" PatchTemp: outputLevel %u, (panpot %u)", patchTemp->outputLevel, patchTemp->panpot);
|
||||
synth->printDebug(" RhythmTemp: timbre %u, outputLevel %u, panpot %u, reverbSwitch %u", rhythmTemp[drumNum].timbre, rhythmTemp[drumNum].outputLevel, rhythmTemp[drumNum].panpot, rhythmTemp[drumNum].reverbSwitch);
|
||||
#endif
|
||||
#endif
|
||||
playPoly(drumCache[drumNum], &rhythmTemp[drumNum], midiKey, key, velocity);
|
||||
}
|
||||
|
||||
void Part::playNote(unsigned int key, int vel) {
|
||||
int freqNum = key;
|
||||
if (freqNum < 12) {
|
||||
synth->printDebug("%s (%s): Attempted to play invalid key %d < 12; moving up by octave", name, currentInstr, key);
|
||||
freqNum += 12;
|
||||
} else if (freqNum > 108) {
|
||||
synth->printDebug("%s (%s): Attempted to play invalid key %d > 108; moving down by octave", name, currentInstr, key);
|
||||
while (freqNum > 108) {
|
||||
freqNum -= 12;
|
||||
}
|
||||
}
|
||||
// POLY1 mode, Single Assign
|
||||
// Haven't found any software that uses any of the other poly modes
|
||||
// FIXME:KG: Should this also apply to rhythm?
|
||||
for (unsigned int i = 0; i < MT32EMU_MAX_POLY; i++) {
|
||||
if (polyTable[i].isActive() && (polyTable[i].key == key)) {
|
||||
//AbortPoly(&polyTable[i]);
|
||||
stopNote(key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#if MT32EMU_MONITOR_INSTRUMENTS == 1
|
||||
synth->printDebug("%s (%s): starting poly - Vel %d Key %d", name, currentInstr, vel, key);
|
||||
#endif
|
||||
void Part::noteOn(unsigned int midiKey, unsigned int velocity) {
|
||||
unsigned int key = midiKeyToKey(midiKey);
|
||||
if (patchCache[0].dirty) {
|
||||
cacheTimbre(patchCache, timbreTemp);
|
||||
}
|
||||
playPoly(patchCache, key, freqNum, vel);
|
||||
#if MT32EMU_MONITOR_INSTRUMENTS > 0
|
||||
synth->printDebug("%s (%s): Start poly: midiKey %u, key %u, velo %u, mod %u, exp %u, bend %u", name, currentInstr, midiKey, key, velocity, modulation, expression, pitchBend);
|
||||
#if MT32EMU_MONITOR_INSTRUMENTS > 1
|
||||
synth->printDebug(" Patch: timbreGroup %u, timbreNum %u, keyShift %u, fineTune %u, benderRange %u, assignMode %u, reverbSwitch %u", patchTemp->patch.timbreGroup, patchTemp->patch.timbreNum, patchTemp->patch.keyShift, patchTemp->patch.fineTune, patchTemp->patch.benderRange, patchTemp->patch.assignMode, patchTemp->patch.reverbSwitch);
|
||||
synth->printDebug(" PatchTemp: outputLevel %u, panpot %u", patchTemp->outputLevel, patchTemp->panpot);
|
||||
#endif
|
||||
#endif
|
||||
playPoly(patchCache, NULL, midiKey, key, velocity);
|
||||
}
|
||||
|
||||
void Part::playPoly(const PatchCache cache[4], unsigned int key, int freqNum, int vel) {
|
||||
unsigned int needPartials = cache[0].partialCount;
|
||||
unsigned int freePartials = synth->partialManager->getFreePartialCount();
|
||||
|
||||
if (freePartials < needPartials) {
|
||||
if (!synth->partialManager->freePartials(needPartials - freePartials, partNum)) {
|
||||
synth->printDebug("%s (%s): Insufficient free partials to play key %d (vel=%d); needed=%d, free=%d", name, currentInstr, key, vel, needPartials, synth->partialManager->getFreePartialCount());
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Find free poly
|
||||
int m;
|
||||
for (m = 0; m < MT32EMU_MAX_POLY; m++) {
|
||||
if (!polyTable[m].isActive()) {
|
||||
void Part::abortPoly(Poly *poly) {
|
||||
if (poly->startAbort()) {
|
||||
while (poly->isActive()) {
|
||||
if (!synth->prerender()) {
|
||||
synth->printDebug("%s (%s): Ran out of prerender space to abort poly gracefully", name, currentInstr);
|
||||
poly->terminate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m == MT32EMU_MAX_POLY) {
|
||||
synth->printDebug("%s (%s): No free poly to play key %d (vel %d)", name, currentInstr, key, vel);
|
||||
return;
|
||||
}
|
||||
|
||||
dpoly *tpoly = &polyTable[m];
|
||||
|
||||
tpoly->isPlaying = true;
|
||||
tpoly->key = key;
|
||||
tpoly->isDecay = false;
|
||||
tpoly->freqnum = freqNum;
|
||||
tpoly->vel = vel;
|
||||
tpoly->pedalhold = false;
|
||||
|
||||
bool allnull = true;
|
||||
for (int x = 0; x < 4; x++) {
|
||||
if (cache[x].playPartial) {
|
||||
tpoly->partials[x] = synth->partialManager->allocPartial(partNum);
|
||||
allnull = false;
|
||||
} else {
|
||||
tpoly->partials[x] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (allnull)
|
||||
synth->printDebug("%s (%s): No partials to play for this instrument", name, this->currentInstr);
|
||||
|
||||
tpoly->sustain = cache[0].sustain;
|
||||
tpoly->volumeptr = &volumeMult;
|
||||
|
||||
for (int x = 0; x < 4; x++) {
|
||||
if (tpoly->partials[x] != NULL) {
|
||||
tpoly->partials[x]->startPartial(tpoly, &cache[x], tpoly->partials[cache[x].structurePair]);
|
||||
tpoly->partials[x]->setBend(bend);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void startDecayPoly(dpoly *tpoly) {
|
||||
if (tpoly->isDecay) {
|
||||
bool Part::abortFirstPoly(unsigned int key) {
|
||||
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
|
||||
Poly *poly = *polyIt;
|
||||
if (poly->getKey() == key) {
|
||||
abortPoly(poly);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Part::abortFirstPoly(PolyState polyState) {
|
||||
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
|
||||
Poly *poly = *polyIt;
|
||||
if (poly->getState() == polyState) {
|
||||
abortPoly(poly);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Part::abortFirstPolyPreferHeld() {
|
||||
if (abortFirstPoly(POLY_Held)) {
|
||||
return true;
|
||||
}
|
||||
return abortFirstPoly();
|
||||
}
|
||||
|
||||
bool Part::abortFirstPoly() {
|
||||
if (activePolys.empty()) {
|
||||
return false;
|
||||
}
|
||||
abortPoly(activePolys.front());
|
||||
return true;
|
||||
}
|
||||
|
||||
void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhythmTemp, unsigned int midiKey, unsigned int key, unsigned int velocity) {
|
||||
// CONFIRMED: Even in single-assign mode, we don't abort playing polys if the timbre to play is completely muted.
|
||||
unsigned int needPartials = cache[0].partialCount;
|
||||
if (needPartials == 0) {
|
||||
synth->printDebug("%s (%s): Completely muted instrument", name, currentInstr);
|
||||
return;
|
||||
}
|
||||
tpoly->isDecay = true;
|
||||
|
||||
for (int t = 0; t < 4; t++) {
|
||||
Partial *partial = tpoly->partials[t];
|
||||
if (partial == NULL)
|
||||
continue;
|
||||
partial->startDecayAll();
|
||||
if ((patchTemp->patch.assignMode & 2) == 0) {
|
||||
// Single-assign mode
|
||||
abortFirstPoly(key);
|
||||
}
|
||||
tpoly->isPlaying = false;
|
||||
|
||||
if (!synth->partialManager->freePartials(needPartials, partNum)) {
|
||||
#if MT32EMU_MONITOR_PARTIALS > 0
|
||||
synth->printDebug("%s (%s): Insufficient free partials to play key %d (velocity %d); needed=%d, free=%d, assignMode=%d", name, currentInstr, midiKey, velocity, needPartials, synth->partialManager->getFreePartialCount(), patchTemp->patch.assignMode);
|
||||
synth->printPartialUsage();
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (freePolys.empty()) {
|
||||
synth->printDebug("%s (%s): No free poly to play key %d (velocity %d)", name, currentInstr, midiKey, velocity);
|
||||
return;
|
||||
}
|
||||
Poly *poly = freePolys.front();
|
||||
freePolys.pop_front();
|
||||
if (patchTemp->patch.assignMode & 1) {
|
||||
// Priority to data first received
|
||||
activePolys.push_front(poly);
|
||||
} else {
|
||||
activePolys.push_back(poly);
|
||||
}
|
||||
|
||||
Partial *partials[4];
|
||||
for (int x = 0; x < 4; x++) {
|
||||
if (cache[x].playPartial) {
|
||||
partials[x] = synth->partialManager->allocPartial(partNum);
|
||||
activePartialCount++;
|
||||
} else {
|
||||
partials[x] = NULL;
|
||||
}
|
||||
}
|
||||
poly->reset(key, velocity, cache[0].sustain, partials);
|
||||
|
||||
for (int x = 0; x < 4; x++) {
|
||||
if (partials[x] != NULL) {
|
||||
#if MT32EMU_MONITOR_PARTIALS > 2
|
||||
synth->printDebug("%s (%s): Allocated partial %d", name, currentInstr, partials[x]->debugGetPartialNum());
|
||||
#endif
|
||||
partials[x]->startPartial(this, poly, &cache[x], rhythmTemp, partials[cache[x].structurePair]);
|
||||
}
|
||||
}
|
||||
#if MT32EMU_MONITOR_PARTIALS > 1
|
||||
synth->printPartialUsage();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Part::allNotesOff() {
|
||||
// Note: Unchecked on real MT-32, but the MIDI specification states that all notes off (0x7B)
|
||||
// The MIDI specification states - and Mok confirms - that all notes off (0x7B)
|
||||
// should treat the hold pedal as usual.
|
||||
// All *sound* off (0x78) should stop notes immediately regardless of the hold pedal.
|
||||
// The latter controller is not implemented on the MT-32 (according to the docs).
|
||||
for (int q = 0; q < MT32EMU_MAX_POLY; q++) {
|
||||
dpoly *tpoly = &polyTable[q];
|
||||
if (tpoly->isPlaying) {
|
||||
if (holdpedal)
|
||||
tpoly->pedalhold = true;
|
||||
else if (tpoly->sustain)
|
||||
startDecayPoly(tpoly);
|
||||
}
|
||||
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
|
||||
Poly *poly = *polyIt;
|
||||
// FIXME: This has special handling of key 0 in NoteOff that Mok has not yet confirmed
|
||||
// applies to AllNotesOff.
|
||||
poly->noteOff(holdpedal);
|
||||
}
|
||||
}
|
||||
|
||||
void Part::allSoundOff() {
|
||||
for (int q = 0; q < MT32EMU_MAX_POLY; q++) {
|
||||
dpoly *tpoly = &polyTable[q];
|
||||
if (tpoly->isPlaying) {
|
||||
startDecayPoly(tpoly);
|
||||
}
|
||||
// MIDI "All sound off" (0x78) should release notes immediately regardless of the hold pedal.
|
||||
// This controller is not actually implemented by the synths, though (according to the docs and Mok) -
|
||||
// we're only using this method internally.
|
||||
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
|
||||
Poly *poly = *polyIt;
|
||||
poly->startDecay();
|
||||
}
|
||||
}
|
||||
|
||||
void Part::stopPedalHold() {
|
||||
for (int q = 0; q < MT32EMU_MAX_POLY; q++) {
|
||||
dpoly *tpoly;
|
||||
tpoly = &polyTable[q];
|
||||
if (tpoly->isActive() && tpoly->pedalhold)
|
||||
stopNote(tpoly->key);
|
||||
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
|
||||
Poly *poly = *polyIt;
|
||||
poly->stopPedalHold();
|
||||
}
|
||||
}
|
||||
|
||||
void RhythmPart::noteOff(unsigned int midiKey) {
|
||||
stopNote(midiKey);
|
||||
}
|
||||
|
||||
void Part::noteOff(unsigned int midiKey) {
|
||||
stopNote(midiKeyToKey(midiKey));
|
||||
}
|
||||
|
||||
void Part::stopNote(unsigned int key) {
|
||||
// Non-sustaining instruments ignore stop commands.
|
||||
// They die away eventually anyway
|
||||
|
||||
#if MT32EMU_MONITOR_INSTRUMENTS == 1
|
||||
#if MT32EMU_MONITOR_INSTRUMENTS > 0
|
||||
synth->printDebug("%s (%s): stopping key %d", name, currentInstr, key);
|
||||
#endif
|
||||
|
||||
if (key != 255) {
|
||||
for (int q = 0; q < MT32EMU_MAX_POLY; q++) {
|
||||
dpoly *tpoly = &polyTable[q];
|
||||
if (tpoly->isPlaying && tpoly->key == key) {
|
||||
if (holdpedal)
|
||||
tpoly->pedalhold = true;
|
||||
else if (tpoly->sustain)
|
||||
startDecayPoly(tpoly);
|
||||
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
|
||||
Poly *poly = *polyIt;
|
||||
// Generally, non-sustaining instruments ignore note off. They die away eventually anyway.
|
||||
// Key 0 (only used by special cases on rhythm part) reacts to note off even if non-sustaining or pedal held.
|
||||
if (poly->getKey() == key && (poly->canSustain() || key == 0)) {
|
||||
if (poly->noteOff(holdpedal && key != 0)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Find oldest poly... yes, the MT-32 can be reconfigured to kill different poly first
|
||||
// This is simplest
|
||||
int oldest = -1;
|
||||
Bit32u oldage = 0;
|
||||
const MemParams::PatchTemp *Part::getPatchTemp() const {
|
||||
return patchTemp;
|
||||
}
|
||||
|
||||
for (int q = 0; q < MT32EMU_MAX_POLY; q++) {
|
||||
dpoly *tpoly = &polyTable[q];
|
||||
unsigned int Part::getActivePartialCount() const {
|
||||
return activePartialCount;
|
||||
}
|
||||
|
||||
if (tpoly->isPlaying && !tpoly->isDecay) {
|
||||
if (tpoly->getAge() >= oldage) {
|
||||
oldage = tpoly->getAge();
|
||||
oldest = q;
|
||||
}
|
||||
unsigned int Part::getActiveNonReleasingPartialCount() const {
|
||||
unsigned int activeNonReleasingPartialCount = 0;
|
||||
for (Common::List<Poly *>::const_iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
|
||||
Poly *poly = *polyIt;
|
||||
if (poly->getState() != POLY_Releasing) {
|
||||
activeNonReleasingPartialCount += poly->getActivePartialCount();
|
||||
}
|
||||
}
|
||||
return activeNonReleasingPartialCount;
|
||||
}
|
||||
|
||||
if (oldest != -1) {
|
||||
startDecayPoly(&polyTable[oldest]);
|
||||
void Part::partialDeactivated(Poly *poly) {
|
||||
activePartialCount--;
|
||||
if (!poly->isActive()) {
|
||||
activePolys.remove(poly);
|
||||
freePolys.push_front(poly);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,35 +1,33 @@
|
|||
/* Copyright (c) 2003-2005 Various contributors
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* 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 Lesser General Public License for more details.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_PART_H
|
||||
#define MT32EMU_PART_H
|
||||
|
||||
#include <common/list.h>
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class PartialManager;
|
||||
class Synth;
|
||||
|
||||
class Part {
|
||||
private:
|
||||
// Pointers to the areas of the MT-32's memory dedicated to this part (for parts 1-8)
|
||||
MemParams::PatchTemp *patchTemp;
|
||||
// Direct pointer to sysex-addressable memory dedicated to this part (valid for parts 1-8, NULL for rhythm)
|
||||
TimbreParam *timbreTemp;
|
||||
|
||||
// 0=Part 1, .. 7=Part 8, 8=Rhythm
|
||||
|
@ -37,55 +35,80 @@ private:
|
|||
|
||||
bool holdpedal;
|
||||
|
||||
StereoVolume volumesetting;
|
||||
|
||||
unsigned int activePartialCount;
|
||||
PatchCache patchCache[4];
|
||||
|
||||
float bend; // -1.0 .. +1.0
|
||||
|
||||
dpoly polyTable[MT32EMU_MAX_POLY];
|
||||
|
||||
void abortPoly(dpoly *poly);
|
||||
|
||||
static int fixKeyfollow(int srckey);
|
||||
static int fixBiaslevel(int srcpnt, int *dir);
|
||||
Common::List<Poly*> freePolys;
|
||||
Common::List<Poly*> activePolys;
|
||||
|
||||
void setPatch(const PatchParam *patch);
|
||||
unsigned int midiKeyToKey(unsigned int midiKey);
|
||||
|
||||
void abortPoly(Poly *poly);
|
||||
bool abortFirstPoly(unsigned int key);
|
||||
|
||||
protected:
|
||||
Synth *synth;
|
||||
// Direct pointer into sysex-addressable memory
|
||||
MemParams::PatchTemp *patchTemp;
|
||||
char name[8]; // "Part 1".."Part 8", "Rhythm"
|
||||
char currentInstr[11];
|
||||
int expression;
|
||||
Bit32u volumeMult;
|
||||
Bit8u modulation;
|
||||
Bit8u expression;
|
||||
Bit32s pitchBend;
|
||||
bool nrpn;
|
||||
Bit16u rpn;
|
||||
Bit16u pitchBenderRange; // (patchTemp->patch.benderRange * 683) at the time of the last MIDI program change or MIDI data entry.
|
||||
|
||||
void updateVolume();
|
||||
void backupCacheToPartials(PatchCache cache[4]);
|
||||
void cacheTimbre(PatchCache cache[4], const TimbreParam *timbre);
|
||||
void playPoly(const PatchCache cache[4], unsigned int key, int freqNum, int vel);
|
||||
void playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhythmTemp, unsigned int midiKey, unsigned int key, unsigned int velocity);
|
||||
void stopNote(unsigned int key);
|
||||
const char *getName() const;
|
||||
|
||||
public:
|
||||
Part(Synth *synth, unsigned int usePartNum);
|
||||
virtual ~Part() {}
|
||||
virtual void playNote(unsigned int key, int vel);
|
||||
void stopNote(unsigned int key);
|
||||
virtual ~Part();
|
||||
void reset();
|
||||
void setDataEntryMSB(unsigned char midiDataEntryMSB);
|
||||
void setNRPN();
|
||||
void setRPNLSB(unsigned char midiRPNLSB);
|
||||
void setRPNMSB(unsigned char midiRPNMSB);
|
||||
void resetAllControllers();
|
||||
virtual void noteOn(unsigned int midiKey, unsigned int velocity);
|
||||
virtual void noteOff(unsigned int midiKey);
|
||||
void allNotesOff();
|
||||
void allSoundOff();
|
||||
int getVolume() const;
|
||||
void setVolume(int midiVolume);
|
||||
void setExpression(int midiExpression);
|
||||
Bit8u getVolume() const; // Internal volume, 0-100, exposed for use by ExternalInterface
|
||||
void setVolume(unsigned int midiVolume);
|
||||
Bit8u getModulation() const;
|
||||
void setModulation(unsigned int midiModulation);
|
||||
Bit8u getExpression() const;
|
||||
void setExpression(unsigned int midiExpression);
|
||||
virtual void setPan(unsigned int midiPan);
|
||||
virtual void setBend(unsigned int midiBend);
|
||||
virtual void setModulation(unsigned int midiModulation);
|
||||
Bit32s getPitchBend() const;
|
||||
void setBend(unsigned int midiBend);
|
||||
virtual void setProgram(unsigned int midiProgram);
|
||||
void setHoldPedal(bool pedalval);
|
||||
void stopPedalHold();
|
||||
void updatePitchBenderRange();
|
||||
virtual void refresh();
|
||||
virtual void refreshTimbre(unsigned int absTimbreNum);
|
||||
virtual void setTimbre(TimbreParam *timbre);
|
||||
virtual unsigned int getAbsTimbreNum() const;
|
||||
const char *getCurrentInstr() const;
|
||||
unsigned int getActivePartialCount() const;
|
||||
unsigned int getActiveNonReleasingPartialCount() const;
|
||||
|
||||
const MemParams::PatchTemp *getPatchTemp() const;
|
||||
|
||||
// This should only be called by Poly
|
||||
void partialDeactivated(Poly *poly);
|
||||
|
||||
// These are rather specialised, and should probably only be used by PartialManager
|
||||
bool abortFirstPoly(PolyState polyState);
|
||||
// Abort the first poly in PolyState_HELD, or if none exists, the first active poly in any state.
|
||||
bool abortFirstPolyPreferHeld();
|
||||
bool abortFirstPoly();
|
||||
};
|
||||
|
||||
class RhythmPart: public Part {
|
||||
|
@ -94,17 +117,15 @@ class RhythmPart: public Part {
|
|||
|
||||
// This caches the timbres/settings in use by the rhythm part
|
||||
PatchCache drumCache[85][4];
|
||||
StereoVolume drumPan[85];
|
||||
public:
|
||||
RhythmPart(Synth *synth, unsigned int usePartNum);
|
||||
void refresh();
|
||||
void refreshTimbre(unsigned int timbreNum);
|
||||
void setTimbre(TimbreParam *timbre);
|
||||
void playNote(unsigned int key, int vel);
|
||||
void noteOn(unsigned int key, unsigned int velocity);
|
||||
void noteOff(unsigned int midiKey);
|
||||
unsigned int getAbsTimbreNum() const;
|
||||
void setPan(unsigned int midiPan);
|
||||
void setBend(unsigned int midiBend);
|
||||
void setModulation(unsigned int midiModulation);
|
||||
void setProgram(unsigned int patchNum);
|
||||
};
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,22 +1,18 @@
|
|||
/* Copyright (c) 2003-2005 Various contributors
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* 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 Lesser General Public License for more details.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_PARTIAL_H
|
||||
|
@ -25,122 +21,97 @@
|
|||
namespace MT32Emu {
|
||||
|
||||
class Synth;
|
||||
struct NoteLookup;
|
||||
class Part;
|
||||
class TVA;
|
||||
struct ControlROMPCMStruct;
|
||||
|
||||
enum EnvelopeType {
|
||||
EnvelopeType_amp = 0,
|
||||
EnvelopeType_filt = 1,
|
||||
EnvelopeType_pitch = 2
|
||||
struct StereoVolume {
|
||||
float leftVol;
|
||||
float rightVol;
|
||||
};
|
||||
|
||||
struct EnvelopeStatus {
|
||||
Bit32s envpos;
|
||||
Bit32s envstat;
|
||||
Bit32s envbase;
|
||||
Bit32s envdist;
|
||||
Bit32s envsize;
|
||||
|
||||
bool sustaining;
|
||||
bool decaying;
|
||||
Bit32s prevlevel;
|
||||
|
||||
Bit32s counter;
|
||||
Bit32s count;
|
||||
};
|
||||
|
||||
// Class definition of MT-32 partials. 32 in all.
|
||||
// A partial represents one of up to four waveform generators currently playing within a poly.
|
||||
class Partial {
|
||||
private:
|
||||
Synth *synth;
|
||||
const int debugPartialNum; // Only used for debugging
|
||||
// Number of the sample currently being rendered by generateSamples(), or 0 if no run is in progress
|
||||
// This is only kept available for debugging purposes.
|
||||
unsigned long sampleNum;
|
||||
|
||||
int ownerPart; // -1 if unassigned
|
||||
int mixType;
|
||||
int structurePosition; // 0 or 1 of a structure pair
|
||||
bool useNoisePair;
|
||||
StereoVolume stereoVolume;
|
||||
|
||||
Bit16s myBuffer[MAX_SAMPLE_OUTPUT];
|
||||
// Distance in (possibly fractional) samples from the start of the current pulse
|
||||
float wavePos;
|
||||
|
||||
// Keyfollowed note value
|
||||
#if MT32EMU_ACCURATENOTES == 1
|
||||
NoteLookup noteLookupStorage;
|
||||
float noteVal;
|
||||
#else
|
||||
int noteVal;
|
||||
int fineShift;
|
||||
#endif
|
||||
const NoteLookup *noteLookup; // LUTs for this noteVal
|
||||
const KeyLookup *keyLookup; // LUTs for the clamped (12..108) key
|
||||
float lastFreq;
|
||||
|
||||
// Keyfollowed filter values
|
||||
int realVal;
|
||||
int filtVal;
|
||||
float myBuffer[MAX_SAMPLES_PER_RUN];
|
||||
|
||||
// Only used for PCM partials
|
||||
int pcmNum;
|
||||
// FIXME: Give this a better name (e.g. pcmWaveInfo)
|
||||
PCMWaveEntry *pcmWave;
|
||||
|
||||
int pulsewidth;
|
||||
// Final pulse width value, with velfollow applied, matching what is sent to the LA32.
|
||||
// Range: 0-255
|
||||
int pulseWidthVal;
|
||||
|
||||
Bit32u lfoPos;
|
||||
soundaddr partialOff;
|
||||
float pcmPosition;
|
||||
|
||||
Bit32u ampEnvVal;
|
||||
Bit32u pitchEnvVal;
|
||||
Poly *poly;
|
||||
|
||||
float history[32];
|
||||
LA32Ramp ampRamp;
|
||||
LA32Ramp cutoffModifierRamp;
|
||||
|
||||
bool pitchSustain;
|
||||
float *mixBuffersRingMix(float *buf1, float *buf2, unsigned long len);
|
||||
float *mixBuffersRing(float *buf1, float *buf2, unsigned long len);
|
||||
|
||||
int loopPos;
|
||||
|
||||
dpoly *poly;
|
||||
|
||||
int bendShift;
|
||||
|
||||
Bit16s *mixBuffers(Bit16s *buf1, Bit16s *buf2, int len);
|
||||
Bit16s *mixBuffersRingMix(Bit16s *buf1, Bit16s *buf2, int len);
|
||||
Bit16s *mixBuffersRing(Bit16s *buf1, Bit16s *buf2, int len);
|
||||
void mixBuffersStereo(Bit16s *buf1, Bit16s *buf2, Bit16s *outBuf, int len);
|
||||
|
||||
Bit32s getFiltEnvelope();
|
||||
Bit32u getAmpEnvelope();
|
||||
Bit32s getPitchEnvelope();
|
||||
|
||||
void initKeyFollow(int freqNum);
|
||||
float getPCMSample(unsigned int position);
|
||||
|
||||
public:
|
||||
const PatchCache *patchCache;
|
||||
EnvelopeStatus envs[3];
|
||||
bool play;
|
||||
TVA *tva;
|
||||
TVP *tvp;
|
||||
TVF *tvf;
|
||||
|
||||
PatchCache cachebackup;
|
||||
|
||||
Partial *pair;
|
||||
bool alreadyOutputed;
|
||||
Bit32u age;
|
||||
|
||||
Partial(Synth *synth);
|
||||
Partial(Synth *synth, int debugPartialNum);
|
||||
~Partial();
|
||||
|
||||
int debugGetPartialNum() const;
|
||||
unsigned long debugGetSampleNum() const;
|
||||
|
||||
int getOwnerPart() const;
|
||||
int getKey() const;
|
||||
const dpoly *getDpoly() const;
|
||||
bool isActive();
|
||||
const Poly *getPoly() const;
|
||||
bool isActive() const;
|
||||
void activate(int part);
|
||||
void deactivate(void);
|
||||
void startPartial(dpoly *usePoly, const PatchCache *useCache, Partial *pairPartial);
|
||||
void startDecay(EnvelopeType envnum, Bit32s startval);
|
||||
void startPartial(const Part *part, Poly *usePoly, const PatchCache *useCache, const MemParams::RhythmTemp *rhythmTemp, Partial *pairPartial);
|
||||
void startAbort();
|
||||
void startDecayAll();
|
||||
void setBend(float factor);
|
||||
bool shouldReverb();
|
||||
bool hasRingModulatingSlave() const;
|
||||
bool isRingModulatingSlave() const;
|
||||
bool isPCM() const;
|
||||
const ControlROMPCMStruct *getControlROMPCMStruct() const;
|
||||
Synth *getSynth() const;
|
||||
|
||||
// Returns true only if data written to buffer
|
||||
// This function (unlike the one below it) returns processed stereo samples
|
||||
// made from combining this single partial with its pair, if it has one.
|
||||
bool produceOutput(Bit16s * partialBuf, long length);
|
||||
bool produceOutput(float *leftBuf, float *rightBuf, unsigned long length);
|
||||
|
||||
// This function produces mono sample output using the partial's private internal buffer
|
||||
Bit16s *generateSamples(long length);
|
||||
// This function writes mono sample output to the provided buffer, and returns the number of samples written
|
||||
unsigned long generateSamples(float *partialBuf, unsigned long length);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -1,65 +1,53 @@
|
|||
/* Copyright (c) 2003-2005 Various contributors
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* 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 Lesser General Public License for more details.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
//#include <cstring>
|
||||
|
||||
#include "mt32emu.h"
|
||||
#include "PartialManager.h"
|
||||
|
||||
using namespace MT32Emu;
|
||||
|
||||
PartialManager::PartialManager(Synth *useSynth) {
|
||||
this->synth = useSynth;
|
||||
for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++)
|
||||
partialTable[i] = new Partial(synth);
|
||||
PartialManager::PartialManager(Synth *useSynth, Part **useParts) {
|
||||
synth = useSynth;
|
||||
parts = useParts;
|
||||
for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
|
||||
partialTable[i] = new Partial(synth, i);
|
||||
}
|
||||
}
|
||||
|
||||
PartialManager::~PartialManager(void) {
|
||||
for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++)
|
||||
delete partialTable[i];
|
||||
}
|
||||
|
||||
void PartialManager::getPerPartPartialUsage(int usage[9]) {
|
||||
memset(usage, 0, 9 * sizeof (int));
|
||||
for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
|
||||
if (partialTable[i]->isActive())
|
||||
usage[partialTable[i]->getOwnerPart()]++;
|
||||
delete partialTable[i];
|
||||
}
|
||||
}
|
||||
|
||||
void PartialManager::clearAlreadyOutputed() {
|
||||
for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++)
|
||||
for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
|
||||
partialTable[i]->alreadyOutputed = false;
|
||||
}
|
||||
|
||||
void PartialManager::ageAll() {
|
||||
for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++)
|
||||
partialTable[i]->age++;
|
||||
}
|
||||
}
|
||||
|
||||
bool PartialManager::shouldReverb(int i) {
|
||||
return partialTable[i]->shouldReverb();
|
||||
}
|
||||
|
||||
bool PartialManager::produceOutput(int i, Bit16s *buffer, Bit32u bufferLength) {
|
||||
return partialTable[i]->produceOutput(buffer, bufferLength);
|
||||
bool PartialManager::produceOutput(int i, float *leftBuf, float *rightBuf, Bit32u bufferLength) {
|
||||
return partialTable[i]->produceOutput(leftBuf, rightBuf, bufferLength);
|
||||
}
|
||||
|
||||
void PartialManager::deactivateAll() {
|
||||
|
@ -70,11 +58,9 @@ void PartialManager::deactivateAll() {
|
|||
|
||||
unsigned int PartialManager::setReserve(Bit8u *rset) {
|
||||
unsigned int pr = 0;
|
||||
for (int x = 0; x < 9; x++) {
|
||||
for (int y = 0; y < rset[x]; y++) {
|
||||
partialReserveTable[pr] = x;
|
||||
pr++;
|
||||
}
|
||||
for (int x = 0; x <= 8; x++) {
|
||||
numReservedPartialsForPart[x] = rset[x];
|
||||
pr += rset[x];
|
||||
}
|
||||
return pr;
|
||||
}
|
||||
|
@ -82,191 +68,183 @@ unsigned int PartialManager::setReserve(Bit8u *rset) {
|
|||
Partial *PartialManager::allocPartial(int partNum) {
|
||||
Partial *outPartial = NULL;
|
||||
|
||||
// Use the first inactive partial reserved for the specified part (if there are any)
|
||||
// Otherwise, use the last inactive partial, if any
|
||||
for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
|
||||
if (!partialTable[i]->isActive()) {
|
||||
outPartial = partialTable[i];
|
||||
if (partialReserveTable[i] == partNum)
|
||||
// Get the first inactive partial
|
||||
for (int partialNum = 0; partialNum < MT32EMU_MAX_PARTIALS; partialNum++) {
|
||||
if (!partialTable[partialNum]->isActive()) {
|
||||
outPartial = partialTable[partialNum];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (outPartial != NULL) {
|
||||
outPartial->activate(partNum);
|
||||
outPartial->age = 0;
|
||||
}
|
||||
return outPartial;
|
||||
}
|
||||
|
||||
unsigned int PartialManager::getFreePartialCount(void) {
|
||||
int count = 0;
|
||||
memset(partialPart, 0, sizeof(partialPart));
|
||||
for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
|
||||
if (!partialTable[i]->isActive())
|
||||
if (!partialTable[i]->isActive()) {
|
||||
count++;
|
||||
else
|
||||
partialPart[partialTable[i]->getOwnerPart()]++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
bool PartialManager::freePartials(unsigned int needed, int partNum) {
|
||||
int i;
|
||||
int myPartPrior = (int)mt32ram.system.reserveSettings[partNum];
|
||||
if (myPartPrior<partialPart[partNum]) {
|
||||
//This can have more parts, must kill off those with less priority
|
||||
int most, mostPart;
|
||||
while (needed > 0) {
|
||||
int selectPart = -1;
|
||||
//Find the worst offender with more partials than allocated and kill them
|
||||
most = -1;
|
||||
mostPart = -1;
|
||||
int diff;
|
||||
|
||||
for (i=0;i<9;i++) {
|
||||
diff = partialPart[i] - (int)mt32ram.system.reserveSettings[i];
|
||||
|
||||
if (diff>0) {
|
||||
if (diff>most) {
|
||||
most = diff;
|
||||
mostPart = i;
|
||||
// This function is solely used to gather data for debug output at the moment.
|
||||
void PartialManager::getPerPartPartialUsage(unsigned int perPartPartialUsage[9]) {
|
||||
memset(perPartPartialUsage, 0, 9 * sizeof(unsigned int));
|
||||
for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
|
||||
if (partialTable[i]->isActive()) {
|
||||
perPartPartialUsage[partialTable[i]->getOwnerPart()]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
selectPart = mostPart;
|
||||
if (selectPart == -1) {
|
||||
// All parts are within the allocated limits, you suck
|
||||
// Look for first partial not of this part that's decaying perhaps?
|
||||
return false;
|
||||
}
|
||||
bool found;
|
||||
int oldest;
|
||||
int oldnum;
|
||||
while (partialPart[selectPart] > (int)mt32ram.system.reserveSettings[selectPart]) {
|
||||
oldest = -1;
|
||||
oldnum = -1;
|
||||
found = false;
|
||||
for (i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
|
||||
if (partialTable[i]->isActive) {
|
||||
if (partialTable[i]->ownerPart == selectPart) {
|
||||
found = true;
|
||||
if (partialTable[i]->age > oldest) {
|
||||
oldest = partialTable[i]->age;
|
||||
oldnum = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) break;
|
||||
partialTable[oldnum]->deactivate();
|
||||
--partialPart[selectPart];
|
||||
--needed;
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
|
||||
} else {
|
||||
//This part has reached its max, must kill off its own
|
||||
bool found;
|
||||
int oldest;
|
||||
int oldnum;
|
||||
while (needed > 0) {
|
||||
oldest = -1;
|
||||
oldnum = -1;
|
||||
found = false;
|
||||
for (i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
|
||||
if (partialTable[i]->isActive) {
|
||||
if (partialTable[i]->ownerPart == partNum) {
|
||||
found = true;
|
||||
if (partialTable[i]->age > oldest) {
|
||||
oldest = partialTable[i]->age;
|
||||
oldnum = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) break;
|
||||
partialTable[oldnum]->deactivate();
|
||||
--needed;
|
||||
}
|
||||
// Couldn't free enough partials, sorry
|
||||
if (needed>0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
// Finds the lowest-priority part that is exceeding its reserved partial allocation and has a poly
|
||||
// in POLY_Releasing, then kills its first releasing poly.
|
||||
// Parts with higher priority than minPart are not checked.
|
||||
// Assumes that getFreePartials() has been called to make numReservedPartialsForPart up-to-date.
|
||||
bool PartialManager::abortFirstReleasingPolyWhereReserveExceeded(int minPart) {
|
||||
if (minPart == 8) {
|
||||
// Rhythm is highest priority
|
||||
minPart = -1;
|
||||
}
|
||||
for (int partNum = 7; partNum >= minPart; partNum--) {
|
||||
int usePartNum = partNum == -1 ? 8 : partNum;
|
||||
if (parts[usePartNum]->getActivePartialCount() > numReservedPartialsForPart[usePartNum]) {
|
||||
// This part has exceeded its reserved partial count.
|
||||
// If it has any releasing polys, kill its first one and we're done.
|
||||
if (parts[usePartNum]->abortFirstPoly(POLY_Releasing)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Finds the lowest-priority part that is exceeding its reserved partial allocation and has a poly, then kills
|
||||
// its first poly in POLY_Held - or failing that, its first poly in any state.
|
||||
// Parts with higher priority than minPart are not checked.
|
||||
// Assumes that getFreePartials() has been called to make numReservedPartialsForPart up-to-date.
|
||||
bool PartialManager::abortFirstPolyPreferHeldWhereReserveExceeded(int minPart) {
|
||||
if (minPart == 8) {
|
||||
// Rhythm is highest priority
|
||||
minPart = -1;
|
||||
}
|
||||
for (int partNum = 7; partNum >= minPart; partNum--) {
|
||||
int usePartNum = partNum == -1 ? 8 : partNum;
|
||||
if (parts[usePartNum]->getActivePartialCount() > numReservedPartialsForPart[usePartNum]) {
|
||||
// This part has exceeded its reserved partial count.
|
||||
// If it has any polys, kill its first (preferably held) one and we're done.
|
||||
if (parts[usePartNum]->abortFirstPolyPreferHeld()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PartialManager::freePartials(unsigned int needed, int partNum) {
|
||||
// CONFIRMED: Barring bugs, this matches the real LAPC-I according to information from Mok.
|
||||
|
||||
// BUG: There's a bug in the LAPC-I implementation:
|
||||
// When allocating for rhythm part, or when allocating for a part that is using fewer partials than it has reserved,
|
||||
// held and playing polys on the rhythm part can potentially be aborted before releasing polys on the rhythm part.
|
||||
// This bug isn't present on MT-32.
|
||||
// I consider this to be a bug because I think that playing polys should always have priority over held polys,
|
||||
// and held polys should always have priority over releasing polys.
|
||||
|
||||
// NOTE: This code generally aborts polys in parts (according to certain conditions) in the following order:
|
||||
// 7, 6, 5, 4, 3, 2, 1, 0, 8 (rhythm)
|
||||
// (from lowest priority, meaning most likely to have polys aborted, to highest priority, meaning least likely)
|
||||
|
||||
if (needed == 0) {
|
||||
return true;
|
||||
}
|
||||
// Reclaim partials reserved for this part
|
||||
// Kill those that are already decaying first
|
||||
/*
|
||||
for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
|
||||
if (partialReserveTable[i] == partNum) {
|
||||
if (partialTable[i]->ownerPart != partNum) {
|
||||
if (partialTable[i]->partCache->envs[AMPENV].decaying) {
|
||||
partialTable[i]->isActive = false;
|
||||
--needed;
|
||||
if (needed == 0)
|
||||
|
||||
// Note that calling getFreePartialCount() also ensures that numReservedPartialsPerPart is up-to-date
|
||||
if (getFreePartialCount() >= needed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Note: These #ifdefs are temporary until we have proper "quirk" configuration.
|
||||
// Also, the MT-32 version isn't properly confirmed yet.
|
||||
#ifdef MT32EMU_QUIRK_FREE_PARTIALS_MT32
|
||||
// On MT-32, we bail out before even killing releasing partials if the allocating part has exceeded its reserve and is configured for priority-to-earlier-polys.
|
||||
if (parts[partNum]->getActiveNonReleasingPartialCount() + needed > numReservedPartialsForPart[partNum] && (synth->getPart(partNum)->getPatchTemp()->patch.assignMode & 1)) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (;;) {
|
||||
#ifdef MT32EMU_QUIRK_FREE_PARTIALS_MT32
|
||||
// Abort releasing polys in parts that have exceeded their partial reservation (working backwards from part 7, with rhythm last)
|
||||
if (!abortFirstReleasingPolyWhereReserveExceeded(-1)) {
|
||||
break;
|
||||
}
|
||||
#else
|
||||
// Abort releasing polys in non-rhythm parts that have exceeded their partial reservation (working backwards from part 7)
|
||||
if (!abortFirstReleasingPolyWhereReserveExceeded(0)) {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
if (getFreePartialCount() >= needed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (parts[partNum]->getActiveNonReleasingPartialCount() + needed > numReservedPartialsForPart[partNum]) {
|
||||
// With the new partials we're freeing for, we would end up using more partials than we have reserved.
|
||||
if (synth->getPart(partNum)->getPatchTemp()->patch.assignMode & 1) {
|
||||
// Priority is given to earlier polys, so just give up
|
||||
return false;
|
||||
}
|
||||
// Only abort held polys in the target part and parts that have a lower priority
|
||||
// (higher part number = lower priority, except for rhythm, which has the highest priority).
|
||||
for (;;) {
|
||||
if (!abortFirstPolyPreferHeldWhereReserveExceeded(partNum)) {
|
||||
break;
|
||||
}
|
||||
if (getFreePartialCount() >= needed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (needed > numReservedPartialsForPart[partNum]) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// At this point, we're certain that we've reserved enough partials to play our poly.
|
||||
// Check all parts from lowest to highest priority to see whether they've exceeded their
|
||||
// reserve, and abort their polys until until we have enough free partials or they're within
|
||||
// their reserve allocation.
|
||||
for (;;) {
|
||||
if (!abortFirstPolyPreferHeldWhereReserveExceeded(-1)) {
|
||||
break;
|
||||
}
|
||||
if (getFreePartialCount() >= needed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
// Then kill those with the lowest part priority -- oldest at the moment
|
||||
while (needed > 0) {
|
||||
Bit32u prior = 0;
|
||||
int priornum = -1;
|
||||
|
||||
for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
|
||||
if (partialReserveTable[i] == partNum && partialTable[i]->isActive() && partialTable[i]->getOwnerPart() != partNum) {
|
||||
/*
|
||||
if (mt32ram.system.reserveSettings[partialTable[i]->ownerPart] < prior) {
|
||||
prior = mt32ram.system.reserveSettings[partialTable[i]->ownerPart];
|
||||
priornum = i;
|
||||
}*/
|
||||
if (partialTable[i]->age >= prior) {
|
||||
prior = partialTable[i]->age;
|
||||
priornum = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (priornum != -1) {
|
||||
partialTable[priornum]->deactivate();
|
||||
--needed;
|
||||
} else {
|
||||
// Abort polys in the target part until there are enough free partials for the new one
|
||||
for (;;) {
|
||||
if (!parts[partNum]->abortFirstPolyPreferHeld()) {
|
||||
break;
|
||||
}
|
||||
if (getFreePartialCount() >= needed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Kill off the oldest partials within this part
|
||||
while (needed > 0) {
|
||||
Bit32u oldest = 0;
|
||||
int oldlist = -1;
|
||||
for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
|
||||
if (partialTable[i]->getOwnerPart() == partNum && partialTable[i]->isActive()) {
|
||||
if (partialTable[i]->age >= oldest) {
|
||||
oldest = partialTable[i]->age;
|
||||
oldlist = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (oldlist != -1) {
|
||||
partialTable[oldlist]->deactivate();
|
||||
--needed;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return needed == 0;
|
||||
// Aww, not enough partials for you.
|
||||
return false;
|
||||
}
|
||||
|
||||
const Partial *PartialManager::getPartial(unsigned int partialNum) const {
|
||||
if (partialNum > MT32EMU_MAX_PARTIALS - 1)
|
||||
if (partialNum > MT32EMU_MAX_PARTIALS - 1) {
|
||||
return NULL;
|
||||
}
|
||||
return partialTable[partialNum];
|
||||
}
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
/* Copyright (c) 2003-2005 Various contributors
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* 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 Lesser General Public License for more details.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_PARTIALMANAGER_H
|
||||
|
@ -29,25 +25,27 @@ class Synth;
|
|||
class PartialManager {
|
||||
private:
|
||||
Synth *synth; // Only used for sending debug output
|
||||
Part **parts;
|
||||
|
||||
Partial *partialTable[MT32EMU_MAX_PARTIALS];
|
||||
Bit32s partialReserveTable[MT32EMU_MAX_PARTIALS];
|
||||
Bit32s partialPart[9]; // The count of partials played per part
|
||||
Bit8u numReservedPartialsForPart[9];
|
||||
|
||||
bool abortFirstReleasingPolyWhereReserveExceeded(int minPart);
|
||||
bool abortFirstPolyPreferHeldWhereReserveExceeded(int minPart);
|
||||
|
||||
public:
|
||||
|
||||
PartialManager(Synth *synth);
|
||||
PartialManager(Synth *synth, Part **parts);
|
||||
~PartialManager();
|
||||
Partial *allocPartial(int partNum);
|
||||
unsigned int getFreePartialCount(void);
|
||||
void getPerPartPartialUsage(unsigned int perPartPartialUsage[9]);
|
||||
bool freePartials(unsigned int needed, int partNum);
|
||||
unsigned int setReserve(Bit8u *rset);
|
||||
void deactivateAll();
|
||||
void ageAll();
|
||||
bool produceOutput(int i, Bit16s *buffer, Bit32u bufferLength);
|
||||
bool produceOutput(int i, float *leftBuf, float *rightBuf, Bit32u bufferLength);
|
||||
bool shouldReverb(int i);
|
||||
void clearAlreadyOutputed();
|
||||
void getPerPartPartialUsage(int usage[9]);
|
||||
const Partial *getPartial(unsigned int partialNum) const;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
/* Copyright (c) 2003-2005 Various contributors
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* 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 Lesser General Public License for more details.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_STRUCTURES_H
|
||||
|
@ -24,8 +20,6 @@
|
|||
|
||||
namespace MT32Emu {
|
||||
|
||||
const unsigned int MAX_SAMPLE_OUTPUT = 4096;
|
||||
|
||||
// MT32EMU_MEMADDR() converts from sysex-padded, MT32EMU_SYSEXMEMADDR converts to it
|
||||
// Roland provides documentation using the sysex-padded addresses, so we tend to use that in code and output
|
||||
#define MT32EMU_MEMADDR(x) ((((x) & 0x7f0000) >> 2) | (((x) & 0x7f00) >> 1) | ((x) & 0x7f))
|
||||
|
@ -33,12 +27,8 @@ const unsigned int MAX_SAMPLE_OUTPUT = 4096;
|
|||
|
||||
#ifdef _MSC_VER
|
||||
#define MT32EMU_ALIGN_PACKED __declspec(align(1))
|
||||
typedef unsigned __int64 Bit64u;
|
||||
typedef signed __int64 Bit64s;
|
||||
#else
|
||||
#define MT32EMU_ALIGN_PACKED __attribute__((packed))
|
||||
typedef unsigned long long Bit64u;
|
||||
typedef signed long long Bit64s;
|
||||
#endif
|
||||
|
||||
typedef unsigned int Bit32u;
|
||||
|
@ -59,65 +49,65 @@ typedef signed char Bit8s;
|
|||
#endif
|
||||
|
||||
struct TimbreParam {
|
||||
struct commonParam {
|
||||
struct CommonParam {
|
||||
char name[10];
|
||||
Bit8u pstruct12; // 1&2 0-12 (1-13)
|
||||
Bit8u pstruct34; // #3&4 0-12 (1-13)
|
||||
Bit8u pmute; // 0-15 (0000-1111)
|
||||
Bit8u nosustain; // 0-1(Normal, No sustain)
|
||||
Bit8u partialStructure12; // 1 & 2 0-12 (1-13)
|
||||
Bit8u partialStructure34; // 3 & 4 0-12 (1-13)
|
||||
Bit8u partialMute; // 0-15 (0000-1111)
|
||||
Bit8u noSustain; // ENV MODE 0-1 (Normal, No sustain)
|
||||
} MT32EMU_ALIGN_PACKED common;
|
||||
|
||||
struct partialParam {
|
||||
struct wgParam {
|
||||
Bit8u coarse; // 0-96 (C1,C#1-C9)
|
||||
Bit8u fine; // 0-100 (-50 to +50 (cents?))
|
||||
Bit8u keyfollow; // 0-16 (-1,-1/2,0,1,1/8,1/4,3/8,1/2,5/8,3/4,7/8,1,5/4,3/2,2.s1,s2)
|
||||
Bit8u bender; // 0,1 (ON/OFF)
|
||||
struct PartialParam {
|
||||
struct WGParam {
|
||||
Bit8u pitchCoarse; // 0-96 (C1,C#1-C9)
|
||||
Bit8u pitchFine; // 0-100 (-50 to +50 (cents - confirmed by Mok))
|
||||
Bit8u pitchKeyfollow; // 0-16 (-1, -1/2, -1/4, 0, 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, 1, 5/4, 3/2, 2, s1, s2)
|
||||
Bit8u pitchBenderEnabled; // 0-1 (OFF, ON)
|
||||
Bit8u waveform; // MT-32: 0-1 (SQU/SAW); LAPC-I: WG WAVEFORM/PCM BANK 0 - 3 (SQU/1, SAW/1, SQU/2, SAW/2)
|
||||
Bit8u pcmwave; // 0-127 (1-128)
|
||||
Bit8u pulsewid; // 0-100
|
||||
Bit8u pwvelo; // 0-14 (-7 - +7)
|
||||
Bit8u pcmWave; // 0-127 (1-128)
|
||||
Bit8u pulseWidth; // 0-100
|
||||
Bit8u pulseWidthVeloSensitivity; // 0-14 (-7 - +7)
|
||||
} MT32EMU_ALIGN_PACKED wg;
|
||||
|
||||
struct envParam {
|
||||
struct PitchEnvParam {
|
||||
Bit8u depth; // 0-10
|
||||
Bit8u sensitivity; // 1-100
|
||||
Bit8u timekeyfollow; // 0-4
|
||||
Bit8u time[4]; // 1-100
|
||||
Bit8u level[5]; // 1-100 (-50 - +50)
|
||||
} MT32EMU_ALIGN_PACKED env;
|
||||
Bit8u veloSensitivity; // 0-100
|
||||
Bit8u timeKeyfollow; // 0-4
|
||||
Bit8u time[4]; // 0-100
|
||||
Bit8u level[5]; // 0-100 (-50 - +50) // [3]: SUSTAIN LEVEL, [4]: END LEVEL
|
||||
} MT32EMU_ALIGN_PACKED pitchEnv;
|
||||
|
||||
struct lfoParam {
|
||||
struct PitchLFOParam {
|
||||
Bit8u rate; // 0-100
|
||||
Bit8u depth; // 0-100
|
||||
Bit8u modsense; // 0-100
|
||||
} MT32EMU_ALIGN_PACKED lfo;
|
||||
Bit8u modSensitivity; // 0-100
|
||||
} MT32EMU_ALIGN_PACKED pitchLFO;
|
||||
|
||||
struct tvfParam {
|
||||
struct TVFParam {
|
||||
Bit8u cutoff; // 0-100
|
||||
Bit8u resonance; // 0-30
|
||||
Bit8u keyfollow; // 0-16 (-1,-1/2,1/4,0,1,1/8,1/4,3/8,1/2,5/8,3/2,7/8,1,5/4,3/2,2,s1,s2)
|
||||
Bit8u biaspoint; // 0-127 (<1A-<7C >1A-7C)
|
||||
Bit8u biaslevel; // 0-14 (-7 - +7)
|
||||
Bit8u envdepth; // 0-100
|
||||
Bit8u envsense; // 0-100
|
||||
Bit8u envdkf; // DEPTH KEY FOLL0W 0-4
|
||||
Bit8u envtkf; // TIME KEY FOLLOW 0-4
|
||||
Bit8u envtime[5]; // 1-100
|
||||
Bit8u envlevel[4]; // 1-100
|
||||
Bit8u keyfollow; // -1, -1/2, -1/4, 0, 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, 1, 5/4, 3/2, 2
|
||||
Bit8u biasPoint; // 0-127 (<1A-<7C >1A-7C)
|
||||
Bit8u biasLevel; // 0-14 (-7 - +7)
|
||||
Bit8u envDepth; // 0-100
|
||||
Bit8u envVeloSensitivity; // 0-100
|
||||
Bit8u envDepthKeyfollow; // DEPTH KEY FOLL0W 0-4
|
||||
Bit8u envTimeKeyfollow; // TIME KEY FOLLOW 0-4
|
||||
Bit8u envTime[5]; // 0-100
|
||||
Bit8u envLevel[4]; // 0-100 // [3]: SUSTAIN LEVEL
|
||||
} MT32EMU_ALIGN_PACKED tvf;
|
||||
|
||||
struct tvaParam {
|
||||
struct TVAParam {
|
||||
Bit8u level; // 0-100
|
||||
Bit8u velosens; // 0-100
|
||||
Bit8u biaspoint1; // 0-127 (<1A-<7C >1A-7C)
|
||||
Bit8u biaslevel1; // 0-12 (-12 - 0)
|
||||
Bit8u biaspoint2; // 0-127 (<1A-<7C >1A-7C)
|
||||
Bit8u biaslevel2; // 0-12 (-12 - 0)
|
||||
Bit8u envtkf; // TIME KEY FOLLOW 0-4
|
||||
Bit8u envvkf; // VELOS KEY FOLL0W 0-4
|
||||
Bit8u envtime[5]; // 1-100
|
||||
Bit8u envlevel[4]; // 1-100
|
||||
Bit8u veloSensitivity; // 0-100
|
||||
Bit8u biasPoint1; // 0-127 (<1A-<7C >1A-7C)
|
||||
Bit8u biasLevel1; // 0-12 (-12 - 0)
|
||||
Bit8u biasPoint2; // 0-127 (<1A-<7C >1A-7C)
|
||||
Bit8u biasLevel2; // 0-12 (-12 - 0)
|
||||
Bit8u envTimeKeyfollow; // TIME KEY FOLLOW 0-4
|
||||
Bit8u envTimeVeloSensitivity; // VELOS KEY FOLL0W 0-4
|
||||
Bit8u envTime[5]; // 0-100
|
||||
Bit8u envLevel[4]; // 0-100 // [3]: SUSTAIN LEVEL
|
||||
} MT32EMU_ALIGN_PACKED tva;
|
||||
} MT32EMU_ALIGN_PACKED partial[4];
|
||||
} MT32EMU_ALIGN_PACKED;
|
||||
|
@ -133,29 +123,35 @@ struct PatchParam {
|
|||
Bit8u dummy; // (DUMMY)
|
||||
} MT32EMU_ALIGN_PACKED;
|
||||
|
||||
const unsigned int SYSTEM_MASTER_TUNE_OFF = 0;
|
||||
const unsigned int SYSTEM_REVERB_MODE_OFF = 1;
|
||||
const unsigned int SYSTEM_REVERB_TIME_OFF = 2;
|
||||
const unsigned int SYSTEM_REVERB_LEVEL_OFF = 3;
|
||||
const unsigned int SYSTEM_RESERVE_SETTINGS_START_OFF = 4;
|
||||
const unsigned int SYSTEM_RESERVE_SETTINGS_END_OFF = 12;
|
||||
const unsigned int SYSTEM_CHAN_ASSIGN_START_OFF = 13;
|
||||
const unsigned int SYSTEM_CHAN_ASSIGN_END_OFF = 21;
|
||||
const unsigned int SYSTEM_MASTER_VOL_OFF = 22;
|
||||
|
||||
struct MemParams {
|
||||
// NOTE: The MT-32 documentation only specifies PatchTemp areas for parts 1-8.
|
||||
// The LAPC-I documentation specified an additional area for rhythm at the end,
|
||||
// where all parameters but fine tune, assign mode and output level are ignored
|
||||
struct PatchTemp {
|
||||
PatchParam patch;
|
||||
Bit8u outlevel; // OUTPUT LEVEL 0-100
|
||||
Bit8u outputLevel; // OUTPUT LEVEL 0-100
|
||||
Bit8u panpot; // PANPOT 0-14 (R-L)
|
||||
Bit8u dummyv[6];
|
||||
} MT32EMU_ALIGN_PACKED;
|
||||
|
||||
PatchTemp patchSettings[9];
|
||||
} MT32EMU_ALIGN_PACKED patchTemp[9];
|
||||
|
||||
struct RhythmTemp {
|
||||
Bit8u timbre; // TIMBRE 0-94 (M1-M64,R1-30,OFF)
|
||||
Bit8u outlevel; // OUTPUT LEVEL 0-100
|
||||
Bit8u timbre; // TIMBRE 0-94 (M1-M64,R1-30,OFF); LAPC-I: 0-127 (M01-M64,R01-R63)
|
||||
Bit8u outputLevel; // OUTPUT LEVEL 0-100
|
||||
Bit8u panpot; // PANPOT 0-14 (R-L)
|
||||
Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON)
|
||||
} MT32EMU_ALIGN_PACKED;
|
||||
} MT32EMU_ALIGN_PACKED rhythmTemp[85];
|
||||
|
||||
RhythmTemp rhythmSettings[85];
|
||||
|
||||
TimbreParam timbreSettings[8];
|
||||
TimbreParam timbreTemp[8];
|
||||
|
||||
PatchParam patches[128];
|
||||
|
||||
|
@ -163,11 +159,9 @@ struct MemParams {
|
|||
struct PaddedTimbre {
|
||||
TimbreParam timbre;
|
||||
Bit8u padding[10];
|
||||
} MT32EMU_ALIGN_PACKED;
|
||||
} MT32EMU_ALIGN_PACKED timbres[64 + 64 + 64 + 64]; // Group A, Group B, Memory, Rhythm
|
||||
|
||||
PaddedTimbre timbres[64 + 64 + 64 + 64]; // Group A, Group B, Memory, Rhythm
|
||||
|
||||
struct SystemArea {
|
||||
struct System {
|
||||
Bit8u masterTune; // MASTER TUNE 0-127 432.1-457.6Hz
|
||||
Bit8u reverbMode; // REVERB MODE 0-3 (room, hall, plate, tap delay)
|
||||
Bit8u reverbTime; // REVERB TIME 0-7 (1-8)
|
||||
|
@ -175,9 +169,7 @@ struct MemParams {
|
|||
Bit8u reserveSettings[9]; // PARTIAL RESERVE (PART 1) 0-32
|
||||
Bit8u chanAssign[9]; // MIDI CHANNEL (PART1) 0-16 (1-16,OFF)
|
||||
Bit8u masterVol; // MASTER VOLUME 0-100
|
||||
} MT32EMU_ALIGN_PACKED;
|
||||
|
||||
SystemArea system;
|
||||
} MT32EMU_ALIGN_PACKED system;
|
||||
};
|
||||
|
||||
#if defined(_MSC_VER) || defined (__MINGW32__)
|
||||
|
@ -186,21 +178,13 @@ struct MemParams {
|
|||
#pragma pack()
|
||||
#endif
|
||||
|
||||
struct ControlROMPCMStruct;
|
||||
|
||||
struct PCMWaveEntry {
|
||||
Bit32u addr;
|
||||
Bit32u len;
|
||||
double tune;
|
||||
bool loop;
|
||||
};
|
||||
|
||||
struct soundaddr {
|
||||
Bit16u pcmplace;
|
||||
Bit16u pcmoffset;
|
||||
};
|
||||
|
||||
struct StereoVolume {
|
||||
Bit16s leftvol;
|
||||
Bit16s rightvol;
|
||||
ControlROMPCMStruct *controlROMPCMStruct;
|
||||
};
|
||||
|
||||
// This is basically a per-partial, pre-processed combination of timbre and patch/rhythm settings
|
||||
|
@ -209,40 +193,6 @@ struct PatchCache {
|
|||
bool PCMPartial;
|
||||
int pcm;
|
||||
char waveform;
|
||||
int pulsewidth;
|
||||
int pwsens;
|
||||
|
||||
float pitch;
|
||||
|
||||
int lfodepth;
|
||||
int lforate;
|
||||
Bit32u lfoperiod;
|
||||
int modsense;
|
||||
|
||||
float pitchKeyfollow;
|
||||
|
||||
int filtkeyfollow;
|
||||
|
||||
int tvfbias;
|
||||
int tvfblevel;
|
||||
int tvfdir;
|
||||
|
||||
int ampbias[2];
|
||||
int ampblevel[2];
|
||||
int ampdir[2];
|
||||
|
||||
int ampdepth;
|
||||
int amplevel;
|
||||
|
||||
bool useBender;
|
||||
float benderRange; // 0.0, 1.0, .., 24.0 (semitones)
|
||||
|
||||
TimbreParam::partialParam::envParam pitchEnv;
|
||||
TimbreParam::partialParam::tvaParam ampEnv;
|
||||
TimbreParam::partialParam::tvfParam filtEnv;
|
||||
|
||||
Bit32s pitchsustain;
|
||||
Bit32s filtsustain;
|
||||
|
||||
Bit32u structureMix;
|
||||
int structurePosition;
|
||||
|
@ -252,33 +202,16 @@ struct PatchCache {
|
|||
bool dirty;
|
||||
Bit32u partialCount;
|
||||
bool sustain;
|
||||
float pitchShift;
|
||||
bool reverb;
|
||||
const StereoVolume *pansetptr;
|
||||
|
||||
TimbreParam::PartialParam srcPartial;
|
||||
|
||||
// The following directly points into live sysex-addressable memory
|
||||
const TimbreParam::PartialParam *partialParam;
|
||||
};
|
||||
|
||||
class Partial; // Forward reference for class defined in partial.h
|
||||
|
||||
struct dpoly {
|
||||
bool isPlaying;
|
||||
|
||||
unsigned int key;
|
||||
int freqnum;
|
||||
int vel;
|
||||
|
||||
bool isDecay;
|
||||
|
||||
const Bit32u *volumeptr;
|
||||
|
||||
Partial *partials[4];
|
||||
|
||||
bool pedalhold; // This marks keys that have been released on the keyboard, but are being held by the pedal
|
||||
bool sustain;
|
||||
|
||||
bool isActive() const;
|
||||
Bit32u getAge() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,38 +1,62 @@
|
|||
/* Copyright (c) 2003-2005 Various contributors
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* 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 Lesser General Public License for more details.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_SYNTH_H
|
||||
#define MT32EMU_SYNTH_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
|
||||
class revmodel;
|
||||
//#include <cstdarg>
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class File;
|
||||
class TableInitialiser;
|
||||
class Partial;
|
||||
class PartialManager;
|
||||
class Part;
|
||||
|
||||
/**
|
||||
* Methods for emulating the connection between the LA32 and the DAC, which involves
|
||||
* some hacks in the real devices for doubling the volume.
|
||||
* See also http://en.wikipedia.org/wiki/Roland_MT-32#Digital_overflow
|
||||
*/
|
||||
enum DACInputMode {
|
||||
// Produces samples at double the volume, without tricks.
|
||||
// * Nicer overdrive characteristics than the DAC hacks (it simply clips samples within range)
|
||||
// * Higher quality than the real devices
|
||||
DACInputMode_NICE,
|
||||
|
||||
// Produces samples that exactly match the bits output from the emulated LA32.
|
||||
// * Nicer overdrive characteristics than the DAC hacks (it simply clips samples within range)
|
||||
// * Much less likely to overdrive than any other mode.
|
||||
// * Half the volume of any of the other modes, meaning its volume relative to the reverb
|
||||
// output when mixed together directly will sound wrong.
|
||||
// * Perfect for developers while debugging :)
|
||||
DACInputMode_PURE,
|
||||
|
||||
// Re-orders the LA32 output bits as in early generation MT-32s (according to Wikipedia).
|
||||
// Bit order at DAC (where each number represents the original LA32 output bit number, and XX means the bit is always low):
|
||||
// 15 13 12 11 10 09 08 07 06 05 04 03 02 01 00 XX
|
||||
DACInputMode_GENERATION1,
|
||||
|
||||
// Re-orders the LA32 output bits as in later generations (personally confirmed on my CM-32L - KG).
|
||||
// Bit order at DAC (where each number represents the original LA32 output bit number):
|
||||
// 15 13 12 11 10 09 08 07 06 05 04 03 02 01 00 14
|
||||
DACInputMode_GENERATION2
|
||||
};
|
||||
|
||||
enum ReportType {
|
||||
// Errors
|
||||
ReportType_errorControlROM = 1,
|
||||
|
@ -57,25 +81,30 @@ enum ReportType {
|
|||
ReportType_newReverbLevel
|
||||
};
|
||||
|
||||
enum LoadResult {
|
||||
LoadResult_OK,
|
||||
LoadResult_NotFound,
|
||||
LoadResult_Unreadable,
|
||||
LoadResult_Invalid
|
||||
};
|
||||
|
||||
struct SynthProperties {
|
||||
// Sample rate to use in mixing
|
||||
int sampleRate;
|
||||
unsigned int sampleRate;
|
||||
|
||||
// Flag to activate reverb. True = use reverb, False = no reverb
|
||||
// Deprecated - ignored. Use Synth::setReverbEnabled() instead.
|
||||
bool useReverb;
|
||||
// True to use software set reverb settings, False to set reverb settings in
|
||||
// following parameters
|
||||
// Deprecated - ignored. Use Synth::setReverbOverridden() instead.
|
||||
bool useDefaultReverb;
|
||||
// When not using the default settings, this specifies one of the 4 reverb types
|
||||
// 1 = Room 2 = Hall 3 = Plate 4 = Tap
|
||||
// Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead.
|
||||
unsigned char reverbType;
|
||||
// This specifies the delay time, from 0-7 (not sure of the actual MT-32's measurement)
|
||||
// Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead.
|
||||
unsigned char reverbTime;
|
||||
// This specifies the reverb level, from 0-7 (not sure of the actual MT-32's measurement)
|
||||
// Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead.
|
||||
unsigned char reverbLevel;
|
||||
// The name of the directory in which the ROM and data files are stored (with trailing slash/backslash)
|
||||
// Not used if "openFile" is set. May be NULL in any case.
|
||||
char *baseDir;
|
||||
const char *baseDir;
|
||||
// This is used as the first argument to all callbacks
|
||||
void *userData;
|
||||
// Callback for reporting various errors and information. May be NULL
|
||||
|
@ -84,22 +113,16 @@ struct SynthProperties {
|
|||
void (*printDebug)(void *userData, const char *fmt, va_list list);
|
||||
// Callback for providing an implementation of File, opened and ready for use
|
||||
// May be NULL, in which case a default implementation will be used.
|
||||
File *(*openFile)(void *userData, const char *filename, File::OpenMode mode);
|
||||
Common::File *(*openFile)(void *userData, const char *filename);
|
||||
// Callback for closing a File. May be NULL, in which case the File will automatically be close()d/deleted.
|
||||
void (*closeFile)(void *userData, File *file);
|
||||
void (*closeFile)(void *userData, Common::File *file);
|
||||
};
|
||||
|
||||
// This is the specification of the Callback routine used when calling the RecalcWaveforms
|
||||
// function
|
||||
typedef void (*recalcStatusCallback)(int percDone);
|
||||
|
||||
// This external function recreates the base waveform file (waveforms.raw) using a specifed
|
||||
// sampling rate. The callback routine provides interactivity to let the user know what
|
||||
// percentage is complete in regenerating the waveforms. When a NULL pointer is used as the
|
||||
// callback routine, no status is reported.
|
||||
bool RecalcWaveforms(char * baseDir, int sampRate, recalcStatusCallback callBack);
|
||||
|
||||
typedef float (*iir_filter_type)(float input,float *hist1_ptr, float *coef_ptr);
|
||||
typedef void (*FloatToBit16sFunc)(Bit16s *target, const float *source, Bit32u len, float outputGain);
|
||||
|
||||
const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41;
|
||||
|
||||
|
@ -116,10 +139,11 @@ const Bit8u SYSEX_CMD_EOD = 0x45; // End of data
|
|||
const Bit8u SYSEX_CMD_ERR = 0x4E; // Communications error
|
||||
const Bit8u SYSEX_CMD_RJC = 0x4F; // Rejection
|
||||
|
||||
const int MAX_SYSEX_SIZE = 512;
|
||||
|
||||
const unsigned int CONTROL_ROM_SIZE = 64 * 1024;
|
||||
|
||||
struct ControlROMPCMStruct
|
||||
{
|
||||
struct ControlROMPCMStruct {
|
||||
Bit8u pos;
|
||||
Bit8u len;
|
||||
Bit8u pitchLSB;
|
||||
|
@ -130,19 +154,25 @@ struct ControlROMMap {
|
|||
Bit16u idPos;
|
||||
Bit16u idLen;
|
||||
const char *idBytes;
|
||||
Bit16u pcmTable;
|
||||
Bit16u pcmTable; // 4 * pcmCount bytes
|
||||
Bit16u pcmCount;
|
||||
Bit16u timbreAMap;
|
||||
Bit16u timbreAMap; // 128 bytes
|
||||
Bit16u timbreAOffset;
|
||||
Bit16u timbreBMap;
|
||||
bool timbreACompressed;
|
||||
Bit16u timbreBMap; // 128 bytes
|
||||
Bit16u timbreBOffset;
|
||||
Bit16u timbreRMap;
|
||||
bool timbreBCompressed;
|
||||
Bit16u timbreRMap; // 2 * timbreRCount bytes
|
||||
Bit16u timbreRCount;
|
||||
Bit16u rhythmSettings;
|
||||
Bit16u rhythmSettings; // 4 * rhythmSettingsCount bytes
|
||||
Bit16u rhythmSettingsCount;
|
||||
Bit16u reserveSettings;
|
||||
Bit16u panSettings;
|
||||
Bit16u programSettings;
|
||||
Bit16u reserveSettings; // 9 bytes
|
||||
Bit16u panSettings; // 8 bytes
|
||||
Bit16u programSettings; // 8 bytes
|
||||
Bit16u rhythmMaxTable; // 4 bytes
|
||||
Bit16u patchMaxTable; // 16 bytes
|
||||
Bit16u systemMaxTable; // 23 bytes
|
||||
Bit16u timbreMaxTable; // 72 bytes
|
||||
};
|
||||
|
||||
enum MemoryRegionType {
|
||||
|
@ -150,10 +180,23 @@ enum MemoryRegionType {
|
|||
};
|
||||
|
||||
class MemoryRegion {
|
||||
private:
|
||||
Synth *synth;
|
||||
Bit8u *realMemory;
|
||||
Bit8u *maxTable;
|
||||
public:
|
||||
MemoryRegionType type;
|
||||
Bit32u startAddr, entrySize, entries;
|
||||
|
||||
MemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable, MemoryRegionType useType, Bit32u useStartAddr, Bit32u useEntrySize, Bit32u useEntries) {
|
||||
synth = useSynth;
|
||||
realMemory = useRealMemory;
|
||||
maxTable = useMaxTable;
|
||||
type = useType;
|
||||
startAddr = useStartAddr;
|
||||
entrySize = useEntrySize;
|
||||
entries = useEntries;
|
||||
}
|
||||
int lastTouched(Bit32u addr, Bit32u len) const {
|
||||
return (offset(addr) + len - 1) / entrySize;
|
||||
}
|
||||
|
@ -183,79 +226,187 @@ public:
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
Bit8u getMaxValue(int off) const {
|
||||
if (maxTable == NULL)
|
||||
return 0xFF;
|
||||
return maxTable[off % entrySize];
|
||||
}
|
||||
Bit8u *getRealMemory() const {
|
||||
return realMemory;
|
||||
}
|
||||
bool isReadable() const {
|
||||
return getRealMemory() != NULL;
|
||||
}
|
||||
void read(unsigned int entry, unsigned int off, Bit8u *dst, unsigned int len) const;
|
||||
void write(unsigned int entry, unsigned int off, const Bit8u *src, unsigned int len, bool init = false) const;
|
||||
};
|
||||
|
||||
class PatchTempMemoryRegion : public MemoryRegion {
|
||||
public:
|
||||
PatchTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_PatchTemp, MT32EMU_MEMADDR(0x030000), sizeof(MemParams::PatchTemp), 9) {}
|
||||
};
|
||||
class RhythmTempMemoryRegion : public MemoryRegion {
|
||||
public:
|
||||
RhythmTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_RhythmTemp, MT32EMU_MEMADDR(0x030110), sizeof(MemParams::RhythmTemp), 85) {}
|
||||
};
|
||||
class TimbreTempMemoryRegion : public MemoryRegion {
|
||||
public:
|
||||
TimbreTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_TimbreTemp, MT32EMU_MEMADDR(0x040000), sizeof(TimbreParam), 8) {}
|
||||
};
|
||||
class PatchesMemoryRegion : public MemoryRegion {
|
||||
public:
|
||||
PatchesMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Patches, MT32EMU_MEMADDR(0x050000), sizeof(PatchParam), 128) {}
|
||||
};
|
||||
class TimbresMemoryRegion : public MemoryRegion {
|
||||
public:
|
||||
TimbresMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Timbres, MT32EMU_MEMADDR(0x080000), sizeof(MemParams::PaddedTimbre), 64 + 64 + 64 + 64) {}
|
||||
};
|
||||
class SystemMemoryRegion : public MemoryRegion {
|
||||
public:
|
||||
SystemMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_System, MT32EMU_MEMADDR(0x100000), sizeof(MemParams::System), 1) {}
|
||||
};
|
||||
class DisplayMemoryRegion : public MemoryRegion {
|
||||
public:
|
||||
DisplayMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Display, MT32EMU_MEMADDR(0x200000), MAX_SYSEX_SIZE - 1, 1) {}
|
||||
};
|
||||
class ResetMemoryRegion : public MemoryRegion {
|
||||
public:
|
||||
ResetMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1) {}
|
||||
};
|
||||
|
||||
class ReverbModel {
|
||||
public:
|
||||
virtual ~ReverbModel() {}
|
||||
// After construction or a close(), open() will be called at least once before any other call (with the exception of close()).
|
||||
virtual void open(unsigned int sampleRate) = 0;
|
||||
// May be called multiple times without an open() in between.
|
||||
virtual void close() = 0;
|
||||
virtual void setParameters(Bit8u time, Bit8u level) = 0;
|
||||
virtual void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) = 0;
|
||||
virtual bool isActive() const = 0;
|
||||
};
|
||||
|
||||
class Synth {
|
||||
friend class Part;
|
||||
friend class RhythmPart;
|
||||
friend class Poly;
|
||||
friend class Partial;
|
||||
friend class Tables;
|
||||
friend class MemoryRegion;
|
||||
friend class TVA;
|
||||
friend class TVF;
|
||||
friend class TVP;
|
||||
private:
|
||||
bool isEnabled;
|
||||
PatchTempMemoryRegion *patchTempMemoryRegion;
|
||||
RhythmTempMemoryRegion *rhythmTempMemoryRegion;
|
||||
TimbreTempMemoryRegion *timbreTempMemoryRegion;
|
||||
PatchesMemoryRegion *patchesMemoryRegion;
|
||||
TimbresMemoryRegion *timbresMemoryRegion;
|
||||
SystemMemoryRegion *systemMemoryRegion;
|
||||
DisplayMemoryRegion *displayMemoryRegion;
|
||||
ResetMemoryRegion *resetMemoryRegion;
|
||||
|
||||
iir_filter_type iirFilter;
|
||||
Bit8u *paddedTimbreMaxTable;
|
||||
|
||||
bool isEnabled;
|
||||
|
||||
PCMWaveEntry *pcmWaves; // Array
|
||||
|
||||
const ControlROMMap *controlROMMap;
|
||||
Bit8u controlROMData[CONTROL_ROM_SIZE];
|
||||
Bit16s *pcmROMData;
|
||||
float *pcmROMData;
|
||||
int pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM
|
||||
|
||||
Bit8s chantable[32];
|
||||
|
||||
#if MT32EMU_MONITOR_PARTIALS == 1
|
||||
static Bit32s samplepos = 0;
|
||||
#endif
|
||||
Bit32u renderedSampleCount;
|
||||
|
||||
Tables tables;
|
||||
|
||||
MemParams mt32ram, mt32default;
|
||||
|
||||
revmodel *reverbModel;
|
||||
ReverbModel *reverbModels[4];
|
||||
ReverbModel *reverbModel;
|
||||
bool reverbEnabled;
|
||||
bool reverbOverridden;
|
||||
|
||||
float masterTune;
|
||||
Bit16u masterVolume;
|
||||
FloatToBit16sFunc la32FloatToBit16sFunc;
|
||||
FloatToBit16sFunc reverbFloatToBit16sFunc;
|
||||
float outputGain;
|
||||
float reverbOutputGain;
|
||||
|
||||
bool isOpen;
|
||||
|
||||
PartialManager *partialManager;
|
||||
Part *parts[9];
|
||||
|
||||
Bit16s tmpBuffer[MAX_SAMPLE_OUTPUT * 2];
|
||||
float sndbufl[MAX_SAMPLE_OUTPUT];
|
||||
float sndbufr[MAX_SAMPLE_OUTPUT];
|
||||
float outbufl[MAX_SAMPLE_OUTPUT];
|
||||
float outbufr[MAX_SAMPLE_OUTPUT];
|
||||
// FIXME: We can reorganise things so that we don't need all these separate tmpBuf, tmp and prerender buffers.
|
||||
// This should be rationalised when things have stabilised a bit (if prerender buffers don't die in the mean time).
|
||||
|
||||
float tmpBufPartialLeft[MAX_SAMPLES_PER_RUN];
|
||||
float tmpBufPartialRight[MAX_SAMPLES_PER_RUN];
|
||||
float tmpBufMixLeft[MAX_SAMPLES_PER_RUN];
|
||||
float tmpBufMixRight[MAX_SAMPLES_PER_RUN];
|
||||
float tmpBufReverbOutLeft[MAX_SAMPLES_PER_RUN];
|
||||
float tmpBufReverbOutRight[MAX_SAMPLES_PER_RUN];
|
||||
|
||||
Bit16s tmpNonReverbLeft[MAX_SAMPLES_PER_RUN];
|
||||
Bit16s tmpNonReverbRight[MAX_SAMPLES_PER_RUN];
|
||||
Bit16s tmpReverbDryLeft[MAX_SAMPLES_PER_RUN];
|
||||
Bit16s tmpReverbDryRight[MAX_SAMPLES_PER_RUN];
|
||||
Bit16s tmpReverbWetLeft[MAX_SAMPLES_PER_RUN];
|
||||
Bit16s tmpReverbWetRight[MAX_SAMPLES_PER_RUN];
|
||||
|
||||
// These ring buffers are only used to simulate delays present on the real device.
|
||||
// In particular, when a partial needs to be aborted to free it up for use by a new Poly,
|
||||
// the controller will busy-loop waiting for the sound to finish.
|
||||
Bit16s prerenderNonReverbLeft[MAX_PRERENDER_SAMPLES];
|
||||
Bit16s prerenderNonReverbRight[MAX_PRERENDER_SAMPLES];
|
||||
Bit16s prerenderReverbDryLeft[MAX_PRERENDER_SAMPLES];
|
||||
Bit16s prerenderReverbDryRight[MAX_PRERENDER_SAMPLES];
|
||||
Bit16s prerenderReverbWetLeft[MAX_PRERENDER_SAMPLES];
|
||||
Bit16s prerenderReverbWetRight[MAX_PRERENDER_SAMPLES];
|
||||
int prerenderReadIx;
|
||||
int prerenderWriteIx;
|
||||
|
||||
SynthProperties myProp;
|
||||
|
||||
bool loadPreset(File *file);
|
||||
void initReverb(Bit8u newRevMode, Bit8u newRevTime, Bit8u newRevLevel);
|
||||
void doRender(Bit16s * stream, Bit32u len);
|
||||
bool prerender();
|
||||
void copyPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u pos, Bit32u len);
|
||||
void checkPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u &pos, Bit32u &len);
|
||||
void doRenderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len);
|
||||
|
||||
void playAddressedSysex(unsigned char channel, const Bit8u *sysex, Bit32u len);
|
||||
void readSysex(unsigned char channel, const Bit8u *sysex, Bit32u len);
|
||||
void readSysex(unsigned char channel, const Bit8u *sysex, Bit32u len) const;
|
||||
void initMemoryRegions();
|
||||
void deleteMemoryRegions();
|
||||
MemoryRegion *findMemoryRegion(Bit32u addr);
|
||||
void writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data);
|
||||
void readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data);
|
||||
|
||||
bool loadControlROM(const char *filename);
|
||||
bool loadPCMROM(const char *filename);
|
||||
bool dumpTimbre(File *file, const TimbreParam *timbre, Bit32u addr);
|
||||
int dumpTimbres(const char *filename, int start, int len);
|
||||
LoadResult loadControlROM(const char *filename);
|
||||
LoadResult loadPCMROM(const char *filename);
|
||||
|
||||
bool initPCMList(Bit16u mapAddress, Bit16u count);
|
||||
bool initRhythmTimbres(Bit16u mapAddress, Bit16u count);
|
||||
bool initTimbres(Bit16u mapAddress, Bit16u offset, int startTimbre);
|
||||
bool initRhythmTimbre(int drumNum, const Bit8u *mem, unsigned int memLen);
|
||||
bool refreshSystem();
|
||||
bool initTimbres(Bit16u mapAddress, Bit16u offset, int timbreCount, int startTimbre, bool compressed);
|
||||
bool initCompressedTimbre(int drumNum, const Bit8u *mem, unsigned int memLen);
|
||||
|
||||
void refreshSystemMasterTune();
|
||||
void refreshSystemReverbParameters();
|
||||
void refreshSystemReserveSettings();
|
||||
void refreshSystemChanAssign(unsigned int firstPart, unsigned int lastPart);
|
||||
void refreshSystemMasterVol();
|
||||
void refreshSystem();
|
||||
void reset();
|
||||
|
||||
unsigned int getSampleRate() const;
|
||||
|
||||
void printPartialUsage(unsigned long sampleOffset = 0);
|
||||
protected:
|
||||
int report(ReportType type, const void *reportData);
|
||||
File *openFile(const char *filename, File::OpenMode mode);
|
||||
void closeFile(File *file);
|
||||
void printDebug(const char *fmt, ...) GCC_PRINTF(2, 3);
|
||||
Common::File *openFile(const char *filename);
|
||||
void closeFile(Common::File *file);
|
||||
void printDebug(const char *fmt, ...);
|
||||
|
||||
public:
|
||||
static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum);
|
||||
|
@ -263,7 +414,7 @@ public:
|
|||
Synth();
|
||||
~Synth();
|
||||
|
||||
// Used to initialize the MT-32. Must be called before any other function.
|
||||
// Used to initialise the MT-32. Must be called before any other function.
|
||||
// Returns true if initialization was sucessful, otherwise returns false.
|
||||
bool open(SynthProperties &useProp);
|
||||
|
||||
|
@ -281,10 +432,31 @@ public:
|
|||
void playSysexWithoutHeader(unsigned char device, unsigned char command, const Bit8u *sysex, Bit32u len);
|
||||
void writeSysex(unsigned char channel, const Bit8u *sysex, Bit32u len);
|
||||
|
||||
// This callback routine is used to have the MT-32 generate samples to the specified
|
||||
// output stream. The length is in whole samples, not bytes. (I.E. in 16-bit stereo,
|
||||
// one sample is 4 bytes)
|
||||
void render(Bit16s * stream, Bit32u len);
|
||||
void setReverbEnabled(bool reverbEnabled);
|
||||
bool isReverbEnabled() const;
|
||||
void setReverbOverridden(bool reverbOverridden);
|
||||
bool isReverbOverridden() const;
|
||||
void setDACInputMode(DACInputMode mode);
|
||||
|
||||
// Sets output gain factor. Applied to all output samples and unrelated with the synth's Master volume.
|
||||
void setOutputGain(float);
|
||||
|
||||
// Sets output gain factor for the reverb wet output. setOutputGain() doesn't change reverb output gain.
|
||||
void setReverbOutputGain(float);
|
||||
|
||||
// Renders samples to the specified output stream.
|
||||
// The length is in frames, not bytes (in 16-bit stereo,
|
||||
// one frame is 4 bytes).
|
||||
void render(Bit16s *stream, Bit32u len);
|
||||
|
||||
// Renders samples to the specified output streams (any or all of which may be NULL).
|
||||
void renderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len);
|
||||
|
||||
// Returns true when there is at least one active partial, otherwise false.
|
||||
bool hasActivePartials() const;
|
||||
|
||||
// Returns true if hasActivePartials() returns true, or reverb is (somewhat unreliably) detected as being active.
|
||||
bool isActive() const;
|
||||
|
||||
const Partial *getPartial(unsigned int partialNum) const;
|
||||
|
||||
|
|
|
@ -1,761 +1,119 @@
|
|||
/* Copyright (c) 2003-2005 Various contributors
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* 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 Lesser General Public License for more details.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
// FIXME: Avoid using rand
|
||||
#define FORBIDDEN_SYMBOL_EXCEPTION_rand
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
//#include <cmath>
|
||||
//#include <cstdlib>
|
||||
//#include <cstring>
|
||||
|
||||
#include "mt32emu.h"
|
||||
#include "mmath.h"
|
||||
|
||||
#if defined(MACOSX) || defined(SOLARIS) || defined(__MINGW32__)
|
||||
// Older versions of Mac OS X didn't supply a powf function, so using it
|
||||
// will cause a binary incompatibility when trying to run a binary built
|
||||
// on a newer OS X release on an older one. And Solaris 8 doesn't provide
|
||||
// powf, floorf, fabsf etc. at all.
|
||||
// Cross-compiled MinGW32 toolchains suffer from a cross-compile bug in
|
||||
// libstdc++. math/stubs.o should be empty, but it comes with a symbol for
|
||||
// powf, resulting in a linker error because of multiple definitions.
|
||||
// Hence we re-define them here. The only potential drawback is that it
|
||||
// might be a little bit slower this way.
|
||||
#define powf(x,y) ((float)pow(x,y))
|
||||
#define floorf(x) ((float)floor(x))
|
||||
#define fabsf(x) ((float)fabs(x))
|
||||
#endif
|
||||
|
||||
#define FIXEDPOINT_MAKE(x, point) ((Bit32u)((1 << point) * x))
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
//Amplitude time velocity follow exponential coefficients
|
||||
static const double tvcatconst[5] = {0.0, 0.002791309, 0.005942882, 0.012652792, 0.026938637};
|
||||
static const double tvcatmult[5] = {1.0, 1.072662811, 1.169129367, 1.288579123, 1.229630539};
|
||||
|
||||
// These are division constants for the TVF depth key follow
|
||||
static const Bit32u depexp[5] = {3000, 950, 485, 255, 138};
|
||||
|
||||
//Envelope time keyfollow exponential coefficients
|
||||
static const double tkcatconst[5] = {0.0, 0.005853144, 0.011148054, 0.019086143, 0.043333215};
|
||||
static const double tkcatmult[5] = {1.0, 1.058245688, 1.048488989, 1.016049301, 1.097538067};
|
||||
|
||||
// Begin filter stuff
|
||||
|
||||
// Pre-warp the coefficients of a numerator or denominator.
|
||||
// Note that a0 is assumed to be 1, so there is no wrapping
|
||||
// of it.
|
||||
static void prewarp(double *a1, double *a2, double fc, double fs) {
|
||||
double wp;
|
||||
|
||||
wp = 2.0 * fs * tan(DOUBLE_PI * fc / fs);
|
||||
|
||||
*a2 = *a2 / (wp * wp);
|
||||
*a1 = *a1 / wp;
|
||||
}
|
||||
|
||||
// Transform the numerator and denominator coefficients
|
||||
// of s-domain biquad section into corresponding
|
||||
// z-domain coefficients.
|
||||
//
|
||||
// Store the 4 IIR coefficients in array pointed by coef
|
||||
// in following order:
|
||||
// beta1, beta2 (denominator)
|
||||
// alpha1, alpha2 (numerator)
|
||||
//
|
||||
// Arguments:
|
||||
// a0-a2 - s-domain numerator coefficients
|
||||
// b0-b2 - s-domain denominator coefficients
|
||||
// k - filter gain factor. initially set to 1
|
||||
// and modified by each biquad section in such
|
||||
// a way, as to make it the coefficient by
|
||||
// which to multiply the overall filter gain
|
||||
// in order to achieve a desired overall filter gain,
|
||||
// specified in initial value of k.
|
||||
// fs - sampling rate (Hz)
|
||||
// coef - array of z-domain coefficients to be filled in.
|
||||
//
|
||||
// Return:
|
||||
// On return, set coef z-domain coefficients
|
||||
static void bilinear(double a0, double a1, double a2, double b0, double b1, double b2, double *k, double fs, float *coef) {
|
||||
double ad, bd;
|
||||
|
||||
// alpha (Numerator in s-domain)
|
||||
ad = 4. * a2 * fs * fs + 2. * a1 * fs + a0;
|
||||
// beta (Denominator in s-domain)
|
||||
bd = 4. * b2 * fs * fs + 2. * b1* fs + b0;
|
||||
|
||||
// update gain constant for this section
|
||||
*k *= ad/bd;
|
||||
|
||||
// Denominator
|
||||
*coef++ = (float)((2. * b0 - 8. * b2 * fs * fs) / bd); // beta1
|
||||
*coef++ = (float)((4. * b2 * fs * fs - 2. * b1 * fs + b0) / bd); // beta2
|
||||
|
||||
// Nominator
|
||||
*coef++ = (float)((2. * a0 - 8. * a2 * fs * fs) / ad); // alpha1
|
||||
*coef = (float)((4. * a2 * fs * fs - 2. * a1 * fs + a0) / ad); // alpha2
|
||||
}
|
||||
|
||||
// a0-a2: numerator coefficients
|
||||
// b0-b2: denominator coefficients
|
||||
// fc: Filter cutoff frequency
|
||||
// fs: sampling rate
|
||||
// k: overall gain factor
|
||||
// coef: pointer to 4 iir coefficients
|
||||
static void szxform(double *a0, double *a1, double *a2, double *b0, double *b1, double *b2, double fc, double fs, double *k, float *coef) {
|
||||
// Calculate a1 and a2 and overwrite the original values
|
||||
prewarp(a1, a2, fc, fs);
|
||||
prewarp(b1, b2, fc, fs);
|
||||
bilinear(*a0, *a1, *a2, *b0, *b1, *b2, k, fs, coef);
|
||||
}
|
||||
|
||||
static void initFilter(float fs, float fc, float *icoeff, float Q) {
|
||||
float *coef;
|
||||
double a0, a1, a2, b0, b1, b2;
|
||||
|
||||
double k = 1.5; // Set overall filter gain factor
|
||||
coef = icoeff + 1; // Skip k, or gain
|
||||
|
||||
// Section 1
|
||||
a0 = 1.0;
|
||||
a1 = 0;
|
||||
a2 = 0;
|
||||
b0 = 1.0;
|
||||
b1 = 0.765367 / Q; // Divide by resonance or Q
|
||||
b2 = 1.0;
|
||||
szxform(&a0, &a1, &a2, &b0, &b1, &b2, fc, fs, &k, coef);
|
||||
coef += 4; // Point to next filter section
|
||||
|
||||
// Section 2
|
||||
a0 = 1.0;
|
||||
a1 = 0;
|
||||
a2 = 0;
|
||||
b0 = 1.0;
|
||||
b1 = 1.847759 / Q;
|
||||
b2 = 1.0;
|
||||
szxform(&a0, &a1, &a2, &b0, &b1, &b2, fc, fs, &k, coef);
|
||||
|
||||
icoeff[0] = (float)k;
|
||||
}
|
||||
|
||||
void Tables::initFiltCoeff(float samplerate) {
|
||||
for (int j = 0; j < FILTERGRAN; j++) {
|
||||
for (int res = 0; res < 31; res++) {
|
||||
float tres = resonanceFactor[res];
|
||||
initFilter((float)samplerate, (((float)(j+1.0)/FILTERGRAN)) * ((float)samplerate/2), filtCoeff[j][res], tres);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Tables::initEnvelopes(float samplerate) {
|
||||
for (int lf = 0; lf <= 100; lf++) {
|
||||
float elf = (float)lf;
|
||||
|
||||
// General envelope
|
||||
// This formula fits observation of the CM-32L by +/- 0.03s or so for the second time value in the filter,
|
||||
// when all other times were 0 and all levels were 100. Note that variations occur depending on the level
|
||||
// delta of the section, which we're not fully emulating.
|
||||
float seconds = powf(2.0f, (elf / 8.0f) + 7.0f) / 32768.0f;
|
||||
int samples = (int)(seconds * samplerate);
|
||||
envTime[lf] = samples;
|
||||
|
||||
// Cap on envelope times depending on the level delta
|
||||
if (elf == 0) {
|
||||
envDeltaMaxTime[lf] = 63;
|
||||
} else {
|
||||
float cap = 11.0f * (float)log(elf) + 64;
|
||||
if (cap > 100.0f) {
|
||||
cap = 100.0f;
|
||||
}
|
||||
envDeltaMaxTime[lf] = (int)cap;
|
||||
}
|
||||
|
||||
|
||||
// This (approximately) represents the time durations when the target level is 0.
|
||||
// Not sure why this is a special case, but it's seen to be from the real thing.
|
||||
seconds = powf(2, (elf / 8.0f) + 6) / 32768.0f;
|
||||
envDecayTime[lf] = (int)(seconds * samplerate);
|
||||
|
||||
// I am certain of this: Verified by hand LFO log
|
||||
lfoPeriod[lf] = (Bit32u)(((float)samplerate) / (powf(1.088883372f, (float)lf) * 0.021236044f));
|
||||
}
|
||||
}
|
||||
|
||||
void Tables::initMT32ConstantTables(Synth *synth) {
|
||||
int lf;
|
||||
synth->printDebug("Initializing Pitch Tables");
|
||||
for (lf = -108; lf <= 108; lf++) {
|
||||
tvfKeyfollowMult[lf + 108] = (int)(256 * powf(2.0f, (float)(lf / 24.0f)));
|
||||
//synth->printDebug("KT %d = %d", f, keytable[f+108]);
|
||||
}
|
||||
|
||||
for (int res = 0; res < 31; res++) {
|
||||
resonanceFactor[res] = powf((float)res / 30.0f, 5.0f) + 1.0f;
|
||||
}
|
||||
|
||||
int period = 65536;
|
||||
|
||||
for (int ang = 0; ang < period; ang++) {
|
||||
int halfang = (period / 2);
|
||||
int angval = ang % halfang;
|
||||
float tval = (((float)angval / (float)halfang) - 0.5f) * 2;
|
||||
if (ang >= halfang)
|
||||
tval = -tval;
|
||||
sintable[ang] = (Bit16s)(tval * 50.0f) + 50;
|
||||
}
|
||||
|
||||
int velt, dep;
|
||||
float tempdep;
|
||||
for (velt = 0; velt < 128; velt++) {
|
||||
for (dep = 0; dep < 5; dep++) {
|
||||
if (dep > 0) {
|
||||
float ff = (float)(exp(3.5f * tvcatconst[dep] * (59.0f - (float)velt)) * tvcatmult[dep]);
|
||||
tempdep = 256.0f * ff;
|
||||
envTimeVelfollowMult[dep][velt] = (int)tempdep;
|
||||
//if ((velt % 16) == 0) {
|
||||
// synth->printDebug("Key %d, depth %d, factor %d", velt, dep, (int)tempdep);
|
||||
//}
|
||||
} else
|
||||
envTimeVelfollowMult[dep][velt] = 256;
|
||||
}
|
||||
|
||||
for (dep = -7; dep < 8; dep++) {
|
||||
float fldep = (float)abs(dep) / 7.0f;
|
||||
fldep = powf(fldep,2.5f);
|
||||
if (dep < 0)
|
||||
fldep = fldep * -1.0f;
|
||||
pwVelfollowAdd[dep+7][velt] = Bit32s((fldep * (float)velt * 100) / 128.0);
|
||||
}
|
||||
}
|
||||
|
||||
for (dep = 0; dep <= 100; dep++) {
|
||||
for (velt = 0; velt < 128; velt++) {
|
||||
float fdep = (float)dep * 0.000347013f; // Another MT-32 constant
|
||||
float fv = ((float)velt - 64.0f)/7.26f;
|
||||
float flogdep = powf(10, fdep * fv);
|
||||
float fbase;
|
||||
|
||||
if (velt > 64)
|
||||
synth->tables.tvfVelfollowMult[velt][dep] = (int)(flogdep * 256.0);
|
||||
else {
|
||||
//lff = 1 - (pow(((128.0 - (float)lf) / 64.0),.25) * ((float)velt / 96));
|
||||
fbase = 1 - (powf(((float)dep / 100.0f),.25f) * ((float)(64-velt) / 96.0f));
|
||||
synth->tables.tvfVelfollowMult[velt][dep] = (int)(fbase * 256.0);
|
||||
}
|
||||
//synth->printDebug("Filvel dep %d velt %d = %x", dep, velt, filveltable[velt][dep]);
|
||||
}
|
||||
}
|
||||
|
||||
for (lf = 0; lf < 128; lf++) {
|
||||
float veloFract = lf / 127.0f;
|
||||
for (int velsens = 0; velsens <= 100; velsens++) {
|
||||
float sensFract = (velsens - 50) / 50.0f;
|
||||
if (velsens < 50) {
|
||||
tvaVelfollowMult[lf][velsens] = FIXEDPOINT_MAKE(1.0f / powf(2.0f, veloFract * -sensFract * 127.0f / 20.0f), 8);
|
||||
} else {
|
||||
tvaVelfollowMult[lf][velsens] = FIXEDPOINT_MAKE(1.0f / powf(2.0f, (1.0f - veloFract) * sensFract * 127.0f / 20.0f), 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (lf = 0; lf <= 100; lf++) {
|
||||
// Converts the 0-100 range used by the MT-32 to volume multiplier
|
||||
volumeMult[lf] = FIXEDPOINT_MAKE(powf((float)lf / 100.0f, FLOAT_LN), 7);
|
||||
}
|
||||
|
||||
for (lf = 0; lf <= 100; lf++) {
|
||||
float mv = lf / 100.0f;
|
||||
float pt = mv - 0.5f;
|
||||
if (pt < 0)
|
||||
pt = 0;
|
||||
|
||||
// Original (CC version)
|
||||
//pwFactor[lf] = (int)(pt * 210.04f) + 128;
|
||||
|
||||
// Approximation from sample comparison
|
||||
pwFactor[lf] = (int)(pt * 179.0f) + 128;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < MAX_SAMPLE_OUTPUT; i++) {
|
||||
int myRand;
|
||||
myRand = rand();
|
||||
//myRand = ((myRand - 16383) * 7168) >> 16;
|
||||
// This one is slower but works with all values of RAND_MAX
|
||||
myRand = (int)((myRand - RAND_MAX / 2) / (float)RAND_MAX * (7168 / 2));
|
||||
//FIXME:KG: Original ultimately set the lowest two bits to 0, for no obvious reason
|
||||
noiseBuf[i] = (Bit16s)myRand;
|
||||
}
|
||||
|
||||
float tdist;
|
||||
float padjtable[51];
|
||||
for (lf = 0; lf <= 50; lf++) {
|
||||
if (lf == 0)
|
||||
padjtable[lf] = 7;
|
||||
else if (lf == 1)
|
||||
padjtable[lf] = 6;
|
||||
else if (lf == 2)
|
||||
padjtable[lf] = 5;
|
||||
else if (lf == 3)
|
||||
padjtable[lf] = 4;
|
||||
else if (lf == 4)
|
||||
padjtable[lf] = 4 - (0.333333f);
|
||||
else if (lf == 5)
|
||||
padjtable[lf] = 4 - (0.333333f * 2);
|
||||
else if (lf == 6)
|
||||
padjtable[lf] = 3;
|
||||
else if ((lf > 6) && (lf <= 12)) {
|
||||
tdist = (lf-6.0f) / 6.0f;
|
||||
padjtable[lf] = 3.0f - tdist;
|
||||
} else if ((lf > 12) && (lf <= 25)) {
|
||||
tdist = (lf - 12.0f) / 13.0f;
|
||||
padjtable[lf] = 2.0f - tdist;
|
||||
} else {
|
||||
tdist = (lf - 25.0f) / 25.0f;
|
||||
padjtable[lf] = 1.0f - tdist;
|
||||
}
|
||||
//synth->printDebug("lf %d = padj %f", lf, (double)padjtable[lf]);
|
||||
}
|
||||
|
||||
float lfp, depf, finalval, tlf;
|
||||
int depat, pval, depti;
|
||||
for (lf = 0; lf <= 10; lf++) {
|
||||
// I believe the depth is cubed or something
|
||||
|
||||
for (depat = 0; depat <= 100; depat++) {
|
||||
if (lf > 0) {
|
||||
depti = abs(depat - 50);
|
||||
tlf = (float)lf - padjtable[depti];
|
||||
if (tlf < 0)
|
||||
tlf = 0;
|
||||
lfp = (float)exp(0.713619942f * tlf) / 407.4945111f;
|
||||
|
||||
if (depat < 50)
|
||||
finalval = 4096.0f * powf(2, -lfp);
|
||||
else
|
||||
finalval = 4096.0f * powf(2, lfp);
|
||||
pval = (int)finalval;
|
||||
|
||||
pitchEnvVal[lf][depat] = pval;
|
||||
//synth->printDebug("lf %d depat %d pval %d tlf %f lfp %f", lf,depat,pval, (double)tlf, (double)lfp);
|
||||
} else {
|
||||
pitchEnvVal[lf][depat] = 4096;
|
||||
//synth->printDebug("lf %d depat %d pval 4096", lf, depat);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (lf = 0; lf <= 100; lf++) {
|
||||
// It's linear - verified on MT-32 - one of the few things linear
|
||||
lfp = ((float)lf * 0.1904f) / 310.55f;
|
||||
|
||||
for (depat = 0; depat <= 100; depat++) {
|
||||
depf = ((float)depat - 50.0f) / 50.0f;
|
||||
//finalval = pow(2, lfp * depf * .5);
|
||||
finalval = 4096.0f + (4096.0f * lfp * depf);
|
||||
|
||||
pval = (int)finalval;
|
||||
|
||||
lfoShift[lf][depat] = pval;
|
||||
|
||||
//synth->printDebug("lf %d depat %d pval %x", lf,depat,pval);
|
||||
}
|
||||
}
|
||||
|
||||
for (lf = 0; lf <= 12; lf++) {
|
||||
for (int distval = 0; distval < 128; distval++) {
|
||||
float amplog, dval;
|
||||
if (lf == 0) {
|
||||
amplog = 0;
|
||||
dval = 1;
|
||||
tvaBiasMult[lf][distval] = 256;
|
||||
} else {
|
||||
/*
|
||||
amplog = powf(1.431817011f, (float)lf) / FLOAT_PI;
|
||||
dval = ((128.0f - (float)distval) / 128.0f);
|
||||
amplog = exp(amplog);
|
||||
dval = powf(amplog, dval) / amplog;
|
||||
tvaBiasMult[lf][distval] = (int)(dval * 256.0);
|
||||
*/
|
||||
// Lets assume for a second it's linear
|
||||
|
||||
// Distance of full volume reduction
|
||||
amplog = (float)(12.0f / (float)lf) * 24.0f;
|
||||
if (distval > amplog) {
|
||||
tvaBiasMult[lf][distval] = 0;
|
||||
} else {
|
||||
dval = (amplog - (float)distval) / amplog;
|
||||
tvaBiasMult[lf][distval] = (int)(dval * 256.0f);
|
||||
}
|
||||
}
|
||||
//synth->printDebug("Ampbias lf %d distval %d = %f (%x) %f", lf, distval, (double)dval, tvaBiasMult[lf][distval],(double)amplog);
|
||||
}
|
||||
}
|
||||
|
||||
for (lf = 0; lf <= 14; lf++) {
|
||||
for (int distval = 0; distval < 128; distval++) {
|
||||
float filval = fabsf((float)((lf - 7) * 12) / 7.0f);
|
||||
float amplog, dval;
|
||||
if (lf == 7) {
|
||||
amplog = 0;
|
||||
dval = 1;
|
||||
tvfBiasMult[lf][distval] = 256;
|
||||
} else {
|
||||
//amplog = pow(1.431817011, filval) / FLOAT_PI;
|
||||
amplog = powf(1.531817011f, filval) / FLOAT_PI;
|
||||
dval = (128.0f - (float)distval) / 128.0f;
|
||||
amplog = (float)exp(amplog);
|
||||
dval = powf(amplog,dval)/amplog;
|
||||
if (lf < 8) {
|
||||
tvfBiasMult[lf][distval] = (int)(dval * 256.0f);
|
||||
} else {
|
||||
dval = powf(dval, 0.3333333f);
|
||||
if (dval < 0.01f)
|
||||
dval = 0.01f;
|
||||
dval = 1 / dval;
|
||||
tvfBiasMult[lf][distval] = (int)(dval * 256.0f);
|
||||
}
|
||||
}
|
||||
//synth->printDebug("Fbias lf %d distval %d = %f (%x) %f", lf, distval, (double)dval, tvfBiasMult[lf][distval],(double)amplog);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Per-note table initialisation follows
|
||||
|
||||
static void initSaw(NoteLookup *noteLookup, Bit32s div2) {
|
||||
int tmpdiv = div2 << 16;
|
||||
for (int rsaw = 0; rsaw <= 100; rsaw++) {
|
||||
float fsaw;
|
||||
if (rsaw < 50)
|
||||
fsaw = 50.0f;
|
||||
else
|
||||
fsaw = (float)rsaw;
|
||||
|
||||
//(66 - (((A8 - 50) / 50) ^ 0.63) * 50) / 132
|
||||
float sawfact = (66.0f - (powf((fsaw - 50.0f) / 50.0f, 0.63f) * 50.0f)) / 132.0f;
|
||||
noteLookup->sawTable[rsaw] = (int)(sawfact * (float)tmpdiv) >> 16;
|
||||
//synth->printDebug("F %d divtable %d saw %d sawtable %d", f, div, rsaw, sawtable[f][rsaw]);
|
||||
}
|
||||
}
|
||||
|
||||
static void initDep(KeyLookup *keyLookup, float f) {
|
||||
for (int dep = 0; dep < 5; dep++) {
|
||||
if (dep == 0) {
|
||||
keyLookup->envDepthMult[dep] = 256;
|
||||
keyLookup->envTimeMult[dep] = 256;
|
||||
} else {
|
||||
float depfac = 3000.0f;
|
||||
float ff, tempdep;
|
||||
depfac = (float)depexp[dep];
|
||||
|
||||
ff = (f - (float)MIDDLEC) / depfac;
|
||||
tempdep = powf(2, ff) * 256.0f;
|
||||
keyLookup->envDepthMult[dep] = (int)tempdep;
|
||||
|
||||
ff = (float)(exp(tkcatconst[dep] * ((float)MIDDLEC - f)) * tkcatmult[dep]);
|
||||
keyLookup->envTimeMult[dep] = (int)(ff * 256.0f);
|
||||
}
|
||||
}
|
||||
//synth->printDebug("F %f d1 %x d2 %x d3 %x d4 %x d5 %x", (double)f, noteLookup->fildepTable[0], noteLookup->fildepTable[1], noteLookup->fildepTable[2], noteLookup->fildepTable[3], noteLookup->fildepTable[4]);
|
||||
}
|
||||
|
||||
Bit16s Tables::clampWF(Synth *synth, const char *n, float ampVal, double input) {
|
||||
Bit32s x = (Bit32s)(input * ampVal);
|
||||
if (x < -ampVal - 1) {
|
||||
synth->printDebug("%s==%d<-WGAMP-1!", n, x);
|
||||
x = (Bit32s)(-ampVal - 1);
|
||||
} else if (x > ampVal) {
|
||||
synth->printDebug("%s==%d>WGAMP!", n, x);
|
||||
x = (Bit32s)ampVal;
|
||||
}
|
||||
return (Bit16s)x;
|
||||
}
|
||||
|
||||
File *Tables::initWave(Synth *synth, NoteLookup *noteLookup, float ampVal, float div2, File *file) {
|
||||
int iDiv2 = (int)div2;
|
||||
noteLookup->waveformSize[0] = iDiv2 << 1;
|
||||
noteLookup->waveformSize[1] = iDiv2 << 1;
|
||||
noteLookup->waveformSize[2] = iDiv2 << 2;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (noteLookup->waveforms[i] == NULL) {
|
||||
noteLookup->waveforms[i] = new Bit16s[noteLookup->waveformSize[i]];
|
||||
}
|
||||
}
|
||||
if (file != NULL) {
|
||||
for (int i = 0; i < 3 && file != NULL; i++) {
|
||||
size_t len = noteLookup->waveformSize[i];
|
||||
for (unsigned int j = 0; j < len; j++) {
|
||||
if (!file->readBit16u((Bit16u *)¬eLookup->waveforms[i][j])) {
|
||||
synth->printDebug("Error reading wave file cache!");
|
||||
file->close();
|
||||
file = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (file == NULL) {
|
||||
double sd = DOUBLE_PI / div2;
|
||||
|
||||
for (int fa = 0; fa < (iDiv2 << 1); fa++) {
|
||||
// sa ranges from 0 to 2PI
|
||||
double sa = fa * sd;
|
||||
|
||||
// Calculate a sample for the bandlimited sawtooth wave
|
||||
double saw = 0.0;
|
||||
int sincs = iDiv2 >> 1;
|
||||
double sinus = 1.0;
|
||||
for (int sincNum = 1; sincNum <= sincs; sincNum++) {
|
||||
saw += sin(sinus * sa) / sinus;
|
||||
sinus++;
|
||||
}
|
||||
|
||||
// This works pretty well
|
||||
// Multiplied by 0.84 so that the spikes caused by bandlimiting don't overdrive the amplitude
|
||||
noteLookup->waveforms[0][fa] = clampWF(synth, "saw", ampVal, -saw / (0.5 * DOUBLE_PI) * 0.84);
|
||||
noteLookup->waveforms[1][fa] = clampWF(synth, "cos", ampVal, -cos(sa / 2.0));
|
||||
noteLookup->waveforms[2][fa * 2] = clampWF(synth, "cosoff_0", ampVal, -cos(sa - DOUBLE_PI));
|
||||
noteLookup->waveforms[2][fa * 2 + 1] = clampWF(synth, "cosoff_1", ampVal, -cos((sa + (sd / 2)) - DOUBLE_PI));
|
||||
}
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
static void initFiltTable(NoteLookup *noteLookup, float freq, float rate) {
|
||||
for (int tr = 0; tr <= 200; tr++) {
|
||||
float ftr = (float)tr;
|
||||
|
||||
// Verified exact on MT-32
|
||||
if (tr > 100)
|
||||
ftr = 100.0f + (powf((ftr - 100.0f) / 100.0f, 3.0f) * 100.0f);
|
||||
|
||||
// I think this is the one
|
||||
float brsq = powf(10.0f, (tr / 50.0f) - 1.0f);
|
||||
noteLookup->filtTable[0][tr] = (int)((freq * brsq) / (rate / 2) * FILTERGRAN);
|
||||
if (noteLookup->filtTable[0][tr]>=((FILTERGRAN*15)/16))
|
||||
noteLookup->filtTable[0][tr] = ((FILTERGRAN*15)/16);
|
||||
|
||||
float brsa = powf(10.0f, ((tr / 55.0f) - 1.0f)) / 2.0f;
|
||||
noteLookup->filtTable[1][tr] = (int)((freq * brsa) / (rate / 2) * FILTERGRAN);
|
||||
if (noteLookup->filtTable[1][tr]>=((FILTERGRAN*15)/16))
|
||||
noteLookup->filtTable[1][tr] = ((FILTERGRAN*15)/16);
|
||||
}
|
||||
}
|
||||
|
||||
static void initNFiltTable(NoteLookup *noteLookup, float freq, float rate) {
|
||||
for (int cf = 0; cf <= 100; cf++) {
|
||||
float cfmult = (float)cf;
|
||||
|
||||
for (int tf = 0;tf <= 100; tf++) {
|
||||
float tfadd = (float)tf;
|
||||
|
||||
//float freqsum = exp((cfmult + tfadd) / 30.0f) / 4.0f;
|
||||
//float freqsum = 0.15f * exp(0.45f * ((cfmult + tfadd) / 10.0f));
|
||||
|
||||
float freqsum = powf(2.0f, ((cfmult + tfadd) - 40.0f) / 16.0f);
|
||||
|
||||
noteLookup->nfiltTable[cf][tf] = (int)((freq * freqsum) / (rate / 2) * FILTERGRAN);
|
||||
if (noteLookup->nfiltTable[cf][tf] >= ((FILTERGRAN * 15) / 16))
|
||||
noteLookup->nfiltTable[cf][tf] = ((FILTERGRAN * 15) / 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File *Tables::initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float masterTune, PCMWaveEntry *pcmWaves, File *file) {
|
||||
float freq = (float)(masterTune * pow(2.0, ((double)note - MIDDLEA) / 12.0));
|
||||
float div2 = rate * 2.0f / freq;
|
||||
noteLookup->div2 = (int)div2;
|
||||
|
||||
if (noteLookup->div2 == 0)
|
||||
noteLookup->div2 = 1;
|
||||
|
||||
initSaw(noteLookup, noteLookup->div2);
|
||||
|
||||
//synth->printDebug("Note %f; freq=%f, div=%f", (double)note, (double)freq, (double) rate / freq);
|
||||
file = initWave(synth, noteLookup, WGAMP, div2, file);
|
||||
|
||||
// Create the pitch tables
|
||||
if (noteLookup->wavTable == NULL)
|
||||
noteLookup->wavTable = new Bit32u[synth->controlROMMap->pcmCount];
|
||||
double rateMult = 32000.0 / rate;
|
||||
double tuner = freq * 65536.0f;
|
||||
for (int pc = 0; pc < synth->controlROMMap->pcmCount; pc++) {
|
||||
noteLookup->wavTable[pc] = (int)(tuner / pcmWaves[pc].tune * rateMult);
|
||||
}
|
||||
|
||||
initFiltTable(noteLookup, freq, rate);
|
||||
initNFiltTable(noteLookup, freq, rate);
|
||||
return file;
|
||||
}
|
||||
|
||||
bool Tables::initNotes(Synth *synth, PCMWaveEntry *pcmWaves, float rate, float masterTune) {
|
||||
const char *NoteNames[12] = {
|
||||
"C ", "C#", "D ", "D#", "E ", "F ", "F#", "G ", "G#", "A ", "A#", "B "
|
||||
};
|
||||
char filename[64];
|
||||
int intRate = (int)rate;
|
||||
char version[4] = {0, 0, 0, 5};
|
||||
sprintf(filename, "waveformcache-%d-%.2f.raw", intRate, (double)masterTune);
|
||||
|
||||
File *file = NULL;
|
||||
char header[20];
|
||||
memcpy(header, "MT32WAVE", 8);
|
||||
int pos = 8;
|
||||
// Version...
|
||||
for (int i = 0; i < 4; i++)
|
||||
header[pos++] = version[i];
|
||||
header[pos++] = (char)((intRate >> 24) & 0xFF);
|
||||
header[pos++] = (char)((intRate >> 16) & 0xFF);
|
||||
header[pos++] = (char)((intRate >> 8) & 0xFF);
|
||||
header[pos++] = (char)(intRate & 0xFF);
|
||||
int intTuning = (int)masterTune;
|
||||
header[pos++] = (char)((intTuning >> 8) & 0xFF);
|
||||
header[pos++] = (char)(intTuning & 0xFF);
|
||||
header[pos++] = 0;
|
||||
header[pos] = (char)((masterTune - intTuning) * 10);
|
||||
#if MT32EMU_WAVECACHEMODE < 2
|
||||
bool reading = false;
|
||||
file = synth->openFile(filename, File::OpenMode_read);
|
||||
if (file != NULL) {
|
||||
char fileHeader[20];
|
||||
if (file->read(fileHeader, 20) == 20) {
|
||||
if (memcmp(fileHeader, header, 20) == 0) {
|
||||
Bit16u endianCheck;
|
||||
if (file->readBit16u(&endianCheck)) {
|
||||
if (endianCheck == 1) {
|
||||
reading = true;
|
||||
} else {
|
||||
synth->printDebug("Endian check in %s does not match expected", filename);
|
||||
}
|
||||
} else {
|
||||
synth->printDebug("Unable to read endian check in %s", filename);
|
||||
}
|
||||
} else {
|
||||
synth->printDebug("Header of %s does not match expected", filename);
|
||||
}
|
||||
} else {
|
||||
synth->printDebug("Error reading 16 bytes of %s", filename);
|
||||
}
|
||||
if (!reading) {
|
||||
file->close();
|
||||
file = NULL;
|
||||
}
|
||||
} else {
|
||||
synth->printDebug("Unable to open %s for reading", filename);
|
||||
}
|
||||
#endif
|
||||
|
||||
float progress = 0.0f;
|
||||
bool abort = false;
|
||||
synth->report(ReportType_progressInit, &progress);
|
||||
for (int f = LOWEST_NOTE; f <= HIGHEST_NOTE; f++) {
|
||||
synth->printDebug("Initializing note %s%d", NoteNames[f % 12], (f / 12) - 2);
|
||||
NoteLookup *noteLookup = ¬eLookups[f - LOWEST_NOTE];
|
||||
file = initNote(synth, noteLookup, (float)f, rate, masterTune, pcmWaves, file);
|
||||
progress = (f - LOWEST_NOTE + 1) / (float)NUM_NOTES;
|
||||
abort = synth->report(ReportType_progressInit, &progress) != 0;
|
||||
if (abort)
|
||||
break;
|
||||
}
|
||||
|
||||
#if MT32EMU_WAVECACHEMODE == 0 || MT32EMU_WAVECACHEMODE == 2
|
||||
if (file == NULL) {
|
||||
file = synth->openFile(filename, File::OpenMode_write);
|
||||
if (file != NULL) {
|
||||
if (file->write(header, 20) == 20 && file->writeBit16u(1)) {
|
||||
for (int f = 0; f < NUM_NOTES; f++) {
|
||||
for (int i = 0; i < 3 && file != NULL; i++) {
|
||||
int len = noteLookups[f].waveformSize[i];
|
||||
for (int j = 0; j < len; j++) {
|
||||
if (!file->writeBit16u(noteLookups[f].waveforms[i][j])) {
|
||||
synth->printDebug("Error writing waveform cache file");
|
||||
file->close();
|
||||
file = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
synth->printDebug("Error writing 16-byte header to %s - won't continue saving", filename);
|
||||
}
|
||||
} else {
|
||||
synth->printDebug("Unable to open %s for writing - won't be created", filename);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (file != NULL)
|
||||
synth->closeFile(file);
|
||||
return !abort;
|
||||
}
|
||||
|
||||
void Tables::freeNotes() {
|
||||
for (int t = 0; t < 3; t++) {
|
||||
for (int m = 0; m < NUM_NOTES; m++) {
|
||||
if (noteLookups[m].waveforms[t] != NULL) {
|
||||
delete[] noteLookups[m].waveforms[t];
|
||||
noteLookups[m].waveforms[t] = NULL;
|
||||
noteLookups[m].waveformSize[t] = 0;
|
||||
}
|
||||
if (noteLookups[m].wavTable != NULL) {
|
||||
delete[] noteLookups[m].wavTable;
|
||||
noteLookups[m].wavTable = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
initializedMasterTune = 0.0f;
|
||||
}
|
||||
using namespace MT32Emu;
|
||||
|
||||
Tables::Tables() {
|
||||
initializedSampleRate = 0.0f;
|
||||
initializedMasterTune = 0.0f;
|
||||
memset(¬eLookups, 0, sizeof(noteLookups));
|
||||
initialised = false;
|
||||
}
|
||||
|
||||
bool Tables::init(Synth *synth, PCMWaveEntry *pcmWaves, float sampleRate, float masterTune) {
|
||||
if (sampleRate <= 0.0f) {
|
||||
synth->printDebug("Bad sampleRate (%f <= 0.0f)", (double)sampleRate);
|
||||
return false;
|
||||
void Tables::init() {
|
||||
if (initialised) {
|
||||
return;
|
||||
}
|
||||
if (initializedSampleRate == 0.0f) {
|
||||
initMT32ConstantTables(synth);
|
||||
}
|
||||
if (initializedSampleRate != sampleRate) {
|
||||
initFiltCoeff(sampleRate);
|
||||
initEnvelopes(sampleRate);
|
||||
for (int key = 12; key <= 108; key++) {
|
||||
initDep(&keyLookups[key - 12], (float)key);
|
||||
}
|
||||
}
|
||||
if (initializedSampleRate != sampleRate || initializedMasterTune != masterTune) {
|
||||
freeNotes();
|
||||
if (!initNotes(synth, pcmWaves, sampleRate, masterTune)) {
|
||||
return false;
|
||||
}
|
||||
initializedSampleRate = sampleRate;
|
||||
initializedMasterTune = masterTune;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
initialised = true;
|
||||
|
||||
int lf;
|
||||
for (lf = 0; lf <= 100; lf++) {
|
||||
// CONFIRMED:KG: This matches a ROM table found by Mok
|
||||
float fVal = (2.0f - LOG10F((float)lf + 1.0f)) * 128.0f;
|
||||
int val = (int)(fVal + 1.0);
|
||||
if (val > 255) {
|
||||
val = 255;
|
||||
}
|
||||
levelToAmpSubtraction[lf] = (Bit8u)val;
|
||||
}
|
||||
|
||||
envLogarithmicTime[0] = 64;
|
||||
for (lf = 1; lf <= 255; lf++) {
|
||||
// CONFIRMED:KG: This matches a ROM table found by Mok
|
||||
envLogarithmicTime[lf] = (Bit8u)ceil(64.0f + LOG2F((float)lf) * 8.0f);
|
||||
}
|
||||
|
||||
#ifdef EMULATE_LAPC_I // Dummy #ifdef - we'll have runtime emulation mode selection in future.
|
||||
// CONFIRMED: Based on a table found by Mok in the LAPC-I control ROM
|
||||
// Note that this matches the MT-32 table, but with the values clamped to a maximum of 8.
|
||||
memset(masterVolToAmpSubtraction, 8, 71);
|
||||
memset(masterVolToAmpSubtraction + 71, 7, 3);
|
||||
memset(masterVolToAmpSubtraction + 74, 6, 4);
|
||||
memset(masterVolToAmpSubtraction + 78, 5, 3);
|
||||
memset(masterVolToAmpSubtraction + 81, 4, 4);
|
||||
memset(masterVolToAmpSubtraction + 85, 3, 3);
|
||||
memset(masterVolToAmpSubtraction + 88, 2, 4);
|
||||
memset(masterVolToAmpSubtraction + 92, 1, 4);
|
||||
memset(masterVolToAmpSubtraction + 96, 0, 5);
|
||||
#else
|
||||
// CONFIRMED: Based on a table found by Mok in the MT-32 control ROM
|
||||
masterVolToAmpSubtraction[0] = 255;
|
||||
for (int masterVol = 1; masterVol <= 100; masterVol++) {
|
||||
masterVolToAmpSubtraction[masterVol] = (int)(106.31 - 16.0f * LOG2F((float)masterVol));
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 0; i <= 100; i++) {
|
||||
pulseWidth100To255[i] = (int)(i * 255 / 100.0f + 0.5f);
|
||||
//synth->printDebug("%d: %d", i, pulseWidth100To255[i]);
|
||||
}
|
||||
|
||||
// Ratio of negative segment to wave length
|
||||
for (int i = 0; i < 128; i++) {
|
||||
// Formula determined from sample analysis.
|
||||
float pt = 0.5f / 127.0f * i;
|
||||
pulseLenFactor[i] = (1.241857812f - pt) * pt; // seems to be 2 ^ (5 / 16) = 1.241857812f
|
||||
}
|
||||
|
||||
for (int i = 0; i < 65536; i++) {
|
||||
// Aka (slightly slower): EXP2F(pitchVal / 4096.0f - 16.0f) * 32000.0f
|
||||
pitchToFreq[i] = EXP2F(i / 4096.0f - 1.034215715f);
|
||||
}
|
||||
|
||||
// found from sample analysis
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
cutoffToCosineLen[i] = EXP2F(i / -128.0f);
|
||||
}
|
||||
|
||||
// found from sample analysis
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
cutoffToFilterAmp[i] = EXP2F(-0.125f * (128.0f - i / 8.0f));
|
||||
}
|
||||
|
||||
// found from sample analysis
|
||||
for (int i = 0; i < 32; i++) {
|
||||
resAmpMax[i] = EXP2F(1.0f - (32 - i) / 4.0f);
|
||||
}
|
||||
|
||||
// found from sample analysis
|
||||
resAmpFadeFactor[7] = 1.0f / 8.0f;
|
||||
resAmpFadeFactor[6] = 2.0f / 8.0f;
|
||||
resAmpFadeFactor[5] = 3.0f / 8.0f;
|
||||
resAmpFadeFactor[4] = 5.0f / 8.0f;
|
||||
resAmpFadeFactor[3] = 8.0f / 8.0f;
|
||||
resAmpFadeFactor[2] = 12.0f / 8.0f;
|
||||
resAmpFadeFactor[1] = 16.0f / 8.0f;
|
||||
resAmpFadeFactor[0] = 31.0f / 8.0f;
|
||||
|
||||
for (int i = 0; i < 5120; i++) {
|
||||
sinf10[i] = sin(FLOAT_PI * i / 2048.0f);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
/* Copyright (c) 2003-2005 Various contributors
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* 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 Lesser General Public License for more details.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_TABLES_H
|
||||
|
@ -24,91 +20,43 @@
|
|||
|
||||
namespace MT32Emu {
|
||||
|
||||
// Mathematical constants
|
||||
const double DOUBLE_PI = 3.1415926535897932384626433832795;
|
||||
const double DOUBLE_LN = 2.3025850929940456840179914546844;
|
||||
const float FLOAT_PI = 3.1415926535897932384626433832795f;
|
||||
const float FLOAT_LN = 2.3025850929940456840179914546844f;
|
||||
|
||||
// Filter settings
|
||||
const int FILTERGRAN = 512;
|
||||
|
||||
// Amplitude of waveform generator
|
||||
// FIXME: This value is the amplitude possible whilst avoiding
|
||||
// overdriven values immediately after filtering when playing
|
||||
// back SQ3MT.MID. Needs to be checked.
|
||||
const int WGAMP = 12382;
|
||||
|
||||
const int MIDDLEC = 60;
|
||||
const int MIDDLEA = 69; // By this I mean "A above middle C"
|
||||
|
||||
// FIXME:KG: may only need to do 12 to 108
|
||||
// 12..108 is the range allowed by note on commands, but the key can be modified by pitch keyfollow
|
||||
// and adjustment for timbre pitch, so the results can be outside that range.
|
||||
// Should we move it (by octave) into the 12..108 range, or keep it in 0..127 range,
|
||||
// or something else altogether?
|
||||
const int LOWEST_NOTE = 12;
|
||||
const int HIGHEST_NOTE = 127;
|
||||
const int NUM_NOTES = HIGHEST_NOTE - LOWEST_NOTE + 1; // Number of slots for note LUT
|
||||
|
||||
class Synth;
|
||||
|
||||
struct NoteLookup {
|
||||
Bit32u div2;
|
||||
Bit32u *wavTable;
|
||||
Bit32s sawTable[101];
|
||||
int filtTable[2][201];
|
||||
int nfiltTable[101][101];
|
||||
Bit16s *waveforms[3];
|
||||
Bit32u waveformSize[3];
|
||||
};
|
||||
|
||||
struct KeyLookup {
|
||||
Bit32s envTimeMult[5]; // For envelope time adjustment for key pressed
|
||||
Bit32s envDepthMult[5];
|
||||
};
|
||||
|
||||
class Tables {
|
||||
float initializedSampleRate;
|
||||
float initializedMasterTune;
|
||||
void initMT32ConstantTables(Synth *synth);
|
||||
static Bit16s clampWF(Synth *synth, const char *n, float ampVal, double input);
|
||||
static File *initWave(Synth *synth, NoteLookup *noteLookup, float ampsize, float div2, File *file);
|
||||
bool initNotes(Synth *synth, PCMWaveEntry *pcmWaves, float rate, float tuning);
|
||||
void initEnvelopes(float sampleRate);
|
||||
void initFiltCoeff(float samplerate);
|
||||
bool initialised;
|
||||
|
||||
public:
|
||||
// Constant LUTs
|
||||
Bit32s tvfKeyfollowMult[217];
|
||||
Bit32s tvfVelfollowMult[128][101];
|
||||
Bit32s tvfBiasMult[15][128];
|
||||
Bit32u tvaVelfollowMult[128][101];
|
||||
Bit32s tvaBiasMult[13][128];
|
||||
Bit16s noiseBuf[MAX_SAMPLE_OUTPUT];
|
||||
Bit16s sintable[65536];
|
||||
Bit32s pitchEnvVal[16][101];
|
||||
Bit32s envTimeVelfollowMult[5][128];
|
||||
Bit32s pwVelfollowAdd[15][128];
|
||||
float resonanceFactor[31];
|
||||
Bit32u lfoShift[101][101];
|
||||
Bit32s pwFactor[101];
|
||||
Bit32s volumeMult[101];
|
||||
|
||||
// LUTs varying with sample rate
|
||||
Bit32u envTime[101];
|
||||
Bit32u envDeltaMaxTime[101];
|
||||
Bit32u envDecayTime[101];
|
||||
Bit32u lfoPeriod[101];
|
||||
float filtCoeff[FILTERGRAN][31][8];
|
||||
// CONFIRMED: This is used to convert several parameters to amp-modifying values in the TVA envelope:
|
||||
// - PatchTemp.outputLevel
|
||||
// - RhythmTemp.outlevel
|
||||
// - PartialParam.tva.level
|
||||
// - expression
|
||||
// It's used to determine how much to subtract from the amp envelope's target value
|
||||
Bit8u levelToAmpSubtraction[101];
|
||||
|
||||
// Various LUTs for each note and key
|
||||
NoteLookup noteLookups[NUM_NOTES];
|
||||
KeyLookup keyLookups[97];
|
||||
// CONFIRMED: ...
|
||||
Bit8u envLogarithmicTime[256];
|
||||
|
||||
// CONFIRMED: ...
|
||||
Bit8u masterVolToAmpSubtraction[101];
|
||||
|
||||
// CONFIRMED:
|
||||
Bit8u pulseWidth100To255[101];
|
||||
|
||||
float pulseLenFactor[128];
|
||||
float pitchToFreq[65536];
|
||||
float cutoffToCosineLen[1024];
|
||||
float cutoffToFilterAmp[1024];
|
||||
float resAmpMax[32];
|
||||
float resAmpFadeFactor[8];
|
||||
float sinf10[5120];
|
||||
|
||||
Tables();
|
||||
bool init(Synth *synth, PCMWaveEntry *pcmWaves, float sampleRate, float masterTune);
|
||||
File *initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float tuning, PCMWaveEntry *pcmWaves, File *file);
|
||||
void freeNotes();
|
||||
void init();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ SdlEventSource::SdlEventSource()
|
|||
: EventSource(), _scrollLock(false), _joystick(0), _lastScreenID(0), _graphicsManager(0) {
|
||||
// Reset mouse state
|
||||
memset(&_km, 0, sizeof(_km));
|
||||
/* ResidualVM doesn't support this
|
||||
|
||||
int joystick_num = ConfMan.getInt("joystick_num");
|
||||
if (joystick_num > -1) {
|
||||
// Initialize SDL joystick subsystem
|
||||
|
@ -66,13 +66,12 @@ SdlEventSource::SdlEventSource()
|
|||
debug("Using joystick: %s", SDL_JoystickName(0));
|
||||
_joystick = SDL_JoystickOpen(joystick_num);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
SdlEventSource::~SdlEventSource() {
|
||||
/* ResidualVM doesn't support this
|
||||
if (_joystick)
|
||||
SDL_JoystickClose(_joystick);*/
|
||||
SDL_JoystickClose(_joystick);
|
||||
}
|
||||
|
||||
int SdlEventSource::mapKey(SDLKey key, SDLMod mod, Uint16 unicode) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* ResidualVM - A 3D game interpreter
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ResidualVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the AUTHORS
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
|
|
@ -75,6 +75,13 @@ public:
|
|||
events.push_back(evt);
|
||||
}
|
||||
|
||||
void addEvent(const EventType evtType) {
|
||||
Event evt;
|
||||
|
||||
evt.type = evtType;
|
||||
events.push_back(evt);
|
||||
}
|
||||
|
||||
void addKeyEvent(const KeyState &ks) {
|
||||
Event evt;
|
||||
|
||||
|
@ -84,24 +91,15 @@ public:
|
|||
}
|
||||
|
||||
void addLeftClickEvent() {
|
||||
Event evt;
|
||||
|
||||
evt.type = EVENT_LBUTTONDOWN;
|
||||
addEvent(evt);
|
||||
addEvent(EVENT_LBUTTONDOWN);
|
||||
}
|
||||
|
||||
void addMiddleClickEvent() {
|
||||
Event evt;
|
||||
|
||||
evt.type = EVENT_MBUTTONDOWN;
|
||||
addEvent(evt);
|
||||
addEvent(EVENT_MBUTTONDOWN);
|
||||
}
|
||||
|
||||
void addRightClickEvent() {
|
||||
Event evt;
|
||||
|
||||
evt.type = EVENT_RBUTTONDOWN;
|
||||
addEvent(evt);
|
||||
addEvent(EVENT_RBUTTONDOWN);
|
||||
}
|
||||
|
||||
Keymap *getParent() {
|
||||
|
|
|
@ -268,6 +268,9 @@ void Keymapper::executeAction(const Action *action, bool keyDown) {
|
|||
case EVENT_MBUTTONUP:
|
||||
if (keyDown) evt.type = EVENT_MBUTTONDOWN;
|
||||
break;
|
||||
case EVENT_MAINMENU:
|
||||
if (!keyDown) evt.type = EVENT_MAINMENU;
|
||||
break;
|
||||
default:
|
||||
// don't deliver other events on key up
|
||||
if (!keyDown) continue;
|
||||
|
|
|
@ -39,7 +39,7 @@ enum {
|
|||
};
|
||||
|
||||
RemapDialog::RemapDialog()
|
||||
: Dialog("KeyMapper"), _keymapTable(0), _activeRemapAction(0), _topAction(0), _remapTimeout(0) {
|
||||
: Dialog("KeyMapper"), _keymapTable(0), _activeRemapAction(0), _topAction(0), _remapTimeout(0), _topKeymapIsGui(false) {
|
||||
|
||||
_keymapper = g_system->getEventManager()->getKeymapper();
|
||||
assert(_keymapper);
|
||||
|
@ -61,7 +61,9 @@ void RemapDialog::open() {
|
|||
const Stack<Keymapper::MapRecord> &activeKeymaps = _keymapper->getActiveStack();
|
||||
|
||||
if (activeKeymaps.size() > 0) {
|
||||
_kmPopUp->appendEntry(activeKeymaps.top().keymap->getName() + _(" (Active)"));
|
||||
if (activeKeymaps.top().keymap->getName() == Common::kGuiKeymapName)
|
||||
_topKeymapIsGui = true;
|
||||
_kmPopUp->appendEntry(activeKeymaps.top().keymap->getName() + _(" (Effective)"));
|
||||
divider = true;
|
||||
}
|
||||
|
||||
|
@ -84,6 +86,10 @@ void RemapDialog::open() {
|
|||
keymapCount += _gameKeymaps->size();
|
||||
}
|
||||
|
||||
if (activeKeymaps.size() > 1) {
|
||||
keymapCount += activeKeymaps.size() - 1;
|
||||
}
|
||||
|
||||
debug(3, "RemapDialog::open keymaps: %d", keymapCount);
|
||||
|
||||
_keymapTable = (Keymap **)malloc(sizeof(Keymap*) * keymapCount);
|
||||
|
@ -91,6 +97,18 @@ void RemapDialog::open() {
|
|||
Keymapper::Domain::iterator it;
|
||||
uint32 idx = 0;
|
||||
|
||||
if (activeKeymaps.size() > 1) {
|
||||
if (divider)
|
||||
_kmPopUp->appendEntry("");
|
||||
int topIndex = activeKeymaps.size() - 1;
|
||||
for (int i = topIndex - 1; i >= 0; --i) {
|
||||
Keymapper::MapRecord mr = activeKeymaps[i];
|
||||
_kmPopUp->appendEntry(mr.keymap->getName() + _(" (Active)"), idx);
|
||||
_keymapTable[idx++] = mr.keymap;
|
||||
}
|
||||
divider = true;
|
||||
}
|
||||
|
||||
if (_globalKeymaps) {
|
||||
if (divider)
|
||||
_kmPopUp->appendEntry("");
|
||||
|
@ -108,6 +126,7 @@ void RemapDialog::open() {
|
|||
_kmPopUp->appendEntry(it->_value->getName() + _(" (Game)"), idx);
|
||||
_keymapTable[idx++] = it->_value;
|
||||
}
|
||||
divider = true;
|
||||
}
|
||||
|
||||
_changes = false;
|
||||
|
@ -149,7 +168,7 @@ void RemapDialog::reflowLayout() {
|
|||
int labelWidth = colWidth - (keyButtonWidth + spacing + clearButtonWidth + spacing);
|
||||
|
||||
_rowCount = (areaH + spacing) / (buttonHeight + spacing);
|
||||
debug("rowCount = %d" , _rowCount);
|
||||
debug(7, "rowCount = %d" , _rowCount);
|
||||
if (colWidth <= 0 || _rowCount <= 0)
|
||||
error("Remap dialog too small to display any keymaps");
|
||||
|
||||
|
@ -307,9 +326,10 @@ void RemapDialog::loadKeymap() {
|
|||
List<const HardwareKey*> freeKeys(_keymapper->getHardwareKeys());
|
||||
|
||||
int topIndex = activeKeymaps.size() - 1;
|
||||
// skip the top gui keymap since it is for the keymapper itself
|
||||
// TODO: Don't use the keymap name as a way to discriminate GUI maps
|
||||
if (topIndex > 0 && activeKeymaps[topIndex].keymap->getName().equals(kGuiKeymapName))
|
||||
|
||||
// This is a WORKAROUND for changing the popup list selected item and changing it back
|
||||
// to the top entry. Upon changing it back, the top keymap is always "gui".
|
||||
if (!_topKeymapIsGui && activeKeymaps[topIndex].keymap->getName() == kGuiKeymapName)
|
||||
--topIndex;
|
||||
|
||||
// add most active keymap's keys
|
||||
|
|
|
@ -91,6 +91,8 @@ protected:
|
|||
|
||||
bool _changes;
|
||||
|
||||
bool _topKeymapIsGui;
|
||||
|
||||
};
|
||||
|
||||
} // End of namespace Common
|
||||
|
|
|
@ -26,23 +26,37 @@
|
|||
|
||||
#ifdef MACOSX
|
||||
|
||||
#include <AvailabilityMacros.h>
|
||||
|
||||
// HACK to disable deprecated warnings under Mac OS X 10.5. Apple deprecated the
|
||||
// With the release of Mac OS X 10.5 in October 2007, Apple deprecated the
|
||||
// AUGraphNewNode & AUGraphGetNodeInfo APIs in favor of the new AUGraphAddNode &
|
||||
// AUGraphNodeInfo APIs. While it is easy to switch to those, it breaks
|
||||
// compatibility with all pre-10.5 systems.
|
||||
// If you want to retain compatibility with old systems, enable the following
|
||||
// switch. But Apple will eventually remove these APIs, at which point the
|
||||
// switch needs to be disabled.
|
||||
//
|
||||
// Also note that only the new API is available on the iPhone!
|
||||
#define USE_DEPRECATED_COREAUDIO_API
|
||||
// Since 10.5 was the last system to support PowerPC, we use the old, deprecated
|
||||
// APIs on PowerPC based systems by default. On all other systems (such as Mac
|
||||
// OS X running on Intel hardware, or iOS running on ARM), we use the new API by
|
||||
// default.
|
||||
//
|
||||
// This leaves Mac OS X 10.4 running on x86 processors as the only system
|
||||
// combination that this code will not support by default. It seems quite
|
||||
// reasonable to assume that anybody with an Intel system has since then moved
|
||||
// on to a newer Mac OS X release. But if for some reason you absolutely need to
|
||||
// build an x86 version of this code using the old, deprecated API, you can
|
||||
// simply do so by manually enable the USE_DEPRECATED_COREAUDIO_API switch (e.g.
|
||||
// by adding setting it suitably in CPPFLAGS).
|
||||
#if !defined(USE_DEPRECATED_COREAUDIO_API)
|
||||
#if TARGET_CPU_PPC || TARGET_CPU_PPC64 || !defined(MAC_OS_X_VERSION_10_6)
|
||||
#define USE_DEPRECATED_COREAUDIO_API 1
|
||||
#else
|
||||
#define USE_DEPRECATED_COREAUDIO_API 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef USE_DEPRECATED_COREAUDIO_API
|
||||
#include <AvailabilityMacros.h>
|
||||
#undef DEPRECATED_ATTRIBUTE
|
||||
#define DEPRECATED_ATTRIBUTE
|
||||
#if USE_DEPRECATED_COREAUDIO_API
|
||||
// Try to silence warnings about use of deprecated APIs
|
||||
#undef DEPRECATED_ATTRIBUTE
|
||||
#define DEPRECATED_ATTRIBUTE
|
||||
#endif
|
||||
|
||||
|
||||
|
@ -114,7 +128,7 @@ int MidiDriver_CORE::open() {
|
|||
RequireNoErr(NewAUGraph(&_auGraph));
|
||||
|
||||
AUNode outputNode, synthNode;
|
||||
#ifdef USE_DEPRECATED_COREAUDIO_API
|
||||
#if USE_DEPRECATED_COREAUDIO_API
|
||||
ComponentDescription desc;
|
||||
#else
|
||||
AudioComponentDescription desc;
|
||||
|
@ -126,7 +140,7 @@ int MidiDriver_CORE::open() {
|
|||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
#ifdef USE_DEPRECATED_COREAUDIO_API
|
||||
#if USE_DEPRECATED_COREAUDIO_API
|
||||
RequireNoErr(AUGraphNewNode(_auGraph, &desc, 0, NULL, &outputNode));
|
||||
#else
|
||||
RequireNoErr(AUGraphAddNode(_auGraph, &desc, &outputNode));
|
||||
|
@ -136,7 +150,7 @@ int MidiDriver_CORE::open() {
|
|||
desc.componentType = kAudioUnitType_MusicDevice;
|
||||
desc.componentSubType = kAudioUnitSubType_DLSSynth;
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
#ifdef USE_DEPRECATED_COREAUDIO_API
|
||||
#if USE_DEPRECATED_COREAUDIO_API
|
||||
RequireNoErr(AUGraphNewNode(_auGraph, &desc, 0, NULL, &synthNode));
|
||||
#else
|
||||
RequireNoErr(AUGraphAddNode(_auGraph, &desc, &synthNode));
|
||||
|
@ -150,7 +164,7 @@ int MidiDriver_CORE::open() {
|
|||
RequireNoErr(AUGraphInitialize(_auGraph));
|
||||
|
||||
// Get the music device from the graph.
|
||||
#ifdef USE_DEPRECATED_COREAUDIO_API
|
||||
#if USE_DEPRECATED_COREAUDIO_API
|
||||
RequireNoErr(AUGraphGetNodeInfo(_auGraph, synthNode, NULL, NULL, NULL, &_synth));
|
||||
#else
|
||||
RequireNoErr(AUGraphNodeInfo(_auGraph, synthNode, NULL, &_synth));
|
||||
|
|
|
@ -41,7 +41,7 @@ int main(int argc, char *argv[]) {
|
|||
PluginManager::instance().addPluginProvider(new SDLPluginProvider());
|
||||
#endif
|
||||
|
||||
// Invoke the actual ResidualVM main entry point:
|
||||
// Invoke the actual ScummVM main entry point:
|
||||
int res = residualvm_main(argc, argv);
|
||||
|
||||
// Free OSystem
|
||||
|
|
|
@ -41,7 +41,7 @@ int main(int argc, char *argv[]) {
|
|||
PluginManager::instance().addPluginProvider(new SDLPluginProvider());
|
||||
#endif
|
||||
|
||||
// Invoke the actual ResidualVM main entry point:
|
||||
// Invoke the actual ScummVM main entry point:
|
||||
int res = residualvm_main(argc, argv);
|
||||
|
||||
// Free OSystem
|
||||
|
|
|
@ -54,7 +54,7 @@ int main(int argc, char *argv[]) {
|
|||
PluginManager::instance().addPluginProvider(new SDLPluginProvider());
|
||||
#endif
|
||||
|
||||
// Invoke the actual ResidualVM main entry point:
|
||||
// Invoke the actual ScummVM main entry point:
|
||||
int res = residualvm_main(argc, argv);
|
||||
|
||||
// Free OSystem
|
||||
|
|
|
@ -41,7 +41,7 @@ int main(int argc, char *argv[]) {
|
|||
PluginManager::instance().addPluginProvider(new SDLPluginProvider());
|
||||
#endif
|
||||
|
||||
// Invoke the actual ResidualVM main entry point:
|
||||
// Invoke the actual ScummVM main entry point:
|
||||
int res = residualvm_main(argc, argv);
|
||||
|
||||
// Free OSystem
|
||||
|
|
|
@ -56,7 +56,7 @@ int main(int argc, char *argv[]) {
|
|||
PluginManager::instance().addPluginProvider(new SDLPluginProvider());
|
||||
#endif
|
||||
|
||||
// Invoke the actual ResidualVM main entry point:
|
||||
// Invoke the actual ScummVM main entry point:
|
||||
int res = residualvm_main(argc, argv);
|
||||
|
||||
// Free OSystem
|
||||
|
|
|
@ -79,6 +79,7 @@ static const char HELP_STRING[] =
|
|||
" --themepath=PATH Path to where GUI themes are stored\n"
|
||||
" --list-themes Display list of all usable GUI themes\n"
|
||||
" -e, --music-driver=MODE Select music driver (see README for details)\n"
|
||||
" --list-audio-devices List all available audio devices\n"
|
||||
" -q, --language=LANG Select language (en,de,fr,it,pt,es,jp,zh,kr,se,gb,\n"
|
||||
" hb,ru,cz)\n"
|
||||
" -m, --music-volume=NUM Set the music volume, 0-255 (default: 192)\n"
|
||||
|
@ -87,10 +88,19 @@ static const char HELP_STRING[] =
|
|||
" --midi-gain=NUM Set the gain for MIDI playback, 0-1000 (default:\n"
|
||||
" 100) (only supported by some MIDI drivers)\n"
|
||||
" -n, --subtitles Enable subtitles (use with games that have voice)\n"
|
||||
" -b, --boot-param=NUM Pass number to the boot script (boot param)\n"
|
||||
" -d, --debuglevel=NUM Set debug verbosity level\n"
|
||||
" --debugflags=FLAGS Enable engine specific debug flags\n"
|
||||
" (separated by commas)\n"
|
||||
" -u, --dump-scripts Enable script dumping if a directory called 'dumps'\n"
|
||||
" exists in the current directory\n"
|
||||
"\n"
|
||||
" --cdrom=NUM CD drive to play CD audio from (default: 0 = first\n"
|
||||
" drive)\n"
|
||||
" --joystick[=NUM] Enable joystick input (default: 0 = first joystick)\n"
|
||||
" --platform=WORD Specify platform of game (allowed values: 2gs, 3do,\n"
|
||||
" acorn, amiga, atari, c64, fmtowns, nes, mac, pc, pc98,\n"
|
||||
" pce, segacd, wii, windows)\n"
|
||||
" --savepath=PATH Path to where savegames are stored\n"
|
||||
" --extrapath=PATH Extra path to additional game data\n"
|
||||
" --soundfont=FILE Select the SoundFont for MIDI playback (only\n"
|
||||
|
@ -165,6 +175,8 @@ void registerDefaults() {
|
|||
ConfMan.registerDefault("platform", Common::kPlatformPC);
|
||||
ConfMan.registerDefault("language", "en");
|
||||
ConfMan.registerDefault("subtitles", false);
|
||||
ConfMan.registerDefault("boot_param", 0);
|
||||
ConfMan.registerDefault("dump_scripts", false);
|
||||
ConfMan.registerDefault("save_slot", -1);
|
||||
ConfMan.registerDefault("autosave_period", 5 * 60); // By default, trigger autosave every 5 minutes
|
||||
|
||||
|
@ -317,6 +329,9 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
|
|||
DO_OPTION('c', "config")
|
||||
END_OPTION
|
||||
|
||||
DO_OPTION_INT('b', "boot-param")
|
||||
END_OPTION
|
||||
|
||||
DO_OPTION_OPT('d', "debuglevel", "0")
|
||||
END_OPTION
|
||||
|
||||
|
@ -326,6 +341,9 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
|
|||
DO_OPTION('e', "music-driver")
|
||||
END_OPTION
|
||||
|
||||
DO_LONG_COMMAND("list-audio-devices")
|
||||
END_OPTION
|
||||
|
||||
DO_LONG_OPTION_INT("output-rate")
|
||||
END_OPTION
|
||||
|
||||
|
@ -364,6 +382,12 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
|
|||
DO_LONG_OPTION_INT("midi-gain")
|
||||
END_OPTION
|
||||
|
||||
DO_OPTION_BOOL('u', "dump-scripts")
|
||||
END_OPTION
|
||||
|
||||
DO_OPTION_OPT('x', "save-slot", "0")
|
||||
END_OPTION
|
||||
|
||||
DO_LONG_OPTION_INT("cdrom")
|
||||
END_OPTION
|
||||
|
||||
|
|
|
@ -233,6 +233,8 @@ static Common::Error runGame(const EnginePlugin *plugin, OSystem &system, const
|
|||
|
||||
static void setupGraphics(OSystem &system) {
|
||||
system.launcherInitSize(640, 400);
|
||||
if (ConfMan.hasKey("fullscreen"))
|
||||
system.setFeatureState(OSystem::kFeatureFullscreenMode, ConfMan.getBool("fullscreen"));
|
||||
|
||||
// When starting up launcher for the first time, the user might have specified
|
||||
// a --gui-theme option, to allow that option to be working, we need to initialize
|
||||
|
@ -253,7 +255,7 @@ static void setupKeymapper(OSystem &system) {
|
|||
using namespace Common;
|
||||
|
||||
Keymapper *mapper = system.getEventManager()->getKeymapper();
|
||||
Keymap *globalMap = new Keymap("global");
|
||||
Keymap *globalMap = new Keymap(kGlobalKeymapName);
|
||||
Action *act;
|
||||
HardwareKeySet *keySet;
|
||||
|
||||
|
@ -264,7 +266,7 @@ static void setupKeymapper(OSystem &system) {
|
|||
|
||||
// Now create the global keymap
|
||||
act = new Action(globalMap, "MENU", _("Menu"), kGenericActionType, kSelectKeyType);
|
||||
act->addKeyEvent(KeyState(KEYCODE_F5, ASCII_F5, 0));
|
||||
act->addEvent(EVENT_MAINMENU);
|
||||
|
||||
act = new Action(globalMap, "SKCT", _("Skip"), kGenericActionType, kActionKeyType);
|
||||
act->addKeyEvent(KeyState(KEYCODE_ESCAPE, ASCII_ESCAPE, 0));
|
||||
|
@ -281,9 +283,12 @@ static void setupKeymapper(OSystem &system) {
|
|||
act = new Action(globalMap, "REMP", _("Remap keys"), kKeyRemapActionType);
|
||||
act->addKeyEvent(KeyState(KEYCODE_F8, ASCII_F8, 0));
|
||||
|
||||
act = new Action(globalMap, "FULS", _("Toggle FullScreen"), kKeyRemapActionType);
|
||||
act->addKeyEvent(KeyState(KEYCODE_RETURN, ASCII_RETURN, KBD_ALT));
|
||||
|
||||
mapper->addGlobalKeymap(globalMap);
|
||||
|
||||
mapper->pushKeymap("global", true);
|
||||
mapper->pushKeymap(kGlobalKeymapName, true);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
#include "common/scummsys.h"
|
||||
|
||||
//
|
||||
// The residualvm main entry point, to be invoked by ports
|
||||
// The scummvm main entry point, to be invoked by ports
|
||||
//
|
||||
extern "C" int residualvm_main(int argc, const char *const argv[]);
|
||||
|
||||
|
|
|
@ -259,8 +259,6 @@ void PluginManagerUncached::init() {
|
|||
unloadAllPlugins();
|
||||
_allEnginePlugins.clear();
|
||||
|
||||
// Resize our pluginsInMem list to prevent fragmentation
|
||||
_pluginsInMem[PLUGIN_TYPE_ENGINE].resize(2);
|
||||
unloadPluginsExcept(PLUGIN_TYPE_ENGINE, NULL, false); // empty the engine plugins
|
||||
|
||||
for (ProviderList::iterator pp = _providers.begin();
|
||||
|
|
|
@ -121,4 +121,8 @@ const char *gResidualVMFeatures = ""
|
|||
#ifdef USE_FAAD
|
||||
"AAC "
|
||||
#endif
|
||||
|
||||
#ifdef USE_FREETYPE2
|
||||
"FreeType2 "
|
||||
#endif
|
||||
;
|
||||
|
|
|
@ -182,7 +182,7 @@ public:
|
|||
*
|
||||
* An observer is supposed to eat the event, with returning true, when
|
||||
* it wants to prevent other observers from receiving the event.
|
||||
* An usage example here is the keymapper:
|
||||
* A usage example here is the keymapper:
|
||||
* If it processes an Event, it should 'eat' it and create a new
|
||||
* event, which the EventDispatcher will then catch.
|
||||
*
|
||||
|
|
|
@ -130,10 +130,10 @@ SeekableReadStream *FSNode::createReadStream() const {
|
|||
|
||||
if (!_realNode->exists()) {
|
||||
warning("FSNode::createReadStream: '%s' does not exist", getName().c_str());
|
||||
return false;
|
||||
return 0;
|
||||
} else if (_realNode->isDirectory()) {
|
||||
warning("FSNode::createReadStream: '%s' is a directory", getName().c_str());
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return _realNode->createReadStream();
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
#undef ARRAYSIZE
|
||||
#endif
|
||||
|
||||
#define TRANSLATIONS_DAT_VER 2
|
||||
#define TRANSLATIONS_DAT_VER 3
|
||||
|
||||
#include "common/translation.h"
|
||||
#include "common/config-manager.h"
|
||||
|
@ -45,7 +45,7 @@ bool operator<(const TLanguage &l, const TLanguage &r) {
|
|||
return strcmp(l.name, r.name) < 0;
|
||||
}
|
||||
|
||||
TranslationManager::TranslationManager() : _currentLang(-1) {
|
||||
TranslationManager::TranslationManager() : _currentLang(-1), _charmap(0) {
|
||||
loadTranslationsInfoDat();
|
||||
|
||||
// Set the default language
|
||||
|
@ -53,6 +53,7 @@ TranslationManager::TranslationManager() : _currentLang(-1) {
|
|||
}
|
||||
|
||||
TranslationManager::~TranslationManager() {
|
||||
delete[] _charmap;
|
||||
}
|
||||
|
||||
int32 TranslationManager::findMatchingLanguage(const String &lang) {
|
||||
|
@ -289,9 +290,14 @@ void TranslationManager::loadTranslationsInfoDat() {
|
|||
// Get number of translations
|
||||
int nbTranslations = in.readUint16BE();
|
||||
|
||||
// Skip all the block sizes
|
||||
for (int i = 0; i < nbTranslations + 2; ++i)
|
||||
in.readUint16BE();
|
||||
// Get number of codepages
|
||||
int nbCodepages = in.readUint16BE();
|
||||
|
||||
// Determine where the codepages start
|
||||
_charmapStart = 0;
|
||||
for (int i = 0; i < nbTranslations + 3; ++i)
|
||||
_charmapStart += in.readUint16BE();
|
||||
_charmapStart += in.pos();
|
||||
|
||||
// Read list of languages
|
||||
_langs.resize(nbTranslations);
|
||||
|
@ -305,6 +311,14 @@ void TranslationManager::loadTranslationsInfoDat() {
|
|||
_langNames[i] = String(buf, len - 1);
|
||||
}
|
||||
|
||||
// Read list of codepages
|
||||
_charmaps.resize(nbCodepages);
|
||||
for (int i = 0; i < nbCodepages; ++i) {
|
||||
len = in.readUint16BE();
|
||||
in.read(buf, len);
|
||||
_charmaps[i] = String(buf, len - 1);
|
||||
}
|
||||
|
||||
// Read messages
|
||||
int numMessages = in.readUint16BE();
|
||||
_messageIds.resize(numMessages);
|
||||
|
@ -344,9 +358,16 @@ void TranslationManager::loadLanguageDat(int index) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Get the number of codepages
|
||||
int nbCodepages = in.readUint16BE();
|
||||
if (nbCodepages != (int)_charmaps.size()) {
|
||||
warning("The 'translations.dat' file has changed since starting ResidualVM. GUI translation will not be available");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get size of blocks to skip.
|
||||
int skipSize = 0;
|
||||
for (int i = 0; i < index + 2; ++i)
|
||||
for (int i = 0; i < index + 3; ++i)
|
||||
skipSize += in.readUint16BE();
|
||||
// We also need to skip the remaining block sizes
|
||||
skipSize += 2 * (nbTranslations - index);
|
||||
|
@ -380,6 +401,29 @@ void TranslationManager::loadLanguageDat(int index) {
|
|||
_currentTranslationMessages[i].msgctxt = String(buf, len - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Find the charset
|
||||
int charmapNum = -1;
|
||||
for (uint i = 0; i < _charmaps.size(); ++i) {
|
||||
if (_charmaps[i].equalsIgnoreCase(_currentCharset)) {
|
||||
charmapNum = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the new charset mapping
|
||||
if (charmapNum == -1) {
|
||||
delete[] _charmap;
|
||||
_charmap = 0;
|
||||
} else {
|
||||
if (!_charmap)
|
||||
_charmap = new uint32[256];
|
||||
|
||||
in.seek(_charmapStart + charmapNum * 256 * 4, SEEK_SET);
|
||||
for (int i = 0; i < 256; ++i)
|
||||
_charmap[i] = in.readUint32BE();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool TranslationManager::checkHeader(File &in) {
|
||||
|
|
|
@ -153,6 +153,21 @@ public:
|
|||
*/
|
||||
String getCurrentCharset() const;
|
||||
|
||||
/**
|
||||
* Returns a pointer to the current charset mapping. This mapping is a
|
||||
* codepage encoding -> unicode mapping and always 256 entries long.
|
||||
*
|
||||
* The MSB of the individual mapped (i.e. unicode) character states
|
||||
* whether the character is required for this charset. If it is set, the
|
||||
* character needs to be present in order to have the text displayed.
|
||||
* This is used in the font loading code to detect whether the font is
|
||||
* able of supporting this language.
|
||||
*
|
||||
* The return value might be 0 in case it's a default ASCII/ISO-8859-1
|
||||
* map.
|
||||
*/
|
||||
const uint32 *getCharsetMapping() const { return _charmap; }
|
||||
|
||||
/**
|
||||
* Returns currently selected translation language
|
||||
*/
|
||||
|
@ -200,11 +215,15 @@ private:
|
|||
|
||||
StringArray _langs;
|
||||
StringArray _langNames;
|
||||
StringArray _charmaps;
|
||||
|
||||
StringArray _messageIds;
|
||||
Array<PoMessageEntry> _currentTranslationMessages;
|
||||
String _currentCharset;
|
||||
int _currentLang;
|
||||
|
||||
uint32 _charmapStart;
|
||||
uint32 *_charmap;
|
||||
};
|
||||
|
||||
} // End of namespace Common
|
||||
|
|
|
@ -113,7 +113,6 @@ template<typename T> inline void SWAP(T &a, T &b) { T tmp = a; a = b; b = tmp; }
|
|||
#define GUIO4(a,b,c,d) (a b c d)
|
||||
#define GUIO5(a,b,c,d,e) (a b c d e)
|
||||
|
||||
|
||||
namespace Common {
|
||||
|
||||
/**
|
||||
|
|
89
configure
vendored
89
configure
vendored
|
@ -147,7 +147,9 @@ _win32path="C:/residualvm"
|
|||
_aos4path="Games:ResidualVM"
|
||||
_staticlibpath=/sw
|
||||
_sdlconfig=sdl-config
|
||||
_freetypeconfig=freetype-config
|
||||
_sdlpath="$PATH"
|
||||
_freetypepath="$PATH"
|
||||
_nasmpath="$PATH"
|
||||
NASMFLAGS=""
|
||||
NASM=""
|
||||
|
@ -342,6 +344,40 @@ find_sdlconfig() {
|
|||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Determine freetype-config
|
||||
#
|
||||
find_freetypeconfig() {
|
||||
echo_n "Looking for freetype-config... "
|
||||
freetypeconfigs="$_freetypeconfig"
|
||||
_freetypeconfig=
|
||||
|
||||
IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="$SEPARATOR"
|
||||
for path_dir in $_freetypepath; do
|
||||
#reset separator to parse freetypeconfigs
|
||||
IFS=":"
|
||||
for freetypeconfig in $freetypeconfigs; do
|
||||
if test -f "$path_dir/$freetypeconfig" ; then
|
||||
_freetypeconfig="$path_dir/$freetypeconfig"
|
||||
echo $_freetypeconfig
|
||||
# Save the prefix
|
||||
_freetypepath=$path_dir
|
||||
if test `basename $path_dir` = bin ; then
|
||||
_freetypepath=`dirname $path_dir`
|
||||
fi
|
||||
# break at first freetype-config found in path
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
IFS="$ac_save_ifs"
|
||||
|
||||
if test -z "$_freetypeconfig"; then
|
||||
echo "none found!"
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Determine extension used for executables
|
||||
#
|
||||
|
@ -779,6 +815,9 @@ Optional Libraries:
|
|||
--with-sdl-prefix=DIR Prefix where the sdl-config script is
|
||||
installed (optional)
|
||||
|
||||
--with-freetype-prefix=DIR Prefix where the freetype-config script is
|
||||
installed (optional)
|
||||
|
||||
--with-nasm-prefix=DIR Prefix where nasm executable is installed (optional)
|
||||
--disable-nasm disable assembly language optimizations [autodetect]
|
||||
|
||||
|
@ -836,6 +875,8 @@ for ac_option in $@; do
|
|||
--disable-fluidsynth) _fluidsynth=no ;;
|
||||
--enable-readline) _readline=yes ;;
|
||||
--disable-readline) _readline=no ;;
|
||||
--enable-freetype2) _freetype2=yes ;;
|
||||
--disable-freetype2) _freetype2=no ;;
|
||||
--enable-taskbar) _taskbar=yes ;;
|
||||
--disable-taskbar) _taskbar=no ;;
|
||||
--enable-updates) _updates=yes ;;
|
||||
|
@ -973,6 +1014,10 @@ for ac_option in $@; do
|
|||
arg=`echo $ac_option | cut -d '=' -f 2`
|
||||
_sdlpath="$arg:$arg/bin"
|
||||
;;
|
||||
--with-freetype2-prefix=*)
|
||||
arg=`echo $ac_option | cut -d '=' -f 2`
|
||||
_freetypepath="$arg:$arg/bin"
|
||||
;;
|
||||
--with-nasm-prefix=*)
|
||||
arg=`echo $ac_option | cut -d '=' -f 2`
|
||||
_nasmpath="$arg:$arg/bin"
|
||||
|
@ -3394,6 +3439,50 @@ define_in_config_h_if_yes "$_libunity" 'USE_TASKBAR_UNITY'
|
|||
fi
|
||||
echo "$_libunity"
|
||||
|
||||
#
|
||||
# Check for FreeType2 to be present
|
||||
#
|
||||
if test "$_freetype2" != "no"; then
|
||||
|
||||
# Look for the freetype-config script
|
||||
find_freetypeconfig
|
||||
|
||||
if test -z "$_freetypeconfig"; then
|
||||
_freetype2=no
|
||||
else
|
||||
FREETYPE2_LIBS=`$_freetypeconfig --prefix="$_freetypepath" --libs`
|
||||
FREETYPE2_CFLAGS=`$_freetypeconfig --prefix="$_freetypepath" --cflags`
|
||||
|
||||
if test "$_freetype2" = "auto"; then
|
||||
_freetype2=no
|
||||
|
||||
cat > $TMPC << EOF
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
FT_Library library;
|
||||
FT_Error error = FT_Init_FreeType(&library);
|
||||
FT_Done_FreeType(library);
|
||||
}
|
||||
EOF
|
||||
|
||||
cc_check $FREETYPE2_CFLAGS $FREETYPE2_LIBS && _freetype2=yes
|
||||
fi
|
||||
|
||||
if test "$_freetype2" = "yes"; then
|
||||
LIBS="$LIBS $FREETYPE2_LIBS"
|
||||
INCLUDES="$INCLUDES $FREETYPE2_CFLAGS"
|
||||
fi
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
echocheck "FreeType2"
|
||||
echo "$_freetype2"
|
||||
|
||||
define_in_config_h_if_yes "$_freetype2" "USE_FREETYPE2"
|
||||
|
||||
#
|
||||
# Check for OpenGL (ES)
|
||||
#
|
||||
|
|
|
@ -576,7 +576,7 @@ int main(int argc, char *argv[]) {
|
|||
// The following are not warnings at all... We should consider adding them to
|
||||
// a different list of parameters.
|
||||
//ResidualVM: disabled:
|
||||
globalWarnings.push_back("-fno-rtti");
|
||||
// globalWarnings.push_back("-fno-rtti");
|
||||
globalWarnings.push_back("-fno-exceptions");
|
||||
globalWarnings.push_back("-fcheck-new");
|
||||
|
||||
|
|
|
@ -23,23 +23,7 @@ if "%~5"=="" goto error_installer
|
|||
echo Copying data files
|
||||
echo.
|
||||
|
||||
REM xcopy /F /Y "%~1/AUTHORS" %~2 1>NUL 2>&1
|
||||
REM xcopy /F /Y "%~1/COPYING.GPL" %~2 1>NUL 2>&1
|
||||
REM xcopy /F /Y "%~1/COPYING" %~2 1>NUL 2>&1
|
||||
REM xcopy /F /Y "%~1/COPYING.LGPL" %~2 1>NUL 2>&1
|
||||
REM xcopy /F /Y "%~1/COPYRIGHT" %~2 1>NUL 2>&1
|
||||
REM xcopy /F /Y "%~1/NEWS" %~2 1>NUL 2>&1
|
||||
REM xcopy /F /Y "%~1/README" %~2 1>NUL 2>&1
|
||||
|
||||
REM xcopy /F /Y "%~1/dists/engine-data/*.dat" %~2 1>NUL 2>&1
|
||||
REM xcopy /F /Y "%~1/dists/engine-data/*.tbl" %~2 1>NUL 2>&1
|
||||
REM xcopy /F /Y "%~1/dists/engine-data/*.cpt" %~2 1>NUL 2>&1
|
||||
REM xcopy /F /Y "%~1/gui/themes/*.zip" %~2 1>NUL 2>&1
|
||||
REM xcopy /F /Y "%~1/gui/themes/translations.dat" %~2 1>NUL 2>&1
|
||||
|
||||
xcopy /F /Y "%~4/lib/%~3/SDL.dll" "%~2" 1>NUL 2>&1
|
||||
xcopy /F /Y "%~4/README-SDL" "%~2" 1>NUL 2>&1
|
||||
|
||||
xcopy /F /Y "%~1/backends/vkeybd/packs/vkeybd_default.zip" "%~2" 1>NUL 2>&1
|
||||
|
||||
if "%~5"=="0" goto done
|
||||
|
|
|
@ -86,34 +86,34 @@ XCodeProvider::XCodeProvider(StringList &global_warnings, std::map<std::string,
|
|||
|
||||
void XCodeProvider::createWorkspace(const BuildSetup &setup) {
|
||||
// Create project folder
|
||||
std::string workspace = setup.outputDir + '/' + "scummvm.xcodeproj";
|
||||
std::string workspace = setup.outputDir + '/' + "residualvm.xcodeproj";
|
||||
|
||||
#if defined(_WIN32) || defined(WIN32)
|
||||
if (!CreateDirectory(workspace.c_str(), NULL))
|
||||
if (GetLastError() != ERROR_ALREADY_EXISTS)
|
||||
error("Could not create folder \"" + setup.outputDir + '/' + "scummvm.xcodeproj\"");
|
||||
error("Could not create folder \"" + setup.outputDir + '/' + "residualvm.xcodeproj\"");
|
||||
#else
|
||||
if (mkdir(workspace.c_str(), 0777) == -1) {
|
||||
if (errno == EEXIST) {
|
||||
// Try to open as a folder (might be a file / symbolic link)
|
||||
DIR *dirp = opendir(workspace.c_str());
|
||||
if (dirp == NULL) {
|
||||
error("Could not create folder \"" + setup.outputDir + '/' + "scummvm.xcodeproj\"");
|
||||
error("Could not create folder \"" + setup.outputDir + '/' + "residualvm.xcodeproj\"");
|
||||
} else {
|
||||
// The folder exists, just close the stream and return
|
||||
closedir(dirp);
|
||||
}
|
||||
} else {
|
||||
error("Could not create folder \"" + setup.outputDir + '/' + "scummvm.xcodeproj\"");
|
||||
error("Could not create folder \"" + setup.outputDir + '/' + "residualvm.xcodeproj\"");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Setup global objects
|
||||
setupDefines(setup);
|
||||
_targets.push_back("ScummVM-iPhone");
|
||||
_targets.push_back("ScummVM-OS X");
|
||||
_targets.push_back("ScummVM-Simulator");
|
||||
_targets.push_back("ResidualVM-iPhone");
|
||||
_targets.push_back("ResidualVM-OS X");
|
||||
_targets.push_back("ResidualVM-Simulator");
|
||||
|
||||
setupCopyFilesBuildPhase();
|
||||
setupFrameworksBuildPhase();
|
||||
|
@ -153,9 +153,9 @@ void XCodeProvider::createProjectFile(const std::string &, const std::string &,
|
|||
// Main Project file
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void XCodeProvider::ouputMainProjectFile(const BuildSetup &setup) {
|
||||
std::ofstream project((setup.outputDir + '/' + "scummvm.xcodeproj" + '/' + "project.pbxproj").c_str());
|
||||
std::ofstream project((setup.outputDir + '/' + "residualvm.xcodeproj" + '/' + "project.pbxproj").c_str());
|
||||
if (!project)
|
||||
error("Could not open \"" + setup.outputDir + '/' + "scummvm.xcodeproj" + '/' + "project.pbxproj\" for writing");
|
||||
error("Could not open \"" + setup.outputDir + '/' + "residualvm.xcodeproj" + '/' + "project.pbxproj\" for writing");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Header
|
||||
|
@ -275,7 +275,7 @@ void XCodeProvider::setupFrameworksBuildPhase() {
|
|||
frameworks_iPhone.push_back("AudioToolbox.framework");
|
||||
frameworks_iPhone.push_back("QuartzCore.framework");
|
||||
frameworks_iPhone.push_back("libmad.a");
|
||||
//frameworks_iPhone.push_back("libmpeg2.a");
|
||||
frameworks_iPhone.push_back("libmpeg2.a");
|
||||
frameworks_iPhone.push_back("libFLAC.a");
|
||||
frameworks_iPhone.push_back("libvorbisidec.a");
|
||||
frameworks_iPhone.push_back("OpenGLES.framework");
|
||||
|
@ -392,8 +392,8 @@ void XCodeProvider::setupNativeTarget() {
|
|||
target->addProperty("dependencies", "", "", SettingsNoValue|SettingsAsList);
|
||||
|
||||
target->addProperty("name", _targets[i], "", SettingsNoValue|SettingsQuoteVariable);
|
||||
target->addProperty("productName", "scummvm", "", SettingsNoValue);
|
||||
target->addProperty("productReference", getHash("PBXFileReference_ScummVM.app_" + _targets[i]), "ScummVM.app", SettingsNoValue);
|
||||
target->addProperty("productName", "residualvm", "", SettingsNoValue);
|
||||
target->addProperty("productReference", getHash("PBXFileReference_ResidualVM.app_" + _targets[i]), "ResidualVM.app", SettingsNoValue);
|
||||
target->addProperty("productType", "com.apple.product-type.application", "", SettingsNoValue|SettingsQuoteVariable);
|
||||
|
||||
_nativeTarget.add(target);
|
||||
|
@ -405,7 +405,7 @@ void XCodeProvider::setupProject() {
|
|||
|
||||
Object *project = new Object(this, "PBXProject", "PBXProject", "PBXProject", "", "Project object");
|
||||
|
||||
project->addProperty("buildConfigurationList", getHash("XCConfigurationList_scummvm"), "Build configuration list for PBXProject \"scummvm\"", SettingsNoValue);
|
||||
project->addProperty("buildConfigurationList", getHash("XCConfigurationList_residualvm"), "Build configuration list for PBXProject \"residualvm\"", SettingsNoValue);
|
||||
project->addProperty("compatibilityVersion", "Xcode 3.2", "", SettingsNoValue|SettingsQuoteVariable);
|
||||
project->addProperty("developmentRegion", "English", "", SettingsNoValue);
|
||||
project->addProperty("hasScannedForEncodings", "1", "", SettingsNoValue);
|
||||
|
@ -439,8 +439,7 @@ void XCodeProvider::setupResourcesBuildPhase() {
|
|||
|
||||
// Setup resource file properties
|
||||
std::map<std::string, FileProperty> properties;
|
||||
properties["scummclassic.zip"] = FileProperty("archive.zip", "", "scummclassic.zip", "\"<group>\"");
|
||||
properties["scummmodern.zip"] = FileProperty("archive.zip", "", "scummmodern.zip", "\"<group>\"");
|
||||
properties["modern.zip"] = FileProperty("archive.zip", "", "modern.zip", "\"<group>\"");
|
||||
|
||||
properties["kyra.dat"] = FileProperty("file", "", "kyra.dat", "\"<group>\"");
|
||||
properties["lure.dat"] = FileProperty("file", "", "lure.dat", "\"<group>\"");
|
||||
|
@ -468,8 +467,7 @@ void XCodeProvider::setupResourcesBuildPhase() {
|
|||
files.flags = SettingsAsList;
|
||||
|
||||
ValueList files_list;
|
||||
files_list.push_back("scummclassic.zip");
|
||||
files_list.push_back("scummmodern.zip");
|
||||
files_list.push_back("modern.zip");
|
||||
files_list.push_back("kyra.dat");
|
||||
files_list.push_back("lure.dat");
|
||||
files_list.push_back("queen.tbl");
|
||||
|
@ -495,8 +493,8 @@ void XCodeProvider::setupResourcesBuildPhase() {
|
|||
}
|
||||
|
||||
// Add custom files depending on the target
|
||||
if (_targets[i] == "ScummVM-OS X") {
|
||||
files.settings[getHash("PBXResources_scummvm.icns")] = Setting("", "scummvm.icns in Resources", SettingsNoValue, 0, 6);
|
||||
if (_targets[i] == "ResidualVM-OS X") {
|
||||
files.settings[getHash("PBXResources_residualvm.icns")] = Setting("", "residualvm.icns in Resources", SettingsNoValue, 0, 6);
|
||||
|
||||
// Remove 2 iphone icon files
|
||||
files.settings.erase(getHash("PBXResources_Default.png"));
|
||||
|
@ -526,7 +524,7 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
// ****************************************/
|
||||
|
||||
// Debug
|
||||
Object *iPhone_Debug_Object = new Object(this, "XCBuildConfiguration_ScummVM-iPhone_Debug", _targets[0] /* ScummVM-iPhone */, "XCBuildConfiguration", "PBXNativeTarget", "Debug");
|
||||
Object *iPhone_Debug_Object = new Object(this, "XCBuildConfiguration_ResidualVM-iPhone_Debug", _targets[0] /* ResidualVM-iPhone */, "XCBuildConfiguration", "PBXNativeTarget", "Debug");
|
||||
Property iPhone_Debug;
|
||||
ADD_SETTING_QUOTE(iPhone_Debug, "ARCHS", "$(ARCHS_UNIVERSAL_IPHONE_OS)");
|
||||
ADD_SETTING_QUOTE(iPhone_Debug, "CODE_SIGN_IDENTITY", "iPhone Developer");
|
||||
|
@ -558,7 +556,7 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
ADD_SETTING_LIST(iPhone_Debug, "LIBRARY_SEARCH_PATHS", iPhone_LibPaths, SettingsAsList, 5);
|
||||
ADD_SETTING(iPhone_Debug, "ONLY_ACTIVE_ARCH", "YES");
|
||||
ADD_SETTING(iPhone_Debug, "PREBINDING", "NO");
|
||||
ADD_SETTING(iPhone_Debug, "PRODUCT_NAME", "ScummVM");
|
||||
ADD_SETTING(iPhone_Debug, "PRODUCT_NAME", "ResidualVM");
|
||||
ADD_SETTING_QUOTE(iPhone_Debug, "PROVISIONING_PROFILE", "EF590570-5FAC-4346-9071-D609DE2B28D8");
|
||||
ADD_SETTING_QUOTE_VAR(iPhone_Debug, "PROVISIONING_PROFILE[sdk=iphoneos*]", "");
|
||||
ADD_SETTING(iPhone_Debug, "SDKROOT", "iphoneos4.0");
|
||||
|
@ -568,7 +566,7 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
iPhone_Debug_Object->properties["buildSettings"] = iPhone_Debug;
|
||||
|
||||
// Release
|
||||
Object *iPhone_Release_Object = new Object(this, "XCBuildConfiguration_ScummVM-iPhone_Release", _targets[0] /* ScummVM-iPhone */, "XCBuildConfiguration", "PBXNativeTarget", "Release");
|
||||
Object *iPhone_Release_Object = new Object(this, "XCBuildConfiguration_ResidualVM-iPhone_Release", _targets[0] /* ResidualVM-iPhone */, "XCBuildConfiguration", "PBXNativeTarget", "Release");
|
||||
Property iPhone_Release(iPhone_Debug);
|
||||
ADD_SETTING(iPhone_Release, "GCC_OPTIMIZATION_LEVEL", "3");
|
||||
ADD_SETTING(iPhone_Release, "COPY_PHASE_STRIP", "YES");
|
||||
|
@ -586,153 +584,153 @@ void XCodeProvider::setupBuildConfiguration() {
|
|||
****************************************/
|
||||
|
||||
// Debug
|
||||
Object *scummvm_Debug_Object = new Object(this, "XCBuildConfiguration_scummvm_Debug", "scummvm", "XCBuildConfiguration", "PBXProject", "Debug");
|
||||
Property scummvm_Debug;
|
||||
ADD_SETTING(scummvm_Debug, "ALWAYS_SEARCH_USER_PATHS", "NO");
|
||||
ADD_SETTING_QUOTE(scummvm_Debug, "ARCHS", "$(ARCHS_STANDARD_32_BIT)");
|
||||
ADD_SETTING_QUOTE(scummvm_Debug, "CODE_SIGN_IDENTITY", "Don't Code Sign");
|
||||
ADD_SETTING_QUOTE_VAR(scummvm_Debug, "CODE_SIGN_IDENTITY[sdk=iphoneos*]", "Don't Code Sign");
|
||||
ADD_SETTING_QUOTE(scummvm_Debug, "FRAMEWORK_SEARCH_PATHS", "");
|
||||
ADD_SETTING(scummvm_Debug, "GCC_C_LANGUAGE_STANDARD", "c99");
|
||||
ADD_SETTING(scummvm_Debug, "GCC_ENABLE_CPP_EXCEPTIONS", "NO");
|
||||
ADD_SETTING(scummvm_Debug, "GCC_ENABLE_CPP_RTTI", "NO");
|
||||
ADD_SETTING(scummvm_Debug, "GCC_INPUT_FILETYPE", "automatic");
|
||||
ADD_SETTING(scummvm_Debug, "GCC_OPTIMIZATION_LEVEL", "0");
|
||||
ValueList scummvm_defines(_defines);
|
||||
ADD_DEFINE(scummvm_defines, "IPHONE");
|
||||
ADD_DEFINE(scummvm_defines, "XCODE");
|
||||
ADD_DEFINE(scummvm_defines, "IPHONE_OFFICIAL");
|
||||
ADD_SETTING_LIST(scummvm_Debug, "GCC_PREPROCESSOR_DEFINITIONS", scummvm_defines, SettingsNoQuote|SettingsAsList, 5);
|
||||
ADD_SETTING(scummvm_Debug, "GCC_THUMB_SUPPORT", "NO");
|
||||
ADD_SETTING(scummvm_Debug, "GCC_USE_GCC3_PFE_SUPPORT", "NO");
|
||||
ADD_SETTING(scummvm_Debug, "GCC_WARN_ABOUT_RETURN_TYPE", "YES");
|
||||
ADD_SETTING(scummvm_Debug, "GCC_WARN_UNUSED_VARIABLE", "YES");
|
||||
ValueList scummvm_HeaderPaths;
|
||||
scummvm_HeaderPaths.push_back("include/");
|
||||
scummvm_HeaderPaths.push_back("../../engines/");
|
||||
scummvm_HeaderPaths.push_back("../../");
|
||||
ADD_SETTING_LIST(scummvm_Debug, "HEADER_SEARCH_PATHS", scummvm_HeaderPaths, SettingsNoQuote|SettingsAsList, 5);
|
||||
ADD_SETTING_QUOTE(scummvm_Debug, "LIBRARY_SEARCH_PATHS", "");
|
||||
ADD_SETTING(scummvm_Debug, "ONLY_ACTIVE_ARCH", "YES");
|
||||
ADD_SETTING_QUOTE(scummvm_Debug, "OTHER_CFLAGS", "");
|
||||
ADD_SETTING_QUOTE(scummvm_Debug, "OTHER_LDFLAGS", "-lz");
|
||||
ADD_SETTING(scummvm_Debug, "PREBINDING", "NO");
|
||||
ADD_SETTING(scummvm_Debug, "SDKROOT", "macosx10.6");
|
||||
Object *residualvm_Debug_Object = new Object(this, "XCBuildConfiguration_residualvm_Debug", "residualvm", "XCBuildConfiguration", "PBXProject", "Debug");
|
||||
Property residualvm_Debug;
|
||||
ADD_SETTING(residualvm_Debug, "ALWAYS_SEARCH_USER_PATHS", "NO");
|
||||
ADD_SETTING_QUOTE(residualvm_Debug, "ARCHS", "$(ARCHS_STANDARD_32_BIT)");
|
||||
ADD_SETTING_QUOTE(residualvm_Debug, "CODE_SIGN_IDENTITY", "Don't Code Sign");
|
||||
ADD_SETTING_QUOTE_VAR(residualvm_Debug, "CODE_SIGN_IDENTITY[sdk=iphoneos*]", "Don't Code Sign");
|
||||
ADD_SETTING_QUOTE(residualvm_Debug, "FRAMEWORK_SEARCH_PATHS", "");
|
||||
ADD_SETTING(residualvm_Debug, "GCC_C_LANGUAGE_STANDARD", "c99");
|
||||
ADD_SETTING(residualvm_Debug, "GCC_ENABLE_CPP_EXCEPTIONS", "NO");
|
||||
ADD_SETTING(residualvm_Debug, "GCC_ENABLE_CPP_RTTI", "NO");
|
||||
ADD_SETTING(residualvm_Debug, "GCC_INPUT_FILETYPE", "automatic");
|
||||
ADD_SETTING(residualvm_Debug, "GCC_OPTIMIZATION_LEVEL", "0");
|
||||
ValueList residualvm_defines(_defines);
|
||||
ADD_DEFINE(residualvm_defines, "IPHONE");
|
||||
ADD_DEFINE(residualvm_defines, "XCODE");
|
||||
ADD_DEFINE(residualvm_defines, "IPHONE_OFFICIAL");
|
||||
ADD_SETTING_LIST(residualvm_Debug, "GCC_PREPROCESSOR_DEFINITIONS", residualvm_defines, SettingsNoQuote|SettingsAsList, 5);
|
||||
ADD_SETTING(residualvm_Debug, "GCC_THUMB_SUPPORT", "NO");
|
||||
ADD_SETTING(residualvm_Debug, "GCC_USE_GCC3_PFE_SUPPORT", "NO");
|
||||
ADD_SETTING(residualvm_Debug, "GCC_WARN_ABOUT_RETURN_TYPE", "YES");
|
||||
ADD_SETTING(residualvm_Debug, "GCC_WARN_UNUSED_VARIABLE", "YES");
|
||||
ValueList residualvm_HeaderPaths;
|
||||
residualvm_HeaderPaths.push_back("include/");
|
||||
residualvm_HeaderPaths.push_back("../../engines/");
|
||||
residualvm_HeaderPaths.push_back("../../");
|
||||
ADD_SETTING_LIST(residualvm_Debug, "HEADER_SEARCH_PATHS", residualvm_HeaderPaths, SettingsNoQuote|SettingsAsList, 5);
|
||||
ADD_SETTING_QUOTE(residualvm_Debug, "LIBRARY_SEARCH_PATHS", "");
|
||||
ADD_SETTING(residualvm_Debug, "ONLY_ACTIVE_ARCH", "YES");
|
||||
ADD_SETTING_QUOTE(residualvm_Debug, "OTHER_CFLAGS", "");
|
||||
ADD_SETTING_QUOTE(residualvm_Debug, "OTHER_LDFLAGS", "-lz");
|
||||
ADD_SETTING(residualvm_Debug, "PREBINDING", "NO");
|
||||
ADD_SETTING(residualvm_Debug, "SDKROOT", "macosx10.6");
|
||||
|
||||
scummvm_Debug_Object->addProperty("name", "Debug", "", SettingsNoValue);
|
||||
scummvm_Debug_Object->properties["buildSettings"] = scummvm_Debug;
|
||||
residualvm_Debug_Object->addProperty("name", "Debug", "", SettingsNoValue);
|
||||
residualvm_Debug_Object->properties["buildSettings"] = residualvm_Debug;
|
||||
|
||||
// Release
|
||||
Object *scummvm_Release_Object = new Object(this, "XCBuildConfiguration_scummvm_Release", "scummvm", "XCBuildConfiguration", "PBXProject", "Release");
|
||||
Property scummvm_Release(scummvm_Debug);
|
||||
REMOVE_SETTING(scummvm_Release, "GCC_C_LANGUAGE_STANDARD"); // Not sure why we remove that, or any of the other warnings
|
||||
REMOVE_SETTING(scummvm_Release, "GCC_WARN_ABOUT_RETURN_TYPE");
|
||||
REMOVE_SETTING(scummvm_Release, "GCC_WARN_UNUSED_VARIABLE");
|
||||
REMOVE_SETTING(scummvm_Release, "ONLY_ACTIVE_ARCH");
|
||||
Object *residualvm_Release_Object = new Object(this, "XCBuildConfiguration_residualvm_Release", "residualvm", "XCBuildConfiguration", "PBXProject", "Release");
|
||||
Property residualvm_Release(residualvm_Debug);
|
||||
REMOVE_SETTING(residualvm_Release, "GCC_C_LANGUAGE_STANDARD"); // Not sure why we remove that, or any of the other warnings
|
||||
REMOVE_SETTING(residualvm_Release, "GCC_WARN_ABOUT_RETURN_TYPE");
|
||||
REMOVE_SETTING(residualvm_Release, "GCC_WARN_UNUSED_VARIABLE");
|
||||
REMOVE_SETTING(residualvm_Release, "ONLY_ACTIVE_ARCH");
|
||||
|
||||
scummvm_Release_Object->addProperty("name", "Release", "", SettingsNoValue);
|
||||
scummvm_Release_Object->properties["buildSettings"] = scummvm_Release;
|
||||
residualvm_Release_Object->addProperty("name", "Release", "", SettingsNoValue);
|
||||
residualvm_Release_Object->properties["buildSettings"] = residualvm_Release;
|
||||
|
||||
_buildConfiguration.add(scummvm_Debug_Object);
|
||||
_buildConfiguration.add(scummvm_Release_Object);
|
||||
_buildConfiguration.add(residualvm_Debug_Object);
|
||||
_buildConfiguration.add(residualvm_Release_Object);
|
||||
|
||||
/****************************************
|
||||
* ScummVM-OS X
|
||||
****************************************/
|
||||
|
||||
// Debug
|
||||
Object *scummvmOSX_Debug_Object = new Object(this, "XCBuildConfiguration_ScummVM-OSX_Debug", _targets[1] /* ScummVM-OS X */, "XCBuildConfiguration", "PBXNativeTarget", "Debug");
|
||||
Property scummvmOSX_Debug;
|
||||
ADD_SETTING_QUOTE(scummvmOSX_Debug, "ARCHS", "$(NATIVE_ARCH)");
|
||||
ADD_SETTING(scummvmOSX_Debug, "COMPRESS_PNG_FILES", "NO");
|
||||
ADD_SETTING(scummvmOSX_Debug, "COPY_PHASE_STRIP", "NO");
|
||||
ADD_SETTING_QUOTE(scummvmOSX_Debug, "DEBUG_INFORMATION_FORMAT", "dwarf-with-dsym");
|
||||
ADD_SETTING_QUOTE(scummvmOSX_Debug, "FRAMEWORK_SEARCH_PATHS", "");
|
||||
ADD_SETTING(scummvmOSX_Debug, "GCC_C_LANGUAGE_STANDARD", "c99");
|
||||
ADD_SETTING(scummvmOSX_Debug, "GCC_ENABLE_CPP_EXCEPTIONS", "NO");
|
||||
ADD_SETTING(scummvmOSX_Debug, "GCC_ENABLE_CPP_RTTI", "NO");
|
||||
ADD_SETTING(scummvmOSX_Debug, "GCC_DYNAMIC_NO_PIC", "NO");
|
||||
ADD_SETTING(scummvmOSX_Debug, "GCC_ENABLE_FIX_AND_CONTINUE", "NO");
|
||||
ADD_SETTING(scummvmOSX_Debug, "GCC_OPTIMIZATION_LEVEL", "0");
|
||||
ADD_SETTING(scummvmOSX_Debug, "GCC_PRECOMPILE_PREFIX_HEADER", "NO");
|
||||
ADD_SETTING_QUOTE(scummvmOSX_Debug, "GCC_PREFIX_HEADER", "");
|
||||
ValueList scummvmOSX_defines(_defines);
|
||||
ADD_DEFINE(scummvmOSX_defines, "SDL_BACKEND");
|
||||
ADD_DEFINE(scummvmOSX_defines, "MACOSX");
|
||||
ADD_SETTING_LIST(scummvmOSX_Debug, "GCC_PREPROCESSOR_DEFINITIONS", scummvmOSX_defines, SettingsNoQuote|SettingsAsList, 5);
|
||||
ADD_SETTING_QUOTE(scummvmOSX_Debug, "GCC_VERSION", "");
|
||||
ValueList scummvmOSX_HeaderPaths;
|
||||
scummvmOSX_HeaderPaths.push_back("/opt/local/include/SDL");
|
||||
scummvmOSX_HeaderPaths.push_back("/opt/local/include");
|
||||
scummvmOSX_HeaderPaths.push_back("include/");
|
||||
scummvmOSX_HeaderPaths.push_back("../../engines/");
|
||||
scummvmOSX_HeaderPaths.push_back("../../");
|
||||
ADD_SETTING_LIST(scummvmOSX_Debug, "HEADER_SEARCH_PATHS", scummvmOSX_HeaderPaths, SettingsNoQuote|SettingsAsList, 5);
|
||||
ADD_SETTING_QUOTE(scummvmOSX_Debug, "INFOPLIST_FILE", "$(SRCROOT)/../macosx/Info.plist");
|
||||
ValueList scummvmOSX_LibPaths;
|
||||
scummvmOSX_LibPaths.push_back("/sw/lib");
|
||||
scummvmOSX_LibPaths.push_back("/opt/local/lib");
|
||||
scummvmOSX_LibPaths.push_back("\"$(inherited)\"");
|
||||
scummvmOSX_LibPaths.push_back("\"\\\\\\\"$(SRCROOT)/lib\\\\\\\"\""); // mmmh, all those slashes, it's almost Christmas \o/
|
||||
ADD_SETTING_LIST(scummvmOSX_Debug, "LIBRARY_SEARCH_PATHS", scummvmOSX_LibPaths, SettingsNoQuote|SettingsAsList, 5);
|
||||
ADD_SETTING_QUOTE(scummvmOSX_Debug, "OTHER_CFLAGS", "");
|
||||
ValueList scummvmOSX_LdFlags;
|
||||
scummvmOSX_LdFlags.push_back("-lSDLmain");
|
||||
scummvmOSX_LdFlags.push_back("-logg");
|
||||
scummvmOSX_LdFlags.push_back("-lvorbisfile");
|
||||
scummvmOSX_LdFlags.push_back("-lvorbis");
|
||||
scummvmOSX_LdFlags.push_back("-lmad");
|
||||
scummvmOSX_LdFlags.push_back("-lFLAC");
|
||||
scummvmOSX_LdFlags.push_back("-lSDL");
|
||||
scummvmOSX_LdFlags.push_back("-lz");
|
||||
ADD_SETTING_LIST(scummvmOSX_Debug, "OTHER_LDFLAGS", scummvmOSX_LdFlags, SettingsAsList, 5);
|
||||
ADD_SETTING(scummvmOSX_Debug, "PREBINDING", "NO");
|
||||
ADD_SETTING(scummvmOSX_Debug, "PRODUCT_NAME", "ScummVM");
|
||||
Object *residualvmOSX_Debug_Object = new Object(this, "XCBuildConfiguration_ResidualVM-OSX_Debug", _targets[1] /* ResidualVM-OS X */, "XCBuildConfiguration", "PBXNativeTarget", "Debug");
|
||||
Property residualvmOSX_Debug;
|
||||
ADD_SETTING_QUOTE(residualvmOSX_Debug, "ARCHS", "$(NATIVE_ARCH)");
|
||||
ADD_SETTING(residualvmOSX_Debug, "COMPRESS_PNG_FILES", "NO");
|
||||
ADD_SETTING(residualvmOSX_Debug, "COPY_PHASE_STRIP", "NO");
|
||||
ADD_SETTING_QUOTE(residualvmOSX_Debug, "DEBUG_INFORMATION_FORMAT", "dwarf-with-dsym");
|
||||
ADD_SETTING_QUOTE(residualvmOSX_Debug, "FRAMEWORK_SEARCH_PATHS", "");
|
||||
ADD_SETTING(residualvmOSX_Debug, "GCC_C_LANGUAGE_STANDARD", "c99");
|
||||
ADD_SETTING(residualvmOSX_Debug, "GCC_ENABLE_CPP_EXCEPTIONS", "NO");
|
||||
ADD_SETTING(residualvmOSX_Debug, "GCC_ENABLE_CPP_RTTI", "NO");
|
||||
ADD_SETTING(residualvmOSX_Debug, "GCC_DYNAMIC_NO_PIC", "NO");
|
||||
ADD_SETTING(residualvmOSX_Debug, "GCC_ENABLE_FIX_AND_CONTINUE", "NO");
|
||||
ADD_SETTING(residualvmOSX_Debug, "GCC_OPTIMIZATION_LEVEL", "0");
|
||||
ADD_SETTING(residualvmOSX_Debug, "GCC_PRECOMPILE_PREFIX_HEADER", "NO");
|
||||
ADD_SETTING_QUOTE(residualvmOSX_Debug, "GCC_PREFIX_HEADER", "");
|
||||
ValueList residualvmOSX_defines(_defines);
|
||||
ADD_DEFINE(residualvmOSX_defines, "SDL_BACKEND");
|
||||
ADD_DEFINE(residualvmOSX_defines, "MACOSX");
|
||||
ADD_SETTING_LIST(residualvmOSX_Debug, "GCC_PREPROCESSOR_DEFINITIONS", residualvmOSX_defines, SettingsNoQuote|SettingsAsList, 5);
|
||||
ADD_SETTING_QUOTE(residualvmOSX_Debug, "GCC_VERSION", "");
|
||||
ValueList residualvmOSX_HeaderPaths;
|
||||
residualvmOSX_HeaderPaths.push_back("/opt/local/include/SDL");
|
||||
residualvmOSX_HeaderPaths.push_back("/opt/local/include");
|
||||
residualvmOSX_HeaderPaths.push_back("include/");
|
||||
residualvmOSX_HeaderPaths.push_back("../../engines/");
|
||||
residualvmOSX_HeaderPaths.push_back("../../");
|
||||
ADD_SETTING_LIST(residualvmOSX_Debug, "HEADER_SEARCH_PATHS", residualvmOSX_HeaderPaths, SettingsNoQuote|SettingsAsList, 5);
|
||||
ADD_SETTING_QUOTE(residualvmOSX_Debug, "INFOPLIST_FILE", "$(SRCROOT)/../macosx/Info.plist");
|
||||
ValueList residualvmOSX_LibPaths;
|
||||
residualvmOSX_LibPaths.push_back("/sw/lib");
|
||||
residualvmOSX_LibPaths.push_back("/opt/local/lib");
|
||||
residualvmOSX_LibPaths.push_back("\"$(inherited)\"");
|
||||
residualvmOSX_LibPaths.push_back("\"\\\\\\\"$(SRCROOT)/lib\\\\\\\"\""); // mmmh, all those slashes, it's almost Christmas \o/
|
||||
ADD_SETTING_LIST(residualvmOSX_Debug, "LIBRARY_SEARCH_PATHS", residualvmOSX_LibPaths, SettingsNoQuote|SettingsAsList, 5);
|
||||
ADD_SETTING_QUOTE(residualvmOSX_Debug, "OTHER_CFLAGS", "");
|
||||
ValueList residualvmOSX_LdFlags;
|
||||
residualvmOSX_LdFlags.push_back("-lSDLmain");
|
||||
residualvmOSX_LdFlags.push_back("-logg");
|
||||
residualvmOSX_LdFlags.push_back("-lvorbisfile");
|
||||
residualvmOSX_LdFlags.push_back("-lvorbis");
|
||||
residualvmOSX_LdFlags.push_back("-lmad");
|
||||
residualvmOSX_LdFlags.push_back("-lFLAC");
|
||||
residualvmOSX_LdFlags.push_back("-lSDL");
|
||||
residualvmOSX_LdFlags.push_back("-lz");
|
||||
ADD_SETTING_LIST(residualvmOSX_Debug, "OTHER_LDFLAGS", residualvmOSX_LdFlags, SettingsAsList, 5);
|
||||
ADD_SETTING(residualvmOSX_Debug, "PREBINDING", "NO");
|
||||
ADD_SETTING(residualvmOSX_Debug, "PRODUCT_NAME", "ResidualVM");
|
||||
|
||||
scummvmOSX_Debug_Object->addProperty("name", "Debug", "", SettingsNoValue);
|
||||
scummvmOSX_Debug_Object->properties["buildSettings"] = scummvmOSX_Debug;
|
||||
residualvmOSX_Debug_Object->addProperty("name", "Debug", "", SettingsNoValue);
|
||||
residualvmOSX_Debug_Object->properties["buildSettings"] = residualvmOSX_Debug;
|
||||
|
||||
// Release
|
||||
Object *scummvmOSX_Release_Object = new Object(this, "XCBuildConfiguration_ScummVMOSX_Release", _targets[1] /* ScummVM-OS X */, "XCBuildConfiguration", "PBXNativeTarget", "Release");
|
||||
Property scummvmOSX_Release(scummvmOSX_Debug);
|
||||
ADD_SETTING(scummvmOSX_Release, "COPY_PHASE_STRIP", "YES");
|
||||
REMOVE_SETTING(scummvmOSX_Release, "GCC_DYNAMIC_NO_PIC");
|
||||
REMOVE_SETTING(scummvmOSX_Release, "GCC_OPTIMIZATION_LEVEL");
|
||||
ADD_SETTING(scummvmOSX_Release, "WRAPPER_EXTENSION", "app");
|
||||
Object *residualvmOSX_Release_Object = new Object(this, "XCBuildConfiguration_ResidualVMOSX_Release", _targets[1] /* ResidualVM-OS X */, "XCBuildConfiguration", "PBXNativeTarget", "Release");
|
||||
Property residualvmOSX_Release(residualvmOSX_Debug);
|
||||
ADD_SETTING(residualvmOSX_Release, "COPY_PHASE_STRIP", "YES");
|
||||
REMOVE_SETTING(residualvmOSX_Release, "GCC_DYNAMIC_NO_PIC");
|
||||
REMOVE_SETTING(residualvmOSX_Release, "GCC_OPTIMIZATION_LEVEL");
|
||||
ADD_SETTING(residualvmOSX_Release, "WRAPPER_EXTENSION", "app");
|
||||
|
||||
scummvmOSX_Release_Object->addProperty("name", "Release", "", SettingsNoValue);
|
||||
scummvmOSX_Release_Object->properties["buildSettings"] = scummvmOSX_Release;
|
||||
residualvmOSX_Release_Object->addProperty("name", "Release", "", SettingsNoValue);
|
||||
residualvmOSX_Release_Object->properties["buildSettings"] = residualvmOSX_Release;
|
||||
|
||||
_buildConfiguration.add(scummvmOSX_Debug_Object);
|
||||
_buildConfiguration.add(scummvmOSX_Release_Object);
|
||||
_buildConfiguration.add(residualvmOSX_Debug_Object);
|
||||
_buildConfiguration.add(residualvmOSX_Release_Object);
|
||||
|
||||
/****************************************
|
||||
* ScummVM-Simulator
|
||||
* ResidualVM-Simulator
|
||||
****************************************/
|
||||
|
||||
// Debug
|
||||
Object *scummvmSimulator_Debug_Object = new Object(this, "XCBuildConfiguration_ScummVM-Simulator_Debug", _targets[2] /* ScummVM-Simulator */, "XCBuildConfiguration", "PBXNativeTarget", "Debug");
|
||||
Property scummvmSimulator_Debug(iPhone_Debug);
|
||||
ADD_SETTING_QUOTE(scummvmSimulator_Debug, "FRAMEWORK_SEARCH_PATHS", "$(inherited)");
|
||||
ADD_SETTING_LIST(scummvmSimulator_Debug, "GCC_PREPROCESSOR_DEFINITIONS", scummvm_defines, SettingsNoQuote|SettingsAsList, 5);
|
||||
ADD_SETTING(scummvmSimulator_Debug, "SDKROOT", "iphonesimulator3.2");
|
||||
REMOVE_SETTING(scummvmSimulator_Debug, "TARGETED_DEVICE_FAMILY");
|
||||
Object *residualvmSimulator_Debug_Object = new Object(this, "XCBuildConfiguration_ResidualVM-Simulator_Debug", _targets[2] /* ResidualVM-Simulator */, "XCBuildConfiguration", "PBXNativeTarget", "Debug");
|
||||
Property residualvmSimulator_Debug(iPhone_Debug);
|
||||
ADD_SETTING_QUOTE(residualvmSimulator_Debug, "FRAMEWORK_SEARCH_PATHS", "$(inherited)");
|
||||
ADD_SETTING_LIST(residualvmSimulator_Debug, "GCC_PREPROCESSOR_DEFINITIONS", residualvm_defines, SettingsNoQuote|SettingsAsList, 5);
|
||||
ADD_SETTING(residualvmSimulator_Debug, "SDKROOT", "iphonesimulator3.2");
|
||||
REMOVE_SETTING(residualvmSimulator_Debug, "TARGETED_DEVICE_FAMILY");
|
||||
|
||||
scummvmSimulator_Debug_Object->addProperty("name", "Debug", "", SettingsNoValue);
|
||||
scummvmSimulator_Debug_Object->properties["buildSettings"] = scummvmSimulator_Debug;
|
||||
residualvmSimulator_Debug_Object->addProperty("name", "Debug", "", SettingsNoValue);
|
||||
residualvmSimulator_Debug_Object->properties["buildSettings"] = residualvmSimulator_Debug;
|
||||
|
||||
// Release
|
||||
Object *scummvmSimulator_Release_Object = new Object(this, "XCBuildConfiguration_ScummVM-Simulator_Release", _targets[2] /* ScummVM-Simulator */, "XCBuildConfiguration", "PBXNativeTarget", "Release");
|
||||
Property scummvmSimulator_Release(scummvmSimulator_Debug);
|
||||
ADD_SETTING(scummvmSimulator_Release, "COPY_PHASE_STRIP", "YES");
|
||||
REMOVE_SETTING(scummvmSimulator_Release, "GCC_DYNAMIC_NO_PIC");
|
||||
ADD_SETTING(scummvmSimulator_Release, "WRAPPER_EXTENSION", "app");
|
||||
Object *residualvmSimulator_Release_Object = new Object(this, "XCBuildConfiguration_ResidualVM-Simulator_Release", _targets[2] /* ResidualVM-Simulator */, "XCBuildConfiguration", "PBXNativeTarget", "Release");
|
||||
Property residualvmSimulator_Release(residualvmSimulator_Debug);
|
||||
ADD_SETTING(residualvmSimulator_Release, "COPY_PHASE_STRIP", "YES");
|
||||
REMOVE_SETTING(residualvmSimulator_Release, "GCC_DYNAMIC_NO_PIC");
|
||||
ADD_SETTING(residualvmSimulator_Release, "WRAPPER_EXTENSION", "app");
|
||||
|
||||
scummvmSimulator_Release_Object->addProperty("name", "Release", "", SettingsNoValue);
|
||||
scummvmSimulator_Release_Object->properties["buildSettings"] = scummvmSimulator_Release;
|
||||
residualvmSimulator_Release_Object->addProperty("name", "Release", "", SettingsNoValue);
|
||||
residualvmSimulator_Release_Object->properties["buildSettings"] = residualvmSimulator_Release;
|
||||
|
||||
_buildConfiguration.add(scummvmSimulator_Debug_Object);
|
||||
_buildConfiguration.add(scummvmSimulator_Release_Object);
|
||||
_buildConfiguration.add(residualvmSimulator_Debug_Object);
|
||||
_buildConfiguration.add(residualvmSimulator_Release_Object);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Configuration List
|
||||
|
|
|
@ -26,11 +26,20 @@
|
|||
|
||||
namespace Graphics {
|
||||
|
||||
int Font::getKerningOffset(byte left, byte right) const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Font::getStringWidth(const Common::String &str) const {
|
||||
int space = 0;
|
||||
uint last = 0;
|
||||
|
||||
for (uint i = 0; i < str.size(); ++i) {
|
||||
const uint cur = str[i];
|
||||
space += getCharWidth(cur) + getKerningOffset(last, cur);
|
||||
last = cur;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < str.size(); ++i)
|
||||
space += getCharWidth(str[i]);
|
||||
return space;
|
||||
}
|
||||
|
||||
|
@ -65,17 +74,22 @@ void Font::drawString(Surface *dst, const Common::String &sOld, int x, int y, in
|
|||
// for now.
|
||||
const int halfWidth = (w - ellipsisWidth) / 2;
|
||||
int w2 = 0;
|
||||
uint last = 0;
|
||||
|
||||
for (i = 0; i < s.size(); ++i) {
|
||||
int charWidth = getCharWidth(s[i]);
|
||||
const uint cur = s[i];
|
||||
int charWidth = getCharWidth(cur) + getKerningOffset(last, cur);
|
||||
if (w2 + charWidth > halfWidth)
|
||||
break;
|
||||
last = cur;
|
||||
w2 += charWidth;
|
||||
str += s[i];
|
||||
str += cur;
|
||||
}
|
||||
|
||||
// At this point we know that the first 'i' chars are together 'w2'
|
||||
// pixels wide. We took the first i-1, and add "..." to them.
|
||||
str += "...";
|
||||
last = '.';
|
||||
|
||||
// The original string is width wide. Of those we already skipped past
|
||||
// w2 pixels, which means (width - w2) remain.
|
||||
|
@ -85,7 +99,9 @@ void Font::drawString(Surface *dst, const Common::String &sOld, int x, int y, in
|
|||
// (width + ellipsisWidth - w)
|
||||
int skip = width + ellipsisWidth - w;
|
||||
for (; i < s.size() && skip > 0; ++i) {
|
||||
skip -= getCharWidth(s[i]);
|
||||
const uint cur = s[i];
|
||||
skip -= getCharWidth(cur) + getKerningOffset(last, cur);
|
||||
last = cur;
|
||||
}
|
||||
|
||||
// Append the remaining chars, if any
|
||||
|
@ -104,8 +120,12 @@ void Font::drawString(Surface *dst, const Common::String &sOld, int x, int y, in
|
|||
x = x + w - width;
|
||||
x += deltax;
|
||||
|
||||
uint last = 0;
|
||||
for (i = 0; i < str.size(); ++i) {
|
||||
w = getCharWidth(str[i]);
|
||||
const uint cur = str[i];
|
||||
x += getKerningOffset(last, cur);
|
||||
last = cur;
|
||||
w = getCharWidth(cur);
|
||||
if (x+w > rightX)
|
||||
break;
|
||||
if (x >= leftX)
|
||||
|
@ -153,9 +173,11 @@ int Font::wordWrapText(const Common::String &str, int maxWidth, Common::Array<Co
|
|||
// of a line. If we encounter such a word, we have to wrap it over multiple
|
||||
// lines.
|
||||
|
||||
uint last = 0;
|
||||
for (Common::String::const_iterator x = str.begin(); x != str.end(); ++x) {
|
||||
const byte c = *x;
|
||||
const int w = getCharWidth(c);
|
||||
const int w = getCharWidth(c) + getKerningOffset(last, c);
|
||||
last = c;
|
||||
const bool wouldExceedWidth = (lineWidth + tmpWidth + w > maxWidth);
|
||||
|
||||
// If this char is a whitespace, then it represents a potential
|
||||
|
@ -187,8 +209,10 @@ int Font::wordWrapText(const Common::String &str, int maxWidth, Common::Array<Co
|
|||
wrapper.add(line, lineWidth);
|
||||
// Trim left side
|
||||
while (tmpStr.size() && isspace(static_cast<unsigned char>(tmpStr[0]))) {
|
||||
tmpWidth -= getCharWidth(tmpStr[0]);
|
||||
tmpStr.deleteChar(0);
|
||||
// This is not very fast, but it is the simplest way to
|
||||
// assure we do not mess something up because of kerning.
|
||||
tmpWidth = getStringWidth(tmpStr);
|
||||
}
|
||||
} else {
|
||||
wrapper.add(tmpStr, tmpWidth);
|
||||
|
|
|
@ -72,6 +72,15 @@ public:
|
|||
*/
|
||||
virtual int getCharWidth(byte chr) const = 0;
|
||||
|
||||
/**
|
||||
* Query the kerning offset between two characters.
|
||||
*
|
||||
* @param left The left character. May be 0.
|
||||
* @param right The right character. May be 0.
|
||||
* @return The horizontal displacement.
|
||||
*/
|
||||
virtual int getKerningOffset(byte left, byte right) const;
|
||||
|
||||
/**
|
||||
* Draw a character at a specific point on a surface.
|
||||
*
|
||||
|
|
483
graphics/fonts/ttf.cpp
Normal file
483
graphics/fonts/ttf.cpp
Normal file
|
@ -0,0 +1,483 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Since FreeType2 includes files, which contain forbidden symbols, we need to
|
||||
// allow all symbols here.
|
||||
#define FORBIDDEN_SYMBOL_ALLOW_ALL
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#ifdef USE_FREETYPE2
|
||||
|
||||
#include "graphics/fonts/ttf.h"
|
||||
#include "graphics/font.h"
|
||||
#include "graphics/surface.h"
|
||||
|
||||
#include "common/singleton.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/hashmap.h"
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include FT_GLYPH_H
|
||||
|
||||
namespace Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
inline int ftFloor26_6(FT_Pos x) {
|
||||
return x / 64;
|
||||
}
|
||||
|
||||
inline int ftCeil26_6(FT_Pos x) {
|
||||
return (x + 63) / 64;
|
||||
}
|
||||
|
||||
} // End of anonymous namespace
|
||||
|
||||
class TTFLibrary : public Common::Singleton<TTFLibrary> {
|
||||
public:
|
||||
TTFLibrary();
|
||||
~TTFLibrary();
|
||||
|
||||
/**
|
||||
* Check whether FreeType2 is initialized properly.
|
||||
*/
|
||||
bool isInitialized() const { return _initialized; }
|
||||
|
||||
bool loadFont(const uint8 *file, const uint32 size, FT_Face &face);
|
||||
void closeFont(FT_Face &face);
|
||||
private:
|
||||
FT_Library _library;
|
||||
bool _initialized;
|
||||
};
|
||||
|
||||
#define g_ttf ::Graphics::TTFLibrary::instance()
|
||||
|
||||
TTFLibrary::TTFLibrary() : _library(), _initialized(false) {
|
||||
if (!FT_Init_FreeType(&_library))
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
TTFLibrary::~TTFLibrary() {
|
||||
if (_initialized) {
|
||||
FT_Done_FreeType(_library);
|
||||
_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool TTFLibrary::loadFont(const uint8 *file, const uint32 size, FT_Face &face) {
|
||||
assert(_initialized);
|
||||
|
||||
return (FT_New_Memory_Face(_library, file, size, 0, &face) == 0);
|
||||
}
|
||||
|
||||
void TTFLibrary::closeFont(FT_Face &face) {
|
||||
assert(_initialized);
|
||||
|
||||
FT_Done_Face(face);
|
||||
}
|
||||
|
||||
class TTFFont : public Font {
|
||||
public:
|
||||
TTFFont();
|
||||
virtual ~TTFFont();
|
||||
|
||||
bool load(Common::SeekableReadStream &stream, int size, bool monochrome, const uint32 *mapping);
|
||||
|
||||
virtual int getFontHeight() const;
|
||||
|
||||
virtual int getMaxCharWidth() const;
|
||||
|
||||
virtual int getCharWidth(byte chr) const;
|
||||
|
||||
virtual int getKerningOffset(byte left, byte right) const;
|
||||
|
||||
virtual void drawChar(Surface *dst, byte chr, int x, int y, uint32 color) const;
|
||||
private:
|
||||
bool _initialized;
|
||||
FT_Face _face;
|
||||
|
||||
uint8 *_ttfFile;
|
||||
uint32 _size;
|
||||
|
||||
int _width, _height;
|
||||
int _ascent, _descent;
|
||||
|
||||
struct Glyph {
|
||||
Surface image;
|
||||
int xOffset, yOffset;
|
||||
int advance;
|
||||
};
|
||||
|
||||
bool cacheGlyph(Glyph &glyph, FT_UInt &slot, uint chr);
|
||||
typedef Common::HashMap<byte, Glyph> GlyphCache;
|
||||
GlyphCache _glyphs;
|
||||
|
||||
FT_UInt _glyphSlots[256];
|
||||
|
||||
bool _monochrome;
|
||||
bool _hasKerning;
|
||||
};
|
||||
|
||||
TTFFont::TTFFont()
|
||||
: _initialized(false), _face(), _ttfFile(0), _size(0), _width(0), _height(0), _ascent(0),
|
||||
_descent(0), _glyphs(), _glyphSlots(), _monochrome(false), _hasKerning(false) {
|
||||
}
|
||||
|
||||
TTFFont::~TTFFont() {
|
||||
if (_initialized) {
|
||||
g_ttf.closeFont(_face);
|
||||
|
||||
delete[] _ttfFile;
|
||||
_ttfFile = 0;
|
||||
|
||||
for (GlyphCache::iterator i = _glyphs.begin(), end = _glyphs.end(); i != end; ++i)
|
||||
i->_value.image.free();
|
||||
|
||||
_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool TTFFont::load(Common::SeekableReadStream &stream, int size, bool monochrome, const uint32 *mapping) {
|
||||
if (!g_ttf.isInitialized())
|
||||
return false;
|
||||
|
||||
_size = stream.size();
|
||||
if (!_size)
|
||||
return false;
|
||||
|
||||
_ttfFile = new uint8[_size];
|
||||
assert(_ttfFile);
|
||||
|
||||
if (stream.read(_ttfFile, _size) != _size) {
|
||||
delete[] _ttfFile;
|
||||
_ttfFile = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!g_ttf.loadFont(_ttfFile, _size, _face)) {
|
||||
delete[] _ttfFile;
|
||||
_ttfFile = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// We only support scalable fonts.
|
||||
if (!FT_IS_SCALABLE(_face)) {
|
||||
delete[] _ttfFile;
|
||||
_ttfFile = 0;
|
||||
|
||||
g_ttf.closeFont(_face);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check whether we have kerning support
|
||||
_hasKerning = (FT_HAS_KERNING(_face) != 0);
|
||||
|
||||
if (FT_Set_Char_Size(_face, 0, size * 64, 0, 0)) {
|
||||
delete[] _ttfFile;
|
||||
_ttfFile = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_monochrome = monochrome;
|
||||
|
||||
FT_Fixed yScale = _face->size->metrics.y_scale;
|
||||
_ascent = ftCeil26_6(FT_MulFix(_face->ascender, yScale));
|
||||
_descent = ftCeil26_6(FT_MulFix(_face->descender, yScale));
|
||||
|
||||
_width = ftCeil26_6(FT_MulFix(_face->max_advance_width, _face->size->metrics.x_scale));
|
||||
_height = _ascent - _descent + 1;
|
||||
|
||||
if (!mapping) {
|
||||
// Load all ISO-8859-1 characters.
|
||||
for (uint i = 0; i < 256; ++i) {
|
||||
if (!cacheGlyph(_glyphs[i], _glyphSlots[i], i))
|
||||
_glyphSlots[i] = 0;
|
||||
}
|
||||
} else {
|
||||
for (uint i = 0; i < 256; ++i) {
|
||||
const uint32 unicode = mapping[i] & 0x7FFFFFFF;
|
||||
const bool isRequired = (mapping[i] & 0x80000000) != 0;
|
||||
// Check whether loading an important glyph fails and error out if
|
||||
// that is the case.
|
||||
if (!cacheGlyph(_glyphs[i], _glyphSlots[i], unicode)) {
|
||||
_glyphSlots[i] = 0;
|
||||
if (isRequired)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_initialized = (_glyphs.size() != 0);
|
||||
return _initialized;
|
||||
}
|
||||
|
||||
int TTFFont::getFontHeight() const {
|
||||
return _height;
|
||||
}
|
||||
|
||||
int TTFFont::getMaxCharWidth() const {
|
||||
return _width;
|
||||
}
|
||||
|
||||
int TTFFont::getCharWidth(byte chr) const {
|
||||
GlyphCache::const_iterator glyphEntry = _glyphs.find(chr);
|
||||
if (glyphEntry == _glyphs.end())
|
||||
return 0;
|
||||
else
|
||||
return glyphEntry->_value.advance;
|
||||
}
|
||||
|
||||
int TTFFont::getKerningOffset(byte left, byte right) const {
|
||||
if (!_hasKerning)
|
||||
return 0;
|
||||
|
||||
FT_UInt leftGlyph = _glyphSlots[left];
|
||||
FT_UInt rightGlyph = _glyphSlots[right];
|
||||
|
||||
if (!leftGlyph || !rightGlyph)
|
||||
return 0;
|
||||
|
||||
FT_Vector kerningVector;
|
||||
FT_Get_Kerning(_face, leftGlyph, rightGlyph, FT_KERNING_DEFAULT, &kerningVector);
|
||||
return (kerningVector.x / 64);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename ColorType>
|
||||
void renderGlyph(uint8 *dstPos, const int dstPitch, const uint8 *srcPos, const int srcPitch, const int w, const int h, ColorType color, const PixelFormat &dstFormat) {
|
||||
uint8 sR, sG, sB;
|
||||
dstFormat.colorToRGB(color, sR, sG, sB);
|
||||
|
||||
for (int y = 0; y < h; ++y) {
|
||||
ColorType *rDst = (ColorType *)dstPos;
|
||||
const uint8 *src = srcPos;
|
||||
|
||||
for (int x = 0; x < w; ++x) {
|
||||
if (*src == 255) {
|
||||
*rDst = color;
|
||||
} else if (*src) {
|
||||
const uint8 a = *src;
|
||||
|
||||
uint8 dR, dG, dB;
|
||||
dstFormat.colorToRGB(*rDst, dR, dG, dB);
|
||||
|
||||
dR = ((255 - a) * dR + a * sR) / 255;
|
||||
dG = ((255 - a) * dG + a * sG) / 255;
|
||||
dB = ((255 - a) * dB + a * sB) / 255;
|
||||
|
||||
*rDst = dstFormat.RGBToColor(dR, dG, dB);
|
||||
}
|
||||
|
||||
++rDst;
|
||||
++src;
|
||||
}
|
||||
|
||||
dstPos += dstPitch;
|
||||
srcPos += srcPitch;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of anonymous namespace
|
||||
|
||||
void TTFFont::drawChar(Surface *dst, byte chr, int x, int y, uint32 color) const {
|
||||
GlyphCache::const_iterator glyphEntry = _glyphs.find(chr);
|
||||
if (glyphEntry == _glyphs.end())
|
||||
return;
|
||||
|
||||
const Glyph &glyph = glyphEntry->_value;
|
||||
|
||||
x += glyph.xOffset;
|
||||
y += glyph.yOffset;
|
||||
|
||||
if (x > dst->w)
|
||||
return;
|
||||
if (y > dst->h)
|
||||
return;
|
||||
|
||||
int w = glyph.image.w;
|
||||
int h = glyph.image.h;
|
||||
|
||||
const uint8 *srcPos = (const uint8 *)glyph.image.getBasePtr(0, 0);
|
||||
|
||||
// Make sure we are not drawing outside the screen bounds
|
||||
if (x < 0) {
|
||||
srcPos -= x;
|
||||
w += x;
|
||||
x = 0;
|
||||
}
|
||||
|
||||
if (x + w > dst->w)
|
||||
w = dst->w - x;
|
||||
|
||||
if (w <= 0)
|
||||
return;
|
||||
|
||||
if (y < 0) {
|
||||
srcPos += y * glyph.image.pitch;
|
||||
h += y;
|
||||
y = 0;
|
||||
}
|
||||
|
||||
if (y + h > dst->h)
|
||||
h = dst->h - y;
|
||||
|
||||
if (h <= 0)
|
||||
return;
|
||||
|
||||
uint8 *dstPos = (uint8 *)dst->getBasePtr(x, y);
|
||||
|
||||
if (dst->format.bytesPerPixel == 1) {
|
||||
for (int cy = 0; cy < h; ++cy) {
|
||||
uint8 *rDst = dstPos;
|
||||
const uint8 *src = srcPos;
|
||||
|
||||
for (int cx = 0; cx < w; ++cx) {
|
||||
// We assume a 1Bpp mode is a color indexed mode, thus we can
|
||||
// not take advantage of anti-aliasing here.
|
||||
if (*src >= 0x80)
|
||||
*rDst = color;
|
||||
|
||||
++rDst;
|
||||
++src;
|
||||
}
|
||||
|
||||
dstPos += dst->pitch;
|
||||
srcPos += glyph.image.pitch;
|
||||
}
|
||||
} else if (dst->format.bytesPerPixel == 2) {
|
||||
renderGlyph<uint16>(dstPos, dst->pitch, srcPos, glyph.image.pitch, w, h, color, dst->format);
|
||||
} else if (dst->format.bytesPerPixel == 4) {
|
||||
renderGlyph<uint32>(dstPos, dst->pitch, srcPos, glyph.image.pitch, w, h, color, dst->format);
|
||||
}
|
||||
}
|
||||
|
||||
bool TTFFont::cacheGlyph(Glyph &glyph, FT_UInt &slot, uint chr) {
|
||||
slot = FT_Get_Char_Index(_face, chr);
|
||||
if (!slot)
|
||||
return false;
|
||||
|
||||
// We use the light target and render mode to improve the looks of the
|
||||
// glyphs. It is most noticable in FreeSansBold.ttf, where otherwise the
|
||||
// 't' glyph looks like it is cut off on the right side.
|
||||
if (FT_Load_Glyph(_face, slot, (_monochrome ? FT_LOAD_TARGET_MONO : FT_LOAD_TARGET_LIGHT)))
|
||||
return false;
|
||||
|
||||
if (FT_Render_Glyph(_face->glyph, (_monochrome ? FT_RENDER_MODE_MONO : FT_RENDER_MODE_LIGHT)))
|
||||
return false;
|
||||
|
||||
if (_face->glyph->format != FT_GLYPH_FORMAT_BITMAP)
|
||||
return false;
|
||||
|
||||
FT_Glyph_Metrics &metrics = _face->glyph->metrics;
|
||||
|
||||
glyph.xOffset = ftFloor26_6(metrics.horiBearingX);
|
||||
int xMax = glyph.xOffset + ftCeil26_6(metrics.width);
|
||||
glyph.yOffset = _ascent - ftFloor26_6(metrics.horiBearingY);
|
||||
|
||||
glyph.advance = ftCeil26_6(metrics.horiAdvance);
|
||||
|
||||
// In case we got a negative xMin we adjust that, this might make some
|
||||
// characters make a bit odd, but it's the only way we can assure no
|
||||
// invalid memory writes with the current font API
|
||||
if (glyph.xOffset < 0) {
|
||||
xMax -= glyph.xOffset;
|
||||
glyph.xOffset = 0;
|
||||
|
||||
if (xMax > glyph.advance)
|
||||
glyph.advance = xMax;
|
||||
}
|
||||
|
||||
const FT_Bitmap &bitmap = _face->glyph->bitmap;
|
||||
glyph.image.create(bitmap.width, bitmap.rows, PixelFormat::createFormatCLUT8());
|
||||
|
||||
const uint8 *src = bitmap.buffer;
|
||||
int srcPitch = bitmap.pitch;
|
||||
if (srcPitch < 0) {
|
||||
src += (bitmap.rows - 1) * srcPitch;
|
||||
srcPitch = -srcPitch;
|
||||
}
|
||||
|
||||
uint8 *dst = (uint8 *)glyph.image.getBasePtr(0, 0);
|
||||
memset(dst, 0, glyph.image.h * glyph.image.pitch);
|
||||
|
||||
switch (bitmap.pixel_mode) {
|
||||
case FT_PIXEL_MODE_MONO:
|
||||
for (int y = 0; y < bitmap.rows; ++y) {
|
||||
const uint8 *curSrc = src;
|
||||
uint8 mask = 0;
|
||||
|
||||
for (int x = 0; x < bitmap.width; ++x) {
|
||||
if ((x % 8) == 0)
|
||||
mask = *curSrc++;
|
||||
|
||||
if (mask & 0x80)
|
||||
*dst = 255;
|
||||
|
||||
mask <<= 1;
|
||||
++dst;
|
||||
}
|
||||
|
||||
src += srcPitch;
|
||||
}
|
||||
break;
|
||||
|
||||
case FT_PIXEL_MODE_GRAY:
|
||||
for (int y = 0; y < bitmap.rows; ++y) {
|
||||
memcpy(dst, src, bitmap.width);
|
||||
dst += glyph.image.pitch;
|
||||
src += srcPitch;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
warning("TTFFont::cacheGlyph: Unsupported pixel mode %d", bitmap.pixel_mode);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Font *loadTTFFont(Common::SeekableReadStream &stream, int size, bool monochrome, const uint32 *mapping) {
|
||||
TTFFont *font = new TTFFont();
|
||||
|
||||
if (!font->load(stream, size, monochrome, mapping)) {
|
||||
delete font;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
} // End of namespace Graphics
|
||||
|
||||
namespace Common {
|
||||
DECLARE_SINGLETON(Graphics::TTFLibrary);
|
||||
} // End of namespace Common
|
||||
|
||||
#endif
|
||||
|
42
graphics/fonts/ttf.h
Normal file
42
graphics/fonts/ttf.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* 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 GRAPHICS_FONTS_TTF_H
|
||||
#define GRAPHICS_FONTS_TTF_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
|
||||
#ifdef USE_FREETYPE2
|
||||
|
||||
#include "common/stream.h"
|
||||
|
||||
namespace Graphics {
|
||||
|
||||
class Font;
|
||||
Font *loadTTFFont(Common::SeekableReadStream &stream, int size, bool monochrome = false, const uint32 *mapping = 0);
|
||||
|
||||
} // End of namespace Graphics
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -8,6 +8,8 @@ MODULE_OBJS := \
|
|||
fonts/consolefont.o \
|
||||
fonts/newfont_big.o \
|
||||
fonts/newfont.o \
|
||||
fonts/ttf.o \
|
||||
|
||||
imagedec.o \
|
||||
jpeg.o \
|
||||
primitives.o \
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include "graphics/surface.h"
|
||||
#include "graphics/VectorRenderer.h"
|
||||
#include "graphics/fonts/bdf.h"
|
||||
#include "graphics/fonts/ttf.h"
|
||||
|
||||
#include "gui/widget.h"
|
||||
#include "gui/ThemeEngine.h"
|
||||
|
@ -557,7 +558,7 @@ bool ThemeEngine::addTextData(const Common::String &drawDataId, TextData textId,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ThemeEngine::addFont(TextData textId, const Common::String &file) {
|
||||
bool ThemeEngine::addFont(TextData textId, const Common::String &file, const Common::String &scalableFile, const int pointsize) {
|
||||
if (textId == -1)
|
||||
return false;
|
||||
|
||||
|
@ -570,15 +571,21 @@ bool ThemeEngine::addFont(TextData textId, const Common::String &file) {
|
|||
_texts[textId]->_fontPtr = _font;
|
||||
} else {
|
||||
Common::String localized = FontMan.genLocalizedFontFilename(file);
|
||||
const Common::String charset
|
||||
#ifdef USE_TRANSLATION
|
||||
(TransMan.getCurrentCharset())
|
||||
#endif
|
||||
;
|
||||
|
||||
// Try localized fonts
|
||||
_texts[textId]->_fontPtr = loadFont(localized, textId == kTextDataDefault);
|
||||
_texts[textId]->_fontPtr = loadFont(localized, scalableFile, charset, pointsize, textId == kTextDataDefault);
|
||||
|
||||
if (!_texts[textId]->_fontPtr) {
|
||||
// Try standard fonts
|
||||
_texts[textId]->_fontPtr = loadFont(file, textId == kTextDataDefault);
|
||||
_texts[textId]->_fontPtr = loadFont(file, scalableFile, Common::String(), pointsize, textId == kTextDataDefault);
|
||||
|
||||
if (!_texts[textId]->_fontPtr)
|
||||
error("Couldn't load font '%s'", file.c_str());
|
||||
error("Couldn't load font '%s'/'%s'", file.c_str(), scalableFile.c_str());
|
||||
|
||||
#ifdef USE_TRANSLATION
|
||||
TransMan.setLanguage("C");
|
||||
|
@ -840,7 +847,7 @@ void ThemeEngine::queueBitmap(const Graphics::Surface *bitmap, const Common::Rec
|
|||
ThemeItemBitmap *q = new ThemeItemBitmap(this, area, bitmap, alpha);
|
||||
|
||||
if (_buffering) {
|
||||
_bufferQueue.push_back(q);
|
||||
_screenQueue.push_back(q);
|
||||
} else {
|
||||
q->drawSelf(true, false);
|
||||
delete q;
|
||||
|
@ -899,7 +906,7 @@ void ThemeEngine::drawCheckbox(const Common::Rect &r, const Common::String &str,
|
|||
r2.left = r2.right + checkBoxSize;
|
||||
r2.right = r.right;
|
||||
|
||||
queueDDText(getTextData(dd), getTextColor(dd), r2, str, false, false, _widgets[kDDCheckboxDefault]->_textAlignH, _widgets[dd]->_textAlignV);
|
||||
queueDDText(getTextData(dd), getTextColor(dd), r2, str, true, false, _widgets[kDDCheckboxDefault]->_textAlignH, _widgets[dd]->_textAlignV);
|
||||
}
|
||||
|
||||
void ThemeEngine::drawRadiobutton(const Common::Rect &r, const Common::String &str, bool checked, WidgetStateInfo state) {
|
||||
|
@ -925,7 +932,7 @@ void ThemeEngine::drawRadiobutton(const Common::Rect &r, const Common::String &s
|
|||
r2.left = r2.right + checkBoxSize;
|
||||
r2.right = r.right;
|
||||
|
||||
queueDDText(getTextData(dd), getTextColor(dd), r2, str, false, false, _widgets[kDDRadiobuttonDefault]->_textAlignH, _widgets[dd]->_textAlignV);
|
||||
queueDDText(getTextData(dd), getTextColor(dd), r2, str, true, false, _widgets[kDDRadiobuttonDefault]->_textAlignH, _widgets[dd]->_textAlignV);
|
||||
}
|
||||
|
||||
void ThemeEngine::drawSlider(const Common::Rect &r, int width, WidgetStateInfo state) {
|
||||
|
@ -1365,6 +1372,10 @@ int ThemeEngine::getCharWidth(byte c, FontStyle font) const {
|
|||
return ready() ? _texts[fontStyleToData(font)]->_fontPtr->getCharWidth(c) : 0;
|
||||
}
|
||||
|
||||
int ThemeEngine::getKerningOffset(byte left, byte right, FontStyle font) const {
|
||||
return ready() ? _texts[fontStyleToData(font)]->_fontPtr->getKerningOffset(left, right) : 0;
|
||||
}
|
||||
|
||||
TextData ThemeEngine::getTextData(DrawData ddId) const {
|
||||
return _widgets[ddId] ? (TextData)_widgets[ddId]->_textDataId : kTextDataNone;
|
||||
}
|
||||
|
@ -1385,15 +1396,48 @@ DrawData ThemeEngine::parseDrawDataId(const Common::String &name) const {
|
|||
* External data loading
|
||||
*********************************************************/
|
||||
|
||||
const Graphics::Font *ThemeEngine::loadFont(const Common::String &filename, const bool makeLocalizedFont) {
|
||||
const Graphics::Font *ThemeEngine::loadScalableFont(const Common::String &filename, const Common::String &charset, const int pointsize, Common::String &name) {
|
||||
#ifdef USE_FREETYPE2
|
||||
name = Common::String::format("%s-%s@%d", filename.c_str(), charset.c_str(), pointsize);
|
||||
|
||||
// Try already loaded fonts.
|
||||
const Graphics::Font *font = FontMan.getFontByName(filename);
|
||||
const Graphics::Font *font = FontMan.getFontByName(name);
|
||||
if (font)
|
||||
return font;
|
||||
|
||||
Common::String cacheFilename = genCacheFilename(filename);
|
||||
Common::ArchiveMemberList members;
|
||||
_themeFiles.listMatchingMembers(members, filename);
|
||||
|
||||
for (Common::ArchiveMemberList::const_iterator i = members.begin(), end = members.end(); i != end; ++i) {
|
||||
Common::SeekableReadStream *stream = (*i)->createReadStream();
|
||||
if (stream) {
|
||||
font = Graphics::loadTTFFont(*stream, pointsize, false,
|
||||
#ifdef USE_TRANSLATION
|
||||
TransMan.getCharsetMapping()
|
||||
#else
|
||||
0
|
||||
#endif
|
||||
);
|
||||
delete stream;
|
||||
|
||||
if (font)
|
||||
return font;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
const Graphics::Font *ThemeEngine::loadFont(const Common::String &filename, Common::String &name) {
|
||||
name = filename;
|
||||
|
||||
// Try already loaded fonts.
|
||||
const Graphics::Font *font = FontMan.getFontByName(name);
|
||||
if (font)
|
||||
return font;
|
||||
|
||||
Common::ArchiveMemberList members;
|
||||
const Common::String cacheFilename(genCacheFilename(filename));
|
||||
_themeFiles.listMatchingMembers(members, cacheFilename);
|
||||
_themeFiles.listMatchingMembers(members, filename);
|
||||
|
||||
|
@ -1410,20 +1454,34 @@ const Graphics::Font *ThemeEngine::loadFont(const Common::String &filename, cons
|
|||
}
|
||||
}
|
||||
delete stream;
|
||||
}
|
||||
|
||||
if (font)
|
||||
break;
|
||||
return font;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const Graphics::Font *ThemeEngine::loadFont(const Common::String &filename, const Common::String &scalableFilename, const Common::String &charset, const int pointsize, const bool makeLocalizedFont) {
|
||||
Common::String fontName;
|
||||
|
||||
const Graphics::Font *font = 0;
|
||||
|
||||
// Prefer scalable fonts over non-scalable fonts
|
||||
font = loadScalableFont(scalableFilename, charset, pointsize, fontName);
|
||||
if (!font)
|
||||
font = loadFont(filename, fontName);
|
||||
|
||||
// If the font is successfully loaded store it in the font manager.
|
||||
if (font) {
|
||||
FontMan.assignFontToName(filename, font);
|
||||
FontMan.assignFontToName(fontName, font);
|
||||
// If this font should be the new default localized font, we set it up
|
||||
// for that.
|
||||
if (makeLocalizedFont)
|
||||
FontMan.setLocalizedFont(filename);
|
||||
FontMan.setLocalizedFont(fontName);
|
||||
}
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
#include "graphics/pixelformat.h"
|
||||
|
||||
|
||||
#define RESIDUALVM_THEME_VERSION_STR "RESIDUALVM_STX0.8.7"
|
||||
#define RESIDUALVM_THEME_VERSION_STR "SCUMMVM_STX0.8.8"
|
||||
|
||||
class OSystem;
|
||||
|
||||
|
@ -310,6 +310,8 @@ public:
|
|||
|
||||
int getCharWidth(byte c, FontStyle font = kFontStyleBold) const;
|
||||
|
||||
int getKerningOffset(byte left, byte right, FontStyle font = kFontStyleBold) const;
|
||||
|
||||
//@}
|
||||
|
||||
|
||||
|
@ -411,10 +413,12 @@ public:
|
|||
* Interface for the ThemeParser class: Loads a font to use on the GUI from the given
|
||||
* filename.
|
||||
*
|
||||
* @param fontName Identifier name for the font.
|
||||
* @param file Name of the font file.
|
||||
* @param fextId Identifier name for the font.
|
||||
* @param file Filename of the non-scalable font version.
|
||||
* @param scalableFile Filename of the scalable version. (Optional)
|
||||
* @param pointsize Point size for the scalable font. (Optional)
|
||||
*/
|
||||
bool addFont(TextData textId, const Common::String &file);
|
||||
bool addFont(TextData textId, const Common::String &file, const Common::String &scalableFile, const int pointsize);
|
||||
|
||||
/**
|
||||
* Interface for the ThemeParser class: adds a text color value.
|
||||
|
@ -536,8 +540,10 @@ protected:
|
|||
*/
|
||||
void unloadTheme();
|
||||
|
||||
const Graphics::Font *loadFont(const Common::String &filename, const bool makeLocalizedFont);
|
||||
const Graphics::Font *loadScalableFont(const Common::String &filename, const Common::String &charset, const int pointsize, Common::String &name);
|
||||
const Graphics::Font *loadFont(const Common::String &filename, Common::String &name);
|
||||
Common::String genCacheFilename(const Common::String &filename) const;
|
||||
const Graphics::Font *loadFont(const Common::String &filename, const Common::String &scalableFilename, const Common::String &charset, const int pointsize, const bool makeLocalizedFont);
|
||||
|
||||
/**
|
||||
* Actual Dirty Screen handling function.
|
||||
|
|
|
@ -176,8 +176,15 @@ bool ThemeParser::parserCallback_font(ParserNode *node) {
|
|||
return true;
|
||||
}
|
||||
|
||||
// Default to a point size of 12.
|
||||
int pointsize = 12;
|
||||
if (node->values.contains("point_size")) {
|
||||
if (sscanf(node->values["point_size"].c_str(), "%d", &pointsize) != 1 || pointsize <= 0)
|
||||
return parserError(Common::String::format("Font \"%s\" has invalid point size \"%s\"", node->values["id"].c_str(), node->values["point_size"].c_str()));
|
||||
}
|
||||
|
||||
TextData textDataId = parseTextDataId(node->values["id"]);
|
||||
if (!_theme->addFont(textDataId, node->values["file"]))
|
||||
if (!_theme->addFont(textDataId, node->values["file"], node->values["scalable_file"], pointsize))
|
||||
return parserError("Error loading Font in theme engine.");
|
||||
|
||||
return true;
|
||||
|
|
|
@ -65,6 +65,8 @@ protected:
|
|||
XML_PROP(id, true)
|
||||
XML_PROP(file, true)
|
||||
XML_PROP(resolution, false)
|
||||
XML_PROP(scalable_file, false)
|
||||
XML_PROP(point_size, false)
|
||||
KEY_END()
|
||||
|
||||
XML_KEY(text_color)
|
||||
|
|
|
@ -124,6 +124,9 @@ void GuiManager::initKeymap() {
|
|||
act = new Action(guiMap, "REMP", _("Remap keys"), kKeyRemapActionType);
|
||||
act->addKeyEvent(KeyState(KEYCODE_F8, ASCII_F8, 0));
|
||||
|
||||
act = new Action(guiMap, "FULS", _("Toggle FullScreen"), kKeyRemapActionType);
|
||||
act->addKeyEvent(KeyState(KEYCODE_RETURN, ASCII_RETURN, KBD_ALT));
|
||||
|
||||
mapper->addGlobalKeymap(guiMap);
|
||||
}
|
||||
|
||||
|
@ -279,7 +282,7 @@ void GuiManager::runLoop() {
|
|||
|
||||
bool tooltipCheck = false;
|
||||
|
||||
while (!_dialogStack.empty() && activeDialog == getTopDialog()) {
|
||||
while (!_dialogStack.empty() && activeDialog == getTopDialog() && !eventMan->shouldQuit()) {
|
||||
redraw();
|
||||
|
||||
// Don't "tickle" the dialog until the theme has had a chance
|
||||
|
@ -358,8 +361,6 @@ void GuiManager::runLoop() {
|
|||
case Common::EVENT_WHEELDOWN:
|
||||
activeDialog->handleMouseWheel(mouse.x, mouse.y, 1);
|
||||
break;
|
||||
case Common::EVENT_QUIT:
|
||||
return;
|
||||
case Common::EVENT_SCREEN_CHANGED:
|
||||
screenChange();
|
||||
break;
|
||||
|
@ -388,6 +389,17 @@ void GuiManager::runLoop() {
|
|||
_system->delayMillis(10);
|
||||
}
|
||||
|
||||
// WORKAROUND: When quitting we might not properly close the dialogs on
|
||||
// the dialog stack, thus we do this here to avoid any problems.
|
||||
// This is most noticable in bug #3481395 "LAUNCHER: Can't quit from unsupported game dialog".
|
||||
// It seems that Dialog::runModal never removes the dialog from the dialog
|
||||
// stack, thus if the dialog does not call Dialog::close to close itself
|
||||
// it will never be removed. Since we can have multiple run loops being
|
||||
// called we cannot rely on catching EVENT_QUIT in the event loop above,
|
||||
// since it would only catch it for the top run loop.
|
||||
if (eventMan->shouldQuit() && activeDialog == getTopDialog())
|
||||
getTopDialog()->close();
|
||||
|
||||
if (didSaveState) {
|
||||
_theme->disable();
|
||||
restoreState();
|
||||
|
|
|
@ -81,6 +81,7 @@ public:
|
|||
int getFontHeight(ThemeEngine::FontStyle style = ThemeEngine::kFontStyleBold) const { return _theme->getFontHeight(style); }
|
||||
int getStringWidth(const Common::String &str, ThemeEngine::FontStyle style = ThemeEngine::kFontStyleBold) const { return _theme->getStringWidth(str, style); }
|
||||
int getCharWidth(byte c, ThemeEngine::FontStyle style = ThemeEngine::kFontStyleBold) const { return _theme->getCharWidth(c, style); }
|
||||
int getKerningOffset(byte left, byte right, ThemeEngine::FontStyle font = ThemeEngine::kFontStyleBold) const { return _theme->getKerningOffset(left, right, font); }
|
||||
|
||||
/**
|
||||
* Tell the GuiManager to check whether the screen resolution has changed.
|
||||
|
|
|
@ -165,6 +165,17 @@ void OptionsDialog::open() {
|
|||
_guioptions = parseGameGUIOptions(_guioptionsString);
|
||||
}
|
||||
|
||||
// Graphic options
|
||||
if (_fullscreenCheckbox) {
|
||||
|
||||
#ifdef SMALL_SCREEN_DEVICE
|
||||
_fullscreenCheckbox->setState(true);
|
||||
_fullscreenCheckbox->setEnabled(false);
|
||||
#else // !SMALL_SCREEN_DEVICE
|
||||
// Fullscreen setting
|
||||
_fullscreenCheckbox->setState(ConfMan.getBool("fullscreen", _domain));
|
||||
}
|
||||
|
||||
// Audio options
|
||||
if (!loadMusicDeviceSetting(_midiPopUp, "music_driver"))
|
||||
_midiPopUp->setSelected(0);
|
||||
|
@ -231,6 +242,14 @@ void OptionsDialog::open() {
|
|||
vol = ConfMan.getInt("speech_volume", _domain);
|
||||
_speechVolumeSlider->setValue(vol);
|
||||
_speechVolumeLabel->setValue(vol);
|
||||
|
||||
bool val = false;
|
||||
if (ConfMan.hasKey("mute", _domain)) {
|
||||
val = ConfMan.getBool("mute", _domain);
|
||||
} else {
|
||||
ConfMan.setBool("mute", false);
|
||||
}
|
||||
_muteCheckbox->setState(val);
|
||||
}
|
||||
|
||||
// Subtitle options
|
||||
|
@ -251,16 +270,53 @@ void OptionsDialog::open() {
|
|||
|
||||
void OptionsDialog::close() {
|
||||
if (getResult()) {
|
||||
|
||||
// Graphic options
|
||||
bool graphicsModeChanged = false;
|
||||
if (_fullscreenCheckbox) {
|
||||
if (_enableGraphicSettings) {
|
||||
if (ConfMan.getBool("fullscreen", _domain) != _fullscreenCheckbox->getState())
|
||||
graphicsModeChanged = true;
|
||||
ConfMan.setBool("fullscreen", _fullscreenCheckbox->getState(), _domain);
|
||||
} else {
|
||||
ConfMan.removeKey("fullscreen", _domain);
|
||||
ConfMan.removeKey("aspect_ratio", _domain);
|
||||
ConfMan.removeKey("disable_dithering", _domain);
|
||||
ConfMan.removeKey("gfx_mode", _domain);
|
||||
ConfMan.removeKey("render_mode", _domain);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup graphics again if needed
|
||||
if (_domain == Common::ConfigManager::kApplicationDomain && graphicsModeChanged) {
|
||||
if (ConfMan.hasKey("fullscreen"))
|
||||
g_system->setFeatureState(OSystem::kFeatureFullscreenMode, ConfMan.getBool("fullscreen", _domain));
|
||||
// Since this might change the screen resolution we need to give
|
||||
// the GUI a chance to update it's internal state. Otherwise we might
|
||||
// get a crash when the GUI tries to grab the overlay.
|
||||
//
|
||||
// This fixes bug #3303501 "Switching from HQ2x->HQ3x crashes ScummVM"
|
||||
//
|
||||
// It is important that this is called *before* any of the current
|
||||
// dialog's widgets are destroyed (for example before
|
||||
// Dialog::close) is called, to prevent crashes caused by invalid
|
||||
// widgets being referenced or similar errors.
|
||||
g_gui.checkScreenChange();
|
||||
}
|
||||
}
|
||||
|
||||
// Volume options
|
||||
if (_musicVolumeSlider) {
|
||||
if (_enableVolumeSettings) {
|
||||
ConfMan.setInt("music_volume", _musicVolumeSlider->getValue(), _domain);
|
||||
ConfMan.setInt("sfx_volume", _sfxVolumeSlider->getValue(), _domain);
|
||||
ConfMan.setInt("speech_volume", _speechVolumeSlider->getValue(), _domain);
|
||||
ConfMan.setBool("mute", _muteCheckbox->getState(), _domain);
|
||||
} else {
|
||||
ConfMan.removeKey("music_volume", _domain);
|
||||
ConfMan.removeKey("sfx_volume", _domain);
|
||||
ConfMan.removeKey("speech_volume", _domain);
|
||||
ConfMan.removeKey("mute", _domain);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -394,6 +450,10 @@ void OptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data
|
|||
_speechVolumeLabel->setValue(_speechVolumeSlider->getValue());
|
||||
_speechVolumeLabel->draw();
|
||||
break;
|
||||
case kMuteAllChanged:
|
||||
// 'true' because if control is disabled then event do not pass
|
||||
setVolumeSettingsState(true);
|
||||
break;
|
||||
case kSubtitleToggle:
|
||||
// We update the slider settings here, when there are sliders, to
|
||||
// disable the speech volume in case we are in subtitle only mode.
|
||||
|
@ -429,6 +489,7 @@ void OptionsDialog::setGraphicSettingsState(bool enabled) {
|
|||
_enableGraphicSettings = enabled;
|
||||
|
||||
#ifndef SMALL_SCREEN_DEVICE
|
||||
_fullscreenCheckbox->setEnabled(enabled);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -491,7 +552,7 @@ void OptionsDialog::setVolumeSettingsState(bool enabled) {
|
|||
|
||||
_enableVolumeSettings = enabled;
|
||||
|
||||
ena = enabled;
|
||||
ena = enabled && !_muteCheckbox->getState();
|
||||
if (_guioptions.contains(GUIO_NOMUSIC))
|
||||
ena = false;
|
||||
|
||||
|
@ -499,7 +560,7 @@ void OptionsDialog::setVolumeSettingsState(bool enabled) {
|
|||
_musicVolumeSlider->setEnabled(ena);
|
||||
_musicVolumeLabel->setEnabled(ena);
|
||||
|
||||
ena = enabled;
|
||||
ena = enabled && !_muteCheckbox->getState();
|
||||
if (_guioptions.contains(GUIO_NOSFX))
|
||||
ena = false;
|
||||
|
||||
|
@ -507,7 +568,7 @@ void OptionsDialog::setVolumeSettingsState(bool enabled) {
|
|||
_sfxVolumeSlider->setEnabled(ena);
|
||||
_sfxVolumeLabel->setEnabled(ena);
|
||||
|
||||
ena = enabled;
|
||||
ena = enabled && !_muteCheckbox->getState();
|
||||
// Disable speech volume slider, when we are in subtitle only mode.
|
||||
if (_subToggleGroup)
|
||||
ena = ena && _subToggleGroup->getValue() != kSubtitlesSubs;
|
||||
|
@ -517,6 +578,8 @@ void OptionsDialog::setVolumeSettingsState(bool enabled) {
|
|||
_speechVolumeDesc->setEnabled(ena);
|
||||
_speechVolumeSlider->setEnabled(ena);
|
||||
_speechVolumeLabel->setEnabled(ena);
|
||||
|
||||
_muteCheckbox->setEnabled(enabled);
|
||||
}
|
||||
|
||||
void OptionsDialog::setSubtitleSettingsState(bool enabled) {
|
||||
|
@ -540,6 +603,8 @@ void OptionsDialog::setSubtitleSettingsState(bool enabled) {
|
|||
}
|
||||
|
||||
void OptionsDialog::addGraphicControls(GuiObject *boss, const Common::String &prefix) {
|
||||
// Fullscreen checkbox
|
||||
_fullscreenCheckbox = new CheckboxWidget(boss, prefix + "grFullscreenCheckbox", _("Fullscreen mode"));
|
||||
|
||||
_enableGraphicSettings = true;
|
||||
}
|
||||
|
@ -742,6 +807,8 @@ void OptionsDialog::addVolumeControls(GuiObject *boss, const Common::String &pre
|
|||
_musicVolumeSlider->setMaxValue(Audio::Mixer::kMaxMixerVolume);
|
||||
_musicVolumeLabel->setFlags(WIDGET_CLEARBG);
|
||||
|
||||
_muteCheckbox = new CheckboxWidget(boss, prefix + "vcMuteCheckbox", _("Mute All"), 0, kMuteAllChanged);
|
||||
|
||||
if (g_system->getOverlayWidth() > 320)
|
||||
_sfxVolumeDesc = new StaticTextWidget(boss, prefix + "vcSfxText", _("SFX volume:"), _("Special sound effects volume"));
|
||||
else
|
||||
|
|
File diff suppressed because it is too large
Load diff
BIN
gui/themes/fonts/FreeMonoBold.ttf
Normal file
BIN
gui/themes/fonts/FreeMonoBold.ttf
Normal file
Binary file not shown.
BIN
gui/themes/fonts/FreeSans.ttf
Normal file
BIN
gui/themes/fonts/FreeSans.ttf
Normal file
Binary file not shown.
BIN
gui/themes/fonts/FreeSansBold.ttf
Normal file
BIN
gui/themes/fonts/FreeSansBold.ttf
Normal file
Binary file not shown.
|
@ -1,3 +1,5 @@
|
|||
These are fonts used in ScummVM. Most of them come from Xorg.
|
||||
|
||||
The GNU FreeFont files are distributed under the license in COPYING.FREEFONT.
|
||||
|
||||
Also other potentially usable fonts are stored here as well.
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
This package is a remake of Amiga built-in Topaz font for use with Lithuanian keyboards.
|
||||
You will find detailed information in topazLT.readme.
|
||||
|
||||
|
5
gui/themes/fonts/topaz/README.ScummVM
Normal file
5
gui/themes/fonts/topaz/README.ScummVM
Normal file
|
@ -0,0 +1,5 @@
|
|||
This package is a remake of Amiga built-in Topaz font for use with Lithuanian keyboards.
|
||||
You will find detailed information in topazLT.readme.
|
||||
|
||||
The Parallaction engine in ScummVM uses this font's bitmap, namely file topazlt/8.
|
||||
Data has been extracted and placed in the array named _amigaTopazFont which can be found in engines/parallaction/staticres.cpp.
|
Binary file not shown.
BIN
gui/themes/modern/FreeMonoBold.ttf
Normal file
BIN
gui/themes/modern/FreeMonoBold.ttf
Normal file
Binary file not shown.
BIN
gui/themes/modern/FreeSans.ttf
Normal file
BIN
gui/themes/modern/FreeSans.ttf
Normal file
Binary file not shown.
BIN
gui/themes/modern/FreeSansBold.ttf
Normal file
BIN
gui/themes/modern/FreeSansBold.ttf
Normal file
Binary file not shown.
|
@ -1 +1 @@
|
|||
[RESIDUALVM_STX0.8.7:ResidualVM Modern Theme:No Author]
|
||||
[SCUMMVM_STX0.8.8:ResidualVM Modern Theme:No Author]
|
||||
|
|
|
@ -105,27 +105,38 @@
|
|||
<fonts>
|
||||
<font id = 'text_default'
|
||||
file = 'helvb12.bdf'
|
||||
scalable_file = 'FreeSansBold.ttf'
|
||||
/>
|
||||
<font resolution = 'y<400'
|
||||
id = 'text_default'
|
||||
file = 'clR6x12.bdf'
|
||||
scalable_file = 'FreeSans.ttf'
|
||||
point_size = '11'
|
||||
/>
|
||||
<font id = 'text_button'
|
||||
file = 'helvb12.bdf'
|
||||
scalable_file = 'FreeSansBold.ttf'
|
||||
/>
|
||||
<font resolution = 'y<400'
|
||||
id = 'text_button'
|
||||
file = 'clR6x12.bdf'
|
||||
scalable_file = 'FreeSans.ttf'
|
||||
point_size = '11'
|
||||
/>
|
||||
<font id = 'text_normal'
|
||||
file = 'helvb12.bdf'
|
||||
scalable_file = 'FreeSansBold.ttf'
|
||||
/>
|
||||
<font resolution = 'y<400'
|
||||
id = 'text_normal'
|
||||
file = 'clR6x12.bdf'
|
||||
scalable_file = 'FreeSans.ttf'
|
||||
point_size = '11'
|
||||
/>
|
||||
<font id = 'tooltip_normal'
|
||||
file = 'fixed5x8.bdf'
|
||||
scalable_file = 'FreeMonoBold.ttf'
|
||||
point_size = '8'
|
||||
/>
|
||||
|
||||
<text_color id = 'color_normal'
|
||||
|
|
|
@ -238,8 +238,13 @@ void EditableWidget::defaultKeyDownHandler(Common::KeyState &state, bool &dirty,
|
|||
|
||||
int EditableWidget::getCaretOffset() const {
|
||||
int caretpos = 0;
|
||||
for (int i = 0; i < _caretPos; i++)
|
||||
caretpos += g_gui.getCharWidth(_editString[i], _font);
|
||||
|
||||
uint last = 0;
|
||||
for (int i = 0; i < _caretPos; ++i) {
|
||||
const uint cur = _editString[i];
|
||||
caretpos += g_gui.getCharWidth(cur, _font) + g_gui.getKerningOffset(last, cur, _font);
|
||||
last = cur;
|
||||
}
|
||||
|
||||
caretpos -= _editScrollOffset;
|
||||
|
||||
|
@ -270,6 +275,8 @@ void EditableWidget::drawCaret(bool erase) {
|
|||
if ((uint)_caretPos < _editString.size()) {
|
||||
GUI::EditableWidget::String chr(_editString[_caretPos]);
|
||||
int chrWidth = g_gui.getCharWidth(_editString[_caretPos], _font);
|
||||
const uint last = (_caretPos > 0) ? _editString[_caretPos - 1] : 0;
|
||||
x += g_gui.getKerningOffset(last, _editString[_caretPos], _font);
|
||||
g_gui.theme()->drawText(Common::Rect(x, y, x + chrWidth, y + editRect.height() - 2), chr, _state, Graphics::kTextAlignLeft, _inversion, 0, false, _font);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,10 +67,13 @@ void EditTextWidget::handleMouseDown(int x, int y, int button, int clickCount) {
|
|||
int width = 0;
|
||||
uint i;
|
||||
|
||||
uint last = 0;
|
||||
for (i = 0; i < _editString.size(); ++i) {
|
||||
width += g_gui.theme()->getCharWidth(_editString[i], _font);
|
||||
const uint cur = _editString[i];
|
||||
width += g_gui.getCharWidth(cur, _font) + g_gui.getKerningOffset(last, cur, _font);
|
||||
if (width >= x)
|
||||
break;
|
||||
last = cur;
|
||||
}
|
||||
if (setCaretPos(i))
|
||||
draw();
|
||||
|
|
4
ports.mk
4
ports.mk
|
@ -183,7 +183,7 @@ osxsnap: bundle
|
|||
residualvmwinres.o: $(srcdir)/icons/residualvm.ico $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) $(srcdir)/dists/residualvm.rc
|
||||
$(QUIET_WINDRES)$(WINDRES) -DHAVE_CONFIG_H $(WINDRESFLAGS) $(DEFINES) -I. -I$(srcdir) $(srcdir)/dists/residualvm.rc residualvmwinres.o
|
||||
|
||||
# Special target to create a win32 snapshot binary
|
||||
# Special target to create a win32 snapshot binary (for Inno Setup)
|
||||
win32dist: $(EXECUTABLE)
|
||||
mkdir -p $(WIN32PATH)
|
||||
mkdir -p $(WIN32PATH)/graphics
|
||||
|
@ -196,6 +196,7 @@ endif
|
|||
cp $(srcdir)/AUTHORS $(WIN32PATH)/AUTHORS.txt
|
||||
cp $(srcdir)/COPYING $(WIN32PATH)/COPYING.txt
|
||||
cp $(srcdir)/COPYING.LGPL $(WIN32PATH)/COPYING.LGPL.txt
|
||||
cp $(srcdir)/COPYING.FREEFONT $(WIN32PATH)/COPYING.FREEFONT.txt
|
||||
cp $(srcdir)/COPYRIGHT $(WIN32PATH)/COPYRIGHT.txt
|
||||
cp $(srcdir)/NEWS $(WIN32PATH)/NEWS.txt
|
||||
cp $(srcdir)/README $(WIN32PATH)/README.txt
|
||||
|
@ -255,6 +256,7 @@ endif
|
|||
cp $(srcdir)/AUTHORS ResidualVMWin32/AUTHORS.txt
|
||||
cp $(srcdir)/COPYING ResidualVMWin32/COPYING.txt
|
||||
cp $(srcdir)/COPYING.LGPL ResidualVMWin32/COPYING.LGPL.txt
|
||||
cp $(srcdir)/COPYING.FREEFONT $(WIN32PATH)/COPYING.FREEFONT.txt
|
||||
cp $(srcdir)/COPYRIGHT ResidualVMWin32/COPYRIGHT.txt
|
||||
cp $(srcdir)/NEWS ResidualVMWin32/NEWS.txt
|
||||
cp $(srcdir)/README ResidualVMWin32/README.txt
|
||||
|
|
|
@ -260,13 +260,7 @@ protected:
|
|||
|
||||
/** Decode an audio packet. */
|
||||
void audioPacket(AudioTrack &audio);
|
||||
|
||||
/**
|
||||
* Decode a video packet.
|
||||
*
|
||||
* This method is virtual because it is overriden in ResidualVM
|
||||
* to export the alpha channel of the video
|
||||
*/
|
||||
/** Decode a video packet. */
|
||||
virtual void videoPacket(VideoFrame &video);
|
||||
|
||||
/** Decode a plane. */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue