Release 1.0
This commit is contained in:
parent
a4dc3014f4
commit
cb436b036b
11
LICENSE
Normal file
11
LICENSE
Normal file
|
@ -0,0 +1,11 @@
|
|||
|
||||
The Gaborator library is Copyright (C) 1992-2018 Andreas Gustafsson.
|
||||
|
||||
License to distribute and modify the code is hereby granted under the
|
||||
terms of the GNU Affero General Public License, version 3 (henceforth,
|
||||
the AGPLv3), but not under other versions of the AGPL. See the file
|
||||
doc/agpl-3.0.txt for the full text of the AGPLv3.
|
||||
|
||||
If the terms of the AGPLv3 are not acceptable to you, commercial
|
||||
licencing under a different license is possible. Please contact
|
||||
info@gaborator.com for more information.
|
661
doc/agpl-3.0.txt
Normal file
661
doc/agpl-3.0.txt
Normal file
|
@ -0,0 +1,661 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are 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.
|
||||
|
||||
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.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
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 Affero 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. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
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 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 work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
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 AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
33
doc/doc.css
Normal file
33
doc/doc.css
Normal file
|
@ -0,0 +1,33 @@
|
|||
body {
|
||||
background-color: #000;
|
||||
font-family: sans-serif;
|
||||
margin: 10%;
|
||||
color: #eee;
|
||||
}
|
||||
a:link, a:visited, a:hover, a:active {
|
||||
color: currentColor;
|
||||
}
|
||||
pre {
|
||||
border: 1px solid #888;
|
||||
margin: 20px;
|
||||
margin-left: 0px;
|
||||
padding: 10px;
|
||||
background: #222;
|
||||
/* To avoid text extending outside the border on narrow displays */
|
||||
white-space:pre-wrap;
|
||||
}
|
||||
img {
|
||||
border: 1px solid #888;
|
||||
margin: 20px;
|
||||
margin-left: 0px;
|
||||
padding: 10px;
|
||||
background: #000;
|
||||
}
|
||||
h2 {
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
/* http://code.stephenmorley.org/html-and-css/fixing-browsers-broken-monospace-font-handling/ */
|
||||
pre, code, kbd, samp, tt {
|
||||
font-family:monospace,monospace;
|
||||
font-size:1em;
|
||||
}
|
BIN
doc/filter-response.png
Normal file
BIN
doc/filter-response.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
250
doc/filter.html
Normal file
250
doc/filter.html
Normal file
|
@ -0,0 +1,250 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright (C) 2017-2018 Andreas Gustafsson. This file is part of
|
||||
the Gaborator library source distribution. See the file LICENSE at
|
||||
the top level of the distribution for license information.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="doc.css" />
|
||||
<title>Gaborator Example 2: Frequency-Domain Filtering</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Example 2: Frequency-Domain Filtering</h1>
|
||||
|
||||
<h2>Introduction</h2>
|
||||
|
||||
<p>This example shows how to apply a filter to an audio file using
|
||||
the Gaborator library, by turning the audio into spectrogram
|
||||
coefficients, modifying the coefficients, and resynthesizing audio
|
||||
from them.</p>
|
||||
|
||||
<p>The specific filter implemented here is a 3 dB/octave lowpass
|
||||
filter. This is sometimes called a <i>pinking filter</i> because it
|
||||
can be used to produce pink noise from white noise. In practice, the
|
||||
3 dB/octave slope is only applied above some minimum frequency, for
|
||||
example 20 Hz, because otherwise the gain of the filter would approach
|
||||
infinity as the frequency approaches 0, and the impulse response would
|
||||
have to be infinitely wide.
|
||||
</p>
|
||||
|
||||
<p>Since the slope of this filter is not a multiple of 6 dB/octave, it
|
||||
is difficult to implement as an analog filter, but by filtering
|
||||
digitally in the frequency domain, arbitrary filter responses such as
|
||||
this can easily be achieved.
|
||||
</p>
|
||||
|
||||
<h2>Preamble</h2>
|
||||
|
||||
<pre>
|
||||
#include <memory.h>
|
||||
#include <iostream>
|
||||
#include <sndfile.h>
|
||||
#include <gaborator/gaborator.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 3) {
|
||||
std::cerr << "usage: filter input.wav output.wav\n";
|
||||
exit(1);
|
||||
}
|
||||
</pre>
|
||||
|
||||
<h2>Reading the Audio</h2>
|
||||
|
||||
<p>The code for reading the input audio file is identical to
|
||||
that in <a href="render.html">Example 1</a>:</p>
|
||||
|
||||
<pre>
|
||||
SF_INFO sfinfo;
|
||||
memset(&sfinfo, 0, sizeof(sfinfo));
|
||||
SNDFILE *sf_in = sf_open(argv[1], SFM_READ, &sfinfo);
|
||||
if (! sf_in) {
|
||||
std::cerr << "could not open input audio file\n";
|
||||
exit(1);
|
||||
}
|
||||
double fs = sfinfo.samplerate;
|
||||
sf_count_t n_frames = sfinfo.frames;
|
||||
sf_count_t n_samples = sfinfo.frames * sfinfo.channels;
|
||||
std::vector<float> audio(n_samples);
|
||||
sf_count_t n_read = sf_readf_float(sf_in, audio.data(), n_frames);
|
||||
if (n_read != n_frames) {
|
||||
std::cerr << "read error\n";
|
||||
exit(1);
|
||||
}
|
||||
sf_close(sf_in);
|
||||
</pre>
|
||||
|
||||
<h2>Spectrum Analysis Parameters</h2>
|
||||
|
||||
<p>The spectrum analysis works much the same as in Example 1,
|
||||
but uses slightly different parameters.
|
||||
We use a larger number of frequency bands per octave (100)
|
||||
to minimize ripple in the frequency response, and the
|
||||
reference frequency argument is omitted as we don't care about the
|
||||
exact alignment of the bands with respect to a musical scale.</p>
|
||||
<pre>
|
||||
gaborator::parameters params(100, 20.0 / fs);
|
||||
gaborator::analyzer<float> analyzer(params);
|
||||
</pre>
|
||||
|
||||
<h2>Precalculating Gains</h2>
|
||||
|
||||
<p>The filtering will be done by multiplying each spectrogram
|
||||
coefficient with a frequency-dependent gain. To avoid having to
|
||||
calculate the gain on the fly for each coefficient, which would
|
||||
be slow, we will precalculate the gains into a vector <code>band_gains</code>
|
||||
of one gain value per band, including one for the
|
||||
special lowpass band that contains the frequencies from 0 to 20 Hz.</p>
|
||||
|
||||
<pre>
|
||||
std::vector<float> band_gains(analyzer.bands_end());
|
||||
</pre>
|
||||
|
||||
<p>First, we calculate the gains for the bandpass bands.
|
||||
For a 3 dB/octave lowpass filter, the voltage gain needs to be
|
||||
proportional to the square root of the inverse of the frequency.
|
||||
To get the frequency of each band, we call the
|
||||
<code>analyzer</code> method <code>band_ff()</code>, which
|
||||
returns the center frequency of the band in units of the
|
||||
sampling frequency. The gain is normalized to unity at 20 Hz.
|
||||
</p>
|
||||
<pre>
|
||||
for (int band = analyzer.bandpass_bands_begin(); band < analyzer.bandpass_bands_end(); band++) {
|
||||
float f_hz = analyzer.band_ff(band) * fs;
|
||||
band_gains[band] = 1.0 / sqrt(f_hz / 20.0);
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>The gain of the lowpass band is set to the the same value as the
|
||||
lowest-frequency bandpass band, so that the overall filter gain
|
||||
plateaus smoothly to a constant value below 20 Hz.</p>
|
||||
|
||||
<pre>
|
||||
band_gains[analyzer.band_lowpass()] = band_gains[analyzer.bandpass_bands_end() - 1];
|
||||
</pre>
|
||||
|
||||
<h2>De-interleaving</h2>
|
||||
|
||||
<p>To handle stereo and other multi-channel audio files,
|
||||
we will loop over the channels and filter each channel separately.
|
||||
Since <i>libsndfile</i> produces interleaved samples, we first
|
||||
de-interleave the current channel into a temporary vector called
|
||||
<code>channel</code>:</p>
|
||||
<pre>
|
||||
for (sf_count_t ch = 0; ch < sfinfo.channels; ch++) {
|
||||
std::vector<float> channel(n_frames);
|
||||
for (sf_count_t i = 0; i < n_frames; i++)
|
||||
channel[i] = audio[i * sfinfo.channels + ch];
|
||||
</pre>
|
||||
<h2>Spectrum Analysis</h2>
|
||||
<p>Now we can spectrum analyze the current channel, producing
|
||||
a set of coefficients:</p>
|
||||
<pre>
|
||||
gaborator::coefs<float> coefs(analyzer);
|
||||
analyzer.analyze(channel.data(), 0, channel.size(), coefs);
|
||||
</pre>
|
||||
|
||||
<h2>Filtering</h2>
|
||||
<p>
|
||||
The filtering is done using the function
|
||||
<code>gaborator::apply()</code>, which applies a user-defined function to
|
||||
each spectrogram coefficient. Here, that user-defined function is a
|
||||
lambda expression that multiplies the coefficient by the appropriate
|
||||
precalculated frequency-dependent gain, modifying the coefficient in
|
||||
place. The unused <code>int64_t</code> argument is the time in units
|
||||
of samples; this could be use to implement a time-varying filter if desired.</p>
|
||||
<pre>
|
||||
gaborator::apply(analyzer, coefs,
|
||||
[&](std::complex<float> &coef, int band, int64_t) {
|
||||
coef *= band_gains[band];
|
||||
});
|
||||
</pre>
|
||||
|
||||
<h2>Resynthesis</h2>
|
||||
<p>We can now resynthesize audio from the filtered coefficients by
|
||||
calling <code>synthesize()</code>. This is a mirror image of the call to
|
||||
<code>analyze()</code>: now the coefficients are the input, and
|
||||
the buffer of samples is the output. The <code>channel</code>
|
||||
vector that originally contained the input samples for the channel
|
||||
is now reused to hold the output samples.</p>
|
||||
<pre>
|
||||
analyzer.synthesize(coefs, 0, channel.size(), channel.data());
|
||||
</pre>
|
||||
|
||||
<h2>Re-interleaving</h2>
|
||||
<p>The <code>audio</code> vector that contained the
|
||||
original interleaved audio is reused for the interleaved
|
||||
filtered audio. This concludes the loop over the channels.
|
||||
</p>
|
||||
<pre>
|
||||
for (sf_count_t i = 0; i < n_frames; i++)
|
||||
audio[i * sfinfo.channels + ch] = channel[i];
|
||||
}
|
||||
</pre>
|
||||
|
||||
<h2>Writing the Audio</h2>
|
||||
<p>The filtered audio is written using <i>libsndfile</i>,
|
||||
using code that closely mirrors that for reading.
|
||||
Note that we use <code>SFC_SET_CLIPPING</code>
|
||||
to make sure that any samples too loud for the file format
|
||||
will saturate; by default, <i>libsndfile</i> makes them
|
||||
wrap around, which sounds really bad.</p>
|
||||
<pre>
|
||||
SNDFILE *sf_out = sf_open(argv[2], SFM_WRITE, &sfinfo);
|
||||
if (! sf_out) {
|
||||
std::cerr << "could not output audio file\n";
|
||||
exit(1);
|
||||
}
|
||||
sf_command(sf_out, SFC_SET_CLIPPING, NULL, SF_TRUE);
|
||||
sf_count_t n_written = sf_writef_float(sf_out, audio.data(), n_frames);
|
||||
if (n_written != n_frames) {
|
||||
std::cerr << "write error\n";
|
||||
exit(1);
|
||||
}
|
||||
sf_close(sf_out);
|
||||
</pre>
|
||||
|
||||
<h2>Postamble</h2>
|
||||
<p>
|
||||
We need a couple more lines of boilerplate to make the example a
|
||||
complete program:
|
||||
</p>
|
||||
<pre>
|
||||
return 0;
|
||||
}
|
||||
</pre>
|
||||
|
||||
<h2>Compiling</h2>
|
||||
<p>Like <a href="render.html">Example 1</a>, this example
|
||||
can be built using a one-line build command:
|
||||
</p>
|
||||
<pre class="build Darwin Linux NetBSD">
|
||||
c++ -std=c++11 -I.. -O3 -ffast-math `pkg-config --cflags sndfile` filter.cc `pkg-config --libs sndfile` -o filter
|
||||
</pre>
|
||||
<p>Or using the vDSP FFT on macOS:</p>
|
||||
<pre class="build Darwin">
|
||||
c++ -std=c++11 -I.. -O3 -ffast-math -DGABORATOR_USE_VDSP `pkg-config --cflags sndfile` filter.cc `pkg-config --libs sndfile` -framework Accelerate -o filter
|
||||
</pre>
|
||||
<p>Or using PFFFT (see <a href="render.html">Example 1</a> for how to download and build PFFFT):</p>
|
||||
<pre class="build Linux NetBSD">
|
||||
c++ -std=c++11 -I.. -Ipffft -O3 -ffast-math -DGABORATOR_USE_PFFFT `pkg-config --cflags sndfile` render.cc pffft/pffft.o pffft/fftpack.o `pkg-config --libs sndfile` -o render
|
||||
</pre>
|
||||
|
||||
<h2>Running</h2>
|
||||
<p>To filter the file <code>guitar.wav</code> that was downloaded in
|
||||
Example 1, simply run</p>
|
||||
<pre class="run">
|
||||
./filter guitar.wav guitar_filtered.wav
|
||||
</pre>
|
||||
<p>The resulting lowpass filtered audio in <code>guitar_filtered.wav</code> will
|
||||
sound muffled compared to the original, but less so than it would with a
|
||||
6 dB/octave filter.</p>
|
||||
|
||||
<h2>Frequency response</h2>
|
||||
<p>The following plot shows the actual measured frequency response of the
|
||||
filter, with the expected 3 dB/octave slope above 20 Hz and minimal
|
||||
ripple:</p>
|
||||
<img src="filter-response.png" alt="Frequency response plot">
|
||||
|
||||
</body>
|
||||
</html>
|
72
doc/index.html
Normal file
72
doc/index.html
Normal file
|
@ -0,0 +1,72 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright (C) 2018 Andreas Gustafsson. This file is part of
|
||||
the Gaborator library source distribution. See the file LICENSE at
|
||||
the top level of the distribution for license information.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="doc.css" />
|
||||
<title>The Gaborator</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>The Gaborator</h1>
|
||||
<p>The Gaborator is a library that generates constant-Q spectrograms
|
||||
for visualization and analysis of audio signals. It also supports an
|
||||
accurate inverse transformation of the spectrogram coefficients back into
|
||||
audio for spectral effects and editing.</p>
|
||||
|
||||
<p>The Gaborator implements the invertible constant-Q transform of
|
||||
Velasco, Holighaus, Dörfler, and Grill, described in the papers
|
||||
<i><a href="http://www.univie.ac.at/nonstatgab/pdf_files/dohogrve11_amsart.pdf">
|
||||
Constructing an invertible constant-Q transform with nonstationary Gabor frames, 2011</a></i>
|
||||
and <i><a href="http://www.univie.ac.at/nonstatgab/pdf_files/dogrhove12_amsart.pdf">
|
||||
A Framework for invertible, real-time constant-Q transforms, 2012</a></i>.
|
||||
</p>
|
||||
|
||||
<p>The Gaborator is written in C++11 and runs on POSIX systems such as
|
||||
macOS, Linux, and NetBSD. It has been tested on Intel x86_64 and ARM
|
||||
processors.</p>
|
||||
|
||||
<p>The Gaborator is open source under the GNU Affero General Public
|
||||
License, version 3, and is also available for commercial licensing.
|
||||
See the file <a href="LICENSE">LICENSE</a> for details.</p>
|
||||
|
||||
<h2>Release Notes</h2>
|
||||
<p>This is the first public release of the Gaborator library. It
|
||||
includes the core spectrum analysis, resynthesis, and spectrogram
|
||||
rendering code, and some examples of how to use it.</p>
|
||||
|
||||
<p>Some features that have been implemented but are not yet ready for
|
||||
release have been omitted, for example the support for parallel
|
||||
analysis and synthesis using multiple CPU cores. Also, the current API
|
||||
still lacks functions for convenient row- or column-wise access to
|
||||
the coefficients at specific frequencies or times; the only way to
|
||||
access the coefficients is the apply() method, which iterates over
|
||||
the entire coefficient set in an indeterminate order.
|
||||
</p>
|
||||
|
||||
<p>This initial release also still lacks reference documentation
|
||||
formally defining the public library API. Until such documentation is
|
||||
released, only the functions used in the example code should be
|
||||
considered part of the API. All other functions are subject to change
|
||||
or removal without notice.</p>
|
||||
|
||||
<h2>Example Code</h2>
|
||||
<p>The following examples demonstrate the use of the library for a
|
||||
couple of different applications. They are presented in a "literate
|
||||
programming" style, with the code embedded in the explanatory
|
||||
text rather than the other way around.
|
||||
Concatenating the code fragments in each example yields a complete C++
|
||||
program, which can also be found as a <code>.cc</code> file in
|
||||
the <code>examples/</code> directory.</p>
|
||||
<ul>
|
||||
<li><a href="render.html">Example 1: Rendering a Spectrogram Image</a></li>
|
||||
<li><a href="filter.html">Example 2: Frequency-Domain Filtering</a></li>
|
||||
</ul>
|
||||
|
||||
<h2>Contact</h2>
|
||||
<p>For more information, email the author at info@gaborator.com.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
390
doc/render.html
Normal file
390
doc/render.html
Normal file
|
@ -0,0 +1,390 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright (C) 2017-2018 Andreas Gustafsson. This file is part of
|
||||
the Gaborator library source distribution. See the file LICENSE at
|
||||
the top level of the distribution for license information.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="doc.css" />
|
||||
<title>Gaborator Example 1: Rendering a Spectrogram Image</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Example 1: Rendering a Spectrogram Image</h1>
|
||||
|
||||
<h2>Introduction</h2>
|
||||
|
||||
<p>This example shows how to generate a greyscale constant-Q
|
||||
spectrogram image from an audio file using the Gaborator library.
|
||||
</p>
|
||||
|
||||
<h2>Preamble</h2>
|
||||
|
||||
<p>We start off with some boilerplate #includes.</p>
|
||||
|
||||
<pre>
|
||||
#include <memory.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sndfile.h>
|
||||
</pre>
|
||||
|
||||
<p>The Gaborator is a header-only library — there are no C++ files
|
||||
to compile, only header files to include.
|
||||
The core spectrum analysis and resynthesis code is in
|
||||
<code>gaborator/gaborator.h</code>, and the code for rendering
|
||||
images from the spectrogram coefficients is in
|
||||
<code>gaborator/render.h</code>.</p>
|
||||
|
||||
<pre>
|
||||
#include <gaborator/gaborator.h>
|
||||
#include <gaborator/render.h>
|
||||
</pre>
|
||||
|
||||
<p>The program takes the names of the input audio file and output spectrogram
|
||||
image file as command line arguments, so we check that they are present:</p>
|
||||
|
||||
<pre>
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 3) {
|
||||
std::cerr << "usage: render input.wav output.pgm\n";
|
||||
exit(1);
|
||||
}
|
||||
</pre>
|
||||
|
||||
<h2>Reading the Audio</h2>
|
||||
|
||||
<p>The audio file is read using the <i>libsndfile</i> library
|
||||
and stored in a <code>std::vector<float></code>.
|
||||
Note that although <i>libsndfile</i> is used in this example,
|
||||
the Gaborator library itself does not depend or
|
||||
use <i>libsndfile</i>.</p>
|
||||
<pre>
|
||||
SF_INFO sfinfo;
|
||||
memset(&sfinfo, 0, sizeof(sfinfo));
|
||||
SNDFILE *sf_in = sf_open(argv[1], SFM_READ, &sfinfo);
|
||||
if (! sf_in) {
|
||||
std::cerr << "could not open input audio file\n";
|
||||
exit(1);
|
||||
}
|
||||
double fs = sfinfo.samplerate;
|
||||
sf_count_t n_frames = sfinfo.frames;
|
||||
sf_count_t n_samples = sfinfo.frames * sfinfo.channels;
|
||||
std::vector<float> audio(n_samples);
|
||||
sf_count_t n_read = sf_readf_float(sf_in, audio.data(), n_frames);
|
||||
if (n_read != n_frames) {
|
||||
std::cerr << "read error\n";
|
||||
exit(1);
|
||||
}
|
||||
sf_close(sf_in);
|
||||
</pre>
|
||||
<p>In case the audio file is a stereo or multi-channel one,
|
||||
mix down the channels to mono, into a new <code>std::vector<float></code>:
|
||||
<pre>
|
||||
std::vector<float> mono(n_frames);
|
||||
for (size_t i = 0; i < (size_t)n_frames; i++) {
|
||||
float v = 0;
|
||||
for (size_t c = 0; c < (size_t)sfinfo.channels; c++)
|
||||
v += audio[i * sfinfo.channels + c];
|
||||
mono[i] = v;
|
||||
}
|
||||
</pre>
|
||||
|
||||
<h2>The Spectrum Analysis Parameters</h2>
|
||||
|
||||
<p>Next, we need to choose some parameters for the spectrum analysis:
|
||||
the frequency resolution, the frequency range, and optionally a
|
||||
reference frequency.</p>
|
||||
|
||||
<p>The frequency resolution is specified as a number of frequency
|
||||
bands per octave. A typical number for analyzing music signals is 48
|
||||
bands per octave, or in other words, four bands per semitone
|
||||
in the 12-note equal tempered scale.</p>
|
||||
|
||||
<p>The frequency range is specified by giving a minimum frequency;
|
||||
this is the lowest frequency that will be included in the spectrogram
|
||||
coefficients.
|
||||
For audio signals, a typical minimum frequency is 20 Hz,
|
||||
the lower limit of human hearing. In the Gaborator library,
|
||||
all frequencies are given in units of the sample rate rather
|
||||
than in Hz, so we need to divide the 20 Hz by the sample
|
||||
rate of the input audio file: <code>20.0 / fs</code>.</p>
|
||||
|
||||
<p>Unlike the minimum frequency, the maximum frequency is not given
|
||||
explicitly — instead, the analysis always produces coefficients
|
||||
for frequencies all the way up to half the sample rate
|
||||
(a.k.a. the Nyquist frequency). If you don't need the coefficients
|
||||
for the highest frequencies, you can simply ignore them.</p>
|
||||
|
||||
<p>If desired, one of the frequency bands can be exactly aligned with
|
||||
a <i>reference frequency</i>. When analyzing music signals, this is
|
||||
typically 440 Hz, the standard tuning of the note <i>A<sub>4</sub></i>.
|
||||
Like the minimum frequency, it is given in
|
||||
units of the sample rate, so we pass <code>440.0 / fs</code>.</p>
|
||||
|
||||
<p>The parameters are held in an object of type
|
||||
<code>gaborator::parameters</code>:
|
||||
<pre>
|
||||
gaborator::parameters params(48, 20.0 / fs, 440.0 / fs);
|
||||
</pre>
|
||||
|
||||
<h2>The Spectrum Analyzer</h2>
|
||||
|
||||
<p>Next, we create an object of type <code>gaborator::analyzer</code>;
|
||||
this is the workhorse that performs the actual spectrum analysis
|
||||
(and/or resynthesis, but that's for a later example).
|
||||
It is a template class, parametrized by the floating point type to
|
||||
use for the calculations; this is typically <code>float</code>.
|
||||
Constructing the <code>gaborator::analyzer</code> involves allocating and
|
||||
precalculating all the filter coefficients and other auxiliary data needed
|
||||
for the analysis and resynthesis, and this takes considerable time and memory,
|
||||
so when analyzing multiple pieces of audio with the same
|
||||
parameters, creating a single <code>gaborator::analyzer</code>
|
||||
and reusing it is preferable to destroying and recreating it.</p>
|
||||
<pre>
|
||||
gaborator::analyzer<float> analyzer(params);
|
||||
</pre>
|
||||
|
||||
<h2>The Spectrogram Coefficients</h2>
|
||||
|
||||
<p>The result of the spectrum analysis will be a set of <i>spectrogram
|
||||
coefficients</i>. To store them, we will use a <code>gaborator::coefs</code>
|
||||
object. Like the <code>analyzer</code>, this is a template class parametrized
|
||||
by the data type. Because the layout of the coefficients is determined by
|
||||
the spectrum analyzer, it must be passed as an argument to the constructor:</p>
|
||||
<pre>
|
||||
gaborator::coefs<float> coefs(analyzer);
|
||||
</pre>
|
||||
|
||||
<h2>Running the Analysis</h2>
|
||||
|
||||
<p>Now we are ready to do the actual spectrum analysis,
|
||||
by calling the <code>analyze</code> method of the spectrum
|
||||
analyzer object.
|
||||
The first argument to <code>analyze</code> is a <code>float</code> pointer
|
||||
pointing to the first element in the array of samples to analyze.
|
||||
The second and third arguments are of type
|
||||
<code>int64_t</code> and indicate the time range covered by the
|
||||
array, in units of samples. Since we are passing the whole file at
|
||||
once, the beginning of the range is sample number zero, and the end is
|
||||
sample number <code>mono.size()</code>. The fourth argument is a
|
||||
reference to the set of coefficients that the results of the spectrum
|
||||
analysis will be stored in.
|
||||
</p>
|
||||
<pre>
|
||||
analyzer.analyze(mono.data(), 0, mono.size(), coefs);
|
||||
</pre>
|
||||
|
||||
<h2>Rendering an Image</h2>
|
||||
|
||||
<p>Now there is a set of spectrogram coefficients in <code>coefs</code>.
|
||||
To render them as an image, we will use the function
|
||||
<code>gaborator::render_p2scale()</code>.
|
||||
</p>
|
||||
|
||||
<p>Rendering involves two different coordinate
|
||||
spaces: the time-frequency coordinates of the spectrogram
|
||||
coefficients, and the x-y coordinates of the image.
|
||||
The two spaces are related by an origin and a scale factor,
|
||||
each with an x and y component.</p>
|
||||
|
||||
<p>The origin specifies the point in time-frequency space that
|
||||
corresponds to the pixel coordinates (0, 0). Here, we will
|
||||
use an origin where the x (time) component
|
||||
is zero (the beginning of the signal), and the y (frequency) component
|
||||
is the band number of the first (highest frequency) band:</p>
|
||||
|
||||
<pre>
|
||||
int64_t x_origin = 0;
|
||||
int64_t y_origin = analyzer.bandpass_bands_begin();
|
||||
</pre>
|
||||
|
||||
<p><code>render_p2scale()</code> supports scaling the spectrogram in
|
||||
both the time (horizontal) and frequency (vertical) dimension, but only
|
||||
by power-of-two scale factors. These scale factors are specified
|
||||
relative to a reference scale of one vertical pixel per frequency band
|
||||
and one horizontal pixel per signal sample.
|
||||
|
||||
<p>Although a horizontal scale of one pixel per signal sample is a
|
||||
mathematically pleasing reference point, this reference scale is not
|
||||
used in practice because it would result in spectrogram that is much
|
||||
too stretched out horizontally. A more typical scale factor might be
|
||||
2<sup>10</sup> = 1024, yielding one pixel for every 1024 signal
|
||||
samples, which is about one pixel per 23 milliseconds of signal at a
|
||||
sample rate of 44.1 kHz.</p>
|
||||
<pre>
|
||||
int x_scale_exp = 10;
|
||||
</pre>
|
||||
|
||||
<p>To ensure that the spectrogram will fit on the screen even in the
|
||||
case of a long audio file, let's auto-scale it down further until
|
||||
it is no more than 1000 pixels wide:</p>
|
||||
<pre>
|
||||
while ((n_frames >> x_scale_exp) > 1000)
|
||||
x_scale_exp++;
|
||||
</pre>
|
||||
|
||||
<p>In the vertical, the reference scale factor of one pixel per
|
||||
frequency band is reasonable, so we will use it as-is. In other words,
|
||||
the vertical scale factor will be 2<sup>0</sup>.</p>
|
||||
<pre>
|
||||
int y_scale_exp = 0;
|
||||
</pre>
|
||||
|
||||
<p>Next, we need to define the rectangular region of the image
|
||||
coordinate space to render. Since we are rendering the entire
|
||||
spectrogram rather than a tile, the top left corner of the
|
||||
rectangle will have an origin of (0, 0).
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
int64_t x0 = 0;
|
||||
int64_t y0 = 0;
|
||||
</pre>
|
||||
|
||||
<p>The coordinates of the bottom right corner are determined by the
|
||||
length of the signal and the number of bands, respectively, taking the
|
||||
scale factors into account.
|
||||
The length of the signal in samples is <code>n_frames</code>,
|
||||
and we get the number of bands as the difference of the end points of
|
||||
the range of band numbers:
|
||||
<code>analyzer.bandpass_bands_end() - analyzer.bandpass_bands_begin()</code>.
|
||||
The scale factor is taken into account by right shifting by the
|
||||
scale exponent.
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
int64_t x1 = n_frames >> x_scale_exp;
|
||||
int64_t y1 = (analyzer.bandpass_bands_end() - analyzer.bandpass_bands_begin()) >> y_scale_exp;
|
||||
</pre>
|
||||
|
||||
<p>The right shift by <code>y_scale_exp</code> above doesn't actually
|
||||
do anything because <code>y_scale_exp</code> is zero, but it would be
|
||||
needed if, for example, you were to change <code>y_scale_exp</code> to
|
||||
1 to get a spectrogram scaled to half the height. You could also make a
|
||||
double-height spectrogram by setting <code>y_scale_exp</code> to -1,
|
||||
but then you also need to change the
|
||||
<code>>> y_scale_exp</code> to
|
||||
<code><< -y_scale_exp</code> since you can't shift by
|
||||
a negative number.
|
||||
</p>
|
||||
|
||||
<p>We are now ready to render the spectrogram, producing
|
||||
a vector of floating-point amplitude values, one per pixel.
|
||||
Although this is stored as a 1-dimensional vector of floats, its
|
||||
contents should be interpreted as a 2-dimensional rectangular array of
|
||||
<code>(y1 - y0)</code> rows of <code>(x1 - x0)</code> columns
|
||||
each, with the row indices increasing towards lower
|
||||
frequencies and column indices increasing towards later
|
||||
sampling times.
|
||||
</p>
|
||||
<pre>
|
||||
std::vector<float> amplitudes((x1 - x0) * (y1 - y0));
|
||||
gaborator::render_p2scale(
|
||||
analyzer,
|
||||
coefs,
|
||||
x_origin, y_origin,
|
||||
x0, x1, x_scale_exp,
|
||||
y0, y1, y_scale_exp,
|
||||
amplitudes.data());
|
||||
</pre>
|
||||
|
||||
<h2>Writing the Image File</h2>
|
||||
|
||||
<p>To keep the code simple and to avoid additional library
|
||||
dependencies, the image is stored in
|
||||
<code>pgm</code> (Portable GreyMap) format, which is simple
|
||||
enough to be generated with just a few lines of inline code.
|
||||
Each amplitude value in <code>amplitudes</code> is converted into an 8-bit
|
||||
gamma corrected pixel value by calling <code>gaborator::float2pixel_8bit()</code>.
|
||||
To control the brightness of the resulting image, each
|
||||
amplitude value is multiplied by a gain; this may have to be adjusted
|
||||
depending on the type of signal and the amount of headroom in the
|
||||
recording, but a gain of about 15 often works well for typical music
|
||||
signals.</p>
|
||||
<pre>
|
||||
float gain = 15;
|
||||
std::ofstream f;
|
||||
f.open(argv[2], std::ios::out | std::ios::binary);
|
||||
f << "P5\n" << (x1 - x0) << ' ' << (y1 - y0) << "\n255\n";
|
||||
for (size_t i = 0; i < amplitudes.size(); i++)
|
||||
f.put(gaborator::float2pixel_8bit(amplitudes[i] * gain));
|
||||
f.close();
|
||||
</pre>
|
||||
|
||||
<h2>Postamble</h2>
|
||||
<p>
|
||||
To make the example code a complete program,
|
||||
we just need to finish <code>main()</code>:
|
||||
</p>
|
||||
<pre>
|
||||
return 0;
|
||||
}
|
||||
</pre>
|
||||
|
||||
<h2>Compiling</h2>
|
||||
<p>
|
||||
If you are using macOS, Linux, NetBSD, or a similar system, you can build
|
||||
the example by running the following command in the <code>examples</code>
|
||||
subdirectory.
|
||||
You need to have <i>libsndfile</i> is installed and supported by
|
||||
<code>pkg-config</code>.
|
||||
</p>
|
||||
<pre class="build Darwin Linux NetBSD">
|
||||
c++ -std=c++11 -I.. -O3 -ffast-math `pkg-config --cflags sndfile` render.cc `pkg-config --libs sndfile` -o render
|
||||
</pre>
|
||||
|
||||
<h2>Compiling for Speed</h2>
|
||||
<p>The above build command uses the Gaborator's built-in FFT implementation,
|
||||
which is simple and portable but rather slow. Performance can be
|
||||
significantly improved by using a faster FFT library. On macOS, you
|
||||
can use the FFT from Apple's vDSP library by defining
|
||||
<code>GABORATOR_USE_VDSP</code> and linking with the <code>Accelerate</code>
|
||||
framework:
|
||||
</p>
|
||||
<pre class="build Darwin">
|
||||
c++ -std=c++11 -I.. -O3 -ffast-math -DGABORATOR_USE_VDSP `pkg-config --cflags sndfile` render.cc `pkg-config --libs sndfile` -framework Accelerate -o render
|
||||
</pre>
|
||||
|
||||
<p>On Linux and NetBSD, you can use the PFFFT (Pretty Fast FFT) library.
|
||||
You can get the latest version from
|
||||
<a href="https://bitbucket.org/jpommier/pffft">https://bitbucket.org/jpommier/pffft</a>,
|
||||
or the exact version that was used for testing from gaborator.com:
|
||||
</p>
|
||||
<!-- ftp https://bitbucket.org/jpommier/pffft/get/29e4f76ac53b.zip -->
|
||||
<pre class="build Linux NetBSD">
|
||||
wget http://download.gaborator.com/mirror/pffft/29e4f76ac53b.zip
|
||||
unzip 29e4f76ac53b.zip
|
||||
mv jpommier-pffft-29e4f76ac53b pffft
|
||||
</pre>
|
||||
<p>Then, compile it:</p>
|
||||
<pre class="build Linux NetBSD">
|
||||
cc -c -O3 -ffast-math pffft/pffft.c -o pffft/pffft.o
|
||||
</pre>
|
||||
<p>PFFFT is single precision only, but it comes with a copy of FFTPACK which can
|
||||
be used for double-precision FFTs. Let's compile that, too:</p>
|
||||
<pre class="build Linux NetBSD">
|
||||
cc -c -O3 -ffast-math -DFFTPACK_DOUBLE_PRECISION pffft/fftpack.c -o pffft/fftpack.o
|
||||
</pre>
|
||||
<p>Then build the example and link it with both PFFFT and FFTPACK:</p>
|
||||
<pre class="build Linux NetBSD">
|
||||
c++ -std=c++11 -I.. -Ipffft -O3 -ffast-math -DGABORATOR_USE_PFFFT `pkg-config --cflags sndfile` render.cc pffft/pffft.o pffft/fftpack.o `pkg-config --libs sndfile` -o render
|
||||
</pre>
|
||||
|
||||
<h2>Running</h2>
|
||||
<p>Running the following shell commands will download a short example
|
||||
audio file (of picking each string on an acoustic guitar), generate
|
||||
a spectrogram from it as a <code>.pgm</code> image, and then convert
|
||||
the <code>.pgm</code> image into a <code>JPEG</code> image:
|
||||
<pre class="run">
|
||||
wget http://download.gaborator.com/audio/guitar.wav
|
||||
./render guitar.wav guitar.pgm
|
||||
cjpeg <guitar.pgm >guitar.jpg
|
||||
</pre>
|
||||
|
||||
<h2>Example Output</h2>
|
||||
<p>The JPEG file produced by the above will look like this:</p>
|
||||
<img src="spectrogram.jpg" alt="Spectrogram">
|
||||
|
||||
</body>
|
||||
</html>
|
BIN
doc/spectrogram.jpg
Normal file
BIN
doc/spectrogram.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
65
examples/filter.cc
Normal file
65
examples/filter.cc
Normal file
|
@ -0,0 +1,65 @@
|
|||
// See ../doc/filter.html for commentary
|
||||
|
||||
#include <memory.h>
|
||||
#include <iostream>
|
||||
#include <sndfile.h>
|
||||
#include <gaborator/gaborator.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 3) {
|
||||
std::cerr << "usage: filter input.wav output.wav\n";
|
||||
exit(1);
|
||||
}
|
||||
SF_INFO sfinfo;
|
||||
memset(&sfinfo, 0, sizeof(sfinfo));
|
||||
SNDFILE *sf_in = sf_open(argv[1], SFM_READ, &sfinfo);
|
||||
if (! sf_in) {
|
||||
std::cerr << "could not open input audio file\n";
|
||||
exit(1);
|
||||
}
|
||||
double fs = sfinfo.samplerate;
|
||||
sf_count_t n_frames = sfinfo.frames;
|
||||
sf_count_t n_samples = sfinfo.frames * sfinfo.channels;
|
||||
std::vector<float> audio(n_samples);
|
||||
sf_count_t n_read = sf_readf_float(sf_in, audio.data(), n_frames);
|
||||
if (n_read != n_frames) {
|
||||
std::cerr << "read error\n";
|
||||
exit(1);
|
||||
}
|
||||
sf_close(sf_in);
|
||||
gaborator::parameters params(100, 20.0 / fs);
|
||||
gaborator::analyzer<float> analyzer(params);
|
||||
std::vector<float> band_gains(analyzer.bands_end());
|
||||
for (int band = analyzer.bandpass_bands_begin(); band < analyzer.bandpass_bands_end(); band++) {
|
||||
float f_hz = analyzer.band_ff(band) * fs;
|
||||
band_gains[band] = 1.0 / sqrt(f_hz / 20.0);
|
||||
}
|
||||
band_gains[analyzer.band_lowpass()] = band_gains[analyzer.bandpass_bands_end() - 1];
|
||||
for (sf_count_t ch = 0; ch < sfinfo.channels; ch++) {
|
||||
std::vector<float> channel(n_frames);
|
||||
for (sf_count_t i = 0; i < n_frames; i++)
|
||||
channel[i] = audio[i * sfinfo.channels + ch];
|
||||
gaborator::coefs<float> coefs(analyzer);
|
||||
analyzer.analyze(channel.data(), 0, channel.size(), coefs);
|
||||
gaborator::apply(analyzer, coefs,
|
||||
[&](std::complex<float> &coef, int band, int64_t) {
|
||||
coef *= band_gains[band];
|
||||
});
|
||||
analyzer.synthesize(coefs, 0, channel.size(), channel.data());
|
||||
for (sf_count_t i = 0; i < n_frames; i++)
|
||||
audio[i * sfinfo.channels + ch] = channel[i];
|
||||
}
|
||||
SNDFILE *sf_out = sf_open(argv[2], SFM_WRITE, &sfinfo);
|
||||
if (! sf_out) {
|
||||
std::cerr << "could not output audio file\n";
|
||||
exit(1);
|
||||
}
|
||||
sf_command(sf_out, SFC_SET_CLIPPING, NULL, SF_TRUE);
|
||||
sf_count_t n_written = sf_writef_float(sf_out, audio.data(), n_frames);
|
||||
if (n_written != n_frames) {
|
||||
std::cerr << "write error\n";
|
||||
exit(1);
|
||||
}
|
||||
sf_close(sf_out);
|
||||
return 0;
|
||||
}
|
68
examples/render.cc
Normal file
68
examples/render.cc
Normal file
|
@ -0,0 +1,68 @@
|
|||
// See ../doc/render.html for commentary
|
||||
|
||||
#include <memory.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sndfile.h>
|
||||
#include <gaborator/gaborator.h>
|
||||
#include <gaborator/render.h>
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 3) {
|
||||
std::cerr << "usage: render input.wav output.pgm\n";
|
||||
exit(1);
|
||||
}
|
||||
SF_INFO sfinfo;
|
||||
memset(&sfinfo, 0, sizeof(sfinfo));
|
||||
SNDFILE *sf_in = sf_open(argv[1], SFM_READ, &sfinfo);
|
||||
if (! sf_in) {
|
||||
std::cerr << "could not open input audio file\n";
|
||||
exit(1);
|
||||
}
|
||||
double fs = sfinfo.samplerate;
|
||||
sf_count_t n_frames = sfinfo.frames;
|
||||
sf_count_t n_samples = sfinfo.frames * sfinfo.channels;
|
||||
std::vector<float> audio(n_samples);
|
||||
sf_count_t n_read = sf_readf_float(sf_in, audio.data(), n_frames);
|
||||
if (n_read != n_frames) {
|
||||
std::cerr << "read error\n";
|
||||
exit(1);
|
||||
}
|
||||
sf_close(sf_in);
|
||||
std::vector<float> mono(n_frames);
|
||||
for (size_t i = 0; i < (size_t)n_frames; i++) {
|
||||
float v = 0;
|
||||
for (size_t c = 0; c < (size_t)sfinfo.channels; c++)
|
||||
v += audio[i * sfinfo.channels + c];
|
||||
mono[i] = v;
|
||||
}
|
||||
gaborator::parameters params(48, 20.0 / fs, 440.0 / fs);
|
||||
gaborator::analyzer<float> analyzer(params);
|
||||
gaborator::coefs<float> coefs(analyzer);
|
||||
analyzer.analyze(mono.data(), 0, mono.size(), coefs);
|
||||
int64_t x_origin = 0;
|
||||
int64_t y_origin = analyzer.bandpass_bands_begin();
|
||||
int x_scale_exp = 10;
|
||||
while ((n_frames >> x_scale_exp) > 1000)
|
||||
x_scale_exp++;
|
||||
int y_scale_exp = 0;
|
||||
int64_t x0 = 0;
|
||||
int64_t y0 = 0;
|
||||
int64_t x1 = n_frames >> x_scale_exp;
|
||||
int64_t y1 = (analyzer.bandpass_bands_end() - analyzer.bandpass_bands_begin()) >> y_scale_exp;
|
||||
std::vector<float> amplitudes((x1 - x0) * (y1 - y0));
|
||||
gaborator::render_p2scale(
|
||||
analyzer,
|
||||
coefs,
|
||||
x_origin, y_origin,
|
||||
x0, x1, x_scale_exp,
|
||||
y0, y1, y_scale_exp,
|
||||
amplitudes.data());
|
||||
float gain = 15;
|
||||
std::ofstream f;
|
||||
f.open(argv[2], std::ios::out | std::ios::binary);
|
||||
f << "P5\n" << (x1 - x0) << ' ' << (y1 - y0) << "\n255\n";
|
||||
for (size_t i = 0; i < amplitudes.size(); i++)
|
||||
f.put(gaborator::float2pixel_8bit(amplitudes[i] * gain));
|
||||
f.close();
|
||||
return 0;
|
||||
}
|
20
gaborator/fft.h
Normal file
20
gaborator/fft.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// Fast Fourier transform
|
||||
//
|
||||
// Copyright (C) 2016-2018 Andreas Gustafsson. This file is part of
|
||||
// the Gaborator library source distribution. See the file LICENSE at
|
||||
// the top level of the distribution for license information.
|
||||
//
|
||||
|
||||
#ifndef _GABORATOR_FFT_H
|
||||
#define _GABORATOR_FFT_H
|
||||
|
||||
#include "gaborator/fft_naive.h"
|
||||
|
||||
#if GABORATOR_USE_VDSP
|
||||
#include "gaborator/fft_vdsp.h"
|
||||
#elif GABORATOR_USE_PFFFT
|
||||
#include "gaborator/fft_pffft.h"
|
||||
#endif
|
||||
|
||||
#endif
|
193
gaborator/fft_naive.h
Normal file
193
gaborator/fft_naive.h
Normal file
|
@ -0,0 +1,193 @@
|
|||
//
|
||||
// Fast Fourier transform, naive reference implementations
|
||||
//
|
||||
// Copyright (C) 1992-2018 Andreas Gustafsson. This file is part of
|
||||
// the Gaborator library source distribution. See the file LICENSE at
|
||||
// the top level of the distribution for license information.
|
||||
//
|
||||
|
||||
// Based on the module "fft" used in audsl/test, audsl/mls,
|
||||
// scope/core, whitesig
|
||||
|
||||
#ifndef _GABORATOR_FFT_NAIVE_H
|
||||
#define _GABORATOR_FFT_NAIVE_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <complex>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
#include <memory.h>
|
||||
|
||||
namespace gaborator {
|
||||
|
||||
template <class I>
|
||||
struct fft {
|
||||
typedef typename std::iterator_traits<I>::value_type C; // complex
|
||||
typedef typename C::value_type T; // float/double
|
||||
typedef typename std::vector<C> twiddle_vector;
|
||||
|
||||
fft(unsigned int n): n_(n), wtab(n / 2) { init_wtab(); }
|
||||
~fft() { }
|
||||
|
||||
unsigned int size() { return n_; }
|
||||
|
||||
// Transform the contents of the array "a", leaving results in
|
||||
// bit-reversed order.
|
||||
void
|
||||
br_transform(I a) {
|
||||
unsigned int i, j, m, n;
|
||||
typename twiddle_vector::iterator wp; // twiddle factor pointer
|
||||
I p, q;
|
||||
|
||||
// n is the number of points in each subtransform (butterfly group)
|
||||
// m is the number of subtransforms (butterfly groups), = n_ / n
|
||||
// i is the index of the first point in the current butterfly group
|
||||
// j is the number of the butterfly within the group
|
||||
|
||||
for (n = 2, m = n_ / 2; n <= n_; n *= 2 , m /= 2) // each stage
|
||||
for (i = 0; i < n_; i += n) // each butterfly group
|
||||
for (j = 0, wp = wtab.begin(), p = a + i, q = a + i + n / 2;
|
||||
j < n / 2;
|
||||
j++, wp += m, p++, q++) // each butterfly
|
||||
{
|
||||
C temp((*q) * (*wp));
|
||||
*q = *p - temp;
|
||||
*p += temp;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
bit_reverse(I a) {
|
||||
unsigned int i, j;
|
||||
for (i = 0, j = 0; i < n_; i++, j = bitrev_inc(j)) {
|
||||
if (i < j)
|
||||
std::swap(*(a + i), *(a + j));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
reverse(I a) {
|
||||
for (unsigned int i = 1; i < n_ / 2; i++)
|
||||
std::swap(*(a + i), *(a + n_ - i));
|
||||
}
|
||||
|
||||
// in-place
|
||||
void
|
||||
transform(I a) {
|
||||
bit_reverse(a);
|
||||
br_transform(a);
|
||||
}
|
||||
|
||||
void
|
||||
itransform(I a) {
|
||||
reverse(a);
|
||||
transform(a);
|
||||
}
|
||||
|
||||
// out-of-place
|
||||
// XXX const
|
||||
void
|
||||
transform(I in, I out) {
|
||||
std::copy(in, in + n_, out);
|
||||
transform(out);
|
||||
}
|
||||
|
||||
void
|
||||
itransform(I in, I out) {
|
||||
std::copy(in, in + n_, out);
|
||||
itransform(out);
|
||||
}
|
||||
|
||||
private:
|
||||
// Initialize twiddle factor array
|
||||
void init_wtab() {
|
||||
unsigned int wt_size = wtab.size();
|
||||
for (unsigned int i = 0; i < wt_size; ++i) {
|
||||
double arg = (-2.0 * M_PI / n_) * i;
|
||||
wtab[i] = C(cos(arg), sin(arg));
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int
|
||||
bitrev_inc(unsigned int i) {
|
||||
unsigned int carry = n_;
|
||||
do {
|
||||
carry >>= 1;
|
||||
unsigned int new_i = i ^ carry;
|
||||
carry &= i;
|
||||
i = new_i;
|
||||
} while(carry);
|
||||
return i;
|
||||
}
|
||||
|
||||
// Size of the transform
|
||||
unsigned int n_;
|
||||
|
||||
// Twiddle factor array (size n / 2)
|
||||
twiddle_vector wtab;
|
||||
};
|
||||
|
||||
#if GABORATOR_USE_REAL_FFT
|
||||
// Real FFT
|
||||
//
|
||||
// This is a trivial implementation offering no performance advantage
|
||||
// over a complex FFT. It is intended as a placeholder to be
|
||||
// overridden with a specialization, and as a reference implementation
|
||||
// to compare the results of specializations against.
|
||||
//
|
||||
|
||||
template <class CI>
|
||||
struct rfft {
|
||||
typedef typename std::iterator_traits<CI>::value_type C; // complex
|
||||
typedef typename C::value_type T; // float/double
|
||||
typedef T *RI; // Real iterator
|
||||
typedef const T *CONST_RI;
|
||||
|
||||
rfft(unsigned int n): cf(n) { }
|
||||
~rfft() { }
|
||||
|
||||
void
|
||||
transform(CONST_RI in, CI out) {
|
||||
size_t n = cf.size();
|
||||
C *tmp = new C[n];
|
||||
C *out_tmp = new C[n];
|
||||
std::copy(in, in + cf.size(), tmp); // Real to complex
|
||||
cf.transform(tmp, out_tmp);
|
||||
delete [] tmp;
|
||||
#if GABORATOR_REAL_FFT_NEGATIVE_FQS
|
||||
std::copy(out_tmp, out_tmp + n, out);
|
||||
#else
|
||||
std::copy(out_tmp, out_tmp + n / 2 + 1, out);
|
||||
#endif
|
||||
delete [] out_tmp;
|
||||
}
|
||||
|
||||
void
|
||||
itransform(CI in, RI out) {
|
||||
size_t n = cf.size();
|
||||
// Make sure not to use the negative frequency part of "in",
|
||||
// because it may not be valid.
|
||||
C *in_tmp = new C[n];
|
||||
for (size_t i = 0; i < n / 2 + 1; i++) {
|
||||
in_tmp[i] = in[i];
|
||||
}
|
||||
for (size_t i = 1; i < n / 2; i++) {
|
||||
in_tmp[n - i] = conj(in[i]);
|
||||
}
|
||||
C *tmp = new C[n];
|
||||
cf.itransform(in_tmp, tmp);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
*out++ = tmp[i].real();
|
||||
}
|
||||
delete [] tmp;
|
||||
delete [] in_tmp;
|
||||
}
|
||||
|
||||
fft<CI> cf;
|
||||
};
|
||||
#endif
|
||||
|
||||
} // Namespace
|
||||
|
||||
#endif
|
216
gaborator/fft_pffft.h
Normal file
216
gaborator/fft_pffft.h
Normal file
|
@ -0,0 +1,216 @@
|
|||
//
|
||||
// Fast Fourier transform using PFFFT
|
||||
//
|
||||
// Copyright (C) 2017-2018 Andreas Gustafsson. This file is part of
|
||||
// the Gaborator library source distribution. See the file LICENSE at
|
||||
// the top level of the distribution for license information.
|
||||
//
|
||||
|
||||
#ifndef _GABORATOR_FFT_PFFFT_H
|
||||
#define _GABORATOR_FFT_PFFFT_H
|
||||
|
||||
#include <assert.h>
|
||||
#include <complex>
|
||||
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
|
||||
#include "pffft.h"
|
||||
|
||||
// XXX disable in production
|
||||
#ifdef __x86_64__
|
||||
#define GABORATOR_PFFFT_CHECK_ALIGN(p) assert((((uint64_t)(p)) & 0xF) == 0)
|
||||
#else
|
||||
#define GABORATOR_PFFFT_CHECK_ALIGN(p) do {} while (0)
|
||||
#endif
|
||||
|
||||
namespace gaborator {
|
||||
|
||||
template <>
|
||||
struct fft<std::complex<float> *> {
|
||||
typedef std::complex<float> *I;
|
||||
typedef const std::complex<float> *CONST_I;
|
||||
typedef std::iterator_traits<I>::value_type C; // complex
|
||||
typedef C::value_type T; // float/double
|
||||
|
||||
fft(unsigned int n_): n(n_) {
|
||||
setup = pffft_new_setup(n, PFFFT_COMPLEX);
|
||||
assert(setup);
|
||||
}
|
||||
~fft() {
|
||||
pffft_destroy_setup(setup);
|
||||
}
|
||||
|
||||
unsigned int size() { return n; }
|
||||
|
||||
// in-place
|
||||
void
|
||||
transform(I a) {
|
||||
pffft_transform_ordered(setup, (float *)a, (float *)a, NULL, PFFFT_FORWARD);
|
||||
}
|
||||
|
||||
void
|
||||
itransform(I a) {
|
||||
pffft_transform_ordered(setup, (float *)a, (float *)a, NULL, PFFFT_BACKWARD);
|
||||
}
|
||||
|
||||
// out-of-place
|
||||
void
|
||||
transform(CONST_I in, I out) {
|
||||
GABORATOR_PFFFT_CHECK_ALIGN(in);
|
||||
GABORATOR_PFFFT_CHECK_ALIGN(out);
|
||||
pffft_transform_ordered(setup, (const float *)in, (float *)out, NULL, PFFFT_FORWARD);
|
||||
}
|
||||
|
||||
void
|
||||
itransform(CONST_I in, I out) {
|
||||
GABORATOR_PFFFT_CHECK_ALIGN(in);
|
||||
GABORATOR_PFFFT_CHECK_ALIGN(out);
|
||||
pffft_transform_ordered(setup, (const float *)in, (float *)out, NULL, PFFFT_BACKWARD);
|
||||
}
|
||||
|
||||
private:
|
||||
// Size of the transform
|
||||
unsigned int n;
|
||||
PFFFT_Setup *setup;
|
||||
};
|
||||
|
||||
// Support transforming std::vector<std::complex<float> >::iterator
|
||||
|
||||
template <>
|
||||
struct fft<std::vector<std::complex<float> >::iterator>:
|
||||
public fft<std::complex<float> *>
|
||||
{
|
||||
typedef fft<std::complex<float> *> base;
|
||||
typedef std::vector<std::complex<float> >::iterator I;
|
||||
fft(unsigned int n_): fft<std::complex<float> *>(n_) { }
|
||||
void
|
||||
transform(I a) {
|
||||
base::transform(&(*a));
|
||||
}
|
||||
void
|
||||
itransform(I a) {
|
||||
base::itransform(&(*a));
|
||||
}
|
||||
void
|
||||
transform(I in, I out) {
|
||||
base::transform(&(*in), &(*out));
|
||||
}
|
||||
void
|
||||
itransform(I in, I out) {
|
||||
base::itransform(&(*in), &(*out));
|
||||
}
|
||||
};
|
||||
|
||||
// Use fftpack for double precision
|
||||
|
||||
#define FFTPACK_DOUBLE_PRECISION 1
|
||||
#include "fftpack.h"
|
||||
#undef FFTPACK_DOUBLE_PRECISION
|
||||
|
||||
template <>
|
||||
struct fft<std::complex<double> *> {
|
||||
typedef std::complex<double> *I;
|
||||
typedef const std::complex<double> *CONST_I;
|
||||
typedef std::iterator_traits<I>::value_type C; // complex
|
||||
typedef C::value_type T; // float/double
|
||||
|
||||
fft(unsigned int n_): n(n_), wsave(4 * n_ + 15) {
|
||||
cffti(n, wsave.data());
|
||||
}
|
||||
~fft() {
|
||||
}
|
||||
|
||||
unsigned int size() { return n; }
|
||||
|
||||
// in-place
|
||||
void
|
||||
transform(I a) {
|
||||
cfftf(n, (double *)a, wsave.data());
|
||||
}
|
||||
|
||||
void
|
||||
itransform(I a) {
|
||||
cfftb(n, (double *)a, wsave.data());
|
||||
}
|
||||
|
||||
// out-of-place
|
||||
void
|
||||
transform(CONST_I in, I out) {
|
||||
std::copy(in, in + n, out);
|
||||
transform(out);
|
||||
}
|
||||
|
||||
void
|
||||
itransform(CONST_I in, I out) {
|
||||
std::copy(in, in + n, out);
|
||||
itransform(out);
|
||||
}
|
||||
|
||||
private:
|
||||
// Size of the transform
|
||||
unsigned int n;
|
||||
std::vector<double> wsave;
|
||||
};
|
||||
|
||||
#if GABORATOR_USE_REAL_FFT
|
||||
// Real FFT
|
||||
|
||||
template <>
|
||||
struct rfft<std::complex<float> *> {
|
||||
typedef std::complex<float> *CI; // Complex iterator
|
||||
typedef const std::complex<float> *CONST_CI;
|
||||
typedef typename std::iterator_traits<CI>::value_type C; // complex
|
||||
typedef typename C::value_type T; // float/double
|
||||
typedef T *RI; // Real iterator
|
||||
typedef const T *CONST_RI;
|
||||
|
||||
rfft(unsigned int n_): n(n_) {
|
||||
setup = pffft_new_setup(n, PFFFT_REAL);
|
||||
assert(setup);
|
||||
}
|
||||
~rfft() {
|
||||
pffft_destroy_setup(setup);
|
||||
}
|
||||
|
||||
unsigned int size() { return n; }
|
||||
|
||||
// out-of-place only
|
||||
void
|
||||
transform(CONST_RI in, CI out) {
|
||||
GABORATOR_PFFFT_CHECK_ALIGN(in);
|
||||
GABORATOR_PFFFT_CHECK_ALIGN(out);
|
||||
pffft_transform_ordered(setup, in, (float *) out, NULL, PFFFT_FORWARD);
|
||||
C tmp = out[0];
|
||||
#if GABORATOR_REAL_FFT_NEGATIVE_FQS
|
||||
for (unsigned int i = 1; i < (n >> 1); i++)
|
||||
out[n - i] = conj(out[i]);
|
||||
#endif
|
||||
out[0] = C(tmp.real(), 0);
|
||||
out[n >> 1] = C(tmp.imag(), 0);
|
||||
}
|
||||
|
||||
// Note: this temporarily modifies in[0], in spite of the const
|
||||
void
|
||||
itransform(CONST_CI in, RI out) {
|
||||
GABORATOR_PFFFT_CHECK_ALIGN(in);
|
||||
GABORATOR_PFFFT_CHECK_ALIGN(out);
|
||||
C tmp = in[0];
|
||||
const_cast<CI>(in)[0] = C(tmp.real(), in[n >> 1].real());
|
||||
pffft_transform_ordered(setup, (const float *) in, out, NULL, PFFFT_BACKWARD);
|
||||
const_cast<CI>(in)[0] = tmp;
|
||||
}
|
||||
|
||||
private:
|
||||
// Size of the transform
|
||||
unsigned int n;
|
||||
PFFFT_Setup *setup;
|
||||
};
|
||||
#endif
|
||||
|
||||
#undef GABORATOR_PFFFT_CHECK_ALIGN
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
140
gaborator/fft_vdsp.h
Normal file
140
gaborator/fft_vdsp.h
Normal file
|
@ -0,0 +1,140 @@
|
|||
//
|
||||
// Fast Fourier transform using the Apple vDSP framework
|
||||
//
|
||||
// Copyright (C) 2013-2018 Andreas Gustafsson. This file is part of
|
||||
// the Gaborator library source distribution. See the file LICENSE at
|
||||
// the top level of the distribution for license information.
|
||||
//
|
||||
|
||||
#ifndef _GABORATOR_FFT_VDSP_H
|
||||
#define _GABORATOR_FFT_VDSP_H
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
#include <memory.h>
|
||||
|
||||
#include <mach/mach.h>
|
||||
#include <mach/task.h>
|
||||
#include <mach/task_info.h>
|
||||
#include <mach/vm_map.h>
|
||||
|
||||
#include <Accelerate/Accelerate.h>
|
||||
|
||||
namespace gaborator {
|
||||
|
||||
static inline int log2_int_exact(int n) {
|
||||
// n must be a power of two
|
||||
assert(n != 0 && ((n & (n >> 1)) == 0));
|
||||
int r = 0;
|
||||
for (;;) {
|
||||
n >>= 1;
|
||||
if (n == 0)
|
||||
break;
|
||||
r++;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
template <>
|
||||
struct fft<std::complex<float> *> {
|
||||
typedef std::complex<float> *I;
|
||||
typedef typename std::iterator_traits<I>::value_type C; // complex
|
||||
typedef typename C::value_type T; // float/double
|
||||
|
||||
fft(unsigned int n_): n(n_), log2n(log2_int_exact(n)) {
|
||||
setup = vDSP_create_fftsetup(log2n, kFFTRadix2);
|
||||
}
|
||||
~fft() {
|
||||
vDSP_destroy_fftsetup(setup);
|
||||
}
|
||||
|
||||
unsigned int size() { return n; }
|
||||
|
||||
// in-place
|
||||
void
|
||||
transform(I a) {
|
||||
DSPSplitComplex s;
|
||||
// XXX this result in disoptimal alignment
|
||||
s.realp = (float *) a;
|
||||
s.imagp = (float *) a + 1;
|
||||
vDSP_fft_zip(setup, &s, 2, log2n, kFFTDirection_Forward);
|
||||
}
|
||||
|
||||
void
|
||||
itransform(I a) {
|
||||
DSPSplitComplex s;
|
||||
s.realp = (float *) a;
|
||||
s.imagp = (float *) a + 1;
|
||||
vDSP_fft_zip(setup, &s, 2, log2n, kFFTDirection_Inverse);
|
||||
}
|
||||
|
||||
// out-of-place
|
||||
// XXX const
|
||||
void
|
||||
transform(I in, I out) {
|
||||
DSPSplitComplex si;
|
||||
si.realp = (float *) in;
|
||||
si.imagp = (float *) in + 1;
|
||||
DSPSplitComplex so;
|
||||
so.realp = (float *) out;
|
||||
so.imagp = (float *) out + 1;
|
||||
vDSP_fft_zop(setup,
|
||||
&si, 2,
|
||||
&so, 2,
|
||||
log2n, kFFTDirection_Forward);
|
||||
}
|
||||
|
||||
void
|
||||
itransform(I in, I out) {
|
||||
DSPSplitComplex si;
|
||||
si.realp = (float *) in;
|
||||
si.imagp = (float *) in + 1;
|
||||
DSPSplitComplex so;
|
||||
so.realp = (float *) out;
|
||||
so.imagp = (float *) out + 1;
|
||||
vDSP_fft_zop(setup,
|
||||
&si, 2,
|
||||
&so, 2,
|
||||
log2n, kFFTDirection_Inverse);
|
||||
}
|
||||
|
||||
private:
|
||||
// Size of the transform
|
||||
unsigned int n;
|
||||
unsigned int log2n;
|
||||
FFTSetup setup;
|
||||
};
|
||||
|
||||
// Support transforming std::vector<std::complex<float> >::iterator
|
||||
|
||||
template <>
|
||||
struct fft<typename std::vector<std::complex<float> >::iterator>:
|
||||
public fft<std::complex<float> *>
|
||||
{
|
||||
typedef fft<std::complex<float> *> base;
|
||||
typedef typename std::vector<std::complex<float> >::iterator I;
|
||||
fft(unsigned int n_): fft<std::complex<float> *>(n_) { }
|
||||
void
|
||||
transform(I a) {
|
||||
base::transform(&(*a));
|
||||
}
|
||||
void
|
||||
itransform(I a) {
|
||||
base::itransform(&(*a));
|
||||
}
|
||||
void
|
||||
transform(I in, I out) {
|
||||
base::transform(&(*in), &(*out));
|
||||
}
|
||||
void
|
||||
itransform(I in, I out) {
|
||||
base::itransform(&(*in), &(*out));
|
||||
}
|
||||
};
|
||||
|
||||
} // Namespace
|
||||
|
||||
#endif
|
2056
gaborator/gaborator.h
Normal file
2056
gaborator/gaborator.h
Normal file
File diff suppressed because it is too large
Load diff
80
gaborator/gaussian.h
Normal file
80
gaborator/gaussian.h
Normal file
|
@ -0,0 +1,80 @@
|
|||
//
|
||||
// The Gaussian and related functions
|
||||
//
|
||||
// Copyright (C) 2015-2018 Andreas Gustafsson. This file is part of
|
||||
// the Gaborator library source distribution. See the file LICENSE at
|
||||
// the top level of the distribution for license information.
|
||||
//
|
||||
|
||||
#ifndef _GABORATOR_GAUSSIAN_H
|
||||
#define _GABORATOR_GAUSSIAN_H
|
||||
|
||||
#include <math.h>
|
||||
|
||||
namespace gaborator {
|
||||
|
||||
// A rough approximation of erfc_inv(), the inverse complementary
|
||||
// error function. This is good for arguments in the range 1e-8 to 1,
|
||||
// to within a few percent.
|
||||
|
||||
inline float erfc_inv(float x) {
|
||||
return sqrtf(-logf(x)) - 0.3f;
|
||||
}
|
||||
|
||||
// Gaussian with peak = 1
|
||||
|
||||
inline double norm_gaussian(double sd, double x) {
|
||||
return exp(-(x * x) / (2 * sd * sd));
|
||||
}
|
||||
|
||||
// Gaussian with integral = 1
|
||||
|
||||
inline double gaussian(double sd, double x) {
|
||||
double a = 1.0 / (sd * sqrt(2.0 * M_PI));
|
||||
return a * norm_gaussian(sd, x);
|
||||
}
|
||||
|
||||
// The convolution of a Heaviside step function with a Gaussian of
|
||||
// standard deviation sd. Goes smoothly from 0 to 1, with the 0.5
|
||||
// point at x=0.
|
||||
|
||||
static inline
|
||||
double gaussian_edge(double sd, double x) {
|
||||
double erf_arg = x / (sd * M_SQRT2);
|
||||
if (erf_arg < -7)
|
||||
return 0; // error < 5e-23
|
||||
if (erf_arg > 7)
|
||||
return 1; // error < 5e-23
|
||||
return (erf(erf_arg) + 1) * 0.5;
|
||||
}
|
||||
|
||||
// Translate the time-domain standard deviation of a gaussian
|
||||
// (in samples) into the corresponding frequency-domain standard
|
||||
// deviation (as a fractional frequency), or vice versa.
|
||||
|
||||
static inline double sd_t2f(double st_sd) {
|
||||
return 1.0 / (2.0 * M_PI * st_sd);
|
||||
}
|
||||
|
||||
static inline double sd_f2t(double ff_sd) {
|
||||
return sd_t2f(ff_sd);
|
||||
}
|
||||
|
||||
// Given a gaussian with standard deviation "sd" and a maximum error
|
||||
// "max_error", calculate the support needed on each side to keep the
|
||||
// area below the curve within max_error of the exact value.
|
||||
|
||||
static inline double gaussian_support(double sd, double max_error) {
|
||||
return sd * M_SQRT2 * erfc_inv(max_error);
|
||||
}
|
||||
|
||||
// Inverse of the above: given a support and maximum error, calculate
|
||||
// the standard deviation.
|
||||
|
||||
static inline double gaussian_support_inv(double support, double max_error) {
|
||||
return support / (M_SQRT2 * erfc_inv(max_error));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
106
gaborator/pod_vector.h
Normal file
106
gaborator/pod_vector.h
Normal file
|
@ -0,0 +1,106 @@
|
|||
//
|
||||
// A vector class without default-initialization, for "plain old data"
|
||||
//
|
||||
// Copyright (C) 2016-2018 Andreas Gustafsson. This file is part of
|
||||
// the Gaborator library source distribution. See the file LICENSE at
|
||||
// the top level of the distribution for license information.
|
||||
//
|
||||
|
||||
#ifndef _GABORATOR_POD_VECTOR_H
|
||||
#define _GABORATOR_POD_VECTOR_H
|
||||
|
||||
#include <stdlib.h> // size_t
|
||||
|
||||
#include <algorithm> // std::swap
|
||||
|
||||
namespace gaborator {
|
||||
|
||||
template <class T>
|
||||
struct pod_vector {
|
||||
typedef T *iterator;
|
||||
pod_vector() {
|
||||
b = e = 0;
|
||||
}
|
||||
pod_vector(size_t size_) {
|
||||
b = static_cast<T *>(::operator new(size_ * sizeof(T)));
|
||||
e = b + size_;
|
||||
}
|
||||
~pod_vector()
|
||||
#if __cplusplus >= 201103L
|
||||
noexcept
|
||||
#endif
|
||||
{
|
||||
_free();
|
||||
}
|
||||
void swap(pod_vector &x) {
|
||||
std::swap(b, x.b);
|
||||
std::swap(e, x.e);
|
||||
}
|
||||
iterator begin() const { return b; }
|
||||
iterator end() const { return e; }
|
||||
T *data() { return b; }
|
||||
const T *data() const { return b; }
|
||||
T &operator[](size_t i) { return b[i]; }
|
||||
const T &operator[](size_t i) const { return b[i]; }
|
||||
size_t size() const { return e - b; }
|
||||
void resize(size_t new_size) {
|
||||
if (new_size == size())
|
||||
return;
|
||||
T *n = static_cast<T *>(::operator new(new_size * sizeof(T)));
|
||||
size_t ncopy = std::min(size(), new_size);
|
||||
std::copy(b, b + ncopy, n);
|
||||
_free();
|
||||
b = n;
|
||||
e = n + new_size;
|
||||
}
|
||||
pod_vector(const pod_vector &a)
|
||||
|
||||
{
|
||||
b = new T[a.size()];
|
||||
e = b + a.size();
|
||||
std::copy(a.b, a.e, b);
|
||||
//if (size()) fprintf(stderr, "pod_vector cc %d\n", (int)size());
|
||||
}
|
||||
void clear() {
|
||||
_free();
|
||||
b = e = 0;
|
||||
}
|
||||
#if __cplusplus >= 201103L
|
||||
pod_vector(pod_vector&& x) noexcept:
|
||||
b(x.b), e(x.e)
|
||||
{
|
||||
x.b = x.e = 0;
|
||||
//if (size()) fprintf(stderr, "pod_vector mv %d\n", (int)size());
|
||||
}
|
||||
#endif
|
||||
pod_vector &operator=(const pod_vector &a) {
|
||||
if (&a == this)
|
||||
return *this;
|
||||
_free();
|
||||
b = new T[a.size()];
|
||||
e = b + a.size();
|
||||
std::copy(a.b, a.e, b);
|
||||
//if (size()) fprintf(stderr, "pod_vector = %d\n", (int)size());
|
||||
return *this;
|
||||
}
|
||||
#if __cplusplus >= 201103L
|
||||
pod_vector &operator=(pod_vector &&x) noexcept {
|
||||
if (&x == this)
|
||||
return *this;
|
||||
b = x.b;
|
||||
e = x.e;
|
||||
x.b = x.e = 0;
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
private:
|
||||
void _free() {
|
||||
::operator delete(b);
|
||||
}
|
||||
T *b;
|
||||
T *e;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
47
gaborator/pool.h
Normal file
47
gaborator/pool.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// Pool of shared objects
|
||||
//
|
||||
// Copyright (C) 2015-2018 Andreas Gustafsson. This file is part of
|
||||
// the Gaborator library source distribution. See the file LICENSE at
|
||||
// the top level of the distribution for license information.
|
||||
//
|
||||
|
||||
#ifndef _GABORATOR_POOL_H
|
||||
#define _GABORATOR_POOL_H
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace gaborator {
|
||||
|
||||
// The "pool" class is for sharing FFT objects so that we don't
|
||||
// create multiple FFTs of the same size. It could also be used
|
||||
// to share objects of some other class T where we don't want to
|
||||
// create multiple Ts with the same K.
|
||||
|
||||
template <class T, class K>
|
||||
struct pool {
|
||||
typedef std::map<K, T *> m_t;
|
||||
~pool() {
|
||||
for (typename m_t::iterator it = m.begin(); it != m.end(); it++) {
|
||||
delete (*it).second;
|
||||
}
|
||||
}
|
||||
T *get(const K &k) {
|
||||
std::pair<typename m_t::iterator, bool> r = m.insert(std::make_pair(k, (T *)0));
|
||||
if (r.second) {
|
||||
// New element was inserted
|
||||
assert((*(r.first)).second == 0);
|
||||
(*r.first).second = new T(k);
|
||||
}
|
||||
return (*r.first).second;
|
||||
}
|
||||
m_t m;
|
||||
static pool shared;
|
||||
};
|
||||
|
||||
template <class T, class K>
|
||||
pool<T, K> pool<T, K>::shared;
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
65
gaborator/ref.h
Normal file
65
gaborator/ref.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// Intrusive reference counting smart pointer
|
||||
//
|
||||
// Copyright (C) 2016-2018 Andreas Gustafsson. This file is part of
|
||||
// the Gaborator library source distribution. See the file LICENSE at
|
||||
// the top level of the distribution for license information.
|
||||
//
|
||||
|
||||
#ifndef _GABORATOR_REF_H
|
||||
#define _GABORATOR_REF_H
|
||||
|
||||
namespace gaborator {
|
||||
|
||||
template <class T> struct ref;
|
||||
|
||||
struct refcounted {
|
||||
refcounted() { refcount = 0; }
|
||||
unsigned int refcount;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct ref {
|
||||
ref(): p(0) { }
|
||||
ref(T *p_): p(p_) {
|
||||
incref();
|
||||
}
|
||||
ref(const ref &o): p(o.p) {
|
||||
incref();
|
||||
}
|
||||
ref &operator=(const ref &o) { reset(o.p); return *this; }
|
||||
~ref() { reset(); }
|
||||
void reset() {
|
||||
decref();
|
||||
p = 0;
|
||||
}
|
||||
void reset(T *n) {
|
||||
if (n == p)
|
||||
return;
|
||||
decref();
|
||||
p = n;
|
||||
incref();
|
||||
}
|
||||
T *get() const { return p; }
|
||||
T *operator->() const { return p; }
|
||||
T &operator*() const { return *p; }
|
||||
operator bool() const { return p; }
|
||||
private:
|
||||
void incref() {
|
||||
if (! p)
|
||||
return;
|
||||
p->refcount++;
|
||||
}
|
||||
void decref() {
|
||||
if (! p)
|
||||
return;
|
||||
p->refcount--;
|
||||
if (p->refcount == 0)
|
||||
delete p;
|
||||
}
|
||||
T *p;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
315
gaborator/render.h
Normal file
315
gaborator/render.h
Normal file
|
@ -0,0 +1,315 @@
|
|||
//
|
||||
// Rendering of spectrogram images
|
||||
//
|
||||
// Copyright (C) 2015-2018 Andreas Gustafsson. This file is part of
|
||||
// the Gaborator library source distribution. See the file LICENSE at
|
||||
// the top level of the distribution for license information.
|
||||
//
|
||||
|
||||
#ifndef _GABORATOR_RENDER_H
|
||||
#define _GABORATOR_RENDER_H
|
||||
|
||||
#include "gaborator/gaborator.h"
|
||||
#include "gaborator/resample2.h"
|
||||
|
||||
namespace gaborator {
|
||||
|
||||
|
||||
// Convert a floating-point linear brightness value in the range 0..1
|
||||
// into an 8-bit pixel value, with clamping and (rough) gamma
|
||||
// correction. This nominally uses the sRGB gamma curve, but the
|
||||
// current implementation cheats and uses a gamma of 2 because it can
|
||||
// be calculated quickly using a square root.
|
||||
|
||||
template <class T>
|
||||
unsigned int float2pixel_8bit(T val) {
|
||||
// Clamp before gamma correction so we don't take the square root
|
||||
// of a negative number; those can arise from bicubic
|
||||
// interpolation. While we're at it, let's also skip the gamma
|
||||
// correction for small numbers that will round to zero anyway,
|
||||
// and especially denormals which could rigger GCC bug target/83240.
|
||||
static const T almost_zero = 1.0 / 65536;
|
||||
if (val < almost_zero)
|
||||
val = 0;
|
||||
if (val > 1)
|
||||
val = 1;
|
||||
return (unsigned int)(sqrtf(val) * 255.0f);
|
||||
}
|
||||
|
||||
|
||||
// Magnitude
|
||||
|
||||
template<class T>
|
||||
struct complex_abs_fob {
|
||||
T operator()(const complex<T> &c) {
|
||||
return complex_abs(c);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// A source object for resample2() that provides the absolute
|
||||
// values of a row of spectrogram coordinates.
|
||||
|
||||
template <class T, class OI, class NORMF>
|
||||
struct abs_row_source {
|
||||
typedef complex<T> C;
|
||||
|
||||
typedef transform_output_iterator<NORMF, OI, C> abs_writer_t;
|
||||
abs_row_source(const analyzer<T> &frs_,
|
||||
const sliced_coefs<C> &sc_,
|
||||
int oct_, unsigned int obno_,
|
||||
NORMF normf_):
|
||||
rs(frs_, sc_, oct_, obno_),
|
||||
normf(normf_)
|
||||
{ }
|
||||
OI operator()(sample_index_t i0, sample_index_t i1, OI output) const {
|
||||
abs_writer_t abswriter(normf, output);
|
||||
abs_writer_t abswriter_end = rs(i0, i1, abswriter);
|
||||
return abswriter_end.output;
|
||||
}
|
||||
row_source<T, abs_writer_t> rs;
|
||||
NORMF normf;
|
||||
};
|
||||
|
||||
// Helper class for abs_row_source specialization below
|
||||
|
||||
template <class C, class OI>
|
||||
struct abs_writer_dest {
|
||||
abs_writer_dest(OI output_): output(output_) { }
|
||||
void process_existing_slice(C *bv, size_t len) {
|
||||
complex_magnitude(bv, output, len);
|
||||
output += len;
|
||||
}
|
||||
void process_missing_slice(size_t len) {
|
||||
for (size_t i = 0; i < len; i++)
|
||||
*output++ = 0;
|
||||
}
|
||||
OI output;
|
||||
};
|
||||
|
||||
// Partial specialization of class abs_row_source for NORMF = complex_abs_fob,
|
||||
// for vectorization.
|
||||
|
||||
template <class T, class OI>
|
||||
struct abs_row_source<T, OI, struct complex_abs_fob<T> > {
|
||||
typedef complex<T> C;
|
||||
// Note unused last arg
|
||||
abs_row_source(const analyzer<T> &frs_,
|
||||
const sliced_coefs<C> &sc_,
|
||||
int oct_, unsigned int obno_,
|
||||
complex_abs_fob<T>):
|
||||
slicer(frs_, sc_, oct_, obno_)
|
||||
{ }
|
||||
OI operator()(coef_index_t i0, coef_index_t i1, OI output) const {
|
||||
abs_writer_dest<C, OI> dest(output);
|
||||
slicer(i0, i1, dest);
|
||||
return dest.output;
|
||||
}
|
||||
row_foreach_slice<T, abs_writer_dest<C, OI>, C> slicer;
|
||||
};
|
||||
|
||||
// Render a single line (single frequency band), with scaling by
|
||||
// powers of two in the horizontal (time) dimension, and filtering to
|
||||
// avoid aliasing when minifying.
|
||||
|
||||
template <class OI, class T, class NORMF>
|
||||
OI render_p2scale_line(const analyzer<T> &frs,
|
||||
const coefs<T> &msc,
|
||||
int gbno,
|
||||
int64_t xorigin,
|
||||
sample_index_t i0, sample_index_t i1, int e,
|
||||
bool interpolate,
|
||||
OI output,
|
||||
NORMF normf)
|
||||
{
|
||||
int oct;
|
||||
unsigned int obno; // Band number within octave
|
||||
bool clip = ! frs.bno_split(gbno, oct, obno, false);
|
||||
if (clip) {
|
||||
for (sample_index_t i = i0; i < i1; i++)
|
||||
*output++ = (T)0;
|
||||
return output;
|
||||
}
|
||||
abs_row_source<T, T *, NORMF>
|
||||
abs_rowsource(frs, msc.octaves[oct], oct, obno, normf);
|
||||
|
||||
// Scale by the downsampling factor of the band
|
||||
int scale_exp = frs.band_scale_exp(oct, obno);
|
||||
output = resample2(abs_rowsource, xorigin,
|
||||
i0, i1, e - scale_exp,
|
||||
interpolate, output);
|
||||
return output;
|
||||
}
|
||||
|
||||
// Render a two-dimensional image with scaling by powers of two in the
|
||||
// horizontal direction only. In the vertical direction, there is
|
||||
// always a one-to-one correspondence between bands and pixels.
|
||||
// yi0 and yi1 already have the yorigin applied, so there is no
|
||||
// yorigin argument.
|
||||
|
||||
template <class OI, class T, class NORMF>
|
||||
OI render_p2scale_noyscale(const analyzer<T> &frs,
|
||||
const coefs<T> &msc,
|
||||
int64_t xorigin,
|
||||
int64_t xi0, int64_t xi1, int xe,
|
||||
int64_t yi0, int64_t yi1,
|
||||
bool interpolate,
|
||||
OI output,
|
||||
NORMF normf)
|
||||
{
|
||||
assert(xi1 >= xi0);
|
||||
int w = xi1 - xi0;
|
||||
int gbno0 = yi0;
|
||||
int gbno1 = yi1;
|
||||
for (int gbno = gbno0; gbno < gbno1; gbno++) {
|
||||
int oct;
|
||||
unsigned int obno; // Band number within octave
|
||||
bool clip = ! frs.bno_split(gbno, oct, obno, false);
|
||||
if (clip) {
|
||||
for (int x = 0; x < w; x++)
|
||||
*output++ = (T)0;
|
||||
} else {
|
||||
output = render_p2scale_line(frs, msc, gbno, xorigin,
|
||||
xi0, xi1, xe,
|
||||
interpolate, output, normf);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
// Source data from a column of a row-major two-dimensional array.
|
||||
// data points to the beginning of a row-major array with an x
|
||||
// range of x0..x1 and an y range from y0..y1, and operator()
|
||||
// returns data from column x (where x is within the range x0..x1).
|
||||
|
||||
template <class OI>
|
||||
struct transverse_source {
|
||||
transverse_source(float *data_,
|
||||
int64_t x0_, int64_t x1_, int64_t y0_, int64_t y1_,
|
||||
int64_t x_):
|
||||
data(data_),
|
||||
x0(x0_), x1(x1_), y0(y0_), y1(y1_),
|
||||
x(x_),
|
||||
stride(x1 - x0)
|
||||
{ }
|
||||
OI operator()(int64_t i0, int64_t i1, OI out) const {
|
||||
assert(x >= x0);
|
||||
assert(x <= x1);
|
||||
assert(i1 >= i0);
|
||||
assert(i0 >= y0);
|
||||
assert(i1 <= y1);
|
||||
float *p = data + (x - x0) + (i0 - y0) * stride;
|
||||
while (i0 != i1) {
|
||||
*out++ = *p;
|
||||
p += stride;
|
||||
++i0;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
float *data;
|
||||
int64_t x0, x1, y0, y1, x;
|
||||
size_t stride;
|
||||
};
|
||||
|
||||
template <class I, class T>
|
||||
struct stride_iterator: public std::iterator<std::forward_iterator_tag, T> {
|
||||
stride_iterator(I it_, size_t stride_): it(it_), stride(stride_) { }
|
||||
T& operator*() { return *it; }
|
||||
stride_iterator<I, T>& operator++() {
|
||||
it += stride;
|
||||
return *this;
|
||||
}
|
||||
stride_iterator operator++(int) {
|
||||
stride_iterator old = *this;
|
||||
it += stride;
|
||||
return old;
|
||||
}
|
||||
I it;
|
||||
size_t stride;
|
||||
};
|
||||
|
||||
// Render a two-dimensional image with scaling by powers of two in
|
||||
// both the horizontal (time) and vertical (frequency) directions.
|
||||
// The output may be written through "output" out of order, so
|
||||
// "output" must be a random access iterator.
|
||||
|
||||
// Note the default template argument for NORMF. This is needed
|
||||
// because the compiler won't deduce the type of NORMF from the
|
||||
// default function argument "NORMF normf = complex_abs_fob<T>()"
|
||||
// when the normf argument is omitted; it is considered a "non-deduced
|
||||
// context", being "a template parameter used in the parameter type of
|
||||
// a function parameter that has a default argument that is being used
|
||||
// in the call for which argument deduction is being done".
|
||||
// Unfortuantely, this work-around of providing a default template
|
||||
// argument requires C++11.
|
||||
|
||||
template <class OI, class T, class NORMF = complex_abs_fob<T> >
|
||||
void render_p2scale(const analyzer<T> &frs,
|
||||
const coefs<T> &msc,
|
||||
int64_t xorigin, int64_t yorigin,
|
||||
int64_t xi0, int64_t xi1, int xe,
|
||||
int64_t yi0, int64_t yi1, int ye,
|
||||
OI output,
|
||||
bool interpolate = true,
|
||||
NORMF normf = complex_abs_fob<T>())
|
||||
{
|
||||
// Construct a temporary float image of the right width,
|
||||
// but still needing scaling of the height. Include
|
||||
// extra scanlines at the top and bottom for interpolation.
|
||||
|
||||
// Find the image bounds in the spectrogram coordinate system,
|
||||
// including the interpolation margin. The Y bounds are in
|
||||
// bands and are used both to determine what to render into the
|
||||
// temporary image and for short-circuiting; the X bounds are in
|
||||
// samples, and are only used for short-circuiting.
|
||||
int64_t ysi0, ysi1;
|
||||
resample2_support(yorigin, yi0, yi1, ye, ysi0, ysi1);
|
||||
int64_t xsi0, xsi1;
|
||||
resample2_support(xorigin, xi0, xi1, xe, xsi0, xsi1);
|
||||
|
||||
// Short-circuiting: if the image to be rendered falls entirely
|
||||
// outside the data, just set it to zero instead of resampling down
|
||||
// (potentially) high-resolution zeros to the display resolution.
|
||||
// This makes a difference when zooming out by a large factor, for
|
||||
// example such that the entire spectrogram falls within a single
|
||||
// tile; that tile will necessarily be expensive to calculate, but
|
||||
// the other tiles need not be, and mustn't be if we are going to
|
||||
// keep the total amount of work bounded by O(L) with respect
|
||||
// to the signal length L regardless of zoom.
|
||||
int64_t cxi0, cxi1;
|
||||
frs.get_coef_bounds(msc, cxi0, cxi1);
|
||||
if (ysi1 < 0 || // Entirely above
|
||||
ysi0 >= frs.n_bands_total - 1 || // Entirely below
|
||||
xsi1 < cxi0 || // Entirely to the left
|
||||
xsi0 >= cxi1) // Entirely to the right
|
||||
{
|
||||
size_t n = (yi1 - yi0) * (xi1 - xi0);
|
||||
for (size_t i = 0; i < n; i++)
|
||||
output[i] = (T)0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate buffer for temporary image resampled in the X
|
||||
// direction but not yet in the Y direction
|
||||
size_t n_pixels = (ysi1 - ysi0) * (xi1 - xi0);
|
||||
pod_vector<float> render_data(n_pixels);
|
||||
|
||||
// Render data resampled in the X direction
|
||||
float *p = render_data.data();
|
||||
render_p2scale_noyscale(frs, msc, xorigin, xi0, xi1, xe,
|
||||
ysi0, ysi1, interpolate, p, normf);
|
||||
|
||||
// Resample in the Y direction
|
||||
for (int64_t xi = xi0; xi < xi1; xi++) {
|
||||
transverse_source<OI> src(render_data.data(),
|
||||
xi0, xi1, ysi0, ysi1,
|
||||
xi);
|
||||
stride_iterator<OI, float> dest(output + (xi - xi0), (xi1 - xi0));
|
||||
resample2(src, yorigin, yi0, yi1, ye, interpolate, dest);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
221
gaborator/resample2.h
Normal file
221
gaborator/resample2.h
Normal file
|
@ -0,0 +1,221 @@
|
|||
//
|
||||
// Fast resampling by powers of two
|
||||
//
|
||||
// Copyright (C) 2016-2018 Andreas Gustafsson. This file is part of
|
||||
// the Gaborator library source distribution. See the file LICENSE at
|
||||
// the top level of the distribution for license information.
|
||||
//
|
||||
|
||||
// Uses a two-lobe Lanczos kernel. Good enough for image data, not
|
||||
// intended for audio.
|
||||
|
||||
#ifndef _GABORATOR_RESAMPLE2_H
|
||||
#define _GABORATOR_RESAMPLE2_H
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <algorithm> // std::copy
|
||||
|
||||
#include "gaborator/pod_vector.h"
|
||||
|
||||
namespace gaborator {
|
||||
|
||||
//
|
||||
// There are two ways to look at this.
|
||||
//
|
||||
// In one point of view, there is only one coordinate space, and
|
||||
// coordinates are floating-point numbers. The various sub- and
|
||||
// supersampled views differ in step sizes and the number of
|
||||
// fractional coordinate bits, but any given coordinates refer to the
|
||||
// same point in the image at any scale. Steps are powers of two,
|
||||
// with integer exponents that may be negative. A step size > 1
|
||||
// implies downsampling (antialias lowpass filtering and subsampling),
|
||||
// and a step size < 1 implies upsampling (aka interpolation).
|
||||
//
|
||||
// The coordinates are always integer multiples of the step size.
|
||||
//
|
||||
// e.g.,
|
||||
// x0 = 33.5, xstep = 0.5
|
||||
// x0 = 12, xstep = 4
|
||||
//
|
||||
// In the other point of view, we introduce an integer exponent e and
|
||||
// substitute x0 = i0 * 2^e and xstep = 2^e. Now instead of floating
|
||||
// point coordinates, we use integer "indices". The above example
|
||||
// now looks like his:
|
||||
//
|
||||
// i0 = 67, e = -1
|
||||
// i0 = 3, e = 2
|
||||
//
|
||||
// This latter point of view is how the code actually works.
|
||||
//
|
||||
|
||||
// Resample data from "source", generating a view between indices
|
||||
// i0 and i1 of the scale determined by exponent e, and storing
|
||||
// i1 - i0 samples starting at *out.
|
||||
//
|
||||
// The source must implement an operator() taking the arguments
|
||||
// (int64_t i0, int64_t i1, T *out) and generating data for the base
|
||||
// resolution (e=0).
|
||||
//
|
||||
// S is the type of the data source
|
||||
// OI is the output iterator type
|
||||
|
||||
template <class S, class T>
|
||||
T *resample2_ptr(const S &source, int64_t origin,
|
||||
int64_t i0, int64_t i1, int e,
|
||||
bool interpolate, T *out)
|
||||
{
|
||||
assert(i1 >= i0);
|
||||
if (e > 0) {
|
||||
// Downsample
|
||||
// Calculate super-octave coordinates
|
||||
// margin is the support of the resampling kernel (on each side,
|
||||
// not counting the center sample)
|
||||
int margin = interpolate ? 1 : 0;
|
||||
// When margin = 1, we use three samples, at 2i-1, 2i, 2i+1
|
||||
// and the corresponding half-open inverval is 2i-1...2i+1+1
|
||||
int64_t si0 = 2 * i0 - margin;
|
||||
int64_t si1 = 2 * i1 + margin + 1;
|
||||
// Get super-octave data
|
||||
gaborator::pod_vector<T> super_data(si1 - si0);
|
||||
T *p = super_data.data();
|
||||
p = resample2_ptr(source, origin, si0, si1, e - 1, interpolate, p);
|
||||
assert(p == super_data.data() + si1 - si0);
|
||||
for (int64_t i = i0; i < i1; i++) {
|
||||
int64_t si = 2 * i - si0;
|
||||
T val;
|
||||
if (!interpolate) {
|
||||
// Point sampling
|
||||
val = super_data[si];
|
||||
} else {
|
||||
// Triangular kernel
|
||||
T v1 = super_data[si - 1];
|
||||
T v0 = super_data[si];
|
||||
v1 += super_data[si + 1];
|
||||
val =
|
||||
v0 * (T)0.5 +
|
||||
v1 * (T)0.25;
|
||||
#if 0 // Lanczos2 is overkill when downsampling.
|
||||
} else {
|
||||
// Two-lobe Lanczos kernel, needs margin = 2
|
||||
// Always aligned
|
||||
T v3 = super_data[si - 3];
|
||||
// There is no v2
|
||||
T v1 = super_data[si - 1];
|
||||
T v0 = super_data[si];
|
||||
// There is still no v2
|
||||
v1 += super_data[si + 1];
|
||||
v3 += super_data[si + 3];
|
||||
val =
|
||||
v0 * (T)0.49530706 +
|
||||
v1 * (T)0.28388978 +
|
||||
v3 * (T)-0.03154331;
|
||||
#endif
|
||||
}
|
||||
*out++ = val;
|
||||
}
|
||||
} else if (e < 0) {
|
||||
// Upsample
|
||||
if (! interpolate) {
|
||||
// Return nearest neighbor. If the pixel lies
|
||||
// exactly at the midpoint between the neighbors,
|
||||
// return their average.
|
||||
int sh = -e;
|
||||
int64_t si0 = i0 >> sh;
|
||||
int64_t si1 = ((i1 - 1) >> sh) + 1 + 1;
|
||||
gaborator::pod_vector<T> source_data(si1 - si0);
|
||||
source(origin + si0, origin + si1, source_data.data());
|
||||
for (int64_t i = i0; i < i1; i++) {
|
||||
int64_t si = (i >> sh) - si0;
|
||||
T val;
|
||||
int rem = i & ((1 << sh) - 1);
|
||||
int half = (1 << sh) >> 1;
|
||||
if (rem < half) {
|
||||
val = source_data[si];
|
||||
} else if (rem == half) {
|
||||
val = (source_data[si] + source_data[si + 1]) * (T)0.5;
|
||||
} else { // rem > half
|
||||
val = source_data[si + 1];
|
||||
}
|
||||
*out++ = val;
|
||||
}
|
||||
} else {
|
||||
// Interpolate
|
||||
// Calculate sub-octave coordinates
|
||||
int margin = 2;
|
||||
int64_t si0 = (i0 >> 1) - margin;
|
||||
int64_t si1 = ((i1 - 1) >> 1) + margin + 1;
|
||||
// Get sub-octave data
|
||||
gaborator::pod_vector<T> sub_data(si1 - si0);
|
||||
T *p = sub_data.data();
|
||||
p = resample2_ptr(source, origin, si0, si1, e + 1, interpolate, p);
|
||||
assert(p == sub_data.data() + si1 - si0);
|
||||
for (int64_t i = i0; i < i1; i++) {
|
||||
int64_t si = (i >> 1) - si0;
|
||||
T val;
|
||||
if (i & 1) {
|
||||
T v1 = sub_data[si - 1];
|
||||
T v0 = sub_data[si];
|
||||
v0 += sub_data[si + 1];
|
||||
v1 += sub_data[si + 2];
|
||||
val =
|
||||
v0 * (T)0.5625 +
|
||||
v1 * (T)-0.0625;
|
||||
} else {
|
||||
val = sub_data[si];
|
||||
}
|
||||
*out++ = val;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// e == 0
|
||||
out = source(origin + i0, origin + i1, out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <class S, class OI>
|
||||
OI resample2(const S &source, int64_t origin,
|
||||
int64_t i0, int64_t i1, int e,
|
||||
bool interpolate, OI out)
|
||||
{
|
||||
typedef typename std::iterator_traits<OI>::value_type T;
|
||||
gaborator::pod_vector<T> data(i1 - i0);
|
||||
T *p = data.data();
|
||||
p = resample2_ptr(source, origin, i0, i1, e, interpolate, p);
|
||||
return std::copy(data.data(), data.data() + (i1 - i0), out);
|
||||
}
|
||||
|
||||
// Calculate the range of source indices that will be accessed
|
||||
// by a call to resample2(source, i0, i1, e) and return it
|
||||
// through si0_ret and si1_ret.
|
||||
|
||||
// XXX this should take an "interpolate" argument so we don't
|
||||
// return an unnecessarily large support when interpolation is off
|
||||
|
||||
inline void
|
||||
resample2_support(int64_t origin, int64_t i0, int64_t i1, int e,
|
||||
int64_t &si0_ret, int64_t &si1_ret)
|
||||
{
|
||||
// Conservative
|
||||
int margin = 2;
|
||||
if (e > 0) {
|
||||
// Note code duplication wrt resample2_ptr().
|
||||
// Also note tail recursion.
|
||||
int64_t si0 = i0 * 2 - margin * 2 + 1;
|
||||
int64_t si1 = i1 * 2 + margin * 2;
|
||||
resample2_support(origin, si0, si1, e - 1, si0_ret, si1_ret);
|
||||
} else if (e < 0) {
|
||||
int64_t si0 = (i0 >> 1) - margin;
|
||||
int64_t si1 = ((i1 - 1) >> 1) + margin + 1;
|
||||
resample2_support(origin, si0, si1, e + 1, si0_ret, si1_ret);
|
||||
} else {
|
||||
si0_ret = origin + i0;
|
||||
si1_ret = origin + i1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
301
gaborator/vector_math.h
Normal file
301
gaborator/vector_math.h
Normal file
|
@ -0,0 +1,301 @@
|
|||
//
|
||||
// Vector math operations
|
||||
//
|
||||
// Copyright (C) 2016-2018 Andreas Gustafsson. This file is part of
|
||||
// the Gaborator library source distribution. See the file LICENSE at
|
||||
// the top level of the distribution for license information.
|
||||
//
|
||||
|
||||
#ifndef _GABORATOR_VECTOR_MATH_H
|
||||
#define _GABORATOR_VECTOR_MATH_H
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#if GABORATOR_USE_SSE3_INTRINSICS
|
||||
#include <pmmintrin.h>
|
||||
#endif
|
||||
|
||||
#include <complex>
|
||||
|
||||
namespace gaborator {
|
||||
|
||||
// The _naive versions are used when SSE is not available, and as
|
||||
// references when testing the SSE versions
|
||||
|
||||
// Naive or not, this is faster than the macOS std::complex
|
||||
// multiplication operator, which checks for infinities even with
|
||||
// -ffast-math.
|
||||
|
||||
template <class T>
|
||||
std::complex<T> complex_mul_naive(std::complex<T> a,
|
||||
std::complex<T> b)
|
||||
{
|
||||
return std::complex<T>(a.real() * b.real() - a.imag() * b.imag(),
|
||||
a.real() * b.imag() + a.imag() * b.real());
|
||||
}
|
||||
|
||||
#if GABORATOR_USE_SSE3_INTRINSICS
|
||||
// Note: this is sometimes slower than the naive code.
|
||||
static inline
|
||||
std::complex<double> complex_mul(std::complex<double> a_,
|
||||
std::complex<double> b_)
|
||||
{
|
||||
__v2df a = _mm_setr_pd(a_.real(), a_.imag());
|
||||
__v2df b = _mm_setr_pd(b_.real(), b_.imag());
|
||||
__v2df as = (__v2df) _mm_shuffle_pd(a, a, 0x1);
|
||||
__v2df t0 = _mm_mul_pd(a, _mm_shuffle_pd(b, b, 0x0));
|
||||
__v2df t1 = _mm_mul_pd(as, _mm_shuffle_pd(b, b, 0x3));
|
||||
__v2df c = __builtin_ia32_addsubpd(t0, t1); // SSE3
|
||||
return std::complex<double>(c[0], c[1]);
|
||||
}
|
||||
#else
|
||||
static inline
|
||||
std::complex<double> complex_mul(std::complex<double> a_,
|
||||
std::complex<double> b_)
|
||||
{
|
||||
return complex_mul_naive(a_, b_);
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline
|
||||
std::complex<float> complex_mul(std::complex<float> a_,
|
||||
std::complex<float> b_)
|
||||
{
|
||||
return complex_mul_naive(a_, b_);
|
||||
}
|
||||
|
||||
template <class T, class U, class V>
|
||||
static inline void
|
||||
elementwise_product_naive(T *r,
|
||||
U *a,
|
||||
V *b,
|
||||
int n)
|
||||
{
|
||||
for (int i = 0; i < n; i++)
|
||||
r[i] = complex_mul(a[i], b[i]);
|
||||
}
|
||||
|
||||
template <class T, class U, class V, class S>
|
||||
static inline void
|
||||
elementwise_product_times_scalar_naive(T *r,
|
||||
U *a,
|
||||
V *b,
|
||||
S s,
|
||||
int n)
|
||||
{
|
||||
for (int i = 0; i < n; i++)
|
||||
r[i] = a[i] * b[i] * s;
|
||||
}
|
||||
|
||||
// I is the input complex data type, O is the output data type
|
||||
template <class I, class O>
|
||||
static inline void
|
||||
complex_magnitude_naive(I *inv,
|
||||
O *outv,
|
||||
int n)
|
||||
{
|
||||
for (int i = 0; i < n; i++)
|
||||
outv[i] = std::sqrt(inv[i].real() * inv[i].real() + inv[i].imag() * inv[i].imag());
|
||||
}
|
||||
|
||||
#if GABORATOR_USE_SSE3_INTRINSICS
|
||||
|
||||
#include <pmmintrin.h>
|
||||
|
||||
// Perform two complex float multiplies in parallel
|
||||
|
||||
static inline
|
||||
__v4sf complex_mul_vec2(__v4sf aa, __v4sf bb) {
|
||||
__v4sf aas =_mm_shuffle_ps(aa, aa, 0xb1);
|
||||
__v4sf t0 = _mm_mul_ps(aa, _mm_moveldup_ps(bb));
|
||||
__v4sf t1 = _mm_mul_ps(aas, _mm_movehdup_ps(bb));
|
||||
return __builtin_ia32_addsubps(t0, t1); // SSE3
|
||||
}
|
||||
|
||||
// Calculate the elementwise product of a complex float vector
|
||||
// and another complex float vector.
|
||||
|
||||
static inline void
|
||||
elementwise_product(std::complex<float> *cv,
|
||||
const std::complex<float> *av,
|
||||
const std::complex<float> *bv,
|
||||
int n)
|
||||
{
|
||||
assert((n & 1) == 0);
|
||||
n >>= 1;
|
||||
__v4sf *c = (__v4sf *) cv;
|
||||
const __v4sf *a = (const __v4sf *) av;
|
||||
const __v4sf *b = (const __v4sf *) bv;
|
||||
while (n--) {
|
||||
__v4sf aa = *a++;
|
||||
__v4sf bb = *b++;
|
||||
*c++ = complex_mul_vec2(aa, bb);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the elementwise product of a complex float vector
|
||||
// and real float vector.
|
||||
//
|
||||
// The input "a" may be unaligned; the output "c" must be aligned.
|
||||
|
||||
static inline void
|
||||
elementwise_product(std::complex<float> *cv,
|
||||
const std::complex<float> *av,
|
||||
const float *bv,
|
||||
int n)
|
||||
{
|
||||
assert((n & 3) == 0);
|
||||
n >>= 2;
|
||||
__v4sf *c = (__v4sf *) cv;
|
||||
const __v4sf *a = (const __v4sf *) av;
|
||||
const __v4sf *b = (const __v4sf *) bv;
|
||||
while (n--) {
|
||||
__v4sf a0 = (__v4sf) _mm_loadu_si128((const __m128i *) a++);
|
||||
__v4sf a1 = (__v4sf) _mm_loadu_si128((const __m128i *) a++);
|
||||
__v4sf bb = *b++;
|
||||
*c++ = _mm_mul_ps(a0, _mm_unpacklo_ps(bb, bb));
|
||||
*c++ = _mm_mul_ps(a1, _mm_unpackhi_ps(bb, bb));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
elementwise_product_times_scalar(std::complex<float> *cv,
|
||||
const std::complex<float> *av,
|
||||
const std::complex<float> *bv,
|
||||
std::complex<float> d,
|
||||
int n)
|
||||
{
|
||||
assert((n & 1) == 0);
|
||||
n >>= 1;
|
||||
const __v4sf *a = (const __v4sf *) av;
|
||||
const __v4sf *b = (const __v4sf *) bv;
|
||||
const __v4sf dd = (__v4sf) { d.real(), d.imag(), d.real(), d.imag() };
|
||||
__v4sf *c = (__v4sf *) cv;
|
||||
while (n--) {
|
||||
__v4sf aa = *a++;
|
||||
__v4sf bb = *b++;
|
||||
*c++ = complex_mul_vec2(complex_mul_vec2(aa, bb), dd);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX arguments reversed wrt others
|
||||
|
||||
static inline void
|
||||
complex_magnitude(std::complex<float> *inv,
|
||||
float *outv,
|
||||
int n)
|
||||
{
|
||||
// Processes four complex values (32 bytes) at a time ,
|
||||
// outputting four scalar magnitudes (16 bytes) at a time.
|
||||
while ((((uintptr_t) inv) & 0x1F) && n) {
|
||||
std::complex<float> v = *inv++;
|
||||
*outv++ = std::sqrt(v.real() * v.real() + v.imag() * v.imag());
|
||||
n--;
|
||||
}
|
||||
const __v4sf *in = (const __v4sf *) inv;
|
||||
__v4sf *out = (__v4sf *) outv;
|
||||
while (n >= 4) {
|
||||
__v4sf aa = *in++; // c0re c0im c1re c1im
|
||||
__v4sf aa2 = _mm_mul_ps(aa, aa); // c0re^2 c0im^2 c1re^2 c1im^2
|
||||
__v4sf bb = *in++; // c2re c2im c3re c3im
|
||||
__v4sf bb2 = _mm_mul_ps(bb, bb); // etc
|
||||
// Gather the real parts: x0 x2 y0 y2
|
||||
// 10 00 10 00 = 0x88
|
||||
__v4sf re2 =_mm_shuffle_ps(aa2, bb2, 0x88);
|
||||
__v4sf im2 =_mm_shuffle_ps(aa2, bb2, 0xdd);
|
||||
__v4sf mag2 = _mm_add_ps(re2, im2);
|
||||
__v4sf mag = __builtin_ia32_sqrtps(mag2);
|
||||
// Unaligned store
|
||||
_mm_storeu_si128((__m128i *)out, (__m128i)mag);
|
||||
out++;
|
||||
n -= 4;
|
||||
}
|
||||
inv = (std::complex<float> *) in;
|
||||
outv = (float *)out;
|
||||
while (n) {
|
||||
std::complex<float> v = *inv++;
|
||||
*outv++ = std::sqrt(v.real() * v.real() + v.imag() * v.imag());
|
||||
n--;
|
||||
}
|
||||
}
|
||||
|
||||
// Double-precision version is unoptimized
|
||||
|
||||
static inline void
|
||||
elementwise_product(std::complex<double> *c,
|
||||
const std::complex<double> *a,
|
||||
const std::complex<double> *b,
|
||||
int n)
|
||||
{
|
||||
elementwise_product_naive(c, a, b, n);
|
||||
}
|
||||
|
||||
static inline void
|
||||
elementwise_product(std::complex<double> *c,
|
||||
const std::complex<double> *a,
|
||||
const double *b,
|
||||
int n)
|
||||
{
|
||||
elementwise_product_naive(c, a, b, n);
|
||||
}
|
||||
|
||||
template <class T, class U, class V, class S>
|
||||
static inline void
|
||||
elementwise_product_times_scalar(T *r,
|
||||
U *a,
|
||||
V *b,
|
||||
S s,
|
||||
int n)
|
||||
{
|
||||
elementwise_product_times_scalar_naive(r, a, b, s, n);
|
||||
}
|
||||
|
||||
template <class O>
|
||||
static inline void
|
||||
complex_magnitude(std::complex<double> *inv,
|
||||
O *outv,
|
||||
int n)
|
||||
{
|
||||
complex_magnitude_naive(inv, outv, n);
|
||||
}
|
||||
|
||||
#else // ! GABORATOR_USE_SSE3_INTRINSICS
|
||||
|
||||
// Forward to the naive implementations. These are inline functions
|
||||
// rather than #defines to avoid namespace pollution.
|
||||
|
||||
template <class T, class U, class V>
|
||||
static inline void
|
||||
elementwise_product(T *r,
|
||||
U *a,
|
||||
V *b,
|
||||
int n)
|
||||
{
|
||||
elementwise_product_naive(r, a, b, n);
|
||||
}
|
||||
|
||||
template <class T, class U, class V, class S>
|
||||
static inline void
|
||||
elementwise_product_times_scalar(T *r,
|
||||
U *a,
|
||||
V *b,
|
||||
S s,
|
||||
int n)
|
||||
{
|
||||
elementwise_product_times_scalar_naive(r, a, b, s, n);
|
||||
}
|
||||
|
||||
template <class I, class O>
|
||||
static inline void
|
||||
complex_magnitude(I *inv,
|
||||
O *outv,
|
||||
int n)
|
||||
{
|
||||
complex_magnitude_naive(inv, outv, n);
|
||||
}
|
||||
|
||||
#endif // ! USE_SSE3_INTINSICS
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue