synced with scummvm

This commit is contained in:
Pawel Kolodziejski 2012-02-10 07:51:41 +01:00
parent cdd1831d23
commit 5fc7ac39ee
97 changed files with 8634 additions and 6380 deletions

5
.gitignore vendored
View file

@ -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
View 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>.

View file

@ -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

View file

@ -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

View 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++;
}
}

View 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

View 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;
}

View 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

View 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;
}

View 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

View 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;
}
}

View 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_ */

View 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);
}
}

View 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_ */

View 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);
}
}

View 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_ */

View 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);
}
}

View 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

View 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();
}
}

View 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

View file

@ -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;
}

View file

@ -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_

View file

@ -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

View file

@ -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

View 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);
};
}

View file

@ -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];
}

View file

@ -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;
};

View file

@ -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

View file

@ -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;

View file

@ -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 *)&noteLookup->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 = &noteLookups[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(&noteLookups, 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);
}
}

View file

@ -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();
};
}

View file

@ -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) {

View file

@ -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

View file

@ -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() {

View file

@ -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;

View file

@ -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

View file

@ -91,6 +91,8 @@ protected:
bool _changes;
bool _topKeymapIsGui;
};
} // End of namespace Common

View file

@ -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));

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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[]);

View file

@ -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();

View file

@ -121,4 +121,8 @@ const char *gResidualVMFeatures = ""
#ifdef USE_FAAD
"AAC "
#endif
#ifdef USE_FREETYPE2
"FreeType2 "
#endif
;

View file

@ -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.
*

View file

@ -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();

View file

@ -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) {

View file

@ -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

View file

@ -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
View file

@ -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)
#

View file

@ -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");

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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
View 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
View 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

View file

@ -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 \

View file

@ -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;
}

View file

@ -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.

View file

@ -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;

View file

@ -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)

View file

@ -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();

View file

@ -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.

View file

@ -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

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -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.

View file

@ -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.

View 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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1 +1 @@
[RESIDUALVM_STX0.8.7:ResidualVM Modern Theme:No Author]
[SCUMMVM_STX0.8.8:ResidualVM Modern Theme:No Author]

View file

@ -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'

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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

View file

@ -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. */